diff --git a/misc/api.func b/misc/api.func index e3d997802..4f7bd624c 100644 --- a/misc/api.func +++ b/misc/api.func @@ -344,21 +344,36 @@ explain_exit_code() { # - Escapes a string for safe JSON embedding # - Strips ANSI escape sequences and non-printable control characters # - Handles backslashes, quotes, newlines, tabs, and carriage returns +# - Uses jq when available (guaranteed correct), falls back to awk # ------------------------------------------------------------------------------ json_escape() { - # Escape a string for safe JSON embedding using awk (handles any input size). - # Pipeline: strip ANSI → remove control chars → escape \ " TAB → join lines with \n - printf '%s' "$1" | + local input + # Pipeline: strip ANSI → remove control chars → escape for JSON + input=$(printf '%s' "$1" | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' | - tr -d '\000-\010\013\014\016-\037\177\r' | + tr -d '\000-\010\013\014\016-\037\177\r') + + # Prefer jq: guaranteed correct JSON string encoding (handles all edge cases) + if command -v jq &>/dev/null; then + # jq -Rs reads raw stdin as string, outputs JSON-encoded string with quotes. + # We strip the surrounding quotes since the heredoc adds them. + printf '%s' "$input" | jq -Rs '.' | sed 's/^"//;s/"$//' + return + fi + + # Fallback: character-by-character processing with awk (avoids gsub replacement pitfalls) + printf '%s' "$input" | awk ' - BEGIN { ORS = "" } + BEGIN { ORS="" } { - gsub(/\\/, "\\\\") # backslash → \\ - gsub(/"/, "\\\"") # double quote → \" - gsub(/\t/, "\\t") # tab → \t - if (NR > 1) printf "\\n" - printf "%s", $0 + if (NR > 1) printf "%s", "\\n" + for (i = 1; i <= length($0); i++) { + c = substr($0, i, 1) + if (c == "\\") printf "%s", "\\\\" + else if (c == "\"") printf "%s", "\\\"" + else if (c == "\t") printf "%s", "\\t" + else printf "%s", c + } }' } diff --git a/misc/build.func b/misc/build.func index 068d6f720..87a609cc5 100644 --- a/misc/build.func +++ b/misc/build.func @@ -979,7 +979,6 @@ base_settings() { fi IPV6_METHOD=${var_ipv6_method:-"none"} - IPV6_STATIC=${var_ipv6_static:-""} GATE=${var_gateway:-""} APT_CACHER=${var_apt_cacher:-""} APT_CACHER_IP=${var_apt_cacher_ip:-""} @@ -1015,8 +1014,12 @@ base_settings() { VLAN=${var_vlan:-""} SSH=${var_ssh:-"no"} SSH_AUTHORIZED_KEY=${var_ssh_authorized_key:-""} - UDHCPC_FIX=${var_udhcpc_fix:-""} - TAGS="community-script,${var_tags:-}" + # Build TAGS: ensure community-script prefix, use semicolons (pct format), no duplicates + if [[ "${var_tags:-}" == *community-script* ]]; then + TAGS="${var_tags:-community-script}" + else + TAGS="community-script${var_tags:+;${var_tags}}" + fi ENABLE_FUSE=${var_fuse:-"${1:-no}"} ENABLE_TUN=${var_tun:-"${1:-no}"} @@ -1798,7 +1801,12 @@ advanced_settings() { trap 'tput rmcup 2>/dev/null || true' RETURN # Initialize defaults - TAGS="community-script;${var_tags:-}" + # Build TAGS: ensure community-script prefix, use semicolons (pct format), no duplicates + if [[ "${var_tags:-}" == *community-script* ]]; then + TAGS="${var_tags:-community-script}" + else + TAGS="community-script${var_tags:+;${var_tags}}" + fi local STEP=1 local MAX_STEP=28 @@ -2535,6 +2543,13 @@ advanced_settings() { # STEP 22: Keyctl Support (Docker/systemd) # ═══════════════════════════════════════════════════════════════════════════ 22) + # Keyctl is always required for unprivileged containers — skip dialog + if [[ "$_ct_type" == "1" ]]; then + _enable_keyctl="1" + ((STEP++)) + continue + fi + local keyctl_default_flag="--defaultno" [[ "$_enable_keyctl" == "1" ]] && keyctl_default_flag="" @@ -2542,7 +2557,7 @@ advanced_settings() { --title "KEYCTL SUPPORT" \ --ok-button "Next" --cancel-button "Back" \ $keyctl_default_flag \ - --yesno "\nEnable Keyctl support?\n\nRequired for: Docker containers, systemd-networkd,\nand kernel keyring operations.\n\nNote: Automatically enabled for unprivileged containers.\n\n(App default: ${var_keyctl:-0})" 16 62; then + --yesno "\nEnable Keyctl support?\n\nRequired for: Docker containers, systemd-networkd,\nand kernel keyring operations.\n\n(App default: ${var_keyctl:-0})" 14 62; then _enable_keyctl="1" else if [ $? -eq 1 ]; then @@ -2802,13 +2817,6 @@ Advanced: [[ -n "$_mac" ]] && MAC=",hwaddr=$_mac" || MAC="" [[ -n "$_vlan" ]] && VLAN=",tag=$_vlan" || VLAN="" - # Alpine UDHCPC fix - if [ "$var_os" == "alpine" ] && [ "$NET" == "dhcp" ] && [ -n "$_ns" ]; then - UDHCPC_FIX="yes" - else - UDHCPC_FIX="no" - fi - export UDHCPC_FIX export SSH_KEYS_FILE # Exit alternate screen buffer before showing summary (so output remains visible) @@ -5797,6 +5805,9 @@ create_lxc_container() { msg_debug "Logfile: $LOGFILE" # First attempt (PCT_OPTIONS is a multi-line string, use it directly) + # Disable globbing: unquoted $PCT_OPTIONS needs word-splitting but must not glob-expand + # (e.g. passwords containing * or ? would match filenames otherwise) + set -f if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >"$LOGFILE" 2>&1; then msg_debug "Container creation failed on ${TEMPLATE_STORAGE}. Checking error..." @@ -5904,6 +5915,7 @@ create_lxc_container() { fi fi # close CTID collision else-branch fi + set +f # re-enable globbing after pct create block # Verify container exists (allow up to 10s for pmxcfs sync in clusters) local _pct_visible=false