Modify a client parameter"
@@ -1399,15 +1447,18 @@ usage() {
echo " (dkms autoinstall + modprobe + start awg-quick)"
echo " help Show this help"
echo ""
- exit 1
+ exit "$_rc"
}
# ==============================================================================
# Main logic
# ==============================================================================
-if [[ "$COMMAND" == "help" || -z "$COMMAND" ]]; then
- usage
+if [[ -z "$COMMAND" ]]; then
+ usage 1
+fi
+if [[ "$COMMAND" == "help" ]]; then
+ usage "$HELP_EXIT_RC"
fi
check_dependencies || exit 1
diff --git a/scripts/check-docs-consistency.sh b/scripts/check-docs-consistency.sh
new file mode 100644
index 0000000..45f90b1
--- /dev/null
+++ b/scripts/check-docs-consistency.sh
@@ -0,0 +1,195 @@
+#!/bin/bash
+# check-docs-consistency.sh - быстрые проверки согласованности документации
+#
+# Лёгкий аналог preflight для документации и метаданных: ничего не собирает,
+# не качает по сети, не гоняет bats. Только дешёвые детерминированные проверки,
+# которые ловят классы рассинхрона, проскакивающие мимо shellcheck/test
+# (битые внутренние ссылки, рассогласование версий, протухшая матрица ОС).
+#
+# Запускается локально и в CI (docs-check workflow), а также включён в
+# preflight-check.sh как один из шагов.
+#
+# Использование:
+# bash scripts/check-docs-consistency.sh
+#
+# Проверки:
+# 1. Внутренние markdown-ссылки (#anchor) резолвятся в этом же файле.
+# 2. CHANGELOG: у каждого version-heading есть reference-link; набор версий
+# в RU == EN; [Unreleased] присутствует в обоих.
+# 3. Version triple: README badge == SCRIPT_VERSION == верхний changelog
+# heading (RU и EN).
+# 4. Матрица ОС: Ubuntu 26.04 присутствует во всех заявленных местах.
+# 5. SECURITY/CONTRIBUTING не протухли (текущий minor в supported-таблице;
+# нет захардкоженного test-count baseline).
+
+set -o pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+cd "$REPO_ROOT" || { echo "ОШИБКА: не удалось перейти в $REPO_ROOT" >&2; exit 2; }
+
+PASS=0
+FAIL=0
+declare -a RESULTS
+
+_ok() { echo "PASS: $1"; RESULTS+=("PASS: $1"); PASS=$((PASS+1)); }
+_bad() { echo "FAIL: $1" >&2; RESULTS+=("FAIL: $1"); FAIL=$((FAIL+1)); }
+
+# Файлы документации с внутренними якорями.
+DOC_FILES=(
+ README.md README.en.md
+ ADVANCED.md ADVANCED.en.md
+ CHANGELOG.md CHANGELOG.en.md
+ SECURITY.md CONTRIBUTING.md INSTALL_VPS.md
+ docs/SIGNING_DESIGN.md docs/RELEASE_PROCESS.md
+)
+
+echo "=== check-docs-consistency ==="
+
+# GitHub-совместимая slug-генерация из текста заголовка:
+# lowercase, убрать всё кроме букв/цифр/пробела/дефиса, пробелы -> дефисы,
+# срезать ведущие/хвостовые дефисы (их даёт, например, emoji в начале heading -
+# GitHub их тоже не оставляет).
+_slug() {
+ local s="$1"
+ s="${s#"${s%%[![:space:]]*}"}" # ltrim
+ s="${s%"${s##*[![:space:]]}"}" # rtrim
+ printf '%s' "$s" \
+ | tr '[:upper:]' '[:lower:]' \
+ | LC_ALL=C sed -E 's/[^a-z0-9 _-]//g' \
+ | tr ' ' '-' \
+ | sed -E 's/^-+//; s/-+$//'
+}
+
+# Удалить из markdown код, чтобы примеры разметки внутри него не парсились как
+# реальные ссылки/якоря: fenced-блоки (``` ... ```) целиком + inline-спаны
+# (`...`). Иначе документированный в backticks пример вида `](#anchor)` дал бы
+# ложный FAIL.
+_strip_code() {
+ awk '/^```/ { infence = !infence; next } !infence { print }' "$1" \
+ | sed 's/`[^`]*`//g'
+}
+
+# --- 1. Внутренние anchor-ссылки резолвятся ---
+anchor_fail=0
+for f in "${DOC_FILES[@]}"; do
+ [[ -f "$f" ]] || continue
+
+ # Анализируем файл с вырезанным кодом (примеры в backticks - не разметка).
+ stripped="$(_strip_code "$f")"
+
+ # Целевые якоря в файле: явные / + slug'и заголовков.
+ declare -A anchors=()
+ while IFS= read -r a; do
+ [[ -n "$a" ]] && anchors["$a"]=1
+ done < <(printf '%s\n' "$stripped" | grep -oiP '&2
+ anchor_fail=1
+ fi
+ done < <(printf '%s\n' "$stripped" | grep -oP '\]\(#\K[^)]+')
+
+ unset anchors
+done
+if [[ "$anchor_fail" -eq 0 ]]; then _ok "внутренние anchor-ссылки резолвятся"; else _bad "битые внутренние anchor-ссылки"; fi
+
+# --- 2. CHANGELOG: heading <-> reference-link, RU == EN ---
+changelog_fail=0
+_changelog_versions() { # печатает версии из "## [X]" headings
+ grep -oP '^##\s+\[\K[^]]+' "$1"
+}
+_changelog_refs() { # печатает версии из "[X]:" reference-links
+ grep -oP '^\[\K[^]]+(?=\]:)' "$1"
+}
+for f in CHANGELOG.md CHANGELOG.en.md; do
+ [[ -f "$f" ]] || { _bad "нет $f"; changelog_fail=1; continue; }
+ while IFS= read -r v; do
+ [[ -z "$v" ]] && continue
+ if ! _changelog_refs "$f" | grep -qxF "$v"; then
+ echo " $f: heading [$v] без reference-link [$v]:" >&2
+ changelog_fail=1
+ fi
+ done < <(_changelog_versions "$f")
+ if ! _changelog_versions "$f" | grep -qxF "Unreleased"; then
+ echo " $f: нет heading [Unreleased]" >&2
+ changelog_fail=1
+ fi
+done
+# RU == EN набор версий (отсортированные уникальные heading-списки).
+if [[ -f CHANGELOG.md && -f CHANGELOG.en.md ]]; then
+ ru_set="$(_changelog_versions CHANGELOG.md | sort -u)"
+ en_set="$(_changelog_versions CHANGELOG.en.md | sort -u)"
+ if [[ "$ru_set" != "$en_set" ]]; then
+ echo " набор версий CHANGELOG.md != CHANGELOG.en.md:" >&2
+ diff <(printf '%s\n' "$ru_set") <(printf '%s\n' "$en_set") >&2 || true
+ changelog_fail=1
+ fi
+fi
+if [[ "$changelog_fail" -eq 0 ]]; then _ok "CHANGELOG headings/refs согласованы, RU == EN"; else _bad "CHANGELOG рассинхрон"; fi
+
+# --- 3. Version triple: badge == SCRIPT_VERSION == верхний changelog heading ---
+ver_fail=0
+script_ver="$(awk -F'"' '/^SCRIPT_VERSION=/{print $2; exit}' install_amneziawg.sh)"
+# Верхний non-Unreleased heading в каждом changelog.
+top_ru="$(grep -oP '^##\s+\[\K[0-9]+\.[0-9]+\.[0-9]+' CHANGELOG.md | head -n1)"
+top_en="$(grep -oP '^##\s+\[\K[0-9]+\.[0-9]+\.[0-9]+' CHANGELOG.en.md | head -n1)"
+for pair in "README.md:$script_ver" "README.en.md:$script_ver"; do
+ rf="${pair%%:*}"; expect="${pair##*:}"
+ badge="$(grep -oP 'Installer_Version-\K[0-9]+\.[0-9]+\.[0-9]+' "$rf" | head -n1)"
+ if [[ "$badge" != "$expect" ]]; then
+ echo " $rf badge='$badge' != SCRIPT_VERSION='$expect'" >&2
+ ver_fail=1
+ fi
+done
+if [[ "$top_ru" != "$script_ver" ]]; then echo " CHANGELOG.md top heading '$top_ru' != SCRIPT_VERSION '$script_ver'" >&2; ver_fail=1; fi
+if [[ "$top_en" != "$script_ver" ]]; then echo " CHANGELOG.en.md top heading '$top_en' != SCRIPT_VERSION '$script_ver'" >&2; ver_fail=1; fi
+if [[ "$ver_fail" -eq 0 ]]; then _ok "version triple согласован ($script_ver)"; else _bad "version triple рассинхрон"; fi
+
+# --- 4. Матрица ОС: Ubuntu 26.04 во всех заявленных местах ---
+os_fail=0
+for f in README.md README.en.md install_amneziawg.sh install_amneziawg_en.sh .github/ISSUE_TEMPLATE/bug_report.yml; do
+ [[ -f "$f" ]] || { echo " нет $f (проверка 26.04)" >&2; os_fail=1; continue; }
+ if ! grep -q '26\.04' "$f"; then
+ echo " $f: нет упоминания Ubuntu 26.04 в матрице ОС" >&2
+ os_fail=1
+ fi
+done
+if [[ "$os_fail" -eq 0 ]]; then _ok "Ubuntu 26.04 присутствует в матрице ОС"; else _bad "Ubuntu 26.04 отсутствует где-то в матрице ОС"; fi
+
+# --- 5. SECURITY/CONTRIBUTING не протухли ---
+stale_fail=0
+# Текущий minor (X.Y) должен фигурировать в SECURITY supported-таблице.
+minor="$(printf '%s' "$script_ver" | grep -oP '^[0-9]+\.[0-9]+')"
+if [[ -f SECURITY.md ]]; then
+ if ! grep -qE "${minor//./\\.}\.[x0-9]" SECURITY.md; then
+ echo " SECURITY.md: текущий minor $minor.x не найден в supported-таблице" >&2
+ stale_fail=1
+ fi
+else
+ echo " нет SECURITY.md" >&2; stale_fail=1
+fi
+# CONTRIBUTING не должен хардкодить число тестов (хрупкий baseline).
+if [[ -f CONTRIBUTING.md ]]; then
+ if grep -qiP '\b[0-9]{3,}\s+tests?\b' CONTRIBUTING.md; then
+ echo " CONTRIBUTING.md: захардкоженный счётчик тестов (хрупкий baseline)" >&2
+ stale_fail=1
+ fi
+fi
+if [[ "$stale_fail" -eq 0 ]]; then _ok "SECURITY/CONTRIBUTING не протухли"; else _bad "SECURITY/CONTRIBUTING протухли"; fi
+
+# --- Summary ---
+echo ""
+echo "=== docs-consistency summary: $PASS passed, $FAIL failed ==="
+for r in "${RESULTS[@]}"; do echo " $r"; done
+
+[[ "$FAIL" -gt 0 ]] && exit 1
+exit 0
diff --git a/scripts/preflight-check.sh b/scripts/preflight-check.sh
index 070c26b..e06eb43 100644
--- a/scripts/preflight-check.sh
+++ b/scripts/preflight-check.sh
@@ -1,16 +1,19 @@
#!/bin/bash
# preflight-check.sh - единый прогон всех pre-tag проверок перед релизом
#
-# Запускает в одной команде полный pre-tag чеклист (см. CLAUDE.md "Release
-# Process" и memory feedback_awg20_release_checklist). Каждая проверка печатает
-# PASS/FAIL, в конце - сводка. Exit!=0 если хоть одна провалилась.
+# Запускает в одной команде полный pre-tag чеклист (см. docs/RELEASE_PROCESS.md).
+# Каждая проверка печатает PASS/FAIL, в конце - сводка. Exit!=0 если хоть одна
+# провалилась.
#
# Использование:
# bash scripts/preflight-check.sh
+# BASE_REF=origin/main bash scripts/preflight-check.sh # detached checkout
#
# Переменные окружения:
-# BASE_REF git-ref для diff-проверок пунктуации/маркеров. Default: main.
-# LOG_RANGE git-диапазон для проверки commit-сообщений. Default: main..HEAD
+# BASE_REF git-ref для diff-проверок пунктуации/маркеров. Если не задан,
+# берётся первый существующий из: main, origin/main. На detached
+# checkout без локального main задавайте явно BASE_REF=origin/main.
+# LOG_RANGE git-диапазон для проверки commit-сообщений. Default: ..HEAD
# (только коммиты ветки релиза; merged-from-main коммиты с legacy
# human/bot Co-authored-by трейлерами в проверку НЕ попадают).
#
@@ -18,11 +21,12 @@
# 1. bash -n на 6 скриптах
# 2. shellcheck -s bash -S warning на 6 скриптах
# 3. bats tests/
-# 4. em/en-dash (U+2013/U+2014) в diff BASE_REF...HEAD = 0
+# 4. реально добавленных em/en-dash (U+2013/U+2014) в diff BASE_REF...HEAD = 0
# 5. AI/tool-mention в diff + commit-логе = 0
# 6. Co-authored-by в commit-логе = 0
# 7. SCRIPT_VERSION консистентен в 4 версионированных скриптах
# 8. SHA-пины синхронны (update-sha-pins.sh --verify)
+# 9. Согласованность документации (check-docs-consistency.sh)
set -o pipefail
@@ -30,8 +34,23 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$REPO_ROOT" || { echo "ОШИБКА: не удалось перейти в $REPO_ROOT" >&2; exit 2; }
-BASE_REF="${BASE_REF:-main}"
-LOG_RANGE="${LOG_RANGE:-main..HEAD}"
+# BASE_REF fallback: уважаем явно заданный, иначе первый резолвящийся из
+# main / origin/main. Это переживает detached checkout (CI на теге), где
+# локального main нет, но origin/main подтянут (fetch-depth:0 + fetch main).
+if [[ -z "${BASE_REF:-}" ]]; then
+ for _cand in main origin/main; do
+ if git rev-parse --verify "$_cand" >/dev/null 2>&1; then
+ BASE_REF="$_cand"
+ break
+ fi
+ done
+fi
+if [[ -z "${BASE_REF:-}" ]]; then
+ echo "ОШИБКА: не найден base-ref для diff-проверок (пробовал main, origin/main)." >&2
+ echo " Задайте явно, например: BASE_REF=origin/main bash scripts/preflight-check.sh" >&2
+ exit 2
+fi
+LOG_RANGE="${LOG_RANGE:-${BASE_REF}..HEAD}"
SCRIPTS=(
install_amneziawg.sh
@@ -97,20 +116,23 @@ else
_bad "bats not found in PATH"
fi
-# --- 4. em/en-dash in diff ---
-if git rev-parse --verify "$BASE_REF" >/dev/null 2>&1; then
- # Байтовые паттерны UTF-8: en-dash U+2013 = E2 80 93, em-dash U+2014 = E2 80 94.
- # LC_ALL=C + \xHH работает на GNU grep 3.0 (Git Bash), в отличие от \x{2013}
- # (codepoint-форма падает "character value in \x{} too large" и глоталась || true).
- dash_hits=$(git diff "${BASE_REF}...HEAD" | LC_ALL=C grep -nP '^\+.*(\xe2\x80\x93|\xe2\x80\x94)' || true)
- if [[ -z "$dash_hits" ]]; then
- _ok "no em/en-dash in diff ${BASE_REF}...HEAD"
- else
- echo "$dash_hits" >&2
- _bad "em/en-dash found in diff ${BASE_REF}...HEAD"
- fi
+# --- 4. newly added em/en-dash in diff ---
+# Политика проекта (CLAUDE.md): новый текст - только hyphen-minus; legacy
+# em/en-dash в существующих строках сохраняется без mass-purge, а point-edit
+# легаси-строки НЕ обязывает переписывать тире на этой строке. Поэтому ловим
+# ТОЛЬКО реально добавленные тире. word-diff=porcelain помечает префиксом '+'
+# лишь изменённые слова, так что тире в неизменённой части правленой строки в
+# '+' не попадает (legacy не даёт ложный FAIL); под флаг идёт только заново
+# внесённый символ тире (новая строка/слово). Байты UTF-8: en-dash U+2013 =
+# E2 80 93, em-dash U+2014 = E2 80 94 (LC_ALL=C + \xHH, codepoint-форма \x{}
+# падает на GNU grep Git Bash).
+dash_added=$(git diff --word-diff=porcelain "${BASE_REF}...HEAD" \
+ | LC_ALL=C grep -nP '^\+.*(\xe2\x80\x93|\xe2\x80\x94)' || true)
+if [[ -z "$dash_added" ]]; then
+ _ok "no newly added em/en-dash in diff ${BASE_REF}...HEAD"
else
- _bad "BASE_REF '$BASE_REF' not found (set BASE_REF=[)"
+ echo "$dash_added" >&2
+ _bad "newly added em/en-dash in diff ${BASE_REF}...HEAD"
fi
# --- 5. AI/tool-mention in diff + commit log ---
@@ -172,6 +194,16 @@ else
_bad "SHA pins out of sync (run: bash scripts/update-sha-pins.sh)"
fi
+# --- 9. Docs consistency ---
+# Тот же скрипт гоняет docs-check workflow. Здесь - часть единого pre-tag gate.
+if bash "$SCRIPT_DIR/check-docs-consistency.sh" >/tmp/preflight-docs.$$ 2>&1; then
+ _ok "docs consistency (check-docs-consistency.sh)"
+else
+ cat /tmp/preflight-docs.$$ >&2
+ _bad "docs consistency (run: bash scripts/check-docs-consistency.sh)"
+fi
+rm -f /tmp/preflight-docs.$$
+
# --- Summary ---
echo ""
echo "=== preflight summary: $PASS passed, $FAIL failed ==="
diff --git a/tests/test_v5151_audit_fixes.bats b/tests/test_v5151_audit_fixes.bats
new file mode 100644
index 0000000..ca857c9
--- /dev/null
+++ b/tests/test_v5151_audit_fixes.bats
@@ -0,0 +1,115 @@
+#!/usr/bin/env bats
+# v5.15.1 audit fixes - regression locks for:
+# C8 / weaq P1: detect_native_ipv6 must require a globally-routable (non-ULA)
+# address AND a default IPv6 route (no false-positive -> no ::/0
+# black-hole).
+# E / C13: install --help OS line lists 26.04; --subnet help states /24.
+# C12: log_msg must not double percent signs.
+# weaq P2: log_msg must route INFO/DEBUG to stderr when JSON_OUTPUT=1.
+#
+# shellcheck disable=SC2034 # mock env vars (NO_COLOR/VERBOSE_LIST/LOG_FILE/JSON_OUTPUT) are consumed by the sourced functions
+# shellcheck disable=SC2154 # $stderr is set by bats `run --separate-stderr`
+
+bats_require_minimum_version 1.5.0
+
+# ---------------------------------------------------------------------------
+# detect_native_ipv6 (extracted from install_amneziawg.sh, `ip` mocked)
+# ---------------------------------------------------------------------------
+
+setup_detect() {
+ local installer="${BATS_TEST_DIRNAME}/../install_amneziawg.sh"
+ # shellcheck source=/dev/null
+ source <(sed -n '/^detect_native_ipv6() {$/,/^}$/p' "$installer")
+ ip() {
+ case "$*" in
+ *"addr show scope global"*) printf '%s\n' "${MOCK_IP6_ADDR:-}" ;;
+ *"route show default"*) printf '%s\n' "${MOCK_IP6_ROUTE:-}" ;;
+ *) : ;;
+ esac
+ }
+ export -f ip
+}
+
+@test "v5.15.1 C8: native = global non-ULA address + default route -> 1" {
+ setup_detect
+ MOCK_IP6_ADDR=" inet6 2a01:4f8:c17:abcd::1/64 scope global"
+ MOCK_IP6_ROUTE="default via fe80::1 dev eth0 proto ra"
+ run detect_native_ipv6
+ [ "$output" = "1" ]
+}
+
+@test "v5.15.1 C8: ULA-only global address (fd..) + default route -> 0" {
+ setup_detect
+ MOCK_IP6_ADDR=" inet6 fddd:2c4:2c4:2c4::1/64 scope global"
+ MOCK_IP6_ROUTE="default via fe80::1 dev eth0"
+ run detect_native_ipv6
+ [ "$output" = "0" ]
+}
+
+@test "v5.15.1 C8: global address but NO default route -> 0 (avoids black-hole)" {
+ setup_detect
+ MOCK_IP6_ADDR=" inet6 2a01:4f8:c17:abcd::1/64 scope global"
+ MOCK_IP6_ROUTE=""
+ run detect_native_ipv6
+ [ "$output" = "0" ]
+}
+
+@test "v5.15.1 C8: no IPv6 at all -> 0" {
+ setup_detect
+ MOCK_IP6_ADDR=""
+ MOCK_IP6_ROUTE=""
+ run detect_native_ipv6
+ [ "$output" = "0" ]
+}
+
+# ---------------------------------------------------------------------------
+# install --help text (E: 26.04, C13: /24) - source-level assertions
+# ---------------------------------------------------------------------------
+
+@test "v5.15.1 E: RU installer help OS line includes 26.04" {
+ grep -q "Ubuntu (24.04 / 25.10 / 26.04)" "${BATS_TEST_DIRNAME}/../install_amneziawg.sh"
+}
+
+@test "v5.15.1 E: EN installer help OS line includes 26.04" {
+ grep -q "Ubuntu (24.04 / 25.10 / 26.04)" "${BATS_TEST_DIRNAME}/../install_amneziawg_en.sh"
+}
+
+@test "v5.15.1 C13: RU installer --subnet help states /24 only" {
+ grep -qP 'subnet.*/24' "${BATS_TEST_DIRNAME}/../install_amneziawg.sh"
+}
+
+# ---------------------------------------------------------------------------
+# log_msg (extracted from manage_amneziawg.sh) - C12 + weaq P2
+# ---------------------------------------------------------------------------
+
+setup_logmsg() {
+ NO_COLOR=1
+ VERBOSE_LIST=0
+ LOG_FILE="${BATS_TEST_TMPDIR:-/tmp}/awg_test_$$.log"
+ JSON_OUTPUT=0
+ local mgr="${BATS_TEST_DIRNAME}/../manage_amneziawg.sh"
+ # shellcheck source=/dev/null
+ source <(sed -n '/^log_msg() {$/,/^}$/p' "$mgr")
+}
+
+@test "v5.15.1 C12: log_msg does not double percent signs" {
+ setup_logmsg
+ run log_msg INFO "disk is 95% full"
+ [[ "$output" == *"95% full"* ]]
+ [[ "$output" != *"95%%"* ]]
+}
+
+@test "v5.15.1 weaq P2: log_msg INFO goes to stderr (empty stdout) when JSON_OUTPUT=1" {
+ setup_logmsg
+ JSON_OUTPUT=1
+ run --separate-stderr log_msg INFO "informational line"
+ [ -z "$output" ]
+ [[ "$stderr" == *"informational line"* ]]
+}
+
+@test "v5.15.1 weaq P2: log_msg INFO goes to stdout when JSON_OUTPUT=0" {
+ setup_logmsg
+ JSON_OUTPUT=0
+ run log_msg INFO "informational line"
+ [[ "$output" == *"informational line"* ]]
+}
diff --git a/tests/test_v5151_ipv6_split_tunnel_option_c.bats b/tests/test_v5151_ipv6_split_tunnel_option_c.bats
new file mode 100644
index 0000000..3d32111
--- /dev/null
+++ b/tests/test_v5151_ipv6_split_tunnel_option_c.bats
@@ -0,0 +1,63 @@
+#!/usr/bin/env bats
+# v5.15.1 (audit C7) - intent-mirroring of the IPv4 routing mode into IPv6.
+#
+# When the client uses a SPLIT tunnel (custom ALLOWED_IPS, not 0.0.0.0/0),
+# render_client_config must keep the IPv4 split list AS-IS and append ONLY the
+# tunnel ULA - never ::/0. There is no IPv6 split-list, so routing all IPv6 into
+# the tunnel would silently break the user's split-tunnel intent.
+#
+# create_init_config seeds a split ALLOWED_IPS ('0.0.0.0/5, 8.0.0.0/7'), so these
+# tests exercise the split path directly without overriding it.
+
+load test_helper
+
+setup_split_native() {
+ create_server_config
+ create_init_config # ALLOWED_IPS='0.0.0.0/5, 8.0.0.0/7' (split)
+ cat >> "$CONFIG_FILE" << 'CONF'
+export ALLOW_IPV6_TUNNEL=1
+export IPV6_SUBNET='fddd:2c4:2c4:2c4::/64'
+export SERVER_HAS_NATIVE_IPV6=1
+CONF
+ safe_load_config "$CONFIG_FILE"
+}
+
+setup_split_no_native() {
+ create_server_config
+ create_init_config
+ cat >> "$CONFIG_FILE" << 'CONF'
+export ALLOW_IPV6_TUNNEL=1
+export IPV6_SUBNET='fddd:2c4:2c4:2c4::/64'
+export SERVER_HAS_NATIVE_IPV6=0
+CONF
+ safe_load_config "$CONFIG_FILE"
+}
+
+@test "v5.15.1 C7: split tunnel + native IPv6 keeps IPv4 split and appends only the ULA" {
+ setup_split_native
+ render_client_config "splitnat" "10.9.9.20" "FAKEPRIVKEY" "FAKEPUBKEY" "1.2.3.4" "39743" "fddd:2c4:2c4:2c4::20"
+ local conf="$AWG_DIR/splitnat.conf"
+ grep -q "AllowedIPs = 0.0.0.0/5, 8.0.0.0/7, fddd:2c4:2c4:2c4::/64" "$conf"
+}
+
+@test "v5.15.1 C7: split tunnel + native IPv6 does NOT add ::/0" {
+ setup_split_native
+ render_client_config "splitnat2" "10.9.9.21" "FAKEPRIVKEY" "FAKEPUBKEY" "1.2.3.4" "39743" "fddd:2c4:2c4:2c4::21"
+ local conf="$AWG_DIR/splitnat2.conf"
+ ! grep -q "::/0" "$conf"
+}
+
+@test "v5.15.1 C7: split tunnel + no native IPv6 keeps IPv4 split and appends only the ULA" {
+ setup_split_no_native
+ render_client_config "splitno" "10.9.9.22" "FAKEPRIVKEY" "FAKEPUBKEY" "1.2.3.4" "39743" "fddd:2c4:2c4:2c4::22"
+ local conf="$AWG_DIR/splitno.conf"
+ grep -q "AllowedIPs = 0.0.0.0/5, 8.0.0.0/7, fddd:2c4:2c4:2c4::/64" "$conf"
+}
+
+@test "v5.15.1 C7: split tunnel preserves the IPv4 split (not collapsed to 0.0.0.0/0)" {
+ setup_split_native
+ render_client_config "splitnat3" "10.9.9.23" "FAKEPRIVKEY" "FAKEPUBKEY" "1.2.3.4" "39743" "fddd:2c4:2c4:2c4::23"
+ local conf="$AWG_DIR/splitnat3.conf"
+ grep -qP '^AllowedIPs = 0\.0\.0\.0/5, 8\.0\.0\.0/7,' "$conf"
+ ! grep -qP '^AllowedIPs = 0\.0\.0\.0/0' "$conf"
+}
diff --git a/tests/test_v515_ipv6_route_all_no_native.bats b/tests/test_v515_ipv6_route_all_no_native.bats
index 71d945b..5beae15 100644
--- a/tests/test_v515_ipv6_route_all_no_native.bats
+++ b/tests/test_v515_ipv6_route_all_no_native.bats
@@ -8,7 +8,11 @@ load test_helper
setup_no_native() {
create_server_config
create_init_config
+ # "route all" (full tunnel) tests: force ALLOWED_IPS=0.0.0.0/0 (overrides the
+ # split default from create_init_config; safe_load_config is last-wins). With
+ # no native IPv6 a full tunnel maps to the tunnel ULA, never ::/0.
cat >> "$CONFIG_FILE" << 'CONF'
+export ALLOWED_IPS='0.0.0.0/0'
export ALLOW_IPV6_TUNNEL=1
export IPV6_SUBNET='fddd:2c4:2c4:2c4::/64'
export SERVER_HAS_NATIVE_IPV6=0
diff --git a/tests/test_v515_ipv6_route_all_with_native.bats b/tests/test_v515_ipv6_route_all_with_native.bats
index a355597..a8661ad 100644
--- a/tests/test_v515_ipv6_route_all_with_native.bats
+++ b/tests/test_v515_ipv6_route_all_with_native.bats
@@ -8,8 +8,12 @@ load test_helper
setup_common() {
create_server_config
create_init_config
- # Append IPv6 keys to the init config
+ # Append IPv6 keys to the init config. These are the "route all" (full
+ # tunnel) tests, so force ALLOWED_IPS=0.0.0.0/0 (overrides the split default
+ # from create_init_config; safe_load_config is last-wins). Under the
+ # intent-mirroring rule a full IPv4 tunnel maps to ::/0 on native IPv6.
cat >> "$CONFIG_FILE" << 'CONF'
+export ALLOWED_IPS='0.0.0.0/0'
export ALLOW_IPV6_TUNNEL=1
export IPV6_SUBNET='fddd:2c4:2c4:2c4::/64'
export SERVER_HAS_NATIVE_IPV6=1
diff --git a/tests/test_v515_sha_pins_lockstep.bats b/tests/test_v515_sha_pins_lockstep.bats
index 8e13795..cb56241 100644
--- a/tests/test_v515_sha_pins_lockstep.bats
+++ b/tests/test_v515_sha_pins_lockstep.bats
@@ -1,5 +1,5 @@
#!/usr/bin/env bats
-# v5.15.0 SHA256 pin lockstep test.
+# SHA256 pin lockstep test (5.15.x line).
#
# The installers (install_amneziawg.sh / install_amneziawg_en.sh) download the
# helper scripts over the network and verify them against hardcoded pins
@@ -14,7 +14,7 @@
# ---------- RU installer pins ----------
-@test "v5.15.0: RU installer COMMON pin matches awg_common.sh" {
+@test "RU installer COMMON pin matches awg_common.sh" {
actual=$(sha256sum "$BATS_TEST_DIRNAME/../awg_common.sh" | cut -d' ' -f1)
pinned=$(grep -oP 'COMMON_SCRIPT_SHA256="\K[0-9a-f]{64}' "$BATS_TEST_DIRNAME/../install_amneziawg.sh")
[ "$actual" = "$pinned" ] || {
@@ -23,7 +23,7 @@
}
}
-@test "v5.15.0: RU installer MANAGE pin matches manage_amneziawg.sh" {
+@test "RU installer MANAGE pin matches manage_amneziawg.sh" {
actual=$(sha256sum "$BATS_TEST_DIRNAME/../manage_amneziawg.sh" | cut -d' ' -f1)
pinned=$(grep -oP 'MANAGE_SCRIPT_SHA256="\K[0-9a-f]{64}' "$BATS_TEST_DIRNAME/../install_amneziawg.sh")
[ "$actual" = "$pinned" ] || {
@@ -34,7 +34,7 @@
# ---------- EN installer pins ----------
-@test "v5.15.0: EN installer COMMON pin matches awg_common_en.sh" {
+@test "EN installer COMMON pin matches awg_common_en.sh" {
actual=$(sha256sum "$BATS_TEST_DIRNAME/../awg_common_en.sh" | cut -d' ' -f1)
pinned=$(grep -oP 'COMMON_SCRIPT_SHA256="\K[0-9a-f]{64}' "$BATS_TEST_DIRNAME/../install_amneziawg_en.sh")
[ "$actual" = "$pinned" ] || {
@@ -43,7 +43,7 @@
}
}
-@test "v5.15.0: EN installer MANAGE pin matches manage_amneziawg_en.sh" {
+@test "EN installer MANAGE pin matches manage_amneziawg_en.sh" {
actual=$(sha256sum "$BATS_TEST_DIRNAME/../manage_amneziawg_en.sh" | cut -d' ' -f1)
pinned=$(grep -oP 'MANAGE_SCRIPT_SHA256="\K[0-9a-f]{64}' "$BATS_TEST_DIRNAME/../install_amneziawg_en.sh")
[ "$actual" = "$pinned" ] || {
]