update scripts gpu

This commit is contained in:
MacRimi
2026-04-01 23:09:51 +02:00
parent bccba6e9b9
commit 5f5dc171be
13 changed files with 3532 additions and 2597 deletions

View File

@@ -0,0 +1,638 @@
#!/bin/bash
# ProxMenux - Universal GPU/iGPU Passthrough to LXC
# ==================================================
# Author : MacRimi
# License : MIT
# Version : 1.0
# Last Updated: 01/04/2026
# ==================================================
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
LOG_FILE="/tmp/add_gpu_lxc.log"
NVIDIA_WORKDIR="/opt/nvidia"
INSTALL_ABORTED=false
NVIDIA_INSTALL_SUCCESS=false
NVIDIA_SMI_OUTPUT=""
screen_capture="/tmp/proxmenux_add_gpu_screen_capture_$$.txt"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ============================================================
# Helper: next available devN index in LXC config
# ============================================================
get_next_dev_index() {
local config="$1"
local idx=0
while grep -q "^dev${idx}:" "$config" 2>/dev/null; do
idx=$((idx + 1))
done
echo "$idx"
}
# ============================================================
# GPU detection on host
# ============================================================
detect_host_gpus() {
HAS_INTEL=false
HAS_AMD=false
HAS_NVIDIA=false
NVIDIA_READY=false
NVIDIA_HOST_VERSION=""
INTEL_NAME=""
AMD_NAME=""
NVIDIA_NAME=""
local intel_line amd_line nvidia_line
intel_line=$(lspci | grep -iE "VGA compatible|3D controller|Display controller" \
| grep -i "Intel" | grep -iv "Ethernet\|Audio\|Network" | head -1)
amd_line=$(lspci | grep -iE "VGA compatible|3D controller|Display controller" \
| grep -iE "AMD|Advanced Micro|Radeon" | head -1)
nvidia_line=$(lspci | grep -iE "VGA compatible|3D controller|Display controller" \
| grep -i "NVIDIA" | head -1)
if [[ -n "$intel_line" ]]; then
HAS_INTEL=true
INTEL_NAME=$(echo "$intel_line" | sed 's/^.*: //' | cut -c1-58)
fi
if [[ -n "$amd_line" ]]; then
HAS_AMD=true
AMD_NAME=$(echo "$amd_line" | sed 's/^.*: //' | cut -c1-58)
fi
if [[ -n "$nvidia_line" ]]; then
HAS_NVIDIA=true
NVIDIA_NAME=$(echo "$nvidia_line" | sed 's/^.*: //' | cut -c1-58)
if lsmod | grep -q "^nvidia " && command -v nvidia-smi >/dev/null 2>&1; then
NVIDIA_HOST_VERSION=$(nvidia-smi --query-gpu=driver_version \
--format=csv,noheader 2>/dev/null | head -n1 | tr -d '[:space:]')
[[ -n "$NVIDIA_HOST_VERSION" ]] && NVIDIA_READY=true
fi
fi
}
# ============================================================
# Container selection
# ============================================================
select_container() {
local menu_items=()
while IFS= read -r line; do
[[ "$line" =~ ^VMID ]] && continue
local ctid status name
ctid=$(echo "$line" | awk '{print $1}')
status=$(echo "$line" | awk '{print $2}')
name=$(echo "$line" | awk '{print $3}')
[[ -z "$ctid" ]] && continue
menu_items+=("$ctid" "${name:-CT-${ctid}} (${status})")
done < <(pct list 2>/dev/null)
if [[ ${#menu_items[@]} -eq 0 ]]; then
dialog --backtitle "ProxMenux" \
--title "$(translate 'Add GPU to LXC')" \
--msgbox "\n$(translate 'No LXC containers found on this system.')" 8 60
exit 0
fi
CONTAINER_ID=$(dialog --backtitle "ProxMenux" \
--title "$(translate 'Add GPU to LXC')" \
--menu "\n$(translate 'Select the LXC container:')" 20 72 12 \
"${menu_items[@]}" \
2>&1 >/dev/tty) || exit 0
}
# ============================================================
# GPU checklist selection
# ============================================================
select_gpus() {
local gpu_items=()
$HAS_INTEL && gpu_items+=("intel" "${INTEL_NAME:-Intel iGPU}" "off")
$HAS_AMD && gpu_items+=("amd" "${AMD_NAME:-AMD GPU}" "off")
$HAS_NVIDIA && gpu_items+=("nvidia" "${NVIDIA_NAME:-NVIDIA GPU}" "off")
local count=$(( ${#gpu_items[@]} / 3 ))
if [[ $count -eq 0 ]]; then
dialog --backtitle "ProxMenux" \
--title "$(translate 'Add GPU to LXC')" \
--msgbox "\n$(translate 'No compatible GPUs detected on this host.')" 8 60
exit 0
fi
# Only one GPU — auto-select without menu
if [[ $count -eq 1 ]]; then
SELECTED_GPUS=("${gpu_items[0]}")
return
fi
# Multiple GPUs — checklist with loop on empty selection
while true; do
local raw_selection
raw_selection=$(dialog --backtitle "ProxMenux" \
--title "$(translate 'Add GPU to LXC')" \
--checklist "\n$(translate 'Select the GPU(s) to add to LXC') ${CONTAINER_ID}:" \
18 80 10 \
"${gpu_items[@]}" \
2>&1 >/dev/tty) || exit 0
local selection
selection=$(echo "$raw_selection" | tr -d '"')
if [[ -z "$selection" ]]; then
dialog --backtitle "ProxMenux" \
--title "$(translate 'Add GPU to LXC')" \
--msgbox "\n$(translate 'No GPU selected. Please select at least one GPU to continue.')" 8 68
continue
fi
read -ra SELECTED_GPUS <<< "$selection"
break
done
}
# ============================================================
# NVIDIA host driver readiness check
# ============================================================
check_nvidia_ready() {
if ! $NVIDIA_READY; then
dialog --colors --backtitle "ProxMenux" \
--title "$(translate 'NVIDIA Drivers Not Found')" \
--msgbox "\n$(translate 'NVIDIA drivers are not installed or not loaded on this host.')\n\n$(translate 'Please install the NVIDIA drivers first using the option:')\n\n \Zb$(translate 'Install NVIDIA Drivers on Host')\Zn\n\n$(translate 'available in this same GPU and TPU menu.')" \
14 72
exit 0
fi
}
# ============================================================
# LXC config: DRI device passthrough (Intel / AMD shared)
# ============================================================
_configure_dri_devices() {
local cfg="$1"
local video_gid render_gid idx gid
video_gid=$(getent group video 2>/dev/null | cut -d: -f3); [[ -z "$video_gid" ]] && video_gid="44"
render_gid=$(getent group render 2>/dev/null | cut -d: -f3); [[ -z "$render_gid" ]] && render_gid="104"
# Remove any pre-existing lxc.mount.entry for /dev/dri — it conflicts with devN: entries
sed -i '/lxc\.mount\.entry:.*dev\/dri.*bind/d' "$cfg" 2>/dev/null || true
for dri_dev in /dev/dri/card0 /dev/dri/card1 /dev/dri/renderD128 /dev/dri/renderD129; do
[[ ! -c "$dri_dev" ]] && continue
if ! grep -qE "dev[0-9]+:.*${dri_dev}[^0-9/]" "$cfg" 2>/dev/null; then
idx=$(get_next_dev_index "$cfg")
case "$dri_dev" in
/dev/dri/renderD*) gid="$render_gid" ;;
*) gid="$video_gid" ;;
esac
echo "dev${idx}: ${dri_dev},gid=${gid}" >> "$cfg"
fi
done
}
_configure_intel() {
local cfg="$1"
_configure_dri_devices "$cfg"
}
_configure_amd() {
local cfg="$1"
local render_gid idx
_configure_dri_devices "$cfg"
# /dev/kfd for ROCm / compute workloads
if [[ -c "/dev/kfd" ]]; then
render_gid=$(getent group render 2>/dev/null | cut -d: -f3)
[[ -z "$render_gid" ]] && render_gid="104"
if ! grep -q "dev.*/dev/kfd" "$cfg" 2>/dev/null; then
idx=$(get_next_dev_index "$cfg")
echo "dev${idx}: /dev/kfd,gid=${render_gid}" >> "$cfg"
fi
fi
}
_configure_nvidia() {
local cfg="$1"
local idx dev video_gid
video_gid=$(getent group video 2>/dev/null | cut -d: -f3)
[[ -z "$video_gid" ]] && video_gid="44"
local -a nv_devs=()
for dev in /dev/nvidia[0-9]* /dev/nvidiactl /dev/nvidia-uvm /dev/nvidia-uvm-tools /dev/nvidia-modeset; do
[[ -c "$dev" ]] && nv_devs+=("$dev")
done
if [[ -d /dev/nvidia-caps ]]; then
for dev in /dev/nvidia-caps/nvidia-cap[0-9]*; do
[[ -c "$dev" ]] && nv_devs+=("$dev")
done
fi
for dev in "${nv_devs[@]}"; do
if ! grep -q "dev.*${dev}" "$cfg" 2>/dev/null; then
idx=$(get_next_dev_index "$cfg")
echo "dev${idx}: ${dev},gid=${video_gid}" >> "$cfg"
fi
done
}
configure_passthrough() {
local ctid="$1"
local cfg="/etc/pve/lxc/${ctid}.conf"
CT_WAS_RUNNING=false
if pct status "$ctid" 2>/dev/null | grep -q "running"; then
CT_WAS_RUNNING=true
msg_info "$(translate 'Stopping container') ${ctid}..."
pct stop "$ctid" >>"$LOG_FILE" 2>&1
msg_ok "$(translate 'Container stopped.')" | tee -a "$screen_capture"
fi
for gpu_type in "${SELECTED_GPUS[@]}"; do
case "$gpu_type" in
intel)
msg_info "$(translate 'Configuring Intel iGPU passthrough...')"
_configure_intel "$cfg"
msg_ok "$(translate 'Intel iGPU passthrough configured.')" | tee -a "$screen_capture"
;;
amd)
msg_info "$(translate 'Configuring AMD GPU passthrough...')"
_configure_amd "$cfg"
msg_ok "$(translate 'AMD GPU passthrough configured.')" | tee -a "$screen_capture"
;;
nvidia)
msg_info "$(translate 'Configuring NVIDIA GPU passthrough...')"
_configure_nvidia "$cfg"
msg_ok "$(translate 'NVIDIA GPU passthrough configured.')" | tee -a "$screen_capture"
;;
esac
done
}
# ============================================================
# Driver / userspace library installation inside container
# ============================================================
# ============================================================
# Detect distro inside container (POSIX sh — works on Alpine too)
# ============================================================
_detect_container_distro() {
local distro
distro=$(pct exec "$1" -- grep "^ID=" /etc/os-release 2>/dev/null \
| cut -d= -f2 | tr -d '[:space:]"')
echo "${distro:-unknown}"
}
# ============================================================
# GID sync helper — POSIX sh, works on all distros
# ============================================================
_sync_gids_in_container() {
local ctid="$1"
local hvid hrid
hvid=$(getent group video 2>/dev/null | cut -d: -f3); [[ -z "$hvid" ]] && hvid="44"
hrid=$(getent group render 2>/dev/null | cut -d: -f3); [[ -z "$hrid" ]] && hrid="104"
pct exec "$ctid" -- sh -c "
sed -i 's/^video:x:[0-9]*:/video:x:${hvid}:/' /etc/group 2>/dev/null || true
sed -i 's/^render:x:[0-9]*:/render:x:${hrid}:/' /etc/group 2>/dev/null || true
grep -q '^video:' /etc/group 2>/dev/null || echo 'video:x:${hvid}:' >> /etc/group
grep -q '^render:' /etc/group 2>/dev/null || echo 'render:x:${hrid}:' >> /etc/group
" >>"$LOG_FILE" 2>&1 || true
}
# ============================================================
_install_intel_drivers() {
local ctid="$1"
local distro="$2"
_sync_gids_in_container "$ctid"
case "$distro" in
alpine)
pct exec "$ctid" -- sh -c \
"apk update && apk add --no-cache mesa-va-gallium libva-utils" \
2>&1 | tee -a "$LOG_FILE"
;;
arch|manjaro|endeavouros)
pct exec "$ctid" -- bash -c \
"pacman -Sy --noconfirm intel-media-driver libva-utils mesa" \
2>&1 | tee -a "$LOG_FILE"
;;
*)
pct exec "$ctid" -- bash -s >>"$LOG_FILE" 2>&1 << EOF
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y va-driver-all vainfo libva2 intel-media-va-driver-non-free i965-va-driver 2>/dev/null || \
apt-get install -y va-driver-all vainfo libva2 2>/dev/null || true
EOF
;;
esac
}
_install_amd_drivers() {
local ctid="$1"
local distro="$2"
_sync_gids_in_container "$ctid"
case "$distro" in
alpine)
pct exec "$ctid" -- sh -c \
"apk update && apk add --no-cache mesa-va-gallium mesa-dri-gallium libva-utils" \
2>&1 | tee -a "$LOG_FILE"
;;
arch|manjaro|endeavouros)
pct exec "$ctid" -- bash -c \
"pacman -Sy --noconfirm mesa libva-mesa-driver libva-utils" \
2>&1 | tee -a "$LOG_FILE"
;;
*)
pct exec "$ctid" -- bash -s >>"$LOG_FILE" 2>&1 << EOF
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y mesa-va-drivers libdrm-amdgpu1 vainfo libva2 2>/dev/null || true
EOF
;;
esac
}
# ============================================================
# Memory management helpers (for NVIDIA .run installer)
# ============================================================
CT_ORIG_MEM=""
NVIDIA_INSTALL_MIN_MB=2048
_ensure_container_memory() {
local ctid="$1"
local cur_mem
cur_mem=$(pct config "$ctid" 2>/dev/null | awk '/^memory:/{print $2}')
[[ -z "$cur_mem" ]] && cur_mem=512
if [[ "$cur_mem" -lt "$NVIDIA_INSTALL_MIN_MB" ]]; then
if whiptail --title "$(translate 'Low Container Memory')" --yesno \
"$(translate 'Container') ${ctid} $(translate 'has') ${cur_mem}MB RAM.\n\n$(translate 'The NVIDIA installer needs at least') ${NVIDIA_INSTALL_MIN_MB}MB $(translate 'to run without being killed by the OOM killer.')\n\n$(translate 'Increase container RAM temporarily to') ${NVIDIA_INSTALL_MIN_MB}MB?" \
13 72; then
CT_ORIG_MEM="$cur_mem"
pct set "$ctid" -memory "$NVIDIA_INSTALL_MIN_MB" >>"$LOG_FILE" 2>&1 || true
else
INSTALL_ABORTED=true
msg_warn "$(translate 'Insufficient memory. NVIDIA install aborted.')"
return 1
fi
fi
return 0
}
_restore_container_memory() {
local ctid="$1"
if [[ -n "$CT_ORIG_MEM" ]]; then
msg_info "$(translate 'Restoring container memory to') ${CT_ORIG_MEM}MB..."
pct set "$ctid" -memory "$CT_ORIG_MEM" >>"$LOG_FILE" 2>&1 || true
msg_ok "$(translate 'Memory restored.')"
CT_ORIG_MEM=""
fi
}
# ============================================================
_install_nvidia_drivers() {
local ctid="$1"
local version="$NVIDIA_HOST_VERSION"
local distro="$2"
case "$distro" in
alpine)
# Alpine uses apk — musl-compatible nvidia-utils from Alpine repos
msg_info2 "$(translate 'Installing NVIDIA utils (Alpine)...')"
pct exec "$ctid" -- sh -c \
"apk update && apk add --no-cache nvidia-utils" \
2>&1 | tee -a "$LOG_FILE"
;;
arch|manjaro|endeavouros)
# Arch uses pacman — nvidia-utils provides nvidia-smi
msg_info2 "$(translate 'Installing NVIDIA utils (Arch)...')"
pct exec "$ctid" -- bash -c \
"pacman -Sy --noconfirm nvidia-utils" \
2>&1 | tee -a "$LOG_FILE"
;;
*)
# Debian / Ubuntu / generic glibc: use the .run binary
local run_file="${NVIDIA_WORKDIR}/NVIDIA-Linux-x86_64-${version}.run"
if [[ ! -f "$run_file" ]]; then
msg_warn "$(translate 'NVIDIA installer not found at') ${run_file}."
msg_warn "$(translate 'Run \"Install NVIDIA Drivers on Host\" first so the installer is cached.')"
return 1
fi
# Memory check — nvidia-installer needs ~2GB during install
_ensure_container_memory "$ctid" || return 1
# Disk space check — NVIDIA libs need ~1.5 GB free in the container
local free_mb
free_mb=$(pct exec "$ctid" -- df -m / 2>/dev/null | awk 'NR==2{print $4}' || echo 0)
if [[ "$free_mb" -lt 1500 ]]; then
_restore_container_memory "$ctid"
dialog --backtitle "ProxMenux" \
--title "$(translate 'Insufficient Disk Space')" \
--msgbox "\n$(translate 'Container') ${ctid} $(translate 'has only') ${free_mb}MB $(translate 'of free disk space.')\n\n$(translate 'NVIDIA libs require approximately 1.5GB of free space.')\n\n$(translate 'Please expand the container disk and run this option again.')" \
12 72
INSTALL_ABORTED=true
return 1
fi
# Extract .run on the host — avoids decompression OOM inside container
# Use msg_info2 (no spinner) so tee output is not mixed with spinner animation
local extract_dir="${NVIDIA_WORKDIR}/extracted_${version}"
local archive="/tmp/nvidia_lxc_${version}.tar.gz"
msg_info2 "$(translate 'Extracting NVIDIA installer on host...')"
rm -rf "$extract_dir"
sh "$run_file" --extract-only --target "$extract_dir" 2>&1 | tee -a "$LOG_FILE"
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
msg_warn "$(translate 'Extraction failed. Check log:') ${LOG_FILE}"
_restore_container_memory "$ctid"
return 1
fi
msg_ok "$(translate 'NVIDIA installer extracted.')" | tee -a "$screen_capture"
msg_info2 "$(translate 'Packing installer archive...')"
tar --checkpoint=5000 --checkpoint-action=dot \
-czf "$archive" -C "$extract_dir" . 2>&1 | tee -a "$LOG_FILE"
echo ""
local archive_size
archive_size=$(du -sh "$archive" 2>/dev/null | cut -f1)
msg_ok "$(translate 'Archive ready') (${archive_size})." | tee -a "$screen_capture"
msg_info "$(translate 'Copying installer to container') ${ctid}..."
if ! pct push "$ctid" "$archive" /tmp/nvidia_lxc.tar.gz >>"$LOG_FILE" 2>&1; then
msg_warn "$(translate 'pct push failed. Check log:') ${LOG_FILE}"
rm -f "$archive"
_restore_container_memory "$ctid"
return 1
fi
rm -f "$archive"
msg_ok "$(translate 'Installer copied to container.')" | tee -a "$screen_capture"
msg_info2 "$(translate 'Installing NVIDIA drivers in container. This may take several minutes...')"
echo "" >>"$LOG_FILE"
pct exec "$ctid" -- bash -c "
mkdir -p /tmp/nvidia_lxc_install
tar -xzf /tmp/nvidia_lxc.tar.gz -C /tmp/nvidia_lxc_install 2>&1
/tmp/nvidia_lxc_install/nvidia-installer \
--no-kernel-modules \
--no-questions \
--ui=none \
--no-nouveau-check \
--no-dkms \
--no-install-compat32-libs
EXIT=\$?
rm -rf /tmp/nvidia_lxc_install /tmp/nvidia_lxc.tar.gz
exit \$EXIT
" 2>&1 | tee -a "$LOG_FILE"
local rc=${PIPESTATUS[0]}
rm -rf "$extract_dir"
_restore_container_memory "$ctid"
if [[ $rc -ne 0 ]]; then
msg_warn "$(translate 'NVIDIA installer returned error') ${rc}. $(translate 'Check log:') ${LOG_FILE}"
return 1
fi
;;
esac
if pct exec "$ctid" -- sh -c "which nvidia-smi" >/dev/null 2>&1; then
return 0
else
msg_warn "$(translate 'nvidia-smi not found after install. Check log:') ${LOG_FILE}"
return 1
fi
}
start_container_and_wait() {
local ctid="$1"
msg_info "$(translate 'Starting container') ${ctid}..."
pct start "$ctid" >>"$LOG_FILE" 2>&1 || true
local ready=false
for _ in {1..15}; do
sleep 2
if pct exec "$ctid" -- true >/dev/null 2>&1; then
ready=true
break
fi
done
if ! $ready; then
msg_warn "$(translate 'Container did not become ready in time. Skipping driver installation.')"
return 1
fi
msg_ok "$(translate 'Container started.')" | tee -a "$screen_capture"
return 0
}
install_drivers() {
local ctid="$1"
# Detect distro once — passed to each install function
msg_info "$(translate 'Detecting container OS...')"
local ct_distro
ct_distro=$(_detect_container_distro "$ctid")
msg_ok "$(translate 'Container OS:') ${ct_distro}" | tee -a "$screen_capture"
for gpu_type in "${SELECTED_GPUS[@]}"; do
case "$gpu_type" in
intel)
msg_info "$(translate 'Installing Intel VA-API drivers in container...')"
_install_intel_drivers "$ctid" "$ct_distro"
msg_ok "$(translate 'Intel VA-API drivers installed.')" | tee -a "$screen_capture"
;;
amd)
msg_info "$(translate 'Installing AMD mesa drivers in container...')"
_install_amd_drivers "$ctid" "$ct_distro"
msg_ok "$(translate 'AMD mesa drivers installed.')" | tee -a "$screen_capture"
;;
nvidia)
# No outer msg_info here — _install_nvidia_drivers manages its own messages
# to avoid a dangling spinner before the whiptail memory dialog
if _install_nvidia_drivers "$ctid" "$ct_distro"; then
msg_ok "$(translate 'NVIDIA userspace libraries installed.')" | tee -a "$screen_capture"
NVIDIA_INSTALL_SUCCESS=true
elif [[ "$INSTALL_ABORTED" == "false" ]]; then
msg_warn "$(translate 'NVIDIA install incomplete. Check log:') ${LOG_FILE}"
fi
;;
esac
done
}
# ============================================================
# Main
# ============================================================
main() {
: >"$LOG_FILE"
: >"$screen_capture"
# ---- Phase 1: all dialogs (no terminal output yet) ----
detect_host_gpus
select_container
select_gpus
# NVIDIA check runs only if NVIDIA was selected
for gpu_type in "${SELECTED_GPUS[@]}"; do
[[ "$gpu_type" == "nvidia" ]] && check_nvidia_ready
done
# ---- Phase 2: processing ----
show_proxmenux_logo
msg_title "$(translate 'Add GPU to LXC')"
configure_passthrough "$CONTAINER_ID"
if start_container_and_wait "$CONTAINER_ID"; then
install_drivers "$CONTAINER_ID"
# Capture nvidia-smi output while container is still running
if $NVIDIA_INSTALL_SUCCESS; then
NVIDIA_SMI_OUTPUT=$(pct exec "$CONTAINER_ID" -- nvidia-smi 2>/dev/null || true)
fi
if [[ "$CT_WAS_RUNNING" == "false" ]]; then
pct stop "$CONTAINER_ID" >>"$LOG_FILE" 2>&1 || true
fi
fi
if [[ "$INSTALL_ABORTED" == "true" ]]; then
rm -f "$screen_capture"
exit 0
fi
show_proxmenux_logo
msg_title "$(translate 'Add GPU to LXC')"
cat "$screen_capture"
echo -e "${TAB}${GN}📄 $(translate 'Log')${CL}: ${BL}${LOG_FILE}${CL}"
if [[ -n "$NVIDIA_SMI_OUTPUT" ]]; then
msg_info2 "$(translate 'NVIDIA driver verification in container:')"
echo "$NVIDIA_SMI_OUTPUT"
fi
msg_success "$(translate 'GPU passthrough configured for LXC') ${CONTAINER_ID}."
msg_success "$(translate 'Completed. Press Enter to return to menu...')"
read -r
rm -f "$screen_capture"
}
main

