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.
767 lines
28 KiB
Bash
Executable File
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
|