feat: Enhance custom domain/subnet input

This commit is contained in:
Ivan K
2024-11-30 17:16:16 +03:00
parent 2fe12f3f4d
commit 82c7c290d9
6 changed files with 502 additions and 132 deletions

View File

@@ -115,14 +115,17 @@ return view.extend({
o.rmempty = false; o.rmempty = false;
o.ucisection = 'main'; o.ucisection = 'main';
o = s.taboption('basic', form.Flag, 'custom_domains_list_enabled', _('User Domain List'), _('Enable and manage your custom list of domains for selective routing')); o = s.taboption('basic', form.ListValue, 'custom_domains_list_enabled', _('User Domain List Type'), _('Select how to add your custom domains'));
o.default = '0'; o.value('disabled', _('Disabled'));
o.value('dynamic', _('Dynamic List'));
o.value('text', _('Text List'));
o.default = 'disabled';
o.rmempty = false; o.rmempty = false;
o.ucisection = 'main'; o.ucisection = 'main';
o = s.taboption('basic', form.DynamicList, 'custom_domains', _('User Domains'), _('Enter domain names without protocols (example: sub.example.com or example.com)')); o = s.taboption('basic', form.DynamicList, 'custom_domains', _('User Domains'), _('Enter domain names without protocols (example: sub.example.com or example.com)'));
o.placeholder = 'Domains list'; o.placeholder = 'Domains list';
o.depends('custom_domains_list_enabled', '1'); o.depends('custom_domains_list_enabled', 'dynamic');
o.rmempty = false; o.rmempty = false;
o.ucisection = 'main'; o.ucisection = 'main';
o.validate = function (section_id, value) { o.validate = function (section_id, value) {
@@ -138,6 +141,31 @@ return view.extend({
return true; return true;
}; };
o = s.taboption('basic', form.TextValue, 'custom_domains_text', _('User Domains List'), _('Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)'));
o.placeholder = 'example.com, sub.example.com\ndomain.com test.com\nsubdomain.domain.com another.com, third.com';
o.depends('custom_domains_list_enabled', 'text');
o.rows = 10;
o.rmempty = false;
o.ucisection = 'main';
o.validate = function (section_id, value) {
if (!value || value.length === 0) {
return true;
}
const domains = value.split(/[,\s\n]/)
.map(d => d.trim())
.filter(d => d.length > 0);
const domainRegex = /^(?!-)[A-Za-z0-9-]+([-.][A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
for (const domain of domains) {
if (!domainRegex.test(domain)) {
return _('Invalid domain format: ' + domain + '. Enter domain without protocol');
}
}
return true;
};
o = s.taboption('basic', form.Flag, 'custom_download_domains_list_enabled', _('Remote Domain Lists'), _('Download and use domain lists from remote URLs')); o = s.taboption('basic', form.Flag, 'custom_download_domains_list_enabled', _('Remote Domain Lists'), _('Download and use domain lists from remote URLs'));
o.default = '0'; o.default = '0';
o.rmempty = false; o.rmempty = false;
@@ -164,14 +192,18 @@ return view.extend({
} }
}; };
o = s.taboption('basic', form.Flag, 'custom_subnets_list_enabled', _('User Subnet List'), _('Enable and manage your custom list of IP subnets for selective routing'));
o.default = '0'; o = s.taboption('basic', form.ListValue, 'custom_subnets_list_enabled', _('User Subnet List Type'), _('Select how to add your custom subnets'));
o.value('disabled', _('Disabled'));
o.value('dynamic', _('Dynamic List'));
o.value('text', _('Text List (comma/space/newline separated)'));
o.default = 'disabled';
o.rmempty = false; o.rmempty = false;
o.ucisection = 'main'; o.ucisection = 'main';
o = s.taboption('basic', form.DynamicList, 'custom_subnets', _('User Subnets'), _('Enter subnet in CIDR notation (example: 103.21.244.0/22)')); o = s.taboption('basic', form.DynamicList, 'custom_subnets', _('User Subnets'), _('Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses'));
o.placeholder = 'Subnets list'; o.placeholder = 'IP or subnet';
o.depends('custom_subnets_list_enabled', '1'); o.depends('custom_subnets_list_enabled', 'dynamic');
o.rmempty = false; o.rmempty = false;
o.ucisection = 'main'; o.ucisection = 'main';
o.validate = function (section_id, value) { o.validate = function (section_id, value) {
@@ -179,15 +211,15 @@ return view.extend({
return true; return true;
} }
const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/; const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
if (!subnetRegex.test(value)) { if (!subnetRegex.test(value)) {
return _('Invalid subnet format. Use format: X.X.X.X/Y (like 103.21.244.0/22)'); return _('Invalid format. Use format: X.X.X.X or X.X.X.X/Y');
} }
// Разбираем IP и маску
const [ip, cidr] = value.split('/'); const [ip, cidr] = value.split('/');
const ipParts = ip.split('.'); const ipParts = ip.split('.');
const cidrNum = parseInt(cidr);
for (const part of ipParts) { for (const part of ipParts) {
const num = parseInt(part); const num = parseInt(part);
@@ -196,13 +228,59 @@ return view.extend({
} }
} }
if (cidrNum < 0 || cidrNum > 32) { if (cidr !== undefined) {
return _('CIDR must be between 0 and 32'); const cidrNum = parseInt(cidr);
if (cidrNum < 0 || cidrNum > 32) {
return _('CIDR must be between 0 and 32');
}
} }
return true; return true;
}; };
o = s.taboption('basic', form.TextValue, 'custom_subnets_text', _('User Subnets List'), _('Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline'));
o.placeholder = '103.21.244.0/22\n8.8.8.8\n1.1.1.1/32, 9.9.9.9 10.10.10.10';
o.depends('custom_subnets_list_enabled', 'text');
o.rows = 10;
o.rmempty = false;
o.ucisection = 'main';
o.validate = function (section_id, value) {
if (!value || value.length === 0) {
return true;
}
// Split by commas, spaces and newlines
const subnets = value.split(/[,\s\n]/)
.map(s => s.trim())
.filter(s => s.length > 0);
const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
for (const subnet of subnets) {
if (!subnetRegex.test(subnet)) {
return _('Invalid format: ' + subnet + '. Use format: X.X.X.X or X.X.X.X/Y');
}
const [ip, cidr] = subnet.split('/');
const ipParts = ip.split('.');
for (const part of ipParts) {
const num = parseInt(part);
if (num < 0 || num > 255) {
return _('IP parts must be between 0 and 255 in: ' + subnet);
}
}
if (cidr !== undefined) {
const cidrNum = parseInt(cidr);
if (cidrNum < 0 || cidrNum > 32) {
return _('CIDR must be between 0 and 32 in: ' + subnet);
}
}
}
return true;
};
o = s.taboption('basic', form.Flag, 'custom_download_subnets_list_enabled', _('Remote Subnet Lists'), _('Download and use subnet lists from remote URLs')); o = s.taboption('basic', form.Flag, 'custom_download_subnets_list_enabled', _('Remote Subnet Lists'), _('Download and use subnet lists from remote URLs'));
o.default = '0'; o.default = '0';
o.rmempty = false; o.rmempty = false;
@@ -409,15 +487,18 @@ return view.extend({
o.rmempty = false; o.rmempty = false;
o.ucisection = 'second'; o.ucisection = 'second';
o = s.taboption('secondary_config', form.Flag, 'second_custom_domains_list_enabled', _('User Domain List'), _('Enable and manage your custom list of domains for selective routing')); o = s.taboption('secondary_config', form.ListValue, 'second_custom_domains_list_enabled', _('User Domain List Type'), _('Select how to add your custom domains'));
o.default = '0'; o.value('disabled', _('Disabled'));
o.value('dynamic', _('Dynamic List'));
o.value('text', _('Text List'));
o.default = 'disabled';
o.rmempty = false; o.rmempty = false;
o.depends('second_enable', '1'); o.depends('second_enable', '1');
o.ucisection = 'second'; o.ucisection = 'second';
o = s.taboption('secondary_config', form.DynamicList, 'second_custom_domains', _('User Domains'), _('Enter domain names without protocols (example: sub.example.com or example.com)')); o = s.taboption('secondary_config', form.DynamicList, 'second_custom_domains', _('User Domains'), _('Enter domain names without protocols (example: sub.example.com or example.com)'));
o.placeholder = 'Domains list'; o.placeholder = 'Domains list';
o.depends('second_custom_domains_list_enabled', '1'); o.depends('second_custom_domains_list_enabled', 'dynamic');
o.rmempty = false; o.rmempty = false;
o.ucisection = 'second'; o.ucisection = 'second';
o.validate = function (section_id, value) { o.validate = function (section_id, value) {
@@ -433,15 +514,10 @@ return view.extend({
return true; return true;
}; };
o = s.taboption('secondary_config', form.Flag, 'second_custom_subnets_list_enabled', _('User Subnet List'), _('Enable and manage your custom list of IP subnets for selective routing')); o = s.taboption('secondary_config', form.TextValue, 'second_custom_domains_text', _('User Domains List'), _('Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)'));
o.default = '0'; o.placeholder = 'example.com, sub.example.com\ndomain.com test.com\nsubdomain.domain.com another.com, third.com';
o.rmempty = false; o.depends('second_custom_domains_list_enabled', 'text');
o.depends('second_enable', '1'); o.rows = 10;
o.ucisection = 'second';
o = s.taboption('secondary_config', form.DynamicList, 'second_custom_subnets', _('User Subnets'), _('Enter subnet in CIDR notation (example: 103.21.244.0/22)'));
o.placeholder = 'Subnets list';
o.depends('second_custom_subnets_list_enabled', '1');
o.rmempty = false; o.rmempty = false;
o.ucisection = 'second'; o.ucisection = 'second';
o.validate = function (section_id, value) { o.validate = function (section_id, value) {
@@ -449,15 +525,47 @@ return view.extend({
return true; return true;
} }
const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/; const domains = value.split(/[,\s\n]/)
.map(d => d.trim())
.filter(d => d.length > 0);
const domainRegex = /^(?!-)[A-Za-z0-9-]+([-.][A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
for (const domain of domains) {
if (!domainRegex.test(domain)) {
return _('Invalid domain format: ' + domain + '. Enter domain without protocol');
}
}
return true;
};
o = s.taboption('secondary_config', form.ListValue, 'second_custom_subnets_list_enabled', _('User Subnet List Type'), _('Select how to add your custom subnets'));
o.value('disabled', _('Disabled'));
o.value('dynamic', _('Dynamic List'));
o.value('text', _('Text List'));
o.default = 'disabled';
o.rmempty = false;
o.depends('second_enable', '1');
o.ucisection = 'second';
o = s.taboption('secondary_config', form.DynamicList, 'second_custom_subnets', _('User Subnets'), _('Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses'));
o.placeholder = 'IP or subnet';
o.depends('second_custom_subnets_list_enabled', 'dynamic');
o.rmempty = false;
o.ucisection = 'second';
o.validate = function (section_id, value) {
if (!value || value.length === 0) {
return true;
}
const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
if (!subnetRegex.test(value)) { if (!subnetRegex.test(value)) {
return _('Invalid subnet format. Use format: X.X.X.X/Y (like 103.21.244.0/22)'); return _('Invalid format. Use format: X.X.X.X or X.X.X.X/Y');
} }
const [ip, cidr] = value.split('/'); const [ip, cidr] = value.split('/');
const ipParts = ip.split('.'); const ipParts = ip.split('.');
const cidrNum = parseInt(cidr);
for (const part of ipParts) { for (const part of ipParts) {
const num = parseInt(part); const num = parseInt(part);
@@ -466,13 +574,58 @@ return view.extend({
} }
} }
if (cidrNum < 0 || cidrNum > 32) { if (cidr !== undefined) {
return _('CIDR must be between 0 and 32'); const cidrNum = parseInt(cidr);
if (cidrNum < 0 || cidrNum > 32) {
return _('CIDR must be between 0 and 32');
}
} }
return true; return true;
}; };
o = s.taboption('secondary_config', form.TextValue, 'second_custom_subnets_text', _('User Subnets List'), _('Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline'));
o.placeholder = '103.21.244.0/22\n8.8.8.8\n1.1.1.1/32, 9.9.9.9 10.10.10.10';
o.depends('second_custom_subnets_list_enabled', 'text');
o.rows = 10;
o.rmempty = false;
o.ucisection = 'second';
o.validate = function (section_id, value) {
if (!value || value.length === 0) {
return true;
}
const subnets = value.split(/[,\s\n]/)
.map(s => s.trim())
.filter(s => s.length > 0);
const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
for (const subnet of subnets) {
if (!subnetRegex.test(subnet)) {
return _('Invalid format: ' + subnet + '. Use format: X.X.X.X or X.X.X.X/Y');
}
const [ip, cidr] = subnet.split('/');
const ipParts = ip.split('.');
for (const part of ipParts) {
const num = parseInt(part);
if (num < 0 || num > 255) {
return _('IP parts must be between 0 and 255 in: ' + subnet);
}
}
if (cidr !== undefined) {
const cidrNum = parseInt(cidr);
if (cidrNum < 0 || cidrNum > 32) {
return _('CIDR must be between 0 and 32 in: ' + subnet);
}
}
}
return true;
};
return m.render(); return m.render();
} }
}); });

View File

@@ -218,4 +218,76 @@ msgid "CIDR must be between 0 and 32"
msgstr "CIDR должен быть между 0 и 32" msgstr "CIDR должен быть между 0 и 32"
msgid "Invalid IP format. Use format: X.X.X.X (like 192.168.1.1)" msgid "Invalid IP format. Use format: X.X.X.X (like 192.168.1.1)"
msgstr "Неверный формат IP. Используйте формат: X.X.X.X (например: 192.168.1.1)" msgstr "Неверный формат IP. Используйте формат: X.X.X.X (например: 192.168.1.1)"
msgid "User Domain List Type"
msgstr "Тип пользовательского списка доменов"
msgid "Select how to add your custom domains"
msgstr "Выберите способ добавления пользовательских доменов"
msgid "Disabled"
msgstr "Отключено"
msgid "Dynamic List"
msgstr "Динамический список"
msgid "Text List"
msgstr "Текстовый список"
msgid "User Domains List"
msgstr "Список пользовательских доменов"
msgid "Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)"
msgstr "Введите имена доменов через запятую, пробел или новую строку (пример: sub.example.com, example.com или один домен на строку)"
msgid "Invalid domain format: %s. Enter domain without protocol"
msgstr "Неверный формат домена: %s. Введите домен без протокола"
msgid "User Subnet List Type"
msgstr "Тип пользовательского списка подсетей"
msgid "Select how to add your custom subnets"
msgstr "Выберите способ добавления пользовательских подсетей"
msgid "Text List (comma/space/newline separated)"
msgstr "Текстовый список (разделенный запятыми/пробелами/новыми строками)"
msgid "Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses"
msgstr "Введите подсети в нотации CIDR (пример: 103.21.244.0/22) или отдельные IP-адреса"
msgid "User Subnets List"
msgstr "Список пользовательских подсетей"
msgid "Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline"
msgstr "Введите подсети в нотации CIDR или отдельные IP-адреса через запятую, пробел или новую строку"
msgid "Invalid format. Use format: X.X.X.X or X.X.X.X/Y"
msgstr "Неверный формат. Используйте формат: X.X.X.X или X.X.X.X/Y"
msgid "IP parts must be between 0 and 255 in: %s"
msgstr "Части IP-адреса должны быть между 0 и 255 в: %s"
msgid "Configuration Type"
msgstr "Тип конфигурации"
msgid "Select how to configure the proxy"
msgstr "Выберите способ настройки прокси"
msgid "Connection URL"
msgstr "URL подключения"
msgid "Outbound Config"
msgstr "Конфигурация Outbound"
msgid "Outbound Configuration"
msgstr "Конфигурация исходящего соединения"
msgid "Enter complete outbound configuration in JSON format"
msgstr "Введите полную конфигурацию исходящего соединения в формате JSON"
msgid "JSON must contain at least type, server and server_port fields"
msgstr "JSON должен содержать как минимум поля type, server и server_port"
msgid "Invalid JSON format"
msgstr "Неверный формат JSON"

View File

@@ -218,4 +218,76 @@ msgid "CIDR must be between 0 and 32"
msgstr "" msgstr ""
msgid "Invalid IP format. Use format: X.X.X.X (like 192.168.1.1)" msgid "Invalid IP format. Use format: X.X.X.X (like 192.168.1.1)"
msgstr ""
msgid "User Domain List Type"
msgstr ""
msgid "Select how to add your custom domains"
msgstr ""
msgid "Disabled"
msgstr ""
msgid "Dynamic List"
msgstr ""
msgid "Text List"
msgstr ""
msgid "User Domains List"
msgstr ""
msgid "Enter domain names separated by comma, space or newline (example: sub.example.com, example.com or one domain per line)"
msgstr ""
msgid "Invalid domain format: %s. Enter domain without protocol"
msgstr ""
msgid "User Subnet List Type"
msgstr ""
msgid "Select how to add your custom subnets"
msgstr ""
msgid "Text List (comma/space/newline separated)"
msgstr ""
msgid "Enter subnets in CIDR notation (example: 103.21.244.0/22) or single IP addresses"
msgstr ""
msgid "User Subnets List"
msgstr ""
msgid "Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline"
msgstr ""
msgid "Invalid format. Use format: X.X.X.X or X.X.X.X/Y"
msgstr ""
msgid "IP parts must be between 0 and 255 in: %s"
msgstr ""
msgid "Configuration Type"
msgstr ""
msgid "Select how to configure the proxy"
msgstr ""
msgid "Connection URL"
msgstr ""
msgid "Outbound Config"
msgstr ""
msgid "Outbound Configuration"
msgstr ""
msgid "Enter complete outbound configuration in JSON format"
msgstr ""
msgid "JSON must contain at least type, server and server_port fields"
msgstr ""
msgid "Invalid JSON format"
msgstr "" msgstr ""

View File

@@ -50,9 +50,6 @@ define Package/podkop/install
$(INSTALL_DIR) $(1)/etc/config $(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/etc/config/podkop $(1)/etc/config/podkop $(INSTALL_CONF) ./files/etc/config/podkop $(1)/etc/config/podkop
$(INSTALL_DIR) $(1)/etc/podkop
$(INSTALL_DATA) ./files/etc/podkop/* $(1)/etc/podkop/
$(INSTALL_DIR) $(1)/etc/hotplug.d/iface $(INSTALL_DIR) $(1)/etc/hotplug.d/iface
$(INSTALL_DATA) ./files/etc/hotplug.d/iface/50-podkop $(1)/etc/hotplug.d/iface/50-podkop $(INSTALL_DATA) ./files/etc/hotplug.d/iface/50-podkop $(1)/etc/hotplug.d/iface/50-podkop
endef endef

View File

@@ -96,20 +96,25 @@ start_service() {
if [ "$proxy_config_type" = "outbound" ]; then if [ "$proxy_config_type" = "outbound" ]; then
config_get outbound_json main "outbound_json" config_get outbound_json main "outbound_json"
if [ -n "$outbound_json" ]; then if [ -n "$outbound_json" ]; then
echo "$outbound_json" > "$outbound_main" echo '{"outbounds":[' > "$outbound_main"
jq '.tag = "main"' "$outbound_main" > "${outbound_main}.tmp" && mv "${outbound_main}.tmp" "$outbound_main" echo "$outbound_json" | jq '. + {tag: "main"}' >> "$outbound_main"
echo ']}' >> "$outbound_main"
else else
log "Missing main outbound JSON configuration" log "Missing main outbound JSON configuration"
rm -f "$outbound_main" "$outbound_second"
return return
fi fi
else else
config_get proxy_string main "proxy_string" config_get proxy_string main "proxy_string"
if [[ "$proxy_string" =~ ^ss:// ]]; then if [[ "$proxy_string" =~ ^ss:// ]]; then
sing_box_config_outbound_shadowsocks "$proxy_string" "$outbound_main" main sing_box_config_shadowsocks "$proxy_string" "1602"
jq '.outbounds[0] + {tag: "main"} | {outbounds: [.]}' /etc/sing-box/config.json > "$outbound_main"
elif [[ "$proxy_string" =~ ^vless:// ]]; then elif [[ "$proxy_string" =~ ^vless:// ]]; then
sing_box_config_outbound_vless "$proxy_string" "$outbound_main" main sing_box_config_vless "$proxy_string" "1602"
jq '.outbounds[0] + {tag: "main"} | {outbounds: [.]}' /etc/sing-box/config.json > "$outbound_main"
else else
log "Unsupported proxy type or missing configuration for main" log "Unsupported proxy type or missing configuration for main"
rm -f "$outbound_main" "$outbound_second"
return return
fi fi
fi fi
@@ -119,26 +124,62 @@ start_service() {
if [ "$proxy_config_type" = "outbound" ]; then if [ "$proxy_config_type" = "outbound" ]; then
config_get outbound_json second "second_outbound_json" config_get outbound_json second "second_outbound_json"
if [ -n "$outbound_json" ]; then if [ -n "$outbound_json" ]; then
echo "$outbound_json" > "$outbound_second" echo '{"outbounds":[' > "$outbound_second"
jq '.tag = "second"' "$outbound_second" > "${outbound_second}.tmp" && mv "${outbound_second}.tmp" "$outbound_second" echo "$outbound_json" | jq '. + {tag: "second"}' >> "$outbound_second"
echo ']}' >> "$outbound_second"
else else
log "Missing second outbound JSON configuration" log "Missing second outbound JSON configuration"
rm -f "$outbound_main" "$outbound_second"
return return
fi fi
else else
config_get proxy_string "second" "second_proxy_string" config_get proxy_string "second" "second_proxy_string"
if [[ "$proxy_string" =~ ^ss:// ]]; then if [[ "$proxy_string" =~ ^ss:// ]]; then
sing_box_config_outbound_shadowsocks "$proxy_string" "$outbound_second" second sing_box_config_shadowsocks "$proxy_string" "1603"
jq '.outbounds[0] + {tag: "second"} | {outbounds: [.]}' /etc/sing-box/config.json > "$outbound_second"
elif [[ "$proxy_string" =~ ^vless:// ]]; then elif [[ "$proxy_string" =~ ^vless:// ]]; then
sing_box_config_outbound_vless "$proxy_string" "$outbound_second" second sing_box_config_vless "$proxy_string" "1603"
jq '.outbounds[0] + {tag: "second"} | {outbounds: [.]}' /etc/sing-box/config.json > "$outbound_second"
else else
log "Unsupported proxy type or missing configuration for second" log "Unsupported proxy type or missing configuration for second"
rm -f "$outbound_main" "$outbound_second"
return return
fi fi
fi fi
jq --argjson outbounds "$(jq -s '{"outbounds": [{"type":"selector","tag":"proxy","outbounds":["main","second"]}] + .[0].outbounds + .[1].outbounds}' "$outbound_main" "$outbound_second")" \ jq -s '{
'.outbounds += $outbounds.outbounds' /etc/podkop/sing-box-two-proxy-template.json >/etc/sing-box/config.json "log": {"level": "warn"},
"inbounds": [
{
"type": "tproxy",
"listen": "::",
"listen_port": 1602,
"sniff": false,
"tag": "main"
},
{
"type": "tproxy",
"listen": "::",
"listen_port": 1603,
"sniff": false,
"tag": "second"
}
],
"outbounds": (.[0].outbounds + .[1].outbounds),
"route": {
"rules": [
{
"inbound": "main",
"outbound": "main"
},
{
"inbound": "second",
"outbound": "second"
}
],
"auto_detect_interface": true
}
}' "$outbound_main" "$outbound_second" > /etc/sing-box/config.json
rm -f "$outbound_main" "$outbound_second" rm -f "$outbound_main" "$outbound_second"
@@ -326,6 +367,7 @@ remove_cron_job() {
} }
list_update() { list_update() {
# Main domains processing
config_get_bool domain_list_enabled "main" "domain_list_enabled" "0" config_get_bool domain_list_enabled "main" "domain_list_enabled" "0"
if [ "$domain_list_enabled" -eq 1 ]; then if [ "$domain_list_enabled" -eq 1 ]; then
log "Adding a common domains list" log "Adding a common domains list"
@@ -335,15 +377,24 @@ list_update() {
dnsmasq_config_check podkop-domains.lst dnsmasq_config_check podkop-domains.lst
fi fi
config_get_bool custom_domains_list_enabled "main" "custom_domains_list_enabled" "0" # Main custom domains processing
if [ "$custom_domains_list_enabled" -eq 1 ]; then config_get custom_domains_list_type "main" "custom_domains_list_enabled" "disabled"
if [ "$custom_domains_list_type" != "disabled" ]; then
log "Adding a custom domains list" log "Adding a custom domains list"
add_set "podkop_domains" "main" add_set "podkop_domains" "main"
rm -f /tmp/dnsmasq.d/podkop-custom-domains.lst rm -f /tmp/dnsmasq.d/podkop-custom-domains.lst
config_list_foreach main custom_domains "list_custom_domains_create" "podkop"
if [ "$custom_domains_list_type" = "dynamic" ]; then
config_list_foreach main custom_domains "list_custom_domains_create" "podkop"
elif [ "$custom_domains_list_type" = "text" ]; then
config_get custom_domains_text main "custom_domains_text"
process_domains_text "$custom_domains_text" "podkop"
fi
dnsmasq_config_check podkop-custom-domains.lst dnsmasq_config_check podkop-custom-domains.lst
fi fi
# Main custom download domains
config_get_bool custom_download_domains_list_enabled "main" "custom_download_domains_list_enabled" "0" config_get_bool custom_download_domains_list_enabled "main" "custom_download_domains_list_enabled" "0"
if [ "$custom_download_domains_list_enabled" -eq 1 ]; then if [ "$custom_download_domains_list_enabled" -eq 1 ]; then
log "Adding a custom domains list from URL" log "Adding a custom domains list from URL"
@@ -351,6 +402,7 @@ list_update() {
config_list_foreach main custom_download_domains "list_custom_download_domains_create" "podkop" config_list_foreach main custom_download_domains "list_custom_download_domains_create" "podkop"
fi fi
# Main domains delist
config_get_bool delist_domains_enabled "main" "delist_domains_enabled" "0" config_get_bool delist_domains_enabled "main" "delist_domains_enabled" "0"
if [ "$delist_domains_enabled" -eq 1 ] && [ "$domain_list_enabled" -eq 1 ]; then if [ "$delist_domains_enabled" -eq 1 ] && [ "$domain_list_enabled" -eq 1 ]; then
log "Exclude domains from the common list" log "Exclude domains from the common list"
@@ -358,20 +410,56 @@ list_update() {
dnsmasq_config_check podkop-domains.lst dnsmasq_config_check podkop-domains.lst
fi fi
if [ "$domain_list_enabled" -eq 1 ] || [ "$custom_domains_list_enabled" -eq 1 ]; then # Main subnets processing
/etc/init.d/dnsmasq restart config_get_bool subnets_list_enabled "main" "subnets_list_enabled" "0"
if [ "$subnets_list_enabled" -eq 1 ]; then
log "Adding a subnets from list"
mkdir -p /tmp/podkop
add_set "podkop_subnets" "main"
config_list_foreach main subnets "list_subnets_download"
fi fi
config_get_bool second_custom_domains_list_enabled "second" "second_custom_domains_list_enabled" "0" # Main custom subnets
if [ "$second_custom_domains_list_enabled" -eq 1 ]; then config_get custom_subnets_list_type "main" "custom_subnets_list_enabled" "disabled"
if [ "$custom_subnets_list_type" != "disabled" ]; then
log "Adding a custom subnets list"
add_set "podkop_subnets" "main"
if [ "$custom_subnets_list_type" = "dynamic" ]; then
config_list_foreach main custom_subnets list_custom_subnets_preprocess "podkop"
elif [ "$custom_subnets_list_type" = "text" ]; then
config_get custom_subnets_text main "custom_subnets_text"
process_subnets_text "$custom_subnets_text" "podkop"
fi
fi
# Main custom download subnets
config_get_bool custom_download_subnets_list_enabled "main" "custom_download_subnets_list_enabled" "0"
if [ "$custom_download_subnets_list_enabled" -eq 1 ]; then
log "Adding a subnets from URL"
mkdir -p /tmp/podkop
add_set "podkop_subnets" "main"
config_list_foreach main custom_download_subnets "list_subnets_download"
fi
# Second custom domains processing
config_get second_custom_domains_list_type "second" "second_custom_domains_list_enabled" "disabled"
if [ "$second_custom_domains_list_type" != "disabled" ]; then
log "Adding a custom domains list. Second podkop" log "Adding a custom domains list. Second podkop"
add_set "podkop2_domains" "second" add_set "podkop2_domains" "second"
rm -f /tmp/dnsmasq.d/podkop2-custom-domains.lst rm -f /tmp/dnsmasq.d/podkop2-custom-domains.lst
config_list_foreach second second_custom_domains "list_delist_domains"
config_list_foreach second second_custom_domains "list_custom_domains_create" "podkop2" if [ "$second_custom_domains_list_type" = "dynamic" ]; then
config_list_foreach second second_custom_domains "list_custom_domains_create" "podkop2"
elif [ "$second_custom_domains_list_type" = "text" ]; then
config_get second_custom_domains_text second "second_custom_domains_text"
process_domains_text "$second_custom_domains_text" "podkop2"
fi
dnsmasq_config_check podkop2-custom-domains.lst dnsmasq_config_check podkop2-custom-domains.lst
fi fi
# Second service domains
config_get_bool second_domain_service_enabled "second" "second_domain_service_enabled" "0" config_get_bool second_domain_service_enabled "second" "second_domain_service_enabled" "0"
if [ "$second_domain_service_enabled" -eq 1 ]; then if [ "$second_domain_service_enabled" -eq 1 ]; then
log "Adding a service for podkop2" log "Adding a service for podkop2"
@@ -382,38 +470,26 @@ list_update() {
dnsmasq_config_check podkop2-domains.lst dnsmasq_config_check podkop2-domains.lst
fi fi
if [ "$second_custom_domains_list_enabled" -eq 1 ] || [ "$second_domain_service_enabled" -eq 1 ]; then # Second custom subnets
/etc/init.d/dnsmasq restart config_get second_custom_subnets_list_type "second" "second_custom_subnets_list_enabled" "disabled"
fi if [ "$second_custom_subnets_list_type" != "disabled" ]; then
config_get_bool subnets_list_enabled "main" "subnets_list_enabled" "0"
if [ "$subnets_list_enabled" -eq 1 ]; then
log "Adding a subnets from list"
mkdir -p /tmp/podkop
add_set "podkop_subnets" "main"
config_list_foreach main subnets "list_subnets_download"
fi
config_get_bool custom_download_subnets_list_enabled "main" "custom_download_subnets_list_enabled" "0"
if [ "$custom_download_subnets_list_enabled" -eq 1 ]; then
log "Adding a subnets from URL"
mkdir -p /tmp/podkop
add_set "podkop_subnets" "main"
config_list_foreach main custom_download_subnets "list_subnets_download"
fi
config_get_bool custom_subnets_list_enabled "main" "custom_subnets_list_enabled" "0"
if [ "$custom_subnets_list_enabled" -eq 1 ]; then
log "Adding a custom subnets list"
add_set "podkop_subnets" "main"
config_list_foreach main custom_subnets "list_custom_subnets_create" "podkop"
fi
config_get_bool second_custom_subnets_list_enabled "second" "second_custom_subnets_list_enabled" "0"
if [ "$second_custom_subnets_list_enabled" -eq 1 ]; then
log "Adding a custom subnets list. Second" log "Adding a custom subnets list. Second"
add_set "podkop2_subnets" "second" add_set "podkop2_subnets" "second"
config_list_foreach second second_custom_subnets "list_custom_subnets_create" "podkop2"
if [ "$second_custom_subnets_list_type" = "dynamic" ]; then
config_list_foreach second second_custom_subnets list_custom_subnets_preprocess "podkop2"
elif [ "$second_custom_subnets_list_type" = "text" ]; then
config_get second_custom_subnets_text second "second_custom_subnets_text"
process_subnets_text "$second_custom_subnets_text" "podkop2"
fi
fi
# Restart dnsmasq if needed
if [ "$domain_list_enabled" -eq 1 ] || [ "$custom_domains_list_type" != "disabled" ] || \
[ "$custom_download_domains_list_enabled" -eq 1 ] || \
[ "$second_custom_domains_list_type" != "disabled" ] || \
[ "$second_domain_service_enabled" -eq 1 ]; then
/etc/init.d/dnsmasq restart
fi fi
} }
@@ -889,38 +965,33 @@ sing_box_config_vless() {
get_param() { get_param() {
local param="$1" local param="$1"
local value=$(echo "$STRING" | sed -n "s/.*[?&]$param=\([^&?#]*\).*/\1/p" | sed 's/%2F/\//g; s/%2C/,/g; s/%3D/=/g') local value=$(echo "$STRING" | sed -n "s/.*[?&]$param=\([^&?#]*\).*/\1/p")
value=$(echo "$value" | sed 's/%2F/\//gi; s/%2C/,/gi; s/%3D/=/gi; s/%2B/+/gi')
echo "$value" echo "$value"
} }
# Extract basic parameters
uuid=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1) uuid=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1)
server=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f1) server=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f1)
port=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f2 | cut -d'?' -f1 | cut -d'/' -f1) port=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f2 | cut -d'?' -f1 | cut -d'/' -f1)
# Get all possible parameters
type=$(get_param "type") type=$(get_param "type")
security=$(get_param "security") security=$(get_param "security")
sni=$(get_param "sni") sni=$(get_param "sni")
fp=$(get_param "fp") fp=$(get_param "fp")
flow=$(get_param "flow") flow=$(get_param "flow")
# Reality specific
pbk=$(get_param "pbk") pbk=$(get_param "pbk")
sid=$(get_param "sid") sid=$(get_param "sid")
# TLS specific
alpn=$(get_param "alpn") alpn=$(get_param "alpn")
if [ -z "$alpn" ]; then if [ -z "$alpn" ]; then
alpn="h2,http/1.1" alpn="h2,http/1.1"
fi fi
alpn_json=$(echo "$alpn" | tr ',' '\n' | jq -R . | jq -s .) alpn_json=$(echo "$alpn" | tr ',' '\n' | jq -R . | jq -s .)
# WebSocket specific
path=$(get_param "path") path=$(get_param "path")
host=$(get_param "host") host=$(get_param "host")
# Create base config
cat > /tmp/vless_config.json << EOF cat > /tmp/vless_config.json << EOF
{ {
"log": { "log": {
@@ -942,7 +1013,6 @@ sing_box_config_vless() {
"uuid": "$uuid" "uuid": "$uuid"
EOF EOF
# Add transport configuration if needed
if [ "$type" = "ws" ]; then if [ "$type" = "ws" ]; then
cat >> /tmp/vless_config.json << EOF cat >> /tmp/vless_config.json << EOF
, ,
@@ -968,7 +1038,6 @@ EOF
EOF EOF
fi fi
# Add security configuration
if [ "$security" = "reality" ]; then if [ "$security" = "reality" ]; then
if [ -n "$flow" ]; then if [ -n "$flow" ]; then
echo " ,\"flow\": \"$flow\"" >> /tmp/vless_config.json echo " ,\"flow\": \"$flow\"" >> /tmp/vless_config.json
@@ -1010,7 +1079,6 @@ EOF
echo " }" >> /tmp/vless_config.json echo " }" >> /tmp/vless_config.json
fi fi
# Close outbound and add route
cat >> /tmp/vless_config.json << EOF cat >> /tmp/vless_config.json << EOF
} }
], ],
@@ -1027,7 +1095,6 @@ sing_box_config_outbound_json() {
local json_config="$1" local json_config="$1"
local listen_port="$2" local listen_port="$2"
# Create temporary file with base config structure
cat > /tmp/base_config.json << EOF cat > /tmp/base_config.json << EOF
{ {
"log": { "log": {
@@ -1048,10 +1115,7 @@ sing_box_config_outbound_json() {
} }
EOF EOF
# Add the outbound config using jq
jq --argjson outbound "$json_config" '.outbounds += [$outbound]' /tmp/base_config.json > /etc/sing-box/config.json jq --argjson outbound "$json_config" '.outbounds += [$outbound]' /tmp/base_config.json > /etc/sing-box/config.json
# Cleanup
rm -f /tmp/base_config.json rm -f /tmp/base_config.json
} }
@@ -1060,4 +1124,51 @@ sing_box_config_check() {
log "Sing-box configuration is invalid" log "Sing-box configuration is invalid"
return return
fi fi
}
process_domains_text() {
local text="$1"
local name="$2"
local tmp_file=$(mktemp)
echo "$text" > "$tmp_file"
sed 's/[, ]\+/\n/g' "$tmp_file" | while IFS= read -r domain; do
domain=$(echo "$domain" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if [ -n "$domain" ]; then
list_custom_domains_create "$domain" "$name"
fi
done
rm -f "$tmp_file"
}
process_subnets_text() {
local text="$1"
local name="$2"
local tmp_file=$(mktemp)
echo "$text" > "$tmp_file"
sed 's/[, ]\+/\n/g' "$tmp_file" | while IFS= read -r subnet; do
subnet=$(echo "$subnet" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if [ -n "$subnet" ]; then
if ! echo "$subnet" | grep -q "/"; then
subnet="$subnet/32"
fi
list_custom_subnets_create "$subnet" "$name"
fi
done
rm -f "$tmp_file"
}
list_custom_subnets_preprocess() {
local subnet="$1"
local name="$2"
if ! echo "$subnet" | grep -q "/"; then
subnet="$subnet/32"
fi
list_custom_subnets_create "$subnet" "$name"
} }

View File

@@ -1,35 +0,0 @@
{
"log": {
"level": "warn"
},
"inbounds": [
{
"type": "tproxy",
"listen": "::",
"listen_port": 1602,
"sniff": false,
"tag": "main"
},
{
"type": "tproxy",
"listen": "::",
"listen_port": 1603,
"sniff": false,
"tag": "second"
}
],
"outbounds": [],
"route": {
"rules": [
{
"inbound": "main",
"outbound": "main"
},
{
"inbound": "second",
"outbound": "second"
}
],
"auto_detect_interface": true
}
}