From 8c4f1ce5318f9a483bbada392b79f3db85b8e3c0 Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 27 Jan 2026 19:31:02 -0500 Subject: [PATCH 01/11] Make check_container_storage() POSIX compliant --- misc/build.func | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/misc/build.func b/misc/build.func index 051c5f4b8..386b37385 100644 --- a/misc/build.func +++ b/misc/build.func @@ -3086,21 +3086,32 @@ check_container_resources() { # ------------------------------------------------------------------------------ # check_container_storage() # -# - Checks /boot partition usage +# - Checks root (/) partition usage # - Warns if usage >80% and asks user confirmation before proceeding # ------------------------------------------------------------------------------ check_container_storage() { - total_size=$(df /boot --output=size | tail -n 1) - local used_size=$(df /boot --output=used | tail -n 1) - usage=$((100 * used_size / total_size)) - if ((usage > 80)); then + usage=$(df / -P | awk 'NR==2 {print $5}' | tr -d '%') + + # shellcheck disable=SC2181 + if [ $? -ne 0 ]; then + echo "Error: Failed to check disk usage." + exit 1 + fi + + if [ "$usage" -gt 80 ]; then echo -e "${INFO}${HOLD} ${YWB}Warning: Storage is dangerously low (${usage}%).${CL}" - echo -ne "Continue anyway? " + printf "Continue anyway? " read -r prompt - if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then + + case "$prompt" in + [yY][eE][sS] | [yY]) + # User input is "yes" or "y"; continue + ;; + *) echo -e "${CROSS}${HOLD}${YWB}Exiting based on user input.${CL}" exit 1 - fi + ;; + esac fi } From 000a7a270166a005f09a53a0604aeb91c2362f90 Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 27 Jan 2026 20:41:55 -0500 Subject: [PATCH 02/11] Fix Error check and error message style --- misc/build.func | 365 ++++++++++++++++++++++++------------------------ 1 file changed, 181 insertions(+), 184 deletions(-) diff --git a/misc/build.func b/misc/build.func index 386b37385..7e5e0a794 100644 --- a/misc/build.func +++ b/misc/build.func @@ -363,7 +363,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 @@ -467,7 +467,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 @@ -517,14 +517,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 @@ -1057,113 +1057,113 @@ 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 + ;; + 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 - ;; - 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 @@ -3092,21 +3092,18 @@ check_container_resources() { check_container_storage() { usage=$(df / -P | awk 'NR==2 {print $5}' | tr -d '%') - # shellcheck disable=SC2181 - if [ $? -ne 0 ]; then - echo "Error: Failed to check disk usage." + if [ -z "$usage" ] || [ "$usage" -lt 0 ]; then + echo -e "${CROSS}${HOLD}${RD}Error: Failed to check disk usage.${CL}" exit 1 fi if [ "$usage" -gt 80 ]; then - echo -e "${INFO}${HOLD} ${YWB}Warning: Storage is dangerously low (${usage}%).${CL}" + echo -e "${INFO}${HOLD}${YWB}Warning: Storage is dangerously low (${usage}%).${CL}" printf "Continue anyway? " read -r prompt case "$prompt" in - [yY][eE][sS] | [yY]) - # User input is "yes" or "y"; continue - ;; + [yY][eE][sS] | [yY]) ;; *) echo -e "${CROSS}${HOLD}${YWB}Exiting based on user input.${CL}" exit 1 @@ -4034,91 +4031,91 @@ EOF' if read -t 60 -r response; then case "${response:-1}" in - 1) - # Remove container - echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID}${CL}" - pct stop "$CTID" &>/dev/null || true - pct destroy "$CTID" &>/dev/null || true - echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" - ;; - 2) - echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}" - # Dev mode: Setup MOTD/SSH for debugging access to broken container - if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then - echo -e "${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}" - if pct exec "$CTID" -- bash -c " + 1) + # Remove container + echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID}${CL}" + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" + ;; + 2) + echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}" + # Dev mode: Setup MOTD/SSH for debugging access to broken container + if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then + echo -e "${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}" + if pct exec "$CTID" -- bash -c " source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/install.func) declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true " >/dev/null 2>&1; then - local ct_ip=$(pct exec "$CTID" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) - echo -e "${BFR}${CM}${GN}MOTD/SSH ready - SSH into container: ssh root@${ct_ip}${CL}" - fi + local ct_ip=$(pct exec "$CTID" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) + echo -e "${BFR}${CM}${GN}MOTD/SSH ready - SSH into container: ssh root@${ct_ip}${CL}" fi - exit $install_exit_code - ;; - 3) - # Retry with verbose mode - echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID} for rebuild...${CL}" + fi + exit $install_exit_code + ;; + 3) + # Retry with verbose mode + echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID} for rebuild...${CL}" + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" + echo "" + # Get new container ID + local old_ctid="$CTID" + export CTID=$(get_valid_container_id "$CTID") + export VERBOSE="yes" + export var_verbose="yes" + + # Show rebuild summary + echo -e "${YW}Rebuilding with preserved settings:${CL}" + echo -e " Container ID: ${old_ctid} → ${CTID}" + echo -e " RAM: ${RAM_SIZE} MiB | CPU: ${CORE_COUNT} cores | Disk: ${DISK_SIZE} GB" + echo -e " Network: ${NET:-dhcp} | Bridge: ${BRG:-vmbr0}" + echo -e " Verbose: ${GN}enabled${CL}" + echo "" + msg_info "Restarting installation..." + # Re-run build_container + build_container + return $? + ;; + 4) + if [[ "$is_oom" == true ]]; then + # Retry with more resources + echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID} for rebuild with more resources...${CL}" pct stop "$CTID" &>/dev/null || true pct destroy "$CTID" &>/dev/null || true echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" echo "" - # Get new container ID + # Get new container ID and increase resources local old_ctid="$CTID" + local old_ram="$RAM_SIZE" + local old_cpu="$CORE_COUNT" export CTID=$(get_valid_container_id "$CTID") - export VERBOSE="yes" - export var_verbose="yes" + export RAM_SIZE=$((RAM_SIZE * 3 / 2)) + export CORE_COUNT=$((CORE_COUNT + 1)) + export var_ram="$RAM_SIZE" + export var_cpu="$CORE_COUNT" # Show rebuild summary - echo -e "${YW}Rebuilding with preserved settings:${CL}" + echo -e "${YW}Rebuilding with increased resources:${CL}" echo -e " Container ID: ${old_ctid} → ${CTID}" - echo -e " RAM: ${RAM_SIZE} MiB | CPU: ${CORE_COUNT} cores | Disk: ${DISK_SIZE} GB" - echo -e " Network: ${NET:-dhcp} | Bridge: ${BRG:-vmbr0}" - echo -e " Verbose: ${GN}enabled${CL}" + echo -e " RAM: ${old_ram} → ${GN}${RAM_SIZE}${CL} MiB (+50%)" + echo -e " CPU: ${old_cpu} → ${GN}${CORE_COUNT}${CL} cores (+1)" + echo -e " Disk: ${DISK_SIZE} GB | Network: ${NET:-dhcp} | Bridge: ${BRG:-vmbr0}" echo "" msg_info "Restarting installation..." # Re-run build_container build_container return $? - ;; - 4) - if [[ "$is_oom" == true ]]; then - # Retry with more resources - echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID} for rebuild with more resources...${CL}" - pct stop "$CTID" &>/dev/null || true - pct destroy "$CTID" &>/dev/null || true - echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" - echo "" - # Get new container ID and increase resources - local old_ctid="$CTID" - local old_ram="$RAM_SIZE" - local old_cpu="$CORE_COUNT" - export CTID=$(get_valid_container_id "$CTID") - export RAM_SIZE=$((RAM_SIZE * 3 / 2)) - export CORE_COUNT=$((CORE_COUNT + 1)) - export var_ram="$RAM_SIZE" - export var_cpu="$CORE_COUNT" - - # Show rebuild summary - echo -e "${YW}Rebuilding with increased resources:${CL}" - echo -e " Container ID: ${old_ctid} → ${CTID}" - echo -e " RAM: ${old_ram} → ${GN}${RAM_SIZE}${CL} MiB (+50%)" - echo -e " CPU: ${old_cpu} → ${GN}${CORE_COUNT}${CL} cores (+1)" - echo -e " Disk: ${DISK_SIZE} GB | Network: ${NET:-dhcp} | Bridge: ${BRG:-vmbr0}" - echo "" - msg_info "Restarting installation..." - # Re-run build_container - build_container - return $? - else - echo -e "\n${TAB}${YW}Invalid option. Container ${CTID} kept.${CL}" - exit $install_exit_code - fi - ;; - *) + else echo -e "\n${TAB}${YW}Invalid option. Container ${CTID} kept.${CL}" exit $install_exit_code - ;; + fi + ;; + *) + echo -e "\n${TAB}${YW}Invalid option. Container ${CTID} kept.${CL}" + exit $install_exit_code + ;; esac else # Timeout - auto-remove From d7d16543e571e48aef165aeea63d76798d9ebd2c Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 27 Jan 2026 14:16:13 -0500 Subject: [PATCH 03/11] Fix url assignment in fetch_and_deploy_gh_release --- misc/alpine-tools.func | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/misc/alpine-tools.func b/misc/alpine-tools.func index 955554216..4f798c45a 100644 --- a/misc/alpine-tools.func +++ b/misc/alpine-tools.func @@ -317,9 +317,15 @@ fetch_and_deploy_gh_release() { return 1 } + # The command is the same for all cases other than tarball/source + get_url() { # $1 pattern + printf '%s' "$json" | jq -r '.assets[].browser_download_url' | + awk -v p="$1" 'BEGIN{IGNORECASE=1} $0 ~ p {print; exit}' + } + case "$mode" in tarball | source) - url="$(printf '%s' "$json" | jq -r '.tarball_url // empty')" + url=$(printf '%s' "$json" | jq -r '.tarball_url // empty') [ -z "$url" ] && url="https://github.com/$repo/archive/refs/tags/v$version.tar.gz" filename="${app_lc}-${version}.tar.gz" download_with_progress "$url" "$tmpd/$filename" || { @@ -342,7 +348,7 @@ fetch_and_deploy_gh_release() { ;; binary) [ -n "$pattern" ] || pattern="*.apk" - url="$(printf '%s' "$json" | jq -r '.assets[].browser_download_url' | awk -v p="$pattern" 'BEGIN{IGNORECASE=1} $0 ~ p {print; exit}')" + url=$(get_url "$pattern") [ -z "$url" ] && { msg_error "binary asset not found for pattern: $pattern" rm -rf "$tmpd" @@ -374,10 +380,7 @@ fetch_and_deploy_gh_release() { rm -rf "$tmpd" return 1 } - url="$(printf '%s' "$json" | jq -r '.assets[].browser_download_url' | awk -v p="$pattern" ' - BEGIN{IGNORECASE=1} - $0 ~ p {print; exit} - ')" + url=$(get_url "$pattern") [ -z "$url" ] && { msg_error "asset not found for pattern: $pattern" rm -rf "$tmpd" @@ -431,10 +434,7 @@ fetch_and_deploy_gh_release() { rm -rf "$tmpd" return 1 } - url="$(printf '%s' "$json" | jq -r '.assets[].browser_download_url' | awk -v p="$pattern" ' - BEGIN{IGNORECASE=1} - $0 ~ p {print; exit} - ')" + url=$(get_url "$pattern") [ -z "$url" ] && { msg_error "asset not found for pattern: $pattern" rm -rf "$tmpd" From bf97037ba52d00e25735f2c031b2fbc7f926a95f Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 27 Jan 2026 14:22:39 -0500 Subject: [PATCH 04/11] Remove bad quotes --- misc/alpine-tools.func | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/misc/alpine-tools.func b/misc/alpine-tools.func index 4f798c45a..6dd06b659 100644 --- a/misc/alpine-tools.func +++ b/misc/alpine-tools.func @@ -277,7 +277,7 @@ fetch_and_deploy_gh_release() { # $1 app, $2 repo, [$3 mode], [$4 version], [$5 target], [$6 asset_pattern local app="$1" repo="$2" mode="${3:-tarball}" version="${4:-latest}" target="${5:-/opt/$1}" pattern="${6:-}" local app_lc - app_lc="$(lower "$app" | tr -d ' ')" + app_lc=$(lower "$app" | tr -d ' ') local vfile="$HOME/.${app_lc}" local json url filename tmpd unpack @@ -288,7 +288,7 @@ fetch_and_deploy_gh_release() { need_tool curl jq tar || return 1 [ "$mode" = "prebuild" ] || [ "$mode" = "singlefile" ] && need_tool unzip >/dev/null 2>&1 || true - tmpd="$(mktemp -d)" || return 1 + tmpd=$(mktemp -d) || return 1 mkdir -p "$target" # Release JSON (with token/rate-limit handling) @@ -305,10 +305,10 @@ fetch_and_deploy_gh_release() { return 1 } fi - json="$(cat "$tmpd/release.json")" + json=$(cat "$tmpd/release.json") # correct Version - version="$(printf '%s' "$json" | jq -r '.tag_name // empty')" + version=$(printf '%s' "$json" | jq -r '.tag_name // empty') version="${version#v}" [ -z "$version" ] && { @@ -337,7 +337,7 @@ fetch_and_deploy_gh_release() { rm -rf "$tmpd" return 1 } - unpack="$(find "$tmpd" -mindepth 1 -maxdepth 1 -type d | head -n1)" + unpack=$(find "$tmpd" -mindepth 1 -maxdepth 1 -type d | head -n1) [ "${CLEAN_INSTALL:-0}" = "1" ] && rm -rf "${target:?}/"* # copy content of unpack to target (cd "$unpack" && tar -cf - .) | (cd "$target" && tar -xf -) || { @@ -414,7 +414,7 @@ fetch_and_deploy_gh_release() { [ "${CLEAN_INSTALL:-0}" = "1" ] && rm -rf "${target:?}/"* # top-level folder strippen if [ "$(find "$tmpd/unp" -mindepth 1 -maxdepth 1 -type d | wc -l)" -eq 1 ] && [ -z "$(find "$tmpd/unp" -mindepth 1 -maxdepth 1 -type f | head -n1)" ]; then - unpack="$(find "$tmpd/unp" -mindepth 1 -maxdepth 1 -type d)" + unpack=$(find "$tmpd/unp" -mindepth 1 -maxdepth 1 -type d) (cd "$unpack" && tar -cf - .) | (cd "$target" && tar -xf -) || { msg_error "copy failed" rm -rf "$tmpd" From 16d0ffa19a880387eb4118324b21e7675ca68e2b Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 27 Jan 2026 14:42:24 -0500 Subject: [PATCH 05/11] $pattern doesn't need to be a parameter --- misc/alpine-tools.func | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/misc/alpine-tools.func b/misc/alpine-tools.func index 6dd06b659..9aee5ca56 100644 --- a/misc/alpine-tools.func +++ b/misc/alpine-tools.func @@ -318,9 +318,9 @@ fetch_and_deploy_gh_release() { } # The command is the same for all cases other than tarball/source - get_url() { # $1 pattern + get_url() { printf '%s' "$json" | jq -r '.assets[].browser_download_url' | - awk -v p="$1" 'BEGIN{IGNORECASE=1} $0 ~ p {print; exit}' + awk -v p="$pattern" 'BEGIN{IGNORECASE=1} $0 ~ p {print; exit}' } case "$mode" in @@ -348,7 +348,7 @@ fetch_and_deploy_gh_release() { ;; binary) [ -n "$pattern" ] || pattern="*.apk" - url=$(get_url "$pattern") + url=$(get_url) [ -z "$url" ] && { msg_error "binary asset not found for pattern: $pattern" rm -rf "$tmpd" @@ -380,7 +380,7 @@ fetch_and_deploy_gh_release() { rm -rf "$tmpd" return 1 } - url=$(get_url "$pattern") + url=$(get_url) [ -z "$url" ] && { msg_error "asset not found for pattern: $pattern" rm -rf "$tmpd" @@ -434,7 +434,7 @@ fetch_and_deploy_gh_release() { rm -rf "$tmpd" return 1 } - url=$(get_url "$pattern") + url=$(get_url) [ -z "$url" ] && { msg_error "asset not found for pattern: $pattern" rm -rf "$tmpd" From f4495280cf8b2abb123f66eb1e36754c60197dfb Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 27 Jan 2026 15:25:05 -0500 Subject: [PATCH 06/11] AWK output needs to be sanitized --- misc/alpine-tools.func | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misc/alpine-tools.func b/misc/alpine-tools.func index 9aee5ca56..3ddc7fda8 100644 --- a/misc/alpine-tools.func +++ b/misc/alpine-tools.func @@ -320,7 +320,8 @@ fetch_and_deploy_gh_release() { # The command is the same for all cases other than tarball/source get_url() { printf '%s' "$json" | jq -r '.assets[].browser_download_url' | - awk -v p="$pattern" 'BEGIN{IGNORECASE=1} $0 ~ p {print; exit}' + awk -v p="$pattern" 'BEGIN{IGNORECASE=1} $0 ~ p {print; exit}' | + tr -d '[:cntrl:]' } case "$mode" in From 54bbdc180a22fdd6e43208debf422387ed7703c0 Mon Sep 17 00:00:00 2001 From: justin Date: Wed, 28 Jan 2026 11:00:41 -0500 Subject: [PATCH 07/11] Remove inline comment in misc/alpine-tools.func --- misc/alpine-tools.func | 1 - 1 file changed, 1 deletion(-) diff --git a/misc/alpine-tools.func b/misc/alpine-tools.func index 3ddc7fda8..6d7ed215a 100644 --- a/misc/alpine-tools.func +++ b/misc/alpine-tools.func @@ -317,7 +317,6 @@ fetch_and_deploy_gh_release() { return 1 } - # The command is the same for all cases other than tarball/source get_url() { printf '%s' "$json" | jq -r '.assets[].browser_download_url' | awk -v p="$pattern" 'BEGIN{IGNORECASE=1} $0 ~ p {print; exit}' | From 622fda4b321a1f93ff12793bb1d8d174299b5742 Mon Sep 17 00:00:00 2001 From: justin Date: Mon, 26 Jan 2026 14:32:47 -0500 Subject: [PATCH 08/11] Fix download_with_progress() content_length calc --- misc/alpine-tools.func | 16 ++++++++++++---- misc/tools.func | 9 +++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/misc/alpine-tools.func b/misc/alpine-tools.func index 6d7ed215a..a830c220c 100644 --- a/misc/alpine-tools.func +++ b/misc/alpine-tools.func @@ -196,11 +196,19 @@ ensure_usr_local_bin_persist() { download_with_progress() { # $1 url, $2 dest - local url="$1" out="$2" cl + local url="$1" out="$2" content_length need_tool curl pv || return 1 - cl=$(curl -fsSLI "$url" 2>/dev/null | awk 'tolower($0) ~ /^content-length:/ {print $2}' | tr -d '\r') - if [ -n "$cl" ]; then - curl -fsSL "$url" | pv -s "$cl" >"$out" || { + + content_length=$( + curl -fsSLI "$url" 2>/dev/null | + # May return multiple values on redirect. i.e., 0 and content-length + # Cast $2 to int by adding 0 to it + awk '(tolower($1) ~ /^content-length:/) && ($2 + 0 > 0) {print $2+0}' | + tr -cd '[:digit:]' || true + ) + + if [ -n "$content_length" ]; then + curl -fsSL "$url" | pv -s "$content_length" >"$out" || { msg_error "Download failed: $url" return 1 } diff --git a/misc/tools.func b/misc/tools.func index 70e8c3fa9..44484ae5b 100644 --- a/misc/tools.func +++ b/misc/tools.func @@ -1692,7 +1692,13 @@ function download_with_progress() { # Content-Length aus HTTP-Header holen local content_length - content_length=$(curl -fsSLI "$url" | awk '/Content-Length/ {print $2}' | tr -d '\r' || true) + content_length=$( + curl -fsSLI "$url" 2>/dev/null | + # May return multiple values on redirect. i.e., 0 and content_length + # Add 0 to $2 to cast it to int + awk '(tolower($1) ~ /^content-length:/) && ($2 + 0 > 0) {print $2+0}' | + tr -cd '[:digit:]' || true + ) if [[ -z "$content_length" ]]; then if ! curl -fL# -o "$output" "$url"; then @@ -6205,4 +6211,3 @@ function fetch_and_deploy_archive() { msg_ok "Successfully deployed archive to $directory" return 0 } - From 75898f498697d40e1ff4b90db3f9a3098758fd70 Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 27 Jan 2026 11:30:26 -0500 Subject: [PATCH 09/11] Add tail -1 before tr to use only the last value --- misc/alpine-tools.func | 4 ++-- misc/tools.func | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/misc/alpine-tools.func b/misc/alpine-tools.func index a830c220c..035565194 100644 --- a/misc/alpine-tools.func +++ b/misc/alpine-tools.func @@ -201,10 +201,10 @@ download_with_progress() { content_length=$( curl -fsSLI "$url" 2>/dev/null | - # May return multiple values on redirect. i.e., 0 and content-length + # May return multiple values on redirect. i.e., 0 and actual content-length value # Cast $2 to int by adding 0 to it awk '(tolower($1) ~ /^content-length:/) && ($2 + 0 > 0) {print $2+0}' | - tr -cd '[:digit:]' || true + tail -1 | tr -cd '[:digit:]' || true ) if [ -n "$content_length" ]; then diff --git a/misc/tools.func b/misc/tools.func index 44484ae5b..18602dc85 100644 --- a/misc/tools.func +++ b/misc/tools.func @@ -1694,10 +1694,10 @@ function download_with_progress() { local content_length content_length=$( curl -fsSLI "$url" 2>/dev/null | - # May return multiple values on redirect. i.e., 0 and content_length + # May return multiple values on redirect. i.e., 0 and actual content-length value # Add 0 to $2 to cast it to int awk '(tolower($1) ~ /^content-length:/) && ($2 + 0 > 0) {print $2+0}' | - tr -cd '[:digit:]' || true + tail -1 | tr -cd '[:digit:]' || true ) if [[ -z "$content_length" ]]; then From f2a847ac81ffa5f5a670d30b055d4d4d47f79ed0 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 28 Jan 2026 15:48:21 +0000 Subject: [PATCH 10/11] Remove inline comment in misc/alpine-tools.func Co-authored-by: Michel Roegl-Brunner <73236783+michelroegl-brunner@users.noreply.github.com> --- misc/alpine-tools.func | 2 -- 1 file changed, 2 deletions(-) diff --git a/misc/alpine-tools.func b/misc/alpine-tools.func index 035565194..9386f737a 100644 --- a/misc/alpine-tools.func +++ b/misc/alpine-tools.func @@ -201,8 +201,6 @@ download_with_progress() { content_length=$( curl -fsSLI "$url" 2>/dev/null | - # May return multiple values on redirect. i.e., 0 and actual content-length value - # Cast $2 to int by adding 0 to it awk '(tolower($1) ~ /^content-length:/) && ($2 + 0 > 0) {print $2+0}' | tail -1 | tr -cd '[:digit:]' || true ) From 62f68959c74b193bc6ab497262608f08240ed652 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 28 Jan 2026 15:48:34 +0000 Subject: [PATCH 11/11] Remove inline comment in misc/tools.func Co-authored-by: Michel Roegl-Brunner <73236783+michelroegl-brunner@users.noreply.github.com> --- misc/tools.func | 2 -- 1 file changed, 2 deletions(-) diff --git a/misc/tools.func b/misc/tools.func index 18602dc85..1096b4a01 100644 --- a/misc/tools.func +++ b/misc/tools.func @@ -1694,8 +1694,6 @@ function download_with_progress() { local content_length content_length=$( curl -fsSLI "$url" 2>/dev/null | - # May return multiple values on redirect. i.e., 0 and actual content-length value - # Add 0 to $2 to cast it to int awk '(tolower($1) ~ /^content-length:/) && ($2 + 0 > 0) {print $2+0}' | tail -1 | tr -cd '[:digit:]' || true )