The repo was committed from WSL with core.filemode=false, so the exec bit was never recorded. After actions/checkout the entry script comes down as 100644 and tests/test_cli.sh fails with Permission denied. Set mode 100755 on every script that is invoked directly (entry, installer, test suite, mock binaries). Sourced helpers under lib/ keep 100644 per convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
183 lines
3.6 KiB
Bash
Executable File
183 lines
3.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -Eeuo pipefail
|
||
|
||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||
LIB_DIR=${SCRIPT_DIR}/lib
|
||
|
||
# shellcheck source=lib/common.sh
|
||
source "${LIB_DIR}/common.sh"
|
||
|
||
on_error() {
|
||
local exit_code=$1
|
||
local line_no=$2
|
||
log_err "脚本在第 ${line_no} 行发生错误,退出码 ${exit_code}。"
|
||
exit "${exit_code}"
|
||
}
|
||
trap 'on_error "$?" "$LINENO"' ERR
|
||
|
||
# shellcheck source=lib/storage.sh
|
||
source "${LIB_DIR}/storage.sh"
|
||
# shellcheck source=lib/persist.sh
|
||
source "${LIB_DIR}/persist.sh"
|
||
# shellcheck source=lib/iptables_ops.sh
|
||
source "${LIB_DIR}/iptables_ops.sh"
|
||
# shellcheck source=lib/env_check.sh
|
||
source "${LIB_DIR}/env_check.sh"
|
||
# shellcheck source=lib/rules_mgr.sh
|
||
source "${LIB_DIR}/rules_mgr.sh"
|
||
|
||
: "${IPF_SKIP_ENV_CHECK:=0}"
|
||
|
||
usage() {
|
||
cat <<USAGE
|
||
用法:
|
||
./iptables-forward.sh
|
||
./iptables-forward.sh --batch add <proto> <listen_port> <target_ip> <target_port> <ipver> [desc]
|
||
./iptables-forward.sh --batch delete <uuid>
|
||
./iptables-forward.sh --batch list
|
||
./iptables-forward.sh --batch save
|
||
./iptables-forward.sh --batch env
|
||
./iptables-forward.sh --help
|
||
|
||
说明:
|
||
- proto: tcp / udp / both
|
||
- ipver: 4 / 6 / both
|
||
- 当 ipver=both 时,target_ip 需使用 IPv4,IPv6 形式,例如 127.0.0.1,::1
|
||
USAGE
|
||
}
|
||
|
||
MENU_LINES=(
|
||
'[1] 查看所有转发规则'
|
||
'[2] 添加新的转发规则'
|
||
'[3] 删除现有转发规则'
|
||
'[4] 查看系统环境状态'
|
||
'[5] 立即保存到磁盘'
|
||
'[0] 退出'
|
||
)
|
||
|
||
bootstrap() {
|
||
if [[ ${IPF_SKIP_ENV_CHECK} != 1 ]]; then
|
||
env_check_all
|
||
fi
|
||
storage_init
|
||
}
|
||
|
||
render_main_menu() {
|
||
local status persist runtime count width line
|
||
status=$(env_status_summary)
|
||
count=$(storage_count)
|
||
runtime=$(rules_runtime_mark)
|
||
if persist_available; then
|
||
persist='[✓]'
|
||
else
|
||
persist='[!]'
|
||
fi
|
||
|
||
if use_box_ui; then
|
||
width=$(box_width)
|
||
box_top "${width}"
|
||
box_line "${width}" "${IPF_APP_NAME}"
|
||
box_separator "${width}"
|
||
box_line "${width}" "状态: ${status} 规则数: ${count} 持久化: ${persist} 运行态: ${runtime}"
|
||
box_separator "${width}"
|
||
for line in "${MENU_LINES[@]}"; do
|
||
box_line "${width}" "${line}"
|
||
done
|
||
box_bottom "${width}"
|
||
else
|
||
printf '%s\n' "${IPF_APP_NAME}"
|
||
printf '状态: %s | 规则数: %s | 持久化: %s | 运行态: %s\n' "${status}" "${count}" "${persist}" "${runtime}"
|
||
printf '%s\n' "${MENU_LINES[@]}"
|
||
fi
|
||
}
|
||
|
||
main_menu_loop() {
|
||
local choice
|
||
while true; do
|
||
render_main_menu
|
||
prompt_input_capture '请选择 [0-5]' ''
|
||
choice=${PROMPT_INPUT_RESULT}
|
||
case ${choice} in
|
||
1) cmd_list ;;
|
||
2) cmd_add ;;
|
||
3) cmd_delete ;;
|
||
4) cmd_show_env_status ;;
|
||
5)
|
||
cmd_save_rules
|
||
pause_return
|
||
;;
|
||
0)
|
||
log_info '已退出。'
|
||
return 0
|
||
;;
|
||
*)
|
||
log_warn '无效选择,请输入 0-5。'
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
run_batch() {
|
||
local action=${1-}
|
||
shift || true
|
||
case ${action} in
|
||
add)
|
||
if (($# < 5)); then
|
||
usage
|
||
return 1
|
||
fi
|
||
cmd_add_batch "$@"
|
||
;;
|
||
delete)
|
||
if (($# != 1)); then
|
||
usage
|
||
return 1
|
||
fi
|
||
cmd_delete_uuid "$1"
|
||
;;
|
||
list)
|
||
storage_list
|
||
;;
|
||
save)
|
||
cmd_save_rules
|
||
;;
|
||
env)
|
||
env_check_all
|
||
;;
|
||
*)
|
||
usage
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
main() {
|
||
case ${1-} in
|
||
--help|-h)
|
||
usage
|
||
return 0
|
||
;;
|
||
esac
|
||
|
||
require_root || return $?
|
||
bootstrap
|
||
|
||
if (($# == 0)); then
|
||
main_menu_loop
|
||
return 0
|
||
fi
|
||
|
||
case ${1-} in
|
||
--batch)
|
||
shift
|
||
run_batch "$@"
|
||
;;
|
||
*)
|
||
usage
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
main "$@"
|