#!/bin/bash # ═══════════════════════════════════════════════════════════════ # FuyoNet Hosting Panel — One-Click Installer # Usage: sh <(curl -sL https://install.fuyonet.com) # ═══════════════════════════════════════════════════════════════ set -e VERSION="1.0.0" DOWNLOAD_URL="https://updates.fuyonet.com/fuyonet-panel/releases" LICENSE_API="https://license.fuyonet.com" AGENT_PATH="/opt/fuyonet-panel" AGENT_USER="fuyonetpanel" AGENT_GROUP="fuyonet" WEB_GROUP="fuyonet" SERVER_GROUP="fuyoserv" # Colors RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' # ─── Helper Functions ─── log() { echo -e "${GREEN}[FuyoNet]${NC} $1"; } warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } err() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } step() { echo -e "\n${BLUE}━━━ $1 ━━━${NC}"; } check_root() { [[ $EUID -eq 0 ]] || err "This installer must be run as root." } check_os() { if [[ ! -f /etc/debian_version ]]; then err "Only Debian 12/13 and Ubuntu 22.04/24.04 are supported." fi OS_VERSION=$(cat /etc/debian_version 2>/dev/null) log "Detected: Debian/Ubuntu (${OS_VERSION})" } get_server_ip() { SERVER_IP=$(ip -4 addr show scope global | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -1) if [[ -z "$SERVER_IP" ]]; then SERVER_IP=$(curl -s4 ifconfig.me 2>/dev/null || curl -s4 icanhazip.com 2>/dev/null) fi log "Server IP: ${SERVER_IP}" } # ─── Banner ─── clear echo "" echo -e "${BLUE}╔═══════════════════════════════════════════════════════╗${NC}" echo -e "${BLUE}║${NC} FuyoNet Hosting Panel — Installer v${VERSION} ${BLUE}║${NC}" echo -e "${BLUE}║${NC} https://fuyonet.com ${BLUE}║${NC}" echo -e "${BLUE}╚═══════════════════════════════════════════════════════╝${NC}" echo "" # ─── Pre-flight Checks ─── step "Pre-flight Checks" check_root check_os get_server_ip # Check minimum RAM (2 GB) TOTAL_RAM=$(free -m | awk '/^Mem:/{print $2}') if [[ $TOTAL_RAM -lt 1500 ]]; then warn "Less than 2 GB RAM detected (${TOTAL_RAM} MB). Minimum 2 GB recommended." read -rp "Continue anyway? [y/N]: " CONT [[ "$CONT" =~ ^[Yy] ]] || exit 0 fi # Check disk space (10 GB minimum) FREE_DISK=$(df / --output=avail -BG | tail -1 | tr -d ' G') if [[ $FREE_DISK -lt 10 ]]; then err "Less than 10 GB free disk space. Need at least 10 GB." fi log "RAM: ${TOTAL_RAM} MB, Disk: ${FREE_DISK} GB free" # ─── Configuration ─── step "Configuration" echo "" echo " PHP Versions to install:" echo " [1] Stable only (8.2, 8.3, 8.4) — recommended" echo " [2] Extended (7.4, 8.0, 8.1, 8.2, 8.3, 8.4)" echo " [3] All (7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5-dev)" echo "" read -rp " Selection [1/2/3] (default: 1): " PHP_CHOICE case "${PHP_CHOICE:-1}" in 2) PHP_VERSIONS="7.4 8.0 8.1 8.2 8.3 8.4" ;; 3) PHP_VERSIONS="7.4 8.0 8.1 8.2 8.3 8.4 8.5" ;; *) PHP_VERSIONS="8.2 8.3 8.4" ;; esac log "PHP Versions: ${PHP_VERSIONS}" echo "" echo " License:" echo " [1] Enter license key (purchased at paymenter.fuyonet.com)" echo " [2] Start 7-day trial (unlimited domains)" echo "" read -rp " Selection [1/2] (default: 2): " LIC_CHOICE LICENSE_KEY="" if [[ "${LIC_CHOICE:-2}" == "1" ]]; then read -rp " License Key (FUYO-XXXX-XXXX-XXXX-XXXX): " LICENSE_KEY [[ -n "$LICENSE_KEY" ]] || err "No key entered." fi echo "" read -rp " Admin Email for Panel Login: " ADMIN_EMAIL [[ -n "$ADMIN_EMAIL" ]] || err "Admin email required." read -rsp " Admin Password: " ADMIN_PASS echo "" [[ ${#ADMIN_PASS} -ge 8 ]] || err "Password must be at least 8 characters." echo "" log "Configuration complete. Starting installation..." echo "" read -rp "Press ENTER to start or Ctrl+C to abort..." # ─── System Packages ─── step "1/10 System Packages" export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq \ curl wget gnupg2 lsb-release ca-certificates apt-transport-https \ git unzip zip tar xz-utils acl \ mariadb-server mariadb-client \ nginx apache2 apache2-suexec-pristine libapache2-mod-fcgid \ postfix postfix-mysql \ dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-sieve dovecot-managesieved \ bind9 bind9-utils \ proftpd-basic \ certbot python3-certbot-dns-rfc2136 \ fail2ban opendkim opendkim-tools opendmarc rspamd \ python3-pymysql \ cron logrotate \ > /dev/null 2>&1 # Redis/Valkey if command -v valkey-server &>/dev/null || systemctl is-active --quiet valkey 2>/dev/null; then log "Valkey already present" else apt-get install -y -qq redis-server > /dev/null 2>&1 || true fi log "System packages installed" # ─── PHP (Sury) ─── step "2/10 PHP Versions" if [[ ! -f /usr/share/keyrings/debsuryorg-archive-keyring.gpg ]]; then curl -sSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb dpkg -i /tmp/debsuryorg-archive-keyring.deb > /dev/null echo "deb [signed-by=/usr/share/keyrings/debsuryorg-archive-keyring.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" \ > /etc/apt/sources.list.d/sury-php.list apt-get update -qq fi for V in $PHP_VERSIONS; do log "Installing PHP ${V}..." apt-get install -y -qq \ php${V}-fpm php${V}-cli php${V}-common php${V}-opcache php${V}-sodium \ php${V}-mysql php${V}-xml php${V}-mbstring php${V}-curl php${V}-zip \ php${V}-gd php${V}-intl php${V}-bcmath php${V}-bz2 \ php${V}-soap php${V}-imap php${V}-xsl \ php${V}-sqlite3 php${V}-pgsql \ php${V}-gmp php${V}-redis php${V}-imagick \ php${V}-apcu \ > /dev/null 2>&1 || warn "PHP ${V}: some packages unavailable" done log "PHP installed: ${PHP_VERSIONS}" # ─── Download Panel ─── step "3/10 Download FuyoNet Panel" mkdir -p "$AGENT_PATH" TARBALL="/tmp/fuyonet-panel-${VERSION}.tar.gz" log "Downloading v${VERSION}..." curl -sL "${DOWNLOAD_URL}/fuyonet-panel-${VERSION}.tar.gz" -o "$TARBALL" || \ err "Download failed. Check network and try again." [[ -f "$TARBALL" && $(stat -c%s "$TARBALL") -gt 10000 ]] || err "Download incomplete." log "Extracting..." tar xzf "$TARBALL" -C "$AGENT_PATH" rm -f "$TARBALL" log "Panel v${VERSION} installed to ${AGENT_PATH}" # ─── System Users + Groups ─── step "4/10 System Users" groupadd -f "$WEB_GROUP" groupadd -f "$SERVER_GROUP" if ! id "$AGENT_USER" &>/dev/null; then useradd -r -g "$AGENT_GROUP" -G "$SERVER_GROUP" -d "$AGENT_PATH" -s /usr/sbin/nologin "$AGENT_USER" fi usermod -aG "$WEB_GROUP" www-data 2>/dev/null || true usermod -aG opendkim postfix 2>/dev/null || true usermod -aG opendmarc postfix 2>/dev/null || true log "Users configured" # ─── Database ─── step "5/10 Database" systemctl enable --now mariadb > /dev/null 2>&1 DB_PASS=$(head -c 24 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 32) API_TOKEN=$(head -c 48 /dev/urandom | xxd -p -c 96 | head -c 64) mysql -e "CREATE DATABASE IF NOT EXISTS fuyonet_panel CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" mysql -e "CREATE USER IF NOT EXISTS 'fuyonet_panel'@'localhost' IDENTIFIED BY '${DB_PASS}';" mysql -e "GRANT ALL PRIVILEGES ON fuyonet_panel.* TO 'fuyonet_panel'@'localhost';" mysql -e "GRANT SELECT ON mysql.user TO 'fuyonet_panel'@'localhost';" mysql -e "FLUSH PRIVILEGES;" log "Database ready" # ─── Panel Configuration ─── step "6/10 Panel Configuration" cd "$AGENT_PATH" # Generate installation_id INSTALLATION_ID=$(python3 -c "import uuid; print(uuid.uuid4())") mkdir -p storage/app/license echo "$INSTALLATION_ID" > storage/app/license/installation.id chmod 600 storage/app/license/installation.id # .env cat > .env << ENVEOF APP_NAME=FuyoNet APP_ENV=production APP_KEY= APP_DEBUG=false APP_URL=https://${SERVER_IP}:8443 DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=fuyonet_panel DB_USERNAME=fuyonet_panel DB_PASSWORD=${DB_PASS} AGENT_API_TOKEN=${API_TOKEN} AGENT_ALLOWED_IPS=127.0.0.1 AGENT_DEFAULT_IP=${SERVER_IP} AGENT_HOSTNAME=$(hostname -f) QUEUE_CONNECTION=database CACHE_STORE=database SESSION_DRIVER=database PHP_VERSIONS="${PHP_VERSIONS}" LICENSE_API_URL=${LICENSE_API} LICENSE_KEY=${LICENSE_KEY} LICENSE_PUBLIC_KEY=LN1G+QexU4H1iQWmtA4J/DWN+GTs2QQ1uHeNCVmrg9w= LICENSE_INSTALLATION_ID=${INSTALLATION_ID} UPDATE_URL=https://updates.fuyonet.com/api ENVEOF # Composer install if [[ -f composer.json && -d vendor ]]; then log "Vendor already bundled" else composer install --no-dev --optimize-autoloader --quiet 2>/dev/null || true fi # Generate app key php artisan key:generate --force --quiet # Migrations php artisan migrate --force --quiet # Permissions chown -R "$AGENT_USER:$AGENT_GROUP" "$AGENT_PATH" chmod -R 750 "$AGENT_PATH" chmod -R 770 storage bootstrap/cache log "Panel configured" # ─── License Activation ─── step "7/10 License" cd "$AGENT_PATH" php artisan config:clear --quiet if [[ -n "$LICENSE_KEY" ]]; then log "Activating license..." php artisan license:activate 2>&1 | grep -v "^$" | sed 's/^/ /' else log "Requesting trial..." php artisan license:trial 2>&1 | grep -v "^$" | sed 's/^/ /' fi # ─── Services Configuration ─── step "8/10 Service Configuration" # This runs the core service configs (Nginx, Apache, Postfix, Dovecot, BIND, etc.) # Uses the bundled install.sh for service-level config if [[ -f "$AGENT_PATH/install.sh" ]]; then # Extract and run only the service configuration parts # For now, use artisan to set up basic services log "Configuring services..." fi # FuyoLoader EXT_DIR="lib/php/extensions/no-debug-non-zts-20230831" if [[ -f "$AGENT_PATH/$EXT_DIR/fuyo_loader.so" ]]; then log "FuyoLoader extension ready" fi # Systemd services for svc in fuyonet-panel-fpm fuyonet-panel-nginx fuyonet-panel-daemon fuyonet-panel-worker fuyonet-panel-ratelimit; do if [[ -f "$AGENT_PATH/resources/systemd/${svc}.service" ]]; then cp "$AGENT_PATH/resources/systemd/${svc}.service" /etc/systemd/system/ fi done systemctl daemon-reload # Start panel services systemctl enable --now fuyonet-panel-daemon 2>/dev/null || true systemctl enable --now fuyonet-panel-fpm 2>/dev/null || true systemctl enable --now fuyonet-panel-nginx 2>/dev/null || true systemctl enable --now fuyonet-panel-worker 2>/dev/null || true # Start system services for svc in nginx apache2 mariadb postfix dovecot named proftpd fail2ban opendkim opendmarc rspamd; do systemctl enable --now "$svc" 2>/dev/null || true done for v in $PHP_VERSIONS; do systemctl enable --now "php${v}-fpm" 2>/dev/null || true done log "Services started" # ─── Admin User ─── step "9/10 Admin User" cd "$AGENT_PATH" php artisan tinker --execute=" \$u = App\Models\User::updateOrCreate( ['email' => '${ADMIN_EMAIL}'], ['name' => 'Admin', 'password' => bcrypt('${ADMIN_PASS}'), 'role' => 'admin'] ); echo 'Admin: ' . \$u->email; " 2>/dev/null log "Admin user created: ${ADMIN_EMAIL}" # ─── Cron + Final ─── step "10/10 Finalize" # Scheduler cron echo "* * * * * ${AGENT_USER} cd ${AGENT_PATH} && php artisan schedule:run >> /dev/null 2>&1" \ > /etc/cron.d/fuyonet-panel chmod 644 /etc/cron.d/fuyonet-panel # Cache cd "$AGENT_PATH" php artisan config:cache --quiet 2>/dev/null || true php artisan route:cache --quiet 2>/dev/null || true php artisan view:cache --quiet 2>/dev/null || true # Save credentials cat > /root/.fuyonet-credentials << CREDEOF # FuyoNet Hosting Panel Credentials # Generated: $(date) PANEL_URL=https://${SERVER_IP}:8443/admin API_URL=https://${SERVER_IP}:8443/api API_TOKEN=${API_TOKEN} DB_NAME=fuyonet_panel DB_USER=fuyonet_panel DB_PASS=${DB_PASS} SERVER_IP=${SERVER_IP} CREDEOF chmod 600 /root/.fuyonet-credentials # ─── Done ─── echo "" echo -e "${GREEN}╔═══════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║${NC} FuyoNet Hosting Panel installed successfully! ${GREEN}║${NC}" echo -e "${GREEN}╚═══════════════════════════════════════════════════════╝${NC}" echo "" echo " Panel: https://${SERVER_IP}:8443/admin" echo " Login: ${ADMIN_EMAIL}" echo " API: https://${SERVER_IP}:8443/api" echo " Token: ${API_TOKEN}" echo "" echo " Credentials saved: /root/.fuyonet-credentials" echo "" echo " Next steps:" echo " - Point your domain DNS to ${SERVER_IP}" echo " - Configure SSL: php artisan ssl:request yourdomain.com" echo " - Import from Plesk: Panel > Migration > Plesk Import" echo "" echo -e " Documentation: ${BLUE}https://docs.fuyonet.com${NC}" echo ""