mirror of
https://github.com/itdoginfo/podkop.git
synced 2026-07-23 07:43:27 +03:00
Compare commits
139 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
235368417b | ||
|
|
5e9d036eca | ||
|
|
a64923db5a | ||
|
|
0c99dddf11 | ||
|
|
922d6d4638 | ||
|
|
34f49c392e | ||
|
|
de7b73af3c | ||
|
|
3c34bd1fd0 | ||
|
|
847895598d | ||
|
|
4146804e5a | ||
|
|
daef80b7b8 | ||
|
|
6e7b8a6be2 | ||
|
|
1365febaa4 | ||
|
|
1543d974cc | ||
|
|
4b03c28c41 | ||
|
|
c3281773d5 | ||
|
|
c9e0b54f2c | ||
|
|
7f33cfcb98 | ||
|
|
177469e212 | ||
|
|
1802818044 | ||
|
|
c62cb2de14 | ||
|
|
f253e4e36f | ||
|
|
41e696c9b2 | ||
|
|
835b455915 | ||
|
|
6089ef4c9b | ||
|
|
617e9a93bc | ||
|
|
6d9aedd236 | ||
|
|
fc9cf79221 | ||
|
|
2184c0c7d4 | ||
|
|
03736c613b | ||
|
|
c37ea6a4d3 | ||
|
|
a0869da4a5 | ||
|
|
23ac3a8d12 | ||
|
|
d8bd87a7da | ||
|
|
b93403c3e3 | ||
|
|
5629425853 | ||
|
|
8b2abc0b28 | ||
|
|
21085f3ac3 | ||
|
|
9a015bbde4 | ||
|
|
a7b6c36dfb | ||
|
|
f39e2d9ca9 | ||
|
|
00594f23fd | ||
|
|
0379cf5966 | ||
|
|
cfee6cec9a | ||
|
|
ad760a78bf | ||
|
|
d854caa4cc | ||
|
|
b44274d7f2 | ||
|
|
12d8537d90 | ||
|
|
8e18426a53 | ||
|
|
41a4f67fd0 | ||
|
|
c30160d8d3 | ||
|
|
42f75c3374 | ||
|
|
2cce383b33 | ||
|
|
81963bfbdf | ||
|
|
f3d2fa5a52 | ||
|
|
474f4b197f | ||
|
|
6083009c80 | ||
|
|
0672c55188 | ||
|
|
e14a7e7dd1 | ||
|
|
3f7e7cbc4b | ||
|
|
782f08b71b | ||
|
|
a40240bb3f | ||
|
|
1e9a7bffa4 | ||
|
|
4448c09c34 | ||
|
|
af95c0dcd0 | ||
|
|
d2fbff17bf | ||
|
|
a73def1f9a | ||
|
|
9976065696 | ||
|
|
aa8f2cef41 | ||
|
|
96f6def701 | ||
|
|
0152f073b7 | ||
|
|
06ce944e1c | ||
|
|
64369a93b0 | ||
|
|
53a3c943f0 | ||
|
|
7c7e1c6244 | ||
|
|
7fc1f39dd6 | ||
|
|
1c4285dfa8 | ||
|
|
ea1273e05e | ||
|
|
5fc3c95928 | ||
|
|
dd3e70153a | ||
|
|
622e092317 | ||
|
|
c045f8f224 | ||
|
|
b45088dad7 | ||
|
|
82345047cb | ||
|
|
0a4ed367bc | ||
|
|
c3f322ae61 | ||
|
|
eb9239696e | ||
|
|
5b3421498e | ||
|
|
6a48a060e1 | ||
|
|
14f704fcb8 | ||
|
|
ff43f477e9 | ||
|
|
576e58fd17 | ||
|
|
d72c98a254 | ||
|
|
7a497f1e31 | ||
|
|
d52f6e26ae | ||
|
|
68c61aed50 | ||
|
|
626ac981eb | ||
|
|
352d10a047 | ||
|
|
031c419ffb | ||
|
|
c13fdf5785 | ||
|
|
1b7ab606ba | ||
|
|
2bf208ecac | ||
|
|
e256e4bee5 | ||
|
|
32c385b309 | ||
|
|
56829c74c8 | ||
|
|
9d78cd2ce4 | ||
|
|
d9ce3b361e | ||
|
|
c67aadf267 | ||
|
|
ac4d7570f3 | ||
|
|
86897fd0af | ||
|
|
230ffbce46 | ||
|
|
dd5ddd1a14 | ||
|
|
cc947f9734 | ||
|
|
f8510cd828 | ||
|
|
23cbe7be4a | ||
|
|
f168fb7e31 | ||
|
|
fe84b3154f | ||
|
|
d09fdc0b95 | ||
|
|
835cd85970 | ||
|
|
8a3b41ec9c | ||
|
|
10d7617739 | ||
|
|
68010ed5f7 | ||
|
|
557e3666eb | ||
|
|
01bff8ccfb | ||
|
|
675a6af89c | ||
|
|
f1a6ff3469 | ||
|
|
d4b3377d68 | ||
|
|
d2ef640d76 | ||
|
|
47457f2c27 | ||
|
|
8a29e176f2 | ||
|
|
9653310208 | ||
|
|
3540610c78 | ||
|
|
fb54d62a7f | ||
|
|
288b8d4cc2 | ||
|
|
e014396ae2 | ||
|
|
694e4ca35a | ||
|
|
788c539e16 | ||
|
|
743cba8936 | ||
|
|
d1d703764c |
29
.github/workflows/close-invalid-issues.yml
vendored
Normal file
29
.github/workflows/close-invalid-issues.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Close Invalid Issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
close-invalid:
|
||||
if: github.event.label.name == 'Close as invalid'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Close issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: 'This issue has been marked as invalid and is being closed automatically.'
|
||||
});
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
state: 'closed'
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM itdoginfo/openwrt-sdk-apk:09102025
|
||||
FROM itdoginfo/openwrt-sdk-apk:25.12.3
|
||||
|
||||
ARG PODKOP_VERSION
|
||||
ENV PODKOP_VERSION=${PODKOP_VERSION}
|
||||
@@ -7,5 +7,5 @@ COPY ./podkop /builder/package/feeds/utilities/podkop
|
||||
COPY ./luci-app-podkop /builder/package/feeds/luci/luci-app-podkop
|
||||
|
||||
RUN make defconfig && \
|
||||
make package/podkop/compile -j1 V=s && \
|
||||
make package/luci-app-podkop/compile -j1 V=s
|
||||
make package/podkop/compile -j4 V=s && \
|
||||
make package/luci-app-podkop/compile -j4 V=s
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM itdoginfo/openwrt-sdk-ipk:24.10.3
|
||||
FROM itdoginfo/openwrt-sdk-ipk:24.10.6
|
||||
|
||||
ARG PODKOP_VERSION
|
||||
|
||||
|
||||
83
README.md
83
README.md
@@ -1,61 +1,54 @@
|
||||
# Вещи, которые вам нужно знать перед установкой
|
||||
# Podkop
|
||||
|
||||
Маршрутизация трафика для OpenWrt.
|
||||
|
||||
Направляйте нужные ресурсы в туннель, а остальное — напрямую. Открытое программное обеспечение на базе [sing-box](https://github.com/SagerNet/sing-box).
|
||||
|
||||
> [!WARNING]
|
||||
> Проект находится в стадии бета-версии. Возможны ошибки, нестабильная работа и существенные изменения функциональности.
|
||||
|
||||
# Вещи, которые необходимо знать перед установкой
|
||||
|
||||
### Обновления и конфигурация
|
||||
- При обновлении **обязательно** [очищайте кэш LuCI](https://podkop.net/docs/clear-browser-cache/).
|
||||
- После обновления проверяйте конфигурацию — она может изменяться между версиями.
|
||||
- При старте Podkop модифицируется конфигурация Dnsmasq.
|
||||
- Podkop изменяет конфигурацию sing-box. Если вы используете собственную конфигурацию, заранее сохраните её.
|
||||
|
||||
### Системные требования
|
||||
- Требуется OpenWrt 24.10 или выше.
|
||||
- Необходимо минимум 25 МБ свободного места на устройстве. Устройства с флеш-памятью 16 МБ не поддерживаются.
|
||||
|
||||
### Важные ограничения и особенности
|
||||
- Если установлен Getdomains, его [необходимо удалить](https://github.com/itdoginfo/domain-routing-openwrt?tab=readme-ov-file#скрипт-для-удаления)
|
||||
- Dashboard доступен только при подключении по HTTP (из-за особенностей Clash API). При использовании HTTPS или домена работа может быть недоступна.
|
||||
|
||||
### Поддержка и диагностика
|
||||
- [Руководство по диагностике](https://podkop.net/docs/diagnostics/)
|
||||
- Актуальные изменения публикуются в [Telegram-чате](https://t.me/itdogchat/81758/420321). Пожалуйста, ознакомьтесь с закрепленными сообщениями.
|
||||
- При возникновении проблем оставляйте технически грамотный фидбэк в GitHub Issues и Telegram-чате.
|
||||
|
||||
- Это бета-версия, которая находится в активной разработке. Из версии в версию что-то может меняться.
|
||||
- При возникновении проблем, нужен технически грамотный фидбэк в чат. Ознакомьтесь с закрепом в топике.
|
||||
- При обновлении **обязательно** [сбрасывайте кэш LuCI](https://podkop.net/docs/clear-browser-cache/).
|
||||
- Также при обновлении всегда заходите в конфигурацию и проверяйте свои настройки. Конфигурация может измениться.
|
||||
- Необходимо минимум 25МБ свободного места на роутере. Роутеры с флешками на 16МБ сразу мимо.
|
||||
- При старте программы редактируется конфиг Dnsmasq.
|
||||
- Podkop редактирует конфиг sing-box. Обязательно сохраните ваш конфиг sing-box перед установкой, если он вам нужен.
|
||||
- Информация здесь может быть устаревшей. Все изменения фиксируются в [телеграм-чате](https://t.me/itdogchat/81758/420321).
|
||||
- [Если у вас что-то не работает.](https://podkop.net/docs/diagnostics/)
|
||||
- Если у вас установлен Getdomains, [его следует удалить](https://github.com/itdoginfo/domain-routing-openwrt?tab=readme-ov-file#%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82-%D0%B4%D0%BB%D1%8F-%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F).
|
||||
- Требуется версия OpenWrt 24.10.
|
||||
- Dashboard доступен, если вы заходите по http (из-за особенностей clash api). И не будет работать, если вы заходите по https и/или домену.
|
||||
|
||||
# Документация
|
||||
https://podkop.net/
|
||||
|
||||
# Установка Podkop
|
||||
Полная информация в [документации](https://podkop.net/docs/install/)
|
||||
Полное руководство доступно в [документации](https://podkop.net/docs/install/)
|
||||
|
||||
Вкратце, достаточно одного скрипта для установки и обновления:
|
||||
Для установки и обновления достаточно выполнить один скрипт:
|
||||
```
|
||||
sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/install.sh)
|
||||
```
|
||||
|
||||
## Изменения 0.7.0
|
||||
Начиная с версии 0.7.0 изменена структура конфига `/etc/config/podkop`. Старые значения несовместимы с новыми. Нужно заново настроить Podkop.
|
||||
|
||||
Скрипт установки обнаружит старую версию и предупредит вас об этом. Если вы согласитесь, то он сделает автоматически написанное ниже.
|
||||
|
||||
При обновлении вручную нужно:
|
||||
|
||||
0. Не ныть в issue и чатик.
|
||||
1. Забэкапить старый конфиг:
|
||||
```
|
||||
mv /etc/config/podkop /etc/config/podkop-070
|
||||
```
|
||||
2. Стянуть новый дефолтный конфиг:
|
||||
```
|
||||
wget -O /etc/config/podkop https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/main/podkop/files/etc/config/podkop
|
||||
```
|
||||
3. Настроить заново ваш Podkop через Luci или UCI.
|
||||
|
||||
# ToDo
|
||||
## Будущее
|
||||
Планы развития проекта в настоящее время не публикуются в виде открытого roadmap. Обсуждение направлений и задач разработки ведётся авторами и контрибьюторами.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> PR принимаются только по issues, у которых стоит label "enhancement". Либо по согласованию с авторами в ТГ-чате. Остальные PR на данный момент не рассматриваются.
|
||||
> Pull Request принимаются только после согласования с авторами в Telegram-чате. На данный момент PR без предварительного обсуждения не рассматриваются.
|
||||
|
||||
## Будущее
|
||||
- [ ] [Подписка](https://github.com/itdoginfo/podkop/issues/118). Здесь нужна реализация, чтоб для каждой секции помимо ручного выбора, был выбор фильтрации по тегу. Например, для main выбираем ключевые слова NL, DE, FI. А для extra секции фильтруем по RU. И создаётся outbound c urltest в которых перечислены outbound из фильтров.
|
||||
- [ ] Весь трафик в sing-box и маршрутизация полностью на его уровне.
|
||||
- [ ] При успешном запуске переходит в фоновый режим и следит за состоянием sing-box. Если вдруг идёт exit 1, выполняется dnsmasq restore и снова следит за состоянием. Вопрос в том, как это искусственно провернуть. Попробовать положить прокси и посмотреть, останется ли работать DNS в этом случае. И здесь, вероятно, можно обойтись триггером в init.d. [Issue](https://github.com/itdoginfo/podkop/issues/111)
|
||||
- [ ] Галочка, которая режет доступ к doh серверам.
|
||||
- [ ] IPv6. Только после наполнения Wiki.
|
||||
|
||||
## Тесты
|
||||
- [ ] Unit тесты (BATS)
|
||||
- [ ] Интеграционные тесты бекенда (OpenWrt rootfs + BATS)
|
||||
> [!WARNING]
|
||||
> Данное программное обеспечение предоставляется «как есть», без каких-либо явных или подразумеваемых гарантий, включая гарантии коммерческой пригодности и соответствия определённой цели.
|
||||
>
|
||||
> Правообладатели и участники проекта не несут ответственности за любые прямые, косвенные, случайные, специальные или иные убытки, возникшие в результате использования программного обеспечения, включая потерю данных, прибыли или прерывание деятельности, даже если они были предупреждены о возможности таких последствий.
|
||||
|
||||
[](https://deepwiki.com/itdoginfo/podkop)
|
||||
@@ -34,6 +34,7 @@ vless://4d21ce62-8723-4c4d-93e3-d586b107aa40@127.0.0.1:51394?type=ws&encryption=
|
||||
# gRPC
|
||||
vless://974b39e3-f7bf-42b9-933c-16699c635e77@127.0.0.1:15633?type=grpc&encryption=none&serviceName=TunService&authority=&security=none#vless-gRPC-none
|
||||
vless://651e7eca-5152-46f1-baf2-d502e0af7b27@127.0.0.1:28535?type=grpc&encryption=none&serviceName=TunService&authority=authority&security=reality&pbk=nhZ7NiKfcqESa5ZeBFfsq9o18W-OWOAHLln9UmuVXSk&fp=chrome&sni=google.com&sid=11cbaeaa&spx=%2F#vless-gRPC-reality
|
||||
vless://221ff905-b783-41a0-a6a6-8089eaf3b34b@abc.def.xyz:443?security=reality&type=grpc&headerType=&authority=abc.def.xyz&serviceName=name&mode=gun&sni=abc.def.xyz&fp=chrome&pbk=C3nhDJw02ZU_rjx4GbC54Sp79-ysF5lWIQVWdY4FOnE&sid=#vless-gRPC-reality-mode
|
||||
vless://af1f8b5f-26c9-4fe8-8ce7-6d6366c5c9ce@127.0.0.1:47904?type=grpc&encryption=none&serviceName=TunService&authority=authority&security=tls&fp=chrome&alpn=h2%2Chttp%2F1.1&sni=google.com#vless-gRPC-tls
|
||||
vless://95f2c4bb-abcb-47ba-bfad-e181c03e4659@127.0.0.1:34530?type=grpc&encryption=none&serviceName=TunService&authority=authority&security=tls&fp=chrome&alpn=h2%2Chttp%2F1.1&allowInsecure=1&sni=google.com#vless-gRPC-tls-insecure
|
||||
vless://bd39490f-9a4f-49b2-96b6-824190cf89e9@127.0.0.1:27779?type=grpc&encryption=none&serviceName=TunService&authority=authority&security=tls&fp=chrome&alpn=h2%2Chttp%2F1.1&sni=google.com&ech=AF3%2BDQBZAAAgACBc%2FiNdo4QkTt9eQCQgkOiJVSfA9G6UWAyipaBFtBD%2FVQAkAAEAAQABAAIAAQADAAIAAQACAAIAAgADAAMAAQADAAIAAwADAApnb29nbGUuY29tAAA%3D#vless-gRPC-tls-ech
|
||||
@@ -81,4 +82,38 @@ trojan://ou8pLSyx9N@127.0.0.1:17737?type=httpupgrade&path=%2Fhttpupgradepath&hos
|
||||
|
||||
# XHTTP
|
||||
trojan://VEetltxLtw@127.0.0.1:59072?type=xhttp&path=%2Fxhttppath&host=google.com&mode=auto&security=none#trojan-xhttp
|
||||
```
|
||||
|
||||
## Hysteria2
|
||||
|
||||
hysteria2://
|
||||
```
|
||||
# With password
|
||||
hysteria2://password@example.com:443/#hysteria2-password
|
||||
hysteria2://password@example.com:443/?insecure=1#hysteria2-password-insecure
|
||||
|
||||
# With SNI
|
||||
hysteria2://password@example.com:443/?sni=example.com#hysteria2-password-sni
|
||||
|
||||
# With obfuscation
|
||||
hysteria2://password@example.com:443/?obfs=salamander&obfs-password=obfspassword#hysteria2-obfs
|
||||
|
||||
# All parameters combined
|
||||
hysteria2://mypassword@example.com:8443/?sni=example.com&obfs=salamander&obfs-password=obfspass&insecure=1#hysteria2-all-params
|
||||
```
|
||||
|
||||
hy2://
|
||||
```
|
||||
# With password
|
||||
hy2://password@example.com:443/#hysteria2-password
|
||||
hy2://password@example.com:443/?insecure=1#hysteria2-password-insecure
|
||||
|
||||
# With SNI
|
||||
hy2://password@example.com:443/?sni=example.com#hysteria2-password-sni
|
||||
|
||||
# With obfuscation
|
||||
hy2://password@example.com:443/?obfs=salamander&obfs-password=obfspassword#hysteria2-obfs
|
||||
|
||||
# All parameters combined
|
||||
hy2://mypassword@example.com:8443/?sni=example.com&obfs=salamander&obfs-password=obfspass&insecure=1#hysteria2-all-params
|
||||
```
|
||||
63
TRADEMARK.md
Normal file
63
TRADEMARK.md
Normal file
@@ -0,0 +1,63 @@
|
||||
Trademark Guidelines
|
||||
|
||||
Version 1.0 dated May 28, 2026
|
||||
|
||||
This trademark policy was prepared to help you understand how to use the Podkop trademarks, service marks, and logos in connection with the Podkop open source project and related software.
|
||||
|
||||
While the Podkop software is available under an open source license, that license does not grant permission to use the Podkop trademarks, service marks, or logos. This policy explains acceptable use of the Podkop brand and related marks.
|
||||
|
||||
This Policy covers:
|
||||
1. Our word trademarks and service marks: Podkop
|
||||
2. Our logos, icons, and other Podkop brand assets
|
||||
|
||||
This policy encompasses all trademarks and service marks, whether they are registered or not.
|
||||
|
||||
## 1. General Guidelines
|
||||
|
||||
Whenever you use one of our marks, you must always do so in a way that does not mislead anyone about what they are getting and from whom. For example, you cannot say you are distributing Podkop software when you are distributing a modified version of it, because recipients may not understand the differences between your modified versions and our own.
|
||||
|
||||
You also cannot use our logo on your website in a way that suggests that your website is an official website or that we endorse your website.
|
||||
|
||||
You can, however, say that you like the Podkop project, that you participate in the Podkop community, or that you are providing an unmodified version of the Podkop software.
|
||||
|
||||
You may not use or register our marks, or variations of them as part of your own trademark, service mark, domain name, company name, trade name, product name or service name.
|
||||
|
||||
Trademark law does not allow your use of names or trademarks that are too similar to ours. You therefore may not use an obvious variation of any of our marks or any phonetic equivalent, foreign language equivalent, takeoff, or abbreviation for a similar or compatible product or service. For example, we would consider the following too similar to one of our Marks:
|
||||
|
||||
- MyPodkop
|
||||
- Open-Podkop
|
||||
- PodkopX
|
||||
- Podkop Lite
|
||||
- Podkop Pro
|
||||
|
||||
## 2. Acceptable Uses
|
||||
### Unmodified Code
|
||||
|
||||
When you redistribute an unmodified copy of Podkop software, you must not remove any Podkop trademarks, notices, or branding included in the original distribution.
|
||||
|
||||
### Modified Code
|
||||
|
||||
If you distribute a modified version of Podkop software, you may not use the Podkop name, trademarks, or logos in connection with your modified version, except to accurately describe the origin of the software in factual statements.
|
||||
|
||||
You must replace any Podkop branding, including names displayed in user interfaces, logs, documentation, and other user-facing elements, with your own distinct name and branding, so that your modified version is clearly distinguishable from the original Podkop software.
|
||||
|
||||
You must remove all Podkop logos and any other brand assets from the modified version.
|
||||
|
||||
You may not present your modified version as Podkop or as an official Podkop release, nor may you use the Podkop name in a way that suggests endorsement, affiliation, or official status.
|
||||
|
||||
You may only refer to Podkop in a factual and descriptive manner, for example: “This software is derived from Podkop open-source software.”
|
||||
|
||||
### Statements about Compatibility
|
||||
|
||||
You may use the word marks, exclusively for descriptive and factual purposes, to truthfully and accurately describe the relationship between your software and ours, including compatibility or origin. Any other use may imply that we have certified or approved your software.
|
||||
|
||||
### No Domain Names
|
||||
|
||||
You must not register any domain that includes our word marks or any variant or combination of them.
|
||||
|
||||
## 3. How to Display our Marks
|
||||
### Use of Trademarks in Text
|
||||
|
||||
Always use trademarks in their exact form with correct spelling. They must not be abbreviated, modified, hyphenated, or combined with other words in a way that creates a new product or service name.
|
||||
|
||||
Unacceptable: Podcop
|
||||
65
TRADEMARK_RU.md
Normal file
65
TRADEMARK_RU.md
Normal file
@@ -0,0 +1,65 @@
|
||||
Руководство по использованию товарных знаков
|
||||
|
||||
Версия 1.0 от 28 мая 2026 года
|
||||
|
||||
Настоящая политика в отношении товарных знаков подготовлена для того, чтобы помочь вам понять, как использовать товарные знаки, знаки обслуживания и логотипы Podkop в связи с открытым исходным кодом проекта Podkop и связанным программным обеспечением.
|
||||
|
||||
Хотя программное обеспечение Podkop распространяется под лицензией с открытым исходным кодом, эта лицензия не предоставляет разрешения на использование товарных знаков Podkop, знаков обслуживания или логотипов. Данная политика объясняет допустимое использование бренда Podkop и связанных обозначений.
|
||||
|
||||
Настоящая Политика охватывает:
|
||||
1. Наши словесные товарные знаки и знаки обслуживания: Podkop
|
||||
2. Наши логотипы, иконки и другие бренд-активы Podkop
|
||||
|
||||
Данная политика распространяется на все товарные знаки и знаки обслуживания, независимо от того, зарегистрированы они или нет.
|
||||
|
||||
## 1. Общие рекомендации
|
||||
|
||||
При использовании любого из наших знаков вы всегда должны делать это таким образом, чтобы никого не вводить в заблуждение относительно того, что именно они получают и от кого. Например, вы не можете утверждать, что распространяете программное обеспечение Podkop, если вы распространяете его модифицированную версию, поскольку получатели могут не понимать различий между вашей модифицированной версией и нашей оригинальной.
|
||||
|
||||
Вы также не можете использовать наш логотип на своём сайте таким образом, чтобы это создавало впечатление, что ваш сайт является официальным сайтом или что мы одобряем ваш сайт.
|
||||
|
||||
Однако вы можете указывать, что вам нравится проект Podkop, что вы участвуете в сообществе Podkop или что вы распространяете немодифицированную версию программного обеспечения Podkop.
|
||||
|
||||
Вы не имеете права использовать или регистрировать наши знаки, а также их вариации, как часть вашего собственного товарного знака, знака обслуживания, доменного имени, названия компании, коммерческого наименования, названия продукта или услуги.
|
||||
|
||||
Закон о товарных знаках не допускает использование названий или знаков, которые слишком похожи на наши. Поэтому вы не можете использовать очевидные вариации наших знаков или любые фонетически, иностранно-языковые эквиваленты, производные, аббревиатуры для похожего или совместимого продукта или услуги. Например, мы считаем слишком похожими на наши знаки следующие варианты:
|
||||
|
||||
- MyPodkop
|
||||
- Open-Podkop
|
||||
- PodkopX
|
||||
- Podkop Lite
|
||||
- Podkop Pro
|
||||
|
||||
## 2. Допустимое использование
|
||||
|
||||
### Немодифицированный код
|
||||
|
||||
При распространении немодифицированной копии программного обеспечения Podkop вы не должны удалять товарные знаки, уведомления или брендинг Podkop, включённые в исходное распространение.
|
||||
|
||||
### Модифицированный код
|
||||
|
||||
Если вы распространяете модифицированную версию программного обеспечения Podkop, вы не можете использовать название Podkop, товарные знаки или логотипы в связи с вашей модифицированной версией, за исключением точного описания происхождения программного обеспечения в фактических утверждениях.
|
||||
|
||||
Вы обязаны заменить все элементы брендинга Podkop, включая названия, отображаемые в пользовательском интерфейсе, логах, документации и других пользовательских элементах, на собственное отличительное название и брендинг, чтобы ваша модифицированная версия была явно отличима от оригинального программного обеспечения Podkop.
|
||||
|
||||
Вы должны удалить все логотипы Podkop и любые другие бренд-материалы из модифицированной версии.
|
||||
|
||||
Вы не можете представлять вашу модифицированную версию как Podkop или как официальную версию Podkop, а также использовать название Podkop таким образом, чтобы это подразумевало одобрение, аффилированность или официальный статус.
|
||||
|
||||
Вы можете ссылаться на Podkop только в фактическом и описательном контексте, например: «Это программное обеспечение основано на программном обеспечении Podkop с открытым исходным кодом».
|
||||
|
||||
### Упоминания о совместимости
|
||||
|
||||
Вы можете использовать словесные знаки исключительно в описательных и фактических целях для правдивого и точного описания связи вашего программного обеспечения с нашим, включая совместимость или происхождение. Любое другое использование может создавать впечатление, что мы сертифицировали или одобрили ваше программное обеспечение.
|
||||
|
||||
### Запрет на доменные имена
|
||||
|
||||
Вы не должны регистрировать доменные имена, содержащие наши словесные знаки или любые их варианты или комбинации.
|
||||
|
||||
## 3. Как отображать наши знаки
|
||||
|
||||
### Использование товарных знаков в тексте
|
||||
|
||||
Всегда используйте товарные знаки в их точной форме с корректным написанием. Их нельзя сокращать, изменять, соединять дефисами или объединять с другими словами таким образом, чтобы это создавало новое название продукта или услуги.
|
||||
|
||||
Недопустимо: Podcop
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,38 +1,38 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# Copyright (C) 2026 THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PODKOP package.
|
||||
# divocat <divocatt@gmail.com>, 2025.
|
||||
# divocatt <210179590+divocatt@users.noreply.github.com>, 2026.
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PODKOP\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-23 17:43+0300\n"
|
||||
"PO-Revision-Date: 2025-10-23 17:43+0300\n"
|
||||
"Last-Translator: divocat <divocatt@gmail.com>\n"
|
||||
"POT-Creation-Date: 2026-05-29 13:40+0300\n"
|
||||
"PO-Revision-Date: 2026-05-29 13:40+0300\n"
|
||||
"Last-Translator: divocatt <210179590+divocatt@users.noreply.github.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:342
|
||||
#: src/podkop/tabs/dashboard/initController.ts:345
|
||||
msgid "✔ Enabled"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:353
|
||||
#: src/podkop/tabs/dashboard/initController.ts:356
|
||||
msgid "✔ Running"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:343
|
||||
#: src/podkop/tabs/dashboard/initController.ts:346
|
||||
msgid "✘ Disabled"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:354
|
||||
#: src/podkop/tabs/dashboard/initController.ts:357
|
||||
msgid "✘ Stopped"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:304
|
||||
#: src/podkop/tabs/dashboard/initController.ts:307
|
||||
msgid "Active Connections"
|
||||
msgstr ""
|
||||
|
||||
@@ -40,15 +40,19 @@ msgstr ""
|
||||
msgid "Additional marking rules found"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:111
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:247
|
||||
msgid "Allows access to YACD from the WAN. Make sure to open the appropriate port in your firewall."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:199
|
||||
msgid "Applicable for SOCKS and Shadowsocks proxy"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:356
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:444
|
||||
msgid "At least one valid domain must be specified. Comments-only content is not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:437
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:525
|
||||
msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed."
|
||||
msgstr ""
|
||||
|
||||
@@ -72,16 +76,17 @@ msgstr ""
|
||||
msgid "Browser is using FakeIP correctly"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:329
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:348
|
||||
msgid "Cache File Path"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:343
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:362
|
||||
msgid "Cache file path cannot be empty"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:27
|
||||
#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:28
|
||||
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:27
|
||||
#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:25
|
||||
msgid "Cannot receive checks result"
|
||||
msgstr ""
|
||||
@@ -89,6 +94,7 @@ msgstr ""
|
||||
#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:15
|
||||
#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:15
|
||||
#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:13
|
||||
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:15
|
||||
#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:13
|
||||
msgid "Checking, please wait"
|
||||
msgstr ""
|
||||
@@ -113,11 +119,11 @@ msgstr ""
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:211
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:299
|
||||
msgid "Community Lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:316
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:335
|
||||
msgid "Config File Path"
|
||||
msgstr ""
|
||||
|
||||
@@ -125,7 +131,7 @@ msgstr ""
|
||||
msgid "Configuration for Podkop service"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:22
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:23
|
||||
msgid "Configuration Type"
|
||||
msgstr ""
|
||||
|
||||
@@ -133,7 +139,7 @@ msgstr ""
|
||||
msgid "Connection Type"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:25
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:26
|
||||
msgid "Connection URL"
|
||||
msgstr ""
|
||||
|
||||
@@ -173,16 +179,16 @@ msgstr ""
|
||||
msgid "Disable autostart"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:246
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:265
|
||||
msgid "Disable QUIC"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:247
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:266
|
||||
msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:302
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:382
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:390
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:470
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
||||
@@ -190,17 +196,17 @@ msgstr ""
|
||||
msgid "DNS on router"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:179
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:267
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:15
|
||||
msgid "DNS over HTTPS (DoH)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:180
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:268
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:16
|
||||
msgid "DNS over TLS (DoT)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:176
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:264
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:12
|
||||
msgid "DNS Protocol Type"
|
||||
msgstr ""
|
||||
@@ -209,7 +215,7 @@ msgstr ""
|
||||
msgid "DNS Rewrite TTL"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:189
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:277
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:24
|
||||
msgid "DNS Server"
|
||||
msgstr ""
|
||||
@@ -222,16 +228,16 @@ msgstr ""
|
||||
msgid "Do not panic, everything can be fixed, just..."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:166
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:254
|
||||
msgid "Domain Resolver"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:307
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:326
|
||||
msgid "Dont Touch My DHCP!"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:238
|
||||
#: src/podkop/tabs/dashboard/initController.ts:272
|
||||
#: src/podkop/tabs/dashboard/initController.ts:241
|
||||
#: src/podkop/tabs/dashboard/initController.ts:275
|
||||
msgid "Downlink"
|
||||
msgstr ""
|
||||
|
||||
@@ -239,24 +245,21 @@ msgstr ""
|
||||
msgid "Download"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:269
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:288
|
||||
msgid "Download Lists via Proxy/VPN"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:278
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:297
|
||||
msgid "Download Lists via specific proxy section"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:270
|
||||
msgid "Downloading all lists via main Proxy/VPN"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:279
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:289
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:298
|
||||
msgid "Downloading all lists via specific Proxy/VPN"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:303
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:383
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:391
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:471
|
||||
msgid "Dynamic List"
|
||||
msgstr ""
|
||||
|
||||
@@ -264,11 +267,15 @@ msgstr ""
|
||||
msgid "Enable autostart"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:167
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:255
|
||||
msgid "Enable built-in DNS resolver for domains handled by this section"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:575
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:691
|
||||
msgid "Enable DNS resolve to get real IP when routing"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:665
|
||||
msgid "Enable Mixed Proxy"
|
||||
msgstr ""
|
||||
|
||||
@@ -276,7 +283,7 @@ msgstr ""
|
||||
msgid "Enable Output Network Interface"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:576
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:666
|
||||
msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies"
|
||||
msgstr ""
|
||||
|
||||
@@ -284,27 +291,47 @@ msgstr ""
|
||||
msgid "Enable YACD"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:65
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:246
|
||||
msgid "Enable YACD WAN Access"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:66
|
||||
msgid "Enter complete outbound configuration in JSON format"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:338
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:426
|
||||
msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:312
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:400
|
||||
msgid "Enter domain names without protocols, e.g. example.com or sub.example.com"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:392
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:480
|
||||
msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:365
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:138
|
||||
msgid "Every 1 minute"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:139
|
||||
msgid "Every 3 minutes"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:137
|
||||
msgid "Every 30 seconds"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:140
|
||||
msgid "Every 5 minutes"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:402
|
||||
msgid "Exclude NTP"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:366
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:403
|
||||
msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN"
|
||||
msgstr ""
|
||||
|
||||
@@ -312,20 +339,21 @@ msgstr ""
|
||||
msgid "Failed to copy!"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:226
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:230
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:260
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:264
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:298
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:227
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:231
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:261
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:265
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:302
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:306
|
||||
msgid "Failed to execute!"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/methods/custom/getDashboardSections.ts:117
|
||||
#: src/podkop/methods/custom/getDashboardSections.ts:148
|
||||
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:59
|
||||
msgid "Fastest"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:550
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:638
|
||||
msgid "Fully Routed IPs"
|
||||
msgstr ""
|
||||
|
||||
@@ -333,7 +361,7 @@ msgstr ""
|
||||
msgid "Get global check"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:221
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:222
|
||||
msgid "Global check"
|
||||
msgstr ""
|
||||
|
||||
@@ -366,11 +394,63 @@ msgstr ""
|
||||
msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:90
|
||||
msgid "Invalid HY2 URL: insecure must be 0 or 1"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:77
|
||||
msgid "Invalid HY2 URL: invalid port number"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:30
|
||||
msgid "Invalid HY2 URL: missing credentials/server"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:47
|
||||
msgid "Invalid HY2 URL: missing host"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:41
|
||||
msgid "Invalid HY2 URL: missing host & port"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:36
|
||||
msgid "Invalid HY2 URL: missing password"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:50
|
||||
msgid "Invalid HY2 URL: missing port"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:18
|
||||
msgid "Invalid HY2 URL: must not contain spaces"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:12
|
||||
msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:108
|
||||
msgid "Invalid HY2 URL: obfs-password required when obfs is set"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:122
|
||||
msgid "Invalid HY2 URL: parsing failed"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:116
|
||||
msgid "Invalid HY2 URL: sni cannot be empty"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:98
|
||||
msgid "Invalid HY2 URL: unsupported obfs type"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateIp.ts:11
|
||||
msgid "Invalid IP address"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateOutboundJson.ts:19
|
||||
#: src/validators/validateOutboundJson.ts:9
|
||||
msgid "Invalid JSON format"
|
||||
msgstr ""
|
||||
|
||||
@@ -466,7 +546,8 @@ msgstr ""
|
||||
msgid "Invalid Trojan URL: parsing failed"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateUrl.ts:18
|
||||
#: src/validators/validateUrl.ts:8
|
||||
#: src/validators/validateUrl.ts:31
|
||||
msgid "Invalid URL format"
|
||||
msgstr ""
|
||||
|
||||
@@ -482,31 +563,35 @@ msgstr ""
|
||||
msgid "Issues detected"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:452
|
||||
#: src/podkop/tabs/diagnostic/helpers/getPodkopVersionRow.ts:48
|
||||
msgid "Latest"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:257
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:276
|
||||
msgid "List Update Frequency"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:458
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:546
|
||||
msgid "Local Domain Lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:481
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:569
|
||||
msgid "Local Subnet Lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:384
|
||||
msgid "Log Level"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:72
|
||||
msgid "Main DNS"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:308
|
||||
#: src/podkop/tabs/dashboard/initController.ts:311
|
||||
msgid "Memory Usage"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:586
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:678
|
||||
msgid "Mixed Proxy Port"
|
||||
msgstr ""
|
||||
|
||||
@@ -514,7 +599,11 @@ msgstr ""
|
||||
msgid "Monitored Interfaces"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:120
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:164
|
||||
msgid "Must be a number in the range of 50 - 1000"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:208
|
||||
msgid "Network Interface"
|
||||
msgstr ""
|
||||
|
||||
@@ -526,10 +615,17 @@ msgstr ""
|
||||
msgid "Not implement yet"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:75
|
||||
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:81
|
||||
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:100
|
||||
msgid "Not responding"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:55
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:63
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:71
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:79
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:87
|
||||
msgid "Not running"
|
||||
msgstr ""
|
||||
|
||||
@@ -537,19 +633,15 @@ msgstr ""
|
||||
msgid "Operation timed out"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:26
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:29
|
||||
msgid "Outbound Config"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:64
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:65
|
||||
msgid "Outbound Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateOutboundJson.ts:11
|
||||
msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:442
|
||||
#: src/podkop/tabs/diagnostic/helpers/getPodkopVersionRow.ts:38
|
||||
msgid "Outdated"
|
||||
msgstr ""
|
||||
|
||||
@@ -561,26 +653,27 @@ msgstr ""
|
||||
msgid "Path cannot be empty"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:347
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:366
|
||||
msgid "Path must be absolute (start with /)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:356
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:375
|
||||
msgid "Path must contain at least one directory (like /tmp/cache.db)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:351
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:370
|
||||
msgid "Path must end with cache.db"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:95
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:103
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:111
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:119
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:127
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:135
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:340
|
||||
#: src/podkop/tabs/dashboard/initController.ts:343
|
||||
msgid "Podkop"
|
||||
msgstr ""
|
||||
|
||||
@@ -588,11 +681,11 @@ msgstr ""
|
||||
msgid "Podkop Settings"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:308
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:327
|
||||
msgid "Podkop will not modify your DHCP configuration"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:34
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:36
|
||||
msgid "Proxy Configuration URL"
|
||||
msgstr ""
|
||||
|
||||
@@ -604,18 +697,22 @@ msgstr ""
|
||||
msgid "Proxy traffic is routed via FakeIP"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:245
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:333
|
||||
msgid "Regional options cannot be used together"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:504
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:592
|
||||
msgid "Remote Domain Lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:527
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:615
|
||||
msgid "Remote Subnet Lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:690
|
||||
msgid "Resolve real IP for routing"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:49
|
||||
msgid "Restart podkop"
|
||||
msgstr ""
|
||||
@@ -628,7 +725,7 @@ msgstr ""
|
||||
msgid "Router DNS is routed through sing-box"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:376
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:413
|
||||
msgid "Routing Excluded IPs"
|
||||
msgstr ""
|
||||
|
||||
@@ -660,15 +757,19 @@ msgstr ""
|
||||
msgid "Run Diagnostic"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:264
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:352
|
||||
msgid "Russia inside restrictions"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:257
|
||||
msgid "Secret key for authenticating remote access to YACD when WAN access is enabled."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:36
|
||||
msgid "Sections"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:212
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:300
|
||||
msgid "Select a predefined list for routing"
|
||||
msgstr ""
|
||||
|
||||
@@ -680,43 +781,47 @@ msgstr ""
|
||||
msgid "Select DNS protocol to use"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:258
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:277
|
||||
msgid "Select how often the domain or subnet lists are updated automatically"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:23
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:24
|
||||
msgid "Select how to configure the proxy"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:121
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:209
|
||||
msgid "Select network interface for VPN connection"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:190
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:278
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:25
|
||||
msgid "Select or enter DNS server address"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:330
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:349
|
||||
msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:317
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:336
|
||||
msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:177
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:265
|
||||
msgid "Select the DNS protocol type for the domain resolver"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:300
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:388
|
||||
msgid "Select the list type for adding custom domains"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:380
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:468
|
||||
msgid "Select the list type for adding custom subnets"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:385
|
||||
msgid "Select the log level for sing-box"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:90
|
||||
msgid "Select the network interface from which the traffic will originate"
|
||||
msgstr ""
|
||||
@@ -729,7 +834,15 @@ msgstr ""
|
||||
msgid "Select the WAN interfaces to be monitored"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:337
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:27
|
||||
msgid "Selector"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:88
|
||||
msgid "Selector Proxy Links"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:340
|
||||
msgid "Services info"
|
||||
msgstr ""
|
||||
|
||||
@@ -737,12 +850,12 @@ msgstr ""
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:289
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:290
|
||||
#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:116
|
||||
msgid "Show sing-box config"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:351
|
||||
#: src/podkop/tabs/dashboard/initController.ts:354
|
||||
msgid "Sing-box"
|
||||
msgstr ""
|
||||
|
||||
@@ -767,31 +880,31 @@ msgid "Sing-box service exist"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:67
|
||||
msgid "Sing-box version >= 1.12.4"
|
||||
msgid "Sing-box version is compatible (newer than 1.12.4)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:89
|
||||
msgid "Source Network Interface"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:377
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:414
|
||||
msgid "Specify a local IP address to be excluded from routing"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:551
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:639
|
||||
msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:505
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:593
|
||||
msgid "Specify remote URLs to download and use domain lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:528
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:616
|
||||
msgid "Specify remote URLs to download and use subnet lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:459
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:482
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:547
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:570
|
||||
msgid "Specify the path to the list file located on the router filesystem"
|
||||
msgstr ""
|
||||
|
||||
@@ -807,7 +920,7 @@ msgstr ""
|
||||
msgid "Successfully copied!"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:301
|
||||
#: src/podkop/tabs/dashboard/initController.ts:304
|
||||
msgid "System info"
|
||||
msgstr ""
|
||||
|
||||
@@ -823,27 +936,36 @@ msgstr ""
|
||||
msgid "Test latency"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:304
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:392
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:472
|
||||
msgid "Text List"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:384
|
||||
msgid "Text List (comma/space/newline separated)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46
|
||||
msgid "The DNS server used to look up the IP address of an upstream DNS server"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:135
|
||||
msgid "The interval between connectivity tests"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:148
|
||||
msgid "The maximum difference in response times (ms) allowed when comparing servers"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:171
|
||||
msgid "The URL used to test server connectivity"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:69
|
||||
msgid "Time in seconds for DNS record caching (default: 60)"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:235
|
||||
#: src/podkop/tabs/dashboard/initController.ts:238
|
||||
msgid "Traffic"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:265
|
||||
#: src/podkop/tabs/dashboard/initController.ts:268
|
||||
msgid "Traffic Total"
|
||||
msgstr ""
|
||||
|
||||
@@ -859,22 +981,22 @@ msgstr ""
|
||||
msgid "TTL value cannot be empty"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:181
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:269
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:17
|
||||
msgid "UDP (Unprotected DNS)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:110
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:198
|
||||
msgid "UDP over TCP"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:37
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:38
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:39
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:40
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:41
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:42
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:416
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:43
|
||||
#: src/podkop/tabs/diagnostic/helpers/getPodkopVersionRow.ts:7
|
||||
msgid "unknown"
|
||||
msgstr ""
|
||||
|
||||
@@ -882,48 +1004,60 @@ msgstr ""
|
||||
msgid "Unknown error"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:237
|
||||
#: src/podkop/tabs/dashboard/initController.ts:268
|
||||
#: src/podkop/tabs/dashboard/initController.ts:240
|
||||
#: src/podkop/tabs/dashboard/initController.ts:271
|
||||
msgid "Uplink"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateProxyUrl.ts:29
|
||||
msgid "URL must start with vless://, ss://, trojan://, or socks4/5://"
|
||||
#: src/validators/validateProxyUrl.ts:37
|
||||
msgid "URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateUrl.ts:13
|
||||
#: src/validators/validateUrl.ts:17
|
||||
msgid "URL must use one of the following protocols:"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:27
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:28
|
||||
msgid "URLTest"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:87
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:134
|
||||
msgid "URLTest Check Interval"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:111
|
||||
msgid "URLTest Proxy Links"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:299
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:170
|
||||
msgid "URLTest Testing URL"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:147
|
||||
msgid "URLTest Tolerance"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:387
|
||||
msgid "User Domain List Type"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:311
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:399
|
||||
msgid "User Domains"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:337
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:425
|
||||
msgid "User Domains List"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:379
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:467
|
||||
msgid "User Subnet List Type"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:391
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:479
|
||||
msgid "User Subnets"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:417
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:505
|
||||
msgid "User Subnets List"
|
||||
msgstr ""
|
||||
|
||||
@@ -931,24 +1065,25 @@ msgstr ""
|
||||
#: src/validators/validateDns.ts:18
|
||||
#: src/validators/validateDomain.ts:13
|
||||
#: src/validators/validateDomain.ts:30
|
||||
#: src/validators/validateHysteriaUrl.ts:120
|
||||
#: src/validators/validateIp.ts:8
|
||||
#: src/validators/validateOutboundJson.ts:17
|
||||
#: src/validators/validateOutboundJson.ts:7
|
||||
#: src/validators/validatePath.ts:16
|
||||
#: src/validators/validateShadowsocksUrl.ts:95
|
||||
#: src/validators/validateSocksUrl.ts:80
|
||||
#: src/validators/validateSubnet.ts:38
|
||||
#: src/validators/validateTrojanUrl.ts:59
|
||||
#: src/validators/validateUrl.ts:16
|
||||
#: src/validators/validateUrl.ts:28
|
||||
#: src/validators/validateVlessUrl.ts:108
|
||||
msgid "Valid"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:370
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:449
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:458
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:537
|
||||
msgid "Validation errors:"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:255
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:256
|
||||
#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:107
|
||||
msgid "View logs"
|
||||
msgstr ""
|
||||
@@ -957,14 +1092,24 @@ msgstr ""
|
||||
msgid "Visit Wiki"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:247
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:37
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:89
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:112
|
||||
msgid "vless://, ss://, trojan://, socks4/5://, hy2/hysteria2:// links"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:335
|
||||
msgid "Warning: %s cannot be used together with %s. Previous selections have been removed."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:266
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:354
|
||||
msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:256
|
||||
msgid "YACD Secret Key"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:127
|
||||
msgid "You can select Output Network Interface, by default autodetect"
|
||||
msgstr ""
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# RU translations for PODKOP package.
|
||||
# Copyright (C) 2025 THE PODKOP'S COPYRIGHT HOLDER
|
||||
# Copyright (C) 2026 THE PODKOP'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PODKOP package.
|
||||
# divocat, 2025.
|
||||
# divocatt, 2026.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PODKOP\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-23 20:43+0300\n"
|
||||
"PO-Revision-Date: 2025-10-23 20:43+0300\n"
|
||||
"Last-Translator: divocat\n"
|
||||
"POT-Creation-Date: 2026-05-29 16:40+0300\n"
|
||||
"PO-Revision-Date: 2026-05-29 16:40+0300\n"
|
||||
"Last-Translator: divocatt\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -35,6 +35,9 @@ msgstr "Активные соединения"
|
||||
msgid "Additional marking rules found"
|
||||
msgstr "Найдены дополнительные правила маркировки"
|
||||
|
||||
msgid "Allows access to YACD from the WAN. Make sure to open the appropriate port in your firewall."
|
||||
msgstr "Обеспечивает доступ к YACD из WAN. Убедитесь, что в брандмауэре открыт соответствующий порт."
|
||||
|
||||
msgid "Applicable for SOCKS and Shadowsocks proxy"
|
||||
msgstr "Применимо для SOCKS и Shadowsocks прокси"
|
||||
|
||||
@@ -182,9 +185,6 @@ msgstr "Скачивать списки через Proxy/VPN"
|
||||
msgid "Download Lists via specific proxy section"
|
||||
msgstr "Скачивать списки через выбранную секцию"
|
||||
|
||||
msgid "Downloading all lists via main Proxy/VPN"
|
||||
msgstr "Загрузка всех списков через основной прокси/VPN"
|
||||
|
||||
msgid "Downloading all lists via specific Proxy/VPN"
|
||||
msgstr "Загрузка всех списков через указанный прокси/VPN"
|
||||
|
||||
@@ -197,6 +197,9 @@ msgstr "Включить автостарт"
|
||||
msgid "Enable built-in DNS resolver for domains handled by this section"
|
||||
msgstr "Включить встроенный DNS-резолвер для доменов, обрабатываемых в этом разделе"
|
||||
|
||||
msgid "Enable DNS resolve to get real IP when routing"
|
||||
msgstr "Разрешать домены в реальные IP-адреса перед маршрутизацией в outbound"
|
||||
|
||||
msgid "Enable Mixed Proxy"
|
||||
msgstr "Включить смешанный прокси"
|
||||
|
||||
@@ -209,6 +212,9 @@ msgstr "Включить смешанный прокси-сервер, разр
|
||||
msgid "Enable YACD"
|
||||
msgstr "Включить YACD"
|
||||
|
||||
msgid "Enable YACD WAN Access"
|
||||
msgstr "Включить доступ YACD WAN"
|
||||
|
||||
msgid "Enter complete outbound configuration in JSON format"
|
||||
msgstr "Введите полную конфигурацию исходящего соединения в формате JSON"
|
||||
|
||||
@@ -221,6 +227,18 @@ msgstr "Введите доменные имена без протоколов,
|
||||
msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses"
|
||||
msgstr "Введите подсети в нотации CIDR (например, 103.21.244.0/22) или отдельные IP-адреса"
|
||||
|
||||
msgid "Every 1 minute"
|
||||
msgstr "Каждую минуту"
|
||||
|
||||
msgid "Every 3 minutes"
|
||||
msgstr "Каждые 3 минуты"
|
||||
|
||||
msgid "Every 30 seconds"
|
||||
msgstr "Каждые 30 секунд"
|
||||
|
||||
msgid "Every 5 minutes"
|
||||
msgstr "Каждые 5 минут"
|
||||
|
||||
msgid "Exclude NTP"
|
||||
msgstr "Исключить NTP"
|
||||
|
||||
@@ -266,6 +284,45 @@ msgstr "Неверный домен"
|
||||
msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y"
|
||||
msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y"
|
||||
|
||||
msgid "Invalid HY2 URL: insecure must be 0 or 1"
|
||||
msgstr "Неверный URL Hysteria2: параметр insecure должен быть 0 или 1"
|
||||
|
||||
msgid "Invalid HY2 URL: invalid port number"
|
||||
msgstr "Неверный URL Hysteria2: неверный номер порта"
|
||||
|
||||
msgid "Invalid HY2 URL: missing credentials/server"
|
||||
msgstr "Неверный URL Hysteria2: отсутствуют учетные данные/сервер"
|
||||
|
||||
msgid "Invalid HY2 URL: missing host"
|
||||
msgstr "Неверный URL Hysteria2: отсутствует хост"
|
||||
|
||||
msgid "Invalid HY2 URL: missing host & port"
|
||||
msgstr "Неверный URL Hysteria2: отсутствуют хост и порт"
|
||||
|
||||
msgid "Invalid HY2 URL: missing password"
|
||||
msgstr "Неверный URL Hysteria2: отсутствует пароль"
|
||||
|
||||
msgid "Invalid HY2 URL: missing port"
|
||||
msgstr "Неверный URL Hysteria2: отсутствует порт"
|
||||
|
||||
msgid "Invalid HY2 URL: must not contain spaces"
|
||||
msgstr "Неверный URL Hysteria2: не должен содержать пробелов"
|
||||
|
||||
msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://"
|
||||
msgstr "Неверный URL Hysteria2: должен начинаться с hysteria2:// или hy2://"
|
||||
|
||||
msgid "Invalid HY2 URL: obfs-password required when obfs is set"
|
||||
msgstr "Неверный URL Hysteria2: требуется obfs-password, когда установлен obfs"
|
||||
|
||||
msgid "Invalid HY2 URL: parsing failed"
|
||||
msgstr "Неверный URL Hysteria2: ошибка разбора"
|
||||
|
||||
msgid "Invalid HY2 URL: sni cannot be empty"
|
||||
msgstr "Неверный URL Hysteria2: sni не может быть пустым"
|
||||
|
||||
msgid "Invalid HY2 URL: unsupported obfs type"
|
||||
msgstr "Неверный URL Hysteria2: неподдерживаемый тип obfs"
|
||||
|
||||
msgid "Invalid IP address"
|
||||
msgstr "Неверный IP-адрес"
|
||||
|
||||
@@ -365,6 +422,9 @@ msgstr "Локальные списки доменов"
|
||||
msgid "Local Subnet Lists"
|
||||
msgstr "Локальные списки подсетей"
|
||||
|
||||
msgid "Log Level"
|
||||
msgstr "Уровень логов"
|
||||
|
||||
msgid "Main DNS"
|
||||
msgstr "Основной DNS"
|
||||
|
||||
@@ -377,6 +437,9 @@ msgstr "Порт смешанного прокси"
|
||||
msgid "Monitored Interfaces"
|
||||
msgstr "Наблюдаемые интерфейсы"
|
||||
|
||||
msgid "Must be a number in the range of 50 - 1000"
|
||||
msgstr "Должно быть числом от 50 до 1000"
|
||||
|
||||
msgid "Network Interface"
|
||||
msgstr "Сетевой интерфейс"
|
||||
|
||||
@@ -386,6 +449,9 @@ msgstr "Другие правила маркировки не найдены"
|
||||
msgid "Not implement yet"
|
||||
msgstr "Ещё не реализовано"
|
||||
|
||||
msgid "Not responding"
|
||||
msgstr "Не отвечает"
|
||||
|
||||
msgid "Not running"
|
||||
msgstr "Не запущено"
|
||||
|
||||
@@ -398,9 +464,6 @@ msgstr "Конфигурация Outbound"
|
||||
msgid "Outbound Configuration"
|
||||
msgstr "Конфигурация исходящего соединения"
|
||||
|
||||
msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields"
|
||||
msgstr "JSON должен содержать поля \"type\", \"server\" и \"server_port\""
|
||||
|
||||
msgid "Outdated"
|
||||
msgstr "Устаревшая"
|
||||
|
||||
@@ -449,6 +512,9 @@ msgstr "Внешние списки доменов"
|
||||
msgid "Remote Subnet Lists"
|
||||
msgstr "Внешние списки подсетей"
|
||||
|
||||
msgid "Resolve real IP for routing"
|
||||
msgstr "Разрешение реальных IP-адресов"
|
||||
|
||||
msgid "Restart podkop"
|
||||
msgstr "Перезапустить Podkop"
|
||||
|
||||
@@ -485,6 +551,9 @@ msgstr "Запустить диагностику"
|
||||
msgid "Russia inside restrictions"
|
||||
msgstr "Ограничения Russia inside"
|
||||
|
||||
msgid "Secret key for authenticating remote access to YACD when WAN access is enabled."
|
||||
msgstr "Секретный ключ для аутентификации удаленного доступа к YACD при включенном доступе через WAN."
|
||||
|
||||
msgid "Sections"
|
||||
msgstr "Секции"
|
||||
|
||||
@@ -524,6 +593,9 @@ msgstr "Выберите тип списка для добавления пол
|
||||
msgid "Select the list type for adding custom subnets"
|
||||
msgstr "Выберите тип списка для добавления пользовательских подсетей"
|
||||
|
||||
msgid "Select the log level for sing-box"
|
||||
msgstr "Выберите уровень логов для sing-box"
|
||||
|
||||
msgid "Select the network interface from which the traffic will originate"
|
||||
msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик"
|
||||
|
||||
@@ -533,6 +605,12 @@ msgstr "Выберите сетевой интерфейс, на который
|
||||
msgid "Select the WAN interfaces to be monitored"
|
||||
msgstr "Выберите WAN интерфейсы для мониторинга"
|
||||
|
||||
msgid "Selector"
|
||||
msgstr "Selector"
|
||||
|
||||
msgid "Selector Proxy Links"
|
||||
msgstr "Ссылки прокси для Selector"
|
||||
|
||||
msgid "Services info"
|
||||
msgstr "Информация о сервисах"
|
||||
|
||||
@@ -560,8 +638,8 @@ msgstr "Процесс sing-box запущен"
|
||||
msgid "Sing-box service exist"
|
||||
msgstr "Сервис sing-box существует"
|
||||
|
||||
msgid "Sing-box version >= 1.12.4"
|
||||
msgstr "Версия sing-box >= 1.12.4"
|
||||
msgid "Sing-box version is compatible (newer than 1.12.4)"
|
||||
msgstr "Версия Sing-box совместима (новее 1.12.4)"
|
||||
|
||||
msgid "Source Network Interface"
|
||||
msgstr "Сетевой интерфейс источника"
|
||||
@@ -605,12 +683,18 @@ msgstr "Тестирование задержки"
|
||||
msgid "Text List"
|
||||
msgstr "Текстовый список"
|
||||
|
||||
msgid "Text List (comma/space/newline separated)"
|
||||
msgstr "Текстовый список (через запятую, пробел или новую строку)"
|
||||
|
||||
msgid "The DNS server used to look up the IP address of an upstream DNS server"
|
||||
msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера"
|
||||
|
||||
msgid "The interval between connectivity tests"
|
||||
msgstr "Интервал между тестами подключения"
|
||||
|
||||
msgid "The maximum difference in response times (ms) allowed when comparing servers"
|
||||
msgstr "Максимально допустимая разница во времени отклика (мс) при сравнении серверов"
|
||||
|
||||
msgid "The URL used to test server connectivity"
|
||||
msgstr "URL-адрес, используемый для проверки подключения к серверу"
|
||||
|
||||
msgid "Time in seconds for DNS record caching (default: 60)"
|
||||
msgstr "Время в секундах для кэширования DNS записей (по умолчанию: 60)"
|
||||
|
||||
@@ -644,8 +728,8 @@ msgstr "Неизвестная ошибка"
|
||||
msgid "Uplink"
|
||||
msgstr "Исходящий"
|
||||
|
||||
msgid "URL must start with vless://, ss://, trojan://, or socks4/5://"
|
||||
msgstr "URL должен начинаться с vless://, ss://, trojan:// или socks4/5://"
|
||||
msgid "URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://"
|
||||
msgstr "URL должен начинаться с vless://, ss://, trojan://, socks4/5:// или hysteria2:// hy2://"
|
||||
|
||||
msgid "URL must use one of the following protocols:"
|
||||
msgstr "URL должен использовать один из следующих протоколов:"
|
||||
@@ -653,9 +737,18 @@ msgstr "URL должен использовать один из следующи
|
||||
msgid "URLTest"
|
||||
msgstr "URLTest"
|
||||
|
||||
msgid "URLTest Check Interval"
|
||||
msgstr "Интервал проверки URLTest"
|
||||
|
||||
msgid "URLTest Proxy Links"
|
||||
msgstr "Ссылки прокси для URLTest"
|
||||
|
||||
msgid "URLTest Testing URL"
|
||||
msgstr "URLTest ссылка для проверки"
|
||||
|
||||
msgid "URLTest Tolerance"
|
||||
msgstr "URLTest допустимое отклонение"
|
||||
|
||||
msgid "User Domain List Type"
|
||||
msgstr "Тип пользовательского списка доменов"
|
||||
|
||||
@@ -686,11 +779,17 @@ msgstr "Посмотреть логи"
|
||||
msgid "Visit Wiki"
|
||||
msgstr "Перейти в wiki"
|
||||
|
||||
msgid "vless://, ss://, trojan://, socks4/5://, hy2/hysteria2:// links"
|
||||
msgstr ""
|
||||
|
||||
msgid "Warning: %s cannot be used together with %s. Previous selections have been removed."
|
||||
msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены."
|
||||
|
||||
msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection."
|
||||
msgstr "Предупреждение: Russia inside может быть использован только с %s. %s уже есть в Russia inside и будет удален из выбранных."
|
||||
|
||||
msgid "YACD Secret Key"
|
||||
msgstr "Секретный ключ YACD"
|
||||
|
||||
msgid "You can select Output Network Interface, by default autodetect"
|
||||
msgstr "Вы можете выбрать выходной сетевой интерфейс, по умолчанию он определяется автоматически."
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
"test": "vitest",
|
||||
"ci": "yarn format && yarn lint --max-warnings=0 && yarn test --run && yarn build",
|
||||
"watch:sftp": "node watch-upload.js",
|
||||
"locales:exctract-calls": "node extract-calls.js",
|
||||
"locales:extract-calls": "node extract-calls.js",
|
||||
"locales:generate-pot": "node generate-pot.js",
|
||||
"locales:generate-po:ru": "node generate-po.js ru",
|
||||
"locales:distribute": "node distribute-locales.js",
|
||||
"locales:actualize": "yarn locales:exctract-calls && yarn locales:generate-pot && yarn locales:generate-po:ru && yarn locales:distribute"
|
||||
"locales:actualize": "yarn locales:extract-calls && yarn locales:generate-pot && yarn locales:generate-po:ru && yarn locales:distribute"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/parser": "7.28.4",
|
||||
|
||||
@@ -26,6 +26,7 @@ export const ALLOWED_WITH_RUSSIA_INSIDE = [
|
||||
'hetzner',
|
||||
'ovh',
|
||||
'hodca',
|
||||
'roblox',
|
||||
'digitalocean',
|
||||
'cloudfront',
|
||||
];
|
||||
@@ -50,6 +51,7 @@ export const DOMAIN_LIST_OPTIONS = {
|
||||
google_ai: 'Google AI',
|
||||
google_play: 'Google Play',
|
||||
hodca: 'H.O.D.C.A',
|
||||
roblox: 'Roblox',
|
||||
hetzner: 'Hetzner ASN',
|
||||
ovh: 'OVH ASN',
|
||||
digitalocean: 'Digital Ocean ASN',
|
||||
|
||||
3
fe-app-podkop/src/helpers/removeVersionPrefix.ts
Normal file
3
fe-app-podkop/src/helpers/removeVersionPrefix.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function removeVersionPrefix(version: string) {
|
||||
return version.replace(/^v/, '');
|
||||
}
|
||||
@@ -4,6 +4,9 @@
|
||||
'require uci';
|
||||
'require ui';
|
||||
|
||||
if (typeof structuredClone !== 'function')
|
||||
globalThis.structuredClone = (obj) => JSON.parse(JSON.stringify(obj));
|
||||
|
||||
export * from './validators';
|
||||
export * from './helpers';
|
||||
export * from './podkop';
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { getConfigSections } from './getConfigSections';
|
||||
|
||||
export async function getClashApiSecret() {
|
||||
const sections = await getConfigSections();
|
||||
|
||||
const settings = sections.find((section) => section['.type'] === 'settings');
|
||||
|
||||
return settings?.yacd_secret_key || '';
|
||||
}
|
||||
@@ -29,7 +29,9 @@ export async function getDashboardSections(): Promise<IGetDashboardSectionsRespo
|
||||
const data = configSections
|
||||
.filter(
|
||||
(section) =>
|
||||
section.connection_type !== 'block' && section['.type'] !== 'settings',
|
||||
section.connection_type !== 'block' &&
|
||||
section.connection_type !== 'exclusion' &&
|
||||
section['.type'] !== 'settings',
|
||||
)
|
||||
.map((section) => {
|
||||
if (section.connection_type === 'proxy') {
|
||||
@@ -86,6 +88,37 @@ export async function getDashboardSections(): Promise<IGetDashboardSectionsRespo
|
||||
};
|
||||
}
|
||||
|
||||
if (section.proxy_config_type === 'selector') {
|
||||
const selector = proxies.find(
|
||||
(proxy) => proxy.code === `${section['.name']}-out`,
|
||||
);
|
||||
|
||||
const links = section.selector_proxy_links ?? [];
|
||||
|
||||
const outbounds = links
|
||||
.map((link, index) => ({
|
||||
link,
|
||||
outbound: proxies.find(
|
||||
(item) => item.code === `${section['.name']}-${index + 1}-out`,
|
||||
),
|
||||
}))
|
||||
.map((item) => ({
|
||||
code: item?.outbound?.code || '',
|
||||
displayName:
|
||||
getProxyUrlName(item.link) || item?.outbound?.value?.name || '',
|
||||
latency: item?.outbound?.value?.history?.[0]?.delay || 0,
|
||||
type: item?.outbound?.value?.type || '',
|
||||
selected: selector?.value?.now === item?.outbound?.code,
|
||||
}));
|
||||
|
||||
return {
|
||||
withTagSelect: true,
|
||||
code: selector?.code || section['.name'],
|
||||
displayName: section['.name'],
|
||||
outbounds,
|
||||
};
|
||||
}
|
||||
|
||||
if (section.proxy_config_type === 'urltest') {
|
||||
const selector = proxies.find(
|
||||
(proxy) => proxy.code === `${section['.name']}-out`,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { getConfigSections } from './getConfigSections';
|
||||
import { getDashboardSections } from './getDashboardSections';
|
||||
import { getClashApiSecret } from './getClashApiSecret';
|
||||
|
||||
export const CustomPodkopMethods = {
|
||||
getConfigSections,
|
||||
getDashboardSections,
|
||||
getClashApiSecret,
|
||||
};
|
||||
|
||||
@@ -29,15 +29,15 @@ export const PodkopShellMethods = {
|
||||
Podkop.AvailableClashAPIMethods.GET_PROXIES,
|
||||
]),
|
||||
getClashApiProxyLatency: async (tag: string) =>
|
||||
callBaseMethod<unknown>(Podkop.AvailableMethods.CLASH_API, [
|
||||
Podkop.AvailableClashAPIMethods.GET_PROXY_LATENCY,
|
||||
tag,
|
||||
]),
|
||||
callBaseMethod<Podkop.GetClashApiProxyLatency>(
|
||||
Podkop.AvailableMethods.CLASH_API,
|
||||
[Podkop.AvailableClashAPIMethods.GET_PROXY_LATENCY, tag, '5000'],
|
||||
),
|
||||
getClashApiGroupLatency: async (tag: string) =>
|
||||
callBaseMethod<unknown>(Podkop.AvailableMethods.CLASH_API, [
|
||||
Podkop.AvailableClashAPIMethods.GET_GROUP_LATENCY,
|
||||
tag,
|
||||
]),
|
||||
callBaseMethod<Podkop.GetClashApiGroupLatency>(
|
||||
Podkop.AvailableMethods.CLASH_API,
|
||||
[Podkop.AvailableClashAPIMethods.GET_GROUP_LATENCY, tag, '10000'],
|
||||
),
|
||||
setClashApiGroupProxy: async (group: string, proxy: string) =>
|
||||
callBaseMethod<unknown>(Podkop.AvailableMethods.CLASH_API, [
|
||||
Podkop.AvailableClashAPIMethods.SET_GROUP_PROXY,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { CustomPodkopMethods, PodkopShellMethods } from '../../methods';
|
||||
import { logger, socket, store, StoreType } from '../../services';
|
||||
import { renderSections, renderWidget } from './partials';
|
||||
import { fetchServicesInfo } from '../../fetchers';
|
||||
import { getClashApiSecret } from '../../methods/custom/getClashApiSecret';
|
||||
|
||||
// Fetchers
|
||||
|
||||
@@ -38,8 +39,10 @@ async function fetchDashboardSections() {
|
||||
}
|
||||
|
||||
async function connectToClashSockets() {
|
||||
const clashApiSecret = await getClashApiSecret();
|
||||
|
||||
socket.subscribe(
|
||||
`${getClashWsUrl()}/traffic?token=`,
|
||||
`${getClashWsUrl()}/traffic?token=${clashApiSecret}`,
|
||||
(msg) => {
|
||||
const parsedMsg = JSON.parse(msg);
|
||||
|
||||
@@ -68,7 +71,7 @@ async function connectToClashSockets() {
|
||||
);
|
||||
|
||||
socket.subscribe(
|
||||
`${getClashWsUrl()}/connections?token=`,
|
||||
`${getClashWsUrl()}/connections?token=${clashApiSecret}`,
|
||||
(msg) => {
|
||||
const parsedMsg = JSON.parse(msg);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ export enum DIAGNOSTICS_CHECKS {
|
||||
SINGBOX = 'SINGBOX',
|
||||
NFT = 'NFT',
|
||||
FAKEIP = 'FAKEIP',
|
||||
OUTBOUNDS = 'OUTBOUNDS',
|
||||
}
|
||||
|
||||
export const DIAGNOSTICS_CHECKS_MAP: Record<
|
||||
@@ -26,8 +27,13 @@ export const DIAGNOSTICS_CHECKS_MAP: Record<
|
||||
title: getCheckTitle('Nftables'),
|
||||
code: DIAGNOSTICS_CHECKS.NFT,
|
||||
},
|
||||
[DIAGNOSTICS_CHECKS.FAKEIP]: {
|
||||
[DIAGNOSTICS_CHECKS.OUTBOUNDS]: {
|
||||
order: 4,
|
||||
title: getCheckTitle('Outbounds'),
|
||||
code: DIAGNOSTICS_CHECKS.OUTBOUNDS,
|
||||
},
|
||||
[DIAGNOSTICS_CHECKS.FAKEIP]: {
|
||||
order: 5,
|
||||
title: getCheckTitle('FakeIP'),
|
||||
code: DIAGNOSTICS_CHECKS.FAKEIP,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import { DIAGNOSTICS_CHECKS_MAP } from './contstants';
|
||||
import { PodkopShellMethods } from '../../../methods';
|
||||
import { updateCheckStore } from './updateCheckStore';
|
||||
import { getMeta } from '../helpers/getMeta';
|
||||
import { getDashboardSections } from '../../../methods/custom/getDashboardSections';
|
||||
import { IDiagnosticsChecksItem } from '../../../services';
|
||||
|
||||
export async function runSectionsCheck() {
|
||||
const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.OUTBOUNDS;
|
||||
|
||||
updateCheckStore({
|
||||
order,
|
||||
code,
|
||||
title,
|
||||
description: _('Checking, please wait'),
|
||||
state: 'loading',
|
||||
items: [],
|
||||
});
|
||||
|
||||
const sections = await getDashboardSections();
|
||||
|
||||
if (!sections.success) {
|
||||
updateCheckStore({
|
||||
order,
|
||||
code,
|
||||
title,
|
||||
description: _('Cannot receive checks result'),
|
||||
state: 'error',
|
||||
items: [],
|
||||
});
|
||||
|
||||
throw new Error('Sections checks failed');
|
||||
}
|
||||
|
||||
const items = (await Promise.all(
|
||||
sections.data.map(async (section) => {
|
||||
async function getLatency() {
|
||||
if (section.withTagSelect) {
|
||||
const latencyGroup = await PodkopShellMethods.getClashApiGroupLatency(
|
||||
section.code,
|
||||
);
|
||||
|
||||
const selectedOutbound = section.outbounds.find(
|
||||
(item) => item.selected,
|
||||
);
|
||||
|
||||
const isUrlTest = selectedOutbound?.type === 'URLTest';
|
||||
|
||||
const success = latencyGroup.success && !latencyGroup.data.message;
|
||||
|
||||
if (success) {
|
||||
if (isUrlTest) {
|
||||
const latency = Object.values(latencyGroup.data)
|
||||
.map((item) => (item ? `${item}ms` : 'n/a'))
|
||||
.join(' / ');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
latency: `[${_('Fastest')}] ${latency}`,
|
||||
};
|
||||
}
|
||||
|
||||
const selectedProxyDelay =
|
||||
latencyGroup.data?.[selectedOutbound?.code ?? ''];
|
||||
|
||||
if (selectedProxyDelay) {
|
||||
return {
|
||||
success: true,
|
||||
latency: `[${selectedOutbound?.displayName ?? ''}] ${selectedProxyDelay}ms`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
latency: `[${selectedOutbound?.displayName ?? ''}] ${_('Not responding')}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
latency: _('Not responding'),
|
||||
};
|
||||
}
|
||||
|
||||
const latencyProxy = await PodkopShellMethods.getClashApiProxyLatency(
|
||||
section.code,
|
||||
);
|
||||
|
||||
const success = latencyProxy.success && !latencyProxy.data.message;
|
||||
|
||||
if (success) {
|
||||
return {
|
||||
success: true,
|
||||
latency: `${latencyProxy.data.delay} ms`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
latency: _('Not responding'),
|
||||
};
|
||||
}
|
||||
|
||||
const { latency, success } = await getLatency();
|
||||
|
||||
return {
|
||||
state: success ? 'success' : 'error',
|
||||
key: section.displayName,
|
||||
value: latency,
|
||||
};
|
||||
}),
|
||||
)) as Array<IDiagnosticsChecksItem>;
|
||||
|
||||
const allGood = items.every((item) => item.state === 'success');
|
||||
|
||||
const atLeastOneGood = items.some((item) => item.state === 'success');
|
||||
|
||||
const { state, description } = getMeta({ atLeastOneGood, allGood });
|
||||
|
||||
updateCheckStore({
|
||||
order,
|
||||
code,
|
||||
title,
|
||||
description,
|
||||
state,
|
||||
items,
|
||||
});
|
||||
|
||||
if (!atLeastOneGood) {
|
||||
throw new Error('Sections checks failed');
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ export async function runSingBoxCheck() {
|
||||
},
|
||||
{
|
||||
state: data.sing_box_version_ok ? 'success' : 'error',
|
||||
key: _('Sing-box version >= 1.12.4'),
|
||||
key: _('Sing-box version is compatible (newer than 1.12.4)'),
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -72,6 +72,14 @@ export const initialDiagnosticStore: Pick<
|
||||
items: [],
|
||||
state: 'skipped',
|
||||
},
|
||||
{
|
||||
code: DIAGNOSTICS_CHECKS.OUTBOUNDS,
|
||||
title: DIAGNOSTICS_CHECKS_MAP.OUTBOUNDS.title,
|
||||
order: DIAGNOSTICS_CHECKS_MAP.OUTBOUNDS.order,
|
||||
description: _('Not running'),
|
||||
items: [],
|
||||
state: 'skipped',
|
||||
},
|
||||
{
|
||||
code: DIAGNOSTICS_CHECKS.FAKEIP,
|
||||
title: DIAGNOSTICS_CHECKS_MAP.FAKEIP.title,
|
||||
@@ -112,6 +120,14 @@ export const loadingDiagnosticsChecksStore: Pick<
|
||||
items: [],
|
||||
state: 'skipped',
|
||||
},
|
||||
{
|
||||
code: DIAGNOSTICS_CHECKS.OUTBOUNDS,
|
||||
title: DIAGNOSTICS_CHECKS_MAP.OUTBOUNDS.title,
|
||||
order: DIAGNOSTICS_CHECKS_MAP.OUTBOUNDS.order,
|
||||
description: _('Pending'),
|
||||
items: [],
|
||||
state: 'skipped',
|
||||
},
|
||||
{
|
||||
code: DIAGNOSTICS_CHECKS.FAKEIP,
|
||||
title: DIAGNOSTICS_CHECKS_MAP.FAKEIP.title,
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { normalizeCompiledVersion } from '../../../../helpers/normalizeCompiledVersion';
|
||||
import { removeVersionPrefix } from '../../../../helpers/removeVersionPrefix';
|
||||
import type { StoreType } from '../../../services/store.service';
|
||||
import type { IRenderSystemInfoRow } from '../partials';
|
||||
|
||||
function isUnknownVersion(version?: string | null): boolean {
|
||||
return version === 'unknown' || version === _('unknown');
|
||||
}
|
||||
|
||||
export function getPodkopVersionRow(
|
||||
diagnosticsSystemInfo: StoreType['diagnosticsSystemInfo'],
|
||||
): IRenderSystemInfoRow {
|
||||
const loading = diagnosticsSystemInfo.loading;
|
||||
const unknown = isUnknownVersion(diagnosticsSystemInfo.podkop_version);
|
||||
const hasActualVersion =
|
||||
Boolean(diagnosticsSystemInfo.podkop_latest_version) &&
|
||||
!isUnknownVersion(diagnosticsSystemInfo.podkop_latest_version);
|
||||
const version = normalizeCompiledVersion(
|
||||
diagnosticsSystemInfo.podkop_version,
|
||||
);
|
||||
const isDevVersion = version === 'dev';
|
||||
|
||||
if (loading || unknown || !hasActualVersion || isDevVersion) {
|
||||
return {
|
||||
key: 'Podkop',
|
||||
value: version,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
removeVersionPrefix(version) !==
|
||||
removeVersionPrefix(diagnosticsSystemInfo.podkop_latest_version)
|
||||
) {
|
||||
return {
|
||||
key: 'Podkop',
|
||||
value: version,
|
||||
tag: {
|
||||
label: _('Outdated'),
|
||||
kind: 'warning',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
key: 'Podkop',
|
||||
value: version,
|
||||
tag: {
|
||||
label: _('Latest'),
|
||||
kind: 'success',
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import { runFakeIPCheck } from './checks/runFakeIPCheck';
|
||||
import { loadingDiagnosticsChecksStore } from './diagnostic.store';
|
||||
import { logger, store, StoreType } from '../../services';
|
||||
import {
|
||||
IRenderSystemInfoRow,
|
||||
renderAvailableActions,
|
||||
renderCheckSection,
|
||||
renderRunAction,
|
||||
@@ -19,6 +18,8 @@ import { renderModal } from '../../../partials';
|
||||
import { PODKOP_LUCI_APP_VERSION } from '../../../constants';
|
||||
import { showToast } from '../../../helpers/showToast';
|
||||
import { renderWikiDisclaimer } from './partials/renderWikiDisclaimer';
|
||||
import { runSectionsCheck } from './checks/runSectionsCheck';
|
||||
import { getPodkopVersionRow } from './helpers/getPodkopVersionRow';
|
||||
|
||||
async function fetchSystemInfo() {
|
||||
const systemInfo = await PodkopShellMethods.getSystemInfo();
|
||||
@@ -287,7 +288,10 @@ async function handleShowSingBoxConfig() {
|
||||
if (showSingBoxConfig.success) {
|
||||
ui.showModal(
|
||||
_('Show sing-box config'),
|
||||
renderModal(showSingBoxConfig.data as string, 'show_sing_box_config'),
|
||||
renderModal(
|
||||
JSON.stringify(showSingBoxConfig.data, null, 2),
|
||||
'show_sing_box_config',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
logger.error(
|
||||
@@ -411,53 +415,9 @@ function renderDiagnosticSystemInfoWidget() {
|
||||
|
||||
const container = document.getElementById('pdk_diagnostic-page-system-info');
|
||||
|
||||
function getPodkopVersionRow(): IRenderSystemInfoRow {
|
||||
const loading = diagnosticsSystemInfo.loading;
|
||||
const unknown = diagnosticsSystemInfo.podkop_version === _('unknown');
|
||||
const hasActualVersion = Boolean(
|
||||
diagnosticsSystemInfo.podkop_latest_version,
|
||||
);
|
||||
const version = normalizeCompiledVersion(
|
||||
diagnosticsSystemInfo.podkop_version,
|
||||
);
|
||||
const isDevVersion = version === 'dev';
|
||||
|
||||
if (loading || unknown || !hasActualVersion || isDevVersion) {
|
||||
return {
|
||||
key: 'Podkop',
|
||||
value: version,
|
||||
};
|
||||
}
|
||||
|
||||
if (version !== `v${diagnosticsSystemInfo.podkop_latest_version}`) {
|
||||
logger.debug(
|
||||
'[DIAGNOSTIC]',
|
||||
'diagnosticsSystemInfo',
|
||||
diagnosticsSystemInfo,
|
||||
);
|
||||
return {
|
||||
key: 'Podkop',
|
||||
value: version,
|
||||
tag: {
|
||||
label: _('Outdated'),
|
||||
kind: 'warning',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
key: 'Podkop',
|
||||
value: version,
|
||||
tag: {
|
||||
label: _('Latest'),
|
||||
kind: 'success',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const renderedSystemInfo = renderSystemInfo({
|
||||
items: [
|
||||
getPodkopVersionRow(),
|
||||
getPodkopVersionRow(diagnosticsSystemInfo),
|
||||
{
|
||||
key: 'Luci App',
|
||||
value: normalizeCompiledVersion(PODKOP_LUCI_APP_VERSION),
|
||||
@@ -518,6 +478,8 @@ async function runChecks() {
|
||||
|
||||
await runNftCheck();
|
||||
|
||||
await runSectionsCheck();
|
||||
|
||||
await runFakeIPCheck();
|
||||
} catch (e) {
|
||||
logger.error('[DIAGNOSTIC]', 'runChecks - e', e);
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { getPodkopVersionRow } from '../helpers/getPodkopVersionRow';
|
||||
import type { StoreType } from '../../../services/store.service';
|
||||
|
||||
function makeDiagnosticsSystemInfo(
|
||||
patch: Partial<StoreType['diagnosticsSystemInfo']> = {},
|
||||
): StoreType['diagnosticsSystemInfo'] {
|
||||
return {
|
||||
loading: false,
|
||||
podkop_version: '1.2.3',
|
||||
podkop_latest_version: '1.2.3',
|
||||
luci_app_version: '1.0.0',
|
||||
sing_box_version: '1.11.0',
|
||||
openwrt_version: 'OpenWrt 25.12',
|
||||
device_model: 'Test Router',
|
||||
...patch,
|
||||
};
|
||||
}
|
||||
|
||||
describe('getPodkopVersionRow', () => {
|
||||
it('returns Latest when versions differ only by leading v', () => {
|
||||
const row = getPodkopVersionRow(
|
||||
makeDiagnosticsSystemInfo({
|
||||
podkop_version: 'v1.2.3',
|
||||
podkop_latest_version: '1.2.3',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(row).toEqual({
|
||||
key: 'Podkop',
|
||||
value: 'v1.2.3',
|
||||
tag: {
|
||||
label: 'Latest',
|
||||
kind: 'success',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns Outdated when versions differ', () => {
|
||||
const row = getPodkopVersionRow(
|
||||
makeDiagnosticsSystemInfo({
|
||||
podkop_version: '1.2.2',
|
||||
podkop_latest_version: '1.2.3',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(row).toEqual({
|
||||
key: 'Podkop',
|
||||
value: '1.2.2',
|
||||
tag: {
|
||||
label: 'Outdated',
|
||||
kind: 'warning',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns plain row without tag for dev build', () => {
|
||||
const row = getPodkopVersionRow(
|
||||
makeDiagnosticsSystemInfo({
|
||||
podkop_version: 'COMPILED_VERSION',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(row).toEqual({
|
||||
key: 'Podkop',
|
||||
value: 'dev',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -95,6 +95,12 @@ export namespace Podkop {
|
||||
urltest_proxy_links: string[];
|
||||
}
|
||||
|
||||
export interface ConfigProxySelectorSection {
|
||||
connection_type: 'proxy';
|
||||
proxy_config_type: 'selector';
|
||||
selector_proxy_links: string[];
|
||||
}
|
||||
|
||||
export interface ConfigProxyUrlSection {
|
||||
connection_type: 'proxy';
|
||||
proxy_config_type: 'url';
|
||||
@@ -116,16 +122,23 @@ export namespace Podkop {
|
||||
connection_type: 'block';
|
||||
}
|
||||
|
||||
export interface ConfigExclusionSection {
|
||||
connection_type: 'exclusion';
|
||||
}
|
||||
|
||||
export type ConfigBaseSection =
|
||||
| ConfigProxyUrlTestSection
|
||||
| ConfigProxySelectorSection
|
||||
| ConfigProxyUrlSection
|
||||
| ConfigProxyOutboundSection
|
||||
| ConfigVpnSection
|
||||
| ConfigBlockSection;
|
||||
| ConfigBlockSection
|
||||
| ConfigExclusionSection;
|
||||
|
||||
export type ConfigSection = ConfigBaseSection & {
|
||||
'.name': string;
|
||||
'.type': 'settings' | 'section';
|
||||
yacd_secret_key?: string;
|
||||
};
|
||||
|
||||
export interface MethodSuccessResponse<T> {
|
||||
@@ -196,4 +209,11 @@ export namespace Podkop {
|
||||
openwrt_version: string;
|
||||
device_model: string;
|
||||
}
|
||||
|
||||
export interface GetClashApiProxyLatency {
|
||||
delay: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export type GetClashApiGroupLatency = Record<string, number>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { validateHysteria2Url } from '../validateHysteriaUrl.js';
|
||||
|
||||
const validUrls = [
|
||||
// Basic password-only
|
||||
['password basic', 'hysteria2://pass@example.com:443/#hy2-basic'],
|
||||
|
||||
// insecure=1
|
||||
[
|
||||
'insecure allowed',
|
||||
'hysteria2://pass@example.com:443/?insecure=1#hy2-insecure',
|
||||
],
|
||||
|
||||
// SNI
|
||||
['SNI param', 'hysteria2://pass@example.com:443/?sni=google.com#hy2-sni'],
|
||||
|
||||
// Obfuscation
|
||||
[
|
||||
'Obfs + password',
|
||||
'hysteria2://mypassword@1.1.1.1:8443/?obfs=salamander&obfs-password=abc123#hy2-obfs',
|
||||
],
|
||||
|
||||
// All params
|
||||
[
|
||||
'All options combined',
|
||||
'hysteria2://pw@8.8.8.8:8443/?sni=example.com&obfs=salamander&obfs-password=hello&insecure=1#hy2-full',
|
||||
],
|
||||
|
||||
// Explicit obfs=none (valid)
|
||||
['obfs none = ok', 'hysteria2://pw@example.com:443/?obfs=none#hy2-none'],
|
||||
|
||||
[
|
||||
'port range 5000-6000',
|
||||
'hysteria2://letmein@example.com:123,5000-6000/?insecure=1&obfs=salamander&obfs-password=gawrgura&pinSHA256=deadbeef&sni=real.example.com',
|
||||
],
|
||||
];
|
||||
|
||||
const invalidUrls = [
|
||||
['No prefix', 'pw@example.com:443'],
|
||||
['Missing password', 'hysteria2://@example.com:443/'],
|
||||
['Missing host', 'hysteria2://pw@:443/'],
|
||||
['Missing port', 'hysteria2://pw@example.com/'],
|
||||
['Non-numeric port', 'hysteria2://pw@example.com:port/'],
|
||||
['Port out of range', 'hysteria2://pw@example.com:99999/'],
|
||||
|
||||
// Obfuscation errors
|
||||
['Unknown obfs type', 'hysteria2://pw@example.com:443/?obfs=weird'],
|
||||
[
|
||||
'obfs without obfs-password',
|
||||
'hysteria2://pw@example.com:443/?obfs=salamander',
|
||||
],
|
||||
|
||||
// insecure only accepts 0/1
|
||||
['invalid insecure', 'hysteria2://pw@example.com:443/?insecure=5'],
|
||||
|
||||
// SNI empty
|
||||
['empty sni', 'hysteria2://pw@example.com:443/?sni='],
|
||||
];
|
||||
|
||||
describe('validateHysteria2Url', () => {
|
||||
describe.each(validUrls)('Valid HY2 URL: %s', (_desc, url) => {
|
||||
it(`returns valid=true for "${url}"`, () => {
|
||||
const res = validateHysteria2Url(url);
|
||||
expect(res.valid).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each(invalidUrls)('Invalid HY2 URL: %s', (_desc, url) => {
|
||||
it(`returns valid=false for "${url}"`, () => {
|
||||
const res = validateHysteria2Url(url);
|
||||
expect(res.valid).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('detects invalid port range', () => {
|
||||
const res = validateHysteria2Url('hysteria2://pw@example.com:70000/');
|
||||
expect(res.valid).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,7 @@ const invalidUrls = [
|
||||
['Unsupported protocol (ftp)', 'ftp://example.com'],
|
||||
['Unsupported protocol (ws)', 'ws://example.com'],
|
||||
['Empty string', ''],
|
||||
['Without tld', 'https://google'],
|
||||
];
|
||||
|
||||
describe('validateUrl', () => {
|
||||
|
||||
124
fe-app-podkop/src/validators/validateHysteriaUrl.ts
Normal file
124
fe-app-podkop/src/validators/validateHysteriaUrl.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { ValidationResult } from './types';
|
||||
import { parseQueryString } from '../helpers/parseQueryString';
|
||||
|
||||
export function validateHysteria2Url(url: string): ValidationResult {
|
||||
try {
|
||||
const isHY2 = url.startsWith('hysteria2://');
|
||||
const isHY2Short = url.startsWith('hy2://');
|
||||
|
||||
if (!isHY2 && !isHY2Short)
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid HY2 URL: must start with hysteria2:// or hy2://'),
|
||||
};
|
||||
|
||||
if (/\s/.test(url))
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid HY2 URL: must not contain spaces'),
|
||||
};
|
||||
|
||||
const prefix = isHY2 ? 'hysteria2://' : 'hy2://';
|
||||
const body = url.slice(prefix.length);
|
||||
|
||||
const [mainPart] = body.split('#');
|
||||
const [authHostPort, queryString] = mainPart.split('?');
|
||||
|
||||
if (!authHostPort)
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid HY2 URL: missing credentials/server'),
|
||||
};
|
||||
|
||||
const [passwordPart, hostPortPart] = authHostPort.split('@');
|
||||
|
||||
if (!passwordPart)
|
||||
return { valid: false, message: _('Invalid HY2 URL: missing password') };
|
||||
|
||||
if (!hostPortPart)
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid HY2 URL: missing host & port'),
|
||||
};
|
||||
|
||||
const [host, port] = hostPortPart.split(':');
|
||||
|
||||
if (!host)
|
||||
return { valid: false, message: _('Invalid HY2 URL: missing host') };
|
||||
|
||||
if (!port)
|
||||
return { valid: false, message: _('Invalid HY2 URL: missing port') };
|
||||
|
||||
const cleanedPort = port.replace('/', '');
|
||||
const portEntries = cleanedPort.split(',');
|
||||
|
||||
const isValidPortNumber = (value: string) => {
|
||||
if (!/^\d+$/.test(value)) return false;
|
||||
const num = Number(value);
|
||||
return num >= 1 && num <= 65535;
|
||||
};
|
||||
|
||||
const isValidPortEntry = (entry: string) => {
|
||||
if (!entry) return false;
|
||||
if (!entry.includes('-')) return isValidPortNumber(entry);
|
||||
|
||||
const rangeParts = entry.split('-');
|
||||
if (rangeParts.length !== 2) return false;
|
||||
|
||||
const [start, end] = rangeParts;
|
||||
if (!isValidPortNumber(start) || !isValidPortNumber(end)) return false;
|
||||
|
||||
return Number(start) <= Number(end);
|
||||
};
|
||||
|
||||
if (!portEntries.every(isValidPortEntry))
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid HY2 URL: invalid port number'),
|
||||
};
|
||||
|
||||
if (queryString) {
|
||||
const params = parseQueryString(queryString);
|
||||
const paramsKeys = Object.keys(params);
|
||||
|
||||
if (
|
||||
paramsKeys.includes('insecure') &&
|
||||
!['0', '1'].includes(params.insecure)
|
||||
)
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid HY2 URL: insecure must be 0 or 1'),
|
||||
};
|
||||
|
||||
const validObfsTypes = ['none', 'salamander'];
|
||||
|
||||
if (paramsKeys.includes('obfs') && !validObfsTypes.includes(params.obfs))
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid HY2 URL: unsupported obfs type'),
|
||||
};
|
||||
|
||||
if (
|
||||
paramsKeys.includes('obfs') &&
|
||||
params.obfs !== 'none' &&
|
||||
!params['obfs-password']
|
||||
)
|
||||
return {
|
||||
valid: false,
|
||||
message: _(
|
||||
'Invalid HY2 URL: obfs-password required when obfs is set',
|
||||
),
|
||||
};
|
||||
|
||||
if (paramsKeys.includes('sni') && !params.sni)
|
||||
return {
|
||||
valid: false,
|
||||
message: _('Invalid HY2 URL: sni cannot be empty'),
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true, message: _('Valid') };
|
||||
} catch (_e) {
|
||||
return { valid: false, message: _('Invalid HY2 URL: parsing failed') };
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,8 @@
|
||||
import { ValidationResult } from './types';
|
||||
|
||||
// TODO refactor current validation and add tests
|
||||
export function validateOutboundJson(value: string): ValidationResult {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
|
||||
if (!parsed.type || !parsed.server || !parsed.server_port) {
|
||||
return {
|
||||
valid: false,
|
||||
message: _(
|
||||
'Outbound JSON must contain at least "type", "server" and "server_port" fields',
|
||||
),
|
||||
};
|
||||
}
|
||||
JSON.parse(value);
|
||||
|
||||
return { valid: true, message: _('Valid') };
|
||||
} catch {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { validateShadowsocksUrl } from './validateShadowsocksUrl';
|
||||
import { validateVlessUrl } from './validateVlessUrl';
|
||||
import { validateTrojanUrl } from './validateTrojanUrl';
|
||||
import { validateSocksUrl } from './validateSocksUrl';
|
||||
import { validateHysteria2Url } from './validateHysteriaUrl';
|
||||
|
||||
// TODO refactor current validation and add tests
|
||||
export function validateProxyUrl(url: string): ValidationResult {
|
||||
@@ -24,10 +25,17 @@ export function validateProxyUrl(url: string): ValidationResult {
|
||||
return validateSocksUrl(trimmedUrl);
|
||||
}
|
||||
|
||||
if (
|
||||
trimmedUrl.startsWith('hysteria2://') ||
|
||||
trimmedUrl.startsWith('hy2://')
|
||||
) {
|
||||
return validateHysteria2Url(trimmedUrl);
|
||||
}
|
||||
|
||||
return {
|
||||
valid: false,
|
||||
message: _(
|
||||
'URL must start with vless://, ss://, trojan://, or socks4/5://',
|
||||
'URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,19 +2,31 @@ import { ValidationResult } from './types';
|
||||
|
||||
export function validateUrl(
|
||||
url: string,
|
||||
protocols: string[] = ['http:', 'https:'],
|
||||
protocols = ['http:', 'https:'],
|
||||
): ValidationResult {
|
||||
try {
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
if (!protocols.includes(parsedUrl.protocol)) {
|
||||
return {
|
||||
valid: false,
|
||||
message: `${_('URL must use one of the following protocols:')} ${protocols.join(', ')}`,
|
||||
};
|
||||
}
|
||||
return { valid: true, message: _('Valid') };
|
||||
} catch (_e) {
|
||||
if (!url.length) {
|
||||
return { valid: false, message: _('Invalid URL format') };
|
||||
}
|
||||
|
||||
const hasValidProtocol = protocols.some((p) => url.indexOf(p + '//') === 0);
|
||||
|
||||
if (!hasValidProtocol)
|
||||
return {
|
||||
valid: false,
|
||||
message:
|
||||
_('URL must use one of the following protocols:') +
|
||||
' ' +
|
||||
protocols.join(', '),
|
||||
};
|
||||
|
||||
const regex = new RegExp(
|
||||
`^(?:${protocols.map((p) => p.replace(':', '')).join('|')})://` +
|
||||
`(?:[A-Za-z0-9-]+\\.)+[A-Za-z]{2,}(?::\\d+)?(?:/[^\\s]*)?$`,
|
||||
);
|
||||
|
||||
if (regex.test(url)) {
|
||||
return { valid: true, message: _('Valid') };
|
||||
}
|
||||
|
||||
return { valid: false, message: _('Invalid URL format') };
|
||||
}
|
||||
|
||||
20
install.sh
20
install.sh
@@ -109,16 +109,16 @@ main() {
|
||||
pkg_list_update || { echo "Packages list update failed"; exit 1; }
|
||||
|
||||
if [ -f "/etc/init.d/podkop" ]; then
|
||||
msg "Podkop is already installed. Upgraded..."
|
||||
msg "Podkop is already installed. Upgrading..."
|
||||
else
|
||||
msg "Installed podkop..."
|
||||
msg "Installing podkop..."
|
||||
fi
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
check_response=$(curl -s "https://api.github.com/repos/itdoginfo/podkop/releases/latest")
|
||||
|
||||
if echo "$check_response" | grep -q 'API rate limit '; then
|
||||
msg "You've reached rate limit from GitHub. Repeat in five minutes."
|
||||
msg "You've reached the GitHub rate limit. Repeat in five minutes."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
@@ -143,7 +143,7 @@ main() {
|
||||
break
|
||||
fi
|
||||
fi
|
||||
msg "Download error $filename. Retry..."
|
||||
msg "Download error for $filename. Retrying..."
|
||||
rm -f "$filepath"
|
||||
attempt=$((attempt+1))
|
||||
done
|
||||
@@ -168,7 +168,7 @@ main() {
|
||||
fi
|
||||
done
|
||||
if [ -n "$file" ]; then
|
||||
msg "Installing $file"
|
||||
msg "Installing $file..."
|
||||
pkg_install "$DOWNLOAD_DIR/$file"
|
||||
sleep 3
|
||||
fi
|
||||
@@ -183,11 +183,11 @@ main() {
|
||||
done
|
||||
if [ -n "$ru" ]; then
|
||||
if pkg_is_installed luci-i18n-podkop-ru; then
|
||||
msg "Upgraded ru translation..."
|
||||
msg "Upgrading Russian translation..."
|
||||
pkg_remove luci-i18n-podkop*
|
||||
pkg_install "$DOWNLOAD_DIR/$ru"
|
||||
else
|
||||
msg "Русский язык интерфейса ставим? y/n (Need a Russian translation?)"
|
||||
msg "Русский язык интерфейса ставим? y/n (Install the Russian interface language?)"
|
||||
while true; do
|
||||
read -r -p '' RUS
|
||||
case $RUS in
|
||||
@@ -236,7 +236,7 @@ check_system() {
|
||||
fi
|
||||
|
||||
if ! nslookup google.com >/dev/null 2>&1; then
|
||||
msg "DNS not working"
|
||||
msg "DNS is not working."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -270,7 +270,7 @@ check_system() {
|
||||
fi
|
||||
|
||||
if pkg_is_installed https-dns-proxy; then
|
||||
msg "Сonflicting package detected: https-dns-proxy. Remove?"
|
||||
msg "Conflicting package detected: https-dns-proxy. Remove?"
|
||||
|
||||
while true; do
|
||||
read -r -p '' DNSPROXY
|
||||
@@ -300,7 +300,7 @@ sing_box() {
|
||||
required_version="1.12.4"
|
||||
|
||||
if [ "$(printf '%s\n%s\n' "$sing_box_version" "$required_version" | sort -V | head -n 1)" != "$required_version" ]; then
|
||||
msg "sing-box version $sing_box_version is older than required $required_version"
|
||||
msg "sing-box version $sing_box_version is older than the required version $required_version."
|
||||
msg "Removing old version..."
|
||||
service podkop stop
|
||||
pkg_remove sing-box
|
||||
|
||||
@@ -58,18 +58,22 @@ function validateDNS(value) {
|
||||
|
||||
// src/validators/validateUrl.ts
|
||||
function validateUrl(url, protocols = ["http:", "https:"]) {
|
||||
try {
|
||||
const parsedUrl = new URL(url);
|
||||
if (!protocols.includes(parsedUrl.protocol)) {
|
||||
return {
|
||||
valid: false,
|
||||
message: `${_("URL must use one of the following protocols:")} ${protocols.join(", ")}`
|
||||
};
|
||||
}
|
||||
return { valid: true, message: _("Valid") };
|
||||
} catch (_e) {
|
||||
if (!url.length) {
|
||||
return { valid: false, message: _("Invalid URL format") };
|
||||
}
|
||||
const hasValidProtocol = protocols.some((p) => url.indexOf(p + "//") === 0);
|
||||
if (!hasValidProtocol)
|
||||
return {
|
||||
valid: false,
|
||||
message: _("URL must use one of the following protocols:") + " " + protocols.join(", ")
|
||||
};
|
||||
const regex = new RegExp(
|
||||
`^(?:${protocols.map((p) => p.replace(":", "")).join("|")})://(?:[A-Za-z0-9-]+\\.)+[A-Za-z]{2,}(?::\\d+)?(?:/[^\\s]*)?$`
|
||||
);
|
||||
if (regex.test(url)) {
|
||||
return { valid: true, message: _("Valid") };
|
||||
}
|
||||
return { valid: false, message: _("Invalid URL format") };
|
||||
}
|
||||
|
||||
// src/validators/validatePath.ts
|
||||
@@ -323,15 +327,7 @@ function validateVlessUrl(url) {
|
||||
// src/validators/validateOutboundJson.ts
|
||||
function validateOutboundJson(value) {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (!parsed.type || !parsed.server || !parsed.server_port) {
|
||||
return {
|
||||
valid: false,
|
||||
message: _(
|
||||
'Outbound JSON must contain at least "type", "server" and "server_port" fields'
|
||||
)
|
||||
};
|
||||
}
|
||||
JSON.parse(value);
|
||||
return { valid: true, message: _("Valid") };
|
||||
} catch {
|
||||
return { valid: false, message: _("Invalid JSON format") };
|
||||
@@ -452,6 +448,97 @@ function validateSocksUrl(url) {
|
||||
return { valid: true, message: _("Valid") };
|
||||
}
|
||||
|
||||
// src/validators/validateHysteriaUrl.ts
|
||||
function validateHysteria2Url(url) {
|
||||
try {
|
||||
const isHY2 = url.startsWith("hysteria2://");
|
||||
const isHY2Short = url.startsWith("hy2://");
|
||||
if (!isHY2 && !isHY2Short)
|
||||
return {
|
||||
valid: false,
|
||||
message: _("Invalid HY2 URL: must start with hysteria2:// or hy2://")
|
||||
};
|
||||
if (/\s/.test(url))
|
||||
return {
|
||||
valid: false,
|
||||
message: _("Invalid HY2 URL: must not contain spaces")
|
||||
};
|
||||
const prefix = isHY2 ? "hysteria2://" : "hy2://";
|
||||
const body = url.slice(prefix.length);
|
||||
const [mainPart] = body.split("#");
|
||||
const [authHostPort, queryString] = mainPart.split("?");
|
||||
if (!authHostPort)
|
||||
return {
|
||||
valid: false,
|
||||
message: _("Invalid HY2 URL: missing credentials/server")
|
||||
};
|
||||
const [passwordPart, hostPortPart] = authHostPort.split("@");
|
||||
if (!passwordPart)
|
||||
return { valid: false, message: _("Invalid HY2 URL: missing password") };
|
||||
if (!hostPortPart)
|
||||
return {
|
||||
valid: false,
|
||||
message: _("Invalid HY2 URL: missing host & port")
|
||||
};
|
||||
const [host, port] = hostPortPart.split(":");
|
||||
if (!host)
|
||||
return { valid: false, message: _("Invalid HY2 URL: missing host") };
|
||||
if (!port)
|
||||
return { valid: false, message: _("Invalid HY2 URL: missing port") };
|
||||
const cleanedPort = port.replace("/", "");
|
||||
const portEntries = cleanedPort.split(",");
|
||||
const isValidPortNumber = (value) => {
|
||||
if (!/^\d+$/.test(value)) return false;
|
||||
const num = Number(value);
|
||||
return num >= 1 && num <= 65535;
|
||||
};
|
||||
const isValidPortEntry = (entry) => {
|
||||
if (!entry) return false;
|
||||
if (!entry.includes("-")) return isValidPortNumber(entry);
|
||||
const rangeParts = entry.split("-");
|
||||
if (rangeParts.length !== 2) return false;
|
||||
const [start, end] = rangeParts;
|
||||
if (!isValidPortNumber(start) || !isValidPortNumber(end)) return false;
|
||||
return Number(start) <= Number(end);
|
||||
};
|
||||
if (!portEntries.every(isValidPortEntry))
|
||||
return {
|
||||
valid: false,
|
||||
message: _("Invalid HY2 URL: invalid port number")
|
||||
};
|
||||
if (queryString) {
|
||||
const params = parseQueryString(queryString);
|
||||
const paramsKeys = Object.keys(params);
|
||||
if (paramsKeys.includes("insecure") && !["0", "1"].includes(params.insecure))
|
||||
return {
|
||||
valid: false,
|
||||
message: _("Invalid HY2 URL: insecure must be 0 or 1")
|
||||
};
|
||||
const validObfsTypes = ["none", "salamander"];
|
||||
if (paramsKeys.includes("obfs") && !validObfsTypes.includes(params.obfs))
|
||||
return {
|
||||
valid: false,
|
||||
message: _("Invalid HY2 URL: unsupported obfs type")
|
||||
};
|
||||
if (paramsKeys.includes("obfs") && params.obfs !== "none" && !params["obfs-password"])
|
||||
return {
|
||||
valid: false,
|
||||
message: _(
|
||||
"Invalid HY2 URL: obfs-password required when obfs is set"
|
||||
)
|
||||
};
|
||||
if (paramsKeys.includes("sni") && !params.sni)
|
||||
return {
|
||||
valid: false,
|
||||
message: _("Invalid HY2 URL: sni cannot be empty")
|
||||
};
|
||||
}
|
||||
return { valid: true, message: _("Valid") };
|
||||
} catch (_e) {
|
||||
return { valid: false, message: _("Invalid HY2 URL: parsing failed") };
|
||||
}
|
||||
}
|
||||
|
||||
// src/validators/validateProxyUrl.ts
|
||||
function validateProxyUrl(url) {
|
||||
const trimmedUrl = url.trim();
|
||||
@@ -467,10 +554,13 @@ function validateProxyUrl(url) {
|
||||
if (/^socks(4|4a|5):\/\//.test(trimmedUrl)) {
|
||||
return validateSocksUrl(trimmedUrl);
|
||||
}
|
||||
if (trimmedUrl.startsWith("hysteria2://") || trimmedUrl.startsWith("hy2://")) {
|
||||
return validateHysteria2Url(trimmedUrl);
|
||||
}
|
||||
return {
|
||||
valid: false,
|
||||
message: _(
|
||||
"URL must start with vless://, ss://, trojan://, or socks4/5://"
|
||||
"URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://"
|
||||
)
|
||||
};
|
||||
}
|
||||
@@ -563,14 +653,14 @@ var PodkopShellMethods = {
|
||||
getClashApiProxies: async () => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [
|
||||
Podkop.AvailableClashAPIMethods.GET_PROXIES
|
||||
]),
|
||||
getClashApiProxyLatency: async (tag) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [
|
||||
Podkop.AvailableClashAPIMethods.GET_PROXY_LATENCY,
|
||||
tag
|
||||
]),
|
||||
getClashApiGroupLatency: async (tag) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [
|
||||
Podkop.AvailableClashAPIMethods.GET_GROUP_LATENCY,
|
||||
tag
|
||||
]),
|
||||
getClashApiProxyLatency: async (tag) => callBaseMethod(
|
||||
Podkop.AvailableMethods.CLASH_API,
|
||||
[Podkop.AvailableClashAPIMethods.GET_PROXY_LATENCY, tag, "5000"]
|
||||
),
|
||||
getClashApiGroupLatency: async (tag) => callBaseMethod(
|
||||
Podkop.AvailableMethods.CLASH_API,
|
||||
[Podkop.AvailableClashAPIMethods.GET_GROUP_LATENCY, tag, "10000"]
|
||||
),
|
||||
setClashApiGroupProxy: async (group, proxy) => callBaseMethod(Podkop.AvailableMethods.CLASH_API, [
|
||||
Podkop.AvailableClashAPIMethods.SET_GROUP_PROXY,
|
||||
group,
|
||||
@@ -626,7 +716,7 @@ async function getDashboardSections() {
|
||||
})
|
||||
);
|
||||
const data = configSections.filter(
|
||||
(section) => section.connection_type !== "block" && section[".type"] !== "settings"
|
||||
(section) => section.connection_type !== "block" && section.connection_type !== "exclusion" && section[".type"] !== "settings"
|
||||
).map((section) => {
|
||||
if (section.connection_type === "proxy") {
|
||||
if (section.proxy_config_type === "url") {
|
||||
@@ -672,6 +762,30 @@ async function getDashboardSections() {
|
||||
]
|
||||
};
|
||||
}
|
||||
if (section.proxy_config_type === "selector") {
|
||||
const selector = proxies.find(
|
||||
(proxy) => proxy.code === `${section[".name"]}-out`
|
||||
);
|
||||
const links = section.selector_proxy_links ?? [];
|
||||
const outbounds = links.map((link, index) => ({
|
||||
link,
|
||||
outbound: proxies.find(
|
||||
(item) => item.code === `${section[".name"]}-${index + 1}-out`
|
||||
)
|
||||
})).map((item) => ({
|
||||
code: item?.outbound?.code || "",
|
||||
displayName: getProxyUrlName(item.link) || item?.outbound?.value?.name || "",
|
||||
latency: item?.outbound?.value?.history?.[0]?.delay || 0,
|
||||
type: item?.outbound?.value?.type || "",
|
||||
selected: selector?.value?.now === item?.outbound?.code
|
||||
}));
|
||||
return {
|
||||
withTagSelect: true,
|
||||
code: selector?.code || section[".name"],
|
||||
displayName: section[".name"],
|
||||
outbounds
|
||||
};
|
||||
}
|
||||
if (section.proxy_config_type === "urltest") {
|
||||
const selector = proxies.find(
|
||||
(proxy) => proxy.code === `${section[".name"]}-out`
|
||||
@@ -735,10 +849,18 @@ async function getDashboardSections() {
|
||||
};
|
||||
}
|
||||
|
||||
// src/podkop/methods/custom/getClashApiSecret.ts
|
||||
async function getClashApiSecret() {
|
||||
const sections = await getConfigSections();
|
||||
const settings = sections.find((section) => section[".type"] === "settings");
|
||||
return settings?.yacd_secret_key || "";
|
||||
}
|
||||
|
||||
// src/podkop/methods/custom/index.ts
|
||||
var CustomPodkopMethods = {
|
||||
getConfigSections,
|
||||
getDashboardSections
|
||||
getDashboardSections,
|
||||
getClashApiSecret
|
||||
};
|
||||
|
||||
// src/constants.ts
|
||||
@@ -767,6 +889,7 @@ var ALLOWED_WITH_RUSSIA_INSIDE = [
|
||||
"hetzner",
|
||||
"ovh",
|
||||
"hodca",
|
||||
"roblox",
|
||||
"digitalocean",
|
||||
"cloudfront"
|
||||
];
|
||||
@@ -790,6 +913,7 @@ var DOMAIN_LIST_OPTIONS = {
|
||||
google_ai: "Google AI",
|
||||
google_play: "Google Play",
|
||||
hodca: "H.O.D.C.A",
|
||||
roblox: "Roblox",
|
||||
hetzner: "Hetzner ASN",
|
||||
ovh: "OVH ASN",
|
||||
digitalocean: "Digital Ocean ASN",
|
||||
@@ -1004,8 +1128,13 @@ var DIAGNOSTICS_CHECKS_MAP = {
|
||||
title: getCheckTitle("Nftables"),
|
||||
code: "NFT" /* NFT */
|
||||
},
|
||||
["FAKEIP" /* FAKEIP */]: {
|
||||
["OUTBOUNDS" /* OUTBOUNDS */]: {
|
||||
order: 4,
|
||||
title: getCheckTitle("Outbounds"),
|
||||
code: "OUTBOUNDS" /* OUTBOUNDS */
|
||||
},
|
||||
["FAKEIP" /* FAKEIP */]: {
|
||||
order: 5,
|
||||
title: getCheckTitle("FakeIP"),
|
||||
code: "FAKEIP" /* FAKEIP */
|
||||
}
|
||||
@@ -1074,6 +1203,14 @@ var initialDiagnosticStore = {
|
||||
items: [],
|
||||
state: "skipped"
|
||||
},
|
||||
{
|
||||
code: "OUTBOUNDS" /* OUTBOUNDS */,
|
||||
title: DIAGNOSTICS_CHECKS_MAP.OUTBOUNDS.title,
|
||||
order: DIAGNOSTICS_CHECKS_MAP.OUTBOUNDS.order,
|
||||
description: _("Not running"),
|
||||
items: [],
|
||||
state: "skipped"
|
||||
},
|
||||
{
|
||||
code: "FAKEIP" /* FAKEIP */,
|
||||
title: DIAGNOSTICS_CHECKS_MAP.FAKEIP.title,
|
||||
@@ -1110,6 +1247,14 @@ var loadingDiagnosticsChecksStore = {
|
||||
items: [],
|
||||
state: "skipped"
|
||||
},
|
||||
{
|
||||
code: "OUTBOUNDS" /* OUTBOUNDS */,
|
||||
title: DIAGNOSTICS_CHECKS_MAP.OUTBOUNDS.title,
|
||||
order: DIAGNOSTICS_CHECKS_MAP.OUTBOUNDS.order,
|
||||
description: _("Pending"),
|
||||
items: [],
|
||||
state: "skipped"
|
||||
},
|
||||
{
|
||||
code: "FAKEIP" /* FAKEIP */,
|
||||
title: DIAGNOSTICS_CHECKS_MAP.FAKEIP.title,
|
||||
@@ -1859,8 +2004,9 @@ async function fetchDashboardSections() {
|
||||
});
|
||||
}
|
||||
async function connectToClashSockets() {
|
||||
const clashApiSecret = await getClashApiSecret();
|
||||
socket.subscribe(
|
||||
`${getClashWsUrl()}/traffic?token=`,
|
||||
`${getClashWsUrl()}/traffic?token=${clashApiSecret}`,
|
||||
(msg) => {
|
||||
const parsedMsg = JSON.parse(msg);
|
||||
store.set({
|
||||
@@ -1887,7 +2033,7 @@ async function connectToClashSockets() {
|
||||
}
|
||||
);
|
||||
socket.subscribe(
|
||||
`${getClashWsUrl()}/connections?token=`,
|
||||
`${getClashWsUrl()}/connections?token=${clashApiSecret}`,
|
||||
(msg) => {
|
||||
const parsedMsg = JSON.parse(msg);
|
||||
store.set({
|
||||
@@ -2492,7 +2638,7 @@ async function runSingBoxCheck() {
|
||||
},
|
||||
{
|
||||
state: data.sing_box_version_ok ? "success" : "error",
|
||||
key: _("Sing-box version >= 1.12.4"),
|
||||
key: _("Sing-box version is compatible (newer than 1.12.4)"),
|
||||
value: ""
|
||||
},
|
||||
{
|
||||
@@ -3685,6 +3831,148 @@ function renderWikiDisclaimer(kind) {
|
||||
]);
|
||||
}
|
||||
|
||||
// src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts
|
||||
async function runSectionsCheck() {
|
||||
const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.OUTBOUNDS;
|
||||
updateCheckStore({
|
||||
order,
|
||||
code,
|
||||
title,
|
||||
description: _("Checking, please wait"),
|
||||
state: "loading",
|
||||
items: []
|
||||
});
|
||||
const sections = await getDashboardSections();
|
||||
if (!sections.success) {
|
||||
updateCheckStore({
|
||||
order,
|
||||
code,
|
||||
title,
|
||||
description: _("Cannot receive checks result"),
|
||||
state: "error",
|
||||
items: []
|
||||
});
|
||||
throw new Error("Sections checks failed");
|
||||
}
|
||||
const items = await Promise.all(
|
||||
sections.data.map(async (section) => {
|
||||
async function getLatency() {
|
||||
if (section.withTagSelect) {
|
||||
const latencyGroup = await PodkopShellMethods.getClashApiGroupLatency(
|
||||
section.code
|
||||
);
|
||||
const selectedOutbound = section.outbounds.find(
|
||||
(item) => item.selected
|
||||
);
|
||||
const isUrlTest = selectedOutbound?.type === "URLTest";
|
||||
const success3 = latencyGroup.success && !latencyGroup.data.message;
|
||||
if (success3) {
|
||||
if (isUrlTest) {
|
||||
const latency2 = Object.values(latencyGroup.data).map((item) => item ? `${item}ms` : "n/a").join(" / ");
|
||||
return {
|
||||
success: true,
|
||||
latency: `[${_("Fastest")}] ${latency2}`
|
||||
};
|
||||
}
|
||||
const selectedProxyDelay = latencyGroup.data?.[selectedOutbound?.code ?? ""];
|
||||
if (selectedProxyDelay) {
|
||||
return {
|
||||
success: true,
|
||||
latency: `[${selectedOutbound?.displayName ?? ""}] ${selectedProxyDelay}ms`
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
latency: `[${selectedOutbound?.displayName ?? ""}] ${_("Not responding")}`
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
latency: _("Not responding")
|
||||
};
|
||||
}
|
||||
const latencyProxy = await PodkopShellMethods.getClashApiProxyLatency(
|
||||
section.code
|
||||
);
|
||||
const success2 = latencyProxy.success && !latencyProxy.data.message;
|
||||
if (success2) {
|
||||
return {
|
||||
success: true,
|
||||
latency: `${latencyProxy.data.delay} ms`
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
latency: _("Not responding")
|
||||
};
|
||||
}
|
||||
const { latency, success } = await getLatency();
|
||||
return {
|
||||
state: success ? "success" : "error",
|
||||
key: section.displayName,
|
||||
value: latency
|
||||
};
|
||||
})
|
||||
);
|
||||
const allGood = items.every((item) => item.state === "success");
|
||||
const atLeastOneGood = items.some((item) => item.state === "success");
|
||||
const { state, description } = getMeta({ atLeastOneGood, allGood });
|
||||
updateCheckStore({
|
||||
order,
|
||||
code,
|
||||
title,
|
||||
description,
|
||||
state,
|
||||
items
|
||||
});
|
||||
if (!atLeastOneGood) {
|
||||
throw new Error("Sections checks failed");
|
||||
}
|
||||
}
|
||||
|
||||
// src/helpers/removeVersionPrefix.ts
|
||||
function removeVersionPrefix(version) {
|
||||
return version.replace(/^v/, "");
|
||||
}
|
||||
|
||||
// src/podkop/tabs/diagnostic/helpers/getPodkopVersionRow.ts
|
||||
function isUnknownVersion(version) {
|
||||
return version === "unknown" || version === _("unknown");
|
||||
}
|
||||
function getPodkopVersionRow(diagnosticsSystemInfo) {
|
||||
const loading = diagnosticsSystemInfo.loading;
|
||||
const unknown = isUnknownVersion(diagnosticsSystemInfo.podkop_version);
|
||||
const hasActualVersion = Boolean(diagnosticsSystemInfo.podkop_latest_version) && !isUnknownVersion(diagnosticsSystemInfo.podkop_latest_version);
|
||||
const version = normalizeCompiledVersion(
|
||||
diagnosticsSystemInfo.podkop_version
|
||||
);
|
||||
const isDevVersion = version === "dev";
|
||||
if (loading || unknown || !hasActualVersion || isDevVersion) {
|
||||
return {
|
||||
key: "Podkop",
|
||||
value: version
|
||||
};
|
||||
}
|
||||
if (removeVersionPrefix(version) !== removeVersionPrefix(diagnosticsSystemInfo.podkop_latest_version)) {
|
||||
return {
|
||||
key: "Podkop",
|
||||
value: version,
|
||||
tag: {
|
||||
label: _("Outdated"),
|
||||
kind: "warning"
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
key: "Podkop",
|
||||
value: version,
|
||||
tag: {
|
||||
label: _("Latest"),
|
||||
kind: "success"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// src/podkop/tabs/diagnostic/initController.ts
|
||||
async function fetchSystemInfo() {
|
||||
const systemInfo = await PodkopShellMethods.getSystemInfo();
|
||||
@@ -3924,7 +4212,10 @@ async function handleShowSingBoxConfig() {
|
||||
if (showSingBoxConfig.success) {
|
||||
ui.showModal(
|
||||
_("Show sing-box config"),
|
||||
renderModal(showSingBoxConfig.data, "show_sing_box_config")
|
||||
renderModal(
|
||||
JSON.stringify(showSingBoxConfig.data, null, 2),
|
||||
"show_sing_box_config"
|
||||
)
|
||||
);
|
||||
} else {
|
||||
logger.error(
|
||||
@@ -4029,49 +4320,9 @@ function renderDiagnosticSystemInfoWidget() {
|
||||
logger.debug("[DIAGNOSTIC]", "renderDiagnosticSystemInfoWidget");
|
||||
const diagnosticsSystemInfo = store.get().diagnosticsSystemInfo;
|
||||
const container = document.getElementById("pdk_diagnostic-page-system-info");
|
||||
function getPodkopVersionRow() {
|
||||
const loading = diagnosticsSystemInfo.loading;
|
||||
const unknown = diagnosticsSystemInfo.podkop_version === _("unknown");
|
||||
const hasActualVersion = Boolean(
|
||||
diagnosticsSystemInfo.podkop_latest_version
|
||||
);
|
||||
const version = normalizeCompiledVersion(
|
||||
diagnosticsSystemInfo.podkop_version
|
||||
);
|
||||
const isDevVersion = version === "dev";
|
||||
if (loading || unknown || !hasActualVersion || isDevVersion) {
|
||||
return {
|
||||
key: "Podkop",
|
||||
value: version
|
||||
};
|
||||
}
|
||||
if (version !== `v${diagnosticsSystemInfo.podkop_latest_version}`) {
|
||||
logger.debug(
|
||||
"[DIAGNOSTIC]",
|
||||
"diagnosticsSystemInfo",
|
||||
diagnosticsSystemInfo
|
||||
);
|
||||
return {
|
||||
key: "Podkop",
|
||||
value: version,
|
||||
tag: {
|
||||
label: _("Outdated"),
|
||||
kind: "warning"
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
key: "Podkop",
|
||||
value: version,
|
||||
tag: {
|
||||
label: _("Latest"),
|
||||
kind: "success"
|
||||
}
|
||||
};
|
||||
}
|
||||
const renderedSystemInfo = renderSystemInfo({
|
||||
items: [
|
||||
getPodkopVersionRow(),
|
||||
getPodkopVersionRow(diagnosticsSystemInfo),
|
||||
{
|
||||
key: "Luci App",
|
||||
value: normalizeCompiledVersion(PODKOP_LUCI_APP_VERSION)
|
||||
@@ -4118,6 +4369,7 @@ async function runChecks() {
|
||||
await runDnsCheck();
|
||||
await runSingBoxCheck();
|
||||
await runNftCheck();
|
||||
await runSectionsCheck();
|
||||
await runFakeIPCheck();
|
||||
} catch (e) {
|
||||
logger.error("[DIAGNOSTIC]", "runChecks - e", e);
|
||||
@@ -4624,6 +4876,10 @@ function insertIf(condition, elements) {
|
||||
function insertIfObj(condition, object) {
|
||||
return condition ? object : {};
|
||||
}
|
||||
|
||||
// src/main.ts
|
||||
if (typeof structuredClone !== "function")
|
||||
globalThis.structuredClone = (obj) => JSON.parse(JSON.stringify(obj));
|
||||
return baseclass.extend({
|
||||
ALLOWED_WITH_RUSSIA_INSIDE,
|
||||
BOOTSTRAP_DNS_SERVER_OPTIONS,
|
||||
|
||||
@@ -15,6 +15,7 @@ function createSectionContent(section) {
|
||||
o.value("proxy", "Proxy");
|
||||
o.value("vpn", "VPN");
|
||||
o.value("block", "Block");
|
||||
o.value("exclusion", "Exclusion");
|
||||
|
||||
o = section.option(
|
||||
form.ListValue,
|
||||
@@ -23,8 +24,9 @@ function createSectionContent(section) {
|
||||
_("Select how to configure the proxy"),
|
||||
);
|
||||
o.value("url", _("Connection URL"));
|
||||
o.value("outbound", _("Outbound Config"));
|
||||
o.value("selector", _("Selector"));
|
||||
o.value("urltest", _("URLTest"));
|
||||
o.value("outbound", _("Outbound Config"));
|
||||
o.default = "url";
|
||||
o.depends("connection_type", "proxy");
|
||||
|
||||
@@ -32,7 +34,7 @@ function createSectionContent(section) {
|
||||
form.TextValue,
|
||||
"proxy_string",
|
||||
_("Proxy Configuration URL"),
|
||||
"",
|
||||
_("vless://, ss://, trojan://, socks4/5://, hy2/hysteria2:// links")
|
||||
);
|
||||
o.depends("proxy_config_type", "url");
|
||||
o.rows = 5;
|
||||
@@ -42,7 +44,6 @@ function createSectionContent(section) {
|
||||
o.textarea = true;
|
||||
o.rmempty = false;
|
||||
o.sectionDescriptions = new Map();
|
||||
o.placeholder = "vless://uuid@server:port?type=tcp&security=tls#main";
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
@@ -83,11 +84,11 @@ function createSectionContent(section) {
|
||||
|
||||
o = section.option(
|
||||
form.DynamicList,
|
||||
"urltest_proxy_links",
|
||||
_("URLTest Proxy Links"),
|
||||
"selector_proxy_links",
|
||||
_("Selector Proxy Links"),
|
||||
_("vless://, ss://, trojan://, socks4/5://, hy2/hysteria2:// links")
|
||||
);
|
||||
o.depends("proxy_config_type", "urltest");
|
||||
o.placeholder = "vless://, ss://, trojan://, socks4/5:// links";
|
||||
o.depends("proxy_config_type", "selector");
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
@@ -104,6 +105,93 @@ function createSectionContent(section) {
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.DynamicList,
|
||||
"urltest_proxy_links",
|
||||
_("URLTest Proxy Links"),
|
||||
_("vless://, ss://, trojan://, socks4/5://, hy2/hysteria2:// links")
|
||||
);
|
||||
o.depends("proxy_config_type", "urltest");
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validateProxyUrl(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.ListValue,
|
||||
"urltest_check_interval",
|
||||
_("URLTest Check Interval"),
|
||||
_("The interval between connectivity tests")
|
||||
);
|
||||
o.value("30s", _("Every 30 seconds"));
|
||||
o.value("1m", _("Every 1 minute"));
|
||||
o.value("3m", _("Every 3 minutes"));
|
||||
o.value("5m", _("Every 5 minutes"));
|
||||
o.default = "3m";
|
||||
o.depends("proxy_config_type", "urltest");
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
"urltest_tolerance",
|
||||
_("URLTest Tolerance"),
|
||||
_("The maximum difference in response times (ms) allowed when comparing servers")
|
||||
);
|
||||
o.default = "50";
|
||||
o.rmempty = false;
|
||||
o.depends("proxy_config_type", "urltest");
|
||||
o.validate = function (section_id, value) {
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const parsed = parseFloat(value);
|
||||
|
||||
if (/^[0-9]+$/.test(value) && !isNaN(parsed) && isFinite(parsed) && parsed >= 50 && parsed <= 1000) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return _('Must be a number in the range of 50 - 1000');
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
"urltest_testing_url",
|
||||
_("URLTest Testing URL"),
|
||||
_("The URL used to test server connectivity")
|
||||
);
|
||||
o.value("https://www.gstatic.com/generate_204", "https://www.gstatic.com/generate_204 (Google)");
|
||||
o.value("https://cp.cloudflare.com/generate_204", "https://cp.cloudflare.com/generate_204 (Cloudflare)");
|
||||
o.value("https://captive.apple.com", "https://captive.apple.com (Apple)");
|
||||
o.value("https://connectivity-check.ubuntu.com", "https://connectivity-check.ubuntu.com (Ubuntu)")
|
||||
o.default = "https://www.gstatic.com/generate_204";
|
||||
o.rmempty = false;
|
||||
o.depends("proxy_config_type", "urltest");
|
||||
|
||||
o.validate = function (section_id, value) {
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validateUrl(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"enable_udp_over_tcp",
|
||||
@@ -381,7 +469,7 @@ function createSectionContent(section) {
|
||||
);
|
||||
o.value("disabled", _("Disabled"));
|
||||
o.value("dynamic", _("Dynamic List"));
|
||||
o.value("text", _("Text List (comma/space/newline separated)"));
|
||||
o.value("text", _("Text List"));
|
||||
o.default = "disabled";
|
||||
o.rmempty = false;
|
||||
|
||||
@@ -554,6 +642,8 @@ function createSectionContent(section) {
|
||||
);
|
||||
o.placeholder = "192.168.1.2 or 192.168.1.0/24";
|
||||
o.rmempty = true;
|
||||
o.depends("connection_type", "proxy");
|
||||
o.depends("connection_type", "vpn");
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
@@ -579,6 +669,8 @@ function createSectionContent(section) {
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
o.depends("connection_type", "proxy");
|
||||
o.depends("connection_type", "vpn");
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
@@ -591,6 +683,17 @@ function createSectionContent(section) {
|
||||
);
|
||||
o.rmempty = false;
|
||||
o.depends("mixed_proxy_enabled", "1");
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"resolve_real_ip_for_routing",
|
||||
_("Resolve real IP for routing"),
|
||||
_("Enable DNS resolve to get real IP when routing"),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
o.depends("connection_type", "proxy");
|
||||
o.depends("connection_type", "vpn");
|
||||
}
|
||||
|
||||
const EntryPoint = {
|
||||
|
||||
@@ -240,6 +240,25 @@ function createSettingsContent(section) {
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"enable_yacd_wan_access",
|
||||
_("Enable YACD WAN Access"),
|
||||
_("Allows access to YACD from the WAN. Make sure to open the appropriate port in your firewall."),
|
||||
);
|
||||
o.depends("enable_yacd", "1");
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
"yacd_secret_key",
|
||||
_("YACD Secret Key"),
|
||||
_("Secret key for authenticating remote access to YACD when WAN access is enabled."),
|
||||
);
|
||||
o.depends("enable_yacd_wan_access", "1");
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"disable_quic",
|
||||
@@ -267,7 +286,7 @@ function createSettingsContent(section) {
|
||||
form.Flag,
|
||||
"download_lists_via_proxy",
|
||||
_("Download Lists via Proxy/VPN"),
|
||||
_("Downloading all lists via main Proxy/VPN"),
|
||||
_("Downloading all lists via specific Proxy/VPN"),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
@@ -292,7 +311,7 @@ function createSettingsContent(section) {
|
||||
|
||||
for (const secName in sections) {
|
||||
const sec = sections[secName];
|
||||
if (sec[".type"] === "section") {
|
||||
if (sec[".type"] === "section" && sec['connection_type'] !== 'block' && sec['connection_type'] !== 'exclusion') {
|
||||
this.keylist.push(secName);
|
||||
this.vallist.push(secName);
|
||||
}
|
||||
@@ -359,6 +378,24 @@ function createSettingsContent(section) {
|
||||
return true;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.ListValue,
|
||||
"log_level",
|
||||
_("Log Level"),
|
||||
_(
|
||||
"Select the log level for sing-box",
|
||||
),
|
||||
);
|
||||
o.value("trace", "Trace");
|
||||
o.value("debug", "Debug");
|
||||
o.value("info", "Info");
|
||||
o.value("warn", "Warn");
|
||||
o.value("error", "Error");
|
||||
o.value("fatal", "Fatal");
|
||||
o.value("panic", "Panic");
|
||||
o.default = "warn";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"exclude_ntp",
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# RU translations for PODKOP package.
|
||||
# Copyright (C) 2025 THE PODKOP'S COPYRIGHT HOLDER
|
||||
# Copyright (C) 2026 THE PODKOP'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PODKOP package.
|
||||
# divocat, 2025.
|
||||
# divocatt, 2026.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PODKOP\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-23 20:43+0300\n"
|
||||
"PO-Revision-Date: 2025-10-23 20:43+0300\n"
|
||||
"Last-Translator: divocat\n"
|
||||
"POT-Creation-Date: 2026-05-29 16:40+0300\n"
|
||||
"PO-Revision-Date: 2026-05-29 16:40+0300\n"
|
||||
"Last-Translator: divocatt\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -35,6 +35,9 @@ msgstr "Активные соединения"
|
||||
msgid "Additional marking rules found"
|
||||
msgstr "Найдены дополнительные правила маркировки"
|
||||
|
||||
msgid "Allows access to YACD from the WAN. Make sure to open the appropriate port in your firewall."
|
||||
msgstr "Обеспечивает доступ к YACD из WAN. Убедитесь, что в брандмауэре открыт соответствующий порт."
|
||||
|
||||
msgid "Applicable for SOCKS and Shadowsocks proxy"
|
||||
msgstr "Применимо для SOCKS и Shadowsocks прокси"
|
||||
|
||||
@@ -182,9 +185,6 @@ msgstr "Скачивать списки через Proxy/VPN"
|
||||
msgid "Download Lists via specific proxy section"
|
||||
msgstr "Скачивать списки через выбранную секцию"
|
||||
|
||||
msgid "Downloading all lists via main Proxy/VPN"
|
||||
msgstr "Загрузка всех списков через основной прокси/VPN"
|
||||
|
||||
msgid "Downloading all lists via specific Proxy/VPN"
|
||||
msgstr "Загрузка всех списков через указанный прокси/VPN"
|
||||
|
||||
@@ -197,6 +197,9 @@ msgstr "Включить автостарт"
|
||||
msgid "Enable built-in DNS resolver for domains handled by this section"
|
||||
msgstr "Включить встроенный DNS-резолвер для доменов, обрабатываемых в этом разделе"
|
||||
|
||||
msgid "Enable DNS resolve to get real IP when routing"
|
||||
msgstr "Разрешать домены в реальные IP-адреса перед маршрутизацией в outbound"
|
||||
|
||||
msgid "Enable Mixed Proxy"
|
||||
msgstr "Включить смешанный прокси"
|
||||
|
||||
@@ -209,6 +212,9 @@ msgstr "Включить смешанный прокси-сервер, разр
|
||||
msgid "Enable YACD"
|
||||
msgstr "Включить YACD"
|
||||
|
||||
msgid "Enable YACD WAN Access"
|
||||
msgstr "Включить доступ YACD WAN"
|
||||
|
||||
msgid "Enter complete outbound configuration in JSON format"
|
||||
msgstr "Введите полную конфигурацию исходящего соединения в формате JSON"
|
||||
|
||||
@@ -221,6 +227,18 @@ msgstr "Введите доменные имена без протоколов,
|
||||
msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses"
|
||||
msgstr "Введите подсети в нотации CIDR (например, 103.21.244.0/22) или отдельные IP-адреса"
|
||||
|
||||
msgid "Every 1 minute"
|
||||
msgstr "Каждую минуту"
|
||||
|
||||
msgid "Every 3 minutes"
|
||||
msgstr "Каждые 3 минуты"
|
||||
|
||||
msgid "Every 30 seconds"
|
||||
msgstr "Каждые 30 секунд"
|
||||
|
||||
msgid "Every 5 minutes"
|
||||
msgstr "Каждые 5 минут"
|
||||
|
||||
msgid "Exclude NTP"
|
||||
msgstr "Исключить NTP"
|
||||
|
||||
@@ -266,6 +284,45 @@ msgstr "Неверный домен"
|
||||
msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y"
|
||||
msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y"
|
||||
|
||||
msgid "Invalid HY2 URL: insecure must be 0 or 1"
|
||||
msgstr "Неверный URL Hysteria2: параметр insecure должен быть 0 или 1"
|
||||
|
||||
msgid "Invalid HY2 URL: invalid port number"
|
||||
msgstr "Неверный URL Hysteria2: неверный номер порта"
|
||||
|
||||
msgid "Invalid HY2 URL: missing credentials/server"
|
||||
msgstr "Неверный URL Hysteria2: отсутствуют учетные данные/сервер"
|
||||
|
||||
msgid "Invalid HY2 URL: missing host"
|
||||
msgstr "Неверный URL Hysteria2: отсутствует хост"
|
||||
|
||||
msgid "Invalid HY2 URL: missing host & port"
|
||||
msgstr "Неверный URL Hysteria2: отсутствуют хост и порт"
|
||||
|
||||
msgid "Invalid HY2 URL: missing password"
|
||||
msgstr "Неверный URL Hysteria2: отсутствует пароль"
|
||||
|
||||
msgid "Invalid HY2 URL: missing port"
|
||||
msgstr "Неверный URL Hysteria2: отсутствует порт"
|
||||
|
||||
msgid "Invalid HY2 URL: must not contain spaces"
|
||||
msgstr "Неверный URL Hysteria2: не должен содержать пробелов"
|
||||
|
||||
msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://"
|
||||
msgstr "Неверный URL Hysteria2: должен начинаться с hysteria2:// или hy2://"
|
||||
|
||||
msgid "Invalid HY2 URL: obfs-password required when obfs is set"
|
||||
msgstr "Неверный URL Hysteria2: требуется obfs-password, когда установлен obfs"
|
||||
|
||||
msgid "Invalid HY2 URL: parsing failed"
|
||||
msgstr "Неверный URL Hysteria2: ошибка разбора"
|
||||
|
||||
msgid "Invalid HY2 URL: sni cannot be empty"
|
||||
msgstr "Неверный URL Hysteria2: sni не может быть пустым"
|
||||
|
||||
msgid "Invalid HY2 URL: unsupported obfs type"
|
||||
msgstr "Неверный URL Hysteria2: неподдерживаемый тип obfs"
|
||||
|
||||
msgid "Invalid IP address"
|
||||
msgstr "Неверный IP-адрес"
|
||||
|
||||
@@ -365,6 +422,9 @@ msgstr "Локальные списки доменов"
|
||||
msgid "Local Subnet Lists"
|
||||
msgstr "Локальные списки подсетей"
|
||||
|
||||
msgid "Log Level"
|
||||
msgstr "Уровень логов"
|
||||
|
||||
msgid "Main DNS"
|
||||
msgstr "Основной DNS"
|
||||
|
||||
@@ -377,6 +437,9 @@ msgstr "Порт смешанного прокси"
|
||||
msgid "Monitored Interfaces"
|
||||
msgstr "Наблюдаемые интерфейсы"
|
||||
|
||||
msgid "Must be a number in the range of 50 - 1000"
|
||||
msgstr "Должно быть числом от 50 до 1000"
|
||||
|
||||
msgid "Network Interface"
|
||||
msgstr "Сетевой интерфейс"
|
||||
|
||||
@@ -386,6 +449,9 @@ msgstr "Другие правила маркировки не найдены"
|
||||
msgid "Not implement yet"
|
||||
msgstr "Ещё не реализовано"
|
||||
|
||||
msgid "Not responding"
|
||||
msgstr "Не отвечает"
|
||||
|
||||
msgid "Not running"
|
||||
msgstr "Не запущено"
|
||||
|
||||
@@ -398,9 +464,6 @@ msgstr "Конфигурация Outbound"
|
||||
msgid "Outbound Configuration"
|
||||
msgstr "Конфигурация исходящего соединения"
|
||||
|
||||
msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields"
|
||||
msgstr "JSON должен содержать поля \"type\", \"server\" и \"server_port\""
|
||||
|
||||
msgid "Outdated"
|
||||
msgstr "Устаревшая"
|
||||
|
||||
@@ -449,6 +512,9 @@ msgstr "Внешние списки доменов"
|
||||
msgid "Remote Subnet Lists"
|
||||
msgstr "Внешние списки подсетей"
|
||||
|
||||
msgid "Resolve real IP for routing"
|
||||
msgstr "Разрешение реальных IP-адресов"
|
||||
|
||||
msgid "Restart podkop"
|
||||
msgstr "Перезапустить Podkop"
|
||||
|
||||
@@ -485,6 +551,9 @@ msgstr "Запустить диагностику"
|
||||
msgid "Russia inside restrictions"
|
||||
msgstr "Ограничения Russia inside"
|
||||
|
||||
msgid "Secret key for authenticating remote access to YACD when WAN access is enabled."
|
||||
msgstr "Секретный ключ для аутентификации удаленного доступа к YACD при включенном доступе через WAN."
|
||||
|
||||
msgid "Sections"
|
||||
msgstr "Секции"
|
||||
|
||||
@@ -524,6 +593,9 @@ msgstr "Выберите тип списка для добавления пол
|
||||
msgid "Select the list type for adding custom subnets"
|
||||
msgstr "Выберите тип списка для добавления пользовательских подсетей"
|
||||
|
||||
msgid "Select the log level for sing-box"
|
||||
msgstr "Выберите уровень логов для sing-box"
|
||||
|
||||
msgid "Select the network interface from which the traffic will originate"
|
||||
msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик"
|
||||
|
||||
@@ -533,6 +605,12 @@ msgstr "Выберите сетевой интерфейс, на который
|
||||
msgid "Select the WAN interfaces to be monitored"
|
||||
msgstr "Выберите WAN интерфейсы для мониторинга"
|
||||
|
||||
msgid "Selector"
|
||||
msgstr "Selector"
|
||||
|
||||
msgid "Selector Proxy Links"
|
||||
msgstr "Ссылки прокси для Selector"
|
||||
|
||||
msgid "Services info"
|
||||
msgstr "Информация о сервисах"
|
||||
|
||||
@@ -560,8 +638,8 @@ msgstr "Процесс sing-box запущен"
|
||||
msgid "Sing-box service exist"
|
||||
msgstr "Сервис sing-box существует"
|
||||
|
||||
msgid "Sing-box version >= 1.12.4"
|
||||
msgstr "Версия sing-box >= 1.12.4"
|
||||
msgid "Sing-box version is compatible (newer than 1.12.4)"
|
||||
msgstr "Версия Sing-box совместима (новее 1.12.4)"
|
||||
|
||||
msgid "Source Network Interface"
|
||||
msgstr "Сетевой интерфейс источника"
|
||||
@@ -605,12 +683,18 @@ msgstr "Тестирование задержки"
|
||||
msgid "Text List"
|
||||
msgstr "Текстовый список"
|
||||
|
||||
msgid "Text List (comma/space/newline separated)"
|
||||
msgstr "Текстовый список (через запятую, пробел или новую строку)"
|
||||
|
||||
msgid "The DNS server used to look up the IP address of an upstream DNS server"
|
||||
msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера"
|
||||
|
||||
msgid "The interval between connectivity tests"
|
||||
msgstr "Интервал между тестами подключения"
|
||||
|
||||
msgid "The maximum difference in response times (ms) allowed when comparing servers"
|
||||
msgstr "Максимально допустимая разница во времени отклика (мс) при сравнении серверов"
|
||||
|
||||
msgid "The URL used to test server connectivity"
|
||||
msgstr "URL-адрес, используемый для проверки подключения к серверу"
|
||||
|
||||
msgid "Time in seconds for DNS record caching (default: 60)"
|
||||
msgstr "Время в секундах для кэширования DNS записей (по умолчанию: 60)"
|
||||
|
||||
@@ -644,8 +728,8 @@ msgstr "Неизвестная ошибка"
|
||||
msgid "Uplink"
|
||||
msgstr "Исходящий"
|
||||
|
||||
msgid "URL must start with vless://, ss://, trojan://, or socks4/5://"
|
||||
msgstr "URL должен начинаться с vless://, ss://, trojan:// или socks4/5://"
|
||||
msgid "URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://"
|
||||
msgstr "URL должен начинаться с vless://, ss://, trojan://, socks4/5:// или hysteria2:// hy2://"
|
||||
|
||||
msgid "URL must use one of the following protocols:"
|
||||
msgstr "URL должен использовать один из следующих протоколов:"
|
||||
@@ -653,9 +737,18 @@ msgstr "URL должен использовать один из следующи
|
||||
msgid "URLTest"
|
||||
msgstr "URLTest"
|
||||
|
||||
msgid "URLTest Check Interval"
|
||||
msgstr "Интервал проверки URLTest"
|
||||
|
||||
msgid "URLTest Proxy Links"
|
||||
msgstr "Ссылки прокси для URLTest"
|
||||
|
||||
msgid "URLTest Testing URL"
|
||||
msgstr "URLTest ссылка для проверки"
|
||||
|
||||
msgid "URLTest Tolerance"
|
||||
msgstr "URLTest допустимое отклонение"
|
||||
|
||||
msgid "User Domain List Type"
|
||||
msgstr "Тип пользовательского списка доменов"
|
||||
|
||||
@@ -686,11 +779,17 @@ msgstr "Посмотреть логи"
|
||||
msgid "Visit Wiki"
|
||||
msgstr "Перейти в wiki"
|
||||
|
||||
msgid "vless://, ss://, trojan://, socks4/5://, hy2/hysteria2:// links"
|
||||
msgstr ""
|
||||
|
||||
msgid "Warning: %s cannot be used together with %s. Previous selections have been removed."
|
||||
msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены."
|
||||
|
||||
msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection."
|
||||
msgstr "Предупреждение: Russia inside может быть использован только с %s. %s уже есть в Russia inside и будет удален из выбранных."
|
||||
|
||||
msgid "YACD Secret Key"
|
||||
msgstr "Секретный ключ YACD"
|
||||
|
||||
msgid "You can select Output Network Interface, by default autodetect"
|
||||
msgstr "Вы можете выбрать выходной сетевой интерфейс, по умолчанию он определяется автоматически."
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# Copyright (C) 2026 THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PODKOP package.
|
||||
# divocat <divocatt@gmail.com>, 2025.
|
||||
# divocatt <210179590+divocatt@users.noreply.github.com>, 2026.
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PODKOP\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-10-23 17:43+0300\n"
|
||||
"PO-Revision-Date: 2025-10-23 17:43+0300\n"
|
||||
"Last-Translator: divocat <divocatt@gmail.com>\n"
|
||||
"POT-Creation-Date: 2026-05-29 13:40+0300\n"
|
||||
"PO-Revision-Date: 2026-05-29 13:40+0300\n"
|
||||
"Last-Translator: divocatt <210179590+divocatt@users.noreply.github.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:342
|
||||
#: src/podkop/tabs/dashboard/initController.ts:345
|
||||
msgid "✔ Enabled"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:353
|
||||
#: src/podkop/tabs/dashboard/initController.ts:356
|
||||
msgid "✔ Running"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:343
|
||||
#: src/podkop/tabs/dashboard/initController.ts:346
|
||||
msgid "✘ Disabled"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:354
|
||||
#: src/podkop/tabs/dashboard/initController.ts:357
|
||||
msgid "✘ Stopped"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:304
|
||||
#: src/podkop/tabs/dashboard/initController.ts:307
|
||||
msgid "Active Connections"
|
||||
msgstr ""
|
||||
|
||||
@@ -40,15 +40,19 @@ msgstr ""
|
||||
msgid "Additional marking rules found"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:111
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:247
|
||||
msgid "Allows access to YACD from the WAN. Make sure to open the appropriate port in your firewall."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:199
|
||||
msgid "Applicable for SOCKS and Shadowsocks proxy"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:356
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:444
|
||||
msgid "At least one valid domain must be specified. Comments-only content is not allowed."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:437
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:525
|
||||
msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed."
|
||||
msgstr ""
|
||||
|
||||
@@ -72,16 +76,17 @@ msgstr ""
|
||||
msgid "Browser is using FakeIP correctly"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:329
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:348
|
||||
msgid "Cache File Path"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:343
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:362
|
||||
msgid "Cache file path cannot be empty"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:27
|
||||
#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:28
|
||||
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:27
|
||||
#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:25
|
||||
msgid "Cannot receive checks result"
|
||||
msgstr ""
|
||||
@@ -89,6 +94,7 @@ msgstr ""
|
||||
#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:15
|
||||
#: src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts:15
|
||||
#: src/podkop/tabs/diagnostic/checks/runNftCheck.ts:13
|
||||
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:15
|
||||
#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:13
|
||||
msgid "Checking, please wait"
|
||||
msgstr ""
|
||||
@@ -113,11 +119,11 @@ msgstr ""
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:211
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:299
|
||||
msgid "Community Lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:316
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:335
|
||||
msgid "Config File Path"
|
||||
msgstr ""
|
||||
|
||||
@@ -125,7 +131,7 @@ msgstr ""
|
||||
msgid "Configuration for Podkop service"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:22
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:23
|
||||
msgid "Configuration Type"
|
||||
msgstr ""
|
||||
|
||||
@@ -133,7 +139,7 @@ msgstr ""
|
||||
msgid "Connection Type"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:25
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:26
|
||||
msgid "Connection URL"
|
||||
msgstr ""
|
||||
|
||||
@@ -173,16 +179,16 @@ msgstr ""
|
||||
msgid "Disable autostart"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:246
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:265
|
||||
msgid "Disable QUIC"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:247
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:266
|
||||
msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:302
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:382
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:390
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:470
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
||||
@@ -190,17 +196,17 @@ msgstr ""
|
||||
msgid "DNS on router"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:179
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:267
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:15
|
||||
msgid "DNS over HTTPS (DoH)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:180
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:268
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:16
|
||||
msgid "DNS over TLS (DoT)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:176
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:264
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:12
|
||||
msgid "DNS Protocol Type"
|
||||
msgstr ""
|
||||
@@ -209,7 +215,7 @@ msgstr ""
|
||||
msgid "DNS Rewrite TTL"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:189
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:277
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:24
|
||||
msgid "DNS Server"
|
||||
msgstr ""
|
||||
@@ -222,16 +228,16 @@ msgstr ""
|
||||
msgid "Do not panic, everything can be fixed, just..."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:166
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:254
|
||||
msgid "Domain Resolver"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:307
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:326
|
||||
msgid "Dont Touch My DHCP!"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:238
|
||||
#: src/podkop/tabs/dashboard/initController.ts:272
|
||||
#: src/podkop/tabs/dashboard/initController.ts:241
|
||||
#: src/podkop/tabs/dashboard/initController.ts:275
|
||||
msgid "Downlink"
|
||||
msgstr ""
|
||||
|
||||
@@ -239,24 +245,21 @@ msgstr ""
|
||||
msgid "Download"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:269
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:288
|
||||
msgid "Download Lists via Proxy/VPN"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:278
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:297
|
||||
msgid "Download Lists via specific proxy section"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:270
|
||||
msgid "Downloading all lists via main Proxy/VPN"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:279
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:289
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:298
|
||||
msgid "Downloading all lists via specific Proxy/VPN"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:303
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:383
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:391
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:471
|
||||
msgid "Dynamic List"
|
||||
msgstr ""
|
||||
|
||||
@@ -264,11 +267,15 @@ msgstr ""
|
||||
msgid "Enable autostart"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:167
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:255
|
||||
msgid "Enable built-in DNS resolver for domains handled by this section"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:575
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:691
|
||||
msgid "Enable DNS resolve to get real IP when routing"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:665
|
||||
msgid "Enable Mixed Proxy"
|
||||
msgstr ""
|
||||
|
||||
@@ -276,7 +283,7 @@ msgstr ""
|
||||
msgid "Enable Output Network Interface"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:576
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:666
|
||||
msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies"
|
||||
msgstr ""
|
||||
|
||||
@@ -284,27 +291,47 @@ msgstr ""
|
||||
msgid "Enable YACD"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:65
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:246
|
||||
msgid "Enable YACD WAN Access"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:66
|
||||
msgid "Enter complete outbound configuration in JSON format"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:338
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:426
|
||||
msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:312
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:400
|
||||
msgid "Enter domain names without protocols, e.g. example.com or sub.example.com"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:392
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:480
|
||||
msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:365
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:138
|
||||
msgid "Every 1 minute"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:139
|
||||
msgid "Every 3 minutes"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:137
|
||||
msgid "Every 30 seconds"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:140
|
||||
msgid "Every 5 minutes"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:402
|
||||
msgid "Exclude NTP"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:366
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:403
|
||||
msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN"
|
||||
msgstr ""
|
||||
|
||||
@@ -312,20 +339,21 @@ msgstr ""
|
||||
msgid "Failed to copy!"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:226
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:230
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:260
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:264
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:298
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:227
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:231
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:261
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:265
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:302
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:306
|
||||
msgid "Failed to execute!"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/methods/custom/getDashboardSections.ts:117
|
||||
#: src/podkop/methods/custom/getDashboardSections.ts:148
|
||||
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:59
|
||||
msgid "Fastest"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:550
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:638
|
||||
msgid "Fully Routed IPs"
|
||||
msgstr ""
|
||||
|
||||
@@ -333,7 +361,7 @@ msgstr ""
|
||||
msgid "Get global check"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:221
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:222
|
||||
msgid "Global check"
|
||||
msgstr ""
|
||||
|
||||
@@ -366,11 +394,63 @@ msgstr ""
|
||||
msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:90
|
||||
msgid "Invalid HY2 URL: insecure must be 0 or 1"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:77
|
||||
msgid "Invalid HY2 URL: invalid port number"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:30
|
||||
msgid "Invalid HY2 URL: missing credentials/server"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:47
|
||||
msgid "Invalid HY2 URL: missing host"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:41
|
||||
msgid "Invalid HY2 URL: missing host & port"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:36
|
||||
msgid "Invalid HY2 URL: missing password"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:50
|
||||
msgid "Invalid HY2 URL: missing port"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:18
|
||||
msgid "Invalid HY2 URL: must not contain spaces"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:12
|
||||
msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:108
|
||||
msgid "Invalid HY2 URL: obfs-password required when obfs is set"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:122
|
||||
msgid "Invalid HY2 URL: parsing failed"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:116
|
||||
msgid "Invalid HY2 URL: sni cannot be empty"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateHysteriaUrl.ts:98
|
||||
msgid "Invalid HY2 URL: unsupported obfs type"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateIp.ts:11
|
||||
msgid "Invalid IP address"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateOutboundJson.ts:19
|
||||
#: src/validators/validateOutboundJson.ts:9
|
||||
msgid "Invalid JSON format"
|
||||
msgstr ""
|
||||
|
||||
@@ -466,7 +546,8 @@ msgstr ""
|
||||
msgid "Invalid Trojan URL: parsing failed"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateUrl.ts:18
|
||||
#: src/validators/validateUrl.ts:8
|
||||
#: src/validators/validateUrl.ts:31
|
||||
msgid "Invalid URL format"
|
||||
msgstr ""
|
||||
|
||||
@@ -482,31 +563,35 @@ msgstr ""
|
||||
msgid "Issues detected"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:452
|
||||
#: src/podkop/tabs/diagnostic/helpers/getPodkopVersionRow.ts:48
|
||||
msgid "Latest"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:257
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:276
|
||||
msgid "List Update Frequency"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:458
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:546
|
||||
msgid "Local Domain Lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:481
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:569
|
||||
msgid "Local Subnet Lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:384
|
||||
msgid "Log Level"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/checks/runDnsCheck.ts:72
|
||||
msgid "Main DNS"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:308
|
||||
#: src/podkop/tabs/dashboard/initController.ts:311
|
||||
msgid "Memory Usage"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:586
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:678
|
||||
msgid "Mixed Proxy Port"
|
||||
msgstr ""
|
||||
|
||||
@@ -514,7 +599,11 @@ msgstr ""
|
||||
msgid "Monitored Interfaces"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:120
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:164
|
||||
msgid "Must be a number in the range of 50 - 1000"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:208
|
||||
msgid "Network Interface"
|
||||
msgstr ""
|
||||
|
||||
@@ -526,10 +615,17 @@ msgstr ""
|
||||
msgid "Not implement yet"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:75
|
||||
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:81
|
||||
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:100
|
||||
msgid "Not responding"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:55
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:63
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:71
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:79
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:87
|
||||
msgid "Not running"
|
||||
msgstr ""
|
||||
|
||||
@@ -537,19 +633,15 @@ msgstr ""
|
||||
msgid "Operation timed out"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:26
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:29
|
||||
msgid "Outbound Config"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:64
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:65
|
||||
msgid "Outbound Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateOutboundJson.ts:11
|
||||
msgid "Outbound JSON must contain at least \"type\", \"server\" and \"server_port\" fields"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:442
|
||||
#: src/podkop/tabs/diagnostic/helpers/getPodkopVersionRow.ts:38
|
||||
msgid "Outdated"
|
||||
msgstr ""
|
||||
|
||||
@@ -561,26 +653,27 @@ msgstr ""
|
||||
msgid "Path cannot be empty"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:347
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:366
|
||||
msgid "Path must be absolute (start with /)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:356
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:375
|
||||
msgid "Path must contain at least one directory (like /tmp/cache.db)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:351
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:370
|
||||
msgid "Path must end with cache.db"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:95
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:103
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:111
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:119
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:127
|
||||
#: src/podkop/tabs/diagnostic/diagnostic.store.ts:135
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:340
|
||||
#: src/podkop/tabs/dashboard/initController.ts:343
|
||||
msgid "Podkop"
|
||||
msgstr ""
|
||||
|
||||
@@ -588,11 +681,11 @@ msgstr ""
|
||||
msgid "Podkop Settings"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:308
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:327
|
||||
msgid "Podkop will not modify your DHCP configuration"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:34
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:36
|
||||
msgid "Proxy Configuration URL"
|
||||
msgstr ""
|
||||
|
||||
@@ -604,18 +697,22 @@ msgstr ""
|
||||
msgid "Proxy traffic is routed via FakeIP"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:245
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:333
|
||||
msgid "Regional options cannot be used together"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:504
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:592
|
||||
msgid "Remote Domain Lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:527
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:615
|
||||
msgid "Remote Subnet Lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:690
|
||||
msgid "Resolve real IP for routing"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:49
|
||||
msgid "Restart podkop"
|
||||
msgstr ""
|
||||
@@ -628,7 +725,7 @@ msgstr ""
|
||||
msgid "Router DNS is routed through sing-box"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:376
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:413
|
||||
msgid "Routing Excluded IPs"
|
||||
msgstr ""
|
||||
|
||||
@@ -660,15 +757,19 @@ msgstr ""
|
||||
msgid "Run Diagnostic"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:264
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:352
|
||||
msgid "Russia inside restrictions"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:257
|
||||
msgid "Secret key for authenticating remote access to YACD when WAN access is enabled."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js:36
|
||||
msgid "Sections"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:212
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:300
|
||||
msgid "Select a predefined list for routing"
|
||||
msgstr ""
|
||||
|
||||
@@ -680,43 +781,47 @@ msgstr ""
|
||||
msgid "Select DNS protocol to use"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:258
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:277
|
||||
msgid "Select how often the domain or subnet lists are updated automatically"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:23
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:24
|
||||
msgid "Select how to configure the proxy"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:121
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:209
|
||||
msgid "Select network interface for VPN connection"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:190
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:278
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:25
|
||||
msgid "Select or enter DNS server address"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:330
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:349
|
||||
msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:317
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:336
|
||||
msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:177
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:265
|
||||
msgid "Select the DNS protocol type for the domain resolver"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:300
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:388
|
||||
msgid "Select the list type for adding custom domains"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:380
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:468
|
||||
msgid "Select the list type for adding custom subnets"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:385
|
||||
msgid "Select the log level for sing-box"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:90
|
||||
msgid "Select the network interface from which the traffic will originate"
|
||||
msgstr ""
|
||||
@@ -729,7 +834,15 @@ msgstr ""
|
||||
msgid "Select the WAN interfaces to be monitored"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:337
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:27
|
||||
msgid "Selector"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:88
|
||||
msgid "Selector Proxy Links"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:340
|
||||
msgid "Services info"
|
||||
msgstr ""
|
||||
|
||||
@@ -737,12 +850,12 @@ msgstr ""
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:289
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:290
|
||||
#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:116
|
||||
msgid "Show sing-box config"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:351
|
||||
#: src/podkop/tabs/dashboard/initController.ts:354
|
||||
msgid "Sing-box"
|
||||
msgstr ""
|
||||
|
||||
@@ -767,31 +880,31 @@ msgid "Sing-box service exist"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts:67
|
||||
msgid "Sing-box version >= 1.12.4"
|
||||
msgid "Sing-box version is compatible (newer than 1.12.4)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:89
|
||||
msgid "Source Network Interface"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:377
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:414
|
||||
msgid "Specify a local IP address to be excluded from routing"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:551
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:639
|
||||
msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:505
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:593
|
||||
msgid "Specify remote URLs to download and use domain lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:528
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:616
|
||||
msgid "Specify remote URLs to download and use subnet lists"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:459
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:482
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:547
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:570
|
||||
msgid "Specify the path to the list file located on the router filesystem"
|
||||
msgstr ""
|
||||
|
||||
@@ -807,7 +920,7 @@ msgstr ""
|
||||
msgid "Successfully copied!"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:301
|
||||
#: src/podkop/tabs/dashboard/initController.ts:304
|
||||
msgid "System info"
|
||||
msgstr ""
|
||||
|
||||
@@ -823,27 +936,36 @@ msgstr ""
|
||||
msgid "Test latency"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:304
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:392
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:472
|
||||
msgid "Text List"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:384
|
||||
msgid "Text List (comma/space/newline separated)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:46
|
||||
msgid "The DNS server used to look up the IP address of an upstream DNS server"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:135
|
||||
msgid "The interval between connectivity tests"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:148
|
||||
msgid "The maximum difference in response times (ms) allowed when comparing servers"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:171
|
||||
msgid "The URL used to test server connectivity"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:69
|
||||
msgid "Time in seconds for DNS record caching (default: 60)"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:235
|
||||
#: src/podkop/tabs/dashboard/initController.ts:238
|
||||
msgid "Traffic"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:265
|
||||
#: src/podkop/tabs/dashboard/initController.ts:268
|
||||
msgid "Traffic Total"
|
||||
msgstr ""
|
||||
|
||||
@@ -859,22 +981,22 @@ msgstr ""
|
||||
msgid "TTL value cannot be empty"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:181
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:269
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:17
|
||||
msgid "UDP (Unprotected DNS)"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:110
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:198
|
||||
msgid "UDP over TCP"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:37
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:38
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:39
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:40
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:41
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:42
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:416
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:43
|
||||
#: src/podkop/tabs/diagnostic/helpers/getPodkopVersionRow.ts:7
|
||||
msgid "unknown"
|
||||
msgstr ""
|
||||
|
||||
@@ -882,48 +1004,60 @@ msgstr ""
|
||||
msgid "Unknown error"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/dashboard/initController.ts:237
|
||||
#: src/podkop/tabs/dashboard/initController.ts:268
|
||||
#: src/podkop/tabs/dashboard/initController.ts:240
|
||||
#: src/podkop/tabs/dashboard/initController.ts:271
|
||||
msgid "Uplink"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateProxyUrl.ts:29
|
||||
msgid "URL must start with vless://, ss://, trojan://, or socks4/5://"
|
||||
#: src/validators/validateProxyUrl.ts:37
|
||||
msgid "URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://"
|
||||
msgstr ""
|
||||
|
||||
#: src/validators/validateUrl.ts:13
|
||||
#: src/validators/validateUrl.ts:17
|
||||
msgid "URL must use one of the following protocols:"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:27
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:28
|
||||
msgid "URLTest"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:87
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:134
|
||||
msgid "URLTest Check Interval"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:111
|
||||
msgid "URLTest Proxy Links"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:299
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:170
|
||||
msgid "URLTest Testing URL"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:147
|
||||
msgid "URLTest Tolerance"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:387
|
||||
msgid "User Domain List Type"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:311
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:399
|
||||
msgid "User Domains"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:337
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:425
|
||||
msgid "User Domains List"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:379
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:467
|
||||
msgid "User Subnet List Type"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:391
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:479
|
||||
msgid "User Subnets"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:417
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:505
|
||||
msgid "User Subnets List"
|
||||
msgstr ""
|
||||
|
||||
@@ -931,24 +1065,25 @@ msgstr ""
|
||||
#: src/validators/validateDns.ts:18
|
||||
#: src/validators/validateDomain.ts:13
|
||||
#: src/validators/validateDomain.ts:30
|
||||
#: src/validators/validateHysteriaUrl.ts:120
|
||||
#: src/validators/validateIp.ts:8
|
||||
#: src/validators/validateOutboundJson.ts:17
|
||||
#: src/validators/validateOutboundJson.ts:7
|
||||
#: src/validators/validatePath.ts:16
|
||||
#: src/validators/validateShadowsocksUrl.ts:95
|
||||
#: src/validators/validateSocksUrl.ts:80
|
||||
#: src/validators/validateSubnet.ts:38
|
||||
#: src/validators/validateTrojanUrl.ts:59
|
||||
#: src/validators/validateUrl.ts:16
|
||||
#: src/validators/validateUrl.ts:28
|
||||
#: src/validators/validateVlessUrl.ts:108
|
||||
msgid "Valid"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:370
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:449
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:458
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:537
|
||||
msgid "Validation errors:"
|
||||
msgstr ""
|
||||
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:255
|
||||
#: src/podkop/tabs/diagnostic/initController.ts:256
|
||||
#: src/podkop/tabs/diagnostic/partials/renderAvailableActions.ts:107
|
||||
msgid "View logs"
|
||||
msgstr ""
|
||||
@@ -957,14 +1092,24 @@ msgstr ""
|
||||
msgid "Visit Wiki"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:247
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:37
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:89
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:112
|
||||
msgid "vless://, ss://, trojan://, socks4/5://, hy2/hysteria2:// links"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:335
|
||||
msgid "Warning: %s cannot be used together with %s. Previous selections have been removed."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:266
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js:354
|
||||
msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:256
|
||||
msgid "YACD Secret Key"
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js:127
|
||||
msgid "You can select Output Network Interface, by default autodetect"
|
||||
msgstr ""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
config settings 'settings'
|
||||
option dns_type 'udp'
|
||||
option dns_server '8.8.8.8'
|
||||
option dns_server '77.88.8.8'
|
||||
option bootstrap_dns_server '77.88.8.8'
|
||||
option dns_rewrite_ttl '60'
|
||||
list source_network_interfaces 'br-lan'
|
||||
@@ -16,6 +16,7 @@ config settings 'settings'
|
||||
option dont_touch_dhcp '0'
|
||||
option config_path '/etc/sing-box/config.json'
|
||||
option cache_path '/tmp/sing-box/cache.db'
|
||||
option log_level 'warn'
|
||||
option exclude_ntp '0'
|
||||
option shutdown_correctly '0'
|
||||
#list routing_excluded_ips '192.168.1.3'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@ TMP_RULESET_FOLDER="$TMP_SING_BOX_FOLDER/rulesets"
|
||||
CLOUDFLARE_OCTETS="8.47 162.159 188.114" # Endpoints https://github.com/ampetelin/warp-endpoint-checker
|
||||
JQ_REQUIRED_VERSION="1.7.1"
|
||||
COREUTILS_BASE64_REQUIRED_VERSION="9.7"
|
||||
RT_TABLE_NAME="podkop"
|
||||
|
||||
## nft
|
||||
NFT_TABLE_NAME="PodkopTable"
|
||||
@@ -19,11 +20,11 @@ NFT_LOCALV4_SET_NAME="localv4"
|
||||
NFT_COMMON_SET_NAME="podkop_subnets"
|
||||
NFT_DISCORD_SET_NAME="podkop_discord_subnets"
|
||||
NFT_INTERFACE_SET_NAME="interfaces"
|
||||
NFT_FAKEIP_MARK="0x00100000"
|
||||
NFT_OUTBOUND_MARK="0x00200000"
|
||||
|
||||
## sing-box
|
||||
SB_REQUIRED_VERSION="1.12.0"
|
||||
# Log
|
||||
SB_DEFAULT_LOG_LEVEL="warn"
|
||||
# DNS
|
||||
SB_DNS_SERVER_TAG="dns-server"
|
||||
SB_FAKEIP_DNS_SERVER_TAG="fakeip-server"
|
||||
@@ -38,7 +39,6 @@ SB_TPROXY_INBOUND_PORT=1602
|
||||
SB_DNS_INBOUND_TAG="dns-in"
|
||||
SB_DNS_INBOUND_ADDRESS="127.0.0.42"
|
||||
SB_DNS_INBOUND_PORT=53
|
||||
SB_MIXED_INBOUND_ADDRESS="0.0.0.0" # TODO(ampetelin): maybe to determine address?
|
||||
SB_SERVICE_MIXED_INBOUND_TAG="service-mixed-in"
|
||||
SB_SERVICE_MIXED_INBOUND_ADDRESS="127.0.0.1"
|
||||
SB_SERVICE_MIXED_INBOUND_PORT=4534
|
||||
@@ -46,23 +46,21 @@ SB_SERVICE_MIXED_INBOUND_PORT=4534
|
||||
SB_DIRECT_OUTBOUND_TAG="direct-out"
|
||||
# Route
|
||||
SB_REJECT_RULE_TAG="reject-rule-tag"
|
||||
SB_EXCLUSION_RULE_TAG="exclusion-rule-tag"
|
||||
# Experimental
|
||||
SB_CLASH_API_CONTROLLER="0.0.0.0:9090"
|
||||
SB_CLASH_API_CONTROLLER_PORT=9090
|
||||
|
||||
## Lists
|
||||
GITHUB_RAW_URL="https://raw.githubusercontent.com/itdoginfo/allow-domains/main"
|
||||
SRS_MAIN_URL="https://github.com/itdoginfo/allow-domains/releases/latest/download"
|
||||
DOMAINS_RU_INSIDE="${GITHUB_RAW_URL}/Russia/inside-dnsmasq-nfset.lst"
|
||||
DOMAINS_RU_OUTSIDE="${GITHUB_RAW_URL}/Russia/outside-dnsmasq-nfset.lst"
|
||||
DOMAINS_UA="${GITHUB_RAW_URL}/Ukraine/inside-dnsmasq-nfset.lst"
|
||||
DOMAINS_YOUTUBE="${GITHUB_RAW_URL}/Services/youtube.lst"
|
||||
SUBNETS_TWITTER="${GITHUB_RAW_URL}/Subnets/IPv4/twitter.lst"
|
||||
SUBNETS_META="${GITHUB_RAW_URL}/Subnets/IPv4/meta.lst"
|
||||
SUBNETS_DISCORD="${GITHUB_RAW_URL}/Subnets/IPv4/discord.lst"
|
||||
SUBNETS_ROBLOX="${GITHUB_RAW_URL}/Subnets/IPv4/roblox.lst"
|
||||
SUBNETS_TELERAM="${GITHUB_RAW_URL}/Subnets/IPv4/telegram.lst"
|
||||
SUBNETS_CLOUDFLARE="${GITHUB_RAW_URL}/Subnets/IPv4/cloudflare.lst"
|
||||
SUBNETS_HETZNER="${GITHUB_RAW_URL}/Subnets/IPv4/hetzner.lst"
|
||||
SUBNETS_OVH="${GITHUB_RAW_URL}/Subnets/IPv4/ovh.lst"
|
||||
SUBNETS_DIGITALOCEAN="${GITHUB_RAW_URL}/Subnets/IPv4/digitalocean.lst"
|
||||
SUBNETS_CLOUDFRONT="${GITHUB_RAW_URL}/Subnets/IPv4/cloudfront.lst"
|
||||
COMMUNITY_SERVICES="russia_inside russia_outside ukraine_inside geoblock block porn news anime youtube hdrezka tiktok google_ai google_play hodca discord meta twitter cloudflare cloudfront digitalocean hetzner ovh telegram"
|
||||
COMMUNITY_SERVICES="russia_inside russia_outside ukraine_inside geoblock block porn news anime youtube hdrezka tiktok google_ai google_play hodca discord meta twitter cloudflare cloudfront digitalocean hetzner ovh telegram roblox"
|
||||
@@ -105,37 +105,6 @@ get_domain_resolver_tag() {
|
||||
echo "$section-$postfix"
|
||||
}
|
||||
|
||||
# Constructs and returns a ruleset tag using section, name, optional type, and a fixed postfix
|
||||
get_ruleset_tag() {
|
||||
local section="$1"
|
||||
local name="$2"
|
||||
local type="$3"
|
||||
local postfix="ruleset"
|
||||
|
||||
if [ -n "$type" ]; then
|
||||
echo "$section-$name-$type-$postfix"
|
||||
else
|
||||
echo "$section-$name-$postfix"
|
||||
fi
|
||||
}
|
||||
|
||||
# Determines the ruleset format based on the file extension (json → source, srs → binary)
|
||||
get_ruleset_format_by_file_extension() {
|
||||
local file_extension="$1"
|
||||
|
||||
local format
|
||||
case "$file_extension" in
|
||||
json) format="source" ;;
|
||||
srs) format="binary" ;;
|
||||
*)
|
||||
log "Unsupported file extension: .$file_extension" "error"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$format"
|
||||
}
|
||||
|
||||
# Converts a comma-separated string into a JSON array string
|
||||
comma_string_to_json_array() {
|
||||
local input="$1"
|
||||
@@ -156,6 +125,12 @@ url_decode() {
|
||||
printf '%b' "$(echo "$encoded" | sed 's/+/ /g; s/%/\\x/g')"
|
||||
}
|
||||
|
||||
# Returns the scheme (protocol) part of a URL
|
||||
url_get_scheme() {
|
||||
local url="$1"
|
||||
echo "${url%%://*}"
|
||||
}
|
||||
|
||||
# Extracts the userinfo (username[:password]) part from a URL
|
||||
url_get_userinfo() {
|
||||
local url="$1"
|
||||
@@ -165,13 +140,23 @@ url_get_userinfo() {
|
||||
# Extracts the host part from a URL
|
||||
url_get_host() {
|
||||
local url="$1"
|
||||
echo "$url" | sed -n -e 's#^[^:/?]*://##' -e 's#^[^/]*@##' -e 's#\([:/].*\|$\)##p'
|
||||
|
||||
url="${url#*://}"
|
||||
url="${url#*@}"
|
||||
url="${url%%[/?#]*}"
|
||||
|
||||
echo "${url%%:*}"
|
||||
}
|
||||
|
||||
# Extracts the port number from a URL
|
||||
url_get_port() {
|
||||
local url="$1"
|
||||
echo "$url" | sed -n -e 's#^[^:/?]*://##' -e 's#^[^/]*@##' -e 's#^[^/]*:\([0-9][0-9]*\).*#\1#p'
|
||||
|
||||
url="${url#*://}"
|
||||
url="${url#*@}"
|
||||
url="${url%%[/?#]*}"
|
||||
|
||||
[[ "$url" == *:* ]] && echo "${url#*:}" || echo ""
|
||||
}
|
||||
|
||||
# Extracts the path from a URL (without query or fragment; returns "/" if empty)
|
||||
@@ -268,25 +253,6 @@ migration_rename_config_key() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Download URL content directly
|
||||
download_to_stream() {
|
||||
local url="$1"
|
||||
local http_proxy_address="$2"
|
||||
local retries="${3:-3}"
|
||||
local wait="${4:-2}"
|
||||
|
||||
for attempt in $(seq 1 "$retries"); do
|
||||
if [ -n "$http_proxy_address" ]; then
|
||||
http_proxy="http://$http_proxy_address" https_proxy="http://$http_proxy_address" wget -qO- "$url" | sed 's/\r$//' && break
|
||||
else
|
||||
wget -qO- "$url" | sed 's/\r$//' && break
|
||||
fi
|
||||
|
||||
log "Attempt $attempt/$retries to download $url failed" "warn"
|
||||
sleep "$wait"
|
||||
done
|
||||
}
|
||||
|
||||
# Download URL to file
|
||||
download_to_file() {
|
||||
local url="$1"
|
||||
@@ -305,29 +271,17 @@ download_to_file() {
|
||||
log "Attempt $attempt/$retries to download $url failed" "warn"
|
||||
sleep "$wait"
|
||||
done
|
||||
|
||||
if grep -q $'\r' "$filepath"; then
|
||||
log "Downloaded file has Windows line endings (CRLF). Converting to Unix (LF)"
|
||||
sed -i 's/\r$//' "$filepath"
|
||||
fi
|
||||
}
|
||||
|
||||
# Decompiles a sing-box SRS binary file into a JSON ruleset file
|
||||
decompile_srs_file() {
|
||||
local binary_filepath="$1"
|
||||
local output_filepath="$2"
|
||||
# Converts Windows-style line endings (CRLF) to Unix-style (LF)
|
||||
convert_crlf_to_lf() {
|
||||
local filepath="$1"
|
||||
|
||||
log "Decompiling $binary_filepath to $output_filepath" "debug"
|
||||
|
||||
if ! file_exists "$binary_filepath"; then
|
||||
log "File $binary_filepath not found" "error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
sing-box rule-set decompile "$binary_filepath" -o "$output_filepath"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
log "Decompilation command failed for $binary_filepath" "error"
|
||||
return 1
|
||||
if grep -q $'\r' "$filepath"; then
|
||||
log "File '$filepath' contains CRLF line endings. Converting to LF..." "debug"
|
||||
local tmpfile
|
||||
tmpfile=$(mktemp)
|
||||
tr -d '\r' < "$filepath" > "$tmpfile" && mv "$tmpfile" "$filepath" || rm -f "$tmpfile"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -399,4 +353,4 @@ parse_domain_or_subnet_file_to_comma_string() {
|
||||
done < "$filepath"
|
||||
|
||||
echo "$result"
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,9 @@ nolog() {
|
||||
local timestamp
|
||||
timestamp=$(date +"%Y-%m-%d %H:%M:%S")
|
||||
|
||||
echo -e "${COLOR_CYAN}[$timestamp]${COLOR_RESET} ${COLOR_GREEN}$message${COLOR_RESET}"
|
||||
if [ -t 1 ]; then
|
||||
echo -e "${COLOR_CYAN}[$timestamp]${COLOR_RESET} ${COLOR_GREEN}$message${COLOR_RESET}"
|
||||
fi
|
||||
}
|
||||
|
||||
echolog() {
|
||||
|
||||
@@ -27,4 +27,44 @@ nft_add_set_elements() {
|
||||
local elements="$3"
|
||||
|
||||
nft add element inet "$table" "$set" "{ $elements }"
|
||||
}
|
||||
|
||||
nft_add_set_elements_from_file_chunked() {
|
||||
local filepath="$1"
|
||||
local nft_table_name="$2"
|
||||
local nft_set_name="$3"
|
||||
local chunk_size="${4:-5000}"
|
||||
|
||||
local array count
|
||||
count=0
|
||||
while IFS= read -r line; do
|
||||
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
|
||||
[ -z "$line" ] && continue
|
||||
|
||||
if ! is_ipv4 "$line" && ! is_ipv4_cidr "$line"; then
|
||||
log "'$line' is not IPv4 or IPv4 CIDR" "debug"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -z "$array" ]; then
|
||||
array="$line"
|
||||
else
|
||||
array="$array,$line"
|
||||
fi
|
||||
|
||||
count=$((count + 1))
|
||||
|
||||
if [ "$count" = "$chunk_size" ]; then
|
||||
log "Adding $count elements to nft set $nft_set_name" "debug"
|
||||
nft_add_set_elements "$nft_table_name" "$nft_set_name" "$array"
|
||||
array=""
|
||||
count=0
|
||||
fi
|
||||
done < "$filepath"
|
||||
|
||||
if [ -n "$array" ]; then
|
||||
log "Adding $count elements to nft set $nft_set_name" "debug"
|
||||
nft_add_set_elements "$nft_table_name" "$nft_set_name" "$array"
|
||||
fi
|
||||
}
|
||||
180
podkop/files/usr/lib/rulesets.sh
Normal file
180
podkop/files/usr/lib/rulesets.sh
Normal file
@@ -0,0 +1,180 @@
|
||||
# Constructs and returns a ruleset tag using section, name, optional type, and a fixed postfix
|
||||
get_ruleset_tag() {
|
||||
local section="$1"
|
||||
local name="$2"
|
||||
local type="$3"
|
||||
local postfix="ruleset"
|
||||
|
||||
if [ -n "$type" ]; then
|
||||
echo "$section-$name-$type-$postfix"
|
||||
else
|
||||
echo "$section-$name-$postfix"
|
||||
fi
|
||||
}
|
||||
|
||||
# Creates a new ruleset JSON file if it doesn't already exist
|
||||
create_source_rule_set() {
|
||||
local ruleset_filepath="$1"
|
||||
|
||||
if file_exists "$ruleset_filepath"; then
|
||||
return 3
|
||||
fi
|
||||
|
||||
jq -n '{version: 3, rules: []}' > "$ruleset_filepath"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Patch a source ruleset JSON file for sing-box by appending a new ruleset object containing the provided key
|
||||
# and value.
|
||||
# Arguments:
|
||||
# filepath: path to the JSON file to patch
|
||||
# key: the ruleset key to insert (e.g., "ip_cidr")
|
||||
# value: a JSON array of values to assign to the key
|
||||
# Example:
|
||||
# patch_source_ruleset_rules "/tmp/sing-box/ruleset.json" "ip_cidr" '["1.1.1.1","2.2.2.2"]'
|
||||
#######################################
|
||||
patch_source_ruleset_rules() {
|
||||
local filepath="$1"
|
||||
local key="$2"
|
||||
local value="$3"
|
||||
|
||||
local tmpfile=$(mktemp)
|
||||
|
||||
jq --arg key "$key" --argjson value "$value" \
|
||||
'( .rules | map(has($key)) | index(true) ) as $idx |
|
||||
if $idx != null then
|
||||
.rules[$idx][$key] = (.rules[$idx][$key] + $value | unique)
|
||||
else
|
||||
.rules += [{ ($key): $value }]
|
||||
end' "$filepath" > "$tmpfile"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
rm -f "$tmpfile"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mv "$tmpfile" "$filepath"
|
||||
}
|
||||
|
||||
# Imports a plain domain list into a ruleset in chunks, validating domains and appending them as domain_suffix rules
|
||||
import_plain_domain_list_to_local_source_ruleset_chunked() {
|
||||
local plain_list_filepath="$1"
|
||||
local ruleset_filepath="$2"
|
||||
local chunk_size="${3:-1000}"
|
||||
|
||||
local array count json_array
|
||||
count=0
|
||||
while IFS= read -r line; do
|
||||
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
|
||||
[ -z "$line" ] && continue
|
||||
|
||||
if ! is_domain_suffix "$line"; then
|
||||
log "'$line' is not a valid domain" "debug"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -z "$array" ]; then
|
||||
array="$line"
|
||||
else
|
||||
array="$array,$line"
|
||||
fi
|
||||
|
||||
count=$((count + 1))
|
||||
|
||||
if [ "$count" = "$chunk_size" ]; then
|
||||
log "Adding $count elements to rule set at $ruleset_filepath" "debug"
|
||||
json_array="$(comma_string_to_json_array "$array")"
|
||||
patch_source_ruleset_rules "$ruleset_filepath" "domain_suffix" "$json_array"
|
||||
array=""
|
||||
count=0
|
||||
fi
|
||||
done < "$plain_list_filepath"
|
||||
|
||||
if [ -n "$array" ]; then
|
||||
log "Adding $count elements to rule set at $ruleset_filepath" "debug"
|
||||
json_array="$(comma_string_to_json_array "$array")"
|
||||
patch_source_ruleset_rules "$ruleset_filepath" "domain_suffix" "$json_array"
|
||||
fi
|
||||
}
|
||||
|
||||
# Imports a plain IPv4/CIDR list into a ruleset in chunks, validating entries and appending them as ip_cidr rules
|
||||
import_plain_subnet_list_to_local_source_ruleset_chunked() {
|
||||
local plain_list_filepath="$1"
|
||||
local ruleset_filepath="$2"
|
||||
local chunk_size="${3:-1000}"
|
||||
|
||||
local array count json_array
|
||||
count=0
|
||||
while IFS= read -r line; do
|
||||
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
|
||||
[ -z "$line" ] && continue
|
||||
|
||||
if ! is_ipv4 "$line" && ! is_ipv4_cidr "$line"; then
|
||||
log "'$line' is not IPv4 or IPv4 CIDR" "debug"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -z "$array" ]; then
|
||||
array="$line"
|
||||
else
|
||||
array="$array,$line"
|
||||
fi
|
||||
|
||||
count=$((count + 1))
|
||||
|
||||
if [ "$count" = "$chunk_size" ]; then
|
||||
log "Adding $count elements to ruleset at $ruleset_filepath" "debug"
|
||||
json_array="$(comma_string_to_json_array "$array")"
|
||||
patch_source_ruleset_rules "$ruleset_filepath" "ip_cidr" "$json_array"
|
||||
array=""
|
||||
count=0
|
||||
fi
|
||||
done < "$plain_list_filepath"
|
||||
|
||||
if [ -n "$array" ]; then
|
||||
log "Adding $count elements to ruleset at $ruleset_filepath" "debug"
|
||||
json_array="$(comma_string_to_json_array "$array")"
|
||||
patch_source_ruleset_rules "$ruleset_filepath" "ip_cidr" "$json_array"
|
||||
fi
|
||||
}
|
||||
|
||||
# Determines the ruleset format based on the file extension (json → source, srs → binary)
|
||||
get_ruleset_format_by_file_extension() {
|
||||
local file_extension="$1"
|
||||
|
||||
local format
|
||||
case "$file_extension" in
|
||||
json) format="source" ;;
|
||||
srs) format="binary" ;;
|
||||
*)
|
||||
log "Unsupported file extension: .$file_extension" "error"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$format"
|
||||
}
|
||||
|
||||
# Decompiles a sing-box SRS binary file into a JSON ruleset file
|
||||
decompile_binary_ruleset() {
|
||||
local binary_filepath="$1"
|
||||
local output_filepath="$2"
|
||||
|
||||
log "Decompiling $binary_filepath to $output_filepath" "debug"
|
||||
sing-box rule-set decompile "$binary_filepath" -o "$output_filepath"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
log "Decompilation command failed for $binary_filepath" "error"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Extracts all ip_cidr entries from a JSON ruleset file and writes them to an output file.
|
||||
extract_ip_cidr_from_json_ruleset_to_file() {
|
||||
local json_file="$1"
|
||||
local output_file="$2"
|
||||
|
||||
log "Extracting ip_cidr entries from $json_file to $output_file" "debug"
|
||||
jq -r '.rules[].ip_cidr[]' "$json_file" > "$output_file"
|
||||
}
|
||||
@@ -64,7 +64,8 @@ sing_box_cf_add_proxy_outbound() {
|
||||
url=$(url_decode "$url")
|
||||
url=$(url_strip_fragment "$url")
|
||||
|
||||
local scheme="${url%%://*}"
|
||||
local scheme
|
||||
scheme="$(url_get_scheme "$url")"
|
||||
case "$scheme" in
|
||||
socks4 | socks4a | socks5)
|
||||
local tag host port version userinfo username password udp_over_tcp
|
||||
@@ -146,6 +147,21 @@ sing_box_cf_add_proxy_outbound() {
|
||||
config=$(_add_outbound_security "$config" "$tag" "$url")
|
||||
config=$(_add_outbound_transport "$config" "$tag" "$url")
|
||||
;;
|
||||
hysteria2 | hy2)
|
||||
local tag host port password obfuscator_type obfuscator_password upload_mbps download_mbps
|
||||
tag=$(get_outbound_tag_by_section "$section")
|
||||
host=$(url_get_host "$url")
|
||||
port="$(url_get_port "$url")"
|
||||
password=$(url_get_userinfo "$url")
|
||||
obfuscator_type=$(url_get_query_param "$url" "obfs")
|
||||
obfuscator_password=$(url_get_query_param "$url" "obfs-password")
|
||||
upload_mbps=$(url_get_query_param "$url" "upmbps")
|
||||
download_mbps=$(url_get_query_param "$url" "downmbps")
|
||||
|
||||
config=$(sing_box_cm_add_hysteria2_outbound "$config" "$tag" "$host" "$port" "$password" "$obfuscator_type" \
|
||||
"$obfuscator_password" "$upload_mbps" "$download_mbps")
|
||||
config=$(_add_outbound_security "$config" "$tag" "$url")
|
||||
;;
|
||||
*)
|
||||
log "Unsupported proxy $scheme type. Aborted." "fatal"
|
||||
exit 1
|
||||
@@ -160,18 +176,30 @@ _add_outbound_security() {
|
||||
local outbound_tag="$2"
|
||||
local url="$3"
|
||||
|
||||
local security
|
||||
local security scheme
|
||||
security=$(url_get_query_param "$url" "security")
|
||||
scheme="$(url_get_scheme "$url")"
|
||||
|
||||
if [ -z "$security" ]; then
|
||||
if [ "$scheme" = "hysteria2" ] || [ "$scheme" = "hy2" ]; then
|
||||
security="tls"
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$security" in
|
||||
tls | reality)
|
||||
local sni insecure alpn fingerprint public_key short_id
|
||||
sni=$(url_get_query_param "$url" "sni")
|
||||
insecure=$(url_get_query_param "$url" "allowInsecure")
|
||||
insecure=$(_get_insecure_query_param_from_url "$url")
|
||||
alpn=$(comma_string_to_json_array "$(url_get_query_param "$url" "alpn")")
|
||||
fingerprint=$(url_get_query_param "$url" "fp")
|
||||
public_key=$(url_get_query_param "$url" "pbk")
|
||||
short_id=$(url_get_query_param "$url" "sid")
|
||||
|
||||
if [ "$scheme" = "hysteria2" ] || [ "$scheme" = "hy2" ]; then
|
||||
fingerprint=""
|
||||
fi
|
||||
|
||||
config=$(
|
||||
sing_box_cm_set_tls_for_outbound \
|
||||
"$config" \
|
||||
@@ -193,6 +221,18 @@ _add_outbound_security() {
|
||||
echo "$config"
|
||||
}
|
||||
|
||||
_get_insecure_query_param_from_url() {
|
||||
local url="$1"
|
||||
|
||||
local insecure
|
||||
insecure=$(url_get_query_param "$url" "allowInsecure")
|
||||
if [ -z "$insecure" ]; then
|
||||
insecure=$(url_get_query_param "$url" "insecure")
|
||||
fi
|
||||
|
||||
echo "$insecure"
|
||||
}
|
||||
|
||||
_add_outbound_transport() {
|
||||
local config="$1"
|
||||
local outbound_tag="$2"
|
||||
@@ -214,7 +254,12 @@ _add_outbound_transport() {
|
||||
;;
|
||||
grpc)
|
||||
# TODO(ampetelin): Add handling of optional gRPC parameters; example links are needed.
|
||||
config=$(sing_box_cm_set_grpc_transport_for_outbound "$config" "$outbound_tag")
|
||||
local grpc_service_name
|
||||
grpc_service_name=$(url_get_query_param "$url" "serviceName")
|
||||
|
||||
config=$(
|
||||
sing_box_cm_set_grpc_transport_for_outbound "$config" "$outbound_tag" "$grpc_service_name"
|
||||
)
|
||||
;;
|
||||
*)
|
||||
log "Unknown transport '$transport' detected." "error"
|
||||
|
||||
@@ -21,9 +21,9 @@ SERVICE_TAG="__service_tag"
|
||||
#######################################
|
||||
# Configure the logging section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string, JSON configuration
|
||||
# disabled: boolean, true to disable logging
|
||||
# level: string, e.g., "info", "debug", "warn"
|
||||
# level: string, log level. One of: trace debug info warn error fatal panic.
|
||||
# timestamp: boolean, true to include timestamps
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
@@ -50,7 +50,7 @@ sing_box_cm_configure_log() {
|
||||
#######################################
|
||||
# Configure the DNS section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# final: string, default dns server tag
|
||||
# strategy: string, default domain strategy for resolving the domain names
|
||||
# independent_cache: boolean, whether to use an independent DNS cache
|
||||
@@ -82,12 +82,12 @@ sing_box_cm_configure_dns() {
|
||||
#######################################
|
||||
# Add a UDP DNS server to the DNS section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the DNS server
|
||||
# server_address: string, IP address or hostname of the DNS server
|
||||
# server_port: string or number, port of the DNS server
|
||||
# domain_resolver: string, domain resolver to use for resolving domain names
|
||||
# detour: string, tag of the upstream outbound
|
||||
# server_port: string or integer, port of the DNS server
|
||||
# domain_resolver: string, domain resolver to use for resolving domain names (optional)
|
||||
# detour: string, tag of the upstream outbound (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -122,12 +122,12 @@ sing_box_cm_add_udp_dns_server() {
|
||||
#######################################
|
||||
# Add a TLS DNS server to the DNS section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the DNS server
|
||||
# server_address: string, IP address or hostname of the DNS server
|
||||
# server_port: string or number, port of the DNS server
|
||||
# domain_resolver: string, domain resolver to use for resolving domain names
|
||||
# detour: string, tag of the upstream outbound
|
||||
# server_port: string or integer, port of the DNS server
|
||||
# domain_resolver: string, domain resolver to use for resolving domain names (optional)
|
||||
# detour: string, tag of the upstream outbound (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -162,14 +162,14 @@ sing_box_cm_add_tls_dns_server() {
|
||||
#######################################
|
||||
# Add an HTTPS DNS server to the DNS section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the DNS server
|
||||
# server_address: string, IP address or hostname of the DNS server
|
||||
# server_port: string or number, port of the DNS server
|
||||
# path: string, optional URL path for HTTPS DNS requests
|
||||
# headers: string, optional additional headers for HTTPS DNS requests
|
||||
# domain_resolver: string, domain resolver to use for resolving domain names
|
||||
# detour: string, tag of the upstream outbound
|
||||
# server_port: string or integer, port of the DNS server
|
||||
# path: string, URL path for HTTPS DNS requests (optional)
|
||||
# headers: string, additional headers for HTTPS DNS requests (optional)
|
||||
# domain_resolver: string, domain resolver to use for resolving domain names (optional)
|
||||
# detour: string, tag of the upstream outbound (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -210,7 +210,7 @@ sing_box_cm_add_https_dns_server() {
|
||||
#######################################
|
||||
# Add a FakeIP DNS server to the DNS section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the DNS server
|
||||
# inet4_range: string, IPv4 range used for fake IP mapping
|
||||
# Outputs:
|
||||
@@ -236,7 +236,7 @@ sing_box_cm_add_fakeip_dns_server() {
|
||||
#######################################
|
||||
# Add a DNS routing rule to the DNS section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# server: string, target DNS server for the rule
|
||||
# tag: string, identifier for the route rule
|
||||
# Outputs:
|
||||
@@ -263,10 +263,10 @@ sing_box_cm_add_dns_route_rule() {
|
||||
#######################################
|
||||
# Patch a DNS routing rule in the DNS section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier of the rule to patch
|
||||
# key: string, the key in the rule to update or add
|
||||
# value: JSON value to assign to the key
|
||||
# value: string, JSON value to assign to the key
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -304,9 +304,9 @@ sing_box_cm_patch_dns_route_rule() {
|
||||
#######################################
|
||||
# Add a DNS reject rule to the DNS section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# key: string, the key to set for the reject rule
|
||||
# value: JSON value to assign to the key
|
||||
# value: string, JSON value to assign to the key
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -331,10 +331,10 @@ sing_box_cm_add_dns_reject_rule() {
|
||||
#######################################
|
||||
# Add a TProxy inbound to the inbounds section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the inbound
|
||||
# listen_address: string, IP address to listen on
|
||||
# listen_port: number, port to listen on
|
||||
# listen_port: integer, port to listen on
|
||||
# tcp_fast_open: boolean, enable or disable TCP Fast Open
|
||||
# udp_fragment: boolean, enable or disable UDP fragmentation
|
||||
# Outputs:
|
||||
@@ -369,10 +369,10 @@ sing_box_cm_add_tproxy_inbound() {
|
||||
#######################################
|
||||
# Add a Direct inbound to the inbounds section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the inbound
|
||||
# listen_address: string, IP address to listen on
|
||||
# listen_port: number, port to listen on
|
||||
# listen_port: integer, port to listen on
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -399,10 +399,10 @@ sing_box_cm_add_direct_inbound() {
|
||||
#######################################
|
||||
# Add a Mixed inbound to the inbounds section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the inbound
|
||||
# listen_address: string, IP address to listen on
|
||||
# listen_port: number, port to listen on
|
||||
# listen_port: integer, port to listen on
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -429,7 +429,7 @@ sing_box_cm_add_mixed_inbound() {
|
||||
#######################################
|
||||
# Add a Direct outbound to the outbounds section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the outbound
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
@@ -451,15 +451,15 @@ sing_box_cm_add_direct_outbound() {
|
||||
#######################################
|
||||
# Add a SOCKS outbound to the outbounds section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the outbound
|
||||
# server_address: string, IP address or hostname of the SOCKS server
|
||||
# server_port: number, port of the SOCKS server
|
||||
# version: string, optional SOCKS version
|
||||
# username: string, optional username for authentication
|
||||
# password: string, optional password for authentication
|
||||
# network: string, optional network type (e.g., "tcp")
|
||||
# udp_over_tcp: number, optional version for UDP over TCP
|
||||
# server_port: integer, port of the SOCKS server
|
||||
# version: string, SOCKS version (optional)
|
||||
# username: string, username for authentication (optional)
|
||||
# password: string, password for authentication (optional)
|
||||
# network: string, network type (e.g., "tcp") (optional)
|
||||
# udp_over_tcp: integer, version for UDP over TCP (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -509,16 +509,16 @@ sing_box_cm_add_socks_outbound() {
|
||||
#######################################
|
||||
# Add a Shadowsocks outbound to the outbounds section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the outbound
|
||||
# server_address: string, IP address or hostname of the Shadowsocks server
|
||||
# server_port: number, port of the Shadowsocks server
|
||||
# server_port: integer, port of the Shadowsocks server
|
||||
# method: string, encryption method
|
||||
# password: string, password for encryption
|
||||
# network: string, optional network type (e.g., "tcp")
|
||||
# udp_over_tcp: number, optional version for UDP over TCP
|
||||
# plugin: string, optional plugin name
|
||||
# plugin_opts: string, optional plugin options
|
||||
# network: string, network type (e.g., "tcp") (optional)
|
||||
# udp_over_tcp: integer, version for UDP over TCP (optional)
|
||||
# plugin: string, plugin name (optional)
|
||||
# plugin_opts: string, plugin options (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -573,14 +573,14 @@ sing_box_cm_add_shadowsocks_outbound() {
|
||||
#######################################
|
||||
# Add a VLESS outbound to the outbounds section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the outbound
|
||||
# server_address: string, IP address or hostname of the VLESS server
|
||||
# server_port: number, port of the VLESS server
|
||||
# server_port: integer, port of the VLESS server
|
||||
# uuid: string, user UUID
|
||||
# flow: string, optional flow setting
|
||||
# network: string, optional network type (e.g., "tcp")
|
||||
# packet_encoding: string, optional packet encoding method
|
||||
# flow: string, flow setting (optional)
|
||||
# network: string, network type (e.g., "tcp") (optional)
|
||||
# packet_encoding: string, packet encoding method (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -624,12 +624,12 @@ sing_box_cm_add_vless_outbound() {
|
||||
#######################################
|
||||
# Add a Trojan outbound to the outbounds section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: string, JSON configuration
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the outbound
|
||||
# server_address: string, IP address or hostname of the Trojan server
|
||||
# server_port: number, port of the Trojan server
|
||||
# server_port: integer, port of the Trojan server
|
||||
# password: string, password for authentication
|
||||
# network: string, optional network type (e.g., "tcp")
|
||||
# network: string, network type (e.g., "tcp") (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -661,15 +661,92 @@ sing_box_cm_add_trojan_outbound() {
|
||||
)]'
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Add a Hysteria2 outbound to the outbounds section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the outbound
|
||||
# server_address: string, IP address or hostname of the Hysteria2 server
|
||||
# server_port: integer, port of the Hysteria2 server
|
||||
# password: string, password for authentication
|
||||
# obfuscator_type: string, obfuscation type (optional)
|
||||
# obfuscator_password: string, obfuscation password (optional)
|
||||
# upload_mbps: integer, upload bandwidth limit in Mbps (optional)
|
||||
# download_mbps: integer, download bandwidth limit in Mbps (optional)
|
||||
# network: string, network type (e.g., "udp") (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
# CONFIG=$(sing_box_cm_add_hysteria2_outbound "$CONFIG" "hysteria2-out" "example.com" 443 "supersecret" \
|
||||
# "salamander" "obfs-pass" "50" "200" "udp")
|
||||
#######################################
|
||||
sing_box_cm_add_hysteria2_outbound() {
|
||||
local config="$1"
|
||||
local tag="$2"
|
||||
local server_address="$3"
|
||||
local server_port="$4"
|
||||
local password="$5"
|
||||
local obfuscator_type="$6"
|
||||
local obfuscator_password="$7"
|
||||
local upload_mbps="$8"
|
||||
local download_mbps="$9"
|
||||
local network="${10}"
|
||||
|
||||
local is_multi_port="false"
|
||||
case "$server_port" in
|
||||
*","*|*"-"*) is_multi_port="true" ;;
|
||||
esac
|
||||
|
||||
echo "$config" | jq \
|
||||
--arg tag "$tag" \
|
||||
--arg server_address "$server_address" \
|
||||
--arg server_port "$server_port" \
|
||||
--arg password "$password" \
|
||||
--arg obfuscator_type "$obfuscator_type" \
|
||||
--arg obfuscator_password "$obfuscator_password" \
|
||||
--arg upload_mbps "$upload_mbps" \
|
||||
--arg download_mbps "$download_mbps" \
|
||||
--arg network "$network" \
|
||||
--argjson is_multi_port "$is_multi_port" \
|
||||
'
|
||||
def parse_port_entry:
|
||||
if index("-") then split("-") | join(":")
|
||||
else . + ":" + .
|
||||
end;
|
||||
|
||||
.outbounds += [(
|
||||
{
|
||||
type: "hysteria2",
|
||||
tag: $tag,
|
||||
server: $server_address,
|
||||
password: $password
|
||||
}
|
||||
+ (if $is_multi_port then {
|
||||
server_ports: ($server_port | split(",") | map(ltrimstr(" ") | rtrimstr(" ") | parse_port_entry))
|
||||
} else {
|
||||
server_port: ($server_port | tonumber)
|
||||
} end)
|
||||
+ (if $obfuscator_type != "" and $obfuscator_password != "" then {
|
||||
obfs: {
|
||||
type: $obfuscator_type,
|
||||
password: $obfuscator_password
|
||||
}
|
||||
} else {} end)
|
||||
+ (if $upload_mbps != "" then {up_mbps: ($upload_mbps | tonumber)} else {} end)
|
||||
+ (if $download_mbps != "" then {down_mbps: ($download_mbps | tonumber)} else {} end)
|
||||
+ (if $network != "" then {network: $network} else {} end)
|
||||
)]'
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Set gRPC transport settings for an outbound in a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier of the outbound to modify
|
||||
# service_name: string, optional gRPC service name
|
||||
# idle_timeout: string or number, optional idle timeout
|
||||
# ping_timeout: string or number, optional ping timeout
|
||||
# permit_without_stream: boolean, optional flag for permitting without stream
|
||||
# service_name: string, gRPC service name (optional)
|
||||
# idle_timeout: string or integer, idle timeout (optional)
|
||||
# ping_timeout: string or integer, ping timeout (optional)
|
||||
# permit_without_stream: boolean, flag for permitting without stream (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -709,12 +786,12 @@ sing_box_cm_set_grpc_transport_for_outbound() {
|
||||
#######################################
|
||||
# Set WebSocket transport settings for an outbound in a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier of the outbound to modify
|
||||
# path: string, WebSocket path
|
||||
# host: string, optional Host header for WebSocket
|
||||
# max_early_data: number, optional maximum early data
|
||||
# early_data_header_name: string, optional header name for early data
|
||||
# host: string, Host header for WebSocket (optional)
|
||||
# max_early_data: integer, maximum early data (optional)
|
||||
# early_data_header_name: string, header name for early data (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -759,14 +836,14 @@ sing_box_cm_set_ws_transport_for_outbound() {
|
||||
#######################################
|
||||
# Set TLS settings for an outbound in a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier of the outbound to modify
|
||||
# server_name: string, optional, used to verify the hostname on the returned certificates
|
||||
# insecure: boolean, accept any server certificate
|
||||
# alpn: JSON value or null, optional supported application level protocols
|
||||
# utls_fingerprint: string, optional uTLS fingerprint
|
||||
# reality_public_key: string, optional Reality public key
|
||||
# reality_short_id: string, optional Reality short ID
|
||||
# server_name: string, used to verify the hostname on the returned certificates (optional)
|
||||
# insecure: boolean, accept any server certificate (optional)
|
||||
# alpn: string, JSON value, supported application level protocols (optional)
|
||||
# utls_fingerprint: string, uTLS fingerprint (optional)
|
||||
# reality_public_key: string, Reality public key (optional)
|
||||
# reality_short_id: string, Reality short ID (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -825,7 +902,7 @@ sing_box_cm_set_tls_for_outbound() {
|
||||
#######################################
|
||||
# Add a Direct outbound with a specific network interface to a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the outbound
|
||||
# interface: string, network interface to bind the outbound
|
||||
# domain_resolver: string, tag of the domain resolver to be used for this outbound (optional)
|
||||
@@ -857,9 +934,9 @@ sing_box_cm_add_interface_outbound() {
|
||||
#######################################
|
||||
# Add a raw outbound JSON object to the outbounds section of a sing-box configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the outbound
|
||||
# raw_outbound: JSON object, the raw outbound configuration to add
|
||||
# raw_outbound: string, JSON object, the raw outbound configuration to add
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -881,14 +958,14 @@ sing_box_cm_add_raw_outbound() {
|
||||
#######################################
|
||||
# Add a URLTest outbound to the outbounds section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the URLTest outbound
|
||||
# outbounds: JSON array of outbound tags to test
|
||||
# url: URL to probe (optional)
|
||||
# interval: test interval (e.g., "10s") (optional)
|
||||
# tolerance: max latency difference tolerated (optional)
|
||||
# idle_timeout: idle timeout duration (optional)
|
||||
# interrupt_exist_connections: flag to interrupt existing connections ("true"/"false") (optional)
|
||||
# outbounds: string, JSON array of outbound tags to test
|
||||
# url: string, URL to probe (optional)
|
||||
# interval: string, test interval (e.g., "10s") (optional)
|
||||
# tolerance: string or integer, max latency difference tolerated (optional)
|
||||
# idle_timeout: string or integer, idle timeout duration (optional)
|
||||
# interrupt_exist_connections: boolean, flag to interrupt existing connections ("true"/"false") (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -929,11 +1006,11 @@ sing_box_cm_add_urltest_outbound() {
|
||||
#######################################
|
||||
# Add a Selector outbound to the outbounds section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the Selector outbound
|
||||
# outbounds: JSON array of outbound tags to choose from
|
||||
# default: default outbound tag if none selected (optional)
|
||||
# interrupt_exist_connections: flag to interrupt existing connections ("true"/"false") (optional)
|
||||
# outbounds: string (JSON), array of outbound tags to choose from
|
||||
# default: string, default outbound tag if none selected
|
||||
# interrupt_exist_connections: boolean, flag to interrupt existing connections ("true"/"false") (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -965,11 +1042,11 @@ sing_box_cm_add_selector_outbound() {
|
||||
#######################################
|
||||
# Configure the route section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# final: string, final outbound tag for unmatched traffic
|
||||
# auto_detect_interface: boolean, enable or disable automatic interface detection
|
||||
# default_domain_resolver: string, default DNS resolver for domain-based routing
|
||||
# default_interface: string, default network interface to use when auto detection is disabled
|
||||
# default_interface: string, default network interface to use when auto detection is disabled (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -1001,7 +1078,7 @@ sing_box_cm_configure_route() {
|
||||
#######################################
|
||||
# Add a routing rule to the route section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the route rule
|
||||
# inbound: string, inbound tag to match
|
||||
# outbound: string, outbound tag to route matched traffic to
|
||||
@@ -1029,13 +1106,49 @@ sing_box_cm_add_route_rule() {
|
||||
}]'
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Insert a resolve rule immediately before a route rule.
|
||||
# Copies rule_set from the target route rule.
|
||||
# Arguments:
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# route_rule_tag: string, tag of the route rule to precede
|
||||
# resolve_rule_tag: string, tag for the new resolve rule
|
||||
# server: string, DNS server tag (optional, default: "dns-server")
|
||||
# Outputs:
|
||||
# Updated JSON config to stdout
|
||||
#######################################
|
||||
sing_box_cm_add_resolve_rule() {
|
||||
local config="$1"
|
||||
local route_rule_tag="$2"
|
||||
local resolve_rule_tag="$3"
|
||||
local server="${4:-dns-server}"
|
||||
|
||||
echo "$config" | jq \
|
||||
--arg service_tag "$SERVICE_TAG" \
|
||||
--arg route_tag "$route_rule_tag" \
|
||||
--arg resolve_tag "$resolve_rule_tag" \
|
||||
--arg server "$server" \
|
||||
'.route.rules |= [
|
||||
.[] |
|
||||
if .[$service_tag] == $route_tag then
|
||||
{
|
||||
action: "resolve",
|
||||
rule_set: (.rule_set // []),
|
||||
server: $server,
|
||||
($service_tag): $resolve_tag
|
||||
}, .
|
||||
else .
|
||||
end
|
||||
]'
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Patch a routing rule in the route section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier of the route rule to patch
|
||||
# key: string, the key in the rule to update or add
|
||||
# value: JSON value to assign to the key
|
||||
# value: string (JSON), value to assign to the key
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -1071,9 +1184,9 @@ sing_box_cm_patch_route_rule() {
|
||||
#######################################
|
||||
# Add a reject rule to the route section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# key: string, the key to set for the reject rule
|
||||
# value: JSON value to assign to the key
|
||||
# value: string (JSON), value to assign to the key
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -1098,9 +1211,9 @@ sing_box_cm_add_reject_route_rule() {
|
||||
#######################################
|
||||
# Add a hijack-dns rule to the route section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# key: string, the key to set for the hijack-dns rule
|
||||
# value: JSON value to assign to the key
|
||||
# value: string (JSON), value to assign to the key
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -1125,7 +1238,7 @@ sing_box_cm_add_hijack_dns_route_rule() {
|
||||
#######################################
|
||||
# Add a route-options rule to the route section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the route-options rule
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
@@ -1148,9 +1261,9 @@ sing_box_cm_add_options_route_rule() {
|
||||
#######################################
|
||||
# Add a sniff rule to the route section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# key: string, the key to set for the sniff rule
|
||||
# value: JSON value to assign to the key
|
||||
# value: string (JSON), value to assign to the key
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -1176,7 +1289,7 @@ sing_box_cm_sniff_route_rule() {
|
||||
#######################################
|
||||
# Add an inline ruleset to the route.rule_set section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the inline ruleset
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
@@ -1198,10 +1311,10 @@ sing_box_cm_add_inline_ruleset() {
|
||||
#######################################
|
||||
# Add or update a rule in an inline ruleset within the route.rule_set section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier of the inline ruleset
|
||||
# key: string, the key in the ruleset to update or add
|
||||
# value: JSON value to assign to the key
|
||||
# value: string (JSON), value to assign to the key
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -1238,7 +1351,7 @@ sing_box_cm_add_inline_ruleset_rule() {
|
||||
#######################################
|
||||
# Add a local ruleset to the route.rule_set section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the local ruleset
|
||||
# format: string, format of the local ruleset ("source" or "binary")
|
||||
# path: string, file path to the local ruleset
|
||||
@@ -1269,12 +1382,12 @@ sing_box_cm_add_local_ruleset() {
|
||||
#######################################
|
||||
# Add a remote ruleset to the route.rule_set section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# tag: string, identifier for the remote ruleset
|
||||
# format: string, format of the remote ruleset ("source" or "binary")
|
||||
# url: string, URL to download the ruleset from
|
||||
# download_detour: string, optional detour tag for downloading
|
||||
# update_interval: string, optional update interval for the ruleset
|
||||
# download_detour: string, detour tag for downloading (optional)
|
||||
# update_interval: string, update interval for the ruleset (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -1310,7 +1423,7 @@ sing_box_cm_add_remote_ruleset() {
|
||||
#######################################
|
||||
# Configure the experimental cache_file section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# enabled: boolean, enable or disable file caching
|
||||
# path: string, file path for cache storage
|
||||
# store_fakeip: boolean, whether to store fake IPs in the cache
|
||||
@@ -1339,9 +1452,10 @@ sing_box_cm_configure_cache_file() {
|
||||
#######################################
|
||||
# Configure the experimental clash_api section of a sing-box JSON configuration.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# external_controller: API listening address; Clash API will be disabled if empty
|
||||
# external_ui: Optional path to static web resources to serve at http://{{external-controller}}/ui
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# external_controller: string, API listening address; Clash API will be disabled if empty
|
||||
# external_ui: string, path to static web resources to serve at http://{{external-controller}}/ui (optional)
|
||||
# secret: string, secret for the RESTful API Authenticate by specifying HTTP header (optional)
|
||||
# Outputs:
|
||||
# Writes updated JSON configuration to stdout
|
||||
# Example:
|
||||
@@ -1351,65 +1465,23 @@ sing_box_cm_configure_clash_api() {
|
||||
local config="$1"
|
||||
local external_controller="$2"
|
||||
local external_ui="$3"
|
||||
local secret="$4"
|
||||
|
||||
echo "$config" | jq \
|
||||
--arg external_controller "$external_controller" \
|
||||
--arg external_ui "$external_ui" \
|
||||
--arg secret "$secret" \
|
||||
'.experimental.clash_api = {
|
||||
external_controller: $external_controller,
|
||||
}
|
||||
+ (if $external_ui != "" then { external_ui: $external_ui } else {} end)'
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Create a local source ruleset JSON file for sing-box.
|
||||
# Arguments:
|
||||
# filepath: path to the JSON file to create
|
||||
# Example:
|
||||
# sing_box_cm_create_local_source_ruleset "/tmp/sing-box/ruleset.json"
|
||||
#######################################
|
||||
sing_box_cm_create_local_source_ruleset() {
|
||||
local filepath="$1"
|
||||
|
||||
jq -n '{version: 3, rules: []}' > "$filepath"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Patch a local source ruleset JSON file for sing-box by adding unique! values to a given key.
|
||||
# Arguments:
|
||||
# filepath: path to the JSON file to patch
|
||||
# key: the ruleset key to update (e.g., "ip_cidr")
|
||||
# value: a JSON array of values to add to the key
|
||||
# Example:
|
||||
# sing_box_cm_patch_local_source_ruleset_rules "/tmp/sing-box/ruleset.json" "ip_cidr" '["1.1.1.1","2.2.2.2"]'
|
||||
#######################################
|
||||
sing_box_cm_patch_local_source_ruleset_rules() {
|
||||
local filepath="$1"
|
||||
local key="$2"
|
||||
local value="$3"
|
||||
|
||||
value=$(_normalize_arg "$value")
|
||||
|
||||
local content
|
||||
content="$(cat "$filepath")"
|
||||
|
||||
echo "$content" | jq \
|
||||
--arg key "$key" \
|
||||
--argjson value "$value" '
|
||||
([.rules[]?[$key][]] | unique) as $existing
|
||||
| ($value - $existing) as $value
|
||||
| if ($value | length) > 0 then
|
||||
.rules += [{($key): $value}]
|
||||
else
|
||||
.
|
||||
end
|
||||
' > "$filepath"
|
||||
+ (if $external_ui != "" then { external_ui: $external_ui } else {} end)
|
||||
+ (if $secret != "" then { secret: $secret } else {} end)'
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Save a sing-box JSON configuration to a file, removing service-specific tags.
|
||||
# Arguments:
|
||||
# config: JSON configuration (string)
|
||||
# config: string (JSON), sing-box configuration to modify
|
||||
# file_path: string, path to save the configuration file
|
||||
# Outputs:
|
||||
# Writes the cleaned JSON configuration to the specified file
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM openwrt/sdk:x86-64-SNAPSHOT
|
||||
FROM openwrt/sdk:x86_64-v25.12.3
|
||||
WORKDIR /builder
|
||||
RUN ./setup.sh \
|
||||
&& ./scripts/feeds update -a \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM openwrt/sdk:x86_64-v24.10.3
|
||||
FROM openwrt/sdk:x86_64-v24.10.6
|
||||
WORKDIR /builder
|
||||
RUN ./scripts/feeds update -a \
|
||||
&& ./scripts/feeds install luci-base \
|
||||
|
||||
0
src/backend/.keep
Normal file
0
src/backend/.keep
Normal file
4
src/frontend/.gitignore
vendored
Normal file
4
src/frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.vite/
|
||||
.env
|
||||
12
src/frontend/package.json
Normal file
12
src/frontend/package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "podkop-frontend",
|
||||
"version": "0.8.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev:sandbox": "cd sandbox/docker && ./up.sh",
|
||||
"sandbox:down": "cd sandbox/docker && docker compose down",
|
||||
"sandbox:logs": "cd sandbox/docker && docker compose logs -f",
|
||||
"sandbox:shell": "docker exec -it podkop-dev-router sh",
|
||||
"sandbox:scenario": "cd sandbox/docker && ./scenario.sh"
|
||||
}
|
||||
}
|
||||
32
src/frontend/sandbox/Makefile
Normal file
32
src/frontend/sandbox/Makefile
Normal file
@@ -0,0 +1,32 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-podkop
|
||||
|
||||
PKG_VERSION := $(if $(PODKOP_VERSION),$(PODKOP_VERSION),0.$(shell date +%d%m%Y))
|
||||
|
||||
PKG_RELEASE:=1
|
||||
|
||||
LUCI_TITLE:=LuCI podkop app
|
||||
# +podkop dropped while the backend is rewritten in Go; the frontend runs
|
||||
# against a stub (docker/files/usr/bin/podkop). Restore before release.
|
||||
LUCI_DEPENDS:=+luci-base
|
||||
LUCI_PKGARCH:=all
|
||||
LUCI_LANG.ru:=Русский (Russian)
|
||||
LUCI_LANG.en:=English
|
||||
|
||||
PKG_LICENSE:=GPL-2.0-or-later
|
||||
PKG_MAINTAINER:=ITDog <podkop@itdog.info>
|
||||
|
||||
LUCI_LANGUAGES:=en ru
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)$(HTDOCS)
|
||||
$(CP) $(PKG_BUILD_DIR)/htdocs/* $(1)$(HTDOCS)/
|
||||
$(INSTALL_DIR) $(1)/
|
||||
$(CP) $(PKG_BUILD_DIR)/root/* $(1)/
|
||||
sed -i -e 's/__COMPILED_VERSION_VARIABLE__/$(PKG_VERSION)/g' $(1)$(HTDOCS)/luci-static/resources/view/podkop/main.js || true
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
26
src/frontend/sandbox/docker/Dockerfile
Normal file
26
src/frontend/sandbox/docker/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
# Dev stand for luci-app-podkop: stock OpenWrt userland + real LuCI.
|
||||
#
|
||||
# The app is loaded by LuCI's own require loader and reaches the system through
|
||||
# rpcd with real ACL checks — neither is exercised by a JS-level mock.
|
||||
# No backend: /usr/bin/podkop is a shell stub serving fixtures.
|
||||
ARG OPENWRT_TAG=x86-64-25.12.4
|
||||
FROM openwrt/rootfs:${OPENWRT_TAG}
|
||||
|
||||
# Absent from the rootfs tarball; on a router procd creates them under the /var
|
||||
# tmpfs. apk needs them earlier.
|
||||
RUN mkdir -p /var/lock /var/run /var/log /var/state
|
||||
|
||||
# firewall/opkg are not about podkop — a page in an empty menu shows nothing
|
||||
# about how the app sits next to its neighbours.
|
||||
RUN set -eu; \
|
||||
apk update; \
|
||||
for p in luci luci-app-firewall luci-app-opkg; do \
|
||||
apk add "$p" || echo "skipped (not in feed): $p"; \
|
||||
done
|
||||
|
||||
COPY files/ /
|
||||
RUN chmod +x /usr/bin/podkop /etc/init.d/podkop /usr/lib/podkop-dev/entrypoint.sh
|
||||
|
||||
EXPOSE 80
|
||||
ENTRYPOINT ["/usr/lib/podkop-dev/entrypoint.sh"]
|
||||
CMD ["/sbin/init"]
|
||||
73
src/frontend/sandbox/docker/README.md
Normal file
73
src/frontend/sandbox/docker/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Dev stand for luci-app-podkop
|
||||
|
||||
Real OpenWrt 25.12 with real LuCI in a container. The app is mounted from the
|
||||
host, so the loop is: edit js, hit F5.
|
||||
|
||||
## Run
|
||||
|
||||
```sh
|
||||
yarn dev:sandbox # from src/frontend
|
||||
./up.sh # or directly from here
|
||||
```
|
||||
|
||||
UI: <http://localhost:8080> → **Services → Podkop** (`root`, empty password).
|
||||
Stop with `docker compose down`.
|
||||
|
||||
Use `up.sh`, not `docker compose up`: `openwrt/rootfs` has no multiarch
|
||||
manifest, and the arm image declares `aarch64_generic` rather than `arm64`.
|
||||
`up.sh` derives both the tag and the platform from `uname -m`.
|
||||
|
||||
## Why a container instead of a browser mock
|
||||
|
||||
A JS mock only exercises our own code. Here everything below the app is real:
|
||||
|
||||
| mocked in the browser | real here |
|
||||
| --- | --- |
|
||||
| `fs.exec` | rpcd + **ACL checks** from `acl.d/luci-app-podkop.json` |
|
||||
| `uci` | uci over `/etc/config/podkop` |
|
||||
| `_()` | translations loaded from `.po` |
|
||||
| theme | `cascade.css` served by LuCI |
|
||||
| "the file parses" | **LuCI's require loader** |
|
||||
|
||||
The last two are the point. An ACL mistake returns 403 only on a router.
|
||||
|
||||
## What is not here
|
||||
|
||||
**The backend.** It is being rewritten in Go; `/usr/bin/podkop` and
|
||||
`/etc/init.d/podkop` are shell stubs serving fixtures. The mock boundary is the
|
||||
binary, not the JS, so the whole frontend above it stays real.
|
||||
|
||||
**Traffic interception.** `kmod-nft-tproxy` is built against OpenWrt's kernel
|
||||
and cannot load in a container. `check_nft_rules` reports some rules missing on
|
||||
purpose — that is the stand's honest state.
|
||||
|
||||
**The luci-app version.** The package Makefile substitutes it at build time;
|
||||
here the js is mounted as-is, so diagnostics shows
|
||||
`__COMPILED_VERSION_VARIABLE__`.
|
||||
|
||||
## Scenarios
|
||||
|
||||
```sh
|
||||
./scenario.sh # current
|
||||
./scenario.sh ok # everything works
|
||||
./scenario.sh fail # commands fail — exercises failed states
|
||||
./scenario.sh stopped # services down, Clash unreachable
|
||||
```
|
||||
|
||||
Reload the page afterwards.
|
||||
|
||||
## Layout
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| `Dockerfile` | OpenWrt 25.12.4 + luci (+ firewall/opkg for menu neighbours) |
|
||||
| `compose.yml` | `router` (8080) and `clash-mock` (9090) |
|
||||
| `files/usr/bin/podkop` | backend stub |
|
||||
| `files/etc/init.d/podkop` | service stub for Start/Stop/Restart |
|
||||
| `files/etc/config/podkop` | uci fixture, one section per parse branch |
|
||||
| `files/etc/rc.local` | seeds runtime state after boot |
|
||||
| `clash-mock/server.mjs` | `/traffic` and `/connections` websockets |
|
||||
|
||||
`/etc/config/podkop` is required: `menu.d` declares
|
||||
`"depends": { "uci": { "podkop": true } }`, so without it LuCI hides the menu
|
||||
entry entirely.
|
||||
109
src/frontend/sandbox/docker/clash-mock/server.mjs
Normal file
109
src/frontend/sandbox/docker/clash-mock/server.mjs
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Clash API metrics for the dev stand: /traffic and /connections on :9090.
|
||||
*
|
||||
* Handshake and framing are written by hand so node:22-alpine needs no
|
||||
* npm install.
|
||||
*/
|
||||
import { createHash } from 'node:crypto';
|
||||
import { createServer } from 'node:http';
|
||||
|
||||
const PORT = 9090;
|
||||
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
||||
|
||||
/** Text frame: FIN + opcode 1, unmasked (servers do not mask). */
|
||||
function encodeFrame(text) {
|
||||
const payload = Buffer.from(text, 'utf8');
|
||||
const length = payload.length;
|
||||
|
||||
let header;
|
||||
|
||||
if (length < 126) {
|
||||
header = Buffer.from([0x81, length]);
|
||||
} else if (length < 65536) {
|
||||
header = Buffer.alloc(4);
|
||||
header[0] = 0x81;
|
||||
header[1] = 126;
|
||||
header.writeUInt16BE(length, 2);
|
||||
} else {
|
||||
header = Buffer.alloc(10);
|
||||
header[0] = 0x81;
|
||||
header[1] = 127;
|
||||
header.writeBigUInt64BE(BigInt(length), 2);
|
||||
}
|
||||
|
||||
return Buffer.concat([header, payload]);
|
||||
}
|
||||
|
||||
const state = {
|
||||
uploadTotal: 4_800_000_000,
|
||||
downloadTotal: 51_200_000_000,
|
||||
};
|
||||
|
||||
function trafficFrame() {
|
||||
return {
|
||||
up: Math.floor(20_000 + Math.random() * 900_000),
|
||||
down: Math.floor(80_000 + Math.random() * 6_000_000),
|
||||
};
|
||||
}
|
||||
|
||||
function connectionsFrame() {
|
||||
state.uploadTotal += Math.floor(Math.random() * 900_000);
|
||||
state.downloadTotal += Math.floor(Math.random() * 6_000_000);
|
||||
|
||||
return {
|
||||
uploadTotal: state.uploadTotal,
|
||||
downloadTotal: state.downloadTotal,
|
||||
memory: Math.floor(28_000_000 + Math.random() * 12_000_000),
|
||||
connections: Array.from(
|
||||
{ length: 8 + Math.floor(Math.random() * 24) },
|
||||
(_unused, index) => ({ id: `conn-${index}` }),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const server = createServer((req, res) => {
|
||||
// The dashboard only uses the websocket; GET just needs to say something
|
||||
res.writeHead(200, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ hello: 'podkop dev clash mock', path: req.url }));
|
||||
});
|
||||
|
||||
server.on('upgrade', (req, socket) => {
|
||||
const key = req.headers['sec-websocket-key'];
|
||||
|
||||
if (!key) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const accept = createHash('sha1')
|
||||
.update(key + GUID)
|
||||
.digest('base64');
|
||||
|
||||
socket.write(
|
||||
'HTTP/1.1 101 Switching Protocols\r\n' +
|
||||
'Upgrade: websocket\r\n' +
|
||||
'Connection: Upgrade\r\n' +
|
||||
`Sec-WebSocket-Accept: ${accept}\r\n\r\n`,
|
||||
);
|
||||
|
||||
const path = (req.url || '').split('?')[0];
|
||||
const build = path === '/traffic' ? trafficFrame : connectionsFrame;
|
||||
|
||||
console.log(`[clash-mock] connected: ${path}`);
|
||||
|
||||
const timer = setInterval(() => {
|
||||
socket.write(encodeFrame(JSON.stringify(build())));
|
||||
}, 1000);
|
||||
|
||||
const stop = () => {
|
||||
clearInterval(timer);
|
||||
socket.destroy();
|
||||
};
|
||||
|
||||
socket.on('close', stop);
|
||||
socket.on('error', stop);
|
||||
});
|
||||
|
||||
server.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`[clash-mock] listening on :${PORT} (/traffic, /connections)`);
|
||||
});
|
||||
37
src/frontend/sandbox/docker/compose.yml
Normal file
37
src/frontend/sandbox/docker/compose.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
name: podkop-dev
|
||||
|
||||
services:
|
||||
router:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
# No multiarch manifest — one tag per arch. Set by ./up.sh.
|
||||
OPENWRT_TAG: ${OPENWRT_TAG:-x86-64-25.12.4}
|
||||
# The arm image declares OpenWrt's own arch name (aarch64_generic),
|
||||
# which Docker does not match to linux/arm64 on its own.
|
||||
platform: ${OPENWRT_PLATFORM:-linux/amd64}
|
||||
container_name: podkop-dev-router
|
||||
hostname: OpenWrt
|
||||
privileged: true # procd as PID 1
|
||||
init: false
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '8080:80'
|
||||
volumes:
|
||||
# Mounted from the host: edit js, hit F5, no rebuild.
|
||||
- ../htdocs/luci-static/resources/view/podkop:/www/luci-static/resources/view/podkop:ro
|
||||
- ../root/usr/share/luci/menu.d/luci-app-podkop.json:/usr/share/luci/menu.d/luci-app-podkop.json:ro
|
||||
- ../root/usr/share/rpcd/acl.d/luci-app-podkop.json:/usr/share/rpcd/acl.d/luci-app-podkop.json:ro
|
||||
|
||||
# Nothing in the OpenWrt container can serve a websocket (sing-box is
|
||||
# deliberately absent), so Clash API metrics live in their own service.
|
||||
clash-mock:
|
||||
image: node:22-alpine
|
||||
container_name: podkop-dev-clash
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '9090:9090'
|
||||
volumes:
|
||||
- ./clash-mock:/app:ro
|
||||
command: ['node', '/app/server.mjs']
|
||||
47
src/frontend/sandbox/docker/files/etc/config/podkop
Normal file
47
src/frontend/sandbox/docker/files/etc/config/podkop
Normal file
@@ -0,0 +1,47 @@
|
||||
# uci fixture for the dev stand.
|
||||
#
|
||||
# Required: menu.d declares "depends": { "uci": { "podkop": true } }, so LuCI
|
||||
# hides the menu entry until this file exists.
|
||||
#
|
||||
# Sections cover every parse branch on the dashboard — url / outbound /
|
||||
# selector / urltest / vpn — plus block and exclusion, which must be filtered out.
|
||||
|
||||
config settings 'settings'
|
||||
option enable_yacd '1'
|
||||
option yacd_secret_key 'dev-stand-secret'
|
||||
option dns_type 'doh'
|
||||
option dns_server 'dns.adguard-dns.com'
|
||||
|
||||
config section 'youtube'
|
||||
option connection_type 'proxy'
|
||||
option proxy_config_type 'url'
|
||||
option proxy_string 'vless://uuid@example.com:443?type=tcp#YouTube%20VLESS'
|
||||
list domain_list 'youtube'
|
||||
|
||||
config section 'netflix'
|
||||
option connection_type 'proxy'
|
||||
option proxy_config_type 'outbound'
|
||||
option outbound_json '{"tag":"Netflix%20Shadowsocks","type":"shadowsocks"}'
|
||||
|
||||
config section 'media'
|
||||
option connection_type 'proxy'
|
||||
option proxy_config_type 'selector'
|
||||
list selector_proxy_links 'vless://uuid@nl.example.com:443#Netherlands'
|
||||
list selector_proxy_links 'vless://uuid@de.example.com:443#Germany'
|
||||
list selector_proxy_links 'trojan://uuid@fi.example.com:443#Finland'
|
||||
|
||||
config section 'fast'
|
||||
option connection_type 'proxy'
|
||||
option proxy_config_type 'urltest'
|
||||
list urltest_proxy_links 'vless://uuid@se.example.com:443#Sweden'
|
||||
list urltest_proxy_links 'vless://uuid@pl.example.com:443#Poland'
|
||||
|
||||
config section 'home_vpn'
|
||||
option connection_type 'vpn'
|
||||
option interface 'wg0'
|
||||
|
||||
config section 'ads'
|
||||
option connection_type 'block'
|
||||
|
||||
config section 'local'
|
||||
option connection_type 'exclusion'
|
||||
9
src/frontend/sandbox/docker/files/etc/config/system
Normal file
9
src/frontend/sandbox/docker/files/etc/config/system
Normal file
@@ -0,0 +1,9 @@
|
||||
config system
|
||||
option hostname 'OpenWrt'
|
||||
option timezone 'UTC'
|
||||
option ttylogin '0'
|
||||
option log_size '64'
|
||||
option urandom_seed '0'
|
||||
|
||||
config timeserver 'ntp'
|
||||
option enabled '0'
|
||||
32
src/frontend/sandbox/docker/files/etc/init.d/podkop
Executable file
32
src/frontend/sandbox/docker/files/etc/init.d/podkop
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Service stub. No daemon behind it — just enough for the Start/Stop/Restart/
|
||||
# Enable/Disable buttons to go through rpcd and change observable state.
|
||||
START=99
|
||||
USE_PROCD=0
|
||||
|
||||
STATE=/tmp/podkop-service-state
|
||||
|
||||
start() {
|
||||
echo running >"$STATE"
|
||||
echo '{"result":"started"}'
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo stopped >"$STATE"
|
||||
echo '{"result":"stopped"}'
|
||||
}
|
||||
|
||||
restart() {
|
||||
echo running >"$STATE"
|
||||
echo '{"result":"restarted"}'
|
||||
}
|
||||
|
||||
enable() {
|
||||
touch /etc/rc.d/S99podkop 2>/dev/null
|
||||
echo '{"result":"enabled"}'
|
||||
}
|
||||
|
||||
disable() {
|
||||
rm -f /etc/rc.d/S99podkop 2>/dev/null
|
||||
echo '{"result":"disabled"}'
|
||||
}
|
||||
9
src/frontend/sandbox/docker/files/etc/rc.local
Normal file
9
src/frontend/sandbox/docker/files/etc/rc.local
Normal file
@@ -0,0 +1,9 @@
|
||||
# Seed runtime state for the backend stub.
|
||||
#
|
||||
# Not in the entrypoint: that runs before procd, which mounts a tmpfs over /tmp
|
||||
# and wipes anything written there earlier.
|
||||
|
||||
echo ok >/tmp/podkop-scenario
|
||||
echo running >/tmp/podkop-service-state
|
||||
|
||||
exit 0
|
||||
203
src/frontend/sandbox/docker/files/usr/bin/podkop
Executable file
203
src/frontend/sandbox/docker/files/usr/bin/podkop
Executable file
@@ -0,0 +1,203 @@
|
||||
#!/bin/sh
|
||||
# Backend stub for the dev stand. The real backend is being rewritten in Go.
|
||||
#
|
||||
# The mock boundary sits here rather than in JS, so rpcd, ACL, fs.exec, uci and
|
||||
# LuCI's require loader all stay real.
|
||||
#
|
||||
# Commands mirror what main.js actually calls, nothing more.
|
||||
#
|
||||
# Scenario is read from a file (see docker/scenario.sh):
|
||||
# ok everything works (default)
|
||||
# fail commands fail with empty stdout
|
||||
# stopped services down, Clash unreachable
|
||||
SCENARIO_FILE=/tmp/podkop-scenario
|
||||
SCENARIO=$(cat "$SCENARIO_FILE" 2>/dev/null || echo ok)
|
||||
|
||||
# Latency tests and node selection must persist between calls
|
||||
LATENCY_DIR=/tmp/podkop-latency
|
||||
SELECTED_DIR=/tmp/podkop-selected
|
||||
mkdir -p "$LATENCY_DIR" "$SELECTED_DIR"
|
||||
|
||||
# Empty stdout is how the frontend detects failure
|
||||
die_if_failing() {
|
||||
[ "$SCENARIO" = "fail" ] && exit 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# 40..2000 ms. Not `date +%s%N` (%N is not portable, emits a literal "N") and
|
||||
# not `od` (absent from the OpenWrt image; empty output pinned every node to 40).
|
||||
rand_latency() {
|
||||
_r="$RANDOM"
|
||||
|
||||
if [ -z "$_r" ]; then
|
||||
_r=$(awk 'BEGIN{srand();print int(rand()*32768)}')
|
||||
fi
|
||||
|
||||
echo $((_r % 1960 + 40))
|
||||
}
|
||||
|
||||
latency_of() { # $1=code $2=fallback
|
||||
if [ -f "$LATENCY_DIR/$1" ]; then cat "$LATENCY_DIR/$1"; else echo "$2"; fi
|
||||
}
|
||||
|
||||
selected_of() { # $1=group $2=fallback
|
||||
if [ -f "$SELECTED_DIR/$1" ]; then cat "$SELECTED_DIR/$1"; else echo "$2"; fi
|
||||
}
|
||||
|
||||
history_of() { # $1=delay
|
||||
if [ "$1" -eq 0 ]; then
|
||||
echo '[]'
|
||||
else
|
||||
echo "[{\"time\":\"2026-07-20T10:00:00Z\",\"delay\":$1}]"
|
||||
fi
|
||||
}
|
||||
|
||||
proxy_json() { # $1=name $2=type $3=delay
|
||||
printf '"%s":{"type":"%s","name":"%s","udp":true,"history":%s}' \
|
||||
"$1" "$2" "$1" "$(history_of "$3")"
|
||||
}
|
||||
|
||||
group_json() { # $1=name $2=type $3=now $4=all(json) $5=delay
|
||||
printf '"%s":{"type":"%s","name":"%s","udp":false,"now":"%s","all":%s,"history":%s}' \
|
||||
"$1" "$2" "$1" "$3" "$4" "$(history_of "$5")"
|
||||
}
|
||||
|
||||
# Mirrors the sections in /etc/config/podkop; covers every parse branch on the
|
||||
# dashboard: url / outbound / selector / urltest / vpn.
|
||||
emit_proxies() {
|
||||
printf '{"proxies":{'
|
||||
proxy_json 'youtube-out' 'vless' "$(latency_of youtube-out 142)"
|
||||
printf ','
|
||||
proxy_json 'netflix-out' 'shadowsocks' "$(latency_of netflix-out 980)"
|
||||
printf ','
|
||||
group_json 'media-out' 'Selector' "$(selected_of media-out 'media-2-out')" \
|
||||
'["media-1-out","media-2-out","media-3-out"]' 0
|
||||
printf ','
|
||||
proxy_json 'media-1-out' 'vless' "$(latency_of media-1-out 210)"
|
||||
printf ','
|
||||
proxy_json 'media-2-out' 'vless' "$(latency_of media-2-out 1320)"
|
||||
printf ','
|
||||
proxy_json 'media-3-out' 'trojan' "$(latency_of media-3-out 2400)"
|
||||
printf ','
|
||||
group_json 'fast-out' 'Selector' "$(selected_of fast-out 'fast-urltest-out')" \
|
||||
'["fast-urltest-out","fast-1-out","fast-2-out"]' 0
|
||||
printf ','
|
||||
group_json 'fast-urltest-out' 'URLTest' '' \
|
||||
'["fast-1-out","fast-2-out"]' "$(latency_of fast-urltest-out 96)"
|
||||
printf ','
|
||||
proxy_json 'fast-1-out' 'vless' "$(latency_of fast-1-out 96)"
|
||||
printf ','
|
||||
proxy_json 'fast-2-out' 'vless' "$(latency_of fast-2-out 0)"
|
||||
printf ','
|
||||
proxy_json 'home_vpn-out' 'wireguard' "$(latency_of home_vpn-out 18)"
|
||||
printf '}}\n'
|
||||
}
|
||||
|
||||
members_of() {
|
||||
case "$1" in
|
||||
media-out) echo 'media-1-out media-2-out media-3-out' ;;
|
||||
fast-out) echo 'fast-urltest-out fast-1-out fast-2-out' ;;
|
||||
fast-urltest-out) echo 'fast-1-out fast-2-out' ;;
|
||||
*) echo '' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
clash_api() {
|
||||
[ "$SCENARIO" = "stopped" ] && exit 1
|
||||
|
||||
case "$1" in
|
||||
get_proxies)
|
||||
emit_proxies
|
||||
;;
|
||||
get_proxy_latency)
|
||||
delay=$(rand_latency)
|
||||
echo "$delay" >"$LATENCY_DIR/$2"
|
||||
echo "{\"delay\":$delay}"
|
||||
;;
|
||||
get_group_latency)
|
||||
i=0
|
||||
printf '{'
|
||||
for member in $(members_of "$2"); do
|
||||
i=$((i + 1))
|
||||
delay=$(rand_latency)
|
||||
echo "$delay" >"$LATENCY_DIR/$member"
|
||||
[ "$i" -gt 1 ] && printf ','
|
||||
printf '"%s":%s' "$member" "$delay"
|
||||
done
|
||||
printf '}\n'
|
||||
;;
|
||||
set_group_proxy)
|
||||
echo "$3" >"$SELECTED_DIR/$2"
|
||||
echo '{"ok":true}'
|
||||
;;
|
||||
*)
|
||||
echo "{\"error\":\"unknown clash_api method: $1\"}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
die_if_failing
|
||||
|
||||
case "$1" in
|
||||
get_status)
|
||||
# Reads the autostart flag so Enable/Disable have a visible effect
|
||||
if [ "$SCENARIO" = "stopped" ] || [ ! -e /etc/rc.d/S99podkop ]; then
|
||||
echo '{"enabled":0,"status":"disabled"}'
|
||||
else
|
||||
echo '{"enabled":1,"status":"enabled"}'
|
||||
fi
|
||||
;;
|
||||
|
||||
get_sing_box_status)
|
||||
# Same for Start/Stop/Restart
|
||||
if [ "$SCENARIO" = "stopped" ] ||
|
||||
[ "$(cat /tmp/podkop-service-state 2>/dev/null)" = "stopped" ]; then
|
||||
echo '{"running":0,"enabled":0,"status":"stopped"}'
|
||||
else
|
||||
echo '{"running":1,"enabled":1,"status":"running"}'
|
||||
fi
|
||||
;;
|
||||
|
||||
get_system_info)
|
||||
echo '{"podkop_version":"0.8.1-dev","podkop_latest_version":"0.8.2","luci_app_version":"0.8.1-dev","sing_box_version":"1.11.4","openwrt_version":"OpenWrt 25.12.4","device_model":"Docker dev stand"}'
|
||||
;;
|
||||
|
||||
check_dns_available)
|
||||
echo '{"dns_type":"doh","dns_server":"dns.adguard-dns.com","dns_status":1,"dns_on_router":1,"bootstrap_dns_server":"1.1.1.1","bootstrap_dns_status":1,"dhcp_config_status":1}'
|
||||
;;
|
||||
|
||||
check_nft_rules)
|
||||
# No tproxy here: OpenWrt kernel modules cannot load in a container, so some
|
||||
# rules are reported missing on purpose.
|
||||
echo '{"table_exist":1,"rules_mangle_exist":1,"rules_mangle_counters":1,"rules_mangle_output_exist":1,"rules_mangle_output_counters":0,"rules_proxy_exist":1,"rules_proxy_counters":0,"rules_other_mark_exist":0}'
|
||||
;;
|
||||
|
||||
check_sing_box)
|
||||
echo '{"sing_box_installed":1,"sing_box_version_ok":1,"sing_box_service_exist":1,"sing_box_autostart_disabled":1,"sing_box_process_running":1,"sing_box_ports_listening":1}'
|
||||
;;
|
||||
|
||||
check_fakeip)
|
||||
echo '{"fakeip":true,"IP":"198.18.0.1"}'
|
||||
;;
|
||||
|
||||
check_logs)
|
||||
echo "$(date '+%a %b %e %H:%M:%S %Y') [info] podkop dev stand: backend stub"
|
||||
echo "$(date '+%a %b %e %H:%M:%S %Y') [info] scenario: $SCENARIO"
|
||||
;;
|
||||
|
||||
global_check)
|
||||
echo '{"result":"ok","note":"dev stand"}'
|
||||
;;
|
||||
|
||||
show_sing_box_config)
|
||||
echo '{"log":{"level":"info"},"experimental":{"clash_api":{"external_controller":"0.0.0.0:9090"}},"note":"dev stand stub"}'
|
||||
;;
|
||||
|
||||
clash_api)
|
||||
clash_api "$2" "$3" "$4"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "{\"error\":\"unknown method: $1\"}"
|
||||
;;
|
||||
esac
|
||||
58
src/frontend/sandbox/docker/files/usr/lib/podkop-dev/entrypoint.sh
Executable file
58
src/frontend/sandbox/docker/files/usr/lib/podkop-dev/entrypoint.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/bin/sh
|
||||
# Prepare the stand, then hand over to procd.
|
||||
#
|
||||
# Networking has to happen HERE, before `exec /sbin/init`: config_generate
|
||||
# writes its own /etc/config/network from board.json (br-lan, hardcoded
|
||||
# 192.168.1.1), netifd applies it and Docker's address is gone. By the time
|
||||
# uci-defaults run, eth0 is already down and flushed.
|
||||
#
|
||||
# config_generate's guard is `[ -s /etc/config/network -a -s /etc/config/system ]`,
|
||||
# so both files must exist — hence /etc/config/system in the image.
|
||||
#
|
||||
# The interface is named 'lan' because OpenWrt's default firewall allows input
|
||||
# in that zone; that is what makes port 80 reachable from the host.
|
||||
#
|
||||
# Approach borrowed from luci-theme-footstrap.
|
||||
set -e
|
||||
|
||||
addr="$(ip -4 -o addr show dev eth0 2>/dev/null | awk '{print $4; exit}')"
|
||||
gw="$(ip -4 route show default 2>/dev/null | awk '{print $3; exit}')"
|
||||
dns="${PODKOP_DEV_DNS:-1.1.1.1 8.8.8.8}"
|
||||
|
||||
# Docker's resolv.conf points at 127.0.0.11, which works via NAT rules that
|
||||
# OpenWrt's boot flushes.
|
||||
for ns in $dns; do echo "nameserver $ns"; done >/etc/resolv.conf
|
||||
|
||||
if [ -n "$addr" ]; then
|
||||
netmask="$(ipcalc.sh "$addr" | sed -n 's/^NETMASK=//p')"
|
||||
cat >/etc/config/network <<-EOF
|
||||
config interface 'loopback'
|
||||
option device 'lo'
|
||||
option proto 'static'
|
||||
option ipaddr '127.0.0.1'
|
||||
option netmask '255.0.0.0'
|
||||
|
||||
config globals 'globals'
|
||||
|
||||
config interface 'lan'
|
||||
option device 'eth0'
|
||||
option proto 'static'
|
||||
option ipaddr '${addr%/*}'
|
||||
option netmask '$netmask'
|
||||
option gateway '$gw'
|
||||
option dns '$dns'
|
||||
option delegate '0'
|
||||
EOF
|
||||
else
|
||||
echo "podkop-dev: eth0 has no IPv4 — /etc/config/network left alone" >&2
|
||||
fi
|
||||
|
||||
# Autostart on by default; get_status reads this. /etc survives boot, /tmp does
|
||||
# not — runtime state is seeded from /etc/rc.local instead.
|
||||
mkdir -p /etc/rc.d
|
||||
touch /etc/rc.d/S99podkop
|
||||
|
||||
# Until the cache is dropped, the mounted menu.d/acl.d stay invisible.
|
||||
rm -f /tmp/luci-indexcache* /var/luci-indexcache* 2>/dev/null || true
|
||||
|
||||
exec "$@"
|
||||
32
src/frontend/sandbox/docker/scenario.sh
Executable file
32
src/frontend/sandbox/docker/scenario.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/bin/sh
|
||||
# Switch the backend stub's scenario on a running stand.
|
||||
#
|
||||
# ./scenario.sh show current
|
||||
# ./scenario.sh ok everything works
|
||||
# ./scenario.sh fail commands fail (empty stdout)
|
||||
# ./scenario.sh stopped services down, Clash unreachable
|
||||
#
|
||||
# Reload the page afterwards.
|
||||
set -e
|
||||
|
||||
CONTAINER=podkop-dev-router
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
# A missing file is fine — the stub falls back to ok
|
||||
current=$(docker exec "$CONTAINER" cat /tmp/podkop-scenario 2>/dev/null || true)
|
||||
echo "scenario: ${current:-ok (default)}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
ok | fail | stopped) ;;
|
||||
*)
|
||||
echo "unknown scenario: $1 (ok | fail | stopped)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
docker exec "$CONTAINER" sh -c "echo '$1' > /tmp/podkop-scenario"
|
||||
# Drop accumulated state so the scenario starts clean
|
||||
docker exec "$CONTAINER" sh -c 'rm -rf /tmp/podkop-latency /tmp/podkop-selected'
|
||||
echo "scenario: $1 — reload the page"
|
||||
36
src/frontend/sandbox/docker/up.sh
Executable file
36
src/frontend/sandbox/docker/up.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
# Start the stand, picking the OpenWrt image for the host arch.
|
||||
#
|
||||
# openwrt/rootfs has no multiarch manifest, and the arm image declares
|
||||
# aarch64_generic rather than arm64 — without both the tag and the platform,
|
||||
# the build fails with "no match for platform in manifest".
|
||||
#
|
||||
# Override: OPENWRT_TAG=... ./up.sh
|
||||
set -e
|
||||
|
||||
OPENWRT_RELEASE=25.12.4
|
||||
|
||||
if [ -z "$OPENWRT_TAG" ]; then
|
||||
case "$(uname -m)" in
|
||||
arm64 | aarch64)
|
||||
OPENWRT_TAG="armsr-armv8-$OPENWRT_RELEASE"
|
||||
OPENWRT_PLATFORM="linux/aarch64_generic"
|
||||
;;
|
||||
x86_64 | amd64)
|
||||
OPENWRT_TAG="x86-64-$OPENWRT_RELEASE"
|
||||
OPENWRT_PLATFORM="linux/amd64"
|
||||
;;
|
||||
*)
|
||||
echo "unknown arch $(uname -m); set OPENWRT_TAG manually" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
echo "image: openwrt/rootfs:$OPENWRT_TAG ($OPENWRT_PLATFORM)"
|
||||
OPENWRT_TAG="$OPENWRT_TAG" OPENWRT_PLATFORM="$OPENWRT_PLATFORM" \
|
||||
docker compose up -d --build "$@"
|
||||
|
||||
echo
|
||||
echo "ui: http://localhost:8080 -> Services -> Podkop (root, empty password)"
|
||||
echo "scenarios: ./scenario.sh ok | fail | stopped"
|
||||
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
"require baseclass";
|
||||
"require form";
|
||||
"require ui";
|
||||
"require uci";
|
||||
"require fs";
|
||||
"require view.podkop.main as main";
|
||||
|
||||
function createDashboardContent(section) {
|
||||
const o = section.option(form.DummyValue, "_mount_node");
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = () => {
|
||||
main.DashboardTab.initController();
|
||||
return main.DashboardTab.render();
|
||||
};
|
||||
}
|
||||
|
||||
const EntryPoint = {
|
||||
createDashboardContent,
|
||||
};
|
||||
|
||||
return baseclass.extend(EntryPoint);
|
||||
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
"require baseclass";
|
||||
"require form";
|
||||
"require ui";
|
||||
"require uci";
|
||||
"require fs";
|
||||
"require view.podkop.main as main";
|
||||
|
||||
function createDiagnosticContent(section) {
|
||||
const o = section.option(form.DummyValue, "_mount_node");
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = () => {
|
||||
main.DiagnosticTab.initController();
|
||||
return main.DiagnosticTab.render();
|
||||
};
|
||||
}
|
||||
|
||||
const EntryPoint = {
|
||||
createDiagnosticContent,
|
||||
};
|
||||
|
||||
return baseclass.extend(EntryPoint);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,98 @@
|
||||
"use strict";
|
||||
"require view";
|
||||
"require form";
|
||||
"require baseclass";
|
||||
"require network";
|
||||
"require view.podkop.main as main";
|
||||
|
||||
// Settings content
|
||||
"require view.podkop.settings as settings";
|
||||
|
||||
// Sections content
|
||||
"require view.podkop.section as section";
|
||||
|
||||
// Dashboard content
|
||||
"require view.podkop.dashboard as dashboard";
|
||||
|
||||
// Diagnostic content
|
||||
"require view.podkop.diagnostic as diagnostic";
|
||||
|
||||
const EntryPoint = {
|
||||
async render() {
|
||||
main.injectGlobalStyles();
|
||||
|
||||
const podkopMap = new form.Map(
|
||||
"podkop",
|
||||
_("Podkop Settings"),
|
||||
_("Configuration for Podkop service"),
|
||||
);
|
||||
// Enable tab views
|
||||
podkopMap.tabbed = true;
|
||||
|
||||
// Sections tab
|
||||
const sectionsSection = podkopMap.section(
|
||||
form.TypedSection,
|
||||
"section",
|
||||
_("Sections"),
|
||||
);
|
||||
sectionsSection.anonymous = false;
|
||||
sectionsSection.addremove = true;
|
||||
sectionsSection.template = "cbi/simpleform";
|
||||
|
||||
// Render section content
|
||||
section.createSectionContent(sectionsSection);
|
||||
|
||||
// Settings tab
|
||||
const settingsSection = podkopMap.section(
|
||||
form.TypedSection,
|
||||
"settings",
|
||||
_("Settings"),
|
||||
);
|
||||
settingsSection.anonymous = true;
|
||||
settingsSection.addremove = false;
|
||||
// Make it named [ config settings 'settings' ]
|
||||
settingsSection.cfgsections = function () {
|
||||
return ["settings"];
|
||||
};
|
||||
|
||||
// Render settings content
|
||||
settings.createSettingsContent(settingsSection);
|
||||
|
||||
// Diagnostic tab
|
||||
const diagnosticSection = podkopMap.section(
|
||||
form.TypedSection,
|
||||
"diagnostic",
|
||||
_("Diagnostics"),
|
||||
);
|
||||
diagnosticSection.anonymous = true;
|
||||
diagnosticSection.addremove = false;
|
||||
diagnosticSection.cfgsections = function () {
|
||||
return ["diagnostic"];
|
||||
};
|
||||
|
||||
// Render diagnostic content
|
||||
diagnostic.createDiagnosticContent(diagnosticSection);
|
||||
|
||||
// Dashboard tab
|
||||
const dashboardSection = podkopMap.section(
|
||||
form.TypedSection,
|
||||
"dashboard",
|
||||
_("Dashboard"),
|
||||
);
|
||||
dashboardSection.anonymous = true;
|
||||
dashboardSection.addremove = false;
|
||||
dashboardSection.cfgsections = function () {
|
||||
return ["dashboard"];
|
||||
};
|
||||
|
||||
// Render dashboard content
|
||||
dashboard.createDashboardContent(dashboardSection);
|
||||
|
||||
// Inject core service
|
||||
main.coreService();
|
||||
|
||||
return podkopMap.render();
|
||||
},
|
||||
};
|
||||
|
||||
return view.extend(EntryPoint);
|
||||
@@ -0,0 +1,703 @@
|
||||
"use strict";
|
||||
"require form";
|
||||
"require baseclass";
|
||||
"require ui";
|
||||
"require tools.widgets as widgets";
|
||||
"require view.podkop.main as main";
|
||||
|
||||
function createSectionContent(section) {
|
||||
let o = section.option(
|
||||
form.ListValue,
|
||||
"connection_type",
|
||||
_("Connection Type"),
|
||||
_("Select between VPN and Proxy connection methods for traffic routing"),
|
||||
);
|
||||
o.value("proxy", "Proxy");
|
||||
o.value("vpn", "VPN");
|
||||
o.value("block", "Block");
|
||||
o.value("exclusion", "Exclusion");
|
||||
|
||||
o = section.option(
|
||||
form.ListValue,
|
||||
"proxy_config_type",
|
||||
_("Configuration Type"),
|
||||
_("Select how to configure the proxy"),
|
||||
);
|
||||
o.value("url", _("Connection URL"));
|
||||
o.value("selector", _("Selector"));
|
||||
o.value("urltest", _("URLTest"));
|
||||
o.value("outbound", _("Outbound Config"));
|
||||
o.default = "url";
|
||||
o.depends("connection_type", "proxy");
|
||||
|
||||
o = section.option(
|
||||
form.TextValue,
|
||||
"proxy_string",
|
||||
_("Proxy Configuration URL"),
|
||||
_("vless://, ss://, trojan://, socks4/5://, hy2/hysteria2:// links")
|
||||
);
|
||||
o.depends("proxy_config_type", "url");
|
||||
o.rows = 5;
|
||||
// Enable soft wrapping for multi-line proxy URLs (e.g., for URLTest proxy links)
|
||||
o.wrap = "soft";
|
||||
// Render as a textarea to allow multiple proxy URLs/configs
|
||||
o.textarea = true;
|
||||
o.rmempty = false;
|
||||
o.sectionDescriptions = new Map();
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validateProxyUrl(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.TextValue,
|
||||
"outbound_json",
|
||||
_("Outbound Configuration"),
|
||||
_("Enter complete outbound configuration in JSON format"),
|
||||
);
|
||||
o.depends("proxy_config_type", "outbound");
|
||||
o.rows = 10;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validateOutboundJson(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.DynamicList,
|
||||
"selector_proxy_links",
|
||||
_("Selector Proxy Links"),
|
||||
_("vless://, ss://, trojan://, socks4/5://, hy2/hysteria2:// links")
|
||||
);
|
||||
o.depends("proxy_config_type", "selector");
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validateProxyUrl(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.DynamicList,
|
||||
"urltest_proxy_links",
|
||||
_("URLTest Proxy Links"),
|
||||
_("vless://, ss://, trojan://, socks4/5://, hy2/hysteria2:// links")
|
||||
);
|
||||
o.depends("proxy_config_type", "urltest");
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validateProxyUrl(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.ListValue,
|
||||
"urltest_check_interval",
|
||||
_("URLTest Check Interval"),
|
||||
_("The interval between connectivity tests")
|
||||
);
|
||||
o.value("30s", _("Every 30 seconds"));
|
||||
o.value("1m", _("Every 1 minute"));
|
||||
o.value("3m", _("Every 3 minutes"));
|
||||
o.value("5m", _("Every 5 minutes"));
|
||||
o.default = "3m";
|
||||
o.depends("proxy_config_type", "urltest");
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
"urltest_tolerance",
|
||||
_("URLTest Tolerance"),
|
||||
_("The maximum difference in response times (ms) allowed when comparing servers")
|
||||
);
|
||||
o.default = "50";
|
||||
o.rmempty = false;
|
||||
o.depends("proxy_config_type", "urltest");
|
||||
o.validate = function (section_id, value) {
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const parsed = parseFloat(value);
|
||||
|
||||
if (/^[0-9]+$/.test(value) && !isNaN(parsed) && isFinite(parsed) && parsed >= 50 && parsed <= 1000) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return _('Must be a number in the range of 50 - 1000');
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
"urltest_testing_url",
|
||||
_("URLTest Testing URL"),
|
||||
_("The URL used to test server connectivity")
|
||||
);
|
||||
o.value("https://www.gstatic.com/generate_204", "https://www.gstatic.com/generate_204 (Google)");
|
||||
o.value("https://cp.cloudflare.com/generate_204", "https://cp.cloudflare.com/generate_204 (Cloudflare)");
|
||||
o.value("https://captive.apple.com", "https://captive.apple.com (Apple)");
|
||||
o.value("https://connectivity-check.ubuntu.com", "https://connectivity-check.ubuntu.com (Ubuntu)")
|
||||
o.default = "https://www.gstatic.com/generate_204";
|
||||
o.rmempty = false;
|
||||
o.depends("proxy_config_type", "urltest");
|
||||
|
||||
o.validate = function (section_id, value) {
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validateUrl(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"enable_udp_over_tcp",
|
||||
_("UDP over TCP"),
|
||||
_("Applicable for SOCKS and Shadowsocks proxy"),
|
||||
);
|
||||
o.default = "0";
|
||||
o.depends("connection_type", "proxy");
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
widgets.DeviceSelect,
|
||||
"interface",
|
||||
_("Network Interface"),
|
||||
_("Select network interface for VPN connection"),
|
||||
);
|
||||
o.depends("connection_type", "vpn");
|
||||
o.noaliases = true;
|
||||
o.nobridges = false;
|
||||
o.noinactive = false;
|
||||
o.filter = function (section_id, value) {
|
||||
// Blocked interface names that should never be selectable
|
||||
const blockedInterfaces = [
|
||||
"br-lan",
|
||||
"eth0",
|
||||
"eth1",
|
||||
"wan",
|
||||
"phy0-ap0",
|
||||
"phy1-ap0",
|
||||
"pppoe-wan",
|
||||
"lan",
|
||||
];
|
||||
|
||||
// Reject immediately if the value matches any blocked interface
|
||||
if (blockedInterfaces.includes(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to find the device object with the given name
|
||||
const device = this.devices.find((dev) => dev.getName() === value);
|
||||
|
||||
// If no device is found, allow the value
|
||||
if (!device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the device type (e.g., "wifi", "ethernet", etc.)
|
||||
const type = device.getType();
|
||||
|
||||
// Reject wireless-related devices
|
||||
const isWireless =
|
||||
type === "wifi" || type === "wireless" || type.includes("wlan");
|
||||
|
||||
return !isWireless;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"domain_resolver_enabled",
|
||||
_("Domain Resolver"),
|
||||
_("Enable built-in DNS resolver for domains handled by this section"),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
o.depends("connection_type", "vpn");
|
||||
|
||||
o = section.option(
|
||||
form.ListValue,
|
||||
"domain_resolver_dns_type",
|
||||
_("DNS Protocol Type"),
|
||||
_("Select the DNS protocol type for the domain resolver"),
|
||||
);
|
||||
o.value("doh", _("DNS over HTTPS (DoH)"));
|
||||
o.value("dot", _("DNS over TLS (DoT)"));
|
||||
o.value("udp", _("UDP (Unprotected DNS)"));
|
||||
o.default = "udp";
|
||||
o.rmempty = false;
|
||||
o.depends("domain_resolver_enabled", "1");
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
"domain_resolver_dns_server",
|
||||
_("DNS Server"),
|
||||
_("Select or enter DNS server address"),
|
||||
);
|
||||
Object.entries(main.DNS_SERVER_OPTIONS).forEach(([key, label]) => {
|
||||
o.value(key, _(label));
|
||||
});
|
||||
o.default = "8.8.8.8";
|
||||
o.rmempty = false;
|
||||
o.depends("domain_resolver_enabled", "1");
|
||||
o.validate = function (section_id, value) {
|
||||
const validation = main.validateDNS(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.DynamicList,
|
||||
"community_lists",
|
||||
_("Community Lists"),
|
||||
_("Select a predefined list for routing") +
|
||||
' <a href="https://github.com/itdoginfo/allow-domains" target="_blank">github.com/itdoginfo/allow-domains</a>',
|
||||
);
|
||||
o.placeholder = "Service list";
|
||||
Object.entries(main.DOMAIN_LIST_OPTIONS).forEach(([key, label]) => {
|
||||
o.value(key, _(label));
|
||||
});
|
||||
o.rmempty = true;
|
||||
let lastValues = [];
|
||||
let isProcessing = false;
|
||||
|
||||
o.onchange = function (ev, section_id, value) {
|
||||
if (isProcessing) return;
|
||||
isProcessing = true;
|
||||
|
||||
try {
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
let newValues = [...values];
|
||||
let notifications = [];
|
||||
|
||||
const selectedRegionalOptions = main.REGIONAL_OPTIONS.filter((opt) =>
|
||||
newValues.includes(opt),
|
||||
);
|
||||
|
||||
if (selectedRegionalOptions.length > 1) {
|
||||
const lastSelected =
|
||||
selectedRegionalOptions[selectedRegionalOptions.length - 1];
|
||||
const removedRegions = selectedRegionalOptions.slice(0, -1);
|
||||
newValues = newValues.filter(
|
||||
(v) => v === lastSelected || !main.REGIONAL_OPTIONS.includes(v),
|
||||
);
|
||||
notifications.push(
|
||||
E("p", {}, [
|
||||
E("strong", {}, _("Regional options cannot be used together")),
|
||||
E("br"),
|
||||
_(
|
||||
"Warning: %s cannot be used together with %s. Previous selections have been removed.",
|
||||
).format(removedRegions.join(", "), lastSelected),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
if (newValues.includes("russia_inside")) {
|
||||
const removedServices = newValues.filter(
|
||||
(v) => !main.ALLOWED_WITH_RUSSIA_INSIDE.includes(v),
|
||||
);
|
||||
if (removedServices.length > 0) {
|
||||
newValues = newValues.filter((v) =>
|
||||
main.ALLOWED_WITH_RUSSIA_INSIDE.includes(v),
|
||||
);
|
||||
notifications.push(
|
||||
E("p", { class: "alert-message warning" }, [
|
||||
E("strong", {}, _("Russia inside restrictions")),
|
||||
E("br"),
|
||||
_(
|
||||
"Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection.",
|
||||
).format(
|
||||
main.ALLOWED_WITH_RUSSIA_INSIDE.map(
|
||||
(key) => main.DOMAIN_LIST_OPTIONS[key],
|
||||
)
|
||||
.filter((label) => label !== "Russia inside")
|
||||
.join(", "),
|
||||
removedServices.join(", "),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON.stringify(newValues.sort()) !== JSON.stringify(values.sort())) {
|
||||
this.getUIElement(section_id).setValue(newValues);
|
||||
}
|
||||
|
||||
notifications.forEach((notification) =>
|
||||
ui.addNotification(null, notification),
|
||||
);
|
||||
lastValues = newValues;
|
||||
} catch (e) {
|
||||
console.error("Error in onchange handler:", e);
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
}
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.ListValue,
|
||||
"user_domain_list_type",
|
||||
_("User Domain List Type"),
|
||||
_("Select the list type for adding custom domains"),
|
||||
);
|
||||
o.value("disabled", _("Disabled"));
|
||||
o.value("dynamic", _("Dynamic List"));
|
||||
o.value("text", _("Text List"));
|
||||
o.default = "disabled";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.DynamicList,
|
||||
"user_domains",
|
||||
_("User Domains"),
|
||||
_(
|
||||
"Enter domain names without protocols, e.g. example.com or sub.example.com",
|
||||
),
|
||||
);
|
||||
o.placeholder = "Domains list";
|
||||
o.depends("user_domain_list_type", "dynamic");
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validateDomain(value, true);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.TextValue,
|
||||
"user_domains_text",
|
||||
_("User Domains List"),
|
||||
_(
|
||||
"Enter domain names separated by commas, spaces, or newlines. You can add comments using //",
|
||||
),
|
||||
);
|
||||
o.placeholder =
|
||||
"example.com, sub.example.com\n// Social networks\ndomain.com test.com // personal domains";
|
||||
o.depends("user_domain_list_type", "text");
|
||||
o.rows = 8;
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const domains = main.parseValueList(value);
|
||||
|
||||
if (!domains.length) {
|
||||
return _(
|
||||
"At least one valid domain must be specified. Comments-only content is not allowed.",
|
||||
);
|
||||
}
|
||||
|
||||
const { valid, results } = main.bulkValidate(domains, (row) =>
|
||||
main.validateDomain(row, true),
|
||||
);
|
||||
|
||||
if (!valid) {
|
||||
const errors = results
|
||||
.filter((validation) => !validation.valid) // Leave only failed validations
|
||||
.map((validation) => `${validation.value}: ${validation.message}`); // Collect validation errors
|
||||
|
||||
return [_("Validation errors:"), ...errors].join("\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.ListValue,
|
||||
"user_subnet_list_type",
|
||||
_("User Subnet List Type"),
|
||||
_("Select the list type for adding custom subnets"),
|
||||
);
|
||||
o.value("disabled", _("Disabled"));
|
||||
o.value("dynamic", _("Dynamic List"));
|
||||
o.value("text", _("Text List"));
|
||||
o.default = "disabled";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.DynamicList,
|
||||
"user_subnets",
|
||||
_("User Subnets"),
|
||||
_(
|
||||
"Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses",
|
||||
),
|
||||
);
|
||||
o.placeholder = "IP or subnet";
|
||||
o.depends("user_subnet_list_type", "dynamic");
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validateSubnet(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.TextValue,
|
||||
"user_subnets_text",
|
||||
_("User Subnets List"),
|
||||
_(
|
||||
"Enter subnets in CIDR notation or single IP addresses, separated by commas, spaces, or newlines. " +
|
||||
"You can add comments using //",
|
||||
),
|
||||
);
|
||||
o.placeholder =
|
||||
"103.21.244.0/22\n// Google DNS\n8.8.8.8\n1.1.1.1/32, 9.9.9.9 // Cloudflare and Quad9";
|
||||
o.depends("user_subnet_list_type", "text");
|
||||
o.rows = 10;
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const subnets = main.parseValueList(value);
|
||||
|
||||
if (!subnets.length) {
|
||||
return _(
|
||||
"At least one valid subnet or IP must be specified. Comments-only content is not allowed.",
|
||||
);
|
||||
}
|
||||
|
||||
const { valid, results } = main.bulkValidate(subnets, main.validateSubnet);
|
||||
|
||||
if (!valid) {
|
||||
const errors = results
|
||||
.filter((validation) => !validation.valid) // Leave only failed validations
|
||||
.map((validation) => `${validation.value}: ${validation.message}`); // Collect validation errors
|
||||
|
||||
return [_("Validation errors:"), ...errors].join("\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.DynamicList,
|
||||
"local_domain_lists",
|
||||
_("Local Domain Lists"),
|
||||
_("Specify the path to the list file located on the router filesystem"),
|
||||
);
|
||||
o.placeholder = "/path/file.lst";
|
||||
o.rmempty = true;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validatePath(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.DynamicList,
|
||||
"local_subnet_lists",
|
||||
_("Local Subnet Lists"),
|
||||
_("Specify the path to the list file located on the router filesystem"),
|
||||
);
|
||||
o.placeholder = "/path/file.lst";
|
||||
o.rmempty = true;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validatePath(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.DynamicList,
|
||||
"remote_domain_lists",
|
||||
_("Remote Domain Lists"),
|
||||
_("Specify remote URLs to download and use domain lists"),
|
||||
);
|
||||
o.placeholder = "https://example.com/domains.srs";
|
||||
o.rmempty = true;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validateUrl(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.DynamicList,
|
||||
"remote_subnet_lists",
|
||||
_("Remote Subnet Lists"),
|
||||
_("Specify remote URLs to download and use subnet lists"),
|
||||
);
|
||||
o.placeholder = "https://example.com/subnets.srs";
|
||||
o.rmempty = true;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validateUrl(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.DynamicList,
|
||||
"fully_routed_ips",
|
||||
_("Fully Routed IPs"),
|
||||
_(
|
||||
"Specify local IP addresses or subnets whose traffic will always be routed through the configured route",
|
||||
),
|
||||
);
|
||||
o.placeholder = "192.168.1.2 or 192.168.1.0/24";
|
||||
o.rmempty = true;
|
||||
o.depends("connection_type", "proxy");
|
||||
o.depends("connection_type", "vpn");
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validateSubnet(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"mixed_proxy_enabled",
|
||||
_("Enable Mixed Proxy"),
|
||||
_(
|
||||
"Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies",
|
||||
),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
o.depends("connection_type", "proxy");
|
||||
o.depends("connection_type", "vpn");
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
"mixed_proxy_port",
|
||||
_("Mixed Proxy Port"),
|
||||
_(
|
||||
"Specify the port number on which the mixed proxy will run for this section. " +
|
||||
"Make sure the selected port is not used by another service",
|
||||
),
|
||||
);
|
||||
o.rmempty = false;
|
||||
o.depends("mixed_proxy_enabled", "1");
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"resolve_real_ip_for_routing",
|
||||
_("Resolve real IP for routing"),
|
||||
_("Enable DNS resolve to get real IP when routing"),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
o.depends("connection_type", "proxy");
|
||||
o.depends("connection_type", "vpn");
|
||||
}
|
||||
|
||||
const EntryPoint = {
|
||||
createSectionContent,
|
||||
};
|
||||
|
||||
return baseclass.extend(EntryPoint);
|
||||
@@ -0,0 +1,438 @@
|
||||
"use strict";
|
||||
"require form";
|
||||
"require uci";
|
||||
"require baseclass";
|
||||
"require tools.widgets as widgets";
|
||||
"require view.podkop.main as main";
|
||||
|
||||
function createSettingsContent(section) {
|
||||
let o = section.option(
|
||||
form.ListValue,
|
||||
"dns_type",
|
||||
_("DNS Protocol Type"),
|
||||
_("Select DNS protocol to use"),
|
||||
);
|
||||
o.value("doh", _("DNS over HTTPS (DoH)"));
|
||||
o.value("dot", _("DNS over TLS (DoT)"));
|
||||
o.value("udp", _("UDP (Unprotected DNS)"));
|
||||
o.default = "udp";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
"dns_server",
|
||||
_("DNS Server"),
|
||||
_("Select or enter DNS server address"),
|
||||
);
|
||||
Object.entries(main.DNS_SERVER_OPTIONS).forEach(([key, label]) => {
|
||||
o.value(key, _(label));
|
||||
});
|
||||
o.default = "8.8.8.8";
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
const validation = main.validateDNS(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
"bootstrap_dns_server",
|
||||
_("Bootstrap DNS server"),
|
||||
_(
|
||||
"The DNS server used to look up the IP address of an upstream DNS server",
|
||||
),
|
||||
);
|
||||
Object.entries(main.BOOTSTRAP_DNS_SERVER_OPTIONS).forEach(([key, label]) => {
|
||||
o.value(key, _(label));
|
||||
});
|
||||
o.default = "77.88.8.8";
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
const validation = main.validateDNS(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
"dns_rewrite_ttl",
|
||||
_("DNS Rewrite TTL"),
|
||||
_("Time in seconds for DNS record caching (default: 60)"),
|
||||
);
|
||||
o.default = "60";
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
if (!value) {
|
||||
return _("TTL value cannot be empty");
|
||||
}
|
||||
|
||||
const ttl = parseInt(value);
|
||||
if (isNaN(ttl) || ttl < 0) {
|
||||
return _("TTL must be a positive number");
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
widgets.DeviceSelect,
|
||||
"source_network_interfaces",
|
||||
_("Source Network Interface"),
|
||||
_("Select the network interface from which the traffic will originate"),
|
||||
);
|
||||
o.default = "br-lan";
|
||||
o.noaliases = true;
|
||||
o.nobridges = false;
|
||||
o.noinactive = false;
|
||||
o.multiple = true;
|
||||
o.filter = function (section_id, value) {
|
||||
// Block specific interface names from being selectable
|
||||
const blocked = ["wan", "phy0-ap0", "phy1-ap0", "pppoe-wan"];
|
||||
if (blocked.includes(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to find the device object by its name
|
||||
const device = this.devices.find((dev) => dev.getName() === value);
|
||||
|
||||
// If no device is found, allow the value
|
||||
if (!device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check the type of the device
|
||||
const type = device.getType();
|
||||
|
||||
// Consider any Wi-Fi / wireless / wlan device as invalid
|
||||
const isWireless =
|
||||
type === "wifi" || type === "wireless" || type.includes("wlan");
|
||||
|
||||
// Allow only non-wireless devices
|
||||
return !isWireless;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"enable_output_network_interface",
|
||||
_("Enable Output Network Interface"),
|
||||
_("You can select Output Network Interface, by default autodetect"),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
widgets.DeviceSelect,
|
||||
"output_network_interface",
|
||||
_("Output Network Interface"),
|
||||
_("Select the network interface to which the traffic will originate"),
|
||||
);
|
||||
o.noaliases = true;
|
||||
o.multiple = false;
|
||||
o.depends("enable_output_network_interface", "1");
|
||||
o.filter = function (section_id, value) {
|
||||
// Blocked interface names that should never be selectable
|
||||
const blockedInterfaces = ["br-lan"];
|
||||
|
||||
// Reject immediately if the value matches any blocked interface
|
||||
if (blockedInterfaces.includes(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reject lan*
|
||||
if (
|
||||
value.startsWith("lan")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reject tun*, wg*, vpn*, awg*, oc*
|
||||
if (
|
||||
value.startsWith("tun") ||
|
||||
value.startsWith("wg") ||
|
||||
value.startsWith("vpn") ||
|
||||
value.startsWith("awg") ||
|
||||
value.startsWith("oc")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to find the device object with the given name
|
||||
const device = this.devices.find((dev) => dev.getName() === value);
|
||||
|
||||
// If no device is found, allow the value
|
||||
if (!device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the device type (e.g., "wifi", "ethernet", etc.)
|
||||
const type = device.getType();
|
||||
|
||||
// Reject wireless-related devices
|
||||
const isWireless =
|
||||
type === "wifi" || type === "wireless" || type.includes("wlan");
|
||||
|
||||
return !isWireless;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"enable_badwan_interface_monitoring",
|
||||
_("Interface Monitoring"),
|
||||
_("Interface monitoring for Bad WAN"),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
widgets.NetworkSelect,
|
||||
"badwan_monitored_interfaces",
|
||||
_("Monitored Interfaces"),
|
||||
_("Select the WAN interfaces to be monitored"),
|
||||
);
|
||||
o.depends("enable_badwan_interface_monitoring", "1");
|
||||
o.multiple = true;
|
||||
o.filter = function (section_id, value) {
|
||||
// Reject if the value is in the blocked list ['lan', 'loopback']
|
||||
if (["lan", "loopback"].includes(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reject if the value starts with '@' (means it's an alias/reference)
|
||||
if (value.startsWith("@")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise allow it
|
||||
return true;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
"badwan_reload_delay",
|
||||
_("Interface Monitoring Delay"),
|
||||
_("Delay in milliseconds before reloading podkop after interface UP"),
|
||||
);
|
||||
o.depends("enable_badwan_interface_monitoring", "1");
|
||||
o.default = "2000";
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
if (!value) {
|
||||
return _("Delay value cannot be empty");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"enable_yacd",
|
||||
_("Enable YACD"),
|
||||
`<a href="${main.getClashUIUrl()}" target="_blank">${main.getClashUIUrl()}</a>`,
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"enable_yacd_wan_access",
|
||||
_("Enable YACD WAN Access"),
|
||||
_("Allows access to YACD from the WAN. Make sure to open the appropriate port in your firewall."),
|
||||
);
|
||||
o.depends("enable_yacd", "1");
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
"yacd_secret_key",
|
||||
_("YACD Secret Key"),
|
||||
_("Secret key for authenticating remote access to YACD when WAN access is enabled."),
|
||||
);
|
||||
o.depends("enable_yacd_wan_access", "1");
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"disable_quic",
|
||||
_("Disable QUIC"),
|
||||
_(
|
||||
"Disable the QUIC protocol to improve compatibility or fix issues with video streaming",
|
||||
),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.ListValue,
|
||||
"update_interval",
|
||||
_("List Update Frequency"),
|
||||
_("Select how often the domain or subnet lists are updated automatically"),
|
||||
);
|
||||
Object.entries(main.UPDATE_INTERVAL_OPTIONS).forEach(([key, label]) => {
|
||||
o.value(key, _(label));
|
||||
});
|
||||
o.default = "1d";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"download_lists_via_proxy",
|
||||
_("Download Lists via Proxy/VPN"),
|
||||
_("Downloading all lists via specific Proxy/VPN"),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.ListValue,
|
||||
"download_lists_via_proxy_section",
|
||||
_("Download Lists via specific proxy section"),
|
||||
_("Downloading all lists via specific Proxy/VPN"),
|
||||
);
|
||||
|
||||
o.rmempty = false;
|
||||
o.depends("download_lists_via_proxy", "1");
|
||||
o.cfgvalue = function (section_id) {
|
||||
return uci.get("podkop", section_id, "download_lists_via_proxy_section");
|
||||
};
|
||||
o.load = function () {
|
||||
const sections = this.map?.data?.state?.values?.podkop ?? {};
|
||||
|
||||
this.keylist = [];
|
||||
this.vallist = [];
|
||||
|
||||
for (const secName in sections) {
|
||||
const sec = sections[secName];
|
||||
if (sec[".type"] === "section" && sec['connection_type'] !== 'block' && sec['connection_type'] !== 'exclusion') {
|
||||
this.keylist.push(secName);
|
||||
this.vallist.push(secName);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"dont_touch_dhcp",
|
||||
_("Dont Touch My DHCP!"),
|
||||
_("Podkop will not modify your DHCP configuration"),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.ListValue,
|
||||
"config_path",
|
||||
_("Config File Path"),
|
||||
_(
|
||||
"Select path for sing-box config file. Change this ONLY if you know what you are doing",
|
||||
),
|
||||
);
|
||||
o.value("/etc/sing-box/config.json", "Flash (/etc/sing-box/config.json)");
|
||||
o.value("/tmp/sing-box/config.json", "RAM (/tmp/sing-box/config.json)");
|
||||
o.default = "/etc/sing-box/config.json";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.Value,
|
||||
"cache_path",
|
||||
_("Cache File Path"),
|
||||
_(
|
||||
"Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing",
|
||||
),
|
||||
);
|
||||
o.value("/tmp/sing-box/cache.db", "RAM (/tmp/sing-box/cache.db)");
|
||||
o.value(
|
||||
"/usr/share/sing-box/cache.db",
|
||||
"Flash (/usr/share/sing-box/cache.db)",
|
||||
);
|
||||
o.default = "/tmp/sing-box/cache.db";
|
||||
o.rmempty = false;
|
||||
o.validate = function (section_id, value) {
|
||||
if (!value) {
|
||||
return _("Cache file path cannot be empty");
|
||||
}
|
||||
|
||||
if (!value.startsWith("/")) {
|
||||
return _("Path must be absolute (start with /)");
|
||||
}
|
||||
|
||||
if (!value.endsWith("cache.db")) {
|
||||
return _("Path must end with cache.db");
|
||||
}
|
||||
|
||||
const parts = value.split("/").filter(Boolean);
|
||||
if (parts.length < 2) {
|
||||
return _("Path must contain at least one directory (like /tmp/cache.db)");
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
o = section.option(
|
||||
form.ListValue,
|
||||
"log_level",
|
||||
_("Log Level"),
|
||||
_(
|
||||
"Select the log level for sing-box",
|
||||
),
|
||||
);
|
||||
o.value("trace", "Trace");
|
||||
o.value("debug", "Debug");
|
||||
o.value("info", "Info");
|
||||
o.value("warn", "Warn");
|
||||
o.value("error", "Error");
|
||||
o.value("fatal", "Fatal");
|
||||
o.value("panic", "Panic");
|
||||
o.default = "warn";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.Flag,
|
||||
"exclude_ntp",
|
||||
_("Exclude NTP"),
|
||||
_(
|
||||
"Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN",
|
||||
),
|
||||
);
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
|
||||
o = section.option(
|
||||
form.DynamicList,
|
||||
"routing_excluded_ips",
|
||||
_("Routing Excluded IPs"),
|
||||
_("Specify a local IP address to be excluded from routing"),
|
||||
);
|
||||
o.placeholder = "IP";
|
||||
o.rmempty = true;
|
||||
o.validate = function (section_id, value) {
|
||||
// Optional
|
||||
if (!value || value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const validation = main.validateIPV4(value);
|
||||
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validation.message;
|
||||
};
|
||||
}
|
||||
|
||||
const EntryPoint = {
|
||||
createSettingsContent,
|
||||
};
|
||||
|
||||
return baseclass.extend(EntryPoint);
|
||||
30
src/frontend/sandbox/msgmerge.sh
Normal file
30
src/frontend/sandbox/msgmerge.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PODIR="po"
|
||||
POTFILE="$PODIR/templates/podkop.pot"
|
||||
WIDTH=120
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: $0 <language_code> (e.g., ru, de, fr)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LANG="$1"
|
||||
POFILE="$PODIR/$LANG/podkop.po"
|
||||
|
||||
if [ ! -f "$POTFILE" ]; then
|
||||
echo "Template $POTFILE not found. Run xgettext first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "$POFILE" ]; then
|
||||
echo "Updating $POFILE"
|
||||
msgmerge --update --width="$WIDTH" --no-location "$POFILE" "$POTFILE"
|
||||
else
|
||||
echo "Creating new $POFILE using msginit"
|
||||
mkdir -p "$PODIR/$LANG"
|
||||
msginit --no-translator --no-location --locale="$LANG" --width="$WIDTH" --input="$POTFILE" --output-file="$POFILE"
|
||||
fi
|
||||
|
||||
echo "Translation file for $LANG updated."
|
||||
795
src/frontend/sandbox/po/ru/podkop.po
Normal file
795
src/frontend/sandbox/po/ru/podkop.po
Normal file
@@ -0,0 +1,795 @@
|
||||
# RU translations for PODKOP package.
|
||||
# Copyright (C) 2026 THE PODKOP'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PODKOP package.
|
||||
# divocatt, 2026.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PODKOP\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-05-29 16:40+0300\n"
|
||||
"PO-Revision-Date: 2026-05-29 16:40+0300\n"
|
||||
"Last-Translator: divocatt\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
msgid "✔ Enabled"
|
||||
msgstr "✔ Включено"
|
||||
|
||||
msgid "✔ Running"
|
||||
msgstr "✔ Работает"
|
||||
|
||||
msgid "✘ Disabled"
|
||||
msgstr "✘ Отключено"
|
||||
|
||||
msgid "✘ Stopped"
|
||||
msgstr "✘ Остановлен"
|
||||
|
||||
msgid "Active Connections"
|
||||
msgstr "Активные соединения"
|
||||
|
||||
msgid "Additional marking rules found"
|
||||
msgstr "Найдены дополнительные правила маркировки"
|
||||
|
||||
msgid "Allows access to YACD from the WAN. Make sure to open the appropriate port in your firewall."
|
||||
msgstr "Обеспечивает доступ к YACD из WAN. Убедитесь, что в брандмауэре открыт соответствующий порт."
|
||||
|
||||
msgid "Applicable for SOCKS and Shadowsocks proxy"
|
||||
msgstr "Применимо для SOCKS и Shadowsocks прокси"
|
||||
|
||||
msgid "At least one valid domain must be specified. Comments-only content is not allowed."
|
||||
msgstr "Необходимо указать хотя бы один действительный домен. Содержимое только из комментариев не допускается."
|
||||
|
||||
msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed."
|
||||
msgstr "Необходимо указать хотя бы одну действительную подсеть или IP. Только комментарии недопустимы."
|
||||
|
||||
msgid "Available actions"
|
||||
msgstr "Доступные действия"
|
||||
|
||||
msgid "Bootsrap DNS"
|
||||
msgstr "Bootstrap DNS"
|
||||
|
||||
msgid "Bootstrap DNS server"
|
||||
msgstr "Bootstrap DNS-сервер"
|
||||
|
||||
msgid "Browser is not using FakeIP"
|
||||
msgstr "Браузер не использует FakeIP"
|
||||
|
||||
msgid "Browser is using FakeIP correctly"
|
||||
msgstr "Браузер использует FakeIP"
|
||||
|
||||
msgid "Cache File Path"
|
||||
msgstr "Путь к файлу кэша"
|
||||
|
||||
msgid "Cache file path cannot be empty"
|
||||
msgstr "Путь к файлу кэша не может быть пустым"
|
||||
|
||||
msgid "Cannot receive checks result"
|
||||
msgstr "Не удалось получить результаты проверки"
|
||||
|
||||
msgid "Checking, please wait"
|
||||
msgstr "Проверяем, пожалуйста подождите"
|
||||
|
||||
msgid "checks"
|
||||
msgstr "проверки"
|
||||
|
||||
msgid "Checks failed"
|
||||
msgstr "Проверки не выполнены"
|
||||
|
||||
msgid "Checks passed"
|
||||
msgstr "Проверки пройдены"
|
||||
|
||||
msgid "CIDR must be between 0 and 32"
|
||||
msgstr "CIDR должен быть между 0 и 32"
|
||||
|
||||
msgid "Close"
|
||||
msgstr "Закрыть"
|
||||
|
||||
msgid "Community Lists"
|
||||
msgstr "Списки сообщества"
|
||||
|
||||
msgid "Config File Path"
|
||||
msgstr "Путь к файлу конфигурации"
|
||||
|
||||
msgid "Configuration for Podkop service"
|
||||
msgstr "Настройки сервиса Podkop"
|
||||
|
||||
msgid "Configuration Type"
|
||||
msgstr "Тип конфигурации"
|
||||
|
||||
msgid "Connection Type"
|
||||
msgstr "Тип подключения"
|
||||
|
||||
msgid "Connection URL"
|
||||
msgstr "URL подключения"
|
||||
|
||||
msgid "Copy"
|
||||
msgstr "Копировать"
|
||||
|
||||
msgid "Currently unavailable"
|
||||
msgstr "Временно недоступно"
|
||||
|
||||
msgid "Dashboard"
|
||||
msgstr "Дашборд"
|
||||
|
||||
msgid "Dashboard currently unavailable"
|
||||
msgstr "Дашборд сейчас недоступен"
|
||||
|
||||
msgid "Delay in milliseconds before reloading podkop after interface UP"
|
||||
msgstr "Задержка в миллисекундах перед перезагрузкой podkop после поднятия интерфейса"
|
||||
|
||||
msgid "Delay value cannot be empty"
|
||||
msgstr "Значение задержки не может быть пустым"
|
||||
|
||||
msgid "DHCP has DNS server"
|
||||
msgstr "DHCP содержит DNS сервер"
|
||||
|
||||
msgid "Diagnostics"
|
||||
msgstr "Диагностика"
|
||||
|
||||
msgid "Disable autostart"
|
||||
msgstr "Отключить автостарт"
|
||||
|
||||
msgid "Disable QUIC"
|
||||
msgstr "Отключить QUIC"
|
||||
|
||||
msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming"
|
||||
msgstr "Отключить QUIC протокол для улучшения совместимости или исправления видео стриминга"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "Отключено"
|
||||
|
||||
msgid "DNS on router"
|
||||
msgstr "DNS на роутере"
|
||||
|
||||
msgid "DNS over HTTPS (DoH)"
|
||||
msgstr "DNS через HTTPS (DoH)"
|
||||
|
||||
msgid "DNS over TLS (DoT)"
|
||||
msgstr "DNS через TLS (DoT)"
|
||||
|
||||
msgid "DNS Protocol Type"
|
||||
msgstr "Тип протокола DNS"
|
||||
|
||||
msgid "DNS Rewrite TTL"
|
||||
msgstr "Перезапись TTL для DNS"
|
||||
|
||||
msgid "DNS Server"
|
||||
msgstr "DNS-сервер"
|
||||
|
||||
msgid "DNS server address cannot be empty"
|
||||
msgstr "Адрес DNS-сервера не может быть пустым"
|
||||
|
||||
msgid "Do not panic, everything can be fixed, just..."
|
||||
msgstr "Не паникуйте, всё можно исправить, просто..."
|
||||
|
||||
msgid "Domain Resolver"
|
||||
msgstr "Резолвер доменов"
|
||||
|
||||
msgid "Dont Touch My DHCP!"
|
||||
msgstr "Dont Touch My DHCP!"
|
||||
|
||||
msgid "Downlink"
|
||||
msgstr "Входящий"
|
||||
|
||||
msgid "Download"
|
||||
msgstr "Скачать"
|
||||
|
||||
msgid "Download Lists via Proxy/VPN"
|
||||
msgstr "Скачивать списки через Proxy/VPN"
|
||||
|
||||
msgid "Download Lists via specific proxy section"
|
||||
msgstr "Скачивать списки через выбранную секцию"
|
||||
|
||||
msgid "Downloading all lists via specific Proxy/VPN"
|
||||
msgstr "Загрузка всех списков через указанный прокси/VPN"
|
||||
|
||||
msgid "Dynamic List"
|
||||
msgstr "Динамический список"
|
||||
|
||||
msgid "Enable autostart"
|
||||
msgstr "Включить автостарт"
|
||||
|
||||
msgid "Enable built-in DNS resolver for domains handled by this section"
|
||||
msgstr "Включить встроенный DNS-резолвер для доменов, обрабатываемых в этом разделе"
|
||||
|
||||
msgid "Enable DNS resolve to get real IP when routing"
|
||||
msgstr "Разрешать домены в реальные IP-адреса перед маршрутизацией в outbound"
|
||||
|
||||
msgid "Enable Mixed Proxy"
|
||||
msgstr "Включить смешанный прокси"
|
||||
|
||||
msgid "Enable Output Network Interface"
|
||||
msgstr "Включить выходной сетевой интерфейс"
|
||||
|
||||
msgid "Enable the mixed proxy, allowing this section to route traffic through both HTTP and SOCKS proxies"
|
||||
msgstr "Включить смешанный прокси-сервер, разрешив этому разделу маршрутизировать трафик как через HTTP, так и через SOCKS-прокси."
|
||||
|
||||
msgid "Enable YACD"
|
||||
msgstr "Включить YACD"
|
||||
|
||||
msgid "Enable YACD WAN Access"
|
||||
msgstr "Включить доступ YACD WAN"
|
||||
|
||||
msgid "Enter complete outbound configuration in JSON format"
|
||||
msgstr "Введите полную конфигурацию исходящего соединения в формате JSON"
|
||||
|
||||
msgid "Enter domain names separated by commas, spaces, or newlines. You can add comments using //"
|
||||
msgstr "Введите доменные имена, разделяя их запятыми, пробелами или переносами строк. Вы можете добавлять комментарии, используя //"
|
||||
|
||||
msgid "Enter domain names without protocols, e.g. example.com or sub.example.com"
|
||||
msgstr "Введите доменные имена без протоколов, например example.com или sub.example.com"
|
||||
|
||||
msgid "Enter subnets in CIDR notation (e.g. 103.21.244.0/22) or single IP addresses"
|
||||
msgstr "Введите подсети в нотации CIDR (например, 103.21.244.0/22) или отдельные IP-адреса"
|
||||
|
||||
msgid "Every 1 minute"
|
||||
msgstr "Каждую минуту"
|
||||
|
||||
msgid "Every 3 minutes"
|
||||
msgstr "Каждые 3 минуты"
|
||||
|
||||
msgid "Every 30 seconds"
|
||||
msgstr "Каждые 30 секунд"
|
||||
|
||||
msgid "Every 5 minutes"
|
||||
msgstr "Каждые 5 минут"
|
||||
|
||||
msgid "Exclude NTP"
|
||||
msgstr "Исключить NTP"
|
||||
|
||||
msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN"
|
||||
msgstr "Исключите трафик протокола NTP из туннеля, чтобы предотвратить его маршрутизацию через прокси-сервер или VPN."
|
||||
|
||||
msgid "Failed to copy!"
|
||||
msgstr "Не удалось скопировать!"
|
||||
|
||||
msgid "Failed to execute!"
|
||||
msgstr "Не удалось выполнить!"
|
||||
|
||||
msgid "Fastest"
|
||||
msgstr "Самый быстрый"
|
||||
|
||||
msgid "Fully Routed IPs"
|
||||
msgstr "Полностью маршрутизированные IP-адреса"
|
||||
|
||||
msgid "Get global check"
|
||||
msgstr "Получить глобальную проверку"
|
||||
|
||||
msgid "Global check"
|
||||
msgstr "Глобальная проверка"
|
||||
|
||||
msgid "HTTP error"
|
||||
msgstr "Ошибка HTTP"
|
||||
|
||||
msgid "Interface Monitoring"
|
||||
msgstr "Мониторинг интерфейса"
|
||||
|
||||
msgid "Interface Monitoring Delay"
|
||||
msgstr "Задержка при мониторинге интерфейсов"
|
||||
|
||||
msgid "Interface monitoring for Bad WAN"
|
||||
msgstr "Мониторинг интерфейса для Bad WAN"
|
||||
|
||||
msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.example.com/nicedns for DoH"
|
||||
msgstr "Неверный формат DNS-сервера. Примеры: 8.8.8.8, dns.example.com или dns.example.com/nicedns для DoH"
|
||||
|
||||
msgid "Invalid domain address"
|
||||
msgstr "Неверный домен"
|
||||
|
||||
msgid "Invalid format. Use X.X.X.X or X.X.X.X/Y"
|
||||
msgstr "Неверный формат. Используйте X.X.X.X или X.X.X.X/Y"
|
||||
|
||||
msgid "Invalid HY2 URL: insecure must be 0 or 1"
|
||||
msgstr "Неверный URL Hysteria2: параметр insecure должен быть 0 или 1"
|
||||
|
||||
msgid "Invalid HY2 URL: invalid port number"
|
||||
msgstr "Неверный URL Hysteria2: неверный номер порта"
|
||||
|
||||
msgid "Invalid HY2 URL: missing credentials/server"
|
||||
msgstr "Неверный URL Hysteria2: отсутствуют учетные данные/сервер"
|
||||
|
||||
msgid "Invalid HY2 URL: missing host"
|
||||
msgstr "Неверный URL Hysteria2: отсутствует хост"
|
||||
|
||||
msgid "Invalid HY2 URL: missing host & port"
|
||||
msgstr "Неверный URL Hysteria2: отсутствуют хост и порт"
|
||||
|
||||
msgid "Invalid HY2 URL: missing password"
|
||||
msgstr "Неверный URL Hysteria2: отсутствует пароль"
|
||||
|
||||
msgid "Invalid HY2 URL: missing port"
|
||||
msgstr "Неверный URL Hysteria2: отсутствует порт"
|
||||
|
||||
msgid "Invalid HY2 URL: must not contain spaces"
|
||||
msgstr "Неверный URL Hysteria2: не должен содержать пробелов"
|
||||
|
||||
msgid "Invalid HY2 URL: must start with hysteria2:// or hy2://"
|
||||
msgstr "Неверный URL Hysteria2: должен начинаться с hysteria2:// или hy2://"
|
||||
|
||||
msgid "Invalid HY2 URL: obfs-password required when obfs is set"
|
||||
msgstr "Неверный URL Hysteria2: требуется obfs-password, когда установлен obfs"
|
||||
|
||||
msgid "Invalid HY2 URL: parsing failed"
|
||||
msgstr "Неверный URL Hysteria2: ошибка разбора"
|
||||
|
||||
msgid "Invalid HY2 URL: sni cannot be empty"
|
||||
msgstr "Неверный URL Hysteria2: sni не может быть пустым"
|
||||
|
||||
msgid "Invalid HY2 URL: unsupported obfs type"
|
||||
msgstr "Неверный URL Hysteria2: неподдерживаемый тип obfs"
|
||||
|
||||
msgid "Invalid IP address"
|
||||
msgstr "Неверный IP-адрес"
|
||||
|
||||
msgid "Invalid JSON format"
|
||||
msgstr "Неверный формат JSON"
|
||||
|
||||
msgid "Invalid path format. Path must start with \"/\" and contain valid characters"
|
||||
msgstr "Неверный формат пути. Путь должен начинаться с \"/\" и содержать допустимые символы"
|
||||
|
||||
msgid "Invalid port number. Must be between 1 and 65535"
|
||||
msgstr "Неверный номер порта. Допустимо от 1 до 65535"
|
||||
|
||||
msgid "Invalid Shadowsocks URL: decoded credentials must contain method:password"
|
||||
msgstr "Неверный URL Shadowsocks: декодированные данные должны содержать method:password"
|
||||
|
||||
msgid "Invalid Shadowsocks URL: missing credentials"
|
||||
msgstr "Неверный URL Shadowsocks: отсутствуют учетные данные"
|
||||
|
||||
msgid "Invalid Shadowsocks URL: missing method and password separator \":\""
|
||||
msgstr "Неверный URL Shadowsocks: отсутствует разделитель метода и пароля \":\""
|
||||
|
||||
msgid "Invalid Shadowsocks URL: missing port"
|
||||
msgstr "Неверный URL Shadowsocks: отсутствует порт"
|
||||
|
||||
msgid "Invalid Shadowsocks URL: missing server"
|
||||
msgstr "Неверный URL Shadowsocks: отсутствует сервер"
|
||||
|
||||
msgid "Invalid Shadowsocks URL: missing server address"
|
||||
msgstr "Неверный URL Shadowsocks: отсутствует адрес сервера"
|
||||
|
||||
msgid "Invalid Shadowsocks URL: must not contain spaces"
|
||||
msgstr "Неверный URL Shadowsocks: не должен содержать пробелов"
|
||||
|
||||
msgid "Invalid Shadowsocks URL: must start with ss://"
|
||||
msgstr "Неверный URL Shadowsocks: должен начинаться с ss://"
|
||||
|
||||
msgid "Invalid Shadowsocks URL: parsing failed"
|
||||
msgstr "Неверный URL Shadowsocks: ошибка разбора"
|
||||
|
||||
msgid "Invalid SOCKS URL: invalid host format"
|
||||
msgstr "Неверный URL SOCKS: неверный формат хоста"
|
||||
|
||||
msgid "Invalid SOCKS URL: invalid port number"
|
||||
msgstr "Неверный URL SOCKS: неверный номер порта"
|
||||
|
||||
msgid "Invalid SOCKS URL: missing host and port"
|
||||
msgstr "Неверный URL SOCKS: отсутствует хост и порт"
|
||||
|
||||
msgid "Invalid SOCKS URL: missing hostname or IP"
|
||||
msgstr "Неверный URL SOCKS: отсутствует имя хоста или IP-адрес"
|
||||
|
||||
msgid "Invalid SOCKS URL: missing port"
|
||||
msgstr "Неверный URL SOCKS: отсутствует порт"
|
||||
|
||||
msgid "Invalid SOCKS URL: missing username"
|
||||
msgstr "Неверный URL SOCKS: отсутствует имя пользователя"
|
||||
|
||||
msgid "Invalid SOCKS URL: must not contain spaces"
|
||||
msgstr "Неверный URL SOCKS: не должен содержать пробелов"
|
||||
|
||||
msgid "Invalid SOCKS URL: must start with socks4://, socks4a://, or socks5://"
|
||||
msgstr "Неверный URL-адрес SOCKS: должен начинаться с socks4://, socks4a:// или socks5://"
|
||||
|
||||
msgid "Invalid SOCKS URL: parsing failed"
|
||||
msgstr "Неверный URL SOCKS: парсинг не удался"
|
||||
|
||||
msgid "Invalid Trojan URL: must not contain spaces"
|
||||
msgstr "Неверный URL Trojan: не должен содержать пробелов"
|
||||
|
||||
msgid "Invalid Trojan URL: must start with trojan://"
|
||||
msgstr "Неверный URL Trojan: должен начинаться с trojan://"
|
||||
|
||||
msgid "Invalid Trojan URL: parsing failed"
|
||||
msgstr "Неверный URL Trojan: ошибка разбора"
|
||||
|
||||
msgid "Invalid URL format"
|
||||
msgstr "Неверный формат URL"
|
||||
|
||||
msgid "Invalid VLESS URL: parsing failed"
|
||||
msgstr "Неверный URL VLESS: ошибка разбора"
|
||||
|
||||
msgid "IP address 0.0.0.0 is not allowed"
|
||||
msgstr "IP-адрес 0.0.0.0 не допускается"
|
||||
|
||||
msgid "Issues detected"
|
||||
msgstr "Обнаружены проблемы"
|
||||
|
||||
msgid "Latest"
|
||||
msgstr "Последняя"
|
||||
|
||||
msgid "List Update Frequency"
|
||||
msgstr "Частота обновления списков"
|
||||
|
||||
msgid "Local Domain Lists"
|
||||
msgstr "Локальные списки доменов"
|
||||
|
||||
msgid "Local Subnet Lists"
|
||||
msgstr "Локальные списки подсетей"
|
||||
|
||||
msgid "Log Level"
|
||||
msgstr "Уровень логов"
|
||||
|
||||
msgid "Main DNS"
|
||||
msgstr "Основной DNS"
|
||||
|
||||
msgid "Memory Usage"
|
||||
msgstr "Использование памяти"
|
||||
|
||||
msgid "Mixed Proxy Port"
|
||||
msgstr "Порт смешанного прокси"
|
||||
|
||||
msgid "Monitored Interfaces"
|
||||
msgstr "Наблюдаемые интерфейсы"
|
||||
|
||||
msgid "Must be a number in the range of 50 - 1000"
|
||||
msgstr "Должно быть числом от 50 до 1000"
|
||||
|
||||
msgid "Network Interface"
|
||||
msgstr "Сетевой интерфейс"
|
||||
|
||||
msgid "No other marking rules found"
|
||||
msgstr "Другие правила маркировки не найдены"
|
||||
|
||||
msgid "Not implement yet"
|
||||
msgstr "Ещё не реализовано"
|
||||
|
||||
msgid "Not responding"
|
||||
msgstr "Не отвечает"
|
||||
|
||||
msgid "Not running"
|
||||
msgstr "Не запущено"
|
||||
|
||||
msgid "Operation timed out"
|
||||
msgstr "Время ожидания истекло"
|
||||
|
||||
msgid "Outbound Config"
|
||||
msgstr "Конфигурация Outbound"
|
||||
|
||||
msgid "Outbound Configuration"
|
||||
msgstr "Конфигурация исходящего соединения"
|
||||
|
||||
msgid "Outdated"
|
||||
msgstr "Устаревшая"
|
||||
|
||||
msgid "Output Network Interface"
|
||||
msgstr "Выходной сетевой интерфейс"
|
||||
|
||||
msgid "Path cannot be empty"
|
||||
msgstr "Путь не может быть пустым"
|
||||
|
||||
msgid "Path must be absolute (start with /)"
|
||||
msgstr "Путь должен быть абсолютным (начинаться с /)"
|
||||
|
||||
msgid "Path must contain at least one directory (like /tmp/cache.db)"
|
||||
msgstr "Путь должен содержать хотя бы одну директорию (например /tmp/cache.db)"
|
||||
|
||||
msgid "Path must end with cache.db"
|
||||
msgstr "Путь должен заканчиваться на cache.db"
|
||||
|
||||
msgid "Pending"
|
||||
msgstr "Ожидает запуска"
|
||||
|
||||
msgid "Podkop"
|
||||
msgstr "Podkop"
|
||||
|
||||
msgid "Podkop Settings"
|
||||
msgstr "Настройки podkop"
|
||||
|
||||
msgid "Podkop will not modify your DHCP configuration"
|
||||
msgstr "Podkop не будет изменять вашу конфигурацию DHCP."
|
||||
|
||||
msgid "Proxy Configuration URL"
|
||||
msgstr "URL конфигурации прокси"
|
||||
|
||||
msgid "Proxy traffic is not routed via FakeIP"
|
||||
msgstr "Прокси-трафик не маршрутизируется через FakeIP"
|
||||
|
||||
msgid "Proxy traffic is routed via FakeIP"
|
||||
msgstr "Прокси-трафик направляется через FakeIP"
|
||||
|
||||
msgid "Regional options cannot be used together"
|
||||
msgstr "Нельзя использовать несколько региональных опций одновременно"
|
||||
|
||||
msgid "Remote Domain Lists"
|
||||
msgstr "Внешние списки доменов"
|
||||
|
||||
msgid "Remote Subnet Lists"
|
||||
msgstr "Внешние списки подсетей"
|
||||
|
||||
msgid "Resolve real IP for routing"
|
||||
msgstr "Разрешение реальных IP-адресов"
|
||||
|
||||
msgid "Restart podkop"
|
||||
msgstr "Перезапустить Podkop"
|
||||
|
||||
msgid "Router DNS is not routed through sing-box"
|
||||
msgstr "DNS роутера не проходит через sing-box"
|
||||
|
||||
msgid "Router DNS is routed through sing-box"
|
||||
msgstr "DNS роутера проходит через sing-box"
|
||||
|
||||
msgid "Routing Excluded IPs"
|
||||
msgstr "Исключённые из маршрутизации IP-адреса"
|
||||
|
||||
msgid "Rules mangle counters"
|
||||
msgstr "Счётчики правил mangle"
|
||||
|
||||
msgid "Rules mangle exist"
|
||||
msgstr "Правила mangle существуют"
|
||||
|
||||
msgid "Rules mangle output counters"
|
||||
msgstr "Счётчики правил mangle output"
|
||||
|
||||
msgid "Rules mangle output exist"
|
||||
msgstr "Правила mangle output существуют"
|
||||
|
||||
msgid "Rules proxy counters"
|
||||
msgstr "Счётчики правил proxy"
|
||||
|
||||
msgid "Rules proxy exist"
|
||||
msgstr "Правила прокси существуют"
|
||||
|
||||
msgid "Run Diagnostic"
|
||||
msgstr "Запустить диагностику"
|
||||
|
||||
msgid "Russia inside restrictions"
|
||||
msgstr "Ограничения Russia inside"
|
||||
|
||||
msgid "Secret key for authenticating remote access to YACD when WAN access is enabled."
|
||||
msgstr "Секретный ключ для аутентификации удаленного доступа к YACD при включенном доступе через WAN."
|
||||
|
||||
msgid "Sections"
|
||||
msgstr "Секции"
|
||||
|
||||
msgid "Select a predefined list for routing"
|
||||
msgstr "Выберите предопределенный список для маршрутизации"
|
||||
|
||||
msgid "Select between VPN and Proxy connection methods for traffic routing"
|
||||
msgstr "Выберите между VPN и Proxy методами для маршрутизации трафика"
|
||||
|
||||
msgid "Select DNS protocol to use"
|
||||
msgstr "Выберите протокол DNS"
|
||||
|
||||
msgid "Select how often the domain or subnet lists are updated automatically"
|
||||
msgstr "Выберите частоту автоматического обновления списков доменов или подсетей."
|
||||
|
||||
msgid "Select how to configure the proxy"
|
||||
msgstr "Выберите способ настройки прокси"
|
||||
|
||||
msgid "Select network interface for VPN connection"
|
||||
msgstr "Выберите сетевой интерфейс для VPN подключения"
|
||||
|
||||
msgid "Select or enter DNS server address"
|
||||
msgstr "Выберите или введите адрес DNS-сервера"
|
||||
|
||||
msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing"
|
||||
msgstr "Выберите или введите путь к файлу кеша sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете"
|
||||
|
||||
msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing"
|
||||
msgstr "Выберите путь к файлу конфигурации sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете"
|
||||
|
||||
msgid "Select the DNS protocol type for the domain resolver"
|
||||
msgstr "Выберите тип протокола DNS для резолвера доменов"
|
||||
|
||||
msgid "Select the list type for adding custom domains"
|
||||
msgstr "Выберите тип списка для добавления пользовательских доменов"
|
||||
|
||||
msgid "Select the list type for adding custom subnets"
|
||||
msgstr "Выберите тип списка для добавления пользовательских подсетей"
|
||||
|
||||
msgid "Select the log level for sing-box"
|
||||
msgstr "Выберите уровень логов для sing-box"
|
||||
|
||||
msgid "Select the network interface from which the traffic will originate"
|
||||
msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик"
|
||||
|
||||
msgid "Select the network interface to which the traffic will originate"
|
||||
msgstr "Выберите сетевой интерфейс, на который будет поступать трафик."
|
||||
|
||||
msgid "Select the WAN interfaces to be monitored"
|
||||
msgstr "Выберите WAN интерфейсы для мониторинга"
|
||||
|
||||
msgid "Selector"
|
||||
msgstr "Selector"
|
||||
|
||||
msgid "Selector Proxy Links"
|
||||
msgstr "Ссылки прокси для Selector"
|
||||
|
||||
msgid "Services info"
|
||||
msgstr "Информация о сервисах"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Настройки"
|
||||
|
||||
msgid "Show sing-box config"
|
||||
msgstr "Показать sing-box конфигурацию"
|
||||
|
||||
msgid "Sing-box"
|
||||
msgstr "Sing-box"
|
||||
|
||||
msgid "Sing-box autostart disabled"
|
||||
msgstr "Автостарт sing-box отключен"
|
||||
|
||||
msgid "Sing-box installed"
|
||||
msgstr "Sing-box установлен"
|
||||
|
||||
msgid "Sing-box listening ports"
|
||||
msgstr "Sing-box слушает порты"
|
||||
|
||||
msgid "Sing-box process running"
|
||||
msgstr "Процесс sing-box запущен"
|
||||
|
||||
msgid "Sing-box service exist"
|
||||
msgstr "Сервис sing-box существует"
|
||||
|
||||
msgid "Sing-box version is compatible (newer than 1.12.4)"
|
||||
msgstr "Версия Sing-box совместима (новее 1.12.4)"
|
||||
|
||||
msgid "Source Network Interface"
|
||||
msgstr "Сетевой интерфейс источника"
|
||||
|
||||
msgid "Specify a local IP address to be excluded from routing"
|
||||
msgstr "Укажите локальный IP-адрес, который следует исключить из маршрутизации."
|
||||
|
||||
msgid "Specify local IP addresses or subnets whose traffic will always be routed through the configured route"
|
||||
msgstr "Укажите локальные IP-адреса или подсети, трафик которых всегда будет направляться через настроенный маршрут."
|
||||
|
||||
msgid "Specify remote URLs to download and use domain lists"
|
||||
msgstr "Укажите URL-адреса для загрузки и использования списков доменов."
|
||||
|
||||
msgid "Specify remote URLs to download and use subnet lists"
|
||||
msgstr "Укажите URL-адреса для загрузки и использования списков подсетей."
|
||||
|
||||
msgid "Specify the path to the list file located on the router filesystem"
|
||||
msgstr "Укажите путь к файлу списка, расположенному в файловой системе маршрутизатора."
|
||||
|
||||
msgid "Start podkop"
|
||||
msgstr "Запустить podkop"
|
||||
|
||||
msgid "Stop podkop"
|
||||
msgstr "Остановить podkop"
|
||||
|
||||
msgid "Successfully copied!"
|
||||
msgstr "Успешно скопировано!"
|
||||
|
||||
msgid "System info"
|
||||
msgstr "Системная информация"
|
||||
|
||||
msgid "System information"
|
||||
msgstr "Системная информация"
|
||||
|
||||
msgid "Table exist"
|
||||
msgstr "Таблица существует"
|
||||
|
||||
msgid "Test latency"
|
||||
msgstr "Тестирование задержки"
|
||||
|
||||
msgid "Text List"
|
||||
msgstr "Текстовый список"
|
||||
|
||||
msgid "The DNS server used to look up the IP address of an upstream DNS server"
|
||||
msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера"
|
||||
|
||||
msgid "The interval between connectivity tests"
|
||||
msgstr "Интервал между тестами подключения"
|
||||
|
||||
msgid "The maximum difference in response times (ms) allowed when comparing servers"
|
||||
msgstr "Максимально допустимая разница во времени отклика (мс) при сравнении серверов"
|
||||
|
||||
msgid "The URL used to test server connectivity"
|
||||
msgstr "URL-адрес, используемый для проверки подключения к серверу"
|
||||
|
||||
msgid "Time in seconds for DNS record caching (default: 60)"
|
||||
msgstr "Время в секундах для кэширования DNS записей (по умолчанию: 60)"
|
||||
|
||||
msgid "Traffic"
|
||||
msgstr "Трафик"
|
||||
|
||||
msgid "Traffic Total"
|
||||
msgstr "Всего трафика"
|
||||
|
||||
msgid "Troubleshooting"
|
||||
msgstr "Устранение неполадок"
|
||||
|
||||
msgid "TTL must be a positive number"
|
||||
msgstr "TTL должно быть положительным числом"
|
||||
|
||||
msgid "TTL value cannot be empty"
|
||||
msgstr "Значение TTL не может быть пустым"
|
||||
|
||||
msgid "UDP (Unprotected DNS)"
|
||||
msgstr "UDP (Незащищённый DNS)"
|
||||
|
||||
msgid "UDP over TCP"
|
||||
msgstr "UDP через TCP"
|
||||
|
||||
msgid "unknown"
|
||||
msgstr "неизвестно"
|
||||
|
||||
msgid "Unknown error"
|
||||
msgstr "Неизвестная ошибка"
|
||||
|
||||
msgid "Uplink"
|
||||
msgstr "Исходящий"
|
||||
|
||||
msgid "URL must start with vless://, ss://, trojan://, socks4/5://, or hysteria2://hy2://"
|
||||
msgstr "URL должен начинаться с vless://, ss://, trojan://, socks4/5:// или hysteria2:// hy2://"
|
||||
|
||||
msgid "URL must use one of the following protocols:"
|
||||
msgstr "URL должен использовать один из следующих протоколов:"
|
||||
|
||||
msgid "URLTest"
|
||||
msgstr "URLTest"
|
||||
|
||||
msgid "URLTest Check Interval"
|
||||
msgstr "Интервал проверки URLTest"
|
||||
|
||||
msgid "URLTest Proxy Links"
|
||||
msgstr "Ссылки прокси для URLTest"
|
||||
|
||||
msgid "URLTest Testing URL"
|
||||
msgstr "URLTest ссылка для проверки"
|
||||
|
||||
msgid "URLTest Tolerance"
|
||||
msgstr "URLTest допустимое отклонение"
|
||||
|
||||
msgid "User Domain List Type"
|
||||
msgstr "Тип пользовательского списка доменов"
|
||||
|
||||
msgid "User Domains"
|
||||
msgstr "Пользовательские домены"
|
||||
|
||||
msgid "User Domains List"
|
||||
msgstr "Список пользовательских доменов"
|
||||
|
||||
msgid "User Subnet List Type"
|
||||
msgstr "Тип пользовательского списка подсетей"
|
||||
|
||||
msgid "User Subnets"
|
||||
msgstr "Пользовательские подсети"
|
||||
|
||||
msgid "User Subnets List"
|
||||
msgstr "Список пользовательских подсетей"
|
||||
|
||||
msgid "Valid"
|
||||
msgstr "Валидно"
|
||||
|
||||
msgid "Validation errors:"
|
||||
msgstr "Ошибки валидации:"
|
||||
|
||||
msgid "View logs"
|
||||
msgstr "Посмотреть логи"
|
||||
|
||||
msgid "Visit Wiki"
|
||||
msgstr "Перейти в wiki"
|
||||
|
||||
msgid "vless://, ss://, trojan://, socks4/5://, hy2/hysteria2:// links"
|
||||
msgstr ""
|
||||
|
||||
msgid "Warning: %s cannot be used together with %s. Previous selections have been removed."
|
||||
msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены."
|
||||
|
||||
msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection."
|
||||
msgstr "Предупреждение: Russia inside может быть использован только с %s. %s уже есть в Russia inside и будет удален из выбранных."
|
||||
|
||||
msgid "YACD Secret Key"
|
||||
msgstr "Секретный ключ YACD"
|
||||
|
||||
msgid "You can select Output Network Interface, by default autodetect"
|
||||
msgstr "Вы можете выбрать выходной сетевой интерфейс, по умолчанию он определяется автоматически."
|
||||
1115
src/frontend/sandbox/po/templates/podkop.pot
Normal file
1115
src/frontend/sandbox/po/templates/podkop.pot
Normal file
File diff suppressed because it is too large
Load Diff
10
src/frontend/sandbox/root/etc/uci-defaults/50_luci-podkop
Normal file
10
src/frontend/sandbox/root/etc/uci-defaults/50_luci-podkop
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
rm -f /var/luci-indexcache*
|
||||
rm -f /tmp/luci-indexcache*
|
||||
|
||||
[ -x /etc/init.d/rpcd ] && /etc/init.d/rpcd reload
|
||||
|
||||
logger -t "podkop" "$timestamp uci-defaults script executed"
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"admin/services/podkop": {
|
||||
"title": "Podkop",
|
||||
"order": 42,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "podkop/podkop"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-podkop" ],
|
||||
"uci": { "podkop": true }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"luci-app-podkop": {
|
||||
"description": "Grant UCI and RPC access to LuCI app podkop",
|
||||
"read": {
|
||||
"file": {
|
||||
"/etc/init.d/podkop": [
|
||||
"exec"
|
||||
],
|
||||
"/usr/bin/podkop": [
|
||||
"exec"
|
||||
]
|
||||
},
|
||||
"ubus": {
|
||||
"service": [
|
||||
"list"
|
||||
]
|
||||
},
|
||||
"uci": [
|
||||
"podkop"
|
||||
]
|
||||
},
|
||||
"write": {
|
||||
"uci": [
|
||||
"podkop"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/frontend/sandbox/xgettext.sh
Normal file
27
src/frontend/sandbox/xgettext.sh
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SRC_DIR="htdocs/luci-static/resources/view/podkop"
|
||||
OUT_POT="po/templates/podkop.pot"
|
||||
ENCODING="UTF-8"
|
||||
WIDTH=120
|
||||
|
||||
mapfile -t FILES < <(find "$SRC_DIR" -type f -name "*.js")
|
||||
if [ ${#FILES[@]} -eq 0 ]; then
|
||||
echo "No JS files found in $SRC_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mapfile -t FILES < <(printf '%s\n' "${FILES[@]}" | sort)
|
||||
mkdir -p "$(dirname "$OUT_POT")"
|
||||
|
||||
echo "Generating POT template from JS files in $SRC_DIR"
|
||||
xgettext --language=JavaScript \
|
||||
--keyword=_ \
|
||||
--from-code="$ENCODING" \
|
||||
--output="$OUT_POT" \
|
||||
--width="$WIDTH" \
|
||||
--package-name="PODKOP" \
|
||||
"${FILES[@]}"
|
||||
|
||||
echo "POT template generated: $OUT_POT"
|
||||
Reference in New Issue
Block a user