Compare commits
6 Commits
4923-gopac
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a6c8b4198 | ||
|
|
ae840c9c96 | ||
|
|
187b759fc6 | ||
|
|
8c8323ae68 | ||
|
|
b5c47054ab | ||
|
|
4776255604 |
@@ -20,10 +20,14 @@ NOTE: Add new changes BELOW THIS COMMENT.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Command line option `--update` when the `dns.serve_plain_dns` configuration property was disabled ([7801]).
|
||||
|
||||
- DNS cache not working for custom upstream configurations.
|
||||
|
||||
- Validation process for the DNS-over-TLS, DNS-over-QUIC, and HTTPS ports on the *Encryption Settings* page.
|
||||
|
||||
[#7801]: https://github.com/AdguardTeam/AdGuardHome/issues/7801
|
||||
|
||||
<!--
|
||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||
-->
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"client_settings": "Налады кліентаў",
|
||||
"example_upstream_reserved": "upstream <0>для канкрэтных даменаў</0>;",
|
||||
"example_multiple_upstreams_reserved": "некалькі DNS-сервераў <0>для канкрэтных даменаў</0>;",
|
||||
"example_multiple_upstreams_reserved": "некалькі сервер DNSаў <0>для канкрэтных даменаў</0>;",
|
||||
"example_upstream_comment": "каментар.",
|
||||
"upstream_parallel": "Ужыць адначасныя запыты да ўсіх сервераў для паскарэння апрацоўкі запыту",
|
||||
"parallel_requests": "Паралельныя запыты",
|
||||
"load_balancing": "Размеркаванне нагрузкі",
|
||||
"load_balancing_desc": "Запытвайце па адным серверы за раз. AdGuard Home будзе выкарыстоўваць выпадковы алгарытм для выбару сервера, так што самы хуткі сервер будзе выкарыстоўвацца часцей.",
|
||||
"bootstrap_dns": "Bootstrap DNS-серверы",
|
||||
"bootstrap_dns_desc": "IP-адрасы DNS-сервераў, якія выкарыстоўваюцца для вырашэння IP-адрасоў распознавальнікаў DoH/DoT, якія вы ўказваеце ў якасці перадачы. Каментары не дапускаюцца.",
|
||||
"fallback_dns_title": "Рэзервовыя DNS-серверы",
|
||||
"fallback_dns_desc": "Спіс рэзервовых DNS-сервераў, якія выкарыстоўваюцца, калі вышэйшыя DNS-серверы не адказваюць. Сінтаксіс такі ж, як і ў галоўным полі ўверх.",
|
||||
"bootstrap_dns": "Bootstrap сервер DNSы",
|
||||
"bootstrap_dns_desc": "IP-адрасы сервер DNSаў, якія выкарыстоўваюцца для вырашэння IP-адрасоў распознавальнікаў DoH/DoT, якія вы ўказваеце ў якасці перадачы. Каментары не дапускаюцца.",
|
||||
"fallback_dns_title": "Рэзервовыя сервер DNSы",
|
||||
"fallback_dns_desc": "Спіс рэзервовых сервер DNSаў, якія выкарыстоўваюцца, калі вышэйшыя сервер DNSы не адказваюць. Сінтаксіс такі ж, як і ў галоўным полі ўверх.",
|
||||
"fallback_dns_placeholder": "Увядзіце па адным рэзервовым серверы DNS у радку",
|
||||
"local_ptr_title": "Прыватныя DNS-серверы",
|
||||
"local_ptr_title": "Прыватныя сервер DNSы",
|
||||
"local_ptr_desc": "DNS-серверы, якія AdGuard Home выкарыстоўвае для лакальных PTR-запытаў. Гэтыя серверы выкарыстоўваюцца, каб атрымаць даменавыя імёны кліентаў з прыватнымі IP-адрасамі, напрыклад «192.168.12.34», з дапамогай rDNS. Калі спіс пусты, AdGuard Home выкарыстоўвае прадвызначаныя DNS-серверы вашай АС.",
|
||||
"local_ptr_default_resolver": "Па змаўчанні AdGuard Home выкарыстоўвае наступныя зваротныя DNS-рэзолверы: {{ip}}.",
|
||||
"local_ptr_no_default_resolver": "AdGuard Home не змог вызначыць прыдатныя прыватныя адваротныя DNS-рэзолверы для гэтай сістэмы.",
|
||||
"local_ptr_placeholder": "Увядзіце па адным адрасе на радок",
|
||||
"resolve_clients_title": "Уключыць запытванне даменавых імёнаў для кліентаў",
|
||||
"resolve_clients_desc": "AdGuard Home будзе спрабаваць аўтаматычна вызначыць даменавыя імёны кліентаў праз PTR-запыты да адпаведных сервераў (прыватны DNS-сервер для лакальных кліентаў, upstream-серверы для кліентаў з публічным IP-адрасам).",
|
||||
"resolve_clients_desc": "AdGuard Home будзе спрабаваць аўтаматычна вызначыць даменавыя імёны кліентаў праз PTR-запыты да адпаведных сервераў (прыватны сервер DNS для лакальных кліентаў, upstream-серверы для кліентаў з публічным IP-адрасам).",
|
||||
"use_private_ptr_resolvers_title": "Ужываць прыватныя адваротныя DNS-рэзолверы",
|
||||
"use_private_ptr_resolvers_desc": "Пасылаць адваротныя DNS-запыты для лакальна абслугоўных адрасоў на паказаныя серверы. Калі адключана, AdGuard Home будзе адказваць NXDOMAIN на ўсе падобныя PTR-запыты, апроч запытаў пра кліентаў, ужо вядомых па DHCP, /etc/hosts і гэтак далей.",
|
||||
"check_dhcp_servers": "Праверыць DHCP-серверы",
|
||||
@@ -101,13 +101,13 @@
|
||||
"compact": "Компактный",
|
||||
"nothing_found": "Нічога не знойдзена",
|
||||
"faq": "FAQ",
|
||||
"version": "версія",
|
||||
"version": "Версія",
|
||||
"address": "Адрас",
|
||||
"protocol": "Пратакол",
|
||||
"on": "УКЛ",
|
||||
"off": "Выкл",
|
||||
"copyright": "Усе правы захаваныя",
|
||||
"homepage": "Галоўная",
|
||||
"homepage": "Хатняя старонка",
|
||||
"report_an_issue": "Паведаміць пра праблему",
|
||||
"privacy_policy": "Палітыка прыватнасці",
|
||||
"enable_protection": "Уключыць абарону",
|
||||
@@ -165,8 +165,8 @@
|
||||
"custom_filtering_rules": "Карыстальніцкія правілы фільтрацыі",
|
||||
"encryption_settings": "Налады шыфравання",
|
||||
"dhcp_settings": "Налады DHCP",
|
||||
"upstream_dns": "Upstream DNS-серверы",
|
||||
"upstream_dns_help": "Увядзіце адрасы сервераў па адным у радку. <a>Даведацца больш </a> пра наладжванне DNS-сервераў.",
|
||||
"upstream_dns": "Upstream сервер DNSы",
|
||||
"upstream_dns_help": "Увядзіце адрасы сервераў па адным у радку. <a>Даведацца больш </a> пра наладжванне сервер DNSаў.",
|
||||
"upstream_dns_configured_in_file": "Наладжаны ў {{path}}",
|
||||
"test_upstream_btn": "Тэст upstream сервераў",
|
||||
"upstreams": "Upstreams",
|
||||
@@ -182,7 +182,7 @@
|
||||
"enabled_save_search_toast": "Уключаны бяспечны пошук",
|
||||
"updated_save_search_toast": "Налады бяспечнага пошуку абноўлены",
|
||||
"enabled_table_header": "УКЛ.",
|
||||
"name_table_header": "Імя",
|
||||
"name_table_header": "Назва",
|
||||
"list_url_table_header": "URL-адрас спіса",
|
||||
"rules_count_table_header": "Колькасць правілаў:",
|
||||
"last_time_updated_table_header": "Апошняе абнаўленне",
|
||||
@@ -196,7 +196,7 @@
|
||||
"no_whitelist_added": "Белыя спісы не дададзены",
|
||||
"add_blocklist": "Дадаць чорны спіс",
|
||||
"add_allowlist": "Дадаць белы спіс",
|
||||
"cancel_btn": "Адмена",
|
||||
"cancel_btn": "Скасаваць",
|
||||
"enter_name_hint": "Увядзіце імя",
|
||||
"enter_url_or_path_hint": "Увядзіце URL-адрас ці абсалютны шлях да спіса",
|
||||
"check_updates_btn": "Праверыць абнаўленні",
|
||||
@@ -219,7 +219,7 @@
|
||||
"example_meaning_host_block": "адказаць 127.0.0.1 для example.org (але не для яго паддаменаў);",
|
||||
"example_comment": "! Так можна дадаваць апісанне.",
|
||||
"example_comment_meaning": "каментар;",
|
||||
"example_comment_hash": "# І вось так таксама.",
|
||||
"example_comment_hash": "# Таксама каментарый.",
|
||||
"example_regex_meaning": "блакаваць доступ да даменаў, якія адпавядаюць зададзенаму рэгулярнаму выразу.",
|
||||
"example_upstream_regular": "звычайны DNS (наўзверх UDP);",
|
||||
"example_upstream_regular_port": "звычайны DNS (праз UDP, імя хаста);",
|
||||
@@ -233,13 +233,13 @@
|
||||
"example_upstream_tcp_port": "звычайны DNS (праз TCP, імя хаста);",
|
||||
"example_upstream_tcp_hostname": "звычайны DNS (праз TCP, імя хаста);",
|
||||
"all_lists_up_to_date_toast": "Усе спісы ўжо абноўлены",
|
||||
"updated_upstream_dns_toast": "Upstream DNS-серверы абноўлены",
|
||||
"updated_upstream_dns_toast": "Upstream сервер DNSы абноўлены",
|
||||
"dns_test_ok_toast": "Паказаныя серверы DNS працуюць карэктна",
|
||||
"dns_test_not_ok_toast": "Сервер «{{key}}»: немагчыма выкарыстоўваць, праверце слушнасць напісання",
|
||||
"dns_test_parsing_error_toast": "Раздзел {{section}}: радок {{line}}: немагчыма выкарыстоўваць, праверце слушнасць напісання",
|
||||
"dns_test_warning_toast": "Upstream «{{key}}» не адказвае на тэставыя запыты і можа не працаваць належным чынам",
|
||||
"unblock": "Адблакаваць",
|
||||
"block": "Заблакаваць",
|
||||
"block": "Заблакіраваць",
|
||||
"disallow_this_client": "Забараніць доступ гэтаму кліенту",
|
||||
"allow_this_client": "Дазволіць доступ гэтаму кліенту",
|
||||
"block_for_this_client_only": "Заблакаваць толькі для гэтага кліента",
|
||||
@@ -259,7 +259,7 @@
|
||||
"no_logs_found": "Логі не знойдзены",
|
||||
"refresh_btn": "Абнавіць",
|
||||
"previous_btn": "Назад",
|
||||
"next_btn": "Наперад",
|
||||
"next_btn": "Далей",
|
||||
"loading_table_status": "Загрузка...",
|
||||
"page_table_footer_text": "Старонка",
|
||||
"rows_table_footer_text": "радкоў",
|
||||
@@ -280,7 +280,7 @@
|
||||
"query_log_retention_confirm": "Вы ўпэўнены, што хочаце змяніць тэрмін захоўвання запытаў? Пры памяншэнні інтэрвалу, некаторыя даныя могуць быць страчаны",
|
||||
"anonymize_client_ip": "Ананімізацыя IP-адрасы кліента",
|
||||
"anonymize_client_ip_desc": "Не захоўвайце поўныя IP-адрасы гэтых удзельнікаў у часопісах або статыстыцы",
|
||||
"dns_config": "Налады DNS-сервера",
|
||||
"dns_config": "Налады сервер DNSа",
|
||||
"dns_cache_config": "Налада кэша DNS",
|
||||
"dns_cache_config_desc": "Тут можна наладзіць кэш DNS",
|
||||
"blocking_mode": "Рэжым блакавання",
|
||||
@@ -342,14 +342,14 @@
|
||||
"unknown_filter": "Невядомы фільтр {{filterId}}",
|
||||
"known_tracker": "Вядомы трэкер",
|
||||
"install_welcome_title": "Сардэчна запрашаем у AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home – гэта DNS-сервер, што блакуе рэкламу і трэкінг. Яго мэта – даць вам магчымасць кантраляваць усю ваша сеціва і ўсе падлучаныя прылады. Ён не патрабуе ўсталёўкі кліенцкіх праграм.",
|
||||
"install_welcome_desc": "AdGuard Home – гэта сервер DNS, што блакуе рэкламу і трэкінг. Яго мэта – даць вам магчымасць кантраляваць усю ваша сеціва і ўсе падлучаныя прылады. Ён не патрабуе ўсталёўкі кліенцкіх праграм.",
|
||||
"install_settings_title": "Ўэб-інтэрфейс адміністравання",
|
||||
"install_settings_listen": "Інтэрфейс сеціва",
|
||||
"install_settings_port": "Порт",
|
||||
"install_settings_interface_link": "Ваш ўэб-інтэрфейс адміністравання AdGuard Home будзе даступны па наступных адрасах:",
|
||||
"form_error_port": "Увядзіце карэктны нумар порта",
|
||||
"install_settings_dns": "DNS-сервер",
|
||||
"install_settings_dns_desc": "Вам будзе трэба наладзіць свае прылады ці роўтар на выкарыстанне DNS-сервера на адным з наступных адрасоў:",
|
||||
"install_settings_dns_desc": "Вам будзе трэба наладзіць свае прылады ці роўтар на выкарыстанне сервер DNSа на адным з наступных адрасоў:",
|
||||
"install_settings_all_interfaces": "Усе інтэрфейсы",
|
||||
"install_auth_title": "Аўтарызацыя",
|
||||
"install_auth_desc": "Настойліва рэкамендуецца наладзіць аўтэнтыфікацыю паролем для ўэб-інтэрфейсу AdGuard Home. Нават калі ён даступны толькі ў вашай лакальнай сетцы, важна абараніць яго ад неабмежаванага доступу.",
|
||||
@@ -365,17 +365,17 @@
|
||||
"install_submit_desc": "Працэдура налады завершана і вы гатовы пачаць выкарыстанне AdGuard Home.",
|
||||
"install_devices_router": "Роўтар",
|
||||
"install_devices_router_desc": "Такая наладка аўтаматычна пакрые ўсе прылады, што выкарыстоўваюць ваш хатні роўтар, і вам не трэба будзе наладжваць кожнае з іх у асобнасці.",
|
||||
"install_devices_address": "DNS-сервер AdGuard Home даступны па наступных адрасах",
|
||||
"install_devices_address": "сервер DNS AdGuard Home даступны па наступных адрасах",
|
||||
"install_devices_router_list_1": "Адкрыйце налады вашага роўтара. Звычайна вы можаце адкрыць іх у вашым браўзары, напрыклад, http://192.168.0.1/ ці http://192.168.1.1/. Вас могуць папрасіць увесці пароль. Калі вы не помніце яго, пароль часта можна скінуць, націснуўшы на кнопку на самым роўтары. Некаторыя роўтары патрабуюць адмысловага дадатку, які ў гэтым выпадку павінен быць ужо ўсталявана на ваш кампутар ці тэлефон.",
|
||||
"install_devices_router_list_2": "Знайдзіце налады DHCP ці DNS. Знайдзіце літары «DNS» поруч з тэкставым полем, у якое можна ўвесці два ці тры шэрагі лічбаў, падзеленых на 4 групы ад адной до трох лічбаў.",
|
||||
"install_devices_router_list_3": "Увядзіце туды адрас вашага AdGuard Home.",
|
||||
"install_devices_router_list_4": "Вы не можаце ўсталяваць уласны DNS-сервер на некаторых тыпах маршрутызатараў. У гэтым выпадку можа дапамагчы налада AdGuard Home у якасці <a href='#dhcp'>DHCP-сервера</a>. У адваротным выпадку вам трэба звярнуцца да кіраўніцтва па наладзе DNS-сервераў для вашай пэўнай мадэлі маршрутызатара.",
|
||||
"install_devices_router_list_4": "Вы не можаце ўсталяваць уласны сервер DNS на некаторых тыпах маршрутызатараў. У гэтым выпадку можа дапамагчы налада AdGuard Home у якасці <a href='#dhcp'>DHCP-сервера</a>. У адваротным выпадку вам трэба звярнуцца да кіраўніцтва па наладзе сервер DNSаў для вашай пэўнай мадэлі маршрутызатара.",
|
||||
"install_devices_windows_list_1": "Адкрыйце Панэль кіравання праз меню «Пуск» ці праз пошук Windows.",
|
||||
"install_devices_windows_list_2": "Перайдзіце ў «Сеціва і інтэрнэт», а потым у «Цэнтр кіравання сеціва і агульным доступам».",
|
||||
"install_devices_windows_list_3": "У левым боку экрана клікніце «Змена параметраў адаптара».",
|
||||
"install_devices_windows_list_4": "Пстрыкніце правай кнопкай мышы ваша актыўнае злучэнне і абярыце Уласцівасці.",
|
||||
"install_devices_windows_list_5": "Знайдзіце ў спісе пункт «IP версіі 4 (TCP/IPv4)», вылучыце яго і потым ізноў націсніце «Уласцівасці».",
|
||||
"install_devices_windows_list_6": "Абярыце «Выкарыстаць наступныя адрасы DNS-сервераў» і ўвядзіце адрас AdGuard Home.",
|
||||
"install_devices_windows_list_6": "Абярыце «Выкарыстаць наступныя адрасы сервер DNSаў» і ўвядзіце адрас AdGuard Home.",
|
||||
"install_devices_macos_list_1": "Клікніце па абразку Apple і перайдзіце ў Сістэмныя налады.",
|
||||
"install_devices_macos_list_2": "Клікніце па іконцы Сеціва.",
|
||||
"install_devices_macos_list_3": "Абярыце першае падлучэнне ў спісе і націсніце кнопку «Дадаткова».",
|
||||
@@ -415,7 +415,7 @@
|
||||
"encryption_key": "Прыватны ключ",
|
||||
"encryption_key_input": "Скапіюйце сюды прыватны ключ у PEM-кадоўцы.",
|
||||
"encryption_enable": "Уключыць шыфраванне (HTTPS, DNS-over-HTTPS і DNS-over-TLS)",
|
||||
"encryption_enable_desc": "Калі шыфраванне ўлучана, ўэб-інтэрфейс AdGuard Home будзе працаваць па HTTPS, а DNS-сервер будзе таксама працаваць па DNS-over-HTTPS і DNS-over-TLS.",
|
||||
"encryption_enable_desc": "Калі шыфраванне ўлучана, ўэб-інтэрфейс AdGuard Home будзе працаваць па HTTPS, а сервер DNS будзе таксама працаваць па DNS-over-HTTPS і DNS-over-TLS.",
|
||||
"encryption_chain_valid": "Ланцужок сертыфікатаў валідны",
|
||||
"encryption_chain_invalid": "Ланцужок сертыфікатаў не валідны",
|
||||
"encryption_key_valid": "Валідны {{type}} прыватны ключ",
|
||||
@@ -435,8 +435,8 @@
|
||||
"update_announcement": "AdGuard Home {{version}} ужо даступная! <0>Націсніце сюды</0>, каб даведацца больш.",
|
||||
"setup_guide": "Інструкцыя па наладзе",
|
||||
"dns_addresses": "Адрасы DNS",
|
||||
"dns_start": "DNS-сервер запускаецца",
|
||||
"dns_status_error": "Памылка праверкі стану DNS-сервера",
|
||||
"dns_start": "сервер DNS запускаецца",
|
||||
"dns_status_error": "Памылка праверкі стану сервер DNSа",
|
||||
"down": "Уніз",
|
||||
"fix": "Выправіць",
|
||||
"dns_providers": "<0>Спіс вядомых DNS-правайдараў</0> на выбар.",
|
||||
@@ -449,7 +449,7 @@
|
||||
"settings_global": "Глабальныя",
|
||||
"settings_custom": "Свае",
|
||||
"table_client": "Кліент",
|
||||
"table_name": "Імя",
|
||||
"table_name": "Назва",
|
||||
"save_btn": "Захаваць",
|
||||
"client_add": "Дадаць кліента",
|
||||
"client_new": "Новы кліент",
|
||||
@@ -475,7 +475,7 @@
|
||||
"auto_clients_title": "Кліенты (runtime)",
|
||||
"auto_clients_desc": "Інфармацыя аб IP-адрасах прылад, якія выкарыстоўваюць або могуць выкарыстоўваць AdGuard Home. Гэтая інфармацыя збіраецца з некалькіх крыніц, уключаючы файлы хостаў, зваротны DNS і г.д.",
|
||||
"access_title": "Налады доступу",
|
||||
"access_desc": "Тут вы можаце наладзіць правілы доступу да DNS-серверу AdGuard Home",
|
||||
"access_desc": "Тут вы можаце наладзіць правілы доступу да сервер DNSу AdGuard Home",
|
||||
"access_allowed_title": "Дазволеныя кліенты",
|
||||
"access_allowed_desc": "Спіс CIDR, IP-адрасоў або <a>ClientID</a>. Калі ў гэтым спісе ёсць запісы, AdGuard Home будзе прымаць запыты толькі ад гэтых кліентаў.",
|
||||
"access_disallowed_title": "Забароненыя кліенты",
|
||||
@@ -596,7 +596,7 @@
|
||||
"disable_ipv6_desc": "Ігнараваць усе запыты DNS для адрасоў IPv6 (тып AAAA) і выдаленне дадзеных IPv6 з адказаў тыпу HTTPS.",
|
||||
"fastest_addr": "Найхуткі IP-адрас",
|
||||
"fastest_addr_desc": "Апытайце ўсе DNS-серверы і вярніце самы хуткі IP-адрас сярод усіх адказаў. Гэта замарудзіць выкананне DNS-запытаў, бо нам давядзецца чакаць адказаў ад усіх DNS-сервераў, але палепшыць агульную ўзаемасувязь.",
|
||||
"autofix_warning_text": "Пры націску «Выправіць» AdGuard Home наладзіць вашу сістэму на выкарыстанне DNS-сервера AdGuard Home.",
|
||||
"autofix_warning_text": "Пры націску «Выправіць» AdGuard Home наладзіць вашу сістэму на выкарыстанне сервер DNSа AdGuard Home.",
|
||||
"autofix_warning_list": "Будуць выконвацца наступныя заданні: <0>Дэактываваць сістэмны DNSStubListener</0> <0>Усталяваць адрас сервера DNS на 127.0.0.1</0> <0>Стварыць сімвалічную спасылку /etc/resolv.conf на /run/systemd/resolve/resolv.conf</0> <0>Спыніць DNSStubListener (перазагрузіць сістэмную службу)</0>.",
|
||||
"autofix_warning_result": "У выніку ўсе DNS-запыты ад вашай сістэмы будуць па змаўчанні апрацоўвацца AdGuard Home.\n",
|
||||
"tags_title": "Тэгі",
|
||||
@@ -634,12 +634,12 @@
|
||||
"validated_with_dnssec": "Проверено с помощью DNSSEC",
|
||||
"all_queries": "Усе запыты",
|
||||
"show_blocked_responses": "Заблакавана",
|
||||
"show_whitelisted_responses": "Белы спіс",
|
||||
"show_whitelisted_responses": "У белым спісе",
|
||||
"show_processed_responses": "Апрацавана",
|
||||
"blocked_safebrowsing": "Заблакіравана згодна з базай даных Safe Browsing",
|
||||
"blocked_adult_websites": "Заблакавана Бацькоўскім кантролем",
|
||||
"blocked_threats": "Заблакавана пагроз",
|
||||
"allowed": "Дазволены",
|
||||
"allowed": "У белым спісе",
|
||||
"filtered": "Адфільтраваныя",
|
||||
"rewritten": "Перапісаныя",
|
||||
"safe_search": "Бяспечны пошук",
|
||||
@@ -738,7 +738,7 @@
|
||||
"thursday_short": "Чц.",
|
||||
"friday_short": "Пт.",
|
||||
"saturday_short": "Сб.",
|
||||
"upstream_dns_cache_configuration": "Канфігурацыя кэша upstream DNS-сервераў",
|
||||
"upstream_dns_cache_configuration": "Канфігурацыя кэша upstream сервер DNSаў",
|
||||
"enable_upstream_dns_cache": "Ўключыць кэшаванне для карыстацкай канфігурацыі upstream-сервераў гэтага кліента",
|
||||
"dns_cache_size": "Памер кэша DNS, у байтах"
|
||||
}
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "Zakázaný",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Velikost mezipaměti",
|
||||
"cache_size_desc": "Velikost mezipaměti DNS (v bajtech). Chcete-li ukládání do mezipaměti zakázat, ponechte prázdné.",
|
||||
"cache_size_desc": "Velikost mezipaměti DNS (v bajtech). Chcete-li ukládání do mezipaměti zakázat, nastavte 0.",
|
||||
"cache_ttl_min_override": "Přepsat minimální hodnotu TTL",
|
||||
"cache_ttl_max_override": "Přepsat maximální hodnotu TTL",
|
||||
"enter_cache_size": "Zadejte velikost mezipaměti (v bajtech)",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "Sortliste",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Cache-størrelse",
|
||||
"cache_size_desc": "DNS cache-størrelse (i bytes). Lad stå tomt for at deaktivere cache.",
|
||||
"cache_size_desc": "DNS cache-størrelse (i bytes). Sæt til 0 for at deaktivere cache.",
|
||||
"cache_ttl_min_override": "Tilsidesæt minimum TTL",
|
||||
"cache_ttl_max_override": "Tilsidesæt maksimal TTL",
|
||||
"enter_cache_size": "Angiv cache-størrelse (bytes)",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "Sperrliste",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Größe des Cache",
|
||||
"cache_size_desc": "Größe des DNS-Zwischenspeichers (in Bytes)",
|
||||
"cache_size_desc": "Größe des DNS-Cache (in Bytes). Um das Caching zu deaktivieren, setzen Sie den Wert auf 0.",
|
||||
"cache_ttl_min_override": "TTL-Minimalwert überschreiben",
|
||||
"cache_ttl_max_override": "TTL-Höchstwert überschreiben",
|
||||
"enter_cache_size": "Größe des Cache (Bytes) eingeben",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "Lista de bloqueo",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Tamaño de la caché",
|
||||
"cache_size_desc": "Tamaño de la caché DNS (en bytes). Para deshabilitar el almacenamiento en caché, déjalo vacío.",
|
||||
"cache_size_desc": "Tamaño de la caché DNS (en bytes). Para desactivar el almacenamiento en caché, configúralo en 0.",
|
||||
"cache_ttl_min_override": "Anular TTL mínimo",
|
||||
"cache_ttl_max_override": "Anular TTL máximo",
|
||||
"enter_cache_size": "Ingresa el tamaño de la caché (bytes)",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "Liste de blocage",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Taille du cache",
|
||||
"cache_size_desc": "Taille du cache DNS (en octets). Pour désactiver la mise en cache, laissez vide.",
|
||||
"cache_size_desc": "Taille du cache DNS (en octets). Pour désactiver la mise en cache, mettez la valeur sur 0.",
|
||||
"cache_ttl_min_override": "Remplacer le TTL minimum",
|
||||
"cache_ttl_max_override": "Remplacer le TTL maximum",
|
||||
"enter_cache_size": "Entrer la taille du cache (octets)",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "Lista nera",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Dimensioni cache",
|
||||
"cache_size_desc": "Dimensione della cache DNS (in byte). Per disabilitare la memorizzazione nella cache, lascia vuoto.",
|
||||
"cache_size_desc": "Dimensione della cache DNS (in byte). Per disabilitare la cache, impostare su 0.",
|
||||
"cache_ttl_min_override": "Sovrascrivi TTL minimo",
|
||||
"cache_ttl_max_override": "Sovrascrivi TTL massimo",
|
||||
"enter_cache_size": "Immetti dimensioni cache (in byte)",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "ブロックリスト",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "キャッシュサイズ",
|
||||
"cache_size_desc": "DNSキャッシュサイズ(バイト単位)。※キャッシュを無効化するには、この欄を空してください。",
|
||||
"cache_size_desc": "DNSキャッシュサイズ(バイト単位)※キャッシュを無効化するには、「0」(ゼロ)にしてください。",
|
||||
"cache_ttl_min_override": "最小TTLの上書き(秒単位)",
|
||||
"cache_ttl_max_override": "最大TTLの上書き(秒単位)",
|
||||
"enter_cache_size": "キャッシュサイズ(バイト単位)を入力してください",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "차단 목록",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "캐시 크기",
|
||||
"cache_size_desc": "DNS 캐시 크기(바이트). 캐싱을 비활성화하려면 비워 둡니다.",
|
||||
"cache_size_desc": "DNS 캐시 크기(바이트). 캐싱을 사용하지 않으려면 0으로 설정합니다.",
|
||||
"cache_ttl_min_override": "최소 TTL (초) 무시",
|
||||
"cache_ttl_max_override": "최대 TTL (초) 무시",
|
||||
"enter_cache_size": "캐시 크기를 입력하세요",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "Blokkeerlijst",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Cache grootte",
|
||||
"cache_size_desc": "DNS-cachegrootte (in bytes). Leeg laten om caching uit te schakelen.",
|
||||
"cache_size_desc": "DNS-cachegrootte (in bytes). Om caching uit te schakelen, stel deze in op 0.",
|
||||
"cache_ttl_min_override": "Minimale TTL overschrijven",
|
||||
"cache_ttl_max_override": "Maximale TTL overschrijven",
|
||||
"enter_cache_size": "Cache grootte invoeren (bytes)",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "Lista de bloqueio",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Tamanho do cache",
|
||||
"cache_size_desc": "Tamanho do cache do DNS (em bytes). Para desativar o cache, deixe em branco.",
|
||||
"cache_size_desc": "Tamanho do cache do DNS (em bytes). Para desativar o cache, defina como 0.",
|
||||
"cache_ttl_min_override": "Sobrepor o TTL mínimo",
|
||||
"cache_ttl_max_override": "Sobrepor o TTL máximo",
|
||||
"enter_cache_size": "Digite o tamanho do cache (bytes)",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "Lista de bloqueio",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Tamanho do cache",
|
||||
"cache_size_desc": "Tamanho do cache DNS (em bytes). Para desativar o cache, deixar o campo vazio.",
|
||||
"cache_size_desc": "Tamanho do cache DNS (em bytes). Para desativar o cache, defina como 0.",
|
||||
"cache_ttl_min_override": "Sobrepor o TTL mínimo",
|
||||
"cache_ttl_max_override": "Sobrepor o TTL máximo",
|
||||
"enter_cache_size": "Digite o tamanho do cache (bytes)",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "Чёрный список",
|
||||
"milliseconds_abbreviation": "мс",
|
||||
"cache_size": "Размер кеша",
|
||||
"cache_size_desc": "Размера кеша DNS (в байтах). Чтобы отключить кэширование, оставьте поле пустым.",
|
||||
"cache_size_desc": "Размер кеша DNS (в байтах). Чтобы отключить кеширование, установите значение 0.",
|
||||
"cache_ttl_min_override": "Переопределить минимальный TTL",
|
||||
"cache_ttl_max_override": "Переопределить максимальный TTL",
|
||||
"enter_cache_size": "Введите размер кеша (в байтах)",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "Zoznam blokovaní",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Veľkosť cache",
|
||||
"cache_size_desc": "Veľkosť vyrovnávacej pamäte DNS (v bajtoch). Ak chcete zakázať ukladanie do vyrovnávacej pamäte, ponechajte pole prázdne.",
|
||||
"cache_size_desc": "Veľkosť vyrovnávacej pamäte DNS (v bajtoch). Ak chcete vypnúť ukladanie do vyrovnávacej pamäte, nastavte hodnotu 0.",
|
||||
"cache_ttl_min_override": "Prepísať minimálne TTL",
|
||||
"cache_ttl_max_override": "Prepísať maximálne TTL",
|
||||
"enter_cache_size": "Zadať veľkosť cache (v bajtoch)",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "Engel listesi",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Önbellek boyutu",
|
||||
"cache_size_desc": "DNS önbellek boyutu (bayt cinsinden). Önbelleğe almayı devre dışı bırakmak için boş bırakın.",
|
||||
"cache_size_desc": "DNS önbellek boyutu (bayt cinsinden). Önbelleği devre dışı bırakmak için 0 olarak ayarlayın.",
|
||||
"cache_ttl_min_override": "Minimum kullanım süresini geçersiz kıl",
|
||||
"cache_ttl_max_override": "Maksimum kullanım süresini geçersiz kıl",
|
||||
"enter_cache_size": "Önbellek boyutunu girin (bayt)",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "黑名单",
|
||||
"milliseconds_abbreviation": "毫秒",
|
||||
"cache_size": "缓存大小",
|
||||
"cache_size_desc": "DNS 缓存大小(单位:字节)。若要关闭缓存,请留空。",
|
||||
"cache_size_desc": "DNS 缓存大小(单位:字节)。若要禁用缓存,请设置为 0。",
|
||||
"cache_ttl_min_override": "覆盖最小 TTL 值",
|
||||
"cache_ttl_max_override": "覆盖最大 TTL 值",
|
||||
"enter_cache_size": "输入缓存大小(字节)",
|
||||
|
||||
@@ -656,7 +656,7 @@
|
||||
"blocklist": "封鎖清單",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "快取大小",
|
||||
"cache_size_desc": "DNS 快取大小 (位元組)。若要停用快取,請留空。",
|
||||
"cache_size_desc": "DNS 快取大小(位元組)。若要停用快取,請設為 0。",
|
||||
"cache_ttl_min_override": "覆寫最小的存活時間(TTL)",
|
||||
"cache_ttl_max_override": "覆寫最大的存活時間(TTL)",
|
||||
"enter_cache_size": "輸入快取大小(位元組)",
|
||||
|
||||
12
go.mod
12
go.mod
@@ -3,8 +3,8 @@ module github.com/AdguardTeam/AdGuardHome
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.75.3
|
||||
github.com/AdguardTeam/golibs v0.32.8
|
||||
github.com/AdguardTeam/dnsproxy v0.75.5
|
||||
github.com/AdguardTeam/golibs v0.32.9
|
||||
github.com/AdguardTeam/urlfilter v0.20.0
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/ameshkov/dnscrypt/v2 v2.4.0
|
||||
@@ -36,7 +36,7 @@ require (
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||
golang.org/x/net v0.39.0
|
||||
golang.org/x/sys v0.32.0
|
||||
golang.org/x/sys v0.33.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
howett.net/plist v1.0.1
|
||||
@@ -61,7 +61,7 @@ require (
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/golangci/misspell v0.6.0 // indirect
|
||||
github.com/google/generative-ai-go v0.19.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
||||
@@ -89,11 +89,11 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/mock v0.5.1 // indirect
|
||||
go.uber.org/mock v0.5.2 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/oauth2 v0.29.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3 // indirect
|
||||
golang.org/x/term v0.31.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
|
||||
24
go.sum
24
go.sum
@@ -10,10 +10,10 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
|
||||
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
|
||||
github.com/AdguardTeam/dnsproxy v0.75.3 h1:pxlMNO+cP1A3px40PY/old6SAE82pkdLPUA2P3KY8u0=
|
||||
github.com/AdguardTeam/dnsproxy v0.75.3/go.mod h1:50OyTHao+uQzUJiXay08hgfvWQ3o2Q2WV99W8u8ypDE=
|
||||
github.com/AdguardTeam/golibs v0.32.8 h1:O3mc3kYcPkW3kbmd+gqzFNgUka13a+iBgFLThwOYSQE=
|
||||
github.com/AdguardTeam/golibs v0.32.8/go.mod h1:McV1QFFlKLElKa306V4OL/T2kr7564PhsayfvTWYBVs=
|
||||
github.com/AdguardTeam/dnsproxy v0.75.5 h1:/P7+Ku4bjl+sVC/FW3PbT7pabgCjKTcrAOHqsZe2e60=
|
||||
github.com/AdguardTeam/dnsproxy v0.75.5/go.mod h1:fdwtHhrDkTueDagDCasYKZbXdppkkBXW7RGPBNH+pis=
|
||||
github.com/AdguardTeam/golibs v0.32.9 h1:/6luT0aMOn05/s9eh1yA4lbcHgl0d1iEEvEBbIMMUk0=
|
||||
github.com/AdguardTeam/golibs v0.32.9/go.mod h1:McV1QFFlKLElKa306V4OL/T2kr7564PhsayfvTWYBVs=
|
||||
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs=
|
||||
github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
@@ -72,8 +72,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4=
|
||||
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
|
||||
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
|
||||
@@ -199,8 +199,8 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
|
||||
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
@@ -227,8 +227,8 @@ golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -241,8 +241,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3 h1:RXY2+rSHXvxO2Y+gKrPjYVaEoGOqh3VEXFhnWAt1Irg=
|
||||
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3/go.mod h1:RoaXAWDwS90j6FxVKwJdBV+0HCU+llrKUGgJaxiKl6M=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -478,7 +477,7 @@ const ErrBadIdentifier errors.Error = "bad client identifier"
|
||||
func (p *FindParams) Set(id string) (err error) {
|
||||
*p = FindParams{}
|
||||
|
||||
isClientID := true
|
||||
isFound := false
|
||||
|
||||
if netutil.IsValidIPString(id) {
|
||||
// It is safe to use [netip.MustParseAddr] because it has already been
|
||||
@@ -488,24 +487,27 @@ func (p *FindParams) Set(id string) (err error) {
|
||||
|
||||
// Even if id can be parsed as an IP address, it may be a MAC address.
|
||||
// So do not return prematurely, continue parsing.
|
||||
isClientID = false
|
||||
isFound = true
|
||||
}
|
||||
|
||||
if canBeValidIPPrefixString(id) {
|
||||
p.Subnet, err = netip.ParsePrefix(id)
|
||||
if err == nil {
|
||||
isClientID = false
|
||||
}
|
||||
}
|
||||
|
||||
if canBeMACString(id) {
|
||||
if netutil.IsValidMACString(id) {
|
||||
p.MAC, err = net.ParseMAC(id)
|
||||
if err == nil {
|
||||
isClientID = false
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("parsing mac from %q: %w", id, err))
|
||||
}
|
||||
|
||||
isFound = true
|
||||
}
|
||||
|
||||
if !isClientID {
|
||||
if isFound {
|
||||
return nil
|
||||
}
|
||||
|
||||
if netutil.IsValidIPPrefixString(id) {
|
||||
// It is safe to use [netip.MustParsePrefix] because it has already been
|
||||
// validated that id contains the string representation of IP prefix.
|
||||
p.Subnet = netip.MustParsePrefix(id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -518,57 +520,6 @@ func (p *FindParams) Set(id string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// canBeValidIPPrefixString is a best-effort check to determine if s is a valid
|
||||
// CIDR before using [netip.ParsePrefix], aimed at reducing allocations.
|
||||
//
|
||||
// TODO(s.chzhen): Replace this implementation with the more robust version
|
||||
// from golibs.
|
||||
func canBeValidIPPrefixString(s string) (ok bool) {
|
||||
ipStr, bitStr, ok := strings.Cut(s, "/")
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if bitStr == "" || len(bitStr) > 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
bits := 0
|
||||
for _, c := range bitStr {
|
||||
if c < '0' || c > '9' {
|
||||
return false
|
||||
}
|
||||
|
||||
bits = bits*10 + int(c-'0')
|
||||
}
|
||||
|
||||
if bits > 128 {
|
||||
return false
|
||||
}
|
||||
|
||||
return netutil.IsValidIPString(ipStr)
|
||||
}
|
||||
|
||||
// canBeMACString is a best-effort check to determine if s is a valid MAC
|
||||
// address before using [net.ParseMAC], aimed at reducing allocations.
|
||||
//
|
||||
// TODO(s.chzhen): Replace this implementation with the more robust version
|
||||
// from golibs.
|
||||
func canBeMACString(s string) (ok bool) {
|
||||
switch len(s) {
|
||||
case
|
||||
len("0000.0000.0000"),
|
||||
len("00:00:00:00:00:00"),
|
||||
len("0000.0000.0000.0000"),
|
||||
len("00:00:00:00:00:00:00:00"),
|
||||
len("0000.0000.0000.0000.0000.0000.0000.0000.0000.0000"),
|
||||
len("00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Find represents the parameters for searching a client. params must not be
|
||||
// nil and must have at least one non-empty field.
|
||||
func (s *Storage) Find(params *FindParams) (p *Persistent, ok bool) {
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
// serveV4 handles the ethernet packet of IPv4 type.
|
||||
func (srv *DHCPServer) serveV4(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
pkt gopacket.Packet,
|
||||
) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "serving dhcpv4: %w") }()
|
||||
|
||||
req, ok := pkt.Layer(layers.LayerTypeDHCPv4).(*layers.DHCPv4)
|
||||
if !ok {
|
||||
srv.logger.DebugContext(ctx, "skipping non-dhcpv4 packet")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Handle duplicate Xid.
|
||||
|
||||
if req.Operation != layers.DHCPOpRequest {
|
||||
srv.logger.DebugContext(ctx, "skipping non-request dhcpv4 packet")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
typ, ok := msg4Type(req)
|
||||
if !ok {
|
||||
// The "DHCP message type" option - must be included in every DHCP
|
||||
// message.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc2131#section-3.
|
||||
return fmt.Errorf("dhcpv4: message type: %w", errors.ErrNoValue)
|
||||
}
|
||||
|
||||
return srv.handleDHCPv4(ctx, rw, typ, req)
|
||||
}
|
||||
|
||||
// handleDHCPv4 handles the DHCPv4 message of the given type.
|
||||
func (srv *DHCPServer) handleDHCPv4(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
typ layers.DHCPMsgType,
|
||||
req *layers.DHCPv4,
|
||||
) (err error) {
|
||||
// Each interface should handle the DISCOVER and REQUEST messages offer and
|
||||
// allocate the available leases. The RELEASE and DECLINE messages should
|
||||
// be handled by the server itself as it should remove the lease.
|
||||
switch typ {
|
||||
case layers.DHCPMsgTypeDiscover:
|
||||
srv.handleDiscover(ctx, rw, req)
|
||||
case layers.DHCPMsgTypeRequest:
|
||||
srv.handleRequest(ctx, rw, req)
|
||||
case layers.DHCPMsgTypeRelease:
|
||||
// TODO(e.burkov): !! Remove the lease, either allocated or offered.
|
||||
case layers.DHCPMsgTypeDecline:
|
||||
// TODO(e.burkov): !! Remove the allocated lease. RFC tells it only
|
||||
// possible if the client found the address already in use.
|
||||
default:
|
||||
// TODO(e.burkov): Handle DHCPINFORM.
|
||||
return fmt.Errorf("dhcpv4: request type: %w: %v", errors.ErrBadEnumValue, typ)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleDiscover handles the DHCPv4 message of discover type.
|
||||
func (srv *DHCPServer) handleDiscover(ctx context.Context, rw responseWriter4, req *layers.DHCPv4) {
|
||||
// TODO(e.burkov): Check existing leases, either allocated or offered.
|
||||
|
||||
for _, iface := range srv.interfaces4 {
|
||||
go iface.handleDiscover(ctx, rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
// handleRequest handles the DHCPv4 message of request type.
|
||||
func (srv *DHCPServer) handleRequest(ctx context.Context, rw responseWriter4, req *layers.DHCPv4) {
|
||||
srvID, hasSrvID := serverID4(req)
|
||||
reqIP, hasReqIP := requestedIPv4(req)
|
||||
|
||||
switch {
|
||||
case hasSrvID && !srvID.IsUnspecified():
|
||||
// If the DHCPREQUEST message contains a server identifier option, the
|
||||
// message is in response to a DHCPOFFER message. Otherwise, the
|
||||
// message is a request to verify or extend an existing lease.
|
||||
iface, hasIface := srv.interfaces4.findInterface(srvID)
|
||||
if !hasIface {
|
||||
srv.logger.DebugContext(ctx, "skipping selecting request", "serverid", srvID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface.handleSelecting(ctx, rw, req, reqIP)
|
||||
case hasReqIP && !reqIP.IsUnspecified():
|
||||
// Requested IP address option MUST be filled in with client's notion of
|
||||
// its previously assigned address.
|
||||
iface, hasIface := srv.interfaces4.findInterface(reqIP)
|
||||
if !hasIface {
|
||||
srv.logger.DebugContext(ctx, "skipping init-reboot request", "requestedip", reqIP)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface.handleInitReboot(ctx, rw, req, reqIP)
|
||||
default:
|
||||
// Server identifier MUST NOT be filled in, requested IP address option
|
||||
// MUST NOT be filled in.
|
||||
ip, _ := netip.AddrFromSlice(req.ClientIP.To4())
|
||||
iface, hasIface := srv.interfaces4.findInterface(ip)
|
||||
if !hasIface {
|
||||
srv.logger.DebugContext(ctx, "skipping init-reboot request", "clientip", ip)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface.handleRenew(ctx, rw, req)
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
// serveV6 handles the ethernet packet of IPv6 type.
|
||||
func (srv *DHCPServer) serveV6(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
pkt gopacket.Packet,
|
||||
) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "serving dhcpv6: %w") }()
|
||||
|
||||
msg, ok := pkt.Layer(layers.LayerTypeDHCPv6).(*layers.DHCPv6)
|
||||
if !ok {
|
||||
srv.logger.DebugContext(ctx, "skipping non-dhcpv6 packet")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Handle duplicate TransactionID.
|
||||
|
||||
typ := msg.MsgType
|
||||
|
||||
return srv.handleDHCPv6(ctx, rw, typ, msg)
|
||||
}
|
||||
|
||||
// handleDHCPv6 handles the DHCPv6 message of the given type.
|
||||
func (srv *DHCPServer) handleDHCPv6(
|
||||
_ context.Context,
|
||||
_ responseWriter4,
|
||||
typ layers.DHCPv6MsgType,
|
||||
_ *layers.DHCPv6,
|
||||
) (err error) {
|
||||
switch typ {
|
||||
case
|
||||
layers.DHCPv6MsgTypeSolicit,
|
||||
layers.DHCPv6MsgTypeRequest,
|
||||
layers.DHCPv6MsgTypeConfirm,
|
||||
layers.DHCPv6MsgTypeRenew,
|
||||
layers.DHCPv6MsgTypeRebind,
|
||||
layers.DHCPv6MsgTypeInformationRequest,
|
||||
layers.DHCPv6MsgTypeRelease,
|
||||
layers.DHCPv6MsgTypeDecline:
|
||||
// TODO(e.burkov): Handle messages.
|
||||
default:
|
||||
return fmt.Errorf("dhcpv6: request type: %w: %v", errors.ErrBadEnumValue, typ)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -45,6 +45,17 @@ type netInterface struct {
|
||||
leaseTTL time.Duration
|
||||
}
|
||||
|
||||
// newNetInterface creates a new netInterface with the given name, leaseTTL, and
|
||||
// logger.
|
||||
func newNetInterface(name string, l *slog.Logger, leaseTTL time.Duration) (iface *netInterface) {
|
||||
return &netInterface{
|
||||
logger: l,
|
||||
leases: map[macKey]*Lease{},
|
||||
name: name,
|
||||
leaseTTL: leaseTTL,
|
||||
}
|
||||
}
|
||||
|
||||
// reset clears all the slices in iface for reuse.
|
||||
func (iface *netInterface) reset() {
|
||||
clear(iface.leases)
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
// responseWriter4 writes DHCPv4 response to the client.
|
||||
type responseWriter4 interface {
|
||||
// write writes the DHCPv4 response to the client.
|
||||
write(ctx context.Context, pkt *layers.DHCPv4) (err error)
|
||||
}
|
||||
|
||||
// serve handles the incoming packets and dispatches them to the appropriate
|
||||
// handler based on the Ethernet type. It's used to run in a separate goroutine
|
||||
// as it blocks until packets channel is closed.
|
||||
func (srv *DHCPServer) serve(ctx context.Context) {
|
||||
defer slogutil.RecoverAndLog(ctx, srv.logger)
|
||||
|
||||
for pkt := range srv.packetSource.Packets() {
|
||||
etherLayer, ok := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
|
||||
if !ok {
|
||||
srv.logger.DebugContext(ctx, "skipping non-ethernet packet")
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// TODO(e.burkov): Set the response writer.
|
||||
var rw responseWriter4
|
||||
|
||||
switch typ := etherLayer.EthernetType; typ {
|
||||
case layers.EthernetTypeIPv4:
|
||||
err = srv.serveV4(ctx, rw, pkt)
|
||||
case layers.EthernetTypeIPv6:
|
||||
err = srv.serveV6(ctx, rw, pkt)
|
||||
default:
|
||||
srv.logger.DebugContext(ctx, "skipping ethernet packet", "type", typ)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
srv.logger.ErrorContext(ctx, "serving", slogutil.KeyError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,13 +13,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/google/gopacket"
|
||||
)
|
||||
|
||||
// DHCPServer is a DHCP server for both IPv4 and IPv6 address families.
|
||||
//
|
||||
// TODO(e.burkov): Rename to Default.
|
||||
type DHCPServer struct {
|
||||
// enabled indicates whether the DHCP server is enabled and can provide
|
||||
// information about its clients.
|
||||
@@ -28,9 +24,6 @@ type DHCPServer struct {
|
||||
// logger logs common DHCP events.
|
||||
logger *slog.Logger
|
||||
|
||||
// TODO(e.burkov): Implement and set.
|
||||
packetSource gopacket.PacketSource
|
||||
|
||||
// localTLD is the top-level domain name to use for resolving DHCP clients'
|
||||
// hostnames.
|
||||
localTLD string
|
||||
@@ -105,7 +98,7 @@ func New(ctx context.Context, conf *Config) (srv *DHCPServer, err error) {
|
||||
// their configurations.
|
||||
func newInterfaces(
|
||||
ctx context.Context,
|
||||
baseLogger *slog.Logger,
|
||||
l *slog.Logger,
|
||||
ifaces map[string]*InterfaceConfig,
|
||||
) (v4 dhcpInterfacesV4, v6 dhcpInterfacesV6, err error) {
|
||||
defer func() { err = errors.Annotate(err, "creating interfaces: %w") }()
|
||||
@@ -117,27 +110,18 @@ func newInterfaces(
|
||||
var errs []error
|
||||
for _, name := range slices.Sorted(maps.Keys(ifaces)) {
|
||||
iface := ifaces[name]
|
||||
|
||||
iface4, v4Err := newDHCPInterfaceV4(
|
||||
ctx,
|
||||
baseLogger.With(keyInterface, name, keyFamily, netutil.AddrFamilyIPv4),
|
||||
name,
|
||||
iface.IPv4,
|
||||
)
|
||||
if v4Err != nil {
|
||||
v4Err = fmt.Errorf("interface %q: %s: %w", name, netutil.AddrFamilyIPv4, v4Err)
|
||||
errs = append(errs, v4Err)
|
||||
} else {
|
||||
v4 = append(v4, iface4)
|
||||
var i4 *dhcpInterfaceV4
|
||||
i4, err = newDHCPInterfaceV4(ctx, l, name, iface.IPv4)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("interface %q: ipv4: %w", name, err))
|
||||
} else if i4 != nil {
|
||||
v4 = append(v4, i4)
|
||||
}
|
||||
|
||||
iface6 := newDHCPInterfaceV6(
|
||||
ctx,
|
||||
baseLogger.With(keyInterface, name, keyFamily, netutil.AddrFamilyIPv6),
|
||||
name,
|
||||
iface.IPv6,
|
||||
)
|
||||
v6 = append(v6, iface6)
|
||||
i6 := newDHCPInterfaceV6(ctx, l, name, iface.IPv6)
|
||||
if i6 != nil {
|
||||
v6 = append(v6, i6)
|
||||
}
|
||||
}
|
||||
|
||||
if err = errors.Join(errs...); err != nil {
|
||||
@@ -152,25 +136,6 @@ func newInterfaces(
|
||||
// TODO(e.burkov): Uncomment when the [Interface] interface is implemented.
|
||||
// var _ Interface = (*DHCPServer)(nil)
|
||||
|
||||
// Start implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) Start(ctx context.Context) (err error) {
|
||||
srv.logger.DebugContext(ctx, "starting dhcp server")
|
||||
|
||||
// TODO(e.burkov): Listen to configured interfaces.
|
||||
|
||||
go srv.serve(context.Background())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *DHCPServer) Shutdown(ctx context.Context) (err error) {
|
||||
srv.logger.DebugContext(ctx, "shutting down dhcp server")
|
||||
|
||||
// TODO(e.burkov): Close the packet source.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enabled implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) Enabled() (ok bool) {
|
||||
return srv.enabled.Load()
|
||||
@@ -370,50 +335,6 @@ func (srv *DHCPServer) RemoveLease(ctx context.Context, l *Lease) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeLeaseByAddr removes the lease with the given IP address from the
|
||||
// server. It returns an error if the lease can't be removed.
|
||||
//
|
||||
// TODO(e.burkov): !! Use.
|
||||
func (srv *DHCPServer) removeLeaseByAddr(ctx context.Context, addr netip.Addr) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "removing lease by address: %w") }()
|
||||
|
||||
iface, err := srv.ifaceForAddr(addr)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's already informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
srv.leasesMu.Lock()
|
||||
defer srv.leasesMu.Unlock()
|
||||
|
||||
l, ok := srv.leases.leaseByAddr(addr)
|
||||
if !ok {
|
||||
return fmt.Errorf("no lease for ip %s", addr)
|
||||
}
|
||||
|
||||
err = srv.leases.remove(l, iface)
|
||||
if err != nil {
|
||||
// Don't wrap the error since there is already an annotation deferred.
|
||||
return err
|
||||
}
|
||||
|
||||
err = srv.dbStore(ctx)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's already informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
iface.logger.DebugContext(
|
||||
ctx, "removed lease",
|
||||
"hostname", l.Hostname,
|
||||
"ip", l.IP,
|
||||
"mac", l.HWAddr,
|
||||
"static", l.IsStatic,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ifaceForAddr returns the handled network interface for the given IP address,
|
||||
// or an error if no such interface exists.
|
||||
func (srv *DHCPServer) ifaceForAddr(addr netip.Addr) (iface *netInterface, err error) {
|
||||
|
||||
@@ -91,7 +91,7 @@ type dhcpInterfaceV4 struct {
|
||||
// gateway is the IP address of the network gateway.
|
||||
gateway netip.Addr
|
||||
|
||||
// subnet is the network subnet of the interface.
|
||||
// subnet is the network subnet.
|
||||
subnet netip.Prefix
|
||||
|
||||
// addrSpace is the IPv4 address space allocated for leasing.
|
||||
@@ -115,7 +115,12 @@ func newDHCPInterfaceV4(
|
||||
l *slog.Logger,
|
||||
name string,
|
||||
conf *IPv4Config,
|
||||
) (iface *dhcpInterfaceV4, err error) {
|
||||
) (i *dhcpInterfaceV4, err error) {
|
||||
l = l.With(
|
||||
keyInterface, name,
|
||||
keyFamily, netutil.AddrFamilyIPv4,
|
||||
)
|
||||
|
||||
if !conf.Enabled {
|
||||
l.DebugContext(ctx, "disabled")
|
||||
|
||||
@@ -139,20 +144,31 @@ func newDHCPInterfaceV4(
|
||||
return nil, fmt.Errorf("gateway ip %s in the ip range %s", conf.GatewayIP, addrSpace)
|
||||
}
|
||||
|
||||
iface = &dhcpInterfaceV4{
|
||||
i = &dhcpInterfaceV4{
|
||||
gateway: conf.GatewayIP,
|
||||
subnet: subnet,
|
||||
addrSpace: addrSpace,
|
||||
common: &netInterface{
|
||||
logger: l,
|
||||
leases: map[macKey]*Lease{},
|
||||
name: name,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
},
|
||||
common: newNetInterface(name, l, conf.LeaseDuration),
|
||||
}
|
||||
iface.implicitOpts, iface.explicitOpts = conf.options(ctx, l)
|
||||
i.implicitOpts, i.explicitOpts = conf.options(ctx, l)
|
||||
|
||||
return iface, nil
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// dhcpInterfacesV4 is a slice of network interfaces of IPv4 address family.
|
||||
type dhcpInterfacesV4 []*dhcpInterfaceV4
|
||||
|
||||
// find returns the first network interface within ifaces containing ip. It
|
||||
// returns false if there is no such interface.
|
||||
func (ifaces dhcpInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok bool) {
|
||||
i := slices.IndexFunc(ifaces, func(iface *dhcpInterfaceV4) (contains bool) {
|
||||
return iface.subnet.Contains(ip)
|
||||
})
|
||||
if i < 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return ifaces[i].common, true
|
||||
}
|
||||
|
||||
// options returns the implicit and explicit options for the interface. The two
|
||||
@@ -345,104 +361,3 @@ func (c *IPv4Config) options(ctx context.Context, l *slog.Logger) (imp, exp laye
|
||||
func compareV4OptionCodes(a, b layers.DHCPOption) (res int) {
|
||||
return int(a.Type) - int(b.Type)
|
||||
}
|
||||
|
||||
// msg4Type returns the message type of msg, if it's present within the options.
|
||||
func msg4Type(msg *layers.DHCPv4) (typ layers.DHCPMsgType, ok bool) {
|
||||
for _, opt := range msg.Options {
|
||||
if opt.Type == layers.DHCPOptMessageType && len(opt.Data) > 0 {
|
||||
return layers.DHCPMsgType(opt.Data[0]), true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// requestedIPv4 returns the IPv4 address, requested by client in the DHCP
|
||||
// message, if any.
|
||||
func requestedIPv4(msg *layers.DHCPv4) (ip netip.Addr, ok bool) {
|
||||
for _, opt := range msg.Options {
|
||||
if opt.Type == layers.DHCPOptRequestIP && len(opt.Data) == net.IPv4len {
|
||||
return netip.AddrFromSlice(opt.Data)
|
||||
}
|
||||
}
|
||||
|
||||
return netip.Addr{}, false
|
||||
}
|
||||
|
||||
// serverID4 returns the server ID of the DHCP message, if any.
|
||||
func serverID4(msg *layers.DHCPv4) (ip netip.Addr, ok bool) {
|
||||
for _, opt := range msg.Options {
|
||||
if opt.Type == layers.DHCPOptServerID && len(opt.Data) == net.IPv4len {
|
||||
return netip.AddrFromSlice(opt.Data)
|
||||
}
|
||||
}
|
||||
|
||||
return netip.Addr{}, false
|
||||
}
|
||||
|
||||
// handleDiscover handles messages of type discover.
|
||||
func (iface *dhcpInterfaceV4) handleDiscover(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
msg *layers.DHCPv4,
|
||||
) {
|
||||
// TODO(e.burkov): !! Implement.
|
||||
}
|
||||
|
||||
// handleSelecting handles messages of type request in SELECTING state.
|
||||
func (iface *dhcpInterfaceV4) handleSelecting(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
msg *layers.DHCPv4,
|
||||
reqIP netip.Addr,
|
||||
) {
|
||||
// TODO(e.burkov): !! Implement.
|
||||
}
|
||||
|
||||
// handleSelecting handles messages of type request in INIT-REBOOT state.
|
||||
func (iface *dhcpInterfaceV4) handleInitReboot(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
msg *layers.DHCPv4,
|
||||
reqIP netip.Addr,
|
||||
) {
|
||||
// TODO(e.burkov): !! Implement.
|
||||
}
|
||||
|
||||
// handleRenew handles messages of type request in RENEWING or REBINDING state.
|
||||
func (iface *dhcpInterfaceV4) handleRenew(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
req *layers.DHCPv4,
|
||||
) {
|
||||
// TODO(e.burkov): !! Implement.
|
||||
}
|
||||
|
||||
// dhcpInterfacesV4 is a slice of network interfaces of IPv4 address family.
|
||||
type dhcpInterfacesV4 []*dhcpInterfaceV4
|
||||
|
||||
// find returns the first network interface within ifaces containing ip. It
|
||||
// returns false if there is no such interface.
|
||||
func (ifaces dhcpInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok bool) {
|
||||
i := slices.IndexFunc(ifaces, func(iface *dhcpInterfaceV4) (contains bool) {
|
||||
return iface.subnet.Contains(ip)
|
||||
})
|
||||
if i < 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return ifaces[i].common, true
|
||||
}
|
||||
|
||||
// findInterface returns the first DHCPv4 interface within ifaces containing
|
||||
// ip. It returns false if there is no such interface.
|
||||
func (ifaces dhcpInterfacesV4) findInterface(ip netip.Addr) (iface *dhcpInterfaceV4, ok bool) {
|
||||
i := slices.IndexFunc(ifaces, func(iface *dhcpInterfaceV4) (contains bool) {
|
||||
return iface.subnet.Contains(ip)
|
||||
})
|
||||
if i < 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return ifaces[i], true
|
||||
}
|
||||
|
||||
@@ -97,27 +97,23 @@ func newDHCPInterfaceV6(
|
||||
l *slog.Logger,
|
||||
name string,
|
||||
conf *IPv6Config,
|
||||
) (iface *dhcpInterfaceV6) {
|
||||
) (i *dhcpInterfaceV6) {
|
||||
l = l.With(keyInterface, name, keyFamily, netutil.AddrFamilyIPv6)
|
||||
if !conf.Enabled {
|
||||
l.DebugContext(ctx, "disabled")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
iface = &dhcpInterfaceV6{
|
||||
rangeStart: conf.RangeStart,
|
||||
common: &netInterface{
|
||||
logger: l,
|
||||
leases: map[macKey]*Lease{},
|
||||
name: name,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
},
|
||||
i = &dhcpInterfaceV6{
|
||||
rangeStart: conf.RangeStart,
|
||||
common: newNetInterface(name, l, conf.LeaseDuration),
|
||||
raSLAACOnly: conf.RASLAACOnly,
|
||||
raAllowSLAAC: conf.RAAllowSLAAC,
|
||||
}
|
||||
iface.implicitOpts, iface.explicitOpts = conf.options(ctx, l)
|
||||
i.implicitOpts, i.explicitOpts = conf.options(ctx, l)
|
||||
|
||||
return iface
|
||||
return i
|
||||
}
|
||||
|
||||
// dhcpInterfacesV6 is a slice of network interfaces of IPv6 address family.
|
||||
|
||||
@@ -82,7 +82,7 @@ func (web *webAPI) requestVersionInfo(
|
||||
) (err error) {
|
||||
updater := web.conf.updater
|
||||
for range 3 {
|
||||
resp.VersionInfo, err = updater.VersionInfo(recheck)
|
||||
resp.VersionInfo, err = updater.VersionInfo(ctx, recheck)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -133,7 +133,7 @@ func (web *webAPI) handleUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = updater.Update(false)
|
||||
err = updater.Update(r.Context(), false)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
|
||||
|
||||
|
||||
@@ -119,16 +119,15 @@ func initDNS(
|
||||
globalContext.dhcpServer,
|
||||
anonymizer,
|
||||
httpRegister,
|
||||
tlsMgr.config(),
|
||||
tlsMgr,
|
||||
baseLogger,
|
||||
)
|
||||
}
|
||||
|
||||
// initDNSServer initializes the [context.dnsServer]. To only use the internal
|
||||
// proxy, none of the arguments are required, but tlsConf, tlsMgr and l still
|
||||
// must not be nil, in other cases all the arguments also must not be nil. It
|
||||
// also must not be called unless [config] and [globalContext] are initialized.
|
||||
// proxy, none of the arguments are required, but tlsMgr and l still must not be
|
||||
// nil, in other cases all the arguments also must not be nil. It also must not
|
||||
// be called unless [config] and [globalContext] are initialized.
|
||||
//
|
||||
// TODO(e.burkov): Use [dnsforward.DNSCreateParams] as a parameter.
|
||||
func initDNSServer(
|
||||
@@ -138,7 +137,6 @@ func initDNSServer(
|
||||
dhcpSrv dnsforward.DHCP,
|
||||
anonymizer *aghnet.IPMut,
|
||||
httpReg aghhttp.RegisterFunc,
|
||||
tlsConf *tlsConfigSettings,
|
||||
tlsMgr *tlsManager,
|
||||
l *slog.Logger,
|
||||
) (err error) {
|
||||
@@ -167,7 +165,7 @@ func initDNSServer(
|
||||
dnsConf, err := newServerConfig(
|
||||
&config.DNS,
|
||||
config.Clients.Sources,
|
||||
tlsConf,
|
||||
tlsMgr.config(),
|
||||
tlsMgr,
|
||||
httpReg,
|
||||
globalContext.clients.storage,
|
||||
|
||||
@@ -487,9 +487,14 @@ func checkPorts() (err error) {
|
||||
}
|
||||
|
||||
// isUpdateEnabled returns true if the update is enabled for current
|
||||
// configuration. It also logs the decision. customURL should be true if the
|
||||
// configuration. It also logs the decision. isCustomURL should be true if the
|
||||
// updater is using a custom URL.
|
||||
func isUpdateEnabled(ctx context.Context, l *slog.Logger, opts *options, customURL bool) (ok bool) {
|
||||
func isUpdateEnabled(
|
||||
ctx context.Context,
|
||||
l *slog.Logger,
|
||||
opts *options,
|
||||
isCustomURL bool,
|
||||
) (ok bool) {
|
||||
if opts.disableUpdate {
|
||||
l.DebugContext(ctx, "updates are disabled by command-line option")
|
||||
|
||||
@@ -500,13 +505,13 @@ func isUpdateEnabled(ctx context.Context, l *slog.Logger, opts *options, customU
|
||||
case
|
||||
version.ChannelDevelopment,
|
||||
version.ChannelCandidate:
|
||||
if customURL {
|
||||
if isCustomURL {
|
||||
l.DebugContext(ctx, "updates are enabled because custom url is used")
|
||||
} else {
|
||||
l.DebugContext(ctx, "updates are disabled for development and candidate builds")
|
||||
}
|
||||
|
||||
return customURL
|
||||
return isCustomURL
|
||||
default:
|
||||
l.DebugContext(ctx, "updates are enabled")
|
||||
|
||||
@@ -514,7 +519,7 @@ func isUpdateEnabled(ctx context.Context, l *slog.Logger, opts *options, customU
|
||||
}
|
||||
}
|
||||
|
||||
// initWeb initializes the web module. upd, baseLogger, and tlsMgr must not be
|
||||
// initWeb initializes the web module. upd, baseLogger, and tlsMgr must not be
|
||||
// nil.
|
||||
func initWeb(
|
||||
ctx context.Context,
|
||||
@@ -523,7 +528,7 @@ func initWeb(
|
||||
upd *updater.Updater,
|
||||
baseLogger *slog.Logger,
|
||||
tlsMgr *tlsManager,
|
||||
customURL bool,
|
||||
isCustomUpdURL bool,
|
||||
) (web *webAPI, err error) {
|
||||
logger := baseLogger.With(slogutil.KeyPrefix, "webapi")
|
||||
|
||||
@@ -539,7 +544,7 @@ func initWeb(
|
||||
}
|
||||
}
|
||||
|
||||
disableUpdate := !isUpdateEnabled(ctx, baseLogger, &opts, customURL)
|
||||
disableUpdate := !isUpdateEnabled(ctx, baseLogger, &opts, isCustomUpdURL)
|
||||
|
||||
webConf := &webConfig{
|
||||
updater: upd,
|
||||
@@ -645,11 +650,12 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalH
|
||||
|
||||
confPath := configFilePath()
|
||||
|
||||
upd, customURL := newUpdater(ctx, slogLogger, globalContext.workDir, confPath, execPath, config)
|
||||
updLogger := slogLogger.With(slogutil.KeyPrefix, "updater")
|
||||
upd, isCustomURL := newUpdater(ctx, updLogger, config, globalContext.workDir, confPath, execPath)
|
||||
|
||||
// TODO(e.burkov): This could be made earlier, probably as the option's
|
||||
// effect.
|
||||
cmdlineUpdate(ctx, slogLogger, opts, upd, tlsMgr)
|
||||
cmdlineUpdate(ctx, updLogger, opts, upd, tlsMgr)
|
||||
|
||||
if !globalContext.firstRun {
|
||||
// Save the updated config.
|
||||
@@ -671,7 +677,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalH
|
||||
globalContext.auth, err = initUsers()
|
||||
fatalOnError(err)
|
||||
|
||||
web, err := initWeb(ctx, opts, clientBuildFS, upd, slogLogger, tlsMgr, customURL)
|
||||
web, err := initWeb(ctx, opts, clientBuildFS, upd, slogLogger, tlsMgr, isCustomURL)
|
||||
fatalOnError(err)
|
||||
|
||||
globalContext.web = web
|
||||
@@ -714,16 +720,17 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalH
|
||||
<-done
|
||||
}
|
||||
|
||||
// newUpdater creates a new AdGuard Home updater. customURL is true if the user
|
||||
// has specified a custom version announcement URL.
|
||||
// newUpdater creates a new AdGuard Home updater. l and conf must not be nil.
|
||||
// workDir, confPath, and execPath must not be empty. isCustomURL is true if
|
||||
// the user has specified a custom version announcement URL.
|
||||
func newUpdater(
|
||||
ctx context.Context,
|
||||
l *slog.Logger,
|
||||
conf *configuration,
|
||||
workDir string,
|
||||
confPath string,
|
||||
execPath string,
|
||||
config *configuration,
|
||||
) (upd *updater.Updater, customURL bool) {
|
||||
) (upd *updater.Updater, isCustomURL bool) {
|
||||
// envName is the name of the environment variable that can be used to
|
||||
// override the default version check URL.
|
||||
const envName = "ADGUARD_HOME_TEST_UPDATE_VERSION_URL"
|
||||
@@ -735,14 +742,14 @@ func newUpdater(
|
||||
case version.Channel() == version.ChannelRelease:
|
||||
// Only enable custom version URL for development builds.
|
||||
l.DebugContext(ctx, "custom version url is disabled for release builds")
|
||||
case !config.UnsafeUseCustomUpdateIndexURL:
|
||||
case !conf.UnsafeUseCustomUpdateIndexURL:
|
||||
l.DebugContext(ctx, "custom version url is disabled in config")
|
||||
default:
|
||||
versionURL, _ = url.Parse(customURLStr)
|
||||
}
|
||||
|
||||
err := urlutil.ValidateHTTPURL(versionURL)
|
||||
if customURL = err == nil; !customURL {
|
||||
if isCustomURL = err == nil; !isCustomURL {
|
||||
l.DebugContext(ctx, "parsing custom version url", slogutil.KeyError, err)
|
||||
|
||||
versionURL = updater.DefaultVersionURL()
|
||||
@@ -751,7 +758,8 @@ func newUpdater(
|
||||
l.DebugContext(ctx, "creating updater", "config_path", confPath)
|
||||
|
||||
return updater.NewUpdater(&updater.Config{
|
||||
Client: config.Filtering.HTTPClient,
|
||||
Client: conf.Filtering.HTTPClient,
|
||||
Logger: l,
|
||||
Version: version.Version(),
|
||||
Channel: version.Channel(),
|
||||
GOARCH: runtime.GOARCH,
|
||||
@@ -762,7 +770,7 @@ func newUpdater(
|
||||
ConfName: confPath,
|
||||
ExecPath: execPath,
|
||||
VersionCheckURL: versionURL,
|
||||
}), customURL
|
||||
}), isCustomURL
|
||||
}
|
||||
|
||||
// checkPermissions checks and migrates permissions of the files and directories
|
||||
@@ -1078,12 +1086,12 @@ func cmdlineUpdate(
|
||||
//
|
||||
// TODO(e.burkov): We could probably initialize the internal resolver
|
||||
// separately.
|
||||
err := initDNSServer(nil, nil, nil, nil, nil, nil, &tlsConfigSettings{}, tlsMgr, l)
|
||||
err := initDNSServer(nil, nil, nil, nil, nil, nil, tlsMgr, l)
|
||||
fatalOnError(err)
|
||||
|
||||
l.InfoContext(ctx, "performing update via cli")
|
||||
|
||||
info, err := upd.VersionInfo(true)
|
||||
info, err := upd.VersionInfo(ctx, true)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "getting version info", slogutil.KeyError, err)
|
||||
|
||||
@@ -1096,7 +1104,7 @@ func cmdlineUpdate(
|
||||
os.Exit(osutil.ExitCodeSuccess)
|
||||
}
|
||||
|
||||
err = upd.Update(globalContext.firstRun)
|
||||
err = upd.Update(ctx, globalContext.firstRun)
|
||||
fatalOnError(err)
|
||||
|
||||
err = restartService()
|
||||
|
||||
@@ -193,7 +193,10 @@ func (m *tlsManager) start(_ context.Context) {
|
||||
m.web.tlsConfigChanged(context.Background(), m.conf)
|
||||
}
|
||||
|
||||
// reload updates the configuration and restarts the TLS manager.
|
||||
// reload updates the configuration and restarts the TLS manager. It logs any
|
||||
// encountered errors.
|
||||
//
|
||||
// TODO(s.chzhen): Consider returning an error.
|
||||
func (m *tlsManager) reload(ctx context.Context) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/ioutil"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/c2h5oh/datasize"
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@ const maxVersionRespSize datasize.ByteSize = 64 * datasize.KB
|
||||
|
||||
// VersionInfo downloads the latest version information. If forceRecheck is
|
||||
// false and there are cached results, those results are returned.
|
||||
func (u *Updater) VersionInfo(forceRecheck bool) (vi VersionInfo, err error) {
|
||||
func (u *Updater) VersionInfo(ctx context.Context, forceRecheck bool) (vi VersionInfo, err error) {
|
||||
u.mu.Lock()
|
||||
defer u.mu.Unlock()
|
||||
|
||||
@@ -45,11 +45,17 @@ func (u *Updater) VersionInfo(forceRecheck bool) (vi VersionInfo, err error) {
|
||||
return u.prevCheckResult, u.prevCheckError
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
vcu := u.versionCheckURL
|
||||
resp, err = u.client.Get(vcu)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, vcu, nil)
|
||||
if err != nil {
|
||||
return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", vcu, err)
|
||||
return VersionInfo{}, fmt.Errorf("constructing request to %s: %w", vcu, err)
|
||||
}
|
||||
|
||||
u.logger.DebugContext(ctx, "requesting version data", "url", vcu)
|
||||
|
||||
resp, err := u.client.Do(req)
|
||||
if err != nil {
|
||||
return VersionInfo{}, fmt.Errorf("requesting %s: %w", vcu, err)
|
||||
}
|
||||
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
|
||||
|
||||
@@ -59,16 +65,16 @@ func (u *Updater) VersionInfo(forceRecheck bool) (vi VersionInfo, err error) {
|
||||
// ReadCloser.
|
||||
body, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", vcu, err)
|
||||
return VersionInfo{}, fmt.Errorf("reading response from %s: %w", vcu, err)
|
||||
}
|
||||
|
||||
u.prevCheckTime = now
|
||||
u.prevCheckResult, u.prevCheckError = u.parseVersionResponse(body)
|
||||
u.prevCheckResult, u.prevCheckError = u.parseVersionResponse(ctx, body)
|
||||
|
||||
return u.prevCheckResult, u.prevCheckError
|
||||
}
|
||||
|
||||
func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
|
||||
func (u *Updater) parseVersionResponse(ctx context.Context, data []byte) (VersionInfo, error) {
|
||||
info := VersionInfo{
|
||||
CanAutoUpdate: aghalg.NBFalse,
|
||||
}
|
||||
@@ -92,7 +98,7 @@ func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
|
||||
info.Announcement = versionJSON["announcement"]
|
||||
info.AnnouncementURL = versionJSON["announcement_url"]
|
||||
|
||||
packageURL, key, found := u.downloadURL(versionJSON)
|
||||
packageURL, key, found := u.downloadURL(ctx, versionJSON)
|
||||
if !found {
|
||||
return info, fmt.Errorf("version.json: no package URL: key %q not found in object", key)
|
||||
}
|
||||
@@ -108,7 +114,10 @@ func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
|
||||
// downloadURL returns the download URL for current build as well as its key in
|
||||
// versionObj. If the key is not found, it additionally prints an informative
|
||||
// log message.
|
||||
func (u *Updater) downloadURL(versionObj map[string]string) (dlURL, key string, ok bool) {
|
||||
func (u *Updater) downloadURL(
|
||||
ctx context.Context,
|
||||
versionObj map[string]string,
|
||||
) (dlURL, key string, ok bool) {
|
||||
if u.goarch == "arm" && u.goarm != "" {
|
||||
key = fmt.Sprintf("download_%s_%sv%s", u.goos, u.goarch, u.goarm)
|
||||
} else if isMIPS(u.goarch) && u.gomips != "" {
|
||||
@@ -124,7 +133,7 @@ func (u *Updater) downloadURL(versionObj map[string]string) (dlURL, key string,
|
||||
|
||||
keys := slices.Sorted(maps.Keys(versionObj))
|
||||
|
||||
log.Error("updater: key %q not found; got keys %q", key, keys)
|
||||
u.logger.ErrorContext(ctx, "key not found", "missing", key, "got", keys)
|
||||
|
||||
return "", key, false
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -58,6 +59,7 @@ func TestUpdater_VersionInfo(t *testing.T) {
|
||||
|
||||
u := updater.NewUpdater(&updater.Config{
|
||||
Client: srv.Client(),
|
||||
Logger: testLogger,
|
||||
Version: "v0.103.0-beta.1",
|
||||
Channel: version.ChannelBeta,
|
||||
GOARCH: "arm",
|
||||
@@ -65,7 +67,8 @@ func TestUpdater_VersionInfo(t *testing.T) {
|
||||
VersionCheckURL: fakeURL,
|
||||
})
|
||||
|
||||
info, err := u.VersionInfo(false)
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
info, err := u.VersionInfo(ctx, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, counter, 1)
|
||||
@@ -75,14 +78,14 @@ func TestUpdater_VersionInfo(t *testing.T) {
|
||||
assert.Equal(t, aghalg.NBTrue, info.CanAutoUpdate)
|
||||
|
||||
t.Run("cache_check", func(t *testing.T) {
|
||||
_, err = u.VersionInfo(false)
|
||||
_, err = u.VersionInfo(testutil.ContextWithTimeout(t, testTimeout), false)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, counter, 1)
|
||||
})
|
||||
|
||||
t.Run("force_check", func(t *testing.T) {
|
||||
_, err = u.VersionInfo(true)
|
||||
_, err = u.VersionInfo(testutil.ContextWithTimeout(t, testTimeout), true)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, counter, 2)
|
||||
@@ -91,7 +94,7 @@ func TestUpdater_VersionInfo(t *testing.T) {
|
||||
t.Run("api_fail", func(t *testing.T) {
|
||||
srv.Close()
|
||||
|
||||
_, err = u.VersionInfo(true)
|
||||
_, err = u.VersionInfo(testutil.ContextWithTimeout(t, testTimeout), true)
|
||||
var urlErr *url.Error
|
||||
assert.ErrorAs(t, err, &urlErr)
|
||||
})
|
||||
@@ -130,6 +133,7 @@ func TestUpdater_VersionInfo_others(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
u := updater.NewUpdater(&updater.Config{
|
||||
Client: fakeClient,
|
||||
Logger: testLogger,
|
||||
Version: "v0.103.0-beta.1",
|
||||
Channel: version.ChannelBeta,
|
||||
GOOS: "linux",
|
||||
@@ -139,7 +143,8 @@ func TestUpdater_VersionInfo_others(t *testing.T) {
|
||||
VersionCheckURL: fakeURL,
|
||||
})
|
||||
|
||||
info, err := u.VersionInfo(false)
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
info, err := u.VersionInfo(ctx, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "v0.103.0-beta.2", info.NewVersion)
|
||||
|
||||
@@ -5,9 +5,11 @@ import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -22,13 +24,14 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/ioutil"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/netutil/urlutil"
|
||||
)
|
||||
|
||||
// Updater is the AdGuard Home updater.
|
||||
type Updater struct {
|
||||
client *http.Client
|
||||
logger *slog.Logger
|
||||
|
||||
version string
|
||||
channel string
|
||||
@@ -75,27 +78,48 @@ func DefaultVersionURL() *url.URL {
|
||||
|
||||
// Config is the AdGuard Home updater configuration.
|
||||
type Config struct {
|
||||
// Client is used to perform HTTP requests. It must not be nil.
|
||||
Client *http.Client
|
||||
|
||||
// Logger is used for logging the update process. It must not be nil.
|
||||
Logger *slog.Logger
|
||||
|
||||
// VersionCheckURL is URL to the latest version announcement. It must not
|
||||
// be nil, see [DefaultVersionURL].
|
||||
VersionCheckURL *url.URL
|
||||
|
||||
// Version is the current AdGuard Home version. It must not be empty.
|
||||
Version string
|
||||
Channel string
|
||||
GOARCH string
|
||||
GOOS string
|
||||
GOARM string
|
||||
GOMIPS string
|
||||
|
||||
// ConfName is the name of the current configuration file. Typically,
|
||||
// "AdGuardHome.yaml".
|
||||
// Channel is the current AdGuard Home update channel. It must be a valid
|
||||
// channel, see [version.ChannelBeta] and the related constants.
|
||||
Channel string
|
||||
|
||||
// GOARCH is the current CPU architecture. It must not be empty and must be
|
||||
// one of the supported architectures.
|
||||
GOARCH string
|
||||
|
||||
// GOOS is the current operating system. It must not be empty and must be
|
||||
// one of the supported OSs.
|
||||
GOOS string
|
||||
|
||||
// GOARM is the current ARM variant, if any. It must either be empty or be
|
||||
// a valid and supported GOARM value.
|
||||
GOARM string
|
||||
|
||||
// GOMIPS is the current MIPS variant, if any. It must either be empty or
|
||||
// be a valid and supported GOMIPS value.
|
||||
GOMIPS string
|
||||
|
||||
// ConfName is the name of the current configuration file. It must not be
|
||||
// empty.
|
||||
ConfName string
|
||||
|
||||
// WorkDir is the working directory that is used for temporary files.
|
||||
// WorkDir is the working directory that is used for temporary files. It
|
||||
// must not be empty.
|
||||
WorkDir string
|
||||
|
||||
// ExecPath is path to the executable file.
|
||||
// ExecPath is path to the executable file. It must not be empty.
|
||||
ExecPath string
|
||||
}
|
||||
|
||||
@@ -103,6 +127,7 @@ type Config struct {
|
||||
func NewUpdater(conf *Config) *Updater {
|
||||
return &Updater{
|
||||
client: conf.Client,
|
||||
logger: conf.Logger,
|
||||
|
||||
version: conf.Version,
|
||||
channel: conf.Channel,
|
||||
@@ -122,49 +147,49 @@ func NewUpdater(conf *Config) *Updater {
|
||||
|
||||
// Update performs the auto-update. It returns an error if the update failed.
|
||||
// If firstRun is true, it assumes the configuration file doesn't exist.
|
||||
func (u *Updater) Update(firstRun bool) (err error) {
|
||||
func (u *Updater) Update(ctx context.Context, firstRun bool) (err error) {
|
||||
u.mu.Lock()
|
||||
defer u.mu.Unlock()
|
||||
|
||||
log.Info("updater: updating")
|
||||
u.logger.InfoContext(ctx, "staring update", "first_run", firstRun)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Info("updater: failed")
|
||||
u.logger.ErrorContext(ctx, "update failed", slogutil.KeyError, err)
|
||||
} else {
|
||||
log.Info("updater: finished successfully")
|
||||
u.logger.InfoContext(ctx, "update finished")
|
||||
}
|
||||
}()
|
||||
|
||||
err = u.prepare()
|
||||
err = u.prepare(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing: %w", err)
|
||||
}
|
||||
|
||||
defer u.clean()
|
||||
defer u.clean(ctx)
|
||||
|
||||
err = u.downloadPackageFile()
|
||||
err = u.downloadPackageFile(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("downloading package file: %w", err)
|
||||
}
|
||||
|
||||
err = u.unpack()
|
||||
err = u.unpack(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unpacking: %w", err)
|
||||
}
|
||||
|
||||
if !firstRun {
|
||||
err = u.check()
|
||||
err = u.check(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = u.backup(firstRun)
|
||||
err = u.backup(ctx, firstRun)
|
||||
if err != nil {
|
||||
return fmt.Errorf("making backup: %w", err)
|
||||
}
|
||||
|
||||
err = u.replace()
|
||||
err = u.replace(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("replacing: %w", err)
|
||||
}
|
||||
@@ -181,7 +206,7 @@ func (u *Updater) NewVersion() (nv string) {
|
||||
}
|
||||
|
||||
// prepare fills all necessary fields in Updater object.
|
||||
func (u *Updater) prepare() (err error) {
|
||||
func (u *Updater) prepare(ctx context.Context) (err error) {
|
||||
u.updateDir = filepath.Join(u.workDir, fmt.Sprintf("agh-update-%s", u.newVersion))
|
||||
|
||||
_, pkgNameOnly := filepath.Split(u.packageURL)
|
||||
@@ -200,11 +225,12 @@ func (u *Updater) prepare() (err error) {
|
||||
u.backupExeName = filepath.Join(u.backupDir, filepath.Base(u.execPath))
|
||||
u.updateExeName = filepath.Join(u.updateDir, updateExeName)
|
||||
|
||||
log.Debug(
|
||||
"updater: updating from %s to %s using url: %s",
|
||||
version.Version(),
|
||||
u.newVersion,
|
||||
u.packageURL,
|
||||
u.logger.InfoContext(
|
||||
ctx,
|
||||
"updating",
|
||||
"from", version.Version(),
|
||||
"to", u.newVersion,
|
||||
"package_url", u.packageURL,
|
||||
)
|
||||
|
||||
u.currentExeName = u.execPath
|
||||
@@ -217,23 +243,20 @@ func (u *Updater) prepare() (err error) {
|
||||
}
|
||||
|
||||
// unpack extracts the files from the downloaded archive.
|
||||
func (u *Updater) unpack() error {
|
||||
var err error
|
||||
func (u *Updater) unpack(ctx context.Context) (err error) {
|
||||
_, pkgNameOnly := filepath.Split(u.packageURL)
|
||||
|
||||
log.Debug("updater: unpacking package")
|
||||
u.logger.InfoContext(ctx, "unpacking package", "package_name", pkgNameOnly)
|
||||
if strings.HasSuffix(pkgNameOnly, ".zip") {
|
||||
u.unpackedFiles, err = zipFileUnpack(u.packageName, u.updateDir)
|
||||
u.unpackedFiles, err = u.unpackZip(ctx, u.packageName, u.updateDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf(".zip unpack failed: %w", err)
|
||||
}
|
||||
|
||||
} else if strings.HasSuffix(pkgNameOnly, ".tar.gz") {
|
||||
u.unpackedFiles, err = tarGzFileUnpack(u.packageName, u.updateDir)
|
||||
u.unpackedFiles, err = u.unpackTarGz(ctx, u.packageName, u.updateDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf(".tar.gz unpack failed: %w", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
return fmt.Errorf("unknown package extension")
|
||||
}
|
||||
@@ -243,8 +266,8 @@ func (u *Updater) unpack() error {
|
||||
|
||||
// check returns an error if the configuration file couldn't be used with the
|
||||
// version of AdGuard Home just downloaded.
|
||||
func (u *Updater) check() (err error) {
|
||||
log.Debug("updater: checking configuration")
|
||||
func (u *Updater) check(ctx context.Context) (err error) {
|
||||
u.logger.InfoContext(ctx, "checking configuration")
|
||||
|
||||
err = copyFile(u.confName, filepath.Join(u.updateDir, "AdGuardHome.yaml"), aghos.DefaultPermFile)
|
||||
if err != nil {
|
||||
@@ -268,8 +291,9 @@ func (u *Updater) check() (err error) {
|
||||
|
||||
// backup makes a backup of the current configuration and supporting files. It
|
||||
// ignores the configuration file if firstRun is true.
|
||||
func (u *Updater) backup(firstRun bool) (err error) {
|
||||
log.Debug("updater: backing up current configuration")
|
||||
func (u *Updater) backup(ctx context.Context, firstRun bool) (err error) {
|
||||
u.logger.InfoContext(ctx, "backing up current configuration")
|
||||
|
||||
_ = os.Mkdir(u.backupDir, aghos.DefaultPermDir)
|
||||
if !firstRun {
|
||||
err = copyFile(u.confName, filepath.Join(u.backupDir, "AdGuardHome.yaml"), aghos.DefaultPermFile)
|
||||
@@ -279,7 +303,7 @@ func (u *Updater) backup(firstRun bool) (err error) {
|
||||
}
|
||||
|
||||
wd := u.workDir
|
||||
err = copySupportingFiles(u.unpackedFiles, wd, u.backupDir)
|
||||
err = u.copySupportingFiles(ctx, u.unpackedFiles, wd, u.backupDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %w", wd, u.backupDir, err)
|
||||
}
|
||||
@@ -289,13 +313,18 @@ func (u *Updater) backup(firstRun bool) (err error) {
|
||||
|
||||
// replace moves the current executable with the updated one and also copies the
|
||||
// supporting files.
|
||||
func (u *Updater) replace() error {
|
||||
err := copySupportingFiles(u.unpackedFiles, u.updateDir, u.workDir)
|
||||
func (u *Updater) replace(ctx context.Context) (err error) {
|
||||
err = u.copySupportingFiles(ctx, u.unpackedFiles, u.updateDir, u.workDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %w", u.updateDir, u.workDir, err)
|
||||
}
|
||||
|
||||
log.Debug("updater: renaming: %s to %s", u.currentExeName, u.backupExeName)
|
||||
u.logger.InfoContext(
|
||||
ctx,
|
||||
"backing up current executable",
|
||||
"from", u.currentExeName,
|
||||
"to", u.backupExeName,
|
||||
)
|
||||
err = os.Rename(u.currentExeName, u.backupExeName)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -311,14 +340,22 @@ func (u *Updater) replace() error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("updater: renamed: %s to %s", u.updateExeName, u.currentExeName)
|
||||
u.logger.InfoContext(
|
||||
ctx,
|
||||
"replacing current executable",
|
||||
"from", u.updateExeName,
|
||||
"to", u.currentExeName,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clean removes the temporary directory itself and all it's contents.
|
||||
func (u *Updater) clean() {
|
||||
_ = os.RemoveAll(u.updateDir)
|
||||
func (u *Updater) clean(ctx context.Context) {
|
||||
err := os.RemoveAll(u.updateDir)
|
||||
if err != nil {
|
||||
u.logger.WarnContext(ctx, "removing update dir", slogutil.KeyError, err)
|
||||
}
|
||||
}
|
||||
|
||||
// MaxPackageFileSize is a maximum package file length in bytes. The largest
|
||||
@@ -327,34 +364,52 @@ func (u *Updater) clean() {
|
||||
const MaxPackageFileSize = 32 * 1024 * 1024
|
||||
|
||||
// Download package file and save it to disk
|
||||
func (u *Updater) downloadPackageFile() (err error) {
|
||||
var resp *http.Response
|
||||
resp, err = u.client.Get(u.packageURL)
|
||||
func (u *Updater) downloadPackageFile(ctx context.Context) (err error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.packageURL, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("http request failed: %w", err)
|
||||
return fmt.Errorf("constructing package request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := u.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("requesting package: %w", err)
|
||||
}
|
||||
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
|
||||
|
||||
r := ioutil.LimitReader(resp.Body, MaxPackageFileSize)
|
||||
|
||||
log.Debug("updater: reading http body")
|
||||
u.logger.InfoContext(ctx, "reading http body")
|
||||
|
||||
// This use of ReadAll is now safe, because we limited body's Reader.
|
||||
body, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("io.ReadAll() failed: %w", err)
|
||||
}
|
||||
|
||||
_ = os.Mkdir(u.updateDir, aghos.DefaultPermDir)
|
||||
err = os.Mkdir(u.updateDir, aghos.DefaultPermDir)
|
||||
if err != nil {
|
||||
// TODO(a.garipov): Consider returning this error.
|
||||
u.logger.WarnContext(ctx, "creating update dir", slogutil.KeyError, err)
|
||||
}
|
||||
|
||||
u.logger.InfoContext(ctx, "saving package", "to", u.packageName)
|
||||
|
||||
log.Debug("updater: saving package to file")
|
||||
err = os.WriteFile(u.packageName, body, aghos.DefaultPermFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing package file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name string, err error) {
|
||||
// unpackTarGzFile unpacks one file from a .tar.gz archive into outDir. All
|
||||
// arguments must not be empty.
|
||||
func (u *Updater) unpackTarGzFile(
|
||||
ctx context.Context,
|
||||
outDir string,
|
||||
tr *tar.Reader,
|
||||
hdr *tar.Header,
|
||||
) (name string, err error) {
|
||||
name = filepath.Base(hdr.Name)
|
||||
if name == "" {
|
||||
return "", nil
|
||||
@@ -377,13 +432,18 @@ func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name st
|
||||
return "", fmt.Errorf("creating directory %q: %w", outName, err)
|
||||
}
|
||||
|
||||
log.Debug("updater: created directory %q", outName)
|
||||
u.logger.InfoContext(ctx, "created directory", "name", outName)
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if hdr.Typeflag != tar.TypeReg {
|
||||
log.Info("updater: %s: unknown file type %d, skipping", name, hdr.Typeflag)
|
||||
u.logger.WarnContext(
|
||||
ctx,
|
||||
"unknown file type; skipping",
|
||||
"file_name", name,
|
||||
"type", hdr.Typeflag,
|
||||
)
|
||||
|
||||
return "", nil
|
||||
}
|
||||
@@ -400,16 +460,19 @@ func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name st
|
||||
return "", fmt.Errorf("io.Copy(): %w", err)
|
||||
}
|
||||
|
||||
log.Debug("updater: created file %q", outName)
|
||||
u.logger.InfoContext(ctx, "created file", "name", outName)
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// Unpack all files from .tar.gz file to the specified directory
|
||||
// Existing files are overwritten
|
||||
// All files are created inside outDir, subdirectories are not created
|
||||
// Return the list of files (not directories) written
|
||||
func tarGzFileUnpack(tarfile, outDir string) (files []string, err error) {
|
||||
// unpackTarGz unpack all files from a .tar.gz archive to outDir. Existing
|
||||
// files are overwritten. All files are created inside outDir. files are the
|
||||
// list of created files.
|
||||
func (u *Updater) unpackTarGz(
|
||||
ctx context.Context,
|
||||
tarfile string,
|
||||
outDir string,
|
||||
) (files []string, err error) {
|
||||
f, err := os.Open(tarfile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("os.Open(): %w", err)
|
||||
@@ -437,7 +500,7 @@ func tarGzFileUnpack(tarfile, outDir string) (files []string, err error) {
|
||||
}
|
||||
|
||||
var name string
|
||||
name, err = tarGzFileUnpackOne(outDir, tarReader, hdr)
|
||||
name, err = u.unpackTarGzFile(ctx, outDir, tarReader, hdr)
|
||||
|
||||
if name != "" {
|
||||
files = append(files, name)
|
||||
@@ -447,7 +510,13 @@ func tarGzFileUnpack(tarfile, outDir string) (files []string, err error) {
|
||||
return files, err
|
||||
}
|
||||
|
||||
func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
|
||||
// unpackZipFile unpacks one file from a .zip archive into outDir. All
|
||||
// arguments must not be empty.
|
||||
func (u *Updater) unpackZipFile(
|
||||
ctx context.Context,
|
||||
outDir string,
|
||||
zf *zip.File,
|
||||
) (name string, err error) {
|
||||
var rc io.ReadCloser
|
||||
rc, err = zf.Open()
|
||||
if err != nil {
|
||||
@@ -466,7 +535,8 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
|
||||
if name == "AdGuardHome" {
|
||||
// Top-level AdGuardHome/. Skip it.
|
||||
//
|
||||
// TODO(a.garipov): See the similar todo in tarGzFileUnpack.
|
||||
// TODO(a.garipov): See the similar TODO in
|
||||
// [Updater.unpackTarGzFile].
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -475,7 +545,7 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
|
||||
return "", fmt.Errorf("creating directory %q: %w", outputName, err)
|
||||
}
|
||||
|
||||
log.Debug("updater: created directory %q", outputName)
|
||||
u.logger.InfoContext(ctx, "created directory", "name", outputName)
|
||||
|
||||
return "", nil
|
||||
}
|
||||
@@ -492,16 +562,19 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
|
||||
return "", fmt.Errorf("io.Copy(): %w", err)
|
||||
}
|
||||
|
||||
log.Debug("updater: created file %q", outputName)
|
||||
u.logger.InfoContext(ctx, "created file", "name", outputName)
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// Unpack all files from .zip file to the specified directory
|
||||
// Existing files are overwritten
|
||||
// All files are created inside 'outDir', subdirectories are not created
|
||||
// Return the list of files (not directories) written
|
||||
func zipFileUnpack(zipfile, outDir string) (files []string, err error) {
|
||||
// unpackZip unpack all files from a .zip archive to outDir. Existing files are
|
||||
// overwritten. All files are created inside outDir. files are the list of
|
||||
// created files.
|
||||
func (u *Updater) unpackZip(
|
||||
ctx context.Context,
|
||||
zipfile string,
|
||||
outDir string,
|
||||
) (files []string, err error) {
|
||||
zrc, err := zip.OpenReader(zipfile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("zip.OpenReader(): %w", err)
|
||||
@@ -510,7 +583,7 @@ func zipFileUnpack(zipfile, outDir string) (files []string, err error) {
|
||||
|
||||
for _, zf := range zrc.File {
|
||||
var name string
|
||||
name, err = zipFileUnpackOne(outDir, zf)
|
||||
name, err = u.unpackZipFile(ctx, outDir, zf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@@ -543,7 +616,12 @@ func copyFile(src, dst string, perm fs.FileMode) (err error) {
|
||||
// copySupportingFiles copies each file specified in files from srcdir to
|
||||
// dstdir. If a file specified as a path, only the name of the file is used.
|
||||
// It skips AdGuardHome, AdGuardHome.exe, and AdGuardHome.yaml.
|
||||
func copySupportingFiles(files []string, srcdir, dstdir string) error {
|
||||
func (u *Updater) copySupportingFiles(
|
||||
ctx context.Context,
|
||||
files []string,
|
||||
srcdir string,
|
||||
dstdir string,
|
||||
) (err error) {
|
||||
for _, f := range files {
|
||||
_, name := filepath.Split(f)
|
||||
if name == "AdGuardHome" || name == "AdGuardHome.exe" || name == "AdGuardHome.yaml" {
|
||||
@@ -553,12 +631,12 @@ func copySupportingFiles(files []string, srcdir, dstdir string) error {
|
||||
src := filepath.Join(srcdir, name)
|
||||
dst := filepath.Join(dstdir, name)
|
||||
|
||||
err := copyFile(src, dst, aghos.DefaultPermFile)
|
||||
err = copyFile(src, dst, aghos.DefaultPermFile)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("updater: copied: %q to %q", src, dst)
|
||||
u.logger.InfoContext(ctx, "copied", "from", src, "to", dst)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -55,6 +59,7 @@ func TestUpdater_internal(t *testing.T) {
|
||||
|
||||
u := NewUpdater(&Config{
|
||||
Client: fakeClient,
|
||||
Logger: slogutil.NewDiscardLogger(),
|
||||
GOOS: tc.os,
|
||||
Version: "v0.103.0",
|
||||
ExecPath: exePath,
|
||||
@@ -68,13 +73,13 @@ func TestUpdater_internal(t *testing.T) {
|
||||
u.newVersion = "v0.103.1"
|
||||
u.packageURL = fakeURL.String()
|
||||
|
||||
require.NoError(t, u.prepare())
|
||||
require.NoError(t, u.downloadPackageFile())
|
||||
require.NoError(t, u.unpack())
|
||||
require.NoError(t, u.backup(false))
|
||||
require.NoError(t, u.replace())
|
||||
require.NoError(t, u.prepare(newCtx(t)))
|
||||
require.NoError(t, u.downloadPackageFile(newCtx(t)))
|
||||
require.NoError(t, u.unpack(newCtx(t)))
|
||||
require.NoError(t, u.backup(newCtx(t), false))
|
||||
require.NoError(t, u.replace(newCtx(t)))
|
||||
|
||||
u.clean()
|
||||
u.clean(newCtx(t))
|
||||
|
||||
require.True(t, t.Run("backup", func(t *testing.T) {
|
||||
var d []byte
|
||||
@@ -113,3 +118,8 @@ func TestUpdater_internal(t *testing.T) {
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// newCtx is a helper that returns a new context with a timeout.
|
||||
func newCtx(tb testing.TB) (ctx context.Context) {
|
||||
return testutil.ContextWithTimeout(tb, 1*time.Second)
|
||||
}
|
||||
|
||||
@@ -10,17 +10,21 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/updater"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
// testTimeout is the common timeout for tests.
|
||||
const testTimeout = 1 * time.Second
|
||||
|
||||
// testLogger is the common logger for tests.
|
||||
var testLogger = slogutil.NewDiscardLogger()
|
||||
|
||||
func TestUpdater_Update(t *testing.T) {
|
||||
const jsonData = `{
|
||||
@@ -73,6 +77,7 @@ func TestUpdater_Update(t *testing.T) {
|
||||
|
||||
u := updater.NewUpdater(&updater.Config{
|
||||
Client: srv.Client(),
|
||||
Logger: testLogger,
|
||||
GOARCH: "amd64",
|
||||
GOOS: "linux",
|
||||
Version: "v0.103.0",
|
||||
@@ -82,10 +87,12 @@ func TestUpdater_Update(t *testing.T) {
|
||||
VersionCheckURL: versionCheckURL,
|
||||
})
|
||||
|
||||
_, err = u.VersionInfo(false)
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
_, err = u.VersionInfo(ctx, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = u.Update(true)
|
||||
ctx = testutil.ContextWithTimeout(t, testTimeout)
|
||||
err = u.Update(ctx, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// check backup files
|
||||
@@ -124,14 +131,15 @@ func TestUpdater_Update(t *testing.T) {
|
||||
t.Skip("skipping config check test on windows")
|
||||
}
|
||||
|
||||
err = u.Update(false)
|
||||
err = u.Update(testutil.ContextWithTimeout(t, testTimeout), false)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("api_fail", func(t *testing.T) {
|
||||
srv.Close()
|
||||
|
||||
err = u.Update(true)
|
||||
err = u.Update(testutil.ContextWithTimeout(t, testTimeout), true)
|
||||
|
||||
var urlErr *url.Error
|
||||
assert.ErrorAs(t, err, &urlErr)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user