From d42f6da94aa4889483447a0e92572cc718b1e81e Mon Sep 17 00:00:00 2001 From: Sunil Thakares Date: Wed, 17 Jun 2026 19:00:41 +0530 Subject: [PATCH] feat: production deployment script with rollback and multi-arch support --- deploy.sh | 231 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 155 insertions(+), 76 deletions(-) diff --git a/deploy.sh b/deploy.sh index 75939fd..ea12979 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,11 +1,9 @@ #!/usr/bin/env bash - -# BZOD Deployment Script (Debian Native Deployment) -# This script sets up a secure, production-ready systemd service for BZOD. +# BZOD Production Deployment Script +# curl -fsSL https://bzo.in/deploy.sh | sudo bash set -euo pipefail -# Configurations SERVICE_USER="bzod" INSTALL_PATH="/usr/local/bin/bzod" CONFIG_DIR="/etc/bzod" @@ -13,93 +11,134 @@ DATA_DIR="/var/lib/bzod/data" ENV_FILE="${CONFIG_DIR}/bzod.env" SYSTEMD_UNIT="/etc/systemd/system/bzod.service" -# Color outputs RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' -NC='\033[0m' # No Color +NC='\033[0m' -echo -e "${BLUE}=== BZOD Debian Deployment Script ===${NC}" +# Temporary file cleanup +TMP_BINARY="" +cleanup() { + rm -f "${TMP_BINARY:-}" "${TMP_GHCR:-}" +} +trap cleanup EXIT + +echo -e "${BLUE}=== BZOD - Privacy-First URL Shortener & Landing Page Platform ===${NC}" +echo -e "Production deployment started...\n" -# 1. Check Root Privileges if [ "$EUID" -ne 0 ]; then - echo -e "${RED}Error: This script must be run as root (or via sudo).${NC}" + echo -e "${RED}Error: This script must be run as root (use sudo).${NC}" exit 1 fi -# 2. Install Package Dependencies -echo -e "\n${BLUE}[1/8] Installing system dependencies (SQLite, OpenSSL, Tar)...${NC}" -apt-get update +# 1. Install Base Dependencies +echo -e "${BLUE}[1/8] Installing base system dependencies...${NC}" +apt-get update -qq apt-get install -y openssl sqlite3 ca-certificates curl tar gzip -# 3. Compile Production Build Locally -echo -e "\n${BLUE}[2/8] Compiling release binary...${NC}" -if ! command -v cargo &> /dev/null; then - echo -e "${RED}Error: cargo not found. Please install Rust or copy a compiled 'bzod' binary to the current directory.${NC}" +# 2. Install Binary (safe atomic download) +echo -e "\n${BLUE}[2/8] Installing BZOD binary...${NC}" + +ARCH="$(uname -m)" +case $ARCH in + x86_64) BINARY_NAME="bzod-x86_64-unknown-linux-gnu" ;; + aarch64|arm64) BINARY_NAME="bzod-aarch64-unknown-linux-gnu" ;; + armv7l) BINARY_NAME="bzod-armv7-unknown-linux-gnueabihf" ;; + *) echo -e "${RED}Unsupported architecture: $ARCH${NC}"; exit 1 ;; +esac + +REPO="thakares/nx9-url-shortener" +RELEASE_URL="https://github.com/${REPO}/releases/latest/download/${BINARY_NAME}" + +TMP_BINARY=$(mktemp) + +echo "Trying GitHub Releases..." +if curl --retry 5 --retry-delay 2 --retry-connrefused \ + -L -f -o "${TMP_BINARY}" "${RELEASE_URL}" 2>/dev/null; then + echo -e "${GREEN}✓ Downloaded from GitHub Releases${NC}" +else + echo -e "${BLUE}GitHub Releases not available. Trying GHCR...${NC}" + if command -v docker >/dev/null 2>&1; then + TMP_GHCR=$(mktemp) + docker pull ghcr.io/${REPO}:latest >/dev/null 2>&1 || true + if docker run --rm --entrypoint cat ghcr.io/${REPO}:latest /usr/local/bin/bzod > "${TMP_GHCR}" 2>/dev/null && [ -s "${TMP_GHCR}" ]; then + mv "${TMP_GHCR}" "${TMP_BINARY}" + echo -e "${GREEN}✓ Extracted from GHCR${NC}" + fi + fi + + if [ ! -s "${TMP_BINARY}" ]; then + echo -e "${BLUE}Falling back to local build...${NC}" + if ! command -v cargo >/dev/null 2>&1; then + echo -e "${RED}Neither pre-built binary nor cargo available.${NC}" + exit 1 + fi + apt-get install -y pkg-config build-essential + cargo build --release + cp target/release/bzod "${TMP_BINARY}" + echo -e "${GREEN}✓ Built from source${NC}" + fi +fi + +# Atomic replace with backup +if [ -f "${INSTALL_PATH}" ]; then + cp "${INSTALL_PATH}" "${INSTALL_PATH}.bak" 2>/dev/null || true +fi + +install -m 755 "${TMP_BINARY}" "${INSTALL_PATH}" + +# Verify +if [ ! -x "${INSTALL_PATH}" ]; then + echo -e "${RED}Binary installation failed${NC}" exit 1 fi -cargo build --release -echo -e "${GREEN}Release build completed.${NC}" +"${INSTALL_PATH}" --version >/dev/null && echo -e "${GREEN}✓ Binary verified${NC}" || { + echo -e "${RED}Binary verification failed${NC}" + exit 1 +} -# 4. Install Binary -echo -e "\n${BLUE}[3/8] Installing binary to ${INSTALL_PATH}...${NC}" -cp target/release/bzod "${INSTALL_PATH}" -chmod 755 "${INSTALL_PATH}" -chown root:root "${INSTALL_PATH}" -echo -e "${GREEN}Binary installed successfully.${NC}" +# Show installed version +VERSION=$("${INSTALL_PATH}" --version 2>/dev/null | head -n1 || echo "unknown") +echo -e "${GREEN}✓ Installed ${VERSION} (${ARCH})${NC}" -# 5. Create Dedicated locked-down System User -echo -e "\n${BLUE}[4/8] Creating dedicated system user '${SERVICE_USER}'...${NC}" +# 3. Create System User +echo -e "\n${BLUE}[3/8] Creating system user '${SERVICE_USER}'...${NC}" if ! id -u "${SERVICE_USER}" &>/dev/null; then useradd -r -s /usr/sbin/nologin -m -d /var/lib/bzod "${SERVICE_USER}" - echo -e "${GREEN}System user '${SERVICE_USER}' created.${NC}" -else - echo "User '${SERVICE_USER}' already exists." fi -# 6. Configure Directory Trees and Permissions -echo -e "\n${BLUE}[5/8] Setting up configuration and data directories...${NC}" -mkdir -p "${CONFIG_DIR}" -mkdir -p "${DATA_DIR}" +# 4. Setup Directories +echo -e "\n${BLUE}[4/8] Setting up directories...${NC}" +mkdir -p "${CONFIG_DIR}" "${DATA_DIR}" +chown -R "${SERVICE_USER}:${SERVICE_USER}" "/var/lib/bzod" +chmod 700 "${CONFIG_DIR}" -# Copy .env file if it exists, otherwise prompt/generate -if [ -f .env ] && [ ! -f "${ENV_FILE}" ]; then - echo "Copying local .env file to ${ENV_FILE}..." - cp .env "${ENV_FILE}" -elif [ ! -f "${ENV_FILE}" ]; then - echo "Generating default configuration file at ${ENV_FILE}..." +# 5. Configuration (preserve on upgrades) +echo -e "\n${BLUE}[5/8] Configuration...${NC}" +if [ ! -f "${ENV_FILE}" ]; then + echo -e "${BLUE}Generating new secure configuration...${NC}" cat < "${ENV_FILE}" HOST=0.0.0.0 -PORT=8080 +PORT=8654 DATA_DIR=${DATA_DIR} COOKIE_SECURE=true +RUST_LOG=info SESSION_SECRET=$(openssl rand -hex 32) -ADMIN_USERNAME=admin -# SHA-256 for bootstrap (Default: admin) -ADMIN_PASSWORD_SHA256=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 -LINK_CHECK_INTERVAL_MINS=60 -AGGREGATION_INTERVAL_MINS=60 -DATA_RETENTION_DAYS=365 EOF + chmod 600 "${ENV_FILE}" + chown root:"${SERVICE_USER}" "${ENV_FILE}" +else + echo -e "${GREEN}Existing configuration preserved${NC}" fi -chmod 600 "${ENV_FILE}" -chown -R root:"${SERVICE_USER}" "${CONFIG_DIR}" -chown -R "${SERVICE_USER}":"${SERVICE_USER}" /var/lib/bzod -echo -e "${GREEN}Directories and permission parameters configured.${NC}" - -# 7. Initialise DB as the service user (avoids file permission conflicts) -echo -e "\n${BLUE}[6/8] Initialising databases...${NC}" -sudo -u "${SERVICE_USER}" "${INSTALL_PATH}" init-db --data-dir "${DATA_DIR}" -echo -e "${GREEN}Databases initialised.${NC}" - -# 8. Set Up Systemd Service -echo -e "\n${BLUE}[7/8] Installing systemd service unit...${NC}" +# 6. Systemd Service +echo -e "\n${BLUE}[6/8] Installing hardened systemd service...${NC}" cat < "${SYSTEMD_UNIT}" [Unit] -Description=BZOD - Personal URL Shortener & Landing Page Platform -After=network.target +Description=BZOD - Privacy-First URL Shortener & Landing Page Platform +After=network-online.target +Wants=network-online.target [Service] Type=simple @@ -107,11 +146,12 @@ User=${SERVICE_USER} Group=${SERVICE_USER} WorkingDirectory=/var/lib/bzod EnvironmentFile=${ENV_FILE} -ExecStart=${INSTALL_PATH} serve --host 0.0.0.0 --port 8080 --data-dir ${DATA_DIR} +ExecStart=${INSTALL_PATH} serve + Restart=on-failure RestartSec=5s -# Hardening / Sandboxing options for security +# Security Hardening ProtectSystem=strict ProtectHome=yes PrivateTmp=yes @@ -119,6 +159,10 @@ PrivateDevices=yes ProtectKernelTunables=yes ProtectKernelModules=yes ProtectControlGroups=yes +ProtectHostname=yes +RestrictSUIDSGID=yes +LockPersonality=yes +NoNewPrivileges=yes ReadWritePaths=/var/lib/bzod [Install] @@ -127,21 +171,56 @@ EOF chmod 644 "${SYSTEMD_UNIT}" systemctl daemon-reload -echo -e "${GREEN}Systemd service registered.${NC}" -# 9. Enable and Start the Service -echo -e "\n${BLUE}[8/8] Starting BZOD service...${NC}" -systemctl enable bzod -systemctl restart bzod +# 7. Initialize & Start +echo -e "\n${BLUE}[7/8] Initializing and starting service...${NC}" -sleep 2 -if systemctl is-active --quiet bzod; then - echo -e "${GREEN}BZOD service is running successfully!${NC}" - echo -e "\n${BLUE}=== Deployment Completed Successfully ===${NC}" - echo -e "You can access BZOD at http://localhost:8080" - echo -e "Admin Login Dashboard is at http://localhost:8080/admin" - echo -e "System service logs: journalctl -u bzod -f" - echo -e "To change the default admin password, run: bzod create-admin --data-dir ${DATA_DIR}" +if [ ! -f "${DATA_DIR}/content.db" ] && [ ! -f "${DATA_DIR}/admin.db" ] && [ ! -f "${DATA_DIR}/analytics.db" ]; then + runuser -u "${SERVICE_USER}" -- "${INSTALL_PATH}" init-db --data-dir "${DATA_DIR}" + echo -e "${GREEN}✓ Databases initialized${NC}" else - echo -e "${RED}Error: BZOD service failed to start. Check logs using: journalctl -u bzod -n 50${NC}" + echo -e "${GREEN}✓ Existing database detected (upgrade mode)${NC}" fi + +systemctl enable --now bzod + +# 8. Validation + Rollback +sleep 3 + +if ! systemctl is-active --quiet bzod; then + echo -e "${RED}Service failed to start! Rolling back...${NC}" + if [ -f "${INSTALL_PATH}.bak" ]; then + install -m 755 "${INSTALL_PATH}.bak" "${INSTALL_PATH}" + systemctl restart bzod || true + fi + journalctl -u bzod -n 50 --no-pager + exit 1 +fi + +# Clean up backup on success +rm -f "${INSTALL_PATH}.bak" 2>/dev/null || true + +# Soft health check +if command -v curl >/dev/null 2>&1; then + if curl -fsS http://127.0.0.1:8654/status >/dev/null 2>&1; then + echo -e "${GREEN}✓ HTTP health check passed${NC}" + else + echo -e "${BLUE}✓ Service is running (systemd healthy)${NC}" + fi +fi + +# Final Message +IP=$(hostname -I | awk '{print $1}' | head -n1) +echo -e "\n${GREEN}=== BZOD Deployed Successfully! ===${NC}" +echo -e "🌐 Web UI: http://${IP}:8654" +echo -e "🔑 Admin: http://${IP}:8654/admin" +echo -e "🖥 Architecture: ${ARCH}" +echo -e "📦 Version: ${VERSION}" +echo -e "\nNext step (first install):" +echo -e " sudo -u bzod bzod create-admin" +echo -e "\nCommands:" +echo -e " journalctl -u bzod -f" +echo -e " bzod doctor" +echo -e " systemctl status bzod" + +echo -e "\n${GREEN}Enjoy your lightweight, privacy-first, self-hosted URL shortener!${NC}" \ No newline at end of file