Files
nixpanel-server/scripts/install.sh
T
NixReaper aa9998cbd8 fix(installer): preserve secrets across re-runs
Re-running the installer after a failed build regenerated JWT_SECRET
and MARIADB_ROOT_PASS, overwriting .env with values that didn't match
the already-set MariaDB password. Now reads existing .env values and
only generates new ones on a fresh install.
2026-04-12 04:56:27 -05:00

767 lines
28 KiB
Bash
Executable File

#!/usr/bin/env bash
# =============================================================================
# NixPanel Installer — Ubuntu 24.04 LTS only
# Usage: bash install.sh [--admin-user NAME] [--admin-pass PASS] [--admin-email EMAIL]
# =============================================================================
set -euo pipefail
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'
_ _ _ ____ _
| \ | (_)_ _| _ \ __ _ _ __ ___| |
| \| | \ \/ / |_) / _` | '_ \ / _ \ |
| |\ | |> <| __/ (_| | | | | __/ |
|_| \_|_/_/\_\_| \__,_|_| |_|\___|_|
Web Hosting Control Panel
EOF
echo -e "${RESET}"
# ── Root check ────────────────────────────────────────────────────────────────
[[ $EUID -ne 0 ]] && error "Must be run as root. Try: sudo bash install.sh"
# ── OS check — Ubuntu 24.04 only ─────────────────────────────────────────────
step "Checking OS"
if [[ ! -f /etc/os-release ]]; then
error "Cannot detect OS — /etc/os-release not found"
fi
. /etc/os-release
if [[ "${ID:-}" != "ubuntu" ]]; then
error "NixPanel requires Ubuntu. Detected: ${PRETTY_NAME:-unknown}"
fi
UBUNTU_MAJOR=$(echo "${VERSION_ID:-0}" | cut -d. -f1)
if [[ "$UBUNTU_MAJOR" -lt 24 ]]; then
error "NixPanel requires Ubuntu 24.04 LTS. Detected: ${PRETTY_NAME:-unknown}"
fi
ARCH=$(uname -m)
[[ "$ARCH" != "x86_64" && "$ARCH" != "aarch64" ]] && error "Unsupported architecture: $ARCH (x86_64 and aarch64 only)"
success "Ubuntu ${VERSION_ID} on ${ARCH} — OK"
# ── Argument parsing ──────────────────────────────────────────────────────────
ADMIN_USER="admin"
ADMIN_PASS=""
ADMIN_EMAIL=""
INSTALL_DIR="/opt/nixpanel"
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; }
GENERATED_PASS=false
if [[ -z "$ADMIN_PASS" ]]; then
ADMIN_PASS=$(openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | head -c 16)
GENERATED_PASS=true
fi
if [[ -z "$ADMIN_EMAIL" ]]; then
ADMIN_EMAIL="admin@$(hostname -f 2>/dev/null || echo localhost)"
fi
# ── Preserve secrets across re-runs ──────────────────────────────────────────
# If .env already exists, reuse the same secrets so a failed-and-retried
# install doesn't leave MariaDB with a password that doesn't match .env.
_EXISTING_ENV="${INSTALL_DIR}/server/.env"
if [[ -f "$_EXISTING_ENV" ]]; then
_read_env() { grep -m1 "^$1=" "$_EXISTING_ENV" 2>/dev/null | cut -d= -f2- | tr -d '"'; }
MARIADB_ROOT_PASS=$(_read_env MARIADB_PASSWORD)
JWT_SECRET=$(_read_env JWT_SECRET)
JWT_REFRESH_SECRET=$(_read_env JWT_REFRESH_SECRET)
fi
# Fill in any values that weren't in the existing .env (or first install)
MARIADB_ROOT_PASS=${MARIADB_ROOT_PASS:-$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)}
JWT_SECRET=${JWT_SECRET:-$(gen_secret)}
JWT_REFRESH_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"
echo " Admin password : $([ "$GENERATED_PASS" = "true" ] && echo "<auto-generated>" || echo "$ADMIN_PASS")"
echo ""
echo " Starting in 5 seconds — press Ctrl+C to abort."
sleep 5
# ── Logging ───────────────────────────────────────────────────────────────────
mkdir -p /var/log/nixpanel
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} Arch: ${ARCH}"
echo "======================================================"
# ── Idempotent step tracker ───────────────────────────────────────────────────
STATE_DIR="/var/lib/nixpanel/install"
mkdir -p "$STATE_DIR"
step_done() { [[ -f "$STATE_DIR/$1.done" ]]; }
mark_done() { touch "$STATE_DIR/$1.done"; }
# ── Update package index ──────────────────────────────────────────────────────
step "Updating package index"
apt-get update -qq
success "Package index updated"
# ── Base dependencies ─────────────────────────────────────────────────────────
step "Installing base dependencies"
if ! step_done "base_deps"; then
apt-get install -y -qq \
curl wget gnupg2 ca-certificates lsb-release software-properties-common \
git build-essential openssl logrotate
mark_done "base_deps"
fi
success "Base dependencies ready"
# ── Add ondrej/php PPA ────────────────────────────────────────────────────────
step "Adding PHP PPA"
if ! step_done "php_ppa"; then
add-apt-repository -y ppa:ondrej/php >/dev/null 2>&1
apt-get update -qq
mark_done "php_ppa"
fi
success "PHP PPA ready"
# ── Node.js 22 via NodeSource ─────────────────────────────────────────────────
step "Installing Node.js 22 LTS"
if ! step_done "nodejs"; then
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - >/dev/null 2>&1
apt-get install -y -qq nodejs
mark_done "nodejs"
fi
success "Node.js $(node --version) ready"
# ── Disable systemd-resolved stub listener (frees port 53 for PowerDNS) ─────
step "Freeing port 53 for PowerDNS"
if ! step_done "resolved_stub"; then
# Ubuntu 24.04 runs systemd-resolved with a stub listener on 127.0.0.53:53.
# PowerDNS's apt post-install restart fails if this is still active.
sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf 2>/dev/null || true
sed -i 's/^DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf 2>/dev/null || true
systemctl restart systemd-resolved 2>/dev/null || true
mark_done "resolved_stub"
fi
success "Port 53 freed"
# ── Install all services in one pass ─────────────────────────────────────────
step "Installing web stack (Apache, PHP, Exim, Dovecot, PowerDNS, MariaDB)"
if ! step_done "packages"; then
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
`# Web server` \
apache2 apache2-utils \
`# PHP 8.2` \
php8.2-fpm php8.2-cli php8.2-mysql php8.2-curl php8.2-gd \
php8.2-mbstring php8.2-xml php8.2-zip php8.2-bcmath php8.2-intl \
php8.2-opcache php8.2-imagick \
`# PHP 8.3` \
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-opcache php8.3-imagick \
`# DNS` \
pdns-server pdns-backend-bind \
`# Mail` \
exim4 exim4-daemon-heavy \
dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-managesieved \
spamassassin spamc \
`# Database (user data)` \
mariadb-server mariadb-client \
`# SSL` \
certbot python3-certbot-apache \
`# Security` \
fail2ban iptables iptables-persistent \
`# Utils` \
sqlite3
mark_done "packages"
fi
success "All packages installed"
# ── Pin packages to prevent apt from overriding NixPanel-managed versions ─────
step "Pinning managed packages"
if ! step_done "apt_pins"; then
cat > /etc/apt/preferences.d/nixpanel-pins <<'PINS'
Package: apache2 apache2-*
Pin: release *
Pin-Priority: 1001
Package: php8.2-* php8.3-*
Pin: release *
Pin-Priority: 1001
Package: pdns-server pdns-backend-*
Pin: release *
Pin-Priority: 1001
Package: exim4 exim4-*
Pin: release *
Pin-Priority: 1001
Package: dovecot-*
Pin: release *
Pin-Priority: 1001
Package: mariadb-server mariadb-client
Pin: release *
Pin-Priority: 1001
PINS
# Disable unattended upgrades (same as cPanel)
systemctl disable --now unattended-upgrades 2>/dev/null || true
mark_done "apt_pins"
fi
success "Package pins set"
# ── Create nixpanel system user ────────────────────────────────────────────────
step "Creating nixpanel system user"
if ! id nixpanel &>/dev/null; then
useradd -r -s /usr/sbin/nologin -M -d "$INSTALL_DIR" nixpanel
fi
success "nixpanel user ready"
# ── Directory structure ────────────────────────────────────────────────────────
step "Creating directory structure"
mkdir -p "$INSTALL_DIR/data"
mkdir -p "$INSTALL_DIR/logs"
mkdir -p /etc/nixpanel
mkdir -p /var/www/letsencrypt/.well-known/acme-challenge
mkdir -p /etc/pdns/zones
success "Directories created"
# ── Configure Apache ──────────────────────────────────────────────────────────
step "Configuring Apache"
if ! step_done "apache"; then
# Enable required modules
a2enmod rewrite ssl proxy proxy_http proxy_wstunnel proxy_fcgi headers expires deflate 2>/dev/null || true
# Disable default vhost
a2dissite 000-default 2>/dev/null || true
# Add panel ports to ports.conf
if ! grep -q "Listen 2087" /etc/apache2/ports.conf 2>/dev/null; then
cat >> /etc/apache2/ports.conf <<'PORTS'
# NixPanel ports
Listen 2087
Listen 2083
PORTS
fi
# Panel reverse proxy vhost — proxies 2087/2083 → Node.js on 127.0.0.1:4000
cat > /etc/apache2/sites-available/nixpanel-panels.conf <<APACHE
# NixPanel admin/user panels — managed by NixPanel installer
<VirtualHost *:2087>
ServerName nixserver
ProxyPreserveHost On
ProxyPass /api/ http://127.0.0.1:4000/api/ retry=0
ProxyPassReverse /api/ http://127.0.0.1:4000/api/
# WebSocket for live stats
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule /ws/(.*) ws://127.0.0.1:4000/ws/\$1 [P,L]
DocumentRoot ${INSTALL_DIR}/nixserver/dist
<Directory ${INSTALL_DIR}/nixserver/dist>
Options -Indexes
AllowOverride None
Require all granted
FallbackResource /index.html
</Directory>
ErrorLog /var/log/apache2/nixserver-error.log
CustomLog /var/log/apache2/nixserver-access.log combined
</VirtualHost>
<VirtualHost *:2083>
ServerName nixclient
ProxyPreserveHost On
ProxyPass /api/ http://127.0.0.1:4000/api/ retry=0
ProxyPassReverse /api/ http://127.0.0.1:4000/api/
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule /ws/(.*) ws://127.0.0.1:4000/ws/\$1 [P,L]
DocumentRoot ${INSTALL_DIR}/nixclient/dist
<Directory ${INSTALL_DIR}/nixclient/dist>
Options -Indexes
AllowOverride None
Require all granted
FallbackResource /index.html
</Directory>
ErrorLog /var/log/apache2/nixclient-error.log
CustomLog /var/log/apache2/nixclient-access.log combined
</VirtualHost>
APACHE
a2ensite nixpanel-panels
systemctl enable apache2
systemctl restart apache2
# Certbot deploy hook to reload Apache on certificate renewal
mkdir -p /etc/letsencrypt/renewal-hooks/deploy
cat > /etc/letsencrypt/renewal-hooks/deploy/nixpanel-reload-apache.sh <<'HOOK'
#!/bin/bash
systemctl reload apache2
HOOK
chmod +x /etc/letsencrypt/renewal-hooks/deploy/nixpanel-reload-apache.sh
mark_done "apache"
fi
success "Apache configured"
# ── Configure PowerDNS ────────────────────────────────────────────────────────
step "Configuring PowerDNS"
if ! step_done "pdns"; then
# Stop any running instance first
systemctl stop pdns 2>/dev/null || true
# Create empty bindbackend config
touch /etc/pdns/bindbackend.conf
chmod 640 /etc/pdns/bindbackend.conf
# Write PowerDNS config using bind backend
cat > /etc/pdns/pdns.conf <<'PDNS'
# NixPanel-managed PowerDNS configuration
launch=bind
bind-config=/etc/pdns/bindbackend.conf
bind-check-interval=60
local-address=0.0.0.0
local-port=53
allow-recursion=0.0.0.0/0
log-dns-queries=no
loglevel=3
# Security
setuid=pdns
setgid=pdns
PDNS
systemctl enable pdns
systemctl start pdns
mark_done "pdns"
fi
success "PowerDNS configured"
# ── Configure Exim4 ───────────────────────────────────────────────────────────
step "Configuring Exim4"
if ! step_done "exim"; then
# Create virtual mail flat files (NixPanel manages these)
touch /etc/exim4/virtual_domains
touch /etc/exim4/virtual_mailboxes
touch /etc/exim4/virtual_aliases
chmod 640 /etc/exim4/virtual_domains /etc/exim4/virtual_mailboxes /etc/exim4/virtual_aliases
# Set Exim to internet site mode via debconf
debconf-set-selections <<'DEBCONF'
exim4-config exim4/dc_eximconfig_configtype select internet site
exim4-config exim4/dc_other_hostnames string localhost
exim4-config exim4/dc_local_interfaces string
exim4-config exim4/dc_readhost string
exim4-config exim4/dc_relay_domains string
exim4-config exim4/dc_minimaldns boolean false
exim4-config exim4/dc_relay_nets string
exim4-config exim4/dc_smarthost string
exim4-config exim4/hide_mailname boolean true
exim4-config exim4/mailname-file string /etc/mailname
DEBCONF
dpkg-reconfigure -f noninteractive exim4-config 2>/dev/null || true
systemctl enable exim4
systemctl restart exim4
mark_done "exim"
fi
success "Exim4 configured"
# ── Configure Dovecot ─────────────────────────────────────────────────────────
step "Configuring Dovecot"
if ! step_done "dovecot"; then
# Create passwd file for virtual mailboxes
touch /etc/dovecot/passwd
chmod 640 /etc/dovecot/passwd
chown root:dovecot /etc/dovecot/passwd
# Configure Dovecot for passwd-file authentication
cat > /etc/dovecot/conf.d/10-auth.conf <<'DOVEAUTH'
disable_plaintext_auth = yes
auth_mechanisms = plain login
!include auth-passwdfile.conf.ext
DOVEAUTH
cat > /etc/dovecot/conf.d/auth-passwdfile.conf.ext <<'DOVEPF'
passdb {
driver = passwd-file
args = scheme=SHA512-CRYPT /etc/dovecot/passwd
}
userdb {
driver = passwd-file
args = /etc/dovecot/passwd
default_fields = uid=vmail gid=vmail home=/home/%d/%n
}
DOVEPF
cat > /etc/dovecot/conf.d/10-mail.conf <<'DOVEMAIL'
mail_location = maildir:/home/%d/%n/Maildir
mail_privileged_group = mail
DOVEMAIL
systemctl enable dovecot
systemctl restart dovecot
mark_done "dovecot"
fi
success "Dovecot configured"
# ── Configure SpamAssassin ────────────────────────────────────────────────────
step "Configuring SpamAssassin"
if ! step_done "spamassassin"; then
# Ubuntu 24.04: service unit is 'spamd', not 'spamassassin'
systemctl enable spamd
systemctl start spamd
mark_done "spamassassin"
fi
success "SpamAssassin configured"
# ── Configure MariaDB ─────────────────────────────────────────────────────────
step "Configuring MariaDB"
if ! step_done "mariadb"; then
systemctl enable --now mariadb
# Secure MariaDB (remove anonymous users, test db, set root password)
mariadb -u root <<SQL
ALTER USER 'root'@'localhost' IDENTIFIED BY '${MARIADB_ROOT_PASS}';
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\\_%';
FLUSH PRIVILEGES;
SQL
# Write MariaDB root credentials file for NixPanel to use
cat > /etc/nixpanel/mariadb.conf <<MYCNF
[client]
host=127.0.0.1
port=3306
user=root
password=${MARIADB_ROOT_PASS}
MYCNF
chmod 600 /etc/nixpanel/mariadb.conf
mark_done "mariadb"
fi
success "MariaDB configured"
# ── 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..."
# The directory may already exist (created by the mkdir step above).
# Clone to a temp dir then copy contents in so data/ and logs/ are preserved.
TMP_CLONE=$(mktemp -d)
git clone https://github.com/NixReaper/nixpanel-server.git "$TMP_CLONE"
cp -a "$TMP_CLONE/." "$INSTALL_DIR/"
rm -rf "$TMP_CLONE"
fi
success "NixPanel source ready at $INSTALL_DIR"
# ── Write .env ────────────────────────────────────────────────────────────────
step "Writing environment config"
SERVER_IP=$(hostname -I | awk '{print $1}')
cat > "$INSTALL_DIR/server/.env" <<ENV
# Generated by NixPanel installer — $(date -u '+%Y-%m-%d %H:%M:%S UTC')
# Panel database (SQLite)
DATABASE_URL="file:${INSTALL_DIR}/data/nixpanel.db"
# JWT
JWT_SECRET="${JWT_SECRET}"
JWT_EXPIRES_IN="15m"
JWT_REFRESH_SECRET="${JWT_REFRESH_SECRET}"
JWT_REFRESH_EXPIRES_IN="7d"
# Server
NODE_ENV="production"
PORT=4000
HOST="127.0.0.1"
# Admin credentials (used during initial seed only — change after first login)
ADMIN_USERNAME="${ADMIN_USER}"
ADMIN_PASSWORD="${ADMIN_PASS}"
ADMIN_EMAIL="${ADMIN_EMAIL}"
# MariaDB (for hosting account databases)
MARIADB_HOST=127.0.0.1
MARIADB_PORT=3306
MARIADB_USER=root
MARIADB_PASSWORD=${MARIADB_ROOT_PASS}
# Licensing
LICENSING_SERVER_URL="https://license.nixpanel.io"
LICENSE_KEY=""
# Paths
HOME_DIR="/home"
APACHE_SITES_DIR="/etc/apache2/sites-available"
APACHE_ENABLED_DIR="/etc/apache2/sites-enabled"
PDNS_ZONES_DIR="/etc/pdns/zones"
PDNS_BIND_BACKEND="/etc/pdns/bindbackend.conf"
EXIM_VIRTUAL_DOMAINS="/etc/exim4/virtual_domains"
EXIM_VIRTUAL_MAILBOXES="/etc/exim4/virtual_mailboxes"
EXIM_VIRTUAL_ALIASES="/etc/exim4/virtual_aliases"
DOVECOT_PASSWD="/etc/dovecot/passwd"
CERTBOT_WEBROOT="/var/www/letsencrypt"
LETSENCRYPT_LIVE="/etc/letsencrypt/live"
ENV
chmod 600 "$INSTALL_DIR/server/.env"
success ".env written"
# ── npm install ───────────────────────────────────────────────────────────────
step "Installing Node.js dependencies"
cd "$INSTALL_DIR"
rm -rf node_modules package-lock.json
npm install
success "npm dependencies installed"
# ── Prisma generate + db push ─────────────────────────────────────────────────
step "Initialising panel database"
cd "$INSTALL_DIR/server"
npx prisma generate
DATABASE_URL="file:${INSTALL_DIR}/data/nixpanel.db" npx prisma db push
chown root:nixpanel "${INSTALL_DIR}/data/nixpanel.db"
chmod 640 "${INSTALL_DIR}/data/nixpanel.db"
success "Panel database (SQLite) ready"
# ── Seed ─────────────────────────────────────────────────────────────────────
step "Seeding initial admin and default packages"
DATABASE_URL="file:${INSTALL_DIR}/data/nixpanel.db" \
ADMIN_USERNAME="${ADMIN_USER}" \
ADMIN_PASSWORD="${ADMIN_PASS}" \
ADMIN_EMAIL="${ADMIN_EMAIL}" \
npx tsx src/db/seed.ts
success "Admin account and default packages seeded"
# ── Build ─────────────────────────────────────────────────────────────────────
step "Building frontends and server"
cd "$INSTALL_DIR"
npm run build --workspace=nixserver
npm run build --workspace=nixclient
cd "$INSTALL_DIR/server"
npm run build
success "Build complete"
# ── Set web-readable permissions on frontend assets ───────────────────────────
step "Setting frontend file permissions"
# Apache runs as www-data — ensure it can traverse parents and read dist files
chmod 755 "$INSTALL_DIR" "$INSTALL_DIR/nixserver" "$INSTALL_DIR/nixclient"
find "$INSTALL_DIR/nixserver/dist" -type d -exec chmod 755 {} \;
find "$INSTALL_DIR/nixserver/dist" -type f -exec chmod 644 {} \;
find "$INSTALL_DIR/nixclient/dist" -type d -exec chmod 755 {} \;
find "$INSTALL_DIR/nixclient/dist" -type f -exec chmod 644 {} \;
success "File permissions set"
# ── systemd service ───────────────────────────────────────────────────────────
step "Creating systemd service"
cat > /etc/systemd/system/nixpanel.service <<UNIT
[Unit]
Description=NixPanel Web Hosting Control Panel
After=network.target mariadb.service apache2.service pdns.service
[Service]
Type=simple
User=root
WorkingDirectory=${INSTALL_DIR}/server
EnvironmentFile=${INSTALL_DIR}/server/.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
systemctl daemon-reload
systemctl enable nixpanel
systemctl start nixpanel
success "nixpanel.service started"
# ── Firewall ──────────────────────────────────────────────────────────────────
step "Configuring firewall (iptables)"
if ! step_done "firewall"; then
# Flush existing rules
iptables -F INPUT
iptables -F FORWARD
iptables -F OUTPUT
# Default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow established connections and loopback
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
# Standard ports
for PORT in 22 80 443 25 465 587 110 143 993 995 4190 2082 2083 2086 2087; do
iptables -A INPUT -p tcp --dport "$PORT" -j ACCEPT
done
# DNS
iptables -A INPUT -p tcp --dport 53 -j ACCEPT
iptables -A INPUT -p udp --dport 53 -j ACCEPT
# Save rules so they persist across reboots
netfilter-persistent save
mark_done "firewall"
fi
success "Firewall rules applied"
# ── Fail2ban ──────────────────────────────────────────────────────────────────
step "Configuring Fail2ban"
if ! step_done "fail2ban"; then
cat > /etc/fail2ban/jail.d/nixpanel.conf <<'F2B'
[sshd]
enabled = true
maxretry = 5
bantime = 3600
findtime = 600
[apache-auth]
enabled = true
maxretry = 10
bantime = 600
[exim]
enabled = true
[dovecot]
enabled = true
F2B
systemctl enable --now fail2ban
mark_done "fail2ban"
fi
success "Fail2ban configured"
# ── logrotate ─────────────────────────────────────────────────────────────────
step "Configuring log rotation"
cat > /etc/logrotate.d/nixpanel <<'LR'
/var/log/nixpanel/*.log
/opt/nixpanel/logs/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
sharedscripts
postrotate
systemctl reload nixpanel > /dev/null 2>&1 || true
endscript
}
LR
success "Log rotation configured"
# ── Health check ──────────────────────────────────────────────────────────────
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))
if [[ $WAITED -ge $MAX_WAIT ]]; then
warn "Health check timed out. Check: journalctl -u nixpanel -n 50"
break
fi
done
if curl -sf http://127.0.0.1:4000/api/health >/dev/null 2>&1; then
success "NixPanel API is responding"
fi
# ── Save credentials ──────────────────────────────────────────────────────────
CRED_FILE="/root/.nixpanel_credentials"
cat > "$CRED_FILE" <<CREDS
NixPanel Credentials — $(date -u '+%Y-%m-%d %H:%M:%S UTC')
============================================================
NixServer (Admin) : http://${SERVER_IP}:2087
NixClient (User) : http://${SERVER_IP}:2083
Admin User : ${ADMIN_USER}
Admin Password : ${ADMIN_PASS}
Admin Email : ${ADMIN_EMAIL}
MariaDB Root Pass : ${MARIADB_ROOT_PASS}
Install Dir : ${INSTALL_DIR}
Install Log : ${LOG_FILE}
CREDS
chmod 600 "$CRED_FILE"
# ── 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} http://${SERVER_IP}:2087"
echo -e " ${BOLD}NixClient (User Panel)${RESET} http://${SERVER_IP}:2083"
echo ""
echo -e " ${BOLD}Admin username:${RESET} ${ADMIN_USER}"
echo -e " ${BOLD}Admin password:${RESET} ${ADMIN_PASS}"
echo ""
echo -e " ${BOLD}Useful commands:${RESET}"
echo -e " systemctl status nixpanel # panel status"
echo -e " journalctl -u nixpanel -f # live logs"
echo -e " systemctl restart nixpanel # restart panel"
echo ""
echo -e " Credentials saved to: ${BOLD}${CRED_FILE}${RESET}"
echo ""
if [[ "$GENERATED_PASS" == "true" ]]; then
echo -e " ${YELLOW}${BOLD}Save your admin password — it will not be shown again!${RESET}"
echo ""
fi