From 8813cffd126459fbb1c66216ceb879ab17e13b4a3232408341352589ca362ccd Mon Sep 17 00:00:00 2001 From: ahdoawhfo Date: Fri, 17 Apr 2026 12:27:17 +0800 Subject: [PATCH] Surface runtime health in menu --- README.md | 2 ++ iptables-forward.sh | 7 ++++--- lib/rules_mgr.sh | 8 ++++++++ tests/test_interactive.sh | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 79757fb..7753784 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,8 @@ sudo ./iptables-forward.sh - 立即保存到磁盘 - 退出 +主菜单状态行会同时显示环境状态、规则数、持久化可用性与运行态健康;若数据库里存在半残规则,`运行态` 会降级为 `[!]`。 + 规则列表中的状态列含义: - `✓`:该条规则对应的 PREROUTING / POSTROUTING / 双向 FORWARD 运行态规则都存在 diff --git a/iptables-forward.sh b/iptables-forward.sh index d8be3bf..db2d41f 100644 --- a/iptables-forward.sh +++ b/iptables-forward.sh @@ -63,9 +63,10 @@ bootstrap() { } render_main_menu() { - local status persist count width line + local status persist runtime count width line status=$(env_status_summary) count=$(storage_count) + runtime=$(rules_runtime_mark) if persist_available; then persist='[✓]' else @@ -77,7 +78,7 @@ render_main_menu() { box_top "${width}" box_line "${width}" "${IPF_APP_NAME}" box_separator "${width}" - box_line "${width}" "状态: ${status} 规则数: ${count} 持久化: ${persist}" + box_line "${width}" "状态: ${status} 规则数: ${count} 持久化: ${persist} 运行态: ${runtime}" box_separator "${width}" for line in "${MENU_LINES[@]}"; do box_line "${width}" "${line}" @@ -85,7 +86,7 @@ render_main_menu() { box_bottom "${width}" else printf '%s\n' "${IPF_APP_NAME}" - printf '状态: %s | 规则数: %s | 持久化: %s\n' "${status}" "${count}" "${persist}" + printf '状态: %s | 规则数: %s | 持久化: %s | 运行态: %s\n' "${status}" "${count}" "${persist}" "${runtime}" printf '%s\n' "${MENU_LINES[@]}" fi } diff --git a/lib/rules_mgr.sh b/lib/rules_mgr.sh index 9ff7db9..69460f9 100644 --- a/lib/rules_mgr.sh +++ b/lib/rules_mgr.sh @@ -146,6 +146,14 @@ rule_health_mark() { fi } +rules_runtime_mark() { + local line + while IFS= read -r line || [[ -n ${line} ]]; do + [[ -z ${line} || $(rule_health_mark "${line}") == '✓' ]] || { printf '[!]\n'; return 0; } + done < <(storage_list) + printf '[✓]\n' +} + render_rules_row() { printf '%s %s %s %s %s %s %s %s\n' \ "$(pad_right "${1-}" 3)" "$(pad_right "${2-}" 10)" "$(pad_right "${3-}" 10)" "$(pad_right "${4-}" 32)" \ diff --git a/tests/test_interactive.sh b/tests/test_interactive.sh index 81b82f4..8116bd0 100644 --- a/tests/test_interactive.sh +++ b/tests/test_interactive.sh @@ -40,6 +40,27 @@ export NETFILTER_PERSISTENT_BIN="${ROOT_DIR}/tests/mocks/persist-mock.sh" export PERSIST_MOCK_LOG="${TMP_DIR}/persist.log" export IPF_SKIP_ENV_CHECK=1 export IPF_FORCE_PLAIN_UI=1 + +# shellcheck source=lib/common.sh +source "${ROOT_DIR}/lib/common.sh" +# shellcheck source=lib/storage.sh +source "${ROOT_DIR}/lib/storage.sh" +# shellcheck source=lib/persist.sh +source "${ROOT_DIR}/lib/persist.sh" +# shellcheck source=lib/iptables_ops.sh +source "${ROOT_DIR}/lib/iptables_ops.sh" +# shellcheck source=lib/rules_mgr.sh +source "${ROOT_DIR}/lib/rules_mgr.sh" + +reset_mock_state() { + rm -rf "${IPTABLES_MOCK_DIR}" "${IPF_STORAGE_DIR}" + mkdir -p "${IPTABLES_MOCK_DIR}" "${IPF_STORAGE_DIR}" + : >"${IPTABLES_MOCK_LOG}" + : >"${PERSIST_MOCK_LOG}" + storage_init +} + +reset_mock_state export IPF_TEST_INPUTS=$'4\n2\n1\n8080\n1\n127.0.0.1\n80\ninteractive web\ny\n5\n1\n3\n1\ny\n0\n' output=$("${ROOT_DIR}/iptables-forward.sh" 2>&1) @@ -59,4 +80,17 @@ assert_eq '4' "$(grep -Ec ' -A ' "${IPTABLES_MOCK_LOG}")" 'interactive add flow assert_eq '4' "$(grep -Ec ' -D ' "${IPTABLES_MOCK_LOG}")" 'interactive delete should emit four delete commands' assert_eq '3' "$(grep -Ec 'persist-mock\.sh save' "${PERSIST_MOCK_LOG}")" 'interactive add/menu-save/delete flow should persist three times' +reset_mock_state +uuid=$(cmd_add_batch tcp 8081 127.0.0.1 81 4 'degraded menu' 2>/dev/null) +"${IPTABLES_BIN}" -D FORWARD \ + -p tcp -s 127.0.0.1 --sport 81 \ + -m conntrack --ctstate ESTABLISHED,RELATED \ + -j ACCEPT \ + -m comment --comment "MGMT:${uuid}" +export IPF_TEST_INPUTS=$'0\n' + +degraded_output=$("${ROOT_DIR}/iptables-forward.sh" 2>&1) +assert_contains "${degraded_output}" '运行态: [!]' 'main menu should surface degraded runtime status' +assert_contains "${degraded_output}" '规则数: 1' 'main menu should still show current rule count' + pass 'test_interactive.sh'