diff --git a/.github/workflows/verify-split-files.yml b/.github/workflows/verify-split-files.yml index 85a3153..7b2f415 100644 --- a/.github/workflows/verify-split-files.yml +++ b/.github/workflows/verify-split-files.yml @@ -29,6 +29,7 @@ on: - "tests/mongodb-upgrade.sh" - "tests/uv-required-version.sh" - "tests/docker-lxcfs.sh" + - "tests/install-script-fetch.sh" - "tests/java-memory.sh" - "tests/sudo-env.sh" - "tests/deb822-local-keyring.sh" @@ -90,6 +91,7 @@ on: - "tests/mongodb-upgrade.sh" - "tests/uv-required-version.sh" - "tests/docker-lxcfs.sh" + - "tests/install-script-fetch.sh" - "tests/java-memory.sh" - "tests/sudo-env.sh" - "tests/deb822-local-keyring.sh" @@ -173,6 +175,9 @@ jobs: - name: Docker lxcfs visibility regression tests run: bash tests/docker-lxcfs.sh + - name: Install script fetch tests + run: bash tests/install-script-fetch.sh + - name: Java memory drop-in tests run: bash tests/java-memory.sh diff --git a/pve/backend.func b/pve/backend.func index da4bd73..701cd6f 100644 --- a/pve/backend.func +++ b/pve/backend.func @@ -218,6 +218,38 @@ _pve_ct_gone_during_setup() { _CS_SETUP_GONE_RC=199 } +# ------------------------------------------------------------------------------ +# _pve_fetch_install_script +# +# Before anything is created: a missing or unreachable install script turned up +# only once the container stood ready, with a fork hint whatever the cause. +# Leaves the script in _install_script, or says why not and returns 115. +# ------------------------------------------------------------------------------ +_pve_fetch_install_script() { + local rel="install/${var_install}.sh" base="${COMMUNITY_SCRIPTS_ROOT:-${COMMUNITY_SCRIPTS_URL}}" out err rc=0 why + out="$(mktemp)" err="$(mktemp)" + _cs_shield_hangup _cs_fetch_text "$rel" >"$out" 2>"$err" || rc=$? + _install_script="$(cat "$out")" + why="$(grep -v '^[[:space:]]*$' "$err" | tail -n 1)" || why="" + rm -f "$out" "$err" + ((rc == 0)) && [[ -n "$_install_script" ]] && return 0 + _install_script="" + if [[ "$why" == *404* ]]; then + msg_error "${rel} is not in ${base}" + msg_custom "💡" "${YW}" "If ${APP} was retired or became an addon, its ct/ script only updates existing containers:" + msg_custom " " "${YW}" "https://community-scripts.org/scripts/${NSAPP}" + if [[ -z "${COMMUNITY_SCRIPTS_ROOT:-}" ]]; then + msg_custom "💡" "${YW}" "Running a fork? A ct/ script fetched with curl cannot tell the engine" + msg_custom " " "${YW}" "where it came from, so set the scripts base explicitly:" + msg_custom " " "${YW}" "COMMUNITY_SCRIPTS_URL=https://raw.githubusercontent.com///" + fi + else + msg_error "Could not fetch ${rel} from ${base}" + [[ -n "$why" ]] && msg_custom "â„šī¸" "${YW}" "$why" + fi + return 115 +} + # ------------------------------------------------------------------------------ # _pve_wait_for_detached_install # @@ -759,6 +791,8 @@ $PCT_OPTIONS_STRING" check_storage_health "$CONTAINER_STORAGE" "$DISK_SIZE" fi + local _install_script="" + _pve_fetch_install_script || exit $? create_lxc_container || exit $? # Transition to 'configuring' — container created, now setting up OS/userland post_progress_to_api "configuring" @@ -1504,25 +1538,6 @@ PROFILE export CONTAINER_INSTALLING=true - local _install_script - # Fetched explicitly rather than letting the ERR trap report it. - # - # A missing install script is almost always one thing: the ct/ script came - # from a fork but COMMUNITY_SCRIPTS_URL was not set, so the engine looked - # here -- upstream -- and found nothing. The trap reported that as - # "exit code 22 (Command failed with exit status 22)", which says neither - # what was missing nor where it was looked for. - if ! _install_script="$(_cs_fetch_text "install/${var_install}.sh")" || - [[ -z "$_install_script" ]]; then - msg_error "Could not fetch install/${var_install}.sh" - msg_custom "📁" "${YW}" "Looked in: ${COMMUNITY_SCRIPTS_ROOT:-${COMMUNITY_SCRIPTS_URL}}" - if [[ -z "${COMMUNITY_SCRIPTS_ROOT:-}" ]]; then - msg_custom "💡" "${YW}" "Running a fork? A ct/ script fetched with curl cannot tell the engine" - msg_custom " " "${YW}" "where it came from, so set the scripts base explicitly:" - msg_custom " " "${YW}" "COMMUNITY_SCRIPTS_URL=https://raw.githubusercontent.com///" - fi - exit 115 - fi # The install owns the terminal now; a block left open on the host would resume its spinner over it. stop_spinner _MSG_BLOCKS=() diff --git a/tests/install-script-fetch.sh b/tests/install-script-fetch.sh new file mode 100644 index 0000000..9b301de --- /dev/null +++ b/tests/install-script-fetch.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# 26 runs a week failed on install/-install.sh only after their container +# stood ready, all with a fork hint: retired scripts (dockge, dokploy), forks, +# and network errors whose reason never reached the log. +set -uo pipefail + +CORE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +BACKEND="$CORE_DIR/pve/backend.func" +eval "$(awk '/^_cs_shield_hangup[(][)] [{]/,/^}/' "$CORE_DIR/core/core.func")" +eval "$(awk '/^_pve_fetch_install_script[(][)] [{]/,/^}/' "$BACKEND")" + +TEST_DIR="$(mktemp -d)" +trap 'rm -rf -- "$TEST_DIR"' EXIT +MSG="$TEST_DIR/msg" +passed=0 failed=0 + +expect() { + if [[ "$2" == "$3" ]]; then + echo "PASS $1" + passed=$((passed + 1)) + else + echo "FAIL $1: expected <$2>, got <$3>" >&2 + failed=$((failed + 1)) + fi +} + +msg_error() { echo "ERROR $1" >>"$MSG"; } +msg_custom() { echo "$3" >>"$MSG"; } +YW="" APP="Dockge" NSAPP="dockge" var_install="dockge-install" +COMMUNITY_SCRIPTS_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main" +unset COMMUNITY_SCRIPTS_ROOT + +fetch() { + : >"$MSG" + _install_script="stale" + _pve_fetch_install_script + echo "rc=$?" +} + +_cs_fetch_text() { printf '#!/usr/bin/env bash\necho installing\n'; } +expect "a fetched script is kept" "rc=0" "$(fetch)" +fetch >/dev/null +expect "with its content" "echo installing" "$(tail -n1 <<<"$_install_script")" + +_cs_fetch_text() { echo "curl: (22) The requested URL returned error: 404" >&2; return 22; } +expect "a missing script stops with 115" "rc=115" "$(fetch)" +fetch >/dev/null +expect "it says where it was not found" "ERROR install/dockge-install.sh is not in https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main" "$(head -n1 "$MSG")" +expect "it names a retired script as one cause" 1 "$(grep -c 'retired or became an addon' "$MSG")" +expect "and a fork as the other" 1 "$(grep -c 'Running a fork' "$MSG")" +expect "nothing is left to run" "" "$_install_script" + +_cs_fetch_text() { echo "curl: (6) Could not resolve host: raw.githubusercontent.com" >&2; return 6; } +fetch >/dev/null +expect "a network failure says so" "ERROR Could not fetch install/dockge-install.sh from https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main" "$(head -n1 "$MSG")" +expect "with curl's reason" "curl: (6) Could not resolve host: raw.githubusercontent.com" "$(sed -n 2p "$MSG")" +expect "and no fork hint" 0 "$(grep -c 'fork' "$MSG" || true)" + +_cs_fetch_text() { printf '#!/usr/bin/env bash\napt-get ins'; echo "curl: (18) transfer closed with 1024 bytes remaining" >&2; return 18; } +fetch >/dev/null +expect "a cut-off script is never run" "" "$_install_script" + +_cs_fetch_text() { return 0; } +expect "an empty answer counts as missing" "rc=115" "$(fetch)" + +rc="$( + set -eEo pipefail + trap 'echo ERR' ERR + _cs_fetch_text() { return 7; } + _pve_fetch_install_script >/dev/null || true + echo "rc=done" +)" +expect "no curl output at all trips neither errexit nor the ERR trap" "rc=done" "$rc" + +fetch_line=$(grep -n '_pve_fetch_install_script || exit' "$BACKEND" | cut -d: -f1) +create_line=$(grep -n ' create_lxc_container || exit' "$BACKEND" | cut -d: -f1) +expect "the script is fetched before the container is created" yes "$([[ -n "$fetch_line" && -n "$create_line" ]] && ((fetch_line < create_line)) && echo yes || echo no)" +expect "and not fetched a second time before the install" 1 "$(grep -c '_cs_fetch_text "install/${var_install}.sh"' "$BACKEND")" + +printf '%s passed, %s failed\n' "$passed" "$failed" +((failed == 0))