Protect install target and add tests
This commit is contained in:
@@ -52,6 +52,8 @@ sudo iptables-forward
|
||||
/usr/local/bin/iptables-forward -> /path/to/IPTables-Management/iptables-forward.sh
|
||||
```
|
||||
|
||||
若目标路径已存在且不是当前仓库脚本的符号链接,`install.sh` 会拒绝覆盖;`--uninstall` 也只会删除当前脚本创建的链接,避免误删其它文件。
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 交互模式
|
||||
@@ -148,6 +150,7 @@ tests/run_all.sh --skip-integration
|
||||
测试覆盖:
|
||||
|
||||
- `tests/test_cli.sh`:入口脚本与安装脚本帮助输出
|
||||
- `tests/test_install.sh`:安装脚本的 install/uninstall 实际行为与误覆盖保护
|
||||
- `tests/test_interactive.sh`:交互主菜单的 add/list/delete/save 回归
|
||||
- `tests/test_common.sh`:输入校验
|
||||
- `tests/test_storage.sh`:规则存储
|
||||
|
||||
21
install.sh
21
install.sh
@@ -25,21 +25,38 @@ require_root() {
|
||||
fi
|
||||
}
|
||||
|
||||
target_is_current_link() {
|
||||
[[ -L ${TARGET_BIN} ]] || return 1
|
||||
[[ $(readlink -- "${TARGET_BIN}") == "${SOURCE_SCRIPT}" ]]
|
||||
}
|
||||
|
||||
install_link() {
|
||||
[[ -f ${SOURCE_SCRIPT} ]] || {
|
||||
printf '未找到入口脚本: %s\n' "${SOURCE_SCRIPT}" >&2
|
||||
exit 1
|
||||
}
|
||||
mkdir -p "$(dirname -- "${TARGET_BIN}")"
|
||||
ln -sfn "${SOURCE_SCRIPT}" "${TARGET_BIN}"
|
||||
if [[ -e ${TARGET_BIN} || -L ${TARGET_BIN} ]]; then
|
||||
if target_is_current_link; then
|
||||
chmod 755 "${SOURCE_SCRIPT}"
|
||||
printf '链接已存在: %s -> %s\n' "${TARGET_BIN}" "${SOURCE_SCRIPT}"
|
||||
return 0
|
||||
fi
|
||||
printf '目标已存在且不是当前脚本的链接,拒绝覆盖: %s\n' "${TARGET_BIN}" >&2
|
||||
exit 1
|
||||
fi
|
||||
ln -s "${SOURCE_SCRIPT}" "${TARGET_BIN}"
|
||||
chmod 755 "${SOURCE_SCRIPT}"
|
||||
printf '已创建链接: %s -> %s\n' "${TARGET_BIN}" "${SOURCE_SCRIPT}"
|
||||
}
|
||||
|
||||
uninstall_link() {
|
||||
if [[ -L ${TARGET_BIN} || -e ${TARGET_BIN} ]]; then
|
||||
if target_is_current_link; then
|
||||
rm -f "${TARGET_BIN}"
|
||||
printf '已删除链接: %s\n' "${TARGET_BIN}"
|
||||
elif [[ -L ${TARGET_BIN} || -e ${TARGET_BIN} ]]; then
|
||||
printf '目标不是当前脚本创建的链接,拒绝删除: %s\n' "${TARGET_BIN}" >&2
|
||||
exit 1
|
||||
else
|
||||
printf '未发现已安装链接: %s\n' "${TARGET_BIN}"
|
||||
fi
|
||||
|
||||
@@ -24,6 +24,7 @@ run_test() {
|
||||
|
||||
run_test "${ROOT_DIR}/tests/test_common.sh"
|
||||
run_test "${ROOT_DIR}/tests/test_cli.sh"
|
||||
run_test "${ROOT_DIR}/tests/test_install.sh"
|
||||
run_test "${ROOT_DIR}/tests/test_interactive.sh"
|
||||
run_test "${ROOT_DIR}/tests/test_storage.sh"
|
||||
run_test "${ROOT_DIR}/tests/test_env_check.sh"
|
||||
|
||||
61
tests/test_install.sh
Normal file
61
tests/test_install.sh
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/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"
|
||||
|
||||
maybe_enter_namespace() {
|
||||
if (( EUID == 0 )); then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ${IPF_IN_NAMESPACE:-0} == 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v unshare >/dev/null 2>&1 && unshare -Ur true >/dev/null 2>&1; then
|
||||
exec unshare -Ur env IPF_IN_NAMESPACE=1 bash "$0"
|
||||
fi
|
||||
|
||||
printf 'SKIP: 安装测试需要 root 或可用的 unshare。\n'
|
||||
exit 0
|
||||
}
|
||||
|
||||
status_of() {
|
||||
set +e
|
||||
"$@" >/dev/null 2>&1
|
||||
local rc=$?
|
||||
set -e
|
||||
printf '%s\n' "${rc}"
|
||||
}
|
||||
|
||||
maybe_enter_namespace
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
trap 'rm -rf "${TMP_DIR}"' EXIT
|
||||
|
||||
TARGET_BIN="${TMP_DIR}/bin/iptables-forward"
|
||||
OTHER_TARGET="${TMP_DIR}/bin/not-ours"
|
||||
SOURCE_SCRIPT="${ROOT_DIR}/iptables-forward.sh"
|
||||
|
||||
install_output=$(INSTALL_TARGET="${TARGET_BIN}" "${ROOT_DIR}/install.sh")
|
||||
assert_contains "${install_output}" '已创建链接:' 'install should create symlink on first run'
|
||||
[[ -L ${TARGET_BIN} ]] || fail 'install should create a symlink'
|
||||
assert_eq "${SOURCE_SCRIPT}" "$(readlink -- "${TARGET_BIN}")" 'install should link to project entry script'
|
||||
|
||||
install_output=$(INSTALL_TARGET="${TARGET_BIN}" "${ROOT_DIR}/install.sh")
|
||||
assert_contains "${install_output}" '链接已存在:' 'install should be idempotent for existing project link'
|
||||
|
||||
printf 'keep-me\n' >"${OTHER_TARGET}"
|
||||
assert_status '1' "$(status_of env INSTALL_TARGET="${OTHER_TARGET}" "${ROOT_DIR}/install.sh")" 'install should refuse to overwrite non-project target'
|
||||
assert_eq 'keep-me' "$(cat "${OTHER_TARGET}")" 'install refusal should keep existing target untouched'
|
||||
|
||||
uninstall_output=$(INSTALL_TARGET="${TARGET_BIN}" "${ROOT_DIR}/install.sh" --uninstall)
|
||||
assert_contains "${uninstall_output}" '已删除链接:' 'uninstall should remove project symlink'
|
||||
[[ ! -e ${TARGET_BIN} && ! -L ${TARGET_BIN} ]] || fail 'uninstall should remove installed symlink'
|
||||
|
||||
assert_status '1' "$(status_of env INSTALL_TARGET="${OTHER_TARGET}" "${ROOT_DIR}/install.sh" --uninstall)" 'uninstall should refuse to delete non-project target'
|
||||
assert_eq 'keep-me' "$(cat "${OTHER_TARGET}")" 'uninstall refusal should keep existing target untouched'
|
||||
|
||||
pass 'test_install.sh'
|
||||
Reference in New Issue
Block a user