View File

@@ -7,8 +7,8 @@
# 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.3
# Last Updated: 28/03/2025
# Version : 1.4 (unprivileged container support, PVE dev API for apex/iGPU)
# Last Updated: 01/04/2026
# ==========================================================
# Description:
# This script automates the configuration and installation of
@@ -158,13 +158,25 @@ add_mount_if_needed() {
cleanup_duplicate_entries() {
local CONFIG_FILE="$1"
local TEMP_FILE=$(mktemp)
awk '!seen[$0]++' "$CONFIG_FILE" > "$TEMP_FILE"
cat "$TEMP_FILE" > "$CONFIG_FILE"
rm -f "$TEMP_FILE"
}
# Returns the next available dev index (dev0, dev1, ...) in a container config.
# The PVE dev API (devN: /dev/foo,gid=N) works in both privileged and unprivileged
# containers, handling cgroup2 permissions automatically.
get_next_dev_index() {
local config="$1"
local idx=0
while grep -q "^dev${idx}:" "$config" 2>/dev/null; do
idx=$((idx + 1))
done
echo "$idx"
}
# ==========================================================
# CONFIGURE LXC HARDWARE PASSTHROUGH
# ==========================================================
@@ -180,25 +192,6 @@ configure_lxc_hardware() {
cleanup_duplicate_entries "$CONFIG_FILE"
# ============================================================
# Convert to privileged container if needed
# ============================================================
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
# ============================================================
@@ -217,19 +210,24 @@ configure_lxc_hardware() {
# iGPU support
# ============================================================
msg_info "$(translate 'Configuring iGPU support...')"
if ! grep -Pq "^lxc.cgroup2.devices.allow: c 226:0 rwm" "$CONFIG_FILE"; then
echo "lxc.cgroup2.devices.allow: c 226:0 rwm # iGPU" >> "$CONFIG_FILE"
fi
if ! grep -Pq "^lxc.cgroup2.devices.allow: c 226:128 rwm" "$CONFIG_FILE"; then
echo "lxc.cgroup2.devices.allow: c 226:128 rwm # iGPU" >> "$CONFIG_FILE"
fi
# Bind-mount the /dev/dri directory so apps can enumerate available devices
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"
# Add each DRI device via the PVE dev API (gid=44 = render group).
# This approach works in unprivileged containers: PVE manages cgroup2
# permissions automatically and maps the GID into the container namespace.
local igpu_dev_idx
igpu_dev_idx=$(get_next_dev_index "$CONFIG_FILE")
for dri_dev in /dev/dri/renderD128 /dev/dri/renderD129 /dev/dri/card0 /dev/dri/card1; do
if [[ -c "$dri_dev" ]]; then
if ! grep -q ":.*${dri_dev}" "$CONFIG_FILE"; then
echo "dev${igpu_dev_idx}: ${dri_dev},gid=44" >> "$CONFIG_FILE"
igpu_dev_idx=$((igpu_dev_idx + 1))
fi
fi
done
msg_ok "$(translate 'iGPU configuration added')"
# ============================================================
@@ -277,18 +275,29 @@ configure_lxc_hardware() {
if lspci | grep -iq "Global Unichip"; then
msg_info "$(translate 'Coral M.2 Apex detected, configuring...')"
if ! grep -Pq "^lxc.cgroup2.devices.allow: c 245:0 rwm" "$CONFIG_FILE"; then
echo "lxc.cgroup2.devices.allow: c 245:0 rwm # Coral M2 Apex" >> "$CONFIG_FILE"
fi
local APEX_GID apex_dev_idx
APEX_GID=$(getent group apex 2>/dev/null | cut -d: -f3 || echo "0")
apex_dev_idx=$(get_next_dev_index "$CONFIG_FILE")
add_mount_if_needed "/dev/apex_0" "dev/apex_0" "$CONFIG_FILE"
if [ -e "/dev/apex_0" ]; then
# Device is visible — use PVE dev API (works in unprivileged containers).
# PVE handles cgroup2 permissions automatically.
if ! grep -q "dev.*apex_0" "$CONFIG_FILE"; then
echo "dev${apex_dev_idx}: /dev/apex_0,gid=${APEX_GID}" >> "$CONFIG_FILE"
fi
msg_ok "$(translate 'Coral M.2 Apex configuration added - device ready')"
else
# Device not yet visible (host module not loaded or reboot pending).
# Use cgroup2 + optional bind-mount as fallback; detect major number
# dynamically from /proc/devices to avoid hardcoding it.
local APEX_MAJOR
APEX_MAJOR=$(awk '/\bapex\b/{print $1}' /proc/devices 2>/dev/null | head -1)
[[ -z "$APEX_MAJOR" ]] && APEX_MAJOR="245"
if ! grep -q "lxc.cgroup2.devices.allow: c ${APEX_MAJOR}:0 rwm" "$CONFIG_FILE"; then
echo "lxc.cgroup2.devices.allow: c ${APEX_MAJOR}:0 rwm # Coral M2 Apex" >> "$CONFIG_FILE"
fi
add_mount_if_needed "/dev/apex_0" "dev/apex_0" "$CONFIG_FILE"
msg_ok "$(translate 'Coral M.2 Apex configuration added - device will be available after reboot')"
fi
fi
@@ -311,7 +320,13 @@ install_coral_in_container() {
if ! pct status "$CONTAINER_ID" | grep -q "running"; then
pct start "$CONTAINER_ID"
sleep 5
for _ in {1..15}; do
pct status "$CONTAINER_ID" | grep -q "running" && break
sleep 1
done
if ! pct status "$CONTAINER_ID" | grep -q "running"; then
msg_error "$(translate 'Container did not start in time.')"; exit 1
fi
fi
@@ -337,7 +352,8 @@ install_coral_in_container() {
# Install drivers inside container
script -q -c "pct exec \"$CONTAINER_ID\" -- bash -c '
set -e
export DEBIAN_FRONTEND=noninteractive
echo \"[1/6] Updating package lists...\"
apt-get update -qq

View File

@@ -3,8 +3,8 @@
# =========================================
# Author : MacRimi
# License : MIT
# Version : 1.3 (PVE9, silent build)
# Last Updated: 25/09/2025
# Version : 1.4 (kernel-conditional patches, direct DKMS, no debuild)
# Last Updated: 01/04/2026
# =========================================
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
@@ -74,68 +74,72 @@ pre_install_prompt() {
install_coral_host() {
show_proxmenux_logo
: >"$LOG_FILE"
: >"$LOG_FILE"
# Detect running kernel and parse major/minor for conditional patches
local KVER KMAJ KMIN
KVER=$(uname -r)
KMAJ=$(echo "$KVER" | cut -d. -f1)
KMIN=$(echo "$KVER" | cut -d. -f2 | cut -d+ -f1 | cut -d- -f1)
msg_info "$(translate 'Installing build dependencies...')"
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq >>"$LOG_FILE" 2>&1
apt-get install -y git devscripts dh-dkms dkms proxmox-headers-$(uname -r) >>"$LOG_FILE" 2>&1
if [[ $? -ne 0 ]]; then msg_error "$(translate 'Error installing build dependencies. Check /tmp/coral_install.log')"; exit 1; fi
msg_ok "$(translate 'Build dependencies installed.')"
if ! apt-get install -y git dkms build-essential "proxmox-headers-${KVER}" >>"$LOG_FILE" 2>&1; then
msg_error "$(translate 'Error installing build dependencies. Check /tmp/coral_install.log')"; exit 1
fi
msg_ok "$(translate 'Build dependencies installed.')"
cd /tmp || exit 1
rm -rf gasket-driver >>"$LOG_FILE" 2>&1
msg_info "$(translate 'Cloning Google Coral driver repository...')"
git clone https://github.com/google/gasket-driver.git >>"$LOG_FILE" 2>&1
if [[ $? -ne 0 ]]; then msg_error "$(translate 'Could not clone the repository. Check /tmp/coral_install.log')"; exit 1; fi
msg_ok "$(translate 'Repository cloned successfully.')"
if ! git clone https://github.com/google/gasket-driver.git >>"$LOG_FILE" 2>&1; then
msg_error "$(translate 'Could not clone the repository. Check /tmp/coral_install.log')"; exit 1
fi
msg_ok "$(translate 'Repository cloned successfully.')"
cd /tmp/gasket-driver || exit 1
msg_info "$(translate 'Patching source for kernel compatibility...')"
sed -i 's/\.llseek = no_llseek/\.llseek = noop_llseek/' src/gasket_core.c
sed -i 's/^MODULE_IMPORT_NS(DMA_BUF);/MODULE_IMPORT_NS("DMA_BUF");/' src/gasket_page_table.c
sed -i "s/\(linux-headers-686-pae | linux-headers-amd64 | linux-headers-generic | linux-headers\)/\1 | proxmox-headers-$(uname -r) | pve-headers-$(uname -r)/" debian/control
if [[ $? -ne 0 ]]; then msg_error "$(translate 'Patching failed. Check /tmp/coral_install.log')"; exit 1; fi
msg_ok "$(translate 'Source patched successfully.')"
msg_info "$(translate 'Building DKMS package...')"
debuild -us -uc -tc -b >>"$LOG_FILE" 2>&1
if [[ $? -ne 0 ]]; then msg_error "$(translate 'Failed to build DKMS package. Check /tmp/coral_install.log')"; exit 1; fi
msg_ok "$(translate 'DKMS package built successfully.')"
msg_info "$(translate 'Installing DKMS package...')"
dpkg -i ../gasket-dkms_*.deb >>"$LOG_FILE" 2>&1 || true
if ! dpkg -s gasket-dkms >/dev/null 2>&1; then
msg_error "$(translate 'Failed to install DKMS package. Check /tmp/coral_install.log')"; exit 1
# Patch 1: no_llseek was removed in kernel 6.5 — replace with noop_llseek
if [[ "$KMAJ" -gt 6 ]] || [[ "$KMAJ" -eq 6 && "$KMIN" -ge 5 ]]; then
sed -i 's/\.llseek = no_llseek/\.llseek = noop_llseek/' src/gasket_core.c
fi
msg_ok "$(translate 'DKMS package installed.')"
# Patch 2: MODULE_IMPORT_NS changed to string-literal syntax in kernel 6.13.
# IMPORTANT: applying this patch on kernel < 6.13 causes a compile error.
if [[ "$KMAJ" -gt 6 ]] || [[ "$KMAJ" -eq 6 && "$KMIN" -ge 13 ]]; then
sed -i 's/^MODULE_IMPORT_NS(DMA_BUF);/MODULE_IMPORT_NS("DMA_BUF");/' src/gasket_page_table.c
fi
msg_ok "$(translate 'Source patched successfully.') (kernel ${KVER})"
msg_info "$(translate 'Preparing DKMS source tree...')"
local GASKET_SRC="/usr/src/gasket-1.0"
# Remove any previous installation (package or manual) to avoid conflicts
dpkg -r gasket-dkms >>"$LOG_FILE" 2>&1 || true
dkms remove gasket/1.0 --all >>"$LOG_FILE" 2>&1 || true
rm -rf "$GASKET_SRC"
cp -r /tmp/gasket-driver/. "$GASKET_SRC"
if ! dkms add "$GASKET_SRC" >>"$LOG_FILE" 2>&1; then
msg_error "$(translate 'DKMS add failed. Check /tmp/coral_install.log')"; exit 1
fi
msg_ok "$(translate 'DKMS source tree prepared.')"
msg_info "$(translate 'Compiling Coral TPU drivers for current kernel...')"
dkms remove -m gasket -v 1.0 -k "$(uname -r)" >>"$LOG_FILE" 2>&1 || true
dkms add -m gasket -v 1.0 >>"$LOG_FILE" 2>&1 || true
dkms build -m gasket -v 1.0 -k "$(uname -r)" >>"$LOG_FILE" 2>&1
if [[ $? -ne 0 ]]; then
if ! dkms build gasket/1.0 -k "$KVER" >>"$LOG_FILE" 2>&1; then
sed -n '1,200p' /var/lib/dkms/gasket/1.0/build/make.log >>"$LOG_FILE" 2>&1 || true
msg_error "$(translate 'DKMS build failed. Check /tmp/coral_install.log')"; exit 1
fi
dkms install -m gasket -v 1.0 -k "$(uname -r)" >>"$LOG_FILE" 2>&1
if [[ $? -ne 0 ]]; then msg_error "$(translate 'DKMS install failed. Check /tmp/coral_install.log')"; exit 1; fi
msg_ok "$(translate 'Drivers compiled and installed via DKMS.')"
if ! dkms install gasket/1.0 -k "$KVER" >>"$LOG_FILE" 2>&1; then
msg_error "$(translate 'DKMS install failed. Check /tmp/coral_install.log')"; exit 1
fi
msg_ok "$(translate 'Drivers compiled and installed via DKMS.')"
ensure_apex_group_and_udev
@@ -150,8 +154,6 @@ install_coral_host() {
msg_warn "$(translate 'Installation finished but drivers are not loaded. Please check dmesg and /tmp/coral_install.log')"
fi
echo "---- dmesg | grep -i apex (last lines) ----" >>"$LOG_FILE"
dmesg | grep -i apex | tail -n 20 >>"$LOG_FILE" 2>&1
}

View File

@@ -0,0 +1,494 @@
#!/bin/bash
# ProxMenux - NVIDIA Driver Updater (Host + LXC)
# ================================================
# Author : MacRimi
# License : MIT
# Version : 1.0
# Last Updated: 01/04/2026
# ================================================
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
LOG_FILE="/tmp/nvidia_update.log"
NVIDIA_BASE_URL="https://download.nvidia.com/XFree86/Linux-x86_64"
NVIDIA_WORKDIR="/opt/nvidia"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ============================================================
# Host NVIDIA state detection
# ============================================================
detect_host_nvidia() {
HOST_NVIDIA_VERSION=""
HOST_NVIDIA_READY=false
if lsmod | grep -q "^nvidia " && command -v nvidia-smi >/dev/null 2>&1; then
HOST_NVIDIA_VERSION=$(nvidia-smi --query-gpu=driver_version \
--format=csv,noheader 2>/dev/null | head -n1 | tr -d '[:space:]')
[[ -n "$HOST_NVIDIA_VERSION" ]] && HOST_NVIDIA_READY=true
fi
if ! $HOST_NVIDIA_READY; then
dialog --backtitle "ProxMenux" \
--title "$(translate 'NVIDIA Not Found')" \
--msgbox "\n$(translate 'No NVIDIA driver is currently loaded on this host.')\n\n$(translate 'Please install NVIDIA drivers first using the option:')\n\n $(translate 'Install NVIDIA Drivers on Host')\n\n$(translate 'from this same GPU and TPU menu.')" \
13 72
exit 0
fi
}
# ============================================================
# LXC containers with NVIDIA passthrough
# ============================================================
find_nvidia_containers() {
NVIDIA_CONTAINERS=()
for conf in /etc/pve/lxc/*.conf; do
[[ -f "$conf" ]] || continue
if grep -qiE "dev[0-9]+:.*nvidia" "$conf"; then
NVIDIA_CONTAINERS+=("$(basename "$conf" .conf)")
fi
done
}
get_lxc_nvidia_version() {
local ctid="$1"
local version=""
# Prefer nvidia-smi when the container is running (works with .run-installed drivers)
if pct status "$ctid" 2>/dev/null | grep -q "running"; then
version=$(pct exec "$ctid" -- nvidia-smi \
--query-gpu=driver_version --format=csv,noheader 2>/dev/null \
| head -1 | tr -d '[:space:]' || true)
fi
# Fallback: dpkg status for apt-installed libcuda1 (dir-type storage, no start needed)
if [[ -z "$version" ]]; then
local rootfs="/var/lib/lxc/${ctid}/rootfs"
if [[ -f "${rootfs}/var/lib/dpkg/status" ]]; then
version=$(grep -A5 "^Package: libcuda1$" "${rootfs}/var/lib/dpkg/status" \
| grep "^Version:" | head -1 | awk '{print $2}' | cut -d- -f1)
fi
fi
echo "${version:-$(translate 'not installed')}"
}
# ============================================================
# Version list from NVIDIA servers
# ============================================================
list_available_versions() {
local html
html=$(curl -s --connect-timeout 15 "${NVIDIA_BASE_URL}/" 2>/dev/null) || true
if [[ -z "$html" ]]; then
echo ""
return 1
fi
echo "$html" \
| grep -o 'href=[^ >]*' \
| awk -F"'" '{print $2}' \
| grep -E '^[0-9]' \
| sed 's/\/$//' \
| sed "s/^[[:space:]]*//;s/[[:space:]]*$//" \
| sort -Vr \
| uniq
}
get_latest_version() {
local latest_line
latest_line=$(curl -fsSL --connect-timeout 15 "${NVIDIA_BASE_URL}/latest.txt" 2>/dev/null) || true
echo "$latest_line" | awk '{print $1}' | tr -d '[:space:]'
}
# ============================================================
# Version selection menu
# ============================================================
select_target_version() {
msg_info "$(translate 'Fetching available NVIDIA versions...')"
local latest versions_list
latest=$(get_latest_version 2>/dev/null)
versions_list=$(list_available_versions 2>/dev/null)
msg_ok "$(translate 'Version list retrieved.')"
if [[ -z "$latest" && -z "$versions_list" ]]; then
dialog --backtitle "ProxMenux" \
--title "$(translate 'Error')" \
--msgbox "\n$(translate 'Could not retrieve versions from NVIDIA. Please check your internet connection.')" \
8 72
exit 1
fi
[[ -z "$latest" && -n "$versions_list" ]] && latest=$(echo "$versions_list" | head -1)
[[ -z "$versions_list" ]] && versions_list="$latest"
latest=$(echo "$latest" | tr -d '[:space:]')
local choices=()
choices+=("latest" "$(translate 'Latest available') (${latest:-?})")
choices+=("" "")
while IFS= read -r ver; do
ver=$(echo "$ver" | tr -d '[:space:]')
[[ -z "$ver" ]] && continue
choices+=("$ver" "$ver")
done <<< "$versions_list"
local menu_text
menu_text="\n$(translate 'Current host version:') ${HOST_NVIDIA_VERSION}\n"
menu_text+="$(translate 'Select the target version to install on host and all affected LXCs:')"
TARGET_VERSION=$(dialog --backtitle "ProxMenux" \
--title "$(translate 'NVIDIA Driver Version')" \
--menu "$menu_text" 26 80 16 \
"${choices[@]}" \
2>&1 >/dev/tty) || exit 0
[[ -z "$TARGET_VERSION" ]] && exit 0
if [[ "$TARGET_VERSION" == "latest" ]]; then
TARGET_VERSION="$latest"
fi
TARGET_VERSION=$(echo "$TARGET_VERSION" | tr -d '[:space:]')
}
# ============================================================
# Update NVIDIA userspace libs inside a single LXC
# ============================================================
update_lxc_nvidia() {
local ctid="$1"
local version="$2"
local was_running=false
# Capture old version before update
local old_version
old_version=$(get_lxc_nvidia_version "$ctid")
if pct status "$ctid" 2>/dev/null | grep -q "running"; then
was_running=true
else
msg_info "$(translate 'Starting container') ${ctid}..."
pct start "$ctid" >>"$LOG_FILE" 2>&1 || true
local ready=false
for _ in {1..15}; do
sleep 2
pct exec "$ctid" -- true >/dev/null 2>&1 && ready=true && break
done
if ! $ready; then
msg_warn "$(translate 'Container') ${ctid} $(translate 'did not start. Skipping.')"
return 1
fi
msg_ok "$(translate 'Container') ${ctid} $(translate 'started.')"
fi
msg_info "$(translate 'Updating NVIDIA libs in container') ${ctid}..."
local run_file="${NVIDIA_WORKDIR}/NVIDIA-Linux-x86_64-${version}.run"
if [[ ! -f "$run_file" ]]; then
msg_warn "$(translate 'Installer not found:') ${run_file}$(translate 'skipping container') ${ctid}"
if [[ "$was_running" == "false" ]]; then pct stop "$ctid" >>"$LOG_FILE" 2>&1 || true; fi
return 1
fi
# Extract .run on the host to avoid decompression failures inside the container
local extract_dir="${NVIDIA_WORKDIR}/extracted_${version}"
local archive="/tmp/nvidia_lxc_${version}.tar.gz"
msg_info "$(translate 'Extracting NVIDIA installer on host...')"
rm -rf "$extract_dir"
if ! sh "$run_file" --extract-only --target "$extract_dir" >>"$LOG_FILE" 2>&1; then
msg_warn "$(translate 'Extraction failed. Check log:') ${LOG_FILE}"
if [[ "$was_running" == "false" ]]; then pct stop "$ctid" >>"$LOG_FILE" 2>&1 || true; fi
return 1
fi
msg_ok "$(translate 'Extracted.')"
msg_info "$(translate 'Packing and copying installer to container') ${ctid}..."
tar -czf "$archive" -C "$extract_dir" . >>"$LOG_FILE" 2>&1
if ! pct push "$ctid" "$archive" /tmp/nvidia_lxc.tar.gz >>"$LOG_FILE" 2>&1; then
msg_warn "$(translate 'pct push failed. Check log:') ${LOG_FILE}"
rm -f "$archive"
if [[ "$was_running" == "false" ]]; then pct stop "$ctid" >>"$LOG_FILE" 2>&1 || true; fi
return 1
fi
rm -f "$archive"
msg_ok "$(translate 'Installer copied to container.')"
msg_info2 "$(translate 'Starting NVIDIA installer in container') ${ctid}. $(translate 'This may take several minutes...')"
echo "" >>"$LOG_FILE"
pct exec "$ctid" -- bash -c "
mkdir -p /tmp/nvidia_lxc_install
tar -xzf /tmp/nvidia_lxc.tar.gz -C /tmp/nvidia_lxc_install 2>&1
/tmp/nvidia_lxc_install/nvidia-installer \
--no-kernel-modules \
--no-questions \
--ui=none \
--no-nouveau-check \
--no-dkms
EXIT=\$?
rm -rf /tmp/nvidia_lxc_install /tmp/nvidia_lxc.tar.gz
exit \$EXIT
" 2>&1 | tee -a "$LOG_FILE"
local rc=${PIPESTATUS[0]}
rm -rf "$extract_dir"
if [[ $rc -ne 0 ]]; then
msg_warn "$(translate 'NVIDIA installer returned error') ${rc}. $(translate 'Check log:') ${LOG_FILE}"
if [[ "$was_running" == "false" ]]; then pct stop "$ctid" >>"$LOG_FILE" 2>&1 || true; fi
return 1
fi
msg_ok "$(translate 'Container') ${ctid}: ${old_version}${version}"
msg_info2 "$(translate 'NVIDIA driver verification in container') ${ctid}:"
pct exec "$ctid" -- nvidia-smi 2>/dev/null || true
if [[ "$was_running" == "false" ]]; then
msg_info "$(translate 'Stopping container') ${ctid}..."
pct stop "$ctid" >>"$LOG_FILE" 2>&1 || true
msg_ok "$(translate 'Container stopped.')"
fi
}
# ============================================================
# Host NVIDIA update
# ============================================================
_stop_nvidia_services() {
for svc in nvidia-persistenced.service nvidia-powerd.service; do
systemctl is-active --quiet "$svc" 2>/dev/null && systemctl stop "$svc" >/dev/null 2>&1 || true
systemctl is-enabled --quiet "$svc" 2>/dev/null && systemctl disable "$svc" >/dev/null 2>&1 || true
done
}
_unload_nvidia_modules() {
for mod in nvidia_uvm nvidia_drm nvidia_modeset nvidia; do
modprobe -r "$mod" >/dev/null 2>&1 || true
done
# Second pass for stubborn modules
for mod in nvidia_uvm nvidia_drm nvidia_modeset nvidia; do
modprobe -r --force "$mod" >/dev/null 2>&1 || true
done
}
_purge_nvidia_host() {
msg_info "$(translate 'Uninstalling current NVIDIA driver from host...')"
_stop_nvidia_services
_unload_nvidia_modules
command -v nvidia-uninstall >/dev/null 2>&1 \
&& nvidia-uninstall --silent >>"$LOG_FILE" 2>&1 || true
# Remove DKMS entries
local dkms_versions
dkms_versions=$(dkms status 2>/dev/null | awk -F, '/nvidia/ {gsub(/ /,"",$2); print $2}' || true)
while IFS= read -r ver; do
[[ -z "$ver" ]] && continue
dkms remove -m nvidia -v "$ver" --all >/dev/null 2>&1 || true
done <<< "$dkms_versions"
apt-get -y purge 'nvidia-*' 'libnvidia-*' 'cuda-*' >>"$LOG_FILE" 2>&1 || true
apt-get -y autoremove --purge >>"$LOG_FILE" 2>&1 || true
rm -f /etc/udev/rules.d/70-nvidia.rules
rm -f /etc/modprobe.d/nvidia*.conf /usr/lib/modprobe.d/nvidia*.conf
msg_ok "$(translate 'Current NVIDIA driver removed from host.')"
}
_download_installer() {
local version="$1"
local run_file="${NVIDIA_WORKDIR}/NVIDIA-Linux-x86_64-${version}.run"
mkdir -p "$NVIDIA_WORKDIR"
# Reuse cached file if valid
local existing_size
existing_size=$(stat -c%s "$run_file" 2>/dev/null || echo "0")
if [[ -f "$run_file" ]] && [[ "$existing_size" -gt 40000000 ]]; then
if file "$run_file" 2>/dev/null | grep -q "executable"; then
msg_ok "$(translate 'Installer already cached.')"
echo "$run_file"
return 0
fi
fi
rm -f "$run_file"
msg_info "$(translate 'Downloading NVIDIA driver') ${version}..."
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 ok=false
for url in "${urls[@]}"; do
if curl -fL --connect-timeout 30 --max-time 600 "$url" -o "$run_file" >>"$LOG_FILE" 2>&1; then
local sz
sz=$(stat -c%s "$run_file" 2>/dev/null || echo "0")
if [[ "$sz" -gt 40000000 ]] && file "$run_file" 2>/dev/null | grep -q "executable"; then
ok=true
break
fi
fi
rm -f "$run_file"
done
if ! $ok; then
msg_error "$(translate 'Download failed. Check /tmp/nvidia_update.log')"
exit 1
fi
chmod +x "$run_file"
msg_ok "$(translate 'Download complete.')"
echo "$run_file"
}
_run_installer() {
local installer="$1"
local tmp_dir="${NVIDIA_WORKDIR}/tmp_extract"
mkdir -p "$tmp_dir"
msg_info "$(translate 'Installing NVIDIA driver on host. This may take several minutes...')"
sh "$installer" \
--tmpdir="$tmp_dir" \
--no-questions \
--ui=none \
--disable-nouveau \
--no-nouveau-check \
--dkms \
>>"$LOG_FILE" 2>&1
local rc=$?
rm -rf "$tmp_dir"
if [[ $rc -ne 0 ]]; then
msg_error "$(translate 'NVIDIA installer failed. Check /tmp/nvidia_update.log')"
exit 1
fi
msg_ok "$(translate 'NVIDIA driver installed on host.')"
}
update_host_nvidia() {
local version="$1"
_purge_nvidia_host
local installer
installer=$(_download_installer "$version")
_run_installer "$installer"
msg_info "$(translate 'Updating initramfs...')"
update-initramfs -u -k all >>"$LOG_FILE" 2>&1 || true
msg_ok "$(translate 'initramfs updated.')"
}
# ============================================================
# Overview dialog (current state)
# ============================================================
show_current_state_dialog() {
find_nvidia_containers
local info
info="\n$(translate 'Host NVIDIA driver:') ${HOST_NVIDIA_VERSION}\n\n"
if [[ ${#NVIDIA_CONTAINERS[@]} -eq 0 ]]; then
info+="$(translate 'No LXC containers with NVIDIA passthrough found.')\n"
else
info+="$(translate 'LXC containers with NVIDIA passthrough:')\n\n"
for ctid in "${NVIDIA_CONTAINERS[@]}"; do
local lxc_ver
lxc_ver=$(get_lxc_nvidia_version "$ctid")
local ct_name
ct_name=$(pct config "$ctid" 2>/dev/null | grep "^hostname:" | awk '{print $2}')
info+=" CT ${ctid} ${ct_name:+(${ct_name})} — libcuda1: ${lxc_ver}\n"
done
fi
info+="\n$(translate 'After selecting a version, LXC containers will be updated first, then the host.')"
info+="\n$(translate 'A reboot is required after the host update.')"
dialog --backtitle "ProxMenux" \
--title "$(translate 'NVIDIA Update — Current State')" \
--yesno "$info" 20 80 \
>/dev/tty 2>&1 || exit 0
}
# ============================================================
# Restart prompt
# ============================================================
restart_prompt() {
if whiptail --title "$(translate 'NVIDIA Update')" --yesno \
"$(translate 'The host driver update requires a reboot to take effect. Reboot now?')" 10 70; then
msg_warn "$(translate 'Restarting the server...')"
reboot
else
msg_success "$(translate 'Update complete. Please reboot the server manually.')"
msg_success "$(translate 'Completed. Press Enter to return to menu...')"
read -r
fi
}
# ============================================================
# Main
# ============================================================
main() {
: >"$LOG_FILE"
# ---- Phase 1: dialogs ----
detect_host_nvidia
show_current_state_dialog
select_target_version
# Same version confirmation
if [[ "$TARGET_VERSION" == "$HOST_NVIDIA_VERSION" ]]; then
if ! dialog --backtitle "ProxMenux" \
--title "$(translate 'Same Version')" \
--yesno "\n$(translate 'Version') ${TARGET_VERSION} $(translate 'is already installed on the host.')\n\n$(translate 'Reinstall and force-update all LXC containers anyway?')" \
10 70 >/dev/tty 2>&1; then
exit 0
fi
fi
# ---- Phase 2: processing ----
show_proxmenux_logo
msg_title "$(translate 'NVIDIA Driver Update')"
# Download installer once — reused by both LXC containers and host
local run_file
run_file=$(_download_installer "$TARGET_VERSION")
# Update LXC containers first (no reboot needed for userspace libs)
if [[ ${#NVIDIA_CONTAINERS[@]} -gt 0 ]]; then
msg_info2 "$(translate 'Updating LXC containers...')"
for ctid in "${NVIDIA_CONTAINERS[@]}"; do
update_lxc_nvidia "$ctid" "$TARGET_VERSION"
done
fi
# Update host kernel module + drivers (reuses the already-downloaded installer)
update_host_nvidia "$TARGET_VERSION"
restart_prompt
}
main

View File

@@ -1,17 +1,15 @@
#!/bin/bash
# ==========================================================
# ProxMenux - A menu-driven script for Proxmox VE management
# ProxMenux - GPU and TPU Menu
# ==========================================================
# 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
# License : MIT
# Version : 2.0
# Last Updated: 01/04/2026
# ==========================================================
# Configuration ============================================
# Configuration
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
@@ -20,39 +18,49 @@ VENV_PATH="/opt/googletrans-env"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
# ==========================================================
while true; do
OPTION=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "GPUs and Coral-TPU Menu")" \
--menu "\n$(translate "Select an option:")" 20 70 8 \
"1" "$(translate "Add HW iGPU acceleration to an LXC")" \
"2" "$(translate "Add Coral TPU to an LXC")" \
"3" "$(translate "Install/Update Coral TPU on the Host")" \
"4" "$(translate "Return to Main Menu")" \
2>&1 >/dev/tty)
while true; do
OPTION=$(dialog --colors --backtitle "ProxMenux" \
--title "$(translate "GPUs and Coral-TPU Menu")" \
--menu "\n$(translate "Select an option:")" 25 80 15 \
"" "\Z4──────────────────────── $(translate "HOST") ─────────────────────────\Zn" \
"1" "$(translate "Install NVIDIA Drivers on Host")" \
"2" "$(translate "Update NVIDIA Drivers (Host + LXC)")" \
"3" "$(translate "Install/Update Coral TPU on Host")" \
"" "\Z4──────────────────────── $(translate "LXC") ──────────────────────────\Zn" \
"4" "$(translate "Add GPU to LXC (Intel / AMD / NVIDIA)")" \
"5" "$(translate "Add Coral TPU to LXC")" \
"" "" \
"0" "$(translate "Return to Main Menu")" \
2>&1 >/dev/tty
) || { exec bash "$LOCAL_SCRIPTS/menus/main_menu.sh"; }
case $OPTION in
1)
bash "$LOCAL_SCRIPTS/gpu_tpu/configure_igpu_lxc.sh"
if [ $? -ne 0 ]; then
return
fi
;;
2)
bash "$LOCAL_SCRIPTS/gpu_tpu/install_coral_lxc.sh"
if [ $? -ne 0 ]; then
return
fi
;;
3)
bash "$LOCAL_SCRIPTS/gpu_tpu/install_coral_pve9.sh"
if [ $? -ne 0 ]; then
return
fi
;;
4) exec bash "$LOCAL_SCRIPTS/menus/main_menu.sh" ;;
*) exec bash "$LOCAL_SCRIPTS/menus/main_menu.sh" ;;
esac
done
case "$OPTION" in
1)
bash "$LOCAL_SCRIPTS/gpu_tpu/nvidia_installer.sh"
;;
2)
bash "$LOCAL_SCRIPTS/gpu_tpu/nvidia_update.sh"
;;
3)
bash "$LOCAL_SCRIPTS/gpu_tpu/install_coral_pve9.sh"
;;
4)
bash "$LOCAL_SCRIPTS/gpu_tpu/add_gpu_lxc.sh"
;;
5)
bash "$LOCAL_SCRIPTS/gpu_tpu/install_coral_lxc.sh"
;;
0)
exec bash "$LOCAL_SCRIPTS/menus/main_menu.sh"
;;
*)
exec bash "$LOCAL_SCRIPTS/menus/main_menu.sh"
;;
esac
done

View File

@@ -32,6 +32,8 @@ while true; do
"1" "$(translate "Configure NFS shared on Host")" \
"2" "$(translate "Configure Samba shared on Host")" \
"3" "$(translate "Configure Local Shared on Host")" \
"9" "$(translate "Add Local Disk as Proxmox Storage")" \
"10" "$(translate "Add iSCSI Target as Proxmox Storage")" \
"" "\Z4──────────────────────── $(translate "LXC") ─────────────────────────\Zn" \
"4" "$(translate "Configure LXC Mount Points (Host ↔ Container)")" \
"" "" \
@@ -59,7 +61,13 @@ while true; do
;;
3)
bash "$LOCAL_SCRIPTS/share/local-shared-manager.sh"
;;
;;
9)
bash "$LOCAL_SCRIPTS/share/disk_host.sh"
;;
10)
bash "$LOCAL_SCRIPTS/share/iscsi_host.sh"
;;
4)
bash "$LOCAL_SCRIPTS/share/lxc-mount-manager_minimal.sh"
;;

