Nginx listens publicly on 2087/2083 and reverse-proxies to 127.0.0.1:4000. Previously both Nginx and Node.js were trying to bind port 2087 causing the nixpanel service to crash-loop. HOST changed to 127.0.0.1 so the API is not directly reachable from outside. Bumps version to 0.3.1.
708 lines
26 KiB
Bash
Executable File
708 lines
26 KiB
Bash
Executable File
#!/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://<your-ip>:2087${RESET}"
|
|
echo -e " NixClient: ${BOLD}https://<your-ip>: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 : <auto-generated, shown at end>"
|
|
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 <<SQL
|
|
ALTER USER IF EXISTS 'root'@'localhost' IDENTIFIED VIA unix_socket;
|
|
DELETE FROM mysql.user WHERE User='';
|
|
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost','127.0.0.1','::1');
|
|
DROP DATABASE IF EXISTS test;
|
|
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
|
|
|
|
CREATE DATABASE IF NOT EXISTS \`${NIXPANEL_DB_NAME}\`
|
|
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
|
|
CREATE OR REPLACE USER '${NIXPANEL_DB_USER}'@'localhost'
|
|
IDENTIFIED BY '${NIXPANEL_DB_PASS}';
|
|
|
|
GRANT ALL PRIVILEGES ON \`${NIXPANEL_DB_NAME}\`.* TO '${NIXPANEL_DB_USER}'@'localhost';
|
|
FLUSH PRIVILEGES;
|
|
SQL
|
|
|
|
success "MariaDB configured — database '${NIXPANEL_DB_NAME}' created"
|
|
|
|
# ── Install Nginx ─────────────────────────────────────────────────────────────
|
|
step "Installing Nginx"
|
|
|
|
if command -v nginx &>/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" <<ENV
|
|
# Generated by NixPanel installer on $(date -u '+%Y-%m-%d %H:%M:%S UTC')
|
|
|
|
# Database
|
|
DATABASE_URL="mysql://${NIXPANEL_DB_USER}:${NIXPANEL_DB_PASS}@localhost:3306/${NIXPANEL_DB_NAME}"
|
|
|
|
# JWT
|
|
JWT_SECRET="${JWT_SECRET}"
|
|
JWT_EXPIRES_IN="15m"
|
|
JWT_REFRESH_SECRET="${JWT_REFRESH_SECRET}"
|
|
JWT_REFRESH_EXPIRES_IN="7d"
|
|
|
|
# Server (internal ports — Nginx proxies public 2087/2083 → these)
|
|
NODE_ENV="production"
|
|
NIXSERVER_PORT=4000
|
|
NIXCLIENT_PORT=4001
|
|
HOST="127.0.0.1"
|
|
|
|
# Admin credentials (used during initial seed only)
|
|
ADMIN_USERNAME="${ADMIN_USER}"
|
|
ADMIN_PASSWORD="${ADMIN_PASS}"
|
|
ADMIN_EMAIL="${ADMIN_EMAIL}"
|
|
|
|
# Paths
|
|
HOME_DIR="/home"
|
|
NGINX_SITES_DIR="/etc/nginx/sites-available"
|
|
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
|
|
APACHE_SITES_DIR="/etc/apache2/sites-available"
|
|
APACHE_ENABLED_DIR="/etc/apache2/sites-enabled"
|
|
BIND_ZONES_DIR="${BIND_ZONES_DIR}"
|
|
CERTBOT_WEBROOT="/var/www/letsencrypt"
|
|
|
|
# Rate limiting
|
|
RATE_LIMIT_MAX=100
|
|
RATE_LIMIT_WINDOW_MS=60000
|
|
|
|
# Features
|
|
ENABLE_TERMINAL=true
|
|
WEBSOCKET_STATS_INTERVAL=5000
|
|
ENV
|
|
|
|
chmod 600 "$INSTALL_DIR/.env"
|
|
success ".env written"
|
|
|
|
# ── npm install ───────────────────────────────────────────────────────────────
|
|
step "Installing Node.js dependencies"
|
|
|
|
cd "$INSTALL_DIR"
|
|
npm install --workspaces --include-workspace-root --silent
|
|
success "npm dependencies installed"
|
|
|
|
# ── Prisma generate + migrate ─────────────────────────────────────────────────
|
|
step "Running database migrations"
|
|
|
|
cd "$INSTALL_DIR/server"
|
|
npx prisma generate
|
|
DATABASE_URL="mysql://${NIXPANEL_DB_USER}:${NIXPANEL_DB_PASS}@localhost:3306/${NIXPANEL_DB_NAME}" \
|
|
npx prisma db push
|
|
success "Database schema applied"
|
|
|
|
# ── Seed default data ─────────────────────────────────────────────────────────
|
|
step "Seeding initial data"
|
|
|
|
DATABASE_URL="mysql://${NIXPANEL_DB_USER}:${NIXPANEL_DB_PASS}@localhost:3306/${NIXPANEL_DB_NAME}" \
|
|
ADMIN_USERNAME="${ADMIN_USER}" \
|
|
ADMIN_PASSWORD="${ADMIN_PASS}" \
|
|
ADMIN_EMAIL="${ADMIN_EMAIL}" \
|
|
npx tsx src/db/seed.ts
|
|
success "Default admin, packages, and settings seeded"
|
|
|
|
# ── Build frontends ───────────────────────────────────────────────────────────
|
|
step "Building NixServer + NixClient frontends"
|
|
|
|
cd "$INSTALL_DIR"
|
|
npm run build --workspace=nixserver --silent
|
|
npm run build --workspace=nixclient --silent
|
|
success "Frontends built"
|
|
|
|
# ── systemd service ───────────────────────────────────────────────────────────
|
|
step "Creating systemd service"
|
|
|
|
cat > /etc/systemd/system/nixpanel.service <<UNIT
|
|
[Unit]
|
|
Description=NixPanel Web Hosting Control Panel
|
|
Documentation=https://github.com/NixReaper/nixpanel-server
|
|
After=network.target mariadb.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=root
|
|
WorkingDirectory=${INSTALL_DIR}/server
|
|
EnvironmentFile=${INSTALL_DIR}/.env
|
|
ExecStart=$(which node) --enable-source-maps dist/index.js
|
|
Restart=always
|
|
RestartSec=5
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=nixpanel
|
|
LimitNOFILE=65536
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
UNIT
|
|
|
|
# Build server for production
|
|
cd "$INSTALL_DIR/server"
|
|
npm run build --silent 2>/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 <<NGINX
|
|
# NixPanel — generated by installer
|
|
# Node.js API listens on 127.0.0.1:4000 (internal only)
|
|
# Nginx proxies public ports 2087/2083 → internal API
|
|
|
|
upstream nixpanel_api {
|
|
server 127.0.0.1:4000;
|
|
}
|
|
|
|
# NixServer admin panel on port 2087
|
|
server {
|
|
listen 2087;
|
|
server_name _;
|
|
|
|
# Serve pre-built NixServer React app
|
|
root ${INSTALL_DIR}/nixserver/dist;
|
|
index index.html;
|
|
|
|
location / {
|
|
try_files \$uri \$uri/ /index.html;
|
|
}
|
|
|
|
location /api/ {
|
|
proxy_pass http://nixpanel_api;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade \$http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host \$host;
|
|
proxy_set_header X-Real-IP \$remote_addr;
|
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
proxy_read_timeout 300s;
|
|
}
|
|
|
|
location /ws {
|
|
proxy_pass http://nixpanel_api;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade \$http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
}
|
|
}
|
|
|
|
# NixClient user panel on port 2083
|
|
server {
|
|
listen 2083;
|
|
server_name _;
|
|
|
|
root ${INSTALL_DIR}/nixclient/dist;
|
|
index index.html;
|
|
|
|
location / {
|
|
try_files \$uri \$uri/ /index.html;
|
|
}
|
|
|
|
location /api/ {
|
|
proxy_pass http://nixpanel_api;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade \$http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host \$host;
|
|
proxy_set_header X-Real-IP \$remote_addr;
|
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
proxy_read_timeout 300s;
|
|
}
|
|
}
|
|
|
|
# HTTP — Let's Encrypt webroot + redirect
|
|
server {
|
|
listen 80;
|
|
server_name _;
|
|
|
|
location /.well-known/acme-challenge/ {
|
|
root /var/www/letsencrypt;
|
|
}
|
|
|
|
location / {
|
|
return 301 https://\$host\$request_uri;
|
|
}
|
|
}
|
|
NGINX
|
|
|
|
ln -sf /etc/nginx/sites-available/nixpanel.conf /etc/nginx/sites-enabled/nixpanel.conf
|
|
|
|
nginx -t && systemctl reload nginx
|
|
success "Nginx configured"
|
|
|
|
# ── Firewall ──────────────────────────────────────────────────────────────────
|
|
step "Configuring firewall"
|
|
|
|
if [[ "$PKG_MGR" == "apt" ]]; then
|
|
# UFW
|
|
ufw --force enable >/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 <<F2B
|
|
[sshd]
|
|
enabled = true
|
|
maxretry = 5
|
|
bantime = 3600
|
|
findtime = 600
|
|
|
|
[nginx-http-auth]
|
|
enabled = true
|
|
maxretry = 10
|
|
bantime = 600
|
|
|
|
[postfix]
|
|
enabled = true
|
|
|
|
[dovecot]
|
|
enabled = true
|
|
F2B
|
|
|
|
systemctl enable --now fail2ban
|
|
success "Fail2ban configured"
|
|
|
|
# ── vsftpd configuration ──────────────────────────────────────────────────────
|
|
step "Configuring vsftpd"
|
|
|
|
cat > /etc/vsftpd.conf <<VSFTPD
|
|
listen=YES
|
|
listen_ipv6=NO
|
|
anonymous_enable=NO
|
|
local_enable=YES
|
|
write_enable=YES
|
|
local_umask=022
|
|
dirmessage_enable=YES
|
|
use_localtime=YES
|
|
xferlog_enable=YES
|
|
connect_from_port_20=YES
|
|
chroot_local_user=YES
|
|
allow_writeable_chroot=YES
|
|
secure_chroot_dir=/var/run/vsftpd/empty
|
|
pam_service_name=vsftpd
|
|
ssl_enable=NO
|
|
utf8_filesystem=YES
|
|
pasv_enable=YES
|
|
pasv_min_port=40000
|
|
pasv_max_port=50000
|
|
VSFTPD
|
|
|
|
systemctl restart vsftpd 2>/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" <<CREDS
|
|
NixPanel Credentials — $(date -u '+%Y-%m-%d %H:%M:%S UTC')
|
|
==========================================================
|
|
NixServer URL : http://${SERVER_IP}:2087
|
|
NixClient URL : http://${SERVER_IP}:2083
|
|
Admin User : ${ADMIN_USER}
|
|
Admin Pass : ${ADMIN_PASS}
|
|
Admin Email: ${ADMIN_EMAIL}
|
|
DB Name : ${NIXPANEL_DB_NAME}
|
|
DB User : ${NIXPANEL_DB_USER}
|
|
DB Pass : ${NIXPANEL_DB_PASS}
|
|
Install Dir: ${INSTALL_DIR}
|
|
CREDS
|
|
chmod 600 "$CRED_FILE"
|
|
|
|
echo -e " Credentials saved to: ${BOLD}${CRED_FILE}${RESET}"
|
|
echo ""
|