Last active
September 5, 2025 06:59
-
-
Save hypnguyen1209/b5bf7f256a7581cbcccbd32b18d9038c to your computer and use it in GitHub Desktop.
Script to upgrade Proxmox Virtual Environment version 8.4 to the latest version 9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -Eeuo pipefail | |
PVE_REPO_MODE="${PVE_REPO_MODE:-no-subscription}" # no-subscription | enterprise | |
ENABLE_CEPH="${ENABLE_CEPH:-auto}" # auto | true | false | |
DPKG_KEEP_LOCAL="${DPKG_KEEP_LOCAL:-true}" # true | false | |
AUTO_REBOOT="${AUTO_REBOOT:-true}" # true | false | |
WORKDIR="${WORKDIR:-/root/pve8-to-9-upgrade}" | |
mkdir -p "$WORKDIR" | |
log() { echo -e "\e[1;32m[+] $*\e[0m"; } | |
warn(){ echo -e "\e[1;33m[!] $*\e[0m"; } | |
err() { echo -e "\e[1;31m[✗] $*\e[0m" >&2; } | |
require_root() { | |
if [[ $EUID -ne 0 ]]; then | |
err "Root required" | |
exit 1 | |
fi | |
} | |
check_cmd() { command -v "$1" >/dev/null 2>&1; } | |
require_root | |
log "Check system requirements:" | |
pveversion -v | tee "$WORKDIR/pveversion-before.txt" || { | |
err "No pveversion found. Is this a PVE host?" | |
exit 1 | |
} | |
uname -a | tee "$WORKDIR/uname-before.txt" | |
if check_cmd pvecm && pvecm status >/dev/null 2>&1; then | |
warn "Nodes belong to cluster. Upgrade EACH NODE, and migrate/roll back workloads accordingly." | |
pvecm status | tee "$WORKDIR/pvecm-status.txt" || true | |
fi | |
export DEBIAN_FRONTEND=noninteractive | |
apt-get update -y || true | |
apt-get -o Dpkg::Options::="--force-confold" full-upgrade -y | |
PVE_MGR_VER="$(pveversion | awk -F'[ /]' '/pve-manager/{print $2}')" | |
if ! dpkg --compare-versions "$PVE_MGR_VER" ge "8.4.1"; then | |
err "pve-manager is now $PVE_MGR_VER (< 8.4.1). Please update and run the script." | |
exit 1 | |
fi | |
if ! check_cmd pve8to9; then | |
err "There is no pve8to9 command. Make sure PVE 8.4 is fully updated." | |
exit 1 | |
fi | |
if ! pve8to9 --full | tee "$WORKDIR/pve8to9-full.txt"; then | |
warn "pve8to9 returned non-zero code - check log $WORKDIR/pve8to9-full.txt" | |
fi | |
if grep -qE '^(ERROR|FATAL):' "$WORKDIR/pve8to9-full.txt"; then | |
err "There is an ERROR in the pve8to9 checklist. Please fix it and run again. (See $WORKDIR/pve8to9-full.txt)" | |
exit 1 | |
fi | |
USE_CEPH=false | |
if [[ "$ENABLE_CEPH" == "true" ]]; then | |
USE_CEPH=true | |
elif [[ "$ENABLE_CEPH" == "auto" ]]; then | |
if check_cmd ceph; then USE_CEPH=true; fi | |
fi | |
if $USE_CEPH; then | |
log "Detect Ceph. Check version (require Ceph 18.x Squid before upgrading PVE)." | |
if ceph --version | tee "$WORKDIR/ceph-version.txt" | grep -qE '\b15\b|\b16\b|\b17\b'; then | |
err "Ceph is not Squid 18.x yet. Upgrade Ceph to Squid 18.x first and then run again." | |
exit 1 | |
fi | |
fi | |
tar czf "$WORKDIR/backup-config.tgz" \ | |
/etc/pve \ | |
/etc/apt \ | |
/etc/network/interfaces \ | |
/etc/hosts \ | |
/etc/resolv.conf \ | |
|| warn "Backup configuration encountered warning (continue)." | |
apt-get install -y --no-install-recommends \ | |
proxmox-archive-keyring debian-archive-keyring ca-certificates | |
DEB_SOURCES_DIR="/etc/apt/sources.list.d" | |
DEB_SOURCES_FILE="$DEB_SOURCES_DIR/debian.sources" | |
mkdir -p "$DEB_SOURCES_DIR" | |
find /etc/apt -maxdepth 2 -type f -name '*.list' -print0 | while IFS= read -r -d '' f; do | |
if grep -qiE 'bookworm|debian|security|updates|backports' "$f"; then | |
cp -a "$f" "$f.bak.$(date +%F-%H%M%S)" | |
mv "$f" "$f.disabled" | |
echo "# moved to $f.disabled" > "$f" | |
fi | |
done | |
cat > "$DEB_SOURCES_FILE" <<'EOF' | |
Types: deb | |
URIs: https://deb.debian.org/debian | |
Suites: trixie trixie-updates | |
Components: main contrib non-free non-free-firmware | |
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg | |
Types: deb | |
URIs: https://security.debian.org/debian-security | |
Suites: trixie-security | |
Components: main contrib non-free non-free-firmware | |
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg | |
EOF | |
# Proxmox repo (deb822) | |
log "Configure Proxmox repo (${PVE_REPO_MODE})." | |
PVE_SOURCES_FILE="$DEB_SOURCES_DIR/proxmox.sources" | |
case "$PVE_REPO_MODE" in | |
"enterprise") | |
cat > "$PVE_SOURCES_FILE" <<'EOF' | |
Types: deb | |
URIs: https://enterprise.proxmox.com/debian/pve | |
Suites: trixie | |
Components: pve-enterprise | |
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg | |
EOF | |
;; | |
"no-subscription") | |
cat > "$PVE_SOURCES_FILE" <<'EOF' | |
Types: deb | |
URIs: http://download.proxmox.com/debian/pve | |
Suites: trixie | |
Components: pve-no-subscription | |
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg | |
EOF | |
;; | |
*) | |
err "Invalid PVE_REPO_MODE: $PVE_REPO_MODE" | |
exit 1 | |
;; | |
esac | |
find /etc/apt/sources.list.d -maxdepth 1 -type f -name 'pve-*.list' -o -name '*proxmox*.list' | while read -r f; do | |
cp -a "$f" "$f.bak.$(date +%F-%H%M%S)" || true | |
mv "$f" "$f.disabled" || true | |
done | |
if $USE_CEPH; then | |
CEPH_SOURCES_FILE="$DEB_SOURCES_DIR/ceph.sources" | |
if [[ "$PVE_REPO_MODE" == "enterprise" ]]; then | |
cat > "$CEPH_SOURCES_FILE" <<'EOF' | |
Types: deb | |
URIs: https://enterprise.proxmox.com/debian/ceph-squid | |
Suites: trixie | |
Components: enterprise | |
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg | |
EOF | |
else | |
cat > "$CEPH_SOURCES_FILE" <<'EOF' | |
Types: deb | |
URIs: http://download.proxmox.com/debian/ceph-squid | |
Suites: trixie | |
Components: no-subscription | |
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg | |
EOF | |
fi | |
if [[ -f /etc/apt/sources.list.d/ceph.list ]]; then | |
mv /etc/apt/sources.list.d/ceph.list{,.disabled} | |
fi | |
fi | |
if dpkg -l | awk '{print $2}' | grep -qx "linux-image-amd64"; then | |
warn "Detected linux-image-amd64 (installed on bare Debian). Removed to avoid meta-package conflicts." | |
apt-get remove -y linux-image-amd64 || true | |
fi | |
sed -i 's/^deb .*backports/# &/g' /etc/apt/sources.list || true | |
sed -i 's/^deb .*backports/# &/g' /etc/apt/sources.list.d/*.list 2>/dev/null || true | |
if grep -q 'glusterfs' /etc/pve/storage.cfg 2>/dev/null || dpkg -l | grep -qi gluster; then | |
err "GlusterFS detected in config/system. PVE 9 does not support it. Please migrate data and uninstall Gluster before upgrading." | |
exit 1 | |
fi | |
apt-get update | |
apt-cache policy | tee "$WORKDIR/apt-policy.txt" | |
DPKG_OPT="--force-confold" | |
$DPKG_KEEP_LOCAL || DPKG_OPT="--force-confnew" | |
apt-get -o Dpkg::Options::="$DPKG_OPT" \ | |
-o Dpkg::Options::="--force-confdef" \ | |
dist-upgrade -y | |
pveversion -v | tee "$WORKDIR/pveversion-after.txt" | |
uname -r | tee "$WORKDIR/uname-after.txt" | |
if ! pveversion | grep -q "pve-manager/9."; then | |
warn "No pve-manager/9.x found (may need reboot to complete)." | |
fi | |
warn "After reboot: Ctrl+Shift+R to refresh Web UI; check HA Rules (PVE 9 replaces HA groups)." | |
if [[ "$AUTO_REBOOT" == "true" ]]; then | |
warn "Will reboot in 10 seconds... (CTRL+C to cancel)" | |
sleep 10 | |
systemctl reboot | |
else | |
log "Upgrade complete. Manually reboot to run kernel 6.14 and finalize all changes." | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment