2022-02-23 06:52:49 -05:00
#!/usr/bin/env bash
2023-02-07 12:15:22 -05:00
2025-01-01 13:37:29 +01:00
# Copyright (c) 2021-2025 tteck
2023-02-07 12:15:22 -05:00
# Author: tteck (tteckster)
2024-12-16 12:41:51 +01:00
# Co-Author: MickLesk
2025-06-20 13:28:06 +02:00
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
2023-02-07 12:15:22 -05:00
2023-05-15 19:15:26 -04:00
# This sets verbose mode if the global variable is set to "yes"
2024-01-09 19:44:38 -05:00
# if [ "$VERBOSE" == "yes" ]; then set -x; fi
2023-05-15 19:15:26 -04:00
2025-06-20 13:28:06 +02:00
if command -v curl >/dev/null 2>& 1; then
source <( curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
load_functions
#echo "(create-lxc.sh) Loaded core.func via curl"
elif command -v wget >/dev/null 2>& 1; then
source <( wget -qO- https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
load_functions
#echo "(create-lxc.sh) Loaded core.func via wget"
fi
2023-05-15 19:15:26 -04:00
# This sets error handling options and defines the error_handler function to handle errors
2023-02-03 11:41:12 -05:00
set -Eeuo pipefail
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
2025-07-04 21:22:50 +02:00
trap on_exit EXIT
trap on_interrupt INT
trap on_terminate TERM
function on_exit( ) {
local exit_code = " $? "
[ [ -n " ${ lockfile :- } " && -e " $lockfile " ] ] && rm -f " $lockfile "
exit " $exit_code "
}
2023-05-15 19:15:26 -04:00
2023-02-03 11:41:12 -05:00
function error_handler( ) {
local exit_code = " $? "
local line_number = " $1 "
local command = " $2 "
2025-07-04 21:22:50 +02:00
printf "\e[?25h"
echo -e " \n ${ RD } [ERROR] ${ CL } in line ${ RD } $line_number ${ CL } : exit code ${ RD } $exit_code ${ CL } : while executing command ${ YW } $command ${ CL } \n "
exit " $exit_code "
}
function on_interrupt( ) {
echo -e " \n ${ RD } Interrupted by user (SIGINT) ${ CL } "
exit 130
}
function on_terminate( ) {
echo -e " \n ${ RD } Terminated by signal (SIGTERM) ${ CL } "
exit 143
}
2025-07-28 11:36:14 +02:00
function exit_script( ) {
clear
printf "\e[?25h"
echo -e " \n ${ CROSS } ${ RD } User exited script ${ CL } \n "
kill 0
exit 1
}
2025-07-04 21:22:50 +02:00
function check_storage_support( ) {
local CONTENT = " $1 "
local -a VALID_STORAGES = ( )
while IFS = read -r line; do
2025-08-19 14:43:54 +02:00
local STORAGE_NAME
STORAGE_NAME = $( awk '{print $1}' <<< " $line " )
[ [ -z " $STORAGE_NAME " ] ] && continue
VALID_STORAGES += ( " $STORAGE_NAME " )
2025-07-04 21:22:50 +02:00
done < <( pvesm status -content " $CONTENT " 2>/dev/null | awk 'NR>1' )
[ [ ${# VALID_STORAGES [@] } -gt 0 ] ]
2023-02-03 11:41:12 -05:00
}
2023-05-15 19:15:26 -04:00
2025-07-28 11:36:14 +02:00
# This function selects a storage pool for a given content type (e.g., rootdir, vztmpl).
2022-02-23 06:52:49 -05:00
function select_storage( ) {
2025-07-04 21:22:50 +02:00
local CLASS = $1 CONTENT CONTENT_LABEL
2022-02-23 06:52:49 -05:00
case $CLASS in
2022-10-29 20:08:41 -04:00
container)
CONTENT = 'rootdir'
CONTENT_LABEL = 'Container'
; ;
template)
CONTENT = 'vztmpl'
CONTENT_LABEL = 'Container template'
; ;
2025-07-04 21:22:50 +02:00
iso)
CONTENT = 'iso'
CONTENT_LABEL = 'ISO image'
; ;
images)
CONTENT = 'images'
CONTENT_LABEL = 'VM Disk image'
; ;
backup)
CONTENT = 'backup'
CONTENT_LABEL = 'Backup'
; ;
snippets)
CONTENT = 'snippets'
CONTENT_LABEL = 'Snippets'
; ;
*)
msg_error " Invalid storage class ' $CLASS ' "
return 1
; ;
2022-02-23 06:52:49 -05:00
esac
2025-02-24 13:55:31 +01:00
2025-07-28 11:36:14 +02:00
# Check for preset STORAGE variable
if [ " $CONTENT " = "rootdir" ] && [ -n " ${ STORAGE :- } " ] ; then
if pvesm status -content " $CONTENT " | awk 'NR>1 {print $1}' | grep -qx " $STORAGE " ; then
STORAGE_RESULT = " $STORAGE "
msg_info " Using preset storage: $STORAGE_RESULT for $CONTENT_LABEL "
return 0
else
msg_error " Preset storage ' $STORAGE ' is not valid for content type ' $CONTENT '. "
return 2
fi
fi
2025-07-04 21:22:50 +02:00
local -A STORAGE_MAP
2025-07-28 11:36:14 +02:00
local -a MENU
2025-07-04 21:22:50 +02:00
local COL_WIDTH = 0
2025-07-04 21:43:17 +02:00
2025-07-04 21:22:50 +02:00
while read -r TAG TYPE _ TOTAL USED FREE _; do
[ [ -n " $TAG " && -n " $TYPE " ] ] || continue
2025-08-19 14:43:54 +02:00
local STORAGE_NAME = " $TAG "
local DISPLAY = " ${ STORAGE_NAME } ( ${ TYPE } ) "
2025-07-04 21:22:50 +02:00
local USED_FMT = $( numfmt --to= iec --from-unit= K --format %.1f <<< " $USED " )
local FREE_FMT = $( numfmt --to= iec --from-unit= K --format %.1f <<< " $FREE " )
local INFO = " Free: ${ FREE_FMT } B Used: ${ USED_FMT } B "
2025-08-19 14:43:54 +02:00
STORAGE_MAP[ " $DISPLAY " ] = " $STORAGE_NAME "
2025-07-04 21:22:50 +02:00
MENU += ( " $DISPLAY " " $INFO " "OFF" )
( ( ${# DISPLAY } > COL_WIDTH) ) && COL_WIDTH = ${# DISPLAY }
done < <( pvesm status -content " $CONTENT " | awk 'NR>1' )
2025-06-20 13:28:06 +02:00
2025-07-04 21:22:50 +02:00
if [ ${# MENU [@] } -eq 0 ] ; then
msg_error " No storage found for content type ' $CONTENT '. "
return 2
fi
2025-06-20 13:28:06 +02:00
2025-07-04 21:22:50 +02:00
if [ $(( ${# MENU [@] } / 3 )) -eq 1 ] ; then
STORAGE_RESULT = " ${ STORAGE_MAP [ ${ MENU [0] } ] } "
2025-07-28 11:36:14 +02:00
STORAGE_INFO = " ${ MENU [1] } "
2025-07-04 21:22:50 +02:00
return 0
fi
2025-06-20 13:28:06 +02:00
2025-07-04 21:22:50 +02:00
local WIDTH = $(( COL_WIDTH + 42 ))
while true; do
2025-07-28 11:36:14 +02:00
local DISPLAY_SELECTED
DISPLAY_SELECTED = $( whiptail --backtitle "Proxmox VE Helper Scripts" \
2025-07-04 21:43:17 +02:00
--title "Storage Pools" \
--radiolist " Which storage pool for ${ CONTENT_LABEL ,, } ?\n(Spacebar to select) " \
16 " $WIDTH " 6 " ${ MENU [@] } " 3>& 1 1>& 2 2>& 3)
2025-06-20 13:28:06 +02:00
2025-07-28 11:36:14 +02:00
# Cancel or ESC
[ [ $? -ne 0 ] ] && exit_script
# Strip trailing whitespace or newline (important for storages like "storage (dir)")
DISPLAY_SELECTED = $( sed 's/[[:space:]]*$//' <<< " $DISPLAY_SELECTED " )
2025-06-20 13:28:06 +02:00
2025-07-04 21:22:50 +02:00
if [ [ -z " $DISPLAY_SELECTED " || -z " ${ STORAGE_MAP [ $DISPLAY_SELECTED ]+_ } " ] ] ; then
whiptail --msgbox "No valid storage selected. Please try again." 8 58
continue
fi
STORAGE_RESULT = " ${ STORAGE_MAP [ $DISPLAY_SELECTED ] } "
2025-07-28 11:36:14 +02:00
for ( ( i = 0; i < ${# MENU [@] } ; i += 3) ) ; do
if [ [ " ${ MENU [ $i ] } " = = " $DISPLAY_SELECTED " ] ] ; then
STORAGE_INFO = " ${ MENU [ $i + 1] } "
break
fi
done
2025-07-04 21:22:50 +02:00
return 0
done
2022-02-23 06:52:49 -05:00
}
2025-07-04 21:43:17 +02:00
2023-05-15 19:15:26 -04:00
# Test if required variables are set
2025-02-24 13:55:31 +01:00
[ [ " ${ CTID :- } " ] ] || {
msg_error "You need to set 'CTID' variable."
exit 203
}
[ [ " ${ PCT_OSTYPE :- } " ] ] || {
msg_error "You need to set 'PCT_OSTYPE' variable."
exit 204
}
2022-02-23 06:52:49 -05:00
2023-05-15 19:15:26 -04:00
# Test if ID is valid
2025-02-24 13:55:31 +01:00
[ " $CTID " -ge "100" ] || {
msg_error "ID cannot be less than 100."
exit 205
}
2023-05-15 19:15:26 -04:00
# Test if ID is in use
2025-02-24 13:55:31 +01:00
if qm status " $CTID " & >/dev/null || pct status " $CTID " & >/dev/null; then
2022-05-21 07:11:10 -04:00
echo -e " ID ' $CTID ' is already in use. "
2022-02-23 06:52:49 -05:00
unset CTID
2025-02-05 16:29:07 +01:00
msg_error "Cannot use ID that is already in use."
exit 206
2022-02-23 06:52:49 -05:00
fi
2025-07-28 11:36:14 +02:00
# This checks for the presence of valid Container Storage and Template Storage locations
2025-08-19 14:43:54 +02:00
msg_info "Validating storage"
2025-07-28 11:36:14 +02:00
if ! check_storage_support "rootdir" ; then
2025-08-19 14:43:54 +02:00
msg_error "No valid storage found for 'rootdir' [Container]"
2025-07-28 11:36:14 +02:00
exit 1
fi
if ! check_storage_support "vztmpl" ; then
2025-08-19 14:43:54 +02:00
msg_error "No valid storage found for 'vztmpl' [Template]"
2025-07-28 11:36:14 +02:00
exit 1
fi
2022-02-23 06:52:49 -05:00
2025-08-25 14:19:58 +02:00
#msg_info "Checking template storage"
2025-07-04 21:22:50 +02:00
while true; do
if select_storage template; then
TEMPLATE_STORAGE = " $STORAGE_RESULT "
2025-07-28 11:36:14 +02:00
TEMPLATE_STORAGE_INFO = " $STORAGE_INFO "
2025-08-19 14:43:54 +02:00
msg_ok " Storage ${ BL } $TEMPLATE_STORAGE ${ CL } ( $TEMPLATE_STORAGE_INFO ) [Template] "
2025-07-04 21:22:50 +02:00
break
fi
done
while true; do
if select_storage container; then
CONTAINER_STORAGE = " $STORAGE_RESULT "
2025-07-28 11:36:14 +02:00
CONTAINER_STORAGE_INFO = " $STORAGE_INFO "
2025-08-19 14:43:54 +02:00
msg_ok " Storage ${ BL } $CONTAINER_STORAGE ${ CL } ( $CONTAINER_STORAGE_INFO ) [Container] "
2025-07-04 21:22:50 +02:00
break
fi
done
2025-07-04 21:47:23 +02:00
2025-06-20 13:28:06 +02:00
# Check free space on selected container storage
STORAGE_FREE = $( pvesm status | awk -v s = " $CONTAINER_STORAGE " '$1 == s { print $6 }' )
REQUIRED_KB = $(( ${ PCT_DISK_SIZE :- 8 } * 1024 * 1024 ))
if [ " $STORAGE_FREE " -lt " $REQUIRED_KB " ] ; then
msg_error " Not enough space on ' $CONTAINER_STORAGE '. Needed: ${ PCT_DISK_SIZE :- 8 } G. "
exit 214
fi
2025-08-19 14:43:54 +02:00
2025-06-19 18:50:23 +02:00
# Check Cluster Quorum if in Cluster
if [ -f /etc/pve/corosync.conf ] ; then
2025-08-19 14:43:54 +02:00
msg_info "Checking cluster quorum"
2025-06-19 18:50:23 +02:00
if ! pvecm status | awk -F':' '/^Quorate/ { exit ($2 ~ /Yes/) ? 0 : 1 }' ; then
2025-08-19 14:43:54 +02:00
2025-06-19 18:50:23 +02:00
msg_error "Cluster is not quorate. Start all nodes or configure quorum device (QDevice)."
exit 210
fi
msg_ok "Cluster is quorate"
fi
2023-05-15 19:15:26 -04:00
# Update LXC template list
2025-06-22 19:54:55 +02:00
TEMPLATE_SEARCH = " ${ PCT_OSTYPE } - ${ PCT_OSVERSION :- } "
2025-08-19 19:40:12 +02:00
case " $PCT_OSTYPE " in
2025-08-25 14:19:58 +02:00
debian | ubuntu)
TEMPLATE_PATTERN = "-standard_"
; ;
alpine | fedora | rocky | centos)
TEMPLATE_PATTERN = "-default_"
; ;
*)
TEMPLATE_PATTERN = ""
; ;
2025-08-19 19:40:12 +02:00
esac
2025-06-20 13:28:06 +02:00
2025-08-19 14:43:54 +02:00
# 1. Check local templates first
msg_info " Searching for template ' $TEMPLATE_SEARCH ' "
mapfile -t TEMPLATES < <(
2025-08-19 16:24:26 +02:00
pveam list " $TEMPLATE_STORAGE " |
2025-08-19 19:40:12 +02:00
awk -v s = " $TEMPLATE_SEARCH " -v p = " $TEMPLATE_PATTERN " '$1 ~ s && $1 ~ p {print $1}' |
2025-08-19 14:43:54 +02:00
sed 's/.*\///' | sort -t - -k 2 -V
)
2025-06-20 13:28:06 +02:00
2025-08-19 14:43:54 +02:00
if [ ${# TEMPLATES [@] } -gt 0 ] ; then
TEMPLATE_SOURCE = "local"
else
msg_info "No local template found, checking online repository"
pveam update >/dev/null 2>& 1
mapfile -t TEMPLATES < <(
2025-08-19 16:24:26 +02:00
pveam update >/dev/null 2>& 1 &&
pveam available -section system |
2025-08-19 19:40:12 +02:00
sed -n " s/.*\( $TEMPLATE_SEARCH .* $TEMPLATE_PATTERN .*\)/\1/p " |
2025-08-25 14:19:58 +02:00
sort -t - -k 2 -V
2025-08-19 14:43:54 +02:00
)
TEMPLATE_SOURCE = "online"
2025-06-20 13:28:06 +02:00
fi
2022-02-23 06:52:49 -05:00
TEMPLATE = " ${ TEMPLATES [-1] } "
2025-08-19 14:43:54 +02:00
TEMPLATE_PATH = " $( pvesm path $TEMPLATE_STORAGE :vztmpl/$TEMPLATE 2>/dev/null ||
echo " /var/lib/vz/template/cache/ $TEMPLATE " ) "
msg_ok " Template ${ BL } $TEMPLATE ${ CL } [ $TEMPLATE_SOURCE ] "
2025-06-20 13:28:06 +02:00
2025-08-19 14:43:54 +02:00
# 4. Validate template (exists & not corrupted)
2025-07-28 11:36:14 +02:00
TEMPLATE_VALID = 1
2025-08-19 14:43:54 +02:00
if [ ! -s " $TEMPLATE_PATH " ] ; then
2025-07-28 11:36:14 +02:00
TEMPLATE_VALID = 0
elif ! tar --use-compress-program= zstdcat -tf " $TEMPLATE_PATH " >/dev/null 2>& 1; then
TEMPLATE_VALID = 0
fi
2025-02-24 13:55:31 +01:00
2025-07-28 11:36:14 +02:00
if [ " $TEMPLATE_VALID " -eq 0 ] ; then
2025-08-19 14:43:54 +02:00
msg_warn " Template $TEMPLATE is missing or corrupted. Re-downloading. "
2025-06-20 13:28:06 +02:00
[ [ -f " $TEMPLATE_PATH " ] ] && rm -f " $TEMPLATE_PATH "
2025-02-24 13:55:31 +01:00
for attempt in { 1..3} ; do
msg_info " Attempt $attempt : Downloading LXC template... "
2025-07-10 11:17:01 +02:00
if pveam download " $TEMPLATE_STORAGE " " $TEMPLATE " >/dev/null 2>& 1; then
2025-02-24 13:55:31 +01:00
msg_ok "Template download successful."
break
fi
if [ $attempt -eq 3 ] ; then
2025-07-28 11:36:14 +02:00
msg_error " Failed after 3 attempts. Please check network access or manually run:\n pveam download $TEMPLATE_STORAGE $TEMPLATE "
2025-02-24 13:55:31 +01:00
exit 208
fi
sleep $(( attempt * 5 ))
done
2022-02-23 06:52:49 -05:00
fi
2025-07-04 21:22:50 +02:00
msg_info "Creating LXC Container"
2025-02-07 15:42:40 +01:00
# Check and fix subuid/subgid
2025-02-24 13:55:31 +01:00
grep -q "root:100000:65536" /etc/subuid || echo "root:100000:65536" >>/etc/subuid
grep -q "root:100000:65536" /etc/subgid || echo "root:100000:65536" >>/etc/subgid
2022-10-29 20:08:41 -04:00
2025-02-07 15:42:40 +01:00
# Combine all options
2022-10-29 20:08:41 -04:00
PCT_OPTIONS = ( ${ PCT_OPTIONS [@] :- ${ DEFAULT_PCT_OPTIONS [@] } } )
2025-02-07 15:42:40 +01:00
[ [ " ${ PCT_OPTIONS [@] } " = ~ " -rootfs " ] ] || PCT_OPTIONS += ( -rootfs " $CONTAINER_STORAGE : ${ PCT_DISK_SIZE :- 8 } " )
2022-02-23 06:52:49 -05:00
2025-06-20 13:28:06 +02:00
# Secure creation of the LXC container with lock and template check
lockfile = " /tmp/template. ${ TEMPLATE } .lock "
2025-07-28 11:36:14 +02:00
exec 9>" $lockfile " || {
2025-07-04 21:22:50 +02:00
msg_error " Failed to create lock file ' $lockfile '. "
exit 200
}
2025-06-20 13:28:06 +02:00
flock -w 60 9 || {
msg_error "Timeout while waiting for template lock"
exit 211
}
2025-07-04 21:22:50 +02:00
2025-02-24 13:55:31 +01:00
if ! pct create " $CTID " " ${ TEMPLATE_STORAGE } :vztmpl/ ${ TEMPLATE } " " ${ PCT_OPTIONS [@] } " & >/dev/null; then
2025-06-20 13:28:06 +02:00
msg_error "Container creation failed. Checking if template is corrupted or incomplete."
2025-02-24 13:55:31 +01:00
2025-06-20 13:28:06 +02:00
if [ [ ! -s " $TEMPLATE_PATH " || " $( stat -c%s " $TEMPLATE_PATH " ) " -lt 1000000 ] ] ; then
msg_error "Template file too small or missing – re-downloading."
rm -f " $TEMPLATE_PATH "
elif ! zstdcat " $TEMPLATE_PATH " | tar -tf - & >/dev/null; then
msg_error "Template appears to be corrupted – re-downloading."
2025-02-24 13:55:31 +01:00
rm -f " $TEMPLATE_PATH "
2025-06-20 13:28:06 +02:00
else
2025-10-20 09:01:51 -07:00
msg_error "Template is valid, but container creation failed. Update your whole Proxmox System (pve-container) first or check https://github.com/community-scripts/ProxmoxVE/discussions/8126"
2025-06-20 13:28:06 +02:00
exit 209
fi
2025-02-24 13:55:31 +01:00
2025-06-20 13:28:06 +02:00
# Retry download
for attempt in { 1..3} ; do
msg_info " Attempt $attempt : Re-downloading template... "
if timeout 120 pveam download " $TEMPLATE_STORAGE " " $TEMPLATE " >/dev/null; then
msg_ok "Template re-download successful."
break
fi
if [ " $attempt " -eq 3 ] ; then
msg_error "Three failed attempts. Aborting."
2025-02-24 13:55:31 +01:00
exit 208
fi
2025-06-20 13:28:06 +02:00
sleep $(( attempt * 5 ))
done
2025-02-24 13:55:31 +01:00
2025-06-20 13:28:06 +02:00
sleep 1 # I/O-Sync-Delay
msg_ok "Re-downloaded LXC Template"
2025-07-04 21:22:50 +02:00
fi
2025-06-20 13:28:06 +02:00
2025-07-04 21:22:50 +02:00
if ! pct list | awk '{print $1}' | grep -qx " $CTID " ; then
msg_error " Container ID $CTID not listed in 'pct list' – unexpected failure. "
exit 215
2025-02-24 13:55:31 +01:00
fi
2025-06-20 13:28:06 +02:00
2025-07-04 21:22:50 +02:00
if ! grep -q '^rootfs:' " /etc/pve/lxc/ $CTID .conf " ; then
msg_error "RootFS entry missing in container config – storage not correctly assigned."
exit 216
fi
if grep -q '^hostname:' " /etc/pve/lxc/ $CTID .conf " ; then
CT_HOSTNAME = $( grep '^hostname:' " /etc/pve/lxc/ $CTID .conf " | awk '{print $2}' )
if [ [ ! " $CT_HOSTNAME " = ~ ^[ a-z0-9-] +$ ] ] ; then
msg_warn " Hostname ' $CT_HOSTNAME ' contains invalid characters – may cause issues with networking or DNS. "
fi
2025-06-20 13:28:06 +02:00
fi
2025-04-15 15:20:46 +02:00
msg_ok " LXC Container ${ BL } $CTID ${ CL } ${ GN } was successfully created. "