#!/usr/bin/env bash # ────────────────────────────────────────────────────────────────────────────── # NixPanel Installer · Ubuntu 24.04 LTS · x86_64 # Installs the NixPanel control panel (WHM + cPanel) on a customer server. # License validation runs against https://license.nixpanel.io # ────────────────────────────────────────────────────────────────────────────── set -euo pipefail # ── Self-update ─────────────────────────────────────────────────────────────── # Always re-exec from the latest version on GitHub so stale local copies # never silently run outdated logic. SELF_URL="https://raw.githubusercontent.com/NixReaper/nixpanel/main/install.sh" if [[ -z "${NIXPANEL_SELF_UPDATED:-}" ]]; then echo "Fetching latest installer from GitHub…" LATEST_SCRIPT=$(curl -fsSL "$SELF_URL") \ || { echo "[WARN] Could not reach GitHub — running local copy"; LATEST_SCRIPT=""; } if [[ -n "$LATEST_SCRIPT" ]]; then export NIXPANEL_SELF_UPDATED=1 exec bash <(printf '%s' "$LATEST_SCRIPT") "$@" fi fi # ── Colours ─────────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m' YELLOW='\033[1;33m'; 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}[ERR ]${RESET} $*" >&2; exit 1; } step() { echo -e "\n${BOLD}${CYAN}━━━ $* ━━━${RESET}"; } # ── Preflight ───────────────────────────────────────────────────────────────── [[ $EUID -ne 0 ]] && error "Must be run as root → sudo bash install.sh" OS_ID=$(. /etc/os-release && echo "$ID") OS_VER=$(. /etc/os-release && echo "$VERSION_ID") ARCH=$(uname -m) [[ "$OS_ID" != "ubuntu" ]] && error "Requires Ubuntu 24.04 LTS (detected: $OS_ID)" [[ "$OS_VER" != "24.04" ]] && error "Requires Ubuntu 24.04 LTS (detected: Ubuntu $OS_VER)" [[ "$ARCH" != "x86_64" ]] && error "Requires x86_64 (detected: $ARCH)" # ── Paths ───────────────────────────────────────────────────────────────────── INSTALL_DIR="/opt/nixpanel" BIN_DIR="$INSTALL_DIR/bin" LOG_DIR="/var/log/nixpanel" STEP_DIR="/var/lib/nixpanel/install" SRC_DIR="$INSTALL_DIR/src" GITHUB_REPO="NixReaper/nixpanel" LICENSING_URL="https://license.nixpanel.io" mkdir -p "$INSTALL_DIR" "$BIN_DIR" "$LOG_DIR" "$STEP_DIR" "$SRC_DIR" mark_done() { touch "$STEP_DIR/$1.done"; } step_done() { [[ -f "$STEP_DIR/$1.done" ]]; } # ── Banner ──────────────────────────────────────────────────────────────────── echo "" echo -e "${BOLD}${CYAN}" echo " ███╗ ██╗██╗██╗ ██╗██████╗ █████╗ ███╗ ██╗███████╗██╗ " echo " ████╗ ██║██║╚██╗██╔╝██╔══██╗██╔══██╗████╗ ██║██╔════╝██║ " echo " ██╔██╗ ██║██║ ╚███╔╝ ██████╔╝███████║██╔██╗ ██║█████╗ ██║ " echo " ██║╚██╗██║██║ ██╔██╗ ██╔═══╝ ██╔══██║██║╚██╗██║██╔══╝ ██║ " echo " ██║ ╚████║██║██╔╝ ██╗██║ ██║ ██║██║ ╚████║███████╗███████╗" echo " ╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝" echo -e "${RESET}" echo -e " Web Hosting Control Panel · Ubuntu 24.04 · x86_64" echo "" # ── 1. System update ────────────────────────────────────────────────────────── step "Updating system packages" if ! step_done "apt_update"; then export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get upgrade -y -qq mark_done "apt_update" fi success "System packages up to date" # ── 2. Free port 53 for PowerDNS ───────────────────────────────────────────── step "Freeing port 53 for PowerDNS" if ! step_done "resolved_stub"; then sed -i '/^#\?DNSStubListener=/d' /etc/systemd/resolved.conf echo "DNSStubListener=no" >> /etc/systemd/resolved.conf systemctl restart systemd-resolved 2>/dev/null || true mark_done "resolved_stub" fi success "Port 53 free" # ── 3. System packages ──────────────────────────────────────────────────────── step "Installing system packages" if ! step_done "packages"; then export DEBIAN_FRONTEND=noninteractive # Prereqs for add-apt-repository apt-get install -y -qq ca-certificates gnupg software-properties-common # ondrej/php PPA is required for PHP 8.2 on Ubuntu 24.04 if ! grep -rq "ondrej/php" /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/null; then info "Adding ondrej/php PPA (required for PHP 8.2 on Ubuntu 24.04)…" add-apt-repository -y ppa:ondrej/php apt-get update -qq success "ondrej/php PPA added" else info "ondrej/php PPA already present" fi apt-get install -y -qq \ build-essential pkg-config libssl-dev libmariadb-dev curl wget git unzip \ ca-certificates gnupg software-properties-common \ apache2 \ php8.3 php8.3-fpm php8.3-cli php8.3-mysql php8.3-curl php8.3-mbstring \ php8.3-xml php8.3-zip php8.3-gd php8.3-intl php8.3-bcmath \ php8.2 php8.2-fpm php8.2-cli php8.2-mysql php8.2-curl php8.2-mbstring \ php8.2-xml php8.2-zip php8.2-gd php8.2-intl php8.2-bcmath \ mariadb-server mariadb-client \ pdns-server pdns-backend-bind \ exim4 exim4-daemon-light \ dovecot-imapd dovecot-pop3d dovecot-lmtpd \ spamassassin spamc \ certbot python3-certbot-apache \ fail2ban \ openssl mark_done "packages" fi success "System packages installed" # ── 4. Rust toolchain ───────────────────────────────────────────────────────── step "Installing Rust toolchain" if ! step_done "rust"; then curl -fsSL https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal source "$HOME/.cargo/env" mark_done "rust" else source "$HOME/.cargo/env" 2>/dev/null || true fi CARGO=$(command -v cargo) || error "cargo not found — Rust install may have failed" success "Rust $(rustc --version) installed" # ── 5. Start services ───────────────────────────────────────────────────────── step "Starting service stack" if ! step_done "services_start"; then a2enmod rewrite ssl proxy proxy_http proxy_wstunnel proxy_fcgi headers expires deflate systemctl enable --now apache2 mariadb systemctl enable pdns && systemctl start pdns 2>/dev/null || true systemctl enable spamd && systemctl start spamd 2>/dev/null || true mark_done "services_start" fi success "Service stack running" # ── 6. MariaDB — panel database only ───────────────────────────────────────── step "Configuring MariaDB" if ! step_done "mariadb_setup"; then DB_PASS_PANEL=$(openssl rand -hex 20) mysql -u root < "$INSTALL_DIR/.db_pass_panel" chmod 600 "$INSTALL_DIR/.db_pass_panel" mark_done "mariadb_setup" else DB_PASS_PANEL=$(cat "$INSTALL_DIR/.db_pass_panel") fi success "Database ready" # ── 7. Download / update panel source ──────────────────────────────────────── step "Downloading NixPanel source" if ! step_done "source"; then LATEST_TAG=$(curl -fsSL "https://api.github.com/repos/${GITHUB_REPO}/releases/latest" \ | grep '"tag_name"' | cut -d'"' -f4 || true) [[ -z "$LATEST_TAG" ]] && LATEST_TAG="main" info "Fetching tag: $LATEST_TAG" if [[ -d "$SRC_DIR/.git" ]]; then git -C "$SRC_DIR" fetch --tags --quiet git -C "$SRC_DIR" checkout "$LATEST_TAG" --quiet else # Try with explicit branch first, then fallback to explicit 'main' branch git clone --depth 1 --branch "$LATEST_TAG" \ "https://github.com/${GITHUB_REPO}.git" "$SRC_DIR" 2>/dev/null \ || git clone --depth 1 --branch main \ "https://github.com/${GITHUB_REPO}.git" "$SRC_DIR" fi mark_done "source" fi success "Source ready" # ── 8. Build panel UIs (React apps) ─────────────────────────────────────────── step "Building NixServer & NixClient UIs" if ! step_done "build_uis"; then cd "$SRC_DIR" # Build NixServer (WHM admin panel) if [[ -f "nixserver/package.json" ]]; then info "Building NixServer…" cd "$SRC_DIR/nixserver" npm install --legacy-peer-deps --quiet 2>/dev/null || true npm run build --silent 2>/dev/null mkdir -p "$INSTALL_DIR/nixserver/dist" cp -r dist/* "$INSTALL_DIR/nixserver/dist/" 2>/dev/null || true success "NixServer built" fi # Build NixClient (cPanel user panel) if [[ -f "nixclient/package.json" ]]; then info "Building NixClient…" cd "$SRC_DIR/nixclient" npm install --legacy-peer-deps --quiet 2>/dev/null || true npm run build --silent 2>/dev/null mkdir -p "$INSTALL_DIR/nixclient/dist" cp -r dist/* "$INSTALL_DIR/nixclient/dist/" 2>/dev/null || true success "NixClient built" fi mark_done "build_uis" fi # ── 9. Build panel binary ───────────────────────────────────────────────────── step "Building nixpanel (Rust)" if ! step_done "build_panel"; then if [[ -f "$SRC_DIR/Cargo.toml" ]]; then cd "$SRC_DIR" $CARGO build --release --quiet [[ -f "target/release/nixpanel" ]] && \ cp target/release/nixpanel "$BIN_DIR/nixpanel" && chmod 755 "$BIN_DIR/nixpanel" mark_done "build_panel" success "Panel binary built" else warn "Panel Rust backend not yet present in repo — skipping build" warn "This is expected during early development. Re-run installer when source is ready." fi fi # ── 9. Secrets ──────────────────────────────────────────────────────────────── step "Generating secrets" if ! step_done "secrets"; then JWT_SECRET_PANEL=$(openssl rand -hex 64) echo "$JWT_SECRET_PANEL" > "$INSTALL_DIR/.jwt_panel" chmod 600 "$INSTALL_DIR/.jwt_panel" mark_done "secrets" else JWT_SECRET_PANEL=$(cat "$INSTALL_DIR/.jwt_panel") fi success "Secrets ready" # ── 10. Admin setup ─────────────────────────────────────────────────────────── step "Panel configuration" if ! step_done "admin_account"; then echo "" echo -e " ${BOLD}Create your NixPanel admin account${RESET}" read -rp " Admin username [admin]: " ADMIN_USER ADMIN_USER="${ADMIN_USER:-admin}" read -rsp " Admin password: " ADMIN_PASS; echo "" read -rsp " Confirm password: " ADMIN_PASS2; echo "" [[ "$ADMIN_PASS" != "$ADMIN_PASS2" ]] && error "Passwords do not match" read -rp " Admin email: " ADMIN_EMAIL read -rp " Server hostname/domain [$(hostname -f)]: " SERVER_HOST SERVER_HOST="${SERVER_HOST:-$(hostname -f)}" read -rp " NixPanel license key: " LICENSE_KEY echo "$SERVER_HOST" > "$INSTALL_DIR/.server_host" echo "$LICENSE_KEY" > "$INSTALL_DIR/.license_key" chmod 600 "$INSTALL_DIR/.server_host" "$INSTALL_DIR/.license_key" # Validate license key against central server info "Validating license key with ${LICENSING_URL}…" SERVER_IP=$(hostname -I | awk '{print $1}') HTTP_STATUS=$(curl -s -o /tmp/lic_resp.json -w "%{http_code}" \ -X POST "${LICENSING_URL}/api/v1/activate" \ -H "Content-Type: application/json" \ -d "{\"license_key\":\"${LICENSE_KEY}\",\"server_ip\":\"${SERVER_IP}\"}" \ 2>/dev/null || echo "000") if [[ "$HTTP_STATUS" == "200" ]]; then success "License activated" elif [[ "$HTTP_STATUS" == "000" ]]; then warn "Could not reach ${LICENSING_URL} — proceeding in grace period mode" else warn "License server returned HTTP ${HTTP_STATUS}:" cat /tmp/lic_resp.json 2>/dev/null || true echo "" warn "Continuing install — fix license key in ${INSTALL_DIR}/.license_key and restart nixpanel" fi mark_done "admin_account" else SERVER_HOST=$(cat "$INSTALL_DIR/.server_host" 2>/dev/null || hostname -f) fi success "Panel configuration done" # ── 11. Environment file ────────────────────────────────────────────────────── step "Writing environment file" cat > "$INSTALL_DIR/.env" < /etc/systemd/system/nixpanel.service <> /etc/apache2/ports.conf done cat > /etc/apache2/sites-available/nixpanel.conf <<'APACHEEOF' # ── NixServer — WHM Admin Panel (port 2087) ─────────────────────────────────── ProxyPreserveHost On ProxyPass /api/ http://127.0.0.1:4000/api/ ProxyPassReverse /api/ http://127.0.0.1:4000/api/ ProxyPass /ws/ ws://127.0.0.1:4000/ws/ ProxyPassReverse /ws/ ws://127.0.0.1:4000/ws/ DocumentRoot /opt/nixpanel/nixserver/dist Options -Indexes AllowOverride All Require all granted FallbackResource /index.html ErrorLog /var/log/nixpanel/nixserver-error.log CustomLog /var/log/nixpanel/nixserver-access.log combined # ── NixClient — cPanel User Panel (port 2083) ───────────────────────────────── ProxyPreserveHost On ProxyPass /api/ http://127.0.0.1:4000/api/ ProxyPassReverse /api/ http://127.0.0.1:4000/api/ DocumentRoot /opt/nixpanel/nixclient/dist Options -Indexes AllowOverride All Require all granted FallbackResource /index.html ErrorLog /var/log/nixpanel/nixclient-error.log CustomLog /var/log/nixpanel/nixclient-access.log combined APACHEEOF a2ensite nixpanel apache2ctl configtest && systemctl reload apache2 mark_done "apache_vhost" fi success "Apache vhosts configured" # ── 14. Fail2ban ────────────────────────────────────────────────────────────── step "Configuring Fail2ban" if ! step_done "fail2ban"; then cat > /etc/fail2ban/jail.d/nixpanel.conf <<'EOF' [nixpanel-auth] enabled = true port = 2083,2087 filter = nixpanel-auth logpath = /var/log/nixpanel/*.log maxretry = 10 bantime = 3600 findtime = 600 EOF systemctl enable --now fail2ban mark_done "fail2ban" fi success "Fail2ban configured" # ── 15. File permissions ────────────────────────────────────────────────────── step "Setting permissions" chown -R root:root "$INSTALL_DIR" chmod 600 "$INSTALL_DIR/.env" 2>/dev/null || true find "$INSTALL_DIR/bin" -type f -exec chmod 755 {} \; 2>/dev/null || true [[ -d "$INSTALL_DIR/nixserver/dist" ]] && \ find "$INSTALL_DIR/nixserver/dist" \( -type d -exec chmod 755 {} \; -o -type f -exec chmod 644 {} \; \) [[ -d "$INSTALL_DIR/nixclient/dist" ]] && \ find "$INSTALL_DIR/nixclient/dist" \( -type d -exec chmod 755 {} \; -o -type f -exec chmod 644 {} \; \) success "Permissions set" # ── 16. Service health check ────────────────────────────────────────────────── step "Verifying services" sleep 2 check_service() { if systemctl is-active --quiet "$1"; then success "$1 is running" else warn "$1 is NOT running — check: journalctl -u $1 -n 50" fi } check_service apache2 check_service mariadb check_service fail2ban if [[ -f "$BIN_DIR/nixpanel" ]]; then check_service nixpanel fi # ── Done ────────────────────────────────────────────────────────────────────── SERVER_IP=$(hostname -I | awk '{print $1}') echo "" echo -e "${BOLD}${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" echo -e "${BOLD}${GREEN} NixPanel Installation Complete!${RESET}" echo -e "${BOLD}${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" echo "" echo -e " ${BOLD}WHM (Admin panel):${RESET} http://${SERVER_IP}:2087" echo -e " ${BOLD}cPanel (User panel):${RESET} http://${SERVER_IP}:2083" echo -e " ${BOLD}Licensing server:${RESET} ${LICENSING_URL} (central)" echo "" echo -e " ${BOLD}Logs:${RESET} ${LOG_DIR}/" echo -e " ${BOLD}Config:${RESET} ${INSTALL_DIR}/.env" echo "" echo -e " To add SSL: certbot --apache" echo -e " To restart: systemctl restart nixpanel" echo ""