Merge pull request #407 from f3rs3n/fix/preserve-enterprise-repositories

fix: preserve configured PVE repositories during dependency checks
This commit is contained in:
MacRimi
2026-10-01 21:24:49 +02:00
committed by GitHub
10 changed files with 1621 additions and 231 deletions
+47
View File
@@ -0,0 +1,47 @@
#!/bin/bash
# Shared repository policy for package-install and safe-update flows.
# Proxmox's own repository API parses .list/.sources and edits individual
# entries. No shell rewriting of operator-maintained APT files.
repository_policy() {
python3 "$(dirname "${BASH_SOURCE[0]}")/repository_policy.py" "$@"
}
ensure_repositories() {
local version suite decision refresh_status
version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
case "$version" in
8) suite=bookworm ;;
9) suite=trixie ;;
*) msg_error "$(translate 'Unsupported or unknown Proxmox version; no repository changed.')"; return 1 ;;
esac
# Do not hide diagnostics or open a spinner during user interaction.
decision=$(repository_policy plan "$suite") || return 1
case "$decision" in
preserve) return 0 ;;
offer) ;;
*) msg_error "$(translate 'Repository policy returned an unexpected result.')"; return 1 ;;
esac
if ! declare -F hybrid_yesno >/dev/null || { [[ ! -t 0 ]] && ! { declare -F is_web_mode >/dev/null && is_web_mode; }; }; then
msg_error "$(translate 'No active subscription and no usable PVE repository. Noninteractive mode cannot change APT sources; configure them in Node > Updates > Repositories.')"
return 1
fi
if ! hybrid_yesno "$(translate 'Proxmox repository')" \
"$(translate 'This host has no active subscription, switch to the no-subscription repository? The inaccessible Enterprise PVE source will be disabled. Enterprise Ceph sources, if present, will be disabled without choosing a replacement Ceph channel; configure Ceph separately if needed.')" 16 90; then
msg_error "$(translate 'Repository switch declined; no APT source changed.')"
return 1
fi
decision=$(repository_policy apply "$suite") || return 1
[[ "$decision" == changed || "$decision" == preserve ]] || return 1
if [[ "$decision" == changed ]]; then
# Direct callers may install immediately; refresh their package indexes
# before returning. Preserve paths must not trigger an extra refresh.
if apt-get update; then
return 0
else
refresh_status=$?
msg_error "$(translate 'Repository sources changed, but APT package-list refresh failed. Operation stopped; sources were not rolled back. Inspect Node > Updates > Repositories and retry apt-get update before continuing.')"
return "$refresh_status"
fi
fi
return 0
}
+177
View File
@@ -0,0 +1,177 @@
#!/usr/bin/env python3
"""Proxmox repository policy via its own parsed APT repository API.
Only `notfound` or `expired` permits an offered switch. Never print raw subscription or
repository API responses: they can contain subscription keys or credentials.
"""
import copy
import json
import re
import subprocess
import sys
ENDPOINT = '/nodes/localhost/apt/repositories'
PVE_CHANNELS = {'pve-enterprise', 'pve-no-subscription', 'pve-test', 'pvetest'}
def run(args):
try:
return subprocess.run(args, check=True, capture_output=True, text=True).stdout
except (OSError, subprocess.CalledProcessError) as exc:
raise ValueError(f'Unable to query or change Proxmox repository state ({args[0]}). Check the service and retry.') from exc
def subscription_status():
# pvesubscription's CLI get printer emits sorted "key: value" lines,
# including a secret key and server ID. Only parse the exact status line.
output = run(['pvesubscription', 'get'])
statuses = re.findall(r'^status: ([a-z]+)$', output, re.MULTILINE)
if len(statuses) != 1 or statuses[0] not in ('active', 'notfound', 'expired'):
raise ValueError('Subscription status is not unambiguously active, notfound or expired. Check pvesubscription get locally; no repository changed.')
return statuses[0]
def repository_state(suite):
try:
data = json.loads(run(['pvesh', 'get', ENDPOINT, '--output-format', 'json']))
if not isinstance(data, dict) or not isinstance(data['files'], list) or not isinstance(data['errors'], list) or not isinstance(data['digest'], str) or not isinstance(data['standard-repos'], list):
raise ValueError()
if data['errors']:
raise ValueError()
entries = []
for file in data['files']:
for index, row in enumerate(file['repositories']):
if not isinstance(row, dict):
raise ValueError()
# Flat repositories (e.g. suite './') legitimately omit this.
row.setdefault('Components', [])
# Captured PVE 9.2 JSON uses integer 0/1; older fixtures use bool.
# Reject float/string coercions and retain the authoritative state.
if type(row['Enabled']) not in (bool, int) or row['Enabled'] not in (0, 1):
raise ValueError()
row['Enabled'] = bool(row['Enabled'])
if not all(
isinstance(row[k], list) and all(isinstance(value, str) for value in row[k])
for k in ('Types', 'URIs', 'Suites', 'Components')
):
raise ValueError()
# deb822 write/readback can insert Options/Enabled. It is
# redundant only when well-formed and consistent with Enabled;
# never discard the authoritative field or unrelated options.
options = row.get('Options', [])
if not isinstance(options, list):
raise ValueError()
remaining = []
seen_enabled = False
boolean_options = {'true': True, 'yes': True, '1': True,
'false': False, 'no': False, '0': False}
for option in options:
if not isinstance(option, dict) or not isinstance(option['Key'], str) \
or not isinstance(option['Values'], list) \
or not all(isinstance(value, str) for value in option['Values']):
raise ValueError()
if option['Key'].lower() != 'enabled':
remaining.append(option)
continue
if seen_enabled or set(option) != {'Key', 'Values'} or len(option['Values']) != 1:
raise ValueError()
value = boolean_options.get(option['Values'][0].lower())
if value is None or value != row['Enabled']:
raise ValueError()
seen_enabled = True
if remaining:
row['Options'] = remaining
else:
row.pop('Options', None)
entries.append((file['path'], index, row))
return data, entries
except (KeyError, TypeError, ValueError, IndexError) as exc:
raise ValueError('Proxmox APT repository inventory is invalid or reports parse errors. Fix sources in Node > Updates > Repositories; no repository changed.') from exc
def evaluate(suite, apply=False):
if suite not in ('bookworm', 'trixie'):
raise ValueError('Unsupported Proxmox suite; no repository changed.')
data, entries = repository_state(suite)
pve = []
ceph = []
debian = False
for path, index, row in entries:
if not row['Enabled'] or 'deb' not in row['Types']:
continue
components = set(row['Components'])
if components & PVE_CHANNELS:
if suite not in row['Suites']:
raise ValueError('PVE repository suite does not match the installed version; no repository changed.')
pve.append((path, index, row))
if 'main' in components and suite in row['Suites'] and not components & PVE_CHANNELS:
debian = True
if 'enterprise' in components and any('/ceph-' in uri for uri in row['URIs']):
if suite not in row['Suites']:
raise ValueError('Enterprise Ceph repository suite does not match this PVE version; correct it in the Proxmox repository UI before switching.')
ceph.append((path, index, row))
if any(set(row['Components']) & {'pve-no-subscription', 'pve-test', 'pvetest'} for _, _, row in pve):
return 'preserve'
# Entitlement lookup is only needed when considering a switch. Existing
# public/test channels are the operator's choice, even if lookup fails.
status = subscription_status() # fail closed before any write
if status == 'active':
if not pve:
raise ValueError('Host has an active subscription but no active PVE repository. Configure its Enterprise source in Node > Updates > Repositories; no repository changed.')
return 'preserve'
if not debian:
raise ValueError('No active Debian base repository for this suite; configure it in the Proxmox repository UI before continuing.')
# No active PVE source or only inaccessible Enterprise. A mixed stanza
# cannot be disabled without also disabling an unrelated component.
disable = [item for item in pve if 'pve-enterprise' in item[2]['Components']] + ceph
for _, _, row in disable:
expected = {'pve-enterprise'} if 'pve-enterprise' in row['Components'] else {'enterprise'}
if set(row['Components']) != expected:
raise ValueError('Enterprise shares an APT stanza with other components. Split it in the Proxmox repository UI; no repository changed.')
handles = [r.get('handle') for r in data['standard-repos'] if r.get('handle') == 'no-subscription']
if len(handles) != 1:
raise ValueError('Proxmox no-subscription standard repository handle unavailable; no repository changed.')
if not apply:
return 'offer'
# Adopt a refreshed digest only after checking the complete expected
# inventory: path/index targets are unsafe if another writer changed it.
expected = copy.deepcopy(data['files'])
try:
for path, index, original in disable:
next(file for file in expected if file['path'] == path)['repositories'][index]['Enabled'] = False
run(['pvesh', 'create', ENDPOINT, '--path', path, '--index', str(index),
'--enabled', '0', '--digest', data['digest']])
refreshed, current = repository_state(suite)
# Per-file digests change when Proxmox serializes the disabled entry.
inventory = lambda files: sorted(
({key: value for key, value in file.items() if key != 'digest'} for file in files),
key=lambda file: file['path'])
if inventory(refreshed['files']) != inventory(expected):
raise ValueError('Could not verify the expected repository inventory after Enterprise disable; possible concurrent edit. Inspect the Proxmox repository UI before retrying.')
data = refreshed
run(['pvesh', 'set', ENDPOINT, '--handle', 'no-subscription', '--digest', data['digest']])
_, current = repository_state(suite)
if not any(row['Enabled'] and 'deb' in row['Types'] and suite in row['Suites']
and 'pve-no-subscription' in row['Components'] for _, _, row in current) \
or any(p == path and i == index and row['Enabled']
for path, index, _ in disable for p, i, row in current):
raise ValueError('Could not verify the switched repositories; inspect the Proxmox repository UI before retrying.')
except ValueError as exc:
raise ValueError('Could not complete or verify the repository switch; changes may be partial and repository state is unknown. Inspect Node > Updates > Repositories before retrying; no automatic rollback was attempted.') from exc
return 'changed'
def main():
try:
if len(sys.argv) != 3 or sys.argv[1] not in ('plan', 'apply'):
raise ValueError('Usage: repository_policy.py plan|apply bookworm|trixie')
print(evaluate(sys.argv[2], apply=sys.argv[1] == 'apply'))
except ValueError as exc:
print(str(exc), file=sys.stderr)
return 1
return 0
if __name__ == '__main__':
sys.exit(main())
+2 -82
View File
@@ -12,88 +12,8 @@
# ==========================================================
# Ensure repositories are properly configured
# ==========================================================
ensure_repositories() {
local pve_version need_update=false
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
if [[ -z "$pve_version" ]]; then
msg_error "$(translate 'Unable to detect Proxmox version.')"
return 1
fi
if (( pve_version >= 9 )); then
# ===== PVE 9 (Debian 13 - trixie) =====
# proxmox.sources (no-subscription) - create if missing.
# chmod 0644 explicit on every new .sources file: under the default
# root umask 0027 the redirect would land at 0640, which the PVE 9
# webgui's repository manager treats as unparseable and silently
# hides the source — issue #230.
if [[ ! -f /etc/apt/sources.list.d/proxmox.sources ]]; then
cat > /etc/apt/sources.list.d/proxmox.sources <<'EOF'
Enabled: true
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 0644 /etc/apt/sources.list.d/proxmox.sources
need_update=true
fi
# debian.sources - create if missing
if [[ ! -f /etc/apt/sources.list.d/debian.sources ]]; then
cat > /etc/apt/sources.list.d/debian.sources <<'EOF'
Types: deb
URIs: http://deb.debian.org/debian/
Suites: trixie trixie-updates
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Types: deb
URIs: http://security.debian.org/debian-security/
Suites: trixie-security
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
EOF
chmod 0644 /etc/apt/sources.list.d/debian.sources
need_update=true
fi
else
# ===== PVE 8 (Debian 12 - bookworm) =====
local sources_file="/etc/apt/sources.list"
# Debian base (create or append minimal lines if missing)
if ! grep -qE 'deb .* bookworm .* main' "$sources_file" 2>/dev/null; then
{
echo "deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware"
echo "deb http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware"
echo "deb http://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware"
} >> "$sources_file"
need_update=true
fi
# Proxmox no-subscription list (classic) if missing
if [[ ! -f /etc/apt/sources.list.d/pve-no-subscription.list ]]; then
echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
> /etc/apt/sources.list.d/pve-no-subscription.list
need_update=true
fi
fi
# apt-get update only if needed or lists are empty
if [[ "$need_update" == true ]] || [[ ! -d /var/lib/apt/lists || -z "$(ls -A /var/lib/apt/lists 2>/dev/null)" ]]; then
msg_info "$(translate 'Updating APT package lists...')"
apt-get update >/dev/null 2>&1 || apt-get update
msg_ok "$(translate 'APT package lists updated')"
fi
return 0
}
# Repository policy shared with utility installers.
source "$(dirname "${BASH_SOURCE[0]}")/repository-functions.sh"
# ==========================================================
+25 -53
View File
@@ -9,36 +9,31 @@
# Description:
# Update path intended for a Proxmox host ALREADY in
# production. Unlike scripts/global/update-pve8.sh and
# update-pve9_2.sh (invoked by post_install), this variant
# NEVER modifies the operator's own configuration:
# update-pve9_2.sh (invoked by post_install), this variant preserves
# operator-maintained sources unless an unsubscribed host explicitly chooses
# to switch. Otherwise, the operator's source configuration is preserved:
#
# - Does NOT disable Enterprise / Ceph repositories
# - Does NOT delete legacy repo files
# - Does NOT silently disable Enterprise / Ceph repositories
# - Does NOT delete or deduplicate existing repo files
# - Does NOT overwrite proxmox.sources / debian.sources
# when they already exist
# - Does NOT purge alternative NTP services
# - Does NOT force-install zfsutils / chrony /
# proxmox-backup-restore-image
# - Does NOT write no-firmware-warnings.conf
#
# What it DOES:
# 1. Sanity checks (disk space, network)
# 2. ensure_repositories() — only when repos are MISSING
# 1. Sanity checks (disk space)
# 2. ensure_repositories() — check subscription and request consent if needed
# 3. apt-get update, with automatic GPG key import when apt
# reports NO_PUBKEY (any repo, user's or ours)
# 4. cleanup_duplicate_repos() — exact URL+Suite+Component
# match against proxmox.sources / debian.sources; leaves
# unrelated custom `download.proxmox.com/*` and
# user-authored pve-*.list files alone; backs each file
# up before modifying
# 5. Detect pending upgrades + security count
# 6. Confirmation dialog
# 7. apt-get full-upgrade with --force-confdef / --force-confold
# 4. Detect pending upgrades + security count
# 5. Confirmation dialog
# 6. apt-get full-upgrade with --force-confdef / --force-confold
# (never overwrites the operator's edited config files)
# 8. lvm_repair_check() — refreshes VG metadata when disks
# 7. lvm_repair_check() — refreshes VG metadata when disks
# passed through to guest VMs (DSM, TrueNAS, …) come back
# with old PV headers
# 9. apt-get autoremove + autoclean
# 8. apt-get autoremove + autoclean
#
# Reboot detection is handled by the caller (utilities/proxmox_update.sh).
# ==========================================================
@@ -66,6 +61,8 @@ source_install_functions() {
local f="$LOCAL_SCRIPTS/global/utils-install-functions.sh"
if [[ -f "$f" ]]; then
source "$f"
else
return 1
fi
}
@@ -91,8 +88,11 @@ update_pve_safe() {
local screen_capture="/tmp/proxmenux_screen_capture_$$.txt"
: > "$screen_capture"
download_common_functions
source_install_functions
if ! download_common_functions || ! source_install_functions; then
msg_error "$(translate 'Required update helpers unavailable. Update stopped.')"
rm -f "$screen_capture"
return 1
fi
{
msg_info2 "$(translate "Detected: Proxmox VE $pve_version — running safe update path")"
@@ -110,39 +110,14 @@ update_pve_safe() {
return 1
fi
# Reachability check: probe the public Proxmox repository over the
# transport apt is most likely to use. Many PVE installs use the
# official HTTP apt URI, while HTTPS may fail before apt ever runs
# if the CDN presents a certificate for another Proxmox hostname.
# Accept either transport and let apt-get update report repo-specific
# errors in the next step.
_repo_reachable() {
local url attempt
for url in "http://download.proxmox.com/" "https://download.proxmox.com/"; do
for attempt in 1 2; do
if curl -sfI --connect-timeout 5 --max-time 10 -o /dev/null "$url"; then
return 0
fi
[[ $attempt -eq 1 ]] && sleep 1
done
done
return 1
}
if ! _repo_reachable; then
msg_error "$(translate "Cannot reach download.proxmox.com. Check network, proxy or DNS.")"
echo -e
msg_success "$(translate "Press Enter to return to menu...")"
read -r
# Enterprise hosts and custom mirrors need not reach the public CDN.
# The configured sources, not that hostname, are checked by APT below.
if ! declare -F ensure_repositories >/dev/null 2>&1 || ! ensure_repositories; then
msg_error "$(translate 'Repository check failed. Update stopped.')"
rm -f "$screen_capture"
return 1
fi
# ── 2. ensure_repositories: adds base Proxmox+Debian repos only if
# they don't already exist. On a configured host this is a no-op. ──
if declare -f ensure_repositories >/dev/null 2>&1; then
ensure_repositories
fi
# ── 3. apt-get update with automatic key recovery ──
local update_output update_exit_code
update_output=$(apt-get update 2>&1)
@@ -191,11 +166,8 @@ update_pve_safe() {
fi
fi
# ── 4. Precise duplicate cleanup (exact URL+Suite+Component match,
# backs up files before modifying). Skipped if unavailable. ──
if declare -f cleanup_duplicate_repos >/dev/null 2>&1; then
cleanup_duplicate_repos
fi
# No duplicate-source rewrite here: even a seemingly duplicate entry may
# be operator-maintained. Only the consented switch above edits sources.
# ── 5-6. Detect + confirm ──
local current_pve_version available_pve_version upgradable security_updates
+3 -84
View File
@@ -39,95 +39,14 @@ PROXMENUX_UTILS=(
)
# Ensure APT repositories are configured for the current PVE version.
# Creates missing no-subscription repo entries for PVE8 (bookworm) or PVE9 (trixie).
# Shared journal helpers, so any script sourcing this file records what
# it installs without arranging for it.
# Shared journal helpers for utility installs.
if [[ -f "${LOCAL_SCRIPTS:-/usr/local/share/proxmenux/scripts}/global/pmx_journal.sh" ]]; then
source "${LOCAL_SCRIPTS:-/usr/local/share/proxmenux/scripts}/global/pmx_journal.sh"
fi
ensure_repositories() {
local FUNC_VERSION="1.0"
pmx_journal_context "ensure_repositories" "$FUNC_VERSION"
local pve_version need_update=false
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 >= 9 )); then
# ===== PVE 9 (Debian 13 - trixie) =====
# Force 0644 (world-readable) on every .sources file we drop.
# Under the default root umask 0027 the redirect would land at
# 0640, which the PVE 9 webgui's repository manager treats as
# unparseable and silently hides the source — issue #230.
if [[ ! -f /etc/apt/sources.list.d/proxmox.sources ]]; then
pmx_write_file /etc/apt/sources.list.d/proxmox.sources <<'EOF'
Enabled: true
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 0644 /etc/apt/sources.list.d/proxmox.sources
need_update=true
fi
if [[ ! -f /etc/apt/sources.list.d/debian.sources ]]; then
pmx_write_file /etc/apt/sources.list.d/debian.sources <<'EOF'
Types: deb
URIs: http://deb.debian.org/debian/
Suites: trixie trixie-updates
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Types: deb
URIs: http://security.debian.org/debian-security/
Suites: trixie-security
Components: main contrib non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
EOF
chmod 0644 /etc/apt/sources.list.d/debian.sources
need_update=true
fi
else
# ===== PVE 8 (Debian 12 - bookworm) =====
local sources_file="/etc/apt/sources.list"
if ! grep -qE 'deb .* bookworm .* main' "$sources_file" 2>/dev/null; then
{
echo "deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware"
echo "deb http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware"
echo "deb http://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware"
} | pmx_append_file "$sources_file"
need_update=true
fi
if [[ ! -f /etc/apt/sources.list.d/pve-no-subscription.list ]]; then
echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
| pmx_write_file /etc/apt/sources.list.d/pve-no-subscription.list
need_update=true
fi
fi
if [[ "$need_update" == true ]] || [[ ! -d /var/lib/apt/lists || -z "$(ls -A /var/lib/apt/lists 2>/dev/null)" ]]; then
msg_info "$(translate "Updating APT package lists...")"
apt-get update >/dev/null 2>&1 || apt-get update
# Spinner pair: msg_info must be closed before returning.
# Without this the next `msg_info` caller spawns a second
# spinner on top of ours and the original line never gets
# ✓'d — leaving a dangling progress char on screen.
msg_ok "$(translate "APT package lists updated")"
fi
return 0
}
# Shared subscription and consent policy for all utility callers.
source "$(dirname "${BASH_SOURCE[0]}")/repository-functions.sh"
# Install a single package and verify the resulting command is available.
+18 -12
View File
@@ -525,21 +525,24 @@ offer_lxc_updates_if_any() {
# ==========================================================
ensure_repos_and_headers() {
pmx_journal_context "ensure_repos_and_headers" "1.3" "nvidia_installer.sh"
# Bootstrap APT repos FIRST. On a fresh Proxmox install the
# pve-no-subscription / debian repos aren't configured by default
# → `pve-headers-$(uname -r)` and `build-essential` come back as
# "Unable to locate package" and the NVIDIA install bails out with
# "no cc found". We delegate to the shared helper (same one the
# post-install flow uses), which owns its own spinner pair — that's
# why this block has to run BEFORE we open our own msg_info.
# Check repositories before opening the headers spinner. A fresh ISO may
# have Enterprise enabled but no subscription; the shared policy asks the
# operator before switching. Refusal stops before driver removal.
if ! declare -F ensure_repositories >/dev/null 2>&1; then
local _utils_install="$LOCAL_SCRIPTS/global/utils-install-functions.sh"
[[ ! -f "$_utils_install" ]] && _utils_install="/usr/local/share/proxmenux/scripts/global/utils-install-functions.sh"
# shellcheck source=/dev/null
[[ -f "$_utils_install" ]] && source "$_utils_install"
fi
if declare -F ensure_repositories >/dev/null 2>&1; then
ensure_repositories >>"$LOG_FILE" 2>&1 || true
if ! declare -F ensure_repositories >/dev/null 2>&1; then
msg_error "$(translate 'Repository helper unavailable. NVIDIA installation stopped.')"
return 1
fi
# Keep the consent dialog and diagnostic visible, not just in the log.
ensure_repositories 2>&1 | tee -a "$LOG_FILE"
if (( PIPESTATUS[0] != 0 )); then
msg_error "$(translate 'Repository check failed. NVIDIA installation stopped.')"
return 1
fi
# Now own the spinner for the headers + build-tools check.
@@ -548,7 +551,10 @@ ensure_repos_and_headers() {
local kver
kver=$(uname -r)
apt-get update -qq >>"$LOG_FILE" 2>&1
if ! apt-get update -qq >>"$LOG_FILE" 2>&1; then
msg_error "$(translate 'APT update failed. Check repository access; NVIDIA installation stopped.')"
return 1
fi
if ! dpkg -s "pve-headers-$kver" >/dev/null 2>&1 && \
! dpkg -s "proxmox-headers-$kver" >/dev/null 2>&1; then
@@ -2184,7 +2190,7 @@ main() {
# Headers before anything else: the build check below needs them,
# and so does DKMS afterwards.
ensure_repos_and_headers
ensure_repos_and_headers || exit 1
installer=$(download_nvidia_installer "$DRIVER_VERSION")
local download_result=$?
@@ -2442,7 +2448,7 @@ auto_reinstall_from_state() {
# dialogs and confirmations.
echo "Reinstalling NVIDIA driver $DRIVER_VERSION non-interactively..." | tee -a "$LOG_FILE"
ensure_workdir
ensure_repos_and_headers >>"$LOG_FILE" 2>&1
ensure_repos_and_headers >>"$LOG_FILE" 2>&1 || return 2
blacklist_nouveau >>"$LOG_FILE" 2>&1
ensure_modules_config >>"$LOG_FILE" 2>&1