#!/usr/bin/env bash # ============================================================================== # nx9-wg Production Deployment Tool # ============================================================================== # PURPOSE: # Deploys the compiled release binary target/release/nx9-wg to the production # system path (/usr/local/bin/nx9-wg) with validated controlled replacement, # automatic backup of the previous binary, and controlled systemd service management. # # PREREQUISITES: # - Root privileges (or sudo) # - Pre-compiled release binary at target/release/nx9-wg # - Linux with systemd # # SAFETY INVARIANTS: # - Never overwrites the existing production binary without creating a timestamped backup. # - Never modifies or overwrites database files (/var/lib/nx9-wg/nx9-wg.db). # - Never deletes WireGuard interfaces or kills processes automatically on port conflict. # - Uses the standard 'install' command for controlled binary replacement and strict permissions. # - Returns non-zero exit code on failure. # # USAGE: # sudo bash scripts/deploy.sh [OPTIONS] # # OPTIONS: # --no-restart Install binary without restarting nx9-wg.service # --dry-run Simulate deployment actions without applying changes # -h, --help Show this help message # ============================================================================== set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" SOURCE_BIN="${ROOT_DIR}/target/release/nx9-wg" DEST_BIN="/usr/local/bin/nx9-wg" CONF_DIR="/etc/nx9-wg" DATA_DIR="/var/lib/nx9-wg" BACKUP_DIR="${DATA_DIR}/backups" LOG_DIR="/var/log/nx9-wg" SERVICE_DEST="/etc/systemd/system/nx9-wg.service" SERVICE_SRC="${ROOT_DIR}/nx9-wg.service" DRY_RUN=0 NO_RESTART=0 usage() { cat <&2 usage ;; esac done log() { echo -e "\033[1;34m[DEPLOY]\033[0m \033[1;37m$*\033[0m" } success() { echo -e "\033[1;32m[SUCCESS]\033[0m $*" } warn() { echo -e "\033[1;33m[WARN]\033[0m $*" } error() { echo -e "\033[1;31m[ERROR]\033[0m $*" >&2 exit 1 } # 1. Privilege Verification if [[ "${EUID}" -ne 0 && "${DRY_RUN}" -eq 0 ]]; then error "Deployment must be run as root (or via sudo)." fi # 2. Source Binary Verification if [[ ! -f "${SOURCE_BIN}" ]]; then error "Source binary not found at ${SOURCE_BIN}. Run 'bash scripts/build-release.sh' first." fi if [[ ! -x "${SOURCE_BIN}" ]]; then error "Source binary at ${SOURCE_BIN} is not executable." fi SOURCE_SIZE="$(du -h "${SOURCE_BIN}" | cut -f1)" SOURCE_SHA="$(sha256sum "${SOURCE_BIN}" | awk '{print $1}')" SOURCE_DATE="$(date -r "${SOURCE_BIN}" '+%Y-%m-%d %H:%M:%S')" log "Source binary verified:" echo " Path: ${SOURCE_BIN}" echo " Size: ${SOURCE_SIZE}" echo " Timestamp: ${SOURCE_DATE}" echo " SHA-256: ${SOURCE_SHA}" # 3. Create Filesystem Layout with Strict Permissions log "Ensuring directory permissions..." if [[ "${DRY_RUN}" -eq 0 ]]; then install -d -m 0750 "${CONF_DIR}" install -d -m 0700 "${DATA_DIR}" install -d -m 0700 "${BACKUP_DIR}" install -d -m 0750 "${LOG_DIR}" else echo " [DRY-RUN] install -d directories: ${CONF_DIR}, ${DATA_DIR}, ${BACKUP_DIR}, ${LOG_DIR}" fi # 4. Backup Existing Production Binary if [[ -f "${DEST_BIN}" ]]; then TIMESTAMP="$(date +%Y%m%d_%H%M%S)" BACKUP_DEST="${DEST_BIN}.backup.${TIMESTAMP}" log "Backing up active binary to ${BACKUP_DEST}..." if [[ "${DRY_RUN}" -eq 0 ]]; then cp -p "${DEST_BIN}" "${BACKUP_DEST}" chmod 0755 "${BACKUP_DEST}" success "Backup created: ${BACKUP_DEST}" else echo " [DRY-RUN] cp -p ${DEST_BIN} ${BACKUP_DEST}" fi fi # 5. Controlled Installation of New Binary log "Installing new release binary to ${DEST_BIN}..." if [[ "${DRY_RUN}" -eq 0 ]]; then install -m 0755 "${SOURCE_BIN}" "${DEST_BIN}" success "Binary installed to ${DEST_BIN}" else echo " [DRY-RUN] install -m 0755 ${SOURCE_BIN} ${DEST_BIN}" fi # 6. Install or Update systemd Service Unit if [[ -f "${SERVICE_SRC}" && -d "/etc/systemd/system" ]]; then log "Installing/updating systemd service unit..." if [[ "${DRY_RUN}" -eq 0 ]]; then install -m 0644 "${SERVICE_SRC}" "${SERVICE_DEST}" if command -v systemctl >/dev/null 2>&1; then systemctl daemon-reload fi success "systemd service unit updated at ${SERVICE_DEST}" else echo " [DRY-RUN] install -m 0644 ${SERVICE_SRC} ${SERVICE_DEST}" fi fi # 7. Safe Socket Inspection if command -v ss >/dev/null 2>&1; then log "Inspecting active UDP listen sockets without modifying the host..." ACTIVE_UDP_SOCKETS="$(ss -lunp 2>/dev/null | grep -v '^State' || true)" if [[ -n "${ACTIVE_UDP_SOCKETS}" ]]; then echo "${ACTIVE_UDP_SOCKETS}" else echo " No UDP listeners reported by ss." fi fi # 8. Service Restart & Verification if [[ "${NO_RESTART}" -eq 0 && "${DRY_RUN}" -eq 0 ]]; then if command -v systemctl >/dev/null 2>&1; then log "Restarting nx9-wg.service..." systemctl restart nx9-wg || error "Failed to restart nx9-wg service." sleep 1 if systemctl is-active --quiet nx9-wg; then success "nx9-wg.service is active and running." else warn "nx9-wg.service is not in active state. Inspecting journal..." journalctl -u nx9-wg -n 20 --no-pager || true error "Service failed to start." fi fi elif [[ "${NO_RESTART}" -eq 1 ]]; then log "Skipping service restart as requested (--no-restart)." fi # 9. Final Deployment Verification log "Verifying deployed binary version..." if [[ "${DRY_RUN}" -eq 0 ]]; then DEPLOYED_VER="$("${DEST_BIN}" version | head -n 1)" success "Deployed binary active: ${DEPLOYED_VER}" fi echo -e "\n=================================================================" echo -e "\033[1;32m DEPLOYMENT COMPLETED SUCCESSFULLY!\033[0m" echo -e "=================================================================" echo " Installed Binary: ${DEST_BIN}" echo " Configuration: ${CONF_DIR}/config.toml" echo " Database: ${DATA_DIR}/nx9-wg.db" echo " Service Status: systemctl status nx9-wg" echo -e "=================================================================\n"