Files
nixpanel-licensing/deploy.sh
T
2026-08-29 21:59:00 -05:00

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 ""