mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-05 20:03:48 +00:00
Update repository and clean up
This commit is contained in:
@@ -1,720 +0,0 @@
|
||||
# base-packages.txt - Generated on 2025-05-15 21:15:29
|
||||
# Proxmox Version: pve-manager/8.4.1/ (running kernel: 6.8.12-9-pve)
|
||||
|
||||
adduser
|
||||
apparmor
|
||||
apt
|
||||
apt-listchanges
|
||||
apt-utils
|
||||
attr
|
||||
base-files
|
||||
base-passwd
|
||||
bash
|
||||
bash-completion
|
||||
bc
|
||||
bind9-dnsutils
|
||||
bind9-host
|
||||
bind9-libs
|
||||
binutils
|
||||
binutils-common
|
||||
binutils-x86-64-linux-gnu
|
||||
bridge-utils
|
||||
bsdextrautils
|
||||
bsd-mailx
|
||||
bsdutils
|
||||
btrfs-progs
|
||||
busybox
|
||||
bzip2
|
||||
ca-certificates
|
||||
ceph-common
|
||||
ceph-fuse
|
||||
chrony
|
||||
cifs-utils
|
||||
console-setup
|
||||
console-setup-linux
|
||||
coreutils
|
||||
corosync
|
||||
cpio
|
||||
criu
|
||||
cron
|
||||
cron-daemon-common
|
||||
cstream
|
||||
curl
|
||||
dash
|
||||
dbus
|
||||
dbus-bin
|
||||
dbus-daemon
|
||||
dbus-session-bus-common
|
||||
dbus-system-bus-common
|
||||
debconf
|
||||
debconf-i18n
|
||||
debian-archive-keyring
|
||||
debian-faq
|
||||
debianutils
|
||||
dialog
|
||||
diffutils
|
||||
dirmngr
|
||||
distro-info-data
|
||||
dmeventd
|
||||
dmidecode
|
||||
dmsetup
|
||||
doc-debian
|
||||
dosfstools
|
||||
dpkg
|
||||
dtach
|
||||
e2fsprogs
|
||||
ebtables
|
||||
efibootmgr
|
||||
eject
|
||||
ethtool
|
||||
faketime
|
||||
fdisk
|
||||
fdutils
|
||||
file
|
||||
findutils
|
||||
fontconfig
|
||||
fontconfig-config
|
||||
fonts-dejavu-core
|
||||
fonts-font-awesome
|
||||
fonts-font-logos
|
||||
fonts-glyphicons-halflings
|
||||
frr
|
||||
frr-pythontools
|
||||
fuse
|
||||
gcc-12-base
|
||||
gdisk
|
||||
genisoimage
|
||||
gettext-base
|
||||
glusterfs-client
|
||||
glusterfs-common
|
||||
gnupg
|
||||
gnupg-l10n
|
||||
gnupg-utils
|
||||
gnutls-bin
|
||||
gpg
|
||||
gpg-agent
|
||||
gpgconf
|
||||
gpgsm
|
||||
gpgv
|
||||
gpg-wks-client
|
||||
gpg-wks-server
|
||||
grep
|
||||
groff-base
|
||||
grub2-common
|
||||
grub-common
|
||||
grub-efi-amd64
|
||||
grub-efi-amd64-bin
|
||||
grub-efi-amd64-signed
|
||||
grub-pc-bin
|
||||
gzip
|
||||
hdparm
|
||||
hostname
|
||||
ifupdown2
|
||||
inetutils-telnet
|
||||
init
|
||||
initramfs-tools
|
||||
initramfs-tools-core
|
||||
init-system-helpers
|
||||
iproute2
|
||||
ipset
|
||||
iptables
|
||||
iputils-ping
|
||||
isc-dhcp-client
|
||||
isc-dhcp-common
|
||||
iso-codes
|
||||
jq
|
||||
kbd
|
||||
keyboard-configuration
|
||||
keyutils
|
||||
klibc-utils
|
||||
kmod
|
||||
krb5-locales
|
||||
ksm-control-daemon
|
||||
less
|
||||
libacl1
|
||||
libaio1
|
||||
libanyevent-http-perl
|
||||
libanyevent-perl
|
||||
libapparmor1
|
||||
libappconfig-perl
|
||||
libapt-pkg6.0
|
||||
libapt-pkg-perl
|
||||
libarchive13
|
||||
libargon2-1
|
||||
libasound2
|
||||
libasound2-data
|
||||
libassuan0
|
||||
libasyncns0
|
||||
libattr1
|
||||
libaudit1
|
||||
libaudit-common
|
||||
libauthen-pam-perl
|
||||
libavahi-client3
|
||||
libavahi-common3
|
||||
libavahi-common-data
|
||||
libbabeltrace1
|
||||
libbinutils
|
||||
libblas3
|
||||
libblkid1
|
||||
libbpf1
|
||||
libbrotli1
|
||||
libbsd0
|
||||
libbytes-random-secure-perl
|
||||
libbz2-1.0
|
||||
libc6
|
||||
libcairo2
|
||||
libcap2
|
||||
libcap2-bin
|
||||
libcap-ng0
|
||||
libc-ares2
|
||||
libc-bin
|
||||
libcbor0.8
|
||||
libcephfs2
|
||||
libcfg7
|
||||
libc-l10n
|
||||
libclone-perl
|
||||
libcmap4
|
||||
libcom-err2
|
||||
libcommon-sense-perl
|
||||
libconvert-asn1-perl
|
||||
libcorosync-common4
|
||||
libcpg4
|
||||
libcrypt1
|
||||
libcrypt-openssl-bignum-perl
|
||||
libcrypt-openssl-random-perl
|
||||
libcrypt-openssl-rsa-perl
|
||||
libcrypt-random-seed-perl
|
||||
libcryptsetup12
|
||||
libcrypt-ssleay-perl
|
||||
libctf0
|
||||
libctf-nobfd0
|
||||
libcurl3-gnutls
|
||||
libcurl4
|
||||
libdatrie1
|
||||
libdb5.3
|
||||
libdbi1
|
||||
libdbus-1-3
|
||||
libdebconfclient0
|
||||
libdevel-cycle-perl
|
||||
libdevmapper1.02.1
|
||||
libdevmapper-event1.02.1
|
||||
libdigest-hmac-perl
|
||||
libdouble-conversion3
|
||||
libdrm2
|
||||
libdrm-common
|
||||
libdw1
|
||||
libedit2
|
||||
libefiboot1
|
||||
libefivar1
|
||||
libelf1
|
||||
libencode-locale-perl
|
||||
libepoxy0
|
||||
libevent-2.1-7
|
||||
libevent-core-2.1-7
|
||||
libexpat1
|
||||
libext2fs2
|
||||
libfaketime
|
||||
libfdisk1
|
||||
libfdt1
|
||||
libffi8
|
||||
libfido2-1
|
||||
libfile-chdir-perl
|
||||
libfile-find-rule-perl
|
||||
libfile-listing-perl
|
||||
libfile-readbackwards-perl
|
||||
libfilesys-df-perl
|
||||
libflac12
|
||||
libfmt9
|
||||
libfontconfig1
|
||||
libfreetype6
|
||||
libfribidi0
|
||||
libfstrm0
|
||||
libfuse2
|
||||
libfuse3-3
|
||||
libgbm1
|
||||
libgcc-s1
|
||||
libgcrypt20
|
||||
libgdbm6
|
||||
libgdbm-compat4
|
||||
libgfapi0
|
||||
libgfchangelog0
|
||||
libgfrpc0
|
||||
libgfxdr0
|
||||
libglib2.0-0
|
||||
libglusterd0
|
||||
libglusterfs0
|
||||
libgmp10
|
||||
libgnutls30
|
||||
libgnutls-dane0
|
||||
libgnutlsxx30
|
||||
libgoogle-perftools4
|
||||
libgpg-error0
|
||||
libgprofng0
|
||||
libgraphite2-3
|
||||
libgssapi-krb5-2
|
||||
libgstreamer1.0-0
|
||||
libgstreamer-plugins-base1.0-0
|
||||
libharfbuzz0b
|
||||
libhogweed6
|
||||
libhtml-parser-perl
|
||||
libhtml-tagset-perl
|
||||
libhtml-tree-perl
|
||||
libhttp-cookies-perl
|
||||
libhttp-daemon-perl
|
||||
libhttp-date-perl
|
||||
libhttp-message-perl
|
||||
libhttp-negotiate-perl
|
||||
libibverbs1
|
||||
libicu72
|
||||
libidn2-0
|
||||
libinih1
|
||||
libio-html-perl
|
||||
libio-multiplex-perl
|
||||
libio-socket-ssl-perl
|
||||
libio-stringy-perl
|
||||
libip4tc2
|
||||
libip6tc2
|
||||
libipset13
|
||||
libiscsi7
|
||||
libisns0
|
||||
libjansson4
|
||||
libjemalloc2
|
||||
libjpeg62-turbo
|
||||
libjq1
|
||||
libjs-bootstrap
|
||||
libjs-extjs
|
||||
libjs-jquery
|
||||
libjson-c5
|
||||
libjson-glib-1.0-0
|
||||
libjson-glib-1.0-common
|
||||
libjson-perl
|
||||
libjson-xs-perl
|
||||
libjs-qrcodejs
|
||||
libjs-sencha-touch
|
||||
libk5crypto3
|
||||
libkeyutils1
|
||||
libklibc
|
||||
libkmod2
|
||||
libknet1
|
||||
libkrb5-3
|
||||
libkrb5support0
|
||||
libksba8
|
||||
libldap-2.5-0
|
||||
libldb2
|
||||
liblinear4
|
||||
liblinux-inotify2-perl
|
||||
liblmdb0
|
||||
liblocale-gettext-perl
|
||||
liblockfile1
|
||||
liblockfile-bin
|
||||
liblttng-ust1
|
||||
liblttng-ust-common1
|
||||
liblttng-ust-ctl5
|
||||
liblua5.3-0
|
||||
liblvm2cmd2.03
|
||||
liblwp-mediatypes-perl
|
||||
liblwp-protocol-https-perl
|
||||
liblz4-1
|
||||
liblzma5
|
||||
liblzo2-2
|
||||
libmagic1
|
||||
libmagic-mgc
|
||||
libmath-random-isaac-perl
|
||||
libmaxminddb0
|
||||
libmd0
|
||||
libmime-base32-perl
|
||||
libmnl0
|
||||
libmount1
|
||||
libmp3lame0
|
||||
libmpg123-0
|
||||
libncurses6
|
||||
libncursesw6
|
||||
libnet1
|
||||
libnetaddr-ip-perl
|
||||
libnet-dbus-perl
|
||||
libnet-dns-perl
|
||||
libnetfilter-conntrack3
|
||||
libnetfilter-log1
|
||||
libnet-http-perl
|
||||
libnet-ip-perl
|
||||
libnet-ldap-perl
|
||||
libnet-ssleay-perl
|
||||
libnet-subnet-perl
|
||||
libnettle8
|
||||
libnewt0.52
|
||||
libnfnetlink0
|
||||
libnfsidmap1
|
||||
libnftables1
|
||||
libnftnl11
|
||||
libnghttp2-14
|
||||
libnl-3-200
|
||||
libnl-route-3-200
|
||||
libnozzle1
|
||||
libnpth0
|
||||
libnsl2
|
||||
libnspr4
|
||||
libnss3
|
||||
libnss-systemd
|
||||
libnuma1
|
||||
libnumber-compare-perl
|
||||
libnvpair3linux
|
||||
liboath0
|
||||
libogg0
|
||||
libonig5
|
||||
libopeniscsiusr
|
||||
libopus0
|
||||
liborc-0.4-0
|
||||
libp11-kit0
|
||||
libpam0g
|
||||
libpam-modules
|
||||
libpam-modules-bin
|
||||
libpam-runtime
|
||||
libpam-systemd
|
||||
libpango-1.0-0
|
||||
libpangocairo-1.0-0
|
||||
libpangoft2-1.0-0
|
||||
libpcap0.8
|
||||
libpci3
|
||||
libpcre2-16-0
|
||||
libpcre2-8-0
|
||||
libpcre3
|
||||
libperl5.36
|
||||
libpipeline1
|
||||
libpixman-1-0
|
||||
libpng16-16
|
||||
libpopt0
|
||||
libposix-strptime-perl
|
||||
libproc2-0
|
||||
libprotobuf32
|
||||
libprotobuf-c1
|
||||
libproxmox-acme-perl
|
||||
libproxmox-acme-plugins
|
||||
libproxmox-backup-qemu0
|
||||
libproxmox-rs-perl
|
||||
libpsl5
|
||||
libpulse0
|
||||
libpve-access-control
|
||||
libpve-apiclient-perl
|
||||
libpve-cluster-api-perl
|
||||
libpve-cluster-perl
|
||||
libpve-common-perl
|
||||
libpve-guest-common-perl
|
||||
libpve-http-server-perl
|
||||
libpve-network-api-perl
|
||||
libpve-network-perl
|
||||
libpve-notify-perl
|
||||
libpve-rs-perl
|
||||
libpve-storage-perl
|
||||
libpve-u2f-server-perl
|
||||
libpython3.11-minimal
|
||||
libpython3.11-stdlib
|
||||
libpython3-stdlib
|
||||
libqb100
|
||||
libqrencode4
|
||||
libqt5core5a
|
||||
libqt5dbus5
|
||||
libqt5network5
|
||||
libquorum5
|
||||
librabbitmq4
|
||||
librados2
|
||||
librados2-perl
|
||||
libradosstriper1
|
||||
librbd1
|
||||
librdkafka1
|
||||
librdmacm1
|
||||
libreadline8
|
||||
libregexp-ipv6-perl
|
||||
librgw2
|
||||
librrd8
|
||||
librrds-perl
|
||||
librtmp1
|
||||
libsasl2-2
|
||||
libsasl2-modules-db
|
||||
libseccomp2
|
||||
libselinux1
|
||||
libsemanage2
|
||||
libsemanage-common
|
||||
libsepol2
|
||||
libslang2
|
||||
libslirp0
|
||||
libsmartcols1
|
||||
libsmbclient
|
||||
libsnappy1v5
|
||||
libsndfile1
|
||||
libsocket6-perl
|
||||
libspice-server1
|
||||
libsqlite3-0
|
||||
libss2
|
||||
libssh2-1
|
||||
libssl3
|
||||
libstatgrab10
|
||||
libstdc++6
|
||||
libstring-shellquote-perl
|
||||
libsubid4
|
||||
libsystemd0
|
||||
libsystemd-shared
|
||||
libtalloc2
|
||||
libtasn1-6
|
||||
libtcmalloc-minimal4
|
||||
libtdb1
|
||||
libtemplate-perl
|
||||
libterm-readline-gnu-perl
|
||||
libtevent0
|
||||
libtext-charwidth-perl
|
||||
libtext-glob-perl
|
||||
libtext-iconv-perl
|
||||
libtext-wrapi18n-perl
|
||||
libthai0
|
||||
libthai-data
|
||||
libthrift-0.17.0
|
||||
libtimedate-perl
|
||||
libtinfo6
|
||||
libtirpc3
|
||||
libtirpc-common
|
||||
libtpms0
|
||||
libtry-tiny-perl
|
||||
libtypes-serialiser-perl
|
||||
libu2f-server0
|
||||
libuchardet0
|
||||
libudev1
|
||||
libunbound8
|
||||
libunistring2
|
||||
libunwind8
|
||||
liburcu8
|
||||
liburing2
|
||||
liburi-perl
|
||||
libusb-1.0-0
|
||||
libusbredirparser1
|
||||
libuuid1
|
||||
libuuid-perl
|
||||
libuutil3linux
|
||||
libuv1
|
||||
libva2
|
||||
libva-drm2
|
||||
libvirglrenderer1
|
||||
libvorbis0a
|
||||
libvorbisenc2
|
||||
libvotequorum8
|
||||
libvulkan1
|
||||
libwayland-server0
|
||||
libwbclient0
|
||||
libwrap0
|
||||
libwww-perl
|
||||
libwww-robotrules-perl
|
||||
libx11-6
|
||||
libx11-data
|
||||
libx11-xcb1
|
||||
libxau6
|
||||
libxcb1
|
||||
libxcb-render0
|
||||
libxcb-shm0
|
||||
libxdmcp6
|
||||
libxext6
|
||||
libxml2
|
||||
libxml-libxml-perl
|
||||
libxml-namespacesupport-perl
|
||||
libxml-parser-perl
|
||||
libxml-sax-base-perl
|
||||
libxml-sax-perl
|
||||
libxml-twig-perl
|
||||
libxrender1
|
||||
libxslt1.1
|
||||
libxtables12
|
||||
libxxhash0
|
||||
libyaml-0-2
|
||||
libyaml-libyaml-perl
|
||||
libyang3
|
||||
libzfs4linux
|
||||
libzpool5linux
|
||||
libzstd1
|
||||
linux-base
|
||||
locales
|
||||
login
|
||||
logrotate
|
||||
logsave
|
||||
lsof
|
||||
lua-lpeg
|
||||
lvm2
|
||||
lxcfs
|
||||
lxc-pve
|
||||
lzop
|
||||
mailcap
|
||||
man-db
|
||||
manpages
|
||||
mawk
|
||||
media-types
|
||||
memtest86+
|
||||
mime-support
|
||||
mokutil
|
||||
mount
|
||||
nano
|
||||
ncurses-base
|
||||
ncurses-bin
|
||||
ncurses-term
|
||||
netbase
|
||||
netcat-traditional
|
||||
nfs-common
|
||||
nftables
|
||||
nmap
|
||||
nmap-common
|
||||
novnc-pve
|
||||
open-iscsi
|
||||
openssh-client
|
||||
openssh-server
|
||||
openssh-sftp-server
|
||||
openssl
|
||||
passwd
|
||||
pci.ids
|
||||
pciutils
|
||||
perl
|
||||
perl-base
|
||||
perl-modules-5.36
|
||||
perl-openssl-defaults
|
||||
pinentry-curses
|
||||
postfix
|
||||
procmail
|
||||
procps
|
||||
proxmox-archive-keyring
|
||||
proxmox-backup-client
|
||||
proxmox-backup-file-restore
|
||||
proxmox-backup-restore-image
|
||||
proxmox-default-kernel
|
||||
proxmox-firewall
|
||||
proxmox-grub
|
||||
proxmox-kernel-6.8
|
||||
proxmox-kernel-6.8.12-10-pve-signed
|
||||
proxmox-kernel-6.8.12-9-pve-signed
|
||||
proxmox-kernel-helper
|
||||
proxmox-mail-forward
|
||||
proxmox-mini-journalreader
|
||||
proxmox-offline-mirror-docs
|
||||
proxmox-offline-mirror-helper
|
||||
proxmox-termproxy
|
||||
proxmox-ve
|
||||
proxmox-websocket-tunnel
|
||||
proxmox-widget-toolkit
|
||||
psmisc
|
||||
pv
|
||||
pve-cluster
|
||||
pve-container
|
||||
pve-docs
|
||||
pve-edk2-firmware
|
||||
pve-edk2-firmware-legacy
|
||||
pve-edk2-firmware-ovmf
|
||||
pve-esxi-import-tools
|
||||
pve-firewall
|
||||
pve-firmware
|
||||
pve-ha-manager
|
||||
pve-i18n
|
||||
pve-lxc-syscalld
|
||||
pve-manager
|
||||
pve-qemu-kvm
|
||||
pve-xtermjs
|
||||
python3
|
||||
python3.11
|
||||
python3.11-minimal
|
||||
python3.11-venv
|
||||
python3-apt
|
||||
python3-ceph-argparse
|
||||
python3-ceph-common
|
||||
python3-cephfs
|
||||
python3-certifi
|
||||
python3-chardet
|
||||
python3-charset-normalizer
|
||||
python3-debconf
|
||||
python3-debian
|
||||
python3-debianbts
|
||||
python3-distutils
|
||||
python3-httplib2
|
||||
python3-idna
|
||||
python3-jwt
|
||||
python3-lib2to3
|
||||
python3-minimal
|
||||
python3-pip-whl
|
||||
python3-pkg-resources
|
||||
python3-prettytable
|
||||
python3-protobuf
|
||||
python3-pycurl
|
||||
python3-pyparsing
|
||||
python3-pysimplesoap
|
||||
python3-pyvmomi
|
||||
python3-rados
|
||||
python3-rbd
|
||||
python3-reportbug
|
||||
python3-requests
|
||||
python3-rgw
|
||||
python3-setuptools
|
||||
python3-setuptools-whl
|
||||
python3-six
|
||||
python3-systemd
|
||||
python3-urllib3
|
||||
python3-venv
|
||||
python3-wcwidth
|
||||
python3-yaml
|
||||
python-apt-common
|
||||
qemu-server
|
||||
qrencode
|
||||
readline-common
|
||||
reportbug
|
||||
rpcbind
|
||||
rrdcached
|
||||
rsync
|
||||
runit-helper
|
||||
samba-common
|
||||
samba-libs
|
||||
sed
|
||||
sensible-utils
|
||||
sgml-base
|
||||
shared-mime-info
|
||||
shim-helpers-amd64-signed
|
||||
shim-signed
|
||||
shim-signed-common
|
||||
shim-unsigned
|
||||
smartmontools
|
||||
smbclient
|
||||
socat
|
||||
spiceterm
|
||||
spl
|
||||
sqlite3
|
||||
ssh
|
||||
ssl-cert
|
||||
strace
|
||||
swtpm
|
||||
swtpm-libs
|
||||
swtpm-tools
|
||||
systemd
|
||||
systemd-boot
|
||||
systemd-boot-efi
|
||||
systemd-sysv
|
||||
sysvinit-utils
|
||||
tar
|
||||
tasksel
|
||||
tasksel-data
|
||||
tcpdump
|
||||
thin-provisioning-tools
|
||||
time
|
||||
traceroute
|
||||
tzdata
|
||||
ucf
|
||||
udev
|
||||
uidmap
|
||||
usbutils
|
||||
usrmerge
|
||||
util-linux
|
||||
util-linux-extra
|
||||
vim-common
|
||||
vim-tiny
|
||||
virtiofsd
|
||||
vncterm
|
||||
wamerican
|
||||
wget
|
||||
whiptail
|
||||
xfsprogs
|
||||
xkb-data
|
||||
xsltproc
|
||||
xz-utils
|
||||
zfs-initramfs
|
||||
zfsutils-linux
|
||||
zfs-zed
|
||||
zlib1g
|
||||
zstd
|
||||
@@ -1,368 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenux - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script allows users to assign physical disks to existing
|
||||
# Proxmox virtual machines (VMs) through an interactive menu.
|
||||
# - Detects the system disk and excludes it from selection.
|
||||
# - Lists all available VMs for the user to choose from.
|
||||
# - Identifies and displays unassigned physical disks.
|
||||
# - Allows the user to select multiple disks and attach them to a VM.
|
||||
# - Supports interface types: SATA, SCSI, VirtIO, and IDE.
|
||||
# - Ensures that disks are not already assigned to active VMs.
|
||||
# - Warns about disk sharing between multiple VMs to avoid data corruption.
|
||||
# - Configures the selected disks for the VM and verifies the assignment.
|
||||
#
|
||||
# The goal of this script is to simplify the process of assigning
|
||||
# physical disks to Proxmox VMs, reducing manual configurations
|
||||
# and preventing potential errors.
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration ============================================
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
load_language
|
||||
initialize_cache
|
||||
show_proxmenux_logo
|
||||
# ==========================================================
|
||||
|
||||
|
||||
|
||||
get_disk_info() {
|
||||
local disk=$1
|
||||
MODEL=$(lsblk -dn -o MODEL "$disk" | xargs)
|
||||
SIZE=$(lsblk -dn -o SIZE "$disk" | xargs)
|
||||
echo "$MODEL" "$SIZE"
|
||||
}
|
||||
|
||||
|
||||
VM_LIST=$(qm list | awk 'NR>1 {print $1, $2}')
|
||||
if [ -z "$VM_LIST" ]; then
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No VMs available in the system.")" 8 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
VMID=$(whiptail --title "$(translate "Select VM")" --menu "$(translate "Select the VM to which you want to add disks:")" 15 60 8 $VM_LIST 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$VMID" ]; then
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No VM was selected.")" 8 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VMID=$(echo "$VMID" | tr -d '"')
|
||||
|
||||
|
||||
msg_ok "$(translate "VM selected successfully.")"
|
||||
|
||||
|
||||
VM_STATUS=$(qm status "$VMID" | awk '{print $2}')
|
||||
if [ "$VM_STATUS" == "running" ]; then
|
||||
whiptail --title "$(translate "Warning")" --msgbox "$(translate "The VM is powered on. Turn it off before adding disks.")" 12 60
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
##########################################
|
||||
|
||||
msg_info "$(translate "Detecting available disks...")"
|
||||
|
||||
USED_DISKS=$(lsblk -n -o PKNAME,TYPE | grep 'lvm' | awk '{print "/dev/" $1}')
|
||||
MOUNTED_DISKS=$(lsblk -ln -o NAME,MOUNTPOINT | awk '$2!="" {print "/dev/" $1}')
|
||||
|
||||
ZFS_DISKS=""
|
||||
ZFS_RAW=$(zpool list -v -H 2>/dev/null | awk '{print $1}' | grep -v '^NAME$' | grep -v '^-' | grep -v '^mirror')
|
||||
|
||||
for entry in $ZFS_RAW; do
|
||||
|
||||
path=""
|
||||
if [[ "$entry" == wwn-* || "$entry" == ata-* ]]; then
|
||||
if [ -e "/dev/disk/by-id/$entry" ]; then
|
||||
path=$(readlink -f "/dev/disk/by-id/$entry")
|
||||
fi
|
||||
elif [[ "$entry" == /dev/* ]]; then
|
||||
path="$entry"
|
||||
fi
|
||||
|
||||
|
||||
if [ -n "$path" ]; then
|
||||
base_disk=$(lsblk -no PKNAME "$path" 2>/dev/null)
|
||||
if [ -n "$base_disk" ]; then
|
||||
ZFS_DISKS+="/dev/$base_disk"$'\n'
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
ZFS_DISKS=$(echo "$ZFS_DISKS" | sort -u)
|
||||
|
||||
|
||||
is_disk_in_use() {
|
||||
local disk="$1"
|
||||
|
||||
|
||||
while read -r part fstype; do
|
||||
case "$fstype" in
|
||||
zfs_member|linux_raid_member)
|
||||
return 0 ;;
|
||||
esac
|
||||
|
||||
if echo "$MOUNTED_DISKS" | grep -q "/dev/$part"; then
|
||||
return 0
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME,FSTYPE "$disk" | tail -n +2)
|
||||
|
||||
|
||||
if echo "$USED_DISKS" | grep -q "$disk" || echo "$ZFS_DISKS" | grep -q "$disk"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
FREE_DISKS=()
|
||||
|
||||
LVM_DEVICES=$(pvs --noheadings -o pv_name 2> >(grep -v 'File descriptor .* leaked') | xargs -n1 readlink -f | sort -u)
|
||||
RAID_ACTIVE=$(grep -Po 'md\d+\s*:\s*active\s+raid[0-9]+' /proc/mdstat | awk '{print $1}' | sort -u)
|
||||
|
||||
while read -r DISK; do
|
||||
|
||||
[[ "$DISK" =~ /dev/zd ]] && continue
|
||||
|
||||
INFO=($(get_disk_info "$DISK"))
|
||||
MODEL="${INFO[@]::${#INFO[@]}-1}"
|
||||
SIZE="${INFO[-1]}"
|
||||
LABEL=""
|
||||
SHOW_DISK=true
|
||||
|
||||
IS_MOUNTED=false
|
||||
IS_RAID=false
|
||||
IS_ZFS=false
|
||||
IS_LVM=false
|
||||
|
||||
while read -r part fstype; do
|
||||
[[ "$fstype" == "zfs_member" ]] && IS_ZFS=true
|
||||
[[ "$fstype" == "linux_raid_member" ]] && IS_RAID=true
|
||||
[[ "$fstype" == "LVM2_member" ]] && IS_LVM=true
|
||||
if grep -q "/dev/$part" <<< "$MOUNTED_DISKS"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME,FSTYPE "$DISK" | tail -n +2)
|
||||
|
||||
REAL_PATH=$(readlink -f "$DISK")
|
||||
if echo "$LVM_DEVICES" | grep -qFx "$REAL_PATH"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
|
||||
|
||||
|
||||
USED_BY=""
|
||||
REAL_PATH=$(readlink -f "$DISK")
|
||||
CONFIG_DATA=$(grep -vE '^\s*#' /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf 2>/dev/null)
|
||||
|
||||
if grep -Fq "$REAL_PATH" <<< "$CONFIG_DATA"; then
|
||||
USED_BY="⚠ $(translate "In use")"
|
||||
else
|
||||
for SYMLINK in /dev/disk/by-id/*; do
|
||||
if [[ "$(readlink -f "$SYMLINK")" == "$REAL_PATH" ]]; then
|
||||
if grep -Fq "$SYMLINK" <<< "$CONFIG_DATA"; then
|
||||
USED_BY="⚠ $(translate "In use")"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
if $IS_RAID && grep -q "$DISK" <<< "$(cat /proc/mdstat)"; then
|
||||
if grep -q "active raid" /proc/mdstat; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if $IS_ZFS; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
|
||||
if $IS_MOUNTED; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
|
||||
if qm config "$VMID" | grep -vE '^\s*#|^description:' | grep -q "$DISK"; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
if $SHOW_DISK; then
|
||||
[[ -n "$USED_BY" ]] && LABEL+=" [$USED_BY]"
|
||||
[[ "$IS_RAID" == true ]] && LABEL+=" ⚠ RAID"
|
||||
[[ "$IS_LVM" == true ]] && LABEL+=" ⚠ LVM"
|
||||
[[ "$IS_ZFS" == true ]] && LABEL+=" ⚠ ZFS"
|
||||
|
||||
DESCRIPTION=$(printf "%-30s %10s%s" "$MODEL" "$SIZE" "$LABEL")
|
||||
FREE_DISKS+=("$DISK" "$DESCRIPTION" "OFF")
|
||||
fi
|
||||
done < <(lsblk -dn -e 7,11 -o PATH)
|
||||
|
||||
|
||||
|
||||
if [ "${#FREE_DISKS[@]}" -eq 0 ]; then
|
||||
cleanup
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No disks available for this VM.")" 8 40
|
||||
clear
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Available disks detected.")"
|
||||
|
||||
|
||||
|
||||
######################################################
|
||||
|
||||
|
||||
|
||||
|
||||
MAX_WIDTH=$(printf "%s\n" "${FREE_DISKS[@]}" | awk '{print length}' | sort -nr | head -n1)
|
||||
TOTAL_WIDTH=$((MAX_WIDTH + 20))
|
||||
|
||||
if [ $TOTAL_WIDTH -lt 50 ]; then
|
||||
TOTAL_WIDTH=50
|
||||
fi
|
||||
|
||||
|
||||
SELECTED=$(whiptail --title "$(translate "Select Disks")" --checklist \
|
||||
"$(translate "Select the disks you want to add:")" 20 $TOTAL_WIDTH 10 "${FREE_DISKS[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$SELECTED" ]; then
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No disks were selected.")" 10 64
|
||||
clear
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Disks selected successfully.")"
|
||||
|
||||
|
||||
INTERFACE=$(whiptail --title "$(translate "Interface Type")" --menu "$(translate "Select the interface type for all disks:")" 15 40 4 \
|
||||
"sata" "$(translate "Add as SATA")" \
|
||||
"scsi" "$(translate "Add as SCSI")" \
|
||||
"virtio" "$(translate "Add as VirtIO")" \
|
||||
"ide" "$(translate "Add as IDE")" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$INTERFACE" ]; then
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No interface type was selected for the disks.")" 8 40
|
||||
clear
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Interface type selected: $INTERFACE")"
|
||||
|
||||
DISKS_ADDED=0
|
||||
ERROR_MESSAGES=""
|
||||
SUCCESS_MESSAGES=""
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Processing selected disks...")"
|
||||
|
||||
for DISK in $SELECTED; do
|
||||
DISK=$(echo "$DISK" | tr -d '"')
|
||||
DISK_INFO=$(get_disk_info "$DISK")
|
||||
|
||||
ASSIGNED_TO=""
|
||||
RUNNING_VMS=""
|
||||
RUNNING_CTS=""
|
||||
|
||||
|
||||
while read -r VM_ID VM_NAME; do
|
||||
if [[ "$VM_ID" =~ ^[0-9]+$ ]] && qm config "$VM_ID" | grep -q "$DISK"; then
|
||||
ASSIGNED_TO+="VM $VM_ID $VM_NAME\n"
|
||||
VM_STATUS=$(qm status "$VM_ID" | awk '{print $2}')
|
||||
if [ "$VM_STATUS" == "running" ]; then
|
||||
RUNNING_VMS+="VM $VM_ID $VM_NAME\n"
|
||||
fi
|
||||
fi
|
||||
done < <(qm list | awk 'NR>1 {print $1, $2}')
|
||||
|
||||
|
||||
while read -r CT_ID CT_NAME; do
|
||||
if [[ "$CT_ID" =~ ^[0-9]+$ ]] && pct config "$CT_ID" | grep -q "$DISK"; then
|
||||
ASSIGNED_TO+="CT $CT_ID $CT_NAME\n"
|
||||
CT_STATUS=$(pct status "$CT_ID" | awk '{print $2}')
|
||||
if [ "$CT_STATUS" == "running" ]; then
|
||||
RUNNING_CTS+="CT $CT_ID $CT_NAME\n"
|
||||
fi
|
||||
fi
|
||||
done < <(pct list | awk 'NR>1 {print $1, $2}')
|
||||
|
||||
if [ -n "$RUNNING_VMS" ] || [ -n "$RUNNING_CTS" ]; then
|
||||
ERROR_MESSAGES+="$(translate "The disk") $DISK_INFO $(translate "is currently in use by the following running VM(s) or CT(s):")\\n$RUNNING_VMS$RUNNING_CTS\\n\\n$(translate "You cannot add this disk while the VM or CT is running.")\\n$(translate "Please shut it down first and run this script again to add the disk.")\\n\\n"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -n "$ASSIGNED_TO" ]; then
|
||||
cleanup
|
||||
whiptail --title "$(translate "Disk Already Assigned")" --yesno "$(translate "The disk") $DISK_INFO $(translate "is already assigned to the following VM(s) or CT(s):")\\n$ASSIGNED_TO\\n\\n$(translate "Do you want to continue anyway?")" 15 70
|
||||
if [ $? -ne 0 ]; then
|
||||
sleep 1
|
||||
exec "$0"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
INDEX=0
|
||||
while qm config "$VMID" | grep -q "${INTERFACE}${INDEX}"; do
|
||||
((INDEX++))
|
||||
done
|
||||
|
||||
RESULT=$(qm set "$VMID" -${INTERFACE}${INDEX} "$DISK" 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
MESSAGE="$(translate "The disk") $DISK_INFO $(translate "has been successfully added to VM") $VMID."
|
||||
if [ -n "$ASSIGNED_TO" ]; then
|
||||
MESSAGE+="\\n\\n$(translate "WARNING: This disk is also assigned to the following VM(s):")\\n$ASSIGNED_TO"
|
||||
MESSAGE+="\\n$(translate "Make sure not to start VMs that share this disk at the same time to avoid data corruption.")"
|
||||
fi
|
||||
SUCCESS_MESSAGES+="$MESSAGE\\n\\n"
|
||||
((DISKS_ADDED++))
|
||||
else
|
||||
ERROR_MESSAGES+="$(translate "Could not add disk") $DISK_INFO $(translate "to VM") $VMID.\\n$(translate "Error:") $RESULT\\n\\n"
|
||||
fi
|
||||
done
|
||||
|
||||
msg_ok "$(translate "Disk processing completed.")"
|
||||
|
||||
|
||||
|
||||
if [ -n "$SUCCESS_MESSAGES" ]; then
|
||||
MSG_LINES=$(echo "$SUCCESS_MESSAGES" | wc -l)
|
||||
whiptail --title "$(translate "Successful Operations")" --msgbox "$SUCCESS_MESSAGES" 16 70
|
||||
fi
|
||||
|
||||
if [ -n "$ERROR_MESSAGES" ]; then
|
||||
whiptail --title "$(translate "Warnings and Errors")" --msgbox "$ERROR_MESSAGES" 16 70
|
||||
fi
|
||||
|
||||
|
||||
|
||||
exit 0
|
||||
@@ -1,537 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenux - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script allows users to assign physical disks to existing
|
||||
# Proxmox containers (CTs) through an interactive menu.
|
||||
# - Detects the system disk and excludes it from selection.
|
||||
# - Lists all available CTs for the user to choose from.
|
||||
# - Identifies and displays unassigned physical disks.
|
||||
# - Allows the user to select multiple disks and attach them to a CT.
|
||||
# - Configures the selected disks for the CT and verifies the assignment.
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration ============================================
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
|
||||
get_disk_info() {
|
||||
local disk=$1
|
||||
MODEL=$(lsblk -dn -o MODEL "$disk" | xargs)
|
||||
SIZE=$(lsblk -dn -o SIZE "$disk" | xargs)
|
||||
echo "$MODEL" "$SIZE"
|
||||
}
|
||||
|
||||
|
||||
|
||||
CT_LIST=$(pct list | awk 'NR>1 {print $1, $3}')
|
||||
if [ -z "$CT_LIST" ]; then
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No CTs available in the system.")" 8 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
CTID=$(whiptail --title "$(translate "Select CT")" --menu "$(translate "Select the CT to which you want to add disks:")" 15 60 8 $CT_LIST 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$CTID" ]; then
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No CT was selected.")" 8 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CTID=$(echo "$CTID" | tr -d '"')
|
||||
|
||||
msg_ok "$(translate "CT selected successfully.")"
|
||||
|
||||
|
||||
|
||||
|
||||
CT_STATUS=$(pct status "$CTID" | awk '{print $2}')
|
||||
if [ "$CT_STATUS" != "running" ]; then
|
||||
msg_info "$(translate "Starting CT") $CTID..."
|
||||
pct start "$CTID"
|
||||
sleep 2
|
||||
if [ "$(pct status "$CTID" | awk '{print $2}')" != "running" ]; then
|
||||
msg_error "$(translate "Failed to start the CT.")"
|
||||
exit 1
|
||||
fi
|
||||
msg_ok "$(translate "CT started successfully.")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
CONF_FILE="/etc/pve/lxc/$CTID.conf"
|
||||
|
||||
if grep -q '^unprivileged: 1' "$CONF_FILE"; then
|
||||
if whiptail --title "$(translate "Privileged Container")" \
|
||||
--yesno "$(translate "The selected container is unprivileged. A privileged container is required for direct device passthrough.")\\n\\n$(translate "Do you want to convert it to a privileged container now?")" 12 70; then
|
||||
|
||||
msg_info "$(translate "Stopping container") $CTID..."
|
||||
pct shutdown "$CTID" &
|
||||
for i in {1..10}; do
|
||||
sleep 1
|
||||
if [ "$(pct status "$CTID" | awk '{print $2}')" != "running" ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$(pct status "$CTID" | awk '{print $2}')" == "running" ]; then
|
||||
msg_error "$(translate "Failed to stop the container.")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Container stopped.")"
|
||||
|
||||
cp "$CONF_FILE" "$CONF_FILE.bak"
|
||||
sed -i '/^unprivileged: 1/d' "$CONF_FILE"
|
||||
echo "unprivileged: 0" >> "$CONF_FILE"
|
||||
|
||||
msg_ok "$(translate "Container successfully converted to privileged.")"
|
||||
|
||||
msg_info "$(translate "Starting container") $CTID..."
|
||||
pct start "$CTID"
|
||||
sleep 2
|
||||
if [ "$(pct status "$CTID" | awk '{print $2}')" != "running" ]; then
|
||||
msg_error "$(translate "Failed to start the container.")"
|
||||
exit 1
|
||||
fi
|
||||
msg_ok "$(translate "Container started successfully.")"
|
||||
|
||||
else
|
||||
whiptail --title "$(translate "Aborted")" \
|
||||
--msgbox "$(translate "Operation cancelled. Cannot continue with an unprivileged container.")" 10 60
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
##########################################
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Detecting available disks...")"
|
||||
|
||||
USED_DISKS=$(lsblk -n -o PKNAME,TYPE | grep 'lvm' | awk '{print "/dev/" $1}')
|
||||
MOUNTED_DISKS=$(lsblk -ln -o NAME,MOUNTPOINT | awk '$2!="" {print "/dev/" $1}')
|
||||
|
||||
ZFS_DISKS=""
|
||||
ZFS_RAW=$(zpool list -v -H 2>/dev/null | awk '{print $1}' | grep -v '^NAME$' | grep -v '^-' | grep -v '^mirror')
|
||||
|
||||
for entry in $ZFS_RAW; do
|
||||
path=""
|
||||
if [[ "$entry" == wwn-* || "$entry" == ata-* ]]; then
|
||||
if [ -e "/dev/disk/by-id/$entry" ]; then
|
||||
path=$(readlink -f "/dev/disk/by-id/$entry")
|
||||
fi
|
||||
elif [[ "$entry" == /dev/* ]]; then
|
||||
path="$entry"
|
||||
fi
|
||||
|
||||
if [ -n "$path" ]; then
|
||||
base_disk=$(lsblk -no PKNAME "$path" 2>/dev/null)
|
||||
if [ -n "$base_disk" ]; then
|
||||
ZFS_DISKS+="/dev/$base_disk"$'\n'
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
ZFS_DISKS=$(echo "$ZFS_DISKS" | sort -u)
|
||||
|
||||
is_disk_in_use() {
|
||||
local disk="$1"
|
||||
|
||||
while read -r part fstype; do
|
||||
case "$fstype" in
|
||||
zfs_member|linux_raid_member)
|
||||
return 0 ;;
|
||||
esac
|
||||
|
||||
if echo "$MOUNTED_DISKS" | grep -q "/dev/$part"; then
|
||||
return 0
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME,FSTYPE "$disk" | tail -n +2)
|
||||
|
||||
if echo "$USED_DISKS" | grep -q "$disk" || echo "$ZFS_DISKS" | grep -q "$disk"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
FREE_DISKS=()
|
||||
|
||||
LVM_DEVICES=$(pvs --noheadings -o pv_name 2> >(grep -v 'File descriptor .* leaked') | xargs -r -n1 readlink -f | sort -u)
|
||||
|
||||
if [[ -n "$LVM_DEVICES" ]] && echo "$LVM_DEVICES" | grep -qFx "$REAL_PATH"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
|
||||
RAID_ACTIVE=$(grep -Po 'md\d+\s*:\s*active\s+raid[0-9]+' /proc/mdstat | awk '{print $1}' | sort -u)
|
||||
|
||||
while read -r DISK; do
|
||||
[[ "$DISK" =~ /dev/zd ]] && continue
|
||||
|
||||
INFO=($(get_disk_info "$DISK"))
|
||||
MODEL="${INFO[@]::${#INFO[@]}-1}"
|
||||
SIZE="${INFO[-1]}"
|
||||
LABEL=""
|
||||
SHOW_DISK=true
|
||||
|
||||
IS_MOUNTED=false
|
||||
IS_RAID=false
|
||||
IS_ZFS=false
|
||||
IS_LVM=false
|
||||
|
||||
while read -r part fstype; do
|
||||
[[ "$fstype" == "zfs_member" ]] && IS_ZFS=true
|
||||
[[ "$fstype" == "linux_raid_member" ]] && IS_RAID=true
|
||||
[[ "$fstype" == "LVM2_member" ]] && IS_LVM=true
|
||||
if grep -q "/dev/$part" <<< "$MOUNTED_DISKS"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME,FSTYPE "$DISK" | tail -n +2)
|
||||
|
||||
REAL_PATH=$(readlink -f "$DISK")
|
||||
if echo "$LVM_DEVICES" | grep -qFx "$REAL_PATH"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
|
||||
|
||||
USED_BY=""
|
||||
REAL_PATH=$(readlink -f "$DISK")
|
||||
CONFIG_DATA=$(grep -vE '^\s*#' /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf 2>/dev/null)
|
||||
|
||||
if grep -Fq "$REAL_PATH" <<< "$CONFIG_DATA"; then
|
||||
USED_BY="⚠ $(translate "In use")"
|
||||
else
|
||||
for SYMLINK in /dev/disk/by-id/*; do
|
||||
if [[ "$(readlink -f "$SYMLINK")" == "$REAL_PATH" ]]; then
|
||||
if grep -Fq "$SYMLINK" <<< "$CONFIG_DATA"; then
|
||||
USED_BY="⚠ $(translate "In use")"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if $IS_RAID && grep -q "$DISK" <<< "$(cat /proc/mdstat)"; then
|
||||
if grep -q "active raid" /proc/mdstat; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
fi
|
||||
|
||||
if $IS_ZFS; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
if $IS_MOUNTED; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
if pct config "$CTID" | grep -vE '^\s*#|^description:' | grep -q "$DISK"; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
if $SHOW_DISK; then
|
||||
[[ -n "$USED_BY" ]] && LABEL+=" [$USED_BY]"
|
||||
[[ "$IS_RAID" == true ]] && LABEL+=" ⚠ RAID"
|
||||
[[ "$IS_LVM" == true ]] && LABEL+=" ⚠ LVM"
|
||||
[[ "$IS_ZFS" == true ]] && LABEL+=" ⚠ ZFS"
|
||||
|
||||
DESCRIPTION=$(printf "%-30s %10s%s" "$MODEL" "$SIZE" "$LABEL")
|
||||
FREE_DISKS+=("$DISK" "$DESCRIPTION" "OFF")
|
||||
fi
|
||||
done < <(lsblk -dn -e 7,11 -o PATH)
|
||||
|
||||
if [ "${#FREE_DISKS[@]}" -eq 0 ]; then
|
||||
cleanup
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No disks available for this CT.")" 8 40
|
||||
clear
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Available disks detected.")"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
######################################################
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
MAX_WIDTH=$(printf "%s\n" "${FREE_DISKS[@]}" | awk '{print length}' | sort -nr | head -n1)
|
||||
TOTAL_WIDTH=$((MAX_WIDTH + 20))
|
||||
|
||||
if [ $TOTAL_WIDTH -lt 50 ]; then
|
||||
TOTAL_WIDTH=50
|
||||
fi
|
||||
|
||||
SELECTED=$(whiptail --title "$(translate "Select Disks")" --radiolist \
|
||||
"$(translate "Select the disks you want to add:")" 20 $TOTAL_WIDTH 10 "${FREE_DISKS[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$SELECTED" ]; then
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No disks were selected.")" 10 64
|
||||
clear
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Disks selected successfully.")"
|
||||
|
||||
DISKS_ADDED=0
|
||||
ERROR_MESSAGES=""
|
||||
SUCCESS_MESSAGES=""
|
||||
|
||||
msg_info "$(translate "Processing selected disks...")"
|
||||
|
||||
for DISK in $SELECTED; do
|
||||
DISK=$(echo "$DISK" | tr -d '"')
|
||||
DISK_INFO=$(get_disk_info "$DISK")
|
||||
|
||||
ASSIGNED_TO=""
|
||||
RUNNING_CTS=""
|
||||
RUNNING_VMS=""
|
||||
|
||||
# Comprobar CTs
|
||||
while read -r CT_ID CT_NAME; do
|
||||
if [[ "$CT_ID" =~ ^[0-9]+$ ]] && pct config "$CT_ID" | grep -q "$DISK"; then
|
||||
ASSIGNED_TO+="CT $CT_ID $CT_NAME\n"
|
||||
CT_STATUS=$(pct status "$CT_ID" | awk '{print $2}')
|
||||
if [ "$CT_STATUS" == "running" ]; then
|
||||
RUNNING_CTS+="CT $CT_ID $CT_NAME\n"
|
||||
fi
|
||||
fi
|
||||
done < <(pct list | awk 'NR>1 {print $1, $3}')
|
||||
|
||||
# Comprobar VMs
|
||||
while read -r VM_ID VM_NAME; do
|
||||
if [[ "$VM_ID" =~ ^[0-9]+$ ]] && qm config "$VM_ID" | grep -q "$DISK"; then
|
||||
ASSIGNED_TO+="VM $VM_ID $VM_NAME\n"
|
||||
VM_STATUS=$(qm status "$VM_ID" | awk '{print $2}')
|
||||
if [ "$VM_STATUS" == "running" ]; then
|
||||
RUNNING_VMS+="VM $VM_ID $VM_NAME\n"
|
||||
fi
|
||||
fi
|
||||
done < <(qm list | awk 'NR>1 {print $1, $2}')
|
||||
|
||||
if [ -n "$RUNNING_CTS" ] || [ -n "$RUNNING_VMS" ]; then
|
||||
ERROR_MESSAGES+="$(translate "The disk") $DISK_INFO $(translate "is in use by the following running VM(s) or CT(s):")\\n$RUNNING_CTS$RUNNING_VMS\\n\\n"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -n "$ASSIGNED_TO" ]; then
|
||||
cleanup
|
||||
whiptail --title "$(translate "Disk Already Assigned")" --yesno "$(translate "The disk") $DISK_INFO $(translate "is already assigned to the following VM(s) or CT(s):")\\n$ASSIGNED_TO\\n\\n$(translate "Do you want to continue anyway?")" 15 70
|
||||
if [ $? -ne 0 ]; then
|
||||
sleep 1
|
||||
exec "$0"
|
||||
fi
|
||||
fi
|
||||
|
||||
cleanup
|
||||
|
||||
|
||||
|
||||
|
||||
if lsblk "$DISK" | grep -q "raid" || grep -q "${DISK##*/}" /proc/mdstat; then
|
||||
whiptail --title "$(translate "RAID Detected")" --msgbox "$(translate "The disk") $DISK_INFO $(translate "appears to be part of a") RAID. $(translate "For security reasons, the system cannot format it.")\\n\\n$(translate "If you are sure you want to use it, please remove the") RAID metadata $(translate "or format it manually using external tools.")\\n\\n$(translate "After that, run this script again to add it.")" 18 70
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
MOUNT_POINT=$(whiptail --title "$(translate "Mount Point")" --inputbox "$(translate "Enter the mount point for the disk (e.g., /mnt/disk_passthrough):")" 10 60 "/mnt/disk_passthrough" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$MOUNT_POINT" ]; then
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No mount point was specified.")" 8 40
|
||||
continue
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Mount point specified: $MOUNT_POINT")"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PARTITION=$(lsblk -rno NAME "$DISK" | awk -v disk="$(basename "$DISK")" '$1 != disk {print $1; exit}')
|
||||
SKIP_FORMAT=false
|
||||
|
||||
if [ -n "$PARTITION" ]; then
|
||||
PARTITION="/dev/$PARTITION"
|
||||
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
|
||||
|
||||
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
|
||||
SKIP_FORMAT=true
|
||||
msg_ok "$(translate "Detected existing filesystem") $CURRENT_FS $(translate "on") $PARTITION."
|
||||
else
|
||||
whiptail --title "$(translate "Unsupported Filesystem")" --yesno "$(translate "The partition") $PARTITION $(translate "has an unsupported filesystem ($CURRENT_FS).\\nDo you want to format it?")" 10 70
|
||||
if [ $? -ne 0 ]; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
else
|
||||
|
||||
CURRENT_FS=$(lsblk -no FSTYPE "$DISK" | xargs)
|
||||
|
||||
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
|
||||
SKIP_FORMAT=true
|
||||
PARTITION="$DISK"
|
||||
msg_ok "$(translate "Detected filesystem") $CURRENT_FS $(translate "directly on disk") $DISK.)"
|
||||
else
|
||||
|
||||
whiptail --title "$(translate "No Valid Partitions")" --yesno "$(translate "The disk has no partitions and no valid filesystem. Do you want to create a new partition and format it?")" 10 70
|
||||
if [ $? -ne 0 ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
echo -e "$(translate "Creating partition table and partition...")"
|
||||
parted -s "$DISK" mklabel gpt
|
||||
parted -s "$DISK" mkpart primary 0% 100%
|
||||
sleep 2
|
||||
partprobe "$DISK"
|
||||
sleep 2
|
||||
|
||||
PARTITION=$(lsblk -rno NAME "$DISK" | awk -v disk="$(basename "$DISK")" '$1 != disk {print $1; exit}')
|
||||
if [ -n "$PARTITION" ]; then
|
||||
PARTITION="/dev/$PARTITION"
|
||||
else
|
||||
whiptail --title "$(translate "Partition Error")" --msgbox "$(translate "Failed to create partition on disk") $DISK_INFO." 8 70
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if [ "$SKIP_FORMAT" != true ]; then
|
||||
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
|
||||
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
|
||||
SKIP_FORMAT=true
|
||||
msg_ok "$(translate "Detected existing filesystem") $CURRENT_FS $(translate "on") $PARTITION. $(translate "Skipping format.")"
|
||||
else
|
||||
|
||||
FORMAT_TYPE=$(whiptail --title "$(translate "Select Format Type")" --menu "$(translate "Select the filesystem type for") $DISK_INFO:" 15 60 6 \
|
||||
"ext4" "$(translate "Extended Filesystem 4 (recommended)")" \
|
||||
"xfs" "$(translate "XFS Filesystem")" \
|
||||
"btrfs" "$(translate "Btrfs Filesystem")" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$FORMAT_TYPE" ]; then
|
||||
whiptail --title "$(translate "Format Cancelled")" --msgbox "$(translate "Format operation cancelled. The disk will not be added.")" 8 60
|
||||
continue
|
||||
fi
|
||||
|
||||
whiptail --title "$(translate "WARNING")" --yesno "$(translate "WARNING: This operation will FORMAT the disk") $DISK_INFO $(translate "with") $FORMAT_TYPE.\\n\\n$(translate "ALL DATA ON THIS DISK WILL BE PERMANENTLY LOST!")\\n\\n$(translate "Are you sure you want to continue")" 15 70
|
||||
if [ $? -ne 0 ]; then
|
||||
whiptail --title "$(translate "Format Cancelled")" --msgbox "$(translate "Format operation cancelled. The disk will not be added.")" 8 60
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if [ "$SKIP_FORMAT" != true ]; then
|
||||
echo -e "$(translate "Formatting partition") $PARTITION $(translate "with") $FORMAT_TYPE..."
|
||||
|
||||
case "$FORMAT_TYPE" in
|
||||
"ext4") mkfs.ext4 -F "$PARTITION" ;;
|
||||
"xfs") mkfs.xfs -f "$PARTITION" ;;
|
||||
"btrfs") mkfs.btrfs -f "$PARTITION" ;;
|
||||
esac
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
whiptail --title "$(translate "Format Failed")" --msgbox "$(translate "Failed to format partition") $PARTITION $(translate "with") $FORMAT_TYPE.\\n\\n$(translate "The disk may be in use by the system or have hardware issues.")" 12 70
|
||||
continue
|
||||
else
|
||||
msg_ok "$(translate "Partition") $PARTITION $(translate "successfully formatted with") $FORMAT_TYPE."
|
||||
partprobe "$DISK"
|
||||
sleep 2
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
INDEX=0
|
||||
while pct config "$CTID" | grep -q "mp${INDEX}:"; do
|
||||
((INDEX++))
|
||||
done
|
||||
|
||||
|
||||
|
||||
|
||||
##############################################################################
|
||||
|
||||
RESULT=$(pct set "$CTID" -mp${INDEX} "$PARTITION,mp=$MOUNT_POINT,backup=0,ro=0,acl=1" 2>&1)
|
||||
|
||||
pct exec "$CTID" -- chmod -R 775 "$MOUNT_POINT"
|
||||
|
||||
##############################################################################
|
||||
|
||||
|
||||
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
MESSAGE="$(translate "The disk") $DISK_INFO $(translate "has been successfully added to CT") $CTID $(translate "as a mount point at") $MOUNT_POINT."
|
||||
if [ -n "$ASSIGNED_TO" ]; then
|
||||
MESSAGE+="\\n\\n$(translate "WARNING: This disk is also assigned to the following CT(s):")\\n$ASSIGNED_TO"
|
||||
MESSAGE+="\\n$(translate "Make sure not to start CTs that share this disk at the same time to avoid data corruption.")"
|
||||
fi
|
||||
SUCCESS_MESSAGES+="$MESSAGE\\n\\n"
|
||||
((DISKS_ADDED++))
|
||||
else
|
||||
ERROR_MESSAGES+="$(translate "Could not add disk") $DISK_INFO $(translate "to CT") $CTID.\\n$(translate "Error:") $RESULT\\n\\n"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
|
||||
msg_ok "$(translate "Disk processing completed.")"
|
||||
|
||||
if [ -n "$SUCCESS_MESSAGES" ]; then
|
||||
MSG_LINES=$(echo "$SUCCESS_MESSAGES" | wc -l)
|
||||
whiptail --title "$(translate "Successful Operations")" --msgbox "$SUCCESS_MESSAGES" 16 70
|
||||
fi
|
||||
|
||||
if [ -n "$ERROR_MESSAGES" ]; then
|
||||
whiptail --title "$(translate "Warnings and Errors")" --msgbox "$ERROR_MESSAGES" 16 70
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,277 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# Remove Subscription Banner - Proxmox VE (v3 - Minimal Intrusive)
|
||||
# ==========================================================
|
||||
# This version makes a surgical change to the checked_command function
|
||||
# by changing the condition to 'if (false)' and commenting out the banner logic.
|
||||
# Also patches the mobile UI to remove the subscription dialog.
|
||||
# ==========================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Source utilities if available
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# File paths
|
||||
JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
|
||||
GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
|
||||
MIN_JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
|
||||
MOBILE_UI_FILE="/usr/share/pve-yew-mobile-gui/index.html.tpl"
|
||||
BACKUP_DIR="$BASE_DIR/backups"
|
||||
APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
|
||||
PATCH_BIN="/usr/local/bin/pve-remove-nag-v3.sh"
|
||||
MARK="/* PROXMENUX_NAG_PATCH_V3 */"
|
||||
MOBILE_MARK="<!-- PROXMENUX_MOBILE_NAG_PATCH -->"
|
||||
|
||||
# Ensure tools JSON exists
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
# Register tool in JSON
|
||||
register_tool() {
|
||||
command -v jq >/dev/null 2>&1 || return 0
|
||||
local tool="$1" state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" \
|
||||
> "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
# Verify JS file integrity
|
||||
verify_js_integrity() {
|
||||
local file="$1"
|
||||
[ -f "$file" ] || return 1
|
||||
[ -s "$file" ] || return 1
|
||||
grep -Eq 'Ext|function|var|const|let' "$file" || return 1
|
||||
if LC_ALL=C grep -qP '\x00' "$file" 2>/dev/null; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Create timestamped backup
|
||||
create_backup() {
|
||||
local file="$1"
|
||||
local timestamp
|
||||
timestamp=$(date +%Y%m%d_%H%M%S)
|
||||
local backup_file="$BACKUP_DIR/$(basename "$file").backup.$timestamp"
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
if [ -f "$file" ]; then
|
||||
rm -f "$BACKUP_DIR"/"$(basename "$file")".backup.* 2>/dev/null || true
|
||||
|
||||
cp -a "$file" "$backup_file"
|
||||
echo "$backup_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Create the patch script that will be called by APT hook
|
||||
create_patch_script() {
|
||||
cat > "$PATCH_BIN" <<'EOFPATCH'
|
||||
#!/usr/bin/env bash
|
||||
# ==========================================================
|
||||
# Proxmox Subscription Banner Patch (v3 - Minimal)
|
||||
# ==========================================================
|
||||
set -euo pipefail
|
||||
|
||||
JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
|
||||
GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
|
||||
MIN_JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
|
||||
MOBILE_UI_FILE="/usr/share/pve-yew-mobile-gui/index.html.tpl"
|
||||
BACKUP_DIR="/usr/local/share/proxmenux/backups"
|
||||
MARK="/* PROXMENUX_NAG_PATCH_V3 */"
|
||||
MOBILE_MARK="<!-- PROXMENUX_MOBILE_NAG_PATCH -->"
|
||||
|
||||
verify_js_integrity() {
|
||||
local file="$1"
|
||||
[ -f "$file" ] && [ -s "$file" ] && grep -Eq 'Ext|function' "$file" && ! LC_ALL=C grep -qP '\x00' "$file" 2>/dev/null
|
||||
}
|
||||
|
||||
patch_checked_command() {
|
||||
[ -f "$JS_FILE" ] || return 0
|
||||
|
||||
# Check if already patched
|
||||
grep -q "$MARK" "$JS_FILE" && return 0
|
||||
|
||||
# Create backup
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
local backup="$BACKUP_DIR/$(basename "$JS_FILE").backup.$(date +%Y%m%d_%H%M%S)"
|
||||
cp -a "$JS_FILE" "$backup"
|
||||
|
||||
# Set trap to restore on error
|
||||
trap "cp -a '$backup' '$JS_FILE' 2>/dev/null || true" ERR
|
||||
|
||||
# Add patch marker at the beginning
|
||||
sed -i "1s|^|$MARK\n|" "$JS_FILE"
|
||||
|
||||
# Surgical patch: Change the condition in checked_command function
|
||||
# This changes the if condition to 'if (false)' making the banner never show
|
||||
if grep -q "res\.data\.status\.toLowerCase() !== 'active'" "$JS_FILE"; then
|
||||
# Pattern for newer versions (8.4.5+)
|
||||
sed -i "/checked_command: function/,/},$/s/res === null || res === undefined || !res || res\.data\.status\.toLowerCase() !== 'active'/false/g" "$JS_FILE"
|
||||
elif grep -q "res\.data\.status !== 'Active'" "$JS_FILE"; then
|
||||
# Pattern for older versions
|
||||
sed -i "/checked_command: function/,/},$/s/res === null || res === undefined || !res || res\.data\.status !== 'Active'/false/g" "$JS_FILE"
|
||||
fi
|
||||
|
||||
# Also handle the NoMoreNagging pattern if present
|
||||
if grep -q "res\.data\.status\.toLowerCase() !== 'NoMoreNagging'" "$JS_FILE"; then
|
||||
sed -i "/checked_command: function/,/},$/s/res === null || res === undefined || !res || res\.data\.status\.toLowerCase() !== 'NoMoreNagging'/false/g" "$JS_FILE"
|
||||
fi
|
||||
|
||||
# Verify integrity after patch
|
||||
if ! verify_js_integrity "$JS_FILE"; then
|
||||
cp -a "$backup" "$JS_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Clean up generated files
|
||||
rm -f "$MIN_JS_FILE" "$GZ_FILE" 2>/dev/null || true
|
||||
find /var/cache/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
|
||||
find /var/lib/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
|
||||
find /var/cache/nginx/ -type f -delete 2>/dev/null || true
|
||||
|
||||
trap - ERR
|
||||
return 0
|
||||
}
|
||||
|
||||
patch_mobile_ui() {
|
||||
[ -f "$MOBILE_UI_FILE" ] || return 0
|
||||
|
||||
# Check if already patched
|
||||
grep -q "$MOBILE_MARK" "$MOBILE_UI_FILE" && return 0
|
||||
|
||||
# Create backup
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
local backup="$BACKUP_DIR/$(basename "$MOBILE_UI_FILE").backup.$(date +%Y%m%d_%H%M%S)"
|
||||
cp -a "$MOBILE_UI_FILE" "$backup"
|
||||
|
||||
# Set trap to restore on error
|
||||
trap "cp -a '$backup' '$MOBILE_UI_FILE' 2>/dev/null || true" ERR
|
||||
|
||||
# Insert the script before </head> tag
|
||||
sed -i "/<\/head>/i\\
|
||||
$MOBILE_MARK\\
|
||||
<!-- Script to remove subscription banner from mobile UI -->\\
|
||||
<script>\\
|
||||
function removeNoSubDialog() {\\
|
||||
const observer = new MutationObserver(() => {\\
|
||||
const diag = document.querySelector('dialog[aria-label=\"No valid subscription\"]');\\
|
||||
if (diag) {\\
|
||||
diag.remove();\\
|
||||
}\\
|
||||
});\\
|
||||
observer.observe(document.body, { childList: true, subtree: true });\\
|
||||
}\\
|
||||
window.addEventListener('load', () => {\\
|
||||
setTimeout(removeNoSubDialog, 200);\\
|
||||
});\\
|
||||
</script>" "$MOBILE_UI_FILE"
|
||||
|
||||
trap - ERR
|
||||
return 0
|
||||
}
|
||||
|
||||
reload_services() {
|
||||
systemctl is-active --quiet pveproxy 2>/dev/null && {
|
||||
systemctl reload pveproxy 2>/dev/null || systemctl restart pveproxy 2>/dev/null || true
|
||||
}
|
||||
systemctl is-active --quiet nginx 2>/dev/null && {
|
||||
systemctl reload nginx 2>/dev/null || true
|
||||
}
|
||||
systemctl is-active --quiet pvedaemon 2>/dev/null && {
|
||||
systemctl reload pvedaemon 2>/dev/null || true
|
||||
}
|
||||
}
|
||||
|
||||
main() {
|
||||
patch_checked_command || return 1
|
||||
patch_mobile_ui || true
|
||||
reload_services
|
||||
}
|
||||
|
||||
main
|
||||
EOFPATCH
|
||||
|
||||
chmod 755 "$PATCH_BIN"
|
||||
}
|
||||
|
||||
# Create APT hook to reapply patch after updates
|
||||
create_apt_hook() {
|
||||
cat > "$APT_HOOK" <<'EOFAPT'
|
||||
/* ProxMenux: reapply minimal nag patch after upgrades */
|
||||
DPkg::Post-Invoke { "/usr/local/bin/pve-remove-nag-v3.sh || true"; };
|
||||
EOFAPT
|
||||
|
||||
chmod 644 "$APT_HOOK"
|
||||
|
||||
# Verify APT hook syntax
|
||||
apt-config dump >/dev/null 2>&1 || {
|
||||
rm -f "$APT_HOOK"
|
||||
}
|
||||
}
|
||||
|
||||
# Main function to remove subscription banner
|
||||
remove_subscription_banner_v3() {
|
||||
local pve_version
|
||||
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1 || echo "unknown")
|
||||
|
||||
msg_info "$(translate "Detected Proxmox VE") ${pve_version} - $(translate "applying banner patch")"
|
||||
|
||||
|
||||
|
||||
# Remove old APT hooks
|
||||
for f in /etc/apt/apt.conf.d/*nag*; do
|
||||
[[ -e "$f" ]] && rm -f "$f"
|
||||
done
|
||||
|
||||
# Create backup for desktop UI
|
||||
local backup_file
|
||||
backup_file=$(create_backup "$JS_FILE")
|
||||
if [ -n "$backup_file" ]; then
|
||||
# msg_ok "$(translate "Desktop UI backup created"): $backup_file"
|
||||
:
|
||||
fi
|
||||
|
||||
if [ -f "$MOBILE_UI_FILE" ]; then
|
||||
local mobile_backup
|
||||
mobile_backup=$(create_backup "$MOBILE_UI_FILE")
|
||||
if [ -n "$mobile_backup" ]; then
|
||||
# msg_ok "$(translate "Mobile UI backup created"): $mobile_backup"
|
||||
:
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create patch script and APT hook
|
||||
create_patch_script
|
||||
create_apt_hook
|
||||
|
||||
# Apply the patch
|
||||
if ! "$PATCH_BIN"; then
|
||||
msg_error "$(translate "Error applying patch. Backups preserved at"): $BACKUP_DIR"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Register tool as applied
|
||||
register_tool "subscription_banner" true
|
||||
|
||||
msg_ok "$(translate "Subscription banner removed successfully")"
|
||||
msg_ok "$(translate "Desktop and Mobile UI patched")"
|
||||
msg_ok "$(translate "Refresh your browser (Ctrl+Shift+R) to see changes")"
|
||||
|
||||
}
|
||||
|
||||
# Run if executed directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
remove_subscription_banner_v3
|
||||
fi
|
||||
@@ -1,124 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# Remove Subscription Banner - Proxmox VE 9.x
|
||||
# ==========================================================
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
register_tool() {
|
||||
local tool="$1"
|
||||
local state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
remove_subscription_banner_pve9() {
|
||||
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
|
||||
local MIN_JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
|
||||
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
|
||||
local APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
|
||||
|
||||
|
||||
local pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1)
|
||||
local pve_major=$(echo "$pve_version" | cut -d. -f1)
|
||||
|
||||
if [ "$pve_major" -lt 9 ] 2>/dev/null; then
|
||||
msg_error "This script is for PVE 9.x only. Detected PVE $pve_version"
|
||||
return 1
|
||||
fi
|
||||
|
||||
msg_info "Detected Proxmox VE $pve_version - Applying PVE 9.x patches"
|
||||
|
||||
|
||||
if [ ! -f "$JS_FILE" ]; then
|
||||
msg_error "JavaScript file not found: $JS_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
local backup_file="${JS_FILE}.backup.pve9.$(date +%Y%m%d_%H%M%S)"
|
||||
cp "$JS_FILE" "$backup_file"
|
||||
|
||||
|
||||
for f in /etc/apt/apt.conf.d/*nag*; do
|
||||
[[ -e "$f" ]] && rm -f "$f"
|
||||
done
|
||||
|
||||
[[ -f "$GZ_FILE" ]] && rm -f "$GZ_FILE"
|
||||
[[ -f "$MIN_JS_FILE" ]] && rm -f "$MIN_JS_FILE"
|
||||
|
||||
find /var/cache/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
|
||||
find /var/lib/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
|
||||
find /var/cache/nginx/ -type f -delete 2>/dev/null || true
|
||||
|
||||
|
||||
sed -i "s/res\.data\.status\.toLowerCase() !== 'active'/false/g" "$JS_FILE"
|
||||
sed -i "s/subscriptionActive: ''/subscriptionActive: true/g" "$JS_FILE"
|
||||
sed -i "s/title: gettext('No valid subscription')/title: gettext('Community Edition')/g" "$JS_FILE"
|
||||
|
||||
|
||||
sed -i "s/You do not have a valid subscription for this server/Community Edition - No subscription required/g" "$JS_FILE"
|
||||
sed -i "s/Enterprise repository needs valid subscription/Enterprise repository configured/g" "$JS_FILE"
|
||||
sed -i "s/icon: Ext\.Msg\.WARNING/icon: Ext.Msg.INFO/g" "$JS_FILE"
|
||||
|
||||
|
||||
sed -i "s/subscription = !(/subscription = false \&\& (/g" "$JS_FILE"
|
||||
|
||||
if grep -q "res\.data\.status\.toLowerCase() !== 'active'" "$JS_FILE"; then
|
||||
msg_warn "Some patches may not have applied correctly, retrying..."
|
||||
sed -i "s/res\.data\.status\.toLowerCase() !== 'active'/false/g" "$JS_FILE"
|
||||
fi
|
||||
|
||||
|
||||
[[ -f "$APT_HOOK" ]] && rm -f "$APT_HOOK"
|
||||
cat > "$APT_HOOK" << 'EOF'
|
||||
DPkg::Post-Invoke {
|
||||
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/res\\.data\\.status\\.toLowerCase() !== '\''active'\''/false/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
|
||||
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/subscriptionActive: '\'\'\''/subscriptionActive: true/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
|
||||
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/title: gettext('\''No valid subscription'\'')/title: gettext('\''Community Edition'\'')/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
|
||||
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/subscription = !(/subscription = false \\&\\& (/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
|
||||
"rm -f /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz || true";
|
||||
};
|
||||
EOF
|
||||
|
||||
chmod 644 "$APT_HOOK"
|
||||
|
||||
|
||||
if ! apt-config dump >/dev/null 2>&1; then
|
||||
msg_warn "APT hook has syntax issues, removing..."
|
||||
rm -f "$APT_HOOK"
|
||||
else
|
||||
msg_ok "APT hook created successfully"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
systemctl reload nginx 2>/dev/null || true
|
||||
|
||||
msg_ok "Subscription banner removed successfully for Proxmox VE $pve_version"
|
||||
msg_ok "Banner removal process completed - refresh your browser to see changes"
|
||||
|
||||
register_tool "subscription_banner" true
|
||||
}
|
||||
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
remove_subscription_banner_pve9
|
||||
fi
|
||||
@@ -1,119 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# Remove Subscription Banner - Proxmox VE 9.x ONLY
|
||||
# ==========================================================
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# Tool registration system
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
register_tool() {
|
||||
local tool="$1"
|
||||
local state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
remove_subscription_banner_pve9() {
|
||||
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
|
||||
local MIN_JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
|
||||
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
|
||||
local APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
|
||||
|
||||
# Verify PVE 9.x
|
||||
local pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1)
|
||||
local pve_major=$(echo "$pve_version" | cut -d. -f1)
|
||||
|
||||
if [ "$pve_major" -lt 9 ] 2>/dev/null; then
|
||||
msg_error "This script is for PVE 9.x only. Detected PVE $pve_version"
|
||||
return 1
|
||||
fi
|
||||
|
||||
msg_info "Detected Proxmox VE $pve_version - Applying PVE 9.x patches"
|
||||
|
||||
# Verify that the file exists
|
||||
if [ ! -f "$JS_FILE" ]; then
|
||||
msg_error "JavaScript file not found: $JS_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
# Create backup of original file
|
||||
local backup_file="${JS_FILE}.backup.pve9.$(date +%Y%m%d_%H%M%S)"
|
||||
cp "$JS_FILE" "$backup_file"
|
||||
|
||||
# Clean any existing problematic APT hooks
|
||||
for f in /etc/apt/apt.conf.d/*nag*; do
|
||||
[[ -e "$f" ]] && rm -f "$f"
|
||||
done
|
||||
|
||||
|
||||
# Main subscription check patches for PVE 9
|
||||
sed -i "s/res\.data\.status\.toLowerCase() !== 'active'/false/g" "$JS_FILE"
|
||||
sed -i "s/subscriptionActive: ''/subscriptionActive: true/g" "$JS_FILE"
|
||||
sed -i "s/title: gettext('No valid subscription')/title: gettext('Community Edition')/g" "$JS_FILE"
|
||||
|
||||
# Additional UX improvements for PVE 9
|
||||
sed -i "s/You do not have a valid subscription for this server/Community Edition - No subscription required/g" "$JS_FILE"
|
||||
sed -i "s/Enterprise repository needs valid subscription/Enterprise repository configured/g" "$JS_FILE"
|
||||
sed -i "s/icon: Ext\.Msg\.WARNING/icon: Ext.Msg.INFO/g" "$JS_FILE"
|
||||
|
||||
# Additional subscription patterns that may exist in PVE 9
|
||||
sed -i "s/subscription = !(/subscription = false \&\& (/g" "$JS_FILE"
|
||||
|
||||
# Remove compressed/minified files to force regeneration
|
||||
[[ -f "$GZ_FILE" ]] && rm -f "$GZ_FILE"
|
||||
[[ -f "$MIN_JS_FILE" ]] && rm -f "$MIN_JS_FILE"
|
||||
|
||||
# Clear various caches
|
||||
find /var/cache/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
|
||||
find /var/lib/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
|
||||
|
||||
# Create PVE 9.x specific APT hook
|
||||
[[ -f "$APT_HOOK" ]] && rm -f "$APT_HOOK"
|
||||
cat > "$APT_HOOK" << 'EOF'
|
||||
DPkg::Post-Invoke {
|
||||
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/res\\.data\\.status\\.toLowerCase() !== '\''active'\''/false/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
|
||||
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/subscriptionActive: '\'\'\''/subscriptionActive: true/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
|
||||
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/title: gettext('\''No valid subscription'\'')/title: gettext('\''Community Edition'\'')/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
|
||||
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/subscription = !(/subscription = false \\&\\& (/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
|
||||
"rm -f /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz || true";
|
||||
};
|
||||
EOF
|
||||
|
||||
chmod 644 "$APT_HOOK"
|
||||
|
||||
# Verify APT hook syntax
|
||||
if ! apt-config dump >/dev/null 2>&1; then
|
||||
msg_warn "APT hook has syntax issues, removing..."
|
||||
rm -f "$APT_HOOK"
|
||||
else
|
||||
msg_ok "APT hook created successfully"
|
||||
fi
|
||||
|
||||
|
||||
msg_ok "Subscription banner removed successfully for Proxmox VE $pve_version"
|
||||
msg_ok "Banner removal process completed"
|
||||
|
||||
|
||||
register_tool "subscription_banner" true
|
||||
}
|
||||
|
||||
|
||||
# Execute function if called directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
remove_subscription_banner_pve9
|
||||
fi
|
||||
@@ -1,257 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# Remove Subscription Banner - Proxmox VE 9.x (Clean Version)
|
||||
# ==========================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
register_tool() {
|
||||
command -v jq >/dev/null 2>&1 || return 0
|
||||
local tool="$1" state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" \
|
||||
> "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
|
||||
MIN_JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
|
||||
GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
|
||||
MOBILE_TPL="/usr/share/pve-yew-mobile-gui/index.html.tpl"
|
||||
APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
|
||||
PATCH_BIN="/usr/local/bin/pve-remove-nag.sh"
|
||||
|
||||
MARK_JS="PROXMENUX_NAG_REMOVED_v2"
|
||||
MARK_MOBILE="<!-- PROXMENUX: MOBILE NAG PATCH v2 -->"
|
||||
|
||||
|
||||
verify_js_integrity() {
|
||||
local file="$1"
|
||||
[ -f "$file" ] || return 1
|
||||
[ -s "$file" ] || return 1
|
||||
grep -Eq 'Ext|function|var|const|let' "$file" || return 1
|
||||
if LC_ALL=C grep -qP '\x00' "$file" 2>/dev/null; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
create_backup() {
|
||||
local file="$1"
|
||||
local backup_dir="$BASE_DIR/backups"
|
||||
local timestamp
|
||||
timestamp=$(date +%Y%m%d_%H%M%S)
|
||||
local backup_file="$backup_dir/$(basename "$file").backup.$timestamp"
|
||||
mkdir -p "$backup_dir"
|
||||
if [ -f "$file" ]; then
|
||||
cp -a "$file" "$backup_file"
|
||||
ls -t "$backup_dir"/"$(basename "$file")".backup.* 2>/dev/null | tail -n +6 | xargs -r rm -f 2>/dev/null || true
|
||||
echo "$backup_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# ----------------------------------------------------
|
||||
|
||||
create_patch_script() {
|
||||
cat > "$PATCH_BIN" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
|
||||
MIN_JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
|
||||
GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
|
||||
MOBILE_TPL="/usr/share/pve-yew-mobile-gui/index.html.tpl"
|
||||
MARK_JS="PROXMENUX_NAG_REMOVED_v2"
|
||||
MARK_MOBILE="<!-- PROXMENUX: MOBILE NAG PATCH v2 -->"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
|
||||
verify_js_integrity() {
|
||||
local file="$1"
|
||||
[ -f "$file" ] && [ -s "$file" ] && grep -Eq 'Ext|function' "$file" && ! LC_ALL=C grep -qP '\x00' "$file" 2>/dev/null
|
||||
}
|
||||
|
||||
patch_web() {
|
||||
[ -f "$JS_FILE" ] || return 0
|
||||
grep -q "$MARK_JS" "$JS_FILE" && return 0
|
||||
|
||||
local backup_dir="$BASE_DIR/backups"
|
||||
mkdir -p "$backup_dir"
|
||||
local backup="$backup_dir/$(basename "$JS_FILE").backup.$(date +%Y%m%d_%H%M%S)"
|
||||
cp -a "$JS_FILE" "$backup"
|
||||
trap "cp -a '$backup' '$JS_FILE' 2>/dev/null || true" ERR
|
||||
|
||||
sed -i '1s|^|/* '"$MARK_JS"' */\n|' "$JS_FILE"
|
||||
|
||||
local patterns_found=0
|
||||
|
||||
if grep -q "res\.data\.status\.toLowerCase() !== 'active'" "$JS_FILE"; then
|
||||
sed -i "s/res\.data\.status\.toLowerCase() !== 'active'/false/g" "$JS_FILE"
|
||||
patterns_found=$((patterns_found + 1))
|
||||
fi
|
||||
|
||||
if grep -q "subscriptionActive: ''" "$JS_FILE"; then
|
||||
sed -i "s/subscriptionActive: ''/subscriptionActive: true/g" "$JS_FILE"
|
||||
patterns_found=$((patterns_found + 1))
|
||||
fi
|
||||
|
||||
if grep -q "title: gettext('No valid subscription')" "$JS_FILE"; then
|
||||
sed -i "s/title: gettext('No valid subscription')/title: gettext('Community Edition')/g" "$JS_FILE"
|
||||
patterns_found=$((patterns_found + 1))
|
||||
fi
|
||||
|
||||
if grep -q "icon: Ext\.Msg\.WARNING" "$JS_FILE"; then
|
||||
sed -i "s/icon: Ext\.Msg\.WARNING/icon: Ext.Msg.INFO/g" "$JS_FILE"
|
||||
patterns_found=$((patterns_found + 1))
|
||||
fi
|
||||
|
||||
if grep -q "subscription = !(" "$JS_FILE"; then
|
||||
sed -i "s/subscription = !(/subscription = false \&\& (/g" "$JS_FILE"
|
||||
patterns_found=$((patterns_found + 1))
|
||||
fi
|
||||
|
||||
# Si nada coincidió (cambio upstream), restaura y sal limpio
|
||||
if [ "${patterns_found:-0}" -eq 0 ]; then
|
||||
cp -a "$backup" "$JS_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Verificación final
|
||||
if ! verify_js_integrity "$JS_FILE"; then
|
||||
cp -a "$backup" "$JS_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Limpiar artefactos/cachés
|
||||
rm -f "$MIN_JS_FILE" "$GZ_FILE" 2>/dev/null || true
|
||||
find /var/cache/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
|
||||
find /var/lib/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
|
||||
find /var/cache/nginx/ -type f -delete 2>/dev/null || true
|
||||
|
||||
trap - ERR
|
||||
}
|
||||
|
||||
patch_mobile() {
|
||||
[ -f "$MOBILE_TPL" ] || return 0
|
||||
grep -q "$MARK_MOBILE" "$MOBILE_TPL" && return 0
|
||||
|
||||
local backup_dir="$BASE_DIR/backups"
|
||||
mkdir -p "$backup_dir"
|
||||
cp -a "$MOBILE_TPL" "$backup_dir/$(basename "$MOBILE_TPL").backup.$(date +%Y%m%d_%H%M%S)"
|
||||
|
||||
cat >> "$MOBILE_TPL" <<EOM
|
||||
$MARK_MOBILE
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
function removeSubscriptionElements() {
|
||||
try {
|
||||
const dialogs = document.querySelectorAll('dialog.pwt-outer-dialog');
|
||||
dialogs.forEach(d => {
|
||||
const text = (d.textContent || '').toLowerCase();
|
||||
if (text.includes('subscription') || text.includes('no valid')) { d.remove(); }
|
||||
});
|
||||
const cards = document.querySelectorAll('.pwt-card.pwt-p-2.pwt-d-flex.pwt-interactive.pwt-justify-content-center');
|
||||
cards.forEach(c => {
|
||||
const text = (c.textContent || '').toLowerCase();
|
||||
const hasButton = c.querySelector('button');
|
||||
if (!hasButton && (text.includes('subscription') || text.includes('no valid'))) { c.remove(); }
|
||||
});
|
||||
const alerts = document.querySelectorAll('[class*="alert"], [class*="warning"], [class*="notice"]');
|
||||
alerts.forEach(a => {
|
||||
const text = (a.textContent || '').toLowerCase();
|
||||
if (text.includes('subscription') || text.includes('no valid')) { a.remove(); }
|
||||
});
|
||||
} catch (e) { console.warn('Error removing subscription elements:', e); }
|
||||
}
|
||||
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', removeSubscriptionElements); }
|
||||
else { removeSubscriptionElements(); }
|
||||
const observer = new MutationObserver(removeSubscriptionElements);
|
||||
if (document.body) {
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
const interval = setInterval(removeSubscriptionElements, 500);
|
||||
setTimeout(() => { try { observer.disconnect(); clearInterval(interval); } catch(e){} }, 30000);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
EOM
|
||||
}
|
||||
|
||||
reload_services() {
|
||||
systemctl is-active --quiet pveproxy 2>/dev/null && {
|
||||
systemctl reload pveproxy 2>/dev/null || systemctl restart pveproxy 2>/dev/null || true
|
||||
}
|
||||
systemctl is-active --quiet nginx 2>/dev/null && {
|
||||
systemctl reload nginx 2>/dev/null || true
|
||||
}
|
||||
systemctl is-active --quiet pvedaemon 2>/dev/null && {
|
||||
systemctl reload pvedaemon 2>/dev/null || true
|
||||
}
|
||||
find /var/cache/pve-manager/ -type f -delete 2>/dev/null || true
|
||||
find /var/lib/pve-manager/ -type f -delete 2>/dev/null || true
|
||||
}
|
||||
|
||||
main() {
|
||||
patch_web || return 1
|
||||
patch_mobile
|
||||
reload_services
|
||||
}
|
||||
|
||||
main
|
||||
EOF
|
||||
chmod 755 "$PATCH_BIN"
|
||||
}
|
||||
# ----------------------------------------------------
|
||||
|
||||
|
||||
create_apt_hook() {
|
||||
cat > "$APT_HOOK" <<'EOF'
|
||||
/* ProxMenux: reapply nag patch after upgrades */
|
||||
DPkg::Post-Invoke { "/usr/local/bin/pve-remove-nag.sh || true"; };
|
||||
EOF
|
||||
chmod 644 "$APT_HOOK"
|
||||
apt-config dump >/dev/null 2>&1 || { msg_warn "APT hook syntax issue"; rm -f "$APT_HOOK"; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
remove_subscription_banner_pve9() {
|
||||
local pve_version
|
||||
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1 || true)
|
||||
local pve_major="${pve_version%%.*}"
|
||||
|
||||
msg_info "$(translate "Detected Proxmox VE ${pve_version:-9.x} – removing subscription banner")"
|
||||
|
||||
create_patch_script
|
||||
create_apt_hook
|
||||
|
||||
if ! "$PATCH_BIN"; then
|
||||
msg_error "$(translate "Error applying patches")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
register_tool "subscription_banner" true
|
||||
msg_ok "$(translate "Subscription banner removed successfully.")"
|
||||
msg_ok "$(translate "Refresh your browser to see changes.")"
|
||||
}
|
||||
|
||||
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
remove_subscription_banner_pve9
|
||||
fi
|
||||
@@ -1,339 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# Proxmox VE Update Script
|
||||
# ==========================================================
|
||||
|
||||
# Configuration
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
register_tool() {
|
||||
local tool="$1"
|
||||
local state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
download_common_functions() {
|
||||
if ! source "$LOCAL_SCRIPTS/global/common-functions.sh"; then
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
update_pve9() {
|
||||
local pve_version=$(pveversion | awk -F'/' '{print $2}' | cut -d'-' -f1)
|
||||
local start_time=$(date +%s)
|
||||
local log_file="/var/log/proxmox-update-$(date +%Y%m%d-%H%M%S).log"
|
||||
local changes_made=false
|
||||
local OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs)"
|
||||
local TARGET_CODENAME="trixie"
|
||||
|
||||
if [ -z "$OS_CODENAME" ]; then
|
||||
OS_CODENAME=$(lsb_release -cs 2>/dev/null || echo "trixie")
|
||||
fi
|
||||
|
||||
download_common_functions
|
||||
|
||||
|
||||
msg_info2 "$(translate "Detected: Proxmox VE $pve_version (Current: $OS_CODENAME, Target: $TARGET_CODENAME)")"
|
||||
echo -e
|
||||
|
||||
local available_space=$(df /var/cache/apt/archives | awk 'NR==2 {print int($4/1024)}')
|
||||
if [ "$available_space" -lt 1024 ]; then
|
||||
msg_error "$(translate "Insufficient disk space. Available: ${available_space}MB")"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! ping -c 1 download.proxmox.com >/dev/null 2>&1; then
|
||||
msg_error "$(translate "Cannot reach Proxmox repositories")"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
disable_sources_repo() {
|
||||
local file="$1"
|
||||
if [[ -f "$file" ]]; then
|
||||
sed -i ':a;/^\n*$/{$d;N;ba}' "$file"
|
||||
|
||||
if grep -q "^Enabled:" "$file"; then
|
||||
sed -i 's/^Enabled:.*$/Enabled: false/' "$file"
|
||||
else
|
||||
echo "Enabled: false" >> "$file"
|
||||
fi
|
||||
|
||||
if ! grep -q "^Types: " "$file"; then
|
||||
msg_warn "$(translate "Malformed .sources file detected, removing: $(basename "$file")")"
|
||||
rm -f "$file"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if disable_sources_repo "/etc/apt/sources.list.d/pve-enterprise.sources"; then
|
||||
msg_ok "$(translate "Enterprise Proxmox repository disabled")"
|
||||
changes_made=true
|
||||
fi
|
||||
|
||||
if disable_sources_repo "/etc/apt/sources.list.d/ceph.sources"; then
|
||||
msg_ok "$(translate "Enterprise Proxmox Ceph repository disabled")"
|
||||
changes_made=true
|
||||
fi
|
||||
|
||||
for legacy_file in /etc/apt/sources.list.d/pve-public-repo.list \
|
||||
/etc/apt/sources.list.d/pve-install-repo.list \
|
||||
/etc/apt/sources.list.d/debian.list; do
|
||||
if [[ -f "$legacy_file" ]]; then
|
||||
rm -f "$legacy_file"
|
||||
msg_ok "$(translate "Removed legacy repository: $(basename "$legacy_file")")"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if [[ -f /etc/apt/sources.list.d/debian.sources ]]; then
|
||||
rm -f /etc/apt/sources.list.d/debian.sources
|
||||
msg_ok "$(translate "Old debian.sources file removed to prevent duplication")"
|
||||
fi
|
||||
|
||||
|
||||
msg_info "$(translate "Creating Proxmox VE 9.x no-subscription repository...")"
|
||||
cat > /etc/apt/sources.list.d/proxmox.sources << EOF
|
||||
Enabled: true
|
||||
Types: deb
|
||||
URIs: http://download.proxmox.com/debian/pve
|
||||
Suites: ${TARGET_CODENAME}
|
||||
Components: pve-no-subscription
|
||||
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
|
||||
EOF
|
||||
msg_ok "$(translate "Proxmox VE 9.x no-subscription repository created")"
|
||||
changes_made=true
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Creating Debian ${TARGET_CODENAME} sources file...")"
|
||||
cat > /etc/apt/sources.list.d/debian.sources << EOF
|
||||
Types: deb
|
||||
URIs: http://deb.debian.org/debian/
|
||||
Suites: ${TARGET_CODENAME} ${TARGET_CODENAME}-updates
|
||||
Components: main contrib non-free non-free-firmware
|
||||
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
||||
|
||||
Types: deb
|
||||
URIs: http://security.debian.org/debian-security/
|
||||
Suites: ${TARGET_CODENAME}-security
|
||||
Components: main contrib non-free non-free-firmware
|
||||
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
||||
EOF
|
||||
|
||||
|
||||
|
||||
msg_ok "$(translate "Debian repositories configured for $TARGET_CODENAME")"
|
||||
|
||||
local firmware_conf="/etc/apt/apt.conf.d/no-firmware-warnings.conf"
|
||||
if [ ! -f "$firmware_conf" ]; then
|
||||
msg_info "$(translate "Disabling non-free firmware warnings...")"
|
||||
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' > "$firmware_conf"
|
||||
msg_ok "$(translate "Non-free firmware warnings disabled")"
|
||||
fi
|
||||
|
||||
update_output=$(apt-get update 2>&1)
|
||||
update_exit_code=$?
|
||||
|
||||
if [ $update_exit_code -eq 0 ]; then
|
||||
msg_ok "$(translate "Package lists updated successfully")"
|
||||
else
|
||||
if echo "$update_output" | grep -q "NO_PUBKEY\|GPG error"; then
|
||||
msg_info "$(translate "Fixing GPG key issues...")"
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys $(echo "$update_output" | grep "NO_PUBKEY" | sed 's/.*NO_PUBKEY //' | head -1) 2>/dev/null
|
||||
if apt-get update > "$log_file" 2>&1; then
|
||||
msg_ok "$(translate "Package lists updated after GPG fix")"
|
||||
else
|
||||
msg_error "$(translate "Failed to update package lists. Check log: $log_file")"
|
||||
return 1
|
||||
fi
|
||||
elif echo "$update_output" | grep -q "404\|Failed to fetch"; then
|
||||
msg_warn "$(translate "Some repositories are not available, continuing with available ones...")"
|
||||
else
|
||||
msg_error "$(translate "Failed to update package lists. Check log: $log_file")"
|
||||
echo "Error details: $update_output"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if apt policy 2>/dev/null | grep -q "${TARGET_CODENAME}.*pve-no-subscription"; then
|
||||
msg_ok "$(translate "Proxmox VE 9.x repositories verified")"
|
||||
else
|
||||
msg_warn "$(translate "Proxmox VE 9.x repositories verification inconclusive, continuing...")"
|
||||
fi
|
||||
|
||||
local current_pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
local available_pve_version=$(apt-cache policy pve-manager 2>/dev/null | grep -oP 'Candidate: \K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
local upgradable=$(apt list --upgradable 2>/dev/null | grep -c "upgradable")
|
||||
local security_updates=$(apt list --upgradable 2>/dev/null | grep -c "security")
|
||||
|
||||
show_update_menu() {
|
||||
local current_version="$1"
|
||||
local target_version="$2"
|
||||
local upgradable_count="$3"
|
||||
local security_count="$4"
|
||||
|
||||
local menu_text="$(translate "System Update Information")\n\n"
|
||||
menu_text+="$(translate "Current PVE Version"): $current_version\n"
|
||||
if [ -n "$target_version" ] && [ "$target_version" != "$current_version" ]; then
|
||||
menu_text+="$(translate "Available PVE Version"): $target_version\n"
|
||||
fi
|
||||
menu_text+="\n$(translate "Package Updates Available"): $upgradable_count\n"
|
||||
menu_text+="$(translate "Security Updates"): $security_count\n\n"
|
||||
|
||||
if [ "$upgradable_count" -eq 0 ]; then
|
||||
menu_text+="$(translate "System is already up to date")"
|
||||
whiptail --title "$(translate "Update Status")" --msgbox "$menu_text" 15 70
|
||||
return 2
|
||||
else
|
||||
menu_text+="$(translate "Do you want to proceed with the system update?")"
|
||||
if whiptail --title "$(translate "Proxmox Update")" --yesno "$menu_text" 18 70; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
show_update_menu "$current_pve_version" "$available_pve_version" "$upgradable" "$security_updates"
|
||||
MENU_RESULT=$?
|
||||
|
||||
if [[ $MENU_RESULT -eq 1 ]]; then
|
||||
msg_info2 "$(translate "Update cancelled by user")"
|
||||
apt-get -y autoremove > /dev/null 2>&1 || true
|
||||
apt-get -y autoclean > /dev/null 2>&1 || true
|
||||
return 0
|
||||
elif [[ $MENU_RESULT -eq 2 ]]; then
|
||||
msg_ok "$(translate "System is already up to date. No update needed.")"
|
||||
apt-get -y autoremove > /dev/null 2>&1 || true
|
||||
apt-get -y autoclean > /dev/null 2>&1 || true
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_info "$(translate "Cleaning up unused time synchronization services...")"
|
||||
|
||||
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' purge ntp openntpd systemd-timesyncd > /dev/null 2>&1; then
|
||||
msg_ok "$(translate "Old time services removed successfully")"
|
||||
else
|
||||
msg_warn "$(translate "Some old time services could not be removed (not installed)")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Updating packages...")"
|
||||
apt-get install pv -y > /dev/null 2>&1
|
||||
msg_ok "$(translate "Packages updated successfully")"
|
||||
|
||||
tput sc
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y \
|
||||
-o Dpkg::Options::='--force-confdef' \
|
||||
-o Dpkg::Options::='--force-confold' \
|
||||
dist-upgrade 2>&1 | while IFS= read -r line; do
|
||||
|
||||
echo "$line" >> "$log_file"
|
||||
|
||||
if [[ "$line" =~ \[[#=\-]+\]\ *[0-9]{1,3}% ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" =~ ^(Setting\ up|Unpacking|Preparing\ to\ unpack|Processing\ triggers\ for) ]]; then
|
||||
package_name=$(echo "$line" | sed -E 's/.*(Setting up|Unpacking|Preparing to unpack|Processing triggers for) ([^ :]+).*/\2/')
|
||||
[ -z "$package_name" ] && package_name="$(translate "Unknown")"
|
||||
|
||||
row=$(( $(tput lines) - 6 ))
|
||||
tput cup $row 0; printf "%s\n" "$(translate "Installing packages...")"
|
||||
tput cup $((row + 1)) 0; printf "%s\n" "──────────────────────────────────────────────"
|
||||
tput cup $((row + 2)) 0; printf "%s %s\n" "$(translate "Package:")" "$package_name"
|
||||
tput cup $((row + 3)) 0; printf "%s\n" "Progress: [ ] 0%"
|
||||
tput cup $((row + 4)) 0; printf "%s\n" "──────────────────────────────────────────────"
|
||||
|
||||
for i in $(seq 1 10); do
|
||||
sleep 0.1
|
||||
progress=$((i * 10))
|
||||
tput cup $((row + 3)) 9
|
||||
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
tput rc
|
||||
tput ed
|
||||
|
||||
upgrade_exit_code=${PIPESTATUS[0]}
|
||||
|
||||
|
||||
|
||||
|
||||
if [ $upgrade_exit_code -eq 0 ]; then
|
||||
msg_ok "$(translate "System upgrade completed successfully")"
|
||||
else
|
||||
msg_error "$(translate "System upgrade failed. Check log: $log_file")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
msg_info "$(translate "Installing essential Proxmox packages...")"
|
||||
local additional_packages="zfsutils-linux proxmox-backup-restore-image chrony"
|
||||
|
||||
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install $additional_packages >> "$log_file" 2>&1; then
|
||||
msg_ok "$(translate "Essential Proxmox packages installed")"
|
||||
else
|
||||
msg_warn "$(translate "Some essential Proxmox packages may not have been installed")"
|
||||
fi
|
||||
|
||||
lvm_repair_check
|
||||
cleanup_duplicate_repos
|
||||
|
||||
#msg_info "$(translate "Performing system cleanup...")"
|
||||
apt-get -y autoremove > /dev/null 2>&1 || true
|
||||
apt-get -y autoclean > /dev/null 2>&1 || true
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
|
||||
local end_time=$(date +%s)
|
||||
local duration=$((end_time - start_time))
|
||||
local minutes=$((duration / 60))
|
||||
local seconds=$((duration % 60))
|
||||
|
||||
echo -e "${TAB}${BGN}$(translate "====== PVE UPDATE COMPLETED ======")${CL}"
|
||||
echo -e "${TAB}${GN}⏱️ $(translate "Duration")${CL}: ${BL}${minutes}m ${seconds}s${CL}"
|
||||
echo -e "${TAB}${GN}📄 $(translate "Log file")${CL}: ${BL}$log_file${CL}"
|
||||
echo -e "${TAB}${GN}📦 $(translate "Packages upgraded")${CL}: ${BL}$upgradable${CL}"
|
||||
echo -e "${TAB}${GN}🖥️ $(translate "Proxmox VE")${CL}: ${BL}$target_version (Debian $OS_CODENAME)${CL}"
|
||||
|
||||
msg_ok "$(translate "Proxmox VE 9.x configuration completed.")"
|
||||
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
update_pve9
|
||||
fi
|
||||
@@ -1,304 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# Proxmox VE Update Script - Improved Version
|
||||
# ==========================================================
|
||||
|
||||
# Configuration
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
register_tool() {
|
||||
local tool="$1"
|
||||
local state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
download_common_functions() {
|
||||
if ! source <(curl -s "$REPO_URL/scripts/global/common-functions.sh"); then
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
update_pve9() {
|
||||
local pve_version=$(pveversion | awk -F'/' '{print $2}' | cut -d'-' -f1)
|
||||
local start_time=$(date +%s)
|
||||
local log_file="/var/log/proxmox-update-$(date +%Y%m%d-%H%M%S).log"
|
||||
local changes_made=false
|
||||
local OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs)"
|
||||
local TARGET_CODENAME="trixie"
|
||||
|
||||
local screen_capture="/tmp/proxmenux_screen_capture_$$.txt"
|
||||
|
||||
if [ -z "$OS_CODENAME" ]; then
|
||||
OS_CODENAME=$(lsb_release -cs 2>/dev/null || echo "trixie")
|
||||
fi
|
||||
|
||||
download_common_functions
|
||||
|
||||
{
|
||||
msg_info2 "$(translate "Detected: Proxmox VE $pve_version (Current: $OS_CODENAME, Target: $TARGET_CODENAME)")"
|
||||
} | tee -a "$screen_capture"
|
||||
|
||||
|
||||
local available_space=$(df /var/cache/apt/archives | awk 'NR==2 {print int($4/1024)}')
|
||||
if [ "$available_space" -lt 1024 ]; then
|
||||
msg_error "$(translate "Insufficient disk space. Available: ${available_space}MB")"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! ping -c 1 download.proxmox.com >/dev/null 2>&1; then
|
||||
msg_error "$(translate "Cannot reach Proxmox repositories")"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
|
||||
disable_sources_repo() {
|
||||
local file="$1"
|
||||
if [[ -f "$file" ]]; then
|
||||
sed -i ':a;/^\n*$/{$d;N;ba}' "$file"
|
||||
|
||||
if grep -q "^Enabled:" "$file"; then
|
||||
sed -i 's/^Enabled:.*$/Enabled: false/' "$file"
|
||||
else
|
||||
echo "Enabled: false" >> "$file"
|
||||
fi
|
||||
|
||||
if ! grep -q "^Types: " "$file"; then
|
||||
msg_warn "$(translate "Malformed .sources file detected, removing: $(basename "$file")")"
|
||||
rm -f "$file"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
if disable_sources_repo "/etc/apt/sources.list.d/pve-enterprise.sources"; then
|
||||
msg_ok "$(translate "Enterprise Proxmox repository disabled")" | tee -a "$screen_capture"
|
||||
changes_made=true
|
||||
fi
|
||||
|
||||
if disable_sources_repo "/etc/apt/sources.list.d/ceph.sources"; then
|
||||
msg_ok "$(translate "Enterprise Proxmox Ceph repository disabled")" | tee -a "$screen_capture"
|
||||
changes_made=true
|
||||
fi
|
||||
|
||||
for legacy_file in /etc/apt/sources.list.d/pve-public-repo.list \
|
||||
/etc/apt/sources.list.d/pve-install-repo.list \
|
||||
/etc/apt/sources.list.d/debian.list; do
|
||||
if [[ -f "$legacy_file" ]]; then
|
||||
rm -f "$legacy_file"
|
||||
msg_ok "$(translate "Removed legacy repository: $(basename "$legacy_file")")" | tee -a "$screen_capture"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -f /etc/apt/sources.list.d/debian.sources ]]; then
|
||||
rm -f /etc/apt/sources.list.d/debian.sources
|
||||
msg_ok "$(translate "Old debian.sources file removed to prevent duplication")" | tee -a "$screen_capture"
|
||||
fi
|
||||
|
||||
msg_info "$(translate "Creating Proxmox VE 9.x no-subscription repository...")"
|
||||
cat > /etc/apt/sources.list.d/proxmox.sources << EOF
|
||||
Enabled: true
|
||||
Types: deb
|
||||
URIs: http://download.proxmox.com/debian/pve
|
||||
Suites: ${TARGET_CODENAME}
|
||||
Components: pve-no-subscription
|
||||
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
|
||||
EOF
|
||||
msg_ok "$(translate "Proxmox VE 9.x no-subscription repository created")" | tee -a "$screen_capture"
|
||||
changes_made=true
|
||||
|
||||
msg_info "$(translate "Creating Debian ${TARGET_CODENAME} sources file...")"
|
||||
cat > /etc/apt/sources.list.d/debian.sources << EOF
|
||||
Types: deb
|
||||
URIs: http://deb.debian.org/debian/
|
||||
Suites: ${TARGET_CODENAME} ${TARGET_CODENAME}-updates
|
||||
Components: main contrib non-free non-free-firmware
|
||||
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
||||
|
||||
Types: deb
|
||||
URIs: http://security.debian.org/debian-security/
|
||||
Suites: ${TARGET_CODENAME}-security
|
||||
Components: main contrib non-free non-free-firmware
|
||||
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
||||
EOF
|
||||
|
||||
msg_ok "$(translate "Debian repositories configured for $TARGET_CODENAME")"
|
||||
|
||||
local firmware_conf="/etc/apt/apt.conf.d/no-firmware-warnings.conf"
|
||||
if [ ! -f "$firmware_conf" ]; then
|
||||
msg_info "$(translate "Disabling non-free firmware warnings...")"
|
||||
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' > "$firmware_conf"
|
||||
msg_ok "$(translate "Non-free firmware warnings disabled")"
|
||||
fi
|
||||
|
||||
#update_output=$(apt-get update 2>&1)
|
||||
update_output=$(apt-get -o Dpkg::Progress-Fancy=1 update 2>&1)
|
||||
update_exit_code=$?
|
||||
|
||||
if [ $update_exit_code -eq 0 ]; then
|
||||
msg_ok "$(translate "Package lists updated successfully")" | tee -a "$screen_capture"
|
||||
else
|
||||
if echo "$update_output" | grep -q "NO_PUBKEY\|GPG error"; then
|
||||
msg_info "$(translate "Fixing GPG key issues...")"
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys $(echo "$update_output" | grep "NO_PUBKEY" | sed 's/.*NO_PUBKEY //' | head -1) 2>/dev/null
|
||||
if apt-get update > "$log_file" 2>&1; then
|
||||
msg_ok "$(translate "Package lists updated after GPG fix")" | tee -a "$screen_capture"
|
||||
else
|
||||
msg_error "$(translate "Failed to update package lists. Check log: $log_file")"
|
||||
return 1
|
||||
fi
|
||||
elif echo "$update_output" | grep -q "404\|Failed to fetch"; then
|
||||
msg_warn "$(translate "Some repositories are not available, continuing with available ones...")"
|
||||
else
|
||||
msg_error "$(translate "Failed to update package lists. Check log: $log_file")"
|
||||
echo "Error details: $update_output"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if apt policy 2>/dev/null | grep -q "${TARGET_CODENAME}.*pve-no-subscription"; then
|
||||
msg_ok "$(translate "Proxmox VE 9.x repositories verified")" | tee -a "$screen_capture"
|
||||
else
|
||||
msg_warn "$(translate "Proxmox VE 9.x repositories verification inconclusive, continuing...")"
|
||||
fi
|
||||
|
||||
local current_pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
local available_pve_version=$(apt-cache policy pve-manager 2>/dev/null | grep -oP 'Candidate: \K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
local upgradable=$(apt list --upgradable 2>/dev/null | grep -c "upgradable")
|
||||
local security_updates=$(apt list --upgradable 2>/dev/null | grep -c "security")
|
||||
|
||||
show_update_menu() {
|
||||
local current_version="$1"
|
||||
local target_version="$2"
|
||||
local upgradable_count="$3"
|
||||
local security_count="$4"
|
||||
|
||||
local menu_text="$(translate "System Update Information")\n\n"
|
||||
menu_text+="$(translate "Current PVE Version"): $current_version\n"
|
||||
if [ -n "$target_version" ] && [ "$target_version" != "$current_version" ]; then
|
||||
menu_text+="$(translate "Available PVE Version"): $target_version\n"
|
||||
fi
|
||||
menu_text+="\n$(translate "Package Updates Available"): $upgradable_count\n"
|
||||
menu_text+="$(translate "Security Updates"): $security_count\n\n"
|
||||
|
||||
if [ "$upgradable_count" -eq 0 ]; then
|
||||
menu_text+="$(translate "System is already up to date")"
|
||||
whiptail --title "$(translate "Update Status")" --msgbox "$menu_text" 15 70
|
||||
return 2
|
||||
else
|
||||
menu_text+="$(translate "Do you want to proceed with the system update?")"
|
||||
if whiptail --title "$(translate "Proxmox Update")" --yesno "$menu_text" 18 70; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
show_update_menu "$current_pve_version" "$available_pve_version" "$upgradable" "$security_updates"
|
||||
MENU_RESULT=$?
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "$SCRIPT_TITLE")"
|
||||
cat "$screen_capture"
|
||||
|
||||
|
||||
if [[ $MENU_RESULT -eq 1 ]]; then
|
||||
msg_info2 "$(translate "Update cancelled by user")"
|
||||
apt-get -y autoremove > /dev/null 2>&1 || true
|
||||
apt-get -y autoclean > /dev/null 2>&1 || true
|
||||
rm -f "$screen_capture"
|
||||
return 0
|
||||
elif [[ $MENU_RESULT -eq 2 ]]; then
|
||||
msg_ok "$(translate "System is already up to date. No update needed.")"
|
||||
apt-get -y autoremove > /dev/null 2>&1 || true
|
||||
apt-get -y autoclean > /dev/null 2>&1 || true
|
||||
rm -f "$screen_capture"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_info "$(translate "Cleaning up unused time synchronization services...")"
|
||||
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' purge ntp openntpd systemd-timesyncd > /dev/null 2>&1; then
|
||||
msg_ok "$(translate "Old time services removed successfully")"
|
||||
else
|
||||
msg_warn "$(translate "Some old time services could not be removed (not installed)")"
|
||||
fi
|
||||
|
||||
echo -e
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y \
|
||||
-o Dpkg::Options::='--force-confdef' \
|
||||
-o Dpkg::Options::='--force-confold' \
|
||||
dist-upgrade 2>&1 | tee -a "$log_file"
|
||||
|
||||
upgrade_exit_code=${PIPESTATUS[0]}
|
||||
echo -e
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "$SCRIPT_TITLE")"
|
||||
cat "$screen_capture"
|
||||
|
||||
|
||||
if [ $upgrade_exit_code -ne 0 ]; then
|
||||
msg_error "$(translate "System upgrade failed. Check log: $log_file")"
|
||||
rm -f "$screen_capture"
|
||||
return 1
|
||||
fi
|
||||
|
||||
msg_info "$(translate "Installing essential Proxmox packages...")"
|
||||
local additional_packages="zfsutils-linux proxmox-backup-restore-image chrony"
|
||||
|
||||
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install $additional_packages >> "$log_file" 2>&1; then
|
||||
msg_ok "$(translate "Essential Proxmox packages installed")"
|
||||
else
|
||||
msg_warn "$(translate "Some essential Proxmox packages may not have been installed")"
|
||||
fi
|
||||
|
||||
lvm_repair_check
|
||||
cleanup_duplicate_repos
|
||||
|
||||
apt-get -y autoremove > /dev/null 2>&1 || true
|
||||
apt-get -y autoclean > /dev/null 2>&1 || true
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
|
||||
local end_time=$(date +%s)
|
||||
local duration=$((end_time - start_time))
|
||||
local minutes=$((duration / 60))
|
||||
local seconds=$((duration % 60))
|
||||
|
||||
echo -e "${TAB}${BGN}$(translate "====== PVE UPDATE COMPLETED ======")${CL}"
|
||||
echo -e "${TAB}${GN}⏱️ $(translate "Duration")${CL}: ${BL}${minutes}m ${seconds}s${CL}"
|
||||
echo -e "${TAB}${GN}📄 $(translate "Log file")${CL}: ${BL}$log_file${CL}"
|
||||
echo -e "${TAB}${GN}📦 $(translate "Packages upgraded")${CL}: ${BL}$upgradable${CL}"
|
||||
echo -e "${TAB}${GN}🖥️ $(translate "Proxmox VE")${CL}: ${BL}$available_pve_version (Debian $OS_CODENAME)${CL}"
|
||||
|
||||
msg_ok "$(translate "Proxmox VE 9.x configuration completed.")"
|
||||
|
||||
rm -f "$screen_capture"
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
update_pve9
|
||||
fi
|
||||
@@ -1,916 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ProxMenux - NVIDIA Driver Installer (PVE 9.x)
|
||||
# ============================================
|
||||
# Author : MacRimi
|
||||
# License : MIT
|
||||
# Version : 0.9 (PVE9, fixed download issues)
|
||||
# Last Updated: 29/11/2025
|
||||
# ============================================
|
||||
|
||||
SCRIPT_TITLE="NVIDIA GPU Driver Installer for Proxmox VE"
|
||||
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
COMPONENTS_STATUS_FILE="$BASE_DIR/components_status.json"
|
||||
LOG_FILE="/tmp/nvidia_install.log"
|
||||
screen_capture="/tmp/proxmenux_nvidia_screen_capture_$$.txt"
|
||||
|
||||
NVIDIA_BASE_URL="https://download.nvidia.com/XFree86/Linux-x86_64"
|
||||
NVIDIA_WORKDIR="/opt/nvidia"
|
||||
|
||||
export BASE_DIR
|
||||
export COMPONENTS_STATUS_FILE
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$COMPONENTS_STATUS_FILE" ]]; then
|
||||
echo "{}" > "$COMPONENTS_STATUS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# ==========================================================
|
||||
# GPU detection and current status
|
||||
# ==========================================================
|
||||
detect_nvidia_gpus() {
|
||||
# Only video controllers (not audio)
|
||||
local lspci_output
|
||||
lspci_output=$(lspci | grep -i "NVIDIA" \
|
||||
| grep -Ei "VGA compatible controller|3D controller|Display controller" || true)
|
||||
|
||||
if [[ -z "$lspci_output" ]]; then
|
||||
NVIDIA_GPU_PRESENT=false
|
||||
DETECTED_GPUS_TEXT="$(translate 'No NVIDIA GPU detected on this system.')"
|
||||
else
|
||||
NVIDIA_GPU_PRESENT=true
|
||||
DETECTED_GPUS_TEXT=""
|
||||
local i=1
|
||||
while IFS= read -r line; do
|
||||
DETECTED_GPUS_TEXT+=" ${i}. ${line}\n"
|
||||
((i++))
|
||||
done <<< "$lspci_output"
|
||||
fi
|
||||
}
|
||||
|
||||
detect_driver_status() {
|
||||
CURRENT_DRIVER_INSTALLED=false
|
||||
CURRENT_DRIVER_VERSION=""
|
||||
|
||||
# First check if nvidia kernel module is actually loaded
|
||||
if lsmod | grep -q "^nvidia "; then
|
||||
|
||||
modprobe nvidia-uvm 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
|
||||
if command -v nvidia-smi >/dev/null 2>&1; then
|
||||
CURRENT_DRIVER_VERSION=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null | head -n1)
|
||||
|
||||
if [[ -n "$CURRENT_DRIVER_VERSION" ]]; then
|
||||
CURRENT_DRIVER_INSTALLED=true
|
||||
# Register the installed driver version in components_status.json
|
||||
update_component_status "nvidia_driver" "installed" "$CURRENT_DRIVER_VERSION" "gpu" '{"patched":false}'
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if $CURRENT_DRIVER_INSTALLED; then
|
||||
CURRENT_STATUS_TEXT="$(printf '%s %s' "$(translate 'NVIDIA driver installed:')" "$CURRENT_DRIVER_VERSION")"
|
||||
else
|
||||
CURRENT_STATUS_TEXT="$(translate 'No NVIDIA driver installed.')"
|
||||
fi
|
||||
|
||||
if $CURRENT_DRIVER_INSTALLED; then
|
||||
CURRENT_STATUS_COLORED="\Z2${CURRENT_STATUS_TEXT}\Zn"
|
||||
else
|
||||
CURRENT_STATUS_COLORED="\Z3${CURRENT_STATUS_TEXT}\Zn"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# System preparation (repos, headers, etc.)
|
||||
# ==========================================================
|
||||
ensure_repos_and_headers() {
|
||||
msg_info "$(translate 'Checking kernel headers and build tools...')"
|
||||
|
||||
local kver
|
||||
kver=$(uname -r)
|
||||
|
||||
apt-get update -qq >>"$LOG_FILE" 2>&1
|
||||
|
||||
if ! dpkg -s "pve-headers-$kver" >/dev/null 2>&1 && \
|
||||
! dpkg -s "proxmox-headers-$kver" >/dev/null 2>&1; then
|
||||
apt-get install -y "pve-headers-$kver" "proxmox-headers-$kver" build-essential dkms >>"$LOG_FILE" 2>&1 || true
|
||||
else
|
||||
apt-get install -y build-essential dkms >>"$LOG_FILE" 2>&1 || true
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'Kernel headers and build tools verified.')" | tee -a "$screen_capture"
|
||||
}
|
||||
|
||||
blacklist_nouveau() {
|
||||
msg_info "$(translate 'Blacklisting nouveau driver...')"
|
||||
if ! grep -q '^blacklist nouveau' /etc/modprobe.d/blacklist.conf 2>/dev/null; then
|
||||
echo "blacklist nouveau" >> /etc/modprobe.d/blacklist.conf
|
||||
fi
|
||||
msg_ok "$(translate 'nouveau driver has been blacklisted.')" | tee -a "$screen_capture"
|
||||
}
|
||||
|
||||
ensure_modules_config() {
|
||||
msg_info "$(translate 'Configuring NVIDIA and VFIO modules...')"
|
||||
cat > /etc/modules-load.d/nvidia-vfio.conf <<'EOF'
|
||||
vfio
|
||||
vfio_iommu_type1
|
||||
vfio_pci
|
||||
vfio_virqfd
|
||||
nvidia
|
||||
nvidia_uvm
|
||||
EOF
|
||||
msg_ok "$(translate 'Modules configuration updated.')" | tee -a "$screen_capture"
|
||||
}
|
||||
|
||||
stop_and_disable_nvidia_services() {
|
||||
local services=(
|
||||
"nvidia-persistenced.service"
|
||||
"nvidia-persistenced"
|
||||
"nvidia-powerd.service"
|
||||
)
|
||||
|
||||
local services_detected=0
|
||||
|
||||
for service in "${services[@]}"; do
|
||||
if systemctl is-active --quiet "$service" 2>/dev/null || \
|
||||
systemctl is-enabled --quiet "$service" 2>/dev/null; then
|
||||
services_detected=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$services_detected" -eq 1 ]; then
|
||||
msg_info "$(translate 'Stopping and disabling NVIDIA services...')"
|
||||
|
||||
for service in "${services[@]}"; do
|
||||
if systemctl is-active --quiet "$service" 2>/dev/null; then
|
||||
systemctl stop "$service" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if systemctl is-enabled --quiet "$service" 2>/dev/null; then
|
||||
systemctl disable "$service" >/dev/null 2>&1 || true
|
||||
fi
|
||||
done
|
||||
|
||||
sleep 2
|
||||
|
||||
msg_ok "$(translate 'NVIDIA services stopped and disabled.')" | tee -a "$screen_capture"
|
||||
fi
|
||||
}
|
||||
|
||||
unload_nvidia_modules() {
|
||||
msg_info "$(translate 'Unloading NVIDIA kernel modules...')"
|
||||
|
||||
for mod in nvidia_uvm nvidia_drm nvidia_modeset nvidia; do
|
||||
modprobe -r "$mod" >/dev/null 2>&1 || true
|
||||
done
|
||||
|
||||
|
||||
if lsmod | grep -qi '\bnvidia'; then
|
||||
for mod in nvidia_uvm nvidia_drm nvidia_modeset nvidia; do
|
||||
modprobe -r --force "$mod" >/dev/null 2>&1 || true
|
||||
done
|
||||
fi
|
||||
|
||||
if lsmod | grep -qi '\bnvidia'; then
|
||||
msg_warn "$(translate 'Some NVIDIA modules could not be unloaded. Installation may fail. Ensure no processes are using the GPU.')"
|
||||
if command -v lsof >/dev/null 2>&1; then
|
||||
echo "$(translate 'Processes using NVIDIA:'):" >> "$LOG_FILE"
|
||||
lsof /dev/nvidia* 2>/dev/null >> "$LOG_FILE" || true
|
||||
fi
|
||||
else
|
||||
msg_ok "$(translate 'NVIDIA kernel modules unloaded successfully.')" | tee -a "$screen_capture"
|
||||
fi
|
||||
}
|
||||
|
||||
complete_nvidia_uninstall() {
|
||||
stop_and_disable_nvidia_services
|
||||
unload_nvidia_modules
|
||||
|
||||
if command -v nvidia-uninstall >/dev/null 2>&1; then
|
||||
msg_info "$(translate 'Running NVIDIA uninstaller...')"
|
||||
nvidia-uninstall --silent >>"$LOG_FILE" 2>&1 || true
|
||||
msg_ok "$(translate 'NVIDIA uninstaller completed.')"
|
||||
fi
|
||||
|
||||
cleanup_nvidia_dkms
|
||||
|
||||
msg_info "$(translate 'Removing NVIDIA packages...')"
|
||||
apt-get -y purge 'nvidia-*' 'libnvidia-*' 'cuda-*' 'libcudnn*' >>"$LOG_FILE" 2>&1 || true
|
||||
apt-get -y autoremove --purge >>"$LOG_FILE" 2>&1 || true
|
||||
apt-get -y autoclean >>"$LOG_FILE" 2>&1 || true
|
||||
|
||||
rm -f /etc/modules-load.d/nvidia-vfio.conf
|
||||
rm -f /etc/udev/rules.d/70-nvidia.rules
|
||||
rm -rf /usr/lib/modprobe.d/nvidia*.conf
|
||||
rm -rf /etc/modprobe.d/nvidia*.conf
|
||||
|
||||
if [[ -d "$NVIDIA_WORKDIR" ]]; then
|
||||
find "$NVIDIA_WORKDIR" -type d -name "nvidia-persistenced" -exec rm -rf {} + 2>/dev/null || true
|
||||
find "$NVIDIA_WORKDIR" -type d -name "nvidia-patch" -exec rm -rf {} + 2>/dev/null || true
|
||||
fi
|
||||
|
||||
update_component_status "nvidia_driver" "removed" "" "gpu" '{}'
|
||||
|
||||
msg_ok "$(translate 'Complete NVIDIA uninstallation finished.')" | tee -a "$screen_capture"
|
||||
}
|
||||
|
||||
cleanup_nvidia_dkms() {
|
||||
local versions
|
||||
versions=$(dkms status 2>/dev/null | awk -F, '/nvidia/ {gsub(/ /,"",$2); print $2}' || true)
|
||||
|
||||
[[ -z "$versions" ]] && return 0
|
||||
|
||||
msg_info "$(translate 'Removing NVIDIA DKMS entries...')"
|
||||
while IFS= read -r ver; do
|
||||
[[ -z "$ver" ]] && continue
|
||||
dkms remove -m nvidia -v "$ver" --all >/dev/null 2>&1 || true
|
||||
done <<< "$versions"
|
||||
msg_ok "$(translate 'NVIDIA DKMS entries removed.')"
|
||||
}
|
||||
|
||||
ensure_workdir() {
|
||||
mkdir -p "$NVIDIA_WORKDIR"
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# Kernel compatibility detection
|
||||
# ==========================================================
|
||||
get_kernel_compatibility_info() {
|
||||
local kernel_version
|
||||
kernel_version=$(uname -r)
|
||||
|
||||
# Determine Proxmox and kernel version
|
||||
if [[ -f /etc/pve/.version ]]; then
|
||||
PVE_VERSION=$(cat /etc/pve/.version)
|
||||
else
|
||||
PVE_VERSION="unknown"
|
||||
fi
|
||||
|
||||
# Extract kernel major version (6.x, 5.x, etc)
|
||||
KERNEL_MAJOR=$(echo "$kernel_version" | cut -d. -f1)
|
||||
KERNEL_MINOR=$(echo "$kernel_version" | cut -d. -f2)
|
||||
|
||||
# Define minimum compatible versions based on kernel
|
||||
# Based on https://docs.nvidia.com/datacenter/tesla/drivers/index.html
|
||||
if [[ "$KERNEL_MAJOR" -ge 6 ]] && [[ "$KERNEL_MINOR" -ge 17 ]]; then
|
||||
# Kernel 6.17+ (Proxmox 9.x) - Requires 580.82.07 or higher
|
||||
MIN_DRIVER_VERSION="580.82.07"
|
||||
RECOMMENDED_BRANCH="580"
|
||||
COMPATIBILITY_NOTE="Kernel $kernel_version requires NVIDIA driver 580.82.07 or newer"
|
||||
elif [[ "$KERNEL_MAJOR" -ge 6 ]] && [[ "$KERNEL_MINOR" -ge 8 ]]; then
|
||||
# Kernel 6.8-6.16 (Proxmox 8.2+) - Works with 550.x or higher
|
||||
MIN_DRIVER_VERSION="550"
|
||||
RECOMMENDED_BRANCH="580"
|
||||
COMPATIBILITY_NOTE="Kernel $kernel_version works best with NVIDIA driver 550.x or newer"
|
||||
elif [[ "$KERNEL_MAJOR" -ge 6 ]]; then
|
||||
# Kernel 6.2-6.7 (Proxmox 8.x initial) - Works with 535.x or higher
|
||||
MIN_DRIVER_VERSION="535"
|
||||
RECOMMENDED_BRANCH="550"
|
||||
COMPATIBILITY_NOTE="Kernel $kernel_version works with NVIDIA driver 535.x or newer"
|
||||
elif [[ "$KERNEL_MAJOR" -eq 5 ]] && [[ "$KERNEL_MINOR" -ge 15 ]]; then
|
||||
# Kernel 5.15+ (Proxmox 7.x, 8.x legacy) - Works with 470.x or higher
|
||||
MIN_DRIVER_VERSION="470"
|
||||
RECOMMENDED_BRANCH="535"
|
||||
COMPATIBILITY_NOTE="Kernel $kernel_version works with NVIDIA driver 470.x or newer"
|
||||
else
|
||||
# Old kernels
|
||||
MIN_DRIVER_VERSION="450"
|
||||
RECOMMENDED_BRANCH="470"
|
||||
COMPATIBILITY_NOTE="For older kernels, compatibility may vary"
|
||||
fi
|
||||
}
|
||||
|
||||
is_version_compatible() {
|
||||
local version="$1"
|
||||
local ver_major ver_minor ver_patch
|
||||
|
||||
# Extract version components (major.minor.patch)
|
||||
ver_major=$(echo "$version" | cut -d. -f1)
|
||||
ver_minor=$(echo "$version" | cut -d. -f2)
|
||||
ver_patch=$(echo "$version" | cut -d. -f3)
|
||||
|
||||
if [[ "$MIN_DRIVER_VERSION" == "580.82.07" ]]; then
|
||||
# Compare full version: must be >= 580.82.07
|
||||
if [[ ${ver_major} -gt 580 ]]; then
|
||||
return 0
|
||||
elif [[ ${ver_major} -eq 580 ]]; then
|
||||
if [[ $((10#${ver_minor})) -gt 82 ]]; then
|
||||
return 0
|
||||
elif [[ $((10#${ver_minor})) -eq 82 ]]; then
|
||||
if [[ $((10#${ver_patch:-0})) -ge 7 ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
if [[ ${ver_major} -ge ${MIN_DRIVER_VERSION} ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# NVIDIA version management - FIXED VERSION
|
||||
# ==========================================================
|
||||
download_latest_version() {
|
||||
local latest_line version
|
||||
|
||||
latest_line=$(curl -fsSL "${NVIDIA_BASE_URL}/latest.txt" 2>&1)
|
||||
if [[ -z "$latest_line" ]]; then
|
||||
echo "" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
version=$(echo "$latest_line" | awk '{print $1}' | tr -d '[:space:]')
|
||||
|
||||
if [[ -z "$version" ]]; then
|
||||
echo "" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
|
||||
echo "" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$version"
|
||||
return 0
|
||||
}
|
||||
|
||||
list_available_versions() {
|
||||
local html_content versions
|
||||
|
||||
html_content=$(curl -s "$NVIDIA_BASE_URL/" 2>&1)
|
||||
|
||||
if [[ -z "$html_content" ]]; then
|
||||
echo "" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
versions=$(echo "$html_content" \
|
||||
| grep -o 'href=[^ >]*' \
|
||||
| awk -F"'" '{print $2}' \
|
||||
| grep -E '^[0-9]' \
|
||||
| sed 's/\/$//' \
|
||||
| sed 's/^[[:space:]]*//;s/[[:space:]]*$//' \
|
||||
| sort -Vr \
|
||||
| uniq)
|
||||
|
||||
if [[ -z "$versions" ]]; then
|
||||
echo "" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$versions"
|
||||
return 0
|
||||
}
|
||||
|
||||
verify_version_exists() {
|
||||
local version="$1"
|
||||
local url="${NVIDIA_BASE_URL}/${version}/"
|
||||
|
||||
if curl -fsSL --head "$url" >/dev/null 2>&1; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
download_nvidia_installer() {
|
||||
ensure_workdir
|
||||
local version="$1"
|
||||
|
||||
version=$(echo "$version" | tr -d '[:space:]' | tr -d '\n' | tr -d '\r')
|
||||
|
||||
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
|
||||
msg_error "Invalid version format: $version" >&2
|
||||
echo "ERROR: Invalid version format: '$version'" >> "$LOG_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local run_file="$NVIDIA_WORKDIR/NVIDIA-Linux-x86_64-${version}.run"
|
||||
|
||||
if [[ -f "$run_file" ]]; then
|
||||
echo "Found existing file: $run_file" >> "$LOG_FILE"
|
||||
local existing_size file_type
|
||||
existing_size=$(stat -c%s "$run_file" 2>/dev/null || stat -f%z "$run_file" 2>/dev/null || echo "0")
|
||||
file_type=$(file "$run_file" 2>/dev/null || echo "unknown")
|
||||
|
||||
echo "Existing file size: $existing_size bytes" >> "$LOG_FILE"
|
||||
echo "Existing file type: $file_type" >> "$LOG_FILE"
|
||||
|
||||
|
||||
if [[ $existing_size -gt 40000000 ]] && echo "$file_type" | grep -q "executable"; then
|
||||
|
||||
if sh "$run_file" --check 2>&1 | tee -a "$LOG_FILE" | grep -q "OK"; then
|
||||
echo "Existing file passed integrity check" >> "$LOG_FILE"
|
||||
msg_ok "$(translate 'Installer already downloaded and verified.')" >&2
|
||||
printf '%s\n' "$run_file"
|
||||
return 0
|
||||
else
|
||||
echo "Existing file FAILED integrity check, removing..." >> "$LOG_FILE"
|
||||
msg_warn "$(translate 'Existing file failed verification, re-downloading...')" >&2
|
||||
rm -f "$run_file"
|
||||
fi
|
||||
else
|
||||
echo "Existing file invalid (size or type), removing..." >> "$LOG_FILE"
|
||||
msg_warn "$(translate 'Removing invalid existing file...')" >&2
|
||||
rm -f "$run_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! verify_version_exists "$version"; then
|
||||
msg_error "Version $version does not exist on NVIDIA servers" >&2
|
||||
echo "ERROR: Version $version not found on server" >> "$LOG_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local urls=(
|
||||
"${NVIDIA_BASE_URL}/${version}/NVIDIA-Linux-x86_64-${version}.run"
|
||||
"${NVIDIA_BASE_URL}/${version}/NVIDIA-Linux-x86_64-${version}-no-compat32.run"
|
||||
)
|
||||
|
||||
local success=false
|
||||
local url_index=0
|
||||
|
||||
for url in "${urls[@]}"; do
|
||||
((url_index++))
|
||||
echo "Attempting download from: $url" >> "$LOG_FILE"
|
||||
|
||||
|
||||
rm -f "$run_file"
|
||||
|
||||
|
||||
if curl -fL --connect-timeout 30 --max-time 600 "$url" -o "$run_file" >> "$LOG_FILE" 2>&1; then
|
||||
echo "Download completed, verifying file..." >> "$LOG_FILE"
|
||||
|
||||
|
||||
if [[ ! -f "$run_file" ]]; then
|
||||
echo "ERROR: File not created after download" >> "$LOG_FILE"
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
local file_size
|
||||
file_size=$(stat -c%s "$run_file" 2>/dev/null || stat -f%z "$run_file" 2>/dev/null || echo "0")
|
||||
echo "Downloaded file size: $file_size bytes" >> "$LOG_FILE"
|
||||
|
||||
if [[ $file_size -lt 40000000 ]]; then
|
||||
echo "ERROR: File too small ($file_size bytes, expected >40MB)" >> "$LOG_FILE"
|
||||
head -c 200 "$run_file" >> "$LOG_FILE" 2>&1
|
||||
rm -f "$run_file"
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
local file_type
|
||||
file_type=$(file "$run_file" 2>/dev/null)
|
||||
echo "File type: $file_type" >> "$LOG_FILE"
|
||||
|
||||
if echo "$file_type" | grep -q "executable"; then
|
||||
echo "SUCCESS: Valid executable downloaded" >> "$LOG_FILE"
|
||||
success=true
|
||||
break
|
||||
else
|
||||
echo "ERROR: Not a valid executable" >> "$LOG_FILE"
|
||||
head -c 200 "$run_file" | od -c >> "$LOG_FILE" 2>&1
|
||||
rm -f "$run_file"
|
||||
fi
|
||||
else
|
||||
echo "ERROR: curl failed for $url (exit code: $?)" >> "$LOG_FILE"
|
||||
rm -f "$run_file"
|
||||
fi
|
||||
done
|
||||
|
||||
if ! $success; then
|
||||
msg_error "$(translate 'Download failed for all attempted URLs')" >&2
|
||||
msg_error "Version $version may not be available for your architecture" >&2
|
||||
echo "ERROR: All download attempts failed" >> "$LOG_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
chmod +x "$run_file"
|
||||
echo "Installation file ready: $run_file" >> "$LOG_FILE"
|
||||
printf '%s\n' "$run_file"
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# Installation / uninstallation
|
||||
# ==========================================================
|
||||
run_nvidia_installer() {
|
||||
local installer="$1"
|
||||
|
||||
msg_info2 "$(translate 'Starting NVIDIA installer. This may take several minutes...')"
|
||||
echo "" >>"$LOG_FILE"
|
||||
echo "=== Running NVIDIA installer: $installer ===" >>"$LOG_FILE"
|
||||
|
||||
local tmp_extract_dir="$NVIDIA_WORKDIR/tmp_extract"
|
||||
mkdir -p "$tmp_extract_dir"
|
||||
|
||||
sh "$installer" --tmpdir="$tmp_extract_dir" --no-questions --ui=none --disable-nouveau --dkms 2>&1 | tee -a "$LOG_FILE"
|
||||
local rc=${PIPESTATUS[0]}
|
||||
echo "" >>"$LOG_FILE"
|
||||
|
||||
rm -rf "$tmp_extract_dir"
|
||||
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
msg_error "$(translate 'NVIDIA installer reported an error. Check /tmp/nvidia_install.log')"
|
||||
update_component_status "nvidia_driver" "failed" "" "gpu" '{"patched":false}'
|
||||
return 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'NVIDIA driver installed successfully.')" | tee -a "$screen_capture"
|
||||
return 0
|
||||
}
|
||||
|
||||
remove_nvidia_driver() {
|
||||
complete_nvidia_uninstall
|
||||
}
|
||||
|
||||
install_udev_rules_and_persistenced() {
|
||||
msg_info "$(translate 'Installing NVIDIA udev rules and persistence service...')"
|
||||
|
||||
cat >/etc/udev/rules.d/70-nvidia.rules <<'EOF'
|
||||
# /etc/udev/rules.d/70-nvidia.rules
|
||||
KERNEL=="nvidia", RUN+="/bin/bash -c '/usr/bin/nvidia-smi -L'"
|
||||
KERNEL=="nvidia_uvm", RUN+="/bin/bash -c '/usr/bin/nvidia-modprobe -c0 -u'"
|
||||
EOF
|
||||
|
||||
udevadm control --reload-rules
|
||||
udevadm trigger --subsystem-match=drm --subsystem-match=pci || true
|
||||
|
||||
ensure_workdir
|
||||
cd "$NVIDIA_WORKDIR" || return 1
|
||||
if [[ ! -d nvidia-persistenced ]]; then
|
||||
git clone https://github.com/NVIDIA/nvidia-persistenced.git >>"$LOG_FILE" 2>&1 || true
|
||||
fi
|
||||
|
||||
if [[ -d nvidia-persistenced/init ]]; then
|
||||
cd nvidia-persistenced/init || return 1
|
||||
./install.sh >>"$LOG_FILE" 2>&1 || true
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'NVIDIA udev rules and persistence service installed.')" | tee -a "$screen_capture"
|
||||
}
|
||||
|
||||
apply_nvidia_patch_if_needed() {
|
||||
if ! whiptail --title "$(translate 'NVIDIA Patch')" --yesno \
|
||||
"\n$(translate 'Do you want to apply the optional NVIDIA patch to remove some GPU limitations?')" 10 70; then
|
||||
msg_info2 "$(translate 'NVIDIA patch not applied.')"
|
||||
update_component_status "nvidia_driver" "installed" "$CURRENT_DRIVER_VERSION" "gpu" '{"patched":false}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_info "$(translate 'Cloning and applying NVIDIA patch (keylase/nvidia-patch)...')"
|
||||
ensure_workdir
|
||||
cd "$NVIDIA_WORKDIR" || return 1
|
||||
if [[ ! -d nvidia-patch ]]; then
|
||||
git clone https://github.com/keylase/nvidia-patch.git >>"$LOG_FILE" 2>&1 || true
|
||||
fi
|
||||
|
||||
if [[ -x nvidia-patch/patch.sh ]]; then
|
||||
cd nvidia-patch || return 1
|
||||
./patch.sh >>"$LOG_FILE" 2>&1 || true
|
||||
msg_ok "$(translate 'NVIDIA patch applied - check README for supported versions.')"
|
||||
update_component_status "nvidia_driver" "installed" "$CURRENT_DRIVER_VERSION" "gpu" '{"patched":true}'
|
||||
else
|
||||
msg_warn "$(translate 'Could not run NVIDIA patch script. Please verify repository and driver version.')"
|
||||
update_component_status "nvidia_driver" "installed" "$CURRENT_DRIVER_VERSION" "gpu" '{"patched":false}'
|
||||
fi
|
||||
}
|
||||
|
||||
restart_prompt() {
|
||||
if whiptail --title "$(translate 'NVIDIA Drivers')" --yesno \
|
||||
"\n$(translate 'The installation/changes require a server restart to apply correctly. Do you want to reboot now?')" 10 70; then
|
||||
msg_success "$(translate 'Installation completed. Press Enter to continue...')"
|
||||
read -r
|
||||
msg_warn "$(translate 'Restarting the server...')"
|
||||
rm -f "$screen_capture"
|
||||
reboot
|
||||
else
|
||||
msg_success "$(translate 'Installation completed. Please reboot the server manually as soon as possible.')"
|
||||
msg_success "$(translate 'Completed. Press Enter to return to menu...')"
|
||||
read -r
|
||||
rm -f "$screen_capture"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# Dialog menus
|
||||
# ==========================================================
|
||||
show_action_menu_if_installed() {
|
||||
if ! $CURRENT_DRIVER_INSTALLED; then
|
||||
ACTION="install"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local menu_choices=(
|
||||
"install" "$(translate 'Reinstall/Update NVIDIA drivers')"
|
||||
"remove" "$(translate 'Uninstall NVIDIA drivers and configuration')"
|
||||
)
|
||||
|
||||
ACTION=$(dialog --clear --stdout \
|
||||
--backtitle "ProxMenux" \
|
||||
--title "$(translate 'NVIDIA GPU Driver Management')" \
|
||||
--menu "$(translate 'Choose an action:')" 14 80 8 \
|
||||
"${menu_choices[@]}") || ACTION="cancel"
|
||||
}
|
||||
|
||||
show_install_overview() {
|
||||
local overview
|
||||
overview="\n$(translate 'This installation will:')\n\n"
|
||||
overview+=" • $(translate 'Install NVIDIA proprietary drivers')\n"
|
||||
overview+=" • $(translate 'Configure GPU passthrough with VFIO')\n"
|
||||
overview+=" • $(translate 'Blacklist nouveau driver')\n"
|
||||
overview+=" • $(translate 'Enable IOMMU support if not enabled')\n\n"
|
||||
|
||||
overview+="$(translate 'Detected GPU(s):')\n"
|
||||
overview+="\Zb\Z4$DETECTED_GPUS_TEXT\Zn\n"
|
||||
|
||||
overview+="\n\Zn$(translate 'Current status: ') "
|
||||
overview+="\Zb${CURRENT_STATUS_TEXT}\Zn\n\n"
|
||||
|
||||
overview+="$(translate 'After confirming, you will be asked to choose the NVIDIA driver version to install.')\n\n"
|
||||
overview+="$(translate 'Do you want to continue?')"
|
||||
|
||||
dialog --colors --backtitle "ProxMenux" \
|
||||
--title "$(translate 'NVIDIA GPU Driver Installation')" \
|
||||
--yesno "$overview" 22 90
|
||||
}
|
||||
|
||||
show_version_menu() {
|
||||
local latest versions_list
|
||||
local kernel_version
|
||||
kernel_version=$(uname -r)
|
||||
|
||||
|
||||
latest=$(download_latest_version 2>/dev/null)
|
||||
|
||||
|
||||
versions_list=$(list_available_versions 2>/dev/null)
|
||||
|
||||
|
||||
if [[ -z "$latest" ]] && [[ -z "$versions_list" ]]; then
|
||||
dialog --backtitle "ProxMenux" --title "$(translate 'Error')" --msgbox \
|
||||
"$(translate 'Could not retrieve versions list from NVIDIA. Please check your internet connection.')\n\nURL: ${NVIDIA_BASE_URL}" 10 80
|
||||
DRIVER_VERSION="cancel"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
if [[ -z "$latest" ]] && [[ -n "$versions_list" ]]; then
|
||||
latest=$(echo "$versions_list" | head -n1)
|
||||
fi
|
||||
|
||||
|
||||
if [[ -n "$latest" ]] && [[ -z "$versions_list" ]]; then
|
||||
versions_list="$latest"
|
||||
fi
|
||||
|
||||
# Clean latest version
|
||||
latest=$(echo "$latest" | tr -d '[:space:]')
|
||||
|
||||
local filter=""
|
||||
local selection
|
||||
local choices
|
||||
local current_list
|
||||
local menu_text
|
||||
|
||||
while true; do
|
||||
current_list="$versions_list"
|
||||
|
||||
if [[ -n "$MIN_DRIVER_VERSION" ]]; then
|
||||
local filtered_list=""
|
||||
while IFS= read -r ver; do
|
||||
[[ -z "$ver" ]] && continue
|
||||
if is_version_compatible "$ver"; then
|
||||
filtered_list+="$ver"$'\n'
|
||||
fi
|
||||
done <<< "$current_list"
|
||||
current_list="$filtered_list"
|
||||
fi
|
||||
|
||||
|
||||
if [[ -n "$filter" ]]; then
|
||||
current_list=$(echo "$current_list" | grep "$filter" || true)
|
||||
fi
|
||||
|
||||
menu_text="$(translate 'Select the NVIDIA driver version to install:')\n\n"
|
||||
menu_text+="$(translate 'Use the filter entry to narrow the list. Latest available (recommended in most cases), or choose a specific version from the list.')"
|
||||
|
||||
choices=()
|
||||
choices+=("latest" "$(translate 'Latest available') (${latest:-unknown})")
|
||||
choices+=("" "")
|
||||
choices+=("filter" "$(translate 'Filter versions')${filter:+: $filter}")
|
||||
|
||||
|
||||
if [[ -n "$current_list" ]]; then
|
||||
while IFS= read -r ver; do
|
||||
[[ -z "$ver" ]] && continue
|
||||
ver=$(echo "$ver" | tr -d '[:space:]')
|
||||
[[ -z "$ver" ]] && continue
|
||||
|
||||
choices+=("$ver" "$ver")
|
||||
done <<< "$current_list"
|
||||
else
|
||||
choices+=("" "$(translate 'No versions match the current filter')")
|
||||
fi
|
||||
|
||||
selection=$(dialog --clear --stdout \
|
||||
--backtitle "ProxMenux" \
|
||||
--title "$(translate 'NVIDIA Driver Version')" \
|
||||
--menu "$menu_text" 26 90 16 \
|
||||
"${choices[@]}") || { DRIVER_VERSION="cancel"; return 1; }
|
||||
|
||||
case "$selection" in
|
||||
"")
|
||||
continue
|
||||
;;
|
||||
filter)
|
||||
filter=$(dialog --clear --stdout \
|
||||
--backtitle "ProxMenux" \
|
||||
--title "$(translate 'Filter NVIDIA versions')" \
|
||||
--inputbox "$(translate 'Enter a filter (e.g., 560, 570, 580). Leave empty to show all.')" 10 80 "$filter") || true
|
||||
;;
|
||||
latest)
|
||||
DRIVER_VERSION="$latest"
|
||||
DRIVER_VERSION=$(echo "$DRIVER_VERSION" | tr -d '[:space:]')
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
DRIVER_VERSION="$selection"
|
||||
DRIVER_VERSION=$(echo "$DRIVER_VERSION" | tr -d '[:space:]')
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# Main flow
|
||||
# ==========================================================
|
||||
main() {
|
||||
: >"$LOG_FILE"
|
||||
: >"$screen_capture"
|
||||
|
||||
detect_nvidia_gpus
|
||||
detect_driver_status
|
||||
|
||||
if ! $NVIDIA_GPU_PRESENT; then
|
||||
dialog --backtitle "ProxMenux" --title "$(translate 'NVIDIA GPU Driver Installation')" --msgbox \
|
||||
"\n$(translate 'No NVIDIA GPU has been detected on this system. The installer will now exit.')" 20 70
|
||||
exit 1
|
||||
fi
|
||||
|
||||
show_action_menu_if_installed
|
||||
|
||||
case "$ACTION" in
|
||||
install)
|
||||
if ! show_install_overview; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
get_kernel_compatibility_info
|
||||
|
||||
show_version_menu
|
||||
if [[ "$DRIVER_VERSION" == "cancel" || -z "$DRIVER_VERSION" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if $CURRENT_DRIVER_INSTALLED; then
|
||||
if [[ "$CURRENT_DRIVER_VERSION" == "$DRIVER_VERSION" ]]; then
|
||||
if ! dialog --colors --backtitle "ProxMenux" --title "$(translate 'Same Version Detected')" --yesno \
|
||||
"$(printf '\n\n\n%s \Zb%s\Zn\n\n%s' \
|
||||
"$(translate 'Version')" "$DRIVER_VERSION" \
|
||||
"$(translate 'is already installed. Do you want to reinstall it? This will perform a clean uninstall first.')")" 14 70; then
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
if ! dialog --colors --backtitle "ProxMenux" --title "$(translate 'Version Change Detected')" --yesno \
|
||||
"$(printf '\n\n%s \Zb%s\Zn\n%s \Zb\Z4%s\Zn\n\n%s' \
|
||||
"$(translate 'Current version:')" "$CURRENT_DRIVER_VERSION" \
|
||||
"$(translate 'New version:')" "$DRIVER_VERSION" \
|
||||
"$(translate 'The current driver will be completely uninstalled before installing the new version. Continue?')")" 20 70; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "$SCRIPT_TITLE")"
|
||||
msg_info2 "$(translate 'Uninstalling current NVIDIA driver before installing new version...')"
|
||||
complete_nvidia_uninstall
|
||||
|
||||
sleep 2
|
||||
|
||||
CURRENT_DRIVER_INSTALLED=false
|
||||
CURRENT_DRIVER_VERSION=""
|
||||
fi
|
||||
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "$SCRIPT_TITLE")"
|
||||
|
||||
ensure_repos_and_headers
|
||||
blacklist_nouveau
|
||||
ensure_modules_config
|
||||
|
||||
stop_and_disable_nvidia_services
|
||||
unload_nvidia_modules
|
||||
|
||||
msg_info "$(translate 'Downloading NVIDIA driver version:') $DRIVER_VERSION"
|
||||
|
||||
local installer
|
||||
installer=$(download_nvidia_installer "$DRIVER_VERSION" 2>>"$LOG_FILE")
|
||||
local download_result=$?
|
||||
|
||||
if [[ $download_result -ne 0 ]]; then
|
||||
msg_error "$(translate 'Failed to download NVIDIA installer')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'NVIDIA installer downloaded successfully')"
|
||||
|
||||
if [[ -z "$installer" || ! -f "$installer" ]]; then
|
||||
msg_error "$(translate 'Internal error: NVIDIA installer path is empty or file not found.')"
|
||||
rm -f "$screen_capture"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! run_nvidia_installer "$installer"; then
|
||||
rm -f "$screen_capture"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "$SCRIPT_TITLE")"
|
||||
cat "$screen_capture"
|
||||
echo -e "${TAB}${GN}📄 $(translate "Log file")${CL}: ${BL}$LOG_FILE${CL}"
|
||||
|
||||
install_udev_rules_and_persistenced
|
||||
|
||||
msg_info "$(translate 'Updating initramfs for all kernels...')"
|
||||
update-initramfs -u -k all >>"$LOG_FILE" 2>&1 || true
|
||||
msg_ok "$(translate 'initramfs updated.')"
|
||||
|
||||
msg_info2 "$(translate 'Checking NVIDIA driver status with nvidia-smi')"
|
||||
if command -v nvidia-smi >/dev/null 2>&1; then
|
||||
nvidia-smi || true
|
||||
CURRENT_DRIVER_VERSION=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null | head -n1)
|
||||
CURRENT_DRIVER_INSTALLED=true
|
||||
else
|
||||
msg_warn "$(translate 'nvidia-smi not found in PATH. Please verify the driver installation.')"
|
||||
fi
|
||||
|
||||
if [[ -n "$CURRENT_DRIVER_VERSION" ]]; then
|
||||
msg_ok "$(translate 'NVIDIA driver') $CURRENT_DRIVER_VERSION $(translate 'installed successfully.')"
|
||||
update_component_status "nvidia_driver" "installed" "$CURRENT_DRIVER_VERSION" "gpu" '{"patched":false}'
|
||||
msg_success "$(translate 'Driver installed successfully. Press Enter to continue...')"
|
||||
read -r
|
||||
else
|
||||
msg_error "$(translate 'Failed to detect installed NVIDIA driver version.')"
|
||||
update_component_status "nvidia_driver" "failed" "" "gpu" '{"patched":false}'
|
||||
fi
|
||||
|
||||
apply_nvidia_patch_if_needed
|
||||
restart_prompt
|
||||
;;
|
||||
remove)
|
||||
if dialog --backtitle "ProxMenux" --title "$(translate 'NVIDIA Driver Uninstall')" --yesno \
|
||||
"\n\n\n$(translate 'This will remove NVIDIA drivers and related configuration. Do you want to continue?')" 14 70; then
|
||||
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "$SCRIPT_TITLE")"
|
||||
|
||||
remove_nvidia_driver
|
||||
|
||||
msg_info "$(translate 'Updating initramfs for all kernels...')"
|
||||
update-initramfs -u -k all >>"$LOG_FILE" 2>&1 || true
|
||||
msg_ok "$(translate 'initramfs updated.')"
|
||||
|
||||
restart_prompt
|
||||
fi
|
||||
;;
|
||||
cancel|*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
main
|
||||
fi
|
||||
@@ -1,194 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenux - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
|
||||
# Version : 1.1
|
||||
# Last Updated: 29/05/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script automates the process of importing disk images into Proxmox VE virtual machines (VMs),
|
||||
# making it easy to attach pre-existing disk files without manual configuration.
|
||||
#
|
||||
# Before running the script, ensure that disk images are available in /var/lib/vz/template/images/.
|
||||
# The script scans this directory for compatible formats (.img, .qcow2, .vmdk, .raw) and lists the available files.
|
||||
#
|
||||
# Using an interactive menu, you can:
|
||||
# - Select a VM to attach the imported disk.
|
||||
# - Choose one or multiple disk images for import.
|
||||
# - Pick a storage volume in Proxmox for disk placement.
|
||||
# - Assign a suitable interface (SATA, SCSI, VirtIO, or IDE).
|
||||
# - Enable optional settings like SSD emulation or bootable disk configuration.
|
||||
#
|
||||
# Once completed, the script ensures the selected images are correctly attached and ready to use.
|
||||
# ==========================================================
|
||||
|
||||
# Configuration ============================================
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
[[ -f "$UTILS_FILE" ]] && source "$UTILS_FILE"
|
||||
load_language
|
||||
initialize_cache
|
||||
# Configuration ============================================
|
||||
|
||||
|
||||
detect_image_dir() {
|
||||
for store in $(pvesm status -content images | awk 'NR>1 {print $1}'); do
|
||||
path=$(pvesm path "${store}:template" 2>/dev/null)
|
||||
if [[ -d "$path" ]]; then
|
||||
for ext in raw img qcow2 vmdk; do
|
||||
if compgen -G "$path/*.$ext" > /dev/null; then
|
||||
echo "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
for sub in images iso; do
|
||||
dir="$path/$sub"
|
||||
if [[ -d "$dir" ]]; then
|
||||
for ext in raw img qcow2 vmdk; do
|
||||
if compgen -G "$dir/*.$ext" > /dev/null; then
|
||||
echo "$dir"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
for fallback in /var/lib/vz/template/images /var/lib/vz/template/iso; do
|
||||
if [[ -d "$fallback" ]]; then
|
||||
for ext in raw img qcow2 vmdk; do
|
||||
if compgen -G "$fallback/*.$ext" > /dev/null; then
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
IMAGES_DIR=$(detect_image_dir)
|
||||
if [[ -z "$IMAGES_DIR" ]]; then
|
||||
dialog --title "$(translate 'No Images Found')" \
|
||||
--msgbox "$(translate 'Could not find any directory containing disk images')\n\n$(translate 'Make sure there is at least one file with extension .img, .qcow2, .vmdk or .raw')" 15 60
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IMAGES=$(ls -A "$IMAGES_DIR" | grep -E "\.(img|qcow2|vmdk|raw)$")
|
||||
if [ -z "$IMAGES" ]; then
|
||||
dialog --title "$(translate 'No Disk Images Found')" \
|
||||
--msgbox "$(translate 'No compatible disk images found in:')\n\n$IMAGES_DIR\n\n$(translate 'Supported formats: .img, .qcow2, .vmdk, .raw')" 15 60
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# === Select VM
|
||||
msg_info "$(translate 'Getting VM list')"
|
||||
VM_LIST=$(qm list | awk 'NR>1 {print $1" "$2}')
|
||||
[[ -z "$VM_LIST" ]] && { msg_error "$(translate 'No VMs available in the system')"; exit 1; }
|
||||
msg_ok "$(translate 'VM list obtained')"
|
||||
|
||||
VMID=$(whiptail --title "$(translate 'Select VM')" \
|
||||
--menu "$(translate 'Select the VM where you want to import the disk image:')" 20 70 10 $VM_LIST 3>&1 1>&2 2>&3)
|
||||
[[ -z "$VMID" ]] && exit 1
|
||||
|
||||
|
||||
|
||||
|
||||
# === Select storage
|
||||
msg_info "$(translate 'Getting storage volumes')"
|
||||
STORAGE_LIST=$(pvesm status -content images | awk 'NR>1 {print $1}')
|
||||
[[ -z "$STORAGE_LIST" ]] && { msg_error "$(translate 'No storage volumes available')"; exit 1; }
|
||||
msg_ok "$(translate 'Storage volumes obtained')"
|
||||
|
||||
STORAGE_OPTIONS=()
|
||||
while read -r storage; do STORAGE_OPTIONS+=("$storage" ""); done <<< "$STORAGE_LIST"
|
||||
STORAGE=$(whiptail --title "$(translate 'Select Storage')" \
|
||||
--menu "$(translate 'Select the storage volume for disk import:')" 20 70 10 "${STORAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
||||
[[ -z "$STORAGE" ]] && exit 1
|
||||
|
||||
|
||||
|
||||
# === Select images
|
||||
IMAGE_OPTIONS=()
|
||||
while read -r img; do IMAGE_OPTIONS+=("$img" "" "OFF"); done <<< "$IMAGES"
|
||||
SELECTED_IMAGES=$(whiptail --title "$(translate 'Select Disk Images')" \
|
||||
--checklist "$(translate 'Select the disk images to import:')" 20 70 12 "${IMAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
||||
[[ -z "$SELECTED_IMAGES" ]] && exit 1
|
||||
|
||||
|
||||
|
||||
# === Import each selected image
|
||||
for IMAGE in $SELECTED_IMAGES; do
|
||||
IMAGE=$(echo "$IMAGE" | tr -d '"')
|
||||
INTERFACE=$(whiptail --title "$(translate 'Interface Type')" --menu "$(translate 'Select the interface type for the image:') $IMAGE" 15 40 4 \
|
||||
"sata" "SATA" "scsi" "SCSI" "virtio" "VirtIO" "ide" "IDE" 3>&1 1>&2 2>&3)
|
||||
[[ -z "$INTERFACE" ]] && { msg_error "$(translate 'No interface type selected for') $IMAGE"; continue; }
|
||||
|
||||
FULL_PATH="$IMAGES_DIR/$IMAGE"
|
||||
msg_info "$(translate 'Importing image:') $IMAGE"
|
||||
TEMP_DISK_FILE=$(mktemp)
|
||||
|
||||
qm importdisk "$VMID" "$FULL_PATH" "$STORAGE" 2>&1 | while read -r line; do
|
||||
if [[ "$line" =~ transferred ]]; then
|
||||
PERCENT=$(echo "$line" | grep -oP "\(\d+\.\d+%\)" | tr -d '()%')
|
||||
echo -ne "\r${TAB}${BL}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
|
||||
elif [[ "$line" =~ successfully\ imported\ disk ]]; then
|
||||
echo "$line" | grep -oP "(?<=successfully imported disk ').*(?=')" > "$TEMP_DISK_FILE"
|
||||
fi
|
||||
done
|
||||
echo -ne "\n"
|
||||
IMPORT_STATUS=${PIPESTATUS[0]}
|
||||
|
||||
|
||||
|
||||
if [ "$IMPORT_STATUS" -eq 0 ]; then
|
||||
msg_ok "$(translate 'Image imported successfully')"
|
||||
IMPORTED_DISK=$(cat "$TEMP_DISK_FILE")
|
||||
rm -f "$TEMP_DISK_FILE"
|
||||
|
||||
if [ -n "$IMPORTED_DISK" ]; then
|
||||
EXISTING_DISKS=$(qm config "$VMID" | grep -oP "${INTERFACE}\d+" | sort -n)
|
||||
NEXT_SLOT=0
|
||||
[[ -n "$EXISTING_DISKS" ]] && NEXT_SLOT=$(( $(echo "$EXISTING_DISKS" | tail -n1 | sed "s/${INTERFACE}//") + 1 ))
|
||||
|
||||
SSD_OPTION=""
|
||||
if [ "$INTERFACE" != "virtio" ]; then
|
||||
whiptail --yesno "$(translate 'Do you want to use SSD emulation for this disk?')" 10 60 && SSD_OPTION=",ssd=1"
|
||||
fi
|
||||
|
||||
msg_info "$(translate 'Configuring disk')"
|
||||
if qm set "$VMID" --${INTERFACE}${NEXT_SLOT} "$IMPORTED_DISK${SSD_OPTION}" &>/dev/null; then
|
||||
msg_ok "$(translate 'Image') $IMAGE $(translate 'configured as') ${INTERFACE}${NEXT_SLOT}"
|
||||
whiptail --yesno "$(translate 'Do you want to make this disk bootable?')" 10 60 && {
|
||||
msg_info "$(translate 'Configuring disk as bootable')"
|
||||
if qm set "$VMID" --boot c --bootdisk ${INTERFACE}${NEXT_SLOT} &>/dev/null; then
|
||||
msg_ok "$(translate 'Disk configured as bootable')"
|
||||
else
|
||||
msg_error "$(translate 'Could not configure the disk as bootable')"
|
||||
fi
|
||||
}
|
||||
else
|
||||
msg_error "$(translate 'Could not configure disk') ${INTERFACE}${NEXT_SLOT} $(translate 'for VM') $VMID"
|
||||
fi
|
||||
else
|
||||
msg_error "$(translate 'Could not find the imported disk')"
|
||||
fi
|
||||
else
|
||||
msg_error "$(translate 'Could not import') $IMAGE"
|
||||
fi
|
||||
done
|
||||
|
||||
msg_ok "$(translate 'All selected images have been processed')"
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
@@ -1,256 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenux - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Revision : @Blaspt (USB passthrough via udev rule with persistent /dev/coral)
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
|
||||
# Version : 1.1
|
||||
# Last Updated: 16/05/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script automates the configuration and installation of
|
||||
# Coral TPU and iGPU support in Proxmox VE containers. It:
|
||||
# - Configures a selected LXC container for hardware acceleration
|
||||
# - Installs and sets up Coral TPU drivers on the Proxmox host
|
||||
# - Installs necessary drivers inside the container
|
||||
# - Manages required system and container restarts
|
||||
#
|
||||
# Supports Coral USB and Coral M.2 (PCIe) devices.
|
||||
# Includes USB passthrough enhancement using persistent udev alias (/dev/coral).
|
||||
# ==========================================================
|
||||
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# ==========================================================
|
||||
|
||||
select_container() {
|
||||
CONTAINERS=$(pct list | awk 'NR>1 {print $1, $3}' | xargs -n2)
|
||||
if [ -z "$CONTAINERS" ]; then
|
||||
msg_error "$(translate 'No containers available in Proxmox.')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONTAINER_ID=$(whiptail --title "$(translate 'Select Container')" \
|
||||
--menu "$(translate 'Select the LXC container:')" 20 70 10 $CONTAINERS 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$CONTAINER_ID" ]; then
|
||||
msg_error "$(translate 'No container selected. Exiting.')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! pct list | awk 'NR>1 {print $1}' | grep -qw "$CONTAINER_ID"; then
|
||||
msg_error "$(translate 'Container with ID') $CONTAINER_ID $(translate 'does not exist. Exiting.')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'Container selected:') $CONTAINER_ID"
|
||||
}
|
||||
|
||||
validate_container_id() {
|
||||
if [ -z "$CONTAINER_ID" ]; then
|
||||
msg_error "$(translate 'Container ID not defined. Make sure to select a container first.')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if pct status "$CONTAINER_ID" | grep -q "running"; then
|
||||
msg_info "$(translate 'Stopping the container before applying configuration...')"
|
||||
pct stop "$CONTAINER_ID"
|
||||
msg_ok "$(translate 'Container stopped.')"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
add_udev_rule_for_coral_usb_() {
|
||||
RULE_FILE="/etc/udev/rules.d/99-coral-usb.rules"
|
||||
RULE_CONTENT='SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="9302", MODE="0666", TAG+="uaccess"'
|
||||
|
||||
if [[ ! -f "$RULE_FILE" ]] || ! grep -qF "$RULE_CONTENT" "$RULE_FILE"; then
|
||||
echo "$RULE_CONTENT" > "$RULE_FILE"
|
||||
udevadm control --reload-rules && udevadm trigger
|
||||
msg_ok "$(translate 'Udev rule for Coral USB added and rules reloaded.')"
|
||||
else
|
||||
msg_ok "$(translate 'Udev rule for Coral USB already exists.')"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
add_udev_rule_for_coral_usb() {
|
||||
RULE_FILE="/etc/udev/rules.d/99-coral-usb.rules"
|
||||
RULE_CONTENT='# Coral USB Accelerator
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="9302", MODE="0666", TAG+="uaccess", SYMLINK+="coral"
|
||||
# Coral Dev Board / Mini PCIe
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="1a6e", ATTRS{idProduct}=="089a", MODE="0666", TAG+="uaccess", SYMLINK+="coral"'
|
||||
|
||||
if [[ ! -f "$RULE_FILE" ]] || ! grep -q "18d1.*9302\|1a6e.*089a" "$RULE_FILE"; then
|
||||
echo "$RULE_CONTENT" > "$RULE_FILE"
|
||||
udevadm control --reload-rules && udevadm trigger
|
||||
msg_ok "$(translate 'Udev rules for Coral USB devices added and rules reloaded.')"
|
||||
else
|
||||
msg_ok "$(translate 'Udev rules for Coral USB devices already exist.')"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
add_mount_if_needed() {
|
||||
local DEVICE="$1"
|
||||
local DEST="$2"
|
||||
local CONFIG_FILE="$3"
|
||||
if [ -e "$DEVICE" ] && ! grep -q "lxc.mount.entry: $DEVICE" "$CONFIG_FILE"; then
|
||||
echo "lxc.mount.entry: $DEVICE $DEST none bind,optional,create=$( [ -c "$DEVICE" ] && echo file || echo dir )" >> "$CONFIG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
configure_lxc_hardware() {
|
||||
validate_container_id
|
||||
CONFIG_FILE="/etc/pve/lxc/${CONTAINER_ID}.conf"
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
msg_error "$(translate 'Configuration file for container') $CONTAINER_ID $(translate 'not found.')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Privileged container
|
||||
if grep -q "^unprivileged: 1" "$CONFIG_FILE"; then
|
||||
msg_info "$(translate 'The container is unprivileged. Changing to privileged...')"
|
||||
sed -i "s/^unprivileged: 1/unprivileged: 0/" "$CONFIG_FILE"
|
||||
STORAGE_TYPE=$(pct config "$CONTAINER_ID" | grep "^rootfs:" | awk -F, '{print $2}' | cut -d'=' -f2)
|
||||
if [[ "$STORAGE_TYPE" == "dir" ]]; then
|
||||
STORAGE_PATH=$(pct config "$CONTAINER_ID" | grep "^rootfs:" | awk '{print $2}' | cut -d',' -f1)
|
||||
chown -R root:root "$STORAGE_PATH"
|
||||
fi
|
||||
msg_ok "$(translate 'Container changed to privileged.')"
|
||||
else
|
||||
msg_ok "$(translate 'The container is already privileged.')"
|
||||
fi
|
||||
|
||||
|
||||
sed -i '/^dev[0-9]\+:/d' "$CONFIG_FILE"
|
||||
|
||||
# Enable nesting feature
|
||||
if ! grep -q "features: nesting=1" "$CONFIG_FILE"; then
|
||||
echo "features: nesting=1" >> "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# iGPU support
|
||||
if ! grep -q "c 226:0 rwm" "$CONFIG_FILE"; then
|
||||
echo "lxc.cgroup2.devices.allow: c 226:0 rwm # iGPU" >> "$CONFIG_FILE"
|
||||
echo "lxc.cgroup2.devices.allow: c 226:128 rwm # iGPU" >> "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
|
||||
add_mount_if_needed "/dev/dri" "dev/dri" "$CONFIG_FILE"
|
||||
add_mount_if_needed "/dev/dri/renderD128" "dev/dri/renderD128" "$CONFIG_FILE"
|
||||
add_mount_if_needed "/dev/dri/card0" "dev/dri/card0" "$CONFIG_FILE"
|
||||
|
||||
# Framebuffer support
|
||||
if ! grep -q "c 29:0 rwm # Framebuffer" "$CONFIG_FILE"; then
|
||||
echo "lxc.cgroup2.devices.allow: c 29:0 rwm # Framebuffer" >> "$CONFIG_FILE"
|
||||
fi
|
||||
add_mount_if_needed "/dev/fb0" "dev/fb0" "$CONFIG_FILE"
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Coral USB passthrough (via udev + /dev/coral)
|
||||
# ----------------------------------------------------------
|
||||
add_udev_rule_for_coral_usb
|
||||
if ! grep -Pq "^lxc.cgroup2.devices.allow: c 189:\* rwm # Coral USB$" "$CONFIG_FILE"; then
|
||||
echo "lxc.cgroup2.devices.allow: c 189:* rwm # Coral USB" >> "$CONFIG_FILE"
|
||||
fi
|
||||
add_mount_if_needed "/dev/coral" "dev/coral" "$CONFIG_FILE"
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Coral M.2 (PCIe) support
|
||||
# ----------------------------------------------------------
|
||||
if lspci | grep -iq "Global Unichip"; then
|
||||
if ! grep -Pq "^lxc.cgroup2.devices.allow: c 245:0 rwm # Coral M2 Apex$" "$CONFIG_FILE"; then
|
||||
echo "lxc.cgroup2.devices.allow: c 245:0 rwm # Coral M2 Apex" >> "$CONFIG_FILE"
|
||||
fi
|
||||
add_mount_if_needed "/dev/apex_0" "dev/apex_0" "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
|
||||
msg_ok "$(translate 'Coral TPU and iGPU configuration added to container') $CONTAINER_ID."
|
||||
}
|
||||
|
||||
install_coral_in_container() {
|
||||
msg_info2 "$(translate 'Installing iGPU and Coral TPU drivers inside the container...')"
|
||||
tput sc
|
||||
LOG_FILE=$(mktemp)
|
||||
|
||||
pct start "$CONTAINER_ID"
|
||||
|
||||
CORAL_M2=$(lspci | grep -i "Global Unichip")
|
||||
if [[ -n "$CORAL_M2" ]]; then
|
||||
DRIVER_OPTION=$(whiptail --title "$(translate 'Select driver version')" \
|
||||
--menu "$(translate 'Choose the driver version for Coral M.2:\n\nCaution: Maximum mode generates more heat.')" 15 60 2 \
|
||||
1 "libedgetpu1-std ($(translate 'standard performance'))" \
|
||||
2 "libedgetpu1-max ($(translate 'maximum performance'))" 3>&1 1>&2 2>&3)
|
||||
|
||||
case "$DRIVER_OPTION" in
|
||||
1) DRIVER_PACKAGE="libedgetpu1-std" ;;
|
||||
2) DRIVER_PACKAGE="libedgetpu1-max" ;;
|
||||
*) DRIVER_PACKAGE="libedgetpu1-std" ;;
|
||||
esac
|
||||
else
|
||||
DRIVER_PACKAGE="libedgetpu1-std"
|
||||
fi
|
||||
|
||||
script -q -c "pct exec \"$CONTAINER_ID\" -- bash -c '
|
||||
set -e
|
||||
echo \"- Updating package lists...\"
|
||||
apt-get update
|
||||
echo \"- Installing iGPU drivers...\"
|
||||
apt-get install -y va-driver-all ocl-icd-libopencl1 intel-opencl-icd vainfo intel-gpu-tools
|
||||
chgrp video /dev/dri && chmod 755 /dev/dri
|
||||
adduser root video && adduser root render
|
||||
|
||||
echo \"- Installing Coral TPU dependencies...\"
|
||||
apt-get install -y gnupg python3 python3-pip python3-venv
|
||||
|
||||
echo \"- Adding Coral TPU repository...\"
|
||||
curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/coral-edgetpu.gpg
|
||||
echo \"deb [signed-by=/usr/share/keyrings/coral-edgetpu.gpg] https://packages.cloud.google.com/apt coral-edgetpu-stable main\" | tee /etc/apt/sources.list.d/coral-edgetpu.list
|
||||
|
||||
echo \"- Updating package lists again...\"
|
||||
apt-get update
|
||||
echo \"- Installing Coral TPU driver ($DRIVER_PACKAGE)...\"
|
||||
apt-get install -y $DRIVER_PACKAGE
|
||||
'" "$LOG_FILE"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
tput rc
|
||||
tput ed
|
||||
rm -f "$LOG_FILE"
|
||||
msg_ok "$(translate 'iGPU and Coral TPU drivers installed inside the container.')"
|
||||
else
|
||||
msg_error "$(translate 'Failed to install iGPU and Coral TPU drivers inside the container.')"
|
||||
cat "$LOG_FILE"
|
||||
rm -f "$LOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
select_container
|
||||
show_proxmenux_logo
|
||||
configure_lxc_hardware
|
||||
install_coral_in_container
|
||||
|
||||
msg_ok "$(translate 'Configuration completed.')"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
@@ -1,116 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
# ProxMenux - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script installs the Coral TPU drivers on the Proxmox VE host.
|
||||
# It ensures that necessary packages are installed and compiles the
|
||||
# Coral TPU drivers for proper functionality.
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration ============================================
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Prompt before installation
|
||||
pre_install_prompt() {
|
||||
if ! whiptail --title "$(translate 'Coral TPU Installation')" --yesno "$(translate 'Installing Coral TPU drivers requires rebooting the server after installation. Do you want to proceed?')" 10 70; then
|
||||
msg_warn "$(translate 'Installation cancelled by user.')"
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Verify and configure repositories on the host
|
||||
verify_and_add_repos() {
|
||||
msg_info "$(translate 'Configuring necessary repositories on the host...')"
|
||||
sleep 2
|
||||
|
||||
if ! grep -q "pve-no-subscription" /etc/apt/sources.list /etc/apt/sources.list.d/* 2>/dev/null; then
|
||||
echo "deb http://download.proxmox.com/debian/pve $(lsb_release -sc) pve-no-subscription" | tee /etc/apt/sources.list.d/pve-no-subscription.list
|
||||
msg_ok "$(translate 'pve-no-subscription repository added.')"
|
||||
fi
|
||||
|
||||
if ! grep -q "non-free-firmware" /etc/apt/sources.list; then
|
||||
echo "deb http://deb.debian.org/debian $(lsb_release -sc) main contrib non-free-firmware
|
||||
deb http://deb.debian.org/debian $(lsb_release -sc)-updates main contrib non-free-firmware
|
||||
deb http://security.debian.org/debian-security $(lsb_release -sc)-security main contrib non-free-firmware" | tee -a /etc/apt/sources.list
|
||||
msg_ok "$(translate 'non-free-firmware repositories added.')"
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'Added repositories')"
|
||||
sleep 2
|
||||
|
||||
msg_info "$(translate 'Verifying repositories...')"
|
||||
apt-get update &>/dev/null
|
||||
|
||||
msg_ok "$(translate 'Verified and updated repositories.')"
|
||||
}
|
||||
|
||||
# Function to install Coral TPU drivers on the host
|
||||
install_coral_host() {
|
||||
show_proxmenux_logo
|
||||
verify_and_add_repos
|
||||
|
||||
apt-get install -y git devscripts dh-dkms dkms pve-headers-$(uname -r) >/dev/null 2>&1
|
||||
|
||||
cd /tmp
|
||||
rm -rf gasket-driver
|
||||
git clone https://github.com/google/gasket-driver.git
|
||||
if [ $? -ne 0 ]; then
|
||||
msg_error "$(translate 'Error: Could not clone the repository.')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd gasket-driver/
|
||||
debuild -us -uc -tc -b
|
||||
if [ $? -ne 0 ]; then
|
||||
msg_error "$(translate 'Error: Failed to build driver packages.')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dpkg -i ../gasket-dkms_*.deb
|
||||
if [ $? -ne 0 ]; then
|
||||
msg_error "$(translate 'Error: Failed to install the driver packages.')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_success "$(translate 'Coral TPU drivers installed successfully on the host.')"
|
||||
echo -e
|
||||
}
|
||||
|
||||
# Prompt for reboot after installation
|
||||
restart_prompt() {
|
||||
if whiptail --title "$(translate 'Coral TPU Installation')" --yesno "$(translate 'The installation requires a server restart to apply changes. Do you want to restart now?')" 10 70; then
|
||||
msg_warn "$(translate 'Restarting the server...')"
|
||||
reboot
|
||||
else
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
pre_install_prompt
|
||||
install_coral_host
|
||||
restart_prompt
|
||||
@@ -35,13 +35,13 @@ initialize_cache
|
||||
|
||||
case $OPTION in
|
||||
1)
|
||||
bash "$LOCAL_SCRIPTS/configure_igpu_lxc.sh"
|
||||
bash "$LOCAL_SCRIPTS/gpu_tpu/configure_igpu_lxc.sh"
|
||||
if [ $? -ne 0 ]; then
|
||||
return
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
bash "$LOCAL_SCRIPTS/install_coral_lxc.sh"
|
||||
bash "$LOCAL_SCRIPTS/gpu_tpu/install_coral_lxc.sh"
|
||||
if [ $? -ne 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenux - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration ============================================
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
load_language
|
||||
initialize_cache
|
||||
# ==========================================================
|
||||
|
||||
|
||||
while true; do
|
||||
OPTION=$(whiptail --title "$(translate "Disk and Storage Manager Menu")" --menu "$(translate "Select an option:")" 20 70 10 \
|
||||
"1" "$(translate "Add Disk Passthrough to a VM")" \
|
||||
"2" "$(translate "Add Disk") Passthrough $(translate "to a CT")" \
|
||||
"3" "$(translate "Import Disk Image to a VM")" \
|
||||
"4" "$(translate "Mount point to CT")" \
|
||||
"5" "$(translate "Mount disk on HOST")" \
|
||||
"6" "$(translate "Unmount disk from HOST")" \
|
||||
"7" "$(translate "Format disk")" \
|
||||
"8" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
|
||||
|
||||
case $OPTION in
|
||||
1)
|
||||
msg_info2 "$(translate "Running script: Add Disk Passthrough to a VM")..."
|
||||
bash "$LOCAL_SCRIPTS/storage/disk-passthrough.sh"
|
||||
;;
|
||||
2)
|
||||
msg_info2 "$(translate "Running script: Add Disk Passthrough to a CT")..."
|
||||
bash "$LOCAL_SCRIPTS/storage/disk-passthrough_ct.sh"
|
||||
;;
|
||||
3)
|
||||
msg_info2 "$(translate "Running script: Import Disk Image to a VM")..."
|
||||
bash "$LOCAL_SCRIPTS/storage/import-disk-image.sh"
|
||||
;;
|
||||
4)
|
||||
msg_info2 "$(translate "Running script: Mount point to CT")..."
|
||||
bash "$LOCAL_SCRIPTS/storage/mount-point-to-ct.sh"
|
||||
;;
|
||||
5)
|
||||
msg_info2 "$(translate "Running script: Mount disk on HOST")..."
|
||||
bash "$LOCAL_SCRIPTS/storage/mount-disk-on-host.sh"
|
||||
;;
|
||||
6)
|
||||
msg_info2 "$(translate "Running script: Unmount disk from HOST")..."
|
||||
bash "$LOCAL_SCRIPTS/storage/unmount-disk-from-host.sh"
|
||||
;;
|
||||
7)
|
||||
msg_info2 "$(translate "Running script: Format disk")..."
|
||||
bash "$LOCAL_SCRIPTS/storage/format-disk.sh"
|
||||
;;
|
||||
8)
|
||||
exec bash "$LOCAL_SCRIPTS/menus/main_menu.sh"
|
||||
;;
|
||||
*)
|
||||
exec bash "$LOCAL_SCRIPTS/menus/main_menu.sh"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user