Files
nixpanel-server/scripts/install.sh
T
NixReaper 8d6fe60168 fix(install): set world-readable permissions on frontend dist dirs
Apache (www-data) couldn't traverse /opt/nixpanel/nixserver because the
directories lacked execute permission for other. Add an explicit chmod
step after the build to ensure all dist directories are 755 and files
are 644 so Apache can serve the static assets.
2026-04-11 08:53:47 -05:00

755 lines
27 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
MARIADB_ROOT_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)
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"
echo " Admin password : $([ "$GENERATED_PASS" = "true" ] && echo "<auto-generated>" || echo "$ADMIN_PASS")"
echo ""
read -r -p "Continue? [Y/n] " CONFIRM
CONFIRM="${CONFIRM:-Y}"
[[ "$CONFIRM" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
# ── 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_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"
npm install --silent
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 --silent
npm run build --workspace=nixclient --silent
cd "$INSTALL_DIR/server"
npm run build --silent
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