2026-05-17 04:11:42 +00:00
|
|
|
|
#!/bin/bash
|
|
|
|
|
|
|
|
|
|
|
|
#############################################################################################
|
|
|
|
|
|
# #
|
|
|
|
|
|
# Pelican Installer — The best way to install Pelican Panel & Wings #
|
|
|
|
|
|
# #
|
|
|
|
|
|
# Copyright (c) 2026, JarvisCLI #
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# https://github.com/everestmcarthur/pelican-installer #
|
2026-05-17 04:11:42 +00:00
|
|
|
|
# #
|
|
|
|
|
|
# This program is free software: you can redistribute it and/or modify #
|
|
|
|
|
|
# it under the terms of the GNU General Public License v3.0 #
|
|
|
|
|
|
# #
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Features: #
|
|
|
|
|
|
# • Full CLI flags for automated/unattended installs #
|
|
|
|
|
|
# • Supports Nginx, Apache, and Caddy (auto-SSL) web servers #
|
|
|
|
|
|
# • Supports SQLite, MySQL, MariaDB, and PostgreSQL databases #
|
|
|
|
|
|
# • Automatic SSL via Let's Encrypt or Caddy #
|
|
|
|
|
|
# • Full Wings daemon installation with systemd + Docker #
|
|
|
|
|
|
# • Writes all config to .env — panel works immediately after install #
|
|
|
|
|
|
# • Runs database migrations automatically #
|
2026-05-17 04:11:42 +00:00
|
|
|
|
# • Health checks & post-install verification #
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# • Colored terminal UI with step-by-step progress #
|
2026-05-17 04:11:42 +00:00
|
|
|
|
# • Comprehensive logging to /var/log/pelican-installer.log #
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# • Uninstall & Update commands #
|
|
|
|
|
|
# • Single script — no external dependencies #
|
2026-05-17 04:11:42 +00:00
|
|
|
|
# #
|
|
|
|
|
|
#############################################################################################
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Version & Defaults
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
INSTALLER_VERSION="2.0.0"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
PELICAN_PANEL_URL="https://github.com/pelican-dev/panel/releases/latest/download/panel.tar.gz"
|
|
|
|
|
|
PELICAN_WINGS_BASE="https://github.com/pelican-dev/wings/releases/latest/download"
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ── Configurable variables (override via flags or environment) ──
|
|
|
|
|
|
PELICAN_DIR="${PELICAN_DIR:-/var/www/pelican}"
|
|
|
|
|
|
WINGS_DIR="${WINGS_DIR:-/etc/pelican}"
|
|
|
|
|
|
WINGS_BIN="${WINGS_BIN:-/usr/local/bin/wings}"
|
|
|
|
|
|
LOG_FILE="${LOG_FILE:-/var/log/pelican-installer.log}"
|
|
|
|
|
|
|
|
|
|
|
|
FQDN="${FQDN:-}"
|
|
|
|
|
|
WEBSERVER="${WEBSERVER:-}" # nginx | apache | caddy
|
|
|
|
|
|
DB_CHOICE="${DB_CHOICE:-}" # sqlite | mysql | mariadb | postgres
|
|
|
|
|
|
DB_HOST="${DB_HOST:-127.0.0.1}"
|
|
|
|
|
|
DB_PORT="${DB_PORT:-}" # auto-set based on DB_CHOICE if empty
|
|
|
|
|
|
DB_NAME="${DB_NAME:-pelican}"
|
|
|
|
|
|
DB_USER="${DB_USER:-pelican}"
|
|
|
|
|
|
DB_PASSWORD="${DB_PASSWORD:-}" # auto-generated if empty
|
|
|
|
|
|
CONFIGURE_SSL="${CONFIGURE_SSL:-}" # true | false
|
|
|
|
|
|
EMAIL="${EMAIL:-}"
|
|
|
|
|
|
TIMEZONE="${TIMEZONE:-UTC}"
|
|
|
|
|
|
APP_URL="${APP_URL:-}" # auto-set from FQDN if empty
|
|
|
|
|
|
|
|
|
|
|
|
INSTALL_PANEL="${INSTALL_PANEL:-}"
|
|
|
|
|
|
INSTALL_WINGS_FLAG="${INSTALL_WINGS_FLAG:-}"
|
|
|
|
|
|
ASSUME_YES="${ASSUME_YES:-false}"
|
|
|
|
|
|
ACTION="${ACTION:-}" # install | update | uninstall
|
|
|
|
|
|
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Colors & Formatting
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
if [[ -t 1 ]]; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
|
|
|
|
|
BLUE='\033[0;34m'; MAGENTA='\033[0;35m'; CYAN='\033[0;36m'
|
|
|
|
|
|
WHITE='\033[1;37m'; BOLD='\033[1m'; DIM='\033[2m'; RESET='\033[0m'
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
|
|
|
|
|
RED='' GREEN='' YELLOW='' BLUE='' MAGENTA='' CYAN='' WHITE='' BOLD='' DIM='' RESET=''
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
CURRENT_STEP=0
|
|
|
|
|
|
TOTAL_STEPS=0
|
|
|
|
|
|
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Logging & Output
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"; }
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
print_header() {
|
2026-05-17 11:53:42 +00:00
|
|
|
|
clear 2>/dev/null || true
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e "${CYAN}${BOLD}"
|
|
|
|
|
|
echo " ╔═══════════════════════════════════════════════════════════╗"
|
|
|
|
|
|
echo " ║ ║"
|
|
|
|
|
|
echo " ║ 🐦 Pelican Panel & Wings Installer 🐦 ║"
|
|
|
|
|
|
echo " ║ ║"
|
|
|
|
|
|
echo " ║ v${INSTALLER_VERSION} ║"
|
|
|
|
|
|
echo " ╚═══════════════════════════════════════════════════════════╝"
|
|
|
|
|
|
echo -e "${RESET}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
info() { echo -e " ${BLUE}ℹ${RESET} $*"; log "INFO: $*"; }
|
|
|
|
|
|
success() { echo -e " ${GREEN}✓${RESET} $*"; log "OK: $*"; }
|
|
|
|
|
|
warn() { echo -e " ${YELLOW}⚠${RESET} $*"; log "WARN: $*"; }
|
|
|
|
|
|
error() { echo -e " ${RED}✗${RESET} $*"; log "ERROR: $*"; }
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
fatal() {
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " ${RED}${BOLD}FATAL:${RESET} ${RED}$*${RESET}"
|
|
|
|
|
|
log "FATAL: $*"
|
2026-05-17 11:53:42 +00:00
|
|
|
|
echo -e " ${DIM}Check ${LOG_FILE} for details${RESET}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo ""
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
step() {
|
2026-05-17 11:53:42 +00:00
|
|
|
|
CURRENT_STEP=$((CURRENT_STEP + 1))
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo ""
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if [[ $TOTAL_STEPS -gt 0 ]]; then
|
|
|
|
|
|
echo -e " ${MAGENTA}${BOLD}[${CURRENT_STEP}/${TOTAL_STEPS}] $*${RESET}"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo -e " ${MAGENTA}${BOLD}▸ $*${RESET}"
|
|
|
|
|
|
fi
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo -e " ${DIM}$(printf '%.0s─' {1..55})${RESET}"
|
2026-05-17 11:53:42 +00:00
|
|
|
|
log "STEP ${CURRENT_STEP}: $*"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
ask() { echo -e -n " ${CYAN}?${RESET} $* "; }
|
|
|
|
|
|
|
|
|
|
|
|
confirm() {
|
|
|
|
|
|
local prompt="$1"
|
|
|
|
|
|
local default="${2:-Y}"
|
|
|
|
|
|
if [[ "$ASSUME_YES" == true ]]; then
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
ask "${prompt}"
|
|
|
|
|
|
read -r answer
|
|
|
|
|
|
answer="${answer:-$default}"
|
|
|
|
|
|
[[ "$answer" =~ ^[Yy]$ ]]
|
2026-05-17 04:11:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
spinner() {
|
|
|
|
|
|
local pid=$1
|
|
|
|
|
|
local msg=$2
|
|
|
|
|
|
local spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
|
|
|
|
|
|
local i=0
|
|
|
|
|
|
while kill -0 "$pid" 2>/dev/null; do
|
|
|
|
|
|
printf "\r ${CYAN}%s${RESET} %s" "${spin:i++%${#spin}:1}" "$msg"
|
|
|
|
|
|
sleep 0.1
|
|
|
|
|
|
done
|
2026-05-17 11:53:42 +00:00
|
|
|
|
printf "\r ${CYAN} ${RESET} %s\n" "$msg"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
run_cmd() {
|
|
|
|
|
|
local msg="$1"
|
|
|
|
|
|
shift
|
|
|
|
|
|
"$@" >> "$LOG_FILE" 2>&1 &
|
|
|
|
|
|
local pid=$!
|
|
|
|
|
|
spinner $pid "$msg"
|
|
|
|
|
|
wait $pid
|
|
|
|
|
|
local exit_code=$?
|
|
|
|
|
|
if [[ $exit_code -ne 0 ]]; then
|
|
|
|
|
|
error "$msg — FAILED (exit code $exit_code)"
|
|
|
|
|
|
error "Check ${LOG_FILE} for details"
|
|
|
|
|
|
return $exit_code
|
|
|
|
|
|
fi
|
|
|
|
|
|
return 0
|
2026-05-17 04:11:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# CLI Argument Parsing
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
show_help() {
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " Pelican Installer v${INSTALLER_VERSION}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " Usage: $0 [ACTION] [OPTIONS]"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " Actions:"
|
|
|
|
|
|
echo " install Install Panel and/or Wings (default)"
|
|
|
|
|
|
echo " update Update Panel and/or Wings"
|
|
|
|
|
|
echo " uninstall Uninstall Panel and/or Wings"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " Components:"
|
|
|
|
|
|
echo " --panel Install/update Panel"
|
|
|
|
|
|
echo " --wings Install/update Wings"
|
|
|
|
|
|
echo " --both Install/update Panel + Wings"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " Panel options:"
|
|
|
|
|
|
echo " --domain FQDN Panel domain name (e.g., panel.example.com)"
|
|
|
|
|
|
echo " --webserver WS Web server: nginx, apache, caddy"
|
|
|
|
|
|
echo " --database DB Database: sqlite, mysql, mariadb, postgres"
|
|
|
|
|
|
echo " --db-host HOST Database host (default: 127.0.0.1)"
|
|
|
|
|
|
echo " --db-port PORT Database port (auto-detected from db type)"
|
|
|
|
|
|
echo " --db-name NAME Database name (default: pelican)"
|
|
|
|
|
|
echo " --db-user USER Database user (default: pelican)"
|
|
|
|
|
|
echo " --db-password PASS Database password (auto-generated if empty)"
|
|
|
|
|
|
echo " --ssl Enable SSL via Let's Encrypt"
|
|
|
|
|
|
echo " --no-ssl Disable SSL"
|
|
|
|
|
|
echo " --email EMAIL Email for Let's Encrypt"
|
|
|
|
|
|
echo " --timezone TZ Timezone (default: UTC)"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " General:"
|
|
|
|
|
|
echo " --yes, -y Skip all confirmation prompts"
|
|
|
|
|
|
echo " --help, -h Show this help"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " Examples:"
|
|
|
|
|
|
echo " # Interactive install"
|
|
|
|
|
|
echo " sudo bash install.sh"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " # Fully automated panel install"
|
|
|
|
|
|
echo " sudo bash install.sh install --panel --domain panel.example.com \\"
|
|
|
|
|
|
echo " --webserver nginx --database postgres --ssl --email you@example.com -y"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " # Wings only"
|
|
|
|
|
|
echo " sudo bash install.sh install --wings -y"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo " # Update everything"
|
|
|
|
|
|
echo " sudo bash install.sh update --both -y"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
parse_args() {
|
|
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
|
|
|
case "$1" in
|
|
|
|
|
|
install|update|uninstall)
|
|
|
|
|
|
ACTION="$1" ;;
|
|
|
|
|
|
--panel)
|
|
|
|
|
|
INSTALL_PANEL=true ;;
|
|
|
|
|
|
--wings)
|
|
|
|
|
|
INSTALL_WINGS_FLAG=true ;;
|
|
|
|
|
|
--both)
|
|
|
|
|
|
INSTALL_PANEL=true; INSTALL_WINGS_FLAG=true ;;
|
|
|
|
|
|
--domain|--fqdn)
|
|
|
|
|
|
FQDN="$2"; shift ;;
|
|
|
|
|
|
--webserver|--ws)
|
|
|
|
|
|
WEBSERVER="$2"; shift ;;
|
|
|
|
|
|
--database|--db)
|
|
|
|
|
|
DB_CHOICE="$2"; shift ;;
|
|
|
|
|
|
--db-host)
|
|
|
|
|
|
DB_HOST="$2"; shift ;;
|
|
|
|
|
|
--db-port)
|
|
|
|
|
|
DB_PORT="$2"; shift ;;
|
|
|
|
|
|
--db-name)
|
|
|
|
|
|
DB_NAME="$2"; shift ;;
|
|
|
|
|
|
--db-user)
|
|
|
|
|
|
DB_USER="$2"; shift ;;
|
|
|
|
|
|
--db-password|--db-pass)
|
|
|
|
|
|
DB_PASSWORD="$2"; shift ;;
|
|
|
|
|
|
--ssl)
|
|
|
|
|
|
CONFIGURE_SSL=true ;;
|
|
|
|
|
|
--no-ssl)
|
|
|
|
|
|
CONFIGURE_SSL=false ;;
|
|
|
|
|
|
--email)
|
|
|
|
|
|
EMAIL="$2"; shift ;;
|
|
|
|
|
|
--timezone|--tz)
|
|
|
|
|
|
TIMEZONE="$2"; shift ;;
|
|
|
|
|
|
--yes|-y)
|
|
|
|
|
|
ASSUME_YES=true ;;
|
|
|
|
|
|
--help|-h)
|
|
|
|
|
|
show_help ;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
warn "Unknown option: $1" ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
shift
|
|
|
|
|
|
done
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# OS Detection
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
detect_os() {
|
2026-05-17 11:53:42 +00:00
|
|
|
|
step "Detecting system environment"
|
|
|
|
|
|
|
|
|
|
|
|
if [[ ! -f /etc/os-release ]]; then
|
|
|
|
|
|
fatal "Cannot detect operating system — /etc/os-release not found."
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
. /etc/os-release
|
|
|
|
|
|
OS_ID="$ID"
|
|
|
|
|
|
OS_VERSION="$VERSION_ID"
|
|
|
|
|
|
OS_NAME="$PRETTY_NAME"
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
case "$OS_ID" in
|
|
|
|
|
|
ubuntu|debian)
|
2026-05-17 11:53:42 +00:00
|
|
|
|
OS_FAMILY="debian"; PKG_MANAGER="apt" ;;
|
|
|
|
|
|
almalinux|rocky|centos|rhel)
|
|
|
|
|
|
OS_FAMILY="rhel"; PKG_MANAGER="dnf" ;;
|
2026-05-17 04:11:42 +00:00
|
|
|
|
*)
|
|
|
|
|
|
if command -v apt &>/dev/null; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
OS_FAMILY="debian"; PKG_MANAGER="apt"
|
|
|
|
|
|
warn "Unknown OS '$OS_ID' — detected apt, proceeding as Debian-like"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
elif command -v dnf &>/dev/null; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
OS_FAMILY="rhel"; PKG_MANAGER="dnf"
|
|
|
|
|
|
warn "Unknown OS '$OS_ID' — detected dnf, proceeding as RHEL-like"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
fatal "Unsupported OS: $OS_ID (no apt or dnf found)"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
|
|
|
|
|
|
ARCH=$(uname -m)
|
|
|
|
|
|
case "$ARCH" in
|
2026-05-17 11:53:42 +00:00
|
|
|
|
x86_64) WINGS_ARCH="amd64" ;;
|
|
|
|
|
|
aarch64|arm64) WINGS_ARCH="arm64" ;;
|
|
|
|
|
|
*) fatal "Unsupported architecture: $ARCH" ;;
|
2026-05-17 04:11:42 +00:00
|
|
|
|
esac
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
success "OS: ${BOLD}$OS_NAME${RESET} ($OS_FAMILY/$PKG_MANAGER)"
|
|
|
|
|
|
success "Architecture: ${BOLD}$ARCH${RESET} → Wings binary: $WINGS_ARCH"
|
|
|
|
|
|
|
|
|
|
|
|
# Detect web server user
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "rhel" ]]; then
|
|
|
|
|
|
case "${WEBSERVER:-nginx}" in
|
|
|
|
|
|
nginx) WEBSERVER_USER="nginx" ;;
|
|
|
|
|
|
apache) WEBSERVER_USER="apache" ;;
|
|
|
|
|
|
caddy) WEBSERVER_USER="caddy" ;;
|
|
|
|
|
|
*) WEBSERVER_USER="nginx" ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
else
|
|
|
|
|
|
WEBSERVER_USER="www-data"
|
|
|
|
|
|
fi
|
2026-05-17 04:11:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Preflight Checks
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
preflight_checks() {
|
|
|
|
|
|
step "Running preflight checks"
|
|
|
|
|
|
|
|
|
|
|
|
# Root check
|
|
|
|
|
|
if [[ $EUID -ne 0 ]]; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
fatal "This script must be run as root. Use: sudo bash $0"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
success "Running as root"
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Essential tools
|
|
|
|
|
|
for cmd in curl tar openssl; do
|
|
|
|
|
|
if ! command -v "$cmd" &>/dev/null; then
|
|
|
|
|
|
info "Installing $cmd..."
|
2026-05-17 04:11:42 +00:00
|
|
|
|
$PKG_MANAGER install -y "$cmd" >> "$LOG_FILE" 2>&1 || fatal "Failed to install $cmd"
|
|
|
|
|
|
fi
|
2026-05-17 11:53:42 +00:00
|
|
|
|
success "$cmd available"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
done
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Virtualization check (for Wings)
|
2026-05-17 04:11:42 +00:00
|
|
|
|
if command -v systemd-detect-virt &>/dev/null; then
|
|
|
|
|
|
local virt
|
2026-05-17 11:53:42 +00:00
|
|
|
|
virt=$(systemd-detect-virt 2>/dev/null || echo "none")
|
2026-05-17 04:11:42 +00:00
|
|
|
|
if [[ "$virt" == "openvz" || "$virt" == "lxc" ]]; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
warn "Detected ${BOLD}$virt${RESET} — Docker/Wings may not work without nested virt"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
success "Virtualization: $virt"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Memory
|
2026-05-17 04:11:42 +00:00
|
|
|
|
local total_mem
|
|
|
|
|
|
total_mem=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo "0")
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if [[ $total_mem -lt 1024 ]]; then
|
|
|
|
|
|
warn "Low memory: ${total_mem}MB (1GB+ recommended)"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
|
|
|
|
|
success "Memory: ${total_mem}MB"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Disk
|
2026-05-17 04:11:42 +00:00
|
|
|
|
local free_disk
|
|
|
|
|
|
free_disk=$(df -BG / | awk 'NR==2 {print $4}' | tr -d 'G')
|
|
|
|
|
|
if [[ $free_disk -lt 5 ]]; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
warn "Low disk: ${free_disk}GB free (10GB+ recommended)"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
success "Disk: ${free_disk}GB free"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# PHP Installation
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
detect_php_version() {
|
2026-05-17 11:53:42 +00:00
|
|
|
|
for ver in 8.4 8.3 8.2; do
|
2026-05-17 04:11:42 +00:00
|
|
|
|
if [[ "$OS_FAMILY" == "debian" ]]; then
|
|
|
|
|
|
if apt-cache show "php${ver}-fpm" &>/dev/null 2>&1; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
PHP_VERSION="$ver"; return
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
else
|
|
|
|
|
|
if dnf list "php" 2>/dev/null | grep -q "$ver"; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
PHP_VERSION="$ver"; return
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
done
|
2026-05-17 11:53:42 +00:00
|
|
|
|
PHP_VERSION="8.3"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
install_php() {
|
|
|
|
|
|
step "Installing PHP $PHP_VERSION & extensions"
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "debian" ]]; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Add PHP repo
|
2026-05-17 04:11:42 +00:00
|
|
|
|
if ! command -v add-apt-repository &>/dev/null; then
|
|
|
|
|
|
apt install -y software-properties-common >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "$OS_ID" == "ubuntu" ]]; then
|
|
|
|
|
|
add-apt-repository -y ppa:ondrej/php >> "$LOG_FILE" 2>&1 || true
|
|
|
|
|
|
elif [[ "$OS_ID" == "debian" ]]; then
|
|
|
|
|
|
apt install -y lsb-release apt-transport-https ca-certificates >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
curl -sSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
dpkg -i /tmp/debsuryorg-archive-keyring.deb >> "$LOG_FILE" 2>&1
|
2026-05-17 11:53:42 +00:00
|
|
|
|
echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" \
|
|
|
|
|
|
> /etc/apt/sources.list.d/sury-php.list
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Updating package lists" apt update
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
local pkgs=(
|
2026-05-17 04:11:42 +00:00
|
|
|
|
"php${PHP_VERSION}" "php${PHP_VERSION}-fpm" "php${PHP_VERSION}-common"
|
|
|
|
|
|
"php${PHP_VERSION}-cli" "php${PHP_VERSION}-gd" "php${PHP_VERSION}-mysql"
|
2026-05-17 11:53:42 +00:00
|
|
|
|
"php${PHP_VERSION}-pgsql" "php${PHP_VERSION}-mbstring" "php${PHP_VERSION}-bcmath"
|
|
|
|
|
|
"php${PHP_VERSION}-xml" "php${PHP_VERSION}-curl" "php${PHP_VERSION}-zip"
|
|
|
|
|
|
"php${PHP_VERSION}-intl" "php${PHP_VERSION}-sqlite3" "php${PHP_VERSION}-tokenizer"
|
|
|
|
|
|
"php${PHP_VERSION}-fileinfo" "php${PHP_VERSION}-dom"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing PHP ${PHP_VERSION} and extensions" apt install -y "${pkgs[@]}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
elif [[ "$OS_FAMILY" == "rhel" ]]; then
|
|
|
|
|
|
dnf install -y epel-release >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
dnf install -y "https://rpms.remirepo.net/enterprise/remi-release-$(rpm -E %rhel).rpm" >> "$LOG_FILE" 2>&1 || true
|
|
|
|
|
|
dnf module reset php -y >> "$LOG_FILE" 2>&1 || true
|
|
|
|
|
|
dnf module enable "php:remi-${PHP_VERSION}" -y >> "$LOG_FILE" 2>&1 || true
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing PHP ${PHP_VERSION} and extensions" dnf install -y \
|
|
|
|
|
|
php php-fpm php-common php-cli php-gd php-mysqlnd php-pgsql \
|
|
|
|
|
|
php-mbstring php-bcmath php-xml php-curl php-zip php-intl php-pdo php-tokenizer
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# Configure PHP-FPM pool
|
|
|
|
|
|
local fpm_conf=""
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "debian" ]]; then
|
|
|
|
|
|
fpm_conf="/etc/php/${PHP_VERSION}/fpm/pool.d/www.conf"
|
|
|
|
|
|
else
|
|
|
|
|
|
fpm_conf="/etc/php-fpm.d/www.conf"
|
|
|
|
|
|
fi
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if [[ -f "$fpm_conf" ]]; then
|
|
|
|
|
|
sed -i "s/^user = .*/user = ${WEBSERVER_USER}/" "$fpm_conf"
|
|
|
|
|
|
sed -i "s/^group = .*/group = ${WEBSERVER_USER}/" "$fpm_conf"
|
|
|
|
|
|
sed -i "s/^listen.owner = .*/listen.owner = ${WEBSERVER_USER}/" "$fpm_conf"
|
|
|
|
|
|
sed -i "s/^listen.group = .*/listen.group = ${WEBSERVER_USER}/" "$fpm_conf"
|
|
|
|
|
|
success "PHP-FPM pool configured for ${WEBSERVER_USER}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
systemctl enable --now "php${PHP_VERSION}-fpm" >> "$LOG_FILE" 2>&1 || \
|
|
|
|
|
|
systemctl enable --now php-fpm >> "$LOG_FILE" 2>&1 || true
|
|
|
|
|
|
success "PHP-FPM started and enabled"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Database Installation
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
generate_db_password() {
|
|
|
|
|
|
if [[ -z "$DB_PASSWORD" && "$DB_CHOICE" != "sqlite" ]]; then
|
|
|
|
|
|
DB_PASSWORD=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32)
|
|
|
|
|
|
log "Generated DB password: $DB_PASSWORD"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto_set_db_port() {
|
|
|
|
|
|
if [[ -z "$DB_PORT" ]]; then
|
|
|
|
|
|
case "$DB_CHOICE" in
|
|
|
|
|
|
mysql|mariadb) DB_PORT="3306" ;;
|
|
|
|
|
|
postgres) DB_PORT="5432" ;;
|
|
|
|
|
|
sqlite) DB_PORT="" ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
install_database() {
|
|
|
|
|
|
if [[ "$DB_CHOICE" == "sqlite" ]]; then
|
|
|
|
|
|
info "Using SQLite — no database server needed"
|
|
|
|
|
|
return
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
step "Installing ${DB_CHOICE^} database server"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
case "$DB_CHOICE" in
|
|
|
|
|
|
mysql)
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "debian" ]]; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing MySQL" apt install -y mysql-server mysql-client
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing MySQL" dnf install -y mysql-server
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
systemctl enable --now mysql >> "$LOG_FILE" 2>&1 || systemctl enable --now mysqld >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
success "MySQL installed and running"
|
|
|
|
|
|
;;
|
|
|
|
|
|
mariadb)
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "debian" ]]; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing MariaDB" apt install -y mariadb-server mariadb-client
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing MariaDB" dnf install -y mariadb-server
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
systemctl enable --now mariadb >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
success "MariaDB installed and running"
|
|
|
|
|
|
;;
|
|
|
|
|
|
postgres)
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "debian" ]]; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing PostgreSQL" apt install -y postgresql postgresql-client
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing PostgreSQL" dnf install -y postgresql-server postgresql
|
2026-05-17 04:11:42 +00:00
|
|
|
|
postgresql-setup --initdb >> "$LOG_FILE" 2>&1 || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
systemctl enable --now postgresql >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
success "PostgreSQL installed and running"
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
setup_database
|
2026-05-17 04:11:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
setup_database() {
|
|
|
|
|
|
step "Creating database & user"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
generate_db_password
|
|
|
|
|
|
auto_set_db_port
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
case "$DB_CHOICE" in
|
|
|
|
|
|
mysql|mariadb)
|
|
|
|
|
|
mysql -u root <<EOSQL
|
2026-05-17 04:11:42 +00:00
|
|
|
|
CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\`;
|
2026-05-17 11:53:42 +00:00
|
|
|
|
CREATE USER IF NOT EXISTS '${DB_USER}'@'${DB_HOST}' IDENTIFIED BY '${DB_PASSWORD}';
|
|
|
|
|
|
GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${DB_USER}'@'${DB_HOST}' WITH GRANT OPTION;
|
2026-05-17 04:11:42 +00:00
|
|
|
|
FLUSH PRIVILEGES;
|
2026-05-17 11:53:42 +00:00
|
|
|
|
EOSQL
|
|
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
|
|
|
|
fatal "Failed to create MySQL/MariaDB database. Check ${LOG_FILE}"
|
|
|
|
|
|
fi
|
|
|
|
|
|
success "Database '${DB_NAME}' created with user '${DB_USER}'"
|
|
|
|
|
|
;;
|
|
|
|
|
|
|
|
|
|
|
|
postgres)
|
|
|
|
|
|
# Create user and database
|
|
|
|
|
|
sudo -u postgres psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" >> "$LOG_FILE" 2>&1 || {
|
|
|
|
|
|
# User might already exist, try ALTER
|
|
|
|
|
|
sudo -u postgres psql -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
}
|
|
|
|
|
|
sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};" >> "$LOG_FILE" 2>&1 || {
|
|
|
|
|
|
sudo -u postgres psql -c "ALTER DATABASE ${DB_NAME} OWNER TO ${DB_USER};" >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
}
|
|
|
|
|
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};" >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
|
|
|
|
|
|
# CRITICAL: Configure pg_hba.conf for password auth over TCP
|
|
|
|
|
|
local pg_hba
|
|
|
|
|
|
pg_hba=$(sudo -u postgres psql -t -c "SHOW hba_file;" 2>/dev/null | xargs)
|
|
|
|
|
|
if [[ -n "$pg_hba" && -f "$pg_hba" ]]; then
|
|
|
|
|
|
# Add md5 auth for our user before any existing host rules
|
|
|
|
|
|
if ! grep -q "pelican" "$pg_hba" 2>/dev/null; then
|
|
|
|
|
|
# Insert password auth rule at the top of the host section
|
|
|
|
|
|
sed -i "/^# IPv4 local connections:/a host ${DB_NAME} ${DB_USER} 127.0.0.1/32 md5" "$pg_hba"
|
|
|
|
|
|
sed -i "/^# IPv6 local connections:/a host ${DB_NAME} ${DB_USER} ::1/128 md5" "$pg_hba"
|
|
|
|
|
|
success "pg_hba.conf configured for password authentication"
|
|
|
|
|
|
systemctl reload postgresql >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
fi
|
|
|
|
|
|
else
|
|
|
|
|
|
# Fallback: try common paths
|
|
|
|
|
|
for hba_path in /etc/postgresql/*/main/pg_hba.conf /var/lib/pgsql/data/pg_hba.conf; do
|
|
|
|
|
|
if [[ -f "$hba_path" ]]; then
|
|
|
|
|
|
if ! grep -q "${DB_USER}" "$hba_path"; then
|
|
|
|
|
|
sed -i "/^# IPv4 local connections:/a host ${DB_NAME} ${DB_USER} 127.0.0.1/32 md5" "$hba_path"
|
|
|
|
|
|
sed -i "/^# IPv6 local connections:/a host ${DB_NAME} ${DB_USER} ::1/128 md5" "$hba_path"
|
|
|
|
|
|
fi
|
|
|
|
|
|
break
|
|
|
|
|
|
fi
|
|
|
|
|
|
done
|
|
|
|
|
|
systemctl reload postgresql >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
success "pg_hba.conf configured for password authentication"
|
|
|
|
|
|
fi
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
success "PostgreSQL database '${DB_NAME}' created with user '${DB_USER}'"
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
|
|
|
|
|
|
# Show credentials in a visible box
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo ""
|
2026-05-17 11:53:42 +00:00
|
|
|
|
echo -e " ${GREEN}${BOLD}┌─────────────────────────────────────────────┐${RESET}"
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}│ DATABASE CREDENTIALS — SAVE THESE! │${RESET}"
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}├─────────────────────────────────────────────┤${RESET}"
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}│${RESET} Host: ${WHITE}${DB_HOST}${RESET}"
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}│${RESET} Port: ${WHITE}${DB_PORT}${RESET}"
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}│${RESET} Database: ${WHITE}${DB_NAME}${RESET}"
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}│${RESET} User: ${WHITE}${DB_USER}${RESET}"
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}│${RESET} Password: ${WHITE}${DB_PASSWORD}${RESET}"
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}└─────────────────────────────────────────────┘${RESET}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Web Server Installation
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
get_fpm_socket() {
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "rhel" ]]; then
|
|
|
|
|
|
echo "/run/php-fpm/www.sock"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "/run/php/php${PHP_VERSION}-fpm.sock"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
install_webserver() {
|
2026-05-17 11:53:42 +00:00
|
|
|
|
step "Installing ${WEBSERVER^} web server"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
case "$WEBSERVER" in
|
2026-05-17 11:53:42 +00:00
|
|
|
|
nginx) install_nginx ;;
|
|
|
|
|
|
apache) install_apache ;;
|
|
|
|
|
|
caddy) install_caddy ;;
|
2026-05-17 04:11:42 +00:00
|
|
|
|
esac
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
install_nginx() {
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "debian" ]]; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing Nginx" apt install -y nginx
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing Nginx" dnf install -y nginx
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
rm -f /etc/nginx/sites-enabled/default /etc/nginx/conf.d/default.conf 2>/dev/null || true
|
|
|
|
|
|
mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled 2>/dev/null || true
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Ensure sites-enabled is included on RHEL
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "rhel" ]] && ! grep -q "sites-enabled" /etc/nginx/nginx.conf 2>/dev/null; then
|
|
|
|
|
|
sed -i '/http {/a \ include /etc/nginx/sites-enabled/*.conf;' /etc/nginx/nginx.conf
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
local fpm_sock
|
|
|
|
|
|
fpm_sock=$(get_fpm_socket)
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if [[ "$CONFIGURE_SSL" == true ]]; then
|
|
|
|
|
|
write_nginx_ssl_config "$fpm_sock"
|
|
|
|
|
|
else
|
|
|
|
|
|
write_nginx_http_config "$fpm_sock"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
ln -sf /etc/nginx/sites-available/pelican.conf /etc/nginx/sites-enabled/pelican.conf
|
|
|
|
|
|
nginx -t >> "$LOG_FILE" 2>&1 || warn "Nginx config test failed — check ${LOG_FILE}"
|
|
|
|
|
|
systemctl enable --now nginx >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
systemctl restart nginx >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
success "Nginx configured and running"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
write_nginx_ssl_config() {
|
|
|
|
|
|
local fpm_sock="$1"
|
|
|
|
|
|
cat > /etc/nginx/sites-available/pelican.conf <<NGINXCFG
|
2026-05-17 04:11:42 +00:00
|
|
|
|
server_tokens off;
|
|
|
|
|
|
|
|
|
|
|
|
server {
|
|
|
|
|
|
listen 80;
|
|
|
|
|
|
server_name ${FQDN};
|
|
|
|
|
|
return 301 https://\$server_name\$request_uri;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
server {
|
|
|
|
|
|
listen 443 ssl http2;
|
|
|
|
|
|
server_name ${FQDN};
|
|
|
|
|
|
|
|
|
|
|
|
root ${PELICAN_DIR}/public;
|
|
|
|
|
|
index index.php;
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
access_log /var/log/nginx/pelican.access.log;
|
|
|
|
|
|
error_log /var/log/nginx/pelican.error.log error;
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
client_max_body_size 100m;
|
|
|
|
|
|
client_body_timeout 120s;
|
|
|
|
|
|
sendfile off;
|
|
|
|
|
|
|
|
|
|
|
|
ssl_certificate /etc/letsencrypt/live/${FQDN}/fullchain.pem;
|
|
|
|
|
|
ssl_certificate_key /etc/letsencrypt/live/${FQDN}/privkey.pem;
|
|
|
|
|
|
ssl_session_cache shared:SSL:10m;
|
|
|
|
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
|
|
|
|
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
|
|
|
|
|
|
ssl_prefer_server_ciphers on;
|
|
|
|
|
|
|
|
|
|
|
|
add_header X-Content-Type-Options nosniff;
|
|
|
|
|
|
add_header X-XSS-Protection "1; mode=block";
|
|
|
|
|
|
add_header X-Robots-Tag none;
|
|
|
|
|
|
add_header Content-Security-Policy "frame-ancestors 'self'";
|
|
|
|
|
|
add_header X-Frame-Options DENY;
|
|
|
|
|
|
add_header Referrer-Policy same-origin;
|
|
|
|
|
|
|
|
|
|
|
|
location / {
|
|
|
|
|
|
try_files \$uri \$uri/ /index.php?\$query_string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
location ~ \.php\$ {
|
|
|
|
|
|
fastcgi_split_path_info ^(.+\.php)(/.+)\$;
|
|
|
|
|
|
fastcgi_pass unix:${fpm_sock};
|
|
|
|
|
|
fastcgi_index index.php;
|
|
|
|
|
|
include fastcgi_params;
|
|
|
|
|
|
fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
|
|
|
|
|
|
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
|
|
|
|
|
|
fastcgi_param HTTP_PROXY "";
|
|
|
|
|
|
fastcgi_intercept_errors off;
|
|
|
|
|
|
fastcgi_buffer_size 16k;
|
|
|
|
|
|
fastcgi_buffers 4 16k;
|
|
|
|
|
|
fastcgi_connect_timeout 300;
|
|
|
|
|
|
fastcgi_send_timeout 300;
|
|
|
|
|
|
fastcgi_read_timeout 300;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
location ~ /\.ht {
|
|
|
|
|
|
deny all;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-17 11:53:42 +00:00
|
|
|
|
NGINXCFG
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
write_nginx_http_config() {
|
|
|
|
|
|
local fpm_sock="$1"
|
|
|
|
|
|
cat > /etc/nginx/sites-available/pelican.conf <<NGINXCFG
|
2026-05-17 04:11:42 +00:00
|
|
|
|
server_tokens off;
|
|
|
|
|
|
|
|
|
|
|
|
server {
|
|
|
|
|
|
listen 80;
|
|
|
|
|
|
server_name ${FQDN};
|
|
|
|
|
|
|
|
|
|
|
|
root ${PELICAN_DIR}/public;
|
|
|
|
|
|
index index.php;
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
access_log /var/log/nginx/pelican.access.log;
|
|
|
|
|
|
error_log /var/log/nginx/pelican.error.log error;
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
client_max_body_size 100m;
|
|
|
|
|
|
client_body_timeout 120s;
|
|
|
|
|
|
sendfile off;
|
|
|
|
|
|
|
|
|
|
|
|
add_header X-Content-Type-Options nosniff;
|
|
|
|
|
|
add_header X-XSS-Protection "1; mode=block";
|
|
|
|
|
|
add_header X-Robots-Tag none;
|
|
|
|
|
|
add_header Content-Security-Policy "frame-ancestors 'self'";
|
|
|
|
|
|
add_header X-Frame-Options DENY;
|
|
|
|
|
|
add_header Referrer-Policy same-origin;
|
|
|
|
|
|
|
|
|
|
|
|
location / {
|
|
|
|
|
|
try_files \$uri \$uri/ /index.php?\$query_string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
location ~ \.php\$ {
|
|
|
|
|
|
fastcgi_split_path_info ^(.+\.php)(/.+)\$;
|
|
|
|
|
|
fastcgi_pass unix:${fpm_sock};
|
|
|
|
|
|
fastcgi_index index.php;
|
|
|
|
|
|
include fastcgi_params;
|
|
|
|
|
|
fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
|
|
|
|
|
|
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
|
|
|
|
|
|
fastcgi_param HTTP_PROXY "";
|
|
|
|
|
|
fastcgi_intercept_errors off;
|
|
|
|
|
|
fastcgi_buffer_size 16k;
|
|
|
|
|
|
fastcgi_buffers 4 16k;
|
|
|
|
|
|
fastcgi_connect_timeout 300;
|
|
|
|
|
|
fastcgi_send_timeout 300;
|
|
|
|
|
|
fastcgi_read_timeout 300;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
location ~ /\.ht {
|
|
|
|
|
|
deny all;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-17 11:53:42 +00:00
|
|
|
|
NGINXCFG
|
2026-05-17 04:11:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
install_apache() {
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "debian" ]]; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing Apache" apt install -y apache2 "libapache2-mod-php${PHP_VERSION}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
a2enmod rewrite ssl headers proxy proxy_fcgi >> "$LOG_FILE" 2>&1 || true
|
|
|
|
|
|
a2dissite 000-default >> "$LOG_FILE" 2>&1 || true
|
|
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing Apache" dnf install -y httpd mod_ssl
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
local fpm_sock
|
|
|
|
|
|
fpm_sock=$(get_fpm_socket)
|
2026-05-17 04:11:42 +00:00
|
|
|
|
local conf_dir="/etc/apache2/sites-available"
|
|
|
|
|
|
[[ "$OS_FAMILY" == "rhel" ]] && conf_dir="/etc/httpd/conf.d"
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "$CONFIGURE_SSL" == true ]]; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
cat > "${conf_dir}/pelican.conf" <<APACHECFG
|
2026-05-17 04:11:42 +00:00
|
|
|
|
<VirtualHost *:80>
|
|
|
|
|
|
ServerName ${FQDN}
|
|
|
|
|
|
Redirect permanent / https://${FQDN}/
|
|
|
|
|
|
</VirtualHost>
|
|
|
|
|
|
|
|
|
|
|
|
<VirtualHost *:443>
|
|
|
|
|
|
ServerName ${FQDN}
|
|
|
|
|
|
DocumentRoot ${PELICAN_DIR}/public
|
|
|
|
|
|
|
|
|
|
|
|
AllowEncodedSlashes On
|
|
|
|
|
|
php_value upload_max_filesize 100M
|
|
|
|
|
|
php_value post_max_size 100M
|
|
|
|
|
|
|
|
|
|
|
|
<Directory "${PELICAN_DIR}/public">
|
|
|
|
|
|
Require all granted
|
|
|
|
|
|
AllowOverride all
|
|
|
|
|
|
</Directory>
|
|
|
|
|
|
|
|
|
|
|
|
SSLEngine on
|
|
|
|
|
|
SSLCertificateFile /etc/letsencrypt/live/${FQDN}/fullchain.pem
|
|
|
|
|
|
SSLCertificateKeyFile /etc/letsencrypt/live/${FQDN}/privkey.pem
|
|
|
|
|
|
</VirtualHost>
|
2026-05-17 11:53:42 +00:00
|
|
|
|
APACHECFG
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
cat > "${conf_dir}/pelican.conf" <<APACHECFG
|
2026-05-17 04:11:42 +00:00
|
|
|
|
<VirtualHost *:80>
|
|
|
|
|
|
ServerName ${FQDN}
|
|
|
|
|
|
DocumentRoot ${PELICAN_DIR}/public
|
|
|
|
|
|
|
|
|
|
|
|
AllowEncodedSlashes On
|
|
|
|
|
|
php_value upload_max_filesize 100M
|
|
|
|
|
|
php_value post_max_size 100M
|
|
|
|
|
|
|
|
|
|
|
|
<Directory "${PELICAN_DIR}/public">
|
|
|
|
|
|
Require all granted
|
|
|
|
|
|
AllowOverride all
|
|
|
|
|
|
</Directory>
|
|
|
|
|
|
</VirtualHost>
|
2026-05-17 11:53:42 +00:00
|
|
|
|
APACHECFG
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "debian" ]]; then
|
|
|
|
|
|
a2ensite pelican >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
systemctl enable --now apache2 >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
systemctl restart apache2 >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
else
|
|
|
|
|
|
systemctl enable --now httpd >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
systemctl restart httpd >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
fi
|
|
|
|
|
|
success "Apache configured and running"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
install_caddy() {
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "debian" ]]; then
|
|
|
|
|
|
apt install -y debian-keyring debian-archive-keyring apt-transport-https >> "$LOG_FILE" 2>&1
|
2026-05-17 11:53:42 +00:00
|
|
|
|
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
|
|
|
|
|
|
gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg 2>/dev/null
|
|
|
|
|
|
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
|
|
|
|
|
|
tee /etc/apt/sources.list.d/caddy-stable.list >> "$LOG_FILE" 2>&1
|
2026-05-17 04:11:42 +00:00
|
|
|
|
apt update >> "$LOG_FILE" 2>&1
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing Caddy" apt install -y caddy
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
|
|
|
|
|
dnf install -y 'dnf-command(copr)' >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
dnf copr enable @caddy/caddy -y >> "$LOG_FILE" 2>&1
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing Caddy" dnf install -y caddy
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
local fpm_sock
|
|
|
|
|
|
fpm_sock=$(get_fpm_socket)
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
cat > /etc/caddy/Caddyfile <<CADDYCFG
|
2026-05-17 04:11:42 +00:00
|
|
|
|
${FQDN} {
|
|
|
|
|
|
root * ${PELICAN_DIR}/public
|
|
|
|
|
|
file_server
|
|
|
|
|
|
|
|
|
|
|
|
php_fastcgi unix/${fpm_sock} {
|
|
|
|
|
|
root ${PELICAN_DIR}/public
|
|
|
|
|
|
index index.php
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
header {
|
|
|
|
|
|
X-Content-Type-Options nosniff
|
|
|
|
|
|
X-XSS-Protection "1; mode=block"
|
|
|
|
|
|
X-Robots-Tag none
|
|
|
|
|
|
Content-Security-Policy "frame-ancestors 'self'"
|
|
|
|
|
|
X-Frame-Options DENY
|
|
|
|
|
|
Referrer-Policy same-origin
|
|
|
|
|
|
-Server
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
encode gzip
|
|
|
|
|
|
|
|
|
|
|
|
log {
|
|
|
|
|
|
output file /var/log/caddy/pelican.log
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-17 11:53:42 +00:00
|
|
|
|
CADDYCFG
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
mkdir -p /var/log/caddy
|
|
|
|
|
|
systemctl enable --now caddy >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
systemctl restart caddy >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
success "Caddy configured and running (auto-SSL enabled)"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# SSL (Let's Encrypt)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
install_ssl() {
|
|
|
|
|
|
if [[ "$CONFIGURE_SSL" != true ]]; then
|
|
|
|
|
|
return
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [[ "$WEBSERVER" == "caddy" ]]; then
|
|
|
|
|
|
info "Caddy handles SSL automatically — skipping certbot"
|
|
|
|
|
|
return
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
step "Obtaining SSL certificate via Let's Encrypt"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "debian" ]]; then
|
|
|
|
|
|
apt install -y certbot >> "$LOG_FILE" 2>&1
|
2026-05-17 11:53:42 +00:00
|
|
|
|
[[ "$WEBSERVER" == "nginx" ]] && apt install -y python3-certbot-nginx >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
[[ "$WEBSERVER" == "apache" ]] && apt install -y python3-certbot-apache >> "$LOG_FILE" 2>&1
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
|
|
|
|
|
dnf install -y certbot >> "$LOG_FILE" 2>&1
|
2026-05-17 11:53:42 +00:00
|
|
|
|
[[ "$WEBSERVER" == "nginx" ]] && dnf install -y python3-certbot-nginx >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
[[ "$WEBSERVER" == "apache" ]] && dnf install -y python3-certbot-apache >> "$LOG_FILE" 2>&1
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
local plugin="--standalone"
|
|
|
|
|
|
[[ "$WEBSERVER" == "nginx" ]] && plugin="--nginx"
|
|
|
|
|
|
[[ "$WEBSERVER" == "apache" ]] && plugin="--apache"
|
|
|
|
|
|
|
|
|
|
|
|
certbot certonly $plugin -d "$FQDN" --non-interactive --agree-tos --email "$EMAIL" >> "$LOG_FILE" 2>&1 || {
|
|
|
|
|
|
warn "Plugin-based cert failed, trying standalone..."
|
|
|
|
|
|
systemctl stop "${WEBSERVER}" 2>/dev/null || systemctl stop apache2 2>/dev/null || systemctl stop httpd 2>/dev/null || true
|
|
|
|
|
|
certbot certonly --standalone -d "$FQDN" --non-interactive --agree-tos --email "$EMAIL" >> "$LOG_FILE" 2>&1 || \
|
|
|
|
|
|
fatal "Failed to obtain SSL certificate. Is the domain pointed to this server?"
|
|
|
|
|
|
systemctl start "${WEBSERVER}" 2>/dev/null || systemctl start apache2 2>/dev/null || systemctl start httpd 2>/dev/null || true
|
|
|
|
|
|
}
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Auto-renewal cron
|
|
|
|
|
|
(crontab -l 2>/dev/null; echo "0 3 * * * certbot renew --quiet --deploy-hook \"systemctl restart ${WEBSERVER}\"") | sort -u | crontab -
|
|
|
|
|
|
success "SSL certificate obtained — auto-renewal enabled"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Pelican Panel Installation
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
install_panel() {
|
|
|
|
|
|
step "Downloading Pelican Panel"
|
|
|
|
|
|
|
|
|
|
|
|
mkdir -p "$PELICAN_DIR"
|
|
|
|
|
|
cd "$PELICAN_DIR"
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Downloading and extracting Pelican Panel" bash -c "curl -sSL '$PELICAN_PANEL_URL' | tar -xzv >> '$LOG_FILE' 2>&1"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
success "Panel files downloaded to ${PELICAN_DIR}"
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
step "Installing Composer & PHP dependencies"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
if ! command -v composer &>/dev/null; then
|
|
|
|
|
|
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
success "Composer installed"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing PHP dependencies (this takes ~1 minute)" \
|
|
|
|
|
|
env COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader --working-dir="$PELICAN_DIR"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
success "Composer dependencies installed"
|
|
|
|
|
|
|
|
|
|
|
|
step "Configuring Pelican Panel"
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
configure_env_file
|
|
|
|
|
|
run_database_setup
|
|
|
|
|
|
set_permissions
|
|
|
|
|
|
setup_queue_worker
|
|
|
|
|
|
setup_cron_job
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Show APP_KEY warning
|
|
|
|
|
|
local app_key
|
|
|
|
|
|
app_key=$(grep "^APP_KEY=" "${PELICAN_DIR}/.env" 2>/dev/null | cut -d= -f2-)
|
|
|
|
|
|
if [[ -n "$app_key" ]]; then
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " ${RED}${BOLD}┌─────────────────────────────────────────────────────────┐${RESET}"
|
|
|
|
|
|
echo -e " ${RED}${BOLD}│ ⚠ ENCRYPTION KEY — BACK THIS UP! LOSING IT = DATA LOSS│${RESET}"
|
|
|
|
|
|
echo -e " ${RED}${BOLD}├─────────────────────────────────────────────────────────┤${RESET}"
|
|
|
|
|
|
echo -e " ${RED}${BOLD}│${RESET} APP_KEY=${WHITE}${app_key}${RESET}"
|
|
|
|
|
|
echo -e " ${RED}${BOLD}└─────────────────────────────────────────────────────────┘${RESET}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
configure_env_file() {
|
|
|
|
|
|
info "Writing .env configuration..."
|
|
|
|
|
|
|
|
|
|
|
|
# Determine APP_URL
|
|
|
|
|
|
if [[ -z "$APP_URL" ]]; then
|
|
|
|
|
|
if [[ "$CONFIGURE_SSL" == true || "$WEBSERVER" == "caddy" ]]; then
|
|
|
|
|
|
APP_URL="https://${FQDN}"
|
|
|
|
|
|
else
|
|
|
|
|
|
APP_URL="http://${FQDN}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Determine DB connection string for Laravel
|
|
|
|
|
|
local laravel_db_connection
|
|
|
|
|
|
case "$DB_CHOICE" in
|
|
|
|
|
|
mysql|mariadb) laravel_db_connection="mysql" ;;
|
|
|
|
|
|
postgres) laravel_db_connection="pgsql" ;;
|
|
|
|
|
|
sqlite) laravel_db_connection="sqlite" ;;
|
|
|
|
|
|
esac
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Copy example .env if needed
|
|
|
|
|
|
if [[ ! -f "${PELICAN_DIR}/.env" && -f "${PELICAN_DIR}/.env.example" ]]; then
|
|
|
|
|
|
cp "${PELICAN_DIR}/.env.example" "${PELICAN_DIR}/.env"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# Generate APP_KEY
|
|
|
|
|
|
php artisan key:generate --force --no-interaction >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
success "Application key generated"
|
|
|
|
|
|
|
|
|
|
|
|
# Helper to set a value in .env
|
|
|
|
|
|
set_env() {
|
|
|
|
|
|
local key="$1"
|
|
|
|
|
|
local value="$2"
|
|
|
|
|
|
if grep -q "^${key}=" "${PELICAN_DIR}/.env" 2>/dev/null; then
|
|
|
|
|
|
sed -i "s|^${key}=.*|${key}=${value}|" "${PELICAN_DIR}/.env"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "${key}=${value}" >> "${PELICAN_DIR}/.env"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Core settings
|
|
|
|
|
|
set_env "APP_URL" "$APP_URL"
|
|
|
|
|
|
set_env "APP_TIMEZONE" "$TIMEZONE"
|
|
|
|
|
|
set_env "APP_ENV" "production"
|
|
|
|
|
|
set_env "APP_DEBUG" "false"
|
|
|
|
|
|
|
|
|
|
|
|
# Database settings
|
|
|
|
|
|
set_env "DB_CONNECTION" "$laravel_db_connection"
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "$DB_CHOICE" == "sqlite" ]]; then
|
|
|
|
|
|
set_env "DB_DATABASE" "${PELICAN_DIR}/database/database.sqlite"
|
|
|
|
|
|
# Create SQLite file
|
|
|
|
|
|
touch "${PELICAN_DIR}/database/database.sqlite"
|
|
|
|
|
|
else
|
|
|
|
|
|
set_env "DB_HOST" "$DB_HOST"
|
|
|
|
|
|
set_env "DB_PORT" "$DB_PORT"
|
|
|
|
|
|
set_env "DB_DATABASE" "$DB_NAME"
|
|
|
|
|
|
set_env "DB_USERNAME" "$DB_USER"
|
|
|
|
|
|
set_env "DB_PASSWORD" "$DB_PASSWORD"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# Cache & session (file driver — reliable defaults)
|
|
|
|
|
|
set_env "CACHE_STORE" "file"
|
|
|
|
|
|
set_env "SESSION_DRIVER" "file"
|
|
|
|
|
|
set_env "QUEUE_CONNECTION" "database"
|
|
|
|
|
|
|
|
|
|
|
|
success ".env configured with all database and app settings"
|
|
|
|
|
|
log "ENV configured: APP_URL=$APP_URL DB_CONNECTION=$laravel_db_connection DB_HOST=$DB_HOST DB_NAME=$DB_NAME"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
run_database_setup() {
|
|
|
|
|
|
info "Running database migrations..."
|
|
|
|
|
|
|
|
|
|
|
|
cd "$PELICAN_DIR"
|
|
|
|
|
|
|
|
|
|
|
|
# Run migrations
|
|
|
|
|
|
php artisan migrate --seed --force >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
|
|
|
|
error "Database migration failed — check ${LOG_FILE}"
|
|
|
|
|
|
error "You can retry manually: cd ${PELICAN_DIR} && php artisan migrate --seed --force"
|
|
|
|
|
|
else
|
|
|
|
|
|
success "Database migrations and seeders completed"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
set_permissions() {
|
|
|
|
|
|
info "Setting file permissions..."
|
|
|
|
|
|
|
|
|
|
|
|
chown -R "${WEBSERVER_USER}:${WEBSERVER_USER}" "$PELICAN_DIR"
|
|
|
|
|
|
chmod -R 775 "${PELICAN_DIR}/storage" "${PELICAN_DIR}/bootstrap/cache"
|
|
|
|
|
|
|
|
|
|
|
|
# Ensure storage subdirectories exist
|
|
|
|
|
|
mkdir -p "${PELICAN_DIR}/storage/framework/"{cache/data,sessions,views}
|
|
|
|
|
|
mkdir -p "${PELICAN_DIR}/storage/logs"
|
|
|
|
|
|
chown -R "${WEBSERVER_USER}:${WEBSERVER_USER}" "${PELICAN_DIR}/storage"
|
|
|
|
|
|
|
|
|
|
|
|
success "Permissions set (owner: ${WEBSERVER_USER}, storage: 775)"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setup_queue_worker() {
|
|
|
|
|
|
info "Creating queue worker service..."
|
|
|
|
|
|
|
|
|
|
|
|
cat > /etc/systemd/system/pelican-queue.service <<QSVC
|
|
|
|
|
|
# Pelican Queue Worker — auto-installed by pelican-installer
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
[Unit]
|
|
|
|
|
|
Description=Pelican Queue Worker
|
|
|
|
|
|
After=network.target
|
|
|
|
|
|
|
|
|
|
|
|
[Service]
|
2026-05-17 11:53:42 +00:00
|
|
|
|
User=${WEBSERVER_USER}
|
|
|
|
|
|
Group=${WEBSERVER_USER}
|
2026-05-17 04:11:42 +00:00
|
|
|
|
Restart=always
|
|
|
|
|
|
ExecStart=/usr/bin/php ${PELICAN_DIR}/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3
|
|
|
|
|
|
StartLimitInterval=180
|
|
|
|
|
|
StartLimitBurst=30
|
|
|
|
|
|
RestartSec=5s
|
|
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
|
WantedBy=multi-user.target
|
2026-05-17 11:53:42 +00:00
|
|
|
|
QSVC
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
systemctl daemon-reload
|
|
|
|
|
|
systemctl enable --now pelican-queue >> "$LOG_FILE" 2>&1
|
2026-05-17 11:53:42 +00:00
|
|
|
|
success "Queue worker service created and running"
|
|
|
|
|
|
}
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
setup_cron_job() {
|
|
|
|
|
|
info "Setting up scheduled task cron..."
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
local cron_line="* * * * * php ${PELICAN_DIR}/artisan schedule:run >> /dev/null 2>&1"
|
|
|
|
|
|
(crontab -u "${WEBSERVER_USER}" -l 2>/dev/null | grep -v "schedule:run"; echo "$cron_line") | crontab -u "${WEBSERVER_USER}" -
|
|
|
|
|
|
success "Cron job configured (runs every minute)"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Wings Installation
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
install_wings() {
|
|
|
|
|
|
step "Installing Docker"
|
|
|
|
|
|
|
|
|
|
|
|
if command -v docker &>/dev/null; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
success "Docker already installed"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Installing Docker CE (this may take a minute)" bash -c "curl -sSL https://get.docker.com/ | CHANNEL=stable sh >> '$LOG_FILE' 2>&1"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
systemctl enable --now docker >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
success "Docker CE installed and running"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
step "Installing Wings daemon"
|
|
|
|
|
|
|
|
|
|
|
|
mkdir -p "$WINGS_DIR" /var/run/wings
|
|
|
|
|
|
|
|
|
|
|
|
local wings_url="${PELICAN_WINGS_BASE}/wings_linux_${WINGS_ARCH}"
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Downloading Wings binary (${WINGS_ARCH})" curl -sSL -o "$WINGS_BIN" "$wings_url"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
chmod u+x "$WINGS_BIN"
|
|
|
|
|
|
success "Wings binary installed to ${WINGS_BIN}"
|
|
|
|
|
|
|
|
|
|
|
|
step "Setting up Wings systemd service"
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
cat > /etc/systemd/system/wings.service <<WSVC
|
2026-05-17 04:11:42 +00:00
|
|
|
|
[Unit]
|
|
|
|
|
|
Description=Wings Daemon
|
|
|
|
|
|
After=docker.service
|
|
|
|
|
|
Requires=docker.service
|
|
|
|
|
|
PartOf=docker.service
|
|
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
|
User=root
|
|
|
|
|
|
WorkingDirectory=${WINGS_DIR}
|
|
|
|
|
|
LimitNOFILE=4096
|
|
|
|
|
|
PIDFile=/var/run/wings/daemon.pid
|
|
|
|
|
|
ExecStart=${WINGS_BIN}
|
|
|
|
|
|
Restart=on-failure
|
|
|
|
|
|
StartLimitInterval=180
|
|
|
|
|
|
StartLimitBurst=30
|
|
|
|
|
|
RestartSec=5s
|
|
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
|
WantedBy=multi-user.target
|
2026-05-17 11:53:42 +00:00
|
|
|
|
WSVC
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
systemctl daemon-reload
|
|
|
|
|
|
systemctl enable wings >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
success "Wings systemd service created and enabled"
|
|
|
|
|
|
|
|
|
|
|
|
echo ""
|
2026-05-17 11:53:42 +00:00
|
|
|
|
echo -e " ${YELLOW}${BOLD}Wings requires manual configuration:${RESET}"
|
|
|
|
|
|
echo -e " ${DIM}1. Go to Panel admin → Nodes → Create New${RESET}"
|
|
|
|
|
|
echo -e " ${DIM}2. Copy the configuration YAML from the Configuration tab${RESET}"
|
|
|
|
|
|
echo -e " ${DIM}3. Paste it into: ${WHITE}${WINGS_DIR}/config.yml${RESET}"
|
|
|
|
|
|
echo -e " ${DIM}4. Then start Wings: ${WHITE}sudo systemctl start wings${RESET}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Uninstall
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
uninstall() {
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " ${RED}${BOLD}⚠ Uninstall Pelican${RESET}"
|
|
|
|
|
|
echo ""
|
2026-05-17 11:53:42 +00:00
|
|
|
|
echo -e " ${CYAN}[1]${RESET} Uninstall Panel"
|
|
|
|
|
|
echo -e " ${CYAN}[2]${RESET} Uninstall Wings"
|
|
|
|
|
|
echo -e " ${CYAN}[3]${RESET} Uninstall Both"
|
|
|
|
|
|
echo -e " ${CYAN}[4]${RESET} Cancel"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo ""
|
2026-05-17 11:53:42 +00:00
|
|
|
|
ask "Select option [1-4]:"
|
|
|
|
|
|
read -r choice
|
|
|
|
|
|
|
|
|
|
|
|
case "$choice" in
|
|
|
|
|
|
1) uninstall_panel ;;
|
|
|
|
|
|
2) uninstall_wings ;;
|
|
|
|
|
|
3) uninstall_panel; uninstall_wings ;;
|
|
|
|
|
|
4|*) info "Cancelled."; exit 0 ;;
|
2026-05-17 04:11:42 +00:00
|
|
|
|
esac
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uninstall_panel() {
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if ! confirm "Remove all panel files in ${PELICAN_DIR}? (y/N):" "N"; then
|
|
|
|
|
|
info "Aborted."; return
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
step "Uninstalling Pelican Panel"
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
systemctl stop pelican-queue 2>/dev/null || true
|
|
|
|
|
|
systemctl disable pelican-queue 2>/dev/null || true
|
|
|
|
|
|
rm -f /etc/systemd/system/pelican-queue.service
|
|
|
|
|
|
|
|
|
|
|
|
rm -f /etc/nginx/sites-enabled/pelican.conf /etc/nginx/sites-available/pelican.conf 2>/dev/null || true
|
|
|
|
|
|
rm -f /etc/apache2/sites-enabled/pelican.conf /etc/apache2/sites-available/pelican.conf 2>/dev/null || true
|
|
|
|
|
|
rm -f /etc/httpd/conf.d/pelican.conf 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
|
|
rm -rf "$PELICAN_DIR"
|
|
|
|
|
|
systemctl daemon-reload
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
systemctl restart nginx 2>/dev/null || systemctl restart apache2 2>/dev/null || \
|
|
|
|
|
|
systemctl restart httpd 2>/dev/null || systemctl restart caddy 2>/dev/null || true
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
success "Pelican Panel uninstalled"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uninstall_wings() {
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if ! confirm "Stop and remove Wings? (y/N):" "N"; then
|
|
|
|
|
|
info "Aborted."; return
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
step "Uninstalling Wings"
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
systemctl stop wings 2>/dev/null || true
|
|
|
|
|
|
systemctl disable wings 2>/dev/null || true
|
|
|
|
|
|
rm -f /etc/systemd/system/wings.service
|
|
|
|
|
|
rm -f "$WINGS_BIN"
|
|
|
|
|
|
systemctl daemon-reload
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if confirm "Also remove Wings config (${WINGS_DIR})? (y/N):" "N"; then
|
2026-05-17 04:11:42 +00:00
|
|
|
|
rm -rf "$WINGS_DIR"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
success "Wings uninstalled"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Update
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
update_panel() {
|
|
|
|
|
|
step "Updating Pelican Panel"
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
[[ ! -d "$PELICAN_DIR" ]] && fatal "Panel not found at ${PELICAN_DIR}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
cd "$PELICAN_DIR"
|
|
|
|
|
|
|
|
|
|
|
|
php artisan down >> "$LOG_FILE" 2>&1 || true
|
|
|
|
|
|
info "Panel in maintenance mode"
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
cp .env ".env.backup.$(date +%Y%m%d%H%M%S)"
|
|
|
|
|
|
success "Backed up .env"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Downloading latest Pelican Panel" bash -c "curl -sSL '$PELICAN_PANEL_URL' | tar -xzv >> '$LOG_FILE' 2>&1"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Updating PHP dependencies" \
|
|
|
|
|
|
env COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
php artisan migrate --seed --force >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
success "Database migrations applied"
|
|
|
|
|
|
|
|
|
|
|
|
php artisan optimize:clear >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
success "Caches cleared"
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Re-detect webserver user
|
2026-05-17 04:11:42 +00:00
|
|
|
|
if [[ "$OS_FAMILY" == "rhel" ]]; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if systemctl is-active --quiet nginx; then WEBSERVER_USER="nginx"
|
|
|
|
|
|
elif systemctl is-active --quiet httpd; then WEBSERVER_USER="apache"
|
|
|
|
|
|
elif systemctl is-active --quiet caddy; then WEBSERVER_USER="caddy"
|
|
|
|
|
|
else WEBSERVER_USER="nginx"; fi
|
|
|
|
|
|
else
|
|
|
|
|
|
WEBSERVER_USER="www-data"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
chown -R "${WEBSERVER_USER}:${WEBSERVER_USER}" "$PELICAN_DIR"
|
|
|
|
|
|
chmod -R 775 "${PELICAN_DIR}/storage" "${PELICAN_DIR}/bootstrap/cache"
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
systemctl restart pelican-queue >> "$LOG_FILE" 2>&1 || true
|
|
|
|
|
|
php artisan queue:restart >> "$LOG_FILE" 2>&1 || true
|
|
|
|
|
|
php artisan up >> "$LOG_FILE" 2>&1
|
2026-05-17 11:53:42 +00:00
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
success "Panel updated and back online!"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
update_wings() {
|
|
|
|
|
|
step "Updating Wings"
|
|
|
|
|
|
|
|
|
|
|
|
systemctl stop wings >> "$LOG_FILE" 2>&1 || true
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
run_cmd "Downloading latest Wings binary" curl -sSL -o "$WINGS_BIN" "${PELICAN_WINGS_BASE}/wings_linux_${WINGS_ARCH}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
chmod u+x "$WINGS_BIN"
|
|
|
|
|
|
|
|
|
|
|
|
systemctl start wings >> "$LOG_FILE" 2>&1
|
|
|
|
|
|
success "Wings updated and restarted!"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Health Checks
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
health_check() {
|
|
|
|
|
|
step "Running post-install health checks"
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
local passed=0
|
|
|
|
|
|
local failed=0
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
check_pass() { success "$1"; passed=$((passed + 1)); }
|
|
|
|
|
|
check_fail() { error "$1"; failed=$((failed + 1)); }
|
|
|
|
|
|
|
|
|
|
|
|
# PHP
|
2026-05-17 04:11:42 +00:00
|
|
|
|
if php -v &>/dev/null; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
check_pass "PHP $(php -v | head -1 | awk '{print $2}') is working"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
check_fail "PHP is not working"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Web server
|
|
|
|
|
|
if [[ "$INSTALL_PANEL" == true ]]; then
|
|
|
|
|
|
local ws_active=false
|
|
|
|
|
|
for svc in nginx apache2 httpd caddy; do
|
|
|
|
|
|
if systemctl is-active --quiet "$svc" 2>/dev/null; then
|
|
|
|
|
|
check_pass "${svc^} is running"
|
|
|
|
|
|
ws_active=true
|
|
|
|
|
|
break
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
2026-05-17 11:53:42 +00:00
|
|
|
|
done
|
|
|
|
|
|
[[ "$ws_active" == false ]] && check_fail "No web server is running"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Panel files
|
2026-05-17 04:11:42 +00:00
|
|
|
|
if [[ -f "${PELICAN_DIR}/artisan" ]]; then
|
2026-05-17 11:53:42 +00:00
|
|
|
|
check_pass "Panel files present at ${PELICAN_DIR}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
check_fail "Panel files missing"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# .env has APP_KEY
|
|
|
|
|
|
if grep -q "^APP_KEY=base64:" "${PELICAN_DIR}/.env" 2>/dev/null; then
|
|
|
|
|
|
check_pass ".env has APP_KEY"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
check_fail ".env missing or no APP_KEY"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# .env has DB_CONNECTION
|
|
|
|
|
|
if grep -q "^DB_CONNECTION=" "${PELICAN_DIR}/.env" 2>/dev/null; then
|
|
|
|
|
|
check_pass ".env has database configuration"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
check_fail ".env missing database configuration"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Queue worker
|
|
|
|
|
|
if systemctl is-active --quiet pelican-queue; then
|
|
|
|
|
|
check_pass "Queue worker is running"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
check_fail "Queue worker is not running"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# Can artisan run without error
|
|
|
|
|
|
if cd "$PELICAN_DIR" && php artisan --version >> "$LOG_FILE" 2>&1; then
|
|
|
|
|
|
check_pass "Artisan CLI works"
|
|
|
|
|
|
else
|
|
|
|
|
|
check_fail "Artisan CLI errors (check ${LOG_FILE})"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# HTTP check
|
|
|
|
|
|
if [[ -n "$FQDN" ]]; then
|
|
|
|
|
|
local proto="http"
|
|
|
|
|
|
[[ "$CONFIGURE_SSL" == true || "${WEBSERVER:-}" == "caddy" ]] && proto="https"
|
|
|
|
|
|
local http_code
|
|
|
|
|
|
http_code=$(curl -sSo /dev/null -w '%{http_code}' --max-time 10 "${proto}://${FQDN}" 2>/dev/null || echo "000")
|
|
|
|
|
|
if [[ "$http_code" =~ ^(200|302|301)$ ]]; then
|
|
|
|
|
|
check_pass "Panel responds at ${proto}://${FQDN} (HTTP ${http_code})"
|
|
|
|
|
|
else
|
|
|
|
|
|
check_fail "Panel not responding at ${proto}://${FQDN} (HTTP ${http_code})"
|
|
|
|
|
|
fi
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Database
|
|
|
|
|
|
if [[ "$INSTALL_PANEL" == true && "$DB_CHOICE" != "sqlite" ]]; then
|
2026-05-17 04:11:42 +00:00
|
|
|
|
case "$DB_CHOICE" in
|
2026-05-17 11:53:42 +00:00
|
|
|
|
mysql) systemctl is-active --quiet mysql 2>/dev/null || systemctl is-active --quiet mysqld 2>/dev/null && check_pass "MySQL is running" || check_fail "MySQL not running" ;;
|
|
|
|
|
|
mariadb) systemctl is-active --quiet mariadb && check_pass "MariaDB is running" || check_fail "MariaDB not running" ;;
|
|
|
|
|
|
postgres) systemctl is-active --quiet postgresql && check_pass "PostgreSQL is running" || check_fail "PostgreSQL not running" ;;
|
2026-05-17 04:11:42 +00:00
|
|
|
|
esac
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Wings
|
|
|
|
|
|
if [[ "$INSTALL_WINGS_FLAG" == true ]]; then
|
|
|
|
|
|
[[ -x "$WINGS_BIN" ]] && check_pass "Wings binary installed" || check_fail "Wings binary missing"
|
|
|
|
|
|
systemctl is-active --quiet docker && check_pass "Docker is running" || check_fail "Docker not running"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo ""
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if [[ $failed -eq 0 ]]; then
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}✓ All ${passed} checks passed!${RESET}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
echo -e " ${YELLOW}${BOLD}⚠ ${passed} passed, ${failed} failed — review issues above${RESET}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Interactive Menus
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
select_components() {
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " ${BOLD}What would you like to install?${RESET}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo ""
|
2026-05-17 11:53:42 +00:00
|
|
|
|
echo -e " ${CYAN}[1]${RESET} Panel only"
|
|
|
|
|
|
echo -e " ${CYAN}[2]${RESET} Wings only"
|
|
|
|
|
|
echo -e " ${CYAN}[3]${RESET} Panel + Wings ${DIM}(same machine)${RESET}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo ""
|
2026-05-17 11:53:42 +00:00
|
|
|
|
ask "Select [1-3]:"
|
|
|
|
|
|
read -r comp
|
|
|
|
|
|
case "${comp:-1}" in
|
|
|
|
|
|
1) INSTALL_PANEL=true; INSTALL_WINGS_FLAG=false ;;
|
|
|
|
|
|
2) INSTALL_PANEL=false; INSTALL_WINGS_FLAG=true ;;
|
|
|
|
|
|
3) INSTALL_PANEL=true; INSTALL_WINGS_FLAG=true ;;
|
|
|
|
|
|
*) INSTALL_PANEL=true; INSTALL_WINGS_FLAG=false ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
collect_panel_options() {
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " ${BOLD}${WHITE}Panel Configuration${RESET}"
|
|
|
|
|
|
echo -e " ${DIM}$(printf '%.0s─' {1..40})${RESET}"
|
|
|
|
|
|
|
|
|
|
|
|
# Domain
|
|
|
|
|
|
if [[ -z "$FQDN" ]]; then
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
ask "Panel domain (e.g., panel.example.com):"
|
|
|
|
|
|
read -r FQDN
|
|
|
|
|
|
[[ -z "$FQDN" ]] && fatal "Domain is required."
|
|
|
|
|
|
fi
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Validate FQDN (basic check)
|
|
|
|
|
|
if [[ ! "$FQDN" =~ ^[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?$ ]]; then
|
|
|
|
|
|
warn "Domain '${FQDN}' looks unusual — double-check it's correct"
|
|
|
|
|
|
fi
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
# Web server
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if [[ -z "$WEBSERVER" ]]; then
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " ${BOLD}Web server:${RESET}"
|
|
|
|
|
|
echo -e " ${CYAN}[1]${RESET} Nginx ${DIM}(recommended)${RESET}"
|
|
|
|
|
|
echo -e " ${CYAN}[2]${RESET} Apache"
|
|
|
|
|
|
echo -e " ${CYAN}[3]${RESET} Caddy ${DIM}(automatic SSL, easiest setup)${RESET}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
ask "Select [1-3] (default: 1):"
|
|
|
|
|
|
read -r ws
|
|
|
|
|
|
case "${ws:-1}" in
|
|
|
|
|
|
1) WEBSERVER="nginx" ;;
|
|
|
|
|
|
2) WEBSERVER="apache" ;;
|
|
|
|
|
|
3) WEBSERVER="caddy" ;;
|
|
|
|
|
|
*) WEBSERVER="nginx" ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# Update WEBSERVER_USER based on selection
|
|
|
|
|
|
if [[ "$OS_FAMILY" == "rhel" ]]; then
|
|
|
|
|
|
case "$WEBSERVER" in
|
|
|
|
|
|
nginx) WEBSERVER_USER="nginx" ;;
|
|
|
|
|
|
apache) WEBSERVER_USER="apache" ;;
|
|
|
|
|
|
caddy) WEBSERVER_USER="caddy" ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
fi
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
# Database
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if [[ -z "$DB_CHOICE" ]]; then
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " ${BOLD}Database:${RESET}"
|
|
|
|
|
|
echo -e " ${CYAN}[1]${RESET} SQLite ${DIM}(simplest, no server needed)${RESET}"
|
|
|
|
|
|
echo -e " ${CYAN}[2]${RESET} MySQL"
|
|
|
|
|
|
echo -e " ${CYAN}[3]${RESET} MariaDB"
|
|
|
|
|
|
echo -e " ${CYAN}[4]${RESET} PostgreSQL ${DIM}(recommended for production)${RESET}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
ask "Select [1-4] (default: 1):"
|
|
|
|
|
|
read -r db
|
|
|
|
|
|
case "${db:-1}" in
|
|
|
|
|
|
1) DB_CHOICE="sqlite" ;;
|
|
|
|
|
|
2) DB_CHOICE="mysql" ;;
|
|
|
|
|
|
3) DB_CHOICE="mariadb" ;;
|
|
|
|
|
|
4) DB_CHOICE="postgres" ;;
|
|
|
|
|
|
*) DB_CHOICE="sqlite" ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
fi
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
auto_set_db_port
|
|
|
|
|
|
generate_db_password
|
|
|
|
|
|
|
|
|
|
|
|
# Custom DB credentials (skip for sqlite)
|
|
|
|
|
|
if [[ "$DB_CHOICE" != "sqlite" && "$ASSUME_YES" != true ]]; then
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo ""
|
2026-05-17 11:53:42 +00:00
|
|
|
|
ask "Database name [${DB_NAME}]:"
|
|
|
|
|
|
read -r input_db_name
|
|
|
|
|
|
DB_NAME="${input_db_name:-$DB_NAME}"
|
|
|
|
|
|
|
|
|
|
|
|
ask "Database user [${DB_USER}]:"
|
|
|
|
|
|
read -r input_db_user
|
|
|
|
|
|
DB_USER="${input_db_user:-$DB_USER}"
|
|
|
|
|
|
|
|
|
|
|
|
ask "Database password [auto-generated]:"
|
|
|
|
|
|
read -r -s input_db_pass
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
DB_PASSWORD="${input_db_pass:-$DB_PASSWORD}"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# SSL
|
|
|
|
|
|
if [[ -z "$CONFIGURE_SSL" ]]; then
|
|
|
|
|
|
if [[ "$WEBSERVER" == "caddy" ]]; then
|
|
|
|
|
|
CONFIGURE_SSL=false # Caddy handles it
|
|
|
|
|
|
info "Caddy handles SSL automatically"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
else
|
2026-05-17 11:53:42 +00:00
|
|
|
|
echo ""
|
|
|
|
|
|
if confirm "Enable SSL with Let's Encrypt? (Y/n):" "Y"; then
|
|
|
|
|
|
CONFIGURE_SSL=true
|
|
|
|
|
|
if [[ -z "$EMAIL" ]]; then
|
|
|
|
|
|
ask "Email for Let's Encrypt certificates:"
|
|
|
|
|
|
read -r EMAIL
|
|
|
|
|
|
[[ -z "$EMAIL" ]] && fatal "Email is required for SSL"
|
|
|
|
|
|
fi
|
|
|
|
|
|
else
|
|
|
|
|
|
CONFIGURE_SSL=false
|
|
|
|
|
|
fi
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
2026-05-17 11:53:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# Timezone
|
|
|
|
|
|
if [[ "$ASSUME_YES" != true ]]; then
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
ask "Timezone [${TIMEZONE}]:"
|
|
|
|
|
|
read -r input_tz
|
|
|
|
|
|
TIMEZONE="${input_tz:-$TIMEZONE}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
show_summary() {
|
|
|
|
|
|
echo ""
|
2026-05-17 11:53:42 +00:00
|
|
|
|
echo -e " ${CYAN}${BOLD}╔═══════════════════════════════════════════╗${RESET}"
|
|
|
|
|
|
echo -e " ${CYAN}${BOLD}║ Installation Summary ║${RESET}"
|
|
|
|
|
|
echo -e " ${CYAN}${BOLD}╚═══════════════════════════════════════════╝${RESET}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo ""
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if [[ "$INSTALL_PANEL" == true ]]; then
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo -e " ${BOLD}Panel:${RESET}"
|
2026-05-17 11:53:42 +00:00
|
|
|
|
echo -e " Domain: ${GREEN}${FQDN}${RESET}"
|
|
|
|
|
|
echo -e " Web server: ${GREEN}${WEBSERVER}${RESET}"
|
|
|
|
|
|
echo -e " Database: ${GREEN}${DB_CHOICE}${RESET}"
|
|
|
|
|
|
if [[ "$DB_CHOICE" != "sqlite" ]]; then
|
|
|
|
|
|
echo -e " DB name: ${GREEN}${DB_NAME}${RESET}"
|
|
|
|
|
|
echo -e " DB user: ${GREEN}${DB_USER}${RESET}"
|
|
|
|
|
|
fi
|
|
|
|
|
|
echo -e " SSL: ${GREEN}$([[ "$CONFIGURE_SSL" == true || "$WEBSERVER" == "caddy" ]] && echo "Yes" || echo "No")${RESET}"
|
|
|
|
|
|
echo -e " Timezone: ${GREEN}${TIMEZONE}${RESET}"
|
|
|
|
|
|
echo -e " PHP: ${GREEN}${PHP_VERSION}${RESET}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
fi
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if [[ "$INSTALL_WINGS_FLAG" == true ]]; then
|
2026-05-17 04:11:42 +00:00
|
|
|
|
echo -e " ${BOLD}Wings:${RESET}"
|
|
|
|
|
|
echo -e " Architecture: ${GREEN}${WINGS_ARCH}${RESET}"
|
|
|
|
|
|
echo -e " Docker: ${GREEN}Will be installed${RESET}"
|
2026-05-17 11:53:42 +00:00
|
|
|
|
echo ""
|
|
|
|
|
|
fi
|
2026-05-17 04:11:42 +00:00
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
if ! confirm "Proceed with installation? (Y/n):" "Y"; then
|
2026-05-17 04:11:42 +00:00
|
|
|
|
info "Installation cancelled."
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Completion Messages
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
print_complete() {
|
|
|
|
|
|
local proto="http"
|
|
|
|
|
|
[[ "$CONFIGURE_SSL" == true || "$WEBSERVER" == "caddy" ]] && proto="https"
|
|
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}╔═══════════════════════════════════════════════════════════╗${RESET}"
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}║ ║${RESET}"
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}║ 🎉 Installation completed successfully! 🎉 ║${RESET}"
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}║ ║${RESET}"
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}╚═══════════════════════════════════════════════════════════╝${RESET}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "$INSTALL_PANEL" == true ]]; then
|
|
|
|
|
|
echo -e " ${BOLD}Panel — next steps:${RESET}"
|
|
|
|
|
|
echo -e " ${WHITE}1.${RESET} Open ${CYAN}${proto}://${FQDN}/installer${RESET} in your browser"
|
|
|
|
|
|
echo -e " ${WHITE}2.${RESET} Complete the web-based setup wizard"
|
|
|
|
|
|
echo -e " ${WHITE}3.${RESET} Create your admin account"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "$INSTALL_WINGS_FLAG" == true ]]; then
|
|
|
|
|
|
echo -e " ${BOLD}Wings — next steps:${RESET}"
|
|
|
|
|
|
echo -e " ${WHITE}1.${RESET} Create a node in Panel admin → Nodes → Create New"
|
|
|
|
|
|
echo -e " ${WHITE}2.${RESET} Copy the config YAML into ${CYAN}${WINGS_DIR}/config.yml${RESET}"
|
|
|
|
|
|
echo -e " ${WHITE}3.${RESET} Test: ${CYAN}sudo wings --debug${RESET}"
|
|
|
|
|
|
echo -e " ${WHITE}4.${RESET} Start: ${CYAN}sudo systemctl start wings${RESET}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
echo -e " ${BOLD}Useful commands:${RESET}"
|
|
|
|
|
|
echo -e " Update: ${CYAN}sudo bash install.sh update --both${RESET}"
|
|
|
|
|
|
echo -e " Uninstall: ${CYAN}sudo bash install.sh uninstall${RESET}"
|
|
|
|
|
|
echo -e " Logs: ${CYAN}tail -f ${LOG_FILE}${RESET}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
# Main
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
main() {
|
2026-05-17 11:53:42 +00:00
|
|
|
|
parse_args "$@"
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
# Initialize log
|
|
|
|
|
|
mkdir -p "$(dirname "$LOG_FILE")"
|
|
|
|
|
|
echo "=== Pelican Installer v${INSTALLER_VERSION} — $(date) ===" >> "$LOG_FILE"
|
|
|
|
|
|
|
|
|
|
|
|
print_header
|
|
|
|
|
|
detect_os
|
|
|
|
|
|
preflight_checks
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
# Determine action
|
|
|
|
|
|
if [[ -z "$ACTION" ]]; then
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " ${BOLD}What would you like to do?${RESET}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " ${CYAN}[1]${RESET} Install Panel / Wings"
|
|
|
|
|
|
echo -e " ${CYAN}[2]${RESET} Update Panel / Wings"
|
|
|
|
|
|
echo -e " ${CYAN}[3]${RESET} Uninstall"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
ask "Select [1-3] (default: 1):"
|
|
|
|
|
|
read -r action_choice
|
|
|
|
|
|
case "${action_choice:-1}" in
|
|
|
|
|
|
1) ACTION="install" ;;
|
|
|
|
|
|
2) ACTION="update" ;;
|
|
|
|
|
|
3) ACTION="uninstall" ;;
|
|
|
|
|
|
*) ACTION="install" ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
case "$ACTION" in
|
|
|
|
|
|
install)
|
|
|
|
|
|
# If components not set via flags, ask
|
|
|
|
|
|
if [[ -z "$INSTALL_PANEL" && -z "$INSTALL_WINGS_FLAG" ]]; then
|
|
|
|
|
|
select_components
|
|
|
|
|
|
fi
|
|
|
|
|
|
INSTALL_PANEL="${INSTALL_PANEL:-false}"
|
|
|
|
|
|
INSTALL_WINGS_FLAG="${INSTALL_WINGS_FLAG:-false}"
|
|
|
|
|
|
|
|
|
|
|
|
# Calculate total steps for progress indicator
|
|
|
|
|
|
TOTAL_STEPS=2 # detect + preflight (already done but we counted them)
|
|
|
|
|
|
CURRENT_STEP=2
|
|
|
|
|
|
[[ "$INSTALL_PANEL" == true ]] && TOTAL_STEPS=$((TOTAL_STEPS + 6)) # php, db, webserver, ssl, panel-download, panel-config
|
|
|
|
|
|
[[ "$INSTALL_WINGS_FLAG" == true ]] && TOTAL_STEPS=$((TOTAL_STEPS + 3)) # docker, wings-download, wings-service
|
|
|
|
|
|
TOTAL_STEPS=$((TOTAL_STEPS + 1)) # health check
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "$INSTALL_PANEL" == true ]]; then
|
|
|
|
|
|
detect_php_version
|
|
|
|
|
|
collect_panel_options
|
|
|
|
|
|
show_summary
|
|
|
|
|
|
install_php
|
|
|
|
|
|
install_database
|
|
|
|
|
|
install_webserver
|
|
|
|
|
|
install_ssl
|
|
|
|
|
|
install_panel
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "$INSTALL_WINGS_FLAG" == true ]]; then
|
|
|
|
|
|
install_wings
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
health_check
|
2026-05-17 11:53:42 +00:00
|
|
|
|
print_complete
|
2026-05-17 04:11:42 +00:00
|
|
|
|
;;
|
2026-05-17 11:53:42 +00:00
|
|
|
|
|
|
|
|
|
|
update)
|
|
|
|
|
|
if [[ -z "$INSTALL_PANEL" && -z "$INSTALL_WINGS_FLAG" ]]; then
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " ${BOLD}What would you like to update?${RESET}"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " ${CYAN}[1]${RESET} Panel"
|
|
|
|
|
|
echo -e " ${CYAN}[2]${RESET} Wings"
|
|
|
|
|
|
echo -e " ${CYAN}[3]${RESET} Both"
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
ask "Select [1-3]:"
|
|
|
|
|
|
read -r upd
|
|
|
|
|
|
case "${upd:-1}" in
|
|
|
|
|
|
1) INSTALL_PANEL=true; INSTALL_WINGS_FLAG=false ;;
|
|
|
|
|
|
2) INSTALL_PANEL=false; INSTALL_WINGS_FLAG=true ;;
|
|
|
|
|
|
3) INSTALL_PANEL=true; INSTALL_WINGS_FLAG=true ;;
|
|
|
|
|
|
*) INSTALL_PANEL=true; INSTALL_WINGS_FLAG=false ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
detect_php_version
|
2026-05-17 11:53:42 +00:00
|
|
|
|
[[ "$INSTALL_PANEL" == true ]] && update_panel
|
|
|
|
|
|
[[ "$INSTALL_WINGS_FLAG" == true ]] && update_wings
|
|
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo -e " ${GREEN}${BOLD}✓ Update complete!${RESET}"
|
|
|
|
|
|
echo ""
|
2026-05-17 04:11:42 +00:00
|
|
|
|
;;
|
2026-05-17 11:53:42 +00:00
|
|
|
|
|
|
|
|
|
|
uninstall)
|
2026-05-17 04:11:42 +00:00
|
|
|
|
uninstall
|
|
|
|
|
|
;;
|
2026-05-17 11:53:42 +00:00
|
|
|
|
|
2026-05-17 04:11:42 +00:00
|
|
|
|
*)
|
2026-05-17 11:53:42 +00:00
|
|
|
|
fatal "Unknown action: ${ACTION}"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
|
2026-05-17 11:53:42 +00:00
|
|
|
|
log "Installer finished at $(date)"
|
2026-05-17 04:11:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ─── Run ───────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
main "$@"
|