#!/usr/bin/env bash if [[ -n ${IPF_ENV_CHECK_SH_LOADED:-} ]]; then return 0 fi IPF_ENV_CHECK_SH_LOADED=1 : "${IPF_CHECK_IPTABLES_CMD:=iptables}" : "${IPF_CHECK_IP6TABLES_CMD:=ip6tables}" : "${IPF_CHECK_PERSIST_CMD:=netfilter-persistent}" : "${DPKG_BIN:=dpkg}" : "${APT_GET_BIN:=apt-get}" : "${DEBCONF_SET_SELECTIONS_BIN:=debconf-set-selections}" : "${SYSCTL_BIN:=sysctl}" : "${SYSTEMCTL_BIN:=systemctl}" : "${IPF_SYSCTL_FILE:=/etc/sysctl.d/99-iptables-forward.conf}" : "${IPF_IPV4_FORWARD_FILE:=/proc/sys/net/ipv4/ip_forward}" : "${IPF_IPV6_FORWARD_FILE:=/proc/sys/net/ipv6/conf/all/forwarding}" : "${IPF_ASSUME_YES:=0}" ENV_CHECK_ISSUES=() ENV_CHECK_WARNINGS=() ENV_CHECK_NEED_PACKAGES=0 ENV_CHECK_NEED_SYSCTL=0 ENV_CHECK_NEED_STORAGE=0 _env_add_issue() { ENV_CHECK_ISSUES+=("$1") } _env_add_warning() { ENV_CHECK_WARNINGS+=("$1") } _env_file_value() { local file=$1 [[ -f ${file} ]] || return 1 tr -d '[:space:]' <"${file}" } _env_package_installed() { if ! command_is_available "${DPKG_BIN}"; then return 1 fi "${DPKG_BIN}" -s iptables-persistent >/dev/null 2>&1 } env_check_collect_issues() { ENV_CHECK_ISSUES=() ENV_CHECK_WARNINGS=() ENV_CHECK_NEED_PACKAGES=0 ENV_CHECK_NEED_SYSCTL=0 ENV_CHECK_NEED_STORAGE=0 if ! command_is_available "${IPF_CHECK_IPTABLES_CMD}"; then _env_add_issue '缺少 iptables 命令。' ENV_CHECK_NEED_PACKAGES=1 fi if ! command_is_available "${IPF_CHECK_IP6TABLES_CMD}"; then _env_add_issue '缺少 ip6tables 命令。' ENV_CHECK_NEED_PACKAGES=1 fi if ! _env_package_installed || ! command_is_available "${IPF_CHECK_PERSIST_CMD}"; then _env_add_issue '缺少 iptables-persistent / netfilter-persistent。' ENV_CHECK_NEED_PACKAGES=1 fi if [[ $(_env_file_value "${IPF_IPV4_FORWARD_FILE}" 2>/dev/null || printf '0') != 1 ]]; then _env_add_issue 'IPv4 转发未开启。' ENV_CHECK_NEED_SYSCTL=1 fi if [[ $(_env_file_value "${IPF_IPV6_FORWARD_FILE}" 2>/dev/null || printf '0') != 1 ]]; then _env_add_issue 'IPv6 转发未开启。' ENV_CHECK_NEED_SYSCTL=1 fi if [[ ! -d ${IPF_STORAGE_DIR} || ! -w ${IPF_STORAGE_DIR} ]]; then _env_add_issue "状态目录不可用: ${IPF_STORAGE_DIR}" ENV_CHECK_NEED_STORAGE=1 fi if command_is_available "${SYSTEMCTL_BIN}"; then if "${SYSTEMCTL_BIN}" is-active --quiet ufw 2>/dev/null; then _env_add_warning '检测到 ufw 正在运行,可能会影响转发规则。' fi if "${SYSTEMCTL_BIN}" is-active --quiet firewalld 2>/dev/null; then _env_add_warning '检测到 firewalld 正在运行,可能会影响转发规则。' fi fi return 0 } env_check_print_report() { local issue if ((${#ENV_CHECK_ISSUES[@]} == 0)); then log_ok '环境检查通过。' else log_warn '发现以下待修复项:' for issue in "${ENV_CHECK_ISSUES[@]}"; do printf ' - %s\n' "${issue}" >&2 done fi if ((${#ENV_CHECK_WARNINGS[@]} > 0)); then log_warn '附加警告:' for issue in "${ENV_CHECK_WARNINGS[@]}"; do printf ' - %s\n' "${issue}" >&2 done fi } _env_install_packages() { command_is_available "${APT_GET_BIN}" || { log_err '缺少 apt-get,无法自动安装依赖。' return 1 } command_is_available "${DEBCONF_SET_SELECTIONS_BIN}" || { log_err '缺少 debconf-set-selections,无法预置安装选项。' return 1 } printf 'iptables-persistent iptables-persistent/autosave_v4 boolean true\n' | "${DEBCONF_SET_SELECTIONS_BIN}" printf 'iptables-persistent iptables-persistent/autosave_v6 boolean true\n' | "${DEBCONF_SET_SELECTIONS_BIN}" DEBIAN_FRONTEND=noninteractive "${APT_GET_BIN}" update -qq DEBIAN_FRONTEND=noninteractive "${APT_GET_BIN}" install -y -qq iptables iptables-persistent } _env_write_sysctl() { local backup='' if [[ -f ${IPF_SYSCTL_FILE} ]]; then backup=$(cat "${IPF_SYSCTL_FILE}") fi cat >"${IPF_SYSCTL_FILE}" <"${IPF_SYSCTL_FILE}" else rm -f "${IPF_SYSCTL_FILE}" fi return 1 fi if ! "${SYSCTL_BIN}" --system >/dev/null; then if [[ -n ${backup} ]]; then printf '%s' "${backup}" >"${IPF_SYSCTL_FILE}" else rm -f "${IPF_SYSCTL_FILE}" fi return 1 fi } env_check_apply_fixes() { if (( ENV_CHECK_NEED_PACKAGES == 1 )); then log_info '正在安装缺失软件包...' _env_install_packages || return 1 fi if (( ENV_CHECK_NEED_STORAGE == 1 )); then log_info '正在创建状态目录...' mkdir -p "${IPF_STORAGE_DIR}" chmod 750 "${IPF_STORAGE_DIR}" fi if (( ENV_CHECK_NEED_SYSCTL == 1 )); then log_info '正在写入 sysctl 配置并启用转发...' _env_write_sysctl || return 1 fi } env_check_all() { env_check_collect_issues env_check_print_report if ((${#ENV_CHECK_ISSUES[@]} == 0)); then return 0 fi if [[ ${IPF_ASSUME_YES} == 1 ]]; then log_info '已启用自动确认,开始修复。' elif ! prompt_confirm '是否自动修复以上问题?' n; then log_err '用户取消自动修复,请根据提示手动安装依赖并重试。' return 3 fi env_check_apply_fixes || return 1 env_check_collect_issues env_check_print_report if ((${#ENV_CHECK_ISSUES[@]} > 0)); then log_err '自动修复后仍存在未解决的问题。' return 1 fi return 0 } env_status_summary() { env_check_collect_issues if ((${#ENV_CHECK_ISSUES[@]} == 0)); then printf '就绪\n' else printf '待修复(%d)\n' "${#ENV_CHECK_ISSUES[@]}" fi }