Skip to content

Instantly share code, notes, and snippets.

@VentByte
Last active October 19, 2026 18:21
Show Gist options
  • Select an option

  • Save VentByte/92dffbcd725b06e9a2db5c10ecd2f119 to your computer and use it in GitHub Desktop.

Select an option

Save VentByte/92dffbcd725b06e9a2db5c10ecd2f119 to your computer and use it in GitHub Desktop.
update-netbox.sh
#!/usr/bin/env bash
#
# update-netbox.sh
#
# Upgrade a NetBox installation (release archive or git checkout) following
# the official procedure:
# https://netboxlabs.com/docs/netbox/installation/upgrading/
#
# Steps performed:
# 1. Pre-flight checks (install method, versions, Python/PostgreSQL/Redis,
# ltree privileges, LDAP requirements, plugins, git working tree,
# data directory locations)
# 2. Backup of the database (pg_dump) and local files (config, media,
# scripts, reports)
# 3. Installation of the new release (archive download or git checkout)
# 4. Execution of upgrade.sh
# 5. Restart and verification of the NetBox services
# 6. Optional cleanup of old release directories (archive installs only)
#
# Usage:
# sudo ./update-netbox.sh [options]
#
# Options:
# -v, --version X.Y.Z Target version (default: latest stable release)
# -p, --python PATH Python interpreter for upgrade.sh (default: python3)
# -b, --backup-dir DIR Backup location (default: /var/netbox/backup)
# -k, --keep N Old release directories to keep (default: 2)
# -y, --yes Do not prompt for confirmation
# --skip-backup Skip the backup (not recommended)
# --check Run the pre-flight checks only, change nothing
# -h, --help Show this help
#
# Examples:
# sudo ./update-netbox.sh --check
# sudo ./update-netbox.sh -v 4.7.3
# sudo ./update-netbox.sh -y -p /usr/bin/python3.12
#
# Data directories:
# Media, scripts and reports are stored outside the NetBox project root,
# so they survive upgrades without copying. Set them in configuration.py:
# MEDIA_ROOT = '/var/netbox/media'
# SCRIPTS_ROOT = '/var/netbox/scripts'
# REPORTS_ROOT = '/var/netbox/reports'
# The paths are read from the NetBox configuration. The values below are
# the expected defaults: a different configured path produces a warning
# and the configured path is used. The script aborts if a data directory
# lies inside the project root of an archive install (data loss on switch).
set -Eeuo pipefail
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
NETBOX_ROOT="/opt/netbox"
NETBOX_USER="netbox"
SERVICES=(netbox netbox-rq)
GITHUB_REPO="netbox-community/netbox"
BACKUP_DIR="/var/netbox/backup"
LOG_DIR="/var/log/netbox"
# Expected data directories (configuration.py takes precedence)
MEDIA_DIR="/var/netbox/media"
SCRIPTS_DIR="/var/netbox/scripts"
REPORTS_DIR="/var/netbox/reports"
KEEP_RELEASES=2
PYTHON_BIN="python3"
TARGET_VERSION=""
ASSUME_YES=false
SKIP_BACKUP=false
CHECK_ONLY=false
# Minimum dependency versions per NetBox minor release, taken from the
# "Version History" table in the upgrade documentation.
# Format: "python_min postgresql_min redis_min"
# Extend this table when a new minor release ships.
declare -A REQS=(
[4.0]="3.10 12 4.0"
[4.1]="3.10 12 4.0"
[4.2]="3.10 13 4.0"
[4.3]="3.10 14 5.0"
[4.4]="3.10 14 5.0"
[4.5]="3.12 14 5.0"
[4.6]="3.12 14 5.0"
[4.7]="3.12 15 6.0"
)
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
LOG_FILE="${LOG_DIR}/upgrade-${TIMESTAMP}.log"
LOCK_FILE="/run/update-netbox.lock"
# Runtime state
METHOD=""
CURRENT_DIR=""
CURRENT_VERSION=""
NEW_DIR=""
DB_DUMP=""
FILES_BACKUP=""
CHANGES_STARTED=false
TMP_DIR=""
declare -A DB=()
declare -A PATHS=()
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
log() { printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*"; }
info() { log "INFO $*"; }
warn() { log "WARN $*" >&2; }
die() { log "ERROR $*" >&2; exit 1; }
usage() { sed -n '/^# Usage:/,/^$/p' "$0" | sed 's/^# \{0,1\}//'; exit 0; }
confirm() {
$ASSUME_YES && return 0
local reply
read -r -p "$1 [y/N] " reply
[[ $reply =~ ^[Yy]$ ]]
}
# ver_ge A B: true if version A >= version B
ver_ge() {
[[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" == "$2" ]]
}
minor_of() { echo "$1" | cut -d. -f1,2; }
major_of() { echo "$1" | cut -d. -f1; }
# Run Python code inside the NetBox Django context of the CURRENT installation.
nb_python() {
local code="import django
django.setup()
$1"
(
cd "$NETBOX_ROOT/netbox" &&
DJANGO_SETTINGS_MODULE=netbox.settings \
"$NETBOX_ROOT/venv/bin/python" -c "$code"
) 2>/dev/null
}
cleanup() {
[[ -n $TMP_DIR && -d $TMP_DIR ]] && rm -rf "$TMP_DIR"
return 0
}
on_error() {
local exit_code=$?
trap - ERR
warn "Upgrade failed (exit code ${exit_code}). Log: ${LOG_FILE}"
if $CHANGES_STARTED; then
warn "Changes were already applied. Roll back manually:"
warn " 1. Stop the services: systemctl stop ${SERVICES[*]}"
if [[ $METHOD == archive ]]; then
warn " 2. Restore the code: ln -sfn ${CURRENT_DIR} ${NETBOX_ROOT}"
else
warn " 2. Restore the code: git -C ${NETBOX_ROOT} checkout v${CURRENT_VERSION} && (cd ${NETBOX_ROOT} && PYTHON=${PYTHON_BIN} ./upgrade.sh)"
fi
if [[ -n $DB_DUMP ]]; then
warn " 3. Restore the DB (only if migrations ran):"
warn " pg_restore --clean --if-exists ${DB[host]:+-h ${DB[host]}} ${DB[port]:+-p ${DB[port]}} -U ${DB[user]} -d ${DB[name]} ${DB_DUMP}"
fi
warn " 4. Start the services: systemctl start ${SERVICES[*]}"
fi
exit "$exit_code"
}
# ---------------------------------------------------------------------------
# Argument parsing
# ---------------------------------------------------------------------------
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-v|--version) TARGET_VERSION="${2#v}"; shift 2 ;;
-p|--python) PYTHON_BIN="$2"; shift 2 ;;
-b|--backup-dir) BACKUP_DIR="$2"; shift 2 ;;
-k|--keep) KEEP_RELEASES="$2"; shift 2 ;;
-y|--yes) ASSUME_YES=true; shift ;;
--skip-backup) SKIP_BACKUP=true; shift ;;
--check) CHECK_ONLY=true; shift ;;
-h|--help) usage ;;
*) die "Unknown option: $1 (see --help)" ;;
esac
done
[[ $KEEP_RELEASES =~ ^[0-9]+$ ]] || die "--keep requires a number"
}
# ---------------------------------------------------------------------------
# Detection
# ---------------------------------------------------------------------------
detect_install() {
if [[ -L $NETBOX_ROOT ]]; then
METHOD="archive"
CURRENT_DIR="$(readlink -f "$NETBOX_ROOT")"
elif [[ -d $NETBOX_ROOT/.git ]]; then
METHOD="git"
CURRENT_DIR="$NETBOX_ROOT"
elif [[ -d $NETBOX_ROOT ]]; then
die "${NETBOX_ROOT} is neither a symlink (archive install) nor a git checkout. Python package installs are experimental and not supported by this script."
else
die "${NETBOX_ROOT} does not exist."
fi
[[ -x $NETBOX_ROOT/venv/bin/python ]] || die "Virtual environment not found in ${NETBOX_ROOT}/venv"
info "Install method: ${METHOD} (${CURRENT_DIR})"
}
detect_current_version() {
local release_file="$CURRENT_DIR/netbox/release.yaml"
if [[ -f $release_file ]]; then
CURRENT_VERSION="$(sed -nE 's/^version:[[:space:]]*"?([0-9]+\.[0-9]+\.[0-9]+)"?.*/\1/p' "$release_file" | head -n1)"
fi
if [[ -z $CURRENT_VERSION && $METHOD == archive ]]; then
CURRENT_VERSION="$(basename "$CURRENT_DIR" | sed -nE 's/^netbox-([0-9]+\.[0-9]+\.[0-9]+)$/\1/p')"
fi
if [[ -z $CURRENT_VERSION && $METHOD == git ]]; then
CURRENT_VERSION="$(git -C "$NETBOX_ROOT" describe --tags --abbrev=0 2>/dev/null | sed 's/^v//')"
fi
[[ -n $CURRENT_VERSION ]] || die "Unable to determine the installed NetBox version."
info "Installed version: ${CURRENT_VERSION}"
}
detect_target_version() {
if [[ -z $TARGET_VERSION ]]; then
TARGET_VERSION="$(curl -fsSL "https://api.github.com/repos/${GITHUB_REPO}/releases/latest" |
sed -nE 's/.*"tag_name":[[:space:]]*"v?([^"]+)".*/\1/p' | head -n1)"
[[ -n $TARGET_VERSION ]] || die "Unable to determine the latest release from GitHub. Specify it with --version."
fi
[[ $TARGET_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "Invalid target version: ${TARGET_VERSION}"
info "Target version: ${TARGET_VERSION}"
}
load_settings() {
local out
out="$(nb_python '
from django.conf import settings
d = settings.DATABASES["default"]
print("name=" + str(d.get("NAME") or ""))
print("user=" + str(d.get("USER") or ""))
print("host=" + str(d.get("HOST") or ""))
print("port=" + str(d.get("PORT") or ""))
print("password=" + str(d.get("PASSWORD") or ""))
print("media=" + str(getattr(settings, "MEDIA_ROOT", "") or ""))
print("scripts=" + str(getattr(settings, "SCRIPTS_ROOT", "") or ""))
print("reports=" + str(getattr(settings, "REPORTS_ROOT", "") or ""))
')" || die "Unable to load the NetBox configuration. Check ${NETBOX_ROOT}/netbox/netbox/configuration.py"
local line key value
while IFS= read -r line; do
key="${line%%=*}"
value="${line#*=}"
case $key in
name|user|host|port|password) DB[$key]="$value" ;;
media|scripts|reports) PATHS[$key]="$value" ;;
esac
done <<<"$out"
}
# ---------------------------------------------------------------------------
# Pre-flight checks
# ---------------------------------------------------------------------------
check_versions() {
if [[ $CURRENT_VERSION == "$TARGET_VERSION" ]]; then
info "NetBox is already at version ${TARGET_VERSION}. Nothing to do."
exit 0
fi
ver_ge "$TARGET_VERSION" "$CURRENT_VERSION" ||
die "Target ${TARGET_VERSION} is older than installed ${CURRENT_VERSION}. Downgrades are not supported."
if [[ $(major_of "$TARGET_VERSION") != "$(major_of "$CURRENT_VERSION")" ]]; then
die "Major version change (${CURRENT_VERSION} to ${TARGET_VERSION}). Upgrade to the latest minor release of v$(major_of "$CURRENT_VERSION") first, then rerun this script."
fi
}
check_dependencies() {
local reqs="${REQS[$(minor_of "$TARGET_VERSION")]:-}"
if [[ -z $reqs ]]; then
warn "No dependency data for NetBox $(minor_of "$TARGET_VERSION"). Verify Python, PostgreSQL and Redis versions manually and extend the REQS table."
return 0
fi
local py_min pg_min redis_min
read -r py_min pg_min redis_min <<<"$reqs"
# Python
local py_ver
py_ver="$("$PYTHON_BIN" -c 'import sys; print("%d.%d" % sys.version_info[:2])' 2>/dev/null)" ||
die "Python interpreter not usable: ${PYTHON_BIN}"
ver_ge "$py_ver" "$py_min" ||
die "Python ${py_ver} (${PYTHON_BIN}) is too old, NetBox ${TARGET_VERSION} requires >= ${py_min}. Use --python to select a newer interpreter."
info "Python ${py_ver}: OK (>= ${py_min})"
# PostgreSQL
local pg_num pg_major
pg_num="$(nb_python '
from django.db import connection
connection.ensure_connection()
print(connection.pg_version)
')" || die "Unable to connect to the database."
pg_major=$((pg_num / 10000))
ver_ge "$pg_major" "$pg_min" ||
die "PostgreSQL ${pg_major} is too old, NetBox ${TARGET_VERSION} requires >= ${pg_min}."
info "PostgreSQL ${pg_major}: OK (>= ${pg_min})"
# Redis (best effort, Sentinel setups are not detected)
local redis_ver
redis_ver="$(nb_python '
import redis
from django.conf import settings
c = settings.REDIS["tasks"]
r = redis.Redis(host=c.get("HOST", "localhost"), port=c.get("PORT", 6379),
username=c.get("USERNAME") or None, password=c.get("PASSWORD") or None,
ssl=c.get("SSL", False), socket_timeout=5)
print(r.info("server")["redis_version"])
')" || redis_ver=""
if [[ -z $redis_ver ]]; then
warn "Unable to determine the Redis version. Verify manually (>= ${redis_min})."
else
ver_ge "$redis_ver" "$redis_min" ||
die "Redis ${redis_ver} is too old, NetBox ${TARGET_VERSION} requires >= ${redis_min}."
info "Redis ${redis_ver}: OK (>= ${redis_min})"
fi
}
check_ltree() {
# NetBox 4.7+ requires the PostgreSQL ltree extension.
ver_ge "$TARGET_VERSION" "4.7.0" || return 0
local result has_ext can_create
result="$(nb_python '
from django.db import connection
with connection.cursor() as c:
c.execute("SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = %s), has_database_privilege(current_user, current_database(), %s)", ["ltree", "CREATE"])
a, b = c.fetchone()
print(a, b)
')" || die "Unable to check the ltree extension."
read -r has_ext can_create <<<"$result"
if [[ $has_ext == True ]]; then
info "ltree extension: present"
elif [[ $can_create == True ]]; then
info "ltree extension: missing, will be created by the migrations (CREATE privilege present)"
else
die "ltree extension missing and user '${DB[user]}' lacks CREATE on '${DB[name]}'. Run as postgres: GRANT CREATE ON DATABASE ${DB[name]} TO ${DB[user]};"
fi
}
check_local_config() {
local cfg_dir="$CURRENT_DIR/netbox/netbox"
local req_file="$CURRENT_DIR/local_requirements.txt"
[[ -f $cfg_dir/configuration.py ]] || die "configuration.py not found in ${cfg_dir}"
if [[ -f $cfg_dir/ldap_config.py ]]; then
if [[ -f $req_file ]] && grep -qiE '^[[:space:]]*django-auth-ldap' "$req_file"; then
info "LDAP: ldap_config.py present, django-auth-ldap listed in local_requirements.txt"
else
die "ldap_config.py exists but django-auth-ldap is missing from local_requirements.txt. upgrade.sh rebuilds the venv, so LDAP login would break."
fi
fi
local plugins
plugins="$(nb_python '
from django.conf import settings
print("\n".join(settings.PLUGINS))
')" || plugins=""
if [[ -n $plugins ]]; then
warn "Installed plugins (verify compatibility with NetBox ${TARGET_VERSION}):"
while IFS= read -r p; do warn " - ${p}"; done <<<"$plugins"
fi
if [[ -f $req_file ]]; then
info "local_requirements.txt:"
grep -vE '^[[:space:]]*(#|$)' "$req_file" | sed 's/^/ /' || true
fi
}
check_git_tree() {
[[ $METHOD == git ]] || return 0
if [[ -n $(git -C "$NETBOX_ROOT" status --porcelain --untracked-files=no) ]]; then
die "The git working tree in ${NETBOX_ROOT} has local modifications. Commit, stash or revert them first."
fi
git -C "$NETBOX_ROOT" fetch --tags --quiet
git -C "$NETBOX_ROOT" rev-parse --quiet --verify "v${TARGET_VERSION}^{commit}" >/dev/null ||
die "Tag v${TARGET_VERSION} not found in the repository."
}
check_archive_target() {
[[ $METHOD == archive ]] || return 0
NEW_DIR="$(dirname "$NETBOX_ROOT")/netbox-${TARGET_VERSION}"
[[ ! -e $NEW_DIR ]] || die "${NEW_DIR} already exists. Remove or rename it first."
}
# Resolve media, scripts and reports from the NetBox configuration and verify
# them. The configured path wins over the expected default. Data inside the
# project root of an archive install would be lost when the symlink switches
# to a new release, so that case aborts.
check_data_dirs() {
local key setting expected configured owner project_root
local failed=false
project_root="$(realpath -m "$CURRENT_DIR")"
for key in media scripts reports; do
case $key in
media) setting="MEDIA_ROOT"; expected="$MEDIA_DIR" ;;
scripts) setting="SCRIPTS_ROOT"; expected="$SCRIPTS_DIR" ;;
reports) setting="REPORTS_ROOT"; expected="$REPORTS_DIR" ;;
esac
if [[ -z ${PATHS[$key]} ]]; then
# Setting not defined by this NetBox release (e.g. REPORTS_ROOT).
# Fall back to the expected directory so it is still backed up.
if [[ -d $expected ]]; then
info "${setting} not defined by NetBox, using ${expected} for the backup"
PATHS[$key]="$expected"
else
info "${setting} not defined by NetBox and ${expected} does not exist, skipping"
fi
continue
fi
configured="$(realpath -m "${PATHS[$key]}")"
PATHS[$key]="$configured"
if [[ $configured != "$(realpath -m "$expected")" ]]; then
warn "${setting} is '${configured}' (expected default '${expected}'). Using the configured path."
fi
if [[ $METHOD == archive && $configured/ == "$project_root"/* ]]; then
warn "${setting} '${configured}' is inside the project root and would be lost on upgrade. Move it to ${expected} and set ${setting} = '${expected}' in configuration.py."
failed=true
continue
fi
if [[ ! -d $configured ]]; then
if [[ $key == reports ]]; then
info "${setting} directory ${configured} does not exist, skipping"
PATHS[$key]=""
continue
fi
warn "${setting} directory ${configured} does not exist."
failed=true
continue
fi
owner="$(stat -c %U "$configured")"
if [[ $owner != "$NETBOX_USER" ]]; then
warn "${configured} is owned by '${owner}', expected '${NETBOX_USER}'. Fix with: chown -R ${NETBOX_USER} ${configured}"
fi
info "${setting}: ${configured}"
done
if $failed; then
die "Data directory check failed. Fix the issues above and rerun the script."
fi
case "$(realpath -m "$BACKUP_DIR")/" in
"$(realpath -m "$CURRENT_DIR")"/*) die "Backup directory ${BACKUP_DIR} must not be inside the NetBox project root." ;;
esac
}
# ---------------------------------------------------------------------------
# Backup
# ---------------------------------------------------------------------------
backup() {
if $SKIP_BACKUP; then
warn "Backup skipped (--skip-backup). A rollback of the database will not be possible."
return 0
fi
[[ -d $BACKUP_DIR ]] || install -d -m 0700 "$BACKUP_DIR"
DB_DUMP="${BACKUP_DIR}/netbox-db-${CURRENT_VERSION}-${TIMESTAMP}.dump"
info "Dumping database '${DB[name]}' to ${DB_DUMP}"
if [[ -n ${DB[host]} ]]; then
PGPASSWORD="${DB[password]}" pg_dump -Fc \
-h "${DB[host]}" ${DB[port]:+-p "${DB[port]}"} \
-U "${DB[user]}" -d "${DB[name]}" -f "$DB_DUMP"
else
# No host configured: Django uses the local socket, use peer auth as postgres.
# The redirect runs as root on purpose, so the dump stays root-owned.
# shellcheck disable=SC2024
sudo -u postgres pg_dump -Fc -d "${DB[name]}" >"$DB_DUMP"
fi
chmod 0600 "$DB_DUMP"
FILES_BACKUP="${BACKUP_DIR}/netbox-files-${CURRENT_VERSION}-${TIMESTAMP}.tar.gz"
local items=()
local f
for f in \
"$CURRENT_DIR/netbox/netbox/configuration.py" \
"$CURRENT_DIR/netbox/netbox/ldap_config.py" \
"$CURRENT_DIR/local_requirements.txt" \
"$CURRENT_DIR/gunicorn.py" \
"${PATHS[media]}" "${PATHS[scripts]}" "${PATHS[reports]}"; do
[[ -n $f && -e $f ]] && items+=("$f")
done
info "Archiving local files to ${FILES_BACKUP}"
tar -czPf "$FILES_BACKUP" "${items[@]}"
chmod 0600 "$FILES_BACKUP"
}
# ---------------------------------------------------------------------------
# Installation
# ---------------------------------------------------------------------------
copy_if_exists() {
local src="$1" dst="$2"
[[ -e $src ]] && cp -p "$src" "$dst" && info "Copied $(basename "$src")"
return 0
}
install_archive() {
TMP_DIR="$(mktemp -d)"
local tarball="$TMP_DIR/netbox-${TARGET_VERSION}.tar.gz"
info "Downloading NetBox ${TARGET_VERSION}"
curl -fsSL -o "$tarball" "https://github.com/${GITHUB_REPO}/archive/refs/tags/v${TARGET_VERSION}.tar.gz"
tar -xzf "$tarball" -C "$(dirname "$NEW_DIR")"
[[ -d $NEW_DIR ]] || die "Extraction did not produce ${NEW_DIR}"
copy_if_exists "$CURRENT_DIR/local_requirements.txt" "$NEW_DIR/"
copy_if_exists "$CURRENT_DIR/netbox/netbox/configuration.py" "$NEW_DIR/netbox/netbox/"
copy_if_exists "$CURRENT_DIR/netbox/netbox/ldap_config.py" "$NEW_DIR/netbox/netbox/"
copy_if_exists "$CURRENT_DIR/gunicorn.py" "$NEW_DIR/"
# Media, scripts and reports live under /var/netbox (verified in
# check_data_dirs), so the new release needs no copy of them.
CHANGES_STARTED=true
ln -sfn "$NEW_DIR" "$NETBOX_ROOT"
info "Symlink ${NETBOX_ROOT} now points to ${NEW_DIR}"
}
install_git() {
CHANGES_STARTED=true
info "Checking out v${TARGET_VERSION}"
git -C "$NETBOX_ROOT" checkout --quiet "v${TARGET_VERSION}"
}
run_upgrade_script() {
info "Running upgrade.sh (this rebuilds the venv and applies migrations)"
(cd "$NETBOX_ROOT" && PYTHON="$PYTHON_BIN" ./upgrade.sh)
}
restart_services() {
info "Restarting ${SERVICES[*]}"
systemctl restart "${SERVICES[@]}"
sleep 5
local s
for s in "${SERVICES[@]}"; do
systemctl is-active --quiet "$s" || die "Service ${s} is not running. Check: journalctl -u ${s}"
info "Service ${s}: active"
done
}
cleanup_old_releases() {
[[ $METHOD == archive ]] || return 0
local parent active
parent="$(dirname "$NETBOX_ROOT")"
active="$(readlink -f "$NETBOX_ROOT")"
local old=()
local d
while IFS= read -r d; do
[[ $d != "$active" ]] && old+=("$d")
done < <(find "$parent" -maxdepth 1 -type d -name 'netbox-[0-9]*' | sort -V)
local remove_count=$(( ${#old[@]} - KEEP_RELEASES ))
(( remove_count > 0 )) || return 0
local to_remove=("${old[@]:0:remove_count}")
info "Old releases to remove (keeping the newest ${KEEP_RELEASES}):"
printf ' %s\n' "${to_remove[@]}"
if confirm "Remove these directories?"; then
rm -rf -- "${to_remove[@]}"
info "Old releases removed"
fi
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
main() {
parse_args "$@"
[[ $EUID -eq 0 ]] || die "Run this script as root (sudo)."
exec 9>"$LOCK_FILE"
flock -n 9 || die "Another upgrade is already running."
# Create the log directory only if missing, keep existing permissions
# (NetBox's own LOGGING configuration may write here as well).
[[ -d $LOG_DIR ]] || install -d -m 0750 "$LOG_DIR"
exec > >(tee -a "$LOG_FILE") 2>&1
trap on_error ERR
trap cleanup EXIT
local cmd
for cmd in curl tar sort git systemctl; do
command -v "$cmd" >/dev/null || die "Required command not found: ${cmd}"
done
$SKIP_BACKUP || command -v pg_dump >/dev/null || die "pg_dump not found (install postgresql-client)."
info "NetBox upgrade started, log: ${LOG_FILE}"
detect_install
detect_current_version
detect_target_version
load_settings
check_versions
check_dependencies
check_ltree
check_local_config
check_git_tree
check_archive_target
check_data_dirs
info "Review the release notes for every version since ${CURRENT_VERSION}:"
info " https://github.com/${GITHUB_REPO}/releases"
if $CHECK_ONLY; then
info "Pre-flight checks passed (--check). No changes made."
exit 0
fi
confirm "Upgrade NetBox from ${CURRENT_VERSION} to ${TARGET_VERSION}?" || die "Aborted by user."
backup
if [[ $METHOD == archive ]]; then install_archive; else install_git; fi
run_upgrade_script
restart_services
CHANGES_STARTED=false
info "NetBox upgraded from ${CURRENT_VERSION} to ${TARGET_VERSION}"
[[ -n $DB_DUMP ]] && info "Database backup: ${DB_DUMP}"
[[ -n $FILES_BACKUP ]] && info "File backup: ${FILES_BACKUP}"
cleanup_old_releases
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment