mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-03-31 06:34:17 -04:00
373 lines
11 KiB
Bash
373 lines
11 KiB
Bash
#!/usr/bin/env bash
|
|
# Copyright (c) 2021-2026 community-scripts ORG
|
|
# Author: tteck (tteckster)
|
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
|
|
|
# Source shared libraries
|
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)
|
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
|
load_functions
|
|
catch_errors
|
|
|
|
APP="TurnKey LXC"
|
|
NSAPP="turnkey"
|
|
DIAGNOSTICS="no"
|
|
METHOD="default"
|
|
RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)"
|
|
EXECUTION_ID="${RANDOM_UUID}"
|
|
|
|
header_info() {
|
|
clear
|
|
cat <<"EOF"
|
|
______ __ __ __ _ _______
|
|
/_ __/_ _________ / //_/__ __ __ / / | |/_/ ___/
|
|
/ / / // / __/ _ \/ ,< / -_) // / / /___> </ /__
|
|
/_/ \_,_/_/ /_//_/_/|_|\__/\_, / /____/_/|_|\___/
|
|
/___/
|
|
EOF
|
|
}
|
|
|
|
# Validate if a container ID is available (cluster-aware)
|
|
validate_container_id() {
|
|
local ctid="$1"
|
|
[[ "$ctid" =~ ^[0-9]+$ ]] || return 1
|
|
|
|
# Cluster-wide check via pvesh
|
|
if command -v pvesh &>/dev/null; then
|
|
local cluster_ids
|
|
cluster_ids=$(pvesh get /cluster/resources --type vm --output-format json 2>/dev/null |
|
|
grep -oP '"vmid":\s*\K[0-9]+' 2>/dev/null || true)
|
|
if [[ -n "$cluster_ids" ]] && echo "$cluster_ids" | grep -qw "$ctid"; then
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Local fallback
|
|
if [[ -f "/etc/pve/qemu-server/${ctid}.conf" ]] || [[ -f "/etc/pve/lxc/${ctid}.conf" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
# Check all cluster nodes
|
|
if [[ -d "/etc/pve/nodes" ]]; then
|
|
for node_dir in /etc/pve/nodes/*/; do
|
|
if [[ -f "${node_dir}qemu-server/${ctid}.conf" ]] || [[ -f "${node_dir}lxc/${ctid}.conf" ]]; then
|
|
return 1
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Check LVM volumes
|
|
if lvs --noheadings -o lv_name 2>/dev/null | grep -qE "(^|[-_])${ctid}($|[-_])"; then
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
get_valid_container_id() {
|
|
local suggested_id="${1:-$(pvesh get /cluster/nextid 2>/dev/null || echo 100)}"
|
|
while ! validate_container_id "$suggested_id"; do
|
|
suggested_id=$((suggested_id + 1))
|
|
done
|
|
echo "$suggested_id"
|
|
}
|
|
|
|
cleanup_ctid() {
|
|
if pct status "$CTID" &>/dev/null; then
|
|
if [[ "$(pct status "$CTID" | awk '{print $2}')" == "running" ]]; then
|
|
pct stop "$CTID"
|
|
fi
|
|
pct destroy "$CTID"
|
|
fi
|
|
}
|
|
|
|
select_storage() {
|
|
local class="$1" content content_label
|
|
case "$class" in
|
|
container)
|
|
content='rootdir'
|
|
content_label='Container'
|
|
;;
|
|
template)
|
|
content='vztmpl'
|
|
content_label='Container template'
|
|
;;
|
|
*)
|
|
msg_error "Invalid storage class '$class'"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
local -a MENU=()
|
|
local MSG_MAX_LENGTH=0
|
|
|
|
while read -r line; do
|
|
local TAG TYPE FREE ITEM OFFSET=2
|
|
TAG=$(echo "$line" | awk '{print $1}')
|
|
TYPE=$(echo "$line" | awk '{printf "%-10s", $2}')
|
|
FREE=$(echo "$line" | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( "%9sB", $6)}')
|
|
ITEM=" Type: $TYPE Free: $FREE "
|
|
((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=$((${#ITEM} + OFFSET))
|
|
MENU+=("$TAG" "$ITEM" "OFF")
|
|
done < <(pvesm status -content "$content" | awk 'NR>1')
|
|
|
|
if [[ $((${#MENU[@]} / 3)) -eq 0 ]]; then
|
|
msg_error "'$content_label' needs to be selected for at least one storage location."
|
|
return 1
|
|
elif [[ $((${#MENU[@]} / 3)) -eq 1 ]]; then
|
|
printf '%s' "${MENU[0]}"
|
|
else
|
|
local STORAGE
|
|
while [[ -z "${STORAGE:+x}" ]]; do
|
|
STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \
|
|
"Which storage pool for the ${content_label,,}?\n\n" \
|
|
16 $((MSG_MAX_LENGTH + 23)) 6 \
|
|
"${MENU[@]}" 3>&1 1>&2 2>&3) || exit_script
|
|
done
|
|
printf '%s' "$STORAGE"
|
|
fi
|
|
}
|
|
|
|
# ==============================================================================
|
|
# MAIN
|
|
# ==============================================================================
|
|
|
|
# Cleanup on error: destroy container, report telemetry, and restart monitor
|
|
turnkey_cleanup() {
|
|
local exit_code=$?
|
|
if [[ $exit_code -ne 0 ]]; then
|
|
# Report failure to telemetry
|
|
if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then
|
|
post_update_to_api "failed" "$exit_code" 2>/dev/null || true
|
|
fi
|
|
# Destroy failed container
|
|
if [[ -n "${CTID:-}" ]]; then
|
|
cleanup_ctid 2>/dev/null || true
|
|
fi
|
|
fi
|
|
if [[ -f /etc/systemd/system/ping-instances.service ]]; then
|
|
systemctl start ping-instances.service 2>/dev/null || true
|
|
fi
|
|
}
|
|
trap turnkey_cleanup EXIT
|
|
|
|
# Stop Proxmox VE Monitor-All if running
|
|
if systemctl is-active -q ping-instances.service; then
|
|
systemctl stop ping-instances.service
|
|
fi
|
|
|
|
pve_check
|
|
shell_check
|
|
root_check
|
|
|
|
# Read diagnostics preference (same logic as build.func diagnostics_check)
|
|
DIAG_CONFIG="/usr/local/community-scripts/diagnostics"
|
|
if [[ -f "$DIAG_CONFIG" ]]; then
|
|
DIAGNOSTICS=$(awk -F '=' '/^DIAGNOSTICS/ {print $2}' "$DIAG_CONFIG") || true
|
|
DIAGNOSTICS="${DIAGNOSTICS:-no}"
|
|
fi
|
|
|
|
header_info
|
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "TurnKey LXCs" --yesno \
|
|
"This will allow for the creation of one of the many TurnKey LXC Containers. Proceed?" 10 68 || exit_script
|
|
|
|
# Update template catalog early so the menu reflects the latest available templates
|
|
msg_info "Updating LXC template list"
|
|
pveam update >/dev/null
|
|
msg_ok "Updated LXC template list"
|
|
|
|
# Build TurnKey selection menu dynamically from available templates
|
|
# Requires gawk for regex capture groups in match()
|
|
command -v gawk &>/dev/null || apt-get install -y gawk &>/dev/null
|
|
declare -A TURNKEY_TEMPLATES
|
|
TURNKEY_MENU=()
|
|
MSG_MAX_LENGTH=0
|
|
while IFS=$'\t' read -r TEMPLATE_FILE TAG ITEM; do
|
|
TURNKEY_TEMPLATES["$TAG"]="$TEMPLATE_FILE"
|
|
OFFSET=2
|
|
((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=$((${#ITEM} + OFFSET))
|
|
TURNKEY_MENU+=("$TAG" "$ITEM " "OFF")
|
|
done < <(pveam available -section turnkeylinux | gawk '{
|
|
tpl = $2
|
|
if (match(tpl, /debian-([0-9]+)-turnkey-([^_]+)_([^_]+)_/, m)) {
|
|
app = m[2]; deb = m[1]; ver = m[3]
|
|
display = app
|
|
gsub(/-/, " ", display)
|
|
n = split(display, words, " ")
|
|
display = ""
|
|
for (i = 1; i <= n; i++) {
|
|
words[i] = toupper(substr(words[i], 1, 1)) substr(words[i], 2)
|
|
display = display (i > 1 ? " " : "") words[i]
|
|
}
|
|
tag = app "-" deb
|
|
printf "%s\t%s\t%s | Debian %s | %s\n", tpl, tag, display, deb, ver
|
|
}
|
|
}' | sort -t$'\t' -k2,2)
|
|
|
|
if [[ ${#TURNKEY_MENU[@]} -eq 0 ]]; then
|
|
msg_error "No TurnKey templates found. Check your internet connection or template repository."
|
|
exit 1
|
|
fi
|
|
|
|
selected=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "TurnKey LXCs" --radiolist \
|
|
"\nSelect a TurnKey LXC to create:\n" 20 $((MSG_MAX_LENGTH + 58)) 12 \
|
|
"${TURNKEY_MENU[@]}" 3>&1 1>&2 2>&3 | tr -d '"') || exit_script
|
|
|
|
if [[ -z "$selected" ]]; then
|
|
whiptail --backtitle "Proxmox VE Helper Scripts" --title "No TurnKey LXC Selected" \
|
|
--msgbox "It appears that no TurnKey LXC container was selected" 10 68
|
|
exit_script
|
|
fi
|
|
|
|
# Extract template filename and app name from selection
|
|
TEMPLATE="${TURNKEY_TEMPLATES[$selected]}"
|
|
turnkey="${selected%-*}"
|
|
|
|
# Generate random password
|
|
PASS="$(openssl rand -base64 8)"
|
|
|
|
# Prompt for Container ID
|
|
NEXT_ID=$(pvesh get /cluster/nextid 2>/dev/null || echo 100)
|
|
while true; do
|
|
CTID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Container ID" \
|
|
--inputbox "Enter the container ID..." 8 40 "$NEXT_ID" 3>&1 1>&2 2>&3) || exit_script
|
|
|
|
if [[ -z "$CTID" ]]; then
|
|
msg_error "No Container ID selected"
|
|
exit_script
|
|
fi
|
|
|
|
if ! validate_container_id "$CTID"; then
|
|
SUGGESTED_ID=$(get_valid_container_id "$CTID")
|
|
if whiptail --backtitle "Proxmox VE Helper Scripts" --title "ID Already In Use" --yesno \
|
|
"Container/VM ID $CTID is already in use.\n\nWould you like to use the next available ID ($SUGGESTED_ID)?" 10 58; then
|
|
CTID="$SUGGESTED_ID"
|
|
break
|
|
fi
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Prompt for Hostname
|
|
HOST_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Hostname" \
|
|
--inputbox "Enter the container hostname..." 8 40 "turnkey-${turnkey}" 3>&1 1>&2 2>&3) || exit_script
|
|
|
|
# Container options
|
|
PCT_OPTIONS=(
|
|
-features keyctl=1,nesting=1
|
|
-hostname "$HOST_NAME"
|
|
-tags community-script
|
|
-onboot 1
|
|
-cores 2
|
|
-memory 2048
|
|
-password "$PASS"
|
|
-net0 name=eth0,bridge=vmbr0,ip=dhcp
|
|
-unprivileged 1
|
|
-arch "$(dpkg --print-architecture)"
|
|
)
|
|
|
|
# Storage selection
|
|
TEMPLATE_STORAGE=$(select_storage template) || {
|
|
msg_error "Failed to select template storage"
|
|
exit 1
|
|
}
|
|
msg_ok "Using '${BL}${TEMPLATE_STORAGE}${CL}' for template storage"
|
|
|
|
CONTAINER_STORAGE=$(select_storage container) || {
|
|
msg_error "Failed to select container storage"
|
|
exit 1
|
|
}
|
|
msg_ok "Using '${BL}${CONTAINER_STORAGE}${CL}' for container storage"
|
|
|
|
# Download template if not already cached
|
|
if ! pveam list "$TEMPLATE_STORAGE" | grep -q "$TEMPLATE"; then
|
|
msg_info "Downloading LXC template"
|
|
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null || {
|
|
msg_error "Failed to download LXC template '${TEMPLATE}'"
|
|
exit 1
|
|
}
|
|
msg_ok "Downloaded LXC template"
|
|
fi
|
|
|
|
# Add rootfs if not specified
|
|
[[ " ${PCT_OPTIONS[*]} " =~ " -rootfs " ]] || PCT_OPTIONS+=(-rootfs "${CONTAINER_STORAGE}:${PCT_DISK_SIZE:-8}")
|
|
|
|
# Set telemetry variables for the selected turnkey
|
|
TELEMETRY_TYPE="turnkey"
|
|
NSAPP="turnkey-${turnkey}"
|
|
CT_TYPE=1
|
|
DISK_SIZE="${PCT_DISK_SIZE:-8}"
|
|
CORE_COUNT=2
|
|
RAM_SIZE=2048
|
|
var_os="turnkey"
|
|
var_version="${turnkey}"
|
|
|
|
# Report installation start to telemetry
|
|
post_to_api
|
|
|
|
# Create LXC container
|
|
msg_info "Creating LXC container"
|
|
pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >/dev/null || {
|
|
msg_error "Failed to create container"
|
|
exit 1
|
|
}
|
|
msg_ok "Created LXC container (ID: ${BL}${CTID}${CL})"
|
|
|
|
# Save credentials securely
|
|
CREDS_FILE=~/turnkey-${turnkey}.creds
|
|
echo "TurnKey ${turnkey} password: ${PASS}" >>"$CREDS_FILE"
|
|
chmod 600 "$CREDS_FILE"
|
|
|
|
# Configure TUN device access for VPN-based turnkeys
|
|
TUN_DEVICE_REQUIRED=("openvpn")
|
|
if printf '%s\n' "${TUN_DEVICE_REQUIRED[@]}" | grep -qw "${turnkey}"; then
|
|
msg_info "Configuring TUN device access for ${turnkey}"
|
|
{
|
|
echo "lxc.cgroup2.devices.allow: c 10:200 rwm"
|
|
echo "lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file 0 0"
|
|
} >>"/etc/pve/lxc/${CTID}.conf"
|
|
msg_ok "TUN device access configured"
|
|
sleep 5
|
|
fi
|
|
|
|
# Start container
|
|
msg_info "Starting LXC container"
|
|
pct start "$CTID"
|
|
msg_ok "Started LXC container"
|
|
sleep 10
|
|
|
|
# Detect container IP
|
|
msg_info "Detecting IP address"
|
|
IP=""
|
|
for attempt in $(seq 1 5); do
|
|
IP=$(pct exec "$CTID" -- ip -4 a show dev eth0 2>/dev/null | grep -oP 'inet \K[^/]+' || true)
|
|
if [[ -n "$IP" ]]; then
|
|
break
|
|
fi
|
|
[[ $attempt -lt 5 ]] && sleep 5
|
|
done
|
|
|
|
if [[ -z "$IP" ]]; then
|
|
msg_warn "IP address not found after 5 attempts"
|
|
IP="NOT FOUND"
|
|
else
|
|
msg_ok "IP address: ${BL}${IP}${CL}"
|
|
fi
|
|
|
|
# Report success to telemetry
|
|
post_update_to_api "done" "none"
|
|
|
|
# Success summary
|
|
header_info
|
|
echo
|
|
msg_ok "TurnKey ${BL}${turnkey}${CL} LXC container '${BL}${CTID}${CL}' was successfully created."
|
|
echo
|
|
echo -e " ${TAB}${YW}IP Address:${CL} ${BL}${IP}${CL}"
|
|
echo -e " ${TAB}${YW}Login:${CL} ${GN}root${CL}"
|
|
echo -e " ${TAB}${YW}Password:${CL} ${GN}${PASS}${CL}"
|
|
echo
|
|
echo -e " ${TAB}Proceed to the LXC console to complete the TurnKey setup."
|
|
echo -e " ${TAB}Credentials stored in: ${BL}~/turnkey-${turnkey}.creds${CL}"
|
|
echo
|