#!/usr/bin/env bash # ============================================================================= # NixPanel Installer # Usage: curl -fsSL https://raw.githubusercontent.com/NixReaper/nixpanel-server/main/scripts/install.sh | bash # Or: bash install.sh [--admin-user NAME] [--admin-pass PASS] [--admin-email EMAIL] # ============================================================================= set -euo pipefail # ── Colours ─────────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m' info() { echo -e "${CYAN}[INFO]${RESET} $*"; } success() { echo -e "${GREEN}[OK]${RESET} $*"; } warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; } error() { echo -e "${RED}[ERROR]${RESET} $*" >&2; exit 1; } step() { echo -e "\n${BOLD}${CYAN}━━━ $* ━━━${RESET}"; } # ── Banner ──────────────────────────────────────────────────────────────────── echo -e "${BOLD}${CYAN}" cat <<'EOF' _ _ _ ____ _ | \ | (_)_ _| _ \ __ _ _ __ ___| | | \| | \ \/ / |_) / _` | '_ \ / _ \ | | |\ | |> <| __/ (_| | | | | __/ | |_| \_|_/_/\_\_| \__,_|_| |_|\___|_| Open-Source Web Hosting Control Panel EOF echo -e "${RESET}" echo -e " Version : ${BOLD}0.3.1${RESET}" echo -e " GitHub : ${BOLD}https://github.com/NixReaper/nixpanel-server${RESET}" echo -e " NixServer: ${BOLD}https://:2087${RESET}" echo -e " NixClient: ${BOLD}https://:2083${RESET}" echo "" # ── Root check ──────────────────────────────────────────────────────────────── [[ $EUID -ne 0 ]] && error "This installer must be run as root. Try: sudo bash install.sh" # ── OS detection ────────────────────────────────────────────────────────────── step "Detecting OS" detect_os() { if [[ -f /etc/os-release ]]; then . /etc/os-release OS_ID="${ID,,}" OS_VERSION_ID="${VERSION_ID:-}" OS_LIKE="${ID_LIKE:-}" else error "Cannot detect OS — /etc/os-release not found" fi case "$OS_ID" in ubuntu|debian) PKG_MGR="apt"; OS_FAMILY="debian" ;; almalinux|rocky|rhel|centos) PKG_MGR="dnf"; OS_FAMILY="rhel" ;; fedora) PKG_MGR="dnf"; OS_FAMILY="rhel" ;; *) # Try ID_LIKE fallback if [[ "$OS_LIKE" == *debian* ]]; then PKG_MGR="apt"; OS_FAMILY="debian" elif [[ "$OS_LIKE" == *rhel* ]] || [[ "$OS_LIKE" == *fedora* ]]; then PKG_MGR="dnf"; OS_FAMILY="rhel" else error "Unsupported OS: $OS_ID. Supported: Ubuntu, Debian, AlmaLinux, Rocky Linux, RHEL, CentOS." fi ;; esac info "Detected OS: ${PRETTY_NAME:-$OS_ID} (family: $OS_FAMILY)" } detect_os # ── Architecture check ──────────────────────────────────────────────────────── ARCH=$(uname -m) [[ "$ARCH" != "x86_64" && "$ARCH" != "aarch64" ]] && error "Unsupported architecture: $ARCH (x86_64 and aarch64 only)" # ── Argument parsing ────────────────────────────────────────────────────────── ADMIN_USER="admin" ADMIN_PASS="" ADMIN_EMAIL="" INSTALL_DIR="/opt/nixpanel" NIXPANEL_DB_USER="nixpanel" NIXPANEL_DB_NAME="nixpanel" NIXPANEL_DB_PASS="" while [[ $# -gt 0 ]]; do case $1 in --admin-user) ADMIN_USER="$2"; shift 2 ;; --admin-pass) ADMIN_PASS="$2"; shift 2 ;; --admin-email) ADMIN_EMAIL="$2"; shift 2 ;; --install-dir) INSTALL_DIR="$2"; shift 2 ;; *) warn "Unknown argument: $1"; shift ;; esac done # ── Generate secrets ────────────────────────────────────────────────────────── gen_secret() { openssl rand -hex 32; } if [[ -z "$ADMIN_PASS" ]]; then ADMIN_PASS=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | head -c 16) GENERATED_PASS=true else GENERATED_PASS=false fi if [[ -z "$ADMIN_EMAIL" ]]; then ADMIN_EMAIL="admin@$(hostname -f 2>/dev/null || echo localhost)" fi if [[ -z "$NIXPANEL_DB_PASS" ]]; then NIXPANEL_DB_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24) fi JWT_SECRET=$(gen_secret) JWT_REFRESH_SECRET=$(gen_secret) # ── Confirm ─────────────────────────────────────────────────────────────────── echo -e "${BOLD}Installation summary:${RESET}" echo " Install directory : $INSTALL_DIR" echo " Admin username : $ADMIN_USER" echo " Admin email : $ADMIN_EMAIL" if [[ "$GENERATED_PASS" == "true" ]]; then echo " Admin password : " else echo " Admin password : $ADMIN_PASS" fi echo "" read -r -p "Continue? [Y/n] " CONFIRM CONFIRM="${CONFIRM:-Y}" [[ "$CONFIRM" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; } # ── Logging ─────────────────────────────────────────────────────────────────── LOG_FILE="/var/log/nixpanel-install.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "==================================================" echo " NixPanel Install Log — $(date -u '+%Y-%m-%d %H:%M:%S UTC')" echo " OS: ${PRETTY_NAME:-$OS_ID} Arch: $(uname -m)" echo "==================================================" # ── Update package index ────────────────────────────────────────────────────── step "Updating package index" if [[ "$PKG_MGR" == "apt" ]]; then apt-get update -qq elif [[ "$PKG_MGR" == "dnf" ]]; then dnf makecache -q fi success "Package index updated" # ── Install base dependencies ───────────────────────────────────────────────── step "Installing base dependencies" APT_DEPS=(curl wget gnupg2 ca-certificates lsb-release software-properties-common git build-essential openssl ufw fail2ban) DNF_DEPS=(curl wget gnupg2 ca-certificates git openssl firewalld fail2ban) if [[ "$PKG_MGR" == "apt" ]]; then apt-get install -y -qq "${APT_DEPS[@]}" else dnf install -y -q "${DNF_DEPS[@]}" fi success "Base dependencies installed" # ── Install Node.js 22 (LTS) ────────────────────────────────────────────────── step "Installing Node.js 22 LTS" if command -v node &>/dev/null; then NODE_VER=$(node --version | cut -c2- | cut -d. -f1) if [[ "$NODE_VER" -ge 20 ]]; then info "Node.js $(node --version) already installed — skipping" else warn "Node.js $NODE_VER found — upgrading to v22" install_node=true fi else install_node=true fi if [[ "${install_node:-false}" == "true" ]] || ! command -v node &>/dev/null; then if [[ "$PKG_MGR" == "apt" ]]; then curl -fsSL https://deb.nodesource.com/setup_22.x | bash - >/dev/null 2>&1 apt-get install -y -qq nodejs else curl -fsSL https://rpm.nodesource.com/setup_22.x | bash - >/dev/null 2>&1 dnf install -y -q nodejs fi fi success "Node.js $(node --version) ready" success "npm $(npm --version) ready" # ── Install MariaDB ─────────────────────────────────────────────────────────── step "Installing MariaDB" if command -v mariadb &>/dev/null || command -v mysqld &>/dev/null; then info "MariaDB/MySQL already installed — skipping" else if [[ "$PKG_MGR" == "apt" ]]; then apt-get install -y -qq mariadb-server mariadb-client else dnf install -y -q mariadb-server mariadb fi fi systemctl enable --now mariadb # Secure MariaDB and create DB/user info "Configuring MariaDB..." mariadb -u root </dev/null; then info "Nginx already installed — skipping" else if [[ "$PKG_MGR" == "apt" ]]; then apt-get install -y -qq nginx else dnf install -y -q nginx fi fi systemctl enable --now nginx success "Nginx ready" # ── Install PHP 8.3 + FPM ───────────────────────────────────────────────────── step "Installing PHP 8.3 + FPM" if [[ "$PKG_MGR" == "apt" ]]; then if ! apt-cache show php8.3 &>/dev/null 2>&1; then add-apt-repository -y ppa:ondrej/php >/dev/null 2>&1 apt-get update -qq fi apt-get install -y -qq php8.3-fpm php8.3-cli php8.3-mysql php8.3-curl \ php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip php8.3-bcmath \ php8.3-intl php8.3-soap php8.3-imagick php8.3-redis php8.3-opcache else dnf install -y -q php8.3 php8.3-fpm php8.3-mysqlnd php8.3-curl \ php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip php8.3-bcmath \ php8.3-intl php8.3-soap php8.3-opcache 2>/dev/null || \ dnf install -y -q php php-fpm php-mysqlnd php-curl php-gd php-mbstring \ php-xml php-zip php-bcmath php-intl php-soap php-opcache fi systemctl enable --now php8.3-fpm 2>/dev/null || systemctl enable --now php-fpm success "PHP 8.3 + FPM ready" # ── Install Postfix + Dovecot ───────────────────────────────────────────────── step "Installing Postfix + Dovecot" if [[ "$PKG_MGR" == "apt" ]]; then DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ postfix postfix-mysql dovecot-core dovecot-imapd dovecot-pop3d \ dovecot-lmtpd dovecot-mysql spamassassin else dnf install -y -q postfix dovecot dovecot-mysql fi systemctl enable postfix dovecot success "Postfix + Dovecot installed" # ── Install vsftpd ──────────────────────────────────────────────────────────── step "Installing vsftpd" if [[ "$PKG_MGR" == "apt" ]]; then apt-get install -y -qq vsftpd else dnf install -y -q vsftpd fi systemctl enable vsftpd success "vsftpd installed" # ── Install BIND9 ───────────────────────────────────────────────────────────── step "Installing BIND9" if [[ "$PKG_MGR" == "apt" ]]; then apt-get install -y -qq bind9 bind9utils bind9-doc else dnf install -y -q bind bind-utils fi BIND_SERVICE="named" [[ "$PKG_MGR" == "apt" ]] && BIND_SERVICE="named" || true systemctl enable --now "$BIND_SERVICE" 2>/dev/null || systemctl enable --now bind9 2>/dev/null || true BIND_ZONES_DIR="/etc/bind/zones" mkdir -p "$BIND_ZONES_DIR" chown root:bind "$BIND_ZONES_DIR" 2>/dev/null || chown root:named "$BIND_ZONES_DIR" 2>/dev/null || true success "BIND9 installed" # ── Install Certbot ─────────────────────────────────────────────────────────── step "Installing Certbot" if [[ "$PKG_MGR" == "apt" ]]; then apt-get install -y -qq certbot python3-certbot-nginx else dnf install -y -q certbot python3-certbot-nginx 2>/dev/null || \ dnf install -y -q --enablerepo=epel certbot python3-certbot-nginx fi mkdir -p /var/www/letsencrypt/.well-known/acme-challenge success "Certbot installed" # ── Clone / update NixPanel ─────────────────────────────────────────────────── step "Installing NixPanel" if [[ -d "$INSTALL_DIR/.git" ]]; then info "Existing installation found — pulling latest..." git -C "$INSTALL_DIR" pull --ff-only else info "Cloning repository into $INSTALL_DIR..." git clone https://github.com/NixReaper/nixpanel-server.git "$INSTALL_DIR" fi success "NixPanel source ready at $INSTALL_DIR" # ── Write .env ──────────────────────────────────────────────────────────────── step "Configuring environment" SERVER_IP=$(hostname -I | awk '{print $1}') cat > "$INSTALL_DIR/.env" < /etc/systemd/system/nixpanel.service </dev/null || npx tsc --noEmit false 2>/dev/null || true systemctl daemon-reload systemctl enable nixpanel systemctl restart nixpanel success "nixpanel.service created and started" # ── Nginx reverse proxy ─────────────────────────────────────────────────────── step "Configuring Nginx reverse proxy" # Ensure sites-available/enabled dirs exist mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled # Main nginx.conf include (idempotent) if ! 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 fi cat > /etc/nginx/sites-available/nixpanel.conf </dev/null ufw allow 22/tcp comment "SSH" >/dev/null ufw allow 80/tcp comment "HTTP" >/dev/null ufw allow 443/tcp comment "HTTPS" >/dev/null ufw allow 2087/tcp comment "NixServer" >/dev/null ufw allow 2083/tcp comment "NixClient" >/dev/null ufw allow 21/tcp comment "FTP" >/dev/null ufw allow 25/tcp comment "SMTP" >/dev/null ufw allow 143/tcp comment "IMAP" >/dev/null ufw allow 993/tcp comment "IMAPS" >/dev/null ufw allow 110/tcp comment "POP3" >/dev/null ufw allow 995/tcp comment "POP3S" >/dev/null ufw allow 587/tcp comment "SMTP submission" >/dev/null ufw allow 465/tcp comment "SMTPS" >/dev/null ufw allow 53 comment "DNS" >/dev/null success "UFW rules applied" else # firewalld systemctl enable --now firewalld for PORT in 22 80 443 2087 2083 21 25 143 993 110 995 587 465 53; do firewall-cmd --permanent --add-port=${PORT}/tcp >/dev/null 2>&1 || true done firewall-cmd --permanent --add-port=53/udp >/dev/null 2>&1 || true firewall-cmd --reload >/dev/null success "firewalld rules applied" fi # ── Fail2ban ────────────────────────────────────────────────────────────────── step "Configuring Fail2ban" cat > /etc/fail2ban/jail.d/nixpanel.conf < /etc/vsftpd.conf </dev/null || true success "vsftpd configured" # ── Wait for service to start ───────────────────────────────────────────────── step "Waiting for NixPanel to start" MAX_WAIT=30 WAITED=0 until curl -sf http://127.0.0.1:4000/api/health >/dev/null 2>&1; do sleep 2 WAITED=$((WAITED + 2)) [[ $WAITED -ge $MAX_WAIT ]] && { warn "Service health check timed out — check: journalctl -u nixpanel -n 50"; break; } done if curl -sf http://127.0.0.1:4000/api/health >/dev/null 2>&1; then success "NixPanel API is responding" else warn "NixPanel may still be starting. Check status with: systemctl status nixpanel" fi # ── Print completion summary ────────────────────────────────────────────────── echo "" echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════════════════╗${RESET}" echo -e "${BOLD}${GREEN}║ NixPanel Installation Complete! ║${RESET}" echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════╝${RESET}" echo "" echo -e " ${BOLD}NixServer (Admin Panel)${RESET}" echo -e " URL : ${CYAN}http://${SERVER_IP}:2087${RESET}" echo -e " Username : ${BOLD}${ADMIN_USER}${RESET}" echo -e " Password : ${BOLD}${ADMIN_PASS}${RESET}" echo "" echo -e " ${BOLD}NixClient (User Panel)${RESET}" echo -e " URL : ${CYAN}http://${SERVER_IP}:2083${RESET}" echo "" echo -e " ${BOLD}Database${RESET}" echo -e " DB Name : ${NIXPANEL_DB_NAME}" echo -e " DB User : ${NIXPANEL_DB_USER}" echo -e " DB Pass : ${NIXPANEL_DB_PASS}" echo "" echo -e " ${BOLD}Files${RESET}" echo -e " Install : ${INSTALL_DIR}" echo -e " Config : ${INSTALL_DIR}/.env" echo -e " Install log: ${LOG_FILE}" echo -e " Service : systemctl status nixpanel" echo -e " Logs : journalctl -u nixpanel -f" echo "" if [[ "$GENERATED_PASS" == "true" ]]; then echo -e " ${YELLOW}${BOLD}⚠ Save your admin password — it will not be shown again!${RESET}" echo "" fi echo -e " ${BOLD}Useful commands:${RESET}" echo -e " systemctl restart nixpanel # restart the panel" echo -e " systemctl status nixpanel # check status" echo -e " journalctl -u nixpanel -f # live logs" echo "" # Save credentials to a file root can read CRED_FILE="/root/.nixpanel_credentials" cat > "$CRED_FILE" <