656
scripts/share/disk_host.sh Normal file
View File

@@ -0,0 +1,656 @@
#!/bin/bash
# ==========================================================
# ProxMenux - Local Disk Manager for Proxmox Host
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT
# ==========================================================
# Description:
# Adds local SCSI/SATA/NVMe disks as Proxmox directory storage
# (pvesm add dir). The disk is formatted (ext4 or xfs), mounted
# permanently, and registered in Proxmox.
# ==========================================================
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
if ! command -v pveversion >/dev/null 2>&1; then
dialog --backtitle "ProxMenux" --title "$(translate "Error")" \
--msgbox "$(translate "This script must be run on a Proxmox host.")" 8 60
exit 1
fi
# ==========================================================
# STORAGE CONFIG READER
# ==========================================================
get_storage_config() {
local storage_id="$1"
awk -v id="$storage_id" '
/^[a-z]+: / { found = ($0 ~ ": "id"$"); next }
found && /^[^ \t]/ { exit }
found { print }
' /etc/pve/storage.cfg
}
# ==========================================================
# DISK DETECTION
# ==========================================================
get_available_disks() {
# List block devices that are:
# - Whole disks (not partitions, not loop, not dm)
# - Not the system disk (where / is mounted)
local system_disk
system_disk=$(lsblk -ndo PKNAME "$(findmnt -n -o SOURCE /)" 2>/dev/null | head -1)
while IFS= read -r line; do
local name size type model ro
name=$(echo "$line" | awk '{print $1}')
size=$(echo "$line" | awk '{print $2}')
type=$(echo "$line" | awk '{print $3}')
model=$(echo "$line" | awk '{for(i=4;i<=NF;i++) printf "%s ", $i; print ""}' | sed 's/[[:space:]]*$//')
ro=$(echo "$line" | awk '{print $NF}')
# Only whole disks
[[ "$type" != "disk" ]] && continue
# Skip read-only
[[ "$ro" == "1" ]] && continue
# Skip system disk
[[ "$name" == "$system_disk" ]] && continue
# Check if fully mounted (any partition or the disk itself is mounted at /)
local is_mounted=false
if lsblk -no MOUNTPOINT "/dev/$name" 2>/dev/null | grep -qE "^/[[:space:]]*$|^/boot"; then
is_mounted=true
fi
[[ "$is_mounted" == true ]] && continue
local info="${size}"
[[ -n "$model" && "$model" != " " ]] && info="${size}${model}"
# Show mount status
local mount_info
mount_info=$(lsblk -no MOUNTPOINT "/dev/$name" 2>/dev/null | grep -v "^$" | tr '\n' ' ' | sed 's/[[:space:]]*$//')
if [[ -n "$mount_info" ]]; then
info="${info} [${mount_info}]"
fi
echo "/dev/$name|$info"
done < <(lsblk -ndo NAME,SIZE,TYPE,MODEL,RO 2>/dev/null)
}
select_disk() {
show_proxmenux_logo
msg_title "$(translate "Add Local Disk as Proxmox Storage")"
msg_info "$(translate "Scanning available disks...")"
local disk_list
disk_list=$(get_available_disks)
if [[ -z "$disk_list" ]]; then
dialog --backtitle "ProxMenux" --title "$(translate "No Disks Found")" \
--msgbox "\n$(translate "No available disks found.")\n\n$(translate "All disks may already be in use or mounted.")" 10 60
return 1
fi
local options=()
while IFS='|' read -r device info; do
[[ -n "$device" ]] && options+=("$device" "$info")
done <<< "$disk_list"
if [[ ${#options[@]} -eq 0 ]]; then
dialog --backtitle "ProxMenux" --title "$(translate "No Disks Found")" \
--msgbox "\n$(translate "No suitable disks found.")" 8 60
return 1
fi
SELECTED_DISK=$(dialog --backtitle "ProxMenux" --title "$(translate "Select Disk")" \
--menu "\n$(translate "Select the disk to add as Proxmox storage:")\n$(translate "WARNING: All data on selected disk will be ERASED if formatted.")" \
20 80 10 "${options[@]}" 3>&1 1>&2 2>&3)
[[ -z "$SELECTED_DISK" ]] && return 1
return 0
}
inspect_disk() {
local disk="$1"
# Check existing partitions/filesystem
local partition_info
partition_info=$(lsblk -no NAME,SIZE,FSTYPE,MOUNTPOINT "$disk" 2>/dev/null | tail -n +2)
local existing_fs
existing_fs=$(blkid -s TYPE -o value "$disk" 2>/dev/null || true)
DISK_HAS_DATA=false
DISK_EXISTING_FS=""
if [[ -n "$partition_info" || -n "$existing_fs" ]]; then
DISK_HAS_DATA=true
DISK_EXISTING_FS="$existing_fs"
fi
return 0
}
select_partition_action() {
local disk="$1"
inspect_disk "$disk"
local disk_size
disk_size=$(lsblk -ndo SIZE "$disk" 2>/dev/null)
if [[ "$DISK_HAS_DATA" == "true" ]]; then
local msg="$(translate "Disk:"): $disk ($disk_size)\n"
[[ -n "$DISK_EXISTING_FS" ]] && msg+="$(translate "Existing filesystem:"): $DISK_EXISTING_FS\n"
msg+="\n$(translate "Options:")\n"
msg+="$(translate "Format: ERASE all data and create new filesystem")\n"
[[ -n "$DISK_EXISTING_FS" ]] && msg+="$(translate "Use existing: mount without formatting")\n"
msg+="\n$(translate "Continue?")"
DISK_ACTION=$(whiptail --title "$(translate "Disk Setup")" \
--menu "$msg" 20 80 3 \
"format" "$(translate "Format disk (ERASE all data)")" \
$(if [[ -n "$DISK_EXISTING_FS" ]]; then echo '"use_existing" "'"$(translate "Use existing filesystem")"'"'; fi) \
"cancel" "$(translate "Cancel")" \
3>&1 1>&2 2>&3)
else
DISK_ACTION=$(whiptail --title "$(translate "Disk Setup")" \
--menu "$(translate "Disk:"): $disk ($disk_size)\n\n$(translate "Disk appears empty. It will be formatted.")" \
14 70 2 \
"format" "$(translate "Format and add as Proxmox storage")" \
"cancel" "$(translate "Cancel")" \
3>&1 1>&2 2>&3)
fi
[[ -z "$DISK_ACTION" || "$DISK_ACTION" == "cancel" ]] && return 1
return 0
}
select_filesystem() {
FILESYSTEM=$(whiptail --title "$(translate "Select Filesystem")" \
--menu "$(translate "Choose filesystem for the disk:")" 14 60 3 \
"ext4" "$(translate "ext4 — recommended, most compatible")" \
"xfs" "$(translate "xfs — better for large files and VMs")" \
3>&1 1>&2 2>&3)
[[ -z "$FILESYSTEM" ]] && return 1
return 0
}
# ==========================================================
# STORAGE CONFIGURATION
# ==========================================================
configure_disk_storage() {
local disk_name
disk_name=$(basename "$SELECTED_DISK")
STORAGE_ID=$(whiptail --inputbox "$(translate "Enter storage ID for Proxmox:")" \
10 60 "disk-${disk_name}" \
--title "$(translate "Storage ID")" 3>&1 1>&2 2>&3)
[[ $? -ne 0 ]] && return 1
[[ -z "$STORAGE_ID" ]] && STORAGE_ID="disk-${disk_name}"
if [[ ! "$STORAGE_ID" =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ ]]; then
whiptail --msgbox "$(translate "Invalid storage ID. Use only letters, numbers, hyphens and underscores.")" 8 70
return 1
fi
MOUNT_PATH="/mnt/${STORAGE_ID}"
MOUNT_PATH=$(whiptail --inputbox "$(translate "Enter mount path on host:")" \
10 60 "$MOUNT_PATH" \
--title "$(translate "Mount Path")" 3>&1 1>&2 2>&3)
[[ $? -ne 0 || -z "$MOUNT_PATH" ]] && return 1
CONTENT_TYPE=$(whiptail --title "$(translate "Content Types")" \
--menu "$(translate "Select content types for this storage:")" 16 70 5 \
"1" "$(translate "VM Storage (images, backup)")" \
"2" "$(translate "Standard NAS (backup, iso, vztmpl)")" \
"3" "$(translate "All types (images, backup, iso, vztmpl, snippets)")" \
"4" "$(translate "Custom")" \
3>&1 1>&2 2>&3)
[[ $? -ne 0 ]] && return 1
case "$CONTENT_TYPE" in
1) MOUNT_CONTENT="images,backup" ;;
2) MOUNT_CONTENT="backup,iso,vztmpl" ;;
3) MOUNT_CONTENT="images,backup,iso,vztmpl,snippets" ;;
4)
MOUNT_CONTENT=$(whiptail --inputbox "$(translate "Enter content types (comma-separated):")" \
10 70 "images,backup" --title "$(translate "Custom Content")" 3>&1 1>&2 2>&3)
[[ $? -ne 0 || -z "$MOUNT_CONTENT" ]] && MOUNT_CONTENT="images,backup"
;;
*) return 1 ;;
esac
return 0
}
# ==========================================================
# DISK SETUP AND MOUNT
# ==========================================================
format_and_mount_disk() {
local disk="$1"
local mount_path="$2"
local filesystem="$3"
# Final confirmation before any destructive operation
local disk_size
disk_size=$(lsblk -ndo SIZE "$disk" 2>/dev/null)
if ! whiptail --yesno \
"$(translate "FINAL CONFIRMATION — DATA WILL BE ERASED")\n\n$(translate "Disk:"): $disk ($disk_size)\n$(translate "Filesystem:"): $filesystem\n$(translate "Mount path:"): $mount_path\n\n$(translate "ALL DATA ON") $disk $(translate "WILL BE PERMANENTLY ERASED.")\n\n$(translate "Are you absolutely sure?")" \
14 80 --title "$(translate "CONFIRM FORMAT")"; then
return 1
fi
msg_info "$(translate "Wiping existing partition table...")"
wipefs -a "$disk" >/dev/null 2>&1 || true
sgdisk --zap-all "$disk" >/dev/null 2>&1 || true
msg_info "$(translate "Creating partition...")"
if ! parted -s "$disk" mklabel gpt mkpart primary 0% 100% >/dev/null 2>&1; then
msg_error "$(translate "Failed to create partition table")"
return 1
fi
# Wait for kernel to recognize new partition
sleep 2
partprobe "$disk" 2>/dev/null || true
sleep 1
# Determine partition device
local partition
if [[ "$disk" =~ [0-9]$ ]]; then
partition="${disk}p1"
else
partition="${disk}1"
fi
msg_info "$(translate "Formatting as") $filesystem..."
case "$filesystem" in
ext4)
if ! mkfs.ext4 -F -L "$STORAGE_ID" "$partition" >/dev/null 2>&1; then
msg_error "$(translate "Failed to format disk as ext4")"
return 1
fi
;;
xfs)
if ! mkfs.xfs -f -L "$STORAGE_ID" "$partition" >/dev/null 2>&1; then
msg_error "$(translate "Failed to format disk as xfs")"
return 1
fi
;;
esac
msg_ok "$(translate "Disk formatted as") $filesystem"
DISK_PARTITION="$partition"
return 0
}
mount_disk_permanently() {
local partition="$1"
local mount_path="$2"
local filesystem="$3"
msg_info "$(translate "Creating mount point...")"
if ! mkdir -p "$mount_path"; then
msg_error "$(translate "Failed to create mount point:") $mount_path"
return 1
fi
msg_info "$(translate "Mounting disk...")"
if ! mount -t "$filesystem" "$partition" "$mount_path" 2>/dev/null; then
msg_error "$(translate "Failed to mount disk")"
return 1
fi
msg_ok "$(translate "Disk mounted at") $mount_path"
msg_info "$(translate "Adding to /etc/fstab for permanent mounting...")"
local disk_uuid
disk_uuid=$(blkid -s UUID -o value "$partition" 2>/dev/null)
if [[ -n "$disk_uuid" ]]; then
# Remove any existing fstab entry for this UUID or mount point
sed -i "\|UUID=$disk_uuid|d" /etc/fstab
sed -i "\|[[:space:]]$mount_path[[:space:]]|d" /etc/fstab
echo "UUID=$disk_uuid $mount_path $filesystem defaults,nofail 0 2" >> /etc/fstab
msg_ok "$(translate "Added to /etc/fstab using UUID")"
else
sed -i "\|[[:space:]]$mount_path[[:space:]]|d" /etc/fstab
echo "$partition $mount_path $filesystem defaults,nofail 0 2" >> /etc/fstab
msg_ok "$(translate "Added to /etc/fstab using device path")"
fi
systemctl daemon-reload 2>/dev/null || true
return 0
}
mount_existing_disk() {
local disk="$1"
local mount_path="$2"
local existing_fs
existing_fs=$(blkid -s TYPE -o value "$disk" 2>/dev/null || true)
if [[ -z "$existing_fs" ]]; then
msg_error "$(translate "Cannot detect filesystem on") $disk"
return 1
fi
msg_info "$(translate "Creating mount point...")"
mkdir -p "$mount_path"
msg_info "$(translate "Mounting existing") $existing_fs $(translate "filesystem...")"
if ! mount "$disk" "$mount_path" 2>/dev/null; then
msg_error "$(translate "Failed to mount disk")"
return 1
fi
msg_ok "$(translate "Disk mounted at") $mount_path"
# Add to fstab
local disk_uuid
disk_uuid=$(blkid -s UUID -o value "$disk" 2>/dev/null)
if [[ -n "$disk_uuid" ]]; then
sed -i "\|UUID=$disk_uuid|d" /etc/fstab
sed -i "\|[[:space:]]$mount_path[[:space:]]|d" /etc/fstab
echo "UUID=$disk_uuid $mount_path $existing_fs defaults,nofail 0 2" >> /etc/fstab
msg_ok "$(translate "Added to /etc/fstab")"
fi
DISK_PARTITION="$disk"
systemctl daemon-reload 2>/dev/null || true
return 0
}
add_proxmox_dir_storage() {
local storage_id="$1"
local path="$2"
local content="$3"
if ! command -v pvesm >/dev/null 2>&1; then
msg_error "$(translate "pvesm command not found. This should not happen on Proxmox.")"
return 1
fi
if pvesm status "$storage_id" >/dev/null 2>&1; then
msg_warn "$(translate "Storage ID already exists:") $storage_id"
if ! whiptail --yesno "$(translate "Storage ID already exists. Do you want to remove and recreate it?")" \
8 60 --title "$(translate "Storage Exists")"; then
return 0
fi
pvesm remove "$storage_id" 2>/dev/null || true
fi
msg_info "$(translate "Registering disk as Proxmox storage...")"
local pvesm_output
if pvesm_output=$(pvesm add dir "$storage_id" \
--path "$path" \
--content "$content" 2>&1); then
msg_ok "$(translate "Directory storage added successfully to Proxmox!")"
echo -e ""
echo -e "${TAB}${BOLD}$(translate "Storage Added:")${CL}"
echo -e "${TAB}${BGN}$(translate "Storage ID:")${CL} ${BL}$storage_id${CL}"
echo -e "${TAB}${BGN}$(translate "Path:")${CL} ${BL}$path${CL}"
echo -e "${TAB}${BGN}$(translate "Content Types:")${CL} ${BL}$content${CL}"
echo -e ""
msg_ok "$(translate "Storage is now available in Proxmox web interface under Datacenter > Storage")"
return 0
else
msg_error "$(translate "Failed to add storage to Proxmox.")"
echo -e "${TAB}$(translate "Error details:"): $pvesm_output"
echo -e ""
msg_info2 "$(translate "You can add it manually through:")"
echo -e "${TAB}$(translate "Proxmox web interface: Datacenter > Storage > Add > Directory")"
echo -e "${TAB}• pvesm add dir $storage_id --path $path --content $content"
return 1
fi
}
# ==========================================================
# MAIN OPERATIONS
# ==========================================================
add_disk_to_proxmox() {
# Check required tools
for tool in parted mkfs.ext4 blkid lsblk; do
if ! command -v "$tool" >/dev/null 2>&1; then
msg_info "$(translate "Installing required tools...")"
apt-get update &>/dev/null
apt-get install -y parted e2fsprogs util-linux xfsprogs gdisk &>/dev/null
break
fi
done
# Step 1: Select disk
select_disk || return
# Step 2: Inspect and choose action
select_partition_action "$SELECTED_DISK" || return
# Step 3: Filesystem selection (only if formatting)
if [[ "$DISK_ACTION" == "format" ]]; then
select_filesystem || return
fi
# Step 4: Configure storage options
configure_disk_storage || return
show_proxmenux_logo
msg_title "$(translate "Add Local Disk as Proxmox Storage")"
msg_ok "$(translate "Disk:") $SELECTED_DISK"
msg_ok "$(translate "Action:") $DISK_ACTION"
[[ "$DISK_ACTION" == "format" ]] && msg_ok "$(translate "Filesystem:") $FILESYSTEM"
msg_ok "$(translate "Mount path:") $MOUNT_PATH"
msg_ok "$(translate "Storage ID:") $STORAGE_ID"
msg_ok "$(translate "Content:") $MOUNT_CONTENT"
echo ""
# Step 5: Format/mount
case "$DISK_ACTION" in
format)
format_and_mount_disk "$SELECTED_DISK" "$MOUNT_PATH" "$FILESYSTEM" || {
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
return 1
}
mount_disk_permanently "$DISK_PARTITION" "$MOUNT_PATH" "$FILESYSTEM" || {
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
return 1
}
;;
use_existing)
mount_existing_disk "$SELECTED_DISK" "$MOUNT_PATH" || {
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
return 1
}
;;
esac
# Step 6: Register in Proxmox
add_proxmox_dir_storage "$STORAGE_ID" "$MOUNT_PATH" "$MOUNT_CONTENT"
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
}
view_disk_storages() {
show_proxmenux_logo
msg_title "$(translate "Local Disk Storages in Proxmox")"
echo "=================================================="
echo ""
if ! command -v pvesm >/dev/null 2>&1; then
msg_error "$(translate "pvesm not found.")"
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
return
fi
# Show all directory storages
DIR_STORAGES=$(pvesm status 2>/dev/null | awk '$2 == "dir" {print $1, $3}')
if [[ -z "$DIR_STORAGES" ]]; then
msg_warn "$(translate "No directory storage configured in Proxmox.")"
echo ""
msg_info2 "$(translate "Use option 1 to add a local disk as Proxmox storage.")"
else
echo -e "${BOLD}$(translate "Directory Storages:")${CL}"
echo ""
while IFS=" " read -r storage_id storage_status; do
[[ -z "$storage_id" ]] && continue
local storage_info path content
storage_info=$(get_storage_config "$storage_id")
path=$(echo "$storage_info" | awk '$1 == "path" {print $2}')
content=$(echo "$storage_info" | awk '$1 == "content" {print $2}')
local disk_device=""
if [[ -n "$path" ]]; then
disk_device=$(findmnt -n -o SOURCE "$path" 2>/dev/null || true)
fi
local disk_size=""
if [[ -n "$disk_device" ]]; then
disk_size=$(lsblk -ndo SIZE "$disk_device" 2>/dev/null || true)
fi
echo -e "${TAB}${BOLD}$storage_id${CL}"
echo -e "${TAB} ${BGN}$(translate "Path:")${CL} ${BL}$path${CL}"
[[ -n "$disk_device" ]] && echo -e "${TAB} ${BGN}$(translate "Device:")${CL} ${BL}$disk_device${CL}"
[[ -n "$disk_size" ]] && echo -e "${TAB} ${BGN}$(translate "Size:")${CL} ${BL}$disk_size${CL}"
echo -e "${TAB} ${BGN}$(translate "Content:")${CL} ${BL}$content${CL}"
if [[ "$storage_status" == "active" ]]; then
echo -e "${TAB} ${BGN}$(translate "Status:")${CL} ${GN}$(translate "Active")${CL}"
else
echo -e "${TAB} ${BGN}$(translate "Status:")${CL} ${RD}$storage_status${CL}"
fi
echo ""
done <<< "$DIR_STORAGES"
fi
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
}
remove_disk_storage() {
if ! command -v pvesm >/dev/null 2>&1; then
dialog --backtitle "ProxMenux" --title "$(translate "Error")" \
--msgbox "\n$(translate "pvesm not found.")" 8 60
return
fi
DIR_STORAGES=$(pvesm status 2>/dev/null | awk '$2 == "dir" {print $1}')
if [[ -z "$DIR_STORAGES" ]]; then
dialog --backtitle "ProxMenux" --title "$(translate "No Disk Storage")" \
--msgbox "\n$(translate "No directory storage found in Proxmox.")" 8 60
return
fi
OPTIONS=()
while IFS= read -r storage_id; do
[[ -z "$storage_id" ]] && continue
local path
path=$(get_storage_config "$storage_id" | awk '$1 == "path" {print $2}')
OPTIONS+=("$storage_id" "${path:-unknown}")
done <<< "$DIR_STORAGES"
SELECTED=$(dialog --backtitle "ProxMenux" --title "$(translate "Remove Disk Storage")" \
--menu "$(translate "Select storage to remove:")" 20 80 10 \
"${OPTIONS[@]}" 3>&1 1>&2 2>&3)
[[ -z "$SELECTED" ]] && return
local path content
path=$(get_storage_config "$SELECTED" | awk '$1 == "path" {print $2}')
content=$(get_storage_config "$SELECTED" | awk '$1 == "content" {print $2}')
if whiptail --yesno "$(translate "Remove Proxmox storage:")\n\n$SELECTED\n\n$(translate "Path:"): $path\n$(translate "Content:"): $content\n\n$(translate "This removes the storage registration from Proxmox.")\n$(translate "The disk and its data will NOT be erased.")\n$(translate "The disk will remain mounted at:"): $path" \
18 80 --title "$(translate "Confirm Remove")"; then
show_proxmenux_logo
msg_title "$(translate "Remove Disk Storage")"
if pvesm remove "$SELECTED" 2>/dev/null; then
msg_ok "$(translate "Storage") $SELECTED $(translate "removed from Proxmox.")"
echo ""
msg_info2 "$(translate "The disk remains mounted at:"): $path"
msg_info2 "$(translate "The fstab entry is still present. Remove manually if needed.")"
else
msg_error "$(translate "Failed to remove storage.")"
fi
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
fi
}
list_available_disks() {
show_proxmenux_logo
msg_title "$(translate "Available Disks on Host")"
echo "=================================================="
echo ""
echo -e "${BOLD}$(translate "All block devices:")${CL}"
echo ""
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,MODEL 2>/dev/null
echo ""
echo -e "${BOLD}$(translate "Proxmox directory storages:")${CL}"
if command -v pvesm >/dev/null 2>&1; then
pvesm status 2>/dev/null | awk '$2 == "dir" {print " " $1, $2, $3}' || echo " $(translate "None")"
fi
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
}
# ==========================================================
# MAIN MENU
# ==========================================================
while true; do
CHOICE=$(dialog --backtitle "ProxMenux" \
--title "$(translate "Local Disk Manager - Proxmox Host")" \
--menu "$(translate "Choose an option:")" 18 70 6 \
"1" "$(translate "Add Local Disk as Proxmox Storage")" \
"2" "$(translate "View Disk Storages")" \
"3" "$(translate "Remove Disk Storage")" \
"4" "$(translate "List Available Disks")" \
"5" "$(translate "Exit")" \
3>&1 1>&2 2>&3)
RETVAL=$?
if [[ $RETVAL -ne 0 ]]; then
exit 0
fi
case $CHOICE in
1) add_disk_to_proxmox ;;
2) view_disk_storages ;;
3) remove_disk_storage ;;
4) list_available_disks ;;
5) exit 0 ;;
*) exit 0 ;;
esac
done

