The function used a chain of `(( NEED_X == 1 )) && { ... }` statements.
When the last flag is 0 the trailing arithmetic evaluates to false and
bash returns 1 from the function. Under the main script's `set -e` that
propagates up to bootstrap which exits with the misleading "line 60"
ERR trap right after the installer announces it is going to create the
state directory.
Rewrite the body as plain `if` blocks and return 0 explicitly. Add two
regression assertions that exercise the "only some flags set" and
"nothing to do" paths so the class of bug is caught in CI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
145 lines
4.2 KiB
Bash
Executable File
145 lines
4.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
ROOT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||
# shellcheck source=tests/lib/assert.sh
|
||
source "${ROOT_DIR}/tests/lib/assert.sh"
|
||
|
||
status_of() {
|
||
set +e
|
||
"$@" >/dev/null 2>&1
|
||
local rc=$?
|
||
set -e
|
||
printf '%s\n' "${rc}"
|
||
}
|
||
|
||
TMP_DIR=$(mktemp -d)
|
||
trap 'rm -rf "${TMP_DIR}"' EXIT
|
||
BIN_DIR="${TMP_DIR}/bin"
|
||
mkdir -p "${BIN_DIR}"
|
||
|
||
IPTABLES_PATH="${BIN_DIR}/iptables"
|
||
IP6TABLES_PATH="${BIN_DIR}/ip6tables"
|
||
PERSIST_PATH="${BIN_DIR}/netfilter-persistent"
|
||
DPKG_PATH="${BIN_DIR}/dpkg"
|
||
SYSCTL_PATH="${BIN_DIR}/sysctl"
|
||
SYSTEMCTL_PATH="${BIN_DIR}/systemctl"
|
||
DEBCONF_PATH="${BIN_DIR}/debconf-set-selections"
|
||
APT_PATH="${BIN_DIR}/apt-get"
|
||
|
||
cat >"${DPKG_PATH}" <<'MOCK'
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
exit 1
|
||
MOCK
|
||
|
||
cat >"${SYSCTL_PATH}" <<MOCK
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
printf '%s\n' "\$*" >>"${TMP_DIR}/sysctl.log"
|
||
printf '1\n' >"${TMP_DIR}/ipv4_forward"
|
||
printf '1\n' >"${TMP_DIR}/ipv6_forward"
|
||
MOCK
|
||
|
||
cat >"${SYSTEMCTL_PATH}" <<'MOCK'
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
exit 1
|
||
MOCK
|
||
|
||
cat >"${DEBCONF_PATH}" <<'MOCK'
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
cat >/dev/null
|
||
MOCK
|
||
|
||
cat >"${APT_PATH}" <<MOCK
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
printf '%s\n' "\$*" >>"${TMP_DIR}/apt.log"
|
||
MOCK
|
||
|
||
chmod +x "${DPKG_PATH}" "${SYSCTL_PATH}" "${SYSTEMCTL_PATH}" "${DEBCONF_PATH}" "${APT_PATH}"
|
||
|
||
export IPF_STORAGE_DIR="${TMP_DIR}/storage"
|
||
export IPF_SYSCTL_FILE="${TMP_DIR}/99-iptables-forward.conf"
|
||
export IPF_IPV4_FORWARD_FILE="${TMP_DIR}/ipv4_forward"
|
||
export IPF_IPV6_FORWARD_FILE="${TMP_DIR}/ipv6_forward"
|
||
export IPF_CHECK_IPTABLES_CMD="${BIN_DIR}/missing-iptables"
|
||
export IPF_CHECK_IP6TABLES_CMD="${BIN_DIR}/missing-ip6tables"
|
||
export IPF_CHECK_PERSIST_CMD="${BIN_DIR}/missing-persist"
|
||
export DPKG_BIN="${DPKG_PATH}"
|
||
export SYSCTL_BIN="${SYSCTL_PATH}"
|
||
export SYSTEMCTL_BIN="${SYSTEMCTL_PATH}"
|
||
export DEBCONF_SET_SELECTIONS_BIN="${DEBCONF_PATH}"
|
||
export APT_GET_BIN="${APT_PATH}"
|
||
|
||
echo 0 >"${IPF_IPV4_FORWARD_FILE}"
|
||
echo 0 >"${IPF_IPV6_FORWARD_FILE}"
|
||
|
||
# shellcheck source=lib/common.sh
|
||
source "${ROOT_DIR}/lib/common.sh"
|
||
# shellcheck source=lib/env_check.sh
|
||
source "${ROOT_DIR}/lib/env_check.sh"
|
||
|
||
env_check_collect_issues
|
||
assert_eq '6' "${#ENV_CHECK_ISSUES[@]}" 'env_check_collect_issues should capture missing binaries, persistence, forwarding and storage'
|
||
|
||
cat >"${IPTABLES_PATH}" <<'MOCK'
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
exit 0
|
||
MOCK
|
||
cat >"${IP6TABLES_PATH}" <<'MOCK'
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
exit 0
|
||
MOCK
|
||
cat >"${PERSIST_PATH}" <<'MOCK'
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
exit 0
|
||
MOCK
|
||
cat >"${DPKG_PATH}" <<'MOCK'
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
exit 0
|
||
MOCK
|
||
chmod +x "${IPTABLES_PATH}" "${IP6TABLES_PATH}" "${PERSIST_PATH}" "${DPKG_PATH}"
|
||
|
||
mkdir -p "${IPF_STORAGE_DIR}"
|
||
export IPF_CHECK_IPTABLES_CMD="${IPTABLES_PATH}"
|
||
export IPF_CHECK_IP6TABLES_CMD="${IP6TABLES_PATH}"
|
||
export IPF_CHECK_PERSIST_CMD="${PERSIST_PATH}"
|
||
export IPF_ASSUME_YES=1
|
||
|
||
env_check_all
|
||
assert_file_contains "${IPF_SYSCTL_FILE}" 'net.ipv4.ip_forward=1' 'env_check_all should write IPv4 forwarding setting'
|
||
assert_file_contains "${IPF_SYSCTL_FILE}" 'net.ipv6.conf.all.forwarding=1' 'env_check_all should write IPv6 forwarding setting'
|
||
assert_file_contains "${TMP_DIR}/sysctl.log" '--system' 'env_check_all should apply sysctl settings'
|
||
|
||
export IPF_CHECK_IPTABLES_CMD="${BIN_DIR}/missing-iptables-again"
|
||
export IPF_CHECK_IP6TABLES_CMD="${BIN_DIR}/missing-ip6tables-again"
|
||
export IPF_CHECK_PERSIST_CMD="${BIN_DIR}/missing-persist-again"
|
||
export IPF_ASSUME_YES=0
|
||
export IPF_TEST_INPUTS=$'n\n'
|
||
|
||
assert_eq '3' "$(status_of env_check_all)" 'env_check_all should return 3 when user rejects autofix'
|
||
|
||
# Regression: env_check_apply_fixes must return 0 even when the last flag is 0.
|
||
# 之前写成 `(( NEED_SYSCTL == 1 )) && { ... }`,NEED_SYSCTL=0 时函数返回 1,
|
||
# 外层 set -e 直接把脚本打死。
|
||
ENV_CHECK_NEED_PACKAGES=1
|
||
ENV_CHECK_NEED_STORAGE=0
|
||
ENV_CHECK_NEED_SYSCTL=0
|
||
assert_eq '0' "$(status_of env_check_apply_fixes)" \
|
||
'env_check_apply_fixes should return 0 when only some fixes are needed'
|
||
|
||
ENV_CHECK_NEED_PACKAGES=0
|
||
ENV_CHECK_NEED_STORAGE=0
|
||
ENV_CHECK_NEED_SYSCTL=0
|
||
assert_eq '0' "$(status_of env_check_apply_fixes)" \
|
||
'env_check_apply_fixes should return 0 when no fixes are needed'
|
||
|
||
pass 'test_env_check.sh'
|