#!/usr/bin/env bash # Nexus production host preflight before applying a release tarball. # # Usage: # bash deploy/preflight-release-host.sh \ # --tarball /tmp/nexus-release-20260708.tar.gz \ # --sha256 a4eb8f35f0b98d40c1ad83117bfc652e7686ffc7d55f6bf90fcfd433a2ad6701 \ # --deploy-path /opt/nexus set -euo pipefail TARBALL="" EXPECTED_SHA256="" DEPLOY_PATH="${NEXUS_DEPLOY_PATH:-/opt/nexus}" MIN_FREE_MB="${NEXUS_RELEASE_MIN_FREE_MB:-1024}" info() { echo -e "\033[0;32m[INFO]\033[0m $*"; } warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; } error() { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; } usage() { sed -n '1,12p' "$0" } while [[ $# -gt 0 ]]; do case "$1" in --tarball) TARBALL="${2:-}" shift 2 ;; --sha256) EXPECTED_SHA256="${2:-}" shift 2 ;; --deploy-path) DEPLOY_PATH="${2:-}" shift 2 ;; --min-free-mb) MIN_FREE_MB="${2:-}" shift 2 ;; -h|--help) usage exit 0 ;; *) error "Unknown argument: $1" usage exit 2 ;; esac done check_cmd() { local cmd="$1" if command -v "$cmd" >/dev/null 2>&1; then info "command OK: $cmd" return 0 fi error "missing command: $cmd" return 1 } docker_cmd() { if docker "$@" 2>/dev/null; then return 0; fi sudo docker "$@" 2>/dev/null } detect_runtime() { if [[ -f "${DEPLOY_PATH}/docker/.env.prod" ]] && command -v docker >/dev/null 2>&1; then echo docker return fi if command -v supervisorctl >/dev/null 2>&1 && supervisorctl status nexus >/dev/null 2>&1; then echo supervisor return fi echo unknown } verify_tarball() { if [[ -z "$TARBALL" ]]; then warn "--tarball not provided; skipping tarball checksum/content checks" return 0 fi [[ -f "$TARBALL" ]] || { error "tarball not found: $TARBALL" return 1 } info "tarball exists: $TARBALL" if [[ -n "$EXPECTED_SHA256" ]]; then local actual actual="$(sha256sum "$TARBALL" | awk '{print $1}')" if [[ "$actual" != "$EXPECTED_SHA256" ]]; then error "SHA256 mismatch: expected=$EXPECTED_SHA256 actual=$actual" return 1 fi info "SHA256 OK: $actual" else warn "--sha256 not provided; checksum not verified" fi tar tzf "$TARBALL" nexus-release/server nexus-release/deploy nexus-release/web/app-v2 >/dev/null info "tarball required paths OK" } check_deploy_path() { if [[ -d "$DEPLOY_PATH" ]]; then info "deploy path exists: $DEPLOY_PATH" else warn "deploy path does not exist yet: $DEPLOY_PATH" fi if [[ -f "${DEPLOY_PATH}/.env" ]]; then info "runtime .env preserved path exists" else warn "${DEPLOY_PATH}/.env not found" fi if [[ -f "${DEPLOY_PATH}/docker/.env.prod" ]]; then info "docker/.env.prod exists" else warn "${DEPLOY_PATH}/docker/.env.prod not found" fi if [[ -d "${DEPLOY_PATH}/web/data" ]]; then info "web/data exists" else warn "${DEPLOY_PATH}/web/data not found" fi } check_disk_space() { local target free_mb target="$DEPLOY_PATH" [[ -e "$target" ]] || target="$(dirname "$DEPLOY_PATH")" free_mb="$(df -Pm "$target" | awk 'NR==2 {print $4}')" if [[ -z "$free_mb" ]]; then warn "unable to determine free disk space for $target" return 0 fi if (( free_mb < MIN_FREE_MB )); then error "low disk space on $target: ${free_mb}MB < ${MIN_FREE_MB}MB" return 1 fi info "disk free OK: ${free_mb}MB >= ${MIN_FREE_MB}MB" } check_runtime() { local runtime runtime="$(detect_runtime)" info "runtime detected: $runtime" case "$runtime" in docker) docker_cmd ps --format '{{.Names}}' | grep -E 'nexus-prod-nexus|nexus-nexus-1' >/dev/null \ && info "Nexus docker container found" \ || warn "Nexus docker container not found by known name pattern" ;; supervisor) supervisorctl status nexus || true ;; *) warn "runtime unknown; apply script may require manual restart" ;; esac } check_local_endpoint() { local port health app app_v2 port="$(grep -E '^NEXUS_PUBLISH_PORT=' "${DEPLOY_PATH}/docker/.env.prod" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\r" ' || true)" port="${port:-8600}" health="$(curl -s "http://127.0.0.1:${port}/health" 2>/dev/null || true)" app="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app/" 2>/dev/null || echo 000)" app_v2="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${port}/app-v2/" 2>/dev/null || echo 000)" echo " current /health -> ${health:-}" echo " current /app/ -> ${app}" echo " current /app-v2/ -> ${app_v2}" } main() { local failures=0 for cmd in tar sha256sum df awk grep sed curl; do check_cmd "$cmd" || failures=$((failures + 1)) done check_cmd rsync || failures=$((failures + 1)) verify_tarball || failures=$((failures + 1)) check_deploy_path check_disk_space || failures=$((failures + 1)) check_runtime check_local_endpoint if (( failures > 0 )); then error "preflight failed: ${failures} blocking issue(s)" return 1 fi info "preflight OK" } main "$@"