diff --git a/docs/.vuepress/config.ts b/docs/.vuepress/config.ts index 45c9c4e00..2bbcac52a 100644 --- a/docs/.vuepress/config.ts +++ b/docs/.vuepress/config.ts @@ -64,6 +64,11 @@ export default defineUserConfig({ title: "Project X", description: "Official document of Xray", }, + "/ru/": { + lang: "ru-RU", + title: "Project X", + description: "Официальная документация Xray", + }, }, theme, head: [["link", { rel: "icon", href: `/logo.png` }]], diff --git a/docs/.vuepress/config/navbar/index.ts b/docs/.vuepress/config/navbar/index.ts index 7183393c3..90c5748b8 100755 --- a/docs/.vuepress/config/navbar/index.ts +++ b/docs/.vuepress/config/navbar/index.ts @@ -1,2 +1,3 @@ export * from './en.js' export * from './zh.js' +export * from './ru.js' \ No newline at end of file diff --git a/docs/.vuepress/config/navbar/ru.ts b/docs/.vuepress/config/navbar/ru.ts new file mode 100644 index 000000000..00b0b0cb9 --- /dev/null +++ b/docs/.vuepress/config/navbar/ru.ts @@ -0,0 +1,9 @@ +import { NavbarConfig } from '@vuepress/theme-default' + +export const navbarRu: NavbarConfig = [ + { text: 'Главная', link: '/ru' }, + { text: 'История сайта', link: '/ru/about/news.md' }, + { text: 'Справочник по конфигурации', link: '/ru/config/' }, + { text: 'Руководство разработчика', link: '/ru/development/' }, + { text: 'Быстрый старт', link: '/ru/document/' }, +] diff --git a/docs/.vuepress/config/sidebar/index.ts b/docs/.vuepress/config/sidebar/index.ts index 7183393c3..527d6a373 100755 --- a/docs/.vuepress/config/sidebar/index.ts +++ b/docs/.vuepress/config/sidebar/index.ts @@ -1,2 +1,3 @@ export * from './en.js' export * from './zh.js' +export * from './ru.js' diff --git a/docs/.vuepress/config/sidebar/ru.ts b/docs/.vuepress/config/sidebar/ru.ts new file mode 100644 index 000000000..9971371c5 --- /dev/null +++ b/docs/.vuepress/config/sidebar/ru.ts @@ -0,0 +1,149 @@ +import type { SidebarConfig } from '@vuepress/theme-default' + +export const sidebarRu: SidebarConfig = { + '/ru/config/': [ + { + text: 'Описание функций', + children: [ + '/ru/config/features/xtls.md', + '/ru/config/features/fallback.md', + '/ru/config/features/browser_dialer.md', + '/ru/config/features/env.md', + '/ru/config/features/multiple.md', + ], + }, + { + text: 'Базовая конфигурация', + children: [ + '/ru/config/README.md', + '/ru/config/log.md', + '/ru/config/api.md', + '/ru/config/dns.md', + '/ru/config/fakedns.md', + '/ru/config/inbound.md', + '/ru/config/outbound.md', + '/ru/config/policy.md', + '/ru/config/reverse.md', + '/ru/config/routing.md', + '/ru/config/stats.md', + '/ru/config/transport.md', + '/ru/config/metrics.md', + '/ru/config/observatory.md', + ], + }, + { + text: 'Входящий прокси', + children: [ + '/ru/config/inbounds/dokodemo.md', + '/ru/config/inbounds/http.md', + '/ru/config/inbounds/shadowsocks.md', + '/ru/config/inbounds/socks.md', + '/ru/config/inbounds/trojan.md', + '/ru/config/inbounds/vless.md', + '/ru/config/inbounds/vmess.md', + ], + }, + { + text: 'Исходящий прокси', + children: [ + '/ru/config/outbounds/blackhole.md', + '/ru/config/outbounds/dns.md', + '/ru/config/outbounds/freedom.md', + '/ru/config/outbounds/http.md', + '/ru/config/outbounds/loopback.md', + '/ru/config/outbounds/shadowsocks.md', + '/ru/config/outbounds/socks.md', + '/ru/config/outbounds/trojan.md', + '/ru/config/outbounds/vless.md', + '/ru/config/outbounds/vmess.md', + '/ru/config/outbounds/wireguard.md', + ], + }, + { + text: 'Транспортный уровень', + children: [ + '/ru/config/transports/domainsocket.md', + '/ru/config/transports/grpc.md', + '/ru/config/transports/h2.md', + '/ru/config/transports/mkcp.md', + '/ru/config/transports/quic.md', + '/ru/config/transports/tcp.md', + '/ru/config/transports/websocket.md', + '/ru/config/transports/httpupgrade.md', + '/ru/config/transports/splithttp.md' + ], + }, + ], + '/ru/document/': [ + { + text: 'Быстрый старт', + children: [ + '/ru/document/README.md', + '/ru/document/install.md', + '/ru/document/config.md', + '/ru/document/command.md', + '/ru/document/document.md', + ], + }, + { + text: 'Простыми словами', + children: [ + '/ru/document/level-0/README.md', + '/ru/document/level-0/ch01-preface.md', + '/ru/document/level-0/ch02-preparation.md', + '/ru/document/level-0/ch03-ssh.md', + '/ru/document/level-0/ch04-security.md', + '/ru/document/level-0/ch05-webpage.md', + '/ru/document/level-0/ch06-certificates.md', + '/ru/document/level-0/ch07-xray-server.md', + '/ru/document/level-0/ch08-xray-clients.md', + '/ru/document/level-0/ch09-appendix.md', + ], + }, + { + text: 'Базовые навыки', + children: [ + '/ru/document/level-1/README.md', + '/ru/document/level-1/fallbacks-lv1.md', + '/ru/document/level-1/routing-lv1-part1.md', + '/ru/document/level-1/routing-lv1-part2.md', + '/ru/document/level-1/work.md', + '/ru/document/level-1/fallbacks-with-sni.md', + ], + }, + { + text: 'Продвинутые навыки', + children: [ + '/ru/document/level-2/README.md', + '/ru/document/level-2/transparent_proxy/transparent_proxy.md', + '/ru/document/level-2/tproxy.md', + '/ru/document/level-2/tproxy_ipv4_and_ipv6.md', + '/ru/document/level-2/nginx_or_haproxy_tls_tunnel.md', + '/ru/document/level-2/iptables_gid.md', + '/ru/document/level-2/redirect.md', + '/ru/document/level-2/warp.md', + '/ru/document/level-2/traffic_stats.md', + ], + } + ], + '/ru/development/': [ + { + text: 'Руководство разработчика', + children: [ + '/ru/development/README.md', + '/ru/development/intro/compile.md', + '/ru/development/intro/design.md', + '/ru/development/intro/guide.md', + ], + }, + { + text: 'Описание протоколов', + children: [ + '/ru/development/protocols/vless.md', + '/ru/development/protocols/vmess.md', + '/ru/development/protocols/muxcool.md', + '/ru/development/protocols/mkcp.md', + ], + }, + ], +} \ No newline at end of file diff --git a/docs/.vuepress/theme.ts b/docs/.vuepress/theme.ts index 6a82d4e05..a7f9a789e 100755 --- a/docs/.vuepress/theme.ts +++ b/docs/.vuepress/theme.ts @@ -2,7 +2,7 @@ import { defaultTheme } from '@vuepress/theme-default' // import { path, getDirname } from '@vuepress/utils' import { path, getDirname } from 'vuepress/utils' import process from 'node:process' -import { navbarEn, navbarZh, sidebarEn, sidebarZh } from './config/index.js' +import { navbarEn, navbarZh, navbarRu, sidebarEn, sidebarZh, sidebarRu } from './config/index.js' let __dirname = getDirname(import.meta.url) const isProduction = process.env.NODE_ENV === 'production' @@ -40,8 +40,8 @@ export default defaultTheme({ sidebar: sidebarEn, navbar: navbarEn, selectLanguageName: 'English (WIP)', - selectLanguageText: '🌏 简体中文 / Change language', - selectLanguageAriaLabel: '简体中文 / Change language', + selectLanguageText: '🌎 English / Change language', + selectLanguageAriaLabel: 'English / Change language', editLinkText: 'Help us improve this page on GitHub!', lastUpdatedText: 'Last Updated', contributorsText: 'contributors', @@ -62,6 +62,24 @@ export default defaultTheme({ toggleColorMode: 'toggle color mode', toggleSidebar: 'toggle side bar', }, + '/ru/': { + navbar: navbarRu, + sidebar: sidebarRu, + repoLabel: 'Посмотреть исходный код', + editLinkText: 'Помогите нам улучшить эту страницу!', + tip: 'Подсказка', + warning: 'Внимание', + danger: 'Предупреждение', + lastUpdatedText: 'Последние изменения', + selectLanguageName: 'Русский (WIP)', + selectLanguageText: '🌍 Русский / Change language', + selectLanguageAriaLabel: 'Русский / Change language', + docsDir: 'docs', + backToHome: 'На главную', + openInNewWindow: 'Открыть в новой вкладке', + toggleColorMode: 'Переключить цветовую схему', + toggleSidebar: 'Переключить боковую панель', + }, // logo: '/logo.png', // sidebar: 'auto', diff --git a/docs/ru/README.md b/docs/ru/README.md new file mode 100644 index 000000000..5b4da382b --- /dev/null +++ b/docs/ru/README.md @@ -0,0 +1,99 @@ +--- +home: true +heroImage: /LogoX2.png +heroText: Project X +tagline: Не бойтесь облаков, застилающих вид – золотые глаза, словно факел, озаряют небо. +actions: + - text: Начать здесь → + link: /ru/document/ + type: primary + - text: Руководство по конфигурации → + link: /ru/config/ + type: secondary +features: + - title: Высокоскоростной протокол + details: Оригинальные протоколы VLESS и XTLS, свободные от избыточного шифрования, высвобождают вычислительную мощность процессора. + + - title: Свободная комбинация + details: | + Безупречный механизм fallback, эффективно предотвращающий активное зондирование, совместное использование портов несколькими сервисами + + - title: Сверхнизкое потребление ресурсов + details: | + Можно использовать на OpenWRT RaspberryPi и других подобных устройствах. + + - title: Мощная маршрутизация + details: | + Высоконастраиваемая система маршрутизации, отвечающая разнообразным требованиям использования и позволяющая максимально эффективно использовать производительность сети. + + + - title: Полная совместимость + details: | + Полная совместимость с конфигурационными файлами и вызовами API v2ray-core. + + - title: Сообщество + details: | + Активные обсуждения и вклад сообщества, лицензия с открытым исходным кодом MPL 2.0. + +footer: Лицензия CC-BY-SA 4.0 | Авторские права 2020-настоящее время Сообщество Project X +--- + +## XTLS? Xray? V2Ray? + +**XTLS — это блестящая идея для TLS, которую мы изучаем, в то время как Xray — это лучшая практика, которую мы поддерживаем.** + +- Xray-core - это расширенная версия v2ray-core с улучшенной общей производительностью, включающая XTLS и другие улучшения. Xray-core ~~полностью~~ совместим с функциональностью и конфигурацией v2ray-core. + - Только один исполняемый файл, включающий функциональность ctl, команда run используется по умолчанию. + - Конфигурация ~~полностью~~ совместима, переменные среды и вызовы API должны начинаться с XRAY\_ + - Открытый raw протокол ReadV на всех платформах. + - Обеспечивает полную поддержку VLESS и Trojan XTLS, обе с ReadV. + - Предоставляет несколько режимов управления потоком XTLS, непревзойденная производительность! + +> Конфигурация без изменений, результат — значительно лучше. + +### Кто мы? + +> **Неважно, кто мы. Важно то, что мы будем продолжать двигаться вперед и никогда не оглядываться назад.** + +### Помогите Xray стать сильнее + +Мы будем рады вашей помощи в развитии Xray! + +- 🖥️ Помогите в разработке и тестировании Xray, отправляйте качественные запросы на включение (Pull Request). +- 📩 Создавайте конструктивные или важные задачи и обсуждения в [GitHub Issues](https://github.com/XTLS/Xray-core/issues) или [Discussion area](https://github.com/XTLS/Xray-core/discussions). +- 📝 Поделитесь своим опытом использования и отправьте его на [сайт документации](https://github.com/XTLS/Xray-docs-next) Xray. +- 💬 Помогите участникам группы / общайтесь в группе Telegram. +- **... На самом деле, любая поддержка Xray сделает его сильнее.** + +### Telegram + +- [Группа обсуждения Project X](https://t.me/projectXray) + + - В группе обсуждения можно свободно общаться, не допускаются оскорбления и злоупотребления. + - Не стесняйтесь задавать вопросы, а если знаете ответ - помогите другим. + - Запрещены политика и контент для взрослых (NSFW). + +- [Канал Project X](https://t.me/projectXtls) + - Публикация последних новостей о Project X. + +### Благодарности + +- Спасибо всем за вашу поддержку! +- Спасибо создателям всевозможных скриптов, образов Docker, клиентам... Спасибо всем, кто помогает улучшать экосистему! +- Спасибо всем, кто вносит свой вклад в веб-сайт и документацию Xray. +- Спасибо всем, кто высказывает ценные предложения и замечания. +- Спасибо каждому участнику группы Telegram, который помогает другим. + +### Подробнее о Project X + +- Если вы хотите узнать больше об истории и развитии Project X, нажмите [здесь](./about/news.md) + +### Лицензия + +[Mozilla Public License Version 2.0](https://github.com/XTLS/Xray-core/blob/main/LICENSE) + +### Динамика звезд на GitHub + +[![Stargazers over time](https://starchart.cc/XTLS/Xray-core.svg)](https://starchart.cc/XTLS/Xray-core) + + diff --git a/docs/ru/about/news.md b/docs/ru/about/news.md new file mode 100644 index 000000000..64e3a43de --- /dev/null +++ b/docs/ru/about/news.md @@ -0,0 +1,240 @@ +--- +sidebar: auto +--- + +# 大史记 + +## 2021.4.6 + +- VuePress Next. +- With Dark Mode. + +## 2021.4.4 + +- 本文档迎来的新的首页。 +- 本文档迎来了暗黑模式。 +- ~~当然,暗黑模式还有各种各样的问题。具体的内容还需要慢慢调整。~~ +- 另:Telegram 群聊突破了 5000 人!还加入了 Anti-Spam 机器人! +- 🎉🎉🎉 + +## 2021.4.1 [v1.4.2](https://github.com/XTLS/Xray-core/releases/tag/v1.4.2) + +- 不是愚人节玩笑,今天更新。 +- 加入 Browser Dialer,用与改变 TLS 指纹与行为。 +- 加入 uTLS,用与改变 TLS Client Hello 的指纹。 +- 顺便修复了一大堆奇妙的问题,具体的内容见更新日志。 + +## 2021.3.25 + + +没错还在变。 -_- + +## 2021.3.15 + +文档网站正在悄悄的进行着某些神秘的变化。。。,🙊🙊🙊 + +## 2021.3.14 [v1.4.0](https://github.com/XTLS/Xray-core/releases/tag/v1.4.0) + +- Happy Pi-Day! +- 这次是个大更新: + - 为链式代理引入了传输层支持。 + - 为 Dialer 引入了 Domain Strategy,解决奇妙的 DNS 问题。 + - 添加了 gRPC 传输方式,与更快一点的 Multi Mode。 + - 添加了 WebSocket Early-Data 功能,减少了 WebSocket 的延迟。 + - 添加了 FakeDNS。 + - 还修复了系列的问题,添加了各类功能,详情请见更新日志。 +- 还是 VuePress 比较爽啊( + +## 2021.3.3 [1.3.1](https://github.com/XTLS/Xray-core/releases/tag/v1.3.1) + +- 这个版本使用了 Golang 1.16,正式原生支持 Apple Silicon。 +- 同时修复了一个会导致 Panic 的 bug。~~Holmium\_认为这是在骗、在偷袭。~~ +- 修复了几个遗留问题。 + +## 2021.2.14 [1.3.0](https://github.com/XTLS/Xray-core/releases/tag/v1.3.0) + +- Happy 🐮 Year 🎉! +- v1.3.0 通过非常巧妙的机制实现了 V 系协议全部 FullCone,同时保证了一定的兼容性。 +- OHHHHHHHHHHHH! + +## 2021.01.31 [1.2.4](https://github.com/XTLS/Xray-core/releases/tag/v1.2.4) + +- 解决两个“连接至标准 Socks 服务端时可能出错”的历史遗留问题。 +- 似乎这个版本没有什么改变,但这只是暴风雨前的宁静。 +- (没错我就是先知) + > 你个傻子,你拿的是 UNO 牌。 + +## 2021.01.25 + +- 全互联网最好最详细的秘籍入门篇同学们练熟了吗? 🍉 老师开始连载[秘籍第一层](../document/level-1/)咯... +- [英文版文档网站](../en)逐渐增加内容 ing, 感谢各位大佬的辛苦付出~! + +## 2021.01.22 [1.2.3](https://github.com/XTLS/Xray-core/releases/tag/v1.2.3) + +- 对 SS 协议的支持**又**变强了, 支持单端口多用户! +- 对 trojan 协议的支持也**又**变强了, trojan 的回落也解锁 SNI 分流的新姿势啦~! +- _(VLESS: 嘤嘤嘤)_ +- UDP 奇奇怪怪的 BUG 被干掉了, 一个字, "稳定". +- 嗅探可以排除你不想嗅探的域名, 可以开启一些新玩法. +- 向发现问题->开 issue->自行测试->自行分析->自行找到问题->自行解决->然后给上下游提交 PR 的大佬 a [@Bohan Yang](https://github.com/bohanyang) 致敬! +- 其他美味小樱桃, 惯例更新品尝就对啦. + +## 2021.01.19 + +- 一些数字 + - 版本发布了 10   个 tag + - 解决掉了 100  个 issue + - 复刻了 300  个 fork + - 点了 2000 个 star + - 群 3000 个 人 + +## 2021.01.17 + +- 辛苦的翻译工作开始了, 感谢a [@玖柒 Max](https://github.com/jiuqi9997)和其他所有的翻译大佬们. +- [English version](https://xtls.github.io/en/) + +## 2021.01.15 [1.2.2](https://github.com/XTLS/Xray-core/releases/tag/v1.2.2) + +- 回落分流又解锁了奇怪的新姿势! 回落中可以根据 SNI 分流啦~! +- 之前预告的 UUID 修改正式上线.([往下看往下看](#2021.01.12)) +- 日志现在看起来比上一次顺眼又更顺眼了一丢丢. +- 远程 DOH 和其他的 DNS 模式一样学会了走路由分流. +- 当然还有其他各种小糖果.(更新品尝就对了) +- 啊, 还有, 世界上第一個 M1 上跑起 Xray 的男人是 Anthony TSE + +## 2021.01.12 + +- 将要到来的 UUID 修改, 支持自定义字符串和 UUID 之间的映射. 这意味着你将可以这样在配置文件中写 id 来对应用户. + - 客户端写 "id": "我爱 🍉 老师 1314", + - 服务端写 "id": "5783a3e7-e373-51cd-8642-c83782b807c5" (此 UUID 是 `我爱🍉老师1314` 的 UUID 映射) +- 🍉 老师的[小小白白话文](../document/level-0/)大结局, 撒花. + +## 2021.01.10 [1.2.1](https://github.com/XTLS/Xray-core/releases/tag/v1.2.1) + +- [小小白白话文](../document/level-0/)连载上线啦,🍉 老师呕心沥血之作, 手把手教你从什么都不会到熟练配置 Xray! +- (可能是整个互联网上, 最详细最有耐心的教你从 0 开始配置的教程) +- [透明代理](../document/level-2/)也增加了更多文章. +- 还有很多细节修改, 文档将会越来越规范! +- 感谢 [@ricuhkaen](https://github.com/ricuhkaen) , [@BioniCosmos](https://github.com/BioniCosmos), [@kirin](https://github.com/kirin10000) + +* 大量的 UDP 相关修复, 甚至可以在育碧的土豆服务器上玩彩虹六号! +* Google Voice 应该也可以正常使用 v2rayNG 拨打了. +* 日志现在看起来更顺眼. + +## 2021.01.07 + +- 礼貌和尊重本应是社区不需要明说的准则之一。 + +## 2021.01.05 + +- 文档网站正在悄悄的进行着某些神秘的变化。。。,🙊🙊🙊 + +## 2021.01.03 + +- 文档仓库第一个 PR。🎉 + [透明代理(TProxy)配置教程 ](../document/level-2/tproxy.md) ,感谢a [@BioniCosmos](https://github.com/BioniCosmos) +- tg 群突破 2500。 + +## 2021.01.01 + +【祝大家新年快乐,嗨皮牛耶!】🎆🎇🎆 [1.2.0](https://github.com/XTLS/Xray-core/releases/tag/v1.2.0) + +🎁 在元旦的最后几分钟,v1.2.0 它来了,带着周五必更的惯例,带着各位贡献大佬的心血以及 @rprxx 的黑眼圈,不负众望的来了! + +- 圣诞礼物[v1.1.5](#20201225)后的元旦礼物 🎁,游戏玩家大福利,全面 FullCone。 +- (UDP 还会继续增强!) +- 如果你已经拆过圣诞礼物,这次还有比圣诞礼物更精美的包装和小糖果哦。(同样不用问,更新品尝就对了) +- (不,下面不是广告,是里程碑。) +- Xray 是有史以来第一个不受限制的多协议平台:只需 Xray 即可解决问题,无需借力其它实现。 + - 一人扛起了所有!支持各大主流协议! + - 一骑绝尘的性能! + - 日趋完善的功能! + - 可怕的生命力与社区亲和力! +- Xray 将继续保持前行! 因此 [Xray 需要更多的英雄!!](https://github.com/XTLS/Xray-core/discussions/56)! +- PS:请品,请细品[release notes](https://github.com/XTLS/Xray-core/releases/tag/v1.2.0)每一句。似乎有一个小秘密小彩蛋 ~~(啊,有人敲门...我一会和你们说)~~ + +## 2020.12.29 + +透明代理的游戏玩家利好! Xray-core tproxy 入站, socks 出站 UDP FullCone 测试版, [TG 群](https://t.me/projectXray)火热测试中 + +## 2020.12.25 [1.1.5](https://github.com/XTLS/Xray-core/releases/tag/v1.1.5) + +圣诞节快乐! + +- 游戏玩家的圣诞礼物!你可以用 xray 爽快的打游戏啦!因为有了 SS/trojan UDP fullcone +- 你可以用你喜欢的格式写配置文件了,比如 yaml,比如 toml... +- (VLESS 的 UDP fullcone 和更多增强很快就到!) +- 无须再担心证书验证被墙,OCSP stapling 已经上线! +- kirin 带来了一大波 脚本更新.[脚本在此](https://github.com/XTLS/Xray-install) +- 还有更多美味小樱桃!(不用问,更新品尝就对了) + +## 2020.12.24 + +因为某些不可描述的原因,Xray 的文档网站已在发布日前偷跑上线。 +网址为:[没错你正在看的就是](https://xtls.github.io) + +大家可以查阅各种内容也欢迎纠错/提出建议(可发往文档 github 仓库的 issue 区) + +文档网站需要不断完善和增加内容,以及完善设计。 +因此更欢迎大家一起为文档建设添砖加瓦。 +[文档的仓库](https://github.com/XTLS/XTLS.github.io) + +仓库的 readme 中有简略教程说明如何帮助 xray 改进文档网站. +欢迎大家查看,纠错,修改,增加心得。 + +## 2020.12.23 + +Xray-core Shadowsocks UDP FullCone 测试版, [TG 群](https://t.me/projectXray)火热测试中 + +## 2020.12.21 + +- Project X 群人数 2000+ +- 群消息(含游戏群) 日均破万 + +## 2020.12.18 [1.1.4](https://github.com/XTLS/Xray-core/releases/tag/v1.1.4) + +- 更低的启动内占用和内存使用优化 +- 随意定制的 TLS 提高你的 SSL 评级 +- 支持 XTLS 入站的 Splice 以及支持 trojan 的 XTLS +- 还有在您路由器上使用的 Splice 最佳使用模式建议 + +## 2020.12.17 + +鉴于日益增长群人数和游戏需求, 开启了[TG 游戏群](https://t.me/joinchat/UO4NixbB_XDQJOUjS6mHEQ) + +## 2020.12.15 + +[安装脚本 dev 分支](https://github.com/XTLS/Xray-install/tree/dev)开启, 持续更新功能中. + +## 2020.12.11 [1.1.3](https://github.com/XTLS/Xray-core/releases/tag/v1.1.3) + +- 完整版本的 REDIRECT 透明代理模式. +- 软路由 splice 流控模式的优化建议. + +## 2020.12.06 [1.1.2](https://github.com/XTLS/Xray-core/releases/tag/v1.1.2) + +- 流控增加 splice 模式, Linux 限定, 性能一骑绝尘. +- 增强了 API 兼容 + +## 2020.12.04 + +增加 splice 模式 + +## 2020.11.27 + +- Project X 的 GitHub 主仓库 Xray-core 已获 500+ stars +- 登上了 GitHub Trending +- Project X 群人数破千,频道订阅数 500+ + +## 2020.11.25 [1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) + +Xray 的第一个版本. + +- 基于 v2ray-core 修改而来,改动较大 +- 全面增强, 性能卓越, 完全兼容 + +## 2020.11.23 + +project X start + +> ~~梦开始的时候~~ diff --git a/docs/ru/config/README.md b/docs/ru/config/README.md new file mode 100644 index 000000000..86ff6f02b --- /dev/null +++ b/docs/ru/config/README.md @@ -0,0 +1,94 @@ +--- +title: Конфигурационный файл +lang: ru-RU +--- + +> **В этом разделе вы узнаете все детали настройки Xray. Овладев этими знаниями, вы сможете раскрыть весь потенциал Xray.** + +## Обзор + +Конфигурационный файл Xray имеет формат JSON. Формат конфигурации одинаков для клиента и сервера, но фактическое содержимое отличается. +Он выглядит следующим образом: + +```json +{ + "log": {}, + "api": {}, + "dns": {}, + "routing": {}, + "policy": {}, + "inbounds": [], + "outbounds": [], + "transport": {}, + "stats": {}, + "reverse": {}, + "fakedns": {}, + "metrics": {}, + "observatory": {}, + "burstObservatory": {} +} +``` + +::: warning +Если вы новичок в Xray, вы можете сначала прочитать раздел [Настройка и запуск в кратком руководстве](../document/install.md), чтобы узнать об основных способах настройки, а затем прочитать этот раздел, чтобы узнать обо всех способах настройки Xray. +::: + +## Основные модули конфигурации + +> log: [LogObject](./log.md) + +Настройка журнала, управляющая способом вывода журналов Xray. + +> api: [ApiObject](./api.md) + +Предоставляет API-интерфейсы для удаленного вызова. + +> dns: [DnsObject](./dns.md) + +Встроенный DNS-сервер. Если этот параметр не настроен, используются системные настройки DNS. + +> routing: [RoutingObject](./routing.md) + +Функция маршрутизации. Позволяет настроить правила для разделения трафика и отправки его через разные исходящие подключения. + +> policy: [PolicyObject](./policy.md) + +Локальная политика, позволяющая настроить разные уровни пользователей и соответствующие им политики. + +> inbounds: \[ [InboundObject](./inbound.md) \] + +Массив, каждый элемент которого представляет собой конфигурацию входящего подключения. + +> outbounds: \[ [OutboundObject](./outbound.md) \] + +Массив, каждый элемент которого представляет собой конфигурацию исходящего подключения. + +> transport: [TransportObject](./transport.md) + +Используется для настройки способа, которым Xray устанавливает и использует сетевые подключения к другим серверам. + +> stats: [StatsObject](./stats.md) + +Используется для настройки сбора статистики трафика. + +> reverse: [ReverseObject](./reverse.md) + +Обратный прокси. Позволяет перенаправлять трафик с сервера на клиент, т.е. перенаправлять трафик в обратном направлении. + +> fakedns: [FakeDnsObject](./fakedns.md) + +Настройка FakeDNS. Может использоваться совместно с прозрачным проксированием для получения фактических доменных имен. + +> metrics: [metricsObject](./metrics.md) + +Настройка метрик. Более прямой (и, надеемся, лучший) способ экспорта статистики. + +> observatory: [ObservatoryObject](./observatory.md#observatoryobject) + +Мониторинг фоновых подключений. Обнаружение состояния подключения исходящего прокси. + +> burstObservatory: [BurstObservatoryObject](./observatory.md#burstobservatoryobject) + +Мониторинг параллельных подключений. Обнаружение состояния подключения исходящего прокси. + + diff --git a/docs/ru/config/api.md b/docs/ru/config/api.md new file mode 100644 index 000000000..44b981e82 --- /dev/null +++ b/docs/ru/config/api.md @@ -0,0 +1,144 @@ +# API + +Настройка API предоставляет API-интерфейсы на основе [gRPC](https://grpc.io/) для удаленного вызова. + +Интерфейсы можно включить с помощью модуля конфигурации api. Когда API включен, Xray создает исходящее подключение с тем же тегом, что и тег API. +Необходимо вручную направить все входящие API-подключения на это исходящее подключение с помощью [правил маршрутизации](./routing.md). +См. раздел [Связанные настройки](#связанные-настройки) в этом документе. + +Начиная с версии [v1.8.12](https://github.com/XTLS/Xray-core/releases/tag/v1.8.12) поддерживается упрощенный режим настройки, в котором достаточно указать только ApiObject, без необходимости настройки inbounds и routing. +Однако при использовании упрощенной настройки статистика трафика не учитывает трафик входящих API-подключений. + +::: warning +Большинству пользователей не нужен этот API, новички могут просто пропустить этот раздел. +::: + +## ApiObject + +`ApiObject` соответствует полю `api` в конфигурационном файле. + +```json +{ + "api": { + "tag": "api", + "listen": "127.0.0.1:8080", + "services": ["HandlerService", "LoggerService", "StatsService", "RoutingService"] + } +} +``` + +> `tag`: string + +Тег исходящего подключения. + +> `listen`: string + +IP-адрес и порт, на котором прослушивает API-сервер. Это необязательный параметр. + +Если этот параметр опущен, необходимо добавить настройки inbounds и routing, как показано в примере в разделе [Связанные настройки](#связанные-настройки). + +> `services`: \[string\] + +Список включенных API. +Доступные значения см. в разделе [Список API](#список-поддерживаемых-api). + +## Связанные настройки + +Можно добавить входящее подключение api в раздел inbounds: + +```json +"inbounds": [ + { + "listen": "127.0.0.1", + "port": 10085, + "protocol": "dokodemo-door", + "settings": { + "address": "127.0.0.1" + }, + "tag": "api" + } +] +``` + +Добавить правило маршрутизации для входящего подключения api в раздел routing: + +```json +"routing": { + "rules": [ + { + "inboundTag": [ + "api" + ], + "outboundTag": "api", + "type": "field" + } + ] +} +``` + +Добавить api в основные настройки: + +```json +"api": { + "tag": "api", + "services": [ + "StatsService" + ] +} +``` + +## Список поддерживаемых API + +### HandlerService + +API для изменения входящих и исходящих подключений. +Доступны следующие функции: + +- Добавление нового входящего подключения; +- Добавление нового исходящего подключения; +- Удаление существующего входящего подключения; +- Удаление существующего исходящего подключения; +- Добавление пользователя к входящему подключению (поддерживается только для VMess, VLESS, Trojan, Shadowsocks (v1.3.0+)); +- Удаление пользователя из входящего подключения (поддерживается только для VMess, VLESS, Trojan, Shadowsocks (v1.3.0+)); + +### RoutingService + +API для добавления, удаления, замены правил маршрутизации и запроса статистики балансировщика. +Доступны следующие функции: + +- adrules - добавление или замена правил маршрутизации; +- rmrules - удаление правил маршрутизации; +- sib - разрыв соединений с указанного IP-адреса; +- bi - запрос статистики балансировщика; +- bo - принудительное переключение балансировщика на указанный outboundTag. + +Конкретное использование можно узнать с помощью команды `./xray help api bi`. + +### LoggerService + +Поддержка перезапуска встроенного логгера. +Можно использовать совместно с logrotate для управления файлами журналов. + +### StatsService + +Встроенная служба статистики данных. +См. [Статистика](./stats.md). + +### ReflectionService + +Позволяет gRPC-клиентам получать список API сервера. + +```bash +$ grpcurl -plaintext localhost:10085 list +grpc.reflection.v1alpha.ServerReflection +v2ray.core.app.proxyman.command.HandlerService +v2ray.core.app.stats.command.StatsService +xray.app.proxyman.command.HandlerService +xray.app.stats.command.StatsService +``` + +## Примеры вызова API + +[Xray-API-documents](https://github.com/XTLS/Xray-API-documents) @crossfw + + diff --git a/docs/ru/config/dns.md b/docs/ru/config/dns.md new file mode 100644 index 000000000..9ce781598 --- /dev/null +++ b/docs/ru/config/dns.md @@ -0,0 +1,344 @@ +# Встроенный DNS-сервер + +## DNS-сервер + +Встроенный DNS-модуль Xray имеет два основных назначения: + +- Разрешение доменных имен в IP-адреса на этапе маршрутизации и сопоставление правил с полученными IP-адресами для разделения трафика. + Разрешение доменных имен и разделение трафика зависят от значения параметра `domainStrategy` в модуле конфигурации маршрутизации. + Встроенный DNS-сервер будет использоваться для DNS-запросов только при следующих значениях: + + - "IPIfNonMatch": при запросе доменного имени Xray сопоставляет его с доменами, указанными в правилах маршрутизации. + Если совпадение не найдено, встроенный DNS-сервер используется для разрешения доменного имени, а затем полученный IP-адрес снова сопоставляется с правилами маршрутизации на основе IP-адресов. + - "IPOnDemand": при сопоставлении правил, основанных на IP-адресах, доменное имя немедленно разрешается в IP-адрес для сопоставления. + +- Разрешение целевого адреса для подключения. + - Например, в исходящем подключении `freedom`, если параметр `domainStrategy` установлен в `UseIP`, исходящие запросы будут сначала разрешать доменное имя в IP-адрес с помощью встроенного DNS-сервера, а затем устанавливать соединение. + - Например, в `sockopt`, если параметр `domainStrategy` установлен в `UseIP`, системные соединения, инициированные этим исходящим подключением, будут сначала разрешать доменное имя в IP-адрес с помощью встроенного DNS-сервера, а затем устанавливать соединение. + +::: tip Совет 1 +DNS-запросы, отправляемые встроенным DNS-сервером, автоматически перенаправляются в соответствии с конфигурацией маршрутизации. +::: + +::: tip Совет 2 +Поддерживаются только базовые запросы IP-адресов (записи A и AAAA). Записи CNAME будут запрашиваться повторно до тех пор, пока не будет возвращена запись A/AAAA. Другие запросы не обрабатываются встроенным DNS-сервером. +::: + +## Процесс обработки DNS-запросов + +Если запрашиваемое доменное имя: + +- Соответствует сопоставлению "домен - IP" или "домен - массив IP" в `hosts`, то этот IP-адрес или массив IP-адресов возвращается в качестве результата DNS-разрешения. +- Соответствует сопоставлению "домен - домен" в `hosts`, то значение этого сопоставления (другой домен) используется в качестве текущего запрашиваемого доменного имени, и процесс обработки DNS-запросов продолжается до тех пор, пока не будет разрешен IP-адрес или не будет возвращен пустой результат. +- Не соответствует `hosts`, но соответствует списку доменов `domains` одного (или нескольких) DNS-серверов, то запросы отправляются на соответствующие DNS-серверы в порядке приоритета. + Если запрос к DNS-серверу завершается неудачей или `expectIPs` не совпадает, используется следующий подходящий DNS-сервер. + В противном случае возвращается полученный IP-адрес. + Если запросы ко всем подходящим DNS-серверам завершаются неудачей или `expectIPs` не совпадает, компонент DNS: + - По умолчанию выполняет "резервный (fallback) запрос DNS": запросы отправляются на "DNS-серверы, которые не использовались в предыдущем раунде неудачных запросов и для которых `skipFallback` имеет значение по умолчанию `false`". + Если запрос завершается неудачей или `expectIPs` не совпадает, возвращается пустой результат. + В противном случае возвращается полученный IP-адрес. + - Если `disableFallback` установлен в `true`, "резервный (fallback) запрос DNS" не выполняется. +- Не соответствует `hosts` и не соответствует списку доменов `domains` ни одного DNS-сервера, то: + - По умолчанию запросы отправляются на "DNS-серверы, для которых `skipFallback` имеет значение по умолчанию `false`". + Если запрос к первому выбранному DNS-серверу завершается неудачей или `expectIPs` не совпадает, используется следующий выбранный DNS-сервер. + В противном случае возвращается полученный IP-адрес. + Если запросы ко всем выбранным DNS-серверам завершаются неудачей или `expectIPs` не совпадает, возвращается пустой результат. + - Если количество "DNS-серверов, для которых `skipFallback` имеет значение по умолчанию `false`", равно 0 или `disableFallback` установлен в `true`, используется первый DNS-сервер в конфигурации DNS. + Если запрос завершается неудачей или `expectIPs` не совпадает, возвращается пустой результат. + В противном случае возвращается полученный IP-адрес. + +## DnsObject + +`DnsObject` соответствует полю `dns` в конфигурационном файле. + +```json +{ + "dns": { + "hosts": { + "baidu.com": "127.0.0.1", + "dns.google": ["8.8.8.8", "8.8.4.4"] + }, + "servers": [ + "8.8.8.8", + "8.8.4.4", + { + "address": "1.2.3.4", + "port": 5353, + "domains": ["domain:xray.com"], + "expectIPs": ["geoip:cn"], + "skipFallback": false, + "clientIP": "1.2.3.4" + }, + { + "address": "https://8.8.8.8/dns-query", + "domains": [ + "geosite:netflix" + ], + "skipFallback": true, + "queryStrategy": "UseIPv4" + }, + { + "address": "https://1.1.1.1/dns-query", + "domains": [ + "geosite:openai" + ], + "skipFallback": true, + "queryStrategy": "UseIPv6" + }, + "localhost" + ], + "clientIp": "1.2.3.4", + "queryStrategy": "UseIP", + "disableCache": false, + "disableFallback": false, + "disableFallbackIfMatch": false, + "tag": "dns_inbound" + } +} +``` + +> `hosts`: map{string: address} | map{string: [address]} + +Статический список IP-адресов, значение которого представляет собой набор сопоставлений "домен": "адрес" или "домен": ["адрес 1", "адрес 2"]. +Адрес может быть IP-адресом или доменным именем. +При разрешении доменного имени, если домен соответствует одному из элементов этого списка: + +- Если адрес элемента - это IP-адрес, то результатом разрешения будет IP-адрес элемента. +- Если адрес элемента - это доменное имя, то для разрешения IP-адреса будет использоваться это доменное имя, а не исходное доменное имя. +- Если в адресе указано несколько IP-адресов и доменных имен, то возвращается только первое доменное имя, остальные IP-адреса и доменные имена игнорируются. + +Доменные имена могут быть представлены в следующих форматах: + +- Простая строка: правило применяется, если эта строка полностью совпадает с целевым доменным именем. + Например, "xray.com" соответствует "xray.com", но не соответствует "www.xray.com". +- Регулярное выражение: начинается с `"regexp:"`, а остальная часть - это регулярное выражение. + Правило применяется, если это регулярное выражение соответствует целевому доменному имени. + Например, "regexp:\\\\.goo.\*\\\\.com\$" соответствует "www.google.com", "fonts.googleapis.com", но не соответствует "google.com". +- Поддомен (рекомендуется): начинается с `"domain:"`, а остальная часть - это доменное имя. + Правило применяется, если это доменное имя является целевым доменным именем или его поддоменом. + Например, "domain:xray.com" соответствует "www.xray.com" и "xray.com", но не соответствует "wxray.com". +- Подстрока: начинается с `"keyword:"`, а остальная часть - это строка. + Правило применяется, если эта строка соответствует любой части целевого доменного имени. + Например, "keyword:sina.com" соответствует "sina.com", "sina.com.cn" и "www.sina.com", но не соответствует "sina.cn". +- Предопределенный список доменов: начинается с `"geosite:"`, а остальная часть - это имя, например `geosite:google` или `geosite:cn`. + Список имен и доменов см. в разделе [Предопределенные списки доменов](./routing.md#предопределенные-списки-доменов). + +> `servers`: \[string | [ServerObject](#serverobject) \] + +Список DNS-серверов, поддерживаются два типа: адрес DNS (в виде строки) и [ServerObject](#serverobject). + +Значение `"localhost"` означает использование локальных настроек DNS. + +Если значение - это адрес DNS `"IP:Port"`, например `"8.8.8.8:53"`, Xray будет использовать указанный UDP-порт этого адреса для DNS-запросов. +Запрос будет следовать правилам маршрутизации. +Если порт не указан, по умолчанию используется порт 53. + +Если значение имеет вид `"tcp://host:port"`, например `"tcp://8.8.8.8:53"`, Xray будет использовать `DNS over TCP` для запросов. +Запрос будет следовать правилам маршрутизации. +Если порт не указан, по умолчанию используется порт 53. + +Если значение имеет вид `"tcp+local://host:port"`, например `"tcp+local://8.8.8.8:53"`, Xray будет использовать `локальный режим TCP (TCPL)` для запросов. +Это означает, что DNS-запросы не будут проходить через компонент маршрутизации, а будут отправляться непосредственно через исходящее подключение Freedom для сокращения времени ожидания. +Если порт не указан, по умолчанию используется порт 53. + +Если значение имеет вид `"https://host:port/dns-query"`, например `"https://dns.google/dns-query"`, Xray будет использовать `DNS over HTTPS` (RFC8484, сокращенно DOH) для запросов. +Некоторые провайдеры имеют сертификаты с IP-псевдонимами, поэтому можно использовать IP-адрес напрямую, например `https://1.1.1.1/dns-query`. +Также можно использовать нестандартные порты и пути, например `"https://a.b.c.d:8443/my-dns-query"`. + +Если значение имеет вид `"https+local://host:port/dns-query"`, например `"https+local://dns.google/dns-query"`, Xray будет использовать `локальный режим DOH (DOHL)` для запросов. +Это означает, что DOH-запросы не будут проходить через компонент маршрутизации, а будут отправляться непосредственно через исходящее подключение Freedom для сокращения времени ожидания. +Обычно этот режим подходит для использования на сервере. +Также можно использовать нестандартные порты и пути. + +Если значение имеет вид `"quic+local://host"`, например `"quic+local://dns.adguard.com"`, Xray будет использовать `локальный режим DNS over QUIC (DOQL)` для запросов. +Это означает, что DNS-запросы не будут проходить через компонент маршрутизации, а будут отправляться непосредственно через исходящее подключение Freedom. +Этот режим требует, чтобы DNS-сервер поддерживал DNS over QUIC. +По умолчанию для запросов используется порт 784, можно использовать нестандартный порт. + +Если значение равно `fakedns`, для запросов будет использоваться FakeDNS. + +::: tip Совет 1 +При использовании `localhost` локальные DNS-запросы не контролируются Xray. +Для того, чтобы DNS-запросы перенаправлялись через Xray, требуется дополнительная настройка. +::: + +::: tip Совет 2 +Разные DNS-клиенты, инициализированные разными правилами, будут отображаться в журнале запуска Xray с уровнем `info`, например, `local DOH`, `remote DOH`, `udp` и т.д. +::: + +::: tip Совет 3 +(v1.4.0+) Можно включить ведение журнала DNS-запросов в [настройках журнала](./log.md). +::: + +> `clientIp`: string + +IP-адрес, который будет сообщаться серверу при выполнении DNS-запросов. +Не может быть частным IP-адресом. + +::: tip Совет 1 +Требуется, чтобы DNS-сервер поддерживал EDNS Client Subnet. +::: + +::: tip Совет 2 +Можно указать `clientIp` для всех DNS-серверов в [DnsObject](#dnsobject) или для каждого DNS-сервера в [ServerObject](#serverobject) (настройка в ServerObject имеет приоритет над настройкой в DnsObject). +::: + +> `queryStrategy`: "UseIP" | "UseIPv4" | "UseIPv6" + +Значение по умолчанию - `UseIP`, запрашиваются как записи A, так и записи AAAA. +`UseIPv4` - запрашиваются только записи A; `UseIPv6` - запрашиваются только записи AAAA. + +Новая функция в Xray-core v1.8.6: `queryStrategy` можно настроить для каждого `DNS`-сервера. + +```json + "dns": { + "servers": [ + "https://1.1.1.1/dns-query", + { + "address": "https://8.8.8.8/dns-query", + "domains": [ + "geosite:netflix" + ], + "skipFallback": true, + "queryStrategy": "UseIPv4" // Запрос записей A для доменов netflix + }, + { + "address": "https://1.1.1.1/dns-query", + "domains": [ + "geosite:openai" + ], + "skipFallback": true, + "queryStrategy": "UseIPv6" // Запрос записей AAAA для доменов openai + } + ], + "queryStrategy": "UseIP" // Запрос записей A и AAAA для всех доменов + } +``` + +::: tip Совет 1 +Глобальное значение `"queryStrategy"` имеет приоритет. +Если значение `"queryStrategy"` в дочернем элементе конфликтует с глобальным значением `"queryStrategy"`, дочерний запрос вернет пустой ответ. +::: + +::: tip Совет 2 +Если параметр `"queryStrategy"` не указан в дочернем элементе, используется глобальное значение `"queryStrategy"`. +Поведение такое же, как и в версиях Xray-core до v1.8.6. +::: + +Например:
+Глобальное значение `"queryStrategy": "UseIPv6"` конфликтует с дочерним значением `"queryStrategy": "UseIPv4"`.
+Глобальное значение `"queryStrategy": "UseIPv4"` конфликтует с дочерним значением `"queryStrategy": "UseIPv6"`.
+Глобальное значение `"queryStrategy": "UseIP"` не конфликтует с дочерним значением `"queryStrategy": "UseIPv6"`.
+Глобальное значение `"queryStrategy": "UseIP"` не конфликтует с дочерним значением `"queryStrategy": "UseIPv4"`. + +```json + "dns": { + "servers": [ + "https://1.1.1.1/dns-query", + { + "address": "https://8.8.8.8/dns-query", + "domains": [ + "geosite:netflix" + ], + "skipFallback": true, + "queryStrategy": "UseIPv6" // Конфликт между глобальным значением "UseIPv4" и дочерним значением "UseIPv6" + } + ], + "queryStrategy": "UseIPv4" + } +``` + +Дочерний запрос для доменов netflix вернет пустой ответ из-за конфликта значений `"queryStrategy"`. +Домены netflix будут разрешены с помощью `https://1.1.1.1/dns-query`, и будут получены записи A. + +> `disableCache`: true | false + +`true` - отключить кэширование DNS, по умолчанию `false` (кэширование включено). + +> `disableFallback`: true | false + +`true` - отключить резервные (fallback) DNS-запросы, по умолчанию `false` (резервные запросы включены). + +> `disableFallbackIfMatch`: true | false + +`true` - отключить резервные (fallback) DNS-запросы, если совпадает приоритетный список доменов DNS-сервера, по умолчанию `false` (резервные запросы включены). + +> `tag`: string + +Трафик запросов, отправляемых встроенным DNS-сервером (кроме режимов `localhost`, `fakedns`, `TCPL`, `DOHL` и `DOQL`), можно сопоставить с помощью этого тега, используя `inboundTag` в правилах маршрутизации. + +### ServerObject + +```json +{ + "address": "1.2.3.4", + "port": 5353, + "domains": ["domain:xray.com"], + "expectIPs": ["geoip:cn"], + "skipFallback": false, + "clientIP": "1.2.3.4" +} +``` + +> `address`: address + +Адрес DNS-сервера. +Поддерживаются два типа: адрес DNS (в виде строки) и ServerObject. + +Значение `"localhost"` означает использование локальных настроек DNS. + +Если значение - это адрес DNS `"IP"`, например `"8.8.8.8"`, Xray будет использовать указанный UDP-порт этого адреса для DNS-запросов. +Запрос будет следовать правилам маршрутизации. +По умолчанию используется порт 53. + +Если значение имеет вид `"tcp://host"`, например `"tcp://8.8.8.8"`, Xray будет использовать `DNS over TCP` для запросов. +Запрос будет следовать правилам маршрутизации. +По умолчанию используется порт 53. + +Если значение имеет вид `"tcp+local://host"`, например `"tcp+local://8.8.8.8"`, Xray будет использовать `локальный режим TCP (TCPL)` для запросов. +Это означает, что DNS-запросы не будут проходить через компонент маршрутизации, а будут отправляться непосредственно через исходящее подключение Freedom для сокращения времени ожидания. +Если порт не указан, по умолчанию используется порт 53. + +Если значение имеет вид `"https://host:port/dns-query"`, например `"https://dns.google/dns-query"`, Xray будет использовать `DNS over HTTPS` (RFC8484, сокращенно DOH) для запросов. +Некоторые провайдеры имеют сертификаты с IP-псевдонимами, поэтому можно использовать IP-адрес напрямую, например `https://1.1.1.1/dns-query`. +Также можно использовать нестандартные порты и пути, например `"https://a.b.c.d:8443/my-dns-query"`. + +Если значение имеет вид `"https+local://host:port/dns-query"`, например `"https+local://dns.google/dns-query"`, Xray будет использовать `локальный режим DOH (DOHL)` для запросов. +Это означает, что DOH-запросы не будут проходить через компонент маршрутизации, а будут отправляться непосредственно через исходящее подключение Freedom для сокращения времени ожидания. +Обычно этот режим подходит для использования на сервере. +Также можно использовать нестандартные порты и пути. + +Если значение имеет вид `"quic+local://host:port"`, например `"quic+local://dns.adguard.com"`, Xray будет использовать `локальный режим DOQ (DOQL)` для запросов. +Это означает, что DNS-запросы не будут проходить через компонент маршрутизации, а будут отправляться непосредственно через исходящее подключение Freedom. +Этот режим требует, чтобы DNS-сервер поддерживал DNS over QUIC. +По умолчанию для запросов используется порт 784, можно использовать нестандартный порт. + +Если значение равно `fakedns`, для запросов будет использоваться FakeDNS. + +> `port`: number + +Порт DNS-сервера, например `53`. +По умолчанию используется порт `53`. +Этот параметр не используется в режимах DOH, DOHL, DOQL. +Нестандартный порт должен быть указан в URL. + +> `domains`: \[string\] + +Список доменов, для которых в первую очередь будет использоваться этот сервер. +Формат доменных имен такой же, как и в [конфигурации маршрутизации](./routing.md#ruleobject). + +> `expectIPs`: \[string\] + +Список диапазонов IP-адресов, формат такой же, как и в [конфигурации маршрутизации](./routing.md#ruleobject). + +Если этот параметр настроен, DNS Xray будет проверять возвращаемые IP-адреса и возвращать только те, которые входят в список `expectIPs`. + +Если этот параметр не настроен, IP-адреса возвращаются без изменений. + +> `skipFallback`: true | false + +`true` - пропустить этот сервер при выполнении резервных (fallback) DNS-запросов, по умолчанию `false` (не пропускать). + + + + diff --git a/docs/ru/config/dns_flow.png b/docs/ru/config/dns_flow.png new file mode 100644 index 000000000..5dd8caa2a Binary files /dev/null and b/docs/ru/config/dns_flow.png differ diff --git a/docs/ru/config/fakedns.md b/docs/ru/config/fakedns.md new file mode 100644 index 000000000..9d7a3a4e8 --- /dev/null +++ b/docs/ru/config/fakedns.md @@ -0,0 +1,202 @@ +# FakeDNS + +FakeDNS подменяет DNS-записи, чтобы получить целевое доменное имя, что позволяет сократить время DNS-запросов и получить целевое доменное имя при использовании прозрачного проксирования. + +::: warning +FakeDNS может загрязнить локальный DNS-кэш, что может привести к "недоступности сети" после отключения Xray. +::: + +## FakeDNSObject + +`FakeDNSObject` соответствует полю `fakedns` в конфигурационном файле. + +```json +{ + "ipPool": "198.18.0.0/16", + "poolSize": 65535 +} +``` + +`FakeDnsObject` также может быть настроен как массив, содержащий несколько пулов FakeIP. +При получении DNS-запроса FakeDNS вернет набор FakeIP, полученных из нескольких пулов FakeIP. + +```json +[ + { + "ipPool": "198.18.0.0/15", + "poolSize": 65535 + }, + { + "ipPool": "fc00::/18", + "poolSize": 65535 + } +] +``` + +> `ipPool`: CIDR + +FakeDNS будет использовать этот блок IP-адресов для выделения адресов. + +> `poolSize`: int + +Максимальное количество сопоставлений "домен - IP", которые FakeDNS может хранить в памяти. +Когда количество сопоставлений превышает это значение, старые сопоставления удаляются по алгоритму LRU. +Значение по умолчанию - 65535. + +::: warning +`poolSize` должен быть меньше или равен общему количеству адресов в `ipPool`. +::: + +::: tip +Если в поле `dns` конфигурационного файла указано `fakedns`, но `FakeDnsObject` не настроен, Xray инициализирует `FakeDnsObject` в соответствии с параметром `queryStrategy` компонента DNS. + +Если `queryStrategy` равен `UseIP`, инициализированный пул FakeIP будет эквивалентен: + +```json +[ + { + "ipPool": "198.18.0.0/15", + "poolSize": 32768 + }, + { + "ipPool": "fc00::/18", + "poolSize": 32768 + } +] +``` + +Если `queryStrategy` равен `UseIPv4`, инициализированный пул FakeIP будет эквивалентен: + +```json +{ + "ipPool": "198.18.0.0/15", + "poolSize": 65535 +} +``` + +Если `queryStrategy` равен `UseIPv6`, инициализированный пул FakeIP будет эквивалентен: + +```json +{ + "ipPool": "fc00::/18", + "poolSize": 65535 +} +``` + +::: + +### Как использовать FakeDNS? + +По сути, FakeDNS - это [DNS-сервер](./dns.md#serverobject), который можно использовать с любыми правилами DNS. + +Чтобы FakeDNS работал, необходимо направить DNS-запросы на него. + +```json +{ + "dns": { + "servers": [ + "fakedns", // fakedns на первом месте + "8.8.8.8" + ] + }, + "outbounds": [ + { + "protocol": "dns", + "tag": "dns-out" + } + ], + "routing": { + "rules": [ + { + "type": "field", + "inboundTag": ["dns-in"], // Перехват DNS-трафика, поступающего от DNS-входа или от входящего подключения прозрачного прокси. + "port": 53, + "outboundTag": "dns-out" + } + ] + } +} +``` + +Когда внешний DNS-запрос поступает в компонент FakeDNS, он возвращает IP-адрес из своего пула `ipPool` в качестве фиктивного результата разрешения доменного имени и сохраняет сопоставление между доменным именем и фиктивным IP-адресом. + +Кроме того, вам нужно включить `Sniffing` во входящем подключении, которое принимает трафик, который нужно проксировать, и использовать `fakedns` для замены целевого адреса **на стороне клиента**. + +```json +"sniffing": { + "enabled": true, + "destOverride": ["fakedns"], // Используйте "fakedns" или в сочетании с другими снифферами, или используйте "fakedns+others". + "metadataOnly": false // Если этот параметр равен true, то в destOverride можно использовать только fakedns. +}, +``` + +::: warning +Если FakeIP не будет правильно заменен на доменное имя, подключение к серверу не будет установлено. +::: + +### Использование FakeDNS с другими типами DNS + +#### Совместное использование с разделением DNS + +При использовании разделения DNS, чтобы `fakedns` имел высокий приоритет, нужно добавить для него тот же параметр `domains`, что и для других типов DNS. + +```json +{ + "servers": [ + { + "address": "fakedns", + "domains": [ + // То же самое, что и в разделе разделения DNS ниже. + "geosite:cn", + "domain:example.com" + ] + }, + { + "address": "1.2.3.4", + "domains": ["geosite:cn"], + "expectIPs": ["geoip:cn"] + }, + { + "address": "1.1.1.1", + "domains": ["domain:example.com"] + }, + "8.8.8.8" + ] +} +``` + +#### Черный список FakeDNS + +Если вы не хотите, чтобы FakeDNS использовался для определенных доменов, вы можете добавить параметр `domains` к другим типам конфигурации DNS, чтобы указать, что эти домены должны иметь более высокий приоритет при сопоставлении с другими DNS-серверами, чем с FakeDNS, тем самым реализовав механизм черного списка FakeDNS. + +```json +{ + "servers": [ + "fakedns", + { + "address": "1.2.3.4", + "domains": ["domain:do-not-use-fakedns.com"] + } + ] +} +``` + +#### Белый список FakeDNS + +Если вы хотите, чтобы FakeDNS использовался только для определенных доменов, вы можете добавить параметр `domains` к `fakedns`, чтобы указать, что эти домены должны иметь более высокий приоритет при сопоставлении с `fakedns`, чем с другими DNS-серверами, тем самым реализовав механизм белого списка FakeDNS. + +```json +{ + "servers": [ + "1.2.3.4", + { + "address": "fakedns", + "domains": ["domain:only-this-use-fakedns.com"] + } + ] +} +``` + + + + diff --git a/docs/ru/config/features/browser_dialer.md b/docs/ru/config/features/browser_dialer.md new file mode 100644 index 000000000..6848aaf59 --- /dev/null +++ b/docs/ru/config/features/browser_dialer.md @@ -0,0 +1,47 @@ +# Браузерный Dialer + + + +## Предыстория + +Основываясь на [идее, возникшей год назад](https://github.com/v2ray/discussion/issues/754#issuecomment-647934994), с помощью нативного JavaScript был реализован простой WSS-браузерный Dialer, который эмулирует TLS-отпечаток и поведенческие характеристики реального браузера. + +Однако WSS все еще имеет очевидные проблемы с ALPN, поэтому следующим шагом будет пересылка `HTTP/2` и `QUIC` через браузер. + +## Xray и JS + +Был создан очень простой и элегантный механизм связи: + +- Xray прослушивает адрес и порт A в качестве HTTP-сервера. + Браузер обращается к A и загружает JavaScript-код с веб-страницы. +- JavaScript-код устанавливает WebSocket-соединение с A. + После успешного установления соединения Xray передает соединение в канал. +- При необходимости установить соединение Xray получает доступное соединение из канала и отправляет целевой URL-адрес и необязательные ранние данные (early data). +- JavaScript-код сообщает Xray об успешном подключении к цели и продолжает использовать это соединение для двунаправленной передачи данных. + Соединение закрывается синхронно. +- Соединение закрывается после использования, но JavaScript-код гарантирует, что всегда есть доступные новые соединения. + +## Ранние данные (Early data) + +Механизм ранних данных был скорректирован в соответствии с потребностями браузера: + +- Заголовок ответа сервера содержит заголовок `Sec-WebSocket-Protocol` запроса, что также частично скрывает характеристики длины ответа рукопожатия WSS. +- Для кодирования ранних данных, отправляемых браузеру, используется `base64.RawURLEncoding`, а не `StdEncoding`. + Сервер обеспечивает совместимость. +- Кроме того, из-за [Xray-core#375](https://github.com/XTLS/Xray-core/pull/375) рекомендуется использовать `?ed=2048`. + В этом PR также увеличен `MaxHeaderBytes` на сервере до 4096. + ~~(Хотя, кажется, это не обязательно)~~ + +## Конфигурация + +Это экспериментальный процесс. +В настоящее время конфигурация выглядит следующим образом (Xray-core v1.4.1): + +- Подготовьте рабочую конфигурацию WSS. + Обратите внимание, что в поле `address` нужно указать доменное имя. + Если нужно указать IP-адрес, настройте DNS или добавьте запись в файл hosts. +- Если трафик браузера также проходит через Xray-core, обязательно настройте прямое подключение для этого домена, чтобы избежать зацикливания трафика. +- Установите переменную окружения, указывающую адрес и порт, который нужно прослушивать, например, `XRAY_BROWSER_DIALER = 127.0.0.1:8080`. +- Сначала запустите Xray-core, а затем откройте указанный адрес и порт в любом браузере. + Вы также можете открыть инструменты разработчика (F12) и посмотреть консоль и вкладку "Сеть". +- Браузеры ограничивают количество WebSocket-соединений, поэтому рекомендуется включить `Mux.Cool`. \ No newline at end of file diff --git a/docs/ru/config/features/env.md b/docs/ru/config/features/env.md new file mode 100644 index 000000000..27fb1b95e --- /dev/null +++ b/docs/ru/config/features/env.md @@ -0,0 +1,31 @@ +# Переменные среды + +Xray предоставляет следующие переменные среды для изменения некоторых базовых настроек Xray. + +## Путь к файлам ресурсов + +- Название: `xray.location.asset` или `XRAY_LOCATION_ASSET`. +- Значение по умолчанию: Определенный каталог [FHS](https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard) или тот же каталог, что и файл Xray. + +Эта переменная среды указывает расположение папки, которая должна содержать файлы `geoip.dat` и `geosite.dat`. +Если значение переменной не указано, программа будет искать файлы ресурсов в следующем порядке: + +``` +./ +/usr/local/share/xray +/usr/share/xray +``` + +## Расположение файла конфигурации + +- Название: `xray.location.config` или `XRAY_LOCATION_CONFIG`. +- Значение по умолчанию: Тот же каталог, что и файл Xray. + +Эта переменная среды указывает расположение папки, которая должна содержать файл `config.json`. + +## Каталог с несколькими конфигурациями + +- Название: `xray.location.confdir` или `XRAY_LOCATION_CONFDIR`. +- Значение по умолчанию: `""`. + +Файлы `.json` в этом каталоге будут читаться в порядке имен файлов как параметры конфигурации. diff --git a/docs/ru/config/features/fallback.md b/docs/ru/config/features/fallback.md new file mode 100644 index 000000000..3bcdffa9a --- /dev/null +++ b/docs/ru/config/features/fallback.md @@ -0,0 +1,102 @@ +# Fallback + +> **Fallback - одна из самых мощных функций Xray, эффективно предотвращающая активное зондирование и позволяющая свободно настраивать совместное использование нескольких служб на часто используемых портах.** + +Fallback обеспечивает Xray высокой степенью защиты от активного зондирования и имеет уникальный механизм резервирования первого пакета. + +Fallback также может разделять трафик различных типов по пути, что позволяет совместно использовать один порт для нескольких служб. + +В настоящее время вы можете использовать функцию fallback при использовании протоколов VLESS или Trojan, настроив `fallbacks`, и создавать очень разнообразные комбинации. + +## Настройка `fallbacks` + +```json + "fallbacks": [ + { + "dest": 80 + } + ] +``` + +> `fallbacks`: \[ [FallbackObject](#fallbackobject) \] + +Массив, содержащий серию мощных конфигураций резервирования и разделения трафика. + +### FallbackObject + +```json +{ + "name": "", + "alpn": "", + "path": "", + "dest": 80, + "xver": 0 +} +``` + +**`fallbacks` - это массив, здесь приведено описание конфигурации одного из его элементов.** + +Элемент `fallbacks` является необязательным и может использоваться только для комбинации транспорта TCP+TLS. + +- Если этот элемент имеет дочерние элементы, в [Inbound TLS](../transport.md#tlsobject) необходимо установить `"alpn":["http/1.1"]`. + +Обычно сначала нужно настроить набор резервных путей по умолчанию с опущенными или пустыми `alpn` и `path`, а затем настроить другие разделения по мере необходимости. + +VLESS будет перенаправлять трафик с длиной первого пакета после дешифрования TLS менее 18 байт, неверной версией протокола или неудачной аутентификацией на адрес, указанный в `dest`. + +Для других комбинаций транспорта необходимо удалить элемент `fallbacks` или все его дочерние элементы. В этом случае Fallback не будет включен, VLESS будет ждать считывания необходимой длины, а в случае неверной версии протокола или сбоя аутентификации соединение будет немедленно разорвано. + +> `name`: string + +Попытка сопоставить TLS SNI (указание имени сервера), любое значение или пустая строка, по умолчанию "". + +> `alpn`: string + +Попытка сопоставить результат согласования TLS ALPN, любое значение или пустая строка, по умолчанию "". + +При необходимости VLESS попытается прочитать результат согласования TLS ALPN, и в случае успеха выведет в лог `realAlpn =`. +Назначение: решает проблему несовместимости службы h2c Nginx с http/1.1, для которой в Nginx требуется написать две строки listen, по одной для 1.1 и h2c. +Примечание: если в `fallbacks alpn` присутствует `"h2"`, в [Inbound TLS](../transport.md#tlsobject) необходимо установить `"alpn":["h2","http/1.1"]` для поддержки доступа h2. + +::: tip +`alpn`, установленный в Fallback, соответствует фактически согласованному ALPN, а `alpn`, установленный в Inbound TLS, - это список дополнительных ALPN во время рукопожатия. Это разные вещи. +::: + +> `path`: string + +Попытка сопоставить HTTP-путь первого пакета, любое значение или пустая строка, по умолчанию пустая строка, если не пустая, то должна начинаться с `/`, h2c не поддерживается. + +Интеллектуальность: при необходимости VLESS попытается просмотреть PATH (не более 55 байт; самый быстрый алгоритм, не выполняет полный разбор HTTP) и в случае успеха выведет в INFO-лог `realPath =`. +Назначение: разделение трафика WebSocket или HTTP-маскировки для других входящих соединений, без лишней обработки, чистая переадресация трафика, теоретически более высокая производительность, чем у Nginx. + +Примечание: **входящее соединение, в котором находится fallbacks, должно быть TCP+TLS**, это необходимо для разделения трафика на другие входящие соединения WS, входящие соединения, на которые разделяется трафик, не нуждаются в настройке TLS. + +> `dest`: string | number + +Определяет, куда перенаправляется TCP-трафик после дешифрования TLS, в настоящее время поддерживаются два типа адресов (это поле является обязательным, иначе запуск невозможен): + +1. TCP, формат `"addr:port"`, где addr поддерживает IPv4, доменное имя, IPv6, если указано доменное имя, TCP-соединение будет установлено напрямую (без использования встроенного DNS). +2. Unix domain socket, формат - абсолютный путь, например, `"/dev/shm/domain.socket"`, в начале можно добавить `@` для обозначения [abstract](https://www.man7.org/linux/man-pages/man7/unix.7.html), `@@` - для обозначения abstract с заполнением. + +Если указан только порт, можно использовать число или строку, например, `80`, `"80"`, обычно указывает на службу http в открытом виде (addr будет дополнен до `"127.0.0.1"`). + +> `xver`: number + +Отправка [PROXY protocol](https://www.haproxy.org/download/2.2/doc/proxy-protocol.txt), специально для передачи реального исходного IP-адреса и порта запроса, заполняется версией 1 или 2, по умолчанию 0, то есть не отправляется. При необходимости рекомендуется указать 1. + +В настоящее время при указании 1 или 2 функциональность полностью идентична, отличается только структура, причем первая может быть распечатана, а вторая - двоичная. Входящие TCP- и WS-соединения Xray уже поддерживают прием PROXY protocol. + +::: warning +Если вы [настраиваете Nginx на прием PROXY protocol](https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/#configuring-nginx-to-accept-the-proxy-protocol), помимо установки `proxy_protocol`, необходимо также установить `set_real_ip_from`, иначе могут возникнуть проблемы. +::: + +### Дополнительные замечания + +- Будет выполнено сопоставление с наиболее точным дочерним элементом, порядок дочерних элементов не имеет значения. Если настроено несколько дочерних элементов с одинаковыми `alpn` и `path`, будет использоваться последний. +- Резервирование и разделение трафика - это переадресация на уровне TCP после дешифрования, а не на уровне HTTP, проверка PATH первого пакета выполняется только при необходимости. +- Вы можете просмотреть больше советов и рекомендаций по использованию Fallbacks: + - [Краткое описание функции Fallbacks](../../document/level-1/fallbacks-lv1) + +## Теория Fallbacks + + diff --git a/docs/ru/config/features/multiple.md b/docs/ru/config/features/multiple.md new file mode 100644 index 000000000..d2fd099c9 --- /dev/null +++ b/docs/ru/config/features/multiple.md @@ -0,0 +1,138 @@ +# Настройка с помощью нескольких файлов + +Программа Xray поддерживает использование нескольких файлов конфигурации. + +Основная цель использования нескольких файлов конфигурации — разделение настроек модулей с разными функциями для удобства управления и обслуживания. + +Эта функция в основном предназначена для обогащения экосистемы Xray. Например, для клиентских GUI обычно реализуются только фиксированные функции, такие как выбор узла, и слишком сложные конфигурации трудно реализовать графически. Можно оставить только один пользовательский каталог конфигурации `confdir` для настройки сложных функций. Для сценариев развертывания сервера достаточно добавить файлы в `confdir` для настройки различных протоколов. + +## Запуск с несколькими файлами + +::: tip +В информации о запуске будет указан каждый считываемый файл конфигурации. Убедитесь, что порядок считывания соответствует ожидаемому. Вы можете контролировать порядок, добавляя префиксы с номерами к именам файлов. Например, `01_имя_файла`, `02_имя_файла`, чем больше число, тем позже файл будет обработан. +::: + +```shell +$ xray run -confdir /etc/xray/confs +``` + +Также можно использовать `Xray.location.confdir` или `Xray_LOCATION_CONFDIR` для указания `confdir`. + +Параметр `-confdir` имеет приоритет над переменной среды. Если параметр указывает на допустимый каталог, значение переменной среды игнорируется. + +## Правила + +### Обычные объекты (`{}`) + +Последующие объекты верхнего уровня перезаписывают или дополняют предыдущие. + +### Массивы (`[]`) + +В конфигурации JSON `inbounds` и `outbounds` имеют структуру массива, для них действуют особые правила: + +- Поиск существующего элемента с тем же `tag` для перезаписи. Если элемент не найден: + - Для `inbounds`: добавляется в конец (порядок элементов в `inbounds` не имеет значения). + - Для `outbounds`: добавляется в начало (по умолчанию используется первый выход в `outbounds`); но если имя файла содержит `tail` (регистр не имеет значения), элемент добавляется в конец. + +## Пример конфигурации + +Предположим, что в папке `confs` есть следующие три файла конфигурации: + +- 01.json + +```json +{ + "log": { + "loglevel": "warning" + }, + "inbounds": [ + { + "tag": "socks", + "protocol": "socks", + "listen": "0.0.0.0", + "port": 8888 + } + ], + "outbounds": [ + { + "tag": "direct", + "protocol": "freedom" + } + ] +} +``` + +- 02.json + +```json +{ + "log": { + "loglevel": "debug" + }, + "inbounds": [ + { + "tag": "socks", + "protocol": "socks", + "listen": "127.0.0.1", + "port": 1080 + } + ], + "outbounds": [ + { + "tag": "block", + "protocol": "blackhole" + } + ] +} +``` + +- 03_tail.json + +```json +{ + "outbounds": [ + { + "tag": "direct2", + "protocol": "freedom" + } + ] +} +``` + +Три конфигурации будут объединены следующим образом: + +```json +{ + "log": { + "loglevel": "debug" // объект верхнего уровня перезаписывает предыдущий + }, + "inbounds": [ + { + "tag": "socks", // перезапись элемента с тем же tag + "protocol": "socks", + "listen": "127.0.0.1", + "port": 1080 + } + ], + "outbounds": [ + { + "tag": "block", // добавлено в начало outbounds + "protocol": "blackhole" + }, + { + "tag": "direct", + "protocol": "freedom" + }, + { + "tag": "direct2", // добавлено в конец, так как имя файла 03_tail.json содержит tail + "protocol": "freedom" + } + ] +} +``` + +::: tip +Вы можете использовать команду `xray run -confdir=./confs -dump` для просмотра объединенной конфигурации. Однако, поскольку ядро использует формат данных protobuf, формат вывода конфигурации для параметра `-dump` будет отличаться. +::: + + diff --git a/docs/ru/config/features/xtls.md b/docs/ru/config/features/xtls.md new file mode 100644 index 000000000..5099ddaba --- /dev/null +++ b/docs/ru/config/features/xtls.md @@ -0,0 +1,7 @@ +# Глубокое погружение в XTLS + +> **XTLS - это оригинальная технология Xray, которая является ключевым фактором высокой производительности Xray.** + + + + diff --git a/docs/ru/config/inbound.md b/docs/ru/config/inbound.md new file mode 100644 index 000000000..b120682ed --- /dev/null +++ b/docs/ru/config/inbound.md @@ -0,0 +1,216 @@ +# Входящие подключения + +Входящие подключения используются для приема данных. Доступные протоколы см. в разделе [Входящие протоколы](./inbounds/). + +## InboundObject + +`InboundObject` соответствует дочернему элементу поля `inbounds` в конфигурационном файле. + +```json +{ + "inbounds": [ + { + "listen": "127.0.0.1", + "port": 1080, + "protocol": "название протокола", + "settings": {}, + "streamSettings": {}, + "tag": "тег", + "sniffing": { + "enabled": true, + "destOverride": ["http", "tls"] + }, + "allocate": { + "strategy": "always", + "refresh": 5, + "concurrency": 3 + } + } + ] +} +``` + +> `listen`: address + +Адрес прослушивания, IP-адрес или Unix domain socket. +Значение по умолчанию - `"0.0.0.0"`, что означает прием подключений на всех сетевых интерфейсах. + +Можно указать любой доступный в системе IP-адрес. + +Поддерживается указание Unix domain socket в формате абсолютного пути, например `"/dev/shm/domain.socket"`. +Можно добавить `@` в начало пути, чтобы использовать [абстрактный сокет](https://www.man7.org/linux/man-pages/man7/unix.7.html), или `@@`, чтобы использовать абстрактный сокет с заполнением. + +При указании Unix domain socket параметры `port` и `allocate` игнорируются. +В настоящее время поддерживаются протоколы VLESS, VMess, Trojan и типы транспорта TCP, WebSocket, HTTP/2, gRPC. + +При указании Unix domain socket можно указать права доступа к сокету, добавив запятую и индикатор прав доступа, например `"/dev/shm/domain.socket,0666"`. +Это может помочь решить проблемы с правами доступа к сокету, которые возникают по умолчанию. + +> `port`: number | "env:variable" | string + +Порт. +Допустимые форматы: + +- Целое число: фактический номер порта. +- Переменная окружения: начинается с `"env:"`, за которым следует имя переменной окружения, например `"env:PORT"`. + Xray будет анализировать эту переменную окружения как строку. +- Строка: может быть числом в виде строки, например `"1234"`, или диапазоном портов, например `"5-10"`, что означает порты с 5 по 10 (6 портов). + Можно использовать запятые для разделения диапазонов, например `11,13,15-17`, что означает порты 11, 13, 15, 16 и 17 (5 портов). + +Если указан только один порт, Xray будет прослушивать входящие подключения на этом порту. +Если указан диапазон портов, то фактическое поведение зависит от настройки `allocate`. + +> `protocol`: string + +Название протокола подключения. +Список доступных протоколов см. в разделе "Входящие подключения" в левой части документации. + +> `settings`: InboundConfigurationObject + +Конкретные настройки зависят от протокола. +См. описание `InboundConfigurationObject` для каждого протокола. + +> `streamSettings`: [StreamSettingsObject](./transport.md#streamsettingsobject) + +Тип транспорта (transport) - это способ взаимодействия текущего узла Xray с другими узлами. + +> `tag`: string + +Тег этого входящего подключения, используемый для идентификации этого подключения в других настройках. + +::: danger +Если это поле не пустое, его значение должно быть **уникальным** среди всех тегов. +::: + +> `sniffing`: [SniffingObject](#sniffingobject) + +Обнаружение трафика в основном используется для прозрачного проксирования и других целей. +Например, типичный сценарий выглядит следующим образом: + +1. Устройство пытается получить доступ к abc.com. + Сначала устройство выполняет DNS-запрос и получает IP-адрес 1.2.3.4 для abc.com. + Затем устройство пытается установить соединение с 1.2.3.4. +2. Если обнаружение трафика не настроено, Xray получает запрос на подключение к 1.2.3.4 и не может использовать доменные правила для маршрутизации и разделения трафика. +3. Если в sniffing включен параметр `enabled`, Xray при обработке трафика этого соединения попытается извлечь доменное имя из данных трафика, т.е. abc.com. +4. Xray заменит 1.2.3.4 на abc.com. + Маршрутизация сможет использовать доменные правила для разделения трафика. + +Так как запрос теперь направляется на abc.com, можно выполнять больше действий, например, повторное разрешение DNS, помимо разделения трафика по доменным правилам. + +Если в sniffing включен параметр `enabled`, Xray также сможет обнаруживать трафик типа bittorrent, а затем можно настроить правила маршрутизации по протоколу, чтобы обрабатывать трафик BT, например, блокировать его на сервере или перенаправлять его на определенный VPS на клиенте. + +> `allocate`: [AllocateObject](#allocateobject) + +Настройки выделения портов при указании нескольких портов. + +### SniffingObject + +```json +{ + "enabled": true, + "destOverride": ["http", "tls", "fakedns"], + "metadataOnly": false, + "domainsExcluded": [], + "routeOnly": false +} +``` + +> `enabled`: true | false + +Включить обнаружение трафика. + +> `destOverride`: \["http" | "tls" | "quic" | "fakedns" | "fakedns+others" \] + +Заменить целевой адрес текущего подключения на указанные типы, если трафик соответствует им. + +`["fakedns+others"]` эквивалентно `["http", "tls", "quic", "fakedns"]`. +Если IP-адрес находится в диапазоне FakeIP, но не найдено соответствие доменному имени, будут использованы `http`, `tls` и `quic`. +Этот параметр действителен только при `metadataOnly` = `false`. + +::: tip +Xray будет использовать доменные имена, обнаруженные с помощью sniffing, только для маршрутизации. +Если вы хотите только обнаруживать доменные имена для маршрутизации, но не хотите изменять целевой адрес (например, при использовании Tor Browser изменение целевого адреса может привести к невозможности подключения), добавьте соответствующие протоколы в этот список и включите `routeOnly`. +::: + +> `metadataOnly`: true | false + +Если этот параметр включен, для обнаружения целевого адреса будут использоваться только метаданные подключения. +В этом случае все снифферы, кроме `fakedns`, будут отключены (включая `fakedns+others`). + +Если этот параметр отключен, для определения целевого адреса будут использоваться не только метаданные, но и данные. +В этом случае клиенту необходимо сначала отправить данные, чтобы прокси-сервер установил соединение. +Это поведение несовместимо с протоколами, которые требуют, чтобы сервер первым отправил сообщение, например, SMTP. + +> `domainsExcluded`: [string] + +Список доменных имен, для которых **не будет** выполняться замена целевого адреса, если они обнаружены с помощью sniffing. + +Поддерживаются прямые доменные имена (точное совпадение) или регулярные выражения, начинающиеся с `regexp:`. + +::: tip +Добавление некоторых доменных имен может решить проблемы с push-уведомлениями iOS, умными устройствами Mijia и голосовым чатом в некоторых играх (Rainbow Six Siege).
+Если вам нужно выяснить причину каких-либо проблем, попробуйте отключить `"sniffing"` или включить `"routeOnly"`. +::: + +```json +"domainsExcluded": [ + "courier.push.apple.com", // Push-уведомления iOS + "Mijia Cloud", // Умные устройства Mijia + "dlg.io.mi.com" +] + +``` + +::: warning +В настоящее время `domainsExcluded` не поддерживает способы сопоставления доменов, аналогичные тем, что используются в маршрутизации. +Этот параметр может быть изменен в будущем, совместимость между версиями не гарантируется. +::: + +> `routeOnly`: true | false + +Использовать обнаруженные доменные имена только для маршрутизации. +Целевой адрес прокси-сервера остается IP-адресом. +Значение по умолчанию - `false`. + +Этот параметр требует, чтобы `destOverride` был включен. + +::: tip +Если вы уверены, что **проксируемое соединение будет правильно разрешено DNS**, то при использовании `routeOnly` и включенном `destOverride` можно установить стратегию сопоставления маршрутов `domainStrategy` в `AsIs`, чтобы реализовать разделение трафика по доменам и IP-адресам без DNS-разрешения. +В этом случае при сопоставлении правил на основе IP-адресов будет использоваться исходный IP-адрес домена. +::: + +### AllocateObject + +```json +{ + "strategy": "always", + "refresh": 5, + "concurrency": 3 +} +``` + +> `strategy`: "always" | "random" + +Стратегия выделения портов. + +- `"always"` - всегда выделять все указанные порты. + Xray будет прослушивать все порты, указанные в `port`. +- `"random"` - случайным образом открывать порты. + Каждые `refresh` минут Xray будет случайным образом выбирать `concurrency` портов из диапазона, указанного в `port`, и прослушивать их. + +> `refresh`: number + +Интервал обновления случайных портов в минутах. +Минимальное значение - `2`, рекомендуемое значение - `5`. +Этот параметр действителен только при `strategy` = `"random"`. + +> `concurrency`: number + +Количество случайных портов. +Минимальное значение - `1`, максимальное значение - треть от диапазона портов, указанного в `port`. +Рекомендуемое значение - `3`. + + + + + diff --git a/docs/ru/config/inbounds/dokodemo.md b/docs/ru/config/inbounds/dokodemo.md new file mode 100644 index 000000000..c36fc819d --- /dev/null +++ b/docs/ru/config/inbounds/dokodemo.md @@ -0,0 +1,77 @@ +# Dokodemo-Door + +Dokodemo door может прослушивать локальный порт и отправлять все данные, поступающие на этот порт, на порт указанного сервера, тем самым реализуя перенаправление портов. + +## InboundConfigurationObject + +```json +{ + "address": "8.8.8.8", + "port": 53, + "network": "tcp", + "timeout": 0, + "followRedirect": false, + "userLevel": 0 +} +``` + +> `address`: address + +Перенаправлять трафик на этот адрес. Может быть IP-адресом, например, `"1.2.3.4"`, или доменным именем, например, `"xray.com"`. Тип данных: строка. + +Если `followRedirect` (см. ниже) равно `true`, то `address` может быть пустым. + +> `port`: number + +Перенаправлять трафик на указанный порт целевого адреса, диапазон \[1, 65535\], тип данных: число. Обязательный параметр. + +> `network`: "tcp" | "udp" | "tcp,udp" + +Поддерживаемые типы сетевых протоколов. Например, если указано `"tcp"`, то будет приниматься только трафик TCP. Значение по умолчанию: `"tcp"`. + +> `timeout`: number + +Ограничение времени простоя соединения. Измеряется в секундах. Значение по умолчанию: `300`. Если во время обработки соединения в течение `timeout` секунд не передается никаких данных, соединение разрывается. + +> `followRedirect`: true | false + +Если значение равно `true`, dokodemo-door будет распознавать данные, перенаправленные iptables, и пересылать их на соответствующий целевой адрес. + +См. настройку `tproxy` в разделе [Конфигурация транспорта](../transport.md#sockoptobject). + +> `userLevel`: number + +Уровень пользователя, для соединения будет использоваться [локальная политика](../policy.md#levelpolicyobject), соответствующая этому уровню пользователя. + +Значение userLevel соответствует значению `level` в разделе [policy](../policy.md#policyobject). Если не указано, используется значение по умолчанию - 0. + +## Использование + +У Dokodemo door есть два основных варианта использования: прозрачное проксирование (см. ниже) и перенаправление портов. + +Иногда некоторые сервисы не поддерживают прямое проксирование, такое как Socks5, а использование Tun или Tproxy является излишним, и эти сервисы взаимодействуют только с одним IP-адресом и одним портом (например: iperf, сервер Minecraft, конечная точка Wireguard), тогда можно использовать произвольную дверь. + +Например, следующая конфигурация (предполагается, что исходящее соединение по умолчанию является допустимым прокси): + +```json +{ + "listen": "127.0.0.1", + "port": 25565, + "protocol": "dokodemo-door", + "settings": { + "address": "mc.hypixel.net", + "port": 25565, + "network": "tcp", + "timeout": 0, + "followRedirect": false, + "userLevel": 0 + }, + "tag": "mc" +} +``` + +В этом случае ядро будет прослушивать 127.0.0.1:25565 и перенаправлять трафик через исходящее соединение по умолчанию на mc.hypixel.net:25565 (сервер MC). При подключении клиента Minecraft к 127.0.0.1:25565 будет осуществлено подключение к серверу Hypixel через прокси. + +## Пример настройки прозрачного прокси + +Эту часть см. в разделе [Руководство по настройке прозрачного прокси (TProxy)](../../document/level-2/tproxy). diff --git a/docs/ru/config/inbounds/http.md b/docs/ru/config/inbounds/http.md new file mode 100644 index 000000000..ac934e0ea --- /dev/null +++ b/docs/ru/config/inbounds/http.md @@ -0,0 +1,81 @@ +# HTTP + +Протокол HTTP. + +::: danger +**Протокол HTTP не обеспечивает шифрования передачи данных, поэтому он не подходит для передачи данных через общедоступные сети и более уязвим для использования в качестве ботнета.** +::: + +Использование входящих соединений `http` более целесообразно в локальной сети или локальной среде, где он может быть использован для прослушивания входящих подключений и предоставления локальных сервисов другим программам. + +::: tip СОВЕТ 1 +`http proxy` может проксировать только протокол tcp, протоколы семейства udp не поддерживаются. +::: + +::: tip СОВЕТ 2 +Используйте следующие переменные среды в Linux, чтобы использовать глобальный HTTP-прокси в текущем сеансе (эта настройка поддерживается многими программами, но не всеми). + +- `export http_proxy=http://127.0.0.1:8080/` (замените адрес на адрес вашего настроенного входящего HTTP-прокси) +- `export https_proxy=$http_proxy` + ::: + +## InboundConfigurationObject + +```json +{ + "timeout": 0, + "accounts": [ + { + "user": "my-username", + "pass": "my-password" + } + ], + "allowTransparent": false, + "userLevel": 0 +} +``` + +> `timeout`: number + +Ограничение времени простоя соединения. Измеряется в секундах. Значение по умолчанию: `300`, значение 0 означает отсутствие ограничения времени. + +Если в течение `timeout` секунд во время обработки соединения не было передано никаких данных, соединение разрывается. + +> `accounts`: \[[AccountObject](#accountobject)\] + +Массив, каждый элемент которого представляет собой учетную запись пользователя. Значение по умолчанию: пустой массив. + +Если `accounts` не пуст, HTTP-прокси будет выполнять проверку подлинности Basic Authentication для входящих соединений. + +> `allowTransparent`: true | false + +Если установлено значение `true`, будут перенаправляться все HTTP-запросы, а не только прокси-запросы. + +::: tip +Неправильная настройка этой опции может привести к бесконечному циклу. +::: + +> `userLevel`: number + +Уровень пользователя, для подключения будет использоваться [локальная политика](../policy.md#levelpolicyobject), соответствующая этому уровню пользователя. + +Значение userLevel соответствует значению `level` в разделе [policy](../policy.md#policyobject). Если не указано, используется значение по умолчанию - 0. + +### AccountObject + +```json +{ + "user": "my-username", + "pass": "my-password" +} +``` + +> `user`: string + +Имя пользователя, тип данных: строка. Обязательный параметр. + +> `pass`: string + +Пароль, тип данных: строка. Обязательный параметр. + + diff --git a/docs/ru/config/inbounds/shadowsocks.md b/docs/ru/config/inbounds/shadowsocks.md new file mode 100644 index 000000000..a34dd48d8 --- /dev/null +++ b/docs/ru/config/inbounds/shadowsocks.md @@ -0,0 +1,110 @@ +# Shadowsocks + +Протокол [Shadowsocks](https://ru.wikipedia.org/wiki/Shadowsocks), совместимый с большинством других реализаций. + +Текущая совместимость: + +- Поддерживает пересылку пакетов TCP и UDP, при этом UDP можно выборочно отключить; +- Рекомендуемые методы шифрования: + - 2022-blake3-aes-128-gcm + - 2022-blake3-aes-256-gcm + - 2022-blake3-chacha20-poly1305 +- Другие методы шифрования: + - aes-256-gcm + - aes-128-gcm + - chacha20-poly1305 или chacha20-ietf-poly1305 + - xchacha20-poly1305 или xchacha20-ietf-poly1305 + - none или plain + +Новый формат протокола Shadowsocks 2022 повышает производительность и обеспечивает полную защиту от повторов, решая следующие проблемы безопасности старого протокола: + +- [Серьезные уязвимости в шифровании Shadowsocks AEAD, которые не могут гарантировать целостность содержимого](https://github.com/shadowsocks/shadowsocks-org/issues/183) +- Возрастающий коэффициент ложных срабатываний исходного фильтра повторов TCP с течением времени +- Отсутствие защиты от повторов UDP +- Поведение TCP, которое можно использовать для активного зондирования + +::: danger +При использовании метода шифрования "none" трафик передается в открытом виде. В целях безопасности не используйте этот метод в общедоступных сетях. +::: + +## InboundConfigurationObject + +```json +{ + "settings": { + "network": "tcp,udp", + "method": "aes-256-gcm", + "password": "114514", + "level": 0, + "email": "love@xray.com", + "clients": [ + { + "password": "1919810", + "method": "aes-128-gcm" + } + ] + } +} +``` + +> `network`: "tcp" | "udp" | "tcp,udp" + +Поддерживаемые типы сетевых протоколов. Например, если указано `"tcp"`, будет приниматься только трафик TCP. Значение по умолчанию: `"tcp"`. + +> `method`: string + +Метод шифрования, доступные варианты см. выше. + +> `password`: string + +Обязательный параметр. + +- Shadowsocks 2022 + +Используется предварительный общий ключ, аналогичный WireGuard, в качестве пароля. + +Используйте команду `openssl rand -base64 <длина>` для генерации ключа, совместимого с shadowsocks-rust, длина зависит от используемого метода шифрования. + +| Метод шифрования | Длина ключа | +| ------------------------------------ | ----------: | +| 2022-blake3-aes-128-gcm | 16 | +| 2022-blake3-aes-256-gcm | 32 | +| 2022-blake3-chacha20-poly1305 | 32 | + +В реализации Go всегда работают 32-битные ключи. + +- Другие методы шифрования + +Любая строка. Длина пароля не ограничена, но короткие пароли более уязвимы для взлома, рекомендуется использовать пароли длиной 16 символов или более. + +> `level`: number + +Уровень пользователя, для соединения будет использоваться [локальная политика](../policy.md#levelpolicyobject), соответствующая этому уровню пользователя. +Значение `level` соответствует значению `level` в разделе [policy](../policy.md#levelpolicyobject). Если не указано, используется значение по умолчанию - 0. + +> `email`: string + +Адрес электронной почты пользователя, используется для разделения трафика разных пользователей (журналы, статистика). + +## ClientObject + +```json +{ + "password": "1919810", + "method": "aes-256-gcm", + "level": 0, + "email": "love@xray.com" +} +``` + +Наличие этой опции означает включение многопользовательского режима. + +Если `method` в InboundConfigurationObject не является опцией SS2022, можно указать `"method"` для каждого пользователя. (`"method"` также поддерживает только опции, не относящиеся к SS2022) и `"password"` (при этом `"password"`, установленный в InboundConfigurationObject, будет игнорироваться). + +Если `method` в InboundConfigurationObject является опцией SS2022, то из соображений безопасности больше не поддерживается установка `"method"` для отдельных пользователей, используется единый `"method"`, указанный в InboundConfigurationObject. + +Обратите внимание, что SS2022, в отличие от старого SS, не игнорирует `"password"` верхнего уровня, правильный способ записи пароля клиента: `ServerPassword:UserPassword`. Например: `"password": "114514:1919810"` + +Остальные опции имеют то же значение, что и в InboundConfigurationObject. + + diff --git a/docs/ru/config/inbounds/socks.md b/docs/ru/config/inbounds/socks.md new file mode 100644 index 000000000..d3ae308a0 --- /dev/null +++ b/docs/ru/config/inbounds/socks.md @@ -0,0 +1,77 @@ +# Socks + +Стандартная реализация протокола Socks, совместимая с [Socks 4](http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol), [Socks 4a](https://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4A.protocol) и Socks 5. + +::: danger +**Протокол Socks не обеспечивает шифрование передачи данных, поэтому он не подходит для передачи данных через общедоступные сети.** +::: + +Использование входящих соединений `SOCKS` более целесообразно в локальной сети или локальной среде, где он может быть использован для прослушивания входящих подключений и предоставления локальных сервисов другим программам. + +## InboundConfigurationObject + +```json +{ + "auth": "noauth", + "accounts": [ + { + "user": "my-username", + "pass": "my-password" + } + ], + "udp": false, + "ip": "127.0.0.1", + "userLevel": 0 +} +``` + +> `auth`: "noauth" | "password" + +Метод аутентификации протокола Socks, поддерживаются режимы `"noauth"` (анонимный) и `"password"` (с использованием пароля пользователя). + +Значение по умолчанию: `"noauth"`. + +> `accounts`: \[ [AccountObject](#accountobject) \] + +Массив, каждый элемент которого представляет собой учетную запись пользователя. + +Этот параметр действителен только если `auth` установлен в значение `password`. + +Значение по умолчанию: пустой массив. + +> `udp`: true | false + +Включает или отключает поддержку протокола UDP. + +Значение по умолчанию: `false`. + +> `ip`: address + +Если UDP включен, Xray должен знать IP-адрес локального хоста. + +Значение по умолчанию: `"127.0.0.1"`. + +> `userLevel`: number + +Уровень пользователя, для соединения будет использоваться [локальная политика](../policy.md#levelpolicyobject), соответствующая этому уровню пользователя. + +Значение userLevel соответствует значению `level` в разделе [policy](../policy.md#policyobject). Если не указано, используется значение по умолчанию - 0. + +### AccountObject + +```json +{ + "user": "my-username", + "pass": "my-password" +} +``` + +> `user`: string + +Имя пользователя, тип данных: строка. Обязательный параметр. + +> `pass`: string + +Пароль, тип данных: строка. Обязательный параметр. + + diff --git a/docs/ru/config/inbounds/trojan.md b/docs/ru/config/inbounds/trojan.md new file mode 100644 index 000000000..af5d90460 --- /dev/null +++ b/docs/ru/config/inbounds/trojan.md @@ -0,0 +1,74 @@ +# Trojan + +Протокол [Trojan](https://trojan-gfw.github.io/trojan/protocol). + +::: danger +Trojan предназначен для работы в правильно настроенном зашифрованном TLS-туннеле. +::: + +## InboundConfigurationObject + +```json +{ + "clients": [ + { + "password": "password", + "email": "love@xray.com", + "level": 0 + } + ], + "fallbacks": [ + { + "dest": 80 + } + ] +} +``` + +> `clients`: \[ [ClientObject](#clientobject) \] + +Массив, представляющий группу пользователей, одобренных сервером. + +Каждый элемент в массиве - это пользователь [ClientObject](#clientobject). + +> `fallbacks`: \[ [FallbackObject](../features/fallback.md) \] + +Массив, содержащий ряд конфигураций fallback-маршрутизации (необязательно). +Подробную информацию о настройке fallbacks см. в разделе [FallbackObject](../features/fallback.md#fallbacks-конфигурация). + +::: tip +Trojan в Xray имеет полную поддержку fallbacks, конфигурация идентична. +Условия запуска fallback также аналогичны VLESS: длина первого пакета < 58 или 57-й байт не равен `\r` (поскольку Trojan не имеет версии протокола) или ошибка аутентификации. +::: + +### ClientObject + +```json +{ + "password": "password", + "email": "love@xray.com", + "level": 0 +} +``` + +> `password`: string + +Обязательный параметр, любая строка. + +> `email`: string + +Адрес электронной почты, необязательный параметр, используется для идентификации пользователя. + +::: danger +Если существует несколько объектов ClientObject, убедитесь, что адреса электронной почты не дублируются. +::: + +> `level`: number + +Уровень пользователя, для соединения будет использоваться [локальная политика](../policy.md#levelpolicyobject), соответствующая этому уровню пользователя. + +Значение userLevel соответствует значению `level` в разделе [policy](../policy.md#policyobject). Если не указано, используется значение по умолчанию - 0. + + + + diff --git a/docs/ru/config/inbounds/vless.md b/docs/ru/config/inbounds/vless.md new file mode 100644 index 000000000..8c4dfcb67 --- /dev/null +++ b/docs/ru/config/inbounds/vless.md @@ -0,0 +1,97 @@ +# VLESS + +::: danger +VLESS не предусматривает встроенного шифрования, поэтому обязательным условием для его использования является наличие надежного канала, такого как TLS или REALITY. +::: + +VLESS - это легкий транспортный протокол без сохранения состояния, который разделен на входящую и исходящую части и может служить мостом между клиентом и сервером Xray. + +В отличие от [VMess](./vmess.md), VLESS не зависит от системного времени, аутентификация также осуществляется с помощью UUID. + +## InboundConfigurationObject + +```json +{ + "clients": [ + { + "id": "5783a3e7-e373-51cd-8642-c83782b807c5", + "level": 0, + "email": "love@xray.com", + "flow": "xtls-rprx-vision" + } + ], + "decryption": "none", + "fallbacks": [ + { + "dest": 80 + } + ] +} +``` + +> `clients`: \[ [ClientObject](#clientobject) \] + +Массив, представляющий группу пользователей, одобренных сервером. + +Каждый элемент является пользователем [ClientObject](#clientobject). + +> `decryption`: "none" + +На данный момент необходимо указать `"none"`, значение не может быть пустым. +Если значение decryption установлено неверно, при использовании Xray или -test будет выдано сообщение об ошибке. + +Обратите внимание, что здесь используется decryption, на том же уровне, что и clients. +Расположение decryption и encryption в протоколе vmess отличается, потому что, если используется дополнительный уровень шифрования, сервер должен сначала расшифровать данные, чтобы узнать, какой это пользователь. + +> `fallbacks`: \[ [FallbackObject](../features/fallback.md) \] + +Массив, содержащий ряд конфигураций fallback-маршрутизации (необязательно). +Подробную информацию о настройке fallbacks см. в разделе [FallbackObject](../features/fallback.md#fallbacks-конфигурация). + +### ClientObject + +```json +{ + "id": "5783a3e7-e373-51cd-8642-c83782b807c5", + "level": 0, + "email": "love@xray.com", + "flow": "xtls-rprx-vision" +} +``` + +> `id`: string + +Идентификатор пользователя VLESS, может быть любой строкой длиной менее 30 байт или допустимым UUID. +Пользовательская строка и ее UUID-отображение эквивалентны, это означает, что вы можете использовать следующие способы записи id в файле конфигурации для идентификации одного и того же пользователя: + +- Напишите `"id": "Я люблю арбуз учителя 1314"`, +- Или напишите `"id": "5783a3e7-e373-51cd-8642-c83782b807c5"` (этот UUID является UUID-отображением строки "Я люблю арбуз учителя 1314") + +Стандарт сопоставления описан в [VLESS UUID Mapping Standard: Mapping Custom Strings to a UUIDv5](https://github.com/XTLS/Xray-core/issues/158). + +Вы можете использовать команду `xray uuid -i "Пользовательская строка"` для генерации UUID, соответствующего пользовательской строке. + +> Вы также можете использовать команду `xray uuid` для генерации случайного UUID. + +> `level`: number + +Уровень пользователя, для подключения будет использоваться [локальная политика](../policy.md#levelpolicyobject), соответствующая этому уровню пользователя. + +Значение level соответствует значению `level` в разделе [policy](../policy.md#policyobject). Если не указано, используется значение по умолчанию - 0. + +> `email`: string + +Адрес электронной почты пользователя, используется для разделения трафика разных пользователей (отображается в журналах, статистике). + +> `flow`: string + +Режим управления потоком, используется для выбора алгоритма XTLS. + +В настоящее время для входящего протокола доступны следующие режимы управления потоком: + +- Отсутствует `flow` или пустая строка: используется обычный TLS-прокси +- `xtls-rprx-vision`: используется новый режим XTLS, включает случайное заполнение внутреннего рукопожатия + +Кроме того, в настоящее время XTLS поддерживает только три транспортных протокола: TCP, mKCP и DomainSocket. + + diff --git a/docs/ru/config/inbounds/vmess.md b/docs/ru/config/inbounds/vmess.md new file mode 100644 index 000000000..a7e284f02 --- /dev/null +++ b/docs/ru/config/inbounds/vmess.md @@ -0,0 +1,107 @@ +# VMess + +[VMess](../../development/protocols/vmess.md) - это зашифрованный транспортный протокол, который обычно используется в качестве моста между клиентами и серверами Xray. + +::: danger +VMess полагается на системное время. Убедитесь, что системное время UTC, используемое Xray, находится в пределах 120 секунд от фактического времени, независимо от часового пояса. В системах Linux вы можете установить службу `ntp` для автоматической синхронизации системного времени. +::: + +## InboundConfigurationObject + +```json +{ + "clients": [ + { + "id": "5783a3e7-e373-51cd-8642-c83782b807c5", + "level": 0, + "email": "love@xray.com" + } + ], + "default": { + "level": 0 + }, + "detour": { + "to": "tag_to_detour" + } +} +``` + +> `clients`: \[ [ClientObject](#clientobject) \] + +Массив, представляющий группу пользователей, одобренных сервером. + +Каждый элемент в массиве - это пользователь [ClientObject](#clientobject). + +Когда эта конфигурация используется для динамических портов, Xray будет автоматически создавать пользователей. + +> `detour`: [DetourObject](#detourobject) + +Указывает, что для соответствующего исходящего протокола следует использовать другой сервер. + +> `default`: [DefaultObject](#defaultobject) + +Необязательно. Конфигурация по умолчанию для клиентов. Действует только при использовании с `detour`. + +### ClientObject + +```json +{ + "id": "5783a3e7-e373-51cd-8642-c83782b807c5", + "level": 0, + "email": "love@xray.com" +} +``` + +> `id`: string + +Идентификатор пользователя VMess. Это может быть любая строка длиной менее 30 байт или допустимый UUID. + +::: tip +Пользовательские строки и соответствующие им UUID эквивалентны, что означает, что вы можете использовать любой из следующих вариантов в файле конфигурации для идентификации одного и того же пользователя: + +- `"id": "Я люблю арбуз учителя 1314"` +- `"id": "5783a3e7-e373-51cd-8642-c83782b807c5"` (этот UUID является сопоставлением строки "Я люблю арбуз учителя 1314") + +Стандарт сопоставления описан в [VLESS UUID Mapping Standard: Mapping a Custom String to a UUIDv5](https://github.com/XTLS/Xray-core/issues/158). + +Вы можете использовать команду `xray uuid -i "пользовательская строка"` для создания UUID, соответствующего пользовательской строке. + +Вы также можете использовать команду `xray uuid` для создания случайного UUID. ::: + +> `level`: number + +Уровень пользователя, который будет использоваться соединением для определения соответствующей [локальной политики](../policy.md#levelpolicyobject). + +Значение `level` соответствует значению `level` в [policy](../policy.md#policyobject). Если не указано, используется значение по умолчанию - 0. + +> `email`: string + +Адрес электронной почты пользователя, используемый для разделения трафика от разных пользователей. + +### DetourObject + +```json +{ + "to": "tag_to_detour" +} +``` + +> `to`: string + +`Тег` входящего соединения, определяющий входящее соединение, использующее протокол VMess. + +### DefaultObject + +```json +{ + "level": 0 +} +``` + +> `level`: number + +Уровень пользователя, который будет использоваться соединением для определения соответствующей [локальной политики](../policy.md#levelpolicyobject). + +Значение `level` соответствует значению `level` в [policy](../policy.md#policyobject). Если не указано, используется значение по умолчанию - 0. + + diff --git a/docs/ru/config/log.md b/docs/ru/config/log.md new file mode 100644 index 000000000..6d1b5f3c1 --- /dev/null +++ b/docs/ru/config/log.md @@ -0,0 +1,55 @@ +# Настройка журнала + +Настройка журнала управляет тем, как Xray выводит журналы. + +Xray имеет два типа журналов: журнал доступа и журнал ошибок. +Вы можете настроить способ вывода каждого типа журнала отдельно. + +## LogObject + +LogObject соответствует полю `log` в конфигурационном файле. + +```json +{ + "log": { + "access": "путь к файлу", + "error": "путь к файлу", + "loglevel": "warning", + "dnsLog": false + } +} +``` + +> `access`: string + +Путь к файлу журнала доступа. +Значение должно быть допустимым путем к файлу, например `"/var/log/Xray/access.log"` (Linux) или `"C:\\Temp\\Xray\\_access.log"` (Windows). +Если этот параметр не указан или имеет пустое значение, журнал выводится в stdout. + +- Специальное значение `none` отключает журнал доступа. + +> `error`: string + +Путь к файлу журнала ошибок. +Значение должно быть допустимым путем к файлу, например `"/var/log/Xray/error.log"` (Linux) или `"C:\\Temp\\Xray\\_error.log"` (Windows). +Если этот параметр не указан или имеет пустое значение, журнал выводится в stdout. + +- Специальное значение `none` отключает журнал ошибок. + +> `loglevel`: "debug" | "info" | "warning" | "error" | "none" + +Уровень журнала ошибок, указывающий, какую информацию следует записывать в журнал ошибок. +Значение по умолчанию - `"warning"`. + +- `"debug"`: информация, используемая при отладке программы. + Включает всю информацию уровня `"info"`. +- `"info"`: информация о состоянии во время выполнения и т.д., не влияющая на нормальную работу. + Включает всю информацию уровня `"warning"`. +- `"warning"`: информация, выводимая при возникновении проблем, не влияющих на нормальную работу, но которые могут повлиять на работу пользователя. + Включает всю информацию уровня `"error"`. +- `"error"`: Xray столкнулся с проблемой, которая не позволяет ему работать нормально, и ее необходимо немедленно решить. +- `"none"`: не записывать ничего. + +> `dnsLog`: bool + +Включить ведение журнала DNS-запросов, например: `DOH//doh.server got answer: domain.com -> [ip1, ip2] 2.333ms`. \ No newline at end of file diff --git a/docs/ru/config/metrics.md b/docs/ru/config/metrics.md new file mode 100644 index 000000000..20dc34c63 --- /dev/null +++ b/docs/ru/config/metrics.md @@ -0,0 +1,280 @@ +# Метрики + +Более простой (и, надеюсь, лучший) способ экспорта статистики. + +## Связанные настройки + +Можно добавить входящее подключение `metrics` в раздел `inbounds`: + +```json + "inbounds": [ + { + "listen": "127.0.0.1", + "port": 11111, + "protocol": "dokodemo-door", + "settings": { + "address": "127.0.0.1" + }, + "tag": "metrics_in" + } + ] +``` + +Добавьте правило маршрутизации для входящего подключения `metrics` в раздел `routing`: + +```json + "routing": { + "rules": [ + { + "type": "field", + "inboundTag": [ + "metrics_in" + ], + "outboundTag": "metrics_out" + } + ] + } +``` + +Добавьте `metrics` в основные настройки: + +```json + "metrics": { + "tag": "metrics_out" + } +``` + +## Использование + +### pprof + +Откройте `http://127.0.0.1:11111/debug/pprof/` или используйте утилиту `go tool pprof`, чтобы начать профилирование или просмотреть запущенные горутины. + +### expvars + +Откройте `http://127.0.0.1:11111/debug/vars`. + +Экспортируемые переменные включают: +* `stats` - статистика по входящим, исходящим подключениям и пользователям. +* `observatory` - результаты мониторинга. + +Например, с помощью [luci-app-xray](https://github.com/yichya/luci-app-xray) вы, скорее всего, получите результат, подобный этому (стандартные переменные expvar, такие как `cmdline` и `memstats`, опущены): + +
Показать
+ +```json +{ + "observatory": { + "tcp_outbound": { + "alive": true, + "delay": 782, + "outbound_tag": "tcp_outbound", + "last_seen_time": 1648477189, + "last_try_time": 1648477189 + }, + "udp_outbound": { + "alive": true, + "delay": 779, + "outbound_tag": "udp_outbound", + "last_seen_time": 1648477191, + "last_try_time": 1648477191 + } + }, + "stats": { + "inbound": { + "api": { + "downlink": 0, + "uplink": 0 + }, + "dns_server_inbound_5300": { + "downlink": 14286, + "uplink": 5857 + }, + "http_inbound": { + "downlink": 74460, + "uplink": 10231 + }, + "https_inbound": { + "downlink": 0, + "uplink": 0 + }, + "metrics": { + "downlink": 6327, + "uplink": 1347 + }, + "socks_inbound": { + "downlink": 19925615, + "uplink": 5512 + }, + "tproxy_tcp_inbound": { + "downlink": 4739161, + "uplink": 1568869 + }, + "tproxy_udp_inbound": { + "downlink": 0, + "uplink": 2608142 + } + }, + "outbound": { + "blackhole_outbound": { + "downlink": 0, + "uplink": 0 + }, + "direct": { + "downlink": 97714548, + "uplink": 3234617 + }, + "dns_server_outbound": { + "downlink": 7116, + "uplink": 2229 + }, + "manual_tproxy_outbound_tcp_1": { + "downlink": 0, + "uplink": 0 + }, + "manual_tproxy_outbound_udp_1": { + "downlink": 0, + "uplink": 0 + }, + "tcp_outbound": { + "downlink": 23873238, + "uplink": 1049595 + }, + "udp_outbound": { + "downlink": 639282, + "uplink": 74634 + } + }, + "user": {} + } +} +``` +
+ +Чтобы лучше визуализировать эти данные, можно использовать [Netdata](https://github.com/netdata/netdata) (с плагином python.d): + +1. Отредактируйте соответствующий файл конфигурации (`sudo /etc/netdata/edit-config python.d/go_expvar.conf`). +2. Используйте следующий файл конфигурации в качестве примера: + +
Показать
+ +``` +xray: + name: 'xray' + update_every: 2 + url: 'http://127.0.0.1:11111/debug/vars' + collect_memstats: false + extra_charts: + - id: 'inbounds' + options: + name: 'inbounds' + title: 'Xray System Inbounds' + units: bytes + family: xray + context: xray.inbounds + chart_type: line + lines: + - expvar_key: stats.inbound.tproxy_tcp_inbound.downlink + id: 'tcp.downlink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.inbound.tproxy_udp_inbound.downlink + id: 'udp.downlink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.inbound.http_inbound.downlink + id: 'http.downlink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.inbound.https_inbound.downlink + id: 'https.downlink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.inbound.socks_inbound.downlink + id: 'socks.downlink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.inbound.tproxy_tcp_inbound.uplink + id: 'tcp.uplink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.inbound.tproxy_udp_inbound.uplink + id: 'udp.uplink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.inbound.http_inbound.uplink + id: 'http.uplink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.inbound.https_inbound.uplink + id: 'https.uplink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.inbound.socks_inbound.uplink + id: 'socks.uplink' + algorithm: incremental + expvar_type: int + - id: 'outbounds' + options: + name: 'outbounds' + title: 'Xray System Outbounds' + units: bytes + family: xray + context: xray.outbounds + chart_type: line + lines: + - expvar_key: stats.outbound.tcp_outbound.downlink + id: 'tcp.downlink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.outbound.udp_outbound.downlink + id: 'udp.downlink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.outbound.direct.downlink + id: 'direct.downlink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.outbound.tcp_outbound.uplink + id: 'tcp.uplink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.outbound.udp_outbound.uplink + id: 'udp.uplink' + algorithm: incremental + expvar_type: int + - expvar_key: stats.outbound.direct.uplink + id: 'direct.uplink' + algorithm: incremental + expvar_type: int + - id: 'observatory' + options: + name: 'observatory' + title: 'Xray Observatory Metrics' + units: milliseconds + family: xray + context: xray.observatory + chart_type: line + lines: + - expvar_key: observatory.tcp_outbound.delay + id: tcp + expvar_type: int + - expvar_key: observatory.udp_outbound.delay + id: udp + expvar_type: int +``` +
+ +И вы получите красивый график, подобный этому: + +![160428235-2988bf69-5d6c-41ec-8267-1bd512508aa8](https://github.com/chika0801/Xray-docs-next/assets/88967758/455e88ce-ced2-4593-a9fa-425bb293215b) + +### Дополнительно + +Возможно, лучше использовать пустой объект `stats` в конфигурационном файле, чем добавлять `metrics`? + +**Изменение:** удалены настройки, связанные с Prometheus, и добавлено использование expvars. + + + + diff --git a/docs/ru/config/observatory.md b/docs/ru/config/observatory.md new file mode 100644 index 000000000..c85c341e1 --- /dev/null +++ b/docs/ru/config/observatory.md @@ -0,0 +1,100 @@ +# Мониторинг подключений + +Компонент мониторинга подключений использует HTTP-пинги для проверки состояния подключения исходящих прокси. Результаты мониторинга могут использоваться другими компонентами, например, балансировщиком нагрузки. +В настоящее время доступны два режима: [observatory](#observatoryobject) (фоновый мониторинг подключений) и [burstObservatory](#burstobservatoryobject) (мониторинг параллельных подключений). +Выберите один из них в соответствии с вашими потребностями. + +## ObservatoryObject + +```json +{ + "subjectSelector":[ + "outbound" + ], + "probeUrl": "https://www.google.com/generate_204", + "probeInterval": "10s", + "enableConcurrency": false +} +``` + +> `subjectSelector`: \[ string \] + +Массив строк, каждый элемент которого будет использоваться для сопоставления с префиксом тега исходящего подключения. +Например, для следующих тегов исходящих подключений: `[ "a", "ab", "c", "ba" ]`, `"subjectSelector": ["a"]` будет соответствовать `[ "a", "ab" ]`. + +> `probeUrl`: string + +URL-адрес, используемый для проверки состояния подключения исходящего прокси. + +> `probeInterval`: string + +Интервал между проверками. +Формат времени: число + единица измерения, например `"10s"`, `"2h45m"`. +Поддерживаемые единицы измерения: `ns`, `us`, `ms`, `s`, `m`, `h` (наносекунды, микросекунды, миллисекунды, секунды, минуты, часы). + +> `enableConcurrency`: true | false + +- `true` - проверять все соответствующие исходящие прокси одновременно, после чего сделать паузу на время, указанное в `probeInterval`. +- `false` - проверять соответствующие исходящие прокси по очереди, делая паузу на время, указанное в `probeInterval`, после проверки каждого прокси. + +## BurstObservatoryObject + +```json +{ + "subjectSelector":[ + "outbound" + ], + "pingConfig": {} +} +``` + +> `subjectSelector`: \[ string \] + +Массив строк, каждый элемент которого будет использоваться для сопоставления с префиксом тега исходящего подключения. +Например, для следующих тегов исходящих подключений: `[ "a", "ab", "c", "ba" ]`, `"subjectSelector": ["a"]` будет соответствовать `[ "a", "ab" ]`. + +> `pingConfig`: [PingConfigObject](#PingConfigObject) + + +### PingConfigObject + +```json +{ + "destination": "https://connectivitycheck.gstatic.com/generate_204", + "connectivity": "", + "interval": "1h", + "sampling": 3, + "timeout": "30s" +} +``` + +> `destination`: string + +URL-адрес, используемый для проверки состояния подключения исходящего прокси. +Этот URL-адрес должен возвращать код состояния HTTP 204. + +> `connectivity`: string + +URL-адрес, используемый для проверки подключения к локальной сети. +Пустая строка означает, что проверка подключения к локальной сети не выполняется. + +> `interval`: string + +Проверить все соответствующие исходящие прокси в течение указанного времени, отправляя `sampling` + 1 запросов на каждый прокси. +Формат времени: число + единица измерения, например `"10s"`, `"2h45m"`. +Поддерживаемые единицы измерения: `ns`, `us`, `ms`, `s`, `m`, `h` (наносекунды, микросекунды, миллисекунды, секунды, минуты, часы). + +> `sampling`: number + +Количество последних результатов проверок, которые нужно сохранить. + +> `timeout`: string + +Время ожидания ответа при проверке. +Формат такой же, как и у `interval`. + + + + + + diff --git a/docs/ru/config/outbound.md b/docs/ru/config/outbound.md new file mode 100644 index 000000000..8814082f1 --- /dev/null +++ b/docs/ru/config/outbound.md @@ -0,0 +1,163 @@ +# Исходящие подключения + +Исходящие подключения используются для отправки данных. Доступные протоколы см. в разделе [Исходящие протоколы](./outbounds/). + +## OutboundObject + +`OutboundObject` соответствует дочернему элементу поля `outbounds` в конфигурационном файле. + +::: tip +Первый элемент в списке используется как основной исходящий узел. +Если совпадений с правилами маршрутизации нет или ни одно правило не сработало, трафик отправляется через основной исходящий узел. +::: + +```json +{ + "outbounds": [ + { + "sendThrough": "0.0.0.0", + "protocol": "название протокола", + "settings": {}, + "tag": "тег", + "streamSettings": {}, + "proxySettings": { + "tag": "another-outbound-tag" + }, + "mux": {} + } + ] +} +``` + +> `sendThrough`: address + +IP-адрес, используемый для отправки данных. +Этот параметр используется, если на хосте настроено несколько IP-адресов. +Значение по умолчанию - `"0.0.0.0"`. + +Разрешается указывать блок IPv6 CIDR (например, `114:514:1919:810::/64`). +Xray будет использовать случайный IP-адрес из этого блока для установления исходящих соединений. +Необходимо правильно настроить сетевое подключение, таблицу маршрутизации и параметры ядра, чтобы разрешить Xray привязываться к любому IP-адресу из этого блока. + +> `protocol`: string + +Название протокола подключения. +Список доступных протоколов см. в разделе "Исходящие подключения" в левой части документации. + +> `settings`: OutboundConfigurationObject + +Конкретные настройки зависят от протокола. +См. описание `OutboundConfigurationObject` для каждого протокола. + +> `tag`: string + +Тег этого исходящего подключения, используемый для идентификации этого подключения в других настройках. + +::: danger +Если это поле не пустое, его значение должно быть **уникальным** среди всех тегов. +::: + +> `streamSettings`: [StreamSettingsObject](./transport.md#streamsettingsobject) + +Тип транспорта (transport) - это способ взаимодействия текущего узла Xray с другими узлами. + +> `proxySettings`: [ProxySettingsObject](#proxysettingsobject) + +Настройки исходящего прокси. +Если исходящий прокси включен, параметр `streamSettings` этого исходящего подключения игнорируется. + +> `mux`: [MuxObject](#muxobject) + +Настройки Mux. + +### ProxySettingsObject + +```json +{ + "tag": "another-outbound-tag" +} +``` + +> `tag`: string + +При указании тега другого исходящего подключения данные, отправляемые этим исходящим подключением, будут перенаправлены через указанное исходящее подключение. + +::: danger +Этот способ пересылки **не использует** транспортный уровень. +Если вам нужна пересылка с использованием транспортного уровня, используйте [SockOpt.dialerProxy](./transport.md#sockoptobject). +::: + +::: danger +Этот параметр несовместим с SockOpt.dialerProxy. +::: + +::: tip +Совместим с настройкой `transportLayer` в v2fly/v2ray-core [transportLayer](https://www.v2fly.org/config/outbounds.html#proxysettingsobject). +::: + +### MuxObject + +Функция Mux позволяет мультиплексировать несколько TCP-соединений по одному TCP-соединению. +Подробнее см. [Mux.Cool](../../development/protocols/muxcool). +Mux предназначен для сокращения задержек при установлении TCP-соединений, а не для увеличения пропускной способности. +Использование Mux при просмотре видео, загрузке файлов или тестировании скорости обычно приводит к обратным результатам. +Mux нужно включать только на клиенте, сервер автоматически адаптируется. + +`MuxObject` соответствует полю `mux` в `OutboundObject`. + +```json +{ + "enabled": true, + "concurrency": 8, + "xudpConcurrency": 16, + "xudpProxyUDP443": "reject" +} +``` + +> `enabled`: true | false + +Включить пересылку запросов через Mux. +Значение по умолчанию - `false`. + +> `concurrency`: number + +Максимальное количество одновременных подключений. +Минимальное значение - `1`, максимальное значение - `1024`. +Если этот параметр опущен или равен `0`, используется значение `8`. + +Это значение определяет максимальное количество дочерних соединений, которые могут быть мультиплексированы по одному TCP-соединению. +Например, если `concurrency` равен `8`, то при отправке 8 TCP-запросов клиентом Xray создаст только одно фактическое TCP-соединение, и все 8 запросов клиента будут передаваться по этому соединению. + +::: tip +Если указать отрицательное значение, например `-1`, трафик TCP не будет проходить через Mux. +::: + +> `xudpConcurrency`: number + +Использовать новый агрегированный туннель XUDP (т.е. другое Mux-соединение) для проксирования UDP-трафика. +Укажите максимальное количество одновременных дочерних UoT-подключений. +Минимальное значение - `1`, максимальное значение - `1024`. +Если этот параметр опущен или равен `0`, UDP-трафик будет использовать тот же путь, что и TCP-трафик (традиционное поведение). + +::: tip +Если указать отрицательное значение, например `-1`, трафик UDP не будет проходить через Mux. +Будет использоваться исходный способ передачи UDP-трафика для данного протокола прокси. +Например, `Shadowsocks` будет использовать нативный UDP, а `VLESS` будет использовать UoT. +::: + +> `xudpProxyUDP443`: string + +Управление обработкой проксируемого трафика UDP/443 (QUIC) в Mux: + +- По умолчанию `reject` - отклонять трафик (обычно браузеры автоматически переключаются на TCP HTTP/2). +- `allow` - разрешить трафик через Mux. +- `skip` - не использовать Mux для трафика UDP/443. + Будет использоваться исходный способ передачи UDP-трафика для данного протокола прокси. + Например, `Shadowsocks` будет использовать нативный UDP, а `VLESS` будет использовать UoT. + + + + + + + diff --git a/docs/ru/config/outbounds/blackhole.md b/docs/ru/config/outbounds/blackhole.md new file mode 100644 index 000000000..0cb698d0e --- /dev/null +++ b/docs/ru/config/outbounds/blackhole.md @@ -0,0 +1,38 @@ +# Blackhole + +Blackhole - это протокол исходящих данных, который блокирует все исходящие данные. В сочетании с [конфигурацией маршрутизации](../routing.md) он может быть использован для запрета доступа к определенным сайтам. + +## OutboundConfigurationObject + +```json +{ + "response": { + "type": "none" + } +} +``` + +> `response`: [ResponseObject](#responseobject) + +Настройка ответа Blackhole. + +Blackhole отправит указанный ответ после получения данных , а затем закроет соединение. Данные для будут отброшены. +Если этот параметр не указан, Blackhole просто закроет соединение. + +### ResponseObject + +```json +{ + "type": "none" +} +``` + +> `type`: "http" | "none" + +Если `type` равен `"none"` (значение по умолчанию), Blackhole просто закроет соединение. + +Если `type` равен `"http"`, Blackhole вернет простой пакет HTTP 403, а затем закроет соединение. + + + + diff --git a/docs/ru/config/outbounds/dns.md b/docs/ru/config/outbounds/dns.md new file mode 100644 index 000000000..d34ffcb9a --- /dev/null +++ b/docs/ru/config/outbounds/dns.md @@ -0,0 +1,36 @@ +# DNS + +DNS — это исходящий протокол, который в основном используется для перехвата и пересылки DNS-запросов. + +Этот исходящий протокол может принимать только DNS-трафик (включая запросы по протоколам UDP и TCP), другие типы трафика будут вызывать ошибки. + +При обработке DNS-запросов этот исходящий протокол перенаправляет IP-запросы (то есть A и AAAA) на встроенный [DNS-сервер](../dns.md). Другие типы запросов будут перенаправлены на их исходные адреса назначения. + +## OutboundConfigurationObject + +```json +{ + "network": "tcp", + "address": "1.1.1.1", + "port": 53, + "nonIPQuery": "drop" +} +``` + +> `network`: "tcp" | "udp" + +Изменяет транспортный протокол DNS-трафика, возможные значения: `"tcp"` и `"udp"`. Если не указан, сохраняется исходный транспортный протокол. + +> `address`: address + +Изменяет адрес DNS-сервера. Если не указан, сохраняется адрес, указанный в источнике. + +> `port`: number + +Изменяет порт DNS-сервера. Если не указан, сохраняется порт, указанный в источнике. + +> `nonIPQuery`: string + +Управляет не IP-запросами (не A и AAAA), `"drop"` - отбрасывать или `"skip"` - не обрабатывать встроенным DNS-сервером, а пересылать на целевой сервер. Значение по умолчанию: `"drop"`. + +## Пример настройки DNS diff --git a/docs/ru/config/outbounds/freedom.md b/docs/ru/config/outbounds/freedom.md new file mode 100644 index 000000000..b2b5291ca --- /dev/null +++ b/docs/ru/config/outbounds/freedom.md @@ -0,0 +1,73 @@ +# Freedom + +Freedom - это исходящий протокол, который можно использовать для отправки (обычных) данных TCP или UDP в любую сеть. + +## OutboundConfigurationObject + +```json +{ + "domainStrategy": "AsIs", + "redirect": "127.0.0.1:3366", + "userLevel": 0, + "fragment": { + "packets": "tlshello", + "length": "100-200", + "interval": "10-20" // в миллисекундах + }, + "proxyProtocol": 0 +} +``` + +> `domainStrategy`: "AsIs"
+> "UseIP" | "UseIPv6v4" | "UseIPv6" | "UseIPv4v6" | "UseIPv4"
+> "ForceIP" | "ForceIPv6v4" | "ForceIPv6" | "ForceIPv4v6" | "ForceIPv4" + +Значение по умолчанию: `"AsIs"`. + +Если целевой адрес является доменным именем, настройте соответствующее значение для режима работы Freedom: + +- При использовании `"AsIs"` Xray будет напрямую использовать системный стек для установления соединения, приоритет и выбор IP будут зависеть от системных настроек. По некоторым причинам UDP-соединения, использующие доменные имена, будут игнорировать системные настройки и отдавать приоритет IPv4. +- При указании других значений для разрешения будет использоваться [встроенный DNS-сервер](../dns.md) Xray-core. Если DNSObject отсутствует, будет использоваться системный DNS. Если существует несколько подходящих IP-адресов, ядро случайным образом выберет один IP-адрес в качестве целевого. +- `"IPv4"` означает попытку подключения только с использованием IPv4, `"IPv4v6"` - попытку подключения с использованием IPv4 или IPv6, но с предпочтением IPv4 для доменных имен с поддержкой обоих протоколов. (То же самое относится и к v4v6, но в обратном порядке, поэтому здесь не приводится). +- Если в настройках встроенного DNS указан параметр `"queryStrategy"`, фактическое поведение будет объединено с этой опцией, и будут разрешаться только те типы IP, которые включены в обе опции. Например, `"queryStrategy": "UseIPv4"` и `"domainStrategy": "UseIP"` фактически эквивалентны `"domainStrategy": "UseIPv4"`. +- При использовании опций, начинающихся с `"Use"`, если результаты разрешения не соответствуют требованиям (например, доменное имя имеет только результат разрешения IPv4, но используется UseIPv6), будет выполнен откат к AsIs. +- При использовании опций, начинающихся с `"Force"`, если результаты разрешения не соответствуют требованиям, соединение не будет установлено. + +::: tip СОВЕТ 1 +При использовании режимов `"UseIP"` или `"ForceIP"` и указании `sendThrough` в [конфигурации исходящего соединения](../outbound.md#outboundobject) Freedom будет автоматически определять необходимый тип IP (IPv4 или IPv6) на основе значения `sendThrough`. Если вручную указать один тип IP (например, UseIPv4), но он не совпадает с локальным адресом, указанным в `sendThrough`, соединение не будет установлено. +::: + +> `redirect`: адрес_порт + +Freedom будет принудительно отправлять все данные на указанный адрес (а не на адрес, указанный во входящем соединении). + +Его значение представляет собой строку, например: `"127.0.0.1:80"`, `":1234"`. + +Если адрес не указан, например, `":443"`, Freedom не будет изменять исходный целевой адрес. +Если порт равен `0`, например, `"xray.com: 0"`, Freedom не будет изменять исходный порт. + +> `userLevel`: number + +Уровень пользователя, для соединения будет использоваться [локальная политика](../policy.md#levelpolicyobject), соответствующая этому уровню пользователя. + +Значение userLevel соответствует значению `level` в разделе [policy](../policy.md#policyobject). Если не указано, используется значение по умолчанию - 0. + +> `fragment`: map + +Некоторые пары "ключ-значение" для управления исходящей TCP-фрагментацией, которые в некоторых случаях могут обмануть систему цензуры, например, обойти черный список SNI. + +`"packets"`: поддерживаются два режима фрагментации: "1-3" - это фрагментация потока TCP, применяемая к первым трем операциям записи данных на стороне клиента. "tlshello" - это фрагментация пакета TLS-рукопожатия. + +`"length"`: длина фрагмента пакета (в байтах). + +`"interval"`: интервал фрагментации (в миллисекундах). + +> `proxyProtocol`: number + +Протокол PROXY обычно используется в сочетании с `redirect` для перенаправления на Nginx или другой сервер, на котором включен протокол PROXY. Если сервер не поддерживает протокол PROXY, соединение будет разорвано. + +Значение proxyProtocol - это номер версии протокола PROXY, возможные значения: `1` или `2`. Если не указано, используется значение по умолчанию - `0` (протокол не используется). + + + + diff --git a/docs/ru/config/outbounds/http.md b/docs/ru/config/outbounds/http.md new file mode 100644 index 000000000..5e4c29e6e --- /dev/null +++ b/docs/ru/config/outbounds/http.md @@ -0,0 +1,93 @@ +# HTTP + +Протокол HTTP. + +::: danger +**Протокол HTTP не обеспечивает шифрования передачи, что делает его непригодным для передачи по общедоступным сетям и более уязвимым для использования в качестве скомпрометированного хоста для атак.** +::: + +::: tip +`HTTP` может проксировать только протоколы TCP и не может обрабатывать протоколы на основе UDP. +::: + +## OutboundConfigurationObject + +```json +{ + "servers": [ + { + "address": "192.168.108.1", + "port": 3128, + "users": [ + { + "user": "my-username", + "pass": "my-password" + } + ] + } + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", + "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" + } +} +``` + +::: tip +В настоящее время в исходящем HTTP-протоколе действительна конфигурация `streamSettings` с параметрами `security` и `tlsSettings`. +::: + +> `servers`: \[ [ServerObject](#serverobject) \] + +Список HTTP-серверов, каждый элемент которого является конфигурацией сервера. Если настроено несколько серверов, они используются по кругу (RoundRobin). + +> `headers`: map{ string, string } + +HTTP-заголовки, пара "ключ-значение". Каждый ключ представляет собой имя HTTP-заголовка, все пары "ключ-значение" будут прикрепляться к каждому запросу. + +### ServerObject + +```json +{ + "address": "192.168.108.1", + "port": 3128, + "users": [ + { + "user": "my-username", + "pass": "my-password" + } + ] +} +``` + +> `address`: string + +Адрес HTTP-прокси-сервера, обязательный параметр. + +> `port`: int + +Порт HTTP-прокси-сервера, обязательный параметр. + +> `user`: \[[AccountObject](#accountobject)\] + +Массив, каждый элемент которого представляет собой учетную запись пользователя. Значение по умолчанию: пустой массив. + +#### AccountObject + +```json +{ + "user": "my-username", + "pass": "my-password" +} +``` + +> `user`: string + +Имя пользователя, тип данных: строка. Обязательный параметр. + +> `pass`: string + +Пароль, тип данных: строка. Обязательный параметр. + + + diff --git a/docs/ru/config/outbounds/loopback.md b/docs/ru/config/outbounds/loopback.md new file mode 100644 index 000000000..2bf4dc05a --- /dev/null +++ b/docs/ru/config/outbounds/loopback.md @@ -0,0 +1,59 @@ +# Loopback + +Loopback - это исходящий протокол данных, который перенаправляет данные, прошедшие через это исходящее соединение, обратно на вход маршрутизатора, что позволяет повторно обработать данные по правилам маршрутизации, не покидая Xray-core. + +## OutboundConfigurationObject + +```json +{ + "inboundTag": "TagUseAsInbound" +} +``` + +> `inboundTag`: string + +Идентификатор входящего протокола, используемый для повторной маршрутизации. + +Этот идентификатор может использоваться в маршрутизации для `inboundTag`, указывая, что данные из этого исходящего соединения могут быть повторно обработаны соответствующими правилами маршрутизации. + +### Как использовать? + +Если необходимо, чтобы трафик, уже разделенный по правилам маршрутизации, был перенаправлен другими правилами маршрутизации (например, трафик TCP и UDP, разделенный одними и теми же правилами маршрутизации, должен идти через разные исходящие соединения), можно использовать исходящее соединение `loopback`. + +``` json +{ + "outbounds": [ + { + "protocol": "loopback", + "tag": "need-to-split", + "settings": { + "inboundTag": "traffic-input" // Этот тег используется ниже для inboundTag в RuleObject + } + }, + { + "tag": "tcp-output", + // Настройки protocol, settings, streamSettings и т. д. + }, + { + "tag": "udp-output", + // Настройки protocol, settings, streamSettings и т. д. + } + ], + "routing": { + "rules": [ + { + "inboundTag": ["traffic-input"], // Тег, установленный в loopback + "network": "tcp", + "outboundTag": "tcp-output" + }, + { + "inboundTag": ["traffic-input"], // Тег, установленный в loopback + "network": "udp", + "outboundTag": "udp-output" + } + ] + } +} +``` + + diff --git a/docs/ru/config/outbounds/shadowsocks.md b/docs/ru/config/outbounds/shadowsocks.md new file mode 100644 index 000000000..0cac2c601 --- /dev/null +++ b/docs/ru/config/outbounds/shadowsocks.md @@ -0,0 +1,123 @@ +# Shadowsocks + +Протокол [Shadowsocks](https://ru.wikipedia.org/wiki/Shadowsocks), совместимый с большинством других реализаций. + +Текущая совместимость: + +- Поддерживает пересылку пакетов TCP и UDP, при этом UDP можно выборочно отключить; +- Рекомендуемые методы шифрования: + - 2022-blake3-aes-128-gcm + - 2022-blake3-aes-256-gcm + - 2022-blake3-chacha20-poly1305 +- Другие методы шифрования: + - aes-256-gcm + - aes-128-gcm + - chacha20-poly1305 или chacha20-ietf-poly1305 + - xchacha20-poly1305 или xchacha20-ietf-poly1305 + - none или plain + +Новый формат протокола Shadowsocks 2022 повышает производительность и обеспечивает полную защиту от повторов, решая следующие проблемы безопасности старого протокола: + +- [Серьезные уязвимости в шифровании Shadowsocks AEAD, которые не могут гарантировать целостность содержимого](https://github.com/shadowsocks/shadowsocks-org/issues/183) +- Возрастающий коэффициент ложных срабатываний исходного фильтра повторов TCP с течением времени +- Отсутствие защиты от повторов UDP +- Поведение TCP, которое можно использовать для активного зондирования + +::: danger +При использовании метода шифрования "none" трафик передается в открытом виде. В целях безопасности не используйте этот метод в общедоступных сетях. +::: + +## OutboundConfigurationObject + +```json +{ + "servers": [ + { + "email": "love@xray.com", + "address": "127.0.0.1", + "port": 1234, + "method": "метод_шифрования", + "password": "пароль", + "uot": true, + "UoTVersion": 2, + "level": 0 + } + ] +} +``` + +> `servers`: \[[ServerObject](#serverobject)\] + +Массив, представляющий собой набор настроек сервера Shadowsocks, каждый элемент которого является [ServerObject](#serverobject). + +### ServerObject + +```json +{ + "email": "love@xray.com", + "address": "127.0.0.1", + "port": 1234, + "method": "метод_шифрования", + "password": "пароль", + "uot": true, + "UoTVersion": 2, + "level": 0 +} +``` + +> `email`: string + +Адрес электронной почты, необязательный параметр, используется для идентификации пользователя. + +> `address`: address + +Адрес сервера Shadowsocks, поддерживаются IPv4, IPv6 и доменные имена. Обязательный параметр. + +> `port`: number + +Порт сервера Shadowsocks. Обязательный параметр. + +> `method`: string + +Обязательный параметр. + +> `password`: string + +Обязательный параметр. + +> `uot`: bool + +Включить `udp over tcp`. + +> `UoTVersion`: number + +Версия реализации `UDP over TCP`. + +Допустимые значения: `1`, `2`. + +- Shadowsocks 2022 + +В качестве пароля используется предварительный общий ключ, аналогичный ключам WireGuard. + +Используйте `openssl rand -base64 <длина>`, чтобы сгенерировать ключ, совместимый с shadowsocks-rust, длина зависит от используемого метода шифрования. + +| Метод шифрования | Длина ключа | +| ------------------------------------ | ----------: | +| 2022-blake3-aes-128-gcm | 16 | +| 2022-blake3-aes-256-gcm | 32 | +| 2022-blake3-chacha20-poly1305 | 32 | + +В реализации Go всегда работают 32-битные ключи. + +- Другие методы шифрования + +Любая строка. Длина пароля не ограничена, но короткие пароли более уязвимы для взлома, рекомендуется использовать пароли длиной 16 символов или более. + +> `level`: number + +Уровень пользователя, для соединения будет использоваться [локальная политика](../policy.md#levelpolicyobject), соответствующая этому уровню пользователя. + +Значение `level` соответствует значению `level` в разделе [policy](../policy.md#policyobject). Если не указано, используется значение по умолчанию - 0. + + + diff --git a/docs/ru/config/outbounds/socks.md b/docs/ru/config/outbounds/socks.md new file mode 100644 index 000000000..1828485be --- /dev/null +++ b/docs/ru/config/outbounds/socks.md @@ -0,0 +1,94 @@ +# Socks + +Стандартная реализация протокола Socks, совместимая с [Socks 4](http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol), [Socks 4a](https://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4A.protocol) и Socks 5. + +::: danger +**Протокол Socks не обеспечивает шифрования передачи, поэтому он не подходит для передачи данных через общедоступные сети.** +::: + +## OutboundConfigurationObject + +```json +{ + "servers": [ + { + "address": "127.0.0.1", + "port": 1234, + "users": [ + { + "user": "test user", + "pass": "test pass", + "level": 0 + } + ] + } + ] +} +``` + +> `servers`: \[ [ServerObject](#serverobject) \] + +Список Socks-серверов, где каждый элемент представляет собой конфигурацию сервера. + +### ServerObject + +```json +{ + "address": "127.0.0.1", + "port": 1234, + "users": [ + { + "user": "test user", + "pass": "test pass", + "level": 0 + } + ] +} +``` + +> `address`: address + +Адрес сервера, обязательный параметр. + +::: tip +Поддерживается подключение только к Socks 5 серверам. +::: + +> `port`: number + +Порт сервера, обязательный параметр. + +> `users`: \[ [UserObject](#userobject) \] + +Массив, представляющий список пользователей, где каждый элемент представляет собой конфигурацию пользователя. + +Если список не пуст, Socks-клиент будет использовать информацию о пользователе для аутентификации; если не указан, аутентификация не выполняется. + +Значение по умолчанию: пустой массив. + +#### UserObject + +```json +{ + "user": "test user", + "pass": "test pass", + "level": 0 +} +``` + +> `user`: string + +Имя пользователя, тип данных: строка. Обязательный параметр. + +> `pass`: string + +Пароль, тип данных: строка. Обязательный параметр. + +> `level`: number + +Уровень пользователя, для соединения будет использоваться [локальная политика](../policy.md#levelpolicyobject), соответствующая этому уровню пользователя. + +Значение userLevel соответствует значению `level` в разделе [policy](../policy.md#policyobject). Если не указано, используется значение по умолчанию - 0. + + + diff --git a/docs/ru/config/outbounds/trojan.md b/docs/ru/config/outbounds/trojan.md new file mode 100644 index 000000000..62cc26992 --- /dev/null +++ b/docs/ru/config/outbounds/trojan.md @@ -0,0 +1,67 @@ +# Trojan + +Протокол [Trojan](https://trojan-gfw.github.io/trojan/protocol). + +::: danger +Trojan предназначен для работы в правильно настроенном зашифрованном TLS-туннеле. +::: + + +## OutboundConfigurationObject + +```json +{ + "servers": [ + { + "address": "127.0.0.1", + "port": 1234, + "password": "password", + "email": "love@xray.com", + "level": 0 + } + ] +} +``` + +> `servers`: \[ [ServerObject](#serverobject) \] + +Массив, каждый элемент которого является [ServerObject](#serverobject). + +### ServerObject + +```json +{ + "address": "127.0.0.1", + "port": 1234, + "password": "password", + "email": "love@xray.com", + "level": 0 +} +``` + +> `address`: address + +Адрес сервера, поддерживаются IPv4, IPv6 и доменные имена. Обязательный параметр. + +> `port`: number + +Порт сервера, обычно тот же, что и порт, прослушиваемый сервером. + +> `password`: string + +Пароль. Обязательный параметр, любая строка. + +> `email`: string + +Адрес электронной почты, необязательный параметр, используется для идентификации пользователя. + +> `level`: number + +Уровень пользователя, для соединения будет использоваться [локальная политика](../policy.md#levelpolicyobject), соответствующая этому уровню пользователя. + +Значение level соответствует значению `level` в разделе [policy](../policy.md#policyobject). Если не указано, используется значение по умолчанию - 0. + + + + + diff --git a/docs/ru/config/outbounds/vless.md b/docs/ru/config/outbounds/vless.md new file mode 100644 index 000000000..2cb72ca0a --- /dev/null +++ b/docs/ru/config/outbounds/vless.md @@ -0,0 +1,139 @@ +# VLESS + +::: danger +VLESS не предусматривает встроенного шифрования, поэтому обязательным условием для его использования является наличие надежного канала, такого как TLS или REALITY. +::: + +VLESS - это легкий транспортный протокол без сохранения состояния, который разделен на входящую и исходящую части и может служить мостом между клиентом и сервером Xray. + +В отличие от [VMess](./vmess.md), VLESS не зависит от системного времени, аутентификация также осуществляется с помощью UUID. + + +## OutboundConfigurationObject + +```json +{ + "vnext": [ + { + "address": "example.com", + "port": 443, + "users": [ + { + "id": "5783a3e7-e373-51cd-8642-c83782b807c5", + "encryption": "none", + "flow": "xtls-rprx-vision", + "level": 0 + } + ] + } + ] +} +``` + +> `vnext`: \[ [ServerObject](#serverobject) \] + +Массив, представляющий список серверов VLESS, содержащий набор конфигураций, указывающих на сервер, где каждый элемент является конфигурацией сервера. + +### ServerObject + +```json +{ + "address": "example.com", + "port": 443, + "users": [ + { + "id": "5783a3e7-e373-51cd-8642-c83782b807c5", + "encryption": "none", + "flow": "xtls-rprx-vision", + "level": 0 + } + ] +} +``` + +> `address`: address + +Адрес сервера, указывающий на сервер, поддерживаются доменные имена, IPv4 и IPv6. + +> `port`: number + +Порт сервера, обычно тот же, что и порт, прослушиваемый сервером. + +> `users`: \[ [UserObject](#userobject) \] + +Массив, представляющий список пользователей, распознаваемых сервером, где каждый элемент является конфигурацией пользователя. + +### UserObject + +```json +{ + "id": "5783a3e7-e373-51cd-8642-c83782b807c5", + "encryption": "none", + "flow": "xtls-rprx-vision", + "level": 0 +} +``` + +> `id`: string + +Идентификатор пользователя VLESS, может быть любой строкой длиной менее 30 байт или допустимым UUID. +Пользовательская строка и ее UUID-отображение эквивалентны, это означает, что вы можете использовать следующие способы записи id в файле конфигурации для идентификации одного и того же пользователя: + +- Напишите `"id": "Я люблю арбуз учителя 1314"`, +- Или напишите `"id": "5783a3e7-e373-51cd-8642-c83782b807c5"` (этот UUID является UUID-отображением строки "Я люблю арбуз учителя 1314") + +Стандарт сопоставления описан в [VLESS UUID Mapping Standard: Mapping Custom Strings to a UUIDv5](https://github.com/XTLS/Xray-core/issues/158). + +Вы можете использовать команду `xray uuid -i "Пользовательская строка"` для генерации UUID, соответствующего пользовательской строке. Вы также можете использовать команду `xray uuid` для генерации случайного UUID. + +> `encryption`: "none" + +Необходимо указать `"none"`, значение не может быть пустым. + +Это требование призвано напомнить пользователю об отсутствии шифрования, а также предотвратить ошибки пользователей при вводе имени атрибута или его расположения в будущем, когда будут доступны методы шифрования. + +Если значение encryption установлено неверно, при использовании Xray или -test будет выдано сообщение об ошибке. + +> `flow`: string + +Режим управления потоком, используется для выбора алгоритма XTLS. + +В настоящее время для исходящего протокола доступны следующие режимы управления потоком: + +- Отсутствует `flow` или пустая строка: используется обычный TLS-прокси. +- `xtls-rprx-vision`: используется новый режим XTLS, включает случайное заполнение внутреннего рукопожатия, поддерживает uTLS для имитации отпечатка клиента. +- `xtls-rprx-vision-udp443`: аналогично `xtls-rprx-vision`, но разрешает UDP-трафик, направленный на порт 443. + +Кроме того, в настоящее время XTLS поддерживает только три транспортных протокола: TCP, mKCP и DomainSocket. + + +::: tip О режимах управления потоком xtls-rprx-*-udp443 + +Когда XTLS в Xray-core включен, трафик, направленный на UDP-порт 443 (обычно QUIC), по умолчанию блокируется, чтобы приложение не использовало QUIC, а использовало TLS, чтобы XTLS действительно вступил в силу. Фактически, QUIC сам по себе не подходит для проксирования, поскольку QUIC имеет встроенные функции TCP, и когда он передается по протоколу VLESS как UDP-трафик, базовый протокол - TCP, что эквивалентно двум уровням TCP. + +Если блокировка не требуется, укажите `xtls-rprx-*-udp443` на стороне клиента, на стороне сервера оставляйте без изменений. +::: + +::: tip О режиме Splice + +Splice - это функция, предоставляемая ядром Linux, где ядро системы напрямую пересылает TCP, минуя память Xray, что значительно сокращает количество операций копирования данных и переключения контекста процессора. + +Ограничения использования режима Splice: + +- Среда Linux. +- Входящий протокол: `Dokodemo door`, `Socks`, `HTTP` и другие чистые TCP-соединения или другие входящие протоколы, использующие XTLS. +- Исходящий протокол: VLESS + XTLS. +- Обратите внимание, что при использовании протокола mKCP Splice не будет использоваться (да, хотя ошибки и нет, на самом деле он не используется). + +Кроме того, при использовании Splice отображение скорости сети будет запаздывать, это особенность, а не ошибка. + +При использовании режима Vision Splice будет включен автоматически, если выполнены вышеуказанные условия. +::: + +> `level`: number + +Уровень пользователя, для соединения будет использоваться [локальная политика](../policy.md#levelpolicyobject), соответствующая этому уровню пользователя. + +Значение level соответствует значению `level` в разделе [policy](../policy.md#policyobject). Если не указано, используется значение по умолчанию - 0. + + diff --git a/docs/ru/config/outbounds/vmess.md b/docs/ru/config/outbounds/vmess.md new file mode 100644 index 000000000..5c092646c --- /dev/null +++ b/docs/ru/config/outbounds/vmess.md @@ -0,0 +1,124 @@ +# VMess + +[VMess](../../development/protocols/vmess.md) - это зашифрованный транспортный протокол, который обычно используется в качестве моста между клиентами и серверами Xray. + +::: danger +VMess полагается на системное время. Убедитесь, что системное время UTC, используемое Xray, находится в пределах 120 секунд от фактического времени, независимо от часового пояса. В системах Linux вы можете установить службу `ntp` для автоматической синхронизации системного времени. +::: + +## OutboundConfigurationObject + +```json +{ + "vnext": [ + { + "address": "127.0.0.1", + "port": 37192, + "users": [ + { + "id": "5783a3e7-e373-51cd-8642-c83782b807c5", + "security": "auto", + "level": 0, + "experiments": "" + } + ] + } + ] +} +``` + +> `vnext`: \[ [ServerObject](#serverobject) \] + +Массив, содержащий набор конфигураций сервера. + +Каждый элемент - это конфигурация сервера [ServerObject](#serverobject). + +### ServerObject + +```json +{ + "address": "127.0.0.1", + "port": 37192, + "users": [] +} +``` + +> `address`: address + +Адрес сервера, поддерживается IP-адрес или доменное имя. + +> `port`: number + +Номер порта, который прослушивает сервер, обязательный параметр. + +> `users`: \[ [UserObject](#userobject) \] + +Массив, представляющий группу пользователей, распознаваемых сервером. + +Каждый элемент - это пользователь [UserObject](#userobject). + +#### UserObject + +```json +{ + "id": "5783a3e7-e373-51cd-8642-c83782b807c5", + "security": "auto", + "level": 0, + "experiments": "" +} +``` + +> `id`: string + +Идентификатор пользователя VMess, может быть любой строкой длиной менее 30 байт или допустимым UUID. + +Пользовательская строка и соответствующий ей UUID эквивалентны, что означает, что вы можете использовать любой из следующих вариантов в файле конфигурации для идентификации одного и того же пользователя: + +- Напишите `"id": "Я люблю арбуз учителя 1314"`, +- Или напишите `"id": "5783a3e7-e373-51cd-8642-c83782b807c5"` (этот UUID является сопоставлением строки "Я люблю арбуз учителя 1314") + +Стандарт сопоставления описан в [VLESS UUID Mapping Standard: Mapping Custom Strings to a UUIDv5](https://github.com/XTLS/Xray-core/issues/158). + +Вы можете использовать команду `xray uuid -i "пользовательская строка"` для создания UUID, соответствующего пользовательской строке. Вы также можете использовать команду `xray uuid` для создания случайного UUID. + +> `level`: number + +Уровень пользователя, для соединения будет использоваться [локальная политика](../policy.md#levelpolicyobject), соответствующая этому уровню пользователя. + +Значение level соответствует значению `level` в разделе [policy](../policy.md#policyobject). Если не указано, используется значение по умолчанию - 0. + +> `security`: "aes-128-gcm" | "chacha20-poly1305" | "auto" | "none" | "zero" + +Метод шифрования. Клиент будет отправлять данные с использованием настроенного метода шифрования, сервер автоматически распознает его, настройка на сервере не требуется. + +- `"aes-128-gcm"`: рекомендуется для использования на ПК. +- `"chacha20-poly1305"`: рекомендуется для использования на мобильных устройствах. +- `"auto"`: значение по умолчанию, автоматический выбор (метод шифрования aes-128-gcm, если платформа выполнения - AMD64, ARM64 или s390x, в противном случае - Chacha20-Poly1305). +- `"none"`: без шифрования. + +* `"zero"`: без шифрования и проверки подлинности сообщений (v1.4.0+). + +::: tip +Рекомендуется использовать метод шифрования `"auto"`, чтобы обеспечить безопасность и совместимость в долгосрочной перспективе. + +Метод псевдошифрования `"none"` будет вычислять и проверять контрольные суммы пакетов данных, но поскольку алгоритм аутентификации не имеет аппаратной поддержки, на некоторых платформах он может быть медленнее, чем `"aes-128-gcm"` с аппаратным ускорением. + +Метод псевдошифрования `"zero"` не шифрует сообщения и не вычисляет контрольные суммы данных, поэтому теоретически он должен быть быстрее любого другого метода шифрования. Фактическая скорость может зависеть от других факторов. + +Не рекомендуется использовать методы псевдошифрования `"none"` и `"zero"` без включенного TLS-шифрования и обязательной проверки сертификатов. +Если для установления соединения используется CDN или другая промежуточная платформа, расшифровывающая TLS, или сетевая среда, не рекомендуется использовать методы псевдошифрования `"none"` и `"zero"`. + +Независимо от используемого метода шифрования, заголовок пакета VMess защищен шифрованием и аутентификацией. +::: + +> `experiments`: string + +Включенные экспериментальные функции протокола VMess. (Функции здесь нестабильны и могут быть удалены в любое время). Несколько включенных экспериментов можно разделить символом |, например, "AuthenticatedLength|NoTerminationSignal". + +"AuthenticatedLength" включает эксперимент с аутентифицированной длиной пакета. Этот эксперимент необходимо включить одновременно на клиенте и сервере, а также запустить одну и ту же версию программы. + +"NoTerminationSignal" включает эксперимент с отключением сигнала завершения соединения. Этот эксперимент может повлиять на стабильность проксируемого соединения. + + + + diff --git a/docs/ru/config/outbounds/wireguard.md b/docs/ru/config/outbounds/wireguard.md new file mode 100644 index 000000000..a06fe8fe6 --- /dev/null +++ b/docs/ru/config/outbounds/wireguard.md @@ -0,0 +1,169 @@ +# WireGuard + +Стандартная реализация протокола WireGuard. + +::: danger +**Протокол WireGuard не предназначен для обхода блокировок, и его использование может привести к блокировке сервера из-за наличия характерных признаков.** +::: + +## OutboundConfigurationObject + +```json +{ + "secretKey": "PRIVATE_KEY", + "address": [ + // необязательно, по умолчанию ["10.0.0.1", "fd59:7153:2388:b5fd:0000:0000:0000:0001"] + "IPv4_CIDR", + "IPv6_CIDR", + "и так далее..." + ], + "peers": [ + { + "endpoint": "ENDPOINT_ADDR", + "publicKey": "PUBLIC_KEY" + } + ], + "kernelMode": true, // необязательно, по умолчанию true, если поддерживается и есть достаточные права + "mtu": 1420, // необязательно, по умолчанию 1420 + "reserved": [1, 2, 3], + "workers": 2, // необязательно, по умолчанию runtime.NumCPU() + "domainStrategy": "ForceIP" +} +``` + +::: tip +В настоящее время в исходящем протоколе WireGuard не поддерживается настройка `streamSettings`. +::: + +> `secretKey`: string + +Приватный ключ пользователя. Обязательный параметр. + +> `address`: string array + +WireGuard создаст виртуальный сетевой интерфейс TUN на локальном компьютере. Используйте один или несколько IP-адресов, IPv6 поддерживается. + +> `kernelMode`: true | false + +Использовать ли TUN виртуального сетевого интерфейса ядра Linux.
+Для использования TUN виртуального сетевого интерфейса ядра Linux требуется поддержка системы и права root, после использования будет занята 1023-я таблица маршрутизации IPv6.
+ +::: tip +Если в 1023-й таблице маршрутизации IPv6 уже есть записи маршрутов и значение `kernelMode` равно `true`, нормальная работа будет невозможна. +::: + +> `mtu`: int + +Размер фрагментации TUN нижнего уровня WireGuard. + +
+Как рассчитать MTU + +Структура пакета WireGuard выглядит следующим образом: + +``` +- 20-байтовый заголовок IPv4 или 40-байтовый заголовок IPv6 +- 8-байтовый заголовок UDP +- 4 байта типа +- 4 байта индекса ключа +- 8 байтов nonce +- N байтов зашифрованных данных +- 16-байтовый тег аутентификации +``` + +`N байтов зашифрованных данных` - это значение MTU, которое нам нужно. В зависимости от того, использует ли конечная точка IPv4 или IPv6, конкретное значение может быть 1440 (IPv4) или 1420 (IPv6), и его можно дополнительно уменьшить, если это необходимо в особых условиях (например, PPPoE для домашнего интернета требует дополнительного вычитания 8). +
+ +> `reserved` \[ number \] + +Зарезервированные байты WireGuard. + +Новый параметр в Xray-core v1.8.0.
+При подключении к Warp через WireGuard из-за ограничений Cloudflare для некоторых IP-адресов в Гонконге и Лос-Анджелесе требуется значение `reserved` для успешного подключения.
+Значение `reserved` можно получить с помощью сторонних инструментов, таких как: [warp-reg](https://github.com/badafans/warp-reg), [warp-reg.sh](https://github.com/chise0713/warp-reg.sh). + +> `workers`: int + +Количество потоков, используемых WireGuard. + +> `peers`: \[ [Peers](#peers) \] + +Список серверов WireGuard, где каждый элемент является конфигурацией сервера. + +> `domainStrategy`: "ForceIPv6v4" | "ForceIPv6" | "ForceIPv4v6" | "ForceIPv4" | "ForceIP" + +Новый параметр в Xray-core v1.8.6.
+Если этот параметр не указан или оставлен пустым, используется значение по умолчанию `"ForceIP"`.
+Если целевой адрес является доменным именем, для получения IP-адреса используется [встроенный DNS-сервер](../dns.md) Xray-core (если конфигурация `"dns"` не указана, используется системный DNS), и этот IP-адрес отправляется через WireGuard для установления соединения.
+ +::: tip +Если значение `domainStrategy` конфликтует со значением `"queryStrategy"` в конфигурации `"dns"`, это может привести к ошибкам при открытии веб-сайтов. +::: + +```json + "dns": { + "servers": [ + "https://1.1.1.1/dns-query", + { + "address": "https://1.1.1.1/dns-query", + "domains": [ + "geosite:openai" + ], + "skipFallback": true, + "queryStrategy": "UseIPv6" // Запрашивать только записи AAAA + } + ], + "queryStrategy": "UseIP" // Запрашивать записи A и AAAA одновременно, если этот параметр не указан, значение по умолчанию - UseIP + }, +``` + +Если `domainStrategy: "ForceIPv4"`, а в поле DNS, управляющем запросами домена geosite:openai, используется `"queryStrategy": "UseIPv6"`, это приведет к ошибкам при открытии веб-сайтов geosite:openai. + +::: tip +В Xray-core v1.8.0 - v1.8.4 нет `"domainStrategy"`.
+Если целевой адрес является доменным именем, для получения IP-адреса используется встроенный DNS-сервер Xray-core. Значение `"queryStrategy"` в конфигурации `"dns"` используется для управления приоритетом IPv4 или IPv6.
+Если конфигурация `"dns"` не указана, для получения IP-адреса используется системный DNS, а приоритет IPv4 или IPv6 определяется системой. +::: + +### Peers + +```json +{ + "endpoint": "ENDPOINT_ADDR", + "publicKey": "PUBLIC_KEY", + "preSharedKey": "PRE_SHARED_KEY", // необязательно, по умолчанию "0000000000000000000000000000000000000000000000000000000000000000" + "keepAlive": 0, // необязательно, по умолчанию 0 + "allowedIPs": ["0.0.0.0/0"] // необязательно, по умолчанию ["0.0.0.0/0", "::/0"] +} +``` + +> `endpoint`: address + +Адрес сервера, обязательный параметр. + +Формат URL:порт, например, `engage.cloudflareclient.com:2408`
+Формат IP:порт, например, `162.159.192.1:2408` или `[2606:4700:d0::a29f:c001]:2408`. + +::: tip +Если целевой адрес имеет тип URL, для получения IP-адреса будет использоваться встроенный DNS-сервер Xray-core, приоритет IPv4 или IPv6 определяется значением `domainStrategy`.
+Если конфигурация `"dns"` не указана, для получения IP-адреса будет использоваться системный DNS, а приоритет IPv4 или IPv6 будет определяться системой. +::: + +> `publicKey`: string + +Публичный ключ сервера, используемый для аутентификации, обязательный параметр. + +> `preSharedKey`: string + +Дополнительный ключ симметричного шифрования. + +> `keepAlive`: int + +Интервал отправки keep-alive пакетов в секундах, значение по умолчанию - 0, что означает отсутствие keep-alive. + +> `allowedIPs`: string array + +WireGuard разрешает трафик только от определенных исходных IP-адресов. + + + diff --git a/docs/ru/config/policy.md b/docs/ru/config/policy.md new file mode 100644 index 000000000..d393861ca --- /dev/null +++ b/docs/ru/config/policy.md @@ -0,0 +1,144 @@ +# Локальные политики + +Локальные политики позволяют настраивать различные уровни пользователей и соответствующие им политики, например, настройки тайм-аута подключения. +Каждое соединение, обрабатываемое Xray, соответствует определенному пользователю, и к нему применяются политики в соответствии с уровнем пользователя (level). + +## PolicyObject + +`PolicyObject` соответствует полю `policy` в конфигурационном файле. + +```json +{ + "policy": { + "levels": { + "0": { + "handshake": 4, + "connIdle": 300, + "uplinkOnly": 2, + "downlinkOnly": 5, + "statsUserUplink": false, + "statsUserDownlink": false, + "bufferSize": 4 + } + }, + "system": { + "statsInboundUplink": false, + "statsInboundDownlink": false, + "statsOutboundUplink": false, + "statsOutboundDownlink": false + } + } +} +``` + +> `level`: map{string: [LevelPolicyObject](#levelpolicyobject)} + +Набор пар ключ-значение, где каждый ключ - это число в виде строки (требование JSON), например `"0"`, `"1"` и т.д. (кавычки обязательны). +Это число соответствует уровню пользователя. +Каждое значение - это [LevelPolicyObject](#levelpolicyobject). + +::: tip +Теперь можно настроить уровень пользователя для каждого входящего и исходящего подключения. +Xray будет применять различные локальные политики в соответствии с фактическим уровнем пользователя. +::: + +> `system`: [SystemPolicyObject](#systempolicyobject) + +Политики уровня системы Xray. + +### LevelPolicyObject + +```json +{ + "handshake": 4, + "connIdle": 300, + "uplinkOnly": 2, + "downlinkOnly": 5, + "statsUserUplink": false, + "statsUserDownlink": false, + "bufferSize": 10240 +} +``` + +> `handshake`: number + +Ограничение времени на установление соединения (рукопожатие). +Измеряется в секундах. +Значение по умолчанию - `4`. +При обработке нового соединения входящим прокси, если время, затраченное на рукопожатие, превышает это значение, соединение разрывается. + +> `connIdle`: number + +Ограничение времени простоя соединения. +Измеряется в секундах. +Значение по умолчанию - `300`. +При обработке соединения входящим или исходящим прокси, если в течение времени `connIdle` не было передано никаких данных (включая исходящие и входящие данные), соединение разрывается. + +> `uplinkOnly`: number + +Ограничение времени ожидания после закрытия исходящего канала соединения. +Измеряется в секундах. +Значение по умолчанию - `2`. +Когда сервер (например, удаленный веб-сайт) закрывает исходящее соединение, исходящий прокси разрывает соединение через `uplinkOnly` секунд. + +> `downlinkOnly`: number + +Ограничение времени ожидания после закрытия входящего канала соединения. +Измеряется в секундах. +Значение по умолчанию - `5`. +Когда клиент (например, браузер) закрывает входящее соединение, входящий прокси разрывает соединение через `downlinkOnly` секунд. + +::: tip +При просмотре веб-страниц можно установить `uplinkOnly` и `downlinkOnly` в `0`, чтобы ускорить закрытие соединений. +::: + +> `statsUserUplink`: true | false + +Если значение равно `true`, включить учет исходящего трафика для всех пользователей текущего уровня. + +> `statsUserDownlink`: true | false + +Если значение равно `true`, включить учет входящего трафика для всех пользователей текущего уровня. + +> `bufferSize`: number + +Размер внутреннего буфера для каждого соединения. +Измеряется в килобайтах. +Если значение равно `0`, внутренний буфер отключается. + +Значение по умолчанию: + +- На платформах ARM, MIPS, MIPSLE значение по умолчанию - `0`. +- На платформах ARM64, MIPS64, MIPS64LE значение по умолчанию - `4`. +- На других платформах значение по умолчанию - `512`. + +### SystemPolicyObject + +```json +{ + "statsInboundUplink": false, + "statsInboundDownlink": false, + "statsOutboundUplink": false, + "statsOutboundDownlink": false +} +``` + +> `statsInboundUplink`: true | false + +Если значение равно `true`, включить учет исходящего трафика для всех входящих подключений. + +> `statsInboundDownlink`: true | false + +Если значение равно `true`, включить учет входящего трафика для всех входящих подключений. + +> `statsOutboundUplink`: true | false + +Если значение равно `true`, включить учет исходящего трафика для всех исходящих подключений. + +> `statsOutboundDownlink`: true | false + +Если значение равно `true`, включить учет входящего трафика для всех исходящих подключений. + + + + diff --git a/docs/ru/config/reverse.md b/docs/ru/config/reverse.md new file mode 100644 index 000000000..2d45b10da --- /dev/null +++ b/docs/ru/config/reverse.md @@ -0,0 +1,247 @@ +# Обратный прокси + +Обратный прокси позволяет перенаправлять трафик с сервера на клиент, то есть перенаправлять трафик в обратном направлении. + +Принцип работы обратного прокси: + +- Предположим, что на хосте A запущен веб-сервер, но у этого хоста нет публичного IP-адреса, и к нему нельзя получить доступ из Интернета. + У нас есть другой хост B с публичным IP-адресом. + Мы хотим использовать хост B в качестве шлюза и перенаправлять трафик с B на A. +- На хосте A настроен Xray, называемый `bridge`, и на хосте B также настроен Xray, называемый `portal`. +- `bridge` устанавливает соединение с `portal`. + Целевой адрес этого соединения можно настроить произвольно. + `portal` получает два типа соединений: соединения от `bridge` и соединения от пользователей из Интернета. + `portal` автоматически объединяет эти два типа соединений. + Таким образом, `bridge` может получать трафик из Интернета. +- После получения трафика из Интернета `bridge` перенаправляет его на веб-сервер на хосте A без изменений. + Конечно, для этого требуется настроить маршрутизацию. +- `bridge` выполняет динамическую балансировку нагрузки в зависимости от объема трафика. + +::: tip +Обратный прокси по умолчанию использует [Mux](../../development/protocols/muxcool/). +Не включайте Mux для исходящих подключений, используемых обратным прокси. +::: + +::: warning +Функция обратного прокси находится в стадии тестирования и может работать некорректно. +::: + +## ReverseObject + +`ReverseObject` соответствует полю `reverse` в конфигурационном файле. + +```json +{ + "reverse": { + "bridges": [ + { + "tag": "bridge", + "domain": "test.xray.com" + } + ], + "portals": [ + { + "tag": "portal", + "domain": "test.xray.com" + } + ] + } +} +``` + +> `bridges`: \[[BridgeObject](#bridgeobject)\] + +Массив, каждый элемент которого представляет собой `bridge`. +Настройки каждого `bridge` определяются в [BridgeObject](#bridgeobject). + +> `portals`: \[[PortalObject](#portalobject)\] + +Массив, каждый элемент которого представляет собой `portal`. +Настройки каждого `portal` определяются в [PortalObject](#bridgeobject). + +### BridgeObject + +```json +{ + "tag": "bridge", + "domain": "test.xray.com" +} +``` + +> `tag`: string + +Все соединения, исходящие от `bridge`, будут иметь этот тег. +Его можно использовать для идентификации соединений в [настройках маршрутизации](./routing.md) с помощью `inboundTag`. + +> `domain`: string + +Доменное имя, которое `bridge` будет использовать для установления соединения с `portal`. +Это доменное имя используется только для связи между `bridge` и `portal` и не должно существовать на самом деле. + +### PortalObject + +```json +{ + "tag": "portal", + "domain": "test.xray.com" +} +``` + +> `tag`: string + +Тег `portal`. +Используйте `outboundTag` в [настройках маршрутизации](./routing.md), чтобы перенаправить трафик на этот `portal`. + +> `domain`: string + +Доменное имя. +Когда `portal` получает трафик, если целевое доменное имя трафика совпадает с этим доменным именем, `portal` считает, что это соединение для связи с `bridge`. +Весь остальной трафик считается трафиком, который нужно перенаправить. +`portal` идентифицирует и объединяет эти два типа соединений. + +::: tip +Один и тот же экземпляр Xray может выступать в роли `bridge`, `portal` или и того, и другого, в зависимости от сценария использования. +::: + +## Примеры полных конфигураций + +::: tip +Рекомендуется сначала запустить `bridge`, а затем `portal`. +::: + +### Настройка bridge + +`bridge` обычно требуется два исходящих подключения: одно для подключения к `portal`, а другое - для отправки фактического трафика. +Другими словами, вам нужно использовать маршрутизацию, чтобы разделять эти два типа трафика. + +Настройки обратного прокси: + +```json +{ + "bridges": [ + { + "tag": "bridge", + "domain": "test.xray.com" + } + ] +} +``` + +Исходящие подключения: + +```json +{ + "tag": "out", + "protocol": "freedom", + "settings": { + "redirect": "127.0.0.1:80" // Перенаправить весь трафик на веб-сервер + } +} +``` + +```json +{ + "protocol": "vmess", + "settings": { + "vnext": [ + { + "address": "IP-адрес portal", + "port": 1024, + "users": [ + { + "id": "5783a3e7-e373-51cd-8642-c83782b807c5" + } + ] + } + ] + }, + "tag": "interconn" +} +``` + +Настройки маршрутизации: + +```json +{ + "rules": [ + { + "type": "field", + "inboundTag": ["bridge"], + "domain": ["full:test.xray.com"], + "outboundTag": "interconn" + }, + { + "type": "field", + "inboundTag": ["bridge"], + "outboundTag": "out" + } + ] +} +``` + +### Настройка portal + +`portal` обычно требуется два входящих подключения: одно для приема соединений от `bridge`, а другое - для приема фактического трафика. +Вам также нужно использовать маршрутизацию, чтобы разделять эти два типа трафика. + +Настройки обратного прокси: + +```json +{ + "portals": [ + { + "tag": "portal", + "domain": "test.xray.com" // Должно совпадать с настройками bridge + } + ] +} +``` + +Входящие подключения: + +```json +{ + "tag": "external", + "port": 80, + "protocol": "dokodemo-door", + "settings": { + "address": "127.0.0.1", + "port": 80, + "network": "tcp" + } +} +``` + +```json +{ + "port": 1024, + "tag": "interconn", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "5783a3e7-e373-51cd-8642-c83782b807c5" + } + ] + } +} +``` + +Настройки маршрутизации: + +```json +{ + "rules": [ + { + "type": "field", + "inboundTag": ["external"], + "outboundTag": "portal" + }, + { + "type": "field", + "inboundTag": ["interconn"], + "outboundTag": "portal" + } + ] +} +``` \ No newline at end of file diff --git a/docs/ru/config/routing.md b/docs/ru/config/routing.md new file mode 100644 index 000000000..2e0d39c18 --- /dev/null +++ b/docs/ru/config/routing.md @@ -0,0 +1,357 @@ +# Маршрутизация + +Модуль маршрутизации позволяет направлять входящие данные через разные исходящие подключения в соответствии с различными правилами, что позволяет реализовать проксирование по требованию. + +Например, распространенным сценарием использования является разделение трафика на внутренний и внешний. +Xray может определять трафик из разных регионов с помощью внутренних механизмов и отправлять его через разные исходящие подключения. + +Более подробное описание функции маршрутизации: [Введение в маршрутизацию (routing)](https://xtls.github.io/ru/document/level-1/routing-lv1-part1.html). + +## RoutingObject + +`RoutingObject` соответствует полю `routing` в конфигурационном файле. + +```json +{ + "routing": { + "domainStrategy": "AsIs", + "domainMatcher": "hybrid", + "rules": [], + "balancers": [] + } +} +``` + +> `domainStrategy`: "AsIs" | "IPIfNonMatch" | "IPOnDemand" + +Стратегия разрешения доменных имен. + +- `"AsIs"`: использовать только доменные имена для выбора маршрута. + Значение по умолчанию. +- `"IPIfNonMatch"`: если доменное имя не соответствует ни одному правилу, разрешить доменное имя в IP-адрес (запись A или AAAA) и снова выполнить сопоставление. + - Если у домена есть несколько записей A, Xray попытается сопоставить все записи A, пока одна из них не совпадет с каким-либо правилом. + - Разрешенный IP-адрес используется только для выбора маршрута, в пересылаемых пакетах данных по-прежнему используется исходное доменное имя. +- `"IPOnDemand"`: при сопоставлении с любым правилом, основанным на IP-адресе, доменное имя немедленно разрешается в IP-адрес для сопоставления. + +> `domainMatcher`: "hybrid" | "linear" + +Алгоритм сопоставления доменных имен. +Этот параметр влияет на все `RuleObject`, для которых не указан алгоритм сопоставления. + +- `"hybrid"`: использовать новый алгоритм сопоставления доменных имен, который работает быстрее и занимает меньше памяти. + Значение по умолчанию. +- `"linear"`: использовать старый алгоритм сопоставления доменных имен. + +> `rules`: \[[RuleObject](#ruleobject)\] + +Массив, каждый элемент которого представляет собой правило. + +Для каждого соединения маршрутизатор проверяет правила сверху вниз. +Когда встречается первое подходящее правило, соединение перенаправляется на исходящее подключение, указанное в его `outboundTag` или `balancerTag`. + +::: tip +Если ни одно правило не подходит, трафик отправляется через первое исходящее подключение по умолчанию. +::: + +> `balancers`: \[ [BalancerObject](#balancerobject) \] + +Массив, каждый элемент которого представляет собой конфигурацию балансировщика нагрузки. + +Если правило указывает на балансировщик нагрузки, Xray выбирает исходящее подключение через этот балансировщик нагрузки и перенаправляет трафик через него. + +### RuleObject + +```json +{ + "domainMatcher": "hybrid", + "type": "field", + "domain": ["baidu.com", "qq.com", "geosite:cn"], + "ip": ["0.0.0.0/8", "10.0.0.0/8", "fc00::/7", "fe80::/10", "geoip:cn"], + "port": "53,443,1000-2000", + "sourcePort": "53,443,1000-2000", + "network": "tcp", + "source": ["10.0.0.1"], + "user": ["love@xray.com"], + "inboundTag": ["tag-vmess"], + "protocol": ["http", "tls", "bittorrent"], + "attrs": { ":method": "GET" }, + "outboundTag": "direct", + "balancerTag": "balancer" +} +``` + +::: danger +Если указано несколько атрибутов, правило применяется только в том случае, если **все** атрибуты совпадают. +::: + +> `domainMatcher`: "hybrid" | "linear" + +Алгоритм сопоставления доменных имен. +Этот параметр имеет приоритет над параметром `domainMatcher` в `RoutingObject`. + +- `"hybrid"`: использовать новый алгоритм сопоставления доменных имен, который работает быстрее и занимает меньше памяти. + Значение по умолчанию. +- `"linear"`: использовать старый алгоритм сопоставления доменных имен. + +> `type`: "field" + +В настоящее время поддерживается только значение `"field"`. + +::: tip +В Xray-core v1.8.7 и более поздних версиях эту строку можно опустить. +::: + +> `domain`: \[string\] + +Массив, каждый элемент которого представляет собой шаблон доменного имени. +Доступны следующие форматы: + +- Простая строка: правило применяется, если эта строка соответствует любой части целевого доменного имени. + Например, "sina.com" соответствует "sina.com", "sina.com.cn" и "www.sina.com", но не соответствует "sina.cn". +- Регулярное выражение: начинается с `"regexp:"`, а остальная часть - это регулярное выражение. + Правило применяется, если это регулярное выражение соответствует целевому доменному имени. + Например, "regexp:\\\\.goo.\*\\\\.com\$" соответствует "www.google.com" или "fonts.googleapis.com", но не соответствует "google.com". +- Поддомен (рекомендуется): начинается с `"domain:"`, а остальная часть - это доменное имя. + Правило применяется, если это доменное имя является целевым доменным именем или его поддоменом. + Например, "domain:xray.com" соответствует "www.xray.com" и "xray.com", но не соответствует "wxray.com". +- Полное совпадение: начинается с `"full:"`, а остальная часть - это доменное имя. + Правило применяется, если это доменное имя полностью совпадает с целевым доменным именем. + Например, "full:xray.com" соответствует "xray.com", но не соответствует "www.xray.com". +- Предопределенный список доменов: начинается с `"geosite:"`, а остальная часть - это имя, например `geosite:google` или `geosite:cn`. + Список имен и доменов см. в разделе [Предопределенные списки доменов](#предопределенные-списки-доменов). +- Загрузка доменов из файла: имеет вид `"ext:file:tag"`, где `ext:` (в нижнем регистре) - префикс, за которым следует имя файла и тег. + Файл должен находиться в [каталоге ресурсов](./features/env.md#пути-к-файлам-ресурсов). + Формат файла такой же, как у `geosite.dat`. + Тег должен присутствовать в файле. + +::: tip +`"ext:geoip.dat:cn"` эквивалентно `"geoip:cn"`. +::: + +> `ip`: \[string\] + +Массив, каждый элемент которого представляет собой диапазон IP-адресов. +Правило применяется, если один из элементов соответствует целевому IP-адресу. +Доступны следующие форматы: + +- IP-адрес: например, `"127.0.0.1"`. +- [CIDR](https://ru.wikipedia.org/wiki/Бесклассовая_междоменная_маршрутизация): например, `"10.0.0.0/8"`. +- Предопределенный список IP-адресов: этот список включен в каждый установочный пакет Xray и называется `geoip.dat`. + Используйте формат `"geoip:cn"`, где `geoip:` (в нижнем регистре) - префикс, за которым следует двухбуквенный код страны. + Поддерживаются практически все страны с доступом в Интернет. + - Специальное значение: `"geoip:private"`, включает все частные IP-адреса, например `127.0.0.1`. + - Инверсия (!): `"geoip:!cn"` означает все IP-адреса, кроме тех, что указаны в `geoip:cn`. +- Загрузка IP-адресов из файла: имеет вид `"ext:file:tag"`, где `ext:` (в нижнем регистре) - префикс, за которым следует имя файла и тег. + Файл должен находиться в [каталоге ресурсов](./features/env.md#пути-к-файлам-ресурсов). + Формат файла такой же, как у `geoip.dat`. + Тег должен присутствовать в файле. + +> `port`: number | string + +Диапазон портов назначения. +Доступны следующие форматы: + +- `"a-b"`: `a` и `b` - положительные целые числа, меньшие 65536. + Диапазон является замкнутым, правило применяется, если целевой порт находится в этом диапазоне. +- `a`: `a` - положительное целое число, меньшее 65536. + Правило применяется, если целевой порт равен `a`. +- Комбинация двух вышеуказанных форматов, разделенных запятыми ",". + Например: `"53,443,1000-2000"`. + +> `sourcePort`: number | string + +Порт источника. +Доступны следующие форматы: + +- `"a-b"`: `a` и `b` - положительные целые числа, меньшие 65536. + Диапазон является замкнутым, правило применяется, если порт источника находится в этом диапазоне. +- `a`: `a` - положительное целое число, меньшее 65536. + Правило применяется, если порт источника равен `a`. +- Комбинация двух вышеуказанных форматов, разделенных запятыми ",". + Например: `"53,443,1000-2000"`. + +> `network`: "tcp" | "udp" | "tcp,udp" + +Допустимые значения: "tcp", "udp" или "tcp,udp". +Правило применяется, если тип сети соединения соответствует указанному значению. + +> `source`: \[string\] + +Массив, каждый элемент которого представляет собой диапазон IP-адресов. +Доступны следующие форматы: IP-адрес, CIDR, GeoIP и загрузка IP-адресов из файла. +Правило применяется, если один из элементов соответствует IP-адресу источника. + +> `user`: \[string\] + +Массив, каждый элемент которого представляет собой адрес электронной почты. +Правило применяется, если один из элементов соответствует пользователю источника. + +> `inboundTag`: \[string\] + +Массив, каждый элемент которого представляет собой тег. +Правило применяется, если один из элементов соответствует тегу входящего протокола. + +> `protocol`: \[ "http" | "tls" | "bittorrent" \] + +Массив, каждый элемент которого представляет собой протокол. +Правило применяется, если один из элементов соответствует типу протокола текущего соединения. + +::: tip +Для определения типа протокола соединения необходимо включить параметр `sniffing` во входящем подключении. +::: + +> `attrs`: object + +Объект JSON, где ключи и значения являются строками, используемый для проверки атрибутов трафика. +Правило применяется, если заголовки HTTP содержат все указанные ключи, а значения содержат указанные подстроки. +Ключи нечувствительны к регистру. +Значения могут быть регулярными выражениями. + +В настоящее время этот атрибут устанавливается только входящим прокси HTTP. + +Примеры: + +- Проверка HTTP GET: `{":method": "GET"}` +- Проверка пути HTTP: `{":path": "/test"}"` +- Проверка типа содержимого: `{"accept": "text/html"}"` + +> `outboundTag`: string + +Тег исходящего подключения. + +> `balancerTag`: string + +Тег балансировщика нагрузки. + +::: tip +Необходимо указать либо `balancerTag`, либо `outboundTag`. +Если указаны оба параметра, используется `outboundTag`. +::: + +### BalancerObject + +Настройки балансировщика нагрузки. +Когда балансировщик нагрузки активируется, он выбирает наиболее подходящее исходящее подключение из указанных и перенаправляет трафик через него. + +```json +{ + "tag": "balancer", + "selector": [], + "fallbackTag": "outbound", + "strategy": {} +} +``` + +> `tag`: string + +Тег этого балансировщика нагрузки, используемый для сопоставления с `balancerTag` в `RuleObject`. + +> `selector`: \[ string \] + +Массив строк, каждый элемент которого будет использоваться для сопоставления с префиксом тега исходящего подключения. +Например, для следующих тегов исходящих подключений: `[ "a", "ab", "c", "ba" ]`, `"selector": ["a"]` будет соответствовать `[ "a", "ab" ]`. + +Если найдено несколько совпадений, балансировщик нагрузки в настоящее время выбирает одно из них случайным образом. + +> `fallbackTag`: string + +Исходящее подключение, которое будет использоваться, если балансировщик нагрузки не сможет выбрать подходящее исходящее подключение. + +> `strategy`: [StrategyObject](#strategyobject) + +#### StrategyObject +```json +{ + "type": "roundRobin", + "settings": {} +} +``` +> `type` : "random" | "roundRobin" | "leastPing" | "leastLoad" + +- `random` (значение по умолчанию): случайный выбор из подходящих исходящих подключений. +- `roundRobin`: последовательный выбор из подходящих исходящих подключений. +- `leastPing`: выбор исходящего подключения с наименьшей задержкой на основе результатов мониторинга подключений. + Требуется настроить [observatory](./observatory.md#observatoryobject). +- `leastLoad`: выбор наиболее стабильного исходящего подключения на основе результатов мониторинга подключений. + Требуется настроить [burstObservatory](./observatory.md#burstobservatoryobject). + +> `settings`: [StrategySettingsObject](#strategysettingsobject) + +##### StrategySettingsObject + +Это необязательный параметр. Формат настройки зависит от стратегии балансировки нагрузки. +В настоящее время этот параметр можно использовать только со стратегией `leastLoad`. + +### Пример конфигурации балансировщика нагрузки + +```json + "routing": { + "rules": [ + { + "inboundTag": [ + "in" + ], + "balancerTag": "round" + } + ], + "balancers" : [ + { + "selector": [ + "out" + ], + "strategy": { + "type":"roundRobin" + }, + "tag": "round" + } + ] + } + + "inbounds": [ + { + // Настройки входящего подключения + "tag": "in" + } + ] + + "outbounds": [ + { + // Настройки исходящего подключения + "tag": "out1" + }, + { + // Настройки исходящего подключения + "tag": "out2" + } + ] +``` + +### Предопределенные списки доменов + +Этот список включен в каждый установочный пакет Xray и называется `geosite.dat`. +Этот файл содержит некоторые распространенные доменные имена. +Формат использования: `geosite:filename`, например `geosite:google` означает сопоставление с доменными именами, указанными в файле в разделе `google`, для маршрутизации или фильтрации DNS. + +Распространенные доменные имена: + +- `category-ads`: содержит доменные имена распространенных рекламных сервисов. +- `category-ads-all`: содержит доменные имена распространенных рекламных сервисов, а также доменные имена поставщиков рекламы. +- `cn`: эквивалентно объединению `geolocation-cn` и `tld-cn`. +- `apple`: содержит большинство доменных имен Apple. +- `google`: содержит большинство доменных имен Google. +- `microsoft`: содержит большинство доменных имен Microsoft. +- `facebook`: содержит большинство доменных имен Facebook. +- `twitter`: содержит большинство доменных имен Twitter. +- `telegram`: содержит большинство доменных имен Telegram. +- `geolocation-cn`: содержит доменные имена распространенных сайтов, расположенных в Китае. +- `geolocation-!cn`: содержит доменные имена распространенных сайтов, расположенных за пределами Китая, а также `tld-!cn`. +- `tld-cn`: содержит доменные имена верхнего уровня, управляемые CNNIC и используемые в Китае, например, домены, оканчивающиеся на `.cn`, `.中国`. +- `tld-!cn`: содержит доменные имена верхнего уровня, не используемые в Китае, например, домены, оканчивающиеся на `.hk` (Гонконг), `.tw` (Тайвань), `.jp` (Япония), `.sg` (Сингапур), `.us` (США), `.ca` (Канада) и т.д. + +Вы также можете просмотреть полный список доменов здесь: [Domain list community](https://github.com/v2fly/domain-list-community). + + + + + + diff --git a/docs/ru/config/stats.md b/docs/ru/config/stats.md new file mode 100644 index 000000000..9ec68f68a --- /dev/null +++ b/docs/ru/config/stats.md @@ -0,0 +1,56 @@ +# Статистика + +Используется для настройки сбора статистики трафика Xray. + +## StatsObject + +`StatsObject` соответствует полю `stats` в конфигурационном файле. + +```json +{ + "stats": {} +} +``` + +В настоящее время для статистики не требуется никаких параметров. +Если поле `StatsObject` присутствует, внутренняя статистика включается. + +После включения статистики вам нужно только включить соответствующие параметры в разделе [Политики](./policy.md), чтобы начать сбор статистики. + +## Получение статистики + +Вы можете получить статистику с помощью соответствующих команд `xray api`. + +В настоящее время доступна следующая статистика: + +- Данные пользователя + + - `user>>>[email]>>>traffic>>>uplink` + + Исходящий трафик для определенного пользователя в байтах. + + - `user>>>[email]>>>traffic>>>downlink` + + Входящий трафик для определенного пользователя в байтах. + +::: tip +Если для пользователя не указан email, статистика для него не будет собираться. +::: + +- Глобальные данные + + - `inbound>>>[tag]>>>traffic>>>uplink` + + Исходящий трафик для определенного входящего подключения в байтах. + + - `inbound>>>[tag]>>>traffic>>>downlink` + + Входящий трафик для определенного входящего подключения в байтах. + + - `outbound>>>[tag]>>>traffic>>>uplink` + + Исходящий трафик для определенного исходящего подключения в байтах. + + - `outbound>>>[tag]>>>traffic>>>downlink` + + Входящий трафик для определенного исходящего подключения в байтах. \ No newline at end of file diff --git a/docs/ru/config/transport.md b/docs/ru/config/transport.md new file mode 100644 index 000000000..5c867861f --- /dev/null +++ b/docs/ru/config/transport.md @@ -0,0 +1,889 @@ +# Транспорт + +Транспорт (transport) - это способ, которым текущий узел Xray взаимодействует с другими узлами. + +Транспорт определяет способ передачи данных. Обычно оба конца сетевого подключения должны использовать одинаковый транспорт. +Например, если один конец использует WebSocket, то другой конец также должен использовать WebSocket, иначе соединение не будет установлено. + +Настройка транспорта (transport) состоит из двух частей: + +1. ~~Глобальные настройки ([TransportObject](#transportobject)) (устарело)~~ +2. Локальные настройки ([StreamSettingsObject](#streamsettingsobject)). + +- Локальные настройки позволяют указать способ передачи данных для каждого отдельного входящего или исходящего подключения. +- Обычно клиент и сервер должны использовать одинаковый транспорт для соответствующих входящих и исходящих подключений. + Если в настройках указан тип транспорта, но не указаны конкретные параметры, будут использованы настройки из глобальной конфигурации. + +
+Глобальные настройки + + +## TransportObject (устарело) + +`TransportObject` соответствует полю `transport` в конфигурационном файле. + +```json +{ + "transport": { + "tcpSettings": {}, + "kcpSettings": {}, + "wsSettings": {}, + "httpSettings": {}, + "quicSettings": {}, + "dsSettings": {}, + "grpcSettings": {}, + "httpupgradeSettings": {} + } +} +``` + +> `tcpSettings`: [TcpObject](./transports/tcp.md) + +Настройки TCP-подключений. + +> `kcpSettings`: [KcpObject](./transports/mkcp.md) + +Настройки mKCP-подключений. + +> `wsSettings`: [WebSocketObject](./transports/websocket.md) + +Настройки WebSocket-подключений. + +> `httpSettings`: [HttpObject](./transports/h2.md) + +Настройки HTTP/2-подключений. + +> `quicSettings`: [QuicObject](./transports/quic.md) + +Настройки QUIC-подключений. + +> `grpcSettings`: [GRPCObject](./transports/grpc.md) + +Настройки gRPC-подключений. + +> `httpupgradeSettings`: [HttpUpgradeObject](./transports/httpupgrade.md) + +Настройки HTTPUpgrade-подключений. + +> `splithttpSettings`: [SplitHttpObject](./transports/splithttp.md) + +Настройки SplitHTTP-подключений. + +> `dsSettings`: [DomainSocketObject](./transports/domainsocket.md) + +Настройки Domain Socket-подключений. + +
+ +## StreamSettingsObject + +`StreamSettingsObject` соответствует полю `streamSettings` во входящем или исходящем подключении. +Каждое входящее или исходящее подключение может иметь свои собственные настройки транспорта. + +```json +{ + "network": "tcp", + "security": "none", + "tlsSettings": {}, + "tcpSettings": {}, + "kcpSettings": {}, + "wsSettings": {}, + "httpSettings": {}, + "quicSettings": {}, + "dsSettings": {}, + "grpcSettings": {}, + "httpupgradeSettings": {}, + "splithttpSettings": {}, + "sockopt": { + "mark": 0, + "tcpMaxSeg": 1440, + "tcpFastOpen": false, + "tproxy": "off", + "domainStrategy": "AsIs", + "dialerProxy": "", + "acceptProxyProtocol": false, + "tcpKeepAliveInterval": 0, + "tcpKeepAliveIdle": 300, + "tcpUserTimeout": 10000, + "tcpCongestion": "bbr", + "interface": "wg0", + "v6only": false, + "tcpWindowClamp": 600, + "tcpMptcp": false, + "tcpNoDelay": false, + "customSockopt": [] + } +} +``` + +> `network`: "tcp" | "ws" | "h2" | "grpc" | "quic" | "kcp" | "httpupgrade" | "splithttp" + +Тип транспорта, используемый для передачи данных. +Значение по умолчанию - `"tcp"`. + +::: tip +"h2" можно записать как "http", "grpc" - как "gun", "kcp" - как "mkcp". +::: + +> `security`: "none" | "tls" | "reality" + +Включить шифрование транспортного уровня. +Доступные значения: + +- `"none"` - без шифрования (значение по умолчанию). +- `"tls"` - использовать [TLS](https://ru.wikipedia.org/wiki/Transport_Layer_Security). +- `"reality"` - использовать REALITY. + +> `tlsSettings`: [TLSObject](#tlsobject) + +Настройки TLS. +TLS предоставляется Golang. +Обычно в результате согласования TLS используется TLS 1.3, DTLS не поддерживается. + +> `realitySettings`: [RealityObject](#realityobject) + +Настройки Reality. +Reality - это оригинальная технология Xray. +Reality обеспечивает более высокий уровень безопасности, чем TLS, и настраивается аналогично TLS. + +::: tip +Reality - это самый безопасный на данный момент способ шифрования транспорта, и внешний трафик выглядит как обычный интернет-трафик. +Включение Reality и настройка правильного режима управления потоком XTLS Vision может привести к увеличению производительности в несколько раз. +::: + +> `tcpSettings`: [TcpObject](./transports/tcp.md) + +Настройки TCP для текущего подключения, действуют только при использовании TCP. +Настройки аналогичны глобальным настройкам, описанным выше. + +> `kcpSettings`: [KcpObject](./transports/mkcp.md) + +Настройки mKCP для текущего подключения, действуют только при использовании mKCP. +Настройки аналогичны глобальным настройкам, описанным выше. + +> `wsSettings`: [WebSocketObject](./transports/websocket.md) + +Настройки WebSocket для текущего подключения, действуют только при использовании WebSocket. +Настройки аналогичны глобальным настройкам, описанным выше. + +> `httpSettings`: [HttpObject](./transports/h2.md) + +Настройки HTTP/2 для текущего подключения, действуют только при использовании HTTP/2. +Настройки аналогичны глобальным настройкам, описанным выше. + +> `quicSettings`: [QUICObject](./transports/quic.md) + +Настройки QUIC для текущего подключения, действуют только при использовании QUIC. +Настройки аналогичны глобальным настройкам, описанным выше. + +> `grpcSettings`: [GRPCObject](./transports/grpc.md) + +Настройки gRPC для текущего подключения, действуют только при использовании gRPC. +Настройки аналогичны глобальным настройкам, описанным выше. + +> `dsSettings`: [DomainSocketObject](./transports/domainsocket.md) + +Настройки Domain socket для текущего подключения, действуют только при использовании Domain socket. +Настройки аналогичны глобальным настройкам, описанным выше. + +> `httpupgradeSettings`: [HttpUpgradeObject](./transports/httpupgrade.md) + +Настройки HTTPUpgrade для текущего подключения, действуют только при использовании HTTPUpgrade. +Настройки аналогичны глобальным настройкам, описанным выше. + +> `splithttpSettings`: [SplitHttpObject](./transports/splithttp.md) + +Настройки SplitHTTP для текущего подключения, действуют только при использовании SplitHTTP. +Настройки аналогичны глобальным настройкам, описанным выше. + +> `sockopt`: [SockoptObject](#sockoptobject) + +Настройки прозрачного прокси. + +### TLSObject + +```json +{ + "serverName": "xray.com", + "rejectUnknownSni": false, + "allowInsecure": false, + "alpn": ["h2", "http/1.1"], + "minVersion": "1.2", + "maxVersion": "1.3", + "cipherSuites": "список наборов шифров, разделенных двоеточиями", + "certificates": [], + "disableSystemRoot": false, + "enableSessionResumption": false, + "fingerprint": "", + "pinnedPeerCertificateChainSha256": [""], + "masterKeyLog": "" +} +``` + +> `serverName`: string + +Доменное имя сертификата сервера. +Используется, если соединение установлено по IP-адресу. + +Если этот параметр не указан, автоматически используется значение из `address` (если это доменное имя). +Это значение также используется для проверки действительности сертификата сервера. + +::: tip +Как упомянуто выше, поскольку это значение также используется для проверки действительности сертификата сервера, если по какой-либо причине вам нужно указать значение, отличное от доменного имени в сертификате сервера, необходимо включить параметр `allowInsecure`, иначе проверка сертификата завершится неудачей. +Из соображений безопасности мы не рекомендуем использовать этот метод постоянно. +Если вам нужно безопасно подменить SNI, рассмотрите возможность использования REALITY. + +В частности, если на клиенте указан IP-адрес, Xray не будет отправлять SNI. +Чтобы использовать эту функцию, также необходимо включить `allowInsecure`. +::: + +> `rejectUnknownSni`: bool + +Если значение равно `true`, сервер отклонит рукопожатие TLS, если полученный SNI не совпадает с доменным именем в сертификате. +Значение по умолчанию - `false`. + +> `alpn`: \[ string \] + +Массив строк, указывающий значения ALPN, используемые при рукопожатии TLS. +Значение по умолчанию - `["h2", "http/1.1"]`. + +> `minVersion`: string + +Минимальная допустимая версия TLS. + +> `maxVersion`: string + +Максимальная допустимая версия TLS. + +> `cipherSuites`: string + +Список поддерживаемых наборов шифров, разделенных двоеточиями. + +Список наборов шифров Golang и их описания можно найти [здесь](https://golang.org/src/crypto/tls/cipher_suites.go#L500) или [здесь](https://golang.org/src/crypto/tls/cipher_suites.go#L44). + +::: danger +Эти два параметра не являются обязательными и обычно не влияют на безопасность. +Если они не настроены, Golang автоматически выбирает их в зависимости от устройства. +Если вы не знакомы с этими параметрами, не настраивайте их. +Вы несете ответственность за любые проблемы, вызванные неправильной настройкой. +::: + +> `allowInsecure`: true | false + +Разрешить небезопасные соединения (только для клиентов). +Значение по умолчанию - `false`. + +Если значение равно `true`, Xray не будет проверять действительность сертификата TLS, предоставленного удаленным хостом. + +::: danger +Из соображений безопасности не рекомендуется устанавливать этот параметр в `true` в реальных сценариях, так как это может сделать вас уязвимыми для атак типа "человек посередине". +::: + +> `disableSystemRoot`: true | false + +Отключить использование корневых сертификатов, предоставляемых операционной системой. +Значение по умолчанию - `false`. + +Если значение равно `true`, Xray будет использовать только сертификаты, указанные в `certificates`, для рукопожатия TLS. +Если значение равно `false`, Xray будет использовать только корневые сертификаты, предоставляемые операционной системой, для рукопожатия TLS. + +> `enableSessionResumption`: true | false + +Если этот параметр установлен в `false`, расширение `session_ticket` не будет включено в ClientHello. +Обычно программы на Golang не используют это расширение в ClientHello, поэтому рекомендуется оставить значение по умолчанию. +Значение по умолчанию - `false`. + +> `fingerprint`: string + +Этот параметр используется для настройки отпечатка `TLS Client Hello`. +Если значение пустое, эта функция отключена. +Если эта функция включена, Xray будет **эмулировать** отпечаток `TLS` с помощью библиотеки uTLS или генерировать его случайным образом. +Поддерживаются три способа настройки: + +1. Отпечатки TLS последних версий популярных браузеров, включая: + +- `"chrome"` +- `"firefox"` +- `"safari"` +- `"ios"` +- `"android"` +- `"edge"` +- `"360"` +- `"qq"` + +2. Автоматическая генерация отпечатка при запуске Xray: + +- `"random"`: случайный выбор из отпечатков последних версий браузеров. +- `"randomized"`: генерация полностью случайного уникального отпечатка (100% поддержка TLS 1.3 с использованием X25519). + +3. Использование имен переменных отпечатков uTLS, например, `"HelloRandomizedNoALPN"`, `"HelloChrome_106_Shuffle"`. + Полный список см. в [библиотеке uTLS](https://github.com/refraction-networking/utls/blob/master/u_common.go#L434). + +::: tip +Эта функция только **эмулирует** отпечаток `TLS Client Hello`, поведение и другие отпечатки такие же, как у Golang. +Если вам нужно более полно эмулировать отпечаток `TLS` и поведение браузера, используйте [Browser Dialer](./transports/websocket.md#browser-dialer). +::: + +> `pinnedPeerCertificateChainSha256`: \[string\] + +SHA256-хэш цепочки сертификатов удаленного сервера в стандартном формате кодировки. +Соединение TLS будет успешно установлено, только если хэш цепочки сертификатов сервера совпадает с одним из значений в этом списке. + +Если соединение не удалось установить из-за этой настройки, будет показан хэш цепочки сертификатов удаленного сервера. + +::: danger +Не рекомендуется использовать этот способ для получения хэша цепочки сертификатов, так как в этом случае у вас не будет возможности проверить, является ли сертификат, предоставленный сервером, подлинным, и поэтому вы не можете гарантировать, что полученный хэш сертификата будет ожидаемым. +::: + +::: tip +Если вам нужно получить хэш сертификата, запустите команду `xray tls certChainHash --cert ` в командной строке, где `` - это путь к файлу сертификата. +::: + +> `certificates`: \[ [CertificateObject](#certificateobject) \] + +Список сертификатов, каждый элемент которого представляет собой сертификат (рекомендуется использовать fullchain). + +::: tip +Если вам нужно получить оценку A/A+ в ssllibs или myssl, см. [здесь](https://github.com/XTLS/Xray-core/discussions/56#discussioncomment-215600). +::: + +> `masterKeyLog` : string + +Путь к файлу журнала (pre)-master-secret, который можно использовать для расшифровки TLS-соединений, отправляемых Xray, в таких программах, как Wireshark. +Пока не поддерживается совместное использование с utls. +Требуется Xray-Core v1.8.7. + +### RealityObject + +```json +{ + "show": false, + "dest": "example.com:443", + "xver": 0, + "serverNames": ["example.com", "www.example.com"], + "privateKey": "", + "minClientVer": "", + "maxClientVer": "", + "maxTimeDiff": 0, + "shortIds": ["", "0123456789abcdef"], + "fingerprint": "chrome", + "serverName": "", + "publicKey": "", + "shortId": "", + "spiderX": "" +} +``` + +::: tip +Дополнительную информацию см. в проекте [REALITY](https://github.com/XTLS/REALITY). +::: + +> `show`: true | false + +Если значение равно `true`, выводить отладочную информацию. + +::: tip +Настройки для **входящего** подключения (**сервер**). +::: + +> `dest`: string + +Обязательный параметр, формат такой же, как у `dest` в `fallbacks` для VLESS [dest](https://xtls.github.io/config/features/fallback.html#fallbackobject). + +::: warning +Для лучшей маскировки Xray **напрямую перенаправляет** трафик, не прошедший аутентификацию Reality (незаконные запросы Reality), на `dest`. +Если IP-адрес сайта `dest` является особенным (например, сайт использует CDN CloudFlare), то ваш сервер будет действовать как переадресатор портов для CloudFlare, что может привести к утечке трафика после сканирования. +Чтобы избежать этого, можно использовать Nginx или другие средства для фильтрации нежелательных SNI. +::: + +> `xver`: number + +Необязательный параметр, формат такой же, как у `xver` в `fallbacks` для VLESS [xver](https://xtls.github.io/config/features/fallback.html#fallbackobject). + +> `serverNames`: \[string\] + +Обязательный параметр, список допустимых `serverName` для клиентов. +Пока не поддерживаются подстановочные знаки \*. + +Обычно это значение совпадает с `dest`. +Фактически допустимыми значениями являются любые SNI, принимаемые сервером (в зависимости от конфигурации `dest`). +В качестве ориентира можно использовать [SAN](https://ru.wikipedia.org/wiki/Subject_Alternative_Name) возвращаемого сертификата. + +Может содержать пустое значение `""`, что означает прием подключений без SNI. + +> `privateKey`: string + +Обязательный параметр, сгенерируйте его, выполнив команду `./xray x25519`. + +> `minClientVer`: string + +Необязательный параметр, минимальная версия Xray на клиенте, формат: `x.y.z`. + +> `maxClientVer`: string + +Необязательный параметр, максимальная версия Xray на клиенте, формат: `x.y.z`. + +> `maxTimeDiff`: number + +Необязательный параметр, максимально допустимая разница во времени в миллисекундах. + +> `shortIds`: \[string\] + +Обязательный параметр, список допустимых `shortId` для клиентов, которые можно использовать для различения разных клиентов. + +Состоит из символов от 0 до f, длина должна быть кратна 2, максимальная длина - 16. + +Если список содержит пустое значение, `shortId` на клиенте может быть пустым. + +::: tip +Настройки для **исходящего** подключения (**клиент**). +::: + +> `serverName`: string + +Одно из значений `serverNames` на сервере. + +Если `serverNames` на сервере содержит пустое значение, на клиенте можно использовать `"serverName": "0.0.0.0"`, как и в TLS, для установления соединения без SNI. +В отличие от TLS, в REALITY для использования этой функции не нужно включать параметр `allowInsecure`. +При использовании этой функции убедитесь, что `dest` возвращает сертификат по умолчанию при приеме соединений без SNI. + +> `fingerprint`: string + +Обязательный параметр, такой же, как в [TLSObject](https://xtls.github.io/config/transport.html#tlsobject). + +> `shortId`: string + +Одно из значений `shortIds` на сервере. + +Состоит из символов от 0 до f, длина должна быть кратна 2, максимальная длина - 16. + +Если `shordIDs` на сервере содержит пустое значение, этот параметр на клиенте может быть пустым. + +> `publicKey`: string + +Обязательный параметр, открытый ключ, соответствующий закрытому ключу сервера. +Сгенерируйте его с помощью команды `./xray x25519 -i "закрытый ключ сервера"`. + +> `spiderX`: string + +Начальный путь и параметры для краулера, рекомендуется использовать разные значения для каждого клиента. + +#### CertificateObject + +```json +{ + "ocspStapling": 3600, + "oneTimeLoading": false, + "usage": "encipherment", + "certificateFile": "/path/to/certificate.crt", + "keyFile": "/path/to/key.key", + "certificate": [ + "--BEGIN CERTIFICATE--", + "MIICwDCCAaigAwIBAgIRAO16JMdESAuHidFYJAR/7kAwDQYJKoZIhvcNAQELBQAw", + "ADAeFw0xODA0MTAxMzU1MTdaFw0xODA0MTAxNTU1MTdaMAAwggEiMA0GCSqGSIb3", + "DQEBAQUAA4IBDwAwggEKAoIBAQCs2PX0fFSCjOemmdm9UbOvcLctF94Ox4BpSfJ+", + "3lJHwZbvnOFuo56WhQJWrclKoImp/c9veL1J4Bbtam3sW3APkZVEK9UxRQ57HQuw", + "OzhV0FD20/0YELou85TwnkTw5l9GVCXT02NG+pGlYsFrxesUHpojdl8tIcn113M5", + "pypgDPVmPeeORRf7nseMC6GhvXYM4txJPyenohwegl8DZ6OE5FkSVR5wFQtAhbON", + "OAkIVVmw002K2J6pitPuJGOka9PxcCVWhko/W+JCGapcC7O74palwBUuXE1iH+Jp", + "noPjGp4qE2ognW3WH/sgQ+rvo20eXb9Um1steaYY8xlxgBsXAgMBAAGjNTAzMA4G", + "A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAA", + "MA0GCSqGSIb3DQEBCwUAA4IBAQBUd9sGKYemzwPnxtw/vzkV8Q32NILEMlPVqeJU", + "7UxVgIODBV6A1b3tOUoktuhmgSSaQxjhYbFAVTD+LUglMUCxNbj56luBRlLLQWo+", + "9BUhC/ow393tLmqKcB59qNcwbZER6XT5POYwcaKM75QVqhCJVHJNb1zSEE7Co7iO", + "6wIan3lFyjBfYlBEz5vyRWQNIwKfdh5cK1yAu13xGENwmtlSTHiwbjBLXfk+0A/8", + "r/2s+sCYUkGZHhj8xY7bJ1zg0FRalP5LrqY+r6BckT1QPDIQKYy615j1LpOtwZe/", + "d4q7MD/dkzRDsch7t2cIjM/PYeMuzh87admSyL6hdtK0Nm/Q", + "--END CERTIFICATE--" + ], + "key": [ + "--BEGIN RSA PRIVATE KEY--", + "MIIEowIBAAKCAQEArNj19HxUgoznppnZvVGzr3C3LRfeDseAaUnyft5SR8GW75zh", + "bqOeloUCVq3JSqCJqf3Pb3i9SeAW7Wpt7FtwD5GVRCvVMUUOex0LsDs4VdBQ9tP9", + "GBC6LvOU8J5E8OZfRlQl09NjRvqRpWLBa8XrFB6aI3ZfLSHJ9ddzOacqYAz1Zj3n", + "jkUX+57HjAuhob12DOLcST8np6IcHoJfA2ejhORZElUecBULQIWzjTgJCFVZsNNN", + "itieqYrT7iRjpGvT8XAlVoZKP1viQhmqXAuzu+KWpcAVLlxNYh/iaZ6D4xqeKhNq", + "IJ1t1h/7IEPq76NtHl2/VJtbLXmmGPMZcYAbFwIDAQABAoIBAFCgG4phfGIxK9Uw", + "qrp+o9xQLYGhQnmOYb27OpwnRCYojSlT+mvLcqwvevnHsr9WxyA+PkZ3AYS2PLue", + "C4xW0pzQgdn8wENtPOX8lHkuBocw1rNsCwDwvIguIuliSjI8o3CAy+xVDFgNhWap", + "/CMzfQYziB7GlnrM6hH838iiy0dlv4I/HKk+3/YlSYQEvnFokTf7HxbDDmznkJTM", + "aPKZ5qbnV+4AcQfcLYJ8QE0ViJ8dVZ7RLwIf7+SG0b0bqloti4+oQXqGtiESUwEW", + "/Wzi7oyCbFJoPsFWp1P5+wD7jAGpAd9lPIwPahdr1wl6VwIx9W0XYjoZn71AEaw4", + "bK4xUXECgYEA3g2o9WqyrhYSax3pGEdvV2qN0VQhw7Xe+jyy98CELOO2DNbB9QNJ", + "8cSSU/PjkxQlgbOJc8DEprdMldN5xI/srlsbQWCj72wXxXnVnh991bI2clwt7oYi", + "pcGZwzCrJyFL+QaZmYzLxkxYl1tCiiuqLm+EkjxCWKTX/kKEFb6rtnMCgYEAx0WR", + "L8Uue3lXxhXRdBS5QRTBNklkSxtU+2yyXRpvFa7Qam+GghJs5RKfJ9lTvjfM/PxG", + "3vhuBliWQOKQbm1ZGLbgGBM505EOP7DikUmH/kzKxIeRo4l64mioKdDwK/4CZtS7", + "az0Lq3eS6bq11qL4mEdE6Gn/Y+sqB83GHZYju80CgYABFm4KbbBcW+1RKv9WSBtK", + "gVIagV/89moWLa/uuLmtApyEqZSfn5mAHqdc0+f8c2/Pl9KHh50u99zfKv8AsHfH", + "TtjuVAvZg10GcZdTQ/I41ruficYL0gpfZ3haVWWxNl+J47di4iapXPxeGWtVA+u8", + "eH1cvgDRMFWCgE7nUFzE8wKBgGndUomfZtdgGrp4ouLZk6W4ogD2MpsYNSixkXyW", + "64cIbV7uSvZVVZbJMtaXxb6bpIKOgBQ6xTEH5SMpenPAEgJoPVts816rhHdfwK5Q", + "8zetklegckYAZtFbqmM0xjOI6bu5rqwFLWr1xo33jF0wDYPQ8RHMJkruB1FIB8V2", + "GxvNAoGBAM4g2z8NTPMqX+8IBGkGgqmcYuRQxd3cs7LOSEjF9hPy1it2ZFe/yUKq", + "ePa2E8osffK5LBkFzhyQb0WrGC9ijM9E6rv10gyuNjlwXdFJcdqVamxwPUBtxRJR", + "cYTY2HRkJXDdtT0Bkc3josE6UUDvwMpO0CfAETQPto1tjNEDhQhT", + "--END RSA PRIVATE KEY--" + ] +} +``` + +> `ocspStapling`: number + +Интервал обновления OCSP-стейплинга. +Совпадает с интервалом перезагрузки сертификата. +Единица измерения: секунды. +Значение по умолчанию - `3600` (1 час). + +> `oneTimeLoading`: true | false + +Загрузить только один раз. +Если значение равно `true`, функция перезагрузки сертификата и OCSP-стейплинга отключаются. + +::: warning +Если значение равно `true`, OCSP-стейплинг будет отключен. +::: + +> `usage`: "encipherment" | "verify" | "issue" + +Назначение сертификата. +Значение по умолчанию - `"encipherment"`. + +- `"encipherment"`: сертификат используется для аутентификации и шифрования TLS. +- `"verify"`: сертификат используется для проверки сертификата удаленного TLS-сервера. + При использовании этого значения текущий сертификат должен быть сертификатом ЦС. +- `"issue"`: сертификат используется для выпуска других сертификатов. + При использовании этого значения текущий сертификат должен быть сертификатом ЦС. + +::: tip Совет 1 +В Windows можно установить самозаверяющий сертификат ЦС в систему, чтобы проверять сертификаты удаленных TLS-серверов. +::: + +::: tip Совет 2 +При получении нового запроса от клиента, если указанный `serverName` равен `"xray.com"`, Xray сначала ищет в списке сертификатов сертификат, который можно использовать для `"xray.com"`. +Если подходящий сертификат не найден, Xray использует любой сертификат с `usage` = `"issue"` для выпуска нового сертификата для `"xray.com"` со сроком действия один час. +Новый сертификат добавляется в список сертификатов для последующего использования. +::: + +::: tip Совет 3 +Если указаны и `certificateFile`, и `certificate`, Xray использует `certificateFile`. +То же самое относится к `keyFile` и `key`. +::: + +::: tip Совет 4 +Если `usage` равен `"verify"`, `keyFile` и `key` могут быть пустыми. +::: + +::: tip Совет 5 +Можно сгенерировать самозаверяющий сертификат ЦС с помощью команды `xray tls cert`. +::: + +::: tip Совет 6 +Если у вас есть доменное имя, вы можете легко получить бесплатный сторонний сертификат с помощью таких инструментов, как [acme.sh](https://github.com/acmesh-official/acme.sh). +::: + +> `certificateFile`: string + +Путь к файлу сертификата, например, сертификат, сгенерированный OpenSSL, с расширением .crt. + +> `certificate`: \[ string \] + +Массив строк, представляющий содержимое сертификата, как показано в примере. +`certificate` и `certificateFile` - взаимоисключающие параметры. + +> `keyFile`: string + +Путь к файлу ключа, например, ключ, сгенерированный OpenSSL, с расширением .key. +В настоящее время не поддерживаются файлы ключей, защищенные паролем. + +> `key`: \[ string \] + +Массив строк, представляющий содержимое ключа, как показано в примере. +`key` и `keyFile` - взаимоисключающие параметры. + +### SockoptObject + +```json +{ + "mark": 0, + "tcpMaxSeg": 1440, + "tcpFastOpen": false, + "tproxy": "off", + "domainStrategy": "AsIs", + "dialerProxy": "", + "acceptProxyProtocol": false, + "tcpKeepAliveInterval": 0, + "tcpKeepAliveIdle": 300, + "tcpUserTimeout": 10000, + "tcpcongestion": "bbr", + "interface": "wg0", + "V6Only": false, + "tcpWindowClamp": 600, + "tcpMptcp": false, + "tcpNoDelay": false, + "customSockopt": [] +} +``` + +> `mark`: number + +Целое число. +Если значение не равно нулю, исходящее соединение будет помечено этим значением SO_MARK. + +- Работает только в Linux. +- Требуются права CAP_NET_ADMIN. + +> `tcpMaxSeg`: number + +Устанавливает максимальный размер сегмента TCP (MSS). + +> `tcpFastOpen`: true | false | number + +Включить [TCP Fast Open](https://ru.wikipedia.org/wiki/TCP_Fast_Open) (TFO). + +Если значение равно `true` или **положительному целому числу**, TFO включен. +Если значение равно `false` или **отрицательному числу**, TFO принудительно отключен. +Если этот параметр не указан или равен `0`, используются настройки системы по умолчанию. +Может использоваться как для входящих, так и для исходящих подключений. + +- Доступно только в следующих (или более поздних) версиях операционных систем: + + - Linux 3.16: требуется настроить параметр ядра `net.ipv4.tcp_fastopen`. + Этот параметр представляет собой битовую маску, где `0x1` означает, что TFO разрешен для клиентов, а `0x2` - для серверов. + Значение по умолчанию - `0x1`. + Если вы хотите включить TFO на сервере, установите значение этого параметра ядра в `0x3`. + - ~~Windows 10 (1607)~~ (неправильная реализация). + - Mac OS 10.11 / iOS 9 (требуется тестирование). + - FreeBSD 10.3 (сервер) / 12.0 (клиент): необходимо установить параметры ядра `net.inet.tcp.fastopen.server_enabled` и `net.inet.tcp.fastopen.client_enabled` в `1`. + (Требуется тестирование.) + +- Для входящих подключений указанное здесь **положительное целое число** представляет собой [максимальное количество ожидающих запросов на подключение TFO](https://tools.ietf.org/html/rfc7413#section-5.1). + **Обратите внимание, что не все операционные системы поддерживают эту настройку:** + + - Linux / FreeBSD: указанное здесь **положительное целое число** представляет собой максимальное значение, максимальное допустимое значение - 2147483647. + Если значение равно `true`, используется значение `256`. + Обратите внимание, что в Linux параметр `net.core.somaxconn` ограничивает максимальное значение этого параметра. + Если значение превышает `somaxconn`, увеличьте `somaxconn`. + - Mac OS: если значение равно `true` или **положительному целому числу**, это означает только, что TFO включен. + Максимальное значение нужно настроить отдельно с помощью параметра ядра `net.inet.tcp.fastopen_backlog`. + - Windows: если значение равно `true` или **положительному целому числу**, это означает только, что TFO включен. + +- Для исходящих подключений значение `true` или **положительное целое число** означает только, что TFO включен, во всех операционных системах. + +> `tproxy`: "redirect" | "tproxy" | "off" + +Включить прозрачное проксирование (только для Linux). + +- `"redirect"`: использовать прозрачное проксирование в режиме Redirect. + Поддерживаются все TCP- и UDP-соединения на основе IPv4/6. +- `"tproxy"`: использовать прозрачное проксирование в режиме TProxy. + Поддерживаются все TCP- и UDP-соединения на основе IPv4/6. +- `"off"`: отключить прозрачное проксирование. + +Для прозрачного проксирования требуются права root или `CAP\_NET\_ADMIN`. + +::: danger +Если в [Dokodemo-door](./inbounds/dokodemo.md) параметр `followRedirect` установлен в `true`, а параметр `tproxy` в Sockopt не указан, значение `tproxy` в Sockopt будет установлено в `"redirect"`. +::: + +> `domainStrategy`: "AsIs"
+> "UseIP" | "UseIPv6v4" | "UseIPv6" | "UseIPv4v6" | "UseIPv4"
+> "ForceIP" | "ForceIPv6v4" | "ForceIPv6" | "ForceIPv4v6" | "ForceIPv4" + + +В предыдущих версиях, когда Xray пытался установить системное соединение с использованием доменного имени, разрешение доменного имени выполнялось системой и не контролировалось Xray. +Это приводило к таким проблемам, как [невозможность разрешения доменных имен в нестандартных средах Linux](https://github.com/v2ray/v2ray-core/issues/1909). +Чтобы решить эту проблему, в Xray 1.3.1 был добавлен параметр `domainStrategy` в Sockopt, аналогичный параметру в Freedom. + +Значение по умолчанию - `"AsIs"`. + +При использовании доменного имени в качестве целевого адреса поведение Freedom зависит от значения этого параметра: + +- `"AsIs"`: Xray будет использовать системный стек для установления соединения, приоритет и выбор IP-адреса зависят от настроек системы. +- При указании других значений будет использоваться [встроенный DNS-сервер](../dns.md) Xray-core для разрешения доменного имени. + Если `DnsObject` не настроен, будет использоваться системный DNS. + Если для домена найдено несколько подходящих IP-адресов, ядро случайным образом выберет один из них в качестве целевого IP-адреса. +- `"IPv4"` - попытаться использовать только IPv4 для подключения, `"IPv4v6"` - попытаться использовать IPv4 или IPv6, но для доменов с двумя стеками отдать предпочтение IPv4. + (То же самое относится к `IPv6v4`, не будем повторяться.) +- Если в настройках встроенного DNS указан параметр `"queryStrategy"`, фактическое поведение будет определяться пересечением этих двух параметров, будут разрешены только IP-адреса тех типов, которые указаны в обоих параметрах. + Например, если `"queryStrategy": "UseIPv4"` и `"domainStrategy": "UseIP"`, это фактически эквивалентно `"domainStrategy": "UseIPv4"`. +- Если используется значение, начинающееся с `"Use"`, и результат разрешения не соответствует требованиям (например, у домена есть только IPv4-адрес, но используется `UseIPv6`), Xray переключится на `AsIs`. +- Если используется значение, начинающееся с `"Force"`, и результат разрешения не соответствует требованиям, соединение не будет установлено. + +::: tip Совет +При использовании режимов `"UseIP"` и `"ForceIP"` и указании `sendThrough` в [настройках исходящего подключения](../outbound.md#outboundobject) Freedom автоматически определит необходимый тип IP-адреса (IPv4 или IPv6) на основе значения `sendThrough`. +Если вручную указан один тип IP-адреса (например, `UseIPv4`), но он не совпадает с локальным адресом, указанным в `sendThrough`, соединение не будет установлено. +::: + +::: danger + +Неправильная настройка этой функции может привести к бесконечному циклу. + +Коротко говоря: для подключения к серверу нужно дождаться результата DNS-запроса, а для завершения DNS-запроса нужно подключиться к серверу. + +> Тони: Что было раньше, курица или яйцо? + +Подробное объяснение: + +1. Условия возникновения: прокси-сервер (proxy.com), встроенный DNS-сервер, не локальный режим. +2. **Перед** установлением TCP-соединения с proxy.com Xray пытается разрешить proxy.com с помощью встроенного DNS-сервера. +3. Встроенный DNS-сервер устанавливает соединение с dns.com и отправляет запрос для получения IP-адреса proxy.com. +4. **Неправильные** правила маршрутизации приводят к тому, что proxy.com проксирует запрос, отправленный на шаге 3. +5. Xray пытается установить еще одно TCP-соединение с proxy.com. +6. Перед установлением соединения Xray пытается разрешить proxy.com с помощью встроенного DNS-сервера. +7. Встроенный DNS-сервер повторно использует соединение, установленное на шаге 3, и отправляет запрос. +8. Возникает проблема: установление соединения на шаге 3 ожидает результата запроса на шаге 7, а завершение запроса на шаге 7 ожидает полного установления соединения на шаге 3. +9. Игра окончена! + +Решения: + +- Изменить правила разделения трафика для встроенного DNS-сервера. +- Использовать Hosts. +- ~~Если вы все еще не знаете, как решить эту проблему, не используйте эту функцию.~~ + +Поэтому **не рекомендуется** неопытным пользователям использовать эту функцию без необходимости. +::: + +> `dialerProxy`: "" + +Тег исходящего прокси. +Если значение не пустое, исходящие соединения будут устанавливаться через указанное исходящее подключение. +Этот параметр можно использовать для цепочечной пересылки с поддержкой транспорта. + +::: danger +Этот параметр несовместим с `ProxySettingsObject.Tag`. +::: + +> `acceptProxyProtocol`: true | false + +Только для входящих подключений. +Указывает, следует ли принимать PROXY protocol. + +[PROXY protocol](https://www.haproxy.org/download/2.2/doc/proxy-protocol.txt) используется для передачи реального IP-адреса и порта источника запроса. +**Если вы не знакомы с ним, пропустите этот параметр.** + +Распространенные обратные прокси (например, HAProxy, Nginx) можно настроить на отправку PROXY protocol, VLESS fallbacks xver также может отправлять его. + +Если значение равно `true`, то после установления TCP-соединения на самом нижнем уровне отправитель запроса должен сначала отправить PROXY protocol v1 или v2, иначе соединение будет разорвано. + +> `tcpKeepAliveInterval`: number + +Интервал между отправкой пакетов TCP keep-alive в секундах. ~~Работает только в Linux.~~ + +Это keep-alive-пакеты, отправляемые при ненормальном состоянии соединения (не получен ACK). + +Если этот параметр не указан или равен 0, используется значение по умолчанию Golang. + +::: tip +Если указать отрицательное значение, например `-1`, TCP keep-alive будет отключен. +::: + +> `tcpKeepAliveIdle`: number + +Порог простоя TCP-соединения в секундах. +Keep-alive-пакеты будут отправляться, когда время простоя TCP-соединения достигнет этого порога. + +Это keep-alive-пакеты, отправляемые при нормальном состоянии соединения. + +Если этот параметр не указан или равен 0, используется значение по умолчанию Golang. + +::: tip +Если указать отрицательное значение, например `-1`, TCP keep-alive будет отключен. +::: + +> `tcpUserTimeout`: number + +Измеряется в миллисекундах. +Подробнее: https://github.com/grpc/proposal/blob/master/A18-tcp-user-timeout.md + +> `tcpcongestion`: "" + +Алгоритм управления перегрузкой TCP. +Поддерживается только в Linux. +Если этот параметр не указан, используется значение по умолчанию системы. + +::: tip Распространенные алгоритмы + +- bbr (рекомендуется) +- cubic +- reno + +::: + +::: tip +Выполните команду `sysctl net.ipv4.tcp_congestion_control`, чтобы узнать значение по умолчанию системы. +::: + +> `interface`: "" + +Имя сетевого интерфейса, к которому нужно привязаться. +Поддерживается в Linux / iOS / Mac OS / Windows.
+Для iOS / Mac OS требуется Xray-core v1.8.6 или более поздней версии.
+Для Windows требуется Xray-core v1.8.7 или более поздней версии. + +> `V6Only`: true | false + +Если значение равно `true`, адрес `::` будет принимать только IPv6-соединения. +Поддерживается только в Linux. + +> `tcpWindowClamp`: number + +Привязать размер рекламируемого окна к этому значению. +Ядро выберет максимальное значение между этим значением и SOCK_MIN_RCVBUF/2. + +> `tcpMptcp`: true | false + +Новый параметр в Xray-core v1.8.6.
+Значение по умолчанию - `false`. +Если значение равно `true`, включить [Multipath TCP](https://ru.wikipedia.org/wiki/Multipath_TCP). +Этот параметр нужно включить как на сервере, так и на клиенте. +В настоящее время поддерживается только в Linux, требуется ядро Linux версии 5.6 или выше. + +> `tcpNoDelay`: true | false + +Значение по умолчанию - `false`. +Рекомендуется включать вместе с `"tcpMptcp": true`. + +> `customSockopt`: [] + +Массив, позволяющий опытным пользователям указать любые необходимые параметры sockopt. +Теоретически все настройки, связанные с подключением, описанные выше, можно настроить с помощью этого параметра. +Вы также можете настроить другие параметры, доступные в Linux, но не добавленные в ядро, например, следующий пример эквивалентен `"tcpcongestion": "bbr"` в ядре. + +Перед использованием убедитесь, что вы понимаете принципы программирования сокетов в Linux. + +```json +"customSockopt": [ + { + "type": "str", + "level":"6", + "opt": "13", + "value": "bbr" + } +] +``` + +> `type`: "" + +Обязательный параметр, тип настройки, в настоящее время доступны `int` и `str`. + +> `level`: "" + +Необязательный параметр, уровень протокола, используемый для указания области действия. +Значение по умолчанию - `6` (TCP). + +> `opt`: "" + +Название настраиваемого параметра в десятичном формате (в этом примере используется значение TCP_CONGESTION, которое равно 0xd в шестнадцатеричном формате и 13 в десятичном формате). + +> `value`: "" + +Значение, которое нужно установить. +В этом примере используется значение `bbr`. + +Если `type` равен `int`, значение должно быть десятичным числом. + + + + + diff --git a/docs/ru/config/transports/domainsocket.md b/docs/ru/config/transports/domainsocket.md new file mode 100644 index 000000000..c4864cbec --- /dev/null +++ b/docs/ru/config/transports/domainsocket.md @@ -0,0 +1,43 @@ +# DomainSocket + +::: danger +Рекомендуется прописать в разделе `listen` файла [inbounds](../inbound.md). В качестве способа передачи можно выбрать TCP, WebSocket, HTTP/2. +В будущем использование DomainSocket может быть прекращено. +::: + +DomainSocket использует стандартные доменные сокеты Unix для передачи данных. + +Его преимущество заключается в использовании встроенного в операционную систему канала передачи, не занимающего сетевой буфер. +Теоретически, по сравнению с локальной петлей (local loopback), доменный сокет работает немного быстрее. + +В настоящее время он доступен только на платформах, поддерживающих доменные сокеты Unix, таких как Linux и macOS. Недоступно в Windows 10 до сборки 17036. + +Если в качестве способа передачи указан DomainSocket, то порт и IP-адрес, настроенные во входящем и исходящем прокси, будут недействительны, и вся передача будет осуществляться через DomainSocket. + +## DomainSocketObject + +`DomainSocketObject` соответствует элементу `dsSettings` конфигурации передачи. + +```json +{ + "path": "/path/to/ds/file", + "abstract": false, + "padding": false +} +``` + +> `path`: string + +Допустимый путь к файлу. + +::: danger +Этот файл не должен существовать до запуска Xray. +::: + +> `abstract`: true | false + +Является ли сокет абстрактным доменным сокетом, значение по умолчанию `false`. + +> `padding`: true | false + +Использовать ли padding для абстрактного доменного сокета, значение по умолчанию `false`. diff --git a/docs/ru/config/transports/grpc.md b/docs/ru/config/transports/grpc.md new file mode 100644 index 000000000..f0a7ed39c --- /dev/null +++ b/docs/ru/config/transports/grpc.md @@ -0,0 +1,126 @@ +# gRPC + +Режим передачи данных, основанный на HTTP/2, полностью соответствует стандарту HTTP/2 и может быть ретранслирован другими HTTP-серверами (такими как Nginx). + +gRPC (HTTP/2) имеет встроенное мультиплексирование, не рекомендуется включать mux.cool при использовании gRPC и HTTP/2. + +::: warning ⚠⚠⚠ + +- gRPC не поддерживает указание Host. Пожалуйста, укажите **правильное доменное имя** в адресе исходящего прокси или укажите `ServerName` в `(x)tlsSettings`, иначе подключение не будет установлено. +- gRPC не поддерживает fallback на другие сервисы. +- Существует риск активного сканирования сервисов gRPC. Рекомендуется использовать обратный прокси-сервер, такой как Caddy или Nginx, для предварительного разделения трафика по пути. + ::: + +::: tip +Если вы используете обратный прокси-сервер, такой как Caddy или Nginx, обратите внимание на следующие моменты: + +- Убедитесь, что на обратном прокси-сервере включен HTTP/2. +- Используйте HTTP/2 или h2c (Caddy), grpc_pass (Nginx) для подключения к Xray. +- Путь в обычном режиме: `/${serviceName}/Tun`, в режиме Multi: `/${serviceName}/TunMulti`. +- Если необходимо получать IP-адрес клиента, его можно передать через заголовок `X-Real-IP`, отправленный Caddy / Nginx. + ::: + +::: tip +Если вы используете fallback, обратите внимание на следующие моменты: + +- Не рекомендуется использовать fallback на gRPC, так как существует риск активного сканирования. +- Убедитесь, что `h2` находится на первом месте в (x)tlsSettings.alpn, иначе gRPC (HTTP/2) может не завершить TLS-рукопожатие. +- gRPC не поддерживает маршрутизацию на основе path с помощью Xray. + ::: + +## GRPCObject + +`GRPCObject` соответствует элементу `grpcSettings` конфигурации передачи. + +```json +{ + "authority": "grpc.example.com", + "serviceName": "name", + "multiMode": false, + "user_agent": "custom user agent", + "idle_timeout": 60, + "health_check_timeout": 20, + "permit_without_stream": false, + "initial_windows_size": 0 +} +``` + +> `authority`: string + +Строка, которая может использоваться как Host для реализации некоторых других целей. + +> `serviceName`: string + +Строка, указывающая имя сервиса, **аналогично** пути в HTTP/2. +Клиент будет использовать это имя для связи, а сервер будет проверять, совпадает ли имя сервиса. + +::: tip +Когда `serviceName` начинается с косой черты, можно настроить собственный путь, используя как минимум две косые черты.
+Например, если на сервере указано `"serviceName": "/my/sample/path1|path2"`, то на клиенте можно указать `"serviceName": "/my/sample/path1"` или `"/my/sample/path2"`. +::: + +> `user_agent`: string + +::: tip +**Необходимо настроить только** в `outbound` **(клиент)**. +::: + +Установка пользовательского агента gRPC, может предотвратить блокировку трафика gRPC некоторыми CDN. + +> `multiMode`: true | false + +`true` включает `multiMode`, значение по умолчанию: `false`. + +Это **экспериментальная** опция, которая может быть удалена в будущем, и ее совместимость между версиями не гарантируется. Этот режим может обеспечить прирост производительности примерно на 20% **в тестовой среде**, фактическая производительность зависит от скорости передачи. + +::: tip +**Необходимо настроить только** в `outbound` **(клиент)**. +::: + +> `idle_timeout`: number + +Проверка работоспособности выполняется, если в течение определенного периода времени, измеряемого в секундах, не происходит передача данных. Если это значение меньше `10`, то в качестве минимального значения будет использоваться `10`. + +::: tip +Если не используется обратный прокси-сервер, такой как Caddy или Nginx (**обычно не используется**), и это значение установлено меньше `60`, сервер может отправить непредвиденный кадр h2 GOAWAY, чтобы закрыть существующее соединение. +::: + +По умолчанию проверка работоспособности **отключена**. + +::: tip +**Необходимо настроить только** в `outbound` **(клиент)**. +::: + +::: tip +Может решить некоторые проблемы с "обрывом" соединения. +::: + +> `health_check_timeout`: number + +Время ожидания ответа проверки работоспособности в секундах. Если в течение этого времени проверка работоспособности не будет завершена и по-прежнему не будет передачи данных, проверка работоспособности будет считаться неудачной. Значение по умолчанию: `20`. + +::: tip +Настройка требуется **только** на стороне **исходящего соединения** (**клиента**). +::: + +> `permit_without_stream`: true | false + +`true` разрешает проверку работоспособности, если нет дочерних подключений. Значение по умолчанию: `false`. + +::: tip +**Необходимо настроить только** в `outbound` **(клиент)**. +::: + +> `initial_windows_size`: number + +Начальный размер окна h2 Stream. Если значение меньше или равно `0`, эта функция не действует. Если значение больше `65535`, механизм динамического окна будет отключен. Значение по умолчанию: `0`, то есть не действует. + +::: tip +**Необходимо настроить только** в `outbound` **(клиент)**. +::: + +::: tip +При использовании CDN Cloudflare можно установить значение `65536` или выше, чтобы отключить механизм динамического окна, что предотвратит отправку непредвиденных кадров h2 GOAWAY CDN Cloudflare для закрытия существующего соединения. +::: + + diff --git a/docs/ru/config/transports/h2.md b/docs/ru/config/transports/h2.md new file mode 100644 index 000000000..92a9d9a30 --- /dev/null +++ b/docs/ru/config/transports/h2.md @@ -0,0 +1,86 @@ +# HTTP/2 + +Способ передачи данных на основе HTTP/2. + +Он полностью реализован в соответствии со стандартом HTTP/2 и может быть перенаправлен через другие HTTP-серверы (например, Nginx). + +В соответствии с рекомендациями HTTP/2, клиент и сервер должны одновременно включать TLS для нормальной работы этого способа передачи. + +HTTP/2 имеет встроенное мультиплексирование, не рекомендуется включать mux.cool при использовании HTTP/2. + +::: tip +Текущая версия способа передачи HTTP/2 **не требует**, чтобы **входящее соединение** (**сервер**) имело конфигурацию TLS. +Это позволяет в среде развертывания с разделением трафика для специальных целей использовать внешний шлюз для обработки TLS-соединения, в то время как Xray будет использоваться в качестве серверного приложения, а связь между шлюзом и Xray будет осуществляться по незашифрованному протоколу http/2, который называется `h2c`. +::: + +::: warning +⚠️ Если вы используете fallback, обратите внимание на следующие моменты: + +- Убедитесь, что (x)tlsSettings.alpn содержит h2, иначе HTTP/2 не сможет завершить TLS-рукопожатие. +- HTTP/2 не может быть разделен по пути, рекомендуется использовать SNI-разделение. + ::: + +## HttpObject + +`HttpObject` соответствует элементу `httpSettings` конфигурации передачи. + +```json +{ + "host": ["xray.com"], + "path": "/random/path", + "read_idle_timeout": 10, + "health_check_timeout": 15, + "method": "PUT", + "headers": { + "Header": ["value"] + } +} +``` + +> `host`: \[string\] + +Массив строк, каждый элемент которого является доменным именем. + +Клиент будет случайным образом выбирать доменное имя из списка для связи, а сервер будет проверять, находится ли доменное имя в списке. + +::: tip +Если не указать `"httpSettings"` или оставить значение `"host": []` пустым, то будет использоваться значение по умолчанию `"www.example.com"`. Для успешного подключения значение `"host"` должно быть одинаковым на обеих сторонах. `"host": [""]` не является пустым значением. +::: + +> `path`: string + +HTTP-путь, начинающийся с `/`, должен быть одинаковым у клиента и сервера. + +Значение по умолчанию: `"/"`. + +> `read_idle_timeout`: number + +Время в секундах, по истечении которого, если данные не были получены, будет выполнена проверка работоспособности. + +По умолчанию проверка работоспособности **не включена**. + +::: tip +Настройка требуется **только** на стороне **исходящего соединения** (**клиента**). +::: + +::: tip +Может решить некоторые проблемы с "обрывом" соединения. +::: + +> `health_check_timeout`: number + +Время ожидания ответа проверки работоспособности в секундах. Если в течение этого времени проверка работоспособности не будет завершена, она будет считаться неудачной. Значение по умолчанию: `15`. + +::: tip +Настройка требуется **только** на стороне **исходящего соединения** (**клиента**). +::: + +> `method`: string + +HTTP-метод. Значение по умолчанию: `"PUT"`. + +При настройке следует использовать значения, перечисленные [здесь](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). + +> `headers`: map{ string: \[string\] } + +Пользовательские HTTP-заголовки, пара ключ-значение, где каждый ключ представляет собой имя HTTP-заголовка, а значение представляет собой массив. diff --git a/docs/ru/config/transports/httpupgrade.md b/docs/ru/config/transports/httpupgrade.md new file mode 100644 index 000000000..208ee3a9a --- /dev/null +++ b/docs/ru/config/transports/httpupgrade.md @@ -0,0 +1,53 @@ +# HTTPUpgrade + +Это протокол, реализующий запросы и ответы на обновление HTTP 1.1, подобно WebSocket. Это позволяет ему, как и WebSocket, быть проксируемым CDN или Nginx, но без необходимости реализации других частей протокола WebSocket, что делает его более эффективным. + +Его дизайн не рекомендуется для самостоятельного использования, а лучше всего работает в сочетании с TLS. + +## HttpUpgradeObject + +`HttpUpgradeObject` соответствует пункту `httpupgradeSettings` в настройках передачи. + +```json +{ + "acceptProxyProtocol": false, + "path": "/", + "host": "xray.com", + "headers": { + "key": "value" + } +} +``` + +> `acceptProxyProtocol`: true | false + +Используется только для входящих соединений и указывает, принимать ли протокол PROXY. + +[PROXY protocol](https://www.haproxy.org/download/2.2/doc/proxy-protocol.txt) предназначен для передачи реального IP-адреса и порта запроса. **Если вы не знакомы с ним, проигнорируйте этот пункт.** + +Распространенные программы для reverse прокси (например, HAProxy, Nginx) и VLESS fallbacks xver могут быть настроены для его включения. + +При установке значения `true`, после установления TCP-соединения на самом нижнем уровне, запрашивающая сторона должна сначала отправить PROXY protocol v1 или v2, в противном случае соединение будет закрыто. + +> `path`: string + +HTTP-путь, используемый HTTPUpgrade, по умолчанию `"/"`. + +Если в пути клиента содержится параметр `ed` (например, ```/mypath?ed=2560```), будет активирована функция `Early Data` для уменьшения задержки, ее значение - порог длины первого пакета. Если длина первого пакета превышает это значение, `Early Data` не будет активирована. Рекомендуемое значение - 2560. + +> `host`: string + +Хост, отправляемый в HTTP-запросе HTTPUpgrade, по умолчанию пустой. Если значение на стороне сервера пустое, значение хоста, отправляемое клиентом, не проверяется. + +Когда на стороне сервера указано это значение или в `headers` указан хост, будет проверено соответствие хоста запроса клиента. + +Приоритет выбора хоста, отправляемого клиентом: `host` > `headers` > `address` + +> `headers`: map \{string: string\} + +Пользовательские HTTP-заголовки, пара ключ-значение, где каждый ключ представляет имя HTTP-заголовка, а соответствующее значение - строка. + +По умолчанию пустое. + + + diff --git a/docs/ru/config/transports/mkcp.md b/docs/ru/config/transports/mkcp.md new file mode 100644 index 000000000..4f55e57c7 --- /dev/null +++ b/docs/ru/config/transports/mkcp.md @@ -0,0 +1,162 @@ +# mKCP + +mKCP использует UDP для имитации TCP-соединения. + +mKCP жертвует пропускной способностью ради уменьшения задержки. При передаче одного и того же контента mKCP, как правило, потребляет больше трафика, чем TCP. + +::: tip +Убедитесь, что на хосте правильно настроена конфигурация брандмауэра. +::: + +## KcpObject + +`KcpObject` соответствует параметрам передачи `kcpSettings`. + +```json +{ + "mtu": 1350, + "tti": 20, + "uplinkCapacity": 5, + "downlinkCapacity": 20, + "congestion": false, + "readBufferSize": 1, + "writeBufferSize": 1, + "header": { + "type": "none", + "domain": "example.com" + }, + "seed": "Password" +} +``` + +> `mtu`: number + +Максимальный размер передаваемого блока (maximum transmission unit). + +Выберите значение от 576 до 1460. + +По умолчанию `1350`. + +> `tti`: number + +Интервал передачи (transmission time interval), в миллисекундах (ms), mKCP будет отправлять данные с этой частотой. + +Выберите значение от 10 до 100. + +По умолчанию `50`. + +> `uplinkCapacity`: number + +Пропускная способность канала отправки, то есть максимальная полоса пропускания, используемая хостом для отправки данных, в Мбит/с, обратите внимание, что это байты, а не биты. + +Может быть установлено в 0, что означает очень маленькую пропускную способность. + +По умолчанию `5`. + +> `downlinkCapacity`: number + +Пропускная способность канала приема, то есть максимальная полоса пропускания, используемая хостом для приема данных, в Мбит/с, обратите внимание, что это байты, а не биты. + +Может быть установлено в 0, что означает очень маленькую пропускную способность. + +По умолчанию `20`. + +::: tip +`uplinkCapacity` и `downlinkCapacity` определяют скорость передачи mKCP. + +В качестве примера, если клиент отправляет данные, то `uplinkCapacity` клиента определяет скорость отправки данных, а `downlinkCapacity` сервера определяет скорость приема данных. Значение, меньшее из двух, будет использоваться в качестве определяющего. + +Рекомендуется установить `downlinkCapacity` в большое значение, например, 100, а `uplinkCapacity` - в фактическое значение скорости сети. Когда скорость недостаточна, можно постепенно увеличивать значение `uplinkCapacity` до примерно двух раз больше, чем фактическая скорость сети. +::: + +> `congestion`: true | false + +Включить или отключить контроль перегрузки. + +При включении контроля перегрузки Xray будет автоматически отслеживать качество сети. При серьезных потерях пакетов он автоматически снизит пропускную способность; при хорошем качестве сети он также будет соответствующим образом увеличивать пропускную способность. + +По умолчанию `false`. + +> `readBufferSize`: number + +Размер буфера чтения для отдельного соединения, в МБ. + +По умолчанию `2`. + +> `writeBufferSize`: number + +Размер буфера записи для отдельного соединения, в МБ. + +По умолчанию `2`. + +::: tip +`readBufferSize` и `writeBufferSize` определяют размер памяти, используемой для каждого соединения. + +При необходимости высокой скорости передачи, установка больших значений `readBufferSize` и `writeBufferSize` может в некоторой степени повысить скорость, но также будет использовать больше памяти. + +При скорости сети не превышающей 20 Мбит/с, значение по умолчанию 1 МБ может удовлетворить требованиям; при превышении этой скорости можно соответствующим образом увеличить значения `readBufferSize` и `writeBufferSize`, а затем вручную сбалансировать скорость и использование памяти. +::: + +> `header`: [HeaderObject](#headerobject) + +Настройка маскировки заголовка данных + +> `seed`: string + +Опциональное шифрование пароля, используемое для шифрования потока данных с помощью алгоритма AES-128-GCM. Клиент и сервер должны использовать одинаковый пароль. + +Эта шифровальная схема не предназначена для обеспечения безопасности контента, но может помочь противостоять некоторым блокировкам. + +> В настоящее время в тестовой среде, после включения этой настройки, не наблюдалось явления блокировки исходного, не зашифрованного варианта. + +### HeaderObject + +```json +{ + "type": "none", + "domain": "example.com" +} +``` + +> `type`: string + +Тип маскировки, доступные значения: + +- `"none"`: значение по умолчанию, не применяется маскировка, отправляемые данные не имеют никаких отличительных признаков. +- `"srtp"`: маскировка под SRTP-пакеты, будет идентифицироваться как данные видеозвонка (например, FaceTime). +- `"utp"`: маскировка под uTP-пакеты, будет идентифицироваться как данные загрузки BT. +- `"wechat-video"`: маскировка под пакеты видеозвонка WeChat. +- `"dtls"`: маскировка под DTLS 1.2-пакеты. +- `"wireguard"`: маскировка под WireGuard-пакеты. (Это не настоящий протокол WireGuard). +- `"dns"`: некоторые корпоративные сети разрешают DNS-запросы без авторизации, добавление DNS-заголовка к KCP-пакетам позволяет обойти некоторые корпоративные сети. + +> `domain`: string + +Используется совместно с типом маскировки `"dns"`, можно указать произвольный домен. + +## Благодарности + +- [@skywind3000](https://github.com/skywind3000) изобрел и реализовал протокол KCP. +- [@xtaci](https://github.com/xtaci) перевел реализацию KCP с C на Go. +- [@xiaokangwang](https://github.com/xiaokangwang) протестировал интеграцию KCP с Xray и внес первый PR. + +## Улучшения протокола KCP + +### Более компактный заголовок протокола + +Протокол KCP использует заголовок размером 24 байта, а mKCP уменьшил его до 18 байт для пакета данных и 16 байт для пакета подтверждения. Более компактный заголовок помогает избежать обнаружения по признакам и ускоряет передачу данных. + +Кроме того, в оригинальном KCP каждый пакет подтверждения может подтвердить только один пакет данных, то есть, если KCP нужно подтвердить получение 100 пакетов данных, он отправит 24 * 100 = 2400 байт данных. В этом случае многократно повторяются заголовки, что приводит к ненужному расходу полосы пропускания. mKCP сжимает несколько пакетов подтверждения, 100 пакетов подтверждения занимают всего 16 + 2 + 100 * 4 = 418 байт, что в шесть раз меньше, чем в оригинальном KCP. + +### Передача пакетов подтверждения + +В оригинальном KCP пакет подтверждения отправляется только один раз, если пакет подтверждения потерян, то обязательно произойдет повторная передача данных, что приводит к ненужному расходу полосы пропускания. mKCP будет повторно отправлять пакеты подтверждения с определенной частотой, пока отправитель не получит подтверждение. Размер одного пакета подтверждения составляет 22 байта, что значительно меньше, чем размер пакета данных, который составляет более 1000 байт, поэтому повторная передача пакета подтверждения имеет гораздо меньшую цену. + +### Управление состоянием соединения + +mKCP может эффективно управлять состоянием соединения. Когда удаленный хост инициализирует закрытие соединения, соединение будет закрыто в течение двух секунд; когда удаленный хост теряет соединение, соединение будет закрыто в течение максимум 30 секунд. + +Оригинальный KCP не поддерживает этот сценарий. + + + diff --git a/docs/ru/config/transports/quic.md b/docs/ru/config/transports/quic.md new file mode 100644 index 000000000..80274d6d2 --- /dev/null +++ b/docs/ru/config/transports/quic.md @@ -0,0 +1,75 @@ +# QUIC + +QUIC (Quick UDP Internet Connection) — это протокол, предложенный Google для многоканальной передачи данных по UDP. Его основные преимущества: + +1. Сокращение времени установки соединения (1-RTT или 0-RTT). +2. Многоканальность и отсутствие проблем с блокировкой, как у TCP. +3. Миграция соединений (в основном на стороне клиента): при переходе с Wi-Fi на 4G соединение не разрывается. + +QUIC в настоящее время находится в экспериментальной стадии и использует реализацию IETF, которая находится в процессе стандартизации, поэтому совместимость с финальной версией не гарантируется. + +- По умолчанию: + - 12-байтовый Connection ID. + - Автоматическое отключение соединения через 30 секунд бездействия (может повлиять на работу некоторых долгоживущих соединений). + +## QuicObject + +`QuicObject` соответствует элементу `quicSettings` в конфигурации транспорта. + +::: danger +Конфигурация на обоих концах соединения должна быть полностью идентичной, иначе соединение установить не удастся. +QUIC требует включения TLS. Если TLS не включён в настройках транспорта, Xray сгенерирует самоподписанный сертификат для использования TLS. +::: + +```json +{ + "security": "none", + "key": "", + "header": { + "type": "none" + } +} +``` + +> `security`: "none" | "aes-128-gcm" | "chacha20-poly1305" + +Метод шифрования. + +Это шифрование применяется к пакетам данных QUIC. Зашифрованные пакеты не могут быть распознаны. + +Значение по умолчанию: без шифрования. + +> `key`: string + +Ключ шифрования. + +Может быть любой строкой. Используется только если `security` не равен `"none"`. + +> `header`: [HeaderObject](#headerobject) + +Настройки маскировки заголовков пакетов. + +### HeaderObject + +```json +{ + "type": "none" +} +``` + +> `type`: string + +Тип маскировки. Допустимые значения: + +- `"none"`: значение по умолчанию, маскировка не используется, отправляемые данные не имеют характерных признаков. +- `"srtp"`: маскировка под SRTP-трафик (например, FaceTime). +- `"utp"`: маскировка под uTP-трафик (например, BitTorrent). +- `"wechat-video"`: маскировка под видеозвонки WeChat. +- `"dtls"`: маскировка под DTLS 1.2. +- `"wireguard"`: маскировка под WireGuard (не является настоящим WireGuard). + +::: tip +Если ни шифрование, ни маскировка не включены, то пакеты QUIC отправляются в исходном виде и могут быть распознаны другими инструментами QUIC. + +Для предотвращения обнаружения рекомендуется включить хотя бы шифрование или маскировку. +::: diff --git a/docs/ru/config/transports/splithttp.md b/docs/ru/config/transports/splithttp.md new file mode 100644 index 000000000..ed9c99593 --- /dev/null +++ b/docs/ru/config/transports/splithttp.md @@ -0,0 +1,81 @@ +# SplitHTTP + + + +Используется для загрузки с помощью HTTP-фрагментированной передачи, загрузка осуществляется с помощью нескольких HTTP POST-запросов. + +Может использоваться через CDN, не поддерживающие WebSocket, но есть несколько требований: + +- CDN должен поддерживать HTTP-фрагментированную передачу и потоковые ответы без буферизации. Ядро будет отправлять `X-Accel-Buffering: no` и `Content-Type: text/event-stream`, чтобы сообщить CDN об этом, но CDN должен соблюдать этот заголовок. Если промежуточный сервер не поддерживает потоковые ответы и зависает, передача, скорее всего, не будет работать. + +Цель та же, что и у V2fly Meek, но благодаря использованию фрагментированной загрузки скорость загрузки выше, а скорость отдачи оптимизирована, но все еще очень ограничена, поэтому к HTTP-прокси предъявляются более высокие требования (см. выше). + +`SplitHTTP` также принимает заголовок `X-Forwarded-For`. + +## SplitHttpObject + +`SplitHttpObject` соответствует элементу `splithttpSettings` в конфигурации транспорта. + +```json +{ + "path": "/", + "host": "xray.com", + "headers": { + "key": "value" + }, + "maxUploadSize": 1000000, + "maxConcurrentUploads": 10 +} +``` + +> `path`: string + +Путь HTTP-протокола, используемый SplitHTTP, значение по умолчанию — `"/"`. + +> `host`: string + +Хост, отправляемый в HTTP-запросе SplitHTTP, по умолчанию пуст. Если значение на стороне сервера пустое, значение хоста, отправленное клиентом, не проверяется. + +Если это значение указано на стороне сервера или `host` указан в `headers`, то проверяется соответствие хоста запроса клиента. + +Приоритет выбора хоста для отправки клиентом: `host` > `headers` > `address`. + +> `headers`: map \{string: string\} + +Пользовательские HTTP-заголовки, пары ключ-значение, где каждый ключ представляет имя HTTP-заголовка, а соответствующее значение является строкой. + +> `maxUploadSize`: int + +Максимальный размер фрагмента загрузки в байтах, по умолчанию 1 МБ. + +Это значение должно быть меньше максимального размера тела запроса, разрешенного CDN или другим обратным HTTP-прокси, иначе будет выдаваться ошибка HTTP 413. + +Увеличение этого значения может увеличить скорость загрузки. + +> `maxConcurrentUploads`: int + +Максимальное количество одновременных загрузок, по умолчанию 10, соединения будут использоваться повторно, насколько это возможно. + +Если соединение нестабильно или потребление памяти на сервере слишком велико, попробуйте уменьшить это значение. + +Значение, установленное клиентом, должно быть меньше, чем на сервере, иначе это может привести к проблемам с подключением. + +## Детали протокола + +Подробное обсуждение см. [#3412](https://github.com/XTLS/Xray-core/pull/3412) и [#3462](https://github.com/XTLS/Xray-core/pull/3462). Ниже приведено краткое описание и требования к совместимой реализации: + +1. Загрузка начинается с `GET /`. Сервер немедленно отвечает `200 OK` и `Transfer Encoding:chunked` и немедленно отправляет двухбайтовую полезную нагрузку, чтобы принудительно обновить заголовки HTTP-прокси. + +2. Отправка данных начинается с `POST //`. `seq` действует как порядковый номер TCP, начиная с 0, пакеты данных могут отправляться одновременно, сервер должен пересобрать данные по порядковому номеру. Порядковый номер не следует сбрасывать. + + Клиент может свободно выбирать порядок открытия исходящих и нисходящих запросов, любой из них может инициировать сеанс, но соединение `GET` должно быть открыто в течение 30 секунд, иначе сеанс будет разорван. + +4. Запрос `GET` будет оставаться открытым до тех пор, пока соединение не будет разорвано, и сервер, и клиент могут закрыть соединение. Конкретное поведение зависит от версии HTTP. + +Рекомендации: + +* Не ожидайте, что CDN будет правильно передавать все заголовки, цель этого протокола — обойти CDN, не поддерживающие WS, а поведение этих CDN обычно не очень дружелюбное. + +* Следует предполагать, что все HTTP-соединения не поддерживают потоковые запросы, поэтому размер каждого пакета, отправляемого исходящим соединением, должен определяться с учетом задержки, пропускной способности и ограничений самого промежуточного сервера (аналогично MTU TCP и алгоритму Нейгла). + +* Что касается версии HTTP, ядро в настоящее время не поддерживает h2c, поэтому Xray будет отправлять только запросы http/1.1 при использовании без HTTPS. Чтобы избежать дополнительных сложностей, связанных с согласованием ALPN, клиент Xray использует h2 при использовании HTTPS (если только alpn не указан вручную как http/1.1 в tlsSettings), а сервер Xray совместим с различными типами входящих подключений (h3 пока нет), поскольку входящие соединения могут быть различных типов из-за промежуточных серверов. diff --git a/docs/ru/config/transports/tcp.md b/docs/ru/config/transports/tcp.md new file mode 100644 index 000000000..ee7875e8d --- /dev/null +++ b/docs/ru/config/transports/tcp.md @@ -0,0 +1,148 @@ +# TCP + +Режим транспорта TCP — один из рекомендуемых в настоящее время режимов транспорта. + +Может использоваться в различных комбинациях с различными протоколами. + +## TcpObject + +`TcpObject` соответствует элементу `tcpSettings` в конфигурации транспорта. + +```json +{ + "acceptProxyProtocol": false, + "header": { + "type": "none" + } +} +``` + +> `acceptProxyProtocol`: true | false + +Только для входящих подключений, указывает, следует ли принимать PROXY protocol. + +[PROXY protocol](https://www.haproxy.org/download/2.2/doc/proxy-protocol.txt) используется для передачи реального исходного IP-адреса и порта запроса, **если вы не знаете, что это такое, проигнорируйте этот параметр**. + +Распространенные программы для обработки обратного прокси (например, HAProxy, Nginx) можно настроить на его отправку, VLESS fallbacks xver также может его отправлять. + +Если установлено значение `true`, то после установления TCP-соединения на самом нижнем уровне запрашивающая сторона должна сначала отправить PROXY protocol v1 или v2, иначе соединение будет закрыто. + +Значение по умолчанию: `false`. + +> `header`: [NoneHeaderObject](#noneheaderobject) | [HttpHeaderobject](#httpheaderobject) + +Настройки маскировки заголовка пакета данных, значение по умолчанию: `NoneHeaderObject`. + +::: tip +HTTP-маскировка не может быть разделена другими HTTP-серверами (например, Nginx), но может быть разделена с помощью VLESS fallbacks path. +::: + +### NoneHeaderObject + +Маскировка не выполняется. + +```json +{ + "type": "none" +} +``` + +> `type`: "none" + +Указывает, что маскировка не выполняется. + +### HttpHeaderObject + +Конфигурация HTTP-маскировки должна быть одинаковой как на входящем, так и на исходящем соединении, и ее содержимое должно совпадать. + +```json +{ + "type": "http", + "request": {}, + "response": {} +} +``` + +> `type`: "http" + +Указывает на выполнение HTTP-маскировки. + +> `request`: [HTTPRequestObject](#httprequestobject) + +HTTP-запрос. + +> `response`: [HTTPResponseObject](#httpresponseobject) + +HTTP-ответ. + +#### HTTPRequestObject + +```json +{ + "version": "1.1", + "method": "GET", + "path": ["/"], + "headers": { + "Host": ["www.baidu.com", "www.bing.com"], + "User-Agent": [ + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", + "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46" + ], + "Accept-Encoding": ["gzip, deflate"], + "Connection": ["keep-alive"], + "Pragma": "no-cache" + } +} +``` + +> `version`: string + +Версия HTTP, значение по умолчанию — `"1.1"`. + +> `method`: string + +Метод HTTP, значение по умолчанию — `"GET"`. + +> `path`: \[ string \] + +Путь, массив строк. Значение по умолчанию — `["/"]`. Если имеется несколько значений, то при каждом запросе случайным образом выбирается одно из них. + +> `headers`: map{ string, \[ string \]} + +HTTP-заголовки, пары ключ-значение, где каждый ключ представляет имя HTTP-заголовка, а соответствующее значение является массивом. + +Каждый запрос будет содержать все ключи и случайно выбранное соответствующее значение. Значение по умолчанию см. в примере выше. + +#### HTTPResponseObject + +```json +{ + "version": "1.1", + "status": "200", + "reason": "OK", + "headers": { + "Content-Type": ["application/octet-stream", "video/mpeg"], + "Transfer-Encoding": ["chunked"], + "Connection": ["keep-alive"], + "Pragma": "no-cache" + } +} +``` + +> `version`: string + +Версия HTTP, значение по умолчанию — `"1.1"`. + +> `status`: string + +Состояние HTTP, значение по умолчанию — `"200"`. + +> `reason`: string + +Описание состояния HTTP, значение по умолчанию — `"OK"`. + +> `headers`: map {string, \[ string \]} + +HTTP-заголовки, пары ключ-значение, где каждый ключ представляет имя HTTP-заголовка, а соответствующее значение является массивом. + +Каждый запрос будет содержать все ключи и случайно выбранное соответствующее значение. Значение по умолчанию см. в примере выше. diff --git a/docs/ru/config/transports/websocket.md b/docs/ru/config/transports/websocket.md new file mode 100644 index 000000000..52c244346 --- /dev/null +++ b/docs/ru/config/transports/websocket.md @@ -0,0 +1,58 @@ +# WebSocket + +Использует стандартный WebSocket для передачи данных. + +Подключение WebSocket может быть проксировано другими HTTP-серверами (например, Nginx) и VLESS fallbacks path. + +::: tip +WebSocket распознает заголовок X-Forwarded-For в HTTP-запросе для перезаписи исходного адреса трафика, приоритет выше, чем у PROXY protocol. +::: + +## WebSocketObject + +`WebSocketObject` соответствует элементу `wsSettings` в конфигурации транспорта. + +```json +{ + "acceptProxyProtocol": false, + "path": "/", + "host": "xray.com", + "headers": { + "key": "value" + } +} +``` + +> `acceptProxyProtocol`: true | false + +Только для входящих подключений, указывает, следует ли принимать PROXY protocol. + +[PROXY protocol](https://www.haproxy.org/download/2.2/doc/proxy-protocol.txt) используется для передачи реального исходного IP-адреса и порта запроса, **если вы не знаете, что это такое, проигнорируйте этот параметр**. + +Распространенные программы-обработчики обратного прокси (например, HAProxy, Nginx) можно настроить на его отправку, VLESS fallbacks xver также может его отправлять. + +Если установлено значение `true`, то после установления TCP-соединения на самом нижнем уровне запрашивающая сторона должна сначала отправить PROXY protocol v1 или v2, иначе соединение будет закрыто. + +> `path`: string + +Путь, используемый WebSocket в HTTP-протоколе, значение по умолчанию — `"/"`. + +Если в пути клиента есть параметр `ed` (например, ```/mypath?ed=2560```), будет активирован `Early Data` для уменьшения задержки. + +> `host`: string + +Хост, отправляемый в HTTP-запросе WebSocket, значение по умолчанию — пустое. Если значение на стороне сервера пустое, значение хоста, отправленное клиентом, не проверяется. + +Если это значение указано на стороне сервера или `host` указан в `headers`, то проверяется соответствие хоста запроса клиента. + +Приоритет выбора хоста для отправки клиентом: `host` > `headers` > `address`. + +> `headers`: map \{string: string\} + +Пользовательские HTTP-заголовки, пары ключ-значение, где каждый ключ представляет имя HTTP-заголовка, а соответствующее значение является строкой. + +Значение по умолчанию: пустое. + +## Browser Dialer + +Использует браузер для обработки TLS, подробнее см. в [Browser Dialer](../features/browser_dialer.md). \ No newline at end of file diff --git a/docs/ru/development/README.md b/docs/ru/development/README.md new file mode 100644 index 000000000..fbea17da7 --- /dev/null +++ b/docs/ru/development/README.md @@ -0,0 +1,41 @@ +# 开发指南 + +## 编译文档 + +Xray 支持各种平台, 您可以在多种平台上自行进行交叉编译。 + +请点击[编译文档](./intro/compile.md)以查看具体编译相关内容。 + +## 设计思路 + +Xray 内核提供了一个平台,在其之上可以进二次开发。 + +这个章节阐述了 Xray 的设计目标和架构。 + +请点击[设计思路](./intro/design.md)以了解 Xray 的设计目标和架构。 + +## 开发规范 + +这个章节阐述了获取代码,进行开发,提交 PR 的流程中需要遵循的准则, 以及相关的编码规范。 + +请点击[开发规范](./intro/guide.md)查看 Xray 开发中应遵循的准则。 + +## 协议详解 + +Xray 用到了很多种协议, 您可以通过各种途径获得协议的详细描述。 + +### [VLESS 协议](./protocols/vless.md) + +VLESS 是一个无状态的轻量传输协议,可以作为 Xray 客户端和服务器之间的桥梁。 + +### [VMess 协议](./protocols/vmess.md) + +VMess 是一个加密传输协议,可以作为 Xray 客户端和服务器之间的桥梁。 + +### [Mux.Cool 协议](./protocols/muxcool.md) + +Mux.Cool 协议是一个多路复用传输协议,用于在一条已建立的数据流中传输多个各自独立的数据流。 + +### [mKCP 协议](./protocols/mkcp.md) + +mKCP 是流式传输协议,由 [KCP 协议](https://github.com/skywind3000/kcp)修改而来,可以按顺序传输任意的数据流。 diff --git a/docs/ru/development/intro/compile.md b/docs/ru/development/intro/compile.md new file mode 100644 index 000000000..720528c6a --- /dev/null +++ b/docs/ru/development/intro/compile.md @@ -0,0 +1,81 @@ +# 编译文档 + +## 前序工作 + +Xray 使用 [Golang](https://golang.org/) 作为编程语言,你需要先安装最新版本 Golang 才能够编译。 + +::: tip TIP +安装 Golang: [golang.org/doc/install](https://golang.org/doc/install) +::: + +> 如果你不幸使用 Windows, 请 **务必** 使用 Powershell + +## 拉取 Xray 源代码 + +```bash +git clone https://github.com/XTLS/Xray-core.git +cd Xray-core && go mod download +``` + +> 如果你闲的没事干,可以试试 GitHub 官方工具: `gh repo clone XTLS/Xray-core` + +注意:在无法正常访问 Google 的网络环境,依赖无法被正常拉取,需要先设置 `GOPROXY`: + +```bash +go env -w GOPROXY=https://goproxy.io,direct +``` + +## 构建二进制 + +:::warning +本小节的命令需要在 Xray 根目录内运行。 +::: + +### Windows(Powershell): + +```powershell +$env:CGO_ENABLED=0 +go build -o xray.exe -trimpath -ldflags "-s -w -buildid=" ./main +``` + +### macOS, Linux: + +```bash +CGO_ENABLED=0 go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main +``` + +运行以上命令会在目录下生成 xray 可执行文件。 + +::: tip +如果需要编译可以进行 debug 的程序,即可以用 dlv 附加到运行的程序进行调试, 请去掉 ldflags 中的 '-w -s' 选项. + +-w 禁止生成 debug 信息。使用该选项后,将无法使用 gdb 进行调试。 +-s 禁用符号表 +PS:其实用 vscode 或其他 IDE 调试似乎更方便。 +::: + +## 交叉编译: + +这里以在 Windows(Powershell) 环境中,编译到 Linux 服务器为例: + +```powershell +$env:CGO_ENABLED=0 +$env:GOOS="linux" +$env:GOARCH="amd64" + +go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main +``` + +上传到服务器后,记得在服务器终端内执行 `chmod +x xray` + +::: tip +执行 `go tool dist list` 查看所有支持的系统与架构。 +::: + +## 可复现构建: + +按照上述步骤,能够编译与 Release 中完全相同的二进制文件。 + +::: warning +请先确认您使用的 Golang 版本与编译 Release 的一致。 +::: diff --git a/docs/ru/development/intro/design.md b/docs/ru/development/intro/design.md new file mode 100644 index 000000000..fdb6aed47 --- /dev/null +++ b/docs/ru/development/intro/design.md @@ -0,0 +1,43 @@ +# 设计目标 + +- Xray 内核提供了一个平台,支持必要的网络代理功能,在其之上可以进二次开发,以提供更好的用户体验; +- 以跨平台为首要原则,以减少二次开发的成本; + +## 架构 + +![Architecture](./framework.png) + +内核分为三层:应用层、代理层和传输层。 + +每一层内包含数个模块,模块间互相独立,同类型的模块可无缝替换。 + +### 应用层 + +应用层包含一些代理层中常用的功能,这些功能被抽象出来,以便在不同的代理模块中复用。 + +应用层的模块应为纯软件实现,与硬件或平台相关的技术无关。 + +重要模块列表: + +- Dispatcher: 用于把入站代理所接收到的数据,传送给出站代理; +- Router: 路由模块,详见 [路由配置](../../config/routing.md); +- DNS: 内置的 DNS 服务器模块; +- Proxy Manager: 代理管理器; + +### 代理层 + +代理层分为两部分:入站代理(Inbound Proxy)和出站代理(Outbound Proxy)。 + +两部分相互独立,入站代理不依赖于某个特定的出站代理,反之亦然。 + +#### 入站代理 + +- 实现 [proxy.Inbound](https://github.com/xtls/Xray-core/blob/main/proxy/proxy.go) 接口; + +#### 出站代理 + +- 实现 [proxy.Outbound](https://github.com/xtls/Xray-core/blob/main/proxy/proxy.go) 接口; + +### 传输层 + +传输层提供一些网络数据传输相关的工具模块。 diff --git a/docs/ru/development/intro/framework.png b/docs/ru/development/intro/framework.png new file mode 100644 index 000000000..194dff8c5 Binary files /dev/null and b/docs/ru/development/intro/framework.png differ diff --git a/docs/ru/development/intro/guide.md b/docs/ru/development/intro/guide.md new file mode 100644 index 000000000..47fe2e939 --- /dev/null +++ b/docs/ru/development/intro/guide.md @@ -0,0 +1,131 @@ +# 开发规范 + +## 基本 + +### 版本控制 + +Project X 的代码被托管在 github 上: + +- Xray 核心 [Xray-core](https://github.com/XTLS/Xray-core) +- 安装脚本 [Xray-install](https://github.com/XTLS/Xray-install) +- 配置模板 [Xray-examples](https://github.com/XTLS/Xray-examples) +- Xray 文档 [Xray-docs-next](https://github.com/XTLS/Xray-docs-next) + +您可以使用 [Git](https://git-scm.com/) 来获取代码。 + +### 分支(Branch) + +- 本项目的主干分支为 main, +- 本项目的发布主分支同为 main, +- 需要确保 main 在任一时刻都是可编译,且可正常使用的。 +- 如果需要开发新的功能,请新建分支进行开发,在开发完成并且经过充分测试后,合并回主干分支。 +- 已经合并入主干且没有必要存在的分支,请删除。 + +### 发布(Release) + + + +- 建立尝鲜版本和稳定版本两个发布通道 + - 尝鲜版本,可以为 daily build,主要用于特定情况的测试,尝鲜和获得即时反馈和再改进。 + - 稳定版本,为定时更新(比如月更),合并稳定的修改并发布。 + +### 引用其它项目 + +- Golang + - 产品代码建议使用 Golang 标准库和 [golang.org/x/](https://pkg.go.dev/search?limit=25&m=package&q=golang.org%2Fx) 下的库; + - 如需引用其它项目,请事先创建 issue 讨论; +- 其它 + - 不违反双方的协议,且对项目有帮助的工具,都可以使用。 + +## 开发流程 + +### 写代码之前 + +发现任何问题,或对项目有任何想法,请创建 [issue](https://github.com/XTLS/Xray-core/issues) 讨论以减少重复劳动和消耗在代码上的时间。 + +### 修改代码 + +- Golang + - 请参考 [Effective Go](https://golang.org/doc/effective_go.html); + - 每一次 push 之前,请运行:`go generate core/format.go`; + - 如果需要修改 protobuf,例如增加新配置项,请运行:`go generate core/proto.go`; + - 提交 pull request 之前,建议测试通过:`go test ./...`; + - 提交 pull request 之前,建议新增代码有超过 70% 的代码覆盖率(code coverage); +- 其它 + - 请注意代码的可读性。 + +### Pull Request + +- 提交 PR 之前,请先运行 `git pull https://github.com/XTLS/Xray-core.git` 以确保 merge 可顺利进行; +- 一个 PR 只做一件事,如有对多个 bug 的修复,请对每一个 bug 提交一个 PR; +- 由于 Golang 的特殊需求(Package path),Go 项目的 PR 流程和其它项目有所不同,建议流程如下: + 1. 先 Fork 本项目,创建你自己的 `github.com//Xray-core.git` 仓库; + 2. 克隆你自己的 Xray 仓库到本地:`git clone https://github.com//Xray-core.git`; + 3. 基于 `main` 分支创建新的分支,例如 `git branch issue24 main`; + 4. 在新创建的分支上作修改并提交修改(commit); + 5. 在推送(push)修改完成的分支到自己的仓库前,先切换到 `main` 分支,运行 `git pull https://github.com/XTLS/Xray-core.git` 拉取最新的远端代码; + 6. 如果上一步拉取得到了新的远端代码,则切换到之前自己创建的分支,运行 `git rebase main` 执行分支合并操作。如遇到文件冲突,则需要解决冲突; + 7. 上一步处理完毕后,就可以把自己创建的分支推送到自己的仓库:`git push -u origin your-branch` + 8. 最后,把自己仓库的新推送的分支往 `XTLS/Xray-core` 的 `main` 分支发 PR 即可; + 9. 请在 PR 的标题和正文中,完整表述此次 PR 解决的问题 / 新增的功能 / 代码所做的修改的用意等; + 10. 耐心等待开发者的回应。 + +### 对代码的修改 + +#### 功能性问题 + +请提交至少一个测试用例(Test Case)来验证对现有功能的改动。 + +#### 性能相关 + +请提交必要的测试数据来证明现有代码的性能缺陷,或是新增代码的性能提升。 + +#### 新功能 + +- 如果新增功能对已有功能不影响,请提供可以开启/关闭的开关(如 flag),并使新功能保持默认关闭的状态; +- 大型新功能(比如增加一个新的协议)开发之前,请先提交一个 issue,讨论完毕之后再进行开发。 + +#### 其它 + +视具体情况而定。 + +## Xray 编码规范 + +以下内容适用于 Xray 中的 Golang 代码。 + +### 代码结构 + +``` +Xray-core +├── app // 应用模块 +│ ├── router // 路由 +├── common // 公用代码 +├── proxy // 通讯协议 +│ ├── blackhole +│ ├── dokodemo-door +│ ├── freedom +│ ├── socks +│ ├── vmess +├── transport // 传输模块 +``` + +### 编码规范 + +基本与 Golang 官方所推荐做法一致,有一些例外。写在这里以方便大家熟悉 Golang。 + +#### 命名 + +- 文件和目录名尽量使用单个英文单词,比如 hello.go; + - 如果实在没办法,则目录使用连接线/文件名使用下划线连接两个(或多个单词),比如 hello-world/hello_again.go; + - 测试代码使用 \_test.go 结尾; +- 类型使用 Pascal 命名法,比如 ConnectionHandler; + - 对缩写不强制小写,即 HTML 不必写成 Html; +- 公开成员变量也使用 Pascal 命名法; +- 私有成员变量使用 [小驼峰式命名法](https://zh.wikipedia.org/wiki/%E9%A7%9D%E5%B3%B0%E5%BC%8F%E5%A4%A7%E5%B0%8F%E5%AF%AB) ,如 `privateAttribute` ; +- 为了方便重构,方法建议全部使用 Pascal 命名法; + - 完全私有的类型放入 `internal` 。 + +#### 内容组织 + +- 一个文件包含一个主要类型,及其相关的私有函数等; +- 测试相关的文件,如 Mock 等工具类,放入 testing 子目录。 diff --git a/docs/ru/development/protocols/mkcp.md b/docs/ru/development/protocols/mkcp.md new file mode 100644 index 000000000..17fde9838 --- /dev/null +++ b/docs/ru/development/protocols/mkcp.md @@ -0,0 +1,92 @@ +# mKCP 协议 + +mKCP 是流式传输协议,由 [KCP 协议](https://github.com/skywind3000/kcp) 修改而来,可以按顺序传输任意的数据流。 + +## 版本 + +mKCP 没有版本号,不保证版本之间兼容性。 + +## 依赖 + +### 底层协议 + +mKCP 是一个基于 UDP 的协议,所有通讯使用 UDP 传输。 + +### 函数 + +- fnv: [FNV-1a](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function) 哈希函数 + - 输入参数为任意长度的字符串; + - 输入出一个 32 位无符号整数; + +## 通讯过程 + +1. mKCP 将数据流拆成若干个数据包进行发送。一个数据流有一个唯一标识,用以区分不同的数据流。数据流中的每一个数据包都携带了同样的标识。 +1. mKCP 没有握手过程,当收到一个数据包时,根据其携带的数据流的标识来判断是否为新的通话,或是正在进行中的通话。 +1. 每一个数据包中包含若干个片段(Segment),片段分为三类:数据(Data)、确认(ACK)、心跳(Ping)。每个片段需要单独处理。 + +## 数据格式 + +### 数据包 + +| 4 字节 | 2 字节 | L 字节 | +| ---------- | ---------- | -------- | +| 认证信息 A | 数据长度 L | 片段部分 | + +其中: + +- 认证信息 A = fnv(片段部分),big endian; +- 片段部分可能包含多个片段; + +### 数据片段 + +| 2 字节 | 1 字节 | 1 字节 | 4 字节 | 4 字节 | 4 字节 | 2 字节 | Len 字节 | +| --------- | -------- | -------- | --------- | --------- | ---------------- | -------- | -------- | +| 标识 Conv | 指令 Cmd | 选项 Opt | 时间戳 Ts | 序列号 Sn | 未确认序列号 Una | 长度 Len | 数据 | + +其中: + +- 标识 Conv: mKCP 数据流的标识 +- 指令 Cmd: 常量 0x01 +- 选项 Opt: 可选的值有: + - 0x00: 空选项 + - 0x01: 对方已发出所有数据 +- 时间戳 Ts: 当前片段从远端发送出来时的时间,big endian +- 序列号 Sn: 该数据片段时数据流中的位置,起始片段的序列号为 0,之后每个新片段按顺序加 1 +- 未确认序列号 Una: 远端主机正在发送的,且尚未收到确认的最小的 Sn + +### 确认片段 + +| 2 字节 | 1 字节 | 1 字节 | 4 字节 | 4 字节 | 4 字节 | 2 字节 | Len \* 4 字节 | +| --------- | -------- | -------- | -------- | ----------------- | --------- | -------- | -------------- | +| 标识 Conv | 指令 Cmd | 选项 Opt | 窗口 Wnd | 下一接收序列号 Sn | 时间戳 Ts | 长度 Len | 已收到的序列号 | + +其中: + +- 标识 Conv: mKCP 数据流的标识 +- 指令 Cmd: 常量 0x00 +- 选项 Opt: 同上 +- 窗口 Wnd: 远端主机可以接收的最大序列号 +- 下一接收序列号 Sn: 远端主机未收到的数据片段中的最小序列号 +- 时间戳 Ts: 远端主机最新收到的数据片段的时间戳,可用于计算延迟 +- 已收到的序列号: 每个 4 字节,表示此序列号的数据已经确认收到 + +注释: + +- 远程主机期待收到序列号 [Sn, Wnd) 范围内的数据 + +### 心跳片段 + +| 2 字节 | 1 字节 | 1 字节 | 4 字节 | 4 字节 | 4 字节 | +| --------- | -------- | -------- | ---------------- | ----------------- | -------- | +| 标识 Conv | 指令 Cmd | 选项 Opt | 未确认序列号 Una | 下一接收序列号 Sn | 延迟 Rto | + +其中: + +- 标识 Conv: mKCP 数据流的标识 +- 指令 Cmd: 可选的值有 + - 0x02: 远端主机强行终止会话 + - 0x03: 正常心跳 +- 选项 Opt: 同上 +- 未确认序列号 Una: 同数据片段的 Una +- 下一接收序列号 Sn: 同确认片段的 Sn +- 延迟 Rto: 远端主机自己计算出的延迟 diff --git a/docs/ru/development/protocols/muxcool.md b/docs/ru/development/protocols/muxcool.md new file mode 100644 index 000000000..c81731524 --- /dev/null +++ b/docs/ru/development/protocols/muxcool.md @@ -0,0 +1,129 @@ +# Mux.Cool 协议 + +Mux.Cool 协议是一个多路复用传输协议,用于在一条已建立的数据流中传输多个各自独立的数据流。 + +## 版本 + +当前版本是 1 Beta。 + +## 依赖 + +### 底层协议 + +Mux.Cool 必须运行在一个已建立的可靠数据流之上。 + +## 通讯过程 + +一个 Mux.Cool 连接中可传输若干个子连接,每个子连接有一个独立的 ID 和状态。传输过程由帧(Frame)组成,每一帧用于传输一个特定的子连接的数据。 + +### 客户端行为 + +当有连接需求时并且没有现有可用的连接时,客户端向服务器发起一个新连接,以下称为“主连接”。 + +1. 一个主连接可用于发送若干个子连接。客户端可自主决定主连接可承载的子连接数量。 +1. 对于一个新的子连接,客户端必须发送状态`New`以通知服务器建立子连接,然后使用状态`Keep`来传送数据。 +1. 当子连接结束时,客户端发送`End`状态来通知服务器关闭子连接。 +1. 客户端可自行决定何时关闭主连接,但必须确定服务器也同时保持连接。 +1. 客户端可使用 KeepAlive 状态来避免服务器关闭主连接。 + +### 服务器端行为 + +当服务器端接收到新的子连接时,服务器应当按正常的连接来处理。 + +1. 当收到状态`End`时,服务器端可以关闭对目标地址的上行连接。 +1. 在服务器的响应中,必须使用与请求相同的 ID 来传输子连接的数据。 +1. 服务器不能使用`New`状态。 +1. 服务器可使用 KeepAlive 状态来避免客户端关闭主连接。 + +## 传输格式 + +Mux.Cool 使用对称传输格式,即客户端和服务器发送和接收相同格式的数据。 + +### 帧格式 + +| 2 字节 | L 字节 | X 字节 | +| ------------ | ------ | -------- | +| 元数据长度 L | 元数据 | 额外数据 | + +### 元数据 + +元数据有若干种类型。所有类型的元数据都包含 ID 和 Opt 两项,其含义为: + +- ID: 子连接的唯一标识 + - 对于一般 Mux 子连接,ID 由 1 开始累加 + - 对于 Xray 实现的 [Single XUDP](https://github.com/XTLS/Xray-core/blob/main/common/xudp/xudp.go),ID 始终为 0 +- Opt: + - D(0x01): 有额外数据 + +当选项 Opt(D) 开启时,额外数据格式如下: + +| 2 字节 | X-2 字节 | +| -------- | -------- | +| 长度 X-2 | 数据 | + +### 新建子连接 (New) + +| 2 字节 | 1 字节 | 1 字节 | 1 字节 | 2 字节 | 1 字节 | A 字节 | 8 字节 | +| ------ | ------ | -------- | ---------- | ------ | ---------- | ------ | ---------------- | +| ID | 0x01 | 选项 Opt | 网络类型 N | 端口 | 地址类型 T | 地址 A | Global ID (XUDP) | + +其中: + +- 网络类型 N: + - 0x01:TCP,表示当前子连接的流量应当以 TCP 的方式发送至目标。 + - 0x02:UDP,表示当前子连接的流量应当以 UDP 的方式发送至目标。 +- 地址类型 T: + - 0x01:IPv4 + - 0x02:域名 + - 0x03:IPv6 +- 地址 A: + - 当 T = 0x01 时,A 为 4 字节 IPv4 地址; + - 当 T = 0x02 时,A 为 1 字节长度(L) + L 字节域名; + - 当 T = 0x03 时,A 为 16 字节 IPv6 地址; +- Global ID (XUDP): + - 客户端计算出 UDP 来源二元组的全局独特 ID,服务端用以确保当 XUDP 断线重连时,仍使用同一个端口与目标通信。 + +在新建子连接时,若 Opt(D) 开启,则这一帧所带的数据需要被发往目标主机。 + +### 保持子连接 (Keep) + +TCP + +| 2 字节 | 1 字节 | 1 字节 | +| ------ | ------ | -------- | +| ID | 0x02 | 选项 Opt | + +UDP + +| 2 字节 | 1 字节 | 1 字节 | 1 字节 | 2 字节 | 1 字节 | A 字节 | +| ------ | ------ | -------- | ---------- | ------ | ---------- | ------ | +| ID | 0x02 | 选项 Opt | 网络类型 N | 端口 | 地址类型 T | 地址 A | + +在保持子连接时,若 Opt(D) 开启,则这一帧所带的数据需要被发往目标主机。 +XUDP 在 Opt(D) 之后加 UDP 地址,格式同新建子连接,但没有 Global ID。 + +### 关闭子连接 (End) + +| 2 字节 | 1 字节 | 1 字节 | +| ------ | ------ | -------- | +| ID | 0x03 | 选项 Opt | + +在保持子连接时,若 Opt(D) 开启,则这一帧所带的数据需要被发往目标主机。 + +### 保持连接 (KeepAlive) + +| 2 字节 | 1 字节 | 1 字节 | +| ------ | ------ | -------- | +| ID | 0x04 | 选项 Opt | + +在保持连接时: + +- 若 Opt(D) 开启,则这一帧所带的数据必须被丢弃。 +- ID 可为随机值。 + +## 应用 + +Mux.Cool 协议与底层协议无关,理论上可以使用任何可靠的流式连接来传输 Mux.Cool 的协议数据。 + +在目标导向的协议如 Shadowsocks 和 VMess 协议中,连接建立时必须包含一个指定的地址。 +为了保持兼容性,Mux.Cool 协议指定地址为“v1.mux.cool”。即当主连接的目标地址与之匹配时,则进行 Mux.Cool 方式的转发,否则按传统方式进行转发。(注:这是一个程序内的标记,VMess 和 VLESS 并不会在数据包中发送“v1.mux.cool”地址) diff --git a/docs/ru/development/protocols/vless.md b/docs/ru/development/protocols/vless.md new file mode 100644 index 000000000..c5297cb42 --- /dev/null +++ b/docs/ru/development/protocols/vless.md @@ -0,0 +1,91 @@ +# VLESS 协议 + +VLESS 是一个无状态的轻量传输协议,可以作为 Xray 客户端和服务器之间的桥梁。 + +## Request & Response + +| 1 字节 | 16 字节 | 1 字节 | M 字节 | 1 字节 | 2 字节 | 1 字节 | S 字节 | X 字节 | +| -------- | --------- | -------------- | ----------------- | ------ | ------ | -------- | ------ | -------- | +| 协议版本 | 等价 UUID | 附加信息长度 M | 附加信息 ProtoBuf | 指令 | 端口 | 地址类型 | 地址 | 请求数据 | + +| 1 字节 | 1 字节 | N 字节 | Y 字节 | +| ---------------------- | -------------- | ----------------- | -------- | +| 协议版本,与请求的一致 | 附加信息长度 N | 附加信息 ProtoBuf | 响应数据 | + +VLESS 早在第二个测试版 ALPHA 2 时就已经是上述结构了(BETA 是第五个测试版): + +> “响应认证”被替换为“协议版本”并移至最前,使 VLESS 可以升级换代,同时消除了生成伪随机数的开销。混淆相关结构被替换为附加信息(ProtoBuf)并前移,赋予协议本身可扩展性,相关开销也极小([gogo/protobuf](https://github.com/gogo/protobuf)),若无附加信息则无相关开销。 + +我一直觉得“响应认证”不是必要的,ALPHA 时为了提升生成随机数的性能,还用 math/rand 替换 crypto/rand,而现在都不需要了。 + +“协议版本”不仅能起到“响应认证”的作用,还赋予了 VLESS 无痛升级协议结构的能力,带来无限的可能性。 +“协议版本”在测试版本中均为 0,正式版本中为 1,以后若有不兼容的协议结构变更则应升级版本。 + +VLESS 服务端的设计是 switch version,即同时支持所有 VLESS 版本。若需要升级协议版本(可能到不了这一步),推荐的做法是服务端提前一个月支持,一个月后再改客户端。VMess 请求也有协议版本,但它的认证信息在外面,指令部分则高度耦合且有固定加密,导致里面的协议版本毫无意义,服务端也没有进行判断,响应则没有协议版本。Trojan 的协议结构中没有协议版本。 + +接下来是 UUID,我本来觉得 16 字节有点长,曾经考虑过缩短它,但后来看到 Trojan 用了 56 个可打印字符(56 字节),就彻底打消了这个念头。服务端每次都要验证 UUID,所以性能也很重要:VLESS 的 Validator 经历了多次重构/升级,相较于 VMess,它十分简洁且耗资源很少,可以同时支持非常多的用户,性能也十分强悍,验证速度极快(sync.Map)。API 动态增删用户则更高效顺滑。 +https://github.com/XTLS/Xray-core/issues/158 + +引入 ProtoBuf 是一个创举,等下会详细讲解。“指令”到“地址”的结构目前与 VMess 完全相同,同样支持 Mux。 + +总体上,ALPHA 2 到 BETA 主要是:结构进化、清理整合、性能提升、更加完善。这些都是一点一滴的,详见 [VLESS Changes](https://github.com/rprx/v2ray-vless/releases)。 + +## ProtoBuf + +似乎只有 VLESS 可选内嵌 ProtoBuf,它是一种数据交换格式,信息被紧密编码成二进制,TLV 结构(Tag Length Value)。 + +起因是我看到一篇文章称 SS 有一些缺点,如没有设计错误回报机制,客户端没办法根据不同的错误采取进一步的动作。 +(但我并不认同所有错误都要回报,不然防不了主动探测。下一个测试版中,服务器可以返回一串自定义信息。) +于是想到一个可扩展的结构是很重要的,未来它也可以承载如动态端口指令。不止响应,请求也需要类似的结构。 +本来打算自己设计 TLV,接着发觉 ProtoBuf 就是此结构、现成的轮子,完全适合用来做这件事,各语言支持等也不错。 + +目前“附加信息”只有 Scheduler 和 SchedulerV,它们是 MessName 和 MessSeed 的替代者,**当你不需要它们时,“附加信息长度”为 0,也就不会有 ProtoBuf 序列化/反序列化的开销**。其实我更愿意称这个过程为“拼接”,因为 pb 实际原理上也只是这么做而已,相关开销极小。拼接后的 bytes 十分紧凑,和 ALPHA 的方案相差无几,有兴趣的可以分别输出并对比。 + +为了指示对附加信息(Addons,也可以理解成插件,以后可以有很多个插件)的不同支持程度,下个测试版会在“附加信息长度”前新增“附加信息版本”。256 - 1 = 255 字节是够用且合理的(65535 就太多了,还可能有人恶意填充),现有的只用了十分之一,以后也不会同时有那么多附加信息,且大多数情况下是完全没有附加信息的。真不够用的话,可以升级 VLESS 版本。 + +为了减少逻辑判断等开销,暂定 Addons 不使用多级结构。一个月前出现过“可变协议格式”的想法,pb 是可以做到打乱顺序,但没必要,因为现代加密的设计不会让旁观者看出两次传输的头部相同。 + +下面介绍 Schedulers 和 Encryption 的构想,**它们都是可选的**,一个应对流量时序特征问题,一个应对密码学上的问题。 + +## ~~Schedulers~~ Flow + +~~中文名暂称:流量调度器~~(2020-09-03 更新:中文名确定为“流控”),指令由 ProtoBuf 承载,控制的是数据部分。 + +我之前发现,VMess 原有的 shake “元数据混淆”在 TLS 上完全不会带来有意义的改变,只会降低性能,所以 VLESS 弃用了它。并且,“混淆”这个表述容易被误解成伪装,也弃用了。顺便一提,我一直是不看好伪装的:做不到完全一样,那不就是强特征吗?做得到完全一样,那为什么不直接用伪装目标?我一开始用的是 SSR,后来发现它只是表面伪装骗运营商,就再也没用过了。 + +那么,“流量调度器”要解决什么问题?它影响的是宏观流量时序特征,而不是微观特征,后者是加密要解决的事情。流量时序特征可以是协议带来的,比如 Socks5 over TLS 时的 Socks5 握手 ,TLS 上不同的这种特征对于监测者来说就是不同的协议,此时无限 Schedulers 就相当于无限协议(重新分配每次发送的数据量大小等)。流量时序特征也可以是行为带来的,比如访问 Google 首页时加载了多少文件、顺序、每个文件的大小,多套一层加密并不能有效掩盖这些信息。 + +Schedulers 没必要像下面的 Encryption 一样整个套在外面,因为头部的一丁点数据相对于后面的数据量来说太微不足道了。 + +BETA 2 预计推出两个初级的 Scheduler:Zstd 压缩、数据量动态扩充。进阶操作才是从宏观层面来控制、分配,暂时咕咕。 + +## Encryption + +与 VMess 的高度耦合不同,VLESS 的服务端、客户端不久后可以提前约定好加密方式,仅在外面套一层加密。这有点类似于使用 TLS,不影响承载的任何数据,也可以理解成底层就是从 TLS 换成预设约定加密。相对于高度耦合,这种方式更合理且灵活:一种加密方式出了安全性问题,直接扔掉并换用其它的就行了,十分方便。VLESS 服务端还会允许不同的加密方式共存。 + +对比 VMess,VLESS 相当于把 security 换成 encryption,把 disableInsecureEncryption 换成 decryption,就解决了所有问题。目前 encryption 和 decryption 只接受 \"none\" 且不能留空(即使以后有连接安全性检查),详见 [VLESS 配置文档](https://github.com/rprx/v2fly-github-io/blob/master/docs/config/protocols/vless.md)。encryption 并不需要往外移一级,一是因为无法复用很多代码,二是因为会影响控制粒度,看未来的应用就明白了。 + +加密支持两类形式,一类是加密完全独立,需要额外密码,适合私用,另一类是结合已有的 UUID 来加密,适合公用。 +(若用第一类加密形式,且密码是以某种形式公开的,比如多人共用,那么中间人攻击就不远了) +重新设计的动态端口可能会随加密同时推出,指令由 ProtoBuf 承载,具体实现和 VMess 的动态端口也会有很多不同。 + +套现成加密是件很简单的事情,也就多一层 writer & reader。BETA 3 预计支持 SS 的 aes-128-gcm 和 chacha20-ietf-poly1305: +客户端的 encryption 可以填 “auto: ss_aes-128-gcm_0_123456, ss_chacha20-ietf-poly1305_0_987654”,auto 会选择最适合当前机器的,0 代表测试版,最后的是密码。服务端的 decryption 也是类似填法,收到请求时会逐一尝试解密。 + +并不是所有组合都需逐一尝试:VMess 的加密分为三段,第一段是认证信息,结合了 UUID、alterId、时间因素,第二段是指令部分,以固定算法加密,指令中含有数据部分使用的加密算法,第三段才是重要的数据部分。可以看出,VMess 的加解密方式实际上是多对一(服务端适配),而不仅是结合 UUID。但仅是结合 UUID 来加密也是件相对麻烦的事情,短时间内不会出,鉴于我们现在有 VMessAEAD 可用,也并不着急。若 VLESS 推出了结合 UUID 的加密方式,相当于重构了整个 VMess。 + +## UDP issues + +[XUDP:VLESS & VMess & Mux UDP FullCone NAT](https://github.com/XTLS/Xray-core/discussions/252) + +## 客户端开发指引 + +1. VLESS 协议本身还会有不兼容升级,但客户端配置文件参数基本上是只增不减的。iOS 客户端的协议实现则需紧跟升级。 +2. **视觉标准:UI 标识请统一用 VLESS**,而不是 VLess / Vless / vless,配置文件不受影响,代码内则顺其自然。 +3. `encryption` 应做成输入框而不是选择框,新配置的默认值应为 `none`,若用户置空则应代填 `none`。 + +## VLESS 分享链接标准 + +感谢 a [@DuckSoft](https://github.com/DuckSoft) 的提案! + +详情请见 [VMessAEAD / VLESS 分享链接标准提案](https://github.com/XTLS/Xray-core/issues/91) diff --git a/docs/ru/development/protocols/vmess.md b/docs/ru/development/protocols/vmess.md new file mode 100644 index 000000000..1a1ee8d01 --- /dev/null +++ b/docs/ru/development/protocols/vmess.md @@ -0,0 +1,175 @@ +# VMess 协议 + +VMess 是一个加密传输协议,可以作为 Xray 客户端和服务器之间的桥梁。 + +## 版本 + +当前版本号为 1。 + +## 依赖 + +### 底层协议 + +VMess 是一个基于 TCP 的协议,所有数据使用 TCP 传输。 + +### 用户 ID + +ID 等价于 [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier),是一个 16 字节长的随机数,它的作用相当于一个令牌(Token)。 +一个 ID 形如:de305d54-75b4-431b-adb2-eb6b9e546014,几乎完全随机,可以使用任何的 UUID 生成器来生成,比如[这个](https://www.uuidgenerator.net/)。 + +用户 ID 可在[配置文件](../../config)中指定。 + +### 函数 + +- MD5: [MD5 函数](https://en.wikipedia.org/wiki/MD5) + - 输入参数为任意长度的 byte 数组 + - 输出为一个 16 byte 的数组 +- HMAC: [HMAC 函数](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) + - 输入参数为: + - H:散列函数 + - K:密钥,任意长度的 byte 数组 + - M:消息,任意长度的 byte 数组 +- Shake: [SHA3-Shake128 函数](https://en.wikipedia.org/wiki/SHA-3) + - 输入参数为任意长度的字符串 + - 输出为任意长度的字符串 + +## 通讯过程 + +VMess 是一个无状态协议,即客户端和服务器之间不需要握手即可直接传输数据,每一次数据传输对之前和之后的其它数据传输没有影响。 + +VMess 的客户端发起一次请求,服务器判断该请求是否来自一个合法的客户端。如验证通过,则转发该请求,并把获得的响应发回给客户端。 + +VMess 使用非对称格式,即客户端发出的请求和服务器端的响应使用了不同的格式。 + +## 客户端请求 + +| 16 字节 | X 字节 | 余下部分 | +| -------- | -------- | -------- | +| 认证信息 | 指令部分 | 数据部分 | + +### 认证信息 + +认证信息是一个 16 字节的哈希(hash)值,它的计算方式如下: + +- H = MD5 +- K = 用户 ID (16 字节) +- M = UTC 时间,精确到秒,取值为当前时间的前后 30 秒随机值(8 字节, Big Endian) +- Hash = HMAC(H, K, M) + +### 指令部分 + +指令部分经过 AES-128-CFB 加密: + +- Key:MD5(用户 ID + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21')) +- IV:MD5(X + X + X + X),X = []byte(认证信息生成的时间) (8 字节, Big Endian) + +| 1 字节 | 16 字节 | 16 字节 | 1 字节 | 1 字节 | 4 位 | 4 位 | 1 字节 | 1 字节 | 2 字节 | 1 字节 | N 字节 | P 字节 | 4 字节 | +| :--------: | :---------: | :----------: | :--------: | :------: | :----: | :----------: | :----: | :------: | :-------: | :--------: | :----: | :----: | :----: | +| 版本号 Ver | 数据加密 IV | 数据加密 Key | 响应认证 V | 选项 Opt | 余量 P | 加密方式 Sec | 保留 | 指令 Cmd | 端口 Port | 地址类型 T | 地址 A | 随机值 | 校验 F | + +选项 Opt 细节:(当某一位为 1 时,表示该选项启用) + +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | +| :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | +| X | X | X | X | X | M | R | S | + +其中: + +- 版本号 Ver:始终为 1; +- 数据加密 IV:随机值; +- 数据加密 Key:随机值; +- 响应认证 V:随机值; +- 选项 Opt: + - S (0x01):标准格式的数据流(建议开启); + - R (0x02):客户端期待重用 TCP 连接(Xray 2.23+ 弃用); + - 只有当 S 开启时,这一项才有效; + - M (0x04):开启元数据混淆(建议开启); + - 只有当 S 开启时,这一项才有效; + - 当其项开启时,客户端和服务器端需要分别构造两个 Shake 实例,分别为 RequestMask = Shake(请求数据 IV), ResponseMask = Shake(响应数据 IV)。 + - X:保留 +- 余量 P:在校验值之前加入 P 字节的随机值; +- 加密方式:指定数据部分的加密方式,可选的值有: + - 0x00:AES-128-CFB; + - 0x01:不加密; + - 0x02:AES-128-GCM; + - 0x03:ChaCha20-Poly1305; +- 指令 Cmd: + - 0x01:TCP 数据; + - 0x02:UDP 数据; +- 端口 Port:Big Endian 格式的整型端口号; +- 地址类型 T: + - 0x01:IPv4 + - 0x02:域名 + - 0x03:IPv6 +- 地址 A: + - 当 T = 0x01 时,A 为 4 字节 IPv4 地址; + - 当 T = 0x02 时,A 为 1 字节长度(L) + L 字节域名; + - 当 T = 0x03 时,A 为 16 字节 IPv6 地址; +- 校验 F:指令部分除 F 外所有内容的 FNV1a hash; + +### 数据部分 + +当 Opt(S) 开启时,数据部分使用此格式。实际的请求数据被分割为若干个小块,每个小块的格式如下。服务器校验完所有的小块之后,再按基本格式的方式进行转发。 + +| 2 字节 | L 字节 | +| :----: | :----: | +| 长度 L | 数据包 | + +其中: + +- 长度 L:Big Endian 格式的整型,最大值为 2^14; + - 当 Opt(M) 开启时,L 的值 = 真实值 xor Mask。Mask = (RequestMask.NextByte() << 8) + RequestMask.NextByte(); +- 数据包:由指定的加密方式加密过的数据包; + +在传输结束之前,数据包中必须有实际数据,即除了长度和认证数据之外的数据。当传输结束时,客户端必须发送一个空的数据包,即 L = 0(不加密) 或认证数据长度(有加密),来表示传输结束。 + +按加密方式不同,数据包的格式如下: + +- 不加密: + - L 字节:实际数据; +- AES-128-CFB:整个数据部分使用 AES-128-CFB 加密 + - 4 字节:实际数据的 FNV1a hash; + - L - 4 字节:实际数据; +- AES-128-GCM:Key 为指令部分的 Key,IV = count (2 字节) + IV (10 字节)。count 从 0 开始递增,每个数据包加 1;IV 为 指令部分 IV 的第 3 至第 12 字节。 + - L - 16 字节:实际数据; + - 16 字节:GCM 认证信息 +- ChaCha20-Poly1305:Key = MD5(指令部分 Key) + MD5(MD5(指令部分 Key)),IV = count (2 字节) + IV (10 字节)。count 从 0 开始递增,每个数据包加 1;IV 为 指令部分 IV 的第 3 至第 12 字节。 + - L - 16 字节:实际数据; + - 16 字节:Poly1305 认证信息 + +## 服务器应答 + +应答头部数据使用 AES-128-CFB 加密,IV 为 MD5(数据加密 IV),Key 为 MD5(数据加密 Key)。实际应答数据视加密设置不同而不同。 + +| 1 字节 | 1 字节 | 1 字节 | 1 字节 | M 字节 | 余下部分 | +| ---------- | -------- | -------- | ---------- | -------- | ------------ | +| 响应认证 V | 选项 Opt | 指令 Cmd | 指令长度 M | 指令内容 | 实际应答数据 | + +其中: + +- 响应认证 V:必须和客户端请求中的响应认证 V 一致; +- 选项 Opt: + - 0x01:服务器端准备重用 TCP 连接(Xray 2.23+ 弃用); +- 指令 Cmd: + - 0x01:动态端口指令 +- 实际应答数据: + - 如果请求中的 Opt(S) 开启,则使用标准格式,否则使用基本格式。 + - 格式均和请求数据相同。 + - 当 Opt(M) 开启时,长度 L 的值 = 真实值 xor Mask。Mask = (ResponseMask.NextByte() << 8) + ResponseMask.NextByte(); + +### 动态端口指令 + +| 1 字节 | 2 字节 | 16 字节 | 2 字节 | 1 字节 | 1 字节 | +| ------ | --------- | ------- | ------- | -------- | ---------- | +| 保留 | 端口 Port | 用户 ID | AlterID | 用户等级 | 有效时间 T | + +其中: + +- 端口 Port:Big Endian 格式的整型端口号; +- 有效时间 T:分钟数; + +客户端在收到动态端口指令时,服务器已开放新的端口用于通信,这时客户端可以将数据发往新的端口。在 T 分钟之后,这个端口将失效,客户端必须重新使用主端口进行通信。 + +## 注释 + +- 为确保向前兼容性,所有保留字段的值必须为 0。 diff --git a/docs/ru/document/README.md b/docs/ru/document/README.md new file mode 100644 index 000000000..a12b6b83b --- /dev/null +++ b/docs/ru/document/README.md @@ -0,0 +1,52 @@ +--- +sidebar: auto +--- +# Быстрый старт + +> **В этой главе вы узнаете, как максимально просто получить Xray и начать его использовать.** + +## Загрузка и установка + +Xray поддерживает разнообразные платформы, и вы можете получить разные версии Xray из множества источников и различными способами. + +Перейдите в раздел [Загрузка и установка](./install.md), чтобы загрузить Xray. + +## Настройка и запуск + +После загрузки и установки Xray вам нужно всего лишь настроить его, чтобы начать использование. + +Перейдите в раздел [Настройка и запуск](./config.md), чтобы изучить самый простой способ настройки. + +## Команды и аргументы + +Xray обладает множеством команд и аргументов, что делает его гибким и мощным. + +Перейдите в раздел [Команды и аргументы](./command.md), чтобы узнать больше о командах и аргументах Xray. + +## Улучшение документации + +Если вы заинтересованы, перейдите в раздел [Использование документации](./document.md), чтобы помочь нам улучшить документацию, или нажмите кнопку `Помогите нам улучшить эту страницу!` внизу страницы. + +Мы очень благодарны каждому участнику за вклад! Вы делаете Project X сильнее! + +## Простыми словами + +Практические советы для новичков. + +Перейдите в раздел [Простыми словами](./level-0/) для просмотра. + +## Базовые навыки + +Освоив основы, вы можете перейти к разделу [Базовые навыки](./level-1/), чтобы узнать о других способах использования. + +## Продвинутая документация + +Практические советы для опытных пользователей. + +Перейдите в раздел [Продвинутая документация](./level-2/) для просмотра. + +::: tip Благодарность +Огромное спасибо всем за то, что делитесь своими навыками и опытом, которые делают Xray с каждым днем ​​лучше. +::: + + diff --git a/docs/ru/document/command.md b/docs/ru/document/command.md new file mode 100644 index 000000000..50b46ce63 --- /dev/null +++ b/docs/ru/document/command.md @@ -0,0 +1,159 @@ +# Командные аргументы + +::: tip +Xray использует команды и аргументы в стиле Go. +::: + +## Базовые команды + +Вы можете запустить `xray help`, чтобы получить список всех базовых команд Xray, а также их описание и примеры использования. + +``` +Xray is a platform for building proxies. + +Usage: + + xray [arguments] + +The commands are: + + run Run Xray with config, the default command + version Show current version of Xray + api Call an API in an Xray process + tls TLS tools + uuid Generate UUIDv4 or UUIDv5 + x25519 Generate key pair for x25519 key exchange + wg Generate key pair for wireguard key exchange + +Use "xray help " for more information about a command. + +``` + +### xray run + +Запуск Xray с указанием одного или нескольких файлов конфигурации. + +Использование: + +``` + xray run [-c config.json] [-confdir dir] +``` + +``` +Run Xray with config, the default command. + +The -config=file, -c=file flags set the config files for +Xray. Multiple assign is accepted. + +The -confdir=dir flag sets a dir with multiple json config + +The -format=json flag sets the format of config files. +Default "auto". + +The -test flag tells Xray to test config files only, +without launching the server. + +The -dump flag tells Xray to print the merged config. +``` + +`-config=` / `-c=`: Указывает путь к файлу конфигурации, поддерживается использование нескольких файлов. +`-confdir=`: Указывает путь к папке, содержащей несколько файлов конфигурации. +`-format=`: Задает формат файлов конфигурации. +`-test`: Проверяет корректность файлов конфигурации. +`-dump`: Выводит объединенный результат слияния нескольких файлов конфигурации. + +::: tip +Помимо формата JSON по умолчанию, файлы конфигурации также могут быть в формате TOML или YAML. Если формат не указан явно, он определяется по расширению файла. +::: + +``` + xray run -dump +``` + +Выводит результат слияния нескольких файлов конфигурации. + +### xray version + +Выводит информацию о версии Xray, версии Golang и т. д. + +Использование: + +``` + xray version +``` + +### xray api + +Вызов gRPC API Xray, который должен быть включен в файле конфигурации. + +Использование: + +``` +xray api [arguments] +``` + +``` + restartlogger Restart the logger + stats Get statistics + statsquery Query statistics + statssys Get system statistics + adi Add inbounds + ado Add outbounds + rmi Remove inbounds + rmo Remove outbounds +``` + +### xray tls + +Инструменты для работы с TLS. + +Использование: + +``` +xray tls [arguments] +``` + +``` + cert Generate TLS certificates + ping Ping the domain with TLS handshake + certChainHash Calculate TLS certificates hash. +``` + +### xray uuid + +Генерация UUID. + +Использование: + +``` +xray uuid [-i "example"] +``` + +### xray x25519 + +Генерация пары ключей x25519. + +Использование: + +``` +xray x25519 [-i "(base64.RawURLEncoding)" --std-encoding ] +``` + +### xray wg + +Генерация пары ключей curve25519 для WireGuard. + +Использование: + +``` +xray wg [-i "(base64.StdEncoding)"] +``` + +::: tip +Если `-config` не указан, Xray попытается загрузить `config.json` из следующих мест: + +- Рабочий каталог (Working Directory); +- Путь, указанный в переменной окружения `Xray.location.asset` (см. [Переменные окружения](../config/features/env.md#ресурсные-файлы)). + ::: + + diff --git a/docs/ru/document/config.md b/docs/ru/document/config.md new file mode 100644 index 000000000..f0c86f103 --- /dev/null +++ b/docs/ru/document/config.md @@ -0,0 +1,108 @@ +# Настройка и запуск + +После того, как вы [скачали и установили](./install) Xray, вам потребуется его настроить. + +В данном руководстве мы рассмотрим только простой способ настройки. Дополнительные шаблоны: [Xray-examples](https://github.com/XTLS/Xray-examples) + +Для настройки более сложных функций обратитесь к подробным инструкциям в разделе [Файл конфигурации](../config/). + +::: danger +Во избежание расшифровки вашего трафика
+следует сгенерировать уникальный UUID с помощью команды `xray uuid` или `uuidgen`,
+который затем нужно вставить на стороне сервера в поле `inbounds[0].settings.clients[0].id`,
+а на стороне клиента - в поле `outbounds[0].settings.vnext[0].users[0].id`.
+::: + +## Настройка сервера + +Вам понадобится сервер с публичным IP-адресом (не за NAT), на котором будет запущен Xray. Конфигурация сервера: + +```json +{ + "inbounds": [ + { + "port": 10086, // Порт, который слушает сервер + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811" // Не забудьте заменить это поле, сгенерировав UUID с помощью `xray uuid` или `uuidgen` + } + ] + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ] +} +``` + +Убедитесь, что `id` и порт в конфигурации сервера совпадают с настройками клиента, чтобы подключение работало correctamente. + +## Настройка клиента + +На вашем компьютере (или телефоне) необходимо запустить Xray со следующей конфигурацией: + +```json +{ + "inbounds": [ + { + "port": 1080, // Порт SOCKS-прокси, на него нужно будет направлять трафик в браузере + "listen": "127.0.0.1", + "protocol": "socks", + "settings": { + "udp": true + } + } + ], + "outbounds": [ + { + "protocol": "vmess", + "settings": { + "vnext": [ + { + "address": "server", // Адрес сервера, замените его на IP-адрес или доменное имя вашего сервера + "port": 10086, // Порт сервера + "users": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811" // Не забудьте заменить это поле, сгенерировав UUID с помощью `xray uuid` или `uuidgen` + } + ] + } + ] + } + }, + { + "protocol": "freedom", + "tag": "direct" + } + ], + "routing": { + "domainStrategy": "IPOnDemand", + "rules": [ + { + "type": "field", + "ip": ["geoip:private","geoip:cn"], // Исключить локальную сеть и диапазоны IP-адресов Китая + "outboundTag": "direct" + } + ] + } +} +``` +Единственное, что вам нужно изменить в приведенной выше конфигурации, - это IP-адрес вашего сервера и UUID пользователя, как указано в комментариях. Эта конфигурация будет перенаправлять весь трафик на ваш сервер, за исключением локальной сети (например, доступ к маршрутизатору) и диапазонов IP-адресов Китая (например, доступ к bilibili, acfun). + +## Запуск + +- В Windows и macOS файл конфигурации обычно находится в том же каталоге, что и Xray, и называется `config.json`. + - Просто запустите `Xray` или `Xray.exe`. +- В Linux файл конфигурации обычно находится в каталоге `/etc/xray/` или `/usr/local/etc/xray/`. + - Запустите команду `xray run -c /etc/xray/config.json`. + - Или используйте systemd или другой инструмент для запуска Xray как службы в фоновом режиме. + +Более подробную информацию можно найти в [документации по конфигурации](../config/) и в разделе [Простыми словами](./level-0/). + + + diff --git a/docs/ru/document/document.md b/docs/ru/document/document.md new file mode 100644 index 000000000..4d9ef5967 --- /dev/null +++ b/docs/ru/document/document.md @@ -0,0 +1,49 @@ +# Вклад в документацию Project X + +Мы приветствуем ваш вклад в документацию Project X и благодарим каждого контрибьютора за помощь! Вы делаете Xray лучше! + +## Улучшение документации + +Документация Project X размещена на [GitHub](https://github.com/XTLS/Xray-docs-next). + +Вы можете внести изменения в документацию, выполнив следующие действия: + +1. Откройте [репозиторий документации Project X](https://github.com/XTLS/Xray-docs-next), нажмите кнопку "Fork" в правом верхнем углу, чтобы создать копию репозитория документации в вашей учетной записи GitHub. + +2. Используйте любой удобный инструмент для клонирования документации из вашего репозитория, например: + +``` +git clone https://github.com/XTLS/Xray-docs-next.git +``` + +3. Создайте новую ветку на основе ветки `main`, например: + +``` +git checkout -b your-branch +``` + +4. Внесите изменения в новую ветку. + + Примечание: рекомендуем придерживаться [Руководства по оформлению текстов на китайском языке](https://github.com/sparanoid/chinese-copywriting-guidelines) (на китайском). + +5. После внесения изменений отформатируйте их с помощью [Prettier](https://prettier.io/docs/en/install.html). + + Примечание: запросы на включение (PR) с ошибками форматирования могут быть отклонены. + +6. Зафиксируйте изменения и отправьте их в ваш репозиторий: + +``` +git push -u origin your-branch +``` + +7. Откройте GitHub, перейдите в раздел "Pull requests" и создайте новый запрос на включение (PR) в [репозиторий документации Project X](https://github.com/XTLS/Xray-docs-next). + +8. В заголовке и описании PR кратко опишите внесенные изменения. + +9. Дождитесь ответа. Если ваш PR будет принят, изменения появятся на [сайте документации Project X](https://xtls.github.io). + +## Нашли ошибку? + +Если вы обнаружили ошибку в документации, вы можете внести исправления или создать задачу (Issue). + + diff --git a/docs/ru/document/install.md b/docs/ru/document/install.md new file mode 100644 index 000000000..1abe00a63 --- /dev/null +++ b/docs/ru/document/install.md @@ -0,0 +1,119 @@ +# Загрузка и установка + +## Поддерживаемые платформы + +Xray доступен на следующих платформах: + +- Windows 7 и выше (x86 / amd64 / arm32 / arm64); +- macOS 10.10 Yosemite и выше (amd64 / arm64); +- Linux 2.6.23 и выше (x86 / amd64 / arm / arm64 / mips64 / mips / ppc64 / s390x / riscv64); + - Включая, но не ограничиваясь: Debian 7 / 8, Ubuntu 12.04 / 14.04 и выше, CentOS 7 / 8, Arch Linux и др.; +- FreeBSD (x86 / amd64); +- OpenBSD (x86 / amd64); +- Dragonfly BSD (amd64); + +## Загрузка Xray + +Предварительно скомпилированные ZIP-архивы с двоичными файлами можно найти в [Github Releases](https://github.com/xtls/Xray-core/releases). + +Скачайте архив для своей платформы, распакуйте его и можете использовать. + +## Проверка установочного пакета + +Xray предлагает два способа проверки: + +- SHA1 / SHA256 хэш-сумма ZIP-архива; +- Воспроизводимая сборка: см. [Сборка Xray](../development/intro/compile.md). + +## Установка на Windows + +- Скачайте ZIP-архив для Windows на [Github Releases](https://github.com/xtls/Xray-core/releases), распакуйте его, чтобы получить исполняемый файл `xray.exe`, а затем [запустите его из командной строки с параметрами](./command). +- Установите с помощью менеджера пакетов [Scoop](https://scoop.sh): Xray был добавлен в [Mochi](https://github.com/Qv2ray/mochi). + +## Установка на macOS + +- Скачайте ZIP-архив для macOS на [Github Releases](https://github.com/xtls/Xray-core/releases), распакуйте его, чтобы получить исполняемый файл `xray`, а затем [запустите его из командной строки с параметрами](./command.md). +- Установите с помощью менеджера пакетов [Homebrew](https://brew.sh): `brew install xray`. +- [homebrew-xray](https://github.com/N4FA/homebrew-xray) Спасибо, [@N4FA](https://github.com/N4FA)! + +## Установка на Linux + +### Установочные скрипты + +- Linux Script + + - [Xray-install](https://github.com/XTLS/Xray-install) + +* One Click + + - [Xray-script](https://github.com/kirin10000/Xray-script) + - [ProxySU](https://github.com/proxysu/ProxySU) + - [v2ray-agent](https://github.com/reeceyng/v2ray-agent) Спасибо, [@mack-a](https://github.com/mack-a) [@Reece](https://github.com/reeceyng)! + - [Xray-yes](https://github.com/jiuqi9997/Xray-yes) + - [Xray-onekey](https://github.com/wulabing/Xray_onekey) + +* Magisk + - [Xray4Magisk](https://github.com/CerteKim/Xray4Magisk) + - [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk) + +### Arch Linux + +#### Arch User Repository + +Требуется [помощник AUR](https://wiki.archlinux.org/index.php/AUR_helpers), например, [yay](https://github.com/Jguer/yay), установка с помощью команды `yay -S xray`. + +#### Arch Linux CN + +Сначала добавьте [репозиторий Arch Linux CN](https://www.archlinuxcn.org/archlinux-cn-repo-and-mirror/), затем установите от имени пользователя root с помощью команды `pacman -S xray`. + +### Linuxbrew + +Использование менеджера пакетов Linuxbrew аналогично Homebrew: `brew install xray`. + +### Debian + +### Gentoo + +В настоящее время существует три оверлея сторонних разработчиков, которые предоставляют сценарии установки Portage: + +- [CHN-beta/touchfish-os](https://github.com/gentoo-mirror/touchfish-os/tree/master/net-proxy/Xray): Поддерживается отдельным пользователем, подходит для систем с systemD. +- [Gentoo-zh](https://github.com/microcai/gentoo-zh): Поддерживается сообществом, подходит для систем с systemD. +- [JuanCldCmt/Xray-Overlay](https://github.com/JuanCldCmt/Xray-Overlay): Поддерживается отдельным пользователем, подходит для систем с openRC, использует группу пользователей xray для повышения безопасности. + +Добавьте оверлей в локальную систему с помощью layman или eselect-repository, а затем выполните установку. + +## Установка с помощью Docker + +- [teddysun/xray](https://hub.docker.com/r/teddysun/xray) + +### Файловая структура образа Docker + +- `/etc/xray/config.json`: файл конфигурации; +- `/usr/bin/xray`: основная программа Xray; +- `/usr/share/xray/geoip.dat`: файл данных IP; +- `/usr/share/xray/geosite.dat`: файл данных доменных имен. + +# Графические клиенты + +- OpenWrt + - [PassWall](https://github.com/xiaorouji/openwrt-passwall) + - [Hello World](https://github.com/jerrykuku/luci-app-vssr) + - [ShadowSocksR Plus+](https://github.com/fw876/helloworld) + - [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray)) +- Windows + - [v2rayN](https://github.com/2dust/v2rayN) + - [Qv2ray](https://github.com/Qv2ray/Qv2ray) (проект заморожен и архивирован) + - [Netch (NetFilter & TUN/TAP)](https://github.com/NetchX/Netch) (проект заморожен и архивирован) +- Android + - [v2rayNG](https://github.com/2dust/v2rayNG) + - [Kitsunebi](https://github.com/rurirei/Kitsunebi/tree/release_xtls) +- iOS / macOS (с чипом ARM) + - [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118) + - [Stash](https://apps.apple.com/app/stash/id1596063349) +- macOS (чип X86 / ARM) + - [Qv2ray](https://github.com/Qv2ray/Qv2ray) (проект заморожен и архивирован) + - [V2RayXS](https://github.com/tzmax/V2RayXS) + +# Генератор UUID + +Генератор UUID от сторонних разработчиков: [uuidgenerator.net](https://www.uuidgenerator.net) diff --git a/docs/ru/document/level-0/README.md b/docs/ru/document/level-0/README.md new file mode 100644 index 000000000..8d8146801 --- /dev/null +++ b/docs/ru/document/level-0/README.md @@ -0,0 +1,25 @@ +# Простые разговоры о сложном + +**Эта глава - базовый курс «С нуля», новичкам читать и учить обязательно** + +::: tip +Сделано с ❤️ [@ricuhkaen](https://github.com/ricuhkaen) +::: + +[【Глава 1】 Введение](./ch01-preface.md) - Чужой или свой сервер? Вот в чём вопрос + +[【Глава 2】 Подготовка](./ch02-preparation.md) - Прежде чем браться за дело, заготовь средства + +[【Глава 3】 Удалённое подключение](./ch03-ssh.md) - Мост между севером и югом, пропасть превращается в путь + +[【Глава 4】 Безопасность](./ch04-security.md) - Небрежность в безопасности, и родные будут лить слёзы + +[【Глава 5】 Создание сайта](./ch05-webpage.md) - Покажи свою красоту + +[【Глава 6】 Управление сертификатами](./ch06-certificates.md) - Законно то, что с лицензией + +[【Глава 7】 Xray сервер](./ch07-xray-server.md) - Наконец-то дождались + +[【Глава 8】 Xray клиенты](./ch08-xray-clients.md) - Новое начало + +[【Глава 9】 Приложение](./ch09-appendix.md) - Все контрольные точки здесь diff --git a/docs/ru/document/level-0/ch01-img01-choice.png b/docs/ru/document/level-0/ch01-img01-choice.png new file mode 100644 index 000000000..9e6e17a1a Binary files /dev/null and b/docs/ru/document/level-0/ch01-img01-choice.png differ diff --git a/docs/ru/document/level-0/ch01-preface.md b/docs/ru/document/level-0/ch01-preface.md new file mode 100644 index 000000000..435429f09 --- /dev/null +++ b/docs/ru/document/level-0/ch01-preface.md @@ -0,0 +1,99 @@ +# 【Глава 1】 Простыми словами + +## 1.1 Для кого эта документация? + +В двух словах: для **① новичков без опыта** **② желающих научиться настраивать свой собственный VPS**. + +## 1.2 Для кого эта документация не предназначена? + +В том числе, но не ограничиваясь: для всевозможных гуру и экспертов, для тех, кто слишком ленив, чтобы во всём разбираться самостоятельно, для тех, кто уже умеет настраивать VPS, для тех, кто точно решил пользоваться платными VPN-сервисами, для тех, кто предпочитает использовать готовые скрипты... Короче говоря, если у вас есть технические знания или вы не хотите настраивать всё сами, можете смело закрывать эту статью. Скорее всего, она покажется вам бесполезной и даже может вызвать раздражение, а оно вам надо? + +## 1.3 Важное замечание и другие примечания + +**Важное замечание:** + +Я не являюсь техническим экспертом, поэтому в этой статье неизбежны пробелы и неточности. Если вы обнаружите какие-либо ошибки, пожалуйста, дайте мне знать об этом деликатно, без лишних эмоций. + +**Отказ от ответственности:** + +Пожалуйста, относитесь к информации, представленной в этой статье, критически и проверяйте её самостоятельно. Я не несу никакой ответственности за любые проблемы или негативные последствия, возникшие в результате использования информации из этой статьи. + +**Предупреждение о многословности:** + +Поскольку эта статья предназначена для **новичков без опыта**, многие вещи будут объяснены максимально подробно. Поэтому будьте готовы к тому, что текст будет довольно многословным. + +## 1.4 Почему самостоятельная настройка — это сложно? + +Чтобы ответить на этот вопрос, нужно немного углубиться в историю вопроса. + +Во-первых, обход блокировок существует уже почти двадцать лет (Шок! Ужас!). Сначала для этого достаточно было пары манипуляций (поправить файл hosts, подключиться по SSH), потом понадобились веб-прокси, затем — собственные протоколы (например, Shadowsocks) и так далее. + +По мере того, как технологии блокировок совершенствовались на протяжении последних десятилетий, для самостоятельного обхода блокировок теперь нужно уметь: + +- Разбираться в основных командах Linux. +- Понимать принципы работы сетевых протоколов. +- Иметь технические навыки и средства для покупки и управления VPS. +- Иметь технические навыки и средства для покупки и управления доменными именами. +- Уметь получать TLS-сертификаты. +- И многое другое. + +Всё это превратило некогда простую задачу в пугающее испытание для новичков. + +Во-вторых, о проблемах новичков. + +Начинающим пользователям без технического бэкграунда, чтобы разобраться во всех этих премудростях, приходится изучать огромные массивы информации, разбросанной по всему интернету: блогам, форумам, группам в мессенджерах, репозиториям на GitHub, видео на YouTube и так далее. + +Вся эта информация часто оказывается противоречивой, неполной или попросту неверной. Новичкам остаётся только гадать, кому верить и как всё это работает на самом деле. + +В итоге вместо нехватки информации новички сталкиваются с её избытком. После нескольких (скорее всего, неудачных) попыток разобраться во всём этом, их энтузиазм угасает. А если по пути им ещё и «посчастливится» обратиться за помощью не в то место, их могут ещё и высмеять: «Ну ты и нуб, проще уж платным VPN пользоваться, зачем изобретать велосипед?» или «Сначала Linux изучи, потом приходи». + +В такие моменты остаётся только горько усмехнуться. + +## 1.5 «Почему бы просто не пользоваться платным VPN?» + +Во-первых, я хотел бы спросить у любителей подобных советов: разве платные VPN — это панацея? + +Во-вторых, я считаю, что «не знать» и «не хотеть знать» — это две большие разницы. Конечно, инфантилы, которые хотят всё и сразу, не прилагая никаких усилий, вызывают только раздражение. Но люди, которые искренне хотят разобраться во всём сами, не заслуживают презрения и издёвок. Именно эта нетерпимость к новичкам и побудила меня написать эту статью. + +Давайте разберёмся, в чём плюсы и минусы платных VPN-сервисов. + +**Плюсы:** + +1. **Простота использования:** сканирование QR-кода, добавление правил в один клик и т.д. +2. **Большой выбор серверов:** доступ к ресурсам разных стран и регионов; например, выделенные серверы с низкой задержкой (iplc), серверы для онлайн-игр и т.д. +3. **Множество точек подключения:** выше устойчивость к блокировкам, если один сервер заблокируют, можно подключиться к другому. + +**Риски:** + +За удобство приходится платить, и в случае с платными VPN-сервисами риски следующие: + +1. **VPN-провайдер имеет полный доступ к вашим данным:** всё, что вы делаете в интернете, **обязательно** проходит и **с большой вероятностью** хранится на серверах провайдера. Эти данные никак не защищены пользовательским соглашением или законом о защите персональных данных **(вас могут отслеживать и записывать всё, что вы делаете)**. +2. **Отсутствие регулирования рынка:** высока вероятность нарваться на мошенников **(провайдер может в любой момент исчезнуть с вашими деньгами)**. +3. **Давление со стороны регулирующих органов:** крупные VPN-провайдеры, с одной стороны, кажутся более надёжными, но, с другой стороны, чаще привлекают к себе внимание властей. В 2020 году было несколько случаев закрытия и прекращения работы крупных VPN-провайдеров, что привело к серьёзным неудобствам для пользователей **(провайдер может быть вынужден прекратить работу)**. +4. **Непрозрачность технических решений:** качество предоставляемых услуг может сильно варьироваться, не редки случаи обмана **(низкая скорость, частые обрывы связи, невозможность подключения)**. + +## 1.6 Так стоит ли настраивать VPN самостоятельно? + +Теперь, когда вы знаете о плюсах и минусах платных VPN, решать вам. В конце концов, лучший вариант — тот, который подходит именно вам. + +![Выбор за вами!](./ch01-img01-choice.png) + +1. Если вы решили воспользоваться платным VPN, можете закрыть эту статью. + +2. Если же вы решили настроить всё самостоятельно, продолжайте чтение! + +Цель этой статьи — стать отправной точкой для новичков, предоставить подробное пошаговое руководство по настройке VPN-сервера на VPS, начиная **с ввода первой команды** и заканчивая **успешным подключением к заблокированным ресурсам**. + +В процессе настройки вы познакомитесь с основными командами Linux, что станет хорошей базой для дальнейшего изучения этой операционной системы. + +## 1.7 Немного лирики + +1. В интернете много дезинформации, поэтому важно научиться критически мыслить, не поддаваться на провокации и не верить всему, что пишут. +2. Искренне надеюсь, что, получив доступ к свободному интернету, вы сможете узнавать больше нового, наслаждаться разнообразным контентом, знакомиться с интересными людьми и находить единомышленников. +3. Ваша личность в интернете — это всё ещё вы. Добиться полной анонимности крайне сложно, поэтому не забывайте о законах вашей страны и стран, IP-адреса которых вы используете. Всегда помните о собственной безопасности. + +## 1.8 Ваш прогресс + +> ⬛⬜⬜⬜⬜⬜⬜⬜ 12.5% + + diff --git a/docs/ru/document/level-0/ch02-img01-a-name.png b/docs/ru/document/level-0/ch02-img01-a-name.png new file mode 100644 index 000000000..b8f732266 Binary files /dev/null and b/docs/ru/document/level-0/ch02-img01-a-name.png differ diff --git a/docs/ru/document/level-0/ch02-preparation.md b/docs/ru/document/level-0/ch02-preparation.md new file mode 100644 index 000000000..26b1041e9 --- /dev/null +++ b/docs/ru/document/level-0/ch02-preparation.md @@ -0,0 +1,57 @@ +# 【Глава 2】 Подготовка + +Эта глава особенная, поскольку затрагивает финансовые операции. В соответствии с нейтральной позицией проекта, здесь не будет конкретных рекомендаций. Всё, что я могу сделать, — это рассказать, что вам понадобится. + +## 2.1 Приобретение VPS + +Вам нужно получить работающий VPS с не заблокированным IP-адресом и выполнить следующие базовые действия в панели управления: + +1. Установить на VPS операционную систему Debian 10 64-bit. +2. Записать IP-адрес VPS (в этой статье он будет обозначаться как `"100.200.300.400"`). + ::: tip + Это **неверный** IP-адрес, используемый только в качестве примера. Не забудьте заменить его на свой реальный IP-адрес. + ::: +3. Записать порт (Port) SSH для удалённого подключения к VPS. +4. Записать имя пользователя и пароль для удалённого подключения по SSH. + +Выбор и покупка VPS — дело непростое. Рекомендуем сначала изучить этот вопрос и выбрать тариф, который соответствует вашим финансовым возможностям и требованиям к скорости и качеству связи. Также можно воспользоваться бесплатными (постоянными или временными) предложениями от крупных облачных провайдеров, таких как Oracle Cloud и Google Cloud. Главное — не влезайте в долги. + +::: tip Пояснение +Несколько слов о выборе Debian 10 в качестве операционной системы. Что бы вы ни слышали в интернете, какой бы дистрибутив Linux ни советовали вам гуру, все эти споры о том, какой Linux лучше, **не имеют к вам никакого отношения**! Debian 10 — это надёжная и стабильная операционная система, которая отлично подходит для работы VPN-сервера и достаточно оптимизирована (например, имеет специальное ядро для облачных сред и своевременную поддержку BBR). Когда вы освоитесь с Linux, можете попробовать и другие дистрибутивы. +::: + +## 2.2 Выбор доменного имени + +Вам нужно получить доменное имя и добавить A-запись, указывающую на IP-адрес вашего VPS, в настройках DNS. + +1. Выберите надёжного международного регистратора доменных имён. Доменная зона (расширение домена) может быть любой, главное — не используйте `.cn`. +2. В настройках DNS добавьте A-запись, указывающую на IP-адрес вашего VPS (имя A-записи может быть любым, в этой статье оно будет обозначаться как `"a-name"`. Полное доменное имя будет выглядеть как `"a-name.yourdomain.com"`). Должно получиться примерно так: + +![Добавление A-записи](./ch02-img01-a-name.png) + +::: tip +Это **не** настоящий URL-адрес. Не забудьте заменить его на свой реальный адрес. +::: + +## 2.3 Необходимое программное обеспечение + +1. SSH-клиент для удалённого подключения: + + - Windows: [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) + - macOS/Linux: Terminal + +2. Программа для передачи файлов: + + - Windows: [WinSCP](https://winscp.net/eng/index.php) + - macOS/Linux: Terminal + +3. Хороший текстовый редактор: + - Windows/macOS/Linux: [VSCode](https://code.visualstudio.com) + +## 2.4 Ваш прогресс + +Если вы выполнили все пункты из этого раздела, у вас уже есть всё необходимое, чтобы открыть для себя новый мир. Так чего же мы ждём? Давайте перейдём к следующей главе и сделаем это! + +> ⬛⬛⬜⬜⬜⬜⬜⬜ 25% + + diff --git a/docs/ru/document/level-0/ch03-img01-putty-download.png b/docs/ru/document/level-0/ch03-img01-putty-download.png new file mode 100644 index 000000000..44dae2db7 Binary files /dev/null and b/docs/ru/document/level-0/ch03-img01-putty-download.png differ diff --git a/docs/ru/document/level-0/ch03-img02-putty-settings.png b/docs/ru/document/level-0/ch03-img02-putty-settings.png new file mode 100644 index 000000000..337288e03 Binary files /dev/null and b/docs/ru/document/level-0/ch03-img02-putty-settings.png differ diff --git a/docs/ru/document/level-0/ch03-img03-putty-keepalive.png b/docs/ru/document/level-0/ch03-img03-putty-keepalive.png new file mode 100644 index 000000000..73f4a7f54 Binary files /dev/null and b/docs/ru/document/level-0/ch03-img03-putty-keepalive.png differ diff --git a/docs/ru/document/level-0/ch03-img04-ssh-login.png b/docs/ru/document/level-0/ch03-img04-ssh-login.png new file mode 100644 index 000000000..824a703d7 Binary files /dev/null and b/docs/ru/document/level-0/ch03-img04-ssh-login.png differ diff --git a/docs/ru/document/level-0/ch03-img05-ssh-login-success.png b/docs/ru/document/level-0/ch03-img05-ssh-login-success.png new file mode 100644 index 000000000..a93400229 Binary files /dev/null and b/docs/ru/document/level-0/ch03-img05-ssh-login-success.png differ diff --git a/docs/ru/document/level-0/ch03-img06-apt-upgrade-full.gif b/docs/ru/document/level-0/ch03-img06-apt-upgrade-full.gif new file mode 100644 index 000000000..5b7237d96 Binary files /dev/null and b/docs/ru/document/level-0/ch03-img06-apt-upgrade-full.gif differ diff --git a/docs/ru/document/level-0/ch03-ssh.md b/docs/ru/document/level-0/ch03-ssh.md new file mode 100644 index 000000000..4d654d6ec --- /dev/null +++ b/docs/ru/document/level-0/ch03-ssh.md @@ -0,0 +1,87 @@ +# 【Глава 3】 Удалённое подключение + +## 3.1 Удалённое подключение к VPS (PuTTY) + +Во-первых, поскольку Windows является самой распространённой операционной системой среди новичков, в этой статье мы будем использовать её в качестве примера. + +Во-вторых, хотя PowerShell и WSL в Windows 10 и выше также предоставляют удобные инструменты для работы по SSH, не все версии Windows имеют эти компоненты. Поэтому в этой статье мы рассмотрим подключение по SSH с помощью старого доброго PuTTY. (После подключения по SSH действия во всех программах будут одинаковыми.) + +Итак, давайте начнём. + +1. Перейдите на [официальный сайт](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) PuTTY и скачайте версию, подходящую для вашей операционной системы (в этой статье мы будем использовать 64-битную версию). + + ![Скачать PuTTY](./ch03-img01-putty-download.png) + +2. Запустите PuTTY. Откроется главное окно программы. Теперь возьмите [блокнот](./ch02-preparation.md#21-получение-vps), в который вы записывали информацию в предыдущей главе, и введите **IP-адрес** и **порт** вашего VPS в соответствующие поля (на скриншоте ниже). Чтобы не вводить эти данные каждый раз, можно сохранить сеанс (Saved Sessions). В дальнейшем вы сможете загрузить сохранённые настройки одним кликом. + + ![Настройка PuTTY](./ch03-img02-putty-settings.png) + +3. Рекомендуем установить значение `keepalive` в разделе `Connection` равным `60` секундам, чтобы предотвратить разрыв SSH-соединения, если вы долгое время не будете выполнять никаких действий. Не забудьте снова сохранить настройки. + + ![Предотвращение разрывов соединения](./ch03-img03-putty-keepalive.png) + +::: warning Внимание +После любых изменений настроек PuTTY необходимо сохранить сеанс, иначе они будут потеряны при закрытии программы. +::: + +4. Нажмите кнопку "Open", чтобы открыть окно SSH-подключения. Введите имя пользователя и пароль для подключения к вашему VPS (в этой статье предполагается, что имя пользователя по умолчанию — `root`. Обратите внимание, что при вводе пароля в Linux не отображаются символы `******`. Это сделано для того, чтобы скрыть длину пароля. Не пугайтесь, ваша клавиатура в порядке!). + + ![Подключение по SSH](./ch03-img04-ssh-login.png) + +## 3.2 Успешное подключение по SSH! Знакомство с командной строкой! + +1. Если вы всё сделали правильно, вы увидите примерно такой экран, как на рисунке ниже. Это означает, что вы успешно подключились к серверу: + + ![Первое подключение к VPS](./ch03-img05-ssh-login-success.png) + + Этот экран — аналог «рабочего стола» на удалённом сервере, но здесь нет привычных значков, курсора мыши и ярких цветов. Только текст. Это и есть **командная строка** — *Command Line Interface* или сокращённо *CLI*. + + Все дальнейшие действия вам придётся выполнять в командной строке, как хакер в кино. Возможно, поначалу это покажется вам непривычным, но поверьте, в использовании командной строки нет ничего страшного или сложного. По сути, это всего лишь способ взаимодействия с компьютером с помощью текстовых команд вместо графического интерфейса. **Вы пишете команду, а компьютер её выполняет.** + +2. Теперь можете немного осмотреться и познакомиться с командной строкой. На этом экране уже есть полезная информация, например, версия ядра системы (в данном случае `4.19.37-5`), время последнего входа в систему, IP-адрес и т.д. Конечно, в зависимости от VPS, ваш экран может выглядеть немного иначе. + +3. Обратите внимание на последнюю строку командной строки. Слева от мигающего курсора находится набор символов. В данном случае это `root@vps-server:~#`. Что это значит? Всё просто: + + - Текущий пользователь: `root`. + - Имя сервера, на котором работает пользователь `root`: `vps-server`. + - Текущий каталог, в котором находится пользователь `root`: `~`. + - Символ `#` указывает на то, что после него можно вводить команды. + + Первые два пункта интуитивно понятны и не требуют пояснений. Третий пункт относится к файловой системе Linux. Сейчас вам не нужно вдаваться в подробности, достаточно знать, что `~` — это «домашний каталог» текущего пользователя. Четвёртый пункт, символ `#`, также не требует особого внимания. Просто знайте, что в дальнейшем все команды, которые вам нужно будет вводить, будут начинаться с `#` или `$`. Это будет означать, что **после** этого символа нужно ввести команду (поэтому при копировании команд **копируйте только текст после**, без символа `#` или `$`). + +## 3.3 Первое обновление программного обеспечения Linux! + +1. Так же, как и ваш телефон, будь то Android или iPhone, Linux нуждается в регулярном обновлении программного обеспечения для получения исправлений безопасности и новых функций. В Linux каждое приложение называется «пакетом» (package). А программа, которая управляет пакетами, называется «менеджером пакетов» (Package Manager). С помощью менеджера пакетов можно устанавливать, обновлять и удалять программы, а также обновлять саму систему Linux. Менеджеры пакетов Linux очень мощные, но сейчас вам достаточно знать, что в Debian используется менеджер пакетов `apt`. Давайте обновим систему с помощью `apt`, чтобы вы познакомились с его основными функциями. + +2. Базовые команды Linux: + + | Номер | Команда | Описание | + | :----: | :----------: | :----------------- | + | `cmd-01` | `apt update` | Проверить обновления | + | `cmd-02` | `apt upgrade` | Установить обновления| + +3. Введите первую команду, чтобы получить информацию об обновлениях: + + ```shell + apt update + ``` + +4. Затем введите вторую команду. При появлении запроса на подтверждение установки `(Y/n)` введите `y` и нажмите Enter, чтобы начать установку. + + ```shell + apt upgrade + ``` + +5. Весь процесс показан на гифке ниже: + + ![Демонстрация процесса обновления](./ch03-img06-apt-upgrade-full.gif) + +## 3.4 Ваш прогресс + +**Поздравляем, вы сделали ещё один важный шаг!** Теперь вы умеете подключаться к своему серверу по SSH! Но что делать после подключения, кроме обновления системы? Узнаем в следующей главе! + +> ⬛⬛⬛⬜⬜⬜⬜⬜ 37.5% + + + + diff --git a/docs/ru/document/level-0/ch04-img01-nano-ui.png b/docs/ru/document/level-0/ch04-img01-nano-ui.png new file mode 100644 index 000000000..5bd086ac0 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img01-nano-ui.png differ diff --git a/docs/ru/document/level-0/ch04-img02-sshd-conf-full.gif b/docs/ru/document/level-0/ch04-img02-sshd-conf-full.gif new file mode 100644 index 000000000..039f2e355 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img02-sshd-conf-full.gif differ diff --git a/docs/ru/document/level-0/ch04-img03-adduser.png b/docs/ru/document/level-0/ch04-img03-adduser.png new file mode 100644 index 000000000..e336a9569 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img03-adduser.png differ diff --git a/docs/ru/document/level-0/ch04-img04-adduser-full.gif b/docs/ru/document/level-0/ch04-img04-adduser-full.gif new file mode 100644 index 000000000..e13805467 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img04-adduser-full.gif differ diff --git a/docs/ru/document/level-0/ch04-img05-sudo-full.gif b/docs/ru/document/level-0/ch04-img05-sudo-full.gif new file mode 100644 index 000000000..20739a456 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img05-sudo-full.gif differ diff --git a/docs/ru/document/level-0/ch04-img06-ssh-no-root-full.gif b/docs/ru/document/level-0/ch04-img06-ssh-no-root-full.gif new file mode 100644 index 000000000..a098b2b68 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img06-ssh-no-root-full.gif differ diff --git a/docs/ru/document/level-0/ch04-img07-putty-default-user.png b/docs/ru/document/level-0/ch04-img07-putty-default-user.png new file mode 100644 index 000000000..2df1ab80d Binary files /dev/null and b/docs/ru/document/level-0/ch04-img07-putty-default-user.png differ diff --git a/docs/ru/document/level-0/ch04-img08-puttygen-save.png b/docs/ru/document/level-0/ch04-img08-puttygen-save.png new file mode 100644 index 000000000..4e185569b Binary files /dev/null and b/docs/ru/document/level-0/ch04-img08-puttygen-save.png differ diff --git a/docs/ru/document/level-0/ch04-img09-puttygen-save-keys.png b/docs/ru/document/level-0/ch04-img09-puttygen-save-keys.png new file mode 100644 index 000000000..3a515f8d9 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img09-puttygen-save-keys.png differ diff --git a/docs/ru/document/level-0/ch04-img10-winscp-import-session.png b/docs/ru/document/level-0/ch04-img10-winscp-import-session.png new file mode 100644 index 000000000..f9fadeb61 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img10-winscp-import-session.png differ diff --git a/docs/ru/document/level-0/ch04-img11-winscp-ui.png b/docs/ru/document/level-0/ch04-img11-winscp-ui.png new file mode 100644 index 000000000..4c0b14d0e Binary files /dev/null and b/docs/ru/document/level-0/ch04-img11-winscp-ui.png differ diff --git a/docs/ru/document/level-0/ch04-img12-winscp-locations.png b/docs/ru/document/level-0/ch04-img12-winscp-locations.png new file mode 100644 index 000000000..5cee0597f Binary files /dev/null and b/docs/ru/document/level-0/ch04-img12-winscp-locations.png differ diff --git a/docs/ru/document/level-0/ch04-img13-winscp-newfolder-key.png b/docs/ru/document/level-0/ch04-img13-winscp-newfolder-key.png new file mode 100644 index 000000000..6547a6346 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img13-winscp-newfolder-key.png differ diff --git a/docs/ru/document/level-0/ch04-img14-winscp-upload-key.png b/docs/ru/document/level-0/ch04-img14-winscp-upload-key.png new file mode 100644 index 000000000..52f60f365 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img14-winscp-upload-key.png differ diff --git a/docs/ru/document/level-0/ch04-img15-winscp-rename-key.png b/docs/ru/document/level-0/ch04-img15-winscp-rename-key.png new file mode 100644 index 000000000..03498cdb1 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img15-winscp-rename-key.png differ diff --git a/docs/ru/document/level-0/ch04-img16-winscp-full.gif b/docs/ru/document/level-0/ch04-img16-winscp-full.gif new file mode 100644 index 000000000..f99898a51 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img16-winscp-full.gif differ diff --git a/docs/ru/document/level-0/ch04-img17-rsa-login-full.gif b/docs/ru/document/level-0/ch04-img17-rsa-login-full.gif new file mode 100644 index 000000000..2fc692562 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img17-rsa-login-full.gif differ diff --git a/docs/ru/document/level-0/ch04-img18-putty-privatekey-location.png b/docs/ru/document/level-0/ch04-img18-putty-privatekey-location.png new file mode 100644 index 000000000..09233f318 Binary files /dev/null and b/docs/ru/document/level-0/ch04-img18-putty-privatekey-location.png differ diff --git a/docs/ru/document/level-0/ch04-img19-putty-privatekey-passphrase.png b/docs/ru/document/level-0/ch04-img19-putty-privatekey-passphrase.png new file mode 100644 index 000000000..3f9c6ceaa Binary files /dev/null and b/docs/ru/document/level-0/ch04-img19-putty-privatekey-passphrase.png differ diff --git a/docs/ru/document/level-0/ch04-img20-winscp-privatekey-location.png b/docs/ru/document/level-0/ch04-img20-winscp-privatekey-location.png new file mode 100644 index 000000000..4e6406a0d Binary files /dev/null and b/docs/ru/document/level-0/ch04-img20-winscp-privatekey-location.png differ diff --git a/docs/ru/document/level-0/ch04-security.md b/docs/ru/document/level-0/ch04-security.md new file mode 100644 index 000000000..11003e5b2 --- /dev/null +++ b/docs/ru/document/level-0/ch04-security.md @@ -0,0 +1,332 @@ +# 【Глава 4】 Обеспечение безопасности + +## 4.1 Зачем нужна безопасность? + +Безопасность Linux-серверов — это обширная и сложная тема. Бесчисленные веб-сайты, приложения, сервисы и даже критически важная инфраструктура построены на базе Linux. За всем этим стоят огромные деньги и коммерческие интересы, что, естественно, привлекает злоумышленников. В то же время надёжная работа этих сервисов крайне важна, поэтому любые серьёзные уязвимости недопустимы. Именно поэтому множество специалистов по безопасности изо дня в день ведут борьбу на передовой, обеспечивая стабильную работу цифрового мира, к которому мы все привыкли. + +Теперь, когда у вас есть собственный VPS-сервер, и вы собираетесь открыть на нём порты для перенаправления трафика, вы фактически оказываетесь на передовой этой борьбы и подвергаетесь тем же рискам. В то же время, новички, не обладающие достаточными знаниями и информацией, склонны впадать в крайности: либо они считают, что им ничего не угрожает, либо же, наоборот, впадают в паранойю. + +- Первым я бы посоветовал не относиться к безопасности легкомысленно и изучить этот вопрос более подробно, чтобы потом не пришлось кусать локти. + +- Вторым я бы посоветовал не паниковать. Ваш сервер вряд ли представляет собой лакомую цель для серьёзных злоумышленников, поэтому вам достаточно базовых мер защиты от автоматических сканеров и ботов, о которых мы и поговорим в этой главе. + +## 4.2 Какие именно риски существуют? + +Как мы уже говорили в главе про удалённое подключение, для доступа к вашему VPS достаточно знать четыре вещи: **IP-адрес**, **порт**, **имя пользователя** и **пароль**. Очевидно, что эти четыре элемента нужно защищать в первую очередь. Давайте разберём каждый из них: + +1. **IP-адрес**: злоумышленники могут сканировать целые диапазоны IP-адресов в поисках уязвимых серверов. Ваш IP-адрес — это публичная информация, которую невозможно скрыть. + +2. **Порт**: если вы используете настройки по умолчанию, то порт SSH равен `22`. + +3. **Имя пользователя**: если вы используете настройки по умолчанию, то имя пользователя — `root`. + +4. **Пароль**: пароль не имеет значения по умолчанию. Он либо генерируется автоматически при создании VPS, либо задаётся вами. Таким образом, если вы не меняли настройки сервера, то три из четырёх элементов уже известны злоумышленникам, и вся безопасность вашего сервера держится на одном только пароле. Возможны следующие варианты: + + - Вы используете автоматически сгенерированный пароль из панели управления VPS. Такие пароли обычно состоят из случайного набора символов (букв в разных регистрах, цифр и спецсимволов) и достаточно надёжны. + + - Вы установили простой пароль, например, `123456`. Взломать такой сервер не составит труда. + + - Вы установили сложный пароль, который используете где-то ещё. Это тоже небезопасно. Злоумышленники используют специальные программы, которые перебирают миллионы ранее скомпрометированных паролей из утечек данных. + +5. Важно понимать, что никакой хакер не будет лично подбирать ваш пароль. Все атаки выполняются автоматически с помощью специальных скриптов, которые работают круглосуточно. Пока вы спите, ваш сервер может подвергаться атакам. + + Если пароль будет подобран, злоумышленники получат полный доступ к вашему серверу (права пользователя `root`), смогут установить на него вредоносное ПО и использовать его в своих целях (например, для майнинга криптовалюты, рассылки спама, фишинговых атак, организации торрент-трекера, размещения публичных узлов для доступа к даркнету и т.д.). При этом злоумышленники могут действовать очень скрытно, и вы даже не заметите, что ваш сервер взломан, пока не получите уведомление от хостинг-провайдера о блокировке вашего аккаунта или, что ещё хуже, повестку в суд. + +6. Не забывайте, что при покупке VPS вы, скорее всего, указывали свои реальные платёжные данные. А при посещении сайтов и использовании социальных сетей ваш IP-адрес также сохраняется. Всё это может быть использовано против вас. **Поэтому, если на вашем сервере произойдёт что-то противозаконное, отвечать за это придётся вам.** + +## 4.3 Какие меры безопасности нужно предпринять? + +Исходя из всего вышесказанного, нам нужно защитить **порт**, **имя пользователя** и **пароль**, чтобы снизить риск взлома. Для этого необходимо: + +1. Изменить порт SSH на **нестандартный** (отличный от 22) (см. раздел 4.4). +2. Создать **нового пользователя** (не `root`) и **запретить удалённое подключение по SSH** для пользователя `root` (см. разделы 4.5 и 4.6). +3. Настроить **аутентификацию по SSH-ключам** и **запретить аутентификацию по паролю** (см. раздел 4.7). + +Выполняйте эти действия по порядку, чтобы не оказаться случайно заблокированным на своём же сервере. + +## 4.4 Изменение порта SSH + +Давайте решим проблему с портом SSH, который по умолчанию равен `22` (обратите внимание: у некоторых хостинг-провайдеров порт SSH по умолчанию уже отличается от 22. В этом случае вы можете пропустить этот шаг, но можете и изменить порт ещё раз, следуя инструкциям ниже). + +1. Базовые команды Linux: + + | Номер | Команда | Описание | + | :----: | :---------------- | :------------------------ | + | `cmd-03` | `nano` | Текстовый редактор | + | `cmd-04` | `systemctl restart` | Перезапуск службы | + +2. Важные файлы конфигурации Linux: + + | Номер | Путь к файлу | Описание | + | :------ | :----------------------- | :------------------------- | + | `conf-01` | `/etc/ssh/sshd_config` | Настройки SSH-сервера | + +3. Первое, что нужно сделать, — это открыть файл настроек SSH-сервера (`/etc/ssh/sshd_config`) в текстовом редакторе `nano`. В Windows вы бы просто нашли этот файл и дважды кликнули по нему. А как это сделать в Linux? Если вы внимательно читали предыдущие разделы, то наверняка уже догадались! Правильно, нужно выполнить команду: + + ```shell + nano /etc/ssh/sshd_config + ``` + +4. После открытия файла вы увидите интерфейс редактора `nano`. Обратите внимание на нижнюю часть экрана, где перечислены основные горячие клавиши (на скриншоте ниже выделены красной рамкой). Не нужно ничего заучивать, всё необходимое всегда перед глазами! + + ![Интерфейс nano](./ch04-img01-nano-ui.png) + +5) Второе, что нужно сделать, — это найти строку, начинающуюся с `Port`, и изменить номер порта. Число после `Port` — это номер порта SSH. Рекомендуется использовать число в диапазоне от `1024` до `65535` (в этой статье мы будем использовать порт `9753`). Как это сделать, используя горячие клавиши `nano`? Вы уже наверняка догадались! + + - Нажмите `Ctrl+W`, чтобы открыть поиск, введите `Port 22` и нажмите Enter. + - Замените `22` на `9753`. + - Примечание: если в начале строки стоит символ `#`, значит, эта строка закомментирована и не будет применяться. Вы можете либо раскомментировать её (удалив `#`), либо добавить новую строку без `#` в конце файла, как показано на скриншоте. + + ::: warning + Использование порта `9753` в этой статье делает его менее безопасным, поскольку злоумышленники могут начать сканировать этот порт в первую очередь. Кроме того, этот порт может быть заблокирован некоторыми провайдерами. Поэтому настоятельно рекомендуем использовать другой порт. У вас в распоряжении более 60 тысяч портов, так что выбрать есть из чего. + ::: + +6. Третье, что нужно сделать, — это сохранить изменения и выйти из редактора. + + - Как вы уже могли заметить, для сохранения файла используется не `Ctrl+S`, как в большинстве программ. + - Горячие клавиши: `Ctrl+O` — сохранить, `Ctrl+X` — выйти. + +7. И последнее, что нужно сделать, — это перезапустить SSH-сервер, чтобы изменения вступили в силу. + + ```shell + systemctl restart ssh + ``` + +8. Весь процесс показан на гифке ниже: + + ![Изменение порта SSH](./ch04-img02-sshd-conf-full.gif) + +9. Изменение настроек PuTTY + + Теперь, когда вы изменили порт SSH, вам нужно указать новый порт (`9753`) в настройках PuTTY. Вы ведь помните, где это делается? (Если нет, вернитесь и перечитайте предыдущие разделы!) + +## 4.5 Создание нового пользователя + +Перейдём ко второму шагу — избавлению от пользователя `root`. + +Прежде всего, нужно понимать, что пользователь `root` в Linux — это не просто администратор. Это корень системы, её основа, верховный правитель. Если безопасность учётной записи `root` будет нарушена, под угрозой окажется вся система. + +1. Базовые команды Linux: + + | Номер | Команда | Описание | + | :----: | :------------ | :---------------------------------- | + | `cmd-05` | `adduser` | Добавление нового пользователя | + | `cmd-06` | `apt install` | Установка программного обеспечения | + | `cmd-07` | `visudo` | Редактор файла sudoers | + +2. Первое, что нужно сделать, — это создать нового пользователя и установить для него пароль. Имя пользователя может быть любым, в этой статье мы будем использовать имя `vpsadmin`. + + ```shell + adduser vpsadmin + ``` + + Следуйте инструкциям на экране. Обязательно укажите пароль для нового пользователя (и не удивляйтесь, что при вводе пароля символы не отображаются). Далее система может запросить дополнительную информацию о пользователе. Можете пропустить эти пункты, нажав Enter. + + ![Создание нового пользователя](./ch04-img03-adduser.png) + + ::: warning + Использование имени пользователя `vpsadmin` в этой статье делает его менее безопасным, поскольку злоумышленники могут начать перебирать пароли для этого имени в первую очередь. Поэтому, как и в случае с портом, настоятельно рекомендуем использовать другое имя пользователя. + ::: + +3. Весь процесс показан на гифке ниже: + + ![Создание нового пользователя](./ch04-img04-adduser-full.gif) + +4. Второе, что нужно сделать, — это установить пакет `sudo` (`sudo` позволяет обычным пользователям временно получать права суперпользователя `root`, чтобы выполнять задачи, требующие повышенных привилегий). + + ```shell + apt update && apt install sudo + ``` + + Вы, наверное, заметили, что эта строка содержит две команды. Первая команда, `apt update`, уже знакома вам — она обновляет информацию о доступных пакетах. Вторая команда, `apt install`, используется для установки программного обеспечения. В данном случае мы обновляем информацию о пакетах и устанавливаем последнюю версию `sudo`. Символы `&&` используются для объединения нескольких команд в одну строку. + +5. Третье, что нужно сделать, — это добавить пользователя `vpsadmin` в группу `sudo`, чтобы он мог использовать команду `sudo`. + + ```shell + visudo + ``` + + Добавьте следующую строку в раздел `User Privilege Specification`: `vpsadmin ALL=(ALL) NOPASSWD: ALL`. + + ::: warning + Обратите внимание на опцию `NOPASSWD`. Она означает, что пользователю `vpsadmin` не нужно будет вводить пароль при использовании команды `sudo`. **Это противоречит общепринятым рекомендациям по безопасности.** Однако я рекомендую сделать именно так, потому что многие новички используют учётную запись `root` именно потому, что им не нужно каждый раз вводить пароль. Я считаю, что **риски от использования учётной записи `root` гораздо выше**, чем риски от использования `sudo` без пароля. + + Если вы всё же хотите, чтобы при каждом использовании `sudo` запрашивался пароль, используйте следующую строку: `vpsadmin ALL=(ALL:ALL) ALL`. + ::: + +6. Весь процесс показан на гифке ниже: + + ![Настройка sudo](./ch04-img05-sudo-full.gif) + +## 4.6 Запрет удалённого подключения по SSH для пользователя root + +1. Вы уже немного освоились в Linux, поэтому попробуйте сами догадаться, что нужно сделать в первую очередь. Правильно, нужно снова открыть файл настроек SSH-сервера (`/etc/ssh/sshd_config`) в редакторе `nano`. Не помните, как это сделать? Вернитесь и перечитайте предыдущие разделы! ... Правильный ответ: + + ```shell + nano /etc/ssh/sshd_config + ``` + +2. Найдите строку `PermitRootLogin Yes` и замените `yes` на `no`. Помните, как это сделать? ... Правильный ответ: + + - Нажмите `Ctrl+W`, чтобы открыть поиск, введите `PermitRootLogin` и нажмите Enter. + - Замените `yes` на `no`. + +3. Сохраните изменения и выйдите из редактора. Помните, как это сделать? ... Правильный ответ: + + - `Ctrl+O` — сохранить, `Enter` — подтвердить сохранение. + - `Ctrl+X` — выйти. + +4. Перезапустите SSH-сервер, чтобы изменения вступили в силу. Помните... Ладно, вот правильный ответ: + + ```shell + systemctl restart ssh + ``` + +5. Весь процесс показан на гифке ниже: + + ![Запрет удалённого подключения для root](./ch04-img06-ssh-no-root-full.gif) + +6. Теперь при попытке подключения по SSH с помощью PuTTY под пользователем `root` вы получите ошибку. Используйте имя пользователя `vpsadmin` для подключения. Для удобства вы можете указать `vpsadmin` в качестве имени пользователя по умолчанию в настройках PuTTY (не забудьте сохранить сеанс!). + + ![Имя пользователя по умолчанию в PuTTY](./ch04-img07-putty-default-user.png) + +## 4.7 Настройка аутентификации по SSH-ключам и запрет аутентификации по паролю + +Перейдём к третьему шагу — защите от подбора пароля. + +Как мы уже говорили, хакеры не будут подбирать ваш пароль вручную. Они используют специальные программы и базы данных с миллионами скомпрометированных паролей. Если вы не используете случайно сгенерированный пароль достаточной длины (например, с помощью менеджера паролей, такого как 1Password или Keychain в macOS), ваш пароль могут подобрать. + +Конечно, можно использовать длинный и сложный пароль, но такой пароль сложно запомнить и легко ввести неправильно. Чтобы решить эту проблему, можно вообще отказаться от аутентификации по паролю и использовать более безопасный способ — аутентификацию по SSH-ключам. + +**Аутентификация по SSH-ключам** основана на использовании пары связанных ключей — **публичного** и **приватного**. Публичный ключ помещается на сервер, а приватный ключ хранится у вас на компьютере. При подключении по SSH клиент предъявляет серверу публичный ключ, а сервер, в свою очередь, шифрует с помощью него случайное сообщение и отправляет клиенту. Клиент расшифровывает сообщение с помощью приватного ключа и отправляет результат обратно на сервер. Если результат совпадает с ожидаемым, аутентификация считается успешной (таким образом, вам не нужно запоминать и вводить сложный пароль, достаточно лишь защитить свой приватный ключ от кражи). + +::: warning +В этой статье мы рассмотрим использование **RSA-ключей**, поскольку они поддерживаются практически всеми SSH-клиентами и до сих пор считаются достаточно надёжными. Однако это не единственный вариант. + +Существуют и другие алгоритмы: + +- **DSA**: этот алгоритм считается небезопасным, поэтому использовать его не рекомендуется. +- **ECDSA**: этот алгоритм обеспечивает высокий уровень безопасности при меньшем размере ключа, однако существуют подозрения, что в нём есть уязвимости, которые могут быть использованы АНБ. Поэтому, если вы храните на своём сервере что-то действительно важное, лучше не использовать этот алгоритм. +- **Ed25519**: этот алгоритм похож на ECDSA, но считается более безопасным, поскольку его код открыт и доступен для изучения. + +Поэтому, если ваше оборудование и программное обеспечение поддерживают этот алгоритм, рекомендуется использовать именно его. +::: + +Итак, давайте настроим аутентификацию по SSH-ключам. + +1. Запустите программу **PuTTYgen** (генератор ключей PuTTY). Она находится в меню «Пуск» --> «Все программы» --> «PuTTY (64-bit)» --> «PuTTYgen». + + 1. Нажмите кнопку **Generate**, чтобы сгенерировать ключи (поводите курсором мыши по пустому пространству окна, чтобы добавить энтропии). + + ![Генерация ключей](./ch04-img08-puttygen-save.png) + + ::: warning + На скриншоте показан пример генерации 2048-битного RSA-ключа. Однако для достижения уровня безопасности, со comparableного с 256-битным ключом ECDSA/Ed25519, вам нужно сгенерировать 3072-битный RSA-ключ (т.е. ввести значение `3072` в поле «Number of bits in a generated key»). + ::: + + 2. Вы можете установить пароль для защиты приватного ключа. + 3. Нажмите кнопку **Save public key**, чтобы сохранить публичный ключ в файл `id_rsa.pub`. + 4. Нажмите кнопку **Save private key**, чтобы сохранить приватный ключ в файл `id_rsa` (приватные ключи PuTTY имеют расширение `.ppk`). + 5. **Важно!** Скопируйте содержимое поля, выделенного красной рамкой (не забудьте прокрутить текст до конца!), и сохраните его в файл `authorized_keys`. (Если вы будете использовать для этого VSCode, файл будет сохранён с расширением `.txt` — `authorized_keys.txt`. Это нормально, позже мы переименуем файл). + + ![Сохранение ключей](./ch04-img09-puttygen-save-keys.png) + +2. Скопируйте публичный ключ на VPS в домашний каталог пользователя `vpsadmin`. + + 1. Для этого используйте программу **WinSCP**, которую мы установили ранее. + 2. Скачайте и установите WinSCP с [официального сайта](https://winscp.net/eng/index.php). При первом запуске программа предложит импортировать настройки из PuTTY. Согласитесь на импорт. + + ![Импорт настроек из PuTTY](./ch04-img10-winscp-import-session.png) + + 3. Если вам не предложили импортировать настройки или вы уже настроили WinSCP ранее, настройте подключение к VPS, как показано на скриншоте ниже. + + ![Настройки подключения в WinSCP](./ch04-img11-winscp-ui.png) + + 4. В левой части окна WinSCP отображаются файлы и папки на вашем компьютере, а в правой — на VPS. Перейдите в папку с сохранёнными ключами на вашем компьютере. + + 5. В правой части окна (VPS) по умолчанию открыт каталог `/home/vpsadmin/`. Нажмите кнопку «Show hidden files» (скрытые файлы), чтобы отобразить скрытые файлы и папки. + + ![Локальные и удалённые папки](./ch04-img12-winscp-locations.png) + + 6. щёлкните правой кнопкой мыши в правой части окна (VPS) и создайте новую папку с именем `.ssh` (обратите внимание на точку в начале имени). + + ![Создание папки для ключей на VPS](./ch04-img13-winscp-newfolder-key.png) + + 7. Скопируйте файл `authorized_keys` в папку `.ssh`. + + ![Копирование authorized_keys](./ch04-img14-winscp-upload-key.png) + + 8. При копировании файла `authorized_keys.txt` переименуйте его в `authorized_keys` (удалите расширение `.txt`). + + ![Удаление расширения файла](./ch04-img15-winscp-rename-key.png) + + 9. Весь процесс показан на гифке ниже: + + ![Работа с WinSCP](./ch04-img16-winscp-full.gif) + +3. Настройте SSH-сервер на использование ключей и отключите аутентификацию по паролю. + + 1. Базовые команды Linux: + | Номер | Команда | Описание | + | :----: | :------ | :--------------------------------------- | + | `cmd-08` | `sudo` | Выполнение команды от имени root | + | `cmd-09` | `chmod` | Изменение прав доступа к файлам и папкам | + + 2. Подключитесь к VPS по SSH (PuTTY). + + 3. Измените права доступа к файлу `authorized_keys` на `600` (только владелец может читать и записывать файл). + + ```shell + chmod 600 ~/.ssh/authorized_keys + ``` + + 4. Откройте файл настроек SSH. Мы уже делали это раньше, но теперь мы работаем не под пользователем `root`, а под пользователем `vpsadmin`. У этого пользователя нет прав на редактирование системных файлов, поэтому нужно использовать команду `sudo`: + + ```shell + sudo nano /etc/ssh/sshd_config + ``` + + 5. Найдите (`Ctrl+W`) строку `PasswordAuthentication` и измените значение на `no`. + + 6. Найдите (`Ctrl+W`) строку `PubkeyAuthentication` и измените значение на `yes`. Сохраните изменения (`Ctrl+O`) и выйдите из редактора (`Ctrl+X`). + + 7. Перезапустите SSH-сервер (не забудьте, что теперь вам нужно использовать команду `sudo`): + + ```shell + sudo systemctl restart ssh + ``` + + 8. Весь процесс показан на гифке ниже: + + ![Включение аутентификации по ключам и отключение аутентификации по паролю](./ch04-img17-rsa-login-full.gif) + +4. Теперь, когда на сервере настроен публичный ключ, нужно указать PuTTY путь к приватному ключу (не забудьте сохранить сеанс!). + + ![Путь к приватному ключу в PuTTY](./ch04-img18-putty-privatekey-location.png) + +5. Готово! Теперь у вас настроена аутентификация по SSH-ключам, а аутентификация по паролю отключена. Кроме того, вы сохранили имя пользователя и путь к приватному ключу в сеансе PuTTY. Теперь для подключения к VPS достаточно будет выбрать сохранённый сеанс `VPS-SERVER` и нажать кнопку «Open». + + Если вы установили пароль для защиты приватного ключа, вам нужно будет ввести его при подключении, как показано на скриншоте ниже: + + ![Парольная фраза для приватного ключа](./ch04-img19-putty-privatekey-passphrase.png) + +6. Не забудьте настроить аутентификацию по ключам и в WinSCP. В противном случае вы не сможете подключаться к серверу для передачи файлов: + + ![Путь к приватному ключу в WinSCP](./ch04-img20-winscp-privatekey-location.png) + +::: warning +Теперь для подключения к вашему серверу по SSH требуется авторизация по ключам. Мы рассмотрели настройку PuTTY и WinSCP, но существует множество других программ, которые также используют SSH. Настройте их самостоятельно при необходимости. +::: + +## 4.8 Ваш прогресс + +На этом этапе ваш VPS защищён базовыми мерами безопасности. Конечно, это не панацея, но большинство автоматизированных атак вам уже не страшны! + +Теперь у нас есть надёжный фундамент, и в следующей главе мы можем приступить к установке и настройке необходимых компонентов Xray (а именно, веб-сервера и SSL-сертификата). + +> ⬛⬛⬛⬛⬜⬜⬜⬜ 50% + + + + + diff --git a/docs/ru/document/level-0/ch05-img01-nginx-default-running.png b/docs/ru/document/level-0/ch05-img01-nginx-default-running.png new file mode 100644 index 000000000..bdefe66af Binary files /dev/null and b/docs/ru/document/level-0/ch05-img01-nginx-default-running.png differ diff --git a/docs/ru/document/level-0/ch05-img02-nginx-conf-full.gif b/docs/ru/document/level-0/ch05-img02-nginx-conf-full.gif new file mode 100644 index 000000000..4cac56d69 Binary files /dev/null and b/docs/ru/document/level-0/ch05-img02-nginx-conf-full.gif differ diff --git a/docs/ru/document/level-0/ch05-img03-nginx-http-running.png b/docs/ru/document/level-0/ch05-img03-nginx-http-running.png new file mode 100644 index 000000000..13a8de95e Binary files /dev/null and b/docs/ru/document/level-0/ch05-img03-nginx-http-running.png differ diff --git a/docs/ru/document/level-0/ch05-webpage.md b/docs/ru/document/level-0/ch05-webpage.md new file mode 100644 index 000000000..de1448823 --- /dev/null +++ b/docs/ru/document/level-0/ch05-webpage.md @@ -0,0 +1,195 @@ +# 【Глава 5】 Создание веб-сайта + +## 5.1 Зачем нужен веб-сайт? + +Начинающие пользователи могут задаться вопросом: зачем создавать веб-сайт для обхода блокировок? Я не программист, это же сложно? + +Начнём с первого вопроса. Веб-сайт нужен для того, чтобы: + +1. Получить действующий SSL-сертификат (это очень важно). +2. Обеспечить маскировку трафика (fallback) и защититься от атак, направленных на выявление VPN-серверов. +3. Создать сайт-прикрытие (например, блог, облачное хранилище, медиа-портал, игровой сайт), который будет отображаться при прямом доступе к серверу, делая использование VPN менее заметным. + +Теперь ответим на второй вопрос: + +1. В этой статье мы создадим максимально простой веб-сайт, состоящий из **одного HTML-файла** и работающий на веб-сервере **Nginx**, чтобы решить поставленные выше задачи. Это очень просто. +2. Этот веб-сайт не обязательно должен быть просто прикрытием. Вы можете развивать его и превратить в полноценный проект. Всё зависит от ваших желаний и возможностей. +3. Создание сайта-прикрытия и его продвижение — это отдельная большая тема, которая выходит за рамки этой статьи. Если вам интересно, вы можете найти информацию об этом в интернете. + +## 5.2 Подключение к VPS и установка Nginx + +1. В этом разделе мы будем использовать команды, которые уже были подробно рассмотрены ранее. Если вы что-то не понимаете, вернитесь и перечитайте предыдущие главы. + + ```shell + sudo apt update && sudo apt install nginx + ``` + +2. После завершения установки Nginx запустится автоматически. Откройте браузер на своём компьютере и введите адрес `http://100.200.300.400:80`. Если вы увидите страницу, как на скриншоте ниже, значит, Nginx работает. + + ![Стандартная страница Nginx](./ch05-img01-nginx-default-running.png) + +3. Если вы не видите страницу Nginx, возможно, вам нужно настроить Uncomplicated Firewall (UFW), стандартный брандмауэр в Debian, чтобы разрешить трафик на портах HTTP (80) и HTTPS (443). + + a. Чтобы проверить, введите: + ```shell + sudo ufw status + ``` + b. Если вывод команды такой, как показано ниже, это означает, что порты 80 и 443 закрыты. Выполните действия, описанные в пункте c. + ```shell + Status: active + To Action From + -- ------ ---- + 22/tcp ALLOW Anywhere + 22/tcp (v6) ALLOW Anywhere (v6) + ``` + c. Чтобы открыть порты 80 и 443 для Nginx в UFW, выполните команду: + ```shell + sudo ufw allow 'Nginx Full' + ``` + d. Снова проверьте статус UFW, выполнив команду из пункта a. Если вы видите вывод, как показано ниже, значит, трафик для Nginx разрешён, и вы должны увидеть стандартную страницу Nginx. + ```shell + Status: active + To Action From + -- ------ ---- + 22/tcp ALLOW Anywhere + Nginx Full ALLOW Anywhere + 22/tcp (v6) ALLOW Anywhere (v6) + Nginx Full (v6) ALLOW Anywhere (v6) + ``` + +## 5.3 Создание простой веб-страницы + +1. Базовые команды Linux: + | Номер | Команда | Описание | + | :----: | :-------- | :------------------------ | + | `cmd-10` | `mkdir` | Создание папки | + | `cmd-11` | `systemctl reload` | Перезагрузка службы | + +2. Важные файлы конфигурации Linux: + | Номер | Путь к файлу | Описание | + | :------ | :-------------------- | :------------------------- | + | `conf-02` | `/etc/nginx/nginx.conf` | Настройки Nginx | + +3. Создайте папку `/home/vpsadmin/www/webpage/` для вашего сайта и файл `index.html` внутри неё: + ```shell + mkdir -p ~/www/webpage/ && nano ~/www/webpage/index.html + ``` + +::: warning +Если вы используете имя пользователя, отличное от `vpsadmin`, обратите внимание на символ `~` в этой команде (это важно для 5-го шага): + +- Для пользователей, **отличных от `root`**: `~` эквивалентно `/home/имя_пользователя`. +- Для пользователя **`root`**: `~` эквивалентно `/root`. + ::: + +4. Скопируйте следующий код в файл `index.html` и сохраните его (`Ctrl+O`, `Enter`), затем выйдите из редактора (`Ctrl+X`): + + ```html + + + + Enter a title, displayed at the top of the window. + + + +

Enter the main heading, usually the same as the title.

+

Be bold in stating your key points. Put them in a list:

+
    +
  • The first item in your list
  • +
  • The second item; italicize key words
  • +
+

Improve your image by including an image.

+

+ A Great HTML Resource +

+

+ Add a link to your favorite + Web site. Break up your page + with a horizontal rule or two. +

+
+

+ Finally, link to another page in your own Web + site. +

+ +

© Wiley Publishing, 2011

+ + + ``` + +5. Дайте другим пользователям право на чтение этого файла: + + ```shell + chmod -R a+r . + ``` + +6. Отредактируйте файл `nginx.conf` и перезапустите Nginx, чтобы он открывал созданную нами страницу `index.html` при обращении к порту `80`. + + 1. Отредактируйте файл `nginx.conf`. + + ```shell + sudo nano /etc/nginx/nginx.conf + ``` + + 2. Добавьте следующий блок кода внутрь блока `http{}` и сохраните изменения (`Ctrl+O`, `Enter`), затем выйдите из редактора (`Ctrl+X`). (Не забудьте заменить доменное имя на ваше реальное доменное имя, включая поддомен). + + ``` + server { + listen 80; + server_name поддомен.ваш_домен.com; + root /home/vpsadmin/www/webpage; + index index.html; + } + ``` + + ::: warning Важно! + Как уже говорилось в 3-м шаге, убедитесь, что путь `/home/vpsadmin/www/webpage` соответствует реальному пути к вашему файлу. + ::: + + 3. Перезагрузите Nginx, чтобы применить изменения. + + ```shell + sudo systemctl reload nginx + ``` + + 4. Весь процесс настройки показан на гифке ниже: + + ![Настройка веб-сайта](./ch05-img02-nginx-conf-full.gif) + + 5. Теперь, если вы откроете в браузере адрес `http://поддомен.ваш_домен.com`, вы должны увидеть созданную нами страницу: + + ![Веб-сайт работает](./ch05-img03-nginx-http-running.png) + +## 5.4 Распространённые ошибки + +Вообще, если вы внимательно следовали инструкциям, ошибок быть не должно. Однако на этом этапе многие пользователи сталкиваются с проблемами. В чём же дело? Ответ прост: **невнимательность**. Здесь возможны только две ошибки, и обе они связаны с невнимательностью. + +**Ошибки:** + +- Путь `/home/vpsadmin/www/webpage` в файле `nginx.conf` не соответствует реальному пути к файлу `index.html`. Nginx не может найти файл. +- Путь указан верно, но у Nginx нет прав на чтение файла. + +**Причины:** + +- Вы работаете не под пользователем `root`, но скопировали команды из статьи без изменений (как будто списали домашнее задание вместе с именем одноклассника). +- Вы работаете под пользователем `root`. + +Если у вас возникли проблемы, вернитесь к разделу 5.3 и внимательно перечитайте пункты 3 и 6.2. + +::: warning +В предыдущих главах мы много говорили о важности использования пользователя, отличного от `root`, и вся статья написана с учётом этого. Поэтому проблемы, связанные с использованием `root`, не рассматриваются в рамках этой статьи. + +Однако я уверен, что те, кто всё-таки работает под `root`, достаточно опытны и смогут решить эти проблемы самостоятельно. +::: + +## 5.5 Ваш прогресс + +Первый компонент Xray — веб-сайт — готов. Давайте перейдём ко второму компоненту — SSL-сертификату! + +> ⬛⬛⬛⬛⬛⬜⬜⬜ 62.5% \ No newline at end of file diff --git a/docs/ru/document/level-0/ch06-certificates.md b/docs/ru/document/level-0/ch06-certificates.md new file mode 100644 index 000000000..49b6f71b9 --- /dev/null +++ b/docs/ru/document/level-0/ch06-certificates.md @@ -0,0 +1,216 @@ +# 【Глава 6】 Управление сертификатами + +## 6.1 Получение SSL-сертификата + +Теперь нам нужно получить действующий SSL-сертификат для нашего доменного имени, чтобы веб-сайт работал по протоколу HTTPS. Это важнейший инструмент для обеспечения безопасности трафика при использовании современных VPN-сервисов, таких как Xray. + +::: warning +Не используйте самоподписанные сертификаты. Это ненамного упростит задачу, но создаст дополнительные риски (например, возможность атак типа «человек посередине»). +::: + +Мы будем использовать инструмент для управления сертификатами [`acme.sh`](https://github.com/acmesh-official/acme.sh). Он простой, лёгкий, эффективный и умеет автоматически обновлять сертификаты. + +Я уверен, что вы уже освоились с базовыми командами Linux, поэтому скриншоты с выводом команд, которые мы уже использовали ранее, будут опущены. Если вы забыли, как выполнять ту или иную команду, вернитесь и перечитайте предыдущие главы. + +## 6.2 Установка `acme.sh` + +1. Базовые команды Linux: + | Номер | Команда | Описание | + | :----: | :---------- | :---------------------------- | + | `cmd-12` | `wget` | Загрузка файла из интернета | + | `cmd-13` | `acme.sh` | Управление сертификатами | + +2. Запустите скрипт установки: + + ```shell + wget -O - https://get.acme.sh | sh + ``` + +3. Сделайте команду `acme.sh` доступной: + + ```shell + . .bashrc + ``` + +4. Включите автоматическое обновление `acme.sh`: + + ```shell + acme.sh --upgrade --auto-upgrade + ``` + +5. Весь процесс установки показан на гифке ниже: + + ![Установка acme.sh](./ch06-img01-acme-install.gif) + +## 6.3 Тестовый запрос сертификата + +Перед тем, как запросить настоящий сертификат, давайте сделаем тестовый запрос (`--issue --test`), чтобы убедиться, что всё настроено правильно. Это позволит избежать превышения лимита на количество запросов Let's Encrypt (например, не более 5 неудачных запросов в час для одного домена и одного аккаунта). + +1. Команда для тестового запроса сертификата (в этой статье мы будем использовать сертификаты **ECC**, поскольку на сегодняшний день нет причин не использовать их): + + ```shell + acme.sh --issue --server letsencrypt --test -d поддомен.ваш_домен.com -w /home/vpsadmin/www/webpage --keylength ec-256 + ``` + + ::: warning Пояснение + Главное преимущество **ECC-сертификатов** — это меньший размер ключа, что означает более высокий уровень безопасности при том же размере ключа, а также более высокую скорость шифрования и расшифровки. Например, ECC-256 обеспечивает уровень безопасности, примерно соответствующий RSA-3072, так зачем же отказываться от ECC? Некоторые утверждают, что рукопожатие TLS с ECC-сертификатами происходит заметно быстрее. Я считаю, что это преувеличение. RSA-рукопожатие и так достаточно быстрое, а разница в скорости, если она и есть, составляет всего несколько миллисекунд и практически незаметна. + + Конечно, если вам нужно обеспечить совместимость с очень старыми устройствами, то можно использовать и RSA-сертификат. + ::: + +2. В случае успеха вы увидите примерно такой вывод: + + ```log + [Wed 30 Dec 2022 04:25:12 AM EST] Using ACME_DIRECTORY: https://acme-staging-v02.api.letsencrypt.org/directory + [Wed 30 Dec 2022 04:25:13 AM EST] Using CA: https://acme-staging-v02.api.letsencrypt.org/directory + [Wed 30 Dec 2022 04:25:13 AM EST] Create account key ok. + [Wed 30 Dec 2022 04:25:13 AM EST] Registering account: https://acme-staging-v02.api.letsencrypt.org/directory + [Wed 30 Dec 2022 04:25:13 AM EST] Registered + [Wed 30 Dec 2022 04:25:13 AM EST] ACCOUNT_THUMBPRINT='CU6qmPKuRqhyTAIrF4swosR375194z_1ddUlWef8xDc' + [Wed 30 Dec 2022 04:25:13 AM EST] Creating domain key + [Wed 30 Dec 2022 04:25:13 AM EST] The domain key is here: /home/vpsadmin/.acme.sh/поддомен.ваш_домен.com_ecc/поддомен.ваш_домен.com.key + [Wed 30 Dec 2022 04:25:13 AM EST] Single domain='поддомен.ваш_домен.com' + [Wed 30 Dec 2022 04:25:13 AM EST] Getting domain auth token for each domain + [Wed 30 Dec 2022 04:25:14 AM EST] Getting webroot for domain='поддомен.ваш_домен.com' + [Wed 30 Dec 2022 04:25:14 AM EST] Verifying: поддомен.ваш_домен.com + [Wed 30 Dec 2022 04:25:23 AM EST] Pending + [Wed 30 Dec 2022 04:25:25 AM EST] Success + [Wed 30 Dec 2022 04:25:25 AM EST] Verify finished, start to sign. + [Wed 30 Dec 2022 04:25:25 AM EST] Lets finalize the order. + [Wed 30 Dec 2022 04:25:25 AM EST] Le_OrderFinalize='https://acme-staging-v02.api.letsencrypt.org/acme/finalize/490205995/7730242871' + [Wed 30 Dec 2022 04:25:25 AM EST] Downloading cert. + [Wed 30 Dec 2022 04:25:25 AM EST] Le_LinkCert='https://acme-staging-v02.api.letsencrypt.org/acme/cert/xujss5xt8i38waubafz2xujss5xt8i38waubz2' + [Wed 30 Dec 2022 15:21:52 AM EST] Cert success. + --BEGIN CERTIFICAT-- + sxlYqPvWreKgD5b8JyOQX0Yg2MLoRUoDyqVkd31PthIiwzdckoh5eD3JU7ysYBtN + cTFK4LGOfjqi8Ks87EVJdK9IaSAu7ZC6h5to0eqpJ5PLhaM3e6yJBbHmYA8w1Smp + wAb3tdoHZ9ttUIm9CrSzvDBt6BBT6GqYdDamMyCYBLooMyDEM4CUFsOzCRrEqqvC + 2mTTEmhvpojo5rhdTSJxibozyNWTGwoTj0v9pTUeQcGqLIzqi4DowjBHD5guwRid + SjAFnm6JT2xUQgWFm58A1gv1OhbH1TRPUUmtE1nFEN7YiSjI4xgxqAXT3CLD2EUb + wXlUrO6c75zSsQP4bRMzgOjJUqHtSb6IEqELzt4M7KzL5iCOruCChCo2DZxUwvVX + tOoaAyQJzCbTqE6aUqwiKi3gVyoxvDP9mI5JdRYzsDL6GVud7EHPnYeMl9ubLZAK + 0vg84mbMP3f6mYM4KRa1cqiyOIcQPT4AzGFYVv4sm049bZQg7sd0Bz9CaFvE7yDA + 1y17XlgCDnsjxl66bqI1vkENN9XT5xeFHONqc18b5fZEKSIvdX7iWPFWp1PyMPpG + 0pMCP1EymZNFxIMJLgbWqExwLWfPc5Ib3PjBaIqhXPnw6sT2MQSxXwDupq1UJVhV + 7E3hQRVlwI4CXi6WLHJMNvNRyyK87gCrLH1bKYsPeRVaz77poWBq49zwBCts6hPY + IeF4ltGXyANNIOPEi8vy138fRU4LYh81d8FjOtFfJZogMjwhfNvapqxPMsioPlmX + TnZu0n7setrVNUEfTMHWqPpDgk5MPrWLA4LapqaDfEX4pwnQJLMwMi6s94z165c0 + iMRSKA1yU5zqv8aNsDfPoY4OkSPWs4MaXgRRSLBsUfZ15DwQXPk76kegHIyxWvwF + tYw9HKR5QCMK66fa0z4aJoFVFLK0IIOGEZOanRFUCnkLUDd3QZ3YU8lEcrj7Uxos + haiRNICyC6UfsCJ94a8vcNyMosPv3xBLMp19WXgiFYqEFQkntkv1FLRI35fjeJmg + 0fmD9VG9bkzGPHihJgQLRlCHasGf6XrdfkSsODAyCUHUHJ0RzqF4YEZMcxDxzuQ2 + YO7bFwj7S3mUdVPZ6MPasjxdyBjJgEBMch2uy4AhmudXfEBQBye8W6ZI4ztZjLVV + FmP4SIuaNUmMe20TjR8b9NVC96AhxOanWT3mRROsdokpKQGTJvl27EHH8KuAbUOc + G6KtPy4wslNZNXWcBy9n63RcWak12r7kAIFn38tZxmlw2WUKoRSMAH64GcDTjRQd + Am65hBHzvGrj93wEuVNIebvNIsJOlng3HFjpIxVqKGMCIfWIKGDE3YzK3p4LbGZ6 + NZFQWYJLNVf2M9CCJfbEImPYgvctrxl39H6KVYPCw1SAdaj9NneUqmREOQkKoEB0 + x6PmNirbMscHhQPSC0JQaqUgaQFgba1ALmzRYAnYhNb0twkTxWbY7DBkAarxqMIp + yiLKcBFc5H7dgJCImo7us7aJeftC44uWkPIjw9AKH= + --END CERTIFICAT-- + [Wed 30 Dec 2022 15:21:52 AM EST] Your cert is in /home/vpsadmin/.acme.sh/поддомен.ваш_домен.com_ecc/поддомен.ваш_домен.com.cer + [Wed 30 Dec 2022 15:21:52 AM EST] Your cert key is in /home/vpsadmin/.acme.sh/поддомен.ваш_домен.com_ecc/поддомен.ваш_домен.com.key + [Wed 30 Dec 2022 15:21:52 AM EST] The intermediate CA cert is in /home/vpsadmin/.acme.sh/поддомен.ваш_домен.com_ecc/ca.cer + [Wed 30 Dec 2022 15:21:52 AM EST] And the full chain certs is there: /home/vpsadmin/.acme.sh/поддомен.ваш_домен.com_ecc/fullchain.cer + ``` + +3. Обратите внимание, что мы запросили тестовый сертификат, который нельзя использовать в реальной среде. Он нужен только для проверки корректности настроек. Если вы посмотрите на вывод команды, то увидите, что сертификат был выпущен сервером `https://acme-staging-v02.api.letsencrypt.org`. Слово `staging` означает, что это тестовый сервер Let's Encrypt. + +4. Если на этом этапе возникли ошибки, выполните следующую команду, чтобы увидеть подробную информацию о процессе запроса сертификата: + + ```shell + acme.sh --issue --server letsencrypt --test -d поддомен.ваш_домен.com -w /home/vpsadmin/www/webpage --keylength ec-256 --debug + ``` + + Мы просто добавили параметр `--debug` в конец команды. + +5. Если тестовый запрос выполнен успешно, можно переходить к запросу настоящего сертификата (тестовый сертификат удалять не нужно, он будет автоматически перезаписан настоящим сертификатом). + +## 6.4 Запрос настоящего сертификата + +1. Команда для запроса настоящего сертификата (мы просто убираем параметр `--test` и добавляем параметр `--force`): + + ```shell + acme.sh --set-default-ca --server letsencrypt + ``` + + ```shell + acme.sh --issue -d поддомен.ваш_домен.com -w /home/vpsadmin/www/webpage --keylength ec-256 --force + ``` + + ::: warning Пояснение + Параметр `--force` используется для принудительного обновления сертификата до истечения срока действия старого. В предыдущем шаге мы получили тестовый сертификат, который всё ещё действителен. Поэтому нам нужно использовать этот параметр. + ::: + +2. В случае успеха вы увидите примерно такой же вывод, как и в предыдущем шаге: + + ```log + vpsadmin@vps-server:~$ acme.sh --issue -d поддомен.ваш_домен.com -w /home/vpsadmin/www/webpage --keylength ec-256 + [Wed 30 Dec 2022 15:22:51 AM EST] Using CA: https://acme-v02.api.letsencrypt.org/directory + [Wed 30 Dec 2022 15:22:51 AM EST] Creating domain key + [Wed 30 Dec 2022 15:22:51 AM EST] The domain key is here: /home/vpsadmin/.acme.sh/поддомен.ваш_домен.com_ecc/поддомен.ваш_домен.com.key + [Wed 30 Dec 2022 15:22:51 AM EST] Single domain='поддомен.ваш_домен.com' + [Wed 30 Dec 2022 15:22:51 AM EST] Getting domain auth token for each domain + [Wed 30 Dec 2022 15:22:51 AM EST] Getting webroot for domain='поддомен.ваш_домен.com' + [Wed 30 Dec 2022 15:22:51 AM EST] Verifying: поддомен.ваш_домен.com + [Wed 30 Dec 2022 15:22:51 AM EST] Pending + [Wed 30 Dec 2022 15:22:51 AM EST] Success + [Wed 30 Dec 2022 15:22:51 AM EST] Verify finished, start to sign. + [Wed 30 Dec 2022 15:22:51 AM EST] Lets finalize the order. + [Wed 30 Dec 2022 15:22:51 AM EST] Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/490205996/7730242872' + [Wed 30 Dec 2022 15:22:51 AM EST] Downloading cert. + [Wed 30 Dec 2022 15:22:51 AM EST] Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/vsxvk0oldnuobe51ayxz4dms62sk2dwmw9zhuw' + [Wed 30 Dec 2022 15:22:52 AM EST] Cert success. + --BEGIN CERTIFICAT-- + sxlYqPvWreKgD5b8JyOQX0Yg2MLoRUoDyqVkd31PthIiwzdckoh5eD3JU7ysYBtN + cTFK4LGOfjqi8Ks87EVJdK9IaSAu7ZC6h5to0eqpJ5PLhaM3e6yJBbHmYA8w1Smp + wAb3tdoHZ9ttUIm9CrSzvDBt6BBT6GqYdDamMyCYBLooMyDEM4CUFsOzCRrEqqvC + 2mTTEmhvpojo5rhdTSJxibozyNWTGwoTj0v9pTUeQcGqLIzqi4DowjBHD5guwRid + SjAFnm6JT2xUQgWFm58A1gv1OhbH1TRPUUmtE1nFEN7YiSjI4xgxqAXT3CLD2EUb + wXlUrO6c75zSsQP4bRMzgOjJUqHtSb6IEqELzt4M7KzL5iCOruCChCo2DZxUwvVX + tOoaAyQJzCbTqE6aUqwiKi3gVyoxvDP9mI5JdRYzsDL6GVud7EHPnYeMl9ubLZAK + 0vg84mbMP3f6mYM4KRa1cqiyOIcQPT4AzGFYVv4sm049bZQg7sd0Bz9CaFvE7yDA + 1y17XlgCDnsjxl66bqI1vkENN9XT5xeFHONqc18b5fZEKSIvdX7iWPFWp1PyMPpG + 0pMCP1EymZNFxIMJLgbWqExwLWfPc5Ib3PjBaIqhXPnw6sT2MQSxXwDupq1UJVhV + 7E3hQRVlwI4CXi6WLHJMNvNRyyK87gCrLH1bKYsPeRVaz77poWBq49zwBCts6hPY + IeF4ltGXyANNIOPEi8vy138fRU4LYh81d8FjOtFfJZogMjwhfNvapqxPMsioPlmX + TnZu0n7setrVNUEfTMHWqPpDgk5MPrWLA4LapqaDfEX4pwnQJLMwMi6s94z165c0 + iMRSKA1yU5zqv8aNsDfPoY4OkSPWs4MaXgRRSLBsUfZ15DwQXPk76kegHIyxWvwF + tYw9HKR5QCMK66fa0z4aJoFVFLK0IIOGEZOanRFUCnkLUDd3QZ3YU8lEcrj7Uxos + haiRNICyC6UfsCJ94a8vcNyMosPv3xBLMp19WXgiFYqEFQkntkv1FLRI35fjeJmg + 0fmD9VG9bkzGPHihJgQLRlCHasGf6XrdfkSsODAyCUHUHJ0RzqF4YEZMcxDxzuQ2 + YO7bFwj7S3mUdVPZ6MPasjxdyBjJgEBMch2uy4AhmudXfEBQBye8W6ZI4ztZjLVV + FmP4SIuaNUmMe20TjR8b9NVC96AhxOanWT3mRROsdokpKQGTJvl27EHH8KuAbUOc + G6KtPy4wslNZNXWcBy9n63RcWak12r7kAIFn38tZxmlw2WUKoRSMAH64GcDTjRQd + Am65hBHzvGrj93wEuVNIebvNIsJOlng3HFjpIxVqKGMCIfWIKGDE3YzK3p4LbGZ6 + NZFQWYJLNVf2M9CCJfbEImPYgvctrxl39H6KVYPCw1SAdaj9NneUqmREOQkKoEB0 + x6PmNirbMscHhQPSC0JQaqUgaQFgba1ALmzRYAnYhNb0twkTxWbY7DBkAarxqMIp + yiLKcBFc5H7dgJCImo7us7aJeftC44uWkPM= + --END CERTIFICAT-- + [Wed 30 Dec 2022 15:22:52 AM EST] Your cert is in /home/vpsadmin/.acme.sh/поддомен.ваш_домен.com_ecc/поддомен.ваш_домен.com.cer + [Wed 30 Dec 2022 15:22:52 AM EST] Your cert key is in /home/vpsadmin/.acme.sh/поддомен.ваш_домен.com_ecc/поддомен.ваш_домен.com.key + [Wed 30 Dec 2022 15:22:52 AM EST] The intermediate CA cert is in /home/vpsadmin/.acme.sh/поддомен.ваш_домен.com_ecc/ca.cer + [Wed 30 Dec 2022 15:22:52 AM EST] And the full chain certs is there: /home/vpsadmin/.acme.sh/поддомен.ваш_домен.com_ecc/fullchain.cer + ``` + +3. Обратите внимание, что теперь сертификат выдан сервером `https://acme-v02.api.letsencrypt.org` (без слова `staging`), т.е. это настоящий, действующий сертификат. + +## 6.5 Установка сертификата + +1. После того, как сертификат получен, его нужно установить в определённое место и указать путь к нему в файле конфигурации: + + ```shell + vpsadmin@vps-server:~$ acme.sh --installcert -d поддомен.ваш_домен.com --cert-file /путь/к/папке/cert.crt --key-file /путь/к/папке/cert.key --fullchain-file /путь/к/папке/fullchain.crt --ecc + [Mon 14 Feb 2022 03:00:25 PM CST] Installing cert to: /etc/xray/cert/cert.crt + [Mon 14 Feb 2022 03:00:25 PM CST] Installing key to: /etc/xray/cert/cert.key + [Mon 14 Feb 2022 03:00:25 PM CST] Installing full chain to: /etc/xray/cert/fullchain.crt + ``` + +## 6.6 Ваш прогресс + +Наконец-то все необходимые компоненты Xray готовы! Мы подошли к самому интересному — установке и настройке самого Xray! + +> ⬛⬛⬛⬛⬛⬛⬜⬜ 75% + + + + diff --git a/docs/ru/document/level-0/ch06-img01-acme-install.gif b/docs/ru/document/level-0/ch06-img01-acme-install.gif new file mode 100644 index 000000000..54d1223f7 Binary files /dev/null and b/docs/ru/document/level-0/ch06-img01-acme-install.gif differ diff --git a/docs/ru/document/level-0/ch07-img01-xray-install.gif b/docs/ru/document/level-0/ch07-img01-xray-install.gif new file mode 100644 index 000000000..3138c3bd8 Binary files /dev/null and b/docs/ru/document/level-0/ch07-img01-xray-install.gif differ diff --git a/docs/ru/document/level-0/ch07-img02-xray-cert-install.png b/docs/ru/document/level-0/ch07-img02-xray-cert-install.png new file mode 100644 index 000000000..9ef2f6987 Binary files /dev/null and b/docs/ru/document/level-0/ch07-img02-xray-cert-install.png differ diff --git a/docs/ru/document/level-0/ch07-img03-crontab-cert-renew.gif b/docs/ru/document/level-0/ch07-img03-crontab-cert-renew.gif new file mode 100644 index 000000000..4939758d5 Binary files /dev/null and b/docs/ru/document/level-0/ch07-img03-crontab-cert-renew.gif differ diff --git a/docs/ru/document/level-0/ch07-img04-xray-log-and-config.gif b/docs/ru/document/level-0/ch07-img04-xray-log-and-config.gif new file mode 100644 index 000000000..5ecfa1219 Binary files /dev/null and b/docs/ru/document/level-0/ch07-img04-xray-log-and-config.gif differ diff --git a/docs/ru/document/level-0/ch07-img05-xray-start-and-status.gif b/docs/ru/document/level-0/ch07-img05-xray-start-and-status.gif new file mode 100644 index 000000000..aaed46ba7 Binary files /dev/null and b/docs/ru/document/level-0/ch07-img05-xray-start-and-status.gif differ diff --git a/docs/ru/document/level-0/ch07-img06-bbr-proper.gif b/docs/ru/document/level-0/ch07-img06-bbr-proper.gif new file mode 100644 index 000000000..306575750 Binary files /dev/null and b/docs/ru/document/level-0/ch07-img06-bbr-proper.gif differ diff --git a/docs/ru/document/level-0/ch07-img07-http-to-https.gif b/docs/ru/document/level-0/ch07-img07-http-to-https.gif new file mode 100644 index 000000000..a418d3c7b Binary files /dev/null and b/docs/ru/document/level-0/ch07-img07-http-to-https.gif differ diff --git a/docs/ru/document/level-0/ch07-img08-http-to-https-check.png b/docs/ru/document/level-0/ch07-img08-http-to-https-check.png new file mode 100644 index 000000000..d4d9d49b2 Binary files /dev/null and b/docs/ru/document/level-0/ch07-img08-http-to-https-check.png differ diff --git a/docs/ru/document/level-0/ch07-xray-server.md b/docs/ru/document/level-0/ch07-xray-server.md new file mode 100644 index 000000000..4e33cb62f --- /dev/null +++ b/docs/ru/document/level-0/ch07-xray-server.md @@ -0,0 +1,574 @@ +# 【Глава 7】Настройка Xray на сервере + +## 7.1 Познать многое, усвоить нужное; копить постепенно, тратить осмотрительно + +Во время написания этого руководства один знакомый в шутку заметил: "Твое руководство уже 6 глав публикуется, а до Xray всё никак не дойдёт. Кто не знает, подумает, что это руководство "Создание сайта с нуля". (И ведь не поспоришь! 😂) + +На самом деле такая структура — результат моего осознанного решения. Ведь только заложив прочный фундамент, можно в дальнейшем двигаться вперёд семимильными шагами. Я видел в чатах много новичков, которые не могут правильно использовать даже `nano`, не говоря уже о `WinSCP`. Из-за этого их `config.json`, написанные вручную на удалённом сервере, пестрят ошибками, а поиск и исправление этих ошибок превращается в мучение. + +::: warning +Пройдя первые 6 глав, мы вместе с вами преодолели несколько важных этапов: освоили базовые операции Linux, научились удалённо управлять VPS, разобрались с установкой веб-сервера, управлением доменными именами, получением SSL-сертификатов. Оглядываясь назад, разве всё это кажется таким уж сложным? Теперь, имея такой солидный багаж знаний, мы подойдём к установке и настройке Xray с чувством лёгкости и уверенности, ведь всё уже готово! +::: + +Дальнейшие шаги предельно просты: + +1. Установка +2. Настройка (например, установка TLS-сертификата, настройка `config.json`) +3. Запуск +4. Оптимизация (например, обновление ядра, включение `bbr`, автоматическое перенаправление `http`-запросов на `https` и т. д.) + +## 7.2 Установка Xray + +Xray основан на проекте с открытым исходным кодом [xray-core](https://github.com/XTLS/Xray-core) (лицензия `MPL 2.0`). Запущенный на сервере, скомпилированный бинарный файл этого проекта, работает как серверная часть Xray; запущенный на локальном компьютере, он становится клиентской частью. Основное различие заключается в **конфигурации**. + +Для установки воспользуемся официальным скриптом. Он предлагает несколько вариантов установки. Вы можете ознакомиться с ними в [репозитории скриптов](https://github.com/XTLS/Xray-install). **В данном руководстве мы будем использовать установку от имени непривилегированного пользователя**. + +На момент написания руководства в скрипте есть небольшая ошибка при установке от имени непривилегированного пользователя, поэтому мы выполним эти шаги вручную. Заодно рассмотрим команду удаления файлов в Linux. + +1. Базовые команды Linux: + + | Номер | Команда | Описание | + | :----: | :--------: | :-------------- | + | `cmd-14` | `rm` | Удаление файлов | + +2. Скачиваем установочный скрипт: + + ```shell + wget https://github.com/XTLS/Xray-install/raw/main/install-release.sh + ``` + +3. Запускаем установку: + + ```shell + sudo bash install-release.sh + ``` + +4. После завершения установки удаляем скрипт: + + ```shell + rm ~/install-release.sh + ``` + + ::: warning + При использовании команды `rm` для удаления файлов по умолчанию подразумевается удаление файлов в текущей директории. Однако **я всё же указал полный путь**: `~/install-release.sh`. Это моя привычка, которая повышает безопасность при использовании `rm`. Думаю, вы слышали истории про "программиста, который удалил базу данных и сбежал". 😉 + ::: + +5. Весь процесс показан на гифке: + + ![Процесс установки Xray на сервер](./ch07-img01-xray-install.gif) + +## 7.3 Установка TLS-сертификата для Xray + +Хотя мы уже получили TLS-сертификат, согласно [официальной документации `acme.sh`](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E#3-copy%E5%AE%89%E8%A3%85-%E8%AF%81%E4%B9%A6), не рекомендуется использовать полученные файлы сертификата напрямую. Правильный способ — использовать команду `--install-cert` для установки сертификата для нужного приложения. Давайте установим сертификат для `xray-core`. + +1. Чтобы избежать проблем с правами доступа при работе от имени непривилегированного пользователя, создадим папку для сертификатов в домашней директории пользователя `vpsadmin`: + + ```shell + mkdir ~/xray_cert + ``` + +2. Используем команду `--install-cert` из `acme.sh` для установки (копирования) файлов сертификата: + + ```shell + acme.sh --install-cert -d sub.yourdomain.com --ecc \ + --fullchain-file ~/xray_cert/xray.crt \ + --key-file ~/xray_cert/xray.key + ``` + +3. По умолчанию файл `xray.key` недоступен для чтения другим пользователям, поэтому нужно выдать права на чтение: + + ```shell + chmod +r ~/xray_cert/xray.key + ``` + +4. Процесс довольно простой, поэтому обойдёмся без гифки: + + ![Установка сертификата для Xray](./ch07-img02-xray-cert-install.png) + +5. `acme.sh` проверяет срок действия сертификата каждые 60 дней и автоматически обновляет его при необходимости. Однако, насколько мне известно, он не устанавливает новый сертификат для `xray-core` автоматически. Поэтому нам нужно добавить автоматическое задание cron, которое будет делать это за нас. + + 1. Базовые команды Linux: + + | Номер | Команда | Описание | + | :----: | :----------: | :------------------------------------ | + | `cmd-15` | `crontab -e` | Редактирование crontab текущего пользователя | + + 2. Создаём файл скрипта (`xray-cert-renew.sh`): + + ```shell + nano ~/xray_cert/xray-cert-renew.sh + ``` + + 3. Копируем в него следующий код, заменив `sub.yourdomain.com` на своё доменное имя, и сохраняем файл: + + ```bash + #!/bin/bash + + /home/vpsadmin/.acme.sh/acme.sh --install-cert -d sub.yourdomain.com --ecc --fullchain-file /home/vpsadmin/xray_cert/xray.crt --key-file /home/vpsadmin/xray_cert/xray.key + echo "Xray Certificates Renewed" + + chmod +r /home/vpsadmin/xray_cert/xray.key + echo "Read Permission Granted for Private Key" + + sudo systemctl restart xray + echo "Xray Restarted" + ``` + + ::: warning + Как заметили пользователи, `acme.sh` имеет команду `reloadcmd`, которая позволяет выполнить произвольную команду после обновления сертификата. Таким образом, можно настроить автоматическую установку сертификата для `Xray`. Однако, поскольку `crontab` — очень полезный и часто используемый инструмент в Linux, в данном руководстве мы будем использовать его для обновления сертификата `Xray`. (Подробнее о команде `reloadcmd` можно прочитать в [документации `acme.sh`](https://github.com/acmesh-official/acme.sh)). + + Кроме того, в момент записи гифки в скрипт не была добавлена команда перезапуска `Xray`, поскольку в планах разработчиков `Xray` — поддержка **горячего обновления сертификатов**. Это означает, что `Xray` сможет автоматически обнаруживать обновление сертификата и перезагружать его без необходимости перезапуска. После добавления этой функции я обновлю конфигурацию `config.json`, включив эту настройку, и удалю команду перезапуска из скрипта. + ::: + + 4. Делаем скрипт исполняемым: + + ```shell + chmod +x ~/xray_cert/xray-cert-renew.sh + ``` + + 5. Запускаем `crontab -e`, чтобы добавить задание на автоматическое выполнение `xray-cert-renew.sh` каждый месяц (не используйте `sudo`, так как мы добавляем задание для пользователя `vpsadmin`. При первом запуске `crontab` вам будет предложено выбрать редактор. Выбираем знакомый нам `nano`!): + + ```shell + crontab -e + ``` + + 6. Добавляем следующую строку в конец файла и сохраняем его: + + ``` + # 1:00am, 1st day each month, run `xray-cert-renew.sh` + 0 1 1 * * bash /home/vpsadmin/xray_cert/xray-cert-renew.sh + ``` + + 7. Весь процесс показан на гифке: + + ![Автоматическая установка сертификата для Xray каждый месяц](./ch07-img03-crontab-cert-renew.gif) + +## 7.4 Настройка Xray + +Для начала можете ознакомиться с [примерами конфигурации VLESS](https://github.com/XTLS/Xray-examples). В этом руководстве мы будем использовать официальный пример для настройки максимально простого варианта: **один входящий прокси-сервер VLESS + перенаправление с порта 80**. Такая конфигурация обеспечит максимальную скорость и необходимый уровень безопасности в большинстве случаев. + +1. Генерируем валидный `UUID` и сохраняем его (грубо говоря, `UUID` — это уникальный идентификатор, который можно сравнить с отпечатком пальца): + + ```shell + xray uuid + ``` + +2. Создаём файлы и папки для логов: + + 1. Базовые команды Linux: + + | Номер | Команда | Описание | + | :----: | :-----: | :---------------------- | + | `cmd-16` | `touch` | Создание пустого файла | + + 2. Создаём папку для логов в домашней директории пользователя `vpsadmin`: + + ```shell + mkdir ~/xray_log + ``` + + 3. Создаём два файла для логов (логи доступа и логи ошибок): + + ```shell + touch ~/xray_log/access.log && touch ~/xray_log/error.log + ``` + + ::: warning + Это не стандартное расположение файлов логов `Xray`. Мы используем его, чтобы избежать проблем с правами доступа, которые могут возникнуть у новичков. Когда разберётесь, рекомендуется использовать стандартные пути: `/var/log/xray/access.log` и `/var/log/xray/error.log`. + ::: + + 4. По умолчанию Xray запускается от имени пользователя `nobody`, поэтому нужно дать другим пользователям права на запись в файлы логов (`*.log` — это все файлы с расширением `log`. Здесь проявляются преимущества использования командной строки): + + ```shell + chmod a+w ~/xray_log/*.log + ``` + +3. Создаём файл конфигурации `Xray` с помощью `nano`: + + ```shell + sudo nano /usr/local/etc/xray/config.json + ``` + +4. Копируем в него следующий код и вставляем сгенерированный ранее `UUID` в строку 61: `"id": "",` (должно получиться что-то вроде `"id": "uuiduuid-uuid-uuid-uuid-uuiduuiduuid"`). В этом файле конфигурации я добавил свои комментарии, чтобы вам было проще понять, за что отвечает каждый модуль. + + ```json + // ССЫЛКИ: + // https://github.com/XTLS/Xray-examples + // https://xtls.github.io/config/ + // Типичный конфигурационный файл, как для сервера, так и для клиента, состоит из 5 основных частей. Разберём их по полочкам: + // ┌─ 1_log Настройки логирования - что и куда писать в лог (чтобы было проще искать ошибки) + // ├─ 2_dns Настройки DNS - как выполнять DNS-запросы (защита от DNS-спуфинга, защита от слежки, предотвращение маршрутизации трафика на китайские серверы и т. д.) + // ├─ 3_routing Настройки маршрутизации - как обрабатывать трафик (фильтрация рекламы, разделение трафика для разных стран) + // ├─ 4_inbounds Настройки входящих подключений - какой трафик может поступать на Xray + // └─ 5_outbounds Настройки исходящих подключений - куда направлять трафик, исходящий от Xray + { + // 1_Настройки логирования + "log": { + "loglevel": "warning", // Уровень детализации логов: "none", "error", "warning", "info", "debug" (от меньшего к большему) + "access": "/home/vpsadmin/xray_log/access.log", // Файл для записи логов доступа + "error": "/home/vpsadmin/xray_log/error.log" // Файл для записи логов ошибок + }, + // 2_Настройки DNS + "dns": { + "servers": [ + "https+local://1.1.1.1/dns-query", // Используем DoH-сервер 1.1.1.1 в первую очередь. Это снижает скорость, но защищает от слежки со стороны интернет-провайдера + "localhost" + ] + }, + // 3_Настройки маршрутизации + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + // 3.1 Предотвращение проблем с локальной маршрутизацией: атаки на внутреннюю сеть, неправильная обработка локальных адресов и т. д. + { + "type": "field", + "ip": [ + "geoip:private" // Условие: адреса из списка "private" в файле geoip (локальные адреса) + ], + "outboundTag": "block" // Действие: отправить трафик на исходящее подключение "block" (блокировка) + }, + { + // 3.2 Предотвращение прямого подключения к китайским серверам + "type": "field", + "ip": ["geoip:cn"], + "outboundTag": "block" + }, + // 3.3 Блокировка рекламы + { + "type": "field", + "domain": [ + "geosite:category-ads-all" // Условие: домены из списка "category-ads-all" в файле geosite (рекламные домены) + ], + "outboundTag": "block" // Действие: отправить трафик на исходящее подключение "block" (блокировка) + } + ] + }, + // 4_Настройки входящих подключений + // 4.1 Здесь указан только один простейший входящий прокси-сервер vless+xtls, так как это самый производительный режим Xray. При необходимости вы можете добавить другие прокси-серверы, используя этот шаблон. + "inbounds": [ + { + "port": 443, + "protocol": "vless", + "settings": { + "clients": [ + { + "id": "", // Укажите свой UUID + "flow": "xtls-rprx-vision", + "level": 0, + "email": "vpsadmin@yourdomain.com" + } + ], + "decryption": "none", + "fallbacks": [ + { + "dest": 80 // Перенаправлять на порт 80 по умолчанию + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "tls", + "tlsSettings": { + "alpn": "http/1.1", + "certificates": [ + { + "certificateFile": "/home/vpsadmin/xray_cert/xray.crt", + "keyFile": "/home/vpsadmin/xray_cert/xray.key" + } + ] + } + } + } + ], + // 5_Настройки исходящих подключений + "outbounds": [ + // 5.1 Первое исходящее подключение - это правило по умолчанию. freedom - это прямое подключение (VPS и так находится во внешней сети) + { + "tag": "direct", + "protocol": "freedom" + }, + // 5.2 Правило блокировки. Протокол blackhole отправляет трафик в никуда (блокирует его) + { + "tag": "block", + "protocol": "blackhole" + } + ] + } + ``` + +5. Весь процесс показан на гифке: + + ![Создание файлов логов и файла конфигурации `config.json`](./ch07-img04-xray-log-and-config.gif) + +## 7.5 Запуск Xray! (и проверка состояния сервиса) + +Если вы всё делали по инструкции, то должны были избежать двух самых распространённых ошибок: **недостаточно прав для записи в файлы логов** и **недостаточно прав для чтения файлов сертификата**. Так что запуск `Xray` должен пройти без сучка и задоринки. + +1. Вводим команду и наслаждаемся историческим моментом запуска `Xray`!!! + + ```shell + sudo systemctl start xray + ``` + +2. Однако просто выполнить команду `start` недостаточно, чтобы убедиться, что сервис `Xray` запущен и работает корректно. Для проверки состояния сервиса используем следующую команду: + + ```shell + sudo systemctl status xray + ``` + + Видите надпись зелёного цвета `active (running)`? Это означает, что `Xray` запущен и работает. + +3. Весь процесс показан на гифке: + + ![Запуск и проверка состояния сервиса Xray](./ch07-img05-xray-start-and-status.gif) + +## 7.6 Базовое управление сервисами с помощью `systemd` + +Мы уже использовали несколько команд `systemctl`: `start`, `status`, `reload`. Все эти команды используются для управления сервисами в Linux с помощью системы инициализации `systemd`. Давайте рассмотрим ещё несколько полезных команд. + +1. Чтобы временно остановить сервис `Xray`, используем команду `stop`: + + ```shell + sudo systemctl stop xray + ``` + +2. Чтобы перезапустить сервис `Xray`, используем команду `restart`: + + ```shell + sudo systemctl restart xray + ``` + +3. Чтобы запретить автозапуск сервиса `Xray` после перезагрузки, используем команду `disable`: + + ```shell + sudo systemctl disable xray + ``` + +4. Чтобы разрешить автозапуск сервиса `Xray` после перезагрузки, используем команду `enable`: + + ```shell + sudo systemctl enable xray + ``` + +## 7.7 Оптимизация сервера: включение BBR + +1. Что говорят о `BBR` + +Уверен, что, изучая различные способы обхода блокировок, вы не раз сталкивались с аббревиатурой `bbr`. В многочисленных блогах её расхваливают на все лады, наделяя чуть ли не магическими свойствами. Существуют также различные модификации, такие как `bbrplus`, `bbr2`, `магически модифицированный bbr` и т. д. Создаётся ощущение, что это какая-то волшебная палочка, которая превращает тыкву в карету, а медленный интернет — в сверхскоростной. + +Так что же такое `BBR` на самом деле? Действительно ли он так хорош? И какую версию использовать? + +2. `BBR` на самом деле + +**BBR** = **B**ottleneck **B**andwidth and **R**ound-trip propagation time — это алгоритм управления перегрузками для протокола TCP. Проще говоря, это **регулировщик дорожного движения для интернет-трафика**: когда на дороге нет пробок, каждая машина может двигаться с максимальной скоростью. + +Так работает ли он? Как правило, разница между включенным и выключенным `BBR` заметна (улучшается скорость, стабильность и снижается пинг), поэтому **настоятельно рекомендуется включить `BBR`**. + +Однако разница между версиями `BBR` для ядер 4.x и 5.x зачастую не столь существенна и зависит от конкретной ситуации. Решающим фактором, влияющим на качество связи, по-прежнему остаётся качество самого интернет-канала. Поэтому **не стоит гнаться за новейшими версиями `BBR`. Достаточно использовать версию, которая поставляется с вашей версией дистрибутива Linux**. + +3. Действительно ли `bbrplus`, `bbr2`, `магически модифицированный bbr` и другие версии с крутыми названиями лучше? + +Одним словом: **нет! Не используйте их! Все эти названия придуманы только для привлечения внимания!** + +Обновления `BBR` выпускаются вместе с обновлениями ядра Linux (`Kernel`). Другими словами, если вы используете относительно новое ядро, то у вас уже будет установлена последняя версия `BBR`. + +А все эти скрипты с крутыми названиями просто скачивают и устанавливают предварительные версии ядра (или даже модифицированные сторонними разработчиками версии) с более новыми версиями `BBR`. + +Стабильность ядра — это основа стабильной работы сервера. **Незначительное повышение производительности, которое могут дать тестовые версии `BBR`, не стоит того, чтобы рисковать стабильностью сервера.** Используйте последнюю версию ядра, которая поддерживается вашим дистрибутивом Linux. Это обеспечит максимальную стабильность и совместимость вашего сервера. + +::: warning +Так называемое "преимущество" модифицированных версий `bbr` **очень недолговечно**. Например, многие скрипты `bbrplus` до сих пор устанавливают ядро версии 4.19, поскольку не обновлялись уже несколько лет. А ведь на дворе уже эпоха Debian 11 с ядром 5.10! То есть, если в январе 2018 года этот скрипт, возможно, и давал какое-то преимущество, то уже к октябрю 2018 года, когда вышла стабильная версия ядра 4.19, он потерял всякий смысл. А сейчас его использование и вовсе можно назвать **даунгрейдом**. +::: + +4. Какой алгоритм управления очередями использовать: `fq`, `fq_codel`, `fq_pie`, `cake` или какой-то другой? + +Одним словом: **если не знаете, что выбрать, оставьте `fq`. Этого достаточно, и это не ухудшит качество связи**. + +5. Стоит ли использовать "ускорители" типа **锐速**, **Finalspeed**, **LotServer**? + +Одним словом: **нет! Забудьте о них, как о страшном сне!** + +Единственная проблема, которую они решают, — это проблема потери пакетов. Если проводить аналогию, то представьте, что вам нужно отправить груз, но машина, которая его везёт, иногда ломается по дороге (теряются пакеты). Эти "ускорители" просто отправляют три одинаковых груза на трёх машинах одновременно. Даже если две машины сломаются, третья всё равно доставит груз. Конечно, если на дороге будет много ваших машин, то вы будете создавать помехи другим участникам движения. Но и они будут создавать помехи вам. А поскольку пропускная способность канала ограничена, то в итоге все будут стоять в пробке. + +::: warning +Эти "ускорители" не оптимизируют алгоритмы и не увеличивают скорость соединения. В большинстве своём это просто **увеличители количества отправляемых пакетов**. Для каналов с **очень высокими потерями пакетов** они могут дать незначительный эффект, но **для хороших каналов с низкими потерями пакетов они бесполезны и даже вредны, поскольку увеличивают расход трафика**. Это создаёт ненужную нагрузку на сервер и на каналы других пользователей. + +Если у вас действительно плохой канал с высокими потерями пакетов, то единственное правильное решение — **сменить провайдера**. +::: + +6. Я так подробно остановился на `BBR`, потому что вокруг него слишком много мифов и откровенной дезинформации, нацеленной на новичков. Надеюсь, теперь у вас есть чёткое представление о том, что такое `BBR` и как он работает. А теперь давайте установим последнюю версию ядра Debian и включим `BBR`! (Это действительно просто) + + 1. Добавляем репозиторий `backports` в Debian 10, чтобы получить доступ к более новым версиям пакетов: + + ```shell + sudo nano /etc/apt/sources.list + ``` + + ::: warning + В Debian 10 можно без проблем использовать файл `/etc/apt/sources.list`. Однако, если вы используете другой дистрибутив Linux или не устанавливали систему с нуля по этой инструкции, то рекомендуется создать папку `/etc/apt/sources.list.d/` и добавлять свои файлы конфигурации в неё, например, `/etc/apt/sources.list.d/vpsadmin.list`. Это обеспечит совместимость с другими дистрибутивами и предотвратит потерю настроек при случайном перезаписывании файла `/etc/apt/sources.list`. + ::: + + 2. Добавляем следующую строку в конец файла и сохраняем его: + + ``` + deb http://archive.debian.org/debian buster-backports main + ``` + + 3. Обновляем список доступных пакетов, ищем последнюю версию ядра Debian и устанавливаем её. Устанавливайте версию ядра, которая подходит для вашей архитектуры (в данном руководстве мы используем `amd64`): + + ```shell + sudo apt update && sudo apt -t buster-backports install linux-image-amd64 + ``` + + ::: warning + Если ваш VPS поддерживает это, вы можете попробовать установить **специальное ядро для облачных серверов** `linux-image-cloud-amd64`. Его преимущества — это меньший размер и меньшее потребление ресурсов. Однако некоторые пользователи сталкивались с проблемами при установке этого ядра на неподдерживаемые системы, вплоть до невозможности загрузки (ядро не определялось). + + Чтобы не попасть в такую ситуацию, перед установкой этого ядра: + + - создайте снапшот системы или + - убедитесь, что у вас есть доступ к `vnc`-консоли (и вы знаете, как ей пользоваться) + + ::: + + 4. Редактируем файл конфигурации `sysctl.conf` и включаем `BBR`: + + ```shell + sudo nano /etc/sysctl.conf + ``` + + ::: warning + В Debian 10 можно без проблем использовать файл `/etc/sysctl.conf`. Однако, если вы используете другой дистрибутив Linux или не устанавливали систему с нуля по этой инструкции, то рекомендуется создать папку `/etc/sysctl.d/` и добавлять свои файлы конфигурации в неё, например, `/etc/sysctl.d/vpsadmin.conf`. Это обеспечит совместимость с другими дистрибутивами, поскольку в некоторых из них, начиная с версии `systemd` 207, параметры из файла `/etc/sysctl.conf` не читаются. Использование отдельного файла конфигурации также предотвратит потерю настроек при случайном перезаписывании файла `/etc/sysctl.conf`. + ::: + + 5. Добавляем следующие строки в конец файла: + + ``` + net.core.default_qdisc=fq + net.ipv4.tcp_congestion_control=bbr + ``` + + 6. Перезагружаем VPS, чтобы изменения вступили в силу: + + ```shell + sudo reboot + ``` + + 7. Весь процесс показан на гифке: + + ::: tip + На моём VPS поддерживается специальное ядро для облачных серверов, поэтому на гифке я устанавливаю `linux-image-cloud-amd64`. Если вы не уверены, поддерживается ли оно на вашем VPS, то установите обычное ядро `linux-image-amd64`, как показано в пункте 3. + ::: + + ![Обновление ядра Debian и включение `BBR`](./ch07-img06-bbr-proper.gif) + + 8. Проверяем, что `BBR` включен + + Чтобы убедиться, что модуль `BBR` загружен, выполните команду: + + ```shell + lsmod | grep bbr + ``` + + В результате вы должны увидеть что-то вроде этого: + + ``` + tcp_bbr + ``` + + Чтобы убедиться, что алгоритм `fq` используется, выполните команду: + + ```shell + lsmod | grep fq + ``` + + В результате вы должны увидеть что-то вроде этого: + + ``` + sch_fq + ``` + +## 7.8 Оптимизация сервера: автоматическое перенаправление HTTP на HTTPS + +1. Ранее мы настроили веб-сервер на порту 80 и получили TLS-сертификат. + + Однако, если вы попытаетесь открыть наш сайт по протоколу HTTP, то заметите, что он не перенаправляется автоматически на HTTPS, как это делают большинство сайтов. Другими словами, в нашей текущей конфигурации HTTP (порт 80) и HTTPS (порт 443) — это два совершенно независимых сайта. Чтобы это исправить, нужно внести некоторые изменения. + +2. Открываем файл конфигурации Nginx: + + ```shell + sudo nano /etc/nginx/nginx.conf + ``` + +3. В настройках сервера, который слушает порт 80, добавляем следующую строку и сохраняем файл (строки `root` и `index` можно удалить): + + ``` + return 301 https://$http_host$request_uri; + ``` + +4. Добавляем новый сервер, который будет прослушивать локальный порт и отдавать файлы сайта. В данном примере мы будем использовать порт 8080 (вы можете использовать любой другой порт): + + ``` + server { + listen 127.0.0.1:8080; + root /home/vpsadmin/www/webpage; + index index.html; + add_header Strict-Transport-Security "max-age=63072000" always; + } + ``` + +5. Перезапускаем Nginx: + + ```shell + sudo systemctl restart nginx + ``` + +6. Изменяем настройки перенаправления в файле конфигурации Xray, заменив порт 80 на 8080 (находим `"dest": 80` и меняем на `"dest": 8080`): + + ```shell + sudo nano /usr/local/etc/xray/config.json + ``` + +7. Перезапускаем Xray: + + ```shell + sudo systemctl restart xray + ``` + +8. Весь процесс показан на гифке: + + ![Автоматическое перенаправление HTTP на HTTPS](./ch07-img07-http-to-https.gif) + +9. Теперь, если вы попытаетесь открыть сайт по адресу `http://sub.yourdomain.com`, он должен автоматически перенаправиться на HTTPS: + + ![Проверка автоматического перенаправления HTTP на HTTPS](./ch07-img08-http-to-https-check.png) + +## 7.9 Оптимизация сервера: более гибкая настройка перенаправления + +Если вам нужны более гибкие настройки перенаправления, обратитесь к статье [«Разбор функции Fallback»](../level-1/fallbacks-lv1/). + +## 7.10 Ваши успехи + +Поздравляю! На этом этапе у вас есть работающий сервер с обходом блокировок и сайт-приманка, который защитит вас от сканирования. Теперь достаточно установить клиентское приложение на ваше устройство — и можно наслаждаться свободным интернетом! + +> ⬛⬛⬛⬛⬛⬛⬛⬜ 87.5% + +## 7.11 Важные исправления + +1. В первоначальной версии руководства был указан неверный путь к файлу конфигурации `Xray` (`config.json`). Если вы настроили `Xray` по старой инструкции, то он не запустится. Приносим извинения за неудобства! + + - Верный путь: `/usr/local/etc/xray/config.json` + - Неверный путь: `/usr/local/etc/config.json` + + Затронутые разделы: + + - 7.4 Настройка Xray - 3. Создание файла конфигурации `Xray` с помощью `nano` + - 7.8 Оптимизация сервера: автоматическое перенаправление HTTP на HTTPS - 6. Изменяем настройки перенаправления в файле конфигурации Xray + +2. В первоначальной версии руководства была ошибка в настройках Nginx (неверный путь к папке с файлами сайта). Если вы настроили Nginx по старой инструкции, то сайт не будет работать. Приносим извинения за неудобства! + + - Верный путь: `root /home/vpsadmin/www/webpage;` + - Неверный путь: `root /var/www/website/html` + + Затронутые разделы: + + - 7.8 Оптимизация сервера: автоматическое перенаправление HTTP на HTTPS - 4. Добавляем новый сервер, который будет прослушивать локальный порт и отдавать файлы сайта. \ No newline at end of file diff --git a/docs/ru/document/level-0/ch08-img01-flow.png b/docs/ru/document/level-0/ch08-img01-flow.png new file mode 100644 index 000000000..dffd80835 Binary files /dev/null and b/docs/ru/document/level-0/ch08-img01-flow.png differ diff --git a/docs/ru/document/level-0/ch08-img02-buzz.png b/docs/ru/document/level-0/ch08-img02-buzz.png new file mode 100644 index 000000000..845a34bc3 Binary files /dev/null and b/docs/ru/document/level-0/ch08-img02-buzz.png differ diff --git a/docs/ru/document/level-0/ch08-xray-clients.md b/docs/ru/document/level-0/ch08-xray-clients.md new file mode 100644 index 000000000..47f70c514 --- /dev/null +++ b/docs/ru/document/level-0/ch08-xray-clients.md @@ -0,0 +1,336 @@ +# 【Глава 8】Настройка Xray на клиенте + +## 8.1 Как работает Xray: краткое описание + +Чтобы правильно настраивать и использовать `Xray`, важно понимать принципы его работы. Новичкам поможет упрощённая схема, на которой не показаны некоторые сложные моменты: + +![Поток данных в Xray](./ch08-img01-flow.png) + +Ключевые моменты: + +1. Приложение должно самостоятельно или с помощью стороннего инструмента перенаправить трафик на **входящее подключение** (`inbounds`) клиента `Xray`. + +2. Поступивший на клиент трафик обрабатывается **модулем маршрутизации** (`routing`) в соответствии с заданными правилами и перенаправляется на разные **исходящие подключения** (`outbounds`) клиента `Xray`, например: + + - Трафик на китайские ресурсы — напрямую (`direct`) + - Трафик на зарубежные ресурсы — через VPS (`proxy`) + - Рекламный трафик — блокируется (`block`) + +3. Трафик на зарубежные ресурсы, перенаправленный на VPS, проходит через Великий Китайский Файрвол и попадает на **входящее подключение** (`inbounds`) сервера `Xray`. + +4. Как и на клиенте, трафик, поступивший на сервер, обрабатывается **модулем маршрутизации** (`routing`) в соответствии с заданными правилами и перенаправляется на разные **исходящие подключения** (`outbounds`): + + - Поскольку сервер находится за пределами Китая, трафик по умолчанию идёт напрямую, что позволяет получить доступ к заблокированным ресурсам (`direct`). + - При необходимости можно настроить перенаправление трафика на другие VPS (`proxy`). + - На сервере также можно блокировать нежелательный трафик, например, рекламу или торренты (`block`). + +:::warning Внимание! + +Важно помнить, что маршрутизация в `Xray` очень гибкая, и описанный выше сценарий — лишь один из множества возможных. + +Используя файлы `geosite.dat` и `geoip.dat`, можно очень гибко управлять маршрутизацией трафика по доменным именам и IP-адресам. Это гораздо удобнее, чем устаревший `GFWList`, поскольку позволяет очень точно настроить правила: например, можно разрешить прямое подключение к доменам Apple, перенаправить трафик на домены Amazon через прокси-сервер, блокировать доступ к доменам Baidu и т. д. + +Более подробно о маршрутизации в Xray читайте в статье [«Разбор функции маршрутизации (routing)»](../level-1/routing-lv1-part1.md). Советую сначала дочитать эту главу и настроить базовый клиент, а потом уже углубляться в тонкости маршрутизации. +::: + +## 8.2 Подключение клиента к серверу + +Теперь, когда вы понимаете принципы работы `Xray`, настройка клиента сводится к тому, чтобы **сообщить ему, как подключиться к вашему VPS**. Это как настроить `PuTTY` для подключения к серверу, только в случае с Xray параметров подключения больше, чем IP-адрес, порт, имя пользователя и пароль. + +Набор параметров подключения в `Xray` зависит от используемого [протокола](../../config/inbounds/). В главе 7 мы настроили сервер на использование протокола `VLESS` с шифрованием `XTLS`. Посмотрим на файл конфигурации сервера, чтобы узнать, какие параметры нужны для подключения: + +- **Адрес сервера**: `sub.yourdomain.com` +- **Порт сервера**: `443` +- **Протокол**: `vless` +- **Поток**: `xtls-rprx-vision` (режим `vision` подходит для всех платформ) +- **Идентификатор**: `uuiduuid-uuid-uuid-uuid-uuiduuiduuid` +- **Безопасность**: `"allowInsecure": false` + +Ниже приведен список популярных клиентов Xray для мобильных и настольных устройств. Каждый клиент имеет свой собственный интерфейс, поэтому я не буду делать скриншоты для каждого из них. Внимательно изучите документацию к выбранному клиенту и укажите нужные параметры подключения. + +:::warning Внимание! + +Многие клиенты поддерживают как `xray-core`, так и `v2fly-core`. Но по умолчанию может использоваться не тот, который вам нужен. Убедитесь, что вы выбрали нужный инструмент! +::: + +- **v2rayN - для Windows** + + - Скачайте последнюю версию из [репозитория на GitHub](https://github.com/2dust/v2rayN/releases) + - Настройте клиент в соответствии с документацией + +- **v2rayNG - для Android** + + - Скачайте последнюю версию из [репозитория на GitHub](https://github.com/2dust/v2rayNG/releases) + - Настройте клиент в соответствии с документацией + +- **Shadowrocket - для iOS, macOS на базе Apple M1** + + - Создайте учётную запись iCloud **не** в китайском регионе + - Купите приложение в App Store + - Настройте клиент в соответствии с документацией + +- **Qv2ray - кроссплатформенный графический интерфейс для Linux, Windows, macOS** + + - Скачайте последнюю версию из [репозитория на GitHub](https://github.com/Qv2ray/Qv2ray/releases) (или более новую версию из раздела [сборок на GitHub](https://github.com/Qv2ray/Qv2ray/actions)) + - Изучите документацию на [сайте проекта](https://qv2ray.net/) + - Настройте клиент в соответствии с документацией + +- **V2RayXS - клиент для macOS, основанный на V2RayX и использующий xray-core** + + - Скачайте последнюю версию из [репозитория на GitHub](https://github.com/tzmax/v2rayXS/releases) + - Поддерживает импорт ссылок на конфигурации VLESS / VMessAEAD по стандарту, предложенному в [этой задаче](https://github.com/XTLS/Xray-core/issues/91) + - Настройте клиент в соответствии с документацией + +На этом этапе ваша система готова к работе! + +## 8.3 Дополнительное задание 1: настройка `xray-core` на ПК вручную + +Хотя на предыдущем шаге мы уже всё настроили, любознательным и обладающим хорошей памятью читателям наверняка вспомнятся мои слова из предыдущей главы о том, что `xray-core` можно запускать как на сервере, так и на клиенте. Так как же использовать `xray-core` в качестве клиента? + +Чтобы ответить на этот вопрос, я добавил этот раздел с дополнительным заданием. Оно немного выходит за рамки основного материала и может показаться сложным, но у него есть свои преимущества: + +- Вы всегда будете использовать самую последнюю версию `xray-core`, не дожидаясь, пока разработчики клиентов выпустят обновления. +- Вы получите максимальную гибкость в настройке маршрутизации (хотя стоит отметить, что Qv2ray имеет мощный редактор маршрутизации, который позволяет настраивать все функции `xray-core`). +- Вы сэкономите системные ресурсы (графические клиенты всегда потребляют больше ресурсов, чем консольные). + +Недостаток этого способа заключается в том, что вам придётся **настраивать клиент вручную, редактируя файл конфигурации**. Но ведь на сервере вы уже делали это, так что ничего сложного здесь нет. Давайте разберёмся по шагам: + +1. Скачайте последнюю версию `xray-core` для вашей платформы из [репозитория на GitHub](https://github.com/XTLS/Xray-core/releases) и распакуйте архив в удобное место. +2. Создайте пустой файл конфигурации `config.json` в той же папке (думаю, с этим проблем не возникнет). +3. Что значит "удобное место"? Это зависит от платформы. +4. Заполните файл конфигурации. + + - Я написал пример конфигурации, основанный на схеме из раздела 8.1 (прямое подключение к китайским ресурсам, проксирование трафика на зарубежные ресурсы через VPS, блокировка рекламы) и параметрах подключения из раздела 8.2. + - Замените `uuid` на идентификатор из вашей конфигурации сервера. + - Замените `address` на доменное имя вашего сервера. + - Замените `serverName` на доменное имя вашего сервера. + - Я добавил подробные комментарии к каждому разделу конфигурации. + + ```json + // ССЫЛКИ: + // https://github.com/XTLS/Xray-examples + // https://xtls.github.io/config/ + + // Типичный конфигурационный файл, как для сервера, так и для клиента, состоит из 5 основных частей. Разберём их по полочкам: + // ┌─ 1_log Настройки логирования - что и куда писать в лог (чтобы было проще искать ошибки) + // ├─ 2_dns Настройки DNS - как выполнять DNS-запросы (защита от DNS-спуфинга, защита от слежки, предотвращение маршрутизации трафика на китайские серверы и т. д.) + // ├─ 3_routing Настройки маршрутизации - как обрабатывать трафик (фильтрация рекламы, разделение трафика для разных стран) + // ├─ 4_inbounds Настройки входящих подключений - какой трафик может поступать на Xray + // └─ 5_outbounds Настройки исходящих подключений - куда направлять трафик, исходящий от Xray + + { + // 1_Настройки логирования + // В этом примере я закомментировал настройки файлов логов, потому что в Windows, macOS и Linux используются разные пути. Укажите свои пути. + "log": { + // "access": "/home/local/xray_log/access.log", // Файл для записи логов доступа + // "error": "/home/local/xray_log/error.log", // Файл для записи логов ошибок + "loglevel": "warning" // Уровень детализации логов: "none", "error", "warning", "info", "debug" (от меньшего к большему) + }, + + // 2_Настройки DNS + "dns": { + "servers": [ + // 2.1 Запросы к зарубежным доменам отправляем на зарубежный DNS-сервер + { + "address": "1.1.1.1", + "domains": ["geosite:geolocation-!cn"] + }, + // 2.2 Запросы к китайским доменам отправляем на китайский DNS-сервер и ожидаем получить китайский IP-адрес. Если адрес не китайский, используем следующий DNS-сервер + { + "address": "223.5.5.5", + "domains": ["geosite:cn"], + "expectIPs": ["geoip:cn"] + }, + // 2.3 Резервный китайский DNS-сервер + { + "address": "114.114.114.114", + "domains": ["geosite:cn"] + }, + // 2.4 Если все предыдущие DNS-серверы не ответили, используем локальный DNS-сервер + "localhost" + ] + }, + + // 3_Настройки маршрутизации + // Маршрутизация позволяет перенаправлять трафик, соответствующий определённым условиям, на определённое исходящее подключение (см. раздел 5). + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + // 3.1 Блокировка рекламных доменов + { + "type": "field", + "domain": ["geosite:category-ads-all"], + "outboundTag": "block" + }, + // 3.2 Прямое подключение к китайским доменам + { + "type": "field", + "domain": ["geosite:cn"], + "outboundTag": "direct" + }, + // 3.3 Прямое подключение к китайским IP-адресам + { + "type": "field", + "ip": ["geoip:cn", "geoip:private"], + "outboundTag": "direct" + }, + // 3.4 Проксирование трафика на зарубежные домены + { + "type": "field", + "domain": ["geosite:geolocation-!cn"], + "outboundTag": "proxy" + }, + // 3.5 Правило по умолчанию + // В Xray любой трафик, который не соответствует ни одному из правил маршрутизации, отправляется на первое исходящее подключение (см. раздел 5.1). Поэтому важно разместить настройки прокси-сервера на первом месте. + // 3.6 Трафик, который идёт на DNS-сервер 223.5.5.5, отправляем напрямую + { + "type": "field", + "ip": ["223.5.5.5"], + "outboundTag": "direct" + } + ] + }, + + // 4_Настройки входящих подключений + "inbounds": [ + // 4.1 Обычно используется протокол SOCKS5 для локального перенаправления трафика + { + "tag": "socks-in", + "protocol": "socks", + "listen": "127.0.0.1", // Адрес, на котором будет слушать SOCKS5-сервер + "port": 10800, // Порт, на котором будет слушать SOCKS5-сервер + "settings": { + "udp": true + } + }, + // 4.2 Некоторые приложения не поддерживают SOCKS. Для них можно использовать HTTP-прокси + { + "tag": "http-in", + "protocol": "http", + "listen": "127.0.0.1", // Адрес, на котором будет слушать HTTP-сервер + "port": 10801 // Порт, на котором будет слушать HTTP-сервер + } + ], + + // 5_Настройки исходящих подключений + "outbounds": [ + // 5.1 Настройки прокси-сервера + // Этот раздел должен быть первым, как уже было сказано в разделе 3.5. Все правила по умолчанию будут использовать эти настройки. + { + "tag": "proxy", + "protocol": "vless", + "settings": { + "vnext": [ + { + "address": "sub.yourdomain.com", // Замените на доменное имя вашего сервера + "port": 443, + "users": [ + { + "id": "uuiduuid-uuid-uuid-uuid-uuiduuiduuid", // Должен совпадать с идентификатором на сервере + "flow": "xtls-rprx-vision", + "encryption": "none", + "level": 0 + } + ] + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "tls", + "tlsSettings": { + "serverName": "sub.yourdomain.com", // Замените на доменное имя вашего сервера + "allowInsecure": false, // Запретить использование недоверенных сертификатов + "fingerprint": "chrome" // Использовать uTLS для подмены отпечатка браузера Chrome / Firefox / Safari или случайный отпечаток + } + } + }, + // 5.2 Прямое подключение + // Используется, если в настройках маршрутизации указан тег "direct" + { + "tag": "direct", + "protocol": "freedom" + }, + // 5.3 Блокировка трафика + // Используется, если в настройках маршрутизации указан тег "block" + { + "tag": "block", + "protocol": "blackhole" + } + ] + } + ``` + +## 8.4 Дополнительное задание 2: запуск `xray-core` на ПК + +Итак, мы создали файл конфигурации. Как теперь запустить `xray-core`? Двойной клик по файлу не работает! + +Во-первых, вам нужно открыть **командную строку**. + +1. Пользователи Linux и macOS наверняка знают, как это сделать. Просто найдите приложение **«Терминал»**. +2. В Windows используйте **«Командную строку»** или **PowerShell** (пользователи WSL, можете использовать привычный вам **«Терминал»**). + +Во-вторых, нам нужно **указать `xray` путь к файлу конфигурации `config.json` и запустить его**. + +1. В Windows, если файл `xray.exe` находится в папке `C:\Xray-windows-64\`, а файл `config.json` — в той же папке, то команда запуска будет выглядеть так: + + ```shell + C:\Xray-windows-64\xray.exe -c C:\Xray-windows-64\config.json + ``` + + :::tip + Параметр `-c` указывает путь к файлу конфигурации. + ::: + +2. Аналогично, в Linux и macOS, если файл `xray` находится в папке `/usr/local/bin/`, а файл `config.json` — в папке `/usr/local/etc/xray/`, то команда запуска будет выглядеть так: + + ```shell + /usr/local/bin/xray -c /usr/local/etc/xray/config.json + ``` + + :::tip + В каждом системе есть переменные окружения, которые хранят пути к часто используемым папкам. Поэтому указывать полный путь к файлу `xray` не обязательно. Но я всё же указал его для надёжности. + ::: + +## 8.5 Дополнительное задание 3: автозапуск `xray-core` на ПК + +Если вы попробовали запускать `xray-core` вручную, то наверняка заметили следующие недостатки: + +1. При каждом запуске `Xray` открывается чёрное окно консоли, что не очень красиво. +2. `Xray` не запускается автоматически при загрузке системы, поэтому его приходится запускать вручную каждый раз. + +Спешу вас обрадовать: **эти проблемы решаемы!** Но как именно их решить, я оставлю вам в качестве домашнего задания (подсказка: загляните в раздел FAQ на сайте документации). + +## 8.6 Финишная прямая! + +Уверен, что те, кто дочитал до этого места, — это любознательные и целеустремлённые люди, которые готовы учиться новому! Я от всей души поздравляю вас, ведь вы **самостоятельно, начиная с самых азов, настроили сервер VPS и клиент Xray!** Это огромная победа! + +Надеюсь, теперь вы больше не боитесь `Linux` и разобрались с тем, как работает `Xray`. + +**На этом наше повествование завершается!** + +> ⬛⬛⬛⬛⬛⬛⬛⬛ 100% + +## 8.7 В бесконечность и далее! + +**Но это ещё не всё, что может Xray.** + +`Xray` — это мощный и многофункциональный инструмент, который можно использовать для решения самых разных задач. В этом руководстве мы лишь поверхностно рассмотрели **самые простые** и **наглядные** варианты его настройки. + +Если вам достаточно и этого, то наслаждайтесь свободой в интернете! Но если ваш пытливый ум жаждет новых знаний, то продолжайте изучать безграничные возможности `Xray`! + +Дополнительную информацию можно найти здесь: + +1. [xtls.github.io](https://xtls.github.io/) — официальная документация +2. [Официальная группа в Telegram](https://t.me/projectXray) — активное и дружелюбное сообщество + +![В бесконечность и далее!](./ch08-img02-buzz.png) + +:::tip Вместо послесловия + +Надеюсь, это небольшое путешествие, в которое я вас отправил, поможет вам сделать интернет лучше. + +Конечно, со временем информация из этого руководства устареет. Но вы будете расти и развиваться, и, возможно, когда-нибудь, вспоминая это руководство и те цели, которые я ставил перед собой, создавая его, вы передадите свои знания другим, чтобы эта эстафета помощи новичкам не прекращалась. + +Мы живём в мире, где царят тьма и цензура. Люди бродят в одиночестве в поисках лучика света. И если мы не будем помогать друг другу и поддерживать друг друга на этом пути, то в конце концов нас ждёт лишь печальная картина запустения. +::: diff --git a/docs/ru/document/level-0/ch09-appendix.md b/docs/ru/document/level-0/ch09-appendix.md new file mode 100644 index 000000000..fa182e4a4 --- /dev/null +++ b/docs/ru/document/level-0/ch09-appendix.md @@ -0,0 +1,46 @@ +# [Глава 9] Приложение + +## 1. Индекс основных команд Linux для начинающих + +| Номер | Название команды | Описание команды | Глава | +| :------: | :------------------ | :--------------------------- | :----------------------------------------: | +| `cmd-01` | `apt update` | Обновление списка пакетов | [Глава о удаленном подключении](./ch03-ssh.md) | +| `cmd-02` | `apt upgrade` | Обновление пакетов системы | [Глава о удаленном подключении](./ch03-ssh.md) | +| `cmd-03` | `nano` | Текстовый редактор | [Глава о безопасности](./ch04-security.md) | +| `cmd-04` | `systemctl restart` | Перезапуск сервиса | [Глава о безопасности](./ch04-security.md) | +| `cmd-05` | `adduser` | Добавление пользователя | [Глава о безопасности](./ch04-security.md) | +| `cmd-06` | `apt install` | Установка пакета | [Глава о безопасности](./ch04-security.md) | +| `cmd-07` | `visudo` | Редактор для настройки sudo | [Глава о безопасности](./ch04-security.md) | +| `cmd-08` | `sudo` | Выполнение команды от имени root | [Глава о безопасности](./ch04-security.md) | +| `cmd-09` | `chmod` | Изменение прав доступа к файлу/папке | [Глава о безопасности](./ch04-security.md) | +| `cmd-10` | `mkdir` | Создание папки | [Глава о создании сайта](./ch05-webpage.md) | +| `cmd-11` | `systemctl reload` | Перезагрузка конфигурации сервиса | [Глава о создании сайта](./ch05-webpage.md) | +| `cmd-12` | `wget` | Загрузка файла/страницы из сети | [Глава об управлении сертификатами](./ch06-certificates.md) | +| `cmd-13` | `acme.sh` | Управление сертификатами с помощью acme.sh | [Глава об управлении сертификатами](./ch06-certificates.md) | +| `cmd-14` | `rm` | Удаление файлов/папок | [Глава о Xray сервере](./ch07-xray-server.md) | +| `cmd-15` | `crontab -e` | Редактирование crontab текущего пользователя | [Глава о Xray сервере](./ch07-xray-server.md) | +| `cmd-16` | `touch` | Создание пустого файла | [Глава о Xray сервере](./ch07-xray-server.md) | +| `cmd-17` | `systemctl` | Базовые команды управления сервисами systemd | [Глава о Xray сервере](./ch07-xray-server.md) | +| `cmd-18` | `reboot` | Перезагрузка Linux | [Глава о Xray сервере](./ch07-xray-server.md) | + +## 2. Индекс важных конфигурационных файлов Linux + +| Номер | Расположение файла | Описание файла | Глава | +| :-------: | :-------------------------------------- | :----------------------------- | :----------------------------------------: | +| `conf-01` | `/etc/ssh/sshd_config` | Конфигурация SSH сервера | [Глава о удаленном подключении](./ch03-ssh.md) | +| `conf-02` | `/etc/nginx/nginx.conf` | Конфигурация Nginx | [Глава о создании сайта](./ch05-webpage.md) | +| `conf-03` | `/etc/apt/sources.list` | Список репозиториев APT | [Глава о Xray сервере](./ch07-xray-server.md) | +| `conf-04` | `/etc/apt/sources.list.d/vpsadmin.list` | Список пользовательских репозиториев APT | [Глава о Xray сервере](./ch07-xray-server.md) | +| `conf-05` | `crontab -e` | Crontab текущего пользователя | [Глава о Xray сервере](./ch07-xray-server.md) | +| `conf-06` | `/etc/sysctl.conf` | Настройки ядра Linux | [Глава о Xray сервере](./ch07-xray-server.md) | +| `conf-07` | `/etc/sysctl.d/vpsadmin.conf` | Пользовательские настройки ядра Linux | [Глава о Xray сервере](./ch07-xray-server.md) | + +## 3. Индекс важных файлов Xray + +| Номер | Расположение файла | Описание файла | Глава | +| :-------: | :----------------------------------- | :------------ | :----------------------------------------: | +| `xray-01` | `/usr/local/etc/xray/config.json` | Конфигурация Xray | [Глава о Xray сервере](./ch07-xray-server.md) | +| `xray-02` | `/home/vpsadmin/xray_cert/xray.cert` | TLS сертификат | [Глава о Xray сервере](./ch07-xray-server.md) | +| `xray-03` | `/home/vpsadmin/xray_cert/xray.key` | TLS ключ | [Глава о Xray сервере](./ch07-xray-server.md) | +| `xray-04` | `/home/vpsadmin/xray_log/access.log` | Лог доступа Xray | [Глава о Xray сервере](./ch07-xray-server.md) | +| `xray-05` | `/home/vpsadmin/xray_log/error.log` | Лог ошибок Xray | [Глава о Xray сервере](./ch07-xray-server.md) | diff --git a/docs/ru/document/level-1/README.md b/docs/ru/document/level-1/README.md new file mode 100644 index 000000000..fa7a0b051 --- /dev/null +++ b/docs/ru/document/level-1/README.md @@ -0,0 +1,13 @@ +# Советы для начинающих + +**В этой главе вы найдете советы по использованию Xray для начинающих, в основном с описанием принципов работы часто используемых модулей Xray.** + +[Обзор функции Fallback](./fallbacks-lv1.md) + +[Обзор функции маршрутизации (routing) (часть 1)](./routing-lv1-part1.md) + +[Обзор функции маршрутизации (routing) (часть 2)](./routing-lv1-part2.md) + +[Обзор режимов работы Xray](./work.md) + +[Маскировка и разделение трафика по доменам с помощью функции SNI Fallback](./fallbacks-with-sni.md) diff --git a/docs/ru/document/level-1/fallbacks-lv1.md b/docs/ru/document/level-1/fallbacks-lv1.md new file mode 100644 index 000000000..713a8ae04 --- /dev/null +++ b/docs/ru/document/level-1/fallbacks-lv1.md @@ -0,0 +1,395 @@ +# Обзор функции Fallback + +При использовании Xray вы наверняка много раз слышали о функции **"fallback"**. В этой статье мы кратко рассмотрим логику этой функции и способы ее применения. + +## 1. Что такое Fallback в простых словах + +Если вы использовали [конфигурацию Xray](../level-0/ch07-xray-server.md#_7-4-配置xray) из нашего руководства и настроили [автоматическое перенаправление HTTP на HTTPS](../level-0/ch07-xray-server.md#_7-8-服务器优化之二-开启http自动跳转https), то у вас уже есть простой fallback на основе протокола `VLESS`: + +```json +{ + "inbounds": [ + { + "port": 443, + "protocol": "vless", + "settings": { + "clients": [ + // ... ... + ], + "decryption": "none", + "fallbacks": [ + { + "dest": 8080 // По умолчанию перенаправлять на прокси-сервер, защищенный от сканирования + } + ] + }, + "streamSettings": { + // ... ... + } + } + ] +} +``` + +Как объяснить этот фрагмент конфигурации простыми словами? + +1. **`Xray` прослушивает порт `[inbound port]` `443`** + + Это означает, что `Xray` отвечает за прослушивание трафика `HTTPS` на порту `443`. + +2. **`Xray` использует протокол `[inbound protocol]` `vless`** + + Только трафик протокола `vless` будет обрабатываться `Xray` и перенаправляться на исходящие модули. + + ::: warning + **Примечание:** Легковесный протокол `VLESS` был разработан с целью добавления функции fallback в `xray`, `v2fly` и другие ядра, а также для уменьшения избыточных проверок/шифрования. (Конечно, на данный момент протокол `trojan` в `xray` также полностью поддерживает функцию fallback.) + ::: + +3. **Целевой порт fallback `[fallback dest]` - `8080`** + + После того, как `Xray` получает входящий трафик на порт `443`, трафик протокола `vless` обрабатывается внутренне и перенаправляется на исходящий модуль. Другой трафик, не относящийся к протоколу `vless`, перенаправляется на порт `8080`. + + ::: warning + **Вопрос:** Использовать единственное или множественное число? + + Ответ: Внимательные читатели, должно быть, заметили, что в файле конфигурации используются множественные числа `inbounds`, `fallbacks`, но почему я использую единственное число: `inbound`, `fallback`? + + Потому что множественное число в файле конфигурации означает, что `xray` поддерживает N элементов одного уровня (т.е. N входящих, M резервных и т.д.), а в приведенном выше примере анализа используется только один, поэтому я использовал единственное число. + ::: + +4. **Трафик, перенаправляемый на порт `8080`, обрабатывается последующей программой** + + В нашем примере порт `8080` обрабатывается `Nginx`, который находит и отображает страницу с маленькой пандой в соответствии с конфигурацией. + +5. **В итоге, полный маршрут данных в нашем примере выглядит следующим образом:** + + ```mermaid + graph LR; + + W(Внешний HTTP:80 запрос) --> N80(HTTP:80) + + subgraph Nginx Внешнее прослушивание + N80 -.- N301(301 перенаправление) -.- N443(HTTPS:443) + end + + N443 --> X(Xray прослушивает 443) .- X1{Проверка входящего трафика} + X1 --> |Трафик VLESS| X2(Внутренние правила Xray) + X2 --> O(Xray Outbounds Исходящий) + X1 ==> |Fallback не VLESS трафика| N8080(Nginx:8080) + N8080:::nginxclass ==> H(index.html) + + H:::nginxclass + classDef nginxclass fill:#FFFFDE + + ``` + +## 2. Что такое Fallback (ЧТО, КАК `v1`) + +Основываясь на приведенном выше примере, вы должны понять, что такое fallback (Что) и как он работает (Как), проще говоря, вот эти несколько элементов: + +1. Fallback происходит после того, как трафик поступает на **порт прослушивания `Xray`**. +2. Fallback основывается на таких характеристиках трафика, как **тип протокола**. +3. Целью fallback является определенный **порт**. +4. Трафик, для которого выполнен fallback, обрабатывается программой, прослушивающей **порт fallback**. + +## 3. Зачем нужен Fallback (ЗАЧЕМ `v1`) + +Изначально он был предназначен для защиты от **активного зондирования** (Active Probing). + +**Активное зондирование:** Проще говоря, это означает, что внешние злоумышленники отправляют определенные сетевые запросы и интерпретируют ответы сервера, чтобы определить, запущены ли на сервере такие прокси-инструменты, как `xray`, `v2fly`, `shadowsocks` и т.д. Если это удается точно определить, сервер может подвергнуться атаке или блокировке. + +Интерпретировать ответы сервера можно потому, что полный запрос данных на самом деле состоит из множества этапов обмена данными, и на каждом этапе генерируются определенные характеристики программного обеспечения. Проще говоря: + +- Ответ обычного сайта **обязательно будет** содержать характеристики таких инструментов веб-сервиса и базы данных, как `Nginx`, `Apache`, `MySQL` и т.д. +- Ответ обычного сайта **не будет** содержать характеристики таких прокси-инструментов, как `xray`, `v2fly`, `shadowsocks` и т.д. + +Таким образом, когда мы предоставляем `Xray` функцию **"fallback"** (как в приведенном выше примере, fallback на `Nginx`), любой запрос, используемый для зондирования, приводит к следующему: + +- Зондирующий трафик не может получить доступ к вашим параметрам `VLESS` и поэтому будет перенаправлен на `Nginx`. +- Весь зондирующий трафик перенаправляется на `Nginx`, поэтому ответ VPS-сервера **обязательно будет** содержать характеристики `Nginx`. +- Поскольку сам `Xray` не отвечает на зондирующий трафик, ответ VPS **не будет** содержать характеристики `Xray`. + +Таким образом, функция **"fallback"** решает проблему безопасности **активного зондирования** сервера с точки зрения логики взаимодействия данных. + +## 4. Полное понимание Fallback (ЧТО, ЗАЧЕМ, КАК `v2`) + +Почему нужно снова говорить о fallback? Потому что выше мы рассмотрели только первую версию fallback, основанную на "протоколе" и защите от **активного зондирования**. + +В процессе постоянного развития и обновления протокола `VLESS` и функции `fallback` командой [RPRX](https://github.com/rprx) постепенно выяснилось, что fallback может быть более гибким и мощным. При условии обеспечения защиты от **активного зондирования** можно в полной мере использовать информацию, содержащуюся в первом пакете данных, для реализации многоэлементного и многоуровневого fallback (например, `path`, `alpn` и т.д.). + +Основываясь на этой концепции разработки, функция **"fallback"** постепенно превратилась в то, чем она является сейчас, - в механизм, реализующий **полную маскировку --> ws-разделение --> многопротокольное и многопараметрическое разделение**. Финальная версия даже полностью заменила функцию разделения, которую раньше выполняли веб-серверы и другие инструменты. А поскольку описанная выше обработка **"fallback/разделения"** выполняется на этапе определения первого пакета за миллисекунды и не связана с какими-либо операциями с данными, она практически не приводит к потерям производительности. + +**Таким образом, сейчас **полноценная функция "fallback" в `Xray`** обладает следующими свойствами:** + +- **Безопасность:** полная защита от атак активного зондирования. +- **Эффективность:** практически полное отсутствие потерь производительности. +- **Гибкость:** гибкое разделение данных, повторное использование часто используемых портов (например, 443). + +::: tip +Хотя такой подробный подход может показаться несколько утомительным, только он позволяет в полной мере раскрыть уникальные преимущества **полноценного "fallback"**. +::: + +## 5. Пример и описание многоуровневого fallback + +Теперь, когда мы понимаем, что такое **"полноценный fallback"**, мы можем приступить к настройке многоуровневого fallback. + +### 5.1 Сначала скопируем фрагмент конфигурации прослушивания порта 443 на стороне сервера: + +```json +{ + "port": 443, + "protocol": "vless", + "settings": { + "clients": [ + { + "id": "", // Укажите свой UUID + "flow": "xtls-rprx-vision", + "level": 0, + "email": "love@example.com" + } + ], + "decryption": "none", + "fallbacks": [ + { + "dest": 1310, // По умолчанию перенаправлять на протокол Trojan Xray + "xver": 1 + }, + { + "path": "/websocket", // Обязательно укажите свой путь + "dest": 1234, + "xver": 1 + }, + { + "path": "/vmesstcp", // Обязательно укажите свой путь + "dest": 2345, + "xver": 1 + }, + { + "path": "/vmessws", // Обязательно укажите свой путь + "dest": 3456, + "xver": 1 + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "tls", + "tlsSettings": { + "alpn": ["http/1.1"], + "certificates": [ + { + "certificateFile": "/path/to/fullchain.crt", // Укажите путь к вашему сертификату, абсолютный путь + "keyFile": "/path/to/private.key" // Укажите путь к вашему закрытому ключу, абсолютный путь + } + ] + } + } +} +``` + +Как объяснить этот фрагмент конфигурации простыми словами? + +1. **`Xray` прослушивает порт (`inbound port`) `443`** + + Это означает, что `Xray` отвечает за прослушивание трафика `HTTPS` на порту `443` и использует сертификат `TLS`, указанный в `certificates`, для аутентификации. + +2. **`Xray` использует протокол (`inbound protocol`) `vless`** + + Трафик протокола `vless` напрямую передается в `Xray` для дальнейшей обработки. + +3. **Трафик, не относящийся к протоколу `VLESS`, перенаправляется на 4 различных порта fallback:** + + 1. Трафик с `path`, равным `websocket`, перенаправляется на порт `1234` для дальнейшей обработки. + 2. Трафик с `path`, равным `vmesstcp`, перенаправляется на порт `2345` для дальнейшей обработки. + 3. Трафик с `path`, равным `vmessws`, перенаправляется на порт `3456` для дальнейшей обработки. + 4. Весь остальной трафик перенаправляется на порт `1310` для дальнейшей обработки. + +4. **`xver`, равный `1`, означает, что функция `proxy protocol` включена, и реальный IP-адрес источника будет передан дальше.** + +5. **Структура fallback показана на рисунке ниже:** + + ```mermaid + graph LR; + + W443(Внешний HTTP:443 запрос) --> X443(Xray-inbound: 443) .- X1{Проверка входящего трафика} + X1 --> |Протокол = VLESS| X2(Внутренние правила Xray) + X2 --> O(Xray Outbounds Исходящий) + + X1 --> |path = /websocket| X1234(Xray-inbound:1234) + X1 --> |path = /vmesstcp| X2345(Xray-inbound:2345) + X1 --> |path = /vmessws| X3456(Xray-inbound:3456) + X1 --> |Весь остальной трафик| X1310(Xray-inbound:1310) + + ``` + +6. **Куда делся fallback на веб-страницу?** + + Верно, внимательные читатели должны были заметить, что `fallback на nginx`, защищающий от **активного зондирования**, исчез!!! Почему? Не опасно ли это? Не волнуйтесь, давайте разбираться дальше: + +### 5.2 Фрагмент конфигурации, отвечающий за обработку fallback: + +1. Трафик, перенаправляемый на порт `1310`, обрабатывается в соответствии со следующей конфигурацией: + + ```json + { + "port": 1310, + "listen": "127.0.0.1", + "protocol": "trojan", + "settings": { + "clients": [ + { + "password": "", // Укажите свой пароль + "level": 0, + "email": "love@example.com" + } + ], + "fallbacks": [ + { + "dest": 80 // Или перенаправлять на другой прокси-сервер, защищенный от сканирования + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "none", + "tcpSettings": { + "acceptProxyProtocol": true + } + } + } + ``` + + Смотрите, произошло чудо, в протоколе `trojan` появился новый `fallbacks`. Как уже говорилось ранее, протокол `trojan` в `xray` также обладает полной функциональностью fallback, поэтому на этом этапе протокол `trojan` может снова выполнять проверку и fallback (это и есть легендарный "fallback в fallback"): + + - Весь трафик протокола `trojan` передается в `Xray` для дальнейшей обработки. + - Весь остальной трафик перенаправляется на порт `80`. **Защита от активного зондирования** реализована! + +2. Трафик, перенаправляемый на порт `1234`, на самом деле является `vless+ws`: + + ```json + { + "port": 1234, + "listen": "127.0.0.1", + "protocol": "vless", + "settings": { + "clients": [ + { + "id": "", // Укажите свой UUID + "level": 0, + "email": "love@example.com" + } + ], + "decryption": "none" + }, + "streamSettings": { + "network": "ws", + "security": "none", + "wsSettings": { + "acceptProxyProtocol": true, // Напоминание: если вы используете Nginx/Caddy и т.д. для обратного проксирования WS, удалите эту строку + "path": "/websocket" // Обязательно укажите свой путь, он должен совпадать с путем разделения + } + } + } + ``` + +3. Трафик, перенаправляемый на порт `2345`, на самом деле является прямым подключением `vmess`: + + ```json + { + "port": 2345, + "listen": "127.0.0.1", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "", // Укажите свой UUID + "level": 0, + "email": "love@example.com" + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "none", + "tcpSettings": { + "acceptProxyProtocol": true, + "header": { + "type": "http", + "request": { + "path": [ + "/vmesstcp" // Обязательно укажите свой путь, он должен совпадать с путем разделения + ] + } + } + } + } + } + ``` + +4. Трафик, перенаправляемый на порт `3456`, на самом деле является `vmess+ws(+cdn)`. + + ::: warning + Да, вы не ослышались, это одна из комбинаций, рекомендованных v2fly, и она полностью поддерживает `CDN`. Теперь она добавлена в наш идеальный набор fallback! + ::: + + ```json + { + "port": 3456, + "listen": "127.0.0.1", + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "", // Укажите свой UUID + "level": 0, + "email": "love@example.com" + } + ] + }, + "streamSettings": { + "network": "ws", + "security": "none", + "wsSettings": { + "acceptProxyProtocol": true, // Напоминание: если вы используете Nginx/Caddy и т.д. для обратного проксирования WS, удалите эту строку + "path": "/vmessws" // Обязательно укажите свой путь, он должен совпадать с путем разделения + } + } + } + ``` + +5. Теперь мы можем нарисовать полную схему fallback: + +```mermaid + graph LR; + + W443(Внешний HTTP:443 запрос) --> X443(Xray-inbound: 443) .- X1{Проверка входящего трафика} + X1 --> |Протокол = VLESS| X2(Внутренние правила Xray) + X2 --> XO(Xray Outbounds Исходящий) + + X1 --> |path = /websocket| X1234(Xray-inbound:1234) + X1 --> |path = /vmesstcp| X2345(Xray-inbound:2345) + X1 --> |path = /vmessws| X3456(Xray-inbound:3456) + X1 --> |Весь остальной трафик| X1310(Xray-inbound:1310) + + X1234 --> X2 + X2345 --> X2 + X3456 --> X2 + + X1310 --> |Протокол = trojan| X2 + X1310 --> |Весь остальной трафик| N80(Nginx:80) + + N80:::nginxclass --> H(index.html) + + H:::nginxclass + classDef nginxclass fill:#FFFFDE +``` + +## 6. Заключение + +На этом обзор функции **"fallback"** в `Xray` завершен. Надеемся, что эта статья поможет вам лучше понять возможности `Xray`. + +## 7. Дополнительное задание + +Позвольте мне нагло оставить вам дополнительное задание: есть ли что-то, что можно оптимизировать в шаблоне [VLESS-TCP-XTLS-WHATEVER](https://github.com/XTLS/Xray-examples/blob/main/VLESS-TCP-XTLS-WHATEVER/), описанном в этой статье? + +Подсказка: автоматическое перенаправление HTTP на HTTPS. diff --git a/docs/ru/document/level-1/fallbacks-with-sni-resources/cf-api-token-permissions-for-acme.webp b/docs/ru/document/level-1/fallbacks-with-sni-resources/cf-api-token-permissions-for-acme.webp new file mode 100644 index 000000000..4d0fe53ba Binary files /dev/null and b/docs/ru/document/level-1/fallbacks-with-sni-resources/cf-api-token-permissions-for-acme.webp differ diff --git a/docs/ru/document/level-1/fallbacks-with-sni-resources/xray-dns-records.webp b/docs/ru/document/level-1/fallbacks-with-sni-resources/xray-dns-records.webp new file mode 100644 index 000000000..1924af70b Binary files /dev/null and b/docs/ru/document/level-1/fallbacks-with-sni-resources/xray-dns-records.webp differ diff --git a/docs/ru/document/level-1/fallbacks-with-sni-resources/xray-fallbacks.svg b/docs/ru/document/level-1/fallbacks-with-sni-resources/xray-fallbacks.svg new file mode 100644 index 000000000..df1260254 --- /dev/null +++ b/docs/ru/document/level-1/fallbacks-with-sni-resources/xray-fallbacks.svg @@ -0,0 +1,3 @@ + + +
Xray 监听
443 端口
Xray 监听...
正常入站
正常入站
回落
回落
VLESS 协议
VLESS 协议
其他协议/认证失败
其他协议/认证失败
Nginx 监听
5004 端口
Nginx 监听...
Xray 监听
5000 端口
Xray 监听...
Nginx 监听
5003 端口
Nginx 监听...
Nginx 监听
5001 端口
Nginx 监听...
默认、伪装
默认、伪装
blog.example.com
(HTTP/2)
blog.example.com...
blog.example.com
blog.example.com
example.com/vmessws
(Xray 其他入站协议)
example.com/vmessws...
Nginx 监听
80 端口
Nginx 监听...
HTTP 跳转到 HTTPS
HTTP 跳转到 HTTPS
Nginx 监听
5002 端口
Nginx 监听...
默认、伪装
(HTTP/2)
默认、伪装 (HTTP/2)
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/ru/document/level-1/fallbacks-with-sni.md b/docs/ru/document/level-1/fallbacks-with-sni.md new file mode 100644 index 000000000..860630574 --- /dev/null +++ b/docs/ru/document/level-1/fallbacks-with-sni.md @@ -0,0 +1,333 @@ +--- +title: SNI Fallback +--- + +# Маскировка и разделение трафика по доменам с помощью функции SNI Fallback + +VLESS - это очень легкий протокол, который, как и Trojan, не использует сложного шифрования и обфускации трафика. Вместо этого он, подобно тому, как искусный мастер кунг-фу скрывает свою силу, шифрует трафик с помощью протокола TLS, маскируя его под обычный HTTPS-трафик и позволяя ему беспрепятственно проходить через Великий китайский файрвол. Для лучшей маскировки от активного зондирования вместе с VLESS была представлена функция Fallbacks (резервирование). В этой статье мы рассмотрим, как использовать функцию Fallbacks входящего протокола VLESS в Xray совместно с Nginx или Caddy для реализации разделения трафика по доменам при обеспечении полной маскировки. + +## Сценарии использования + +Из-за XTLS Xray необходимо прослушивать порт 443, что создает проблему, если на сервере уже запущен веб-сайт - сайт либо не сможет работать, либо его придется запускать на другом порту, что нежелательно. Есть три способа решить эту проблему: + +- Xray прослушивает другие часто используемые порты (например, 22, 3389, 8443). + + Это самое простое решение, но оно не идеально. + +- Nginx или HAProxy прослушивает порт 443 и выполняет обратное проксирование на уровне L4 с разделением трафика по SNI, что позволяет использовать один порт для нескольких сервисов. + + Этот вариант более сложный и требует определенных знаний Nginx или HAProxy, поэтому мы не будем его здесь подробно рассматривать. + +- Xray прослушивает порт 443 и использует функцию Fallbacks для перенаправления трафика веб-сайта на Nginx или Caddy на основе SNI. + + Этот вариант имеет среднюю сложность и является тем, который мы рассмотрим в этом руководстве. + +## Что такое SNI + +**SNI** (Server Name Indication) - это расширение протокола TLS. Те, кто знаком с обратным проксированием, знают, что для правильной маршрутизации трафика по доменному имени необходимо следующее правило: + +```nginx +proxy_set_header Host имя_хоста; +``` + +Эта строка устанавливает HTTP-заголовок "Host" на определенное имя хоста. Зачем это нужно? Обычно у одного сервера один IP-адрес, но на нем может быть запущено несколько сайтов. Пользователи получают IP-адрес по доменному имени и обращаются к серверу, но как сервер определяет, какой именно сайт запрашивает пользователь? Для этого используются виртуальные хосты, основанные на имени. + +Когда веб-сервер получает запрос, он проверяет заголовок "Host" и направляет пользователя на нужный сайт. Однако, когда HTTP-трафик шифруется с помощью TLS, этот простой метод перестает работать. TLS-рукопожатие происходит до того, как сервер увидит какие-либо HTTP-заголовки, поэтому сервер не может использовать информацию из заголовка "Host", чтобы решить, какой сертификат предоставить, и тем более не может определить, к какому сайту обращается пользователь. + +SNI решает эту проблему, позволяя клиенту отправлять имя хоста как часть TLS-рукопожатия. Поэтому при использовании Nginx для обратного проксирования HTTPS-трафика необходимо добавить в конфигурацию `proxy_ssl_server_name on;`. В этом случае Nginx будет отправлять информацию SNI на проксируемый сервер, решая проблему неработающих виртуальных хостов по HTTPS. Кроме того, при использовании SNI можно получить доступ к нужному сайту, даже не указывая заголовок "Host". + +## Идея + +![Схема работы Xray Fallbacks](./fallbacks-with-sni-resources/xray-fallbacks.svg) + +После получения трафика на порт 443 Xray расшифровывает TLS и перенаправляет трафик с длиной первого пакета менее 18 байт, неверной версией протокола или ошибкой аутентификации на адрес, указанный в `dest`, на основе совпадения `name`, `path` или `alpn`. + +## Добавление DNS-записей + +![DNS-записи](./fallbacks-with-sni-resources/xray-dns-records.webp) + +Измените домен и IP-адрес в соответствии с вашей ситуацией. + +## Запрос TLS-сертификата + +Поскольку нам нужно разделять трафик по доменам с разными поддоменами, а wildcard-сертификат покрывает только домены между двумя точками (например, сертификат для `*.example.com` не будет действовать для `example.com` и `*.*.example.com`), нам нужно запросить wildcard-сертификат с [SAN](https://ru.wikipedia.org/wiki/Subject_Alternative_Name). Согласно информации на сайте Let's Encrypt[^1], для запроса wildcard-сертификата требуется проверка DNS-01. В этом руководстве мы рассмотрим, как запросить бесплатный TLS-сертификат Let's Encrypt с помощью [acme.sh](https://acme.sh) для домена, управляемого Cloudflare. Инструкции для других провайдеров DNS можно найти в [dnsapi · acmesh-official/acme.sh Wiki](https://github.com/acmesh-official/acme.sh/wiki/dnsapi). + +Сначала нужно создать API-токен на [панели управления Cloudflare](https://dash.cloudflare.com/profile/api-tokens). Параметры следующие: + +![Настройка прав доступа API-токена](./fallbacks-with-sni-resources/cf-api-token-permissions-for-acme.webp) + +Настройка прав доступа очень важна, остальные параметры можно оставить по умолчанию. + +После создания вы получите строку символов - это и есть ваш API-токен (`CF_Token`). Сохраните его в надежном месте, так как он больше не будет отображаться. + +::: tip Внимание +Следующие действия необходимо выполнять от имени пользователя root. Использование sudo может привести к ошибкам. +::: + +```bash +curl https://get.acme.sh | sh # Установка acme.sh +export CF_Token="sdfsdfsdfljlbjkljlkjsdfoiwje" # Установка переменной окружения с API-токеном +acme.sh --issue -d example.com -d *.example.com --dns dns_cf # Запрос сертификата с проверкой DNS-01 +mkdir /etc/ssl/xray # Создание каталога для хранения сертификата +acme.sh --install-cert -d example.com --fullchain-file /etc/ssl/xray/cert.pem --key-file /etc/ssl/xray/privkey.key --reloadcmd "chown nobody:nogroup -R /etc/ssl/xray && systemctl restart xray" # Установка сертификата в указанный каталог и настройка команды для автоматического перезапуска Xray после обновления сертификата +``` + +## Конфигурация Xray + +```json +{ + "log": { + "loglevel": "warning" + }, + "inbounds": [ + { + "port": 443, + "protocol": "vless", + "settings": { + "clients": [ + { + "id": "UUID", + "flow": "xtls-rprx-vision" + } + ], + "decryption": "none", + "fallbacks": [ + { + "name": "example.com", + "path": "/vmessws", + "dest": 5000, + "xver": 1 + }, + { + "dest": 5001, + "xver": 1 + }, + { + "alpn": "h2", + "dest": 5002, + "xver": 1 + }, + { + "name": "blog.example.com", + "dest": 5003, + "xver": 1 + }, + { + "name": "blog.example.com", + "alpn": "h2", + "dest": 5004, + "xver": 1 + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "tls", + "tlsSettings": { + "alpn": ["h2", "http/1.1"], + "certificates": [ + { + "certificateFile": "/etc/ssl/xray/cert.pem", + "keyFile": "/etc/ssl/xray/privkey.key" + } + ] + } + } + }, + { + "listen": "127.0.0.1", + "port": 5000, + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "UUID" + } + ] + }, + "streamSettings": { + "network": "ws", + "wsSettings": { + "acceptProxyProtocol": true, + "path": "/vmessws" + } + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ] +} +``` + +Эта конфигурация предназначена для Nginx. Обратите внимание на следующие детали: + +- О Proxy Protocol + + Proxy Protocol - это протокол, разработанный HaProxy для решения проблемы потери информации о клиенте при проксировании, часто используемый в цепочках прокси-серверов и обратных прокси. Традиционные методы решения этой проблемы, как правило, сложны и имеют много ограничений, в то время как Proxy Protocol очень прост - он просто добавляет пакет данных с информацией об исходном соединении (четверка "источник-назначение:порт") при передаче данных. + + У всего есть свои плюсы и минусы, и Proxy Protocol не исключение. + + - Если есть отправка, должен быть и прием, и наоборот. + - Один и тот же порт не может одновременно поддерживать соединения с данными Proxy Protocol и без них (например, разные виртуальные хосты (server) Nginx на одном порту, что по сути является следствием предыдущего пункта)[^2][^3]. + + Если вы столкнулись с ошибками, убедитесь, что ваша конфигурация соответствует этим условиям. + + Здесь мы используем Proxy Protocol, чтобы целевой сервер, на который перенаправляется трафик, получал реальный IP-адрес клиента. + + Кроме того, если в конфигурации входящего трафика Xray есть `"acceptProxyProtocol": true`, ReadV будет отключен. + +- О HTTP/2 + + Во-первых, порядок элементов в `inbounds.streamSettings.tlsSettings.alpn` важен: `h2` должен быть перед `http/1.1`, чтобы обеспечить совместимость при использовании HTTP/2. Обратный порядок приведет к тому, что HTTP/2 будет понижен до HTTP/1.1 во время согласования, делая конфигурацию недействительной. + + В приведенной выше конфигурации каждая запись fallback для Nginx разделена на две. Это связано с тем, что h2 - это обязательное зашифрованное соединение HTTP/2, что хорошо для безопасности передачи данных в Интернете, но не нужно внутри сервера. h2c же - это незашифрованное соединение HTTP/2, подходящее для этой среды. Однако Nginx не может одновременно прослушивать HTTP/1.1 и h2c на одном порту. Чтобы решить эту проблему, необходимо указать `alpn` (в разделе `fallbacks`, а не `tlsSettings`), чтобы сопоставить результаты согласования TLS ALPN. + + Рекомендуется указывать `alpn` только в двух случаях[^4]: + + - опустить + - `"h2"` + + Если вы используете Caddy, то вам не нужно так усложнять, поскольку он **может** одновременно прослушивать HTTP/1.1 и h2c на одном порту. Изменения в конфигурации следующие: + + ```json + { + "fallbacks": [ + { + "name": "example.com", + "path": "/vmessws", + "dest": 5000, + "xver": 1 + }, + { + "dest": 5001, + "xver": 1 + }, + { + "name": "blog.example.com", + "dest": 5002, + "xver": 1 + } + ] + } + ``` + +## Конфигурация Nginx + +Nginx будет установлен из официального репозитория. + +```bash +sudo apt install curl gnupg2 ca-certificates lsb-release +echo "deb [arch=amd64] http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \ + | sudo tee /etc/apt/sources.list.d/nginx.list +curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add - +sudo apt update +sudo apt install nginx +``` + +Удалите `/etc/nginx/conf.d/default.conf` и создайте `/etc/nginx/conf.d/fallbacks.conf` со следующим содержимым: + +```nginx +set_real_ip_from 127.0.0.1; +real_ip_header proxy_protocol; + +server { + listen 127.0.0.1:5001 proxy_protocol default_server; + listen 127.0.0.1:5002 proxy_protocol default_server http2; + + location / { + root /srv/http/default; + } +} + +server { + listen 127.0.0.1:5003 proxy_protocol; + listen 127.0.0.1:5004 proxy_protocol http2; + + server_name blog.example.com; + + location / { + root /srv/http/blog.example.com; + } +} + +server { + listen 80; + return 301 https://$host$request_uri; +} +``` + +## Конфигурация Caddy + +Инструкции по установке Caddy можно найти в [Install — Caddy Documentation](https://caddyserver.com/docs/install). + +Чтобы Caddy мог получать реальный IP-адрес посетителя, необходимо скомпилировать Caddy с модулем Proxy Protocol. Рекомендуется скомпилировать его прямо на сайте Caddy. + +```bash +sudo curl -o /usr/bin/caddy "https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com%2Fmastercactapus%2Fcaddy2-proxyprotocol&idempotency=79074247675458" +sudo chmod +x /usr/bin/caddy +``` + +Просто замените существующий бинарный файл. + +::: tip +Рекомендуется сначала установить Caddy, следуя инструкциям на официальном сайте, а затем заменить бинарный файл. Это избавит от необходимости настраивать запуск сервиса вручную. +::: + +Отредактируйте `/etc/caddy/Caddyfile`: + +```Caddyfile +{ + servers 127.0.0.1:5001 { + listener_wrappers { + proxy_protocol + } + protocol { + allow_h2c + } + } + servers 127.0.0.1:5002 { + listener_wrappers { + proxy_protocol + } + protocol { + allow_h2c + } + } +} + +:5001 { + root * /srv/http/default + file_server + log + bind 127.0.0.1 +} + +http://blog.example.com:5002 { + root * /srv/http/blog.example.com + file_server + log + bind 127.0.0.1 +} + +:80 { + redir https://{host}{uri} permanent +} +``` + +## Ссылки + +1. [Указание имени сервера - Википедия](https://ru.wikipedia.org/wiki/Указание_имени_сервера) +2. [Home · acmesh-official/acme.sh Wiki](https://github.com/acmesh-official/acme.sh/wiki) +3. [HTTP/2 - Википедия](https://ru.wikipedia.org/wiki/HTTP/2) + +## Примечания + +[^1]: [Часто задаваемые вопросы - Let's Encrypt - бесплатные SSL/TLS сертификаты](https://letsencrypt.org/ru/docs/faq/) +[^2]: [Proxy Protocol - HAProxy Technologies](https://www.haproxy.com/blog/haproxy/proxy-protocol/) +[^3]: [proxy protocol 介绍及 nginx 配置 - 简书](https://www.jianshu.com/p/cc8d592582c9) +[^4]: [v2fly-github-io/vless.md at master · rprx/v2fly-github-io](https://github.com/rprx/v2fly-github-io/blob/master/docs/config/protocols/vless.md) + + diff --git a/docs/ru/document/level-1/routing-lv1-img01-trio.png b/docs/ru/document/level-1/routing-lv1-img01-trio.png new file mode 100644 index 000000000..3f3e27ae8 Binary files /dev/null and b/docs/ru/document/level-1/routing-lv1-img01-trio.png differ diff --git a/docs/ru/document/level-1/routing-lv1-part1.md b/docs/ru/document/level-1/routing-lv1-part1.md new file mode 100644 index 000000000..c5467b120 --- /dev/null +++ b/docs/ru/document/level-1/routing-lv1-part1.md @@ -0,0 +1,422 @@ +# Краткий обзор функции маршрутизации (routing) (часть 1) + +Если "мощность" Xray в основном заключается в его высокой скорости и широкой совместимости, то его "гибкость" в первую очередь связана с продуманной функцией **"routing" (маршрутизация)**. В этой статье мы кратко рассмотрим логику этой функции и способы ее применения. + +## 1. Знакомство с тремя братьями-маршрутизаторами + +Чтобы понять маршрутизацию, нужно понимать, что для ее полноценной работы нужны три компонента: 1. **входящий трафик** (inbound); 2. **маршрутизация** (routing); 3. **исходящий трафик** (outbound). + +![Три брата-маршрутизатора](./routing-lv1-img01-trio.png) + +Три брата, поклявшиеся в верности, не обязательно родились в один день, но должны быть готовы умереть в один день. + +Поэтому запомните: если один из элементов работает неправильно, функция маршрутизации может не работать. + +Поскольку маршрутизация очень гибкая, чтение только технической документации может вас запутать, поэтому в этой статье мы будем использовать конкретные примеры, чтобы объяснить все пошагово. + +::: warning +Функция маршрутизации настолько гибкая, что примеры в этой статье приведены только для объяснения соответствующих концепций. На практике, пожалуйста, корректируйте их в соответствии с вашими потребностями. +::: + +## 2. Основы: "Братья едины" + +На рисунке ниже показан пример, когда входящий трафик от приложения поступает на `Xray` на клиенте, маршрутизируется на исходящий трафик и отправляется на VPS. + +```mermaid + graph LR; + + S(Данные приложения) .-> I[Входящий трафик] + + subgraph Xray + I --> R[Маршрутизация] --> O[Исходящий трафик] + end + + O .-> V(VPS) + + V:::greyclass + S:::greyclass + R:::routingclass + classDef greyclass fill:#C0C0C0 + classDef routingclass fill:#FFFFDE + +``` + +Давайте проанализируем каждый шаг: + +### 2.1 Входящий трафик + +::: tip +**Входящий трафик (inbound):** это то, как трафик попадает в `Xray`. +::: + +Пример конфигурации входящего трафика ниже означает, что данные поступают в `Xray` по протоколу `socks` через порт `10808` с локального адреса `127.0.0.1`. `Xray` присваивает этому входящему трафику имя `inbound-10808` с помощью `[tag]`. + +```json +{ + "inbounds": [ + { + "tag": "inbound-10808", + "protocol": "socks", + "listen": "127.0.0.1", + "port": 10808, + "settings": { + "udp": true + } + } + ] +} +``` + +### 2.2 Исходящий трафик + +::: tip +**Исходящий трафик (outbound):** это то, как трафик выходит из `Xray`. +::: + +Пример конфигурации исходящего трафика ниже означает, что данные отправляются на соответствующий VPS по протоколу `VLESS` с использованием `tcp + xtls` и других параметров. `Xray` присваивает этому исходящему трафику имя `proxy-out-vless` с помощью `[tag]`. + +```json +{ + "outbounds": [ + { + "tag": "proxy-out-vless", + "protocol": "vless", + "settings": { + "vnext": [ + { + "address": "a-name.yourdomain.com", + "port": 443, + "users": [ + { + "id": "uuiduuid-uuid-uuid-uuid-uuiduuiduuid", + "flow": "xtls-rprx-vision", + "encryption": "none", + "level": 0 + } + ] + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "tls", + "tlsSettings": { + "serverName": "a-name.yourdomain.com", + "allowInsecure": false, + "fingerprint": "chrome" + } + } + } + ] +} +``` + +### 2.3 Маршрутизация + +::: tip +**Маршрутизация (routing):** это соединение канала между **входящим** и **исходящим** трафиком с помощью определенного **условия**. +::: + +Пример конфигурации маршрутизации ниже означает, что весь трафик, поступающий в `Xray` через входящий трафик с `[tag]="inbound-10808"`, **на 100%** перенаправляется на исходящий трафик с `[tag]="proxy-out-vless"` без разделения или каких-либо других действий. + +```json +{ + "routing": { + "domainStrategy": "AsIs", + "rules": [ + { + "type": "field", + "inboundTag": ["inbound-10808"], + "outboundTag": "proxy-out-vless" + } + ] + } +} +``` + +Таким образом, мы реализовали очень простое правило, описанное в начале: **входящий трафик от приложения поступает на `Xray` на клиенте, маршрутизируется на исходящий трафик и отправляется на VPS**. + +### 2.4 Анализ параметров конфигурации маршрутизации: критерии фильтрации трафика + +Обратите внимание на конфигурацию маршрутизации. Мы видим несколько новых терминов: + +1. `"domainStrategy": "AsIs"` +2. `“rules”` +3. `"type": "field"` +4. `"inboundTag": ["inbound-10808"]` +5. `"outboundTag": "proxy-out-vless"` + +Пока оставим `domainStrategy` в стороне и кратко объясним остальные: + +| Название параметра | Значение параметра | Описание параметра | +| :---------------- | :----------------- | :------------------ | +| `“rules”` |                                                     | Внутри этого параметра находятся подробные настройки **правил маршрутизации**. | +| `"type"` | `"field"` | На данный момент этот параметр не имеет особого значения, но его нельзя опускать, поэтому просто укажите его. | +| `"inboundTag"` | `["inbound-10808"]` | **Критерий** фильтрации трафика - это **тег входящего трафика**, а **условие** сейчас только одно: **источник входящего трафика - `inbound-10808`**. | +| `"outboundTag"` | `"proxy-out-vless"` | Если указанное выше условие фильтрации выполняется (т.е. входящий трафик имеет `[tag]="inbound-10808"`), `Xray` направит трафик на исходящий трафик с `[tag]="proxy-out-vless"`. | + +В этом примере у нас есть только один входящий трафик с `"inboundTag" = "inbound-10808"` и один исходящий трафик с `[tag]="proxy-out-vless"`. Поэтому, согласно приведенному выше правилу маршрутизации, весь трафик, поступающий в `Xray` через порт `10808`, **на 100%** соответствует условиям фильтрации, выбирается модулем маршрутизации и перенаправляется на единственный исходящий трафик. + +Таким образом, **входящий трафик**, **маршрутизация** и **исходящий трафик** уже могут работать вместе. Конечно, в данном случае 100% перенаправление не имеет особого смысла. Давайте посмотрим, какие преимущества может дать такой механизм разделения труда. + +## 3. Первые шаги: "Разделение мира на три части" - "Разделение по домену" + +> `[geosite.dat]` + +```mermaid + graph LR; + + S(Данные приложения) .-> I[Входящий трафик] + + subgraph Xray + I --> R[Маршрутизация] -- "geosite:category-ads-all" --> O1[block] + R[Маршрутизация] -- "geosite:cn" --> O2[direct] + R[Маршрутизация] -- "geosite:geolocation-!cn" --> O3[proxy] + + end + + O2 .-> D(Внутренний сервер) + O3 .-> V(VPS) + + O1:::redclass + V:::greyclass + S:::greyclass + R:::routingclass + + classDef redclass fill:#FF0000 + classDef greyclass fill:#C0C0C0 + classDef routingclass fill:#FFFFDE,stroke:#000000 + +``` + +Эта конфигурация реализует самый простой и распространенный (используемый в нашем руководстве) набор правил маршрутизации: + +1. Блокировка рекламы (`[block]`) +2. Прямое подключение к внутренним ресурсам (`[direct]`) +3. Перенаправление трафика на VPS (`[proxy]`) + +::: warning Внимание +В нашем руководстве прямое подключение настроено для **внутренних доменов**, **внутренних IP-адресов** и **локальных IP-адресов**. Здесь мы рассмотрим только **внутренние домены**. +::: + +### 3.1 Входящий трафик + +Оставляем `inbound-10808` из предыдущего примера без изменений. + +### 3.2 Исходящий трафик + +В предыдущем примере у нас уже есть исходящий трафик `[proxy]` - `"proxy-out-vless"`, поэтому он остается без изменений. Очевидно, что нам нужно добавить два новых типа исходящего трафика: `[block]` и `[direct]`, как показано ниже: + +```json +{ + "outbounds": [ + { + "tag": "proxy-out-vless" + // ... ... + }, + { + "tag": "block", + "protocol": "blackhole" + }, + { + "tag": "direct-out", + "protocol": "freedom" + } + ] +} +``` + +Приведенная выше конфигурация означает: + +1. Конфигурация исходящего трафика `[proxy-out-vless]` из предыдущего примера остается без изменений. +2. Добавлен протокол **`blackhole` (черная дыра)**. Трафик, отправляемый через этот протокол, попадает в "черную дыру" внутри `Xray` и не может выйти наружу, что фактически блокирует его (`[block]`). +3. Добавлен протокол **`freedom` (свобода)**. Трафик, отправляемый через этот протокол, свободно покидает `Xray` и следует к своему первоначальному адресу, как будто его и не было, что фактически означает прямое подключение (`[direct]`). (Здесь я назвал его `[direct-out]`, чтобы подчеркнуть, что это исходящий трафик). + +### 3.3 Маршрутизация + +Настало время для чуда! Мы можем связать все это вместе с помощью конфигурации **маршрутизации**! + +```json +{ + "routing": { + "domainStrategy": "AsIs", + "rules": [ + { + "type": "field", + "domain": ["geosite:category-ads-all"], + "outboundTag": "block" + }, + { + "type": "field", + "domain": ["geosite:cn"], + "outboundTag": "direct-out" + }, + { + "type": "field", + "domain": ["geosite:geolocation-!cn"], + "outboundTag": "proxy-out-vless" + } + ] + } +} +``` + +Чтобы понять этот файл конфигурации, нам нужно кратко объяснить несколько новых параметров: + +- `"domain": ["geosite:category-ads-all"]` +- `"domain": ["geosite:cn"]` +- `"domain": ["geosite:geolocation-!cn"]` + +### 3.4 Краткий обзор файла доменов: `geosite.dat` + +На самом деле, вы, вероятно, уже догадались по названиям этих параметров: + +- `"domain"`: **критерий** фильтрации трафика в этот раз - это **доменное имя** (а не тег входящего трафика). +- `"geosite"`: `Xray` будет искать **соответствующие доменные имена** в файле `geosite.dat`. +- `"category-ads-all"`: **все рекламные домены**, указанные в этом файле. +- `"cn"`: **китайские домены**, указанные в этом файле. +- `"geolocation-!cn"`: **не китайские домены**, указанные в этом файле. + +С учетом этих пояснений конфигурацию из пункта 3.3 можно перевести так: + +1. Трафик от приложений, пытающихся получить доступ к иностранным доменам (`"domain": "geolocation-!cn"`), перенаправляется на VPS через исходящий трафик `[proxy-out-vless]`. +2. Трафик от приложений, пытающихся получить доступ к иностранным рекламным доменам (`"domain": "geosite:category-ads-all"`), блокируется (`[block]`) путем перенаправления в "черную дыру". +3. Трафик от приложений, пытающихся получить доступ к китайским доменам (`"domain": "geosite:cn"`), отправляется напрямую (`[direct-out]`). + +Вот так проявляются преимущества **функции маршрутизации**. + +### 3.5 Так что же такое `geosite.dat`? Разве у нас нет `GFWList`? + +Представьте, что в мире миллионы доменов. Если бы нам приходилось вручную собирать и вводить каждый домен для каждого правила маршрутизации, основанного на доменном имени, это было бы крайне неэффективно! + +А если бы все домены относились только к одному типу и могли быть обработаны только одним из трех способов: `[direct], [proxy], [block]`, это было бы очень неудобно! + +Как Гуань Юю нужен его Цинлун Яньюэдао, так и **функции маршрутизации** нужен свой волшебный меч - файл `geosite.dat`, который представляет собой готовый к использованию **список категорий доменов**. Он позволяет пользователям легко вызывать любую подкатегорию с помощью формата `geosite:xxx` и настраивать правила маршрутизации в соответствии со своими потребностями. + +Такая модульная структура обеспечивает гораздо большую гибкость, чем традиционный список заблокированных доменов [`GFWList`](https://github.com/gfwlist/gfwlist). Например, вы можете указать, что домены Apple (`geosite:apple`) и домены, связанные с iCloud (`geosite:icloud`), должны проксироваться (`[proxy]`), а домены обновлений Apple (`geosite:apple-update`) должны подключаться напрямую (`[direct]`) для максимальной скорости загрузки. + +::: warning +**Внимание:** на данный момент существует несколько вариантов файла `geosite.dat`: + +- Изначально, когда `Victoria Raymond` активно занималась проектом `Project V`, она предоставляла соответствующий проект [`domain-list-community`](https://github.com/v2ray/domain-list-community), который использовался для сбора, хранения и классификации часто используемых типов доменов. +- После того, как Виктория внезапно исчезла, и разработка `Project V` приостановилась, сообщество `v2fly` продолжило поддерживать и обновлять свою версию [`domain-list-community`](https://github.com/v2fly/domain-list-community). +- В то же время [@Loyalsoldier](Loyalsoldier) ведет свой собственный, модифицированный и расширенный файл правил маршрутизации [v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat), который предлагает множество различных вариантов и логики классификации. +- Кроме того, команда `Project X` планирует в будущем создать и поддерживать файл правил маршрутизации [Xray-rules-dat](https://github.com/XTLS/Xray-rules-dat), который будет лучше подходить для использования с `Xray`. ~~(Как видите, папка уже создана, так что это дело времени)~~ + +Вы даже можете создать свой собственный файл `geosite` и подключить его к `Xray`, но это выходит за рамки данной статьи, поэтому мы не будем на этом останавливаться. + +Если вы обнаружите, что некоторые домены не классифицированы должным образом, пожалуйста, создайте issue или отправьте pull request в один из перечисленных выше проектов! Поддерживайте сообщество - каждый за всех, и все за одного! + +::: + +### 3.6 Секретное оружие: скрытое правило маршрутизации + +На самом деле, если вы внимательно посмотрите на приведенные выше правила, то заметите одну проблему: все наши правила определяют только то, **куда** следует перенаправлять входящий трафик, **если он соответствует определенному условию**. Но что произойдет, если файл `geosite.dat` неполный, и наш входящий трафик **не соответствует ни одному условию**? Как поступит `Xray`? + +::: warning Внимание +Если вы думаете, что **если условие не выполняется, то соединение не будет установлено**, то вам нужно подумать еще раз. Соединение будет разорвано только в том случае, если указано правило `[block]`, которое перенаправляет трафик в "черную дыру" (`blackhole`). +::: + +На самом деле, чтобы избежать путаницы из-за неполных правил маршрутизации, `Xray` предоставляет скрытое правило: **если входящий трафик не соответствует ни одному условию, он перенаправляется на первый исходящий трафик**. + +Таким образом, ни один трафик не будет потерян. Поэтому важно поместить ваш самый надежный исходящий трафик на **первое место**, чтобы он служил вам верным стражем. + +### 3.7 Снова смотрим на карту "трех царств" + +Поскольку в предыдущем примере мы поместили `[proxy-out-vless]` на первое место в списке исходящего трафика, при срабатывании скрытого правила трафик будет перенаправляться на удаленный VPS по протоколу `VLESS`. Таким образом, полная логика работы `Xray` выглядит следующим образом: + +```mermaid + graph LR; + + S(Данные приложения) .-> I[Входящий трафик] + + subgraph Xray + I --> R[Маршрутизация] -- "geosite:category-ads-all" --> O1[block] + R[Маршрутизация] -- "geosite:cn" --> O2[direct] + R[Маршрутизация] -- "geosite:geolocation-!cn" --> O3[proxy] + R[Маршрутизация] -. "Трафик, не соответствующий ни одному правилу" .-> O4[Первый исходящий трафик] + + end + + O2 .-> D(Внутренний сервер) + O3 .-> V(VPS) + O4 .-> V(VPS) + + O1:::redclass + V:::greyclass + S:::greyclass + R:::routingclass + + classDef redclass fill:#FF0000 + classDef greyclass fill:#C0C0C0 + classDef routingclass fill:#FFFFDE,stroke:#000000 + +``` + +Фактически, это и есть то, что традиционно называется **"проксирование по умолчанию, прямой доступ к внутренним сайтам по белому списку"**. + +## 4. "Разделение мира на три части" - "Битва Вэй и Шу" + +Теперь, когда вы знаете о скрытом правиле маршрутизации по умолчанию (**"если входящий трафик не соответствует ни одному условию, он перенаправляется на первый исходящий трафик"**), вы должны понимать, что то, будет ли **проксирование** или **прямое подключение** основным режимом работы, зависит от того, какой исходящий трафик стоит на первом месте! + +На предыдущем шаге мы настроили правило **"проксирование по умолчанию, прямой доступ к внутренним сайтам по белому списку"**. Теперь, чтобы получить правило **"прямое подключение по умолчанию, проксирование иностранных сайтов по белому списку"**, нам просто нужно **поместить правило прямого подключения на первое место**. + +Это очень просто, не правда ли? + +```json +{ + "outbounds": [ + { + "tag": "direct-out", + "protocol": "freedom" + }, + { + "tag": "proxy-out-vless" + // ... ... + }, + { + "tag": "block", + "protocol": "blackhole" + } + ] +} +``` + +Теперь правила маршрутизации выглядят так: + +```mermaid + graph LR; + + S(Данные приложения) .-> I[Входящий трафик] + + subgraph Xray + I --> R[Маршрутизация] -- "geosite:category-ads-all" --> O1[block] + R[Маршрутизация] -- "geosite:geolocation-!cn" --> O3[proxy] + R[Маршрутизация] -- "geosite:cn" --> O2[direct] + R[Маршрутизация] -. "Трафик, не соответствующий ни одному правилу" .-> O4[Первый исходящий трафик] + + end + + O2 .-> D(Внутренний сервер) + O3 .-> V(VPS) + O4 .-> D + + O1:::redclass + V:::greyclass + S:::greyclass + R:::routingclass + classDef redclass fill:#FF0000 + classDef greyclass fill:#C0C0C0 + classDef routingclass fill:#FFFFDE,stroke:#000000 + +``` + +В этом и заключается гибкость функции маршрутизации - вы можете свободно менять порядок правил для достижения различных результатов. + +На этом мы закончили объяснение того, **как использовать файл `geosite.dat` для разделения сетевого трафика по доменному имени с помощью правил маршрутизации**. + +## 5. Покорение новых высот - Различные условия сопоставления маршрутов + +Пожалуйста, убедитесь, что вы хорошо усвоили материал, изложенный выше, поскольку это основа для понимания принципов работы **функции маршрутизации**. Имея эту базу, мы можем двигаться дальше и рассмотреть более подробные параметры конфигурации и условия сопоставления. + +После того, как вы прочитаете следующий раздел, вы сможете свободно настраивать свои собственные правила маршрутизации! Так чего же мы ждем? Давайте перейдем к [части 2](./routing-lv1-part2.md)! diff --git a/docs/ru/document/level-1/routing-lv1-part2.md b/docs/ru/document/level-1/routing-lv1-part2.md new file mode 100644 index 000000000..1ff0f536b --- /dev/null +++ b/docs/ru/document/level-1/routing-lv1-part2.md @@ -0,0 +1,434 @@ +# Краткий обзор функции маршрутизации (routing) (часть 2) + +Добро пожаловать на продолжение изучения **функции маршрутизации** в `Xray`! + +В [части 1](./routing-lv1-part1.md) мы разобрались с логикой работы **функции маршрутизации** и настроили простое разделение трафика по домену на основе файла `geosite.dat`. + +Как уже было сказано, разделение по домену — это лишь верхушка айсберга возможностей **функции маршрутизации**. Давайте посмотрим, что еще, кроме домена, можно использовать в качестве критерия для разделения трафика! + +## 5. Покорение новых высот - Различные условия сопоставления маршрутов + +> `[домен], [IP], [протокол], etc.` + +Разделение по домену уже позволяет нам в общих чертах разделить сетевой трафик. Почему **в общих чертах**? + +Потому что, хотя **"разделение мира на три части"** — это правильная стратегия, ее реализация только с помощью **доменов** имеет множество недостатков, например: + +1. После прочтения нашего руководства я зарегистрировал новый домен `proxy.yourdomain.com` для своего VPS и хочу, чтобы трафик на него всегда проксировался. Есть ли он в `geosite.dat`? +2. У меня есть еще один домен `direct.yourdomain.com`, и я хочу, чтобы трафик на него всегда шел напрямую. Есть ли он в `geosite.dat`? +3. Правильно ли настроен прямой доступ для локального трафика на `127.0.0.1` (например, `docker`)? +4. Правильно ли настроен прямой доступ для трафика в моей локальной сети `192.168.*.*` (например, роутер, NAS)? +5. Правильно ли настроен прямой доступ для моих DNS-запросов к внутренним DNS-серверам (например, `223.5.5.5`)? +6. Правильно ли настроен прокси для моих DNS-запросов к внешним DNS-серверам (например, `1.1.1.1`)? +7. Правильно ли настроен прямой доступ для других внутренних сайтов, у которых нет доменного имени, а только IP-адрес, как у внутренних DNS-серверов? +8. Правильно ли настроен прокси для других внешних сайтов, у которых нет доменного имени, а только IP-адрес, как у внешних DNS-серверов? +9. Как настроить принудительное прямое подключение для торрент-трафика, который, хотя и поступает извне, может привести к блокировке VPS при проксировании? +10. ...... + +Я говорю, что разделение по домену имеет много недостатков, потому что файл `geosite.dat` содержит только ограниченный набор часто используемых доменов. Другими словами, полагаясь только на него, мы: + +- не сможем сопоставить новые домены, которых нет в файле; +- не сможем сопоставить правила на основе IP-адресов; +- не сможем сопоставить правила на основе сетевых протоколов. + +::: warning +Давайте вспомним, что происходит, когда эти условия не выполняются? Верно, срабатывает скрытое правило маршрутизации: **трафик перенаправляется на первый исходящий трафик**. Это означает, что: + +- если вашим первым исходящим трафиком является `[direct-out]`: **все, что должно идти напрямую, будет работать правильно, а все, что должно проксироваться, будет работать неправильно**; +- если вашим первым исходящим трафиком является `[proxy-out-vless]`: **все, что должно проксироваться, будет работать правильно, а все, что должно идти напрямую, будет работать неправильно**. +::: + +Поэтому нам нужен способ, который позволит нам получить и то, и другое. Существует ли такой способ? **Конечно, существует!** Нам просто нужны дополнительные **критерии сопоставления** помимо **домена**. + +### 5.1 Разделение по определенному домену: `[domain], [full]` и т. д. + +1. Для сопоставления поддомена, например `a-name.yourdomain.com`, мы используем `full: "a-name.yourdomain.com"`. +2. **Проблемы 1** и **2**, описанные выше, можно решить, указав исходящий трафик `[proxy-out-vless]` для `proxy.yourdomain.com` и исходящий трафик `[direct-out]` для `direct.yourdomain.com`. +3. Для сопоставления всех поддоменов `yourdomain.com` мы используем `domain: "yourdomain.com"`. +4. Эти два правила могут быть независимыми, чтобы настроить прямое подключение для одних поддоменов и проксирование для других. +5. Кроме того, `[domain]` поддерживает сопоставление с помощью регулярных выражений. Подробнее см. [документацию по модулю маршрутизации](../../../config/base/routing/). + +Конфигурация выглядит следующим образом: + +```json +{ + "routing": { + "domainStrategy": "AsIs", + "rules": [ + // Прямое подключение для определенного поддомена + { + "type": "field", + "domain": ["full:direct.yourdomain.com"], + "outboundTag": "direct-out" + }, + // Проксирование для определенного поддомена + { + "type": "field", + "domain": ["full:proxy.yourdomain.com"], + "outboundTag": "proxy-out-vless" + }, + // Проксирование для всех поддоменов + { + "type": "field", + "domain": ["yourdomain.com"], + "outboundTag": "proxy-out-vless" + } + ] + } +} +``` + +### 5.2 Разделение по IP-адресам из файла: `geoip.dat` + +Подобно файлу правил `geosite.dat`, у нас есть файл правил `geoip.dat`, который предоставляет **готовый к использованию список категорий IP-адресов**. Он позволяет пользователям легко вызывать любую подкатегорию с помощью формата `geoip:xxx` и настраивать правила маршрутизации в соответствии со своими потребностями. + +1. Чтобы решить **проблемы 3** и **4**, описанные выше, мы используем категорию `geoip:private` и указываем исходящий трафик `[direct-out]`. +2. Чтобы решить **проблему 7**, мы используем категорию `geoip:cn` и указываем исходящий трафик `[direct-out]`. +3. Чтобы решить **проблему 8**, мы используем скрытое правило маршрутизации, поскольку в `geoip` нет категории "не китайские IP-адреса" (потому что это равносильно сбору всех IP-адресов в мире). Другими словами, мы размещаем `[proxy-out-vless]` на первом месте в списке исходящего трафика. + +Конфигурация выглядит следующим образом: + +```json +{ + "routing": { + "domainStrategy": "AsIs", + "rules": [ + // Прямое подключение для локальных и внутренних IP-адресов + { + "type": "field", + "ip": ["geoip:private"], + "outboundTag": "direct-out" + }, + // Прямое подключение для китайских IP-адресов + { + "type": "field", + "ip": ["geoip:cn"], + "outboundTag": "direct-out" + } + ] + } +} +``` + +### 5.3 Разделение по определенному IP-адресу + +1. Чтобы решить **проблему 5**, мы используем `ip: "223.5.5.5"` и указываем исходящий трафик `[direct-out]`. +2. Чтобы решить **проблему 6**, мы используем `ip: "1.1.1.1"` и указываем исходящий трафик `[proxy-out-vless]`. + +Конфигурация выглядит следующим образом: + +```json +{ + "routing": { + "domainStrategy": "AsIs", + "rules": [ + // Прямое подключение для определенного IP-адреса + { + "type": "field", + "ip": ["223.5.5.5"], + "outboundTag": "direct-out" + }, + // Проксирование для определенного IP-адреса + { + "type": "field", + "ip": ["1.1.1.1"], + "outboundTag": "proxy-out-vless" + } + ] + } +} +``` + +### 5.4 Разделение по типу протокола: `[protocol]` и т. д. + +1. Чтобы решить **проблему 9**, мы используем `"protocol": ["bittorrent"]` и указываем исходящий трафик `[direct-out]`. + +::: tip +Вам нужно включить `sniffing` во входящем прокси, чтобы использовать этот метод разделения. +::: + +```json +{ + "routing": { + "domainStrategy": "AsIs", + "rules": [ + // Прямое подключение для торрент-трафика + { + "type": "field", + "protocol": ["bittorrent"], + "outboundTag": "direct-out" + } + ] + } +} +``` + +### 5.5 Разделение по другим критериям + +На данный момент мы рассмотрели лишь малую часть возможностей разделения трафика **функции маршрутизации**! Она поддерживает множество других критериев сопоставления! Я перечислю их здесь: + +Критерии, которые мы уже рассмотрели: + +- `inboundTag` +- `domain` +- `ip` +- `protocol` + +Критерии, которые мы еще не рассмотрели: + +- `port` +- `sourcePort` +- `network` +- `source` +- `user` +- `attrs` + +Однако, это слишком много информации для уровня 1, поэтому, если вам нужны эти сложные критерии, пожалуйста, внимательно изучите [документацию по модулю маршрутизации](../../config/base/routing/) самостоятельно! Если у вас возникнут вопросы, задавайте их в Telegram-группе! + +## 6. "Начало новой эры": обзор правил маршрутизации + +К настоящему моменту у нас накопился внушительный набор правил маршрутизации. Чтобы избежать путаницы, давайте проведем полный обзор. + +::: warning Внимание +Правила маршрутизации обрабатываются **последовательно сверху вниз**. Поэтому я рекомендую следующий порядок: + +`[1-block] --> [2-direct] --> [3-proxy] --> [4-first-outbound]` +::: + +```json +{ + "routing": { + "domainStrategy": "AsIs", + "rules": [ + // [1-block Блокировка рекламы] + // 1.1 Блокировка рекламных доменов + { + "type": "field", + "domain": ["geosite:category-ads-all"], + "outboundTag": "block" + }, + // [2-direct Прямое подключение к внутренним ресурсам] + // 2.1 Прямое подключение для китайских доменов и определенного поддомена + { + "type": "field", + "domain": ["geosite:cn", "full:direct.yourdomain.com"], + "outboundTag": "direct-out" + }, + // 2.2 Прямое подключение для локальных, внутренних, китайских и определенных IP-адресов + { + "type": "field", + "ip": ["geoip:private", "geoip:cn", "223.5.5.5"], + "outboundTag": "direct-out" + }, + // 2.3 Прямое подключение для торрент-трафика + { + "type": "field", + "protocol": ["bittorrent"], + "outboundTag": "direct-out" + }, + // [3-proxy Проксирование для внешних ресурсов] + // 3.1 Проксирование для иностранных доменов, определенного поддомена и всех поддоменов + { + "type": "field", + "domain": [ + "geosite:geolocation-!cn", + "full:proxy.yourdomain.com", + "yourdomain.com" + ], + "outboundTag": "proxy-out-vless" + }, + // 3.2 Проксирование для определенного IP-адреса + { + "type": "field", + "ip": ["1.1.1.1"], + "outboundTag": "proxy-out-vless" + } + // [4-default-routing Первый исходящий трафик] + // Трафик, не соответствующий ни одному правилу, обрабатывается первым исходящим трафиком + ] + } +} +``` + +Теперь правила маршрутизации выглядят так: + +```mermaid + graph LR; + + S(Данные приложения) .-> I[Входящий трафик] + + subgraph Xray + I --> R[Маршрутизация] -- "geosite:category-ads-all" --> O1[block] + + R[Маршрутизация] -- "geosite:cn" --> O2[direct] + R[Маршрутизация] -- "direct.yourdomain.com" --> O2[direct] + R[Маршрутизация] -- "geoip:private" --> O2[direct] + R[Маршрутизация] -- "geoip:cn" --> O2[direct] + R[Маршрутизация] -- "ip:223.5.5.5" --> O2[direct] + R[Маршрутизация] -- "protocol:bittorrent" --> O2[direct] + + R[Маршрутизация] -- "geosite:geolocation-!cn" --> O3[proxy] + R[Маршрутизация] -- "proxy.yourdomain.com" --> O3[proxy] + R[Маршрутизация] -- "*.yourdomain.com" --> O3[proxy] + R[Маршрутизация] -- "ip:1.1.1.1" --> O3[proxy] + + R[Маршрутизация] -. "Трафик, не соответствующий ни одному правилу" .-> O4[Первый исходящий трафик] + + end + + O2 .-> D(Внутренний сервер) + O3 .-> V(VPS) + + O1:::redclass + V:::greyclass + S:::greyclass + R:::routingclass + classDef redclass fill:#FF0000 + classDef greyclass fill:#C0C0C0 + classDef routingclass fill:#FFFFDE,stroke:#000000 + +``` + +Будет ли первым исходящим трафиком `[direct-out]` или `[proxy-out-vless]`, зависит от ваших потребностей. + +## 7. Распространенные ошибки в конфигурации маршрутизации + +Обратите внимание, что каждое правило маршрутизации, которое я привел выше, имеет **только один критерий сопоставления**. Это необходимо для того, чтобы правило работало корректно. Частая ошибка новичков при настройке маршрутизации заключается в том, что они **указывют несколько критериев сопоставления в одном правиле, что делает его недействительным**. + +Например, предположим, что нужно настроить следующее: + +1. Прямое подключение для собственного домена `direct.yourdomain.com`. +2. Прямое подключение для DNS-запросов к внутренним DNS-серверам (например, `223.5.5.5`). + +### 7.1 Неправильный пример + +Чтобы достичь этой цели, новичок может написать следующее правило маршрутизации: + +```json +{ + "routing": { + "domainStrategy": "AsIs", + "rules": [ + { + "type": "field", + "ip": ["223.5.5.5"], + "domain": ["full:direct.yourdomain.com"], + "outboundTag": "direct-out" + } + ] + } +} +``` + +Видите ли вы здесь ошибку? На первый взгляд, кажется, все правильно? + +::: warning Внимание +**В одном правиле все критерии должны выполняться одновременно**, чтобы правило сработало. Логическое отношение — "**И**", а не "**ИЛИ**". +::: + +Другими словами, это правило означает: **`Xray` направит трафик на исходящий трафик `[direct-out]` только в том случае, если целевой адрес равен `direct.yourdomain.com` **и** `223.5.5.5` одновременно**. + +Очевидно, что один адрес не может быть равен двум разным значениям одновременно, поэтому это не только невыполнимое правило, но и не имеет ничего общего с нашей первоначальной целью. + +### 7.2 Правильный пример + +Правильное решение — разделить разные критерии сопоставления на разные правила: + +```json +{ + "routing": { + "domainStrategy": "AsIs", + "rules": [ + { + "type": "field", + "ip": ["223.5.5.5"], + "outboundTag": "direct-out" + }, + { + "type": "field", + "domain": ["full:direct.yourdomain.com"], + "outboundTag": "direct-out" + } + ] + } +} +``` + +На самом деле, в пункте 6 я уже привел упорядоченные правила, принцип которых заключается в том, что **одинаковые критерии сопоставления можно объединять, а разные критерии сопоставления должны быть разделены**. + +## 8. Скрытые пути + +> Секретный проход для преобразования `[domain]` в `[ip]`: `domainStrategy` + +В пункте 5.4 мы рассмотрели различные **критерии** для сопоставления трафика, среди которых были **домен** `[domain]` и **IP-адрес** `[IP]`. + +Если вы знакомы с принципами работы DNS, то знаете, что при обращении к домену `[domain]` сначала нужно отправить запрос на DNS-сервер, чтобы получить IP-адрес `[IP]`, соответствующий домену, а затем отправить фактический запрос на этот IP-адрес. + +Поэтому у `Xray` есть два шанса определить тип входящего запроса к домену. Использовать ли эти два шанса, решает параметр `domainStrategy`. Он имеет три значения: + +- `AsIs` +- `IPIfNonMatch` +- `IPOnDemand` + +Давайте рассмотрим каждое из них: + +### 8.1 Стратегия домена: `"AsIs"` + +"As Domain Is" означает **"как есть, без лишних действий"**. + +Проще говоря, **"сопоставлять только по домену"**. + +::: tip +`AsIs` на самом деле означает **"как есть, без изменений"**. Описание 🍉-sensei не совсем точное. +::: + +В этом режиме вся обработка выполняется внутри `Xray` без обмена данными с внешним миром, поэтому скорость максимальна. Стратегия обработки несопоставимых доменов также ясна: как уже говорилось ранее, они автоматически перенаправляются на первый исходящий трафик. Поэтому это **наиболее рекомендуемая стратегия** для обычного использования функции маршрутизации. + +### 8.2 Стратегия домена: `"IPIfNonMatch"` + +"lookup IP if (there's) no matching rule" означает **"искать IP-адрес, если не найдено совпадений по другим правилам"**. + +Проще говоря, **"сначала сопоставить целевой адрес со всеми правилами, а если совпадений не найдено, то получить IP-адрес через DNS и снова сопоставить его со всеми правилами"**. + +В этом режиме домены, не сопоставленные ни с одним правилом, будут проходить через DNS-запрос и второй этап сопоставления правил, что займет больше времени, чем в режиме `AsIs`. Поэтому это **не самая рекомендуемая стратегия**. + +### 8.3 Стратегия домена: `"IPOnDemand"` + +"Demand IP" означает **"запрашивать IP-адрес"**. + +Проще говоря, **"если в правилах маршрутизации есть правила на основе IP-адреса, то все запросы на основе домена `[domain]` будут преобразованы в IP-адреса `[IP]` и сопоставлены с правилами на основе IP-адреса"**. + +В этом режиме все запросы к доменам будут проходить через DNS-запрос, поэтому первый запрос будет медленным. Хотя благодаря механизму кэширования DNS в `Xray` последующие запросы к тому же домену будут быстрыми, в целом это **не самая рекомендуемая стратегия**. + +::: warning +`domainStrategy` действует **только для доменов**, не путайте! +::: + +## 9. Задание для размышления + +До сих пор мы рассматривали логику конфигурации **маршрутизации** на основе **одного входящего** и **одного исходящего** трафика. + +Но, как вы знаете, `Xray` поддерживает несколько портов и протоколов. Что, если я спрошу вас: + +1. Я хочу, чтобы протокол `VLESS` перенаправлял мой обычный веб-трафик и трафик приложений на высокоскоростной сервер в США. +2. Я хочу, чтобы протокол `trojan` перенаправлял весь мой трафик Netflix на сервер в Японии, чтобы разблокировать все аниме. +3. Я хочу, чтобы протокол `shadowsocks` перенаправлял весь мой игровой трафик на сервер в Гонконге для минимальной задержки. +4. Я хочу, чтобы был отдельный порт, который перенаправлял бы весь трафик `telegram` на VPS. +5. Я хочу, чтобы был отдельный порт, который перенаправлял бы весь торрент-трафик на мощный сервер в Европе. +6. Я хочу...... + +Можно ли реализовать эти сценарии с помощью **функции маршрутизации**? + +Ответ, конечно же, **да**! Однако, это выходит за рамки уровня 1, поэтому я оставлю это вам для самостоятельного изучения! + +## 10. Заключение + +На этом обзор **функции маршрутизации** в `Xray` завершен. Надеюсь, эта статья помогла вам лучше понять гибкость `Xray`. + +## 11. Примечания + +- Теперь вы можете перечитать раздел [Маршрутизация](../../config/routing.md) и посмотреть, стало ли вам что-то понятнее. +- 🍉🍉🍉🍉🍉 :D + + + diff --git a/docs/ru/document/level-1/work.md b/docs/ru/document/level-1/work.md new file mode 100644 index 000000000..38dc5ed20 --- /dev/null +++ b/docs/ru/document/level-1/work.md @@ -0,0 +1,56 @@ +# Режимы работы Xray + +## Режим одного сервера + +Как и в случае с другими прокси-инструментами, вам понадобится сервер с настроенным Xray, а затем установить и настроить клиент Xray на вашем устройстве, после чего вы сможете свободно пользоваться Интернетом. + +```mermaid +graph LR; +A(ПК) -.- B(Брандмауэр); +B -.-> C(Внешний сайт); +A --> D(Xray/VPS); +D --> C; +A --> E(Внутренний сайт); +``` + +Один сервер Xray может одновременно обслуживать несколько устройств, использующих разные протоколы проксирования. При правильной настройке Xray может распознавать и различать трафик, который нужно проксировать, и трафик, который можно отправлять напрямую, без проксирования. + +## Режим моста + +Если вы не хотите настраивать маршрутизацию на каждом устройстве, вы можете настроить промежуточный сервер, который будет принимать весь трафик от клиентов и перенаправлять его в зависимости от настроек. + +```mermaid +graph LR; +A(ПК) -.-> B(Брандмауэр); +B -.-> C(Внешний сайт); +A --> D(Внутренний VPS); +D --> E(Внешний VPS); +E --> C; +D --> F(Внутренний сайт); +``` + +## Принцип работы + +Перед настройкой Xray давайте рассмотрим, как он работает. Ниже представлена схема внутреннего устройства одного процесса Xray. Несколько процессов Xray работают независимо друг от друга. + +```mermaid +graph LR; +A1(inbound) --> D(Dispatcher / Router / DNS); +A2(inbound) --> D; +A3(inbound) --> D; +A4(inbound) --> D; +D --> B1(outbound); +D --> B2(outbound); +D --> B3(outbound); +D --> B4(outbound); +``` + +- Для нормальной работы необходимо настроить как минимум одно входящее соединение (Inbound) и одно исходящее соединение (Outbound). + - Входящее соединение отвечает за связь с клиентом (например, браузером): + - Входящее соединение обычно можно настроить с аутентификацией пользователя, например, с использованием ID и пароля; + - После получения данных входящее соединение передает их диспетчеру (Dispatcher) для распределения. + - Исходящее соединение отвечает за отправку данных на сервер, например, на другой Xray, работающий на другом хосте. +- При наличии нескольких исходящих соединений можно настроить маршрутизацию (Routing) для указания, какое исходящее соединение должно использоваться для определенного типа трафика. + - При необходимости маршрутизатор обращается к DNS для получения дополнительной информации для принятия решения. + + diff --git a/docs/ru/document/level-2/README.md b/docs/ru/document/level-2/README.md new file mode 100644 index 000000000..1c94b38f5 --- /dev/null +++ b/docs/ru/document/level-2/README.md @@ -0,0 +1,37 @@ +# Продвинутая документация + +**В этом разделе представлены советы и рекомендации по использованию Xray для продвинутых пользователей. Если вы уже знакомы с Xray, то информация, представленная здесь, поможет вам использовать Xray по максимуму.** + +[Введение в прозрачное проксирование](./transparent_proxy/transparent_proxy.md) от a [@kirin](https://github.com/kirin10000) + +Вводная статья о прозрачном проксировании. + +[Руководство по настройке прозрачного проксирования (TProxy) ](./tproxy.md) от a [@BioniCosmos](https://github.com/BioniCosmos) + +Полное руководство по настройке прозрачного проксирования (TProxy) на основе Xray. + +[Руководство по настройке прозрачного проксирования TProxy (ipv4 и ipv6)](./tproxy_ipv4_and_ipv6.md) от a [@SQLimit](https://github.com/SQLimit) + +Руководство по настройке прозрачного проксирования TProxy (ipv4 и ipv6) на основе Xray. + +[Создание TLS-туннеля с помощью Nginx или Haproxy для скрытия отпечатков](./nginx_or_haproxy_tls_tunnel.md) от a [@SQLimit](https://github.com/SQLimit) + +Создание TLS-туннеля с помощью Nginx или Haproxy на стороне клиента и сервера для скрытия отпечатков. + +[[Прозрачное проксирование] Исключение трафика Xray с помощью GID](./iptables_gid.md) от a [@kirin](https://github.com/kirin10000) + +Новый способ исключения трафика Xray при реализации прозрачного проксирования с помощью iptables/nftables. + +[Направление определенного трафика через определенный выходной узел с помощью Xray для реализации "разделения" глобальной маршрутизации](./redirect.md) от a [@Zzz3m](https://github.com/Zzz3m) + +Использование Xray по максимуму: реализация "разделения" трафика на основе fwmark, sendThrough или sockopt.interface. + +[Повышение безопасности проксирования с помощью Cloudflare Warp](./warp.md) от a [@yuhan6665](https://github.com/yuhan6665) + +Введение в использование исходящего подключения WireGuard, добавленного в Xray v1.6.5. + +[Статистика трафика Xray](./traffic_stats.md) от a [@yuhan6665](https://github.com/yuhan6665) + +Статистика трафика и скрипты для Xray. + + diff --git a/docs/ru/document/level-2/iptables_gid.md b/docs/ru/document/level-2/iptables_gid.md new file mode 100644 index 000000000..1a5ee961f --- /dev/null +++ b/docs/ru/document/level-2/iptables_gid.md @@ -0,0 +1,240 @@ +--- +title: GID Прозрачное проксирование +--- + +# Прозрачное проксирование: Исключение трафика Xray с помощью GID + +В существующих русскоязычных руководствах по прозрачному проксированию с использованием iptables (**[Новое руководство по V2Ray на русском языке - Прозрачное проксирование](https://guide.v2fly.org/app/transparent_proxy.html)**, **[Новое руководство по V2Ray на русском языке - Прозрачное проксирование (TPROXY)](https://guide.v2fly.org/app/tproxy.html)**, **[Руководство по настройке прозрачного проксирования (TProxy)](./tproxy)**) исключение трафика Xray осуществляется с помощью меток. Исходящий трафик Xray помечается, а затем с помощью правил iptables трафик с соответствующей меткой направляется напрямую, минуя Xray и предотвращая зацикливание. + +У такого подхода есть несколько недостатков: + +1. **[Необъяснимый трафик попадает в цепочку PREROUTING](https://github.com/v2ray/v2ray-core/issues/2621)** + +2. Android использует собственный механизм меток, поэтому данный метод не применим к Android + +Предлагаемый в данном руководстве подход не требует использования меток, теоретически обеспечивая более высокую производительность и избегая описанных выше проблем. + +## Идея + +Tproxy трафик может приниматься только пользователями с правами root (uid==0) или CAP_NET_ADMIN. + +Правила iptables позволяют разделять трафик на основе UID (идентификатор пользователя) и GID (идентификатор группы). + +Запустим Xray от имени пользователя с uid==0 и gid!=0 и настроим правила iptables, чтобы исключить трафик с этим GID, избегая проксирования трафика Xray. + +## Настройка + +### 1. Предварительная подготовка + +**Android** + +1. На устройстве должны быть получены root-права. +2. Установите **[busybox](https://play.google.com/store/apps/details?id=stericson.busybox)**. +3. Наличие терминала для выполнения команд, например, adb shell, Termux и т.д. + +**Другие Linux системы** + +Необходимо наличие sudo, модуля tproxy для iptables и модуля extra. + +Обычно все это уже установлено в системе, для OpenWRT выполните: + +```bash +opkg install sudo iptables-mod-tproxy iptables-mod-extra +``` + +Также могут понадобиться следующие зависимости для OpenWRT, их отсутствие может помешать запуску Xray: + +```bash +opkg install libopenssl ca-certificates +``` + +### 2. Добавление пользователя (пропустите для Android) + +Android не поддерживает файл /etc/passwd для управления пользователями, пропустите этот шаг и перейдите к следующему. + +```bash +grep -qw xray_tproxy /etc/passwd || echo "xray_tproxy:x:0:23333:::" >> /etc/passwd +``` + +Где xray_tproxy - имя пользователя, 0 - UID, 23333 - GID. Имя пользователя и GID можно задать произвольно, UID должен быть равен 0. +Проверьте, успешно ли добавлен пользователь, выполнив: + +```bash +sudo -u xray_tproxy id +``` + +В результате должен отобразиться UID 0 и GID 23333. + +### 3. Настройка запуска Xray и правил iptables + +Внесите изменения в существующие русскоязычные руководства по прозрачному проксированию с использованием iptables (**[Новое руководство по V2Ray на русском языке - Прозрачное проксирование](https://guide.v2fly.org/app/transparent_proxy.html)**, **[Новое руководство по V2Ray на русском языке - Прозрачное проксирование (TPROXY)](https://guide.v2fly.org/app/tproxy.html)**, **[Руководство по настройке прозрачного проксирования (TProxy)](./tproxy)**): + +1. Измените конфигурационный файл JSON, удалив все, что связано с метками. + +2. Измените правила iptables, удалив все, что связано с метками, и добавьте опцию "-m owner ! --gid-owner 23333" в цепочку OUTPUT перед применением правила XRAY_SELF. + +Например: + +```bash +iptables -t mangle -A OUTPUT -j XRAY_SELF +``` + +Замените на: + +```bash +iptables -t mangle -A OUTPUT -m owner ! --gid-owner 23333 -j XRAY_SELF +``` + +3. Измените способ запуска Xray, чтобы он запускался от имени пользователя с UID 0 и GID 23333, см. [здесь](#3-настройка-максимального-количества-открытых-файлов-и-запуск-клиента-xray). + +## Ниже приведен пример полной настройки глобального проксирования с использованием TPROXY + +### 1. Выполните [предварительную подготовку](#1-предварительная-подготовка) и [добавление пользователя](#2-добавление-пользователя-пропустите-для-android). + +### 2. Подготовьте конфигурационный файл Xray. + +Настройте произвольную дверь Xray для прослушивания порта 12345, включите followRedirect и tproxy, sniffing не требуется: + +```json +{ + "inbounds": [ + { + "port": 12345, + "protocol": "dokodemo-door", + "settings": { + "network": "tcp,udp", + "followRedirect": true + }, + "streamSettings": { + "sockopt": { + "tproxy": "tproxy" + } + } + } + ], + "outbounds": [ + { + // Конфигурация вашего сервера + } + ] +} +``` + +### 3. Настройка максимального количества открытых файлов и запуск клиента Xray + +О проблеме "too many open files" см.: **[Проблема too many open files](https://guide.v2fly.org/app/tproxy.html#решение-проблемы-too-many-open-files)** + +В настоящее время при установке сервера Xray с помощью официального скрипта максимальное количество открытых файлов настраивается автоматически, никаких дополнительных действий не требуется. + +**Android** + +```bash +ulimit -SHn 1000000 +setuidgid 0:23333 "команда запуска Xray"& +``` + +**Другие Linux системы** + +```bash +ulimit -SHn 1000000 +sudo -u xray_tproxy "команда запуска Xray"& +``` + +Например: + +```bash +ulimit -SHn 1000000 +sudo -u xray_tproxy xray -c /etc/xray/config.json & +``` + +_Первая команда:_ + +Изменяет максимальное количество открытых файлов, действует только в текущем терминале, необходимо выполнять перед каждым запуском Xray. Эта команда устанавливает максимальное количество открытых файлов для клиента. + +_Вторая команда:_ + +Запускает клиент Xray от имени пользователя с UID 0 и GID, отличным от 0. Символ & в конце команды означает запуск в фоновом режиме. + +**Проверка настройки максимального количества открытых файлов** + +```bash +cat /proc/PID Xray/limits +``` + +Найдите строку "Max open files", значение должно соответствовать установленному вами. PID процесса Xray можно узнать, выполнив команду `ps`, `ps -aux`, `ps -a` или `pidof xray`. + +Проверьте как сервер, так и клиент. + +### 4. Настройка правил iptables + +**Проксирование IPv4** + +```bash +ip rule add fwmark 1 table 100 +ip route add local 0.0.0.0/0 dev lo table 100 + +# Проксирование устройств локальной сети +iptables -t mangle -N XRAY +# "Сегмент IPv4-сети шлюза" можно получить, выполнив команду "ip address | grep -w inet | awk '{print $2}'", как правило, их несколько +iptables -t mangle -A XRAY -d Сегмент IPv4-сети шлюза 1 -j RETURN +iptables -t mangle -A XRAY -d Сегмент IPv4-сети шлюза 2 -j RETURN +... + +# Прямое подключение для многоадресных адресов/адресов класса E/широковещательных адресов +iptables -t mangle -A XRAY -d 224.0.0.0/3 -j RETURN + +# Если шлюз является основным маршрутизатором, добавьте эту строку, см.: https://xtls.github.io/documents/level-2/transparent_proxy/transparent_proxy.md#iptables-прозрачное-проксирование-другие-замечания +# "Диапазон LAN-адресов IPv4 шлюза" можно получить, выполнив команду "ip address | grep -w "inet" | awk '{print $2}'", это будет один из адресов +iptables -t mangle -A XRAY ! -s Диапазон LAN-адресов IPv4 шлюза -j RETURN + +# Пометить TCP-трафик меткой 1 и перенаправить на порт 12345 +# Трафик будет приниматься произвольной дверью Xray только при наличии метки 1 +iptables -t mangle -A XRAY -p tcp -j TPROXY --on-port 12345 --tproxy-mark 1 +iptables -t mangle -A XRAY -p udp -j TPROXY --on-port 12345 --tproxy-mark 1 +# Применить правило +iptables -t mangle -A PREROUTING -j XRAY + +# Проксирование хоста шлюза +iptables -t mangle -N XRAY_MASK +iptables -t mangle -A XRAY_MASK -m owner --gid-owner 23333 -j RETURN +iptables -t mangle -A XRAY_MASK -d Сегмент IPv4-сети шлюза 1 -j RETURN +iptables -t mangle -A XRAY_MASK -d Сегмент IPv4-сети шлюза 2 -j RETURN +... +iptables -t mangle -A XRAY_MASK -d 224.0.0.0/3 -j RETURN +iptables -t mangle -A XRAY_MASK -j MARK --set-mark 1 +iptables -t mangle -A OUTPUT -p tcp -j XRAY_MASK +iptables -t mangle -A OUTPUT -p udp -j XRAY_MASK +``` + +**Проксирование IPv6 (необязательно)** + +```bash +ip -6 rule add fwmark 1 table 106 +ip -6 route add local ::/0 dev lo table 106 + +# Проксирование устройств локальной сети +ip6tables -t mangle -N XRAY6 +# "Сегмент IPv6-сети шлюза" можно получить, выполнив команду "ip address | grep -w inet6 | awk '{print $2}'". +ip6tables -t mangle -A XRAY6 -d Сегмент IPv6-сети шлюза 1 -j RETURN +ip6tables -t mangle -A XRAY6 -d Сегмент IPv6-сети шлюза 2 -j RETURN +... + +# Если шлюз является основным маршрутизатором, добавьте эту строку, см.: https://xtls.github.io/documents/level-2/transparent_proxy/transparent_proxy.md#iptables-прозрачное-проксирование-другие-замечания +# "Диапазон LAN-адресов IPv6 шлюза" можно получить, выполнив команду "ip address | grep -w "inet6" | awk '{print $2}'", это будет один из адресов +ip6tables -t mangle -A XRAY6 ! -s Диапазон LAN-адресов IPv6 шлюза -j RETURN + +ip6tables -t mangle -A XRAY6 -p udp -j TPROXY --on-port 12345 --tproxy-mark 1 +ip6tables -t mangle -A XRAY6 -p tcp -j TPROXY --on-port 12345 --tproxy-mark 1 +ip6tables -t mangle -A PREROUTING -j XRAY6 + +# Проксирование хоста шлюза +ip6tables -t mangle -N XRAY6_MASK +ip6tables -t mangle -A XRAY6_MASK -m owner --gid-owner 23333 -j RETURN +ip6tables -t mangle -A XRAY6_MASK -d Сегмент IPv6-сети шлюза 1 -j RETURN +ip6tables -t mangle -A XRAY6_MASK -d Сегмент IPv6-сети шлюза 2 -j RETURN +... +ip6tables -t mangle -A XRAY6_MASK -j MARK --set-mark 1 +ip6tables -t mangle -A OUTPUT -p tcp -j XRAY6_MASK +ip6tables -t mangle -A OUTPUT -p udp -j XRAY6_MASK +``` + diff --git a/docs/ru/document/level-2/nginx_or_haproxy_tls_tunnel.md b/docs/ru/document/level-2/nginx_or_haproxy_tls_tunnel.md new file mode 100644 index 000000000..661bc7a0c --- /dev/null +++ b/docs/ru/document/level-2/nginx_or_haproxy_tls_tunnel.md @@ -0,0 +1,729 @@ +--- +title: Создание TLS-туннеля с помощью Nginx или Haproxy для скрытия отпечатков +--- + +Nginx или Haproxy реализуют HTTPS-туннели, туннели HTTP/2 over HTTPS, туннели WebSocket over HTTP/2 over HTTPS, туннели gRPC over HTTP/2 over HTTPS, а также туннели gRPC over HTTP/2 over HTTPS с двусторонней аутентификацией по самозаверяющему сертификату. + +# Создание HTTPS-туннеля с помощью Nginx на стороне клиента и сервера для скрытия отпечатков + +Сетевая структура: + +xray_client ---tcp--- nginx_client ---HTTPS--- nginx_sever ---tcp--- xray_server + +## Компиляция nginx с поддержкой --with-stream + +Выполните компиляцию как на клиенте, так и на сервере. + +`curl -O -L http://nginx.org/download/nginx-1.22.1.tar.gz` + +`tar -zxvf nginx-1.22.1.tar.gz` + +`cd nginx-1.22.1` + +`apt install gcc make` // Для компиляции требуются gcc и make + +`./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-http_v2_module --with-stream --with-stream_ssl_module` // На этом шаге могут потребоваться дополнительные библиотеки, установите их в соответствии с сообщениями об ошибках. + +`make && make install` + +После компиляции папка nginx будет находиться в `/usr/local/nginx`. + +## Настройка nginx + +Отредактируйте конфигурационный файл nginx.conf. + +`vim /usr/local/nginx/conf/nginx.conf` + +Добавьте следующую конфигурацию на стороне сервера. + +Получение сертификата для сервера не рассматривается в данном руководстве. Обратитесь к [документации](https://xtls.github.io/document/level-0/ch06-certificates.html). + +``` +stream { + server { + listen 443 ssl; + listen [::]:443 ssl; + ssl_protocols TLSv1.3; + ssl_certificate /path/to/cert/domain.crt; # Путь к файлу crt + ssl_certificate_key /path/to/cert/domain.key; # Путь к файлу key + proxy_pass unix:/dev/shm/vless.sock; # Использование доменного сокета + } +} +``` + +::: warning Внимание + +Раздел stream находится на одном уровне с модулем http. Клиент может удалить раздел http, а сервер может удалить его или настроить веб-сайт для маскировки. +::: + +Добавьте следующую конфигурацию на стороне клиента. + +``` +stream { + server { + listen 6666; + listen [::]:6666; + proxy_ssl on; + proxy_ssl_protocols TLSv1.3; + proxy_ssl_server_name on; + proxy_ssl_name yourdomain.domain; # Доменное имя сервера + proxy_pass ip:443; # IP-адрес сервера, например, proxy_pass 6.6.6.6:443; или proxy_pass [2401:0:0::1]:443; + } +} +``` + +Создайте файл `nginx.service` в папке `/etc/systemd/system`. + +`vim /etc/systemd/system/nginx.service` + +Добавьте следующий текст: + +``` +[Unit] +Description=The NGINX HTTP and reverse proxy server +After=syslog.target network-online.target remote-fs.target nss-lookup.target +After=xray.service + +[Service] +Type=forking +ExecStartPre=/usr/local/nginx/sbin/nginx -t +ExecStart=/usr/local/nginx/sbin/nginx +ExecReload=/usr/local/nginx/sbin/nginx -s reload +ExecStop=/bin/kill -s QUIT $MAINPID +PrivateTmp=true + +[Install] +WantedBy=multi-user.target +``` + +Добавьте автоматический запуск при загрузке системы. + +`systemctl enable nginx` + +## Настройка Xray + +Конфигурация Xray на стороне сервера: + +```json +{ + "log": { + "loglevel": "none" + }, + "inbounds": [ + { + "listen": "/dev/shm/vless.sock,0666", + "protocol": "vless", + "settings": { + "clients": [ + { + "id": "uuid" + } + ], + "decryption": "none" + }, + "streamSettings": { + "network": "tcp" + }, + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls" + ] + } + } + ], + "outbounds": [ + { + "protocol": "freedom" + } + ] +} +``` + +Конфигурация Xray на стороне клиента (в данном примере используется прозрачное проксирование пограничного маршрутизатора): + +```json +{ + "log": { + "loglevel": "none" + }, + "dns": { + "servers": [ + "1.1.1.1", + { + "address": "119.29.29.29", + "domains": [ + "geosite:cn" + ], + "expectIP": [ + "geoip:cn" + ] + } + ], + "disableFallback": true, + "disableFallbackIfMatch": true + }, + "inbounds": [ + { + "tag": "tproxy-in", + "port": 12345, + "protocol": "dokodemo-door", + "settings": { + "network": "tcp,udp", + "followRedirect": true + }, + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls" + ] + }, + "streamSettings": { + "sockopt": { + "tproxy": "tproxy", + "mark": 255 + } + } + }, + { + "tag": "http", + "port": 10808, + "listen": "127.0.0.1", + "protocol": "http", + "sniffing": { + "enabled": true, + "destOverride": [ + "http", + "tls" + ] + } + } + ], + "outbounds": [ + { + "tag": "nginxtls", + "protocol": "vless", + "settings": { + "vnext": [ + { + "address": "127.0.0.1", + "port": 6666, + "users": [ + { + "id": "uuid", + "encryption": "none" + } + ] + } + ] + }, + "streamSettings": { + "sockopt": { + "mark": 255 + }, + "network": "tcp" + } + }, + { + "tag": "direct", + "protocol": "freedom", + "streamSettings": { + "sockopt": { + "mark": 255 + } + } + }, + { + "tag": "block", + "protocol": "blackhole", + "settings": { + "response": { + "type": "http" + } + } + } + ], + "routing": { + "domainMatcher": "mph", + "domainStrategy": "AsIs", + "rules": [ + { + "type": "field", + "domain": [ + "geosite:category-ads-all" + ], + "outboundTag": "block" + }, + { + "type": "field", + "port": 123, + "network": "udp", + "outboundTag": "direct" + }, + { + "type": "field", + "ip": [ + "1.1.1.1" + ], + "outboundTag": "proxy" + }, + { + "type": "field", + "domain": [ + "geosite:cn" + ], + "outboundTag": "direct" + }, + { + "type": "field", + "protocol": [ + "bittorrent" + ], + "outboundTag": "direct" + }, + { + "type": "field", + "ip": [ + "geoip:private" + ], + "outboundTag": "direct" + }, + { + "type": "field", + "inboundTag": [ + "tproxy-in" + ], + "outboundTag": "nginxtls" + } + ] + } +} +``` + +При использовании прозрачного проксирования необходимо добавить следующие правила в конфигурацию iptables или ip6tables: + +``` +# Настройка маршрутизации по политике для IPv4 +ip rule add fwmark 1 table 100 +ip route add local 0.0.0.0/0 dev lo table 100 + +# Настройка маршрутизации по политике для IPv6 +ip -6 rule add fwmark 1 table 106 +ip -6 route add local ::/0 dev lo table 106 + +# Прямое подключение для IP-адреса VPS +iptables -t mangle -A XRAY_MASK -d VSP_IPv4/32 -j RETURN +ip6tables -t mangle -A XRAY6_MASK -d VPS_IPv6/128 -j RETURN +``` + +## Запуск сервисов на клиенте и сервере + +`systemctl restart xray` + +`systemctl restart nginx` + +## Завершение + +# Создание HTTPS-туннеля с помощью Haproxy на стороне клиента и сервера для скрытия отпечатков + +Установка Haproxy: + +`pacman -Su haproxy` или `apt install haproxy` + +Haproxy требует OpenSSL для обработки SSL. Проверьте версию OpenSSL и при необходимости установите или обновите ее. + +## HTTPS-туннель + +Haproxy может легко реализовать HTTPS-туннель, как и описанный выше Nginx. + +Сетевая структура: + +xray_client ---tcp--- haproxy_client ---HTTPS--- haproxy_sever ---tcp--- xray_server + +### Конфигурация haproxy_client (удалите комментарии перед запуском): + +``` +global + log /dev/log local0 alert + log /dev/log local1 alert + stats socket /dev/shm/admin.sock mode 660 level admin expose-fd listeners + stats timeout 30s + user root + group root + daemon + + # Принудительное использование TLS 1.3 для туннеля + ssl-default-server-options ssl-min-ver TLSv1.3 + +defaults + log global + mode tcp + timeout connect 5s + timeout client 300s + timeout server 300s + +frontend xray + bind 127.0.0.1:6666 # Прослушивание порта 6666 на локальном хосте + default_backend tunnel + +backend tunnel + server tunnel www.example.com:443 ssl verify none sni req.hdr(host) alpn h2,http/1.1 + # Можно использовать доменное имя или IP-адрес. При использовании доменного имени рекомендуется указать IP-адрес в файле hosts, чтобы сократить время разрешения имени. + # alpn используется для согласования с сервером. Если на стороне сервера установлено alpn h2,http1.1, то клиент может указать h2 для подключения по HTTP/2 или http1.1 для подключения по HTTP. + # Рекомендуется указывать h2 в обоих случаях. +``` + +### Конфигурация haproxy_server (удалите комментарии перед запуском): + +``` +global + log /dev/log local0 alert + log /dev/log local1 alert + stats socket /dev/shm/admin.sock mode 660 level admin expose-fd listeners + stats timeout 30s + user root + group root + daemon + + # Указание наборов шифров и минимальной версии SSL 1.2 для повышения безопасности + ssl-default-bind-ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256 + ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 + ssl-default-bind-options ssl-min-ver TLSv1.2 + +defaults + log global + mode tcp + timeout connect 5s + timeout client 300s + timeout server 300s + +frontend tls-in + bind :::443 ssl crt /path/to/pem alpn h2,http/1.1 # Haproxy использует pem для расшифровки SSL. Файл pem можно получить с помощью команды cat www.example.com.crt www.example.com.key > www.example.com.pem + default_backend xray + tcp-request inspect-delay 5s + tcp-request content accept if HTTP + use_backend web if HTTP + +backend xray + server xray /dev/shm/vless.sock # Поддерживаются абстрактные сокеты: "abns@vless.sock" и loopback: 127.0.0.1:6666 + +backend web + server web /dev/shm/h1h2c.sock # Перенаправление на веб-сайт +``` + +### Настройка Xray + +Аналогично разделу Nginx: простейшая конфигурация TCP, совместимая с любым протоколом. Рекомендуется использовать VLESS+TCP без дополнительного шифрования. Обратитесь к документации или другим примерам. + +## WebSocket over HTTP/2 + +Haproxy поддерживает h2c как для входящих, так и для исходящих подключений HTTP/2. + +Однако в документации Xray по HTTP/2 говорится: + +“В соответствии с рекомендациями по HTTP/2, клиент и сервер должны одновременно включать TLS для корректной работы этого метода передачи... В текущей версии HTTP/2 для входящих подключений (сервер) не требуется настройка TLS.” + +То есть для входящих подключений можно использовать h2c, но для исходящих подключений h2c не поддерживается. Поэтому невозможно использовать схему xray_client ---h2c--- haproxy_client ---HTTP/2+TLS--- haproxy_sever ---h2c--- xray_server. + +Однако можно обойти это ограничение, используя WebSocket. Haproxy поддерживает ws over HTTP/2. + +Тогда сетевая структура будет выглядеть следующим образом: xray_client ---ws--- haproxy_client ---ws over HTTP/2 over HTTPS--- haproxy_sever ---ws--- xray_server. + +### Конфигурация haproxy_client: + +``` +global + log /dev/log local0 alert + log /dev/log local1 alert + stats socket /dev/shm/admin.sock mode 660 level admin expose-fd listeners + stats timeout 30s + user root + group root + daemon + + # Настройка производительности HTTP/2. Эти параметры можно изменять при возникновении проблем с производительностью HTTP/2. + # Дополнительные настройки см. в разделе tune.h2 документации Haproxy: https://docs.haproxy.org/2.7/configuration.html + tune.h2.initial-window-size 536870912 # Начальный размер окна, рекомендуется настроить, значение по умолчанию - 65536 байт. + # При резком увеличении трафика может потребоваться время на загрузку, рекомендуется настраивать в зависимости от скорости интернета. + tune.h2.max-concurrent-streams 512 # Количество одновременных потоков, можно настроить при необходимости, значение по умолчанию - 100. + # Обычно не требуется изменять (не рекомендуется официальной документацией). + + ssl-default-server-options ssl-min-ver TLSv1.3 + +defaults + log global + mode http + timeout connect 5s + timeout client 300s + timeout server 300s + +frontend xray + bind 127.0.0.1:6666 + default_backend tunnel + +backend tunnel + server tunnel www.example.com:443 ssl verify none sni req.hdr(host) ws h2 alpn h2 + # ws over HTTP/2 +``` + +### Конфигурация haproxy_server: + +``` +global + log /dev/log local0 alert + log /dev/log local1 alert + stats socket /dev/shm/admin.sock mode 660 level admin expose-fd listeners + stats timeout 30s + user root + group root + daemon + + # Настройка производительности HTTP/2 (необязательно, но рекомендуется). + tune.h2.initial-window-size 536870912 + tune.h2.max-concurrent-streams 512 + + ssl-default-bind-ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256 + ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 + ssl-default-bind-options ssl-min-ver TLSv1.2 + +defaults + log global + mode http + timeout connect 5s + timeout client 300s + timeout server 300s + +frontend tls-in + bind :::443 ssl crt /path/to/pem alpn h2,http/1.1 + use_backend xray if { ssl_fc_alpn -i h2 } { path_beg /tunnel } + use_backend server1 if { ssl_fc_alpn -i h2 } { path_beg /path1 } + use_backend server2 if { ssl_fc_alpn -i h2 } { path_beg /path2 } + use_backend server3 if { ssl_fc_alpn -i h2 } { path_beg /path3 } + default_backend web + # Haproxy в режиме http может разделять трафик на основе пути. + +backend xray + server xray abns@vless.sock ws h1 + +backend server1 + server server1 abns@server1.sock ws h1 + +backend server2 + server server2 abns@server2.sock ws h1 + +backend server3 + server server3 abns@server3.sock ws h1 + +backend web + server web /dev/shm/h1h2c.sock +``` + +### Настройка Xray + +Простая конфигурация WebSocket, TLS не требуется. Пример конфигурации см. в документации Xray. +Параметр "path" можно использовать для разделения трафика на стороне сервера Haproxy (клиент также может разделять трафик с помощью Haproxy, принцип аналогичен, см. конфигурацию разделения трафика на стороне сервера). + +## gRPC over HTTP/2 + +Хотя двусторонний h2c невозможен, gRPC не требует обязательного использования TLS. + +Сетевая структура: xray_client ---gRPC h2c--- haproxy_client ---gRPC over HTTP/2 over HTTPS--- haproxy_sever ---gRPC h2c--- xray_server + +### Конфигурация haproxy_client: + +``` +global + log /dev/log local0 alert + log /dev/log local1 alert + stats socket /dev/shm/admin.sock mode 660 level admin expose-fd listeners + stats timeout 30s + user root + group root + daemon + + tune.h2.initial-window-size 536870912 + tune.h2.max-concurrent-streams 512 + + ssl-default-server-options ssl-min-ver TLSv1.3 + +defaults + log global + mode http + timeout connect 5s + timeout client 300s + timeout server 300s + +frontend xray + bind 127.0.0.1:6666 proto h2 # Укажите proto h2 для использования h2c + default_backend tunnel + +backend tunnel + server tunnel www.example.com:443 ssl verify none sni req.hdr(host) alpn h2 +``` + +### Конфигурация haproxy_server: + +``` +global + log /dev/log local0 alert + log /dev/log local1 alert + stats socket /dev/shm/admin.sock mode 660 level admin expose-fd listeners + stats timeout 30s + user root + group root + daemon + + tune.h2.initial-window-size 536870912 + tune.h2.max-concurrent-streams 512 + + ssl-default-bind-ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256 + ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 + ssl-default-bind-options ssl-min-ver TLSv1.2 + +defaults + log global + mode http + timeout connect 5s + timeout client 300s + timeout server 300s + +frontend tls-in + bind :::443 ssl crt /path/to/pem alpn h2,http/1.1 + use_backend xray if { ssl_fc_alpn -i h2 } { path_beg /tunnel } # "serviceName", настроенное в gRPC Xray, можно использовать для разделения трафика в Haproxy с помощью пути. + # Для удобства использования "multiMode" используйте параметр path_beg для сопоставления пути. + use_backend server1 if { ssl_fc_alpn -i h2 } { path_beg /path1 } + use_backend server2 if { ssl_fc_alpn -i h2 } { path_beg /path2 } + use_backend server3 if { ssl_fc_alpn -i h2 } { path_beg /path3 } + default_backend web + +backend xray + server xray abns@vless.sock proto h2 + +backend server1 + server server1 abns@server1.sock proto h2 + +backend server2 + server server2 abns@server2.sock proto h2 + +backend server3 + server server3 abns@server3.sock proto h2 + +backend web + server web /dev/shm/h1h2c.sock +``` + +### Настройка Xray + +Простая конфигурация gRPC, TLS не требуется. Конфигурация см. в документации. +Параметр serviceName можно использовать для разделения трафика. + +# Двусторонняя аутентификация Haproxy с использованием самозаверяющего сертификата (пример gRPC) + +Здесь используется двусторонняя аутентификация по самозаверяющему сертификату для повышения безопасности туннеля (это немного увеличивает задержку, но с gRPC это не так заметно). Сервер обрабатывает как доверенные, так и самозаверяющие сертификаты и разделяет трафик на поддельный веб-сайт и туннель. + +www.example.com - доменное имя поддельного веб-сайта с доверенным сертификатом (например, сертификат, полученный в соответствии с документацией). + +tunnel.example.com - доменное имя с самозаверяющим сертификатом. +Самозаверяющий сертификат можно создать, например, с помощью инструкции https://learn.microsoft.com/ru-ru/azure/application-gateway/self-signed-certificates. + +Корневой сертификат: ca.crt, сертификат сервера: server.crt, ключ сервера: server.key. + +Необходимо создать как минимум файл server.pem, который клиент может использовать для двусторонней аутентификации. +Также можно создать два сертификата - client и server - для двусторонней аутентификации. + +Необходимо подготовить файл fullchain.crt для аутентификации (cat server.crt ca.crt > fullchain.crt) и server.pem (cat server.crt server.key ca.crt > server.pem) для расшифровки. + +### Конфигурация haproxy_client: + +``` +global + log /dev/log local0 alert + log /dev/log local1 alert + stats socket /dev/shm/admin.sock mode 660 level admin expose-fd listeners + stats timeout 30s + user root + group root + daemon + + tune.h2.initial-window-size 536870912 + tune.h2.max-concurrent-streams 512 + + ssl-default-server-options ssl-min-ver TLSv1.3 + +defaults + log global + mode http + timeout connect 5s + timeout client 300s + timeout server 300s + +frontend xray + bind 127.0.0.1:6666 proto h2 + default_backend tunnel + +backend tunnel + server tunnel tunnel.example.com:443 tfo allow-0rtt ssl crt /path/to/client.pem verify required ca-file /path/to/fullchain.crt sni str(tunnel.example.com) alpn h2 + # Доменное имя можно настроить произвольно, оно должно совпадать с самозаверяющим сертификатом. + # Укажите IP-адрес в файле hosts. + # Параметр str в sni устанавливает SNI, который используется сервером для идентификации. +``` + +### Конфигурация haproxy_server: + +``` +global + log /dev/log local0 alert + log /dev/log local1 alert + stats socket /dev/shm/admin.sock mode 660 level admin expose-fd listeners + stats timeout 30s + user root + group root + daemon + + tune.h2.initial-window-size 536870912 + tune.h2.max-concurrent-streams 512 + + ssl-default-bind-ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256 + ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 + ssl-default-bind-options ssl-min-ver TLSv1.2 + +defaults + log global + mode http + timeout connect 5s + timeout client 300s + timeout server 300s + +frontend tls-in + bind :::443 tfo allow-0rtt ssl crt /path/to/server.pem verify optional ca-file /path/to/fullchain.crt crt /path/to/www.example.com.pem alpn h2,http/1.1 + use_backend xray if { ssl_fc_sni tunnel.example.com } { ssl_c_used } { ssl_fc_alpn -i h2 } { path_beg /tunnel } + use_backend server1 if { ssl_fc_sni atunnel.example.com } { ssl_c_used } { ssl_fc_alpn -i h2 } { path_beg /path2 } + use_backend server2 if { ssl_fc_sni btunnel.example.com } { ssl_c_used } { ssl_fc_alpn -i h2 } { path_beg /path3 } + use_backend server3 if { ssl_fc_sni ctunnel.example.com } { ssl_c_used } { ssl_fc_alpn -i h2 } { path_beg /path4 } + default_backend web + # Haproxy поддерживает несколько файлов pem для расшифровки. + # Разделение трафика можно выполнять на основе SNI или пути, доступны различные способы. + # Дополнительные сведения об ACL см. в документации Haproxy. + +backend xray + server xray abns@vless.sock proto h2 + +backend server1 + server server1 abns@server1.sock proto h2 + +backend server2 + server server2 abns@server2.sock proto h2 + +backend server3 + server server3 abns@server3.sock proto h2 + +backend web + server web /dev/shm/h1h2c.sock +``` + +### Настройка Xray + +Простая конфигурация gRPC, TLS не требуется. Конфигурация см. в документации. +Параметр serviceName можно использовать для разделения трафика. diff --git a/docs/ru/document/level-2/redirect.md b/docs/ru/document/level-2/redirect.md new file mode 100644 index 000000000..b43b88e55 --- /dev/null +++ b/docs/ru/document/level-2/redirect.md @@ -0,0 +1,246 @@ +--- +title: Перенаправление исходящего трафика +--- + +# Перенаправление трафика на основе fwmark или sendThrough + +Направление определенного трафика через определенный выходной узел с помощью Xray для реализации "разделения" глобальной маршрутизации + +## Введение + +Я видел много прокси-серверов или VPN, которые перехватывают весь трафик, что приводит к неработоспособности Xray, если он установлен одновременно с ними. Многие руководства, которые я находил, предлагали решать эту проблему путем разделения трафика на основе таблиц маршрутизации CIDR. Это не очень элегантно, и если я хочу иметь возможность гибко переключаться между маршрутами и реализовывать разделение трафика по требованию, то есть ли лучший способ? Да, есть! + +С помощью fwmark или sendThrough/sockopt.interface в Xray и простой настройки таблицы маршрутизации можно добиться следующего: + +1. Xray может направлять трафик с определенным тегом, доменным именем и т.д. через определенный интерфейс. Если ваш интерфейс поддерживает dual-stack, вы можете указать IPv4 или IPv6. +2. Остальной трафик будет идти через исходный интерфейс IPv4 или IPv6. + +Вот как это настроить (на примере Debian 10): + +## 1. Установите прокси-сервер или VPN-клиент (например, Wireguard, IPsec и т.д.) + +Обратитесь к официальной документации для получения инструкций по установке для вашей системы и программного обеспечения. + +## 2. Отредактируйте конфигурационный файл VPN (на примере WireGuard) + +Исходный файл: + +```ini +[Interface] +PrivateKey = +Address = +Address = +DNS = 8.8.8.8 +MTU = 1280 +[Peer] +PublicKey = +AllowedIPs = ::/0 +AllowedIPs = 0.0.0.0/0 +Endpoint = : +``` + +Добавьте следующие строки в раздел `[Interface]`: +```ini +Table = +### fwmark +PostUP = ip rule add fwmark lookup
+PostDown = ip rule del fwmark lookup
+PostUP = ip -6 rule add fwmark lookup
+PostDown = ip -6 rule del fwmark lookup
+## sendThrough +PreUp = ip rule add from lookup
+PostDown = ip rule del from lookup
+PreUp = ip -6 rule add from lookup
+PostDown = ip -6 rule del from lookup
+## sockopt.interface +PreUp = ip rule add oif %i lookup
+PostDown = ip rule del oif %i lookup
+PreUp = ip -6 rule add oif %i lookup
+PostDown = ip -6 rule del oif %i lookup
+``` +::: tip +- В этом конфигурационном файле объединены `fwmark`, `sendThrough` и `sockopt.interface`. +- Подключения, поступающие на этот интерфейс `%i`, с этого IP-адреса `` или помеченные `fwmark` как ``, +- будут перенаправлены через WireGuard. +- `%i` - это заполнитель в конфигурационном файле WireGuard, который будет заменен на имя интерфейса во время запуска. +::: + + +Сохраните файл. + +Рекомендуется установить: + +::: warning +Если вы используете поле `DNS` в разделе `[Interface]`, то эта программа будет обязательной. +::: + +```bash +apt install openresolv +``` + +## 3. Активируйте сетевой интерфейс WireGuard. + +Загрузите модуль ядра: + +```bash +modprobe wireguard +``` + +Проверьте, правильно ли загружен модуль WG: + +```bash +lsmod | grep wireguard +``` + +## 4. Измените конфигурационный файл Xray-core. + +```json +{ + "api": { + "services": [ + "HandlerService", + "LoggerService", + "StatsService" + ], + "tag": "api" + }, + "inbounds": [ + { + "listen": "127.0.0.1", + "port": , + "protocol": "dokodemo-door", + "settings": { + "address": "127.0.0.1" + }, + "tag": "api" + } + ], + "outbounds": [ + { + "protocol": "freedom", + "settings": { + "domainStrategy": "UseIPv4" + } + // Измените на UseIPv4 или UseIPv6 по вашему выбору + }, + // <--Выберите один из вариантов--> Вариант 1: fwmark + { + "protocol": "freedom", + "tag": "wg0", + "streamSettings": { + "sockopt": { + "mark": + } + }, + "settings": { + "domainStrategy": "UseIPv6" + } + } // Трафик с меткой fwmark, равной , будет направлен через UseIPv6/UseIPv4. + // <--Выберите один из вариантов--> Вариант 2: sendThrough + { + "tag": "wg0", + "protocol": "freedom", + "sendThrough": "your wg0 v4 address", + // Измените на UseIPv4 или UseIPv6 по вашему выбору + "settings": { + "domainStrategy": "UseIPv4" + } + // Измените на UseIPv4 или UseIPv6 по вашему выбору + }, + // <--Выберите один из вариантов--> Вариант 3: sockopt.interface + { + "tag": "wg0", + "protocol": "freedom", + "settings": { + "domainStrategy": "UseIPv4" + }, + "streamSettings": { + "sockopt": { + "interface": "wg0" + } + } + }, + // <--Выберите один из вариантов--> Конец + { + "protocol": "blackhole", + "settings": {}, + "tag": "blocked" + } + ], + "policy": { + "system": { + "statsInboundDownlink": true, + "statsInboundUplink": true + } + }, + "routing": { + "rules": [ + { + "inboundTag": [ + "api" + ], + "outboundTag": "api", + "type": "field" + }, + { + "type": "field", + "outboundTag": "wg0", + "inboundTag": [ + "" + // Укажите тег входящего подключения, определенный ранее в разделе inbound. + // Здесь используется тег api, сгенерированный автоматически. Вы также можете добавить доменные имена и т.д. + ] + }, + { + "outboundTag": "blocked", + "protocol": [ + "bittorrent" + ], + "type": "field" + } + ] + }, + "stats": {} +} +``` + +::: tip +Вы можете изменить "domainStrategy": "UseIPv6", чтобы управлять способом доступа для определенных пользователей. +По моим тестам, этот параметр имеет более высокий приоритет, чем gai.config в системе. +::: + +## 5. Настройка системы + +::: tip +Необходимо включить ip_forward в системе: +`sysctl -w net.ipv4.ip_forward=1` +`sysctl -w net.ipv6.conf.all.forwarding=1` +::: + +## 6. Завершение настройки WireGuard + +Запустите туннель: + +```bash +wg-quick up wg0 +``` + +Настройте автоматический запуск: + +```bash +systemctl enable wg-quick@wg0 +systemctl start wg-quick@wg0 +``` + +Проверьте IPv4/IPv6: + +> На прокси-сервере выполните команду `curl ip-api.com -4/-6` / откройте в браузере сайт ip-api.com + +## Послесловие + +Цель этой статьи - показать, как избежать ненужных затрат трафика, переложив функции маршрутизации и разделения трафика на Xray. Это позволяет избежать утомительной работы по обслуживанию таблиц маршрутизации и повышает технический уровень. + +## Благодарности + +[XTLS/Xray-core](https://github.com/XTLS/Xray-core); [v2fly/v2ray-core](https://github.com/v2fly/v2ray-core); [WireGuard](https://www.wireguard.com/); [@p3terx](https://p3terx.com/); @w; @Hiram; @Luminous; @Ln; @JackChou; + diff --git a/docs/ru/document/level-2/tproxy.md b/docs/ru/document/level-2/tproxy.md new file mode 100644 index 000000000..10c594125 --- /dev/null +++ b/docs/ru/document/level-2/tproxy.md @@ -0,0 +1,357 @@ +--- +title: Прозрачное проксирование TProxy +--- + +# Руководство по настройке прозрачного проксирования (TProxy) + +Эта конфигурация основана на [Новом руководстве по V2Ray на русском языке - Прозрачное проксирование (TPROXY)](https://guide.v2fly.org/app/tproxy.html) с добавлением новых функций Xray, использованием схемы VLESS + XTLS Vision и изменением режима разделения трафика с проксирования по умолчанию на прямое подключение по умолчанию. Пользователи должны настроить конфигурацию в соответствии со своими потребностями. + +Все конфигурации, представленные в этой статье, были успешно протестированы в средах Raspberry Pi 2B и Ubuntu 20.04. При использовании в других средах вам может потребоваться изменить конфигурацию. + +## Перед началом работы + +Убедитесь, что на вашем устройстве есть доступное сетевое подключение, сервер настроен, а клиент установлен. + +Обратите внимание, что многие руководства по настройке прозрачного проксирования предлагают включить переадресацию IP в системе Linux, но это может привести к снижению производительности Splice. Дополнительную информацию см. в статье [Расследование снижения производительности Splice до уровня ниже, чем Direct](https://github.com/XTLS/Xray-core/discussions/59). + +Хочу добавить, что многие руководства по настройке прозрачного проксирования используют Netfilter для разделения трафика, отправляя прямой трафик напрямую, минуя Xray. В этом случае необходимо включить переадресацию IP. +Другие руководства, например это, направляют весь трафик через Xray, где он разделяется модулем маршрутизации Xray. В этом случае переадресацию IP включать не нужно. + +## Настройка Xray + +Для лучшего разделения трафика замените файл правил маршрутизации по умолчанию на [Loyalsoldier/v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat), иначе Xray-core не сможет загрузить эту конфигурацию. + +```bash +sudo curl -oL /usr/local/share/xray/geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat +sudo curl -oL /usr/local/share/xray/geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat +``` + +```json +{ + "log": { + "loglevel": "warning", + "error": "/var/log/xray/error.log", + "access": "/var/log/xray/access.log" + }, + "inbounds": [ + { + "tag": "all-in", + "port": 12345, + "protocol": "dokodemo-door", + "settings": { + "network": "tcp,udp", + "followRedirect": true + }, + "sniffing": { + "enabled": true, + "destOverride": ["http", "tls"] + }, + "streamSettings": { + "sockopt": { + "tproxy": "tproxy" + } + } + } + ], + "outbounds": [ + { + "tag": "direct", + "protocol": "freedom", + "settings": { + "domainStrategy": "UseIPv4" + }, + "streamSettings": { + "sockopt": { + "mark": 2 + } + } + }, + { + "tag": "proxy", + "protocol": "vless", + "settings": { + "vnext": [ + { + "address": "домен сервера", + "port": 443, + "users": [ + { + "id": "UUID", + "flow": "xtls-rprx-vision", + "encryption": "none" + } + ] + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "xtls", + "sockopt": { + "mark": 2 + } + } + }, + { + "tag": "block", + "protocol": "blackhole", + "settings": { + "response": { + "type": "http" + } + } + }, + { + "tag": "dns-out", + "protocol": "dns", + "settings": { + "address": "8.8.8.8" + }, + "proxySettings": { + "tag": "proxy" + }, + "streamSettings": { + "sockopt": { + "mark": 2 + } + } + } + ], + "dns": { + "hosts": { + "домен сервера": "IP-адрес сервера" + }, + "servers": [ + { + "address": "119.29.29.29", + "port": 53, + "domains": ["geosite:cn"], + "expectIPs": ["geoip:cn"] + }, + { + "address": "223.5.5.5", + "port": 53, + "domains": ["geosite:cn"], + "expectIPs": ["geoip:cn"] + }, + "8.8.8.8", + "1.1.1.1", + "https+local://doh.dns.sb/dns-query" + ] + }, + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + { + "type": "field", + "inboundTag": ["all-in"], + "port": 53, + "outboundTag": "dns-out" + }, + { + "type": "field", + "ip": ["8.8.8.8", "1.1.1.1"], + "outboundTag": "proxy" + }, + { + "type": "field", + "domain": ["geosite:category-ads-all"], + "outboundTag": "block" + }, + { + "type": "field", + "domain": ["geosite:geolocation-!cn"], + "outboundTag": "proxy" + }, + { + "type": "field", + "ip": ["geoip:telegram"], + "outboundTag": "proxy" + } + ] + } +} +``` + +::: tip Совет +Эта конфигурация перехватывает весь трафик, направляемый на порт 53, для решения проблемы загрязнения DNS, поэтому адрес DNS-сервера на клиенте и на самом устройстве можно настроить произвольно. +::: + +## Настройка маршрутизации по политике + +``` +sudo ip route add local default dev lo table 100 # Добавить таблицу маршрутизации 100 +sudo ip rule add fwmark 1 table 100 # Добавить правило для таблицы маршрутизации 100 +``` + +## Настройка Netfilter + +::: warning Внимание +Выберите одну из следующих конфигураций: nftables или iptables. Не используйте обе одновременно. +::: + + + + + +```nftables +#!/usr/sbin/nft -f + +flush ruleset + +define RESERVED_IP = { + 10.0.0.0/8, + 100.64.0.0/10, + 127.0.0.0/8, + 169.254.0.0/16, + 172.16.0.0/12, + 192.0.0.0/24, + 224.0.0.0/4, + 240.0.0.0/4, + 255.255.255.255/32 +} + +table ip xray { + chain prerouting { + type filter hook prerouting priority mangle; policy accept; + ip daddr $RESERVED_IP return + ip daddr 192.168.0.0/16 tcp dport != 53 return + ip daddr 192.168.0.0/16 udp dport != 53 return + ip protocol tcp tproxy to 127.0.0.1:12345 meta mark set 1 + ip protocol udp tproxy to 127.0.0.1:12345 meta mark set 1 + } + chain output { + type route hook output priority mangle; policy accept; + ip daddr $RESERVED_IP return + ip daddr 192.168.0.0/16 tcp dport != 53 return + ip daddr 192.168.0.0/16 udp dport != 53 return + meta mark 2 return + ip protocol tcp meta mark set 1 + ip protocol udp meta mark set 1 + } +} +``` + +::: tip Использование + +Запишите приведенную выше конфигурацию в файл (например, `nft.conf`), затем предоставьте файлу права на выполнение и выполните его от имени пользователя root ( `# ./nft.conf` ). +::: + + + + + +```bash +iptables -t mangle -N XRAY +iptables -t mangle -A XRAY -d 10.0.0.0/8 -j RETURN +iptables -t mangle -A XRAY -d 100.64.0.0/10 -j RETURN +iptables -t mangle -A XRAY -d 127.0.0.0/8 -j RETURN +iptables -t mangle -A XRAY -d 169.254.0.0/16 -j RETURN +iptables -t mangle -A XRAY -d 172.16.0.0/12 -j RETURN +iptables -t mangle -A XRAY -d 192.0.0.0/24 -j RETURN +iptables -t mangle -A XRAY -d 224.0.0.0/4 -j RETURN +iptables -t mangle -A XRAY -d 240.0.0.0/4 -j RETURN +iptables -t mangle -A XRAY -d 255.255.255.255/32 -j RETURN +iptables -t mangle -A XRAY -d 192.168.0.0/16 -p tcp ! --dport 53 -j RETURN +iptables -t mangle -A XRAY -d 192.168.0.0/16 -p udp ! --dport 53 -j RETURN +iptables -t mangle -A XRAY -p tcp -j TPROXY --on-port 12345 --tproxy-mark 1 +iptables -t mangle -A XRAY -p udp -j TPROXY --on-port 12345 --tproxy-mark 1 +iptables -t mangle -A PREROUTING -j XRAY + +iptables -t mangle -N XRAY_SELF +iptables -t mangle -A XRAY_SELF -d 10.0.0.0/8 -j RETURN +iptables -t mangle -A XRAY_SELF -d 100.64.0.0/10 -j RETURN +iptables -t mangle -A XRAY_SELF -d 127.0.0.0/8 -j RETURN +iptables -t mangle -A XRAY_SELF -d 169.254.0.0/16 -j RETURN +iptables -t mangle -A XRAY_SELF -d 172.16.0.0/12 -j RETURN +iptables -t mangle -A XRAY_SELF -d 192.0.0.0/24 -j RETURN +iptables -t mangle -A XRAY_SELF -d 224.0.0.0/4 -j RETURN +iptables -t mangle -A XRAY_SELF -d 240.0.0.0/4 -j RETURN +iptables -t mangle -A XRAY_SELF -d 255.255.255.255/32 -j RETURN +iptables -t mangle -A XRAY_SELF -d 192.168.0.0/16 -p tcp ! --dport 53 -j RETURN +iptables -t mangle -A XRAY_SELF -d 192.168.0.0/16 -p udp ! --dport 53 -j RETURN +iptables -t mangle -A XRAY_SELF -m mark --mark 2 -j RETURN +iptables -t mangle -A XRAY_SELF -p tcp -j MARK --set-mark 1 +iptables -t mangle -A XRAY_SELF -p udp -j MARK --set-mark 1 +iptables -t mangle -A OUTPUT -j XRAY_SELF +``` + + + + + +После завершения настройки измените шлюз по умолчанию на других устройствах в локальной сети на IP-адрес этого устройства, и они смогут использовать VPN. После того, как вы проверите, что все работает правильно на других хостах и на самом устройстве, перейдите к следующему шагу. + +## Настройка автозагрузки и сохранения конфигурации + +
+ + + + + +Сначала переместите отредактированный файл конфигурации nftables в каталог `/etc` и переименуйте его в `nftables.conf`. +Затем отредактируйте файл `/lib/systemd/system/nftables.service`. + +```ini +[Unit] +Description=nftables +Documentation=man:nft(8) http://wiki.nftables.org +Wants=network-pre.target +Before=network-pre.target shutdown.target +Conflicts=shutdown.target +DefaultDependencies=no + +[Service] +Type=oneshot +RemainAfterExit=yes +StandardInput=null +ProtectSystem=full +ProtectHome=true +ExecStart=/usr/sbin/nft -f /etc/nftables.conf ; /usr/sbin/ip route add local default dev lo table 100 ; /usr/sbin/ip rule add fwmark 1 table 100 +ExecReload=/usr/sbin/nft -f /etc/nftables.conf +ExecStop=/usr/sbin/nft flush ruleset ; /usr/sbin/ip route del local default dev lo table 100 ; /usr/sbin/ip rule del table 100 + +[Install] +WantedBy=sysinit.target +``` + +Наконец, выполните команду `systemctl enable nftables`. + + + + + +Для сохранения конфигурации iptables рекомендуется установить пакет `iptables-persistent`. + +Во время установки вам будет предложено сохранить конфигурацию. +Если вы уже записали конфигурацию iptables в систему, выберите "Да". +Если вы еще не записали конфигурацию, это не проблема. После установки запишите конфигурацию и выполните команду `netfilter-persistent save` (требуются права root). + +Затем отредактируйте файл `/lib/systemd/system/netfilter-persistent.service`. + +```ini +[Unit] +Description=netfilter persistent configuration +DefaultDependencies=no +Wants=network-pre.target systemd-modules-load.service local-fs.target +Before=network-pre.target shutdown.target +After=systemd-modules-load.service local-fs.target +Conflicts=shutdown.target +Documentation=man:netfilter-persistent(8) + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/sbin/netfilter-persistent start ; /usr/sbin/ip route add local default dev lo table 100 ; /usr/sbin/ip rule add fwmark 1 table 100 +ExecStop=/usr/sbin/netfilter-persistent stop ; /usr/sbin/ip route flush dev lo table 100 ; /usr/sbin/ip rule del table 100 + +[Install] +WantedBy=multi-user.target +``` + + + + + + diff --git a/docs/ru/document/level-2/tproxy_ipv4_and_ipv6.md b/docs/ru/document/level-2/tproxy_ipv4_and_ipv6.md new file mode 100644 index 000000000..ec315054a --- /dev/null +++ b/docs/ru/document/level-2/tproxy_ipv4_and_ipv6.md @@ -0,0 +1,597 @@ +--- +title: Прозрачное проксирование TProxy (ipv4 и ipv6) +--- + +# Руководство по настройке прозрачного проксирования TProxy (ipv4 и ipv6) + +Эта конфигурация основана на [Новом руководстве по V2Ray на русском языке - Прозрачное проксирование (TPROXY)](https://guide.v2fly.org/app/tproxy.html), [Руководстве по настройке прозрачного проксирования (TProxy)](https://xtls.github.io/document/level-2/tproxy.html#%E5%BC%80%E5%A7%8B%E4%B9%8B%E5%89%8D) и [Прозрачное проксирование: Исключение трафика Xray с помощью GID](https://xtls.github.io/document/level-2/iptables_gid.html). Она включает поддержку IPv6 для прозрачного проксирования и использует схему VLESS-TCP-XTLS-RPRX-Vision для обхода блокировок (рекомендуется использовать версии 1.7.2 и выше). + +Настройка Xray не является основной темой данной статьи. Пользователи могут изменять ее в соответствии со своими потребностями. Подробную информацию можно найти в [примерах официальной документации](https://github.com/XTLS/Xray-examples) или в других отличных примерах, таких как [@chika0801](https://github.com/chika0801/Xray-examples) и [@lxhao61](https://github.com/lxhao61/integrated-examples). + +::: warning Внимание + +При использовании других конфигураций обратите особое внимание на часть `outbound` с тегом `proxy` в конфигурации клиента. Остальные части остаются неизменными. + +Конфигурация сервера также должна быть изменена соответственно. +::: + +Эта конфигурация предназначена для решения проблемы, когда такие сайты, как Netflix, которые по умолчанию используют IPv6, не могут быть проксированы через пограничный маршрутизатор, или когда требуется проксирование IPv6. + +В данной статье используется сетевая структура с пограничным маршрутизатором с одним интерфейсом. + +Все конфигурации, представленные в этой статье, были успешно протестированы в среде Arch Linux (Kernel: 6.0.10). В других средах настройка аналогична. + +Убедитесь, что установлены необходимые программы: `# sudo apt install iptables ip6tables` или `# sudo apt install nftables`. + +Если на пограничном маршрутизаторе не установлена программа Xray, можно вручную скачать соответствующую версию Xray, например [Xray-linux-64.zip](https://github.com/XTLS/Xray-core/releases/download/v1.7.0/Xray-linux-64.zip), а затем скопировать файл [install-release.sh](https://github.com/XTLS/Xray-install/blob/main/install-release.sh) на пограничный маршрутизатор. Предоставьте файлу права на выполнение `# chmod 700 install-release.sh` и запустите его с помощью команды `# ./install-release.sh --local Xray-linux-64.zip`. Следуйте инструкциям для локальной установки. + +## Настройка Xray + +### Конфигурация клиента + +```json +{ + "log": { + "loglevel": "warning" + }, + "inbounds": [ + { + "tag": "all-in", + "port": 12345, + "protocol": "dokodemo-door", + "settings": { + "network": "tcp,udp", + "followRedirect": true + }, + "sniffing": { + "enabled": true, + "destOverride": ["http", "tls", "quic"] + }, + "streamSettings": { + "sockopt": { + "tproxy": "tproxy" + } + } + }, + { + "port": 10808, + "protocol": "socks", + "sniffing": { + "enabled": true, + "destOverride": ["http", "tls", "quic"] + }, + "settings": { + "auth": "noauth", + "udp": true + } + } + ], + "outbounds": [ + { + // Это исходящее подключение по умолчанию. Если модуль маршрутизации (routing) не найдет подходящего правила, трафик будет направлен через этот выходной узел proxy. + // Если вы хотите, чтобы трафик в Китай направлялся напрямую, переместите исходящее подключение direct на первое место в списке outbound. + // Если вы не понимаете, что это значит, просто пропустите этот комментарий. + "tag": "proxy", + "protocol": "vless", + "settings": { + "vnext": [ + { + "address": "yourdomain.domain", // Замените на ваше доменное имя, также можно использовать IPv4- или IPv6-адрес. + "port": 443, + "users": [ + { + "id": "uuid", // Введите UUID, который можно сгенерировать, выполнив команду xray uuid в терминале. + // Также поддерживаются произвольные строки (https://xtls.github.io/config/inbounds/vless.html#clientobject). + "encryption": "none", + "flow": "xtls-rprx-vision" + } + ] + } + ] + }, + "streamSettings": { + "sockopt": { + "mark": 255 + }, + "network": "tcp", + "security": "tls", // При использовании управления потоком xtls-rprx-vision здесь должно быть указано tls. + "tlsSettings": { + // При использовании управления потоком xtls-rprx-vision здесь должно быть указано tlsSettings. + "allowInsecure": false, + "serverName": "yourdomain.domain", // Замените на ваше доменное имя. + "fingerprint": "chrome" // Рекомендуется сначала ознакомиться с разделом Release: https://github.com/XTLS/Xray-core/releases/tag/v1.7.3 + } + } + }, + { + "tag": "direct", + "protocol": "freedom", + "settings": { + "domainStrategy": "UseIP" + }, + "streamSettings": { + "sockopt": { + "mark": 255 + } + } + }, + { + "tag": "block", + "protocol": "blackhole", + "settings": { + "response": { + "type": "http" + } + } + }, + { + "tag": "dns-out", + "protocol": "dns", + "streamSettings": { + "sockopt": { + "mark": 255 + } + } + } + ], + "dns": { + "hosts": { + "domain:googleapis.cn": "googleapis.com", + "dns.google": "8.8.8.8", + "yourdomain.domain": "your VPS IP" // Если в разделе address исходящего подключения proxy указано доменное имя: + // для проксирования через IPv4 укажите IPv4-адрес VPS, для проксирования через IPv6 укажите IPv6-адрес VPS. + // Если в разделе address исходящего подключения proxy указан IP-адрес, эту строку можно удалить. + }, + "servers": [ + "https://1.1.1.1/dns-query", + { + "address": "119.29.29.29", + "domains": ["geosite:cn"], + "expectIPs": ["geoip:cn"] + }, + "https://dns.google/dns-query", + "223.5.5.5", + "localhost" + ] + }, + "routing": { + "domainMatcher": "mph", + "domainStrategy": "IPIfNonMatch", + "rules": [ + { + "type": "field", + "domain": ["geosite:category-ads-all"], + "outboundTag": "block" + }, + { + "type": "field", + "inboundTag": ["all-in"], + "port": 123, + "network": "udp", + "outboundTag": "direct" + }, + { + "type": "field", + "inboundTag": ["all-in"], + "port": 53, + "network": "udp", + "outboundTag": "dns-out" + }, + { + "type": "field", + "ip": ["119.29.29.29", "223.5.5.5"], + "outboundTag": "direct" + }, + { + "type": "field", + "protocol": ["bittorrent"], + "outboundTag": "direct" + }, + { + "type": "field", + "ip": ["geoip:private", "geoip:cn"], // Здесь можно добавить IP-адрес VPS, чтобы избежать проксирования трафика SSH. + "outboundTag": "direct" + }, + { + "type": "field", + "domain": ["geosite:cn"], + "outboundTag": "direct" + }, + { + "type": "field", + "ip": ["1.1.1.1", "8.8.8.8"], + "outboundTag": "proxy" + }, + { + "type": "field", + "domain": [ + "geosite:geolocation-!cn", + "domain:googleapis.cn", + "dns.google" + ], + "outboundTag": "proxy" + } + ] + } +} +``` + +### Конфигурация сервера + +```json +{ + "log": { + "loglevel": "warning" + }, + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + { + // Блокировка китайских IP-адресов для повышения безопасности. + // Также можно направить китайский трафик через Warp, см. https://xtls.github.io/document/level-2/warp.html + "type": "field", + "ip": ["geoip:cn"], + "outboundTag": "block" + } + ] + }, + "inbounds": [ + { + "port": 443, + "protocol": "vless", + "settings": { + "clients": [ + { + "id": "uuid", // Должен совпадать с UUID клиента. + "flow": "xtls-rprx-vision" + } + ], + "decryption": "none", + "fallbacks": [ + { + "dest": 8080 // Резервный порт. Требуется настройка веб-сервера, см. документацию. Можно не указывать. + } + ] + }, + "streamSettings": { + "network": "tcp", + "security": "tls", + "tlsSettings": { + "certificates": [ + { + "certificateFile": "/etc/ssl/private/fullchain.crt", + "keyFile": "/etc/ssl/private/crt.key" // Укажите пути к файлам fullchain.crt и cert.key, сгенерированным в соответствии с руководством (https://xtls.github.io/document/level-0/ch06-certificates.html#_6-4-%E6%AD%A3%E5%BC%8F%E8%AF%81%E4%B9%A6%E7%94%B3%E8%AF%B7). + } + ] + } + }, + "sniffing": { + "enabled": true, + "destOverride": ["http", "tls"] + } + } + ], + "outbounds": [ + { + "protocol": "freedom", + "tag": "direct" + }, + { + "protocol": "blackhole", + "tag": "block" + } + ] +} +``` + +## Настройка Netfilter + +### Настройка маршрутизации по политике + +```bash +# Настройка маршрутизации по политике для IPv4 +ip rule add fwmark 1 table 100 +ip route add local 0.0.0.0/0 dev lo table 100 + +# Настройка маршрутизации по политике для IPv6 +ip -6 rule add fwmark 1 table 106 +ip -6 route add local ::/0 dev lo table 106 + +# Прямое подключение через основной маршрутизатор +ip route add default via 192.168.31.1 # Укажите IPv4-адрес основного маршрутизатора. +# Если используется метод 1 для настройки доступа к интернету на устройствах локальной сети, эту команду можно не выполнять. +ip -6 route add default via fd00:6868:6868::1 # Укажите IPv6-адрес основного маршрутизатора. +# Если используется метод 1 для настройки доступа к интернету на устройствах локальной сети, эту команду можно не выполнять. + +``` + +::: tip Использование + +Скопируйте команды в терминал пограничного маршрутизатора и выполните их. +::: + +::: tip О прямом подключении через основной маршрутизатор + +Выполните команду `ip route show` на пограничном маршрутизаторе. Если используется метод 1, то после `default via` должен быть указан IP-адрес основного маршрутизатора, ничего менять не нужно. +Если используется метод 2, то после `default via` должен быть указан IP-адрес пограничного маршрутизатора. В этом случае DNS-запросы для сайтов, к которым должно быть установлено прямое подключение, будут зацикливаться, что приведет к невозможности доступа к этим сайтам. Поэтому необходимо указать IP-адрес основного маршрутизатора. +::: + +Если в настройках маршрутизатора указан пограничный маршрутизатор в качестве шлюза по умолчанию (то есть используется метод 2 для настройки доступа к интернету на устройствах локальной сети), то необходимо выполнить команду `# Прямое подключение через основной маршрутизатор`. +Кроме настройки через командную строку iproute2, можно использовать dhcpcd или systemctl-network для настройки статического IP-адреса. +В качестве примера рассмотрим dhcpcd. Отредактируйте файл `/etc/dhcpcd.conf` и добавьте следующие строки в конец файла. Измените IP-адреса в соответствии с вашей конфигурацией. +`interface` - это имя сетевого интерфейса или беспроводного устройства, которое можно узнать с помощью команды `# ip link show`. + +``` +interface enp0s25 +static ip_address=192.168.31.100/24 +static ip6_address=fd00:6868:6868::8888/64 +static routers=192.168.31.1 +static domain_name_servers=192.168.31.1 fd00:6868:6868::1 +``` + +После настройки статического IP-адреса и шлюза вам не нужно будет выполнять команду `# Прямое подключение через основной маршрутизатор` при каждой загрузке. + +::: warning Внимание + +Выберите одну из следующих конфигураций: nftables или iptables. Не используйте обе одновременно. +::: + +### Использование iptables + +В этой конфигурации IPv4 и IPv6 объединены в одном файле. + +```bash +# Проксирование устройств локальной сети (IPv4) +iptables -t mangle -N XRAY +iptables -t mangle -A XRAY -d 127.0.0.1/32 -j RETURN +iptables -t mangle -A XRAY -d 224.0.0.0/4 -j RETURN +iptables -t mangle -A XRAY -d 255.255.255.255/32 -j RETURN +iptables -t mangle -A XRAY -d 192.168.0.0/16 -p tcp -j RETURN +iptables -t mangle -A XRAY -d 192.168.0.0/16 -p udp ! --dport 53 -j RETURN +iptables -t mangle -A XRAY -j RETURN -m mark --mark 0xff +iptables -t mangle -A XRAY -p udp -j TPROXY --on-ip 127.0.0.1 --on-port 12345 --tproxy-mark 1 +iptables -t mangle -A XRAY -p tcp -j TPROXY --on-ip 127.0.0.1 --on-port 12345 --tproxy-mark 1 +iptables -t mangle -A PREROUTING -j XRAY + +# Проксирование устройств локальной сети (IPv6) +ip6tables -t mangle -N XRAY6 +ip6tables -t mangle -A XRAY6 -d ::1/128 -j RETURN +ip6tables -t mangle -A XRAY6 -d fe80::/10 -j RETURN +ip6tables -t mangle -A XRAY6 -d fd00::/8 -p tcp -j RETURN +ip6tables -t mangle -A XRAY6 -d fd00::/8 -p udp ! --dport 53 -j RETURN +ip6tables -t mangle -A XRAY6 -j RETURN -m mark --mark 0xff +ip6tables -t mangle -A XRAY6 -p udp -j TPROXY --on-ip ::1 --on-port 12345 --tproxy-mark 1 +ip6tables -t mangle -A XRAY6 -p tcp -j TPROXY --on-ip ::1 --on-port 12345 --tproxy-mark 1 +ip6tables -t mangle -A PREROUTING -j XRAY6 + +# Проксирование хоста шлюза (IPv4) +iptables -t mangle -N XRAY_MASK +iptables -t mangle -A XRAY_MASK -d 224.0.0.0/4 -j RETURN +iptables -t mangle -A XRAY_MASK -d 255.255.255.255/32 -j RETURN +iptables -t mangle -A XRAY_MASK -d 192.168.0.0/16 -p tcp -j RETURN +iptables -t mangle -A XRAY_MASK -d 192.168.0.0/16 -p udp ! --dport 53 -j RETURN +iptables -t mangle -A XRAY_MASK -j RETURN -m mark --mark 0xff +iptables -t mangle -A XRAY_MASK -p udp -j MARK --set-mark 1 +iptables -t mangle -A XRAY_MASK -p tcp -j MARK --set-mark 1 +iptables -t mangle -A OUTPUT -j XRAY_MASK + +# Проксирование хоста шлюза (IPv6) +ip6tables -t mangle -N XRAY6_MASK +ip6tables -t mangle -A XRAY6_MASK -d fe80::/10 -j RETURN +ip6tables -t mangle -A XRAY6_MASK -d fd00::/8 -p tcp -j RETURN +ip6tables -t mangle -A XRAY6_MASK -d fd00::/8 -p udp ! --dport 53 -j RETURN +ip6tables -t mangle -A XRAY6_MASK -j RETURN -m mark --mark 0xff +ip6tables -t mangle -A XRAY6_MASK -p udp -j MARK --set-mark 1 +ip6tables -t mangle -A XRAY6_MASK -p tcp -j MARK --set-mark 1 +ip6tables -t mangle -A OUTPUT -j XRAY6_MASK + +# Создание правила DIVERT, чтобы избежать повторного прохождения пакетов с существующими подключениями через TPROXY, что теоретически повышает производительность (IPv4) +iptables -t mangle -N DIVERT +iptables -t mangle -A DIVERT -j MARK --set-mark 1 +iptables -t mangle -A DIVERT -j ACCEPT +iptables -t mangle -I PREROUTING -p tcp -m socket -j DIVERT + +# Создание правила DIVERT, чтобы избежать повторного прохождения пакетов с существующими подключениями через TPROXY, что теоретически повышает производительность (IPv6) +ip6tables -t mangle -N DIVERT +ip6tables -t mangle -A DIVERT -j MARK --set-mark 1 +ip6tables -t mangle -A DIVERT -j ACCEPT +ip6tables -t mangle -I PREROUTING -p tcp -m socket -j DIVERT + +``` + +::: tip Использование + +Запишите приведенную выше конфигурацию в файл (например, `iptables.rules`), затем предоставьте файлу права на выполнение `# chmod 700 ./iptables.rules`. + +Наконец, выполните файл от имени пользователя root: `# ./iptables.rules` или `# source iptables.rules`. +::: + +### Использование nftables + +В этой конфигурации IPv4 и IPv6 объединены. + +``` +#!/usr/sbin/nft -f + +flush ruleset + +table inet xray { + chain prerouting { + type filter hook prerouting priority filter; policy accept; + ip daddr { 127.0.0.0/8, 224.0.0.0/4, 255.255.255.255 } return + meta l4proto tcp ip daddr 192.168.0.0/16 return + ip daddr 192.168.0.0/16 udp dport != 53 return + ip6 daddr { ::1, fe80::/10 } return + meta l4proto tcp ip6 daddr fd00::/8 return + ip6 daddr fd00::/8 udp dport != 53 return + meta mark 0x000000ff return + meta l4proto { tcp, udp } meta mark set 0x00000001 tproxy ip to 127.0.0.1:12345 accept + meta l4proto { tcp, udp } meta mark set 0x00000001 tproxy ip6 to [::1]:12345 accept + } + + chain output { + type route hook output priority filter; policy accept; + ip daddr { 127.0.0.0/8, 224.0.0.0/4, 255.255.255.255 } return + meta l4proto tcp ip daddr 192.168.0.0/16 return + ip daddr 192.168.0.0/16 udp dport != 53 return + ip6 daddr { ::1, fe80::/10 } return + meta l4proto tcp ip6 daddr fd00::/8 return + ip6 daddr fd00::/8 udp dport != 53 return + meta mark 0x000000ff return + meta l4proto { tcp, udp } meta mark set 0x00000001 accept + } + + chain divert { + type filter hook prerouting priority mangle; policy accept; + meta l4proto tcp socket transparent 1 meta mark set 0x00000001 accept + } +} + +``` + +::: tip Использование + +Запишите приведенную выше конфигурацию в файл (например, `nftables.rules`), затем предоставьте файлу права на выполнение `# chmod 700 ./nftables.rules`. + +Наконец, выполните файл от имени пользователя root: `# ./nftables.rules` или `# source nftables.rules`. +::: + +Адреса шлюза `192.168.0.0/16`, `fd00::/8` и т.д. можно получить с помощью команд `ip address | grep -w inet | awk '{print $2}'` и `ip address | grep -w inet6 | awk '{print $2}'` [ссылка](https://xtls.github.io/document/level-2/iptables_gid.html#_4-%E8%AE%BE%E7%BD%AE-iptables-%E8%A7%84%E5%88%99). + +Или посмотреть в настройках сети Windows. + +Или посмотреть в настройках интернета на маршрутизаторе. + +Если префиксы `192.168`, `fd00:` совпадают, их можно не менять. +Если они отличаются, например, `fc00:`, `fe00:` и т.д., замените их на соответствующие значения. +Синтаксис можно найти в Google, например, `fc00::/7`, `fe00::/9`. + +### Автоматический запуск конфигурации Netfilter при загрузке + +Сначала убедитесь, что вы выполнили соответствующие команды Netfilter, описанные выше, и успешно протестировали настройку прозрачного проксирования, чтобы убедиться, что в дальнейшем будет сгенерирован правильный файл. + +#### При использовании конфигурации iptables + +1. Сохраните конфигурацию iptables в файлы `iptables.rulesv4` и `iptables.rulesv6` с помощью команд `# iptables-save > /root/iptables.rulesv4` и `# ip6tables-save > /root/iptables.rulesv6`. + +2. Создайте файл с именем `tproxyrules.service` в каталоге `/etc/systemd/system/` и добавьте следующее содержимое: + +``` +[Unit] +Description=Tproxy rules + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStartPre=/bin/sh -c 'until ping -c1 192.168.31.1; do sleep 1; done;' +ExecStart=/sbin/ip rule add fwmark 1 table 100 ; \ +/sbin/ip -6 rule add fwmark 1 table 106 ; \ +/sbin/ip route add local 0.0.0.0/0 dev lo table 100 ; \ +/sbin/ip -6 route add local ::/0 dev lo table 106 ; \ +/sbin/ip route add default via 192.168.31.1 ; \ +/sbin/ip -6 route add default via fd00:6868:6868::1 ; \ +/sbin/iptables-restore /root/iptables.rulesv4 ; \ +/sbin/ip6tables-restore /root/iptables.rulesv6 +ExecStop=/sbin/ip rule del fwmark 1 table 100 ; \ +/sbin/ip -6 rule del fwmark 1 table 106 ; \ +/sbin/ip route del local 0.0.0.0/0 dev lo table 100 ; \ +/sbin/ip -6 route del local ::/0 dev lo table 106 ; \ +/sbin/ip route del default via 192.168.31.1 ; \ +/sbin/ip -6 route del default via fd00:6868:6868::1 ; \ +/sbin/iptables -t mangle -F ; \ +/sbin/ip6tables -t mangle -F + +[Install] +WantedBy=multi-user.target +``` + +3. Выполните команду `systemctl enable tproxyrules`. + +#### При использовании конфигурации nftables + +1. Сохраните конфигурацию nftables в файл `nftables.rulesv46` с помощью команды `# nft list ruleset > /root/nftables.rulesv46`. + +2. Создайте файл с именем `tproxyrules.service` в каталоге `/etc/systemd/system/` и добавьте следующее содержимое: + +``` +[Unit] +Description=Tproxy rules + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStartPre=/bin/sh -c 'until ping -c1 192.168.31.1; do sleep 1; done;' +ExecStart=/sbin/ip rule add fwmark 1 table 100 ; \ +/sbin/ip -6 rule add fwmark 1 table 106 ; \ +/sbin/ip route add local 0.0.0.0/0 dev lo table 100 ; \ +/sbin/ip -6 route add local ::/0 dev lo table 106 ; \ +/sbin/ip route add default via 192.168.31.1 ; \ +/sbin/ip -6 route add default via fd00:6868:6868::1 ; \ +/sbin/nft -f /root/nftables.rulesv46 ; +ExecStop=/sbin/ip rule del fwmark 1 table 100 ; \ +/sbin/ip -6 rule del fwmark 1 table 106 ; \ +/sbin/ip route del local 0.0.0.0/0 dev lo table 100 ; \ +/sbin/ip -6 route del local ::/0 dev lo table 106 ; \ +/sbin/ip route del default via 192.168.31.1 ; \ +/sbin/ip -6 route del default via fd00:6868:6868::1 ; \ +/sbin/nft flush ruleset + +[Install] +WantedBy=multi-user.target +``` + +3. Выполните команду `systemctl enable tproxyrules`. + +::: tip tproxyrules.service + +Обратите внимание на IP-адрес основного маршрутизатора и измените его в соответствии с вашей конфигурацией. + +Команда `ExecStartPre=/bin/sh -c 'until ping -c1 192.168.31.1; do sleep 1; done;'` гарантирует, что команды будут выполнены только после получения IP-адреса, иначе могут возникнуть странные ошибки. IP-адрес - это адрес основного маршрутизатора, измените его в соответствии с вашей конфигурацией. +::: + +::: warning Внимание + +Если вы настроили статический IP-адрес и шлюз с помощью dhcpcd и т.д., удалите соответствующие строки `ip route add/del` из приведенных выше конфигураций. +::: + +## Настройка доступа к интернету на устройствах локальной сети + +Предположим, что IPv4- и IPv6-адреса пограничного маршрутизатора - `192.168.31.100` и `fd00:6868:6868::8866` соответственно. +IP-адреса пограничного маршрутизатора можно узнать с помощью команды `ip add`. + +### Метод 1 + +Есть два способа настроить доступ к интернету на устройствах локальной сети. +Первый способ - настроить статический IP-адрес на каждом устройстве и указать IP-адрес пограничного маршрутизатора в качестве шлюза. +Обратите внимание, что большинство мобильных устройств поддерживают только ручную настройку IPv4-шлюза и не поддерживают ручную настройку IPv6-шлюза, если не получены root-права и не выполнены соответствующие настройки. + +В качестве примера рассмотрим устройство Windows. +Можно сначала включить DHCP и записать автоматически назначенный IP-адрес для справки, а затем вручную настроить статический IP-адрес. + +::: tip Настройка DNS + +В этой конфигурации перехватывается DNS-трафик, поэтому DNS можно указать произвольно. + +Рекомендуется указать IP-адрес пограничного маршрутизатора, чтобы избежать утечки DNS. +::: + +image image + +### Метод 2 + +Второй способ настроить доступ к интернету на устройствах локальной сети - указать пограничный маршрутизатор в качестве шлюза в настройках маршрутизатора. +Этот метод не требует настройки на каждом устройстве, подключенном к маршрутизатору, но обратите внимание, что некоторые маршрутизаторы не поддерживают настройку IPv6-шлюза, поэтому устройствам, которым требуется IPv6, необходимо вручную настроить IPv6 в соответствии с методом 1. + +image + +## Результаты + +После настройки в соответствии с вышеуказанными инструкциями устройства смогут получать доступ к интернету по IPv4 и IPv6. +На тестовом сайте, например https://ipv6-test.com/, вы увидите следующие результаты (сайт должен быть проксирован, чтобы увидеть эти результаты): + +image + +## Заключение + +В настоящее время IPv6 еще не получил широкого распространения, и 99% трафика, к которому мы обращаемся, по-прежнему приходится на IPv4. +Многие провайдеры VPS \ No newline at end of file diff --git a/docs/ru/document/level-2/traffic_stats.md b/docs/ru/document/level-2/traffic_stats.md new file mode 100644 index 000000000..440c413db --- /dev/null +++ b/docs/ru/document/level-2/traffic_stats.md @@ -0,0 +1,122 @@ +--- +title: Статистика трафика +--- + +# Руководство по настройке статистики трафика + +Ознакомьтесь с [руководством по статистике трафика](https://guide.v2fly.org/advanced/traffic.html). +Эта статья адаптирует его для Xray (1.5.9+). + +## Просмотр статистики трафика + +Способ настройки такой же, как и для v2fly. +Просмотр статистики трафика - одна из функций командной строки Xray. Порт api dokodemo-door, указанный в конфигурации, - это порт, используемый в параметре `--server`. + +```bash +xray api statsquery --server=127.0.0.1:10085 # Просмотр всей статистики трафика +xray help api statsquery # statsquery - запрос соответствующих записей +xray help api stats # stats - запрос одной записи +``` + +Пример вывода: + +```json +{ + "stat": [ + { + "name": "inbound>>>vmess-quic>>>traffic>>>downlink", + "value": "1176" + }, + { + "name": "user>>>love@example.com>>>traffic>>>downlink", + "value": "2040" + }, + { + "name": "inbound>>>api>>>traffic>>>uplink", + "value": "14247" + }, + { + "name": "user>>>love@example.com>>>traffic>>>uplink", + "value": "2520" + }, + { + "name": "inbound>>>api>>>traffic>>>downlink", + "value": "87618" + }, + { + "name": "outbound>>>direct>>>traffic>>>downlink", + "value": "0" + }, + { + "name": "inbound>>>vmess-quic>>>traffic>>>uplink", + "value": "1691" + }, + { + "name": "outbound>>>direct>>>traffic>>>uplink", + "value": "0" + } + ] +} +``` + +## Обработка статистики трафика + +Сохраните следующий скрипт в файл `traffic.sh` и предоставьте ему права на выполнение с помощью команды `chmod 755 traffic.sh`. +Не забудьте изменить строку `_APISERVER`, указав правильный порт. + +```bash +#!/bin/bash + +_APISERVER=127.0.0.1:10085 +_XRAY=/usr/local/bin/xray + +apidata () { + local ARGS= + if [[ $1 == "reset" ]]; then + ARGS="-reset=true" + fi + $_XRAY api statsquery --server=$_APISERVER "${ARGS}" \ + | awk '{ + if (match($1, /"name":/)) { + f=1; gsub(/^"|link"|,$/, "", $2); + split($2, p, ">>>"); + printf "%s:%s->%s\t", p[1],p[2],p[4]; + } + else if (match($1, /"value":/) && f){ + f = 0; + gsub(/"/, "", $2); + printf "%.0f\n", $2; + } + else if (match($0, /}/) && f) { f = 0; print 0; } + }' +} + +print_sum() { + local DATA="$1" + local PREFIX="$2" + local SORTED=$(echo "$DATA" | grep "^${PREFIX}" | sort -r) + local SUM=$(echo "$SORTED" | awk ' + /->up/{us+=$2} + /->down/{ds+=$2} + END{ + printf "SUM->up:\t%.0f\nSUM->down:\t%.0f\nSUM->TOTAL:\t%.0f\n", us, ds, us+ds; + }') + echo -e "${SORTED}\n${SUM}" \ + | numfmt --field=2 --suffix=B --to=iec \ + | column -t +} + +DATA=$(apidata $1) +echo "------------Inbound----------" +print_sum "$DATA" "inbound" +echo "-----------------------------" +echo "------------Outbound----------" +print_sum "$DATA" "outbound" +echo "-----------------------------" +echo +echo "-------------User------------" +print_sum "$DATA" "user" +echo "-----------------------------" +``` + + diff --git a/docs/ru/document/level-2/transparent_proxy/netfilter.png b/docs/ru/document/level-2/transparent_proxy/netfilter.png new file mode 100644 index 000000000..175a75ea4 Binary files /dev/null and b/docs/ru/document/level-2/transparent_proxy/netfilter.png differ diff --git a/docs/ru/document/level-2/transparent_proxy/transparent_proxy.md b/docs/ru/document/level-2/transparent_proxy/transparent_proxy.md new file mode 100644 index 000000000..3065e6882 --- /dev/null +++ b/docs/ru/document/level-2/transparent_proxy/transparent_proxy.md @@ -0,0 +1,281 @@ +## Погружение в прозрачное проксирование + +### Что такое прозрачное проксирование? + +Проще говоря, прозрачное проксирование не позволяет проксируемому устройству понять, что оно проксируется. Это означает, что на проксируемом устройстве не нужно запускать какое-либо программное обеспечение для проксирования (например, Xray, V2RayNG и т. д.). Когда вы подключаетесь к сети, ваше устройство уже проксируется. + +Это также означает, что программное обеспечение прокси работает в другом месте, например, на маршрутизаторе, и устройства, подключенные к Интернету через маршрутизатор, автоматически проксируются. + +### Реализация прозрачного проксирования + +В настоящее время существует два основных способа реализации прозрачного проксирования: + +### tun2socks + +Доступно для Windows/Linux (включая Android). Поскольку процесс реализации относительно прост, существует не так много руководств, поэтому я кратко опишу его здесь. + +**Windows** + +1. Установите **[Netch](https://github.com/NetchX/Netch/releases)**, используя режим `[3] [TUN/TAP] Обход локальной сети` для запуска. + +2. Включите точку доступа. + +3. Откройте `Панель управления` -> `Сеть и Интернет` -> `Центр управления сетями и общим доступом` -> `Изменение параметров адаптера`, найдите `TAP-Windows Adapter` и `Microsoft Wi-Fi Direct Virtual Adapter`. + +4. Щелкните правой кнопкой мыши `TAP-Windows Adapter`, `Свойства` -> `Доступ`, установите флажок `Разрешить другим пользователям сети подключаться к Интернету через это подключение к Интернету`, в `Домашнее сетевое подключение` выберите сетевое подключение `Microsoft Wi-Fi Direct Virtual Adapter`, нажмите `ОК`. + +**Android** + +1. Настройте подключение V2RayNG. + +2. Включите точку доступа. + +3. Настройки точки доступа -> Разрешить использование VPN для точки доступа (эта опция может отсутствовать в некоторых системах Android). + +### iptables/nftables + +iptables и nftables реализуют прозрачное проксирование по одному и тому же принципу, в дальнейшем мы будем использовать iptables. + +Реализация прозрачного проксирования на основе iptables применима только к системам Linux (включая openwrt/Android). Благодаря своей эффективности по сравнению с tun2socks и возможности настройки на маршрутизаторах, она получила широкое распространение. + +Существующие три русскоязычных руководства по прозрачному проксированию на самом деле описывают реализацию прозрачного проксирования на основе этого решения: **[Новое руководство по V2Ray на русском языке - Прозрачное проксирование](https://guide.v2fly.org/app/transparent_proxy.html)**, **[Новое руководство по V2Ray на русском языке - Прозрачное проксирование (TPROXY)](https://guide.v2fly.org/app/tproxy.html)**, **[Руководство по настройке прозрачного проксирования (TProxy)](../tproxy.md)**. Первое основано на устаревшем режиме iptables-redirect, который не рекомендуется использовать и приводится только для справки. Второе и третье описывают реализацию прозрачного проксирования на основе режима iptables-tproxy. + +## Принцип реализации прозрачного проксирования с помощью iptables + +Linux использует `Netfilter` для управления сетью, модель `Netfilter` выглядит следующим образом: + +![Netfilter](./netfilter.png) + +**Предположим, что в качестве шлюза используется маршрутизатор (т. е. наш обычный способ подключения к Интернету), тогда:** + +Направление трафика от устройств локальной сети к Интернету через маршрутизатор: + +`Цепочка PREROUTING -> Цепочка FORWARD -> Цепочка POSTINGROUTING` + +Направление трафика от устройств локальной сети к маршрутизатору (например, вход в веб-интерфейс маршрутизатора/подключение к маршрутизатору по ssh/доступ к DNS-серверу маршрутизатора и т. д.): + +`Цепочка PREROUTING -> Цепочка INPUT -> Хост шлюза` + +Направление трафика от маршрутизатора к Интернету: + +`Хост шлюза -> Цепочка OUTPUT -> Цепочка POSTINGROUTING` + +**Управляя направлением трафика цепочек `PREROUTING` и `OUTPUT` с помощью iptables и перенаправляя его на Xray, мы можем проксировать устройства локальной сети и хост шлюза.** + +## В чем сложность прозрачного проксирования? + +Сложность прозрачного проксирования заключается в маршрутизации, то есть в различении того, какой трафик должен быть прямым, а какой должен проксироваться, поэтому я лично считаю, что термин **разделение трафика** более уместен. + +Мы можем разделить маршрутизацию на следующие этапы по возрастанию сложности: + +1. Проксирование всех запросов. +2. Прямое подключение для локальных IP-адресов/многоадресных IP-адресов, проксирование для остальных запросов. +3. На основе пункта 2, прямое подключение для исходящих запросов, инициированных Xray. +4. На основе пункта 3, прямое подключение для запросов, адресованных китайским IP-адресам, и выбор внутренних и внешних DNS-серверов для разрешения внутренних и внешних доменных имен. + +Три вышеупомянутых руководства описывают четвертый этап. Поэтому новичкам может быть сложно понять их, читая напрямую. + +## Пошаговая реализация прозрачного проксирования на основе iptables-tproxy с нуля + +### Прежде чем начать, вам необходимо иметь базовые знания: + +1. Примерное представление о протоколах TCP/IP, доменных именах и DNS-серверах. +2. Знание того, что такое WAN-порт, LAN-порт, LAN_IP, WAN_IP и DHCP-сервер. Для пограничных маршрутизаторов есть только один сетевой порт, который мы будем называть LAN-портом. +3. Базовое понимание системы Linux (знание того, как запускать команды). +4. Умение писать конфигурационные файлы клиента в формате json или, по крайней мере, понимать их. + +### Предварительная подготовка +::: warning +Перед началом работы не забудьте включить пересылку пакетов ipv4 в Linux с помощью команды `sysctl -w net.ipv4.ip_forward=1` +::: +**1. Подготовьте шлюз под управлением Linux** + +Например, маршрутизатор с прошивкой OpenWRT. + +**2. Подготовьте исполняемый файл Xray и конфигурационный файл на шлюзе (маршрутизаторе)** + +Конфигурационный файл прослушивает порт 12345 и включает tproxy: + +```json +{ + "log": { + "loglevel": "warning" + }, + "inbounds": [ + { + "port": 12345, + "protocol": "dokodemo-door", + "settings": { + "network": "tcp,udp", + "followRedirect": true + }, + "streamSettings": { + "sockopt": { + "tproxy": "tproxy" + } + } + } + ], + "outbounds": [ + { + // Конфигурация вашего сервера + } + ] +} +``` + +Мы пойдем от простого к сложному, не будем писать routing, а напишем только один inbound и один outbound. + +### Сначала давайте попробуем достичь первого этапа + +::: warning +Если вы не хотите перезагружать свою машину, лучше сначала попрактиковаться на виртуальной машине +::: + +Перенаправьте весь трафик цепочки `PREROUTING` в Xray. + +Запустите Xray и выполните следующие команды: + +```bash +ip rule add fwmark 1 table 100 +ip route add local 0.0.0.0/0 dev lo table 100 +iptables -t mangle -N XRAY +iptables -t mangle -A XRAY -p tcp -j TPROXY --on-port 12345 --tproxy-mark 1 +iptables -t mangle -A XRAY -p udp -j TPROXY --on-port 12345 --tproxy-mark 1 +iptables -t mangle -A PREROUTING -j XRAY +``` + +После ввода команд, если вы подключены к шлюзу по ssh, вы обнаружите, что соединение ssh разорвано (не беспокойтесь, перезагрузка восстановит его), и прозрачное проксирование не работает; если ваш шлюз - это виртуальная машина, вы обнаружите, что сам шлюз не может получить доступ к Интернету, и в журнале доступа Xray появится множество запросов с исходным адресом, равным целевому адресу, и целевым адресом, равным WAN_IP. + +Теоретически доступ хоста шлюза к общедоступной сети должен проходить только через цепочки `OUTPUT` и `POSTINGROUTING`, так почему же управление цепочкой `PREROUTING` приводит к тому, что шлюз не может получить доступ к Интернету? Это связано с тем, что сетевое взаимодействие, как правило, двунаправленное, и хотя доступ шлюза к общедоступному IP-адресу не требует прохождения через цепочку `PREROUTING`, информация, возвращаемая сервером на шлюз, должна проходить через цепочку `PREROUTING`, и эта часть перенаправляется на Xray, что приводит к обратным запросам в журнале. + +Давайте изменим правило, чтобы возвращать трафик, источник которого не находится в локальной сети. Перезагрузите шлюз, запустите Xray и выполните следующие команды: + +```bash +ip rule add fwmark 1 table 100 +ip route add local 0.0.0.0/0 dev lo table 100 +iptables -t mangle -N XRAY +# "Диапазон LAN-адресов шлюза" можно получить, выполнив команду "ip address | grep -w "inet" | awk '{print $2}'", это будет один из адресов +iptables -t mangle -A XRAY ! -s Диапазон LAN-адресов шлюза -j RETURN +iptables -t mangle -A XRAY -p tcp -j TPROXY --on-port 12345 --tproxy-mark 1 +iptables -t mangle -A XRAY -p udp -j TPROXY --on-port 12345 --tproxy-mark 1 +iptables -t mangle -A PREROUTING -j XRAY +``` + +Теперь вы обнаружите, что, хотя соединение ssh разорвано, прозрачное проксирование уже работает. Если мы изменим системный DNS на общедоступный DNS, мы сможем нормально просматривать веб-страницы (поскольку в настоящее время шлюз недоступен, мы не можем установить DNS на шлюз). + +На этом первый этап завершен. Причина, по которой мы не можем получить доступ к шлюзу, заключается в том, что правило проксирования проксирует весь трафик, включая трафик, адресованный шлюзу. Представьте, что вы пытаетесь получить доступ к своему локальному шлюзу с VPS, вы не сможете этого сделать, поэтому нам нужно сделать этот трафик прямым, смотрите второй этап: + +### Второй этап + +Перезагрузите шлюз, запустите Xray и выполните следующие команды: + +```bash +ip rule add fwmark 1 table 100 +ip route add local 0.0.0.0/0 dev lo table 100 +iptables -t mangle -N XRAY + +# Прямое подключение для всех запросов, адресованных сегменту сети шлюза +# Получите сегменты сети, выполнив команду "ip address | grep -w "inet" | awk '{print $2}'", как правило, их несколько +iptables -t mangle -A XRAY -d Сегмент сети шлюза 1 -j RETURN +iptables -t mangle -A XRAY -d Сегмент сети шлюза 2 -j RETURN +... + +# Прямое подключение для запросов, адресованных многоадресным IP-адресам/адресам класса E/широковещательным IP-адресам +iptables -t mangle -A XRAY -d 224.0.0.0/3 -j RETURN + +iptables -t mangle -A XRAY -p tcp -j TPROXY --on-port 12345 --tproxy-mark 1 +iptables -t mangle -A XRAY -p udp -j TPROXY --on-port 12345 --tproxy-mark 1 +iptables -t mangle -A PREROUTING -j XRAY +``` + +После использования этого правила предыдущее правило `iptables -t mangle -A XRAY ! -s Диапазон LAN-адресов шлюза -j RETURN` становится лишним и его можно удалить. + +На этом второй этап завершен. Шлюз доступен, ssh не разрывается. + +### Третий этап + +Обычно мы используем DNS, предоставляемый маршрутизатором, но это правило iptables проксирует только устройства в локальной сети, а не сам хост шлюза, поэтому возвращаемые результаты DNS-запросов могут быть неверными или загрязненными. + +iptables-tproxy не поддерживает работу с цепочкой `OUTPUT`, но мы можем настроить `маршрутизацию по политике`, чтобы перенаправлять соответствующие пакеты из цепочки `OUTPUT` обратно в цепочку `PREROUTING`. + +```bash +# Добавить маршрут по политике: пакеты с меткой 1 направляются в таблицу маршрутизации 100 +ip rule add fwmark 1 table 100 +# Добавить запись маршрута в таблицу маршрутизации 100: все пакеты направляются локально +ip route add local 0.0.0.0/0 dev lo table 100 +``` + +Настроив `маршрутизацию по политике` выше, нам нужно только пометить пакеты, которые необходимо проксировать от хоста шлюза, меткой `1` в цепочке `OUTPUT`, и соответствующие пакеты будут направлены на сам хост шлюза, то есть в цепочку `PREROUTING`. + +Если мы хотим проксировать все запросы, отправленные хостом шлюза, это вызовет проблему: Xray работает на шлюзе, Xray отправляет запросы на сервер прокси, и эти запросы снова проксируются, образуя петлю. + +Поэтому, чтобы проксировать хост шлюза, необходимо избежать зацикливания, то есть исключить трафик запросов Xray из правил проксирования. + +**Существуют три распространенных метода:** + +1. Прямое подключение трафика, адресованного VPS + +Перезагрузите шлюз, запустите Xray и выполните следующие команды: + +```bash +# Проксирование устройств локальной сети +# Наследуем достижения предыдущего этапа +ip rule add fwmark 1 table 100 +ip route add local 0.0.0.0/0 dev lo table 100 +iptables -t mangle -N XRAY +iptables -t mangle -A XRAY -d Сегмент сети шлюза 1 -j RETURN +iptables -t mangle -A XRAY -d Сегмент сети шлюза 2 -j RETURN +... +iptables -t mangle -A XRAY -d 224.0.0.0/3 -j RETURN +iptables -t mangle -A XRAY -p tcp -j TPROXY --on-port 12345 --tproxy-mark 1 +iptables -t mangle -A XRAY -p udp -j TPROXY --on-port 12345 --tproxy-mark 1 +iptables -t mangle -A PREROUTING -j XRAY + +# Проксирование хоста шлюза +iptables -t mangle -N XRAY_MASK +iptables -t mangle -A XRAY_MASK -d Сегмент сети шлюза 1 -j RETURN +iptables -t mangle -A XRAY_MASK -d Сегмент сети шлюза 2 -j RETURN +... +iptables -t mangle -A XRAY_MASK -d 224.0.0.0/3 -j RETURN +iptables -t mangle -A XRAY_MASK -d Общедоступный IP-адрес VPS/32 -j RETURN +iptables -t mangle -A XRAY_MASK -j MARK --set-mark 1 +iptables -t mangle -A OUTPUT -p tcp -j XRAY_MASK +iptables -t mangle -A OUTPUT -p udp -j XRAY_MASK +``` + +Однако у этого метода есть недостаток: если используется CDN или много VPS, то написание правил становится неудобным. + +2. Исключение с помощью меток + +Три вышеупомянутых русскоязычных руководства используют этот метод исключения, обратитесь к ним, мы не будем повторяться здесь. + +3. Исключение с помощью gid (рекомендуется) + +См. **[[Прозрачное проксирование] Исключение трафика Xray с помощью gid](../iptables_gid.md)** + +На этом третий этап проксирования, также известный как глобальное проксирование, завершен. Но не забудьте установить DNS-сервер шлюза на зарубежный DNS-сервер, иначе вы все равно можете получить загрязненные результаты. + +### Четвертый этап + +На самом деле, не всем нужно реализовывать четвертый этап. Глобальное проксирование подходит для большинства случаев. + +Особенно для пограничных маршрутизаторов. При необходимости проксирования установите шлюз на IP-адрес пограничного маршрутизатора, при отсутствии необходимости проксирования - на IP-адрес основного маршрутизатора. + +Что касается конкретной реализации четвертого этапа, то об этом подробно рассказывается в трех вышеупомянутых русскоязычных руководствах. После того, как вы поймете вышеизложенное, вам будет легче понять эти руководства. + +### Проксирование IPv6 + +Вышеуказанные правила действительны только для IPv4, если вы хотите проксировать запросы IPv6, используйте команду ip6tables, ее использование в основном такое же, как и у iptables. См. **[[Прозрачное проксирование] Исключение трафика Xray с помощью gid # 4-Настройка правил iptables](../iptables_gid # 4-Настройка правил iptables.md)** + +# Другие замечания по прозрачному проксированию с помощью iptables + +1. Если шлюз, выступающий в качестве прокси, также является основным маршрутизатором, необходимо добавить правило `iptables -t mangle -A XRAY ! -s Диапазон LAN-адресов шлюза -j RETURN` в цепочку `PREROUTING`, то есть команду, использованную на первом этапе и удаленную на втором этапе. Если этого не сделать, другие люди в той же подсети, что и WAN-порт, смогут использовать ваш WAN_IP в качестве шлюза, чтобы злоупотреблять вашим прозрачным прокси, что может быть небезопасно. + +2. В **[Новое руководство по V2Ray на русском языке - Прозрачное проксирование (TPROXY) # Настройка шлюза](https://guide.v2fly.org/app/tproxy.html # Настройка шлюза)** третий пункт гласит: `Настройте сеть ПК вручную, указав в качестве шлюза по умолчанию адрес Raspberry Pi, то есть 192.168.1.22. Теперь ПК должен иметь доступ к Интернету (поскольку проксирование еще не настроено, “доступ” означает возможность доступа к внутренним веб-сайтам)`. На самом деле, даже если включена переадресация IP, ПК под управлением Ubuntu, CentOS, Debian и других систем не смогут получить доступ к Интернету, это нормально. Фактически, только OpenWRT может сделать то, что описано в статье, как указал **[@BioniCosmos](https://github.com/BioniCosmos)**, это связано с тем, что в обычных системах Linux нет правил Masquerade. + +3. **[Проблема too many open files](https://guide.v2fly.org/app/tproxy.html#решение-проблемы-too-many-open-files)**, см. решение в **[[Прозрачное проксирование] Исключение трафика Xray с помощью gid - Настройка максимального количества открытых файлов и запуск клиента Xray](../iptables_gid#3-настройка-максимального-количества-открытых-файлов-запуск-клиента-xray)** + +4. Избегайте повторного прохождения пакетов с существующими подключениями через TPROXY, будет дополнено... + +5. Основной маршрутизатор, однорукий маршрутизатор и пограничный маршрутизатор, будет дополнено... diff --git a/docs/ru/document/level-2/warp.md b/docs/ru/document/level-2/warp.md new file mode 100644 index 000000000..b67eaa661 --- /dev/null +++ b/docs/ru/document/level-2/warp.md @@ -0,0 +1,233 @@ +--- +title: Повышение безопасности проксирования с помощью Cloudflare Warp +--- + +# Повышение безопасности проксирования с помощью Cloudflare Warp + +В Xray (1.6.5+) добавлен исходящий WireGuard. Хотя это увеличивает размер ядра из-за дополнительных кода и зависимостей, мы считаем, что это важная новая функция по трем причинам: + +1. Из недавних обсуждений и [экспериментов](https://github.com/net4people/bbs/issues/129#issuecomment-1308102504) мы знаем, что проксирование трафика в Китай небезопасно. + Одним из способов решения этой проблемы является перенаправление трафика в Китай в черный дыры. + Недостаток этого метода заключается в том, что geosite и geoip обновляются нерегулярно, и новички могут не знать, как правильно настроить разделение трафика на клиенте, в результате чего трафик попадает в черный дыры, что снижает удобство использования. + В этом случае мы можем просто перенаправить трафик в Китай через Cloudflare Warp, что обеспечит такую же безопасность без ущерба для удобства использования. +2. Как известно, большинство VPN-провайдеров ведут журналы посещенных пользователями доменов, а некоторые даже проверяют и блокируют определенный трафик. + Один из способов защиты конфиденциальности пользователей - использовать цепочку прокси-серверов на клиенте. + Warp использует легкий VPN-протокол WireGuard, который добавляет дополнительный уровень шифрования. + Для VPN-провайдера весь трафик пользователя будет направляться на Warp, что обеспечивает максимальную защиту конфиденциальности. +3. Простота использования. + Для настройки разделения трафика, WireGuard-туннеля и цепочки прокси-серверов достаточно одного ядра. + +## Создание аккаунта Warp + +### Спасибо Cloudflare за содействие свободному интернету! Теперь вы можете бесплатно пользоваться услугами Warp. При подключении автоматически выбирается ближайший сервер. +#### Метод 1: +1. Используйте VPS и загрузите [wgcf](https://github.com/ViRb3/wgcf/releases). +2. Запустите `wgcf register`, чтобы создать файл `wgcf-account.toml`. +3. Запустите `wgcf generate`, чтобы создать файл `wgcf-profile.conf`. Скопируйте его содержимое: + +```ini +[Interface] +PrivateKey = Мой закрытый ключ +Address = 172.16.0.2/32 +Address = 2606:4700:110:8949:fed8:2642:a640:c8e1/128 +DNS = 1.1.1.1 +MTU = 1280 +[Peer] +PublicKey = Открытый ключ Warp +AllowedIPs = 0.0.0.0/0 +AllowedIPs = ::/0 +Endpoint = engage.cloudflareclient.com:2408 +``` +#### Метод 2: +1. Используйте [warp-reg.sh](https://github.com/chise0713/warp-reg.sh). Запустите: +``` +bash -c "$(curl -L warp-reg.vercel.app)" +``` +- Вывод: +```json +{ + "endpoint":{ + "v4": "162.159.192.7", + "v6": "[2606:4700:d0::a29f:c007]", + }, + "reserved_dec": [35, 74, 190], + "reserved_hex": "0x234abe", + "reserved_str": "I0q+", + "private_key": "yL0kApRiZW4VFfNkKAQ/nYxnMFT3AH0dfVkj1GAlr1k=", + "public_key": "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=", + "v4": "172.16.0.2", + "v6": "2606:4700:110:81f3:2a5b:3cad:9d4:9ea6" +} +``` +2. Скопируйте вывод. +#### Метод 3: +1. Используйте [wgcf-cli](https://github.com/ArchiveNetwork/wgcf-cli). Запустите следующие команды для установки: +``` +bash -c "$(curl -L wgcf-cli.vercel.app)" +``` +2. Запустите `wgcf-cli -r` для регистрации. Вывод: +```json +❯ wgcf-cli -r +{ + "endpoint": { + "v4": "162.159.192.7:0", + "v6": "[2606:4700:d0::a29f:c007]:0" + }, + "reserved_str": "6nT5", + "reserved_hex": "0xea74f9", + "reserved_dec": [ + 234, + 116, + 249 + ], + "private_key": "WIAKvgUlq5fBazhttCvjhEGpu8MmGHcb1H0iHSGlU0Q=", + "public_key": "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=", + "addresses": { + "v4": "172.16.0.2", + "v6": "2606:4700:110:8d9c:3c4e:2190:59d1:2d3c" + } +} +``` +- Полный файл будет сохранен в файле `wgcf.json` в рабочем каталоге. +3. Если у вас есть ключ Warp+, вы можете привязать его, запустив `wgcf-cli -l [ключ]`. +- (Ключ можно получить, отправив `/keyget@getwarpplusbot` в [нашем чате](https://t.me/projectXray/)). + Вывод: +```json +❯ wgcf-cli -l 9zs5I61a-l9j8m7T5-4pC6k20X +{ + "id": "cd7f4695-e9ef-4bb0-b412-5f4d84919db7", + "created": "0001-01-01T00:00:00Z", + "updated": "2023-12-14T12:32:18.689777921Z", + "premium_data": 0, + "quota": 0, + "warp_plus": true, + "referral_count": 0, + "referral_renewal_countdown": 0, + "role": "child" +} +``` +4. Запустите `wgcf-cli -g xray`, чтобы создать исходящий WireGuard. + Содержимое будет сохранено в файле `wgcf.json.xray.json`. +- Пример файла: +```json +{ + "protocol": "wireguard", + "settings": { + "secretKey": "6CRVRLgFwGajnikoVOPTDNZnDhx3EydhPsMgpxHfBCY=", + "address": [ + "172.16.0.2/32", + "2606:4700:110:857a:6a95:fe27:1870:2a9d/128" + ], + "peers": [ + { + "publicKey": "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=", + "allowedIPs": [ + "0.0.0.0/0", + "::/0" + ], + "endpoint": "162.159.192.1:2408" + } + ], + "reserved": [ + 240, + 25, + 146 + ], + "mtu": 1280 + }, + "tag": "wireguard" +} +``` +## Перенаправление трафика в Китай через Warp на сервере + +Добавьте исходящий WireGuard к существующим исходящим подключениям: + +```json +{ + "protocol": "wireguard", + "settings": { + "secretKey": "Мой закрытый ключ", + "address": ["172.16.0.2/32", "2606:4700:110:8949:fed8:2642:a640:c8e1/128"], + "peers": [ + { + "publicKey": "Открытый ключ Warp", + "endpoint": "engage.cloudflareclient.com:2408" + } + ], + "reserved":[0, 0, 0] // Вставьте reserved, если у вас есть. + }, + "tag": "wireguard-1" +} +``` + +Рекомендуется использовать стратегию маршрутизации `IPIfNonMatch`. + +Добавьте следующие правила к существующим правилам маршрутизации: + +```json + { + "type": "field", + "domain": [ + "geosite:cn" + ], + "outboundTag": "wireguard-1" + }, + { + "type": "field", + "ip": [ + "geoip:cn" + ], + "outboundTag": "wireguard-1" + }, +``` + +## Использование Warp в качестве прокси-сервера в цепочке на клиенте + +```json +{ + "outbounds":[ + { + "protocol":"wireguard", + "settings":{ + "secretKey":"Мой закрытый ключ", + "peers":[ + { + "publicKey":"Открытый ключ Warp", + "endpoint":"engage.cloudflareclient.com:2408" + } + ], + "reserved":[0, 0, 0] // Вставьте reserved, если у вас есть. + }, + "streamSettings":{ + "sockopt":{ + "dialerProxy":"proxy" + } + }, + "tag":"wireguard-1" + }, + { + "tag":"proxy", + "protocol":"vmess", + "settings":{ + "vnext":[ + { + "address":"Мой IP-адрес", + "port":Мой порт, + "users":[ + { + "id":"Мой UUID", + "security":"auto" + } + ] + } + ] + }, + "streamSettings":{ + "network":"tcp" + } + } + ] +} +``` + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..0b1d9ac0b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,12479 @@ +{ + "name": "Xray-docs-next", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "Xray-docs-next", + "version": "1.0.0", + "license": "CC-BY-SA-4.0", + "dependencies": { + "@popperjs/core": "^2.11.5", + "@vuepress/plugin-google-analytics": "2.0.0-rc.3", + "@vuepress/plugin-register-components": "2.0.0-rc.3", + "@vuepress/plugin-shiki": "2.0.0-rc.3", + "@vuepress/theme-default": "2.0.0-rc.3", + "@vueuse/core": "^10.11.0", + "anywhere": "^1.6.0", + "bootstrap": "^5.2.0", + "esbuild": "^0.22.0", + "jquery": "^3.6.0", + "markdown-it-footnote": "^4.0.0", + "mermaid": "^10.9.0", + "vue": "3.3.13" + }, + "devDependencies": { + "@types/bootstrap": "^5.2.0", + "@types/jquery": "^3.5.14", + "@types/node": "^20.14.9", + "@vuepress/bundler-vite": "2.0.0-rc.2", + "@vuepress/bundler-webpack": "2.0.0-rc.2", + "@vuepress/plugin-back-to-top": "^2.0.0-rc.3", + "@vuepress/plugin-search": "2.0.0-rc.3", + "postcss-loader": "^8.0.0", + "prettier": "^3.3.2", + "sass": "^1.51.0", + "sass-loader": "^14.2.0", + "typescript": "^5.5.2", + "vue-property-decorator": "^9.1.2", + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "devOptional": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "devOptional": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "devOptional": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "devOptional": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "devOptional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "devOptional": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", + "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", + "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.22.0.tgz", + "integrity": "sha512-uvQR2crZ/zgzSHDvdygHyNI+ze9zwS8mqz0YtGXotSqvEE0UkYE9s+FZKQNTt1VtT719mfP3vHrUdCpxBNQZhQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.22.0.tgz", + "integrity": "sha512-PBnyP+r8vJE4ifxsWys9l+Mc2UY/yYZOpX82eoyGISXXb3dRr0M21v+s4fgRKWMFPMSf/iyowqPW/u7ScSUkjQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.22.0.tgz", + "integrity": "sha512-UKhPb3o2gAB/bfXcl58ZXTn1q2oVu1rEu/bKrCtmm+Nj5MKUbrOwR5WAixE2v+lk0amWuwPvhnPpBRLIGiq7ig==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.22.0.tgz", + "integrity": "sha512-IjTYtvIrjhR41Ijy2dDPgYjQHWG/x/A4KXYbs1fiU3efpRdoxMChK3oEZV6GPzVEzJqxFgcuBaiX1kwEvWUxSw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.22.0.tgz", + "integrity": "sha512-mqt+Go4y9wRvEz81bhKd9RpHsQR1LwU8Xm6jZRUV/xpM7cIQFbFH6wBCLPTNsdELBvfoHeumud7X78jQQJv2TA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.22.0.tgz", + "integrity": "sha512-vTaTQ9OgYc3VTaWtOE5pSuDT6H3d/qSRFRfSBbnxFfzAvYoB3pqKXA0LEbi/oT8GUOEAutspfRMqPj2ezdFaMw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.22.0.tgz", + "integrity": "sha512-0e1ZgoobJzaGnR4reD7I9rYZ7ttqdh1KPvJWnquUoDJhL0rYwdneeLailBzd2/4g/U5p4e5TIHEWa68NF2hFpQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.22.0.tgz", + "integrity": "sha512-BFgyYwlCwRWyPQJtkzqq2p6pJbiiWgp0P9PNf7a5FQ1itKY4czPuOMAlFVItirSmEpRPCeImuwePNScZS0pL5Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.22.0.tgz", + "integrity": "sha512-KEMWiA9aGuPUD4BH5yjlhElLgaRXe+Eri6gKBoDazoPBTo1BXc/e6IW5FcJO9DoL19FBeCxgONyh95hLDNepIg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.22.0.tgz", + "integrity": "sha512-V/K2rctCUgC0PCXpN7AqT4hoazXKgIYugFGu/myk2+pfe6jTW2guz/TBwq4cZ7ESqusR/IzkcQaBkcjquuBWsw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.22.0.tgz", + "integrity": "sha512-r2ZZqkOMOrpUhzNwxI7uLAHIDwkfeqmTnrv1cjpL/rjllPWszgqmprd/om9oviKXUBpMqHbXmppvjAYgISb26Q==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.22.0.tgz", + "integrity": "sha512-qaowLrV/YOMAL2RfKQ4C/VaDzAuLDuylM2sd/LH+4OFirMl6CuDpRlCq4u49ZBaVV8pkI/Y+hTdiibvQRhojCA==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.22.0.tgz", + "integrity": "sha512-hgrezzjQTRxjkQ5k08J6rtZN5PNnkWx/Rz6Kmj9gnsdCAX1I4Dn4ZPqvFRkXo55Q3pnVQJBwbdtrTO7tMGtyVA==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.22.0.tgz", + "integrity": "sha512-ewxg6FLLUio883XgSjfULEmDl3VPv/TYNnRprVAS3QeGFLdCYdx1tIudBcd7n9jIdk82v1Ajov4jx87qW7h9+g==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.22.0.tgz", + "integrity": "sha512-Az5XbgSJC2lE8XK8pdcutsf9RgdafWdTpUK/+6uaDdfkviw/B4JCwAfh1qVeRWwOohwdsl4ywZrWBNWxwrPLFg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.22.0.tgz", + "integrity": "sha512-8j4a2ChT9+V34NNNY9c/gMldutaJFmfMacTPq4KfNKwv2fitBCLYjee7c+Vxaha2nUhPK7cXcZpJtJ3+Y7ZdVQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.22.0.tgz", + "integrity": "sha512-JUQyOnpbAkkRFOk/AhsEemz5TfWN4FJZxVObUlnlNCbe7QBl61ZNfM4cwBXayQA6laMJMUcqLHaYQHAB6YQ95Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.22.0.tgz", + "integrity": "sha512-11PoCoHXo4HFNbLsXuMB6bpMPWGDiw7xETji6COdJss4SQZLvcgNoeSqWtATRm10Jj1uEHiaIk4N0PiN6x4Fcg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.22.0.tgz", + "integrity": "sha512-Ezlhu/YyITmXwKSB+Zu/QqD7cxrjrpiw85cc0Rbd3AWr2wsgp+dWbWOE8MqHaLW9NKMZvuL0DhbJbvzR7F6Zvg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.22.0.tgz", + "integrity": "sha512-ufjdW5tFJGUjlH9j/5cCE9lrwRffyZh+T4vYvoDKoYsC6IXbwaFeV/ENxeNXcxotF0P8CDzoICXVSbJaGBhkrw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.22.0.tgz", + "integrity": "sha512-zY6ly/AoSmKnmNTowDJsK5ehra153/5ZhqxNLfq9NRsTTltetr+yHHcQ4RW7QDqw4JC8A1uC1YmeSfK9NRcK1w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.22.0.tgz", + "integrity": "sha512-Kml5F7tv/1Maam0pbbCrvkk9vj046dPej30kFzlhXnhuCtYYBP6FGy/cLbc5yUT1lkZznGLf2OvuvmLjscO5rw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.22.0.tgz", + "integrity": "sha512-IOgwn+mYTM3RrcydP4Og5IpXh+ftN8oF+HELTXSmbWBlujuci4Qa3DTeO+LEErceisI7KUSfEIiX+WOUlpELkw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.22.0.tgz", + "integrity": "sha512-4bDHJrk2WHBXJPhy1y80X7/5b5iZTZP3LGcKIlAP1J+KqZ4zQAPMLEzftGyjjfcKbA4JDlPt/+2R/F1ZTeRgrw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "devOptional": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "devOptional": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "devOptional": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "devOptional": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "devOptional": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "devOptional": true + }, + "node_modules/@mdit-vue/plugin-component": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-component/-/plugin-component-2.1.3.tgz", + "integrity": "sha512-9AG17beCgpEw/4ldo/M6Y/1Rh4E1bqMmr/rCkWKmCAxy9tJz3lzY7HQJanyHMJufwsb3WL5Lp7Om/aPcQTZ9SA==", + "dependencies": { + "@types/markdown-it": "^14.1.1", + "markdown-it": "^14.1.0" + } + }, + "node_modules/@mdit-vue/plugin-component/node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" + }, + "node_modules/@mdit-vue/plugin-component/node_modules/@types/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@mdit-vue/plugin-component/node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, + "node_modules/@mdit-vue/plugin-frontmatter": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-frontmatter/-/plugin-frontmatter-2.1.3.tgz", + "integrity": "sha512-KxsSCUVBEmn6sJcchSTiI5v9bWaoRxe68RBYRDGcSEY1GTnfQ5gQPMIsM48P4q1luLEIWurVGGrRu7u93//LDQ==", + "dependencies": { + "@mdit-vue/types": "2.1.0", + "@types/markdown-it": "^14.1.1", + "gray-matter": "^4.0.3", + "markdown-it": "^14.1.0" + } + }, + "node_modules/@mdit-vue/plugin-frontmatter/node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" + }, + "node_modules/@mdit-vue/plugin-frontmatter/node_modules/@types/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@mdit-vue/plugin-frontmatter/node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, + "node_modules/@mdit-vue/plugin-headers": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-headers/-/plugin-headers-2.1.3.tgz", + "integrity": "sha512-AcL7a7LHQR3ISINhfjGJNE/bHyM0dcl6MYm1Sr//zF7ZgokPGwD/HhD7TzwmrKA9YNYCcO9P3QmF/RN9XyA6CA==", + "dependencies": { + "@mdit-vue/shared": "2.1.3", + "@mdit-vue/types": "2.1.0", + "@types/markdown-it": "^14.1.1", + "markdown-it": "^14.1.0" + } + }, + "node_modules/@mdit-vue/plugin-headers/node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" + }, + "node_modules/@mdit-vue/plugin-headers/node_modules/@types/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@mdit-vue/plugin-headers/node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, + "node_modules/@mdit-vue/plugin-sfc": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-sfc/-/plugin-sfc-2.1.3.tgz", + "integrity": "sha512-Ezl0dNvQNS639Yl4siXm+cnWtQvlqHrg+u+lnau/OHpj9Xh3LVap/BSQVugKIV37eR13jXXYf3VaAOP1fXPN+w==", + "dependencies": { + "@mdit-vue/types": "2.1.0", + "@types/markdown-it": "^14.1.1", + "markdown-it": "^14.1.0" + } + }, + "node_modules/@mdit-vue/plugin-sfc/node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" + }, + "node_modules/@mdit-vue/plugin-sfc/node_modules/@types/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@mdit-vue/plugin-sfc/node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, + "node_modules/@mdit-vue/plugin-title": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-title/-/plugin-title-2.1.3.tgz", + "integrity": "sha512-XWVOQoZqczoN97xCDrnQicmXKoqwOjIymIm9HQnRXhHnYKOgJPW1CxSGhkcOGzvDU1v0mD/adojVyyj/s6ggWw==", + "dependencies": { + "@mdit-vue/shared": "2.1.3", + "@mdit-vue/types": "2.1.0", + "@types/markdown-it": "^14.1.1", + "markdown-it": "^14.1.0" + } + }, + "node_modules/@mdit-vue/plugin-title/node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" + }, + "node_modules/@mdit-vue/plugin-title/node_modules/@types/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@mdit-vue/plugin-title/node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, + "node_modules/@mdit-vue/plugin-toc": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-toc/-/plugin-toc-2.1.3.tgz", + "integrity": "sha512-41Q+iXpLHZt0zJdApVwoVt7WF6za/xUjtjEPf90Z3KLzQO01TXsv48Xp9BsrFHPcPcm8tiZ0+O1/ICJO80V/MQ==", + "dependencies": { + "@mdit-vue/shared": "2.1.3", + "@mdit-vue/types": "2.1.0", + "@types/markdown-it": "^14.1.1", + "markdown-it": "^14.1.0" + } + }, + "node_modules/@mdit-vue/plugin-toc/node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" + }, + "node_modules/@mdit-vue/plugin-toc/node_modules/@types/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@mdit-vue/plugin-toc/node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, + "node_modules/@mdit-vue/shared": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mdit-vue/shared/-/shared-2.1.3.tgz", + "integrity": "sha512-27YI8b0VVZsAlNwaWoaOCWbr4eL8B04HxiYk/y2ktblO/nMcOEOLt4p0RjuobvdyUyjHvGOS09RKhq7qHm1CHQ==", + "dependencies": { + "@mdit-vue/types": "2.1.0", + "@types/markdown-it": "^14.1.1", + "markdown-it": "^14.1.0" + } + }, + "node_modules/@mdit-vue/shared/node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" + }, + "node_modules/@mdit-vue/shared/node_modules/@types/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@mdit-vue/shared/node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, + "node_modules/@mdit-vue/types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@mdit-vue/types/-/types-2.1.0.tgz", + "integrity": "sha512-TMBB/BQWVvwtpBdWD75rkZx4ZphQ6MN0O4QB2Bc0oI5PC2uE57QerhNxdRZ7cvBHE2iY2C+BUNUziCfJbjIRRA==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz", + "integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz", + "integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz", + "integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz", + "integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz", + "integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz", + "integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz", + "integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz", + "integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz", + "integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz", + "integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz", + "integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz", + "integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz", + "integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz", + "integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz", + "integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz", + "integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "devOptional": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "devOptional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/bootstrap": { + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.10.tgz", + "integrity": "sha512-F2X+cd6551tep0MvVZ6nM8v7XgGN/twpdNDjqS1TUM7YFNEtQYWk+dKAnH+T1gr6QgCoGMPl487xw/9hXooa2g==", + "dev": true, + "dependencies": { + "@popperjs/core": "^2.9.2" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "devOptional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "devOptional": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==" + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "devOptional": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "devOptional": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "devOptional": true + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "devOptional": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", + "devOptional": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, + "node_modules/@types/hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-UP28RddqY8xcU0SCEp9YKutQICXpaAq9N8U2klqF5hegGha7KzTOL8EdhIIV3bOSGBzjEpN9bU/d+nNZBdJYVw==" + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "devOptional": true + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "devOptional": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "devOptional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jquery": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.30.tgz", + "integrity": "sha512-nbWKkkyb919DOUxjmRVk8vwtDb0/k8FKncmUKFi+NY+QXqWltooxTrswvz4LspQwxvLdvzBN1TImr6cw3aQx2A==", + "dev": true, + "dependencies": { + "@types/sizzle": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "devOptional": true + }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", + "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==" + }, + "node_modules/@types/markdown-it": { + "version": "13.0.8", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.8.tgz", + "integrity": "sha512-V+KmpgiipS+zoypeUSS9ojesWtY/0k4XfqcK2fnVrX/qInJhX7rsCxZ/rygiPH2zxlPPrhfuW0I6ddMcWTKLsg==", + "dependencies": { + "@types/linkify-it": "^3", + "@types/mdurl": "^1" + } + }, + "node_modules/@types/markdown-it-emoji": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/markdown-it-emoji/-/markdown-it-emoji-2.0.5.tgz", + "integrity": "sha512-iJLsmCNpSWKtV6Ia3mLSjcXJPEt7ubGG342z+hGvYx++TpM19oTUrJcI7XjbOqRQ+W2UQ323E7B0eCLwlgT/9g==", + "dependencies": { + "@types/markdown-it": "*" + } + }, + "node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", + "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "devOptional": true + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, + "node_modules/@types/node": { + "version": "20.14.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", + "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "devOptional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "devOptional": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "devOptional": true + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "devOptional": true + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "devOptional": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "devOptional": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "devOptional": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sizzle": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", + "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", + "dev": true + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "devOptional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" + }, + "node_modules/@types/webpack-env": { + "version": "1.18.5", + "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.5.tgz", + "integrity": "sha512-wz7kjjRRj8/Lty4B+Kr0LN6Ypc/3SymeCCGSbaXp2leH0ZVg/PriNiOwNj4bD4uphI7A8NXS4b6Gl373sfO5mA==", + "devOptional": true + }, + "node_modules/@types/ws": { + "version": "8.5.11", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.11.tgz", + "integrity": "sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==", + "devOptional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz", + "integrity": "sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==", + "devOptional": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.13.tgz", + "integrity": "sha512-bwi9HShGu7uaZLOErZgsH2+ojsEdsjerbf2cMXPwmvcgZfVPZ2BVZzCVnwZBxTAYd6Mzbmf6izcUNDkWnBBQ6A==", + "dependencies": { + "@babel/parser": "^7.23.5", + "@vue/shared": "3.3.13", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-core/node_modules/@vue/shared": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.13.tgz", + "integrity": "sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==" + }, + "node_modules/@vue/compiler-dom": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.13.tgz", + "integrity": "sha512-EYRDpbLadGtNL0Gph+HoKiYqXLqZ0xSSpR5Dvnu/Ep7ggaCbjRDIus1MMxTS2Qm0koXED4xSlvTZaTnI8cYAsw==", + "dependencies": { + "@vue/compiler-core": "3.3.13", + "@vue/shared": "3.3.13" + } + }, + "node_modules/@vue/compiler-dom/node_modules/@vue/shared": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.13.tgz", + "integrity": "sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==" + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.13.tgz", + "integrity": "sha512-DQVmHEy/EKIgggvnGRLx21hSqnr1smUS9Aq8tfxiiot8UR0/pXKHN9k78/qQ7etyQTFj5em5nruODON7dBeumw==", + "dependencies": { + "@babel/parser": "^7.23.5", + "@vue/compiler-core": "3.3.13", + "@vue/compiler-dom": "3.3.13", + "@vue/compiler-ssr": "3.3.13", + "@vue/reactivity-transform": "3.3.13", + "@vue/shared": "3.3.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.5", + "postcss": "^8.4.32", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/@vue/shared": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.13.tgz", + "integrity": "sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.13.tgz", + "integrity": "sha512-d/P3bCeUGmkJNS1QUZSAvoCIW4fkOKK3l2deE7zrp0ypJEy+En2AcypIkqvcFQOcw3F0zt2VfMvNsA9JmExTaw==", + "dependencies": { + "@vue/compiler-dom": "3.3.13", + "@vue/shared": "3.3.13" + } + }, + "node_modules/@vue/compiler-ssr/node_modules/@vue/shared": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.13.tgz", + "integrity": "sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==" + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.3.tgz", + "integrity": "sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==" + }, + "node_modules/@vue/reactivity": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.13.tgz", + "integrity": "sha512-fjzCxceMahHhi4AxUBzQqqVhuA21RJ0COaWTbIBl1PruGW1CeY97louZzLi4smpYx+CHfFPPU/CS8NybbGvPKQ==", + "dependencies": { + "@vue/shared": "3.3.13" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.13.tgz", + "integrity": "sha512-oWnydGH0bBauhXvh5KXUy61xr9gKaMbtsMHk40IK9M4gMuKPJ342tKFarY0eQ6jef8906m35q37wwA8DMZOm5Q==", + "dependencies": { + "@babel/parser": "^7.23.5", + "@vue/compiler-core": "3.3.13", + "@vue/shared": "3.3.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.5" + } + }, + "node_modules/@vue/reactivity-transform/node_modules/@vue/shared": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.13.tgz", + "integrity": "sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==" + }, + "node_modules/@vue/reactivity/node_modules/@vue/shared": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.13.tgz", + "integrity": "sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==" + }, + "node_modules/@vue/runtime-core": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.13.tgz", + "integrity": "sha512-1TzA5TvGuh2zUwMJgdfvrBABWZ7y8kBwBhm7BXk8rvdx2SsgcGfz2ruv2GzuGZNvL1aKnK8CQMV/jFOrxNQUMA==", + "dependencies": { + "@vue/reactivity": "3.3.13", + "@vue/shared": "3.3.13" + } + }, + "node_modules/@vue/runtime-core/node_modules/@vue/shared": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.13.tgz", + "integrity": "sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==" + }, + "node_modules/@vue/runtime-dom": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.13.tgz", + "integrity": "sha512-JJkpE8R/hJKXqVTgUoODwS5wqKtOsmJPEqmp90PDVGygtJ4C0PtOkcEYXwhiVEmef6xeXcIlrT3Yo5aQ4qkHhQ==", + "dependencies": { + "@vue/runtime-core": "3.3.13", + "@vue/shared": "3.3.13", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/runtime-dom/node_modules/@vue/shared": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.13.tgz", + "integrity": "sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==" + }, + "node_modules/@vue/server-renderer": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.13.tgz", + "integrity": "sha512-vSnN+nuf6iSqTL3Qgx/9A+BT+0Zf/VJOgF5uMZrKjYPs38GMYyAU1coDyBNHauehXDaP+zl73VhwWv0vBRBHcg==", + "dependencies": { + "@vue/compiler-ssr": "3.3.13", + "@vue/shared": "3.3.13" + }, + "peerDependencies": { + "vue": "3.3.13" + } + }, + "node_modules/@vue/server-renderer/node_modules/@vue/shared": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.13.tgz", + "integrity": "sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==" + }, + "node_modules/@vue/shared": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.31.tgz", + "integrity": "sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==" + }, + "node_modules/@vuepress/bundler-vite": { + "version": "2.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@vuepress/bundler-vite/-/bundler-vite-2.0.0-rc.2.tgz", + "integrity": "sha512-bjkn0krtucciUkGGdckCkGGg+wlv3Pj7s1lO/ChACLQncDwc3GgMTuMm0JyaCDKzTXE8sufjHHGWMftRL8qFOg==", + "devOptional": true, + "dependencies": { + "@vitejs/plugin-vue": "^5.0.3", + "@vuepress/client": "2.0.0-rc.2", + "@vuepress/core": "2.0.0-rc.2", + "@vuepress/shared": "2.0.0-rc.2", + "@vuepress/utils": "2.0.0-rc.2", + "autoprefixer": "^10.4.17", + "connect-history-api-fallback": "^2.0.0", + "postcss": "^8.4.33", + "postcss-load-config": "^5.0.2", + "rollup": "^4.9.6", + "vite": "~5.0.12", + "vue": "^3.4.15", + "vue-router": "^4.2.5" + } + }, + "node_modules/@vuepress/bundler-vite/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "devOptional": true, + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/bundler-vite/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "devOptional": true, + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/bundler-vite/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "devOptional": true, + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/bundler-vite/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "devOptional": true, + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/bundler-vite/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "devOptional": true, + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/bundler-vite/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "devOptional": true, + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/bundler-vite/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "devOptional": true, + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vuepress/bundler-vite/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "devOptional": true, + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vuepress/bundler-vite/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "devOptional": true, + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vuepress/bundler-webpack": { + "version": "2.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@vuepress/bundler-webpack/-/bundler-webpack-2.0.0-rc.2.tgz", + "integrity": "sha512-ukYXFFTI5vOQ24zv+oIGRjYwBjgeIuTe6S8pKBZj5dJuQIafVFYTEd6YSJbuBtqTJBtxbwcF4oW/pMynKkMYBQ==", + "devOptional": true, + "dependencies": { + "@types/express": "^4.17.21", + "@types/webpack-env": "^1.18.4", + "@vuepress/client": "2.0.0-rc.2", + "@vuepress/core": "2.0.0-rc.2", + "@vuepress/shared": "2.0.0-rc.2", + "@vuepress/utils": "2.0.0-rc.2", + "autoprefixer": "^10.4.17", + "chokidar": "^3.5.3", + "copy-webpack-plugin": "^12.0.2", + "css-loader": "^6.9.1", + "esbuild-loader": "~4.0.3", + "express": "^4.18.2", + "html-webpack-plugin": "^5.6.0", + "mini-css-extract-plugin": "^2.7.7", + "postcss": "^8.4.33", + "postcss-csso": "^6.0.1", + "postcss-loader": "^8.0.0", + "style-loader": "^3.3.4", + "vue": "^3.4.15", + "vue-loader": "^17.4.2", + "vue-router": "^4.2.5", + "webpack": "^5.90.0", + "webpack-chain": "^6.5.1", + "webpack-dev-server": "^4.15.1", + "webpack-merge": "^5.10.0" + } + }, + "node_modules/@vuepress/bundler-webpack/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "devOptional": true, + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/bundler-webpack/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "devOptional": true, + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/bundler-webpack/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "devOptional": true, + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/bundler-webpack/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "devOptional": true, + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/bundler-webpack/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "devOptional": true, + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/bundler-webpack/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "devOptional": true, + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/bundler-webpack/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "devOptional": true, + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vuepress/bundler-webpack/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "devOptional": true, + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vuepress/bundler-webpack/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "devOptional": true, + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vuepress/cli": { + "version": "2.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@vuepress/cli/-/cli-2.0.0-rc.2.tgz", + "integrity": "sha512-a5qAtd6gNndNcqENBNkMn9xRDbb1B2kJ62dWUaE5KjhkM/Fed4CTvBTDd0qfYmwiwsSFPL08VWavo1FcdMNXsA==", + "dependencies": { + "@vuepress/core": "2.0.0-rc.2", + "@vuepress/shared": "2.0.0-rc.2", + "@vuepress/utils": "2.0.0-rc.2", + "cac": "^6.7.14", + "chokidar": "^3.5.3", + "envinfo": "^7.11.0", + "esbuild": "~0.19.12" + }, + "bin": { + "vuepress-cli": "bin/vuepress.js" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@vuepress/cli/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/@vuepress/client": { + "version": "2.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@vuepress/client/-/client-2.0.0-rc.2.tgz", + "integrity": "sha512-gQ4CfBhzWYOCW4OcAUd6S8Jr9m/8UkZZuN/70t12GltbX/cdm6zrGnf89GiVjgvoK8+OYoc7luoBuWbyc/X5sg==", + "dependencies": { + "@vue/devtools-api": "^6.5.1", + "@vuepress/shared": "2.0.0-rc.2", + "@vueuse/core": "^10.7.2", + "vue": "^3.4.15", + "vue-router": "^4.2.5" + } + }, + "node_modules/@vuepress/client/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/client/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/client/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/client/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/client/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/client/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/client/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vuepress/client/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vuepress/client/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vuepress/core": { + "version": "2.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@vuepress/core/-/core-2.0.0-rc.2.tgz", + "integrity": "sha512-C/bHG0y+v5oeCrfaesy2yFa0dyCah05g1w7vArZk8ABsVECjZKAC5/ev39UFQm2dCdNzsj2E3KgLIxYWqpcKeg==", + "dependencies": { + "@vuepress/client": "2.0.0-rc.2", + "@vuepress/markdown": "2.0.0-rc.2", + "@vuepress/shared": "2.0.0-rc.2", + "@vuepress/utils": "2.0.0-rc.2", + "vue": "^3.4.15" + } + }, + "node_modules/@vuepress/core/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/core/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/core/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/core/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/core/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/core/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/core/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vuepress/core/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vuepress/core/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vuepress/helper": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/helper/-/helper-2.0.0-rc.3.tgz", + "integrity": "sha512-Z6LuiFjIupgf6KecDqiO4o4M8GHhx0ShTrZvv/a5y2o2iofE5j5E74Q6Ww7ECQojwhRMvpzJ/Bq9YaIg25T2Yw==", + "dependencies": { + "@vue/shared": "^3.4.15", + "cheerio": "1.0.0-rc.12", + "fflate": "^0.8.1", + "gray-matter": "^4.0.3", + "vue": "^3.4.15" + }, + "engines": { + "node": ">=18.16.0", + "npm": ">=8", + "pnpm": ">=7", + "yarn": ">=2" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/helper/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/helper/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/helper/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/helper/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/helper/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/helper/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/helper/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vuepress/helper/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vuepress/helper/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vuepress/markdown": { + "version": "2.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@vuepress/markdown/-/markdown-2.0.0-rc.2.tgz", + "integrity": "sha512-5/RmJnap+MGKxDhSO+Mv6zB8PoPHhhBujnNKKO3PnyfPrj0LyL0AuTm8m3Ea271wMp9956WINjw8jlpn+Z1sBg==", + "dependencies": { + "@mdit-vue/plugin-component": "^2.0.0", + "@mdit-vue/plugin-frontmatter": "^2.0.0", + "@mdit-vue/plugin-headers": "^2.0.0", + "@mdit-vue/plugin-sfc": "^2.0.0", + "@mdit-vue/plugin-title": "^2.0.0", + "@mdit-vue/plugin-toc": "^2.0.0", + "@mdit-vue/shared": "^2.0.0", + "@mdit-vue/types": "^2.0.0", + "@types/markdown-it": "^13.0.7", + "@types/markdown-it-emoji": "^2.0.4", + "@vuepress/shared": "2.0.0-rc.2", + "@vuepress/utils": "2.0.0-rc.2", + "markdown-it": "^14.0.0", + "markdown-it-anchor": "^8.6.7", + "markdown-it-emoji": "^3.0.0", + "mdurl": "^2.0.0" + } + }, + "node_modules/@vuepress/plugin-active-header-links": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-active-header-links/-/plugin-active-header-links-2.0.0-rc.3.tgz", + "integrity": "sha512-ddr8VIrYA/kpWGWx80GeWclSvQoYR9QjXvkx//AMbl5mQqX70GTZrgv5Rbnp6MC8mBxVg6Izy/9eqaJELE+A3g==", + "dependencies": { + "ts-debounce": "^4.0.0", + "vue": "^3.4.15", + "vue-router": "^4.2.5" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-active-header-links/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-active-header-links/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-active-header-links/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-active-header-links/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-active-header-links/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-active-header-links/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-active-header-links/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vuepress/plugin-active-header-links/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-active-header-links/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vuepress/plugin-back-to-top": { + "version": "2.0.0-rc.38", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-back-to-top/-/plugin-back-to-top-2.0.0-rc.38.tgz", + "integrity": "sha512-KrEeyv2QX7YZVrvCBohPWdwFXwKFIwyb6HjEKW/2H82+XfBMg1D9b7vqCp/W4EJ1F6ERPYgJLj0sv2AockHZtg==", + "dev": true, + "dependencies": { + "@vuepress/helper": "2.0.0-rc.38", + "@vueuse/core": "^10.11.0", + "vue": "^3.4.31" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.14" + } + }, + "node_modules/@vuepress/plugin-back-to-top/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-back-to-top/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-back-to-top/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-back-to-top/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-back-to-top/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "dev": true, + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-back-to-top/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "dev": true, + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-back-to-top/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "dev": true, + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vuepress/plugin-back-to-top/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "dev": true, + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-back-to-top/node_modules/@vuepress/helper": { + "version": "2.0.0-rc.38", + "resolved": "https://registry.npmjs.org/@vuepress/helper/-/helper-2.0.0-rc.38.tgz", + "integrity": "sha512-IgKQCCbfX4zLkRxLwzNtTMKTZdflAlmBTUEkuD/uJrfFJjGvLShnkw2ONIlwSM6U+SWVHKfW5Ls8pndPvxpI1Q==", + "dev": true, + "dependencies": { + "@vue/shared": "^3.4.31", + "cheerio": "1.0.0-rc.12", + "fflate": "^0.8.2", + "gray-matter": "^4.0.3", + "vue": "^3.4.31" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.14" + } + }, + "node_modules/@vuepress/plugin-back-to-top/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vuepress/plugin-container": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-container/-/plugin-container-2.0.0-rc.3.tgz", + "integrity": "sha512-wwwtQQYiA21JKKYM4TCjDWxS2p91RPiv62NoWp+Q6BG+E+qnwaA1sOrd36CsEOS2IbIcJdBeHY/zskL0aWKEoQ==", + "dependencies": { + "@types/markdown-it": "^13.0.7", + "markdown-it": "^14.0.0", + "markdown-it-container": "^4.0.0" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-external-link-icon": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-external-link-icon/-/plugin-external-link-icon-2.0.0-rc.3.tgz", + "integrity": "sha512-DX4Dn2uj2rAEausB2tz5fe/Ra3oR/B9uR67+yG2lQzZqcZZDW5txsXpZaM0FkXHijzNoS4NKP7k/7qNVe5WvcQ==", + "dependencies": { + "vue": "^3.4.15" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-external-link-icon/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-external-link-icon/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-external-link-icon/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-external-link-icon/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-external-link-icon/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-external-link-icon/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-external-link-icon/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vuepress/plugin-external-link-icon/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-external-link-icon/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vuepress/plugin-git": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-git/-/plugin-git-2.0.0-rc.3.tgz", + "integrity": "sha512-BQoDWmksC+aSc5V933OuNeU7mfrP4hryhckIFadAhKLVRl6CSXDb96SGVTBxcO1gIgKzItdE67UzMw4T9JJN6A==", + "dependencies": { + "execa": "^8.0.1" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-google-analytics": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-google-analytics/-/plugin-google-analytics-2.0.0-rc.3.tgz", + "integrity": "sha512-Hp8MxdPX7SwLhoBOQ6D34/5d5DOBGMp2W9bMUlYD2zzUhKOamSleqI4E3KYyq+WkkQmU4naSHxlwJlsjhNwjig==", + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-medium-zoom": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-medium-zoom/-/plugin-medium-zoom-2.0.0-rc.3.tgz", + "integrity": "sha512-qRdUt914tQGfmxcqOwnoC6paZLsAJf2S7kllvf59dDKIDg2JlYsAuOq13wps4/hfuUySe9RDlitl6KcqWfvKfw==", + "dependencies": { + "medium-zoom": "^1.1.0", + "vue": "^3.4.15" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-medium-zoom/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-medium-zoom/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-medium-zoom/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-medium-zoom/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-medium-zoom/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-medium-zoom/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-medium-zoom/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vuepress/plugin-medium-zoom/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-medium-zoom/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vuepress/plugin-nprogress": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-nprogress/-/plugin-nprogress-2.0.0-rc.3.tgz", + "integrity": "sha512-UkbMLvkRps56KF2fsIXtJ0ylRacuPRW9qA1PYwq7XkDqNr4FE5aZsnRHuYIMPUxuhU5t3hRx/zeKOwbctzd2Yw==", + "dependencies": { + "vue": "^3.4.15", + "vue-router": "^4.2.5" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-nprogress/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-nprogress/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-nprogress/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-nprogress/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-nprogress/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-nprogress/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-nprogress/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vuepress/plugin-nprogress/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-nprogress/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vuepress/plugin-palette": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-palette/-/plugin-palette-2.0.0-rc.3.tgz", + "integrity": "sha512-BCP2SMEPmaqg4/oHsfA7lpp79iQDZvgeGQCYyWRP2aB5LZ2IiFdyRfZMFOUVzbetsqX7ciOFX3ELG9U59lCOjw==", + "dependencies": { + "chokidar": "^3.5.3" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-prismjs": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-prismjs/-/plugin-prismjs-2.0.0-rc.3.tgz", + "integrity": "sha512-tbyGKXDR/AbhwiUQIU49Le2VaUYGBnjFqdhGlrC47kRl4Iy+XtxyKgRJcFGcWU0AHRdTtC9MAmPIfz2nJkNhSQ==", + "dependencies": { + "prismjs": "^1.29.0" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-register-components": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-register-components/-/plugin-register-components-2.0.0-rc.3.tgz", + "integrity": "sha512-l7tLwcgjV54T8ObF1cpgg9qWtKuaF9Dru14aHFeXxM2f7I1AjmAGFXHbBEMOb781HMRi32LYCwodsTahAn3uqg==", + "dependencies": { + "chokidar": "^3.5.3" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-search": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-search/-/plugin-search-2.0.0-rc.3.tgz", + "integrity": "sha512-dTxOYKPNziWX2vx+RO0PLwj/auABlCCAZT42wQt65R5y5RBA7eptFlqF/jmRXKG+CPvSMUOrIo1FrCTvB3yFig==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.3", + "vue": "^3.4.15", + "vue-router": "^4.2.5" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-search/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-search/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-search/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-search/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-search/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "dev": true, + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-search/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "dev": true, + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-search/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "dev": true, + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vuepress/plugin-search/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "dev": true, + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-search/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vuepress/plugin-seo": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-seo/-/plugin-seo-2.0.0-rc.3.tgz", + "integrity": "sha512-0E8l09tRzdvu/rTxX5J+zzDHMIFBhnNzaLSgTG5RWnmqFoHZSZnjHjk8Haecu4Nb6Os+TPYucXj34eX9rt+lWw==", + "dependencies": { + "@vuepress/helper": "2.0.0-rc.3" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-shiki": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-shiki/-/plugin-shiki-2.0.0-rc.3.tgz", + "integrity": "sha512-q6God6sEWr1InLizZHBLXIboEkCVXSOXNeJSoH+buofGrrHXCjA937UVMSUTLP4RZ9b21QXHLsyrRQvoEpa4yQ==", + "dependencies": { + "shikiji": "^0.10.2" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-sitemap": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-sitemap/-/plugin-sitemap-2.0.0-rc.3.tgz", + "integrity": "sha512-iWVYmmTjqqRejAq+fh6DduElVki9U379F+A79/SDl5AWf3fe5DNysYTGqyRUwXj8vBzkBWndrBscMHW8TtU4UA==", + "dependencies": { + "@vuepress/helper": "2.0.0-rc.3", + "sitemap": "^7.1.1" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-theme-data": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-theme-data/-/plugin-theme-data-2.0.0-rc.3.tgz", + "integrity": "sha512-Uiso+0b2sIaHtPVftjpCEIT+/T4/E9ZpTCL0fnUEa8APnrP7SiIBqph7+KZENvXGg+0B5+MtZROOfFksFfMyFw==", + "dependencies": { + "@vue/devtools-api": "^6.5.1", + "vue": "^3.4.15" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/plugin-theme-data/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-theme-data/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-theme-data/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/plugin-theme-data/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-theme-data/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-theme-data/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-theme-data/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vuepress/plugin-theme-data/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vuepress/plugin-theme-data/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vuepress/shared": { + "version": "2.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@vuepress/shared/-/shared-2.0.0-rc.2.tgz", + "integrity": "sha512-2kmm0rw+WalRWrSC5pW0TXRz8Wyuh57XmOZEUOhPOflw4o8Dno+PcaWbdOZ/TLkTgTt3X1n7r1/c1ALtaLta8g==", + "dependencies": { + "@mdit-vue/types": "^2.0.0" + } + }, + "node_modules/@vuepress/theme-default": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/theme-default/-/theme-default-2.0.0-rc.3.tgz", + "integrity": "sha512-IOgAkAgYFyq3O/404KLTXFeJGVF6UyNx5fa8ywO89mpUlJZycUpgIvBS7vQN/hnew43VMEfVEGYwqoCmHVioPw==", + "dependencies": { + "@vuepress/plugin-active-header-links": "2.0.0-rc.3", + "@vuepress/plugin-back-to-top": "2.0.0-rc.3", + "@vuepress/plugin-container": "2.0.0-rc.3", + "@vuepress/plugin-external-link-icon": "2.0.0-rc.3", + "@vuepress/plugin-git": "2.0.0-rc.3", + "@vuepress/plugin-medium-zoom": "2.0.0-rc.3", + "@vuepress/plugin-nprogress": "2.0.0-rc.3", + "@vuepress/plugin-palette": "2.0.0-rc.3", + "@vuepress/plugin-prismjs": "2.0.0-rc.3", + "@vuepress/plugin-seo": "2.0.0-rc.3", + "@vuepress/plugin-sitemap": "2.0.0-rc.3", + "@vuepress/plugin-theme-data": "2.0.0-rc.3", + "@vueuse/core": "^10.7.2", + "sass": "^1.70.0", + "vue": "^3.4.15", + "vue-router": "^4.2.5" + }, + "peerDependencies": { + "sass-loader": "^14.0.0", + "vuepress": "2.0.0-rc.2" + }, + "peerDependenciesMeta": { + "sass-loader": { + "optional": true + } + } + }, + "node_modules/@vuepress/theme-default/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/theme-default/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/theme-default/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vuepress/theme-default/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/theme-default/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/theme-default/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/@vuepress/theme-default/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/@vuepress/theme-default/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/@vuepress/theme-default/node_modules/@vuepress/plugin-back-to-top": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-back-to-top/-/plugin-back-to-top-2.0.0-rc.3.tgz", + "integrity": "sha512-VkzRWMLBHcJ+MVCMl+Yd5Z6Uc1LAaSQkoHrwap1xBU3a3Zjgcqf73+fDTgenTF1usANlztDE9F/Jg8YjREJ6yg==", + "dependencies": { + "ts-debounce": "^4.0.0", + "vue": "^3.4.15" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + } + }, + "node_modules/@vuepress/theme-default/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vuepress/utils": { + "version": "2.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@vuepress/utils/-/utils-2.0.0-rc.2.tgz", + "integrity": "sha512-g93yFJKtztpdXm4XyOIQ9QcUrKuvuWizvH3qWDQ5/WKlxa6VqE7nVNPlkudgGUIc7Bl4AGrlHcmgvkwaNoMcfA==", + "dependencies": { + "@types/debug": "^4.1.12", + "@types/fs-extra": "^11.0.4", + "@types/hash-sum": "^1.0.2", + "@vuepress/shared": "2.0.0-rc.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0", + "globby": "^14.0.0", + "hash-sum": "^2.0.0", + "ora": "^8.0.1", + "picocolors": "^1.0.0", + "upath": "^2.0.1" + } + }, + "node_modules/@vueuse/core": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.0.tgz", + "integrity": "sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.0", + "@vueuse/shared": "10.11.0", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.0.tgz", + "integrity": "sha512-kQX7l6l8dVWNqlqyN3ePW3KmjCQO3ZMgXuBMddIu83CmucrsBfXlH+JoviYyRBws/yLTQO8g3Pbw+bdIoVm4oQ==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.0.tgz", + "integrity": "sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "devOptional": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "devOptional": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "devOptional": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "devOptional": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "devOptional": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "devOptional": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "devOptional": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "devOptional": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "devOptional": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "devOptional": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "devOptional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "devOptional": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "devOptional": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "devOptional": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "devOptional": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "devOptional": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "devOptional": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anywhere": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/anywhere/-/anywhere-1.6.0.tgz", + "integrity": "sha512-X+MpvNGoZNAFsQkyvgBcCdAoz8yHULnW5gfmNrjkGXr6VrtWxsCSlT3bAbbmAE2/umhU9TMF2ffRBL/H2hHvzA==", + "dependencies": { + "connect": "^3.6.6", + "connect-history-api-fallback": "^1.2.0", + "debug": "^2.2.0", + "http-proxy-middleware": "^0.19.1", + "minimist": "^1.2.0", + "serve-index": "^1.9.1", + "serve-static": "^1.13.2" + }, + "bin": { + "anywhere": "bin/anywhere" + } + }, + "node_modules/anywhere/node_modules/connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/anywhere/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/anywhere/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "devOptional": true + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "devOptional": true + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "devOptional": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "devOptional": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "devOptional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "devOptional": true + }, + "node_modules/bonjour-service": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "devOptional": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "devOptional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001640", + "electron-to-chromium": "^1.4.820", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "devOptional": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "devOptional": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "devOptional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "devOptional": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001642", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz", + "integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "devOptional": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "devOptional": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "devOptional": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "devOptional": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "devOptional": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "devOptional": true + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "devOptional": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "devOptional": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "devOptional": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "devOptional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "devOptional": true + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "devOptional": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "devOptional": true + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "devOptional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "devOptional": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "devOptional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "devOptional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "devOptional": true + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "devOptional": true, + "dependencies": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "devOptional": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "devOptional": true + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "devOptional": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "devOptional": true + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "devOptional": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "devOptional": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "devOptional": true, + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "devOptional": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "devOptional": true, + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/cytoscape": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.30.0.tgz", + "integrity": "sha512-l590mjTHT6/Cbxp13dGPC2Y7VXdgc+rUeF8AnF/JPzhjNevbDJfObnJgaSjlldOgBQZbue+X6IUZ7r5GAgvauQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz", + "integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==", + "dependencies": { + "d3": "^7.8.2", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/deepmerge": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz", + "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "devOptional": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/default-gateway/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "devOptional": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/default-gateway/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "devOptional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "devOptional": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/default-gateway/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "devOptional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/default-gateway/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "devOptional": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/default-gateway/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "devOptional": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true + }, + "node_modules/default-gateway/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "devOptional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "devOptional": true + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "devOptional": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "devOptional": true, + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/dompurify": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", + "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==" + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "devOptional": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.827", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.827.tgz", + "integrity": "sha512-VY+J0e4SFcNfQy19MEoMdaIcZLmDCprqvBtkii1WTCTQHpRvf5N8+3kTYCgL/PcntvwQvmMJWTuDPsq+IlhWKQ==", + "devOptional": true + }, + "node_modules/elkjs": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", + "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==" + }, + "node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "devOptional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", + "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "devOptional": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/envinfo": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", + "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "devOptional": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "devOptional": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "devOptional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "devOptional": true + }, + "node_modules/esbuild": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.22.0.tgz", + "integrity": "sha512-zNYA6bFZsVnsU481FnGAQjLDW0Pl/8BGG7EvAp15RzUvGC+ME7hf1q7LvIfStEQBz/iEHuBJCYcOwPmNCf1Tlw==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.22.0", + "@esbuild/android-arm": "0.22.0", + "@esbuild/android-arm64": "0.22.0", + "@esbuild/android-x64": "0.22.0", + "@esbuild/darwin-arm64": "0.22.0", + "@esbuild/darwin-x64": "0.22.0", + "@esbuild/freebsd-arm64": "0.22.0", + "@esbuild/freebsd-x64": "0.22.0", + "@esbuild/linux-arm": "0.22.0", + "@esbuild/linux-arm64": "0.22.0", + "@esbuild/linux-ia32": "0.22.0", + "@esbuild/linux-loong64": "0.22.0", + "@esbuild/linux-mips64el": "0.22.0", + "@esbuild/linux-ppc64": "0.22.0", + "@esbuild/linux-riscv64": "0.22.0", + "@esbuild/linux-s390x": "0.22.0", + "@esbuild/linux-x64": "0.22.0", + "@esbuild/netbsd-x64": "0.22.0", + "@esbuild/openbsd-arm64": "0.22.0", + "@esbuild/openbsd-x64": "0.22.0", + "@esbuild/sunos-x64": "0.22.0", + "@esbuild/win32-arm64": "0.22.0", + "@esbuild/win32-ia32": "0.22.0", + "@esbuild/win32-x64": "0.22.0" + } + }, + "node_modules/esbuild-loader": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/esbuild-loader/-/esbuild-loader-4.0.3.tgz", + "integrity": "sha512-YpaSRisj7TSg6maKKKG9OJGGm0BZ7EXeov8J8cXEYdugjlAJ0wL7aj2JactoQvPJ113v2Ar204pdJWrZsAQc8Q==", + "devOptional": true, + "dependencies": { + "esbuild": "^0.19.0", + "get-tsconfig": "^4.7.0", + "loader-utils": "^2.0.4", + "webpack-sources": "^1.4.3" + }, + "funding": { + "url": "https://github.com/privatenumber/esbuild-loader?sponsor=1" + }, + "peerDependencies": { + "webpack": "^4.40.0 || ^5.0.0" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-loader/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "devOptional": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "devOptional": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "devOptional": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "devOptional": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "devOptional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "devOptional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "devOptional": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/expand-brackets/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "devOptional": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "devOptional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "devOptional": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "devOptional": true + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "devOptional": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "devOptional": true + }, + "node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "devOptional": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "devOptional": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "devOptional": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "devOptional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "devOptional": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", + "devOptional": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "devOptional": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "devOptional": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", + "devOptional": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "devOptional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "devOptional": true + }, + "node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "devOptional": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "devOptional": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "devOptional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "devOptional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "devOptional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "devOptional": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "devOptional": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "devOptional": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "devOptional": true + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "devOptional": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "devOptional": true, + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", + "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", + "devOptional": true, + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "devOptional": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "devOptional": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.2.tgz", + "integrity": "sha512-aYk1rTKqLTus23X3L96LGNCGNgWpG4cG0XoZIT1GUPhhulEHX/QalnO6Vbo+WmKWi4AL2IidjuC0wZtbpg0yhQ==", + "dependencies": { + "http-proxy": "^1.18.1", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/micromatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "devOptional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "devOptional": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", + "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==" + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "devOptional": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "devOptional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "devOptional": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", + "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", + "dependencies": { + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "devOptional": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "node_modules/is-data-descriptor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", + "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "devOptional": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "devOptional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", + "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "devOptional": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/javascript-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", + "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==", + "devOptional": true + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "devOptional": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "devOptional": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "devOptional": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "devOptional": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "devOptional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "devOptional": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/katex": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz", + "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/launch-editor": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.8.0.tgz", + "integrity": "sha512-vJranOAJrI/llyWGRQqiDM+adrw+k83fvmmx3+nV47g3+36xM15jE+zyZ6Ffel02+xSvuM0b2GDRosXZkbb6wA==", + "devOptional": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==" + }, + "node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "devOptional": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "devOptional": true + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "devOptional": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "devOptional": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "devOptional": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/markdown-it-container": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-container/-/markdown-it-container-4.0.0.tgz", + "integrity": "sha512-HaNccxUH0l7BNGYbFbjmGpf5aLHAMTinqRZQAEQbMr2cdD3z91Q6kIo1oUn1CQndkT03jat6ckrdRYuwwqLlQw==" + }, + "node_modules/markdown-it-emoji": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-3.0.0.tgz", + "integrity": "sha512-+rUD93bXHubA4arpEZO3q80so0qgoFJEKRkRbjKX8RTdca89v2kfyF+xR3i2sQTwql9tpPZPOQN5B+PunspXRg==" + }, + "node_modules/markdown-it-footnote": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-footnote/-/markdown-it-footnote-4.0.0.tgz", + "integrity": "sha512-WYJ7urf+khJYl3DqofQpYfEYkZKbmXmwxQV8c8mO/hGIhgZ1wOe7R4HLFNwqx7TjILbnC98fuyeSsin19JdFcQ==" + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "devOptional": true + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "devOptional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/medium-zoom": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/medium-zoom/-/medium-zoom-1.1.0.tgz", + "integrity": "sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ==" + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "devOptional": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "devOptional": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/mermaid": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.9.1.tgz", + "integrity": "sha512-Mx45Obds5W1UkW1nv/7dHRsbfMM1aOKA2+Pxs/IGHNonygDHwmng8xTHyS9z4KWVi0rbko8gjiBmuwwXQ7tiNA==", + "dependencies": { + "@braintree/sanitize-url": "^6.0.1", + "@types/d3-scale": "^4.0.3", + "@types/d3-scale-chromatic": "^3.0.0", + "cytoscape": "^3.28.1", + "cytoscape-cose-bilkent": "^4.1.0", + "d3": "^7.4.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.10", + "dayjs": "^1.11.7", + "dompurify": "^3.0.5", + "elkjs": "^0.9.0", + "katex": "^0.16.9", + "khroma": "^2.0.0", + "lodash-es": "^4.17.21", + "mdast-util-from-markdown": "^1.3.0", + "non-layered-tidy-tree-layout": "^2.0.2", + "stylis": "^4.1.3", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.0", + "web-worker": "^1.2.0" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "devOptional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "devOptional": true, + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "devOptional": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "devOptional": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "devOptional": true + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "devOptional": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "devOptional": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "devOptional": true + }, + "node_modules/non-layered-tidy-tree-layout": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "devOptional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "devOptional": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "devOptional": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "devOptional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "devOptional": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.0.1.tgz", + "integrity": "sha512-ANIvzobt1rls2BDny5fWZ3ZVKyD6nscLvfFRpQgfWsythlcsVUC9kL0zq6j2Z5z9wwp1kd7wpsD/T9qNPVLCaQ==", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "devOptional": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "devOptional": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "devOptional": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "devOptional": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "devOptional": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "devOptional": true + }, + "node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-csso": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-csso/-/postcss-csso-6.0.1.tgz", + "integrity": "sha512-ZV4yEziMrx6CEiqabGLrDva0pMD7Fbw7yP+LzJvaynM4OJgTssGN6dHiMsJMJdpmNaLJltXVLsrb/5sxbFa8sA==", + "devOptional": true, + "dependencies": { + "csso": "^5.0.5" + }, + "engines": { + "node": "^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-load-config": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz", + "integrity": "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.1.1", + "yaml": "^2.4.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + } + } + }, + "node_modules/postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "devOptional": true, + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "devOptional": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "devOptional": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "devOptional": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "devOptional": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", + "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", + "devOptional": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "devOptional": true + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "devOptional": true, + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "devOptional": true + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "devOptional": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "devOptional": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "devOptional": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "devOptional": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "devOptional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "devOptional": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "devOptional": true, + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/renderkid/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/renderkid/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "devOptional": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/renderkid/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "devOptional": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "devOptional": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "devOptional": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "devOptional": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "devOptional": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/renderkid/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "devOptional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "devOptional": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "deprecated": "https://github.com/lydell/resolve-url#deprecated" + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "devOptional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "devOptional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, + "node_modules/rollup": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz", + "integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==", + "devOptional": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.1", + "@rollup/rollup-android-arm64": "4.18.1", + "@rollup/rollup-darwin-arm64": "4.18.1", + "@rollup/rollup-darwin-x64": "4.18.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.1", + "@rollup/rollup-linux-arm-musleabihf": "4.18.1", + "@rollup/rollup-linux-arm64-gnu": "4.18.1", + "@rollup/rollup-linux-arm64-musl": "4.18.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.1", + "@rollup/rollup-linux-riscv64-gnu": "4.18.1", + "@rollup/rollup-linux-s390x-gnu": "4.18.1", + "@rollup/rollup-linux-x64-gnu": "4.18.1", + "@rollup/rollup-linux-x64-musl": "4.18.1", + "@rollup/rollup-win32-arm64-msvc": "4.18.1", + "@rollup/rollup-win32-ia32-msvc": "4.18.1", + "@rollup/rollup-win32-x64-msvc": "4.18.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sass": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", + "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-loader": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.2.1.tgz", + "integrity": "sha512-G0VcnMYU18a4N7VoNDegg2OuMjYtxnqzQWARVWCIVSZwJeiL9kg8QMsuIZOplsJgTzZLF6jGxI3AClj8I9nRdQ==", + "devOptional": true, + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "devOptional": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "devOptional": true + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "devOptional": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "devOptional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "devOptional": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "devOptional": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "devOptional": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "devOptional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shikiji": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/shikiji/-/shikiji-0.10.2.tgz", + "integrity": "sha512-wtZg3T0vtYV2PnqusWQs3mDaJBdCPWxFDrBM/SE5LfrX92gjUvfEMlc+vJnoKY6Z/S44OWaCRzNIsdBRWcTAiw==", + "dependencies": { + "shikiji-core": "0.10.2" + } + }, + "node_modules/shikiji-core": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/shikiji-core/-/shikiji-core-0.10.2.tgz", + "integrity": "sha512-9Of8HMlF96usXJHmCL3Gd0Fcf0EcyJUF9m8EoAKKd98mHXi0La2AZl1h6PegSFGtiYcBDK/fLuKbDa1l16r1fA==" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "devOptional": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sitemap": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz", + "integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=5.6.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/snapdragon/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/snapdragon/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "devOptional": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "devOptional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "devOptional": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "devOptional": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated" + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "devOptional": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "devOptional": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "devOptional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "devOptional": true, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "devOptional": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.31.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.2.tgz", + "integrity": "sha512-LGyRZVFm/QElZHy/CPr/O4eNZOZIzsrQ92y4v9UJe/pFJjypje2yI3C2FmPtvUEnhadlSbmG2nXtdcjHOjCfxw==", + "devOptional": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "devOptional": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "devOptional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "devOptional": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "devOptional": true + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "devOptional": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "devOptional": true + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "devOptional": true + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/to-regex/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-debounce": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ts-debounce/-/ts-debounce-4.0.0.tgz", + "integrity": "sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==" + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "devOptional": true + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "devOptional": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "devOptional": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "devOptional": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "deprecated": "Please see https://github.com/lydell/urix#deprecated" + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "devOptional": true + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "devOptional": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "devOptional": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.13.tgz", + "integrity": "sha512-/9ovhv2M2dGTuA+dY93B9trfyWMDRQw2jdVBhHNP6wr0oF34wG2i/N55801iZIpgUpnHDm4F/FabGQLyc+eOgg==", + "devOptional": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.32", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "devOptional": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/vue": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.13.tgz", + "integrity": "sha512-LDnUpQvDgsfc0u/YgtAgTMXJlJQqjkxW1PVcOnJA5cshPleULDjHi7U45pl2VJYazSSvLH8UKcid/kzH8I0a0Q==", + "dependencies": { + "@vue/compiler-dom": "3.3.13", + "@vue/compiler-sfc": "3.3.13", + "@vue/runtime-dom": "3.3.13", + "@vue/server-renderer": "3.3.13", + "@vue/shared": "3.3.13" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-class-component": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.6.tgz", + "integrity": "sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w==", + "dev": true, + "peer": true, + "peerDependencies": { + "vue": "^2.0.0" + } + }, + "node_modules/vue-loader": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.4.2.tgz", + "integrity": "sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==", + "devOptional": true, + "dependencies": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "watchpack": "^2.4.0" + }, + "peerDependencies": { + "webpack": "^4.1.0 || ^5.0.0-0" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/vue-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "devOptional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/vue-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "devOptional": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/vue-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/vue-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true + }, + "node_modules/vue-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "devOptional": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vue-property-decorator": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/vue-property-decorator/-/vue-property-decorator-9.1.2.tgz", + "integrity": "sha512-xYA8MkZynPBGd/w5QFJ2d/NM0z/YeegMqYTphy7NJQXbZcuU6FC6AOdUAcy4SXP+YnkerC6AfH+ldg7PDk9ESQ==", + "dev": true, + "peerDependencies": { + "vue": "*", + "vue-class-component": "*" + } + }, + "node_modules/vue-router": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.0.tgz", + "integrity": "sha512-HB+t2p611aIZraV2aPSRNXf0Z/oLZFrlygJm+sZbdJaW6lcFqEDQwnzUBXn+DApw+/QzDU/I9TeWx9izEjTmsA==", + "dependencies": { + "@vue/devtools-api": "^6.5.1" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue/node_modules/@vue/shared": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.13.tgz", + "integrity": "sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==" + }, + "node_modules/vuepress": { + "version": "2.0.0-rc.2", + "resolved": "https://registry.npmjs.org/vuepress/-/vuepress-2.0.0-rc.2.tgz", + "integrity": "sha512-OEHfXx4Q3IzkXqcY9bKZqHXYAnSR82AGrmWYX5R1I3+ntzjaAbUhUKbG/jjMzLg40XqAHS++pM/zzMBNrcY3rg==", + "dependencies": { + "@vuepress/cli": "2.0.0-rc.2", + "@vuepress/client": "2.0.0-rc.2", + "@vuepress/core": "2.0.0-rc.2", + "@vuepress/markdown": "2.0.0-rc.2", + "@vuepress/shared": "2.0.0-rc.2", + "@vuepress/utils": "2.0.0-rc.2", + "vue": "^3.4.15" + }, + "bin": { + "vuepress": "bin/vuepress.js", + "vuepress-vite": "bin/vuepress-vite.js", + "vuepress-webpack": "bin/vuepress-webpack.js" + }, + "engines": { + "node": ">=18.16.0" + }, + "peerDependencies": { + "@vuepress/bundler-vite": "2.0.0-rc.2", + "@vuepress/bundler-webpack": "2.0.0-rc.2", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "@vuepress/bundler-vite": { + "optional": true + }, + "@vuepress/bundler-webpack": { + "optional": true + } + } + }, + "node_modules/vuepress/node_modules/@vue/compiler-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/vuepress/node_modules/@vue/compiler-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", + "dependencies": { + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/vuepress/node_modules/@vue/compiler-sfc": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/vuepress/node_modules/@vue/compiler-ssr": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/vuepress/node_modules/@vue/reactivity": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", + "dependencies": { + "@vue/shared": "3.4.31" + } + }, + "node_modules/vuepress/node_modules/@vue/runtime-core": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" + } + }, + "node_modules/vuepress/node_modules/@vue/runtime-dom": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", + "dependencies": { + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", + "csstype": "^3.1.3" + } + }, + "node_modules/vuepress/node_modules/@vue/server-renderer": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", + "dependencies": { + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "vue": "3.4.31" + } + }, + "node_modules/vuepress/node_modules/vue": { + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/watchpack": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", + "devOptional": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "devOptional": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/web-worker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz", + "integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==" + }, + "node_modules/webpack": { + "version": "5.93.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", + "integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==", + "devOptional": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-chain": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-6.5.1.tgz", + "integrity": "sha512-7doO/SRtLu8q5WM0s7vPKPWX580qhi0/yBHkOxNkv50f6qB76Zy9o2wRTrrPULqYTvQlVHuvbA8v+G5ayuUDsA==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "devOptional": true, + "dependencies": { + "deepmerge": "^1.5.2", + "javascript-stringify": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "devOptional": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "devOptional": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.4", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "devOptional": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "devOptional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "devOptional": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "devOptional": true, + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "devOptional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "devOptional": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "devOptional": true + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "devOptional": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack/node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "devOptional": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "devOptional": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "devOptional": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "devOptional": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "devOptional": true + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "devOptional": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "devOptional": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + } + } +}