diff --git a/.github/workflows/bash-syntax.yml b/.github/workflows/bash-syntax.yml index 2e27cee..f1d0f45 100644 --- a/.github/workflows/bash-syntax.yml +++ b/.github/workflows/bash-syntax.yml @@ -26,6 +26,9 @@ jobs: - name: Test step 1 selections run: bash tests/test-step1-selection.sh + - name: Test step 2 IPv4 firewall mode + run: bash tests/test-step2-ipv4-firewall.sh + - name: Test master language selection run: bash tests/test-master-language.sh diff --git a/README.md b/README.md index 8a3fc1e..6b719c9 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ The script does not request or store model-provider API keys. After installation ## Firewall and SSH notes -- Step 2 supports IPv4-only, IPv6-only, and dual-stack UFW rules. +- Step 2 supports IPv4-only, IPv6-only, and dual-stack UFW rules. IPv4-only mode blocks public inbound IPv6 through UFW without disabling the kernel IPv6 stack. - A repeated step 2 run can change the IP-family mode or add ports without reinstalling everything. - Step 3 reads the SSH port chosen in step 2 and creates an administrator with sudo access. - The home directory, `.ssh`, and `authorized_keys` ownership and permissions are checked on every run. diff --git a/docs/readme/README.ru.md b/docs/readme/README.ru.md index a412b41..640c4c6 100644 --- a/docs/readme/README.ru.md +++ b/docs/readme/README.ru.md @@ -164,11 +164,11 @@ sysctl vm.swappiness Для входящих подключений можно выбрать: -- только IPv4 — IPv6 отключается в UFW и системно через `sysctl`; этот режим рекомендуется, если 3x-ui используется только по IPv4; +- только IPv4 — публичные входящие IPv6-порты закрываются через UFW, но IPv6-стек ядра не отключается; этот режим рекомендуется, если 3x-ui используется только по IPv4; - только IPv6 — управляемые скриптом порты открываются лишь для IPv6; - IPv4 + IPv6 — порты открываются для обоих семейств. -Выбор сохраняется в `/etc/ochenstarik-server/ip-family.conf`, а список управляемых портов — в `/etc/ochenstarik-server/ufw-managed-ports.conf`. При переходе на режим с одним семейством старые правила этих портов пересоздаются. Скрипт не разрешит отключить семейство адресов, через которое работает текущая SSH-сессия. Для режима IPv6 также проверяются глобальный IPv6-адрес и маршрут по умолчанию. +Выбор сохраняется в `/etc/ochenstarik-server/ip-family.conf`, а список управляемых портов — в `/etc/ochenstarik-server/ufw-managed-ports.conf`. При переходе на режим с одним семейством старые правила этих портов пересоздаются. Скрипт не разрешит отключить семейство адресов, через которое работает текущая SSH-сессия. Для режима IPv4 UFW остаётся с поддержкой IPv6, чтобы политика `deny incoming` продолжала закрывать публичный IPv6. Для режима IPv6 также проверяются глобальный IPv6-адрес и маршрут по умолчанию. В режиме «только IPv6» системный IPv4 остаётся доступен для исходящих соединений и работы менеджера пакетов, но входящие управляемые порты по IPv4 закрываются UFW. Полное переносимое отключение IPv4 в Linux без привязки к конкретной сетевой конфигурации не выполняется. diff --git a/ochenstarik-server-2.sh b/ochenstarik-server-2.sh old mode 100644 new mode 100755 index 2969f69..1c5be54 --- a/ochenstarik-server-2.sh +++ b/ochenstarik-server-2.sh @@ -87,7 +87,7 @@ choose_ip_mode() { esac printf '\nВыберите семейства IP для входящих подключений:\n' - printf ' 1) Только IPv4 — IPv6 отключается системно и в UFW\n' + printf ' 1) Только IPv4 — входящие IPv6-порты закрываются через UFW\n' printf ' 2) Только IPv6 — открытые входящие порты доступны лишь по IPv6\n' printf ' 3) IPv4 + IPv6\n' while :; do @@ -206,19 +206,6 @@ set_ufw_ipv6_setting() { fi } -disable_ipv6_systemwide() { - cat > "$IPV6_SYSCTL_FILE" <<'EOF' -# Managed by ochenstarik-server-2.sh -net.ipv6.conf.all.disable_ipv6 = 1 -net.ipv6.conf.default.disable_ipv6 = 1 -net.ipv6.conf.lo.disable_ipv6 = 1 -EOF - chmod 644 "$IPV6_SYSCTL_FILE" - sysctl -p "$IPV6_SYSCTL_FILE" >/dev/null - [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null || true)" == 1 ]] \ - || die "Не удалось отключить IPv6" -} - enable_ipv6_systemwide() { local sysctl_path local -a sysctl_paths=(/proc/sys/net/ipv6/conf/*/disable_ipv6) @@ -230,6 +217,11 @@ enable_ipv6_systemwide() { done } +restore_ipv6_stack_if_managed() { + [[ -e "$IPV6_SYSCTL_FILE" ]] || return 0 + enable_ipv6_systemwide +} + check_session_safety() { local client_ip client_port server_ip server_port @@ -284,9 +276,10 @@ apply_ip_mode_and_firewall() { check_session_safety if [[ "$IP_MODE" == ipv4 ]]; then + restore_ipv6_stack_if_managed + set_ufw_ipv6_setting yes + ufw reload >/dev/null 2>&1 || true delete_managed_ufw_rules - set_ufw_ipv6_setting no - disable_ipv6_systemwide else enable_ipv6_systemwide [[ "$IP_MODE" != ipv6 ]] || verify_ipv6_connectivity diff --git a/tests/test-step2-ipv4-firewall.sh b/tests/test-step2-ipv4-firewall.sh new file mode 100755 index 0000000..56f8016 --- /dev/null +++ b/tests/test-step2-ipv4-firewall.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +script_path="${1:-ochenstarik-server-2.sh}" + +[[ -f "$script_path" ]] || { + printf '[x] Missing script: %s\n' "$script_path" >&2 + exit 1 +} + +if grep -q 'disable_ipv6 = 1' "$script_path"; then + printf '[x] Step 2 must not disable the IPv6 kernel stack for IPv4 mode\n' >&2 + exit 1 +fi + +if grep -q 'disable_ipv6_systemwide' "$script_path"; then + printf '[x] Step 2 still references the old system-wide IPv6 disable helper\n' >&2 + exit 1 +fi + +if ! grep -q 'restore_ipv6_stack_if_managed' "$script_path"; then + printf '[x] Step 2 should restore IPv6 when a legacy managed sysctl file exists\n' >&2 + exit 1 +fi + +ipv4_block="$( + awk ' + /if \[\[ "\$IP_MODE" == ipv4 \]\]; then/ { capture=1 } + capture { print } + capture && /^ else$/ { exit } + ' "$script_path" +)" + +if [[ "$ipv4_block" != *'set_ufw_ipv6_setting yes'* ]]; then + printf '[x] IPv4 mode must keep UFW IPv6 support enabled so deny incoming applies to IPv6\n' >&2 + exit 1 +fi + +if [[ "$ipv4_block" != *'delete_managed_ufw_rules'* ]]; then + printf '[x] IPv4 mode must delete old managed IPv6 allow rules before applying IPv4-only rules\n' >&2 + exit 1 +fi + +printf '[+] Step 2 IPv4 firewall behavior looks correct\n'