mirror of
https://github.com/karen07/openwrt-mesh-builder.git
synced 2026-10-19 07:23:32 +00:00
Add c-ares package support and improve DoH failover
- Generalize custom release package downloading for AWG and c-ares. - Download libcares from c-ares-openwrt-package releases and add --skip-cares-download. - Add libcares to required router packages. - Simplify DoH failover and rely on procd for https-dns-proxy lifecycle management. - Repair missing provider-domain dnsmasq forwards without requiring an endpoint switch. - Remove the custom https-dns-proxy respawn patch. - Update README examples and package documentation.
This commit is contained in:
parent
fe95095843
commit
39c89af7c6
5 changed files with 114 additions and 39 deletions
33
README.md
33
README.md
|
|
@ -561,20 +561,20 @@ vim config.json
|
|||
./upgrade_routers.py
|
||||
```
|
||||
|
||||
Для локального просмотра структуры, без загрузки AWG пакетов и без синхронизации `packages/`, можно запускать так:
|
||||
Для локального просмотра структуры, без загрузки AWG/c-ares пакетов и без синхронизации `packages/`, можно запускать так:
|
||||
|
||||
```sh
|
||||
./generate_configs.py --skip-awg-download --skip-package-sync
|
||||
./generate_configs.py --skip-awg-download --skip-cares-download --skip-package-sync
|
||||
```
|
||||
|
||||
Такой режим удобен, если AWG `.apk` и per-router package repos уже не нужны для текущей проверки.
|
||||
Такой режим удобен, если custom `.apk` и per-router package repos уже не нужны для текущей проверки.
|
||||
|
||||
Hooks при этом все равно запускаются, поэтому `tools/generate.py` все еще может требовать `wg` и `openssl`, если нужно создать недостающие WireGuard/OpenVPN secrets.
|
||||
|
||||
Для просмотра только синхронизированной template структуры без generator hooks добавляйте `--skip-hooks`:
|
||||
|
||||
```sh
|
||||
./generate_configs.py --skip-awg-download --skip-package-sync --skip-hooks
|
||||
./generate_configs.py --skip-awg-download --skip-cares-download --skip-package-sync --skip-hooks
|
||||
```
|
||||
|
||||
Dynamic direct-list sources из `tools/default.py` должны быть доступны, если они включены.
|
||||
|
|
@ -582,7 +582,7 @@ Dynamic direct-list sources из `tools/default.py` должны быть дос
|
|||
Если нужен полностью локальный smoke run без загрузки country/ASN direct-list IP sets, добавьте `--skip-direct-downloads`:
|
||||
|
||||
```sh
|
||||
./generate_configs.py --skip-awg-download --skip-package-sync --skip-direct-downloads
|
||||
./generate_configs.py --skip-awg-download --skip-cares-download --skip-package-sync --skip-direct-downloads
|
||||
```
|
||||
|
||||
В этом режиме generated `direct.txt` будет содержать только static direct entries:
|
||||
|
|
@ -1034,7 +1034,16 @@ Dnsmasq по умолчанию смотрит на:
|
|||
1. DoH endpoints из `https-dns-proxy`.
|
||||
1. DNS servers из `/tmp/resolv.conf.d/resolv.conf.auto`.
|
||||
|
||||
Скрипт проверяет endpoints сверху вниз, выбирает первый отвечающий endpoint и синхронизирует с ним:
|
||||
Выбор устроен так же, как в `exit-route.sh`: скрипт идет по приоритетному списку сверху вниз и останавливается на первом работающем endpoint. Поэтому в steady state автоматически проверяются только endpoints выше текущего и сам текущий:
|
||||
|
||||
- если более приоритетный endpoint снова заработал, dnsmasq переключается на него;
|
||||
- если текущий endpoint жив, менее приоритетные endpoints не проверяются;
|
||||
- если текущий endpoint перестал отвечать, сразу выбирается следующий работающий endpoint по приоритету;
|
||||
- каждый новый цикл снова начинается с самого приоритетного endpoint, поэтому failback происходит автоматически.
|
||||
|
||||
`check-doh.sh` занимается только проверкой DNS и выбором endpoint. Жизненным циклом `https-dns-proxy` занимается procd; отдельный recovery `stop`/`start` перед DNS downgrade больше не нужен.
|
||||
|
||||
Выбранный endpoint синхронизируется с:
|
||||
|
||||
```text
|
||||
dhcp.@dnsmasq[0].server
|
||||
|
|
@ -1042,8 +1051,6 @@ dhcp.@dnsmasq[0].server
|
|||
|
||||
Если ни один endpoint не отвечает, скрипт ставит последний endpoint из списка как fail-open резерв. Обычно это DNS провайдера.
|
||||
|
||||
При переключении общего DNS endpoint на DNS провайдера скрипт после перезапуска dnsmasq также пытается перезапустить `https-dns-proxy`. Это best-effort попытка заново выполнить bootstrap DoH через уже работающий провайдерский DNS. Ошибка перезапуска не мешает DNS failover, а следующая проверка автоматически вернет dnsmasq на DoH, если тот снова начнет отвечать.
|
||||
|
||||
Дополнительно поддерживается split DNS по доменным зонам.
|
||||
|
||||
При каждом применении нового endpoint скрипт заново собирает `dhcp.@dnsmasq[0].server`:
|
||||
|
|
@ -1342,6 +1349,7 @@ babeld
|
|||
curl
|
||||
iperf3
|
||||
jq-full
|
||||
libcares
|
||||
luci
|
||||
luci-app-https-dns-proxy
|
||||
luci-app-watchcat
|
||||
|
|
@ -1513,7 +1521,7 @@ Profile name является безопасным ASCII identifier.
|
|||
- OpenVPN defaults
|
||||
- DoH/DNS failover defaults
|
||||
- direct-list sources
|
||||
- OpenWrt/AWG package URLs
|
||||
- OpenWrt/AWG/c-ares package URLs
|
||||
- имена managed файлов и директорий
|
||||
|
||||
Именно там меняются правила, которые должны быть одинаковыми для всех конфигов.
|
||||
|
|
@ -1527,7 +1535,7 @@ Profile name является безопасным ASCII identifier.
|
|||
```sh
|
||||
./generate_configs.py
|
||||
./generate_configs.py --config prod.json
|
||||
./generate_configs.py --skip-awg-download --skip-package-sync
|
||||
./generate_configs.py --skip-awg-download --skip-cares-download --skip-package-sync
|
||||
./generate_configs.py --skip-hooks
|
||||
./generate_configs.py --force
|
||||
./generate_configs.py --details
|
||||
|
|
@ -1538,6 +1546,7 @@ Profile name является безопасным ASCII identifier.
|
|||
1. читает и валидирует `config.json`
|
||||
1. создает `routers/` из `routers/example`
|
||||
1. скачивает AWG2 `.apk`, если не указан `--skip-awg-download`
|
||||
1. скачивает `libcares` из `c-ares-openwrt-package`, если не указан `--skip-cares-download`
|
||||
1. синхронизирует per-router `packages/`, если не указан `--skip-package-sync`
|
||||
1. синхронизирует шаблонные файлы из `routers/example`
|
||||
1. запускает `tools/generate.py`
|
||||
|
|
@ -1923,10 +1932,10 @@ Python syntax:
|
|||
python3 -m py_compile *.py tools/*.py
|
||||
```
|
||||
|
||||
Быстрая проверка template/config flow без загрузки AWG packages:
|
||||
Быстрая проверка template/config flow без загрузки custom packages:
|
||||
|
||||
```sh
|
||||
./generate_configs.py --skip-awg-download --skip-package-sync --skip-direct-downloads
|
||||
./generate_configs.py --skip-awg-download --skip-cares-download --skip-package-sync --skip-direct-downloads
|
||||
```
|
||||
|
||||
Валидация generated config:
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ from tools.default import (
|
|||
CONFIG_PATH,
|
||||
AWG_PACKAGE_NAMES,
|
||||
AWG_RELEASE_BASE_URL,
|
||||
CARES_PACKAGE_NAMES,
|
||||
CARES_RELEASE_BASE_URL,
|
||||
MIN_OPENWRT_VERSION_TEXT,
|
||||
PACKAGE_EXTENSION,
|
||||
PACKAGE_SOURCE_ROOT,
|
||||
|
|
@ -61,9 +63,12 @@ def package_source_dir_from_profile(version: str, profile: DeviceProfile) -> Pat
|
|||
return PACKAGE_SOURCE_ROOT / version / profile.board / profile.arch
|
||||
|
||||
|
||||
def ensure_named_awg_packages_for_profile(
|
||||
def ensure_named_release_packages_for_profile(
|
||||
version: str,
|
||||
profile: DeviceProfile,
|
||||
package_names: list[str],
|
||||
release_base_url: str,
|
||||
label: str,
|
||||
) -> None:
|
||||
board, arch, target, subtarget = profile_board_arch(profile)
|
||||
|
||||
|
|
@ -72,9 +77,9 @@ def ensure_named_awg_packages_for_profile(
|
|||
remove_package_indexes(dst_dir)
|
||||
|
||||
postfix = f"_v{version}_{arch}_{target}_{subtarget}"
|
||||
base_url = f"{AWG_RELEASE_BASE_URL}/v{version}"
|
||||
base_url = f"{release_base_url}/v{version}"
|
||||
|
||||
for package_name in AWG_PACKAGE_NAMES:
|
||||
for package_name in package_names:
|
||||
simple_dst = dst_dir / f"{package_name}.{PACKAGE_EXTENSION}"
|
||||
|
||||
if simple_dst.is_file() and simple_dst.stat().st_size > 0:
|
||||
|
|
@ -86,7 +91,7 @@ def ensure_named_awg_packages_for_profile(
|
|||
|
||||
if not try_download_file(url, tmp_dst):
|
||||
die(
|
||||
"failed to download AWG2 apk package "
|
||||
f"failed to download {label} apk package "
|
||||
f"{package_name} for {version} {board} {arch}. Tried: {url}"
|
||||
)
|
||||
|
||||
|
|
@ -94,10 +99,13 @@ def ensure_named_awg_packages_for_profile(
|
|||
print(f"Downloaded: {simple_dst}")
|
||||
|
||||
|
||||
def ensure_awg_packages(
|
||||
def ensure_release_packages(
|
||||
cfg: ConfigData,
|
||||
routers: list[RouterDef],
|
||||
version: str,
|
||||
package_names: list[str],
|
||||
release_base_url: str,
|
||||
label: str,
|
||||
) -> None:
|
||||
seen: set[tuple[str, str]] = set()
|
||||
|
||||
|
|
@ -110,7 +118,13 @@ def ensure_awg_packages(
|
|||
continue
|
||||
|
||||
seen.add(key)
|
||||
ensure_named_awg_packages_for_profile(version, profile)
|
||||
ensure_named_release_packages_for_profile(
|
||||
version,
|
||||
profile,
|
||||
package_names,
|
||||
release_base_url,
|
||||
label,
|
||||
)
|
||||
|
||||
|
||||
def copy_apk_repo_with_canonical_names(
|
||||
|
|
@ -192,6 +206,11 @@ def main() -> None:
|
|||
action="store_true",
|
||||
help="do not download AWG packages from GitHub releases",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--skip-cares-download",
|
||||
action="store_true",
|
||||
help="do not download libcares from the c-ares GitHub release",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--skip-package-sync",
|
||||
action="store_true",
|
||||
|
|
@ -243,7 +262,24 @@ def main() -> None:
|
|||
ensure_router_from_example(EXAMPLE_ROUTER_DIR, router)
|
||||
|
||||
if not args.skip_awg_download:
|
||||
ensure_awg_packages(cfg_data, routers, cfg_data.openwrt_version)
|
||||
ensure_release_packages(
|
||||
cfg_data,
|
||||
routers,
|
||||
cfg_data.openwrt_version,
|
||||
AWG_PACKAGE_NAMES,
|
||||
AWG_RELEASE_BASE_URL,
|
||||
"AWG2",
|
||||
)
|
||||
|
||||
if not args.skip_cares_download:
|
||||
ensure_release_packages(
|
||||
cfg_data,
|
||||
routers,
|
||||
cfg_data.openwrt_version,
|
||||
CARES_PACKAGE_NAMES,
|
||||
CARES_RELEASE_BASE_URL,
|
||||
"c-ares",
|
||||
)
|
||||
|
||||
if not args.skip_package_sync:
|
||||
for router in routers:
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ nslookup_ok() {
|
|||
nslookup "$DOMAIN" "$_ep" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
first_working_endpoint() {
|
||||
best_available_endpoint() {
|
||||
for _ep in "$@"; do
|
||||
if nslookup_ok "$_ep"; then
|
||||
echo "$_ep"
|
||||
|
|
@ -158,6 +158,31 @@ dnsmasq_domain_server_value() {
|
|||
echo "/${_domain_server_domain}/${_domain_server_upstream}"
|
||||
}
|
||||
|
||||
dnsmasq_server_value_configured() {
|
||||
_expected="$1"
|
||||
|
||||
for _configured in $(uci -q get dhcp.@dnsmasq[0].server 2>/dev/null); do
|
||||
[ "$_configured" = "$_expected" ] && return 0
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
dnsmasq_provider_domains_configured() {
|
||||
_provider_eps_resolv="$(get_nameservers_from_resolv)"
|
||||
|
||||
for _provider_domain in $PROVIDER_DOMAINS; do
|
||||
for _provider_ep_resolv in $_provider_eps_resolv; do
|
||||
_provider_val="$(
|
||||
dnsmasq_domain_server_value "$_provider_domain" "$_provider_ep_resolv"
|
||||
)"
|
||||
dnsmasq_server_value_configured "$_provider_val" || return 1
|
||||
done
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
add_dnsmasq_provider_domain_servers() {
|
||||
_provider_eps_resolv="$(get_nameservers_from_resolv)"
|
||||
|
||||
|
|
@ -199,18 +224,26 @@ apply_once() {
|
|||
cur="$(current_endpoint || true)"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
target="$(first_working_endpoint $endpoints || true)"
|
||||
target="$(best_available_endpoint $endpoints || true)"
|
||||
|
||||
if [ -z "$target" ]; then
|
||||
# shellcheck disable=SC2086
|
||||
target="$(last_endpoint $endpoints || true)"
|
||||
echo "no working DNS endpoint; use fallback=${target:-none} endpoints=[$endpoints]"
|
||||
echo "no working DNS endpoint; fallback=${target:-none} endpoints=[$endpoints]"
|
||||
fi
|
||||
|
||||
[ -n "$target" ] || return 0
|
||||
[ "$cur" = "$target" ] && return 0
|
||||
|
||||
echo "switch DNS endpoint: current=${cur:-none} target=$target"
|
||||
if [ "$cur" = "$target" ]; then
|
||||
if dnsmasq_provider_domains_configured; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "repair dnsmasq provider-domain forwards"
|
||||
else
|
||||
echo "switch DNS endpoint: current=${cur:-none} target=$target"
|
||||
fi
|
||||
|
||||
set_dnsmasq_server_endpoint "$target" || return 1
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,16 +25,6 @@ update_release_from_deploy_version() {
|
|||
rm -f /etc/deploy_version
|
||||
}
|
||||
|
||||
patch_https_dns_proxy_respawn() {
|
||||
init='/etc/init.d/https-dns-proxy'
|
||||
|
||||
[ -f "$init" ] || return 0
|
||||
|
||||
sed -i \
|
||||
's/^\([[:space:]]*procd_set_param[[:space:]][[:space:]]*respawn\).*$/\1 3600 5 0/' \
|
||||
"$init"
|
||||
}
|
||||
|
||||
setup_doh() {
|
||||
echo 'doh:x:4453:' >>/etc/group
|
||||
echo 'doh:x:4453:4453:doh:/var/run/doh:/bin/false' >>/etc/passwd
|
||||
|
|
@ -60,9 +50,6 @@ append_if_exists /etc/config/firewall_part /etc/config/firewall
|
|||
# Log size
|
||||
uci -q set system.@system[0].log_size='10240'
|
||||
|
||||
# Make https-dns-proxy retry forever after crashes
|
||||
patch_https_dns_proxy_respawn
|
||||
|
||||
# Set DOH
|
||||
setup_doh
|
||||
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ WIFI_ENCRYPTION = "psk2"
|
|||
WIFI_CELL_DENSITY = "0"
|
||||
|
||||
# ============================================================
|
||||
# OPENWRT / AWG PACKAGE BUILD DEFAULTS
|
||||
# OPENWRT / CUSTOM PACKAGE BUILD DEFAULTS
|
||||
# ============================================================
|
||||
|
||||
MIN_OPENWRT_MAJOR = 25
|
||||
|
|
@ -325,7 +325,12 @@ MIN_OPENWRT_VERSION = (MIN_OPENWRT_MAJOR, MIN_OPENWRT_MINOR)
|
|||
MIN_OPENWRT_VERSION_TEXT = f"{MIN_OPENWRT_MAJOR}.{MIN_OPENWRT_MINOR}"
|
||||
|
||||
OPENWRT_RELEASE_BASE_URL = "https://downloads.openwrt.org/releases"
|
||||
AWG_RELEASE_BASE_URL = "https://github.com/karen07/amneziawg-openwrt/releases/download"
|
||||
AWG_RELEASE_BASE_URL = (
|
||||
"https://github.com/karen07/amneziawg-openwrt-package/releases/download"
|
||||
)
|
||||
CARES_RELEASE_BASE_URL = (
|
||||
"https://github.com/karen07/c-ares-openwrt-package/releases/download"
|
||||
)
|
||||
|
||||
AWG_PACKAGE_NAMES = [
|
||||
"kmod-amneziawg",
|
||||
|
|
@ -333,6 +338,10 @@ AWG_PACKAGE_NAMES = [
|
|||
"luci-proto-amneziawg",
|
||||
]
|
||||
|
||||
CARES_PACKAGE_NAMES = [
|
||||
"libcares",
|
||||
]
|
||||
|
||||
# OpenWrt image packages required by generated router configs and
|
||||
# router example files. User-facing config.packages is for extra packages.
|
||||
ROUTER_REQUIRED_PACKAGES = [
|
||||
|
|
@ -340,6 +349,7 @@ ROUTER_REQUIRED_PACKAGES = [
|
|||
"curl",
|
||||
"iperf3",
|
||||
"jq-full",
|
||||
"libcares",
|
||||
"luci-app-https-dns-proxy",
|
||||
"luci-app-watchcat",
|
||||
"luci-proto-amneziawg",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue