2025-08-14 19:39:12 +02:00
#!/bin/bash
# ==========================================================
# ProxMenuX - Upgrade PVE 8 → 9 (Simplified, per official guide)
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
# Version : 1.0
# Last Updated: 14/08/2025
# ==========================================================
REPO_URL = "https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
BASE_DIR = "/usr/local/share/proxmenux"
UTILS_FILE = " $BASE_DIR /utils.sh "
if [ [ -f " $UTILS_FILE " ] ] ; then
source " $UTILS_FILE "
fi
load_language
initialize_cache
# ==========================================================
LOG = " /var/log/pve8-a-pve9- $( date +%Y%m%d-%H%M%S) .log "
: > " $LOG "
REPO_MODE = "no-subscription"
DISABLE_AUDIT = "1"
ASSUME_YES = "0"
CEPH_ENFORCE = "auto"
for arg in " $@ " ; do
case " $arg " in
--enterprise) REPO_MODE = "enterprise" ; ;
--no-subscription) REPO_MODE = "no-subscription" ; ;
--skip-audit-disable) DISABLE_AUDIT = "0" ; ;
--assume-yes| -y) ASSUME_YES = "1" ; ;
--ignore-ceph-check) CEPH_ENFORCE = "skip" ; ;
--warn-ceph-check) CEPH_ENFORCE = "warn" ; ;
*) ; ;
esac
done
# ==========================================================
2025-08-14 20:03:47 +02:00
run_manual_guide( ) {
local url = " $REPO_URL /scripts/utilities/proxmox-upgrade-pve8-to-pve9-manual-guide.sh "
if command -v curl >/dev/null 2>& 1; then
bash <( curl -fsSL " $url " )
else
bash <( wget -qO- " $url " )
fi
}
2025-08-14 19:39:12 +02:00
ask_run_mode( ) {
if [ [ " ${ ASSUME_YES :- 0 } " = = "1" ] ] ; then
msg_ok " $( translate "Run mode: Unattended" ) "
export DEBIAN_FRONTEND = noninteractive
export APT_LISTCHANGES_FRONTEND = none
return 0
fi
2025-08-14 20:03:47 +02:00
while true; do
if command -v dialog >/dev/null 2>& 1; then
local title text choice status
title = " $( translate "Select run mode" ) " ; [ [ -z " $title " ] ] && title = "Select run mode"
text = " $( translate "Choose how to perform the upgrade:" ) " ; [ [ -z " $text " ] ] && text = "Choose how to perform the upgrade:"
title = ${ title // $'\r' / } ; title = ${ title // $'\n' / ' ' }
text = ${ text // $'\r' / } ; text = ${ text // $'\n' / ' ' }
choice = $(
dialog --backtitle "ProxMenux" \
--title " $title " \
--menu " $text " 17 78 3 \
1 " $( translate "Automatic/Unattended" ) " \
2 " $( translate "Interactive (guided, prompts visible)" ) " \
3 " $( translate "Manual upgrade guide step by step" ) " \
3>& 1 1>& 2 2>& 3
) ; status = $?
if [ [ $status -ne 0 ] ] ; then
2025-08-14 19:39:12 +02:00
show_proxmenux_logo || true
2025-08-14 20:03:47 +02:00
msg_warn " $( translate "Action canceled by user" ) "
exit 0
fi
case " $choice " in
1)
ASSUME_YES = "1"
show_proxmenux_logo || true
msg_title " $( translate "Upgrade assistant: Proxmox VE 8 → 9 (Trixie)" ) "
msg_info2 " $( translate "Run mode selected: Unattended" ) "
export DEBIAN_FRONTEND = noninteractive
export APT_LISTCHANGES_FRONTEND = none
break
; ;
3)
run_manual_guide
continue
; ;
*)
show_proxmenux_logo || true
msg_title " $( translate "Upgrade assistant: Proxmox VE 8 → 9 (Trixie)" ) "
msg_info2 " $( translate "Run mode selected: Interactive" ) "
break
; ;
esac
elif command -v whiptail >/dev/null 2>& 1; then
local choice
if ! choice = $(
whiptail --title " $( translate "Select run mode" ) " \
--menu " $( translate "Choose how to perform the upgrade:" ) " 17 78 3 \
"1" " $( translate "Automatic/Unattended" ) " \
"2" " $( translate "Interactive (guided, prompts visible)" ) " \
"3" " $( translate "Manual upgrade guide step by step" ) " \
3>& 1 1>& 2 2>& 3
) ; then
2025-08-14 19:39:12 +02:00
show_proxmenux_logo || true
2025-08-14 20:03:47 +02:00
msg_warn " $( translate "Action canceled by user" ) "
exit 0
fi
case " $choice " in
1)
ASSUME_YES = "1"
show_proxmenux_logo || true
msg_title " $( translate "Upgrade assistant: Proxmox VE 8 → 9 (Trixie)" ) "
msg_info2 " $( translate "Run mode selected: Unattended" ) "
export DEBIAN_FRONTEND = noninteractive
export APT_LISTCHANGES_FRONTEND = none
break
; ;
3)
run_manual_guide
continue
; ;
*)
show_proxmenux_logo || true
msg_title " $( translate "Upgrade assistant: Proxmox VE 8 → 9 (Trixie)" ) "
msg_info2 " $( translate "Run mode selected: Interactive" ) "
break
; ;
esac
else
printf "%s" " $( translate "Select run mode: [1]unattended / [2]interactive / [3]manual guide? (default: 2): " ) "
read -r ans
case " $ans " in
1)
ASSUME_YES = "1"
show_proxmenux_logo || true
msg_title " $( translate "Upgrade assistant: Proxmox VE 8 → 9 (Trixie)" ) "
msg_info2 " $( translate "Run mode selected: Unattended" ) "
export DEBIAN_FRONTEND = noninteractive
export APT_LISTCHANGES_FRONTEND = none
break
; ;
3)
run_manual_guide
continue
; ;
*)
show_proxmenux_logo || true
msg_title " $( translate "Upgrade assistant: Proxmox VE 8 → 9 (Trixie)" ) "
msg_info2 " $( translate "Run mode selected: Interactive" ) "
break
; ;
esac
2025-08-14 19:39:12 +02:00
fi
2025-08-14 20:03:47 +02:00
done
2025-08-14 19:39:12 +02:00
}
2025-08-14 20:03:47 +02:00
2025-08-14 19:39:12 +02:00
# ==========================================================
confirm( ) {
local prompt = " $1 "
if [ [ " $ASSUME_YES " = = "1" ] ] ; then
return 0
fi
if command -v whiptail >/dev/null 2>& 1; then
whiptail --title " $( translate "Confirmation" ) " --yesno " $prompt " 14 80
else
echo -n " $( translate " $prompt " ) [y/N]: "
read -r ans
[ [ " $ans " = ~ ^[ Yy] $ ] ]
fi
}
ask_choice_auto_or_manual( ) {
if [ [ " $ASSUME_YES " = = "1" ] ] ; then
echo "auto" ; return 0
fi
if command -v whiptail >/dev/null 2>& 1; then
if whiptail --title " $( translate "Cluster upgrade mode" ) " --yesno " $( translate "Pending upgrades detected on a clustered node.\n\nTo proceed safely, update this node to the latest Proxmox VE 8.x before switching to Trixie/PVE 9.\n\nSelect Yes for AUTOMATIC upgrade (recommended), or No for MANUAL instructions." ) " 16 78; then
echo "auto"
else
echo "manual"
fi
else
echo -n " $( translate "Pending upgrades detected on a clustered node. Perform AUTOMATIC upgrade now? (y = automatic, n = manual): " ) "
read -r ans
[ [ " $ans " = ~ ^[ Yy] $ ] ] && echo "auto" || echo "manual"
fi
}
run_step( ) {
local pre = " $1 " ; local ok = " $2 " ; local cmd = " $3 "
echo -ne " ${ TAB } ${ YW } $( translate " $pre " ) ${ CL } "
if bash -lc " set -o pipefail; $cmd 2>&1 | tee -a \" $LOG \" " ; then
echo -e " ${ BFR } ${ TAB } ${ CM } ${ GN } $( translate " $ok " ) ${ CL } "
return 0
else
echo -e " ${ BFR } ${ TAB } ${ RD } [ERROR] $( translate "Failed. See log:" ) $LOG ${ CL } "
exit 1
fi
}
run_step_interactive( ) {
local pre = " $1 " ; local ok = " $2 " ; shift 2
echo -ne " ${ TAB } ${ YW } $( translate " $pre " ) ${ CL } "
if " $@ " ; then
echo -e " ${ BFR } ${ TAB } ${ CM } ${ GN } $( translate " $ok " ) ${ CL } "
return 0
else
echo -e " ${ BFR } ${ TAB } ${ RD } [ERROR] $( translate "Failed. See log:" ) $LOG ${ CL } "
exit 1
fi
}
append_step( ) {
local pre = " $1 " ; local ok = " $2 " ; local cmd = " $3 "
echo -ne " ${ TAB } ${ YW } $( translate " $pre " ) ${ CL } "
if bash -lc " set -o pipefail; $cmd 2>&1 | tee -a \" $LOG \" " ; then
echo -e " ${ BFR } ${ TAB } ${ CM } ${ GN } $( translate " $ok " ) ${ CL } "
else
echo -e " ${ BFR } ${ TAB } ${ CM } ${ GN } $( translate " $ok " ) ${ CL } "
fi
}
file_contains( ) {
local file = " $1 "
local pattern = " $2 "
grep -qE " $pattern " " $file " 2>/dev/null
}
is_cluster_node( ) {
if command -v pvecm >/dev/null 2>& 1 && pvecm status >> " $LOG " 2>& 1; then
return 0
fi
return 1
}
is_hyperconverged_ceph_node( ) {
if command -v pveceph >/dev/null 2>& 1; then
if pveceph status >> " $LOG " 2>& 1; then
return 0
fi
fi
if systemctl list-units --type= service --all 2>/dev/null | grep -Eq 'ceph-(mon|osd|mgr)@' ; then
return 0
fi
if ls /var/lib/ceph/osd/ceph-* >/dev/null 2>& 1; then
return 0
fi
if [ [ -f /etc/pve/ceph.conf ] ] ; then
return 0
fi
return 1
}
# ==========================================================
create_pve_repo_enterprise_if_missing( ) {
local f = "/etc/apt/sources.list.d/pve-enterprise.sources"
[ [ -f " $f " ] ] && return 0
cat > " $f " << '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
chmod 644 " $f "
}
create_pve_repo_nosub_if_missing( ) {
local f = "/etc/apt/sources.list.d/proxmox.sources"
[ [ -f " $f " ] ] && return 0
cat > " $f " << '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
chmod 644 " $f "
}
create_ceph_repo_enterprise_if_missing( ) {
local f = "/etc/apt/sources.list.d/ceph.sources"
[ [ -f " $f " ] ] && return 0
cat > " $f " << '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
chmod 644 " $f "
}
create_ceph_repo_nosub_if_missing( ) {
local f = "/etc/apt/sources.list.d/ceph.sources"
[ [ -f " $f " ] ] && return 0
cat > " $f " << '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
chmod 644 " $f "
}
disable_enterprise_repo_if_present( ) {
local s = "/etc/apt/sources.list.d/pve-enterprise.sources"
local l = "/etc/apt/sources.list.d/pve-enterprise.list"
if [ [ -f " $s " ] ] ; then
if grep -qi '^Enabled:' " $s " ; then
sed -i 's/^Enabled:.*/Enabled: false/i' " $s "
else
echo "Enabled: false" >> " $s "
fi
fi
if [ [ -f " $l " ] ] ; then
sed -i 's/^[[:space:]]*deb/# deb/' " $l "
fi
}
comment_legacy_pve8_lists( ) {
for f in /etc/apt/sources.list.d/pve-public-repo.list /etc/apt/sources.list.d/pve-install-repo.list; do
[ [ -f " $f " ] ] || continue
sed -i 's/^[[:space:]]*deb/# deb/' " $f " || true
done
}
comment_legacy_ceph_list( ) {
local f = "/etc/apt/sources.list.d/ceph.list"
[ [ -f " $f " ] ] || return 0
sed -i 's/^[[:space:]]*deb/# deb/' " $f " || true
}
apt_update_with_repo_fallback( ) {
local output status
output = " $( apt-get update >/dev/null 2>& 1 | tee -a " $LOG " ) " ; status = ${ PIPESTATUS [0] }
if [ [ $status -eq 0 ] ] ; then
msg_ok2 " $( translate "APT indexes updated for Trixie" ) "
return 0
fi
if [ [ " $REPO_MODE " = = "enterprise" ] ] && { echo " $output " | grep -qE '401[[:space:]]+Unauthorized' || echo " $output " | grep -qi 'enterprise\.proxmox\.com' ; } ; then
if [ [ " $ASSUME_YES " = = "1" ] ] || confirm " $( translate "Enterprise repository returned 401 Unauthorized (no valid subscription). Switch to the no-subscription repository and retry?" ) " ; then
disable_enterprise_repo_if_present
create_pve_repo_nosub_if_missing
if [ [ -f /etc/apt/sources.list.d/ceph.sources ] ] ; then
create_ceph_repo_nosub_if_missing
fi
REPO_MODE = "no-subscription"
msg_ok2 " $( translate "Repositories switched to no-subscription" ) "
if apt-get update >> " $LOG " >/dev/null 2>& 1; then
msg_ok2 " $( translate "APT indexes updated for Trixie (no-subscription)" ) "
return 0
else
echo -e " ${ BFR } ${ TAB } ${ RD } [ERROR] $( translate "APT index update failed after switching to no-subscription. See log:" ) $LOG ${ CL } "
exit 1
fi
else
echo -e " ${ BFR } ${ TAB } ${ RD } [ERROR] $( translate "Enterprise repository unauthorized and fallback declined by user" ) $LOG ${ CL } "
exit 1
fi
fi
echo -e " ${ BFR } ${ TAB } ${ RD } [ERROR] $( translate "APT index update failed. See log:" ) $LOG ${ CL } "
exit 1
}
# ==========================================================
proxmox_repo_candidate_ok( ) {
local cand
cand = " $( apt-cache policy proxmox-ve 2>/dev/null | awk -F': ' '/Candidate:/{print $2}' ) "
[ [ -n " $cand " && " $cand " != "(none)" && " $cand " = ~ ^9 ] ]
}
simulate_would_remove_proxmox_ve( ) {
apt-get -s dist-upgrade >/dev/null 2>& 1 | grep -Eq 'Remv[[:space:]]+proxmox-ve|The following packages will be REMOVED:.*proxmox-ve'
}
guard_against_proxmox_ve_removal( ) {
msg_info " $( translate "Validating Proxmox 9 repositories (checking 'proxmox-ve' candidate)..." ) "
if proxmox_repo_candidate_ok; then
msg_ok " $( translate "Proxmox repository OK (candidate is 9.x)" ) "
else
if [ [ " $REPO_MODE " = = "enterprise" ] ] ; then
msg_warn " $( translate "Proxmox enterprise repo might be missing or inaccessible. Trying to switch to no-subscription..." ) "
disable_enterprise_repo_if_present
create_pve_repo_nosub_if_missing
REPO_MODE = "no-subscription"
apt_update_with_repo_fallback
if proxmox_repo_candidate_ok; then
msg_ok " $( translate "Proxmox repository fixed (no-subscription, candidate is 9.x)" ) "
else
msg_error " $( translate "Could not find a valid 'proxmox-ve' 9.x candidate after switching to no-subscription. Please verify your repository configuration and network, then retry." ) "
exit 1
fi
else
msg_error " $( translate "Invalid 'proxmox-ve' candidate (not 9.x or none). Please verify your repository configuration and network, then retry." ) "
exit 1
fi
fi
msg_info " $( translate "Running pre-upgrade simulation to verify 'proxmox-ve' will remain installed..." ) "
if simulate_would_remove_proxmox_ve; then
msg_error " $( translate "Pre-upgrade check FAILED: the simulation shows that 'proxmox-ve' would be REMOVED.\n This indicates a repository or dependency issue and upgrading now could break your Proxmox installation." ) "
echo " ---- $( translate "Recommended diagnostic commands" ) ---- "
echo "apt-cache policy proxmox-ve"
echo "apt policy | sed -n '1,120p'"
echo "cat /etc/apt/sources.list"
echo "ls -l /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources"
exit 1
else
msg_ok " $( translate "Pre-upgrade simulation passed: 'proxmox-ve' will be kept or upgraded safely." ) "
fi
}
# ==========================================================
check_not_web_terminal( ) {
[ [ " ${ PVE_ALLOW_WEBTERM :- 0 } " = "1" ] ] && return 0
[ [ -n " ${ SSH_CONNECTION :- } " ] ] && return 0
local pid ppid comm args i
pid = $$
show_web_terminal_block_msg( ) {
local title msg_line1 msg_line2 msg
title = " $( translate "Unsupported Terminal" ) "
msg_line1 = " $( translate "This script cannot be executed from the Proxmox web terminal." ) "
msg_line2 = " $( translate "Please use an SSH session (Linux, macOS, Windows/PuTTY) or a physical console to perform the upgrade." ) "
msg = " ${ msg_line1 } " $'\n\n' " ${ msg_line2 } "
whiptail --title " $title " --msgbox " $msg " 12 72
}
for i in { 1..12} ; do
ppid = $( ps -o ppid = -p " $pid " 2>/dev/null | tr -d ' ' )
[ [ -z " $ppid " || " $ppid " -le 1 ] ] && break
comm = $( ps -o comm = -p " $ppid " 2>/dev/null || true )
args = $( ps -o args = -p " $ppid " 2>/dev/null || true )
if echo " $comm $args " | grep -Eqi 'termproxy|vncshell' ; then
show_web_terminal_block_msg
msg_warn " $( translate "Upgrade canceled by user" ) "
exit 1
fi
pid = " $ppid "
done
}
ask_run_mode
check_not_web_terminal
export NCURSES_NO_UTF8_ACS = 1
show_upgrade_confirm( ) {
local title
title = " $( translate "Upgrade to Proxmox VE 9" ) "
local intro req l1 l2 l3 l4 cont
intro = " $( translate "This will upgrade this node to Proxmox VE 9 on Debian Trixie." ) "
req = " $( translate "REQUIREMENTS:" ) "
l1 = " $( translate "Valid backups for all VMs/CTs" ) "
l2 = " $( translate "Run from console, or SSH inside tmux/screen" ) "
l3 = " $( translate "Migrate away any guests that must keep running" ) "
l4 = ""
cont = " $( translate "Continue?" ) "
local bullet = "•"
if ! locale charmap 2>/dev/null | grep -qi 'utf-8' ; then
bullet = "-"
fi
local msg
msg = " ${ intro } " $'\n\n' " ${ req } " $'\n'
msg += " ${ bullet } ${ l1 } " $'\n'
msg += " ${ bullet } ${ l2 } " $'\n'
msg += " ${ bullet } ${ l3 } "
[ [ -n " $l4 " ] ] && msg += $'\n' " ${ bullet } ${ l4 } "
msg += $'\n\n' " ${ cont } "
local cols width height
cols = $( tput cols 2>/dev/null || echo 80)
width = $(( cols < 78 ? ( cols>40?cols-2:40) : 78 ))
height = 18
if whiptail --title " $title " --yesno " $msg " " $height " " $width " ; then
return 0
else
msg_warn " $( translate "Upgrade canceled by user" ) "
exit 0
fi
}
show_upgrade_confirm
# ---------------------------
# Step 1
# ---------------------------
apt_upgrade( ) {
local pve_version
pve_version = $( pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
if [ [ -z " $pve_version " ] ] ; then
msg_error "Unable to detect Proxmox version."
return 1
fi
if [ [ " $pve_version " -ge 9 ] ] ; then
msg_info2 " $( translate "Proxmox 9 system update allready" ) "
msg_success " $( translate "Press Enter to return to menu..." ) "
read -r
exit 1
else
bash <( curl -fsSL " $REPO_URL /scripts/global/update-pve8.sh " )
hash -r
fi
}
apt_upgrade
run_pve8to9_check( ) {
local tmp
tmp = " $( mktemp) "
echo -e
set -o pipefail
pve8to9 --full 2>& 1 | tee -a " $LOG " | tee " $tmp "
local rc = ${ PIPESTATUS [0] }
local fails warns
fails = $( grep -c 'FAIL:' " $tmp " || true )
warns = $( grep -c 'WARN:' " $tmp " || true )
if ( ( fails > 0 ) ) ; then
echo -e
echo -e " ${ BFR } ${ RD } [ERROR] $( translate "Pre-check found" ) $fails $( translate "blocking issue(s)." ) \n $( translate "Please resolve the problem(s) as described above, then re-run the upgrade script." ) ${ CL } "
echo -e
if grep -q 'systemd-boot meta-package installed' " $tmp " ; then
echo -e
echo -e " $( translate "Fix: remove the conflicting meta-package:" ) apt remove systemd-boot "
echo -e
fi
msg_success " $( translate "Press Enter to exit and repair" ) "
read -r
rm -f " $tmp "
exit 1
fi
echo -e
msg_ok " $( translate "Checklist pre-check finished. Warnings:" ) $warns "
rm -f " $tmp "
return $rc
}
run_pve8to9_check
# ---------------------------
# Step 2
# ---------------------------
run_step \
"" \
"Connectivity to Proxmox repository OK" \
"ping -c1 -W2 download.proxmox.com >/dev/null"
msg_info " $( translate "Checking free space in /var/cache/apt/archives..." ) "
FREE_MB = $( df /var/cache/apt/archives | awk 'NR==2 {print int($4/1024)}' )
if [ [ " ${ FREE_MB :- 0 } " -lt 1024 ] ] ; then
msg_error " $( translate "Insufficient space:" ) ${ FREE_MB } MB $( translate "(need ≥ 1024MB)" ) "
exit 1
else
msg_ok " $( translate "Free space OK:" ) ${ FREE_MB } MB "
fi
if [ [ " $DISABLE_AUDIT " = = "1" ] ] ; then
append_step \
"" \
"Audit socket disabled or not required" \
"systemctl disable --now systemd-journald-audit.socket >/dev/null 2>&1 || true"
fi
# ---------------------------
# Step 3
# ---------------------------
if command -v ceph >/dev/null 2>& 1; then
if [ [ " $CEPH_ENFORCE " = = "skip" ] ] ; then
msg_ok2 " $( translate "Ceph check skipped by user flag (--ignore-ceph-check)" ) "
else
if is_hyperconverged_ceph_node; then
msg_info " $( translate "Ceph detected as hyper-converged on this node. Checking version (require 19.x Squid)..." ) "
CEPH_V = $( ceph --version 2>/dev/null | head -1 || true )
if echo " $CEPH_V " | grep -Eq 'ceph.*(version 19|squid)' ; then
msg_ok " $( translate "Ceph version OK:" ) $CEPH_V "
else
if [ [ " $CEPH_ENFORCE " = = "warn" ] ] ; then
msg_warn " $( translate "Ceph is not 19.x (Squid). Proceeding due to --warn-ceph-check. Detected:" ) ${ CEPH_V :- N /A } "
else
msg_error " $( translate "Ceph is not 19.x (Squid). Upgrade Ceph first. Detected:" ) ${ CEPH_V :- N /A } "
exit 1
fi
fi
else
CEPH_V = $( ceph --version 2>/dev/null | head -1 || true )
echo -e " ${ BFR } ${ TAB } ${ CM } ${ GN } ${ CEPH_V :- N /A } ${ CL } "
fi
fi
fi
# ---------------------------
# Step 4
# ---------------------------
OS_FILE = "/etc/apt/sources.list"
if [ [ -f " $OS_FILE " ] ] ; then
msg_info " $( translate "Updating Debian Bookworm → Trixie in sources.list..." ) "
if sed -i 's/bookworm/trixie/g' " $OS_FILE " ; then
msg_ok " $( translate "sources.list updated to Trixie" ) "
else
msg_ok " $( translate "sources.list update skipped (no change)" ) "
fi
else
msg_ok " $( translate "No main sources.list present (skipped)" ) "
fi
PVE_ENT_LIST = "/etc/apt/sources.list.d/pve-enterprise.list"
msg_info " $( translate "Updating pve-enterprise.list (if present) to Trixie..." ) "
if [ [ -f " $PVE_ENT_LIST " ] ] ; then
if sed -i 's/bookworm/trixie/g' " $PVE_ENT_LIST " ; then
msg_ok " $( translate "pve-enterprise.list updated to Trixie" ) "
else
msg_ok " $( translate "pve-enterprise.list update skipped (no change)" ) "
fi
else
msg_ok " $( translate "No pve-enterprise.list present (skipped)" ) "
fi
msg_info " $( translate "Commenting any residual Bookworm lines in *.list..." ) "
for f in /etc/apt/sources.list.d/*.list; do
[ [ -f " $f " ] ] || continue
sed -i '/bookworm/s/^/# /' " $f " || true
done
sed -i '/bookworm/s/^/# /' " $OS_FILE " 2>/dev/null || true
msg_ok " $( translate "Residual Bookworm entries commented where applicable" ) "
# ---------------------------
# Step 5
# ---------------------------
if [ [ " $REPO_MODE " = = "enterprise" ] ] ; then
msg_info " $( translate "Ensuring pve-enterprise.sources (PVE 9, deb822) is present..." ) "
create_pve_repo_enterprise_if_missing
msg_ok " $( translate "Enterprise repository present" ) "
else
msg_info " $( translate "Ensuring proxmox.sources (PVE 9, no-subscription, deb822) is present..." ) "
create_pve_repo_nosub_if_missing
msg_ok " $( translate "No-subscription repository present" ) "
fi
msg_info " $( translate "Commenting legacy PVE 8 repository .list files (if any)..." ) "
comment_legacy_pve8_lists
msg_ok " $( translate "Legacy PVE 8 .list files commented or not present" ) "
# ---------------------------
# Step 6
# ---------------------------
if command -v ceph >/dev/null 2>& 1; then
if [ [ " $REPO_MODE " = = "enterprise" ] ] ; then
msg_info " $( translate "Ensuring ceph.sources (enterprise, Trixie) is present..." ) "
create_ceph_repo_enterprise_if_missing
msg_ok " $( translate "Ceph enterprise repository present" ) "
else
msg_info " $( translate "Ensuring ceph.sources (no-subscription, Trixie) is present..." ) "
create_ceph_repo_nosub_if_missing
msg_ok " $( translate "Ceph no-subscription repository present" ) "
fi
msg_info " $( translate "Commenting legacy ceph.list (if present)..." ) "
comment_legacy_ceph_list
msg_ok " $( translate "Legacy ceph.list commented or not present" ) "
fi
# ---------------------------
# Step 7
# ---------------------------
apt_update_with_repo_fallback
run_step \
"" \
"Repository verification completed (see log)" \
"apt policy >/dev/null 2>&1"
# ---------------------------
# Step 8
# ---------------------------
guard_against_proxmox_ve_removal
disable_translation_post_upgrade( ) {
translate( ) { echo " $1 " ; }
}
disable_translation_post_upgrade
# ==========================================================
# ---------------------------
# Step 9
# ---------------------------
if [ [ " $ASSUME_YES " = = "1" ] ] ; then
run_step \
"" \
"System upgraded to Trixie/PVE 9" \
"DEBIAN_FRONTEND=noninteractive APT_LISTCHANGES_FRONTEND=none apt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade"
else
run_step_interactive \
"" \
"System upgraded to Trixie/PVE 9" \
bash -lc "apt-get dist-upgrade"
fi
# ==========================================================
# ---------------------------
# Step 10
# ---------------------------
if [ [ -d /sys/firmware/efi ] ] ; then
if [ [ " $ASSUME_YES " = = "1" ] ] ; then
append_step \
"Installing grub-efi-amd64" \
"grub-efi-amd64 installed or already present" \
"apt-get install -y grub-efi-amd64 >/dev/null 2>&1"
else
run_step \
"Installing grub-efi-amd64" \
"grub-efi-amd64 installed or already present" \
"apt-get install grub-efi-amd64 >/dev/null 2>&1"
fi
fi
if command -v pve8to9 >/dev/null 2>& 1; then
sleep 2
append_step \
"" \
"Checklist post-upgrade finished" \
"pve8to9 --full"
fi
echo
echo
echo
echo "════════════════════════════════════════════════════════════════"
echo " UPGRADE TO PVE 9 COMPLETED (reboot required) "
echo "════════════════════════════════════════════════════════════════"
echo
echo
echo
2025-08-14 20:08:22 +02:00
msg_success " $( translate "Press Enter to continue..." ) "
read -r
2025-08-14 19:39:12 +02:00
if confirm "It is RECOMMENDED to reboot now to load the new kernel and services.\n\nReboot now?" ; then
echo -e
msg_warn " $( translate "Rebooting the system..." ) "
echo -e
reboot
else
msg_info2 " $( translate "You can reboot later manually." ) "
echo -e
2025-08-14 20:08:22 +02:00
msg_success " $( translate "Press Enter to exit" ) "
2025-08-14 19:39:12 +02:00
read -r
fi
exit 0