release: nexus btpanel session fix and app-v2
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env bash
|
||||
# Apply a Nexus release tarball on the production host.
|
||||
#
|
||||
# Usage:
|
||||
# sudo bash deploy/apply-release-bundle.sh \
|
||||
# --tarball /tmp/nexus-release-20260708.tar.gz \
|
||||
# --sha256 6474d58fdccde668b4c91d5e7454fa08e01e20660e3bcbb363eeadbb7c2346a1 \
|
||||
# --deploy-path /opt/nexus
|
||||
#
|
||||
# Validate only (no backup, no rsync, no rebuild):
|
||||
# bash deploy/apply-release-bundle.sh --validate-only \
|
||||
# --tarball /tmp/nexus-release-20260708.tar.gz \
|
||||
# --sha256 6474d58fdccde668b4c91d5e7454fa08e01e20660e3bcbb363eeadbb7c2346a1
|
||||
#
|
||||
# This script intentionally preserves runtime secrets and data:
|
||||
# - .env
|
||||
# - docker/.env.prod
|
||||
# - web/data
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TARBALL=""
|
||||
EXPECTED_SHA256=""
|
||||
DEPLOY_PATH="${NEXUS_DEPLOY_PATH:-/opt/nexus}"
|
||||
BACKUP_DIR="${NEXUS_BACKUP_DIR:-/opt/nexus-backups}"
|
||||
SKIP_REBUILD=0
|
||||
DRY_RUN=0
|
||||
VALIDATE_ONLY=0
|
||||
APPLY_TMP=""
|
||||
|
||||
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,26p' "$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
|
||||
;;
|
||||
--backup-dir)
|
||||
BACKUP_DIR="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--skip-rebuild)
|
||||
SKIP_REBUILD=1
|
||||
shift
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
shift
|
||||
;;
|
||||
--validate-only)
|
||||
VALIDATE_ONLY=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
error "Unknown argument: $1"
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
require_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || {
|
||||
error "Missing required command: $1"
|
||||
exit 127
|
||||
}
|
||||
}
|
||||
|
||||
require_runtime_cmds() {
|
||||
require_cmd tar
|
||||
require_cmd sha256sum
|
||||
if [[ "$VALIDATE_ONLY" == 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
require_cmd rsync
|
||||
require_cmd curl
|
||||
}
|
||||
|
||||
run() {
|
||||
echo "+ $*"
|
||||
if [[ "$DRY_RUN" == 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
"$@"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${APPLY_TMP:-}" && -d "$APPLY_TMP" ]]; then
|
||||
rm -rf "$APPLY_TMP"
|
||||
fi
|
||||
}
|
||||
|
||||
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_sha256() {
|
||||
if [[ -z "$EXPECTED_SHA256" ]]; then
|
||||
warn "No --sha256 provided; skipping checksum verification"
|
||||
return 0
|
||||
fi
|
||||
local actual
|
||||
actual="$(sha256sum "$TARBALL" | awk '{print $1}')"
|
||||
if [[ "$actual" != "$EXPECTED_SHA256" ]]; then
|
||||
error "SHA256 mismatch"
|
||||
error "expected: $EXPECTED_SHA256"
|
||||
error "actual: $actual"
|
||||
exit 1
|
||||
fi
|
||||
info "SHA256 OK: $actual"
|
||||
}
|
||||
|
||||
validate_extracted_tree() {
|
||||
local src="$1"
|
||||
for required in server deploy Dockerfile.prod frontend-v2 web/app-v2; do
|
||||
if [[ ! -e "${src}/${required}" ]]; then
|
||||
error "Release bundle missing required path: ${required}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
for forbidden in .env docker/.env.prod web/data .venv-py312-codex frontend-v2/node_modules 2025.2 =2025.2; do
|
||||
if [[ -e "${src}/${forbidden}" ]]; then
|
||||
error "Release bundle contains forbidden path: ${forbidden}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
info "Extracted release tree validation OK"
|
||||
}
|
||||
|
||||
backup_current() {
|
||||
if [[ ! -d "$DEPLOY_PATH" ]]; then
|
||||
warn "Deploy path does not exist yet: $DEPLOY_PATH"
|
||||
return 0
|
||||
fi
|
||||
local stamp backup
|
||||
stamp="$(date +%Y%m%d-%H%M%S)"
|
||||
backup="${BACKUP_DIR}/nexus-before-${stamp}.tar.gz"
|
||||
run mkdir -p "$BACKUP_DIR"
|
||||
info "Backup current deploy path -> $backup"
|
||||
run tar czf "$backup" \
|
||||
--exclude='./web/data' \
|
||||
--exclude='./.venv*' \
|
||||
--exclude='./venv' \
|
||||
--exclude='./frontend/node_modules' \
|
||||
--exclude='./frontend-v2/node_modules' \
|
||||
-C "$(dirname "$DEPLOY_PATH")" "$(basename "$DEPLOY_PATH")"
|
||||
}
|
||||
|
||||
sync_release() {
|
||||
local src="$1"
|
||||
run mkdir -p "$DEPLOY_PATH"
|
||||
info "Sync release tree -> $DEPLOY_PATH"
|
||||
run rsync -a --delete \
|
||||
--exclude='.env' \
|
||||
--exclude='docker/.env.prod' \
|
||||
--exclude='web/data' \
|
||||
"${src}/" "${DEPLOY_PATH}/"
|
||||
}
|
||||
|
||||
rebuild_or_restart() {
|
||||
if [[ "$SKIP_REBUILD" == 1 ]]; then
|
||||
warn "--skip-rebuild set; not rebuilding/restarting Nexus"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local runtime
|
||||
runtime="$(detect_runtime)"
|
||||
info "Runtime: $runtime"
|
||||
case "$runtime" in
|
||||
docker)
|
||||
if [[ -x "${DEPLOY_PATH}/deploy/nexus-1panel.sh" || -f "${DEPLOY_PATH}/deploy/nexus-1panel.sh" ]]; then
|
||||
run env NEXUS_ROOT="$DEPLOY_PATH" bash "${DEPLOY_PATH}/deploy/nexus-1panel.sh" upgrade --skip-git
|
||||
else
|
||||
error "Docker runtime detected but deploy/nexus-1panel.sh missing"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -f "${DEPLOY_PATH}/deploy/sync_webapp_to_container.sh" ]]; then
|
||||
run env NEXUS_ROOT="$DEPLOY_PATH" bash "${DEPLOY_PATH}/deploy/sync_webapp_to_container.sh"
|
||||
fi
|
||||
;;
|
||||
supervisor)
|
||||
run supervisorctl restart nexus
|
||||
;;
|
||||
*)
|
||||
error "Unknown runtime. Rebuild/restart manually."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
verify_local_endpoints() {
|
||||
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 " /health -> ${health}"
|
||||
echo " /app/ -> ${app}"
|
||||
echo " /app-v2/ -> ${app_v2}"
|
||||
if [[ "$health" != "ok" || "$app" != "200" || "$app_v2" != "200" ]]; then
|
||||
error "Local endpoint verification failed"
|
||||
exit 1
|
||||
fi
|
||||
info "Local endpoint verification OK"
|
||||
}
|
||||
|
||||
main() {
|
||||
if [[ -z "$TARBALL" ]]; then
|
||||
error "--tarball is required"
|
||||
usage
|
||||
exit 2
|
||||
fi
|
||||
[[ -f "$TARBALL" ]] || {
|
||||
error "Tarball not found: $TARBALL"
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_runtime_cmds
|
||||
|
||||
info "Tarball: $TARBALL"
|
||||
info "Deploy path: $DEPLOY_PATH"
|
||||
info "Backup dir: $BACKUP_DIR"
|
||||
verify_sha256
|
||||
|
||||
local extracted
|
||||
APPLY_TMP="$(mktemp -d /tmp/nexus-release-apply.XXXXXX)"
|
||||
trap cleanup EXIT
|
||||
run tar xzf "$TARBALL" -C "$APPLY_TMP"
|
||||
extracted="${APPLY_TMP}/nexus-release"
|
||||
validate_extracted_tree "$extracted"
|
||||
if [[ "$VALIDATE_ONLY" == 1 ]]; then
|
||||
info "Validate-only mode: tarball checksum and content validation passed"
|
||||
return 0
|
||||
fi
|
||||
backup_current
|
||||
sync_release "$extracted"
|
||||
rebuild_or_restart
|
||||
if [[ "$DRY_RUN" != 1 && "$SKIP_REBUILD" != 1 ]]; then
|
||||
verify_local_endpoints
|
||||
fi
|
||||
info "Release applied successfully"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user