diff --git a/misc/build.func b/misc/build.func index 8880d9037..927068eb6 100644 --- a/misc/build.func +++ b/misc/build.func @@ -3336,43 +3336,25 @@ create_lxc_container() { fi msg_info "Validating storage '$CONTAINER_STORAGE'" - STORAGE_CONTENT=$(grep -A10 -E "^(dir|nfs|cifs|btrfs|cephfs|lvm|lvmthin|zfspool|rbd|iscsi|iscsidirect|zfs|linstor|pbs): $CONTAINER_STORAGE$" /etc/pve/storage.cfg | grep -m1 content | awk '{$1=""; print $0}' | xargs) - - if [[ -z "$STORAGE_CONTENT" ]]; then - if pvesm status -content rootdir 2>/dev/null | awk 'NR>1{print $1}' | grep -qx "$CONTAINER_STORAGE"; then - STORAGE_CONTENT="rootdir" - fi - fi - STORAGE_TYPE=$(grep -E "^[^:]+: $CONTAINER_STORAGE$" /etc/pve/storage.cfg | cut -d: -f1 | head -1) case "$STORAGE_TYPE" in - linstor | rbd | cephfs | iscsi | iscsidirect | nfs | cifs) - if ! pvesm status -storage "$CONTAINER_STORAGE" &>/dev/null; then - msg_error "Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) not accessible" - exit 217 - fi + iscsidirect) exit 212 ;; + iscsi | zfs) exit 213 ;; + cephfs) exit 219 ;; + pbs) exit 224 ;; + linstor | rbd | nfs | cifs) + pvesm status -storage "$CONTAINER_STORAGE" &>/dev/null || exit 217 ;; esac - if ! pvesm status -content rootdir 2>/dev/null | awk 'NR>1{print $1}' | grep -qx "$CONTAINER_STORAGE"; then - if ! grep -qw "rootdir" <<<"$STORAGE_CONTENT"; then - msg_error "Storage '$CONTAINER_STORAGE' does not support 'rootdir'" - exit 217 - fi - fi + pvesm status -content rootdir 2>/dev/null | awk 'NR>1{print $1}' | grep -qx "$CONTAINER_STORAGE" || exit 213 msg_ok "Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) validated" msg_info "Validating template storage '$TEMPLATE_STORAGE'" - TEMPLATE_CONTENT=$(grep -A10 -E "^(dir|nfs|cifs|btrfs|cephfs|lvm|lvmthin|zfspool|rbd|iscsi|iscsidirect|zfs|linstor|pbs): $TEMPLATE_STORAGE$" /etc/pve/storage.cfg | grep -m1 content | awk '{$1=""; print $0}' | xargs) - if [[ -z "$TEMPLATE_CONTENT" ]]; then - if pvesm status -content vztmpl 2>/dev/null | awk 'NR>1{print $1}' | grep -qx "$TEMPLATE_STORAGE"; then - TEMPLATE_CONTENT="vztmpl" - fi - fi TEMPLATE_TYPE=$(grep -E "^[^:]+: $TEMPLATE_STORAGE$" /etc/pve/storage.cfg | cut -d: -f1) - if ! grep -qw "vztmpl" <<<"$TEMPLATE_CONTENT"; then + if ! pvesm status -content vztmpl 2>/dev/null | awk 'NR>1{print $1}' | grep -qx "$TEMPLATE_STORAGE"; then msg_warn "Template storage '$TEMPLATE_STORAGE' may not support 'vztmpl'" fi msg_ok "Template storage '$TEMPLATE_STORAGE' validated" diff --git a/misc/error_handler.func b/misc/error_handler.func index 5418a490e..d44250c62 100644 --- a/misc/error_handler.func +++ b/misc/error_handler.func @@ -40,106 +40,86 @@ # - Returns description string for given exit code # ------------------------------------------------------------------------------ explain_exit_code() { - local code="$1" - case "$code" in - # --- Generic / Shell --- - 1) echo "General error / Operation not permitted" ;; - 2) echo "Misuse of shell builtins (e.g. syntax error)" ;; - 126) echo "Command invoked cannot execute (permission problem?)" ;; - 127) echo "Command not found" ;; - 128) echo "Invalid argument to exit" ;; - 130) echo "Terminated by Ctrl+C (SIGINT)" ;; - 137) echo "Killed (SIGKILL / Out of memory?)" ;; - 139) echo "Segmentation fault (core dumped)" ;; - 143) echo "Terminated (SIGTERM)" ;; + local code="$1" + case "$code" in + # --- Generic / Shell --- + 1) echo "General error / Operation not permitted" ;; + 2) echo "Misuse of shell builtins (e.g. syntax error)" ;; + 126) echo "Command invoked cannot execute (permission problem?)" ;; + 127) echo "Command not found" ;; + 128) echo "Invalid argument to exit" ;; + 130) echo "Terminated by Ctrl+C (SIGINT)" ;; + 137) echo "Killed (SIGKILL / Out of memory?)" ;; + 139) echo "Segmentation fault (core dumped)" ;; + 143) echo "Terminated (SIGTERM)" ;; - # --- Package manager / APT / DPKG --- - 100) echo "APT: Package manager error (broken packages / dependency problems)" ;; - 101) echo "APT: Configuration error (bad sources.list, malformed config)" ;; - 255) echo "DPKG: Fatal internal error" ;; + # --- Package manager / APT / DPKG --- + 100) echo "APT: Package manager error (broken packages / dependency problems)" ;; + 101) echo "APT: Configuration error (bad sources.list, malformed config)" ;; + 255) echo "DPKG: Fatal internal error" ;; - # --- Node.js / npm / pnpm / yarn --- - 243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;; - 245) echo "Node.js: Invalid command-line option" ;; - 246) echo "Node.js: Internal JavaScript Parse Error" ;; - 247) echo "Node.js: Fatal internal error" ;; - 248) echo "Node.js: Invalid C++ addon / N-API failure" ;; - 249) echo "Node.js: Inspector error" ;; - 254) echo "npm/pnpm/yarn: Unknown fatal error" ;; + # --- Node.js / npm / pnpm / yarn --- + 243) echo "Node.js: Out of memory (JavaScript heap out of memory)" ;; + 245) echo "Node.js: Invalid command-line option" ;; + 246) echo "Node.js: Internal JavaScript Parse Error" ;; + 247) echo "Node.js: Fatal internal error" ;; + 248) echo "Node.js: Invalid C++ addon / N-API failure" ;; + 249) echo "Node.js: Inspector error" ;; + 254) echo "npm/pnpm/yarn: Unknown fatal error" ;; - # --- Python / pip / uv --- - 210) echo "Python: Virtualenv / uv environment missing or broken" ;; - 211) echo "Python: Dependency resolution failed" ;; - 212) echo "Python: Installation aborted (permissions or EXTERNALLY-MANAGED)" ;; + # --- Python / pip / uv --- + 210) echo "Python: Virtualenv / uv environment missing or broken" ;; + 211) echo "Python: Dependency resolution failed" ;; + 212) echo "Python: Installation aborted (permissions or EXTERNALLY-MANAGED)" ;; - # --- PostgreSQL --- - 231) echo "PostgreSQL: Connection failed (server not running / wrong socket)" ;; - 232) echo "PostgreSQL: Authentication failed (bad user/password)" ;; - 233) echo "PostgreSQL: Database does not exist" ;; - 234) echo "PostgreSQL: Fatal error in query / syntax" ;; + # --- PostgreSQL --- + 231) echo "PostgreSQL: Connection failed (server not running / wrong socket)" ;; + 232) echo "PostgreSQL: Authentication failed (bad user/password)" ;; + 233) echo "PostgreSQL: Database does not exist" ;; + 234) echo "PostgreSQL: Fatal error in query / syntax" ;; - # --- MySQL / MariaDB --- - 241) echo "MySQL/MariaDB: Connection failed (server not running / wrong socket)" ;; - 242) echo "MySQL/MariaDB: Authentication failed (bad user/password)" ;; - 243) echo "MySQL/MariaDB: Database does not exist" ;; - 244) echo "MySQL/MariaDB: Fatal error in query / syntax" ;; + # --- MySQL / MariaDB --- + 241) echo "MySQL/MariaDB: Connection failed (server not running / wrong socket)" ;; + 242) echo "MySQL/MariaDB: Authentication failed (bad user/password)" ;; + 243) echo "MySQL/MariaDB: Database does not exist" ;; + 244) echo "MySQL/MariaDB: Fatal error in query / syntax" ;; - # --- MongoDB --- - 251) echo "MongoDB: Connection failed (server not running)" ;; - 252) echo "MongoDB: Authentication failed (bad user/password)" ;; - 253) echo "MongoDB: Database not found" ;; - 254) echo "MongoDB: Fatal query error" ;; + # --- MongoDB --- + 251) echo "MongoDB: Connection failed (server not running)" ;; + 252) echo "MongoDB: Authentication failed (bad user/password)" ;; + 253) echo "MongoDB: Database not found" ;; + 254) echo "MongoDB: Fatal query error" ;; - # --- Proxmox Custom Codes --- - 200) echo "Custom: Failed to create lock file" ;; - 203) echo "Custom: Missing CTID variable" ;; - 204) echo "Custom: Missing PCT_OSTYPE variable" ;; - 205) echo "Custom: Invalid CTID (<100)" ;; - 206) echo "Custom: CTID already in use (check 'pct list' and /etc/pve/lxc/)" ;; - # --- Proxmox Custom Codes --- - 200) echo "Custom: Failed to create lock file" ;; - 203) echo "Custom: Missing CTID variable" ;; - 204) echo "Custom: Missing PCT_OSTYPE variable" ;; - 205) echo "Custom: Invalid CTID (<100)" ;; - 206) echo "Custom: CTID already in use (check 'pct list' and /etc/pve/lxc/)" ;; - 207) echo "Custom: Password contains unescaped special characters (-, /, \\, *, etc.)" ;; - 208) echo "Custom: Invalid configuration (DNS/MAC/Network format error)" ;; - 209) echo "Custom: Container creation failed (check logs for pct create output)" ;; - 210) echo "Custom: Cluster not quorate" ;; - 211) echo "Custom: Timeout waiting for template lock (concurrent download in progress)" ;; - 214) echo "Custom: Not enough storage space" ;; - 215) echo "Custom: Container created but not listed (ghost state - check /etc/pve/lxc/)" ;; - 216) echo "Custom: RootFS entry missing in config (incomplete creation)" ;; - 217) echo "Custom: Storage does not support rootdir (check storage capabilities)" ;; - 218) echo "Custom: Template file corrupted or incomplete download (size <1MB or invalid archive)" ;; - 220) echo "Custom: Unable to resolve template path" ;; - 221) echo "Custom: Template file exists but not readable (check file permissions)" ;; - 222) echo "Custom: Template download failed after 3 attempts (network/storage issue)" ;; - 223) echo "Custom: Template not available after download (storage sync issue)" ;; - 225) echo "Custom: No template available for OS/Version (check 'pveam available')" ;; - 231) echo "Custom: LXC stack upgrade/retry failed (outdated pve-container - check https://github.com/community-scripts/ProxmoxVE/discussions/8126)" ;; + # --- Proxmox Custom Codes --- + 200) echo "Proxmox: Failed to create lock file" ;; + 203) echo "Proxmox: Missing CTID variable" ;; + 204) echo "Proxmox: Missing PCT_OSTYPE variable" ;; + 205) echo "Proxmox: Invalid CTID (<100)" ;; + 206) echo "Proxmox: CTID already in use" ;; + 207) echo "Proxmox: Password contains unescaped special characters" ;; + 208) echo "Proxmox: Invalid configuration (DNS/MAC/Network format)" ;; + 209) echo "Proxmox: Container creation failed" ;; + 210) echo "Proxmox: Cluster not quorate" ;; + 211) echo "Proxmox: Timeout waiting for template lock" ;; + 212) echo "Proxmox: Storage type 'iscsidirect' does not support containers (VMs only)" ;; + 213) echo "Proxmox: Storage type does not support 'rootdir' content" ;; + 214) echo "Proxmox: Not enough storage space" ;; + 215) echo "Proxmox: Container created but not listed (ghost state)" ;; + 216) echo "Proxmox: RootFS entry missing in config" ;; + 217) echo "Proxmox: Storage not accessible" ;; + 219) echo "Proxmox: CephFS does not support containers - use RBD" ;; + 224) echo "Proxmox: PBS storage is for backups only" ;; + 218) echo "Proxmox: Template file corrupted or incomplete" ;; + 220) echo "Proxmox: Unable to resolve template path" ;; + 221) echo "Proxmox: Template file not readable" ;; + 222) echo "Proxmox: Template download failed" ;; + 223) echo "Proxmox: Template not available after download" ;; + 225) echo "Proxmox: No template available for OS/Version" ;; + 231) echo "Proxmox: LXC stack upgrade failed" ;; - # --- Default --- - *) echo "Unknown error" ;; - 208) echo "Custom: Invalid configuration (DNS/MAC/Network format error)" ;; - 209) echo "Custom: Container creation failed (check logs for pct create output)" ;; - 210) echo "Custom: Cluster not quorate" ;; - 211) echo "Custom: Timeout waiting for template lock (concurrent download in progress)" ;; - 214) echo "Custom: Not enough storage space" ;; - 215) echo "Custom: Container created but not listed (ghost state - check /etc/pve/lxc/)" ;; - 216) echo "Custom: RootFS entry missing in config (incomplete creation)" ;; - 217) echo "Custom: Storage does not support rootdir (check storage capabilities)" ;; - 218) echo "Custom: Template file corrupted or incomplete download (size <1MB or invalid archive)" ;; - 220) echo "Custom: Unable to resolve template path" ;; - 221) echo "Custom: Template file exists but not readable (check file permissions)" ;; - 222) echo "Custom: Template download failed after 3 attempts (network/storage issue)" ;; - 223) echo "Custom: Template not available after download (storage sync issue)" ;; - 225) echo "Custom: No template available for OS/Version (check 'pveam available')" ;; - 231) echo "Custom: LXC stack upgrade/retry failed (outdated pve-container - check https://github.com/community-scripts/ProxmoxVE/discussions/8126)" ;; - - # --- Default --- - *) echo "Unknown error" ;; - esac + # --- Default --- + *) echo "Unknown error" ;; + esac } # ============================================================================== @@ -163,100 +143,100 @@ explain_exit_code() { # * Exits with original exit code # ------------------------------------------------------------------------------ error_handler() { - local exit_code=${1:-$?} - local command=${2:-${BASH_COMMAND:-unknown}} - local line_number=${BASH_LINENO[0]:-unknown} + local exit_code=${1:-$?} + local command=${2:-${BASH_COMMAND:-unknown}} + local line_number=${BASH_LINENO[0]:-unknown} - command="${command//\$STD/}" + command="${command//\$STD/}" - if [[ "$exit_code" -eq 0 ]]; then - return 0 - fi - - local explanation - explanation="$(explain_exit_code "$exit_code")" - - printf "\e[?25h" - - # Use msg_error if available, fallback to echo - if declare -f msg_error >/dev/null 2>&1; then - msg_error "in line ${line_number}: exit code ${exit_code} (${explanation}): while executing command ${command}" - else - echo -e "\n${RD}[ERROR]${CL} in line ${RD}${line_number}${CL}: exit code ${RD}${exit_code}${CL} (${explanation}): while executing command ${YWB}${command}${CL}\n" - fi - - if [[ -n "${DEBUG_LOGFILE:-}" ]]; then - { - echo "------ ERROR ------" - echo "Timestamp : $(date '+%Y-%m-%d %H:%M:%S')" - echo "Exit Code : $exit_code ($explanation)" - echo "Line : $line_number" - echo "Command : $command" - echo "-------------------" - } >>"$DEBUG_LOGFILE" - fi - - # Get active log file (BUILD_LOG or INSTALL_LOG) - local active_log="" - if declare -f get_active_logfile >/dev/null 2>&1; then - active_log="$(get_active_logfile)" - elif [[ -n "${SILENT_LOGFILE:-}" ]]; then - active_log="$SILENT_LOGFILE" - fi - - if [[ -n "$active_log" && -s "$active_log" ]]; then - echo "--- Last 20 lines of silent log ---" - tail -n 20 "$active_log" - echo "-----------------------------------" - - # Detect context: Container (INSTALL_LOG set + /root exists) vs Host (BUILD_LOG) - if [[ -n "${INSTALL_LOG:-}" && -d /root ]]; then - # CONTAINER CONTEXT: Copy log and create flag file for host - local container_log="/root/.install-${SESSION_ID:-error}.log" - cp "$active_log" "$container_log" 2>/dev/null || true - - # Create error flag file with exit code for host detection - echo "$exit_code" >"/root/.install-${SESSION_ID:-error}.failed" 2>/dev/null || true - - if declare -f msg_custom >/dev/null 2>&1; then - msg_custom "📋" "${YW}" "Log saved to: ${container_log}" - else - echo -e "${YW}Log saved to:${CL} ${BL}${container_log}${CL}" - fi - else - # HOST CONTEXT: Show local log path and offer container cleanup - if declare -f msg_custom >/dev/null 2>&1; then - msg_custom "📋" "${YW}" "Full log: ${active_log}" - else - echo -e "${YW}Full log:${CL} ${BL}${active_log}${CL}" - fi - - # Offer to remove container if it exists (build errors after container creation) - if [[ -n "${CTID:-}" ]] && command -v pct &>/dev/null && pct status "$CTID" &>/dev/null; then - echo "" - echo -en "${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 - echo -e "\n${YW}Removing container ${CTID}${CL}" - pct stop "$CTID" &>/dev/null || true - pct destroy "$CTID" &>/dev/null || true - echo -e "${GN}✔${CL} Container ${CTID} removed" - elif [[ "$response" =~ ^[Nn]$ ]]; then - echo -e "\n${YW}Container ${CTID} kept for debugging${CL}" - fi - else - # Timeout - auto-remove - echo -e "\n${YW}No response - auto-removing container${CL}" - pct stop "$CTID" &>/dev/null || true - pct destroy "$CTID" &>/dev/null || true - echo -e "${GN}✔${CL} Container ${CTID} removed" - fi - fi + if [[ "$exit_code" -eq 0 ]]; then + return 0 fi - fi - exit "$exit_code" + local explanation + explanation="$(explain_exit_code "$exit_code")" + + printf "\e[?25h" + + # Use msg_error if available, fallback to echo + if declare -f msg_error >/dev/null 2>&1; then + msg_error "in line ${line_number}: exit code ${exit_code} (${explanation}): while executing command ${command}" + else + echo -e "\n${RD}[ERROR]${CL} in line ${RD}${line_number}${CL}: exit code ${RD}${exit_code}${CL} (${explanation}): while executing command ${YWB}${command}${CL}\n" + fi + + if [[ -n "${DEBUG_LOGFILE:-}" ]]; then + { + echo "------ ERROR ------" + echo "Timestamp : $(date '+%Y-%m-%d %H:%M:%S')" + echo "Exit Code : $exit_code ($explanation)" + echo "Line : $line_number" + echo "Command : $command" + echo "-------------------" + } >>"$DEBUG_LOGFILE" + fi + + # Get active log file (BUILD_LOG or INSTALL_LOG) + local active_log="" + if declare -f get_active_logfile >/dev/null 2>&1; then + active_log="$(get_active_logfile)" + elif [[ -n "${SILENT_LOGFILE:-}" ]]; then + active_log="$SILENT_LOGFILE" + fi + + if [[ -n "$active_log" && -s "$active_log" ]]; then + echo "--- Last 20 lines of silent log ---" + tail -n 20 "$active_log" + echo "-----------------------------------" + + # Detect context: Container (INSTALL_LOG set + /root exists) vs Host (BUILD_LOG) + if [[ -n "${INSTALL_LOG:-}" && -d /root ]]; then + # CONTAINER CONTEXT: Copy log and create flag file for host + local container_log="/root/.install-${SESSION_ID:-error}.log" + cp "$active_log" "$container_log" 2>/dev/null || true + + # Create error flag file with exit code for host detection + echo "$exit_code" >"/root/.install-${SESSION_ID:-error}.failed" 2>/dev/null || true + + if declare -f msg_custom >/dev/null 2>&1; then + msg_custom "📋" "${YW}" "Log saved to: ${container_log}" + else + echo -e "${YW}Log saved to:${CL} ${BL}${container_log}${CL}" + fi + else + # HOST CONTEXT: Show local log path and offer container cleanup + if declare -f msg_custom >/dev/null 2>&1; then + msg_custom "📋" "${YW}" "Full log: ${active_log}" + else + echo -e "${YW}Full log:${CL} ${BL}${active_log}${CL}" + fi + + # Offer to remove container if it exists (build errors after container creation) + if [[ -n "${CTID:-}" ]] && command -v pct &>/dev/null && pct status "$CTID" &>/dev/null; then + echo "" + echo -en "${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 + echo -e "\n${YW}Removing container ${CTID}${CL}" + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + echo -e "${GN}✔${CL} Container ${CTID} removed" + elif [[ "$response" =~ ^[Nn]$ ]]; then + echo -e "\n${YW}Container ${CTID} kept for debugging${CL}" + fi + else + # Timeout - auto-remove + echo -e "\n${YW}No response - auto-removing container${CL}" + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + echo -e "${GN}✔${CL} Container ${CTID} removed" + fi + fi + fi + fi + + exit "$exit_code" } # ============================================================================== @@ -272,9 +252,9 @@ error_handler() { # - Always runs on script termination (success or failure) # ------------------------------------------------------------------------------ on_exit() { - local exit_code=$? - [[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile" - exit "$exit_code" + local exit_code=$? + [[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile" + exit "$exit_code" } # ------------------------------------------------------------------------------ @@ -285,12 +265,12 @@ on_exit() { # - Exits with code 130 (128 + SIGINT=2) # ------------------------------------------------------------------------------ on_interrupt() { - if declare -f msg_error >/dev/null 2>&1; then - msg_error "Interrupted by user (SIGINT)" - else - echo -e "\n${RD}Interrupted by user (SIGINT)${CL}" - fi - exit 130 + if declare -f msg_error >/dev/null 2>&1; then + msg_error "Interrupted by user (SIGINT)" + else + echo -e "\n${RD}Interrupted by user (SIGINT)${CL}" + fi + exit 130 } # ------------------------------------------------------------------------------ @@ -302,12 +282,12 @@ on_interrupt() { # - Triggered by external process termination # ------------------------------------------------------------------------------ on_terminate() { - if declare -f msg_error >/dev/null 2>&1; then - msg_error "Terminated by signal (SIGTERM)" - else - echo -e "\n${RD}Terminated by signal (SIGTERM)${CL}" - fi - exit 143 + if declare -f msg_error >/dev/null 2>&1; then + msg_error "Terminated by signal (SIGTERM)" + else + echo -e "\n${RD}Terminated by signal (SIGTERM)${CL}" + fi + exit 143 } # ============================================================================== @@ -330,13 +310,13 @@ on_terminate() { # - Call this function early in every script # ------------------------------------------------------------------------------ catch_errors() { - set -Ee -o pipefail - if [ "${STRICT_UNSET:-0}" = "1" ]; then - set -u - fi + set -Ee -o pipefail + if [ "${STRICT_UNSET:-0}" = "1" ]; then + set -u + fi - trap 'error_handler' ERR - trap on_exit EXIT - trap on_interrupt INT - trap on_terminate TERM + trap 'error_handler' ERR + trap on_exit EXIT + trap on_interrupt INT + trap on_terminate TERM }