mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-07-18 11:55:06 +02:00
pve-tool: storage share helper script for Proxmox
This script provides a menu-driven interface for managing various storage types in Proxmox, including SMB, NFS, and iSCSI. It includes functions for testing mounts, adding storage, and managing LXC mountpoints.
This commit is contained in:
committed by
GitHub
parent
c24bba00de
commit
829704e09e
@@ -0,0 +1,517 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
|
||||
|
||||
set -eEuo pipefail
|
||||
|
||||
function header_info() {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
_____ __ ___ _________
|
||||
/ ___// /_____ _________ _____ ____ / | / _/ __ \
|
||||
\__ \/ __/ __ \/ ___/ __ `/ __ `/ _ \ / /| | / // / / /
|
||||
___/ / /_/ /_/ / / / /_/ / /_/ / __/ / ___ |_/ // /_/ /
|
||||
/____/\__/\____/_/ \__,_/\__, /\___/ /_/ |_/___/_____/
|
||||
/____/
|
||||
|
||||
Proxmox Storage Allrounder
|
||||
SMB | NFS | iSCSI | LVM-on-iSCSI | LXC Mountpoints | Host Shares
|
||||
EOF
|
||||
}
|
||||
|
||||
YW="\033[33m"
|
||||
BL="\033[36m"
|
||||
GN="\033[1;92m"
|
||||
RD="\033[01;31m"
|
||||
CL="\033[m"
|
||||
|
||||
msg_info() { echo -e "${BL}[INFO]${CL} ${YW}$1${CL}"; }
|
||||
msg_ok() { echo -e "${GN}[OK]${CL} $1"; }
|
||||
msg_warn() { echo -e "${YW}[WARN]${CL} $1"; }
|
||||
msg_error() { echo -e "${RD}[ERROR]${CL} $1"; }
|
||||
|
||||
pause() {
|
||||
read -r -p "Press Enter to continue..." _
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
msg_error "Run this script as root."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_pve() {
|
||||
if ! command -v pct >/dev/null 2>&1 || ! command -v pvesm >/dev/null 2>&1; then
|
||||
msg_error "This script must run on a Proxmox VE host (pct/pvesm missing)."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_packages() {
|
||||
local packages=()
|
||||
|
||||
command -v whiptail >/dev/null 2>&1 || packages+=("whiptail")
|
||||
command -v mount.cifs >/dev/null 2>&1 || packages+=("cifs-utils")
|
||||
command -v showmount >/dev/null 2>&1 || packages+=("nfs-common")
|
||||
command -v iscsiadm >/dev/null 2>&1 || packages+=("open-iscsi")
|
||||
|
||||
if [[ ${#packages[@]} -gt 0 ]]; then
|
||||
msg_info "Installing required packages: ${packages[*]}"
|
||||
apt update >/dev/null 2>&1
|
||||
apt install -y "${packages[@]}" >/dev/null 2>&1
|
||||
msg_ok "Dependencies installed"
|
||||
fi
|
||||
}
|
||||
|
||||
confirm_start() {
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Share Allrounder" \
|
||||
--yesno "This AIO wizard can test and configure SMB/NFS/iSCSI, create/remove Proxmox storages, manage LXC mountpoints and optionally create host shares. Proceed?" 12 96
|
||||
}
|
||||
|
||||
read_input() {
|
||||
local title="$1"
|
||||
local prompt="$2"
|
||||
local default_value="${3:-}"
|
||||
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" \
|
||||
--inputbox "$prompt" 11 84 "$default_value" 3>&1 1>&2 2>&3
|
||||
}
|
||||
|
||||
read_password() {
|
||||
local title="$1"
|
||||
local prompt="$2"
|
||||
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" \
|
||||
--passwordbox "$prompt" 11 84 3>&1 1>&2 2>&3
|
||||
}
|
||||
|
||||
confirm_yes_no() {
|
||||
local title="$1"
|
||||
local prompt="$2"
|
||||
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" \
|
||||
--yesno "$prompt" 11 84
|
||||
}
|
||||
|
||||
manual_smb_test() {
|
||||
header_info
|
||||
local server share username password domain vers mount_dir mount_opts
|
||||
|
||||
server=$(read_input "SMB Test" "SMB server/IP (e.g. 10.0.1.9)") || return
|
||||
share=$(read_input "SMB Test" "Share name (without leading //)" "Proxmox") || return
|
||||
username=$(read_input "SMB Test" "Username" "proxmox") || return
|
||||
password=$(read_password "SMB Test" "Password for ${username}") || return
|
||||
domain=$(read_input "SMB Test" "Domain/Workgroup (optional, leave empty if not needed)") || return
|
||||
vers=$(read_input "SMB Test" "SMB version (e.g. 3.0, 3.1.1)" "3.1.1") || return
|
||||
mount_dir="/mnt/test-smb"
|
||||
|
||||
mkdir -p "$mount_dir"
|
||||
|
||||
mount_opts="username=${username},password=${password},vers=${vers},sec=ntlmssp"
|
||||
if [[ -n "$domain" ]]; then
|
||||
mount_opts+=";domain=${domain}"
|
||||
fi
|
||||
|
||||
msg_info "Testing SMB mount on ${mount_dir}"
|
||||
if mount -t cifs "//${server}/${share}" "$mount_dir" -o "${mount_opts//;/,}" >/dev/null 2>&1; then
|
||||
touch "${mount_dir}/smb-test-$(date +%s)" >/dev/null 2>&1 || true
|
||||
umount "$mount_dir" >/dev/null 2>&1 || true
|
||||
msg_ok "SMB test successful"
|
||||
else
|
||||
msg_error "SMB test failed. Check network/firewall/credentials/share permissions."
|
||||
fi
|
||||
|
||||
pause
|
||||
}
|
||||
|
||||
manual_nfs_test() {
|
||||
header_info
|
||||
local server export_path mount_dir
|
||||
|
||||
server=$(read_input "NFS Test" "NFS server/IP (e.g. 10.0.6.159)") || return
|
||||
export_path=$(read_input "NFS Test" "NFS export path (e.g. /srv/proxmox-nfs)") || return
|
||||
mount_dir="/mnt/test-nfs"
|
||||
|
||||
mkdir -p "$mount_dir"
|
||||
|
||||
msg_info "Testing NFS mount on ${mount_dir}"
|
||||
if mount -t nfs "${server}:${export_path}" "$mount_dir" >/dev/null 2>&1; then
|
||||
touch "${mount_dir}/nfs-test-$(date +%s)" >/dev/null 2>&1 || true
|
||||
umount "$mount_dir" >/dev/null 2>&1 || true
|
||||
msg_ok "NFS test successful"
|
||||
else
|
||||
msg_error "NFS test failed. Check export/firewall/network permissions."
|
||||
fi
|
||||
|
||||
pause
|
||||
}
|
||||
|
||||
manual_iscsi_discovery() {
|
||||
header_info
|
||||
local portal
|
||||
|
||||
portal=$(read_input "iSCSI Discovery" "iSCSI portal IP/FQDN (e.g. 10.0.1.20)") || return
|
||||
msg_info "Running iSCSI target discovery on ${portal}"
|
||||
if iscsiadm -m discovery -t sendtargets -p "$portal"; then
|
||||
msg_ok "iSCSI discovery completed"
|
||||
else
|
||||
msg_error "iSCSI discovery failed"
|
||||
fi
|
||||
|
||||
pause
|
||||
}
|
||||
|
||||
add_smb_storage() {
|
||||
header_info
|
||||
local storage_id server share username password content nodes options
|
||||
|
||||
storage_id=$(read_input "Add SMB/CIFS Storage" "Storage ID (unique, e.g. smb-media)") || return
|
||||
server=$(read_input "Add SMB/CIFS Storage" "SMB server/IP") || return
|
||||
share=$(read_input "Add SMB/CIFS Storage" "Share name (without //)") || return
|
||||
username=$(read_input "Add SMB/CIFS Storage" "Username") || return
|
||||
password=$(read_password "Add SMB/CIFS Storage" "Password for ${username}") || return
|
||||
content=$(read_input "Add SMB/CIFS Storage" "Content types (comma separated)" "backup,iso,vztmpl,snippets") || return
|
||||
nodes=$(read_input "Add SMB/CIFS Storage" "Nodes (optional, comma separated)") || return
|
||||
options=$(read_input "Add SMB/CIFS Storage" "Mount options (optional, e.g. vers=3.1.1,domain=WORKGROUP)") || return
|
||||
|
||||
local cmd=(pvesm add cifs "$storage_id" --server "$server" --share "$share" --username "$username" --password "$password" --content "$content")
|
||||
[[ -n "$nodes" ]] && cmd+=(--nodes "$nodes")
|
||||
[[ -n "$options" ]] && cmd+=(--options "$options")
|
||||
|
||||
if "${cmd[@]}" >/dev/null 2>&1; then
|
||||
msg_ok "SMB storage '${storage_id}' added"
|
||||
else
|
||||
msg_error "Failed to add SMB storage '${storage_id}'"
|
||||
fi
|
||||
|
||||
pause
|
||||
}
|
||||
|
||||
add_nfs_storage() {
|
||||
header_info
|
||||
local storage_id server export_path content nodes options
|
||||
|
||||
storage_id=$(read_input "Add NFS Storage" "Storage ID (unique, e.g. nfs-vmdata)") || return
|
||||
server=$(read_input "Add NFS Storage" "NFS server/IP") || return
|
||||
export_path=$(read_input "Add NFS Storage" "Export path") || return
|
||||
content=$(read_input "Add NFS Storage" "Content types (comma separated)" "images,rootdir") || return
|
||||
nodes=$(read_input "Add NFS Storage" "Nodes (optional, comma separated)") || return
|
||||
options=$(read_input "Add NFS Storage" "Mount options (optional)") || return
|
||||
|
||||
local cmd=(pvesm add nfs "$storage_id" --server "$server" --export "$export_path" --content "$content")
|
||||
[[ -n "$nodes" ]] && cmd+=(--nodes "$nodes")
|
||||
[[ -n "$options" ]] && cmd+=(--options "$options")
|
||||
|
||||
if "${cmd[@]}" >/dev/null 2>&1; then
|
||||
msg_ok "NFS storage '${storage_id}' added"
|
||||
else
|
||||
msg_error "Failed to add NFS storage '${storage_id}'"
|
||||
fi
|
||||
|
||||
pause
|
||||
}
|
||||
|
||||
add_iscsi_storage() {
|
||||
header_info
|
||||
local storage_id portal target nodes
|
||||
|
||||
storage_id=$(read_input "Add iSCSI Storage" "Storage ID (unique, e.g. iscsi-synology)") || return
|
||||
portal=$(read_input "Add iSCSI Storage" "Portal IP/FQDN") || return
|
||||
target=$(read_input "Add iSCSI Storage" "Target IQN (e.g. iqn.2000-01.com.synology:...)") || return
|
||||
nodes=$(read_input "Add iSCSI Storage" "Nodes (optional, comma separated)") || return
|
||||
|
||||
local cmd=(pvesm add iscsi "$storage_id" --portal "$portal" --target "$target")
|
||||
[[ -n "$nodes" ]] && cmd+=(--nodes "$nodes")
|
||||
|
||||
if "${cmd[@]}" >/dev/null 2>&1; then
|
||||
msg_ok "iSCSI storage '${storage_id}' added"
|
||||
else
|
||||
msg_error "Failed to add iSCSI storage '${storage_id}'"
|
||||
fi
|
||||
|
||||
pause
|
||||
}
|
||||
|
||||
add_lvm_on_base_storage() {
|
||||
header_info
|
||||
local storage_id base_storage vgname content shared
|
||||
|
||||
storage_id=$(read_input "Add LVM Storage" "LVM Storage ID (unique, e.g. lvm-iscsi01)") || return
|
||||
base_storage=$(read_input "Add LVM Storage" "Base storage ID (usually iSCSI storage ID)") || return
|
||||
vgname=$(read_input "Add LVM Storage" "Volume Group name on target") || return
|
||||
content=$(read_input "Add LVM Storage" "Content types (comma separated)" "images,rootdir") || return
|
||||
shared=$(read_input "Add LVM Storage" "Shared across nodes? 1=yes, 0=no" "1") || return
|
||||
|
||||
local cmd=(pvesm add lvm "$storage_id" --base "$base_storage" --vgname "$vgname" --content "$content" --shared "$shared")
|
||||
if "${cmd[@]}" >/dev/null 2>&1; then
|
||||
msg_ok "LVM storage '${storage_id}' added"
|
||||
else
|
||||
msg_error "Failed to add LVM storage '${storage_id}'"
|
||||
fi
|
||||
|
||||
pause
|
||||
}
|
||||
|
||||
remove_storage() {
|
||||
header_info
|
||||
local storage_id
|
||||
|
||||
storage_id=$(read_input "Remove Storage" "Storage ID to remove") || return
|
||||
confirm_yes_no "Remove Storage" "Really remove storage '${storage_id}' from Proxmox config?" || return
|
||||
|
||||
if pvesm remove "$storage_id" >/dev/null 2>&1; then
|
||||
msg_ok "Storage '${storage_id}' removed"
|
||||
else
|
||||
msg_error "Failed to remove storage '${storage_id}'"
|
||||
fi
|
||||
|
||||
pause
|
||||
}
|
||||
|
||||
find_next_mp_slot() {
|
||||
local ctid="$1"
|
||||
local used
|
||||
|
||||
used=$(pct config "$ctid" | awk -F: '/^mp[0-9]+:/ {gsub("mp", "", $1); print $1}')
|
||||
for i in $(seq 0 255); do
|
||||
if ! grep -qx "$i" <<<"$used"; then
|
||||
echo "$i"
|
||||
return
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
add_lxc_mountpoint() {
|
||||
header_info
|
||||
local ctid host_path ct_path mp_slot
|
||||
|
||||
ctid=$(read_input "LXC Mountpoint" "Container ID (CTID)") || return
|
||||
host_path=$(read_input "LXC Mountpoint" "Host path (must exist, e.g. /mnt/pve/smb-media)") || return
|
||||
ct_path=$(read_input "LXC Mountpoint" "Container path (e.g. /mnt/media)") || return
|
||||
|
||||
if ! pct config "$ctid" >/dev/null 2>&1; then
|
||||
msg_error "Container ${ctid} not found"
|
||||
pause
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ ! -d "$host_path" ]]; then
|
||||
msg_error "Host path does not exist: ${host_path}"
|
||||
pause
|
||||
return
|
||||
fi
|
||||
|
||||
mp_slot=$(find_next_mp_slot "$ctid")
|
||||
if [[ -z "$mp_slot" ]]; then
|
||||
msg_error "No free mp slot available for container ${ctid}"
|
||||
pause
|
||||
return
|
||||
fi
|
||||
|
||||
if pct set "$ctid" -mp"$mp_slot" "$host_path",mp="$ct_path" >/dev/null 2>&1; then
|
||||
msg_ok "Added mp${mp_slot}: ${host_path} -> ${ct_path} on CT ${ctid}"
|
||||
msg_warn "If CT is unprivileged, ensure UID/GID mapping and filesystem permissions fit your workload."
|
||||
else
|
||||
msg_error "Failed to add mountpoint to CT ${ctid}"
|
||||
fi
|
||||
|
||||
pause
|
||||
}
|
||||
|
||||
remove_lxc_mountpoint() {
|
||||
header_info
|
||||
local ctid mp_key
|
||||
|
||||
ctid=$(read_input "Remove LXC Mountpoint" "Container ID (CTID)") || return
|
||||
mp_key=$(read_input "Remove LXC Mountpoint" "Mountpoint key (e.g. mp0, mp1)") || return
|
||||
|
||||
if ! pct config "$ctid" >/dev/null 2>&1; then
|
||||
msg_error "Container ${ctid} not found"
|
||||
pause
|
||||
return
|
||||
fi
|
||||
|
||||
if pct set "$ctid" -delete "$mp_key" >/dev/null 2>&1; then
|
||||
msg_ok "Removed ${mp_key} from CT ${ctid}"
|
||||
else
|
||||
msg_error "Failed to remove ${mp_key} from CT ${ctid}"
|
||||
fi
|
||||
|
||||
pause
|
||||
}
|
||||
|
||||
list_lxc_mountpoints() {
|
||||
header_info
|
||||
local ctid
|
||||
|
||||
ctid=$(read_input "List LXC Mountpoints" "Container ID (CTID)") || return
|
||||
if ! pct config "$ctid" >/dev/null 2>&1; then
|
||||
msg_error "Container ${ctid} not found"
|
||||
pause
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "${BL}Mountpoints for CT ${ctid}${CL}\n"
|
||||
pct config "$ctid" | awk '/^mp[0-9]+:/{print}'
|
||||
echo
|
||||
pause
|
||||
}
|
||||
|
||||
host_create_samba_share() {
|
||||
header_info
|
||||
local share_name share_path user_name user_pass
|
||||
|
||||
share_name=$(read_input "Host Samba Share" "Share name (e.g. data)") || return
|
||||
share_path=$(read_input "Host Samba Share" "Share path on host (e.g. /srv/samba/data)" "/srv/samba/${share_name}") || return
|
||||
user_name=$(read_input "Host Samba Share" "Linux/Samba username") || return
|
||||
user_pass=$(read_password "Host Samba Share" "Password for ${user_name}") || return
|
||||
|
||||
msg_info "Installing Samba on host if needed"
|
||||
apt update >/dev/null 2>&1
|
||||
apt install -y samba >/dev/null 2>&1
|
||||
|
||||
mkdir -p "$share_path"
|
||||
getent group sambashare >/dev/null 2>&1 || groupadd sambashare
|
||||
id "$user_name" >/dev/null 2>&1 || useradd -M -s /usr/sbin/nologin -G sambashare "$user_name"
|
||||
usermod -aG sambashare "$user_name"
|
||||
chown -R root:sambashare "$share_path"
|
||||
chmod 2775 "$share_path"
|
||||
|
||||
if ! (
|
||||
echo "$user_pass"
|
||||
echo "$user_pass"
|
||||
) | smbpasswd -s -a "$user_name" >/dev/null 2>&1; then
|
||||
msg_error "Failed to set Samba password for ${user_name}"
|
||||
pause
|
||||
return
|
||||
fi
|
||||
|
||||
if ! grep -q "^\[${share_name}\]" /etc/samba/smb.conf; then
|
||||
cat <<EOF >>/etc/samba/smb.conf
|
||||
|
||||
[${share_name}]
|
||||
comment = Proxmox Host Share (${share_name})
|
||||
path = ${share_path}
|
||||
browseable = yes
|
||||
read only = no
|
||||
guest ok = no
|
||||
create mask = 0664
|
||||
directory mask = 2775
|
||||
valid users = @sambashare
|
||||
EOF
|
||||
fi
|
||||
|
||||
testparm -s >/dev/null 2>&1 || {
|
||||
msg_error "Samba config validation failed (testparm). Check /etc/samba/smb.conf"
|
||||
pause
|
||||
return
|
||||
}
|
||||
|
||||
systemctl enable --now smbd nmbd >/dev/null 2>&1
|
||||
systemctl restart smbd nmbd >/dev/null 2>&1
|
||||
|
||||
msg_ok "Host SMB share created: //$(hostname -I | awk '{print $1}')/${share_name}"
|
||||
msg_warn "Best practice: prefer running Samba in a dedicated LXC/VM for cleaner host separation."
|
||||
|
||||
pause
|
||||
}
|
||||
|
||||
host_create_nfs_export() {
|
||||
header_info
|
||||
local export_path subnet options
|
||||
|
||||
export_path=$(read_input "Host NFS Export" "Export path on host (e.g. /srv/proxmox-nfs)" "/srv/proxmox-nfs") || return
|
||||
subnet=$(read_input "Host NFS Export" "Allowed subnet/CIDR (e.g. 10.0.0.0/16)") || return
|
||||
options=$(read_input "Host NFS Export" "Export options" "rw,sync,no_subtree_check,no_root_squash") || return
|
||||
|
||||
msg_info "Installing NFS server on host if needed"
|
||||
apt update >/dev/null 2>&1
|
||||
apt install -y nfs-kernel-server >/dev/null 2>&1
|
||||
|
||||
mkdir -p "$export_path"
|
||||
chmod 0770 "$export_path"
|
||||
|
||||
if ! grep -qE "^${export_path//\//\/}[[:space:]]+${subnet//\//\/}\(" /etc/exports; then
|
||||
echo "${export_path} ${subnet}(${options})" >>/etc/exports
|
||||
fi
|
||||
|
||||
exportfs -ra >/dev/null 2>&1
|
||||
systemctl enable --now nfs-kernel-server >/dev/null 2>&1
|
||||
|
||||
msg_ok "Host NFS export created: ${export_path} ${subnet}(${options})"
|
||||
msg_warn "Best practice: use host exports carefully; dedicated storage VM/LXC is often cleaner."
|
||||
pause
|
||||
}
|
||||
|
||||
show_status() {
|
||||
header_info
|
||||
echo -e "${BL}pvesm status${CL}"
|
||||
pvesm status || true
|
||||
echo
|
||||
echo -e "${BL}Mounted /mnt/pve paths${CL}"
|
||||
mount | grep /mnt/pve || true
|
||||
echo
|
||||
echo -e "${BL}Mounted CIFS/NFS/iSCSI related paths${CL}"
|
||||
mount | grep -E ' type (cifs|nfs|nfs4)' || true
|
||||
echo
|
||||
echo -e "${BL}iSCSI sessions${CL}"
|
||||
iscsiadm -m session 2>/dev/null || true
|
||||
echo
|
||||
pause
|
||||
}
|
||||
|
||||
main_menu() {
|
||||
while true; do
|
||||
local choice
|
||||
choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Share Allrounder" \
|
||||
--menu "Select action:" 30 110 20 \
|
||||
"1" "SMB: manual mount test" \
|
||||
"2" "NFS: manual mount test" \
|
||||
"3" "iSCSI: discovery test" \
|
||||
"4" "Proxmox: add SMB/CIFS storage" \
|
||||
"5" "Proxmox: add NFS storage" \
|
||||
"6" "Proxmox: add iSCSI storage" \
|
||||
"7" "Proxmox: add LVM on base storage (e.g. iSCSI)" \
|
||||
"8" "Proxmox: remove storage definition" \
|
||||
"9" "LXC: add bind mountpoint (pct set -mpX)" \
|
||||
"10" "LXC: remove mountpoint (pct set -delete mpX)" \
|
||||
"11" "LXC: list mountpoints" \
|
||||
"12" "Host: install Samba + create SMB share" \
|
||||
"13" "Host: install NFS server + create export" \
|
||||
"14" "Show storage/mount/iSCSI status" \
|
||||
"0" "Exit" 3>&1 1>&2 2>&3) || break
|
||||
|
||||
case "$choice" in
|
||||
1) manual_smb_test ;;
|
||||
2) manual_nfs_test ;;
|
||||
3) manual_iscsi_discovery ;;
|
||||
4) add_smb_storage ;;
|
||||
5) add_nfs_storage ;;
|
||||
6) add_iscsi_storage ;;
|
||||
7) add_lvm_on_base_storage ;;
|
||||
8) remove_storage ;;
|
||||
9) add_lxc_mountpoint ;;
|
||||
10) remove_lxc_mountpoint ;;
|
||||
11) list_lxc_mountpoints ;;
|
||||
12) host_create_samba_share ;;
|
||||
13) host_create_nfs_export ;;
|
||||
14) show_status ;;
|
||||
0) break ;;
|
||||
*) ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
header_info
|
||||
require_root
|
||||
require_pve
|
||||
ensure_packages
|
||||
confirm_start || exit 0
|
||||
main_menu
|
||||
|
||||
header_info
|
||||
msg_ok "Finished."
|
||||
Reference in New Issue
Block a user