299 lines
13 KiB
Bash
299 lines
13 KiB
Bash
#!/usr/bin/env bash
|
|||
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
|
# NixPanel Licensing Server — Deploy Script
|
||
|
|
# Run this on the server that will host license.nixpanel.io
|
||
|
|
# Requires: Ubuntu 22.04+ / Debian 12+, run as root
|
||
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'
|
||
|
|
YELLOW='\033[1;33m'; 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}[ERR ]${RESET} $*" >&2; exit 1; }
|
||
|
|
step() { echo -e "\n${BOLD}${CYAN}━━━ $* ━━━${RESET}"; }
|
||
|
|
|
||
|
|
[[ $EUID -ne 0 ]] && error "Must be run as root"
|
||
|
|
|
||
|
|
INSTALL_DIR="/opt/nixpanel-licensing"
|
||
|
|
BIN_DIR="$INSTALL_DIR/bin"
|
||
|
|
KEYS_DIR="$INSTALL_DIR/keys"
|
||
|
|
LOG_DIR="/var/log/nixpanel-licensing"
|
||
|
|
SRC_DIR="$INSTALL_DIR/src"
|
||
|
|
STEP_DIR="/var/lib/nixpanel-licensing/install"
|
||
|
|
GITHUB_REPO="NixReaper/nixpanel-licensing"
|
||
|
|
|
||
|
|
mkdir -p "$INSTALL_DIR" "$BIN_DIR" "$KEYS_DIR" "$LOG_DIR" "$SRC_DIR" "$STEP_DIR"
|
||
|
|
chmod 700 "$KEYS_DIR"
|
||
|
|
|
||
|
|
mark_done() { touch "$STEP_DIR/$1.done"; }
|
||
|
|
step_done() { [[ -f "$STEP_DIR/$1.done" ]]; }
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo -e "${BOLD}${CYAN} NixPanel Licensing Server — Deployer${RESET}"
|
||
|
|
echo -e " Repo: https://github.com/${GITHUB_REPO}"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
# ── 1. System deps ────────────────────────────────────────────────────────────
|
||
|
|
step "Installing system dependencies"
|
||
|
|
if ! step_done "sys_deps"; then
|
||
|
|
export DEBIAN_FRONTEND=noninteractive
|
||
|
|
apt-get update -qq
|
||
|
|
apt-get install -y -qq \
|
||
|
|
build-essential pkg-config libssl-dev libmariadb-dev \
|
||
|
|
curl wget git unzip ca-certificates gnupg \
|
||
|
|
mariadb-server mariadb-client \
|
||
|
|
apache2 certbot python3-certbot-apache \
|
||
|
|
openssl
|
||
|
|
mark_done "sys_deps"
|
||
|
|
fi
|
||
|
|
success "System dependencies installed"
|
||
|
|
|
||
|
|
# ── 2. Rust ───────────────────────────────────────────────────────────────────
|
||
|
|
step "Installing Rust toolchain"
|
||
|
|
if ! step_done "rust"; then
|
||
|
|
curl -fsSL https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
|
||
|
|
mark_done "rust"
|
||
|
|
fi
|
||
|
|
source "$HOME/.cargo/env" 2>/dev/null || true
|
||
|
|
CARGO=$(command -v cargo) || error "cargo not found"
|
||
|
|
success "Rust $(rustc --version)"
|
||
|
|
|
||
|
|
# ── 3. MariaDB ────────────────────────────────────────────────────────────────
|
||
|
|
step "Configuring MariaDB"
|
||
|
|
if ! step_done "mariadb"; then
|
||
|
|
systemctl enable --now mariadb
|
||
|
|
|
||
|
|
DB_PASS=$(openssl rand -hex 24)
|
||
|
|
mysql -u root <<SQL
|
||
|
|
CREATE DATABASE IF NOT EXISTS nixpanel_licensing CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||
|
|
CREATE USER IF NOT EXISTS 'nixpanel_license'@'127.0.0.1' IDENTIFIED BY '${DB_PASS}';
|
||
|
|
GRANT ALL PRIVILEGES ON nixpanel_licensing.* TO 'nixpanel_license'@'127.0.0.1';
|
||
|
|
FLUSH PRIVILEGES;
|
||
|
|
SQL
|
||
|
|
|
||
|
|
echo "$DB_PASS" > "$INSTALL_DIR/.db_pass"
|
||
|
|
chmod 600 "$INSTALL_DIR/.db_pass"
|
||
|
|
mark_done "mariadb"
|
||
|
|
else
|
||
|
|
DB_PASS=$(cat "$INSTALL_DIR/.db_pass")
|
||
|
|
fi
|
||
|
|
success "MariaDB ready"
|
||
|
|
|
||
|
|
# ── 4. Clone / update source ──────────────────────────────────────────────────
|
||
|
|
step "Fetching source"
|
||
|
|
if [[ -d "$SRC_DIR/.git" ]]; then
|
||
|
|
info "Updating existing checkout…"
|
||
|
|
git -C "$SRC_DIR" pull --ff-only
|
||
|
|
else
|
||
|
|
git clone "https://github.com/${GITHUB_REPO}.git" "$SRC_DIR"
|
||
|
|
fi
|
||
|
|
success "Source at $SRC_DIR"
|
||
|
|
|
||
|
|
# ── 5. Build ──────────────────────────────────────────────────────────────────
|
||
|
|
step "Building binaries (release)"
|
||
|
|
cd "$SRC_DIR"
|
||
|
|
$CARGO build --release
|
||
|
|
cp target/release/nixpanel-licensing "$BIN_DIR/nixpanel-licensing"
|
||
|
|
cp target/release/keygen "$BIN_DIR/nixpanel-keygen"
|
||
|
|
chmod 755 "$BIN_DIR/nixpanel-licensing" "$BIN_DIR/nixpanel-keygen"
|
||
|
|
success "Binaries built → $BIN_DIR"
|
||
|
|
|
||
|
|
# ── 6. RSA keys ───────────────────────────────────────────────────────────────
|
||
|
|
step "RSA-4096 license signing keys"
|
||
|
|
if ! step_done "keygen"; then
|
||
|
|
info "Generating RSA-4096 keypair (this takes a moment)…"
|
||
|
|
mkdir -p "$SRC_DIR/keys"
|
||
|
|
"$BIN_DIR/nixpanel-keygen"
|
||
|
|
cp "$SRC_DIR/keys/private.pem" "$KEYS_DIR/license_private.pem"
|
||
|
|
cp "$SRC_DIR/keys/public.pem" "$KEYS_DIR/license_public.pem"
|
||
|
|
chmod 600 "$KEYS_DIR/license_private.pem"
|
||
|
|
chmod 644 "$KEYS_DIR/license_public.pem"
|
||
|
|
mark_done "keygen"
|
||
|
|
fi
|
||
|
|
success "Keys → $KEYS_DIR"
|
||
|
|
info "Public key (distribute to panel nodes):"
|
||
|
|
echo ""
|
||
|
|
cat "$KEYS_DIR/license_public.pem"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
# ── 7. Secrets + env ──────────────────────────────────────────────────────────
|
||
|
|
step "Generating secrets"
|
||
|
|
if ! step_done "secrets"; then
|
||
|
|
JWT_SECRET=$(openssl rand -hex 64)
|
||
|
|
API_KEY=$(openssl rand -hex 32)
|
||
|
|
echo "$JWT_SECRET" > "$INSTALL_DIR/.jwt_secret"
|
||
|
|
echo "$API_KEY" > "$INSTALL_DIR/.api_key"
|
||
|
|
chmod 600 "$INSTALL_DIR/.jwt_secret" "$INSTALL_DIR/.api_key"
|
||
|
|
mark_done "secrets"
|
||
|
|
else
|
||
|
|
JWT_SECRET=$(cat "$INSTALL_DIR/.jwt_secret")
|
||
|
|
API_KEY=$(cat "$INSTALL_DIR/.api_key")
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
info "Enter your server's public hostname (e.g. license.nixpanel.io):"
|
||
|
|
read -rp " Hostname: " SERVER_HOST
|
||
|
|
SERVER_HOST="${SERVER_HOST:-license.nixpanel.io}"
|
||
|
|
|
||
|
|
cat > "$INSTALL_DIR/licensing.env" <<EOF
|
||
|
|
HOST=127.0.0.1
|
||
|
|
PORT=3000
|
||
|
|
RUST_LOG=nixpanel_licensing=info,tower_http=warn
|
||
|
|
DATABASE_URL=mysql://nixpanel_license:${DB_PASS}@127.0.0.1:3306/nixpanel_licensing
|
||
|
|
JWT_SECRET=${JWT_SECRET}
|
||
|
|
JWT_EXPIRES_IN_SECS=86400
|
||
|
|
LICENSE_PRIVATE_KEY_PATH=${KEYS_DIR}/license_private.pem
|
||
|
|
LICENSE_PUBLIC_KEY_PATH=${KEYS_DIR}/license_public.pem
|
||
|
|
LICENSE_TOKEN_TTL_DAYS=30
|
||
|
|
LICENSE_GRACE_PERIOD_DAYS=7
|
||
|
|
SERVER_URL=https://${SERVER_HOST}
|
||
|
|
EOF
|
||
|
|
chmod 600 "$INSTALL_DIR/licensing.env"
|
||
|
|
success "Environment written → $INSTALL_DIR/licensing.env"
|
||
|
|
|
||
|
|
# ── 8. First admin account ────────────────────────────────────────────────────
|
||
|
|
step "Creating initial admin account"
|
||
|
|
if ! step_done "admin"; then
|
||
|
|
echo ""
|
||
|
|
read -rp " Admin username [admin]: " ADMIN_USER; ADMIN_USER="${ADMIN_USER:-admin}"
|
||
|
|
read -rsp " Admin password: " ADMIN_PASS; echo ""
|
||
|
|
read -rsp " Confirm password: " ADMIN_PASS2; echo ""
|
||
|
|
[[ "$ADMIN_PASS" != "$ADMIN_PASS2" ]] && error "Passwords do not match"
|
||
|
|
read -rp " Admin email: " ADMIN_EMAIL
|
||
|
|
|
||
|
|
# Start the server temporarily to run migrations, then use the API to create admin
|
||
|
|
info "Starting server for first-run migration + admin creation…"
|
||
|
|
source "$INSTALL_DIR/licensing.env" 2>/dev/null || true
|
||
|
|
export $(grep -v '^#' "$INSTALL_DIR/licensing.env" | xargs)
|
||
|
|
"$BIN_DIR/nixpanel-licensing" &
|
||
|
|
SERVER_PID=$!
|
||
|
|
sleep 3
|
||
|
|
|
||
|
|
# Create admin via API
|
||
|
|
HTTP_STATUS=$(curl -s -o /tmp/admin_create_resp.json -w "%{http_code}" \
|
||
|
|
-X POST "http://127.0.0.1:3000/api/v1/admin/bootstrap" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d "{\"username\":\"${ADMIN_USER}\",\"password\":\"${ADMIN_PASS}\",\"email\":\"${ADMIN_EMAIL}\"}")
|
||
|
|
|
||
|
|
kill "$SERVER_PID" 2>/dev/null || true
|
||
|
|
|
||
|
|
if [[ "$HTTP_STATUS" == "200" || "$HTTP_STATUS" == "201" ]]; then
|
||
|
|
success "Admin account created"
|
||
|
|
else
|
||
|
|
warn "Bootstrap returned HTTP $HTTP_STATUS — admin may already exist, or set one up manually"
|
||
|
|
cat /tmp/admin_create_resp.json 2>/dev/null || true
|
||
|
|
echo ""
|
||
|
|
fi
|
||
|
|
mark_done "admin"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ── 9. systemd service ────────────────────────────────────────────────────────
|
||
|
|
step "Installing systemd service"
|
||
|
|
cat > /etc/systemd/system/nixpanel-licensing.service <<EOF
|
||
|
|
[Unit]
|
||
|
|
Description=NixPanel Licensing Server
|
||
|
|
After=network.target mariadb.service
|
||
|
|
Wants=mariadb.service
|
||
|
|
|
||
|
|
[Service]
|
||
|
|
Type=simple
|
||
|
|
User=root
|
||
|
|
ExecStart=${BIN_DIR}/nixpanel-licensing
|
||
|
|
WorkingDirectory=${INSTALL_DIR}
|
||
|
|
EnvironmentFile=${INSTALL_DIR}/licensing.env
|
||
|
|
Restart=on-failure
|
||
|
|
RestartSec=5
|
||
|
|
StandardOutput=append:${LOG_DIR}/server.log
|
||
|
|
StandardError=append:${LOG_DIR}/server-error.log
|
||
|
|
LimitNOFILE=65536
|
||
|
|
|
||
|
|
[Install]
|
||
|
|
WantedBy=multi-user.target
|
||
|
|
EOF
|
||
|
|
systemctl daemon-reload
|
||
|
|
systemctl enable nixpanel-licensing
|
||
|
|
systemctl restart nixpanel-licensing
|
||
|
|
success "nixpanel-licensing service running"
|
||
|
|
|
||
|
|
# ── 10. Apache reverse proxy ──────────────────────────────────────────────────
|
||
|
|
step "Configuring Apache reverse proxy"
|
||
|
|
a2enmod proxy proxy_http headers ssl rewrite
|
||
|
|
cat > /etc/apache2/sites-available/nixpanel-licensing.conf <<APACHEEOF
|
||
|
|
<VirtualHost *:80>
|
||
|
|
ServerName ${SERVER_HOST}
|
||
|
|
# Redirect all HTTP to HTTPS (certbot will update this after SSL)
|
||
|
|
RewriteEngine On
|
||
|
|
RewriteRule ^(.*)$ https://%{HTTP_HOST}%1 [R=301,L]
|
||
|
|
</VirtualHost>
|
||
|
|
|
||
|
|
<VirtualHost *:443>
|
||
|
|
ServerName ${SERVER_HOST}
|
||
|
|
|
||
|
|
ProxyPreserveHost On
|
||
|
|
ProxyPass / http://127.0.0.1:3000/
|
||
|
|
ProxyPassReverse / http://127.0.0.1:3000/
|
||
|
|
|
||
|
|
# Security headers
|
||
|
|
Header always set X-Frame-Options "DENY"
|
||
|
|
Header always set X-Content-Type-Options "nosniff"
|
||
|
|
Header always set X-XSS-Protection "1; mode=block"
|
||
|
|
|
||
|
|
ErrorLog ${LOG_DIR}/apache-error.log
|
||
|
|
CustomLog ${LOG_DIR}/apache-access.log combined
|
||
|
|
|
||
|
|
# SSL placeholder — certbot will fill these in
|
||
|
|
# SSLEngine on
|
||
|
|
# SSLCertificateFile ...
|
||
|
|
# SSLCertificateKeyFile ...
|
||
|
|
</VirtualHost>
|
||
|
|
APACHEEOF
|
||
|
|
|
||
|
|
a2ensite nixpanel-licensing
|
||
|
|
a2dissite 000-default 2>/dev/null || true
|
||
|
|
systemctl enable --now apache2
|
||
|
|
apache2ctl configtest && systemctl reload apache2
|
||
|
|
success "Apache configured for ${SERVER_HOST}"
|
||
|
|
|
||
|
|
# ── 11. SSL ───────────────────────────────────────────────────────────────────
|
||
|
|
step "SSL certificate (Let's Encrypt)"
|
||
|
|
echo ""
|
||
|
|
echo -e " ${BOLD}Obtain SSL cert now?${RESET} (requires DNS A record pointing ${SERVER_HOST} → this server)"
|
||
|
|
read -rp " Get SSL cert? [Y/n]: " DO_SSL
|
||
|
|
DO_SSL="${DO_SSL:-Y}"
|
||
|
|
|
||
|
|
if [[ "${DO_SSL,,}" == "y" ]]; then
|
||
|
|
read -rp " Email for Let's Encrypt notices: " LE_EMAIL
|
||
|
|
certbot --apache -d "$SERVER_HOST" --non-interactive --agree-tos -m "$LE_EMAIL" \
|
||
|
|
&& success "SSL certificate installed" \
|
||
|
|
|| warn "certbot failed — run manually: certbot --apache -d ${SERVER_HOST}"
|
||
|
|
else
|
||
|
|
info "Skipped — run later: certbot --apache -d ${SERVER_HOST}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ── Done ──────────────────────────────────────────────────────────────────────
|
||
|
|
SERVER_IP=$(hostname -I | awk '{print $1}')
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo -e "${BOLD}${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
||
|
|
echo -e "${BOLD}${GREEN} Licensing Server Deployed!${RESET}"
|
||
|
|
echo -e "${BOLD}${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
||
|
|
echo ""
|
||
|
|
echo -e " ${BOLD}API:${RESET} https://${SERVER_HOST}"
|
||
|
|
echo -e " ${BOLD}Health check:${RESET} curl https://${SERVER_HOST}/health"
|
||
|
|
echo -e " ${BOLD}Logs:${RESET} ${LOG_DIR}/"
|
||
|
|
echo -e " ${BOLD}Config:${RESET} ${INSTALL_DIR}/licensing.env"
|
||
|
|
echo -e " ${BOLD}Public key:${RESET} ${KEYS_DIR}/license_public.pem"
|
||
|
|
echo ""
|
||
|
|
echo -e " ${BOLD}Internal API key${RESET} (add to website.env LICENSING_SERVER_API_KEY):"
|
||
|
|
echo -e " ${YELLOW}$(cat "$INSTALL_DIR/.api_key")${RESET}"
|
||
|
|
echo ""
|
||
|
|
echo -e " ${BOLD}Next steps:${RESET}"
|
||
|
|
echo -e " 1. Copy public key to panel nodes: ${KEYS_DIR}/license_public.pem"
|
||
|
|
echo -e " 2. Set LICENSING_SERVER_URL=https://${SERVER_HOST} in nixpanel.io website.env"
|
||
|
|
echo -e " 3. Create license plans via: POST https://${SERVER_HOST}/api/v1/admin/plans"
|
||
|
|
echo -e " 4. Status: systemctl status nixpanel-licensing"
|
||
|
|
echo ""
|