diff --git a/misc/api.func b/misc/api.func index 14e1b5fda..4a554ecfc 100644 --- a/misc/api.func +++ b/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,6 +266,22 @@ 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/--.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 diff --git a/misc/build.func b/misc/build.func index b96e96f24..dcf8fb42c 100644 --- a/misc/build.func +++ b/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,56 @@ 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}" + echo -e "${GN}✔${CL} Installation log: ${BL}${combined_log}${CL}" fi # Dev mode: Keep container or open breakpoint shell @@ -5137,6 +5199,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 +5266,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 +5274,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 diff --git a/misc/core.func b/misc/core.func index 60074b31b..23a5e3b37 100644 --- a/misc/core.func +++ b/misc/core.func @@ -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() # @@ -555,6 +618,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,6 +664,7 @@ msg_ok() { stop_spinner clear_line echo -e "$CM ${GN}${msg}${CL}" + log_msg "[OK] $msg" unset MSG_INFO_SHOWN["$msg"] } @@ -613,6 +680,7 @@ msg_error() { stop_spinner local msg="$1" echo -e "${BFR:-}${CROSS:-✖️} ${RD}${msg}${CL}" >&2 + log_msg "[ERROR] $msg" } # ------------------------------------------------------------------------------ @@ -627,6 +695,7 @@ msg_warn() { stop_spinner local msg="$1" echo -e "${BFR:-}${INFO:-ℹ️} ${YWB}${msg}${CL}" >&2 + log_msg "[WARN] $msg" } # ------------------------------------------------------------------------------ @@ -644,6 +713,7 @@ msg_custom() { [[ -z "$msg" ]] && return stop_spinner echo -e "${BFR:-} ${symbol} ${color}${msg}${CL:-\e[0m}" + log_msg "$msg" } # ------------------------------------------------------------------------------