cleanup
This commit is contained in:
parent
ebc1ce2c88
commit
3dc76ce5d2
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,355 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==============================================================================
|
||||
# TEST SUITE FOR tools.func
|
||||
# ==============================================================================
|
||||
# This script tests all setup_* functions from tools.func
|
||||
# Can be run standalone in any Debian-based system
|
||||
#
|
||||
# Usage:
|
||||
# bash <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/test-tools-func.sh)
|
||||
# ==============================================================================
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Counters
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
TESTS_SKIPPED=0
|
||||
|
||||
# Log file
|
||||
TEST_LOG="/tmp/tools-func-test-$(date +%Y%m%d-%H%M%S).log"
|
||||
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} TOOLS.FUNC TEST SUITE${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "Log file: ${TEST_LOG}\n"
|
||||
|
||||
# Source tools.func from repository
|
||||
echo -e "${BLUE}► Sourcing tools.func from repository...${NC}"
|
||||
if ! source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func); then
|
||||
echo -e "${RED}✖ Failed to source tools.func${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}✔ tools.func loaded${NC}\n"
|
||||
|
||||
# Source core functions if available
|
||||
if curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func &>/dev/null; then
|
||||
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) || true
|
||||
fi
|
||||
|
||||
# Override STD to show all output for debugging
|
||||
export STD=''
|
||||
|
||||
# Force non-interactive mode for all apt operations
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Update PATH to include common installation directories
|
||||
export PATH="/usr/local/bin:/usr/local/go/bin:/root/.cargo/bin:/root/.rbenv/bin:/root/.rbenv/shims:/opt/java/bin:$PATH"
|
||||
|
||||
# Helper functions (override if needed from core.func)
|
||||
msg_info() { echo -e "${BLUE}ℹ ${1}${CL:-${NC}}"; }
|
||||
msg_ok() { echo -e "${GREEN}✔ ${1}${CL:-${NC}}"; }
|
||||
msg_error() { echo -e "${RED}✖ ${1}${CL:-${NC}}"; }
|
||||
msg_warn() { echo -e "${YELLOW}⚠ ${1}${CL:-${NC}}"; }
|
||||
|
||||
# Color definitions if not already set
|
||||
GN="${GN:-${GREEN}}"
|
||||
BL="${BL:-${BLUE}}"
|
||||
RD="${RD:-${RED}}"
|
||||
YW="${YW:-${YELLOW}}"
|
||||
CL="${CL:-${NC}}"
|
||||
|
||||
# Reload environment helper
|
||||
reload_path() {
|
||||
export PATH="/usr/local/bin:/usr/local/go/bin:/root/.cargo/bin:/root/.rbenv/bin:/root/.rbenv/shims:/opt/java/bin:$PATH"
|
||||
# Source profile files if they exist
|
||||
[ -f "/root/.bashrc" ] && source /root/.bashrc 2>/dev/null || true
|
||||
[ -f "/root/.profile" ] && source /root/.profile 2>/dev/null || true
|
||||
[ -f "/root/.cargo/env" ] && source /root/.cargo/env 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Clean up before test to avoid interactive prompts and locks
|
||||
cleanup_before_test() {
|
||||
# Kill any hanging apt processes
|
||||
killall apt-get apt 2>/dev/null || true
|
||||
|
||||
# Remove apt locks
|
||||
rm -f /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/cache/apt/archives/lock 2>/dev/null || true
|
||||
|
||||
# Clean up broken repository files from previous tests
|
||||
# Remove all custom sources files
|
||||
rm -f /etc/apt/sources.list.d/*.sources 2>/dev/null || true
|
||||
rm -f /etc/apt/sources.list.d/*.list 2>/dev/null || true
|
||||
|
||||
# Remove all keyrings
|
||||
rm -f /etc/apt/keyrings/*.gpg 2>/dev/null || true
|
||||
rm -f /etc/apt/keyrings/*.asc 2>/dev/null || true
|
||||
|
||||
# Update package lists to ensure clean state
|
||||
apt-get update -qq 2>/dev/null || true
|
||||
|
||||
# Wait a moment for processes to clean up
|
||||
sleep 1
|
||||
}
|
||||
[ -f "/root/.profile" ] && source /root/.profile 2>/dev/null || true
|
||||
[ -f "/root/.cargo/env" ] && source /root/.cargo/env 2>/dev/null || true
|
||||
|
||||
# Test validation function
|
||||
test_function() {
|
||||
local test_name="$1"
|
||||
local test_command="$2"
|
||||
local validation_cmd="${3:-}"
|
||||
|
||||
# Clean up before starting test
|
||||
cleanup_before_test
|
||||
|
||||
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${CYAN}Testing: ${test_name}${NC}"
|
||||
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
|
||||
{
|
||||
echo "=== Test: ${test_name} ==="
|
||||
echo "Command: ${test_command}"
|
||||
echo "Started: $(date)"
|
||||
} | tee -a "$TEST_LOG"
|
||||
|
||||
# Execute installation with output visible AND logged
|
||||
if eval "$test_command" 2>&1 | tee -a "$TEST_LOG"; then
|
||||
# Reload PATH after installation
|
||||
reload_path
|
||||
|
||||
if [[ -n "$validation_cmd" ]]; then
|
||||
local output
|
||||
if output=$(bash -c "$validation_cmd" 2>&1); then
|
||||
msg_ok "${test_name} - $(echo "$output" | head -n1)"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
msg_error "${test_name} - Installation succeeded but validation failed"
|
||||
{
|
||||
echo "Validation command: $validation_cmd"
|
||||
echo "Validation output: $output"
|
||||
echo "PATH: $PATH"
|
||||
} | tee -a "$TEST_LOG"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
else
|
||||
msg_ok "${test_name}"
|
||||
((TESTS_PASSED++))
|
||||
fi
|
||||
else
|
||||
msg_error "${test_name} - Installation failed"
|
||||
echo "Installation failed" | tee -a "$TEST_LOG"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
|
||||
echo "Completed: $(date)" | tee -a "$TEST_LOG"
|
||||
echo "" | tee -a "$TEST_LOG"
|
||||
}
|
||||
|
||||
# Skip test with reason
|
||||
skip_test() {
|
||||
local test_name="$1"
|
||||
local reason="$2"
|
||||
|
||||
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${CYAN}Testing: ${test_name}${NC}"
|
||||
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
msg_warn "Skipped: ${reason}"
|
||||
((TESTS_SKIPPED++))
|
||||
}
|
||||
|
||||
# Update system
|
||||
msg_info "Updating system packages"
|
||||
apt-get update &>/dev/null && msg_ok "System updated"
|
||||
|
||||
# Install base dependencies
|
||||
msg_info "Installing base dependencies"
|
||||
apt-get install -y curl wget gpg jq git build-essential ca-certificates &>/dev/null && msg_ok "Base dependencies installed"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 1: YQ - YAML Processor
|
||||
# ==============================================================================
|
||||
# test_function "YQ" \
|
||||
# "setup_yq" \
|
||||
# "yq --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 2: ADMINER - Database Management
|
||||
# ==============================================================================
|
||||
# test_function "Adminer" \
|
||||
# "setup_adminer" \
|
||||
# "dpkg -l adminer 2>/dev/null | grep -q '^ii' && a2query -c adminer 2>/dev/null && echo 'Adminer installed'"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 3: CLICKHOUSE
|
||||
# ==============================================================================
|
||||
# test_function "ClickHouse" \
|
||||
# "setup_clickhouse" \
|
||||
# "clickhouse-server --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 4: POSTGRESQL
|
||||
# ==============================================================================
|
||||
test_function "PostgreSQL 16" \
|
||||
"PG_VERSION=16 setup_postgresql" \
|
||||
"psql --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 6: MARIADB
|
||||
# ==============================================================================
|
||||
test_function "MariaDB 11.4" \
|
||||
"MARIADB_VERSION=11.4 setup_mariadb" \
|
||||
"mariadb --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 7: MYSQL (Remove MariaDB first)
|
||||
# ==============================================================================
|
||||
msg_info "Removing MariaDB before MySQL installation"
|
||||
systemctl stop mariadb &>/dev/null || true
|
||||
apt-get purge -y mariadb-server mariadb-client mariadb-common &>/dev/null || true
|
||||
apt-get autoremove -y &>/dev/null
|
||||
rm -rf /etc/mysql /var/lib/mysql
|
||||
msg_ok "MariaDB removed"
|
||||
|
||||
test_function "MySQL 8.0" \
|
||||
"MYSQL_VERSION=8.0 setup_mysql" \
|
||||
"mysql --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 8: MONGODB (Check AVX support)
|
||||
# ==============================================================================
|
||||
# if grep -q avx /proc/cpuinfo; then
|
||||
# test_function "MongoDB 8.0" \
|
||||
# "MONGO_VERSION=8.0 setup_mongodb" \
|
||||
# "mongod --version"
|
||||
# else
|
||||
# skip_test "MongoDB 8.0" "CPU does not support AVX"
|
||||
# fi
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 9: NODE.JS
|
||||
# ==============================================================================
|
||||
# test_function "Node.js 22 with modules" \
|
||||
# "NODE_VERSION=22 NODE_MODULE='yarn,pnpm@10.1.0,pm2' setup_nodejs" \
|
||||
# "node --version && npm --version && yarn --version && pnpm --version && pm2 --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 10: PYTHON (UV)
|
||||
# ==============================================================================
|
||||
# test_function "Python 3.12 via uv" \
|
||||
# "PYTHON_VERSION=3.12 setup_uv" \
|
||||
# "uv --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 11: PHP
|
||||
# ==============================================================================
|
||||
# test_function "PHP 8.3 with FPM" \
|
||||
# "PHP_VERSION=8.3 PHP_FPM=YES PHP_MODULE='redis,imagick,apcu,zip,mbstring' setup_php" \
|
||||
# "php --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 12: COMPOSER
|
||||
# # ==============================================================================
|
||||
# test_function "Composer" \
|
||||
# "setup_composer" \
|
||||
# "composer --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 13: JAVA
|
||||
# ==============================================================================
|
||||
# test_function "Java Temurin 21" \
|
||||
# "JAVA_VERSION=21 setup_java" \
|
||||
# "java --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 14: GO
|
||||
# ==============================================================================
|
||||
# test_function "Go (latest)" \
|
||||
# "GO_VERSION=latest setup_go" \
|
||||
# "go version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 15: RUBY
|
||||
# ==============================================================================
|
||||
test_function "Ruby 3.4.1 with Rails" \
|
||||
"RUBY_VERSION=3.4.1 RUBY_INSTALL_RAILS=true setup_ruby" \
|
||||
"ruby --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 16: RUST
|
||||
# ==============================================================================
|
||||
# test_function "Rust (stable)" \
|
||||
# "RUST_TOOLCHAIN=stable RUST_CRATES='cargo-edit' setup_rust" \
|
||||
# "source \$HOME/.cargo/env && rustc --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 17: GHOSTSCRIPT
|
||||
# ==============================================================================
|
||||
# test_function "Ghostscript" \
|
||||
# "setup_gs" \
|
||||
# "gs --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 18: IMAGEMAGICK
|
||||
# ==============================================================================
|
||||
# test_function "ImageMagick" \
|
||||
# "setup_imagemagick" \
|
||||
# "magick --version"
|
||||
|
||||
# ==============================================================================
|
||||
# TEST 19: FFMPEG
|
||||
# ==============================================================================
|
||||
# test_function "FFmpeg n7.1.1 (full)" \
|
||||
# "FFMPEG_VERSION=n7.1.1 FFMPEG_TYPE=full setup_ffmpeg" \
|
||||
# "ffmpeg -version"
|
||||
|
||||
# ==============================================================================
|
||||
# FINAL SUMMARY
|
||||
# ==============================================================================
|
||||
echo -e "\n${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} TEST SUMMARY${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${GREEN}✔ Passed: ${TESTS_PASSED}${NC}"
|
||||
echo -e "${RED}✖ Failed: ${TESTS_FAILED}${NC}"
|
||||
echo -e "${YELLOW}⚠ Skipped: ${TESTS_SKIPPED}${NC}"
|
||||
echo -e "\nDetailed log: ${TEST_LOG}"
|
||||
|
||||
# Generate summary report
|
||||
{
|
||||
echo ""
|
||||
echo "=== FINAL SUMMARY ==="
|
||||
echo "Tests Passed: ${TESTS_PASSED}"
|
||||
echo "Tests Failed: ${TESTS_FAILED}"
|
||||
echo "Tests Skipped: ${TESTS_SKIPPED}"
|
||||
echo ""
|
||||
echo "=== Installed Versions ==="
|
||||
command -v yq &>/dev/null && echo "yq: $(yq --version 2>&1)"
|
||||
command -v clickhouse-server &>/dev/null && echo "ClickHouse: $(clickhouse-server --version 2>&1 | head -n1)"
|
||||
command -v psql &>/dev/null && echo "PostgreSQL: $(psql --version)"
|
||||
command -v mysql &>/dev/null && echo "MySQL: $(mysql --version)"
|
||||
command -v mongod &>/dev/null && echo "MongoDB: $(mongod --version 2>&1 | head -n1)"
|
||||
command -v node &>/dev/null && echo "Node.js: $(node --version)"
|
||||
command -v php &>/dev/null && echo "PHP: $(php --version | head -n1)"
|
||||
command -v java &>/dev/null && echo "Java: $(java --version 2>&1 | head -n1)"
|
||||
command -v go &>/dev/null && echo "Go: $(go version)"
|
||||
command -v ruby &>/dev/null && echo "Ruby: $(ruby --version)"
|
||||
command -v rustc &>/dev/null && echo "Rust: $(rustc --version)"
|
||||
command -v ffmpeg &>/dev/null && echo "FFmpeg: $(ffmpeg -version 2>&1 | head -n1)"
|
||||
} >>"$TEST_LOG"
|
||||
|
||||
if [ $TESTS_FAILED -eq 0 ]; then
|
||||
echo -e "\n${GREEN}All tests completed successfully!${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "\n${RED}Some tests failed. Check the log for details.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 69 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,648 +0,0 @@
|
||||
config_file() {
|
||||
CONFIG_FILE="/opt/community-scripts/.settings"
|
||||
|
||||
if [[ -f "/opt/community-scripts/${NSAPP}.conf" ]]; then
|
||||
CONFIG_FILE="/opt/community-scripts/${NSAPP}.conf"
|
||||
fi
|
||||
|
||||
if CONFIG_FILE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set absolute path to config file" 8 58 "$CONFIG_FILE" --title "CONFIG FILE" 3>&1 1>&2 2>&3); then
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
echo -e "${CROSS}${RD}Config file not found, exiting script!.${CL}"
|
||||
exit
|
||||
else
|
||||
echo -e "${INFO}${BOLD}${DGN}Using config File: ${BGN}$CONFIG_FILE${CL}"
|
||||
source "$CONFIG_FILE"
|
||||
fi
|
||||
fi
|
||||
if [[ -n "${CT_ID-}" ]]; then
|
||||
if [[ "$CT_ID" =~ ^([0-9]{3,4})-([0-9]{3,4})$ ]]; then
|
||||
MIN_ID=${BASH_REMATCH[1]}
|
||||
MAX_ID=${BASH_REMATCH[2]}
|
||||
if ((MIN_ID >= MAX_ID)); then
|
||||
msg_error "Invalid Container ID range. The first number must be smaller than the second number, was ${CT_ID}"
|
||||
exit
|
||||
fi
|
||||
|
||||
LIST_OF_IDS=$(pvesh get /cluster/resources --type vm --output-format json 2>/dev/null | grep -oP '"vmid":\s*\K\d+') || true
|
||||
if [[ -n "$LIST_OF_IDS" ]]; then
|
||||
for ((ID = MIN_ID; ID <= MAX_ID; ID++)); do
|
||||
if ! grep -q "^$ID$" <<<"$LIST_OF_IDS"; then
|
||||
CT_ID=$ID
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
|
||||
|
||||
elif [[ "$CT_ID" =~ ^[0-9]+$ ]]; then
|
||||
LIST_OF_IDS=$(pvesh get /cluster/resources --type vm --output-format json 2>/dev/null | grep -oP '"vmid":\s*\K\d+') || true
|
||||
if [[ -n "$LIST_OF_IDS" ]]; then
|
||||
|
||||
if ! grep -q "^$CT_ID$" <<<"$LIST_OF_IDS"; then
|
||||
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
|
||||
else
|
||||
msg_error "Container ID $CT_ID already exists"
|
||||
exit
|
||||
fi
|
||||
else
|
||||
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
|
||||
fi
|
||||
else
|
||||
msg_error "Invalid Container ID format. Needs to be 0000-9999 or 0-9999, was ${CT_ID}"
|
||||
exit
|
||||
fi
|
||||
else
|
||||
if CT_ID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Container ID" 8 58 "$NEXTID" --title "CONTAINER ID" 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$CT_ID" ]; then
|
||||
CT_ID="$NEXTID"
|
||||
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
|
||||
else
|
||||
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
|
||||
fi
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
|
||||
fi
|
||||
if [[ -n "${CT_TYPE-}" ]]; then
|
||||
if [[ "$CT_TYPE" -eq 0 ]]; then
|
||||
CT_TYPE_DESC="Privileged"
|
||||
elif [[ "$CT_TYPE" -eq 1 ]]; then
|
||||
CT_TYPE_DESC="Unprivileged"
|
||||
else
|
||||
msg_error "Unknown setting for CT_TYPE, should be 1 or 0, was ${CT_TYPE}"
|
||||
exit
|
||||
fi
|
||||
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}"
|
||||
else
|
||||
if CT_TYPE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CONTAINER TYPE" --radiolist "Choose Type" 10 58 2 \
|
||||
"1" "Unprivileged" ON \
|
||||
"0" "Privileged" OFF \
|
||||
3>&1 1>&2 2>&3); then
|
||||
if [ -n "$CT_TYPE" ]; then
|
||||
CT_TYPE_DESC="Unprivileged"
|
||||
if [ "$CT_TYPE" -eq 0 ]; then
|
||||
CT_TYPE_DESC="Privileged"
|
||||
fi
|
||||
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}"
|
||||
fi
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if [[ -n "${PW-}" ]]; then
|
||||
if [[ "$PW" == "none" ]]; then
|
||||
PW=""
|
||||
else
|
||||
if [[ "$PW" == *" "* ]]; then
|
||||
msg_error "Password cannot be empty"
|
||||
exit
|
||||
elif [[ ${#PW} -lt 5 ]]; then
|
||||
msg_error "Password must be at least 5 characters long"
|
||||
exit
|
||||
else
|
||||
echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}********${CL}"
|
||||
fi
|
||||
PW="-password $PW"
|
||||
fi
|
||||
else
|
||||
while true; do
|
||||
if PW1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --passwordbox "\nSet Root Password (needed for root ssh access)" 9 58 --title "PASSWORD (leave blank for automatic login)" 3>&1 1>&2 2>&3); then
|
||||
if [[ -n "$PW1" ]]; then
|
||||
if [[ "$PW1" == *" "* ]]; then
|
||||
whiptail --msgbox "Password cannot contain spaces. Please try again." 8 58
|
||||
elif [ ${#PW1} -lt 5 ]; then
|
||||
whiptail --msgbox "Password must be at least 5 characters long. Please try again." 8 58
|
||||
else
|
||||
if PW2=$(whiptail --backtitle "Proxmox VE Helper Scripts" --passwordbox "\nVerify Root Password" 9 58 --title "PASSWORD VERIFICATION" 3>&1 1>&2 2>&3); then
|
||||
if [[ "$PW1" == "$PW2" ]]; then
|
||||
PW="-password $PW1"
|
||||
echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}********${CL}"
|
||||
break
|
||||
else
|
||||
whiptail --msgbox "Passwords do not match. Please try again." 8 58
|
||||
fi
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
fi
|
||||
else
|
||||
PW1="Automatic Login"
|
||||
PW=""
|
||||
echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}$PW1${CL}"
|
||||
break
|
||||
fi
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -n "${HN-}" ]]; then
|
||||
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}"
|
||||
else
|
||||
if CT_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 "$NSAPP" --title "HOSTNAME" 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$CT_NAME" ]; then
|
||||
HN="$NSAPP"
|
||||
else
|
||||
HN=$(echo "${CT_NAME,,}" | tr -d ' ')
|
||||
fi
|
||||
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}"
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "${DISK_SIZE-}" ]]; then
|
||||
if [[ "$DISK_SIZE" =~ ^-?[0-9]+$ ]]; then
|
||||
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}"
|
||||
else
|
||||
msg_error "DISK_SIZE must be an integer, was ${DISK_SIZE}"
|
||||
exit
|
||||
fi
|
||||
else
|
||||
if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GB" 8 58 "$var_disk" --title "DISK SIZE" 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$DISK_SIZE" ]; then
|
||||
DISK_SIZE="$var_disk"
|
||||
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}"
|
||||
else
|
||||
if ! [[ $DISK_SIZE =~ $INTEGER ]]; then
|
||||
echo -e "{INFO}${HOLD}${RD} DISK SIZE MUST BE AN INTEGER NUMBER!${CL}"
|
||||
advanced_settings
|
||||
fi
|
||||
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}"
|
||||
fi
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "${CORE_COUNT-}" ]]; then
|
||||
if [[ "$CORE_COUNT" =~ ^-?[0-9]+$ ]]; then
|
||||
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}"
|
||||
else
|
||||
msg_error "CORE_COUNT must be an integer, was ${CORE_COUNT}"
|
||||
exit
|
||||
fi
|
||||
else
|
||||
if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate CPU Cores" 8 58 "$var_cpu" --title "CORE COUNT" 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$CORE_COUNT" ]; then
|
||||
CORE_COUNT="$var_cpu"
|
||||
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}"
|
||||
else
|
||||
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}"
|
||||
fi
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "${RAM_SIZE-}" ]]; then
|
||||
if [[ "$RAM_SIZE" =~ ^-?[0-9]+$ ]]; then
|
||||
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}"
|
||||
else
|
||||
msg_error "RAM_SIZE must be an integer, was ${RAM_SIZE}"
|
||||
exit
|
||||
fi
|
||||
else
|
||||
if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 "$var_ram" --title "RAM" 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$RAM_SIZE" ]; then
|
||||
RAM_SIZE="$var_ram"
|
||||
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}"
|
||||
else
|
||||
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}"
|
||||
fi
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
fi
|
||||
|
||||
IFACE_FILEPATH_LIST="/etc/network/interfaces"$'\n'$(find "/etc/network/interfaces.d/" -type f)
|
||||
BRIDGES=""
|
||||
OLD_IFS=$IFS
|
||||
IFS=$'\n'
|
||||
|
||||
for iface_filepath in ${IFACE_FILEPATH_LIST}; do
|
||||
|
||||
iface_indexes_tmpfile=$(mktemp -q -u '.iface-XXXX')
|
||||
( grep -Pn '^\s*iface' "${iface_filepath}" | cut -d':' -f1 && wc -l "${iface_filepath}" | cut -d' ' -f1 ) | awk 'FNR==1 {line=$0; next} {print line":"$0-1; line=$0}' > "${iface_indexes_tmpfile}" || true
|
||||
|
||||
if [ -f "${iface_indexes_tmpfile}" ]; then
|
||||
|
||||
while read -r pair; do
|
||||
start=$(echo "${pair}" | cut -d':' -f1)
|
||||
end=$(echo "${pair}" | cut -d':' -f2)
|
||||
if awk "NR >= ${start} && NR <= ${end}" "${iface_filepath}" | grep -qP '^\s*(bridge[-_](ports|stp|fd|vlan-aware|vids)|ovs_type\s+OVSBridge)\b'; then
|
||||
iface_name=$(sed "${start}q;d" "${iface_filepath}" | awk '{print $2}')
|
||||
BRIDGES="${iface_name}"$'\n'"${BRIDGES}"
|
||||
fi
|
||||
|
||||
done < "${iface_indexes_tmpfile}"
|
||||
rm -f "${iface_indexes_tmpfile}"
|
||||
fi
|
||||
|
||||
done
|
||||
IFS=$OLD_IFS
|
||||
BRIDGES=$(echo "$BRIDGES" | grep -v '^\s*$' | sort | uniq)
|
||||
|
||||
if [[ -n "${BRG-}" ]]; then
|
||||
if echo "$BRIDGES" | grep -q "${BRG}"; then
|
||||
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
|
||||
else
|
||||
msg_error "Bridge '${BRG}' does not exist in /etc/network/interfaces or /etc/network/interfaces.d/sdn"
|
||||
exit
|
||||
fi
|
||||
else
|
||||
BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --menu "Select network bridge:" 15 40 6 $(echo "$BRIDGES" | awk '{print $0, "Bridge"}') 3>&1 1>&2 2>&3)
|
||||
if [ -z "$BRG" ]; then
|
||||
exit_script
|
||||
else
|
||||
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
|
||||
fi
|
||||
fi
|
||||
|
||||
local ip_cidr_regex='^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/([0-9]{1,2})$'
|
||||
local ip_regex='^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$'
|
||||
|
||||
if [[ -n ${NET-} ]]; then
|
||||
if [ "$NET" == "dhcp" ]; then
|
||||
echo -e "${NETWORK}${BOLD}${DGN}IP Address: ${BGN}DHCP${CL}"
|
||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}Default${CL}"
|
||||
elif [[ "$NET" =~ $ip_cidr_regex ]]; then
|
||||
echo -e "${NETWORK}${BOLD}${DGN}IP Address: ${BGN}$NET${CL}"
|
||||
if [ ! -z "$GATE" ]; then
|
||||
if [[ "$GATE" =~ $ip_regex ]]; then
|
||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE${CL}"
|
||||
GATE=",gw=$GATE"
|
||||
else
|
||||
msg_error "Invalid IP Address format for Gateway. Needs to be 0.0.0.0, was ${GATE}"
|
||||
exit
|
||||
fi
|
||||
|
||||
else
|
||||
while true; do
|
||||
GATE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Enter gateway IP address" 8 58 --title "Gateway IP" 3>&1 1>&2 2>&3)
|
||||
if [ -z "$GATE1" ]; then
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "Gateway IP address cannot be empty" 8 58
|
||||
elif [[ ! "$GATE1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "Invalid IP address format" 8 58
|
||||
else
|
||||
GATE=",gw=$GATE1"
|
||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE1${CL}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
elif [[ "$NET" == *-* ]]; then
|
||||
IFS="-" read -r ip_start ip_end <<< "$NET"
|
||||
|
||||
if [[ ! "$ip_start" =~ $ip_cidr_regex ]] || [[ ! "$ip_end" =~ $ip_cidr_regex ]]; then
|
||||
msg_error "Invalid IP range format, was $NET should be 0.0.0.0/0-0.0.0.0/0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ip1="${ip_start%%/*}"
|
||||
ip2="${ip_end%%/*}"
|
||||
cidr="${ip_start##*/}"
|
||||
|
||||
ip_to_int() {
|
||||
local IFS=.
|
||||
read -r i1 i2 i3 i4 <<< "$1"
|
||||
echo $(( (i1 << 24) + (i2 << 16) + (i3 << 8) + i4 ))
|
||||
}
|
||||
|
||||
int_to_ip() {
|
||||
local ip=$1
|
||||
echo "$(( (ip >> 24) & 0xFF )).$(( (ip >> 16) & 0xFF )).$(( (ip >> 8) & 0xFF )).$(( ip & 0xFF ))"
|
||||
}
|
||||
|
||||
start_int=$(ip_to_int "$ip1")
|
||||
end_int=$(ip_to_int "$ip2")
|
||||
|
||||
for ((ip_int=start_int; ip_int<=end_int; ip_int++)); do
|
||||
ip=$(int_to_ip $ip_int)
|
||||
msg_info "Checking IP: $ip"
|
||||
if ! ping -c 2 -W 1 "$ip" >/dev/null 2>&1; then
|
||||
NET="$ip/$cidr"
|
||||
msg_ok "Using free IP Address: ${BGN}$NET${CL}"
|
||||
sleep 3
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ "$NET" == *-* ]]; then
|
||||
msg_error "No free IP found in range"
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$GATE" ]; then
|
||||
if [[ "$GATE" =~ $ip_regex ]]; then
|
||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE${CL}"
|
||||
GATE=",gw=$GATE"
|
||||
else
|
||||
msg_error "Invalid IP Address format for Gateway. Needs to be 0.0.0.0, was ${GATE}"
|
||||
exit
|
||||
fi
|
||||
else
|
||||
while true; do
|
||||
GATE1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Enter gateway IP address" 8 58 --title "Gateway IP" 3>&1 1>&2 2>&3)
|
||||
if [ -z "$GATE1" ]; then
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "Gateway IP address cannot be empty" 8 58
|
||||
elif [[ ! "$GATE1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "Invalid IP address format" 8 58
|
||||
else
|
||||
GATE=",gw=$GATE1"
|
||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE1${CL}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
else
|
||||
msg_error "Invalid IP Address format. Needs to be 0.0.0.0/0 or a range like 10.0.0.1/24-10.0.0.10/24, was ${NET}"
|
||||
exit
|
||||
fi
|
||||
else
|
||||
while true; do
|
||||
NET=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Static IPv4 CIDR Address (/24)" 8 58 dhcp --title "IP ADDRESS" 3>&1 1>&2 2>&3)
|
||||
exit_status=$?
|
||||
if [ $exit_status -eq 0 ]; then
|
||||
if [ "$NET" = "dhcp" ]; then
|
||||
echo -e "${NETWORK}${BOLD}${DGN}IP Address: ${BGN}$NET${CL}"
|
||||
break
|
||||
else
|
||||
if [[ "$NET" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then
|
||||
echo -e "${NETWORK}${BOLD}${DGN}IP Address: ${BGN}$NET${CL}"
|
||||
break
|
||||
else
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "$NET is an invalid IPv4 CIDR address. Please enter a valid IPv4 CIDR address or 'dhcp'" 8 58
|
||||
fi
|
||||
fi
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
done
|
||||
if [ "$NET" != "dhcp" ]; then
|
||||
while true; do
|
||||
GATE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Enter gateway IP address" 8 58 --title "Gateway IP" 3>&1 1>&2 2>&3)
|
||||
if [ -z "$GATE1" ]; then
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "Gateway IP address cannot be empty" 8 58
|
||||
elif [[ ! "$GATE1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox "Invalid IP address format" 8 58
|
||||
else
|
||||
GATE=",gw=$GATE1"
|
||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE1${CL}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
else
|
||||
GATE=""
|
||||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}Default${CL}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$var_os" == "alpine" ]; then
|
||||
APT_CACHER=""
|
||||
APT_CACHER_IP=""
|
||||
else
|
||||
if [[ -n "${APT_CACHER_IP-}" ]]; then
|
||||
if [[ ! $APT_CACHER_IP == "none" ]]; then
|
||||
APT_CACHER="yes"
|
||||
echo -e "${NETWORK}${BOLD}${DGN}APT-CACHER IP Address: ${BGN}$APT_CACHER_IP${CL}"
|
||||
else
|
||||
APT_CACHER=""
|
||||
echo -e "${NETWORK}${BOLD}${DGN}APT-Cacher IP Address: ${BGN}No${CL}"
|
||||
fi
|
||||
else
|
||||
if APT_CACHER_IP=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set APT-Cacher IP (leave blank for none)" 8 58 --title "APT-Cacher IP" 3>&1 1>&2 2>&3); then
|
||||
APT_CACHER="${APT_CACHER_IP:+yes}"
|
||||
echo -e "${NETWORK}${BOLD}${DGN}APT-Cacher IP Address: ${BGN}${APT_CACHER_IP:-Default}${CL}"
|
||||
if [[ -n $APT_CACHER_IP ]]; then
|
||||
APT_CACHER_IP="none"
|
||||
fi
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${DISABLEIP6-}" == "yes" ]]; then
|
||||
echo -e "${DISABLEIPV6}${BOLD}${DGN}Disable IPv6: ${BGN}Yes${CL}"
|
||||
elif [[ "${DISABLEIP6-}" == "no" ]]; then
|
||||
echo -e "${DISABLEIPV6}${BOLD}${DGN}Disable IPv6: ${BGN}No${CL}"
|
||||
else
|
||||
if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "IPv6" --yesno "Disable IPv6?" 10 58); then
|
||||
DISABLEIP6="yes"
|
||||
else
|
||||
DISABLEIP6="no"
|
||||
fi
|
||||
echo -e "${DISABLEIPV6}${BOLD}${DGN}Disable IPv6: ${BGN}$DISABLEIP6${CL}"
|
||||
fi
|
||||
|
||||
if [[ -n "${MTU-}" ]]; then
|
||||
if [[ "$MTU" =~ ^-?[0-9]+$ ]]; then
|
||||
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU${CL}"
|
||||
MTU=",mtu=$MTU"
|
||||
else
|
||||
msg_error "MTU must be an integer, was ${MTU}"
|
||||
exit
|
||||
fi
|
||||
else
|
||||
if MTU1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default [The MTU of your selected vmbr, default is 1500])" 8 58 --title "MTU SIZE" 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$MTU1" ]; then
|
||||
MTU1="Default"
|
||||
MTU=""
|
||||
else
|
||||
MTU=",mtu=$MTU1"
|
||||
fi
|
||||
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}"
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "${SD-}" ]]; then
|
||||
if [[ "$SD" == "none" ]]; then
|
||||
SD=""
|
||||
echo -e "${SEARCH}${BOLD}${DGN}DNS Search Domain: ${BGN}Host${CL}"
|
||||
else
|
||||
echo -e "${SEARCH}${BOLD}${DGN}DNS Search Domain: ${BGN}$SD${CL}"
|
||||
SD="-searchdomain=$SD"
|
||||
fi
|
||||
else
|
||||
if SD=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a DNS Search Domain (leave blank for HOST)" 8 58 --title "DNS Search Domain" 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$SD" ]; then
|
||||
SX=Host
|
||||
SD=""
|
||||
else
|
||||
SX=$SD
|
||||
SD="-searchdomain=$SD"
|
||||
fi
|
||||
echo -e "${SEARCH}${BOLD}${DGN}DNS Search Domain: ${BGN}$SX${CL}"
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "${NS-}" ]]; then
|
||||
if [[ $NS == "none" ]]; then
|
||||
NS=""
|
||||
echo -e "${NETWORK}${BOLD}${DGN}DNS Server IP Address: ${BGN}Host${CL}"
|
||||
else
|
||||
if [[ "$NS" =~ $ip_regex ]]; then
|
||||
echo -e "${NETWORK}${BOLD}${DGN}DNS Server IP Address: ${BGN}$NS${CL}"
|
||||
NS="-nameserver=$NS"
|
||||
else
|
||||
msg_error "Invalid IP Address format for DNS Server. Needs to be 0.0.0.0, was ${NS}"
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if NX=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a DNS Server IP (leave blank for HOST)" 8 58 --title "DNS SERVER IP" 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$NX" ]; then
|
||||
NX=Host
|
||||
NS=""
|
||||
else
|
||||
NS="-nameserver=$NX"
|
||||
fi
|
||||
echo -e "${NETWORK}${BOLD}${DGN}DNS Server IP Address: ${BGN}$NX${CL}"
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "${MAC-}" ]]; then
|
||||
if [[ "$MAC" == "none" ]]; then
|
||||
MAC=""
|
||||
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}Host${CL}"
|
||||
else
|
||||
if [[ "$MAC" =~ ^([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}$ ]]; then
|
||||
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}"
|
||||
MAC=",hwaddr=$MAC"
|
||||
else
|
||||
msg_error "MAC Address must be in the format xx:xx:xx:xx:xx:xx, was ${MAC}"
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a MAC Address(leave blank for generated MAC)" 8 58 --title "MAC ADDRESS" 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$MAC1" ]; then
|
||||
MAC1="Default"
|
||||
MAC=""
|
||||
else
|
||||
MAC=",hwaddr=$MAC1"
|
||||
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}"
|
||||
fi
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "${VLAN-}" ]]; then
|
||||
if [[ "$VLAN" == "none" ]]; then
|
||||
VLAN=""
|
||||
echo -e "${VLANTAG}${BOLD}${DGN}Vlan: ${BGN}Host${CL}"
|
||||
else
|
||||
if [[ "$VLAN" =~ ^-?[0-9]+$ ]]; then
|
||||
echo -e "${VLANTAG}${BOLD}${DGN}Vlan: ${BGN}$VLAN${CL}"
|
||||
VLAN=",tag=$VLAN"
|
||||
else
|
||||
msg_error "VLAN must be an integer, was ${VLAN}"
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Vlan(leave blank for no VLAN)" 8 58 --title "VLAN" 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$VLAN1" ]; then
|
||||
VLAN1="Default"
|
||||
VLAN=""
|
||||
else
|
||||
VLAN=",tag=$VLAN1"
|
||||
fi
|
||||
echo -e "${VLANTAG}${BOLD}${DGN}Vlan: ${BGN}$VLAN1${CL}"
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "${TAGS-}" ]]; then
|
||||
if [[ "$TAGS" == *"DEFAULT"* ]]; then
|
||||
TAGS="${TAGS//DEFAULT/}"
|
||||
TAGS="${TAGS//;/}"
|
||||
TAGS="$TAGS;${var_tags:-}"
|
||||
echo -e "${NETWORK}${BOLD}${DGN}Tags: ${BGN}$TAGS${CL}"
|
||||
fi
|
||||
else
|
||||
TAGS="community-scripts;"
|
||||
if ADV_TAGS=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Custom Tags?[If you remove all, there will be no tags!]" 8 58 "${TAGS}" --title "Advanced Tags" 3>&1 1>&2 2>&3); then
|
||||
if [ -n "${ADV_TAGS}" ]; then
|
||||
ADV_TAGS=$(echo "$ADV_TAGS" | tr -d '[:space:]')
|
||||
TAGS="${ADV_TAGS}"
|
||||
else
|
||||
TAGS=";"
|
||||
fi
|
||||
echo -e "${NETWORK}${BOLD}${DGN}Tags: ${BGN}$TAGS${CL}"
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "${SSH-}" ]]; then
|
||||
if [[ "$SSH" == "yes" ]]; then
|
||||
echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}"
|
||||
if [[ ! -z "$SSH_AUTHORIZED_KEY" ]]; then
|
||||
echo -e "${ROOTSSH}${BOLD}${DGN}SSH Authorized Key: ${BGN}********************${CL}"
|
||||
else
|
||||
echo -e "${ROOTSSH}${BOLD}${DGN}SSH Authorized Key: ${BGN}None${CL}"
|
||||
fi
|
||||
elif [[ "$SSH" == "no" ]]; then
|
||||
echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}"
|
||||
else
|
||||
msg_error "SSH needs to be 'yes' or 'no', was ${SSH}"
|
||||
exit
|
||||
fi
|
||||
else
|
||||
SSH_AUTHORIZED_KEY="$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "SSH Authorized key for root (leave empty for none)" 8 58 --title "SSH Key" 3>&1 1>&2 2>&3)"
|
||||
if [[ -z "${SSH_AUTHORIZED_KEY}" ]]; then
|
||||
SSH_AUTHORIZED_KEY=""
|
||||
fi
|
||||
if [[ "$PW" == -password* || -n "$SSH_AUTHORIZED_KEY" ]]; then
|
||||
if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "SSH ACCESS" --yesno "Enable Root SSH Access?" 10 58); then
|
||||
SSH="yes"
|
||||
else
|
||||
SSH="no"
|
||||
fi
|
||||
echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}"
|
||||
else
|
||||
SSH="no"
|
||||
echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "${VERBOSE-}" ]]; then
|
||||
if [[ "$VERBOSE" == "yes" ]]; then
|
||||
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERBOSE${CL}"
|
||||
elif [[ "$VERBOSE" == "no" ]]; then
|
||||
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}No${CL}"
|
||||
else
|
||||
msg_error "Verbose Mode needs to be 'yes' or 'no', was ${VERBOSE}"
|
||||
exit
|
||||
fi
|
||||
else
|
||||
if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "VERBOSE MODE" --yesno "Enable Verbose Mode?" 10 58); then
|
||||
VERBOSE="yes"
|
||||
else
|
||||
VERBOSE="no"
|
||||
fi
|
||||
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERBOSE${CL}"
|
||||
fi
|
||||
|
||||
if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS WITH CONFIG FILE COMPLETE" --yesno "Ready to create ${APP} LXC?" 10 58); then
|
||||
echo -e "${CREATING}${BOLD}${RD}Creating a ${APP} LXC using the above settings${CL}"
|
||||
else
|
||||
clear
|
||||
header_info
|
||||
echo -e "${INFO}${HOLD} ${GN}Using Config File on node $PVEHOST_NAME${CL}"
|
||||
config_file
|
||||
fi
|
||||
|
||||
}
|
||||
@ -1,633 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) 2021-2026 tteck
|
||||
# Author: tteck (tteckster)
|
||||
# Co-Author: MickLesk
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Optional verbose mode (debug tracing)
|
||||
# ------------------------------------------------------------------------------
|
||||
if [[ "${CREATE_LXC_VERBOSE:-no}" == "yes" ]]; then set -x; fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Load core functions (msg_info/msg_ok/msg_error/…)
|
||||
# ------------------------------------------------------------------------------
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
# Achtung: bewusst exakt diese URL-Struktur
|
||||
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func)
|
||||
load_functions
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func)
|
||||
load_functions
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Strict error handling
|
||||
# ------------------------------------------------------------------------------
|
||||
# set -Eeuo pipefail
|
||||
# trap 'error_handler $? $LINENO "$BASH_COMMAND"' ERR
|
||||
# trap on_exit EXIT
|
||||
# trap on_interrupt INT
|
||||
# trap on_terminate TERM
|
||||
|
||||
# error_handler() {
|
||||
# local exit_code="$1"
|
||||
# local line_number="$2"
|
||||
# local command="${3:-}"
|
||||
|
||||
# if [[ "$exit_code" -eq 0 ]]; then
|
||||
# return 0
|
||||
# fi
|
||||
|
||||
# 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"
|
||||
# }
|
||||
|
||||
# on_exit() {
|
||||
# local exit_code="$?"
|
||||
# [[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile"
|
||||
# exit "$exit_code"
|
||||
# }
|
||||
|
||||
# on_interrupt() {
|
||||
# echo -e "\n${RD}Interrupted by user (SIGINT)${CL}"
|
||||
# exit 130
|
||||
# }
|
||||
|
||||
# on_terminate() {
|
||||
# echo -e "\n${RD}Terminated by signal (SIGTERM)${CL}"
|
||||
# exit 143
|
||||
# }
|
||||
|
||||
exit_script() {
|
||||
clear
|
||||
printf "\e[?25h"
|
||||
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
|
||||
kill 0
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Helpers (dynamic versioning / template parsing)
|
||||
# ------------------------------------------------------------------------------
|
||||
pkg_ver() { dpkg-query -W -f='${Version}\n' "$1" 2>/dev/null || echo ""; }
|
||||
pkg_cand() { apt-cache policy "$1" 2>/dev/null | awk '/Candidate:/ {print $2}'; }
|
||||
|
||||
ver_ge() { dpkg --compare-versions "$1" ge "$2"; }
|
||||
ver_gt() { dpkg --compare-versions "$1" gt "$2"; }
|
||||
ver_lt() { dpkg --compare-versions "$1" lt "$2"; }
|
||||
|
||||
# Extract Debian OS minor from template name: debian-13-standard_13.1-1_amd64.tar.zst => "13.1"
|
||||
parse_template_osver() { sed -n 's/.*_\([0-9][0-9]*\(\.[0-9]\+\)\?\)-.*/\1/p' <<<"$1"; }
|
||||
|
||||
# Offer upgrade for pve-container/lxc-pve if candidate > installed; optional auto-retry pct create
|
||||
# Returns:
|
||||
# 0 = no upgrade needed
|
||||
# 1 = upgraded (and if do_retry=yes and retry succeeded, creation done)
|
||||
# 2 = user declined
|
||||
# 3 = upgrade attempted but failed OR retry failed
|
||||
offer_lxc_stack_upgrade_and_maybe_retry() {
|
||||
local do_retry="${1:-no}" # yes|no
|
||||
local _pvec_i _pvec_c _lxcp_i _lxcp_c need=0
|
||||
|
||||
_pvec_i="$(pkg_ver pve-container)"
|
||||
_lxcp_i="$(pkg_ver lxc-pve)"
|
||||
_pvec_c="$(pkg_cand pve-container)"
|
||||
_lxcp_c="$(pkg_cand lxc-pve)"
|
||||
|
||||
if [[ -n "$_pvec_c" && "$_pvec_c" != "none" ]]; then
|
||||
ver_gt "$_pvec_c" "${_pvec_i:-0}" && need=1
|
||||
fi
|
||||
if [[ -n "$_lxcp_c" && "$_lxcp_c" != "none" ]]; then
|
||||
ver_gt "$_lxcp_c" "${_lxcp_i:-0}" && need=1
|
||||
fi
|
||||
if [[ $need -eq 0 ]]; then
|
||||
msg_debug "No newer candidate for pve-container/lxc-pve (installed=$_pvec_i/$_lxcp_i, cand=$_pvec_c/$_lxcp_c)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "An update for the Proxmox LXC stack is available:"
|
||||
echo " pve-container: installed=${_pvec_i:-n/a} candidate=${_pvec_c:-n/a}"
|
||||
echo " lxc-pve : installed=${_lxcp_i:-n/a} candidate=${_lxcp_c:-n/a}"
|
||||
echo
|
||||
read -rp "Do you want to upgrade now? [y/N] " _ans
|
||||
case "${_ans,,}" in
|
||||
y | yes)
|
||||
msg_info "Upgrading Proxmox LXC stack (pve-container, lxc-pve)"
|
||||
if apt-get update -qq >/dev/null && apt-get install -y --only-upgrade pve-container lxc-pve >/dev/null; then
|
||||
msg_ok "LXC stack upgraded."
|
||||
if [[ "$do_retry" == "yes" ]]; then
|
||||
msg_info "Retrying container creation after upgrade"
|
||||
if pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then
|
||||
msg_ok "Container created successfully after upgrade."
|
||||
return 1
|
||||
else
|
||||
msg_error "pct create still failed after upgrade. See $LOGFILE"
|
||||
return 3
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
else
|
||||
msg_error "Upgrade failed. Please check APT output."
|
||||
return 3
|
||||
fi
|
||||
;;
|
||||
*) return 2 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Storage discovery / selection helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
resolve_storage_preselect() {
|
||||
local class="$1" preselect="$2" required_content=""
|
||||
case "$class" in
|
||||
template) required_content="vztmpl" ;;
|
||||
container) required_content="rootdir" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
[[ -z "$preselect" ]] && return 1
|
||||
if ! pvesm status -content "$required_content" | awk 'NR>1{print $1}' | grep -qx -- "$preselect"; then
|
||||
msg_warn "Preselected storage '${preselect}' does not support content '${required_content}' (or not found)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local line total used free
|
||||
line="$(pvesm status | awk -v s="$preselect" 'NR>1 && $1==s {print $0}')"
|
||||
if [[ -z "$line" ]]; then
|
||||
STORAGE_INFO="n/a"
|
||||
else
|
||||
total="$(awk '{print $4}' <<<"$line")"
|
||||
used="$(awk '{print $5}' <<<"$line")"
|
||||
free="$(awk '{print $6}' <<<"$line")"
|
||||
local total_h used_h free_h
|
||||
if command -v numfmt >/dev/null 2>&1; then
|
||||
total_h="$(numfmt --to=iec --suffix=B --format %.1f "$total" 2>/dev/null || echo "$total")"
|
||||
used_h="$(numfmt --to=iec --suffix=B --format %.1f "$used" 2>/dev/null || echo "$used")"
|
||||
free_h="$(numfmt --to=iec --suffix=B --format %.1f "$free" 2>/dev/null || echo "$free")"
|
||||
STORAGE_INFO="Free: ${free_h} Used: ${used_h}"
|
||||
else
|
||||
STORAGE_INFO="Free: ${free} Used: ${used}"
|
||||
fi
|
||||
fi
|
||||
STORAGE_RESULT="$preselect"
|
||||
return 0
|
||||
}
|
||||
|
||||
check_storage_support() {
|
||||
local CONTENT="$1" VALID=0
|
||||
while IFS= read -r line; do
|
||||
local STORAGE_NAME
|
||||
STORAGE_NAME=$(awk '{print $1}' <<<"$line")
|
||||
[[ -n "$STORAGE_NAME" ]] && VALID=1
|
||||
done < <(pvesm status -content "$CONTENT" 2>/dev/null | awk 'NR>1')
|
||||
[[ $VALID -eq 1 ]]
|
||||
}
|
||||
|
||||
select_storage() {
|
||||
local CLASS=$1 CONTENT CONTENT_LABEL
|
||||
case $CLASS in
|
||||
container)
|
||||
CONTENT='rootdir'
|
||||
CONTENT_LABEL='Container'
|
||||
;;
|
||||
template)
|
||||
CONTENT='vztmpl'
|
||||
CONTENT_LABEL='Container template'
|
||||
;;
|
||||
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
|
||||
;;
|
||||
esac
|
||||
|
||||
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
|
||||
|
||||
declare -A STORAGE_MAP
|
||||
local -a MENU=()
|
||||
local COL_WIDTH=0
|
||||
|
||||
while read -r TAG TYPE _ TOTAL USED FREE _; do
|
||||
[[ -n "$TAG" && -n "$TYPE" ]] || continue
|
||||
local DISPLAY="${TAG} (${TYPE})"
|
||||
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"
|
||||
STORAGE_MAP["$DISPLAY"]="$TAG"
|
||||
MENU+=("$DISPLAY" "$INFO" "OFF")
|
||||
((${#DISPLAY} > COL_WIDTH)) && COL_WIDTH=${#DISPLAY}
|
||||
done < <(pvesm status -content "$CONTENT" | awk 'NR>1')
|
||||
|
||||
if [[ ${#MENU[@]} -eq 0 ]]; then
|
||||
msg_error "No storage found for content type '$CONTENT'."
|
||||
return 2
|
||||
fi
|
||||
|
||||
if [[ $((${#MENU[@]} / 3)) -eq 1 ]]; then
|
||||
STORAGE_RESULT="${STORAGE_MAP[${MENU[0]}]}"
|
||||
STORAGE_INFO="${MENU[1]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local WIDTH=$((COL_WIDTH + 42))
|
||||
while true; do
|
||||
local DISPLAY_SELECTED
|
||||
DISPLAY_SELECTED=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
|
||||
--title "Storage Pools" \
|
||||
--radiolist "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \
|
||||
16 "$WIDTH" 6 "${MENU[@]}" 3>&1 1>&2 2>&3) || exit_script
|
||||
|
||||
DISPLAY_SELECTED=$(sed 's/[[:space:]]*$//' <<<"$DISPLAY_SELECTED")
|
||||
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]}"
|
||||
for ((i = 0; i < ${#MENU[@]}; i += 3)); do
|
||||
if [[ "${MENU[$i]}" == "$DISPLAY_SELECTED" ]]; then
|
||||
STORAGE_INFO="${MENU[$i + 1]}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
done
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Required input variables
|
||||
# ------------------------------------------------------------------------------
|
||||
[[ "${CTID:-}" ]] || {
|
||||
msg_error "You need to set 'CTID' variable."
|
||||
exit 203
|
||||
}
|
||||
[[ "${PCT_OSTYPE:-}" ]] || {
|
||||
msg_error "You need to set 'PCT_OSTYPE' variable."
|
||||
exit 204
|
||||
}
|
||||
|
||||
msg_debug "CTID=$CTID"
|
||||
msg_debug "PCT_OSTYPE=$PCT_OSTYPE"
|
||||
msg_debug "PCT_OSVERSION=${PCT_OSVERSION:-default}"
|
||||
|
||||
# ID checks
|
||||
[[ "$CTID" -ge 100 ]] || {
|
||||
msg_error "ID cannot be less than 100."
|
||||
exit 205
|
||||
}
|
||||
if qm status "$CTID" &>/dev/null || pct status "$CTID" &>/dev/null; then
|
||||
echo -e "ID '$CTID' is already in use."
|
||||
unset CTID
|
||||
msg_error "Cannot use ID that is already in use."
|
||||
exit 206
|
||||
fi
|
||||
|
||||
# Storage capability check
|
||||
check_storage_support "rootdir" || {
|
||||
msg_error "No valid storage found for 'rootdir' [Container]"
|
||||
exit 1
|
||||
}
|
||||
check_storage_support "vztmpl" || {
|
||||
msg_error "No valid storage found for 'vztmpl' [Template]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Template storage selection
|
||||
if resolve_storage_preselect template "${TEMPLATE_STORAGE:-}"; then
|
||||
TEMPLATE_STORAGE="$STORAGE_RESULT"
|
||||
TEMPLATE_STORAGE_INFO="$STORAGE_INFO"
|
||||
msg_ok "Storage ${BL}${TEMPLATE_STORAGE}${CL} (${TEMPLATE_STORAGE_INFO}) [Template]"
|
||||
else
|
||||
while true; do
|
||||
if select_storage template; then
|
||||
TEMPLATE_STORAGE="$STORAGE_RESULT"
|
||||
TEMPLATE_STORAGE_INFO="$STORAGE_INFO"
|
||||
msg_ok "Storage ${BL}${TEMPLATE_STORAGE}${CL} (${TEMPLATE_STORAGE_INFO}) [Template]"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Container storage selection
|
||||
if resolve_storage_preselect container "${CONTAINER_STORAGE:-}"; then
|
||||
CONTAINER_STORAGE="$STORAGE_RESULT"
|
||||
CONTAINER_STORAGE_INFO="$STORAGE_INFO"
|
||||
msg_ok "Storage ${BL}${CONTAINER_STORAGE}${CL} (${CONTAINER_STORAGE_INFO}) [Container]"
|
||||
else
|
||||
while true; do
|
||||
if select_storage container; then
|
||||
CONTAINER_STORAGE="$STORAGE_RESULT"
|
||||
CONTAINER_STORAGE_INFO="$STORAGE_INFO"
|
||||
msg_ok "Storage ${BL}${CONTAINER_STORAGE}${CL} (${CONTAINER_STORAGE_INFO}) [Container]"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Validate content types
|
||||
msg_info "Validating content types of storage '$CONTAINER_STORAGE'"
|
||||
STORAGE_CONTENT=$(grep -A4 -E "^(zfspool|dir|lvmthin|lvm): $CONTAINER_STORAGE" /etc/pve/storage.cfg | grep content | awk '{$1=""; print $0}' | xargs)
|
||||
msg_debug "Storage '$CONTAINER_STORAGE' has content types: $STORAGE_CONTENT"
|
||||
grep -qw "rootdir" <<<"$STORAGE_CONTENT" || {
|
||||
msg_error "Storage '$CONTAINER_STORAGE' does not support 'rootdir'. Cannot create LXC."
|
||||
exit 217
|
||||
}
|
||||
$STD msg_ok "Storage '$CONTAINER_STORAGE' supports 'rootdir'"
|
||||
|
||||
msg_info "Validating content types of template storage '$TEMPLATE_STORAGE'"
|
||||
TEMPLATE_CONTENT=$(grep -A4 -E "^[^:]+: $TEMPLATE_STORAGE" /etc/pve/storage.cfg | grep content | awk '{$1=""; print $0}' | xargs)
|
||||
msg_debug "Template storage '$TEMPLATE_STORAGE' has content types: $TEMPLATE_CONTENT"
|
||||
if ! grep -qw "vztmpl" <<<"$TEMPLATE_CONTENT"; then
|
||||
msg_warn "Template storage '$TEMPLATE_STORAGE' does not declare 'vztmpl'. This may cause pct create to fail."
|
||||
else
|
||||
$STD msg_ok "Template storage '$TEMPLATE_STORAGE' supports 'vztmpl'"
|
||||
fi
|
||||
|
||||
# Free space check
|
||||
STORAGE_FREE=$(pvesm status | awk -v s="$CONTAINER_STORAGE" '$1 == s { print $6 }')
|
||||
REQUIRED_KB=$((${PCT_DISK_SIZE:-8} * 1024 * 1024))
|
||||
[[ "$STORAGE_FREE" -ge "$REQUIRED_KB" ]] || {
|
||||
msg_error "Not enough space on '$CONTAINER_STORAGE'. Needed: ${PCT_DISK_SIZE:-8}G."
|
||||
exit 214
|
||||
}
|
||||
|
||||
# Cluster quorum (if cluster)
|
||||
if [[ -f /etc/pve/corosync.conf ]]; then
|
||||
msg_info "Checking cluster quorum"
|
||||
if ! pvecm status | awk -F':' '/^Quorate/ { exit ($2 ~ /Yes/) ? 0 : 1 }'; then
|
||||
msg_error "Cluster is not quorate. Start all nodes or configure quorum device (QDevice)."
|
||||
exit 210
|
||||
fi
|
||||
msg_ok "Cluster is quorate"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Template discovery & validation
|
||||
# ------------------------------------------------------------------------------
|
||||
TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION:-}"
|
||||
case "$PCT_OSTYPE" in
|
||||
debian | ubuntu) TEMPLATE_PATTERN="-standard_" ;;
|
||||
alpine | fedora | rocky | centos) TEMPLATE_PATTERN="-default_" ;;
|
||||
*) TEMPLATE_PATTERN="" ;;
|
||||
esac
|
||||
|
||||
msg_info "Searching for template '$TEMPLATE_SEARCH'"
|
||||
|
||||
mapfile -t LOCAL_TEMPLATES < <(
|
||||
pveam list "$TEMPLATE_STORAGE" 2>/dev/null |
|
||||
awk -v s="$TEMPLATE_SEARCH" -v p="$TEMPLATE_PATTERN" '$1 ~ s && $1 ~ p {print $1}' |
|
||||
sed 's|.*/||' | sort -t - -k 2 -V
|
||||
)
|
||||
|
||||
pveam update >/dev/null 2>&1 || msg_warn "Could not update template catalog (pveam update failed)."
|
||||
mapfile -t ONLINE_TEMPLATES < <(
|
||||
pveam available -section system 2>/dev/null |
|
||||
sed -n "s/.*\($TEMPLATE_SEARCH.*$TEMPLATE_PATTERN.*\)/\1/p" |
|
||||
sort -t - -k 2 -V
|
||||
)
|
||||
ONLINE_TEMPLATE=""
|
||||
[[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]] && ONLINE_TEMPLATE="${ONLINE_TEMPLATES[-1]}"
|
||||
|
||||
if [[ ${#LOCAL_TEMPLATES[@]} -gt 0 ]]; then
|
||||
TEMPLATE="${LOCAL_TEMPLATES[-1]}"
|
||||
TEMPLATE_SOURCE="local"
|
||||
else
|
||||
TEMPLATE="$ONLINE_TEMPLATE"
|
||||
TEMPLATE_SOURCE="online"
|
||||
fi
|
||||
|
||||
TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)"
|
||||
if [[ -z "$TEMPLATE_PATH" ]]; then
|
||||
TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg)
|
||||
[[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE"
|
||||
fi
|
||||
[[ -n "$TEMPLATE_PATH" ]] || {
|
||||
msg_error "Unable to resolve template path for $TEMPLATE_STORAGE. Check storage type and permissions."
|
||||
exit 220
|
||||
}
|
||||
|
||||
msg_ok "Template ${BL}$TEMPLATE${CL} [$TEMPLATE_SOURCE]"
|
||||
msg_debug "Resolved TEMPLATE_PATH=$TEMPLATE_PATH"
|
||||
|
||||
NEED_DOWNLOAD=0
|
||||
if [[ ! -f "$TEMPLATE_PATH" ]]; then
|
||||
msg_info "Template not present locally – will download."
|
||||
NEED_DOWNLOAD=1
|
||||
elif [[ ! -r "$TEMPLATE_PATH" ]]; then
|
||||
msg_error "Template file exists but is not readable – check permissions."
|
||||
exit 221
|
||||
elif [[ "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then
|
||||
if [[ -n "$ONLINE_TEMPLATE" ]]; then
|
||||
msg_warn "Template file too small (<1MB) – re-downloading."
|
||||
NEED_DOWNLOAD=1
|
||||
else
|
||||
msg_warn "Template looks too small, but no online version exists. Keeping local file."
|
||||
fi
|
||||
elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then
|
||||
if [[ -n "$ONLINE_TEMPLATE" ]]; then
|
||||
msg_warn "Template appears corrupted – re-downloading."
|
||||
NEED_DOWNLOAD=1
|
||||
else
|
||||
msg_warn "Template appears corrupted, but no online version exists. Keeping local file."
|
||||
fi
|
||||
else
|
||||
$STD msg_ok "Template $TEMPLATE is present and valid."
|
||||
fi
|
||||
|
||||
if [[ "$TEMPLATE_SOURCE" == "local" && -n "$ONLINE_TEMPLATE" && "$TEMPLATE" != "$ONLINE_TEMPLATE" ]]; then
|
||||
msg_warn "Local template is outdated: $TEMPLATE (latest available: $ONLINE_TEMPLATE)"
|
||||
if whiptail --yesno "A newer template is available:\n$ONLINE_TEMPLATE\n\nDo you want to download and use it instead?" 12 70; then
|
||||
TEMPLATE="$ONLINE_TEMPLATE"
|
||||
NEED_DOWNLOAD=1
|
||||
else
|
||||
msg_info "Continuing with local template $TEMPLATE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$NEED_DOWNLOAD" -eq 1 ]]; then
|
||||
[[ -f "$TEMPLATE_PATH" ]] && rm -f "$TEMPLATE_PATH"
|
||||
for attempt in {1..3}; do
|
||||
msg_info "Attempt $attempt: Downloading template $TEMPLATE to $TEMPLATE_STORAGE"
|
||||
if pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1; then
|
||||
msg_ok "Template download successful."
|
||||
break
|
||||
fi
|
||||
if [[ $attempt -eq 3 ]]; then
|
||||
msg_error "Failed after 3 attempts. Please check network access, permissions, or manually run:\n pveam download $TEMPLATE_STORAGE $TEMPLATE"
|
||||
exit 222
|
||||
fi
|
||||
sleep $((attempt * 5))
|
||||
done
|
||||
fi
|
||||
|
||||
if ! pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE"; then
|
||||
msg_error "Template $TEMPLATE not available in storage $TEMPLATE_STORAGE after download."
|
||||
exit 223
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Dynamic preflight for Debian 13.x: offer upgrade if available (no hard mins)
|
||||
# ------------------------------------------------------------------------------
|
||||
if [[ "$PCT_OSTYPE" == "debian" ]]; then
|
||||
OSVER="$(parse_template_osver "$TEMPLATE")"
|
||||
if [[ -n "$OSVER" ]]; then
|
||||
# Proactive, aber ohne Abbruch – nur Angebot
|
||||
offer_lxc_stack_upgrade_and_maybe_retry "no" || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Create LXC Container
|
||||
# ------------------------------------------------------------------------------
|
||||
msg_info "Creating LXC container"
|
||||
|
||||
# Ensure subuid/subgid entries exist
|
||||
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
|
||||
|
||||
# Assemble pct options
|
||||
PCT_OPTIONS=(${PCT_OPTIONS[@]:-${DEFAULT_PCT_OPTIONS[@]}})
|
||||
[[ " ${PCT_OPTIONS[*]} " =~ " -rootfs " ]] || PCT_OPTIONS+=(-rootfs "$CONTAINER_STORAGE:${PCT_DISK_SIZE:-8}")
|
||||
|
||||
# Lock by template file (avoid concurrent downloads/creates)
|
||||
lockfile="/tmp/template.${TEMPLATE}.lock"
|
||||
exec 9>"$lockfile" || {
|
||||
msg_error "Failed to create lock file '$lockfile'."
|
||||
exit 200
|
||||
}
|
||||
flock -w 60 9 || {
|
||||
msg_error "Timeout while waiting for template lock."
|
||||
exit 211
|
||||
}
|
||||
|
||||
LOGFILE="/tmp/pct_create_${CTID}.log"
|
||||
msg_debug "pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}"
|
||||
msg_debug "Logfile: $LOGFILE"
|
||||
|
||||
# First attempt
|
||||
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >"$LOGFILE" 2>&1; then
|
||||
msg_error "Container creation failed on ${TEMPLATE_STORAGE}. Checking template..."
|
||||
|
||||
# Validate template file
|
||||
if [[ ! -s "$TEMPLATE_PATH" || "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then
|
||||
msg_warn "Template file too small or missing – re-downloading."
|
||||
rm -f "$TEMPLATE_PATH"
|
||||
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE"
|
||||
elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then
|
||||
if [[ -n "$ONLINE_TEMPLATE" ]]; then
|
||||
msg_warn "Template appears corrupted – re-downloading."
|
||||
rm -f "$TEMPLATE_PATH"
|
||||
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE"
|
||||
else
|
||||
msg_warn "Template appears corrupted, but no online version exists. Skipping re-download."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Retry after repair
|
||||
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then
|
||||
# Fallback to local storage
|
||||
if [[ "$TEMPLATE_STORAGE" != "local" ]]; then
|
||||
msg_warn "Retrying container creation with fallback to local storage..."
|
||||
LOCAL_TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE"
|
||||
if [[ ! -f "$LOCAL_TEMPLATE_PATH" ]]; then
|
||||
msg_info "Downloading template to local..."
|
||||
pveam download local "$TEMPLATE" >/dev/null 2>&1
|
||||
fi
|
||||
if pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then
|
||||
msg_ok "Container successfully created using local fallback."
|
||||
else
|
||||
# --- Dynamic stack upgrade + auto-retry on the well-known error pattern ---
|
||||
if grep -qiE 'unsupported .* version' "$LOGFILE"; then
|
||||
echo
|
||||
echo "pct reported 'unsupported ... version' – your LXC stack might be too old for this template."
|
||||
echo "We can try to upgrade 'pve-container' and 'lxc-pve' now and retry automatically."
|
||||
if offer_lxc_stack_upgrade_and_maybe_retry "yes"; then
|
||||
: # success after retry
|
||||
else
|
||||
rc=$?
|
||||
case $rc in
|
||||
2) echo "Upgrade was declined. Please update and re-run:
|
||||
apt update && apt install --only-upgrade pve-container lxc-pve" ;;
|
||||
3) echo "Upgrade and/or retry failed. Please inspect: $LOGFILE" ;;
|
||||
esac
|
||||
exit 231
|
||||
fi
|
||||
else
|
||||
msg_error "Container creation failed even with local fallback. See $LOGFILE"
|
||||
if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then
|
||||
set -x
|
||||
bash -x -c "pct create $CTID local:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" 2>&1 | tee -a "$LOGFILE"
|
||||
set +x
|
||||
fi
|
||||
exit 209
|
||||
fi
|
||||
fi
|
||||
else
|
||||
msg_error "Container creation failed on local storage. See $LOGFILE"
|
||||
# --- Dynamic stack upgrade + auto-retry on the well-known error pattern ---
|
||||
if grep -qiE 'unsupported .* version' "$LOGFILE"; then
|
||||
echo
|
||||
echo "pct reported 'unsupported ... version' – your LXC stack might be too old for this template."
|
||||
echo "We can try to upgrade 'pve-container' and 'lxc-pve' now and retry automatically."
|
||||
if offer_lxc_stack_upgrade_and_maybe_retry "yes"; then
|
||||
: # success after retry
|
||||
else
|
||||
rc=$?
|
||||
case $rc in
|
||||
2) echo "Upgrade was declined. Please update and re-run:
|
||||
apt update && apt install --only-upgrade pve-container lxc-pve" ;;
|
||||
3) echo "Upgrade and/or retry failed. Please inspect: $LOGFILE" ;;
|
||||
esac
|
||||
exit 231
|
||||
fi
|
||||
else
|
||||
if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then
|
||||
set -x
|
||||
bash -x -c "pct create $CTID local:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" 2>&1 | tee -a "$LOGFILE"
|
||||
set +x
|
||||
fi
|
||||
exit 209
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify container exists
|
||||
pct list | awk '{print $1}' | grep -qx "$CTID" || {
|
||||
msg_error "Container ID $CTID not listed in 'pct list'. See $LOGFILE"
|
||||
exit 215
|
||||
}
|
||||
|
||||
# Verify config rootfs
|
||||
grep -q '^rootfs:' "/etc/pve/lxc/$CTID.conf" || {
|
||||
msg_error "RootFS entry missing in container config. See $LOGFILE"
|
||||
exit 216
|
||||
}
|
||||
|
||||
msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created."
|
||||
@ -1,79 +0,0 @@
|
||||
#source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func)
|
||||
|
||||
get_gh_release() {
|
||||
local repo="$1"
|
||||
local app="${repo##*/}"
|
||||
local api_url="https://api.github.com/repos/$repo/releases/latest"
|
||||
local header=()
|
||||
local attempt=0
|
||||
local max_attempts=3
|
||||
local api_response tag
|
||||
|
||||
echo "🔍 Checking latest release for: $repo"
|
||||
|
||||
[[ -n "${GITHUB_TOKEN:-}" ]] && header=(-H "Authorization: token $GITHUB_TOKEN")
|
||||
|
||||
until [[ $attempt -ge $max_attempts ]]; do
|
||||
((attempt++))
|
||||
$STD msg_info "[$attempt/$max_attempts] Fetching GitHub release for $repo...\n"
|
||||
|
||||
if ! api_response=$(curl -fsSL "${header[@]}" "$api_url"); then
|
||||
$STD msg_info "Request failed, retrying...\n"
|
||||
sleep 2
|
||||
continue
|
||||
fi
|
||||
|
||||
if echo "$api_response" | grep -q "API rate limit exceeded"; then
|
||||
msg_error "GitHub API rate limit exceeded."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if echo "$api_response" | jq -e '.message == "Not Found"' &>/dev/null; then
|
||||
msg_error "Repository not found: $repo"
|
||||
return 1
|
||||
fi
|
||||
|
||||
tag=$(echo "$api_response" | jq -r '.tag_name // .name // empty')
|
||||
[[ "$tag" =~ ^v[0-9] ]] && tag="${tag:1}"
|
||||
|
||||
if [[ -z "$tag" ]]; then
|
||||
$STD msg_info "Empty tag received, retrying...\n"
|
||||
sleep 2
|
||||
continue
|
||||
fi
|
||||
|
||||
$STD msg_ok "Found release: $tag for $repo"
|
||||
echo "$tag"
|
||||
return 0
|
||||
done
|
||||
|
||||
msg_error "Failed to fetch release for $repo after $max_attempts attempts."
|
||||
return 1
|
||||
}
|
||||
|
||||
fetch_and_extract_gh_release() {
|
||||
set -Eeuo pipefail
|
||||
trap 'echo -e "\n❌ [fetch_and_extract_gh_release] Error on line $LINENO: $BASH_COMMAND"' ERR
|
||||
|
||||
local repo="$1"
|
||||
local tag="$2"
|
||||
local app="${repo##*/}"
|
||||
|
||||
local temp_file
|
||||
temp_file=$(mktemp)
|
||||
local tarball_url="https://github.com/$repo/archive/refs/tags/v$tag.tar.gz"
|
||||
|
||||
msg_info "Downloading tarball for $app from $tarball_url..."
|
||||
if ! curl -fsSL "$tarball_url" -o "$temp_file"; then
|
||||
msg_error "Failed to download tarball: $tarball_url"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p "/opt/$app"
|
||||
echo "📦 Extracting tarball to /opt/$app..."
|
||||
tar -xzf "$temp_file" -C /opt
|
||||
mv "/opt/${app}-${tag}"/* "/opt/$app/" 2>/dev/null || msg_warn "Could not move extracted files."
|
||||
rm -rf "/opt/${app}-${tag}"
|
||||
|
||||
msg_ok "Extracted $app to /opt/$app"
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user