mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-02-14 17:23:25 +01:00
Compare commits
8 Commits
copilot/ad
...
fix/teleme
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
290bef96df | ||
|
|
58f8158c47 | ||
|
|
f762155870 | ||
|
|
867d02d969 | ||
|
|
1a9bbdd6e7 | ||
|
|
3e0db150bd | ||
|
|
6b3653627c | ||
|
|
733ad75dc1 |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -403,6 +403,16 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
## 2026-02-14
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- core: unified logging system with combined logs [@MickLesk](https://github.com/MickLesk) ([#11761](https://github.com/community-scripts/ProxmoxVE/pull/11761))
|
||||
|
||||
### ❔ Uncategorized
|
||||
|
||||
- Disable UniFi script - APT packages no longer available [@Copilot](https://github.com/Copilot) ([#11898](https://github.com/community-scripts/ProxmoxVE/pull/11898))
|
||||
|
||||
## 2026-02-13
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-02-14T06:14:47Z",
|
||||
"generated": "2026-02-14T12:08:41Z",
|
||||
"versions": [
|
||||
{
|
||||
"slug": "2fauth",
|
||||
@@ -284,9 +284,9 @@
|
||||
{
|
||||
"slug": "domain-locker",
|
||||
"repo": "Lissy93/domain-locker",
|
||||
"version": "v0.1.3",
|
||||
"version": "v0.1.4",
|
||||
"pinned": false,
|
||||
"date": "2026-02-11T10:03:32Z"
|
||||
"date": "2026-02-14T07:41:29Z"
|
||||
},
|
||||
{
|
||||
"slug": "domain-monitor",
|
||||
@@ -354,9 +354,9 @@
|
||||
{
|
||||
"slug": "firefly",
|
||||
"repo": "firefly-iii/firefly-iii",
|
||||
"version": "v6.4.18",
|
||||
"version": "v6.4.19",
|
||||
"pinned": false,
|
||||
"date": "2026-02-08T07:28:00Z"
|
||||
"date": "2026-02-14T11:55:40Z"
|
||||
},
|
||||
{
|
||||
"slug": "fladder",
|
||||
@@ -823,9 +823,9 @@
|
||||
{
|
||||
"slug": "metube",
|
||||
"repo": "alexta69/metube",
|
||||
"version": "2026.02.13",
|
||||
"version": "2026.02.14",
|
||||
"pinned": false,
|
||||
"date": "2026-02-13T15:18:17Z"
|
||||
"date": "2026-02-14T07:49:11Z"
|
||||
},
|
||||
{
|
||||
"slug": "miniflux",
|
||||
|
||||
127
misc/api.func
127
misc/api.func
@@ -242,7 +242,7 @@ explain_exit_code() {
|
||||
json_escape() {
|
||||
local s="$1"
|
||||
s=${s//\\/\\\\}
|
||||
s=${s//"/\\"}
|
||||
s=${s//"/\\"/}
|
||||
s=${s//$'\n'/\\n}
|
||||
s=${s//$'\r'/}
|
||||
s=${s//$'\t'/\\t}
|
||||
@@ -253,7 +253,8 @@ json_escape() {
|
||||
# get_error_text()
|
||||
#
|
||||
# - Returns last 20 lines of the active log (INSTALL_LOG or BUILD_LOG)
|
||||
# - Falls back to empty string if no log is available
|
||||
# - Falls back to combined log or BUILD_LOG if primary is not accessible
|
||||
# - Handles container paths that don't exist on the host
|
||||
# ------------------------------------------------------------------------------
|
||||
get_error_text() {
|
||||
local logfile=""
|
||||
@@ -265,11 +266,53 @@ get_error_text() {
|
||||
logfile="$BUILD_LOG"
|
||||
fi
|
||||
|
||||
# If logfile is inside container (e.g. /root/.install-*), try the host copy
|
||||
if [[ -n "$logfile" && ! -s "$logfile" ]]; then
|
||||
# Try combined log: /tmp/<app>-<CTID>-<SESSION_ID>.log
|
||||
if [[ -n "${CTID:-}" && -n "${SESSION_ID:-}" ]]; then
|
||||
local combined_log="/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log"
|
||||
if [[ -s "$combined_log" ]]; then
|
||||
logfile="$combined_log"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Also try BUILD_LOG as fallback if primary log is empty/missing
|
||||
if [[ -z "$logfile" || ! -s "$logfile" ]] && [[ -n "${BUILD_LOG:-}" && -s "${BUILD_LOG}" ]]; then
|
||||
logfile="$BUILD_LOG"
|
||||
fi
|
||||
|
||||
if [[ -n "$logfile" && -s "$logfile" ]]; then
|
||||
tail -n 20 "$logfile" 2>/dev/null | sed 's/\r$//'
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# build_error_string()
|
||||
#
|
||||
# - Builds a structured error string for telemetry reporting
|
||||
# - Format: "exit_code=<N> | <explanation>\n---\n<last 20 log lines>"
|
||||
# - If no log lines available, returns just the explanation
|
||||
# - Arguments:
|
||||
# * $1: exit_code (numeric)
|
||||
# * $2: log_text (optional, output from get_error_text)
|
||||
# - Returns structured error string via stdout
|
||||
# ------------------------------------------------------------------------------
|
||||
build_error_string() {
|
||||
local exit_code="${1:-1}"
|
||||
local log_text="${2:-}"
|
||||
local explanation
|
||||
explanation=$(explain_exit_code "$exit_code")
|
||||
|
||||
if [[ -n "$log_text" ]]; then
|
||||
# Structured format: header + separator + log lines
|
||||
printf 'exit_code=%s | %s\n---\n%s' "$exit_code" "$explanation" "$log_text"
|
||||
else
|
||||
# No log available - just the explanation with exit code
|
||||
printf 'exit_code=%s | %s' "$exit_code" "$explanation"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# SECTION 2: TELEMETRY FUNCTIONS
|
||||
# ==============================================================================
|
||||
@@ -648,13 +691,12 @@ post_update_to_api() {
|
||||
else
|
||||
exit_code=1
|
||||
fi
|
||||
# Get log lines and build structured error string
|
||||
local error_text=""
|
||||
error_text=$(get_error_text)
|
||||
if [[ -n "$error_text" ]]; then
|
||||
error=$(json_escape "$error_text")
|
||||
else
|
||||
error=$(json_escape "$(explain_exit_code "$exit_code")")
|
||||
fi
|
||||
local full_error
|
||||
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||
error=$(json_escape "$full_error")
|
||||
short_error=$(json_escape "$(explain_exit_code "$exit_code")")
|
||||
error_category=$(categorize_error "$exit_code")
|
||||
[[ -z "$error" ]] && error="Unknown error"
|
||||
@@ -797,31 +839,52 @@ EOF
|
||||
categorize_error() {
|
||||
local code="$1"
|
||||
case "$code" in
|
||||
# Network errors
|
||||
6 | 7 | 22 | 28 | 35) echo "network" ;;
|
||||
# Network errors (curl/wget)
|
||||
6 | 7 | 22 | 35) echo "network" ;;
|
||||
|
||||
# Storage errors
|
||||
214 | 217 | 219) echo "storage" ;;
|
||||
# Timeout errors
|
||||
28 | 124 | 211) echo "timeout" ;;
|
||||
|
||||
# Dependency/Package errors
|
||||
100 | 101 | 102 | 127 | 160 | 161 | 162) echo "dependency" ;;
|
||||
# Storage errors (Proxmox storage)
|
||||
214 | 217 | 219 | 224) echo "storage" ;;
|
||||
|
||||
# Dependency/Package errors (APT, DPKG, pip, commands)
|
||||
100 | 101 | 102 | 127 | 160 | 161 | 162 | 255) echo "dependency" ;;
|
||||
|
||||
# Permission errors
|
||||
126 | 152) echo "permission" ;;
|
||||
|
||||
# Timeout errors
|
||||
124 | 28 | 211) echo "timeout" ;;
|
||||
# Configuration errors (Proxmox config, invalid args)
|
||||
128 | 203 | 204 | 205 | 206 | 207 | 208) echo "config" ;;
|
||||
|
||||
# Configuration errors
|
||||
203 | 204 | 205 | 206 | 207 | 208) echo "config" ;;
|
||||
# Proxmox container/template errors
|
||||
200 | 209 | 210 | 212 | 213 | 215 | 216 | 218 | 220 | 221 | 222 | 223 | 225 | 231) echo "proxmox" ;;
|
||||
|
||||
# Service/Systemd errors
|
||||
150 | 151 | 153 | 154) echo "service" ;;
|
||||
|
||||
# Database errors (PostgreSQL, MySQL, MongoDB)
|
||||
170 | 171 | 172 | 173 | 180 | 181 | 182 | 183 | 190 | 191 | 192 | 193) echo "database" ;;
|
||||
|
||||
# Node.js / JavaScript runtime errors
|
||||
243 | 245 | 246 | 247 | 248 | 249) echo "runtime" ;;
|
||||
|
||||
# Python environment errors
|
||||
# (already covered: 160-162 under dependency)
|
||||
|
||||
# Aborted by user
|
||||
130) echo "aborted" ;;
|
||||
|
||||
# Resource errors (OOM, etc)
|
||||
137 | 134) echo "resource" ;;
|
||||
# Resource errors (OOM, SIGKILL, SIGABRT)
|
||||
134 | 137) echo "resource" ;;
|
||||
|
||||
# Default
|
||||
# Signal/Process errors (SIGTERM, SIGPIPE, SIGSEGV)
|
||||
139 | 141 | 143) echo "signal" ;;
|
||||
|
||||
# Shell errors (general error, syntax error)
|
||||
1 | 2) echo "shell" ;;
|
||||
|
||||
# Default - truly unknown
|
||||
*) echo "unknown" ;;
|
||||
esac
|
||||
}
|
||||
@@ -884,11 +947,9 @@ post_tool_to_api() {
|
||||
[[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1
|
||||
local error_text=""
|
||||
error_text=$(get_error_text)
|
||||
if [[ -n "$error_text" ]]; then
|
||||
error=$(json_escape "$error_text")
|
||||
else
|
||||
error=$(json_escape "$(explain_exit_code "$exit_code")")
|
||||
fi
|
||||
local full_error
|
||||
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||
error=$(json_escape "$full_error")
|
||||
error_category=$(categorize_error "$exit_code")
|
||||
fi
|
||||
|
||||
@@ -951,11 +1012,9 @@ post_addon_to_api() {
|
||||
[[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1
|
||||
local error_text=""
|
||||
error_text=$(get_error_text)
|
||||
if [[ -n "$error_text" ]]; then
|
||||
error=$(json_escape "$error_text")
|
||||
else
|
||||
error=$(json_escape "$(explain_exit_code "$exit_code")")
|
||||
fi
|
||||
local full_error
|
||||
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||
error=$(json_escape "$full_error")
|
||||
error_category=$(categorize_error "$exit_code")
|
||||
fi
|
||||
|
||||
@@ -1050,11 +1109,9 @@ post_update_to_api_extended() {
|
||||
fi
|
||||
local error_text=""
|
||||
error_text=$(get_error_text)
|
||||
if [[ -n "$error_text" ]]; then
|
||||
error=$(json_escape "$error_text")
|
||||
else
|
||||
error=$(json_escape "$(explain_exit_code "$exit_code")")
|
||||
fi
|
||||
local full_error
|
||||
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||
error=$(json_escape "$full_error")
|
||||
error_category=$(categorize_error "$exit_code")
|
||||
[[ -z "$error" ]] && error="Unknown error"
|
||||
fi
|
||||
|
||||
415
misc/build.func
415
misc/build.func
@@ -38,15 +38,16 @@
|
||||
# - Captures app-declared resource defaults (CPU, RAM, Disk)
|
||||
# ------------------------------------------------------------------------------
|
||||
variables() {
|
||||
NSAPP=$(echo "${APP,,}" | tr -d ' ') # This function sets the NSAPP variable by converting the value of the APP variable to lowercase and removing any spaces.
|
||||
var_install="${NSAPP}-install" # sets the var_install variable by appending "-install" to the value of NSAPP.
|
||||
INTEGER='^[0-9]+([.][0-9]+)?$' # it defines the INTEGER regular expression pattern.
|
||||
PVEHOST_NAME=$(hostname) # gets the Proxmox Hostname and sets it to Uppercase
|
||||
DIAGNOSTICS="yes" # sets the DIAGNOSTICS variable to "yes", used for the API call.
|
||||
METHOD="default" # sets the METHOD variable to "default", used for the API call.
|
||||
RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" # generates a random UUID and sets it to the RANDOM_UUID variable.
|
||||
SESSION_ID="${RANDOM_UUID:0:8}" # Short session ID (first 8 chars of UUID) for log files
|
||||
BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log
|
||||
NSAPP=$(echo "${APP,,}" | tr -d ' ') # This function sets the NSAPP variable by converting the value of the APP variable to lowercase and removing any spaces.
|
||||
var_install="${NSAPP}-install" # sets the var_install variable by appending "-install" to the value of NSAPP.
|
||||
INTEGER='^[0-9]+([.][0-9]+)?$' # it defines the INTEGER regular expression pattern.
|
||||
PVEHOST_NAME=$(hostname) # gets the Proxmox Hostname and sets it to Uppercase
|
||||
DIAGNOSTICS="yes" # sets the DIAGNOSTICS variable to "yes", used for the API call.
|
||||
METHOD="default" # sets the METHOD variable to "default", used for the API call.
|
||||
RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" # generates a random UUID and sets it to the RANDOM_UUID variable.
|
||||
SESSION_ID="${RANDOM_UUID:0:8}" # Short session ID (first 8 chars of UUID) for log files
|
||||
BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log
|
||||
combined_log="/tmp/install-${SESSION_ID}-combined.log" # Combined log (build + install) for failed installations
|
||||
CTTYPE="${CTTYPE:-${CT_TYPE:-1}}"
|
||||
|
||||
# Parse dev_mode early
|
||||
@@ -217,7 +218,7 @@ update_motd_ip() {
|
||||
local current_os="$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '"') - Version: $(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"')"
|
||||
local current_hostname="$(hostname)"
|
||||
local current_ip="$(hostname -I | awk '{print $1}')"
|
||||
|
||||
|
||||
# Update only if values actually changed
|
||||
if ! grep -q "OS:.*$current_os" "$PROFILE_FILE" 2>/dev/null; then
|
||||
sed -i "s|OS:.*|OS: \${GN}$current_os\${CL}\\\"|" "$PROFILE_FILE"
|
||||
@@ -385,7 +386,7 @@ validate_hostname() {
|
||||
|
||||
# Split by dots and validate each label
|
||||
local IFS='.'
|
||||
read -ra labels <<< "$hostname"
|
||||
read -ra labels <<<"$hostname"
|
||||
for label in "${labels[@]}"; do
|
||||
# Each label: 1-63 chars, alphanumeric, hyphens allowed (not at start/end)
|
||||
if [[ -z "$label" ]] || [[ ${#label} -gt 63 ]]; then
|
||||
@@ -489,7 +490,7 @@ validate_ipv6_address() {
|
||||
# Check that no segment exceeds 4 hex chars
|
||||
local IFS=':'
|
||||
local -a segments
|
||||
read -ra segments <<< "$addr"
|
||||
read -ra segments <<<"$addr"
|
||||
for seg in "${segments[@]}"; do
|
||||
if [[ ${#seg} -gt 4 ]]; then
|
||||
return 1
|
||||
@@ -539,14 +540,14 @@ validate_gateway_in_subnet() {
|
||||
|
||||
# Convert IPs to integers
|
||||
local IFS='.'
|
||||
read -r i1 i2 i3 i4 <<< "$ip"
|
||||
read -r g1 g2 g3 g4 <<< "$gateway"
|
||||
read -r i1 i2 i3 i4 <<<"$ip"
|
||||
read -r g1 g2 g3 g4 <<<"$gateway"
|
||||
|
||||
local ip_int=$(( (i1 << 24) + (i2 << 16) + (i3 << 8) + i4 ))
|
||||
local gw_int=$(( (g1 << 24) + (g2 << 16) + (g3 << 8) + g4 ))
|
||||
local ip_int=$(((i1 << 24) + (i2 << 16) + (i3 << 8) + i4))
|
||||
local gw_int=$(((g1 << 24) + (g2 << 16) + (g3 << 8) + g4))
|
||||
|
||||
# Check if both are in same network
|
||||
if (( (ip_int & mask) != (gw_int & mask) )); then
|
||||
if (((ip_int & mask) != (gw_int & mask))); then
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -1079,117 +1080,117 @@ load_vars_file() {
|
||||
# Validate values before setting (skip empty values - they use defaults)
|
||||
if [[ -n "$var_val" ]]; then
|
||||
case "$var_key" in
|
||||
var_mac)
|
||||
if ! validate_mac_address "$var_val"; then
|
||||
msg_warn "Invalid MAC address '$var_val' in $file, ignoring"
|
||||
var_mac)
|
||||
if ! validate_mac_address "$var_val"; then
|
||||
msg_warn "Invalid MAC address '$var_val' in $file, ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_vlan)
|
||||
if ! validate_vlan_tag "$var_val"; then
|
||||
msg_warn "Invalid VLAN tag '$var_val' in $file (must be 1-4094), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_mtu)
|
||||
if ! validate_mtu "$var_val"; then
|
||||
msg_warn "Invalid MTU '$var_val' in $file (must be 576-65535), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_tags)
|
||||
if ! validate_tags "$var_val"; then
|
||||
msg_warn "Invalid tags '$var_val' in $file (alphanumeric, -, _, ; only), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_timezone)
|
||||
if ! validate_timezone "$var_val"; then
|
||||
msg_warn "Invalid timezone '$var_val' in $file, ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_brg)
|
||||
if ! validate_bridge "$var_val"; then
|
||||
msg_warn "Bridge '$var_val' not found in $file, ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_gateway)
|
||||
if ! validate_gateway_ip "$var_val"; then
|
||||
msg_warn "Invalid gateway IP '$var_val' in $file, ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_hostname)
|
||||
if ! validate_hostname "$var_val"; then
|
||||
msg_warn "Invalid hostname '$var_val' in $file, ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_cpu)
|
||||
if ! [[ "$var_val" =~ ^[0-9]+$ ]] || ((var_val < 1 || var_val > 128)); then
|
||||
msg_warn "Invalid CPU count '$var_val' in $file (must be 1-128), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_ram)
|
||||
if ! [[ "$var_val" =~ ^[0-9]+$ ]] || ((var_val < 256)); then
|
||||
msg_warn "Invalid RAM '$var_val' in $file (must be >= 256 MiB), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_disk)
|
||||
if ! [[ "$var_val" =~ ^[0-9]+$ ]] || ((var_val < 1)); then
|
||||
msg_warn "Invalid disk size '$var_val' in $file (must be >= 1 GB), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_unprivileged)
|
||||
if [[ "$var_val" != "0" && "$var_val" != "1" ]]; then
|
||||
msg_warn "Invalid unprivileged value '$var_val' in $file (must be 0 or 1), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_nesting)
|
||||
if [[ "$var_val" != "0" && "$var_val" != "1" ]]; then
|
||||
msg_warn "Invalid nesting value '$var_val' in $file (must be 0 or 1), ignoring"
|
||||
continue
|
||||
fi
|
||||
# Warn about potential issues with systemd-based OS when nesting is disabled via vars file
|
||||
if [[ "$var_val" == "0" && "${var_os:-debian}" != "alpine" ]]; then
|
||||
msg_warn "Nesting disabled in $file - modern systemd-based distributions may require nesting for proper operation"
|
||||
fi
|
||||
;;
|
||||
var_keyctl)
|
||||
if [[ "$var_val" != "0" && "$var_val" != "1" ]]; then
|
||||
msg_warn "Invalid keyctl value '$var_val' in $file (must be 0 or 1), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_net)
|
||||
# var_net can be: dhcp, static IP/CIDR, or IP range
|
||||
if [[ "$var_val" != "dhcp" ]]; then
|
||||
if is_ip_range "$var_val"; then
|
||||
: # IP range is valid, will be resolved at runtime
|
||||
elif ! validate_ip_address "$var_val"; then
|
||||
msg_warn "Invalid network '$var_val' in $file (must be dhcp or IP/CIDR), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_vlan)
|
||||
if ! validate_vlan_tag "$var_val"; then
|
||||
msg_warn "Invalid VLAN tag '$var_val' in $file (must be 1-4094), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_mtu)
|
||||
if ! validate_mtu "$var_val"; then
|
||||
msg_warn "Invalid MTU '$var_val' in $file (must be 576-65535), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_tags)
|
||||
if ! validate_tags "$var_val"; then
|
||||
msg_warn "Invalid tags '$var_val' in $file (alphanumeric, -, _, ; only), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_timezone)
|
||||
if ! validate_timezone "$var_val"; then
|
||||
msg_warn "Invalid timezone '$var_val' in $file, ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_brg)
|
||||
if ! validate_bridge "$var_val"; then
|
||||
msg_warn "Bridge '$var_val' not found in $file, ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_gateway)
|
||||
if ! validate_gateway_ip "$var_val"; then
|
||||
msg_warn "Invalid gateway IP '$var_val' in $file, ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_hostname)
|
||||
if ! validate_hostname "$var_val"; then
|
||||
msg_warn "Invalid hostname '$var_val' in $file, ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_cpu)
|
||||
if ! [[ "$var_val" =~ ^[0-9]+$ ]] || ((var_val < 1 || var_val > 128)); then
|
||||
msg_warn "Invalid CPU count '$var_val' in $file (must be 1-128), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_ram)
|
||||
if ! [[ "$var_val" =~ ^[0-9]+$ ]] || ((var_val < 256)); then
|
||||
msg_warn "Invalid RAM '$var_val' in $file (must be >= 256 MiB), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_disk)
|
||||
if ! [[ "$var_val" =~ ^[0-9]+$ ]] || ((var_val < 1)); then
|
||||
msg_warn "Invalid disk size '$var_val' in $file (must be >= 1 GB), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_unprivileged)
|
||||
if [[ "$var_val" != "0" && "$var_val" != "1" ]]; then
|
||||
msg_warn "Invalid unprivileged value '$var_val' in $file (must be 0 or 1), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_nesting)
|
||||
if [[ "$var_val" != "0" && "$var_val" != "1" ]]; then
|
||||
msg_warn "Invalid nesting value '$var_val' in $file (must be 0 or 1), ignoring"
|
||||
continue
|
||||
fi
|
||||
# Warn about potential issues with systemd-based OS when nesting is disabled via vars file
|
||||
if [[ "$var_val" == "0" && "${var_os:-debian}" != "alpine" ]]; then
|
||||
msg_warn "Nesting disabled in $file - modern systemd-based distributions may require nesting for proper operation"
|
||||
fi
|
||||
;;
|
||||
var_keyctl)
|
||||
if [[ "$var_val" != "0" && "$var_val" != "1" ]]; then
|
||||
msg_warn "Invalid keyctl value '$var_val' in $file (must be 0 or 1), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_net)
|
||||
# var_net can be: dhcp, static IP/CIDR, or IP range
|
||||
if [[ "$var_val" != "dhcp" ]]; then
|
||||
if is_ip_range "$var_val"; then
|
||||
: # IP range is valid, will be resolved at runtime
|
||||
elif ! validate_ip_address "$var_val"; then
|
||||
msg_warn "Invalid network '$var_val' in $file (must be dhcp or IP/CIDR), ignoring"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
var_fuse|var_tun|var_gpu|var_ssh|var_verbose|var_protection)
|
||||
if [[ "$var_val" != "yes" && "$var_val" != "no" ]]; then
|
||||
msg_warn "Invalid boolean '$var_val' for $var_key in $file (must be yes/no), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_ipv6_method)
|
||||
if [[ "$var_val" != "auto" && "$var_val" != "dhcp" && "$var_val" != "static" && "$var_val" != "none" ]]; then
|
||||
msg_warn "Invalid IPv6 method '$var_val' in $file (must be auto/dhcp/static/none), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
fi
|
||||
;;
|
||||
var_fuse | var_tun | var_gpu | var_ssh | var_verbose | var_protection)
|
||||
if [[ "$var_val" != "yes" && "$var_val" != "no" ]]; then
|
||||
msg_warn "Invalid boolean '$var_val' for $var_key in $file (must be yes/no), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_ipv6_method)
|
||||
if [[ "$var_val" != "auto" && "$var_val" != "dhcp" && "$var_val" != "static" && "$var_val" != "none" ]]; then
|
||||
msg_warn "Invalid IPv6 method '$var_val' in $file (must be auto/dhcp/static/none), ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
@@ -2764,6 +2765,26 @@ Advanced:
|
||||
[[ "$APT_CACHER" == "yes" ]] && echo -e "${INFO}${BOLD}${DGN}APT Cacher: ${BGN}$APT_CACHER_IP${CL}"
|
||||
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERBOSE${CL}"
|
||||
echo -e "${CREATING}${BOLD}${RD}Creating an LXC of ${APP} using the above advanced settings${CL}"
|
||||
|
||||
# Log settings to file
|
||||
log_section "CONTAINER SETTINGS (ADVANCED) - ${APP}"
|
||||
log_msg "Application: ${APP}"
|
||||
log_msg "PVE Version: ${PVEVERSION} (Kernel: ${KERNEL_VERSION})"
|
||||
log_msg "Operating System: $var_os ($var_version)"
|
||||
log_msg "Container Type: $([ "$CT_TYPE" == "1" ] && echo "Unprivileged" || echo "Privileged")"
|
||||
log_msg "Container ID: $CT_ID"
|
||||
log_msg "Hostname: $HN"
|
||||
log_msg "Disk Size: ${DISK_SIZE} GB"
|
||||
log_msg "CPU Cores: $CORE_COUNT"
|
||||
log_msg "RAM Size: ${RAM_SIZE} MiB"
|
||||
log_msg "Bridge: $BRG"
|
||||
log_msg "IPv4: $NET"
|
||||
log_msg "IPv6: $IPV6_METHOD"
|
||||
log_msg "FUSE Support: ${ENABLE_FUSE:-no}"
|
||||
log_msg "Nesting: $([ "${ENABLE_NESTING:-1}" == "1" ] && echo "Enabled" || echo "Disabled")"
|
||||
log_msg "GPU Passthrough: ${ENABLE_GPU:-no}"
|
||||
log_msg "Verbose Mode: $VERBOSE"
|
||||
log_msg "Session ID: ${SESSION_ID}"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
@@ -2871,6 +2892,7 @@ diagnostics_menu() {
|
||||
# - Prints summary of default values (ID, OS, type, disk, RAM, CPU, etc.)
|
||||
# - Uses icons and formatting for readability
|
||||
# - Convert CT_TYPE to description
|
||||
# - Also logs settings to log file for debugging
|
||||
# ------------------------------------------------------------------------------
|
||||
echo_default() {
|
||||
CT_TYPE_DESC="Unprivileged"
|
||||
@@ -2892,6 +2914,20 @@ echo_default() {
|
||||
fi
|
||||
echo -e "${CREATING}${BOLD}${BL}Creating a ${APP} LXC using the above default settings${CL}"
|
||||
echo -e " "
|
||||
|
||||
# Log settings to file
|
||||
log_section "CONTAINER SETTINGS - ${APP}"
|
||||
log_msg "Application: ${APP}"
|
||||
log_msg "PVE Version: ${PVEVERSION} (Kernel: ${KERNEL_VERSION})"
|
||||
log_msg "Container ID: ${CT_ID}"
|
||||
log_msg "Operating System: $var_os ($var_version)"
|
||||
log_msg "Container Type: $CT_TYPE_DESC"
|
||||
log_msg "Disk Size: ${DISK_SIZE} GB"
|
||||
log_msg "CPU Cores: ${CORE_COUNT}"
|
||||
log_msg "RAM Size: ${RAM_SIZE} MiB"
|
||||
[[ -n "${var_gpu:-}" && "${var_gpu}" == "yes" ]] && log_msg "GPU Passthrough: Enabled"
|
||||
[[ "$VERBOSE" == "yes" ]] && log_msg "Verbose Mode: Enabled"
|
||||
log_msg "Session ID: ${SESSION_ID}"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -4049,30 +4085,55 @@ EOF'
|
||||
# Copy install log from container BEFORE API call so get_error_text() can read it
|
||||
local build_log_copied=false
|
||||
local install_log_copied=false
|
||||
local host_install_log="/tmp/install-lxc-${CTID}-${SESSION_ID}.log"
|
||||
local combined_log="/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log"
|
||||
|
||||
if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then
|
||||
# Copy BUILD_LOG (creation log) if it exists
|
||||
# Create combined log with header
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "COMBINED INSTALLATION LOG - ${APP:-LXC}"
|
||||
echo "Container ID: ${CTID}"
|
||||
echo "Session ID: ${SESSION_ID}"
|
||||
echo "Timestamp: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "================================================================================"
|
||||
echo ""
|
||||
} >"$combined_log"
|
||||
|
||||
# Append BUILD_LOG (host-side creation log) if it exists
|
||||
if [[ -f "${BUILD_LOG}" ]]; then
|
||||
cp "${BUILD_LOG}" "/tmp/create-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null && build_log_copied=true
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "PHASE 1: CONTAINER CREATION (Host)"
|
||||
echo "================================================================================"
|
||||
cat "${BUILD_LOG}"
|
||||
echo ""
|
||||
} >>"$combined_log"
|
||||
build_log_copied=true
|
||||
fi
|
||||
|
||||
# Copy INSTALL_LOG from container to host
|
||||
if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "$host_install_log" 2>/dev/null; then
|
||||
# Copy and append INSTALL_LOG from container
|
||||
local temp_install_log="/tmp/.install-temp-${SESSION_ID}.log"
|
||||
if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "$temp_install_log" 2>/dev/null; then
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "PHASE 2: APPLICATION INSTALLATION (Container)"
|
||||
echo "================================================================================"
|
||||
cat "$temp_install_log"
|
||||
echo ""
|
||||
} >>"$combined_log"
|
||||
rm -f "$temp_install_log"
|
||||
install_log_copied=true
|
||||
# Point INSTALL_LOG to host copy so get_error_text() finds it
|
||||
INSTALL_LOG="$host_install_log"
|
||||
# Point INSTALL_LOG to combined log so get_error_text() finds it
|
||||
INSTALL_LOG="$combined_log"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Report failure to telemetry API (now with log available on host)
|
||||
post_update_to_api "failed" "$install_exit_code"
|
||||
|
||||
# Show available logs
|
||||
# Show combined log location
|
||||
if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then
|
||||
echo ""
|
||||
[[ "$build_log_copied" == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}"
|
||||
[[ "$install_log_copied" == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}${host_install_log}${CL}"
|
||||
msg_custom "📋" "${YW}" "Installation log: ${combined_log}"
|
||||
fi
|
||||
|
||||
# Dev mode: Keep container or open breakpoint shell
|
||||
@@ -4095,19 +4156,21 @@ EOF'
|
||||
exit $install_exit_code
|
||||
fi
|
||||
|
||||
# Prompt user for cleanup with 60s timeout (plain echo - no msg_info to avoid spinner)
|
||||
# Prompt user for cleanup with 60s timeout
|
||||
echo ""
|
||||
echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
|
||||
echo -en "${TAB}❓${TAB}${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
|
||||
|
||||
if read -t 60 -r response; then
|
||||
if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
|
||||
# Remove container
|
||||
echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID}${CL}"
|
||||
echo ""
|
||||
msg_info "Removing container ${CTID}"
|
||||
pct stop "$CTID" &>/dev/null || true
|
||||
pct destroy "$CTID" &>/dev/null || true
|
||||
echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}"
|
||||
msg_ok "Container ${CTID} removed"
|
||||
elif [[ "$response" =~ ^[Nn]$ ]]; then
|
||||
echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}"
|
||||
echo ""
|
||||
msg_warn "Container ${CTID} kept for debugging"
|
||||
|
||||
# Dev mode: Setup MOTD/SSH for debugging access to broken container
|
||||
if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then
|
||||
@@ -4123,11 +4186,11 @@ EOF'
|
||||
fi
|
||||
else
|
||||
# Timeout - auto-remove
|
||||
echo -e "\n${YW}No response - auto-removing container${CL}"
|
||||
echo -e "${TAB}${HOLD}${YW}Removing container ${CTID}${CL}"
|
||||
echo ""
|
||||
msg_info "No response - removing container ${CTID}"
|
||||
pct stop "$CTID" &>/dev/null || true
|
||||
pct destroy "$CTID" &>/dev/null || true
|
||||
echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}"
|
||||
msg_ok "Container ${CTID} removed"
|
||||
fi
|
||||
|
||||
# Force one final status update attempt after cleanup
|
||||
@@ -5137,6 +5200,61 @@ EOF
|
||||
# SECTION 10: ERROR HANDLING & EXIT TRAPS
|
||||
# ==============================================================================
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ensure_log_on_host()
|
||||
#
|
||||
# - Ensures INSTALL_LOG points to a readable file on the host
|
||||
# - If INSTALL_LOG points to a container path (e.g. /root/.install-*),
|
||||
# tries to pull it from the container and create a combined log
|
||||
# - This allows get_error_text() to find actual error output for telemetry
|
||||
# ------------------------------------------------------------------------------
|
||||
ensure_log_on_host() {
|
||||
# Already readable on host? Nothing to do.
|
||||
[[ -n "${INSTALL_LOG:-}" && -s "${INSTALL_LOG}" ]] && return 0
|
||||
|
||||
# Try pulling from container and creating combined log
|
||||
if [[ -n "${CTID:-}" && -n "${SESSION_ID:-}" ]] && command -v pct &>/dev/null; then
|
||||
local combined_log="/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log"
|
||||
if [[ ! -s "$combined_log" ]]; then
|
||||
# Create combined log
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "COMBINED INSTALLATION LOG - ${APP:-LXC}"
|
||||
echo "Container ID: ${CTID}"
|
||||
echo "Session ID: ${SESSION_ID}"
|
||||
echo "Timestamp: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "================================================================================"
|
||||
echo ""
|
||||
} >"$combined_log" 2>/dev/null || return 0
|
||||
# Append BUILD_LOG if it exists
|
||||
if [[ -f "${BUILD_LOG:-}" ]]; then
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "PHASE 1: CONTAINER CREATION (Host)"
|
||||
echo "================================================================================"
|
||||
cat "${BUILD_LOG}"
|
||||
echo ""
|
||||
} >>"$combined_log"
|
||||
fi
|
||||
# Pull INSTALL_LOG from container
|
||||
local temp_log="/tmp/.install-temp-${SESSION_ID}.log"
|
||||
if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "$temp_log" 2>/dev/null; then
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "PHASE 2: APPLICATION INSTALLATION (Container)"
|
||||
echo "================================================================================"
|
||||
cat "$temp_log"
|
||||
echo ""
|
||||
} >>"$combined_log"
|
||||
rm -f "$temp_log"
|
||||
fi
|
||||
fi
|
||||
if [[ -s "$combined_log" ]]; then
|
||||
INSTALL_LOG="$combined_log"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# api_exit_script()
|
||||
#
|
||||
@@ -5149,6 +5267,7 @@ EOF
|
||||
api_exit_script() {
|
||||
exit_code=$?
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
ensure_log_on_host
|
||||
post_update_to_api "failed" "$exit_code"
|
||||
fi
|
||||
}
|
||||
@@ -5156,6 +5275,6 @@ api_exit_script() {
|
||||
if command -v pveversion >/dev/null 2>&1; then
|
||||
trap 'api_exit_script' EXIT
|
||||
fi
|
||||
trap 'post_update_to_api "failed" "$?"' ERR
|
||||
trap 'post_update_to_api "failed" "130"' SIGINT
|
||||
trap 'post_update_to_api "failed" "143"' SIGTERM
|
||||
trap 'ensure_log_on_host; post_update_to_api "failed" "$?"' ERR
|
||||
trap 'ensure_log_on_host; post_update_to_api "failed" "130"' SIGINT
|
||||
trap 'ensure_log_on_host; post_update_to_api "failed" "143"' SIGTERM
|
||||
|
||||
652
misc/core.func
652
misc/core.func
@@ -115,7 +115,7 @@ icons() {
|
||||
BRIDGE="${TAB}🌉${TAB}${CL}"
|
||||
NETWORK="${TAB}📡${TAB}${CL}"
|
||||
GATEWAY="${TAB}🌐${TAB}${CL}"
|
||||
DISABLEIPV6="${TAB}🚫${TAB}${CL}"
|
||||
ICON_DISABLEIPV6="${TAB}🚫${TAB}${CL}"
|
||||
DEFAULT="${TAB}⚙️${TAB}${CL}"
|
||||
MACADDRESS="${TAB}🔗${TAB}${CL}"
|
||||
VLANTAG="${TAB}🏷️${TAB}${CL}"
|
||||
@@ -413,6 +413,69 @@ get_active_logfile() {
|
||||
# Legacy compatibility: SILENT_LOGFILE points to active log
|
||||
SILENT_LOGFILE="$(get_active_logfile)"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# strip_ansi()
|
||||
#
|
||||
# - Removes ANSI escape sequences from input text
|
||||
# - Used to clean colored output for log files
|
||||
# - Handles both piped input and arguments
|
||||
# ------------------------------------------------------------------------------
|
||||
strip_ansi() {
|
||||
if [[ $# -gt 0 ]]; then
|
||||
echo -e "$*" | sed 's/\x1b\[[0-9;]*m//g; s/\x1b\[[0-9;]*[a-zA-Z]//g'
|
||||
else
|
||||
sed 's/\x1b\[[0-9;]*m//g; s/\x1b\[[0-9;]*[a-zA-Z]//g'
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# log_msg()
|
||||
#
|
||||
# - Writes message to active log file without ANSI codes
|
||||
# - Adds timestamp prefix for log correlation
|
||||
# - Creates log file if it doesn't exist
|
||||
# - Arguments: message text (can include ANSI codes, will be stripped)
|
||||
# ------------------------------------------------------------------------------
|
||||
log_msg() {
|
||||
local msg="$*"
|
||||
local logfile
|
||||
logfile="$(get_active_logfile)"
|
||||
|
||||
[[ -z "$msg" ]] && return
|
||||
[[ -z "$logfile" ]] && return
|
||||
|
||||
# Ensure log directory exists
|
||||
mkdir -p "$(dirname "$logfile")" 2>/dev/null || true
|
||||
|
||||
# Strip ANSI codes and write with timestamp
|
||||
local clean_msg
|
||||
clean_msg=$(strip_ansi "$msg")
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $clean_msg" >>"$logfile"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# log_section()
|
||||
#
|
||||
# - Writes a section header to the log file
|
||||
# - Used for separating different phases of installation
|
||||
# - Arguments: section name
|
||||
# ------------------------------------------------------------------------------
|
||||
log_section() {
|
||||
local section="$1"
|
||||
local logfile
|
||||
logfile="$(get_active_logfile)"
|
||||
|
||||
[[ -z "$logfile" ]] && return
|
||||
mkdir -p "$(dirname "$logfile")" 2>/dev/null || true
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "================================================================================"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $section"
|
||||
echo "================================================================================"
|
||||
} >>"$logfile"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# silent()
|
||||
#
|
||||
@@ -459,15 +522,9 @@ silent() {
|
||||
msg_custom "→" "${YWB}" "${cmd}"
|
||||
|
||||
if [[ -s "$logfile" ]]; then
|
||||
local log_lines=$(wc -l <"$logfile")
|
||||
echo "--- Last 10 lines of silent log ---"
|
||||
echo -e "\n${TAB}--- Last 10 lines of log ---"
|
||||
tail -n 10 "$logfile"
|
||||
echo "-----------------------------------"
|
||||
|
||||
# Show how to view full log if there are more lines
|
||||
if [[ $log_lines -gt 10 ]]; then
|
||||
msg_custom "📋" "${YW}" "View full log (${log_lines} lines): ${logfile}"
|
||||
fi
|
||||
echo -e "${TAB}-----------------------------------\n"
|
||||
fi
|
||||
|
||||
exit "$rc"
|
||||
@@ -488,7 +545,7 @@ spinner() {
|
||||
local i=0
|
||||
while true; do
|
||||
local index=$((i++ % ${#chars[@]}))
|
||||
printf "\r\033[2K%s %b" "${CS_YWB}${TAB}${chars[$index]}${TAB}${CS_CL}" "${CS_YWB}${msg}${CS_CL}"
|
||||
printf "\r\033[2K%s %b" "${CS_YWB}${chars[$index]}${CS_CL}" "${CS_YWB}${msg}${CS_CL}"
|
||||
sleep 0.1
|
||||
done
|
||||
}
|
||||
@@ -555,6 +612,9 @@ msg_info() {
|
||||
[[ -n "${MSG_INFO_SHOWN["$msg"]+x}" ]] && return
|
||||
MSG_INFO_SHOWN["$msg"]=1
|
||||
|
||||
# Log to file
|
||||
log_msg "[INFO] $msg"
|
||||
|
||||
stop_spinner
|
||||
SPINNER_MSG="$msg"
|
||||
|
||||
@@ -598,7 +658,10 @@ msg_ok() {
|
||||
stop_spinner
|
||||
clear_line
|
||||
echo -e "$CM ${GN}${msg}${CL}"
|
||||
unset MSG_INFO_SHOWN["$msg"]
|
||||
log_msg "[OK] $msg"
|
||||
local sanitized_msg
|
||||
sanitized_msg=$(printf '%s' "$msg" | sed 's/\x1b\[[0-9;]*m//g; s/[^a-zA-Z0-9_]/_/g')
|
||||
unset 'MSG_INFO_SHOWN['"$sanitized_msg"']' 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -613,6 +676,7 @@ msg_error() {
|
||||
stop_spinner
|
||||
local msg="$1"
|
||||
echo -e "${BFR:-}${CROSS:-✖️} ${RD}${msg}${CL}" >&2
|
||||
log_msg "[ERROR] $msg"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -627,6 +691,7 @@ msg_warn() {
|
||||
stop_spinner
|
||||
local msg="$1"
|
||||
echo -e "${BFR:-}${INFO:-ℹ️} ${YWB}${msg}${CL}" >&2
|
||||
log_msg "[WARN] $msg"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -644,6 +709,7 @@ msg_custom() {
|
||||
[[ -z "$msg" ]] && return
|
||||
stop_spinner
|
||||
echo -e "${BFR:-} ${symbol} ${color}${msg}${CL:-\e[0m}"
|
||||
log_msg "$msg"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -808,6 +874,562 @@ is_verbose_mode() {
|
||||
[[ "$verbose" != "no" || ! -t 2 ]]
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# is_unattended()
|
||||
#
|
||||
# - Detects if script is running in unattended/non-interactive mode
|
||||
# - Checks MODE variable first (primary method)
|
||||
# - Falls back to legacy flags (PHS_SILENT, var_unattended)
|
||||
# - Returns 0 (true) if unattended, 1 (false) otherwise
|
||||
# - Used by prompt functions to auto-apply defaults
|
||||
#
|
||||
# Modes that are unattended:
|
||||
# - default (1) : Use script defaults, no prompts
|
||||
# - mydefaults (3) : Use user's default.vars, no prompts
|
||||
# - appdefaults (4) : Use app-specific defaults, no prompts
|
||||
#
|
||||
# Modes that are interactive:
|
||||
# - advanced (2) : Full wizard with all options
|
||||
#
|
||||
# Note: Even in advanced mode, install scripts run unattended because
|
||||
# all values are already collected during the wizard phase.
|
||||
# ------------------------------------------------------------------------------
|
||||
is_unattended() {
|
||||
# Primary: Check MODE variable (case-insensitive)
|
||||
local mode="${MODE:-${mode:-}}"
|
||||
mode="${mode,,}" # lowercase
|
||||
|
||||
case "$mode" in
|
||||
default | 1)
|
||||
return 0
|
||||
;;
|
||||
mydefaults | userdefaults | 3)
|
||||
return 0
|
||||
;;
|
||||
appdefaults | 4)
|
||||
return 0
|
||||
;;
|
||||
advanced | 2)
|
||||
# Advanced mode is interactive ONLY during wizard
|
||||
# Inside container (install scripts), it should be unattended
|
||||
# Check if we're inside a container (no pveversion command)
|
||||
if ! command -v pveversion &>/dev/null; then
|
||||
# We're inside the container - all values already collected
|
||||
return 0
|
||||
fi
|
||||
# On host during wizard - interactive
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Legacy fallbacks for compatibility
|
||||
[[ "${PHS_SILENT:-0}" == "1" ]] && return 0
|
||||
[[ "${var_unattended:-}" =~ ^(yes|true|1)$ ]] && return 0
|
||||
[[ "${UNATTENDED:-}" =~ ^(yes|true|1)$ ]] && return 0
|
||||
|
||||
# No TTY available = unattended
|
||||
[[ ! -t 0 ]] && return 0
|
||||
|
||||
# Default: interactive
|
||||
return 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# show_missing_values_warning()
|
||||
#
|
||||
# - Displays a summary of required values that used fallback defaults
|
||||
# - Should be called at the end of install scripts
|
||||
# - Only shows warning if MISSING_REQUIRED_VALUES array has entries
|
||||
# - Provides clear guidance on what needs manual configuration
|
||||
#
|
||||
# Global:
|
||||
# MISSING_REQUIRED_VALUES - Array of variable names that need configuration
|
||||
#
|
||||
# Example:
|
||||
# # At end of install script:
|
||||
# show_missing_values_warning
|
||||
# ------------------------------------------------------------------------------
|
||||
show_missing_values_warning() {
|
||||
if [[ ${#MISSING_REQUIRED_VALUES[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo -e "${YW}╔════════════════════════════════════════════════════════════╗${CL}"
|
||||
echo -e "${YW}║ ⚠️ MANUAL CONFIGURATION REQUIRED ║${CL}"
|
||||
echo -e "${YW}╠════════════════════════════════════════════════════════════╣${CL}"
|
||||
echo -e "${YW}║ The following values were not provided and need to be ║${CL}"
|
||||
echo -e "${YW}║ configured manually for the service to work properly: ║${CL}"
|
||||
echo -e "${YW}╟────────────────────────────────────────────────────────────╢${CL}"
|
||||
for val in "${MISSING_REQUIRED_VALUES[@]}"; do
|
||||
printf "${YW}║${CL} • %-56s ${YW}║${CL}\n" "$val"
|
||||
done
|
||||
echo -e "${YW}╟────────────────────────────────────────────────────────────╢${CL}"
|
||||
echo -e "${YW}║ Check the service configuration files or environment ║${CL}"
|
||||
echo -e "${YW}║ variables and update the placeholder values. ║${CL}"
|
||||
echo -e "${YW}╚════════════════════════════════════════════════════════════╝${CL}"
|
||||
echo ""
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# prompt_confirm()
|
||||
#
|
||||
# - Prompts user for yes/no confirmation with timeout and unattended support
|
||||
# - In unattended mode: immediately returns default value
|
||||
# - In interactive mode: waits for user input with configurable timeout
|
||||
# - After timeout: auto-applies default value
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - Prompt message (required)
|
||||
# $2 - Default value: "y" or "n" (optional, default: "n")
|
||||
# $3 - Timeout in seconds (optional, default: 60)
|
||||
#
|
||||
# Returns:
|
||||
# 0 - User confirmed (yes)
|
||||
# 1 - User declined (no) or timeout with default "n"
|
||||
#
|
||||
# Example:
|
||||
# if prompt_confirm "Proceed with installation?" "y" 30; then
|
||||
# echo "Installing..."
|
||||
# fi
|
||||
#
|
||||
# # Unattended: prompt_confirm will use default without waiting
|
||||
# var_unattended=yes
|
||||
# prompt_confirm "Delete files?" "n" && echo "Deleting" || echo "Skipped"
|
||||
# ------------------------------------------------------------------------------
|
||||
prompt_confirm() {
|
||||
local message="${1:-Confirm?}"
|
||||
local default="${2:-n}"
|
||||
local timeout="${3:-60}"
|
||||
local response
|
||||
|
||||
# Normalize default to lowercase
|
||||
default="${default,,}"
|
||||
[[ "$default" != "y" ]] && default="n"
|
||||
|
||||
# Build prompt hint
|
||||
local hint
|
||||
if [[ "$default" == "y" ]]; then
|
||||
hint="[Y/n]"
|
||||
else
|
||||
hint="[y/N]"
|
||||
fi
|
||||
|
||||
# Unattended mode: apply default immediately
|
||||
if is_unattended; then
|
||||
if [[ "$default" == "y" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if running in a TTY
|
||||
if [[ ! -t 0 ]]; then
|
||||
# Not a TTY, use default
|
||||
if [[ "$default" == "y" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Interactive prompt with timeout
|
||||
echo -en "${YW}${message} ${hint} (auto-${default} in ${timeout}s): ${CL}"
|
||||
|
||||
if read -t "$timeout" -r response; then
|
||||
# User provided input
|
||||
response="${response,,}" # lowercase
|
||||
case "$response" in
|
||||
y | yes)
|
||||
return 0
|
||||
;;
|
||||
n | no)
|
||||
return 1
|
||||
;;
|
||||
"")
|
||||
# Empty response, use default
|
||||
if [[ "$default" == "y" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
# Invalid input, use default
|
||||
echo -e "${YW}Invalid response, using default: ${default}${CL}"
|
||||
if [[ "$default" == "y" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# Timeout occurred
|
||||
echo "" # Newline after timeout
|
||||
echo -e "${YW}Timeout - auto-selecting: ${default}${CL}"
|
||||
if [[ "$default" == "y" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# prompt_input()
|
||||
#
|
||||
# - Prompts user for text input with timeout and unattended support
|
||||
# - In unattended mode: immediately returns default value
|
||||
# - In interactive mode: waits for user input with configurable timeout
|
||||
# - After timeout: auto-applies default value
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - Prompt message (required)
|
||||
# $2 - Default value (optional, default: "")
|
||||
# $3 - Timeout in seconds (optional, default: 60)
|
||||
#
|
||||
# Output:
|
||||
# Prints the user input or default value to stdout
|
||||
#
|
||||
# Example:
|
||||
# username=$(prompt_input "Enter username:" "admin" 30)
|
||||
# echo "Using username: $username"
|
||||
#
|
||||
# # With validation
|
||||
# while true; do
|
||||
# port=$(prompt_input "Enter port:" "8080" 30)
|
||||
# [[ "$port" =~ ^[0-9]+$ ]] && break
|
||||
# echo "Invalid port number"
|
||||
# done
|
||||
# ------------------------------------------------------------------------------
|
||||
prompt_input() {
|
||||
local message="${1:-Enter value:}"
|
||||
local default="${2:-}"
|
||||
local timeout="${3:-60}"
|
||||
local response
|
||||
|
||||
# Build display default hint
|
||||
local hint=""
|
||||
[[ -n "$default" ]] && hint=" (default: ${default})"
|
||||
|
||||
# Unattended mode: return default immediately
|
||||
if is_unattended; then
|
||||
echo "$default"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if running in a TTY
|
||||
if [[ ! -t 0 ]]; then
|
||||
# Not a TTY, use default
|
||||
echo "$default"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Interactive prompt with timeout
|
||||
echo -en "${YW}${message}${hint} (auto-default in ${timeout}s): ${CL}" >&2
|
||||
|
||||
if read -t "$timeout" -r response; then
|
||||
# User provided input (or pressed Enter for empty)
|
||||
if [[ -n "$response" ]]; then
|
||||
echo "$response"
|
||||
else
|
||||
echo "$default"
|
||||
fi
|
||||
else
|
||||
# Timeout occurred
|
||||
echo "" >&2 # Newline after timeout
|
||||
echo -e "${YW}Timeout - using default: ${default}${CL}" >&2
|
||||
echo "$default"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# prompt_input_required()
|
||||
#
|
||||
# - Prompts user for REQUIRED text input with fallback support
|
||||
# - In unattended mode: Uses fallback value if no env var set (with warning)
|
||||
# - In interactive mode: loops until user provides non-empty input
|
||||
# - Tracks missing required values for end-of-script summary
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - Prompt message (required)
|
||||
# $2 - Fallback/example value for unattended mode (optional)
|
||||
# $3 - Timeout in seconds (optional, default: 120)
|
||||
# $4 - Environment variable name hint for error messages (optional)
|
||||
#
|
||||
# Output:
|
||||
# Prints the user input or fallback value to stdout
|
||||
#
|
||||
# Returns:
|
||||
# 0 - Success (value provided or fallback used)
|
||||
# 1 - Failed (interactive timeout without input)
|
||||
#
|
||||
# Global:
|
||||
# MISSING_REQUIRED_VALUES - Array tracking fields that used fallbacks
|
||||
#
|
||||
# Example:
|
||||
# # With fallback - script continues even in unattended mode
|
||||
# token=$(prompt_input_required "Enter API Token:" "YOUR_TOKEN_HERE" 60 "var_api_token")
|
||||
#
|
||||
# # Check at end of script if any values need manual configuration
|
||||
# if [[ ${#MISSING_REQUIRED_VALUES[@]} -gt 0 ]]; then
|
||||
# msg_warn "Please configure: ${MISSING_REQUIRED_VALUES[*]}"
|
||||
# fi
|
||||
# ------------------------------------------------------------------------------
|
||||
# Global array to track missing required values
|
||||
declare -g -a MISSING_REQUIRED_VALUES=()
|
||||
|
||||
prompt_input_required() {
|
||||
local message="${1:-Enter required value:}"
|
||||
local fallback="${2:-CHANGE_ME}"
|
||||
local timeout="${3:-120}"
|
||||
local env_var_hint="${4:-}"
|
||||
local response=""
|
||||
|
||||
# Check if value is already set via environment variable (if hint provided)
|
||||
if [[ -n "$env_var_hint" ]]; then
|
||||
local env_value="${!env_var_hint:-}"
|
||||
if [[ -n "$env_value" ]]; then
|
||||
echo "$env_value"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Unattended mode: use fallback with warning
|
||||
if is_unattended; then
|
||||
if [[ -n "$env_var_hint" ]]; then
|
||||
echo -e "${YW}⚠ Required value '${env_var_hint}' not set - using fallback: ${fallback}${CL}" >&2
|
||||
MISSING_REQUIRED_VALUES+=("$env_var_hint")
|
||||
else
|
||||
echo -e "${YW}⚠ Required value not provided - using fallback: ${fallback}${CL}" >&2
|
||||
MISSING_REQUIRED_VALUES+=("(unnamed)")
|
||||
fi
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if running in a TTY
|
||||
if [[ ! -t 0 ]]; then
|
||||
echo -e "${YW}⚠ Not interactive - using fallback: ${fallback}${CL}" >&2
|
||||
MISSING_REQUIRED_VALUES+=("${env_var_hint:-unnamed}")
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Interactive prompt - loop until non-empty input or use fallback on timeout
|
||||
local attempts=0
|
||||
while [[ -z "$response" ]]; do
|
||||
attempts=$((attempts + 1))
|
||||
|
||||
if [[ $attempts -gt 3 ]]; then
|
||||
echo -e "${YW}Too many empty inputs - using fallback: ${fallback}${CL}" >&2
|
||||
MISSING_REQUIRED_VALUES+=("${env_var_hint:-manual_input}")
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -en "${YW}${message} (required, timeout ${timeout}s): ${CL}" >&2
|
||||
|
||||
if read -t "$timeout" -r response; then
|
||||
if [[ -z "$response" ]]; then
|
||||
echo -e "${YW}This field is required. Please enter a value. (attempt ${attempts}/3)${CL}" >&2
|
||||
fi
|
||||
else
|
||||
# Timeout occurred - use fallback
|
||||
echo "" >&2
|
||||
echo -e "${YW}Timeout - using fallback value: ${fallback}${CL}" >&2
|
||||
MISSING_REQUIRED_VALUES+=("${env_var_hint:-timeout}")
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$response"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# prompt_select()
|
||||
#
|
||||
# - Prompts user to select from a list of options with timeout support
|
||||
# - In unattended mode: immediately returns default selection
|
||||
# - In interactive mode: displays numbered menu and waits for choice
|
||||
# - After timeout: auto-applies default selection
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - Prompt message (required)
|
||||
# $2 - Default option number, 1-based (optional, default: 1)
|
||||
# $3 - Timeout in seconds (optional, default: 60)
|
||||
# $4+ - Options to display (required, at least 2)
|
||||
#
|
||||
# Output:
|
||||
# Prints the selected option value to stdout
|
||||
#
|
||||
# Returns:
|
||||
# 0 - Success
|
||||
# 1 - No options provided or invalid state
|
||||
#
|
||||
# Example:
|
||||
# choice=$(prompt_select "Select database:" 1 30 "PostgreSQL" "MySQL" "SQLite")
|
||||
# echo "Selected: $choice"
|
||||
#
|
||||
# # With array
|
||||
# options=("Option A" "Option B" "Option C")
|
||||
# selected=$(prompt_select "Choose:" 2 60 "${options[@]}")
|
||||
# ------------------------------------------------------------------------------
|
||||
prompt_select() {
|
||||
local message="${1:-Select option:}"
|
||||
local default="${2:-1}"
|
||||
local timeout="${3:-60}"
|
||||
shift 3
|
||||
|
||||
local options=("$@")
|
||||
local num_options=${#options[@]}
|
||||
|
||||
# Validate options
|
||||
if [[ $num_options -eq 0 ]]; then
|
||||
echo "" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate default
|
||||
if [[ ! "$default" =~ ^[0-9]+$ ]] || [[ "$default" -lt 1 ]] || [[ "$default" -gt "$num_options" ]]; then
|
||||
default=1
|
||||
fi
|
||||
|
||||
# Unattended mode: return default immediately
|
||||
if is_unattended; then
|
||||
echo "${options[$((default - 1))]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if running in a TTY
|
||||
if [[ ! -t 0 ]]; then
|
||||
echo "${options[$((default - 1))]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Display menu
|
||||
echo -e "${YW}${message}${CL}" >&2
|
||||
local i
|
||||
for i in "${!options[@]}"; do
|
||||
local num=$((i + 1))
|
||||
if [[ $num -eq $default ]]; then
|
||||
echo -e " ${GN}${num})${CL} ${options[$i]} ${YW}(default)${CL}" >&2
|
||||
else
|
||||
echo -e " ${GN}${num})${CL} ${options[$i]}" >&2
|
||||
fi
|
||||
done
|
||||
|
||||
# Interactive prompt with timeout
|
||||
echo -en "${YW}Select [1-${num_options}] (auto-select ${default} in ${timeout}s): ${CL}" >&2
|
||||
|
||||
local response
|
||||
if read -t "$timeout" -r response; then
|
||||
if [[ -z "$response" ]]; then
|
||||
# Empty response, use default
|
||||
echo "${options[$((default - 1))]}"
|
||||
elif [[ "$response" =~ ^[0-9]+$ ]] && [[ "$response" -ge 1 ]] && [[ "$response" -le "$num_options" ]]; then
|
||||
# Valid selection
|
||||
echo "${options[$((response - 1))]}"
|
||||
else
|
||||
# Invalid input, use default
|
||||
echo -e "${YW}Invalid selection, using default: ${options[$((default - 1))]}${CL}" >&2
|
||||
echo "${options[$((default - 1))]}"
|
||||
fi
|
||||
else
|
||||
# Timeout occurred
|
||||
echo "" >&2 # Newline after timeout
|
||||
echo -e "${YW}Timeout - auto-selecting: ${options[$((default - 1))]}${CL}" >&2
|
||||
echo "${options[$((default - 1))]}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# prompt_password()
|
||||
#
|
||||
# - Prompts user for password input with hidden characters
|
||||
# - In unattended mode: returns default or generates random password
|
||||
# - Supports auto-generation of secure passwords
|
||||
# - After timeout: generates random password if allowed
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - Prompt message (required)
|
||||
# $2 - Default value or "generate" for auto-generation (optional)
|
||||
# $3 - Timeout in seconds (optional, default: 60)
|
||||
# $4 - Minimum length for validation (optional, default: 0 = no minimum)
|
||||
#
|
||||
# Output:
|
||||
# Prints the password to stdout
|
||||
#
|
||||
# Example:
|
||||
# password=$(prompt_password "Enter password:" "generate" 30 8)
|
||||
# echo "Password set"
|
||||
#
|
||||
# # Require user input (no default)
|
||||
# db_pass=$(prompt_password "Database password:" "" 60 12)
|
||||
# ------------------------------------------------------------------------------
|
||||
prompt_password() {
|
||||
local message="${1:-Enter password:}"
|
||||
local default="${2:-}"
|
||||
local timeout="${3:-60}"
|
||||
local min_length="${4:-0}"
|
||||
local response
|
||||
|
||||
# Generate random password if requested
|
||||
local generated=""
|
||||
if [[ "$default" == "generate" ]]; then
|
||||
generated=$(openssl rand -base64 16 2>/dev/null | tr -dc 'a-zA-Z0-9' | head -c 16)
|
||||
[[ -z "$generated" ]] && generated=$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 16)
|
||||
default="$generated"
|
||||
fi
|
||||
|
||||
# Unattended mode: return default immediately
|
||||
if is_unattended; then
|
||||
echo "$default"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if running in a TTY
|
||||
if [[ ! -t 0 ]]; then
|
||||
echo "$default"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Build hint
|
||||
local hint=""
|
||||
if [[ -n "$generated" ]]; then
|
||||
hint=" (Enter for auto-generated)"
|
||||
elif [[ -n "$default" ]]; then
|
||||
hint=" (Enter for default)"
|
||||
fi
|
||||
[[ "$min_length" -gt 0 ]] && hint="${hint} [min ${min_length} chars]"
|
||||
|
||||
# Interactive prompt with timeout (silent input)
|
||||
echo -en "${YW}${message}${hint} (timeout ${timeout}s): ${CL}" >&2
|
||||
|
||||
if read -t "$timeout" -rs response; then
|
||||
echo "" >&2 # Newline after hidden input
|
||||
if [[ -n "$response" ]]; then
|
||||
# Validate minimum length
|
||||
if [[ "$min_length" -gt 0 ]] && [[ ${#response} -lt "$min_length" ]]; then
|
||||
echo -e "${YW}Password too short (min ${min_length}), using default${CL}" >&2
|
||||
echo "$default"
|
||||
else
|
||||
echo "$response"
|
||||
fi
|
||||
else
|
||||
echo "$default"
|
||||
fi
|
||||
else
|
||||
# Timeout occurred
|
||||
echo "" >&2 # Newline after timeout
|
||||
echo -e "${YW}Timeout - using generated password${CL}" >&2
|
||||
echo "$default"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# SECTION 6: CLEANUP & MAINTENANCE
|
||||
# ==============================================================================
|
||||
@@ -896,15 +1518,13 @@ check_or_create_swap() {
|
||||
|
||||
msg_error "No active swap detected"
|
||||
|
||||
read -p "Do you want to create a swap file? [y/N]: " create_swap
|
||||
create_swap="${create_swap,,}" # to lowercase
|
||||
|
||||
if [[ "$create_swap" != "y" && "$create_swap" != "yes" ]]; then
|
||||
if ! prompt_confirm "Do you want to create a swap file?" "n" 60; then
|
||||
msg_info "Skipping swap file creation"
|
||||
return 1
|
||||
fi
|
||||
|
||||
read -p "Enter swap size in MB (e.g., 2048 for 2GB): " swap_size_mb
|
||||
local swap_size_mb
|
||||
swap_size_mb=$(prompt_input "Enter swap size in MB (e.g., 2048 for 2GB):" "2048" 60)
|
||||
if ! [[ "$swap_size_mb" =~ ^[0-9]+$ ]]; then
|
||||
msg_error "Invalid size input. Aborting."
|
||||
return 1
|
||||
|
||||
@@ -175,9 +175,9 @@ error_handler() {
|
||||
fi
|
||||
|
||||
if [[ -n "$active_log" && -s "$active_log" ]]; then
|
||||
echo "--- Last 20 lines of silent log ---"
|
||||
echo -e "\n${TAB}--- Last 20 lines of log ---"
|
||||
tail -n 20 "$active_log"
|
||||
echo "-----------------------------------"
|
||||
echo -e "${TAB}-----------------------------------\n"
|
||||
|
||||
# Detect context: Container (INSTALL_LOG set + /root exists) vs Host (BUILD_LOG)
|
||||
if [[ -n "${INSTALL_LOG:-}" && -d /root ]]; then
|
||||
@@ -204,23 +204,50 @@ error_handler() {
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
|
||||
if declare -f msg_custom >/dev/null 2>&1; then
|
||||
echo -en "${TAB}❓${TAB}${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
|
||||
else
|
||||
echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
|
||||
fi
|
||||
|
||||
if read -t 60 -r response; then
|
||||
if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
|
||||
echo -e "\n${YW}Removing container ${CTID}${CL}"
|
||||
echo ""
|
||||
if declare -f msg_info >/dev/null 2>&1; then
|
||||
msg_info "Removing container ${CTID}"
|
||||
else
|
||||
echo -e "${YW}Removing container ${CTID}${CL}"
|
||||
fi
|
||||
pct stop "$CTID" &>/dev/null || true
|
||||
pct destroy "$CTID" &>/dev/null || true
|
||||
echo -e "${GN}✔${CL} Container ${CTID} removed"
|
||||
if declare -f msg_ok >/dev/null 2>&1; then
|
||||
msg_ok "Container ${CTID} removed"
|
||||
else
|
||||
echo -e "${GN}✔${CL} Container ${CTID} removed"
|
||||
fi
|
||||
elif [[ "$response" =~ ^[Nn]$ ]]; then
|
||||
echo -e "\n${YW}Container ${CTID} kept for debugging${CL}"
|
||||
echo ""
|
||||
if declare -f msg_warn >/dev/null 2>&1; then
|
||||
msg_warn "Container ${CTID} kept for debugging"
|
||||
else
|
||||
echo -e "${YW}Container ${CTID} kept for debugging${CL}"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# Timeout - auto-remove
|
||||
echo -e "\n${YW}No response - auto-removing container${CL}"
|
||||
echo ""
|
||||
if declare -f msg_info >/dev/null 2>&1; then
|
||||
msg_info "No response - removing container ${CTID}"
|
||||
else
|
||||
echo -e "${YW}No response - removing container ${CTID}${CL}"
|
||||
fi
|
||||
pct stop "$CTID" &>/dev/null || true
|
||||
pct destroy "$CTID" &>/dev/null || true
|
||||
echo -e "${GN}✔${CL} Container ${CTID} removed"
|
||||
if declare -f msg_ok >/dev/null 2>&1; then
|
||||
msg_ok "Container ${CTID} removed"
|
||||
else
|
||||
echo -e "${GN}✔${CL} Container ${CTID} removed"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Force one final status update attempt after cleanup
|
||||
@@ -254,6 +281,10 @@ on_exit() {
|
||||
# post_to_api was called ("installing" sent) but post_update_to_api was never called
|
||||
if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then
|
||||
if declare -f post_update_to_api >/dev/null 2>&1; then
|
||||
# Ensure log is accessible on host before reporting
|
||||
if declare -f ensure_log_on_host >/dev/null 2>&1; then
|
||||
ensure_log_on_host
|
||||
fi
|
||||
if [[ $exit_code -ne 0 ]]; then
|
||||
post_update_to_api "failed" "$exit_code"
|
||||
else
|
||||
@@ -273,6 +304,10 @@ on_exit() {
|
||||
# - Exits with code 130 (128 + SIGINT=2)
|
||||
# ------------------------------------------------------------------------------
|
||||
on_interrupt() {
|
||||
# Ensure log is accessible on host before reporting
|
||||
if declare -f ensure_log_on_host >/dev/null 2>&1; then
|
||||
ensure_log_on_host
|
||||
fi
|
||||
# Report interruption to telemetry API (prevents stuck "installing" records)
|
||||
if declare -f post_update_to_api >/dev/null 2>&1; then
|
||||
post_update_to_api "failed" "130"
|
||||
@@ -294,6 +329,10 @@ on_interrupt() {
|
||||
# - Triggered by external process termination
|
||||
# ------------------------------------------------------------------------------
|
||||
on_terminate() {
|
||||
# Ensure log is accessible on host before reporting
|
||||
if declare -f ensure_log_on_host >/dev/null 2>&1; then
|
||||
ensure_log_on_host
|
||||
fi
|
||||
# Report termination to telemetry API (prevents stuck "installing" records)
|
||||
if declare -f post_update_to_api >/dev/null 2>&1; then
|
||||
post_update_to_api "failed" "143"
|
||||
|
||||
@@ -207,15 +207,9 @@ silent() {
|
||||
msg_custom "→" "${YWB}" "${cmd}"
|
||||
|
||||
if [[ -s "$logfile" ]]; then
|
||||
local log_lines=$(wc -l <"$logfile")
|
||||
echo "--- Last 10 lines of log ---"
|
||||
echo -e "\n${TAB}--- Last 10 lines of log ---"
|
||||
tail -n 10 "$logfile"
|
||||
echo "----------------------------"
|
||||
|
||||
# Show how to view full log if there are more lines
|
||||
if [[ $log_lines -gt 10 ]]; then
|
||||
msg_custom "📋" "${YW}" "View full log (${log_lines} lines): ${logfile}"
|
||||
fi
|
||||
echo -e "${TAB}----------------------------\n"
|
||||
fi
|
||||
|
||||
exit "$rc"
|
||||
|
||||
Reference in New Issue
Block a user