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.
755 lines
27 KiB
Bash
Executable File
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
|