518
scripts/share/iscsi_host.sh Normal file
View File

@@ -0,0 +1,518 @@
#!/bin/bash
# ==========================================================
# ProxMenux - iSCSI Host Manager for Proxmox Host
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : MIT
# ==========================================================
# Description:
# Adds iSCSI targets as Proxmox storage (pvesm add iscsi).
# Proxmox manages the connection natively via open-iscsi.
# iSCSI storage provides block devices for VM disk images.
# ==========================================================
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
BASE_DIR="/usr/local/share/proxmenux"
UTILS_FILE="$BASE_DIR/utils.sh"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
load_language
initialize_cache
if ! command -v pveversion >/dev/null 2>&1; then
dialog --backtitle "ProxMenux" --title "$(translate "Error")" \
--msgbox "$(translate "This script must be run on a Proxmox host.")" 8 60
exit 1
fi
# ==========================================================
# STORAGE CONFIG READER
# ==========================================================
get_storage_config() {
local storage_id="$1"
awk -v id="$storage_id" '
/^[a-z]+: / { found = ($0 ~ ": "id"$"); next }
found && /^[^ \t]/ { exit }
found { print }
' /etc/pve/storage.cfg
}
# ==========================================================
# TOOLS
# ==========================================================
ensure_iscsi_tools() {
if ! command -v iscsiadm >/dev/null 2>&1; then
msg_info "$(translate "Installing iSCSI initiator tools...")"
apt-get update &>/dev/null
apt-get install -y open-iscsi &>/dev/null
systemctl enable --now iscsid 2>/dev/null || true
msg_ok "$(translate "iSCSI tools installed")"
fi
if ! systemctl is-active --quiet iscsid 2>/dev/null; then
msg_info "$(translate "Starting iSCSI daemon...")"
systemctl start iscsid 2>/dev/null || true
fi
}
# ==========================================================
# TARGET DISCOVERY
# ==========================================================
select_iscsi_portal() {
ISCSI_PORTAL=$(whiptail --inputbox \
"$(translate "Enter iSCSI target portal IP or hostname:")\n\n$(translate "Examples:")\n 192.168.1.100\n 192.168.1.100:3260\n nas.local" \
14 65 \
--title "$(translate "iSCSI Portal")" 3>&1 1>&2 2>&3)
[[ $? -ne 0 || -z "$ISCSI_PORTAL" ]] && return 1
# Normalise: if no port specified, add default 3260
if [[ ! "$ISCSI_PORTAL" =~ :[0-9]+$ ]]; then
ISCSI_PORTAL_DISPLAY="$ISCSI_PORTAL"
ISCSI_PORTAL_FULL="${ISCSI_PORTAL}:3260"
else
ISCSI_PORTAL_DISPLAY="$ISCSI_PORTAL"
ISCSI_PORTAL_FULL="$ISCSI_PORTAL"
fi
# Extract host for ping
ISCSI_HOST=$(echo "$ISCSI_PORTAL" | cut -d: -f1)
return 0
}
discover_iscsi_targets() {
show_proxmenux_logo
msg_title "$(translate "Add iSCSI Target as Proxmox Storage")"
msg_ok "$(translate "Portal:") $ISCSI_PORTAL_DISPLAY"
msg_info "$(translate "Testing connectivity to portal...")"
if ! ping -c 1 -W 3 "$ISCSI_HOST" >/dev/null 2>&1; then
msg_error "$(translate "Cannot reach portal:") $ISCSI_HOST"
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
return 1
fi
msg_ok "$(translate "Portal is reachable")"
if ! nc -z -w 3 "$ISCSI_HOST" "${ISCSI_PORTAL_FULL##*:}" 2>/dev/null; then
msg_warn "$(translate "iSCSI port") ${ISCSI_PORTAL_FULL##*:} $(translate "may be closed — trying discovery anyway...")"
fi
msg_info "$(translate "Discovering iSCSI targets...")"
DISCOVERY_OUTPUT=$(iscsiadm --mode discovery --type sendtargets \
--portal "$ISCSI_PORTAL_FULL" 2>&1)
DISCOVERY_RESULT=$?
if [[ $DISCOVERY_RESULT -ne 0 ]]; then
msg_error "$(translate "iSCSI discovery failed")"
echo -e "${TAB}$(translate "Error:"): $DISCOVERY_OUTPUT"
echo ""
msg_info2 "$(translate "Please check:")"
echo -e "${TAB}$(translate "Portal IP and port are correct")"
echo -e "${TAB}$(translate "iSCSI service is running on the target")"
echo -e "${TAB}$(translate "Firewall allows port") ${ISCSI_PORTAL_FULL##*:}"
echo -e "${TAB}$(translate "Initiator IQN is authorised on the target")"
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
return 1
fi
# Parse discovered targets: format is <portal> <iqn>
TARGETS=$(echo "$DISCOVERY_OUTPUT" | awk '{print $2}' | grep "^iqn\." | sort -u)
if [[ -z "$TARGETS" ]]; then
msg_warn "$(translate "No iSCSI targets found on portal") $ISCSI_PORTAL_DISPLAY"
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
return 1
fi
msg_ok "$(translate "Discovery successful")"
return 0
}
select_iscsi_target() {
local target_count
target_count=$(echo "$TARGETS" | wc -l)
if [[ "$target_count" -eq 1 ]]; then
ISCSI_TARGET=$(echo "$TARGETS" | head -1)
msg_ok "$(translate "Single target found — selected automatically:") $ISCSI_TARGET"
return 0
fi
local options=()
local i=1
while IFS= read -r iqn; do
[[ -z "$iqn" ]] && continue
# Try to get LUN info for display
local lun_info
lun_info=$(iscsiadm --mode node --targetname "$iqn" --portal "$ISCSI_PORTAL_FULL" \
--op show 2>/dev/null | grep "node.conn\[0\].address" | awk -F= '{print $2}' | tr -d ' ' || true)
options+=("$i" "$iqn")
i=$((i + 1))
done <<< "$TARGETS"
local choice
choice=$(dialog --backtitle "ProxMenux" --title "$(translate "Select iSCSI Target")" \
--menu "\n$(translate "Select target IQN:")" 20 90 10 \
"${options[@]}" 3>&1 1>&2 2>&3)
[[ -z "$choice" ]] && return 1
ISCSI_TARGET=$(echo "$TARGETS" | sed -n "${choice}p")
[[ -z "$ISCSI_TARGET" ]] && return 1
return 0
}
# ==========================================================
# STORAGE CONFIGURATION
# ==========================================================
configure_iscsi_storage() {
# Suggest a storage ID derived from target IQN
local iqn_suffix
iqn_suffix=$(echo "$ISCSI_TARGET" | awk -F: '{print $NF}' | tr '.' '-' | cut -c1-20)
local default_id="iscsi-${iqn_suffix}"
STORAGE_ID=$(whiptail --inputbox "$(translate "Enter storage ID for Proxmox:")" \
10 65 "$default_id" \
--title "$(translate "Storage ID")" 3>&1 1>&2 2>&3)
[[ $? -ne 0 ]] && return 1
[[ -z "$STORAGE_ID" ]] && STORAGE_ID="$default_id"
if [[ ! "$STORAGE_ID" =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ ]]; then
whiptail --msgbox "$(translate "Invalid storage ID. Use only letters, numbers, hyphens and underscores.")" 8 70
return 1
fi
# iSCSI in Proxmox exposes block devices — content is always 'images'
# (no file-level access like NFS/CIFS)
MOUNT_CONTENT="images"
whiptail --title "$(translate "iSCSI Content Type")" \
--msgbox "$(translate "iSCSI storage provides raw block devices for VM disk images.")\n\n$(translate "Content type is fixed to:")\n\n images\n\n$(translate "Each LUN will appear as a block device assignable to VMs.")" \
12 70
return 0
}
# ==========================================================
# PROXMOX INTEGRATION
# ==========================================================
add_proxmox_iscsi_storage() {
local storage_id="$1"
local portal="$2"
local target="$3"
local content="${4:-images}"
if ! command -v pvesm >/dev/null 2>&1; then
msg_error "$(translate "pvesm command not found. This should not happen on Proxmox.")"
return 1
fi
if pvesm status "$storage_id" >/dev/null 2>&1; then
msg_warn "$(translate "Storage ID already exists:") $storage_id"
if ! whiptail --yesno "$(translate "Storage ID already exists. Do you want to remove and recreate it?")" \
8 60 --title "$(translate "Storage Exists")"; then
return 0
fi
pvesm remove "$storage_id" 2>/dev/null || true
fi
msg_ok "$(translate "Storage ID is available")"
msg_info "$(translate "Adding iSCSI storage to Proxmox...")"
local pvesm_output pvesm_result
pvesm_output=$(pvesm add iscsi "$storage_id" \
--portal "$portal" \
--target "$target" \
--content "$content" 2>&1)
pvesm_result=$?
if [[ $pvesm_result -eq 0 ]]; then
msg_ok "$(translate "iSCSI storage added successfully to Proxmox!")"
echo -e ""
echo -e "${TAB}${BOLD}$(translate "Storage Added:")${CL}"
echo -e "${TAB}${BGN}$(translate "Storage ID:")${CL} ${BL}$storage_id${CL}"
echo -e "${TAB}${BGN}$(translate "Portal:")${CL} ${BL}$portal${CL}"
echo -e "${TAB}${BGN}$(translate "Target IQN:")${CL} ${BL}$target${CL}"
echo -e "${TAB}${BGN}$(translate "Content Types:")${CL} ${BL}$content${CL}"
echo -e ""
msg_ok "$(translate "Storage is now available in Proxmox web interface under Datacenter > Storage")"
msg_info2 "$(translate "LUNs appear as block devices assignable to VMs")"
return 0
else
msg_error "$(translate "Failed to add iSCSI storage to Proxmox.")"
echo -e "${TAB}$(translate "Error details:"): $pvesm_output"
echo -e ""
msg_info2 "$(translate "You can add it manually through:")"
echo -e "${TAB}$(translate "Proxmox web interface: Datacenter > Storage > Add > iSCSI")"
echo -e "${TAB}• pvesm add iscsi $storage_id --portal $portal --target $target --content $content"
return 1
fi
}
# ==========================================================
# MAIN OPERATIONS
# ==========================================================
add_iscsi_to_proxmox() {
ensure_iscsi_tools
# Step 1: Enter portal
select_iscsi_portal || return
# Step 2: Discover targets
discover_iscsi_targets || return
# Step 3: Select target
select_iscsi_target || return
show_proxmenux_logo
msg_title "$(translate "Add iSCSI Target as Proxmox Storage")"
msg_ok "$(translate "Portal:") $ISCSI_PORTAL_DISPLAY"
msg_ok "$(translate "Target:") $ISCSI_TARGET"
# Step 4: Configure storage
configure_iscsi_storage || return
# Step 5: Add to Proxmox
show_proxmenux_logo
msg_title "$(translate "Add iSCSI Target as Proxmox Storage")"
msg_ok "$(translate "Portal:") $ISCSI_PORTAL_DISPLAY"
msg_ok "$(translate "Target:") $ISCSI_TARGET"
msg_ok "$(translate "Storage ID:") $STORAGE_ID"
msg_ok "$(translate "Content:") $MOUNT_CONTENT"
echo -e ""
add_proxmox_iscsi_storage "$STORAGE_ID" "$ISCSI_PORTAL_FULL" "$ISCSI_TARGET" "$MOUNT_CONTENT"
echo -e ""
msg_success "$(translate "Press Enter to continue...")"
read -r
}
view_iscsi_storages() {
show_proxmenux_logo
msg_title "$(translate "iSCSI Storages in Proxmox")"
echo "=================================================="
echo ""
if ! command -v pvesm >/dev/null 2>&1; then
msg_error "$(translate "pvesm not found.")"
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
return
fi
ISCSI_STORAGES=$(pvesm status 2>/dev/null | awk '$2 == "iscsi" {print $1, $3}')
if [[ -z "$ISCSI_STORAGES" ]]; then
msg_warn "$(translate "No iSCSI storage configured in Proxmox.")"
echo ""
msg_info2 "$(translate "Use option 1 to add an iSCSI target as Proxmox storage.")"
else
echo -e "${BOLD}$(translate "iSCSI Storages:")${CL}"
echo ""
while IFS=" " read -r storage_id storage_status; do
[[ -z "$storage_id" ]] && continue
local storage_info portal target content
storage_info=$(get_storage_config "$storage_id")
portal=$(echo "$storage_info" | awk '$1 == "portal" {print $2}')
target=$(echo "$storage_info" | awk '$1 == "target" {print $2}')
content=$(echo "$storage_info" | awk '$1 == "content" {print $2}')
echo -e "${TAB}${BOLD}$storage_id${CL}"
echo -e "${TAB} ${BGN}$(translate "Portal:")${CL} ${BL}$portal${CL}"
echo -e "${TAB} ${BGN}$(translate "Target IQN:")${CL} ${BL}$target${CL}"
echo -e "${TAB} ${BGN}$(translate "Content:")${CL} ${BL}$content${CL}"
if [[ "$storage_status" == "active" ]]; then
echo -e "${TAB} ${BGN}$(translate "Status:")${CL} ${GN}$(translate "Active")${CL}"
else
echo -e "${TAB} ${BGN}$(translate "Status:")${CL} ${RD}$storage_status${CL}"
fi
echo ""
done <<< "$ISCSI_STORAGES"
fi
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
}
remove_iscsi_storage() {
if ! command -v pvesm >/dev/null 2>&1; then
dialog --backtitle "ProxMenux" --title "$(translate "Error")" \
--msgbox "\n$(translate "pvesm not found.")" 8 60
return
fi
ISCSI_STORAGES=$(pvesm status 2>/dev/null | awk '$2 == "iscsi" {print $1}')
if [[ -z "$ISCSI_STORAGES" ]]; then
dialog --backtitle "ProxMenux" --title "$(translate "No iSCSI Storage")" \
--msgbox "\n$(translate "No iSCSI storage found in Proxmox.")" 8 60
return
fi
local options=()
while IFS= read -r storage_id; do
[[ -z "$storage_id" ]] && continue
local storage_info portal target
storage_info=$(get_storage_config "$storage_id")
portal=$(echo "$storage_info" | awk '$1 == "portal" {print $2}')
target=$(echo "$storage_info" | awk '$1 == "target" {print $2}')
options+=("$storage_id" "$portal${target:0:40}")
done <<< "$ISCSI_STORAGES"
local SELECTED
SELECTED=$(dialog --backtitle "ProxMenux" --title "$(translate "Remove iSCSI Storage")" \
--menu "$(translate "Select storage to remove:")" 20 90 10 \
"${options[@]}" 3>&1 1>&2 2>&3)
[[ -z "$SELECTED" ]] && return
local storage_info portal target content
storage_info=$(get_storage_config "$SELECTED")
portal=$(echo "$storage_info" | awk '$1 == "portal" {print $2}')
target=$(echo "$storage_info" | awk '$1 == "target" {print $2}')
content=$(echo "$storage_info" | awk '$1 == "content" {print $2}')
if whiptail --yesno "$(translate "Remove Proxmox iSCSI storage:")\n\n$SELECTED\n\n$(translate "Portal:"): $portal\n$(translate "Target:"): $target\n$(translate "Content:"): $content\n\n$(translate "This removes the storage from Proxmox. The iSCSI target is not affected.")" \
16 80 --title "$(translate "Confirm Remove")"; then
show_proxmenux_logo
msg_title "$(translate "Remove iSCSI Storage")"
if pvesm remove "$SELECTED" 2>/dev/null; then
msg_ok "$(translate "Storage") $SELECTED $(translate "removed successfully from Proxmox.")"
else
msg_error "$(translate "Failed to remove storage.")"
fi
echo -e ""
msg_success "$(translate "Press Enter to continue...")"
read -r
fi
}
test_iscsi_connectivity() {
show_proxmenux_logo
msg_title "$(translate "Test iSCSI Connectivity")"
echo "=================================================="
echo ""
if command -v iscsiadm >/dev/null 2>&1; then
msg_ok "$(translate "iSCSI Initiator: AVAILABLE")"
local initiator_iqn
initiator_iqn=$(cat /etc/iscsi/initiatorname.iscsi 2>/dev/null | grep "^InitiatorName=" | cut -d= -f2)
[[ -n "$initiator_iqn" ]] && echo -e " ${BGN}$(translate "Initiator IQN:")${CL} ${BL}$initiator_iqn${CL}"
if systemctl is-active --quiet iscsid 2>/dev/null; then
msg_ok "$(translate "iSCSI Daemon (iscsid): RUNNING")"
else
msg_warn "$(translate "iSCSI Daemon (iscsid): STOPPED")"
fi
else
msg_warn "$(translate "iSCSI Initiator: NOT INSTALLED")"
echo -e " $(translate "Install with: apt-get install open-iscsi")"
fi
echo ""
if command -v pvesm >/dev/null 2>&1; then
echo -e "${BOLD}$(translate "Proxmox iSCSI Storage Status:")${CL}"
ISCSI_STORAGES=$(pvesm status 2>/dev/null | awk '$2 == "iscsi" {print $1, $3}')
if [[ -n "$ISCSI_STORAGES" ]]; then
while IFS=" " read -r storage_id storage_status; do
[[ -z "$storage_id" ]] && continue
local portal
portal=$(get_storage_config "$storage_id" | awk '$1 == "portal" {print $2}')
local portal_host="${portal%%:*}"
echo -n " $storage_id ($portal): "
if ping -c 1 -W 2 "$portal_host" >/dev/null 2>&1; then
echo -ne "${GN}$(translate "Reachable")${CL}"
local portal_port="${portal##*:}"
[[ "$portal_port" == "$portal" ]] && portal_port="3260"
if nc -z -w 2 "$portal_host" "$portal_port" 2>/dev/null; then
echo -e " | iSCSI port $portal_port: ${GN}$(translate "Open")${CL}"
else
echo -e " | iSCSI port $portal_port: ${RD}$(translate "Closed")${CL}"
fi
else
echo -e "${RD}$(translate "Unreachable")${CL}"
fi
if [[ "$storage_status" == "active" ]]; then
echo -e " $(translate "Proxmox status:") ${GN}$storage_status${CL}"
else
echo -e " $(translate "Proxmox status:") ${RD}$storage_status${CL}"
fi
# Show active iSCSI sessions for this target
local target
target=$(get_storage_config "$storage_id" | awk '$1 == "target" {print $2}')
if command -v iscsiadm >/dev/null 2>&1; then
local session
session=$(iscsiadm --mode session 2>/dev/null | grep "$target" || true)
if [[ -n "$session" ]]; then
echo -e " $(translate "Active session:") ${GN}$(translate "Connected")${CL}"
else
echo -e " $(translate "Active session:") ${YW}$(translate "No active session")${CL}"
fi
fi
echo ""
done <<< "$ISCSI_STORAGES"
else
echo " $(translate "No iSCSI storage configured.")"
fi
else
msg_warn "$(translate "pvesm not available.")"
fi
echo ""
msg_success "$(translate "Press Enter to continue...")"
read -r
}
# ==========================================================
# MAIN MENU
# ==========================================================
while true; do
CHOICE=$(dialog --backtitle "ProxMenux" \
--title "$(translate "iSCSI Host Manager - Proxmox Host")" \
--menu "$(translate "Choose an option:")" 18 70 6 \
"1" "$(translate "Add iSCSI Target as Proxmox Storage")" \
"2" "$(translate "View iSCSI Storages")" \
"3" "$(translate "Remove iSCSI Storage")" \
"4" "$(translate "Test iSCSI Connectivity")" \
"5" "$(translate "Exit")" \
3>&1 1>&2 2>&3)
RETVAL=$?
if [[ $RETVAL -ne 0 ]]; then
exit 0
fi
case $CHOICE in
1) add_iscsi_to_proxmox ;;
2) view_iscsi_storages ;;
3) remove_iscsi_storage ;;
4) test_iscsi_connectivity ;;
5) exit 0 ;;
*) exit 0 ;;
esac
done

File diff suppressed because it is too large Load Diff

View File

@@ -414,7 +414,7 @@ mount_nfs_share() {
# Add to fstab if permanent
if [[ "$PERMANENT_MOUNT" == "true" ]]; then
pct exec "$CTID" -- sed -i "\|$MOUNT_POINT|d" /etc/fstab
FSTAB_ENTRY="$NFS_PATH $MOUNT_POINT nfs $MOUNT_OPTIONS 0 0"
FSTAB_ENTRY="$NFS_PATH $MOUNT_POINT nfs ${MOUNT_OPTIONS},_netdev,x-systemd.automount,noauto 0 0"
pct exec "$CTID" -- bash -c "echo '$FSTAB_ENTRY' >> /etc/fstab"
msg_ok "$(translate "Added to /etc/fstab for permanent mounting.")"
fi

File diff suppressed because it is too large Load Diff

View File

@@ -643,7 +643,7 @@ configure_mount_options() {
1)
MOUNT_OPTIONS="rw,file_mode=0664,dir_mode=0775,iocharset=utf8"
;;
1)
2)
MOUNT_OPTIONS="ro,file_mode=0444,dir_mode=0555,iocharset=utf8"
;;
3)
@@ -792,7 +792,7 @@ mount_samba_share() {
pct exec "$CTID" -- sed -i "\|$MOUNT_POINT|d" /etc/fstab
FSTAB_ENTRY="$UNC_PATH $MOUNT_POINT cifs $FULL_OPTIONS 0 0"
FSTAB_ENTRY="$UNC_PATH $MOUNT_POINT cifs ${FULL_OPTIONS},_netdev,x-systemd.automount,noauto 0 0"
pct exec "$CTID" -- bash -c "echo '$FSTAB_ENTRY' >> /etc/fstab"
msg_ok "$(translate "Added to /etc/fstab for permanent mounting.")"
fi

File diff suppressed because it is too large Load Diff