mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 17:55:13 +08:00
🍓 Sync 2026-08-18 01:07:28
This commit is contained in:
1 parent
522b0fc0b6
commit
f3ba5f7b5d
53 files changed
+4613
-4898
No files matched your search
+2
-2
@@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=amule
|
||||
PKG_VERSION:=3.0.0
|
||||
PKG_RELEASE:=2
|
||||
PKG_VERSION:=2.3.3
|
||||
PKG_RELEASE:=3
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/amule-project/amule/tar.gz/$(PKG_VERSION)?
|
||||
|
||||
+2
-2
@@ -5,12 +5,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=antileech
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_DATE:=2021-12-05
|
||||
PKG_SOURCE_URL:=https://github.com/persmule/amule-dlp.antiLeech.git
|
||||
PKG_SOURCE_VERSION:=1b14ed828ed227933333de31ffb8e24e6c10451c
|
||||
PKG_SOURCE_VERSION:=null
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_FIXUP:=autoreconf
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ include $(TOPDIR)/rules.mk
|
||||
PKG_ARCH_BAIDUDRIVE:=$(ARCH)
|
||||
|
||||
PKG_NAME:=baidudrive
|
||||
PKG_VERSION:=linkeasefull-runtime-v3.0.8
|
||||
PKG_RELEASE:=10
|
||||
PKG_VERSION:=1.0.4
|
||||
PKG_RELEASE:=11
|
||||
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/baidudrive-runtime-v$(PKG_VERSION)/
|
||||
PKG_HASH:=skip
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=BaiduPCS-Go
|
||||
PKG_VERSION:=4.0.1
|
||||
PKG_RELEASE:=5
|
||||
PKG_VERSION:=3.9.0
|
||||
PKG_RELEASE:=6
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/qjfoidnh/BaiduPCS-Go/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=boltbrowser
|
||||
PKG_RELEASE:=6
|
||||
PKG_RELEASE:=7
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/br0xen/boltbrowser.git
|
||||
PKG_SOURCE_DATE:=2023-05-31
|
||||
PKG_SOURCE_VERSION:=6ad3511ddb3c271fbaed2f22e409aa453ed89042
|
||||
PKG_SOURCE_VERSION:=null
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-3.0-or-later
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# Copyright (C) 2021 ImmortalWrt.org
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ipt2socks-rs
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/fokx/ipt2socks-rs.git
|
||||
PKG_SOURCE_VERSION:=5ec743ea93e88c5733614b6588f5eb0a25a3d5db
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_BUILD_DEPENDS:=rust/host
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/rust/rust-package.mk
|
||||
|
||||
define Package/ipt2socks-rs
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=Web Servers/Proxies
|
||||
TITLE:=Convert iptables/nftables to SOCKS5
|
||||
URL:=https://github.com/fokx/ipt2socks-rs.git
|
||||
DEPENDS:=$$(RUST_ARCH_DEPENDS)
|
||||
endef
|
||||
|
||||
define Package/ipt2socks-rs/description
|
||||
A utility to convert iptables/nftables (REDIRECT/TPROXY) traffic to SOCKS5 (TCP/UDP).
|
||||
endef
|
||||
|
||||
define Package/ipt2socks-rs/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) \
|
||||
$(PKG_INSTALL_DIR)/bin/ipt2socks-rs \
|
||||
$(1)/usr/bin/ipt2socks
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,ipt2socks-rs))
|
||||
@@ -12,7 +12,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
||||
PKG_NAME:=linkease-common-bin
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=1.7.5
|
||||
PKG_RELEASE:=6
|
||||
PKG_RELEASE:=7
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
|
||||
ARCH_HEXCODE=1b0c
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=78c1b63d4be857054a8e6e37bc22eb235e3f151e
|
||||
PKG_SOURCE_VERSION:=3cdc853c91c55560e118f1f7e343d1ae0bd5e566
|
||||
PKG_SOURCE:=linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
||||
PKG_NAME:=linkease
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=1.7.5
|
||||
PKG_RELEASE:=9
|
||||
PKG_RELEASE:=10
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
|
||||
ARCH_HEXCODE=1b0c
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=78c1b63d4be857054a8e6e37bc22eb235e3f151e
|
||||
PKG_SOURCE_VERSION:=3cdc853c91c55560e118f1f7e343d1ae0bd5e566
|
||||
PKG_SOURCE:=linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
|
||||
|
||||
@@ -11,7 +11,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
|
||||
PKG_NAME:=linkeasefull
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
|
||||
PKG_SOURCE_DATE:=3.0.5
|
||||
PKG_RELEASE:=8
|
||||
PKG_RELEASE:=9
|
||||
|
||||
ARCH_HEXCODE:=
|
||||
ifeq ($(ARCH),x86_64)
|
||||
@@ -24,7 +24,7 @@ LINKEASE_RUNTIME_ARCH:=arm64
|
||||
PKG_HASH:=skip
|
||||
endif
|
||||
|
||||
PKG_SOURCE_VERSION:=78c1b63d4be857054a8e6e37bc22eb235e3f151e
|
||||
PKG_SOURCE_VERSION:=3cdc853c91c55560e118f1f7e343d1ae0bd5e566
|
||||
PKG_SOURCE:=linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkeasefull-runtime-v$(PKG_SOURCE_DATE)/
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH)
|
||||
|
||||
@@ -11,7 +11,7 @@ LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix
|
||||
PKG_MAINTAINER:=timsaya
|
||||
|
||||
PKG_VERSION:=0.12.9
|
||||
PKG_RELEASE:=7
|
||||
PKG_RELEASE:=8
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
|
||||
@@ -9,11 +9,6 @@
|
||||
var BANDIX_COLOR_UPLOAD = '#f97316'; // 橙色 - 上传/上行
|
||||
var BANDIX_COLOR_DOWNLOAD = '#06b6d4'; // 青色 - 下载/下行
|
||||
|
||||
// 远程广告配置
|
||||
var BANDIX_AD_CONFIG_URL = 'https://raw.githubusercontent.com/timsaya/bandix-ads/main/ads.json';
|
||||
var BANDIX_AD_DEFAULT_REFRESH_INTERVAL = 3600;
|
||||
var BANDIX_AD_RETRY_INTERVAL = 900;
|
||||
|
||||
// 悬浮框背景色定义
|
||||
var BANDIX_TOOLTIP_BG_OPENWRT2020_LIGHT = '#ffffff'; // OpenWrt 2020 浅色主题悬浮框背景
|
||||
var BANDIX_TOOLTIP_BG_OPENWRT2020_DARK = '#2a2a2a'; // OpenWrt 2020 深色主题悬浮框背景
|
||||
@@ -562,143 +557,11 @@ return view.extend({
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.bandix-ad-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--bandix-ad-count, 6), minmax(112px, 1fr));
|
||||
gap: 8px;
|
||||
margin: 12px 0;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.bandix-ad-slot {
|
||||
flex: 1 1 0;
|
||||
min-width: 112px;
|
||||
padding: 0;
|
||||
aspect-ratio: 3 / 1;
|
||||
border: 1px solid rgba(107, 114, 128, 0.35);
|
||||
border-radius: 6px;
|
||||
background: linear-gradient(135deg, rgba(225, 37, 27, 0.08), rgba(107, 114, 128, 0.04));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: inherit;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
transition: border-color 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.bandix-ad-slot:not(.bandix-ad-disabled):hover,
|
||||
.bandix-ad-slot:not(.bandix-ad-disabled):focus-visible {
|
||||
border-color: rgba(225, 37, 27, 0.75);
|
||||
box-shadow: 0 4px 12px rgba(225, 37, 27, 0.12);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.bandix-ad-disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.bandix-ad-label {
|
||||
align-self: flex-start;
|
||||
padding: 1px 5px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
background: rgba(225, 37, 27, 0.88);
|
||||
color: #ffffff;
|
||||
font-size: 0.5625rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.bandix-ad-copy {
|
||||
align-self: stretch;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
padding: 6px 4px 6px 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
gap: 3px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bandix-ad-title {
|
||||
width: 100%;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.bandix-ad-media {
|
||||
align-self: stretch;
|
||||
flex: 0 0 33.333333%;
|
||||
min-width: 0;
|
||||
aspect-ratio: 1 / 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
|
||||
.bandix-ad-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.bandix-ad-placeholder {
|
||||
flex: 0 0 33.333333%;
|
||||
aspect-ratio: 1 / 1;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3px;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
color: #e1251b;
|
||||
font-size: 0.625rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
/* 移动端隐藏版本信息和更新徽章 */
|
||||
@media (max-width: 768px) {
|
||||
.bandix-version-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bandix-ad-row {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.bandix-ad-slot {
|
||||
flex: 0 0 180px;
|
||||
min-width: 180px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.bandix-ad-copy {
|
||||
padding-left: 7px;
|
||||
}
|
||||
|
||||
.bandix-ad-title {
|
||||
font-size: 0.625rem;
|
||||
}
|
||||
}
|
||||
|
||||
.device-mode-group {
|
||||
@@ -2956,25 +2819,6 @@ return view.extend({
|
||||
])
|
||||
]),
|
||||
|
||||
// 广告数据从公开仓库动态加载
|
||||
E('div', {
|
||||
'class': 'bandix-ad-row',
|
||||
'id': 'bandix-ad-row',
|
||||
'aria-label': _('Advertising spaces')
|
||||
}, [1, 2, 3, 4, 5, 6].map(function (index) {
|
||||
return E('div', {
|
||||
'class': 'bandix-ad-slot bandix-ad-disabled',
|
||||
'data-ad-slot': String(index),
|
||||
'aria-disabled': 'true'
|
||||
}, [
|
||||
E('div', { 'class': 'bandix-ad-copy' }, [
|
||||
E('span', { 'class': 'bandix-ad-label' }, _('AD')),
|
||||
E('span', { 'class': 'bandix-ad-title' }, _('Advertising space') + ' ' + index)
|
||||
]),
|
||||
E('span', { 'class': 'bandix-ad-placeholder' }, _('Advertising space'))
|
||||
]);
|
||||
})),
|
||||
|
||||
// 警告提示(包含在线设备数)
|
||||
E('div', {
|
||||
'class': 'bandix-alert' + (getThemeType() === 'wide' ? ' wide-theme' : '')
|
||||
@@ -8147,205 +7991,6 @@ return view.extend({
|
||||
updateTrafficIncrements(startMs, endMs, null, null);
|
||||
}, 700);
|
||||
|
||||
// 异步加载远程广告(不阻塞主流程)
|
||||
(function () {
|
||||
var refreshTimer = null;
|
||||
|
||||
function resolveHttpsUrl(value, baseUrl) {
|
||||
if (typeof value !== 'string' || !value) return null;
|
||||
|
||||
try {
|
||||
var resolved = new URL(value, baseUrl);
|
||||
return resolved.protocol === 'https:' ? resolved.href : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleRefresh(seconds) {
|
||||
var interval = Number(seconds);
|
||||
if (!Number.isFinite(interval)) interval = BANDIX_AD_DEFAULT_REFRESH_INTERVAL;
|
||||
interval = Math.max(300, Math.min(86400, interval));
|
||||
|
||||
if (refreshTimer !== null) window.clearTimeout(refreshTimer);
|
||||
refreshTimer = window.setTimeout(loadAdvertisements, interval * 1000);
|
||||
}
|
||||
|
||||
function normalizeAdvertisementLocale(value) {
|
||||
if (typeof value !== 'string') return '';
|
||||
|
||||
var locale = value.trim().replace(/_/g, '-').toLowerCase();
|
||||
if (!locale || locale === 'auto' || locale === 'und') return '';
|
||||
|
||||
if (locale === 'zh' || locale === 'zh-cn' || locale === 'zh-sg' || locale === 'zh-hans') {
|
||||
return 'zh-hans';
|
||||
}
|
||||
if (locale === 'zh-tw' || locale === 'zh-hk' || locale === 'zh-mo' || locale === 'zh-hant') {
|
||||
return 'zh-hant';
|
||||
}
|
||||
|
||||
return locale;
|
||||
}
|
||||
|
||||
function getAdvertisementLocale() {
|
||||
var candidates = [];
|
||||
if (L.env) candidates.push(L.env.lang || L.env.language || '');
|
||||
var htmlLanguage = document.documentElement.getAttribute('lang');
|
||||
if (htmlLanguage) candidates.push(htmlLanguage);
|
||||
if (window.navigator) candidates.push(window.navigator.language || '');
|
||||
|
||||
for (var i = 0; i < candidates.length; i++) {
|
||||
var locale = normalizeAdvertisementLocale(candidates[i]);
|
||||
if (locale) return locale;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function selectLocalizedAdvertisements(config) {
|
||||
// 兼容旧版单语言配置,方便两个仓库分开部署
|
||||
if (config.version === 1 && Array.isArray(config.ads)) {
|
||||
return { locale: 'legacy', ads: config.ads };
|
||||
}
|
||||
|
||||
if (config.version !== 2 || !config.locales || typeof config.locales !== 'object') {
|
||||
throw new Error('Invalid advertising configuration');
|
||||
}
|
||||
|
||||
var currentLocale = getAdvertisementLocale();
|
||||
var defaultLocale = normalizeAdvertisementLocale(config.default_locale);
|
||||
var localeCandidates = [];
|
||||
|
||||
if (currentLocale) {
|
||||
localeCandidates.push(currentLocale);
|
||||
var separatorIndex = currentLocale.indexOf('-');
|
||||
if (separatorIndex > 0) localeCandidates.push(currentLocale.substring(0, separatorIndex));
|
||||
}
|
||||
if (defaultLocale) localeCandidates.push(defaultLocale);
|
||||
|
||||
for (var i = 0; i < localeCandidates.length; i++) {
|
||||
var candidate = localeCandidates[i];
|
||||
if (Object.prototype.hasOwnProperty.call(config.locales, candidate) &&
|
||||
Array.isArray(config.locales[candidate])) {
|
||||
return { locale: candidate, ads: config.locales[candidate] };
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('No advertising locale is available');
|
||||
}
|
||||
|
||||
function renderAdvertisements(config) {
|
||||
var adRow = document.getElementById('bandix-ad-row');
|
||||
if (!adRow || !config) {
|
||||
throw new Error('Invalid advertising configuration');
|
||||
}
|
||||
|
||||
var selection = selectLocalizedAdvertisements(config);
|
||||
adRow.setAttribute('data-ad-locale', selection.locale);
|
||||
|
||||
var ads = selection.ads.slice(0, 6).map(function (ad, index) {
|
||||
if (!ad || typeof ad !== 'object') ad = {};
|
||||
|
||||
var href = resolveHttpsUrl(ad.href, BANDIX_AD_CONFIG_URL);
|
||||
var image = resolveHttpsUrl(ad.image, BANDIX_AD_CONFIG_URL);
|
||||
var title = typeof ad.title === 'string' ? ad.title.trim() : _('Advertising space') + ' ' + (index + 1);
|
||||
if (!title) title = _('Advertising space') + ' ' + (index + 1);
|
||||
var alt = typeof ad.alt === 'string' ? ad.alt.trim() : title;
|
||||
|
||||
return {
|
||||
enabled: ad.enabled === true && href !== null,
|
||||
href: href,
|
||||
image: image,
|
||||
title: title,
|
||||
alt: alt || title
|
||||
};
|
||||
});
|
||||
|
||||
while (ads.length < 6) {
|
||||
ads.push({
|
||||
enabled: false,
|
||||
href: null,
|
||||
image: null,
|
||||
title: _('Advertising space') + ' ' + (ads.length + 1),
|
||||
alt: _('Advertising space')
|
||||
});
|
||||
}
|
||||
|
||||
while (adRow.firstChild) adRow.removeChild(adRow.firstChild);
|
||||
|
||||
ads.forEach(function (ad, index) {
|
||||
var tagName = ad.enabled ? 'a' : 'div';
|
||||
var attributes = {
|
||||
'class': 'bandix-ad-slot' + (ad.enabled ? '' : ' bandix-ad-disabled'),
|
||||
'data-ad-slot': String(index + 1),
|
||||
'title': ad.title
|
||||
};
|
||||
|
||||
if (ad.enabled) {
|
||||
attributes.href = ad.href;
|
||||
attributes.target = '_blank';
|
||||
attributes.rel = 'noopener noreferrer sponsored';
|
||||
} else {
|
||||
attributes['aria-disabled'] = 'true';
|
||||
}
|
||||
|
||||
var content = [E('div', { 'class': 'bandix-ad-copy' }, [
|
||||
E('span', { 'class': 'bandix-ad-label' }, _('AD')),
|
||||
E('span', { 'class': 'bandix-ad-title' }, ad.title)
|
||||
])];
|
||||
if (ad.enabled && ad.image) {
|
||||
content.push(E('div', { 'class': 'bandix-ad-media' }, [
|
||||
E('img', {
|
||||
'class': 'bandix-ad-image',
|
||||
'src': ad.image,
|
||||
'alt': ad.alt,
|
||||
'loading': 'lazy',
|
||||
'referrerpolicy': 'no-referrer'
|
||||
})
|
||||
]));
|
||||
} else {
|
||||
content.push(E('span', { 'class': 'bandix-ad-placeholder' }, _('Advertising space')));
|
||||
}
|
||||
|
||||
adRow.appendChild(E(tagName, attributes, content));
|
||||
});
|
||||
|
||||
adRow.style.setProperty('--bandix-ad-count', '6');
|
||||
}
|
||||
|
||||
function loadAdvertisements() {
|
||||
var adRow = document.getElementById('bandix-ad-row');
|
||||
if (!adRow) return;
|
||||
|
||||
if (typeof window.fetch !== 'function') {
|
||||
console.debug('Failed to load advertisements: Fetch API is unavailable');
|
||||
scheduleRefresh(BANDIX_AD_RETRY_INTERVAL);
|
||||
return;
|
||||
}
|
||||
|
||||
var separator = BANDIX_AD_CONFIG_URL.indexOf('?') === -1 ? '?' : '&';
|
||||
var configUrl = BANDIX_AD_CONFIG_URL + separator + '_=' + Date.now();
|
||||
|
||||
window.fetch(configUrl, {
|
||||
method: 'GET',
|
||||
mode: 'cors',
|
||||
credentials: 'omit',
|
||||
cache: 'no-store'
|
||||
}).then(function (response) {
|
||||
if (!response.ok) throw new Error('HTTP ' + response.status);
|
||||
return response.json();
|
||||
}).then(function (config) {
|
||||
renderAdvertisements(config);
|
||||
scheduleRefresh(config.refresh_interval);
|
||||
}).catch(function (err) {
|
||||
console.debug('Failed to load advertisements:', err);
|
||||
scheduleRefresh(BANDIX_AD_RETRY_INTERVAL);
|
||||
});
|
||||
}
|
||||
|
||||
window.setTimeout(loadAdvertisements, 200);
|
||||
})();
|
||||
|
||||
// 异步加载版本信息(不阻塞主流程)
|
||||
(function () {
|
||||
// 延迟执行,确保页面先完成初始化
|
||||
|
||||
@@ -1094,15 +1094,6 @@ msgstr "有更新"
|
||||
msgid "Update available, click to go to settings"
|
||||
msgstr "有更新,点击前往设置"
|
||||
|
||||
msgid "Advertising spaces"
|
||||
msgstr "广告位"
|
||||
|
||||
msgid "Advertising space"
|
||||
msgstr "广告位"
|
||||
|
||||
msgid "AD"
|
||||
msgstr "广告"
|
||||
|
||||
msgid "Traffic Statistics"
|
||||
msgstr "流量统计"
|
||||
|
||||
|
||||
@@ -1279,12 +1279,3 @@ msgstr "TCX 出站錨點程式 ID"
|
||||
|
||||
msgid "Used when tc_order is before/after. Must be a valid egress program id on the same interface."
|
||||
msgstr "當 tc_order 為 before/after 時使用。必須是同一介面上的有效出站程式 ID。"
|
||||
|
||||
msgid "Advertising spaces"
|
||||
msgstr "廣告位"
|
||||
|
||||
msgid "Advertising space"
|
||||
msgstr "廣告位"
|
||||
|
||||
msgid "AD"
|
||||
msgstr "廣告"
|
||||
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-mosdns
|
||||
PKG_VERSION:=1.7.5
|
||||
PKG_RELEASE:=12
|
||||
PKG_VERSION:=1.7.6
|
||||
PKG_RELEASE:=13
|
||||
|
||||
LUCI_TITLE:=LuCI Support for mosdns
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { popen, writefile, stat, readfile } from 'fs';
|
||||
import { popen, stat, readfile, open } from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
|
||||
function to_array(val) {
|
||||
@@ -99,8 +99,12 @@ const methods = {
|
||||
try {
|
||||
let path = get_logfile_path_internal();
|
||||
if (path) {
|
||||
writefile(path, '');
|
||||
return { success: true };
|
||||
let f = open(path, 'w');
|
||||
if (f) {
|
||||
f.close();
|
||||
return { success: true };
|
||||
}
|
||||
return { error: 'Failed to open log file for writing.' };
|
||||
} else {
|
||||
return { error: 'Log file path could not be determined.' };
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.8.12
|
||||
PKG_RELEASE:=225
|
||||
PKG_RELEASE:=226
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -8,7 +8,6 @@ if not arg[1] or not m:get(arg[1]) then
|
||||
luci.http.redirect(m.redirect)
|
||||
end
|
||||
|
||||
fs = require "nixio.fs"
|
||||
formvalue_key = "cbid." .. m.config .. "." .. arg[1] .. "."
|
||||
|
||||
m:appendTemplate("/node_config/header", {section = arg[1]})
|
||||
@@ -24,7 +23,7 @@ m:foreach("nodes", function(s)
|
||||
end
|
||||
end)
|
||||
|
||||
s = m:section(NamedSection, arg[1], "nodes", translate("Node Config"))
|
||||
local s = m:section(NamedSection, arg[1], "nodes", translate("Node Config"))
|
||||
s.addremove = false
|
||||
s.dynamic = false
|
||||
|
||||
@@ -74,33 +73,35 @@ end
|
||||
o = s:option(ListValue, "type", translate("Type"))
|
||||
|
||||
if api.is_finded("ipt2socks") then
|
||||
s.fields["type"]:value("Socks", translate("Socks"))
|
||||
local type_name = "Socks"
|
||||
|
||||
if s.val["type"] == "Socks" then
|
||||
local function _n(name)
|
||||
return "socks_" .. name
|
||||
end
|
||||
o = s:option(ListValue, _n("del_protocol"), " ") --始终隐藏,用于删除 protocol
|
||||
o:depends({ [_n("__hide")] = "1" })
|
||||
s.fields["type"]:value(type_name, "Socks")
|
||||
|
||||
if s.val["type"] == type_name then
|
||||
local s2 = NamedSection(m, arg[1], "server")
|
||||
s2.type_name = type_name
|
||||
s2.option_prefix = "socks_"
|
||||
|
||||
o = s2:option(ListValue, "del_protocol", " ") --始终隐藏,用于删除 protocol
|
||||
o:depends({ __hide = "1" })
|
||||
o.rewrite_option = "protocol"
|
||||
|
||||
o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
|
||||
o = s2:option(Value, "address", translate("Address (Support Domain Name)"))
|
||||
|
||||
o = s:option(Value, _n("port"), translate("Port"))
|
||||
o = s2:option(Value, "port", translate("Port"))
|
||||
o.datatype = "port"
|
||||
|
||||
o = s:option(Value, _n("username"), translate("Username"))
|
||||
o = s2:option(Value, "username", translate("Username"))
|
||||
|
||||
o = s:option(Value, _n("password"), translate("Password"))
|
||||
o = s2:option(Value, "password", translate("Password"))
|
||||
o.password = true
|
||||
|
||||
api.luci_types(arg[1], m, s, "Socks", "socks_")
|
||||
api.luci_types(s, s2)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local type_table = {}
|
||||
for filename in fs.dir(types_dir) do
|
||||
for filename in api.fs.dir(types_dir) do
|
||||
table.insert(type_table, filename)
|
||||
end
|
||||
table.sort(type_table)
|
||||
|
||||
@@ -76,6 +76,20 @@ o = s:option(Flag, "log", translate("Enable") .. " " .. translate("Log"))
|
||||
o.default = 1
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(DummyValue, "_log", translate("Log File"))
|
||||
o.rawhtml = true
|
||||
o.cfgvalue = function(t, n)
|
||||
local log_path = api.TMP_PATH .. "/" .. arg[1] .. ".log"
|
||||
local log_url = api.url("get_socks_log") .. "?name=" .. arg[1]
|
||||
return string.format(
|
||||
'<code>%s</code> <input class="btn cbi-button cbi-button-apply" type="button" value="%s" onclick="window.open(\'%s\', \'_blank\')" />',
|
||||
log_path,
|
||||
translate("View Log"),
|
||||
log_url
|
||||
)
|
||||
end
|
||||
o:depends("log", true)
|
||||
|
||||
o = s:option(Flag, "enable_autoswitch", translate("Auto Switch"))
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
|
||||
@@ -0,0 +1,917 @@
|
||||
local singbox_bin = api.finded_com("sing-box")
|
||||
|
||||
if not singbox_bin then
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ sing-box ]]
|
||||
local m, s1 = ...
|
||||
local type_name = "sing-box"
|
||||
|
||||
s1.fields["type"]:value(type_name, "Sing-Box")
|
||||
if not s1.fields["type"].default then
|
||||
s1.fields["type"].default = type_name
|
||||
end
|
||||
|
||||
if s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local s = NamedSection(m, arg[1], "server")
|
||||
s.type_name = type_name
|
||||
s.option_prefix = "singbox_"
|
||||
|
||||
local formvalue_proto = luci.http.formvalue(formvalue_key .. "protocol")
|
||||
|
||||
if formvalue_proto then s1.val["protocol"] = formvalue_proto end
|
||||
|
||||
local arg_select_proto = luci.http.formvalue("select_proto") or ""
|
||||
|
||||
local ss_method_new_list = {
|
||||
"none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
local ss_method_old_list = {
|
||||
"aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20",
|
||||
}
|
||||
|
||||
local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" }
|
||||
|
||||
local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'")
|
||||
|
||||
local singbox_version = api.get_app_version("sing-box"):match("[^v]+")
|
||||
|
||||
o = s:option(ListValue, "protocol", translate("Protocol"))
|
||||
o:value("socks", "Socks")
|
||||
o:value("http", "HTTP")
|
||||
o:value("shadowsocks", "Shadowsocks")
|
||||
o:value("vmess", "Vmess")
|
||||
o:value("trojan", "Trojan")
|
||||
if singbox_tags:find("with_wireguard") then
|
||||
o:value("wireguard", "WireGuard")
|
||||
end
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("hysteria", "Hysteria")
|
||||
end
|
||||
o:value("vless", "VLESS")
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("tuic", "TUIC")
|
||||
end
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("hysteria2", "Hysteria2")
|
||||
end
|
||||
o:value("anytls", "AnyTLS")
|
||||
o:value("ssh", "SSH")
|
||||
if singbox_tags:find("with_naive_outbound") then
|
||||
o:value("naive", "NaïveProxy")
|
||||
end
|
||||
o:value("_urltest", translate("URLTest"))
|
||||
o:value("_shunt", translate("Shunt"))
|
||||
o:value("_iface", translate("Custom Interface"))
|
||||
function o.custom_cfgvalue(self, section)
|
||||
if arg_select_proto ~= "" then
|
||||
return arg_select_proto
|
||||
else
|
||||
return m:get(section, self.config_option)
|
||||
end
|
||||
end
|
||||
|
||||
local load_urltest_options = s1.val["protocol"] == "_urltest" or arg_select_proto == "_urltest"
|
||||
local load_shunt_options = s1.val["protocol"] == "_shunt" or arg_select_proto == "_shunt"
|
||||
local load_iface_options = s1.val["protocol"] == "_iface" or arg_select_proto == "_iface"
|
||||
local load_normal_options = true
|
||||
if load_urltest_options or load_shunt_options or load_iface_options then
|
||||
load_normal_options = nil
|
||||
end
|
||||
if not arg_select_proto:find("_") then
|
||||
load_normal_options = true
|
||||
end
|
||||
|
||||
local netdev_list = api.get_network_devices()
|
||||
local node_list = api.get_node_list()
|
||||
|
||||
if load_urltest_options then -- [[ URLTest Start ]]
|
||||
o = s:option(ListValue, "node_add_mode", translate("Node Addition Method"))
|
||||
o:depends({ protocol = "_urltest" })
|
||||
o.default = "manual"
|
||||
o:value("manual", translate("Manual"))
|
||||
o:value("batch", translate("Batch"))
|
||||
|
||||
o = s:option(MultiValue, "urltest_node", translate("URLTest node list"), translate("List of nodes to test, <a target='_blank' href='https://sing-box.sagernet.org/configuration/outbound/urltest'>document</a>"))
|
||||
o:depends({ node_add_mode = "manual" })
|
||||
o.widget = "checkbox"
|
||||
o.template = m:template_path("/cbi/nodes_multivalue")
|
||||
o.group = {}
|
||||
for k1, v1 in pairs(node_list) do
|
||||
if k1 == "socks_list" or k1 == "normal_list" then
|
||||
for i, v in ipairs(v1) do
|
||||
o:value(v.id, v.remark)
|
||||
o.group[#o.group+1] = v.group or ""
|
||||
end
|
||||
end
|
||||
end
|
||||
-- 读取旧 DynamicList
|
||||
function o.custom_cfgvalue(self, section)
|
||||
return table.concat(m:get(section, "urltest_node") or {}, " ")
|
||||
end
|
||||
-- 写入保持 DynamicList
|
||||
function o.custom_write(self, section, value)
|
||||
local old = m:get(section, "urltest_node") or {}
|
||||
local new, set = {}, {}
|
||||
for v in value:gmatch("%S+") do
|
||||
new[#new + 1] = v
|
||||
set[v] = 1
|
||||
end
|
||||
for _, v in ipairs(old) do
|
||||
if not set[v] then
|
||||
m:set(section, "urltest_node", new)
|
||||
return
|
||||
end
|
||||
set[v] = nil
|
||||
end
|
||||
for _ in pairs(set) do
|
||||
m:set(section, "urltest_node", new)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(MultiValue, "node_group", translate("Select Group"))
|
||||
o:depends({ node_add_mode = "batch" })
|
||||
o.widget = "checkbox"
|
||||
o:value("default", translate("default"))
|
||||
for k, v in pairs(groups) do
|
||||
o:value(api.UrlEncode(k), k)
|
||||
end
|
||||
|
||||
o = s:option(Value, "node_match_rule", translate("Node Matching Rules"))
|
||||
o:depends({ node_add_mode = "batch" })
|
||||
local descrStr = "Example: <code>^A && B && !C && D$</code><br>"
|
||||
descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
|
||||
descrStr = descrStr .. "Conditions are joined by <code>&&</code> (AND), and their order does not affect the result.<br>"
|
||||
descrStr = descrStr .. "Multiple groups can be separated by <code>||</code> (OR), matching succeeds if any group matches.<br>"
|
||||
descrStr = descrStr .. "Example: <code>A && B || C && D</code> means (A AND B) OR (C AND D)."
|
||||
o.description = translate(descrStr)
|
||||
|
||||
o = s:option(Value, "urltest_url", translate("Probe URL"))
|
||||
o:depends({ protocol = "_urltest" })
|
||||
o:value("https://cp.cloudflare.com/", "Cloudflare")
|
||||
o:value("https://www.gstatic.com/generate_204", "Gstatic")
|
||||
o:value("https://www.google.com/generate_204", "Google")
|
||||
o:value("https://www.youtube.com/generate_204", "YouTube")
|
||||
o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
|
||||
o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
|
||||
o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
|
||||
o.default = o.keylist[3]
|
||||
o.description = translate("The URL used to detect the connection status.")
|
||||
|
||||
o = s:option(Value, "urltest_interval", translate("Test interval"))
|
||||
o:depends({ protocol = "_urltest" })
|
||||
o.default = "3m"
|
||||
o.placeholder = "3m"
|
||||
o.description = translate("The interval between initiating probes.") .. "<br>" ..
|
||||
translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are <code>s</code>, <code>m</code>, <code>h</code>, which correspond to seconds, minutes, and hours, respectively.") .. "<br>" ..
|
||||
translate("When the unit is not filled in, it defaults to seconds.") .. "<br>" ..
|
||||
translate("Test interval must be less or equal than idle timeout.")
|
||||
|
||||
o = s:option(Value, "urltest_tolerance", translate("Test tolerance"), translate("The test tolerance in milliseconds."))
|
||||
o:depends({ protocol = "_urltest" })
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "50"
|
||||
o.default = "50"
|
||||
|
||||
o = s:option(Value, "urltest_idle_timeout", translate("Idle timeout"))
|
||||
o:depends({ protocol = "_urltest" })
|
||||
o.placeholder = "30m"
|
||||
o.default = "30m"
|
||||
o.description = translate("The idle timeout.") .. "<br>" ..
|
||||
translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are <code>s</code>, <code>m</code>, <code>h</code>, which correspond to seconds, minutes, and hours, respectively.") .. "<br>" ..
|
||||
translate("When the unit is not filled in, it defaults to seconds.")
|
||||
|
||||
o = s:option(Flag, "urltest_interrupt_exist_connections", translate("Interrupt existing connections"))
|
||||
o:depends({ protocol = "_urltest" })
|
||||
o.default = "0"
|
||||
o.description = translate("Interrupt existing connections when the selected outbound has changed.")
|
||||
end -- [[ URLTest End ]]
|
||||
|
||||
if load_iface_options then -- [[ 自定义接口 Start ]]
|
||||
o = s:option(Value, "iface", translate("Interface"))
|
||||
o:depends({ protocol = "_iface" })
|
||||
for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if load_normal_options then
|
||||
|
||||
o = s:option(Value, "address", translate("Address (Support Domain Name)"))
|
||||
|
||||
o = s:option(Value, "port", translate("Port"))
|
||||
o.datatype = "port"
|
||||
|
||||
o = s:option(Value, "uuid", translate("ID"))
|
||||
o.password = true
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless" })
|
||||
o:depends({ protocol = "tuic" })
|
||||
|
||||
o = s:option(Value, "username", translate("Username"))
|
||||
o:depends({ protocol = "http" })
|
||||
o:depends({ protocol = "socks" })
|
||||
o:depends({ protocol = "ssh" })
|
||||
o:depends({ protocol = "naive" })
|
||||
|
||||
o = s:option(Value, "password", translate("Password"))
|
||||
o.password = true
|
||||
o:depends({ protocol = "http" })
|
||||
o:depends({ protocol = "socks" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
o:depends({ protocol = "tuic" })
|
||||
o:depends({ protocol = "anytls" })
|
||||
o:depends({ protocol = "ssh" })
|
||||
o:depends({ protocol = "naive" })
|
||||
|
||||
o = s:option(ListValue, "security", translate("Encrypt Method"))
|
||||
for a, t in ipairs(security_list) do o:value(t) end
|
||||
o:depends({ protocol = "vmess" })
|
||||
|
||||
o = s:option(ListValue, "ss_method", translate("Encrypt Method"))
|
||||
o.rewrite_option = "method"
|
||||
for a, t in ipairs(ss_method_new_list) do o:value(t) end
|
||||
for a, t in ipairs(ss_method_old_list) do o:value(t) end
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
|
||||
o = s:option(Flag, "uot", translate("UDP over TCP"))
|
||||
o:depends({ protocol = "socks" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "naive" })
|
||||
|
||||
o = s:option(Value, "alter_id", "Alter ID")
|
||||
o.datatype = "uinteger"
|
||||
o.default = "0"
|
||||
o:depends({ protocol = "vmess" })
|
||||
|
||||
o = s:option(Flag, "global_padding", "global_padding", translate("Protocol parameter. Will waste traffic randomly if enabled."))
|
||||
o.default = "0"
|
||||
o:depends({ protocol = "vmess" })
|
||||
|
||||
o = s:option(Flag, "authenticated_length", "authenticated_length", translate("Protocol parameter. Enable length block encryption."))
|
||||
o.default = "0"
|
||||
o:depends({ protocol = "vmess" })
|
||||
|
||||
o = s:option(ListValue, "flow", translate("flow"))
|
||||
o.default = ""
|
||||
o:value("", translate("Disable"))
|
||||
o:value("xtls-rprx-vision")
|
||||
o:depends({ protocol = "vless", tls = true })
|
||||
|
||||
if singbox_tags:find("with_quic") then
|
||||
o = s:option(Value, "hysteria_hop", translate("Port hopping range"))
|
||||
o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Value, "hysteria_hop_interval", translate("Hop Interval(second)"), translate("Example:") .. "30 (≥5)")
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "30"
|
||||
o.default = "30"
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Value, "hysteria_obfs", translate("Obfs Password"))
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(ListValue, "hysteria_auth_type", translate("Auth Type"))
|
||||
o:value("disable", translate("Disable"))
|
||||
o:value("string", translate("STRING"))
|
||||
o:value("base64", translate("BASE64"))
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Value, "hysteria_auth_password", translate("Auth Password"))
|
||||
o.password = true
|
||||
o:depends({ protocol = "hysteria", hysteria_auth_type = "string"})
|
||||
o:depends({ protocol = "hysteria", hysteria_auth_type = "base64"})
|
||||
|
||||
o = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps"))
|
||||
o.default = "10"
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps"))
|
||||
o.default = "50"
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window"))
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Value, "hysteria_recv_window", translate("QUIC connection receive window"))
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection"))
|
||||
o:depends({ protocol = "hysteria" })
|
||||
end
|
||||
|
||||
if singbox_tags:find("with_quic") then
|
||||
o = s:option(ListValue, "tuic_congestion_control", translate("Congestion control algorithm"))
|
||||
o.default = "cubic"
|
||||
o:value("bbr", translate("BBR"))
|
||||
o:value("cubic", translate("CUBIC"))
|
||||
o:value("new_reno", translate("New Reno"))
|
||||
o:depends({ protocol = "tuic" })
|
||||
|
||||
o = s:option(ListValue, "tuic_udp_relay_mode", translate("UDP relay mode"))
|
||||
o.default = "native"
|
||||
o:value("native", translate("native"))
|
||||
o:value("quic", translate("QUIC"))
|
||||
o:depends({ protocol = "tuic" })
|
||||
|
||||
--[[
|
||||
o = s:option(Flag, "tuic_udp_over_stream", translate("UDP over stream"))
|
||||
o:depends({ protocol = "tuic" })
|
||||
]]--
|
||||
|
||||
o = s:option(Flag, "tuic_zero_rtt_handshake", translate("Enable 0-RTT QUIC handshake"))
|
||||
o.default = 0
|
||||
o:depends({ protocol = "tuic" })
|
||||
|
||||
o = s:option(Value, "tuic_heartbeat", translate("Heartbeat interval(second)"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = "3"
|
||||
o:depends({ protocol = "tuic" })
|
||||
|
||||
o = s:option(ListValue, "tuic_alpn", translate("QUIC TLS ALPN"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("h3")
|
||||
o:value("h2")
|
||||
o:value("h3,h2")
|
||||
o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
o:value("spdy/3.1")
|
||||
o:value("h3,spdy/3.1")
|
||||
o:depends({ protocol = "tuic" })
|
||||
end
|
||||
|
||||
if singbox_tags:find("with_quic") then
|
||||
o = s:option(Value, "hysteria2_hop", translate("Port hopping range"))
|
||||
o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
|
||||
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
|
||||
|
||||
o = s:option(Value, "hysteria2_hop_interval", translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
|
||||
o.datatype = "or(uinteger,portrange)"
|
||||
o.placeholder = "30"
|
||||
o.default = "30"
|
||||
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
|
||||
|
||||
o = s:option(Flag, "hysteria2_realms", translate("Realms"))
|
||||
o.default = "0"
|
||||
if api.compare_versions(singbox_version, ">=", "1.14.0") then
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
else
|
||||
o:depends({ protocol = "__hide"})
|
||||
end
|
||||
|
||||
o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
|
||||
o = s:option(Value, "hysteria2_auth_password", translate("Auth Password"))
|
||||
o.password = true
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
|
||||
o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
o:value("gecko")
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password"))
|
||||
o:depends({ hysteria2_obfs_type = "salamander" })
|
||||
o:depends({ hysteria2_obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "512"
|
||||
o.default = "512"
|
||||
o:depends({ hysteria2_obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "1200"
|
||||
o.default = "1200"
|
||||
o:depends({ hysteria2_obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps"))
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps"))
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Value, "hysteria2_idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
|
||||
o.datatype = "range(4,120)"
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
|
||||
o = s:option(Value, "hysteria2_keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
|
||||
o.datatype = "range(2,60)"
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
|
||||
o = s:option(Flag, "hysteria2_disable_mtu_discovery", translate("Disable MTU detection"))
|
||||
o.default = "0"
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
end
|
||||
|
||||
-- [[ SSH config start ]] --
|
||||
o = s:option(TextValue, "ssh_priv_key", translate("Private Key"))
|
||||
o.rows = 5
|
||||
o.wrap = "off"
|
||||
o:depends({ protocol = "ssh" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
|
||||
return value
|
||||
end
|
||||
|
||||
o = s:option(Value, "ssh_priv_key_pp", translate("Private Key Passphrase"))
|
||||
o.password = true
|
||||
o:depends({ protocol = "ssh" })
|
||||
|
||||
o = s:option(DynamicList, "ssh_host_key", translate("Host Key"), translate("Accept any if empty."))
|
||||
o:depends({ protocol = "ssh" })
|
||||
|
||||
o = s:option(DynamicList, "ssh_host_key_algo", translate("Host Key Algorithms"))
|
||||
o:depends({ protocol = "ssh" })
|
||||
|
||||
o = s:option(Value, "ssh_client_version", translate("Client Version"), translate("Random version will be used if empty."))
|
||||
o:depends({ protocol = "ssh" })
|
||||
-- [[ SSH config end ]] --
|
||||
|
||||
-- [[ naive start ]] --
|
||||
o = s:option(Value, "naive_insecure_concurrency", translate("Concurrent Tunnels"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "0"
|
||||
o.default = "0"
|
||||
o:depends({ protocol = "naive" })
|
||||
|
||||
o = s:option(Flag, "naive_quic", translate("QUIC"))
|
||||
o.default = 0
|
||||
o:depends({ protocol = "naive" })
|
||||
|
||||
o = s:option(ListValue, "naive_congestion_control", translate("Congestion control algorithm"))
|
||||
o.default = "bbr"
|
||||
o:value("bbr", translate("BBR"))
|
||||
o:value("bbr2", translate("BBRv2"))
|
||||
o:value("cubic", translate("CUBIC"))
|
||||
o:value("reno", translate("New Reno"))
|
||||
o:depends({ naive_quic = "1" })
|
||||
-- [[ naive end ]] --
|
||||
|
||||
o = s:option(Flag, "tls", translate("TLS"))
|
||||
o.default = 0
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless" })
|
||||
o:depends({ protocol = "http" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "anytls" })
|
||||
|
||||
o = s:option(ListValue, "alpn", translate("ALPN"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("h3")
|
||||
o:value("h2")
|
||||
o:value("h3,h2")
|
||||
o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
o:depends({ tls = true })
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Flag, "tls_disable_sni", translate("Disable SNI"), translate("Do not send server name in ClientHello."))
|
||||
o.default = "0"
|
||||
o:depends({ tls = true })
|
||||
o:depends({ protocol = "hysteria"})
|
||||
o:depends({ protocol = "tuic" })
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Value, "tls_serverName", "SNI " .. translate("Domain"))
|
||||
o:depends({ tls = true })
|
||||
o:depends({ protocol = "hysteria"})
|
||||
o:depends({ protocol = "tuic" })
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
o:depends({ protocol = "naive" })
|
||||
|
||||
o = s:option(Flag, "tls_allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
|
||||
o.default = "0"
|
||||
o:depends({ tls = true })
|
||||
o:depends({ protocol = "hysteria"})
|
||||
o:depends({ protocol = "tuic" })
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Flag, "tls_certificate", translate("TLS Certificate (PEM)"))
|
||||
o.default = "0"
|
||||
o:depends({ tls = true, reality = false })
|
||||
o:depends({ protocol = "hysteria"})
|
||||
o:depends({ protocol = "tuic" })
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
o:depends({ protocol = "naive" })
|
||||
|
||||
o = s:option(TextValue, "tls_certificate_pem", " ", translate("Full certificate (chain), PEM format."))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "off"
|
||||
o:depends({ tls_certificate = true })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
|
||||
return value
|
||||
end
|
||||
|
||||
o = s:option(Value, "cipherSuites", translate("Cipher Suites"), '<a href="https://go.dev/src/crypto/tls/cipher_suites.go#L44" target="_blank">***</a>' .. " " .. translate("Configures the list of supported cipher suites, separated by :"))
|
||||
o:depends({ tls = true })
|
||||
|
||||
o = s:option(Flag, "ech", translate("ECH"))
|
||||
o.default = "0"
|
||||
o:depends({ tls = true, flow = "", reality = false })
|
||||
o:depends({ protocol = "tuic" })
|
||||
o:depends({ protocol = "hysteria" })
|
||||
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
|
||||
o:depends({ protocol = "naive" })
|
||||
|
||||
o = s:option(TextValue, "ech_config", translate("ECH Config"))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "off"
|
||||
o:depends({ ech = true })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
|
||||
return value
|
||||
end
|
||||
|
||||
o = s:option(Value, "ech_query_server_name", translate("ECH Query Domain"), translate("Overrides the domain name used for ECH HTTPS record queries."))
|
||||
o:depends({ ech = true })
|
||||
|
||||
if singbox_tags:find("with_utls") then
|
||||
o = s:option(Flag, "utls", translate("uTLS"))
|
||||
o.default = "0"
|
||||
o:depends({ tls = true })
|
||||
|
||||
o = s:option(ListValue, "fingerprint", translate("Finger Print"))
|
||||
o:value("chrome")
|
||||
o:value("firefox")
|
||||
o:value("edge")
|
||||
o:value("safari")
|
||||
o:value("360")
|
||||
o:value("qq")
|
||||
o:value("ios")
|
||||
o:value("android")
|
||||
o:value("random")
|
||||
o:value("randomized")
|
||||
o.default = "chrome"
|
||||
o:depends({ utls = true })
|
||||
|
||||
-- [[ REALITY部分 ]] --
|
||||
o = s:option(Flag, "reality", translate("REALITY"))
|
||||
o.default = 0
|
||||
o:depends({ protocol = "vless", tls = true })
|
||||
o:depends({ protocol = "vmess", tls = true })
|
||||
o:depends({ protocol = "shadowsocks", tls = true })
|
||||
o:depends({ protocol = "trojan", tls = true })
|
||||
o:depends({ protocol = "anytls", tls = true })
|
||||
|
||||
o = s:option(Value, "reality_publicKey", translate("Public Key"))
|
||||
o:depends({ reality = true })
|
||||
|
||||
o = s:option(Value, "reality_shortId", translate("Short Id"))
|
||||
o:depends({ reality = true })
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "transport", translate("Transport"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("http", "HTTP")
|
||||
o:value("ws", "WebSocket")
|
||||
o:value("httpupgrade", "HTTPUpgrade")
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("quic", "QUIC")
|
||||
end
|
||||
if singbox_tags:find("with_grpc") then
|
||||
o:value("grpc", "gRPC")
|
||||
else o:value("grpc", "gRPC-lite")
|
||||
end
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
|
||||
if singbox_tags:find("with_wireguard") then
|
||||
o = s:option(Value, "wireguard_public_key", translate("Public Key"))
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(Value, "wireguard_secret_key", translate("Private Key"))
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(Value, "wireguard_preSharedKey", translate("Pre shared key"))
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(Value, "wireguard_mtu", translate("MTU"))
|
||||
o.default = "1420"
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
|
||||
o:depends({ protocol = "wireguard" })
|
||||
end
|
||||
|
||||
-- [[ TCP部分(模拟) ]]--
|
||||
o = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
|
||||
o:value("none", "none")
|
||||
o:value("http", "http")
|
||||
o:depends({ transport = "tcp" })
|
||||
|
||||
o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
|
||||
o:depends({ tcp_guise = "http" })
|
||||
|
||||
o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ tcp_guise = "http" })
|
||||
|
||||
-- [[ HTTP部分 ]]--
|
||||
o = s:option(DynamicList, "http_host", translate("HTTP Host"))
|
||||
o:depends({ transport = "http" })
|
||||
|
||||
o = s:option(Value, "http_path", translate("HTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ transport = "http" })
|
||||
|
||||
o = s:option(Flag, "http_h2_health_check", translate("Health check"))
|
||||
o:depends({ tls = true, transport = "http" })
|
||||
|
||||
o = s:option(Value, "http_h2_read_idle_timeout", translate("Idle timeout"))
|
||||
o.default = "15"
|
||||
o:depends({ http_h2_health_check = true })
|
||||
|
||||
o = s:option(Value, "http_h2_health_check_timeout", translate("Health check timeout"))
|
||||
o.default = "15"
|
||||
o:depends({ http_h2_health_check = true })
|
||||
|
||||
-- [[ WebSocket部分 ]]--
|
||||
o = s:option(Value, "ws_host", translate("WebSocket Host"))
|
||||
o:depends({ transport = "ws" })
|
||||
|
||||
o = s:option(Value, "ws_path", translate("WebSocket Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ transport = "ws" })
|
||||
|
||||
o = s:option(Flag, "ws_enableEarlyData", translate("Enable early data"))
|
||||
o:depends({ transport = "ws" })
|
||||
|
||||
o = s:option(Value, "ws_maxEarlyData", translate("Early data length"))
|
||||
o.default = "1024"
|
||||
o:depends({ ws_enableEarlyData = true })
|
||||
|
||||
o = s:option(Value, "ws_earlyDataHeaderName", translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol"))
|
||||
o:depends({ ws_enableEarlyData = true })
|
||||
|
||||
-- [[ HTTPUpgrade部分 ]]--
|
||||
o = s:option(Value, "httpupgrade_host", translate("HTTPUpgrade Host"))
|
||||
o:depends({ transport = "httpupgrade" })
|
||||
|
||||
o = s:option(Value, "httpupgrade_path", translate("HTTPUpgrade Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ transport = "httpupgrade" })
|
||||
|
||||
-- [[ gRPC部分 ]]--
|
||||
o = s:option(Value, "grpc_serviceName", "ServiceName")
|
||||
o:depends({ transport = "grpc" })
|
||||
|
||||
o = s:option(Flag, "grpc_health_check", translate("Health check"))
|
||||
o:depends({ transport = "grpc" })
|
||||
|
||||
o = s:option(Value, "grpc_idle_timeout", translate("Idle timeout"))
|
||||
o.default = "15"
|
||||
o:depends({ grpc_health_check = true })
|
||||
|
||||
o = s:option(Value, "grpc_health_check_timeout", translate("Health check timeout"))
|
||||
o.default = "15"
|
||||
o:depends({ grpc_health_check = true })
|
||||
|
||||
o = s:option(Flag, "grpc_permit_without_stream", translate("Permit without stream"))
|
||||
o.default = "0"
|
||||
o:depends({ grpc_health_check = true })
|
||||
|
||||
-- [[ User-Agent ]]--
|
||||
o = s:option(Value, "user_agent", translate("User-Agent"))
|
||||
o.default = ""
|
||||
o:value("", translate("default"))
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", "chrome")
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", "firefox")
|
||||
o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", "safari")
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", "edge")
|
||||
o:value("Go-http-client/1.1", "golang")
|
||||
o:value("curl/7.68.0", "curl")
|
||||
o:depends({ tcp_guise = "http" })
|
||||
o:depends({ transport = "http" })
|
||||
o:depends({ transport = "ws" })
|
||||
o:depends({ transport = "httpupgrade" })
|
||||
o:depends({ protocol = "naive" })
|
||||
|
||||
-- [[ Mux ]]--
|
||||
o = s:option(Flag, "mux", translate("Mux"))
|
||||
o.rmempty = false
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless", flow = "" })
|
||||
o:depends({ protocol = "shadowsocks", uot = "" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
|
||||
o = s:option(ListValue, "mux_type", translate("Mux"))
|
||||
o:value("smux")
|
||||
o:value("yamux")
|
||||
o:value("h2mux")
|
||||
o:depends({ mux = true })
|
||||
|
||||
o = s:option(Value, "mux_concurrency", translate("Mux concurrency"))
|
||||
o.default = 4
|
||||
o:depends({ mux = true, tcpbrutal = false })
|
||||
|
||||
o = s:option(Flag, "mux_padding", translate("Padding"))
|
||||
o.default = 0
|
||||
o:depends({ mux = true })
|
||||
|
||||
-- [[ TCP Brutal ]]--
|
||||
o = s:option(Flag, "tcpbrutal", translate("TCP Brutal"))
|
||||
o.default = 0
|
||||
o:depends({ mux = true })
|
||||
|
||||
o = s:option(Value, "tcpbrutal_up_mbps", translate("Max upload Mbps"))
|
||||
o.default = "10"
|
||||
o:depends({ tcpbrutal = true })
|
||||
|
||||
o = s:option(Value, "tcpbrutal_down_mbps", translate("Max download Mbps"))
|
||||
o.default = "50"
|
||||
o:depends({ tcpbrutal = true })
|
||||
|
||||
o = s:option(Flag, "shadowtls", "ShadowTLS")
|
||||
o.default = 0
|
||||
o:depends({ protocol = "vmess", tls = false })
|
||||
o:depends({ protocol = "shadowsocks", tls = false })
|
||||
|
||||
o = s:option(ListValue, "shadowtls_version", "ShadowTLS " .. translate("Version"))
|
||||
o.default = "1"
|
||||
o:value("1", "ShadowTLS v1")
|
||||
o:value("2", "ShadowTLS v2")
|
||||
o:value("3", "ShadowTLS v3")
|
||||
o:depends({ shadowtls = true })
|
||||
|
||||
o = s:option(Value, "shadowtls_password", "ShadowTLS " .. translate("Password"))
|
||||
o.password = true
|
||||
o:depends({ shadowtls = true, shadowtls_version = "2" })
|
||||
o:depends({ shadowtls = true, shadowtls_version = "3" })
|
||||
|
||||
o = s:option(Value, "shadowtls_serverName", "ShadowTLS " .. translate("Domain"))
|
||||
o:depends({ shadowtls = true })
|
||||
|
||||
if singbox_tags:find("with_utls") then
|
||||
o = s:option(Flag, "shadowtls_utls", "ShadowTLS " .. translate("uTLS"))
|
||||
o.default = "0"
|
||||
o:depends({ shadowtls = true })
|
||||
|
||||
o = s:option(ListValue, "shadowtls_fingerprint", "ShadowTLS " .. translate("Finger Print"))
|
||||
o:value("chrome")
|
||||
o:value("firefox")
|
||||
o:value("edge")
|
||||
o:value("safari")
|
||||
-- o:value("360")
|
||||
o:value("qq")
|
||||
o:value("ios")
|
||||
-- o:value("android")
|
||||
o:value("random")
|
||||
-- o:value("randomized")
|
||||
o.default = "chrome"
|
||||
o:depends({ shadowtls = true, shadowtls_utls = true })
|
||||
end
|
||||
|
||||
-- [[ SIP003 plugin ]]--
|
||||
o = s:option(Flag, "plugin_enabled", translate("plugin"))
|
||||
o.default = 0
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
|
||||
o = s:option(ListValue, "plugin", "SIP003 " .. translate("plugin"))
|
||||
o.default = "obfs-local"
|
||||
o:depends({ plugin_enabled = true })
|
||||
o:value("obfs-local")
|
||||
o:value("v2ray-plugin")
|
||||
|
||||
o = s:option(Value, "plugin_opts", translate("opts"))
|
||||
o:depends({ plugin_enabled = true })
|
||||
|
||||
o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve"))
|
||||
o.description = translate("If the node address is a domain name, this DNS will be used for resolution.") .. "<br>" .. string.format('<font color="red">%s</font>',
|
||||
translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead."))
|
||||
o:value("", translate("Auto"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
o:value("https", "DoH")
|
||||
|
||||
o = s:option(Value, "domain_resolver_dns", "DNS")
|
||||
o.datatype = "or(ipaddr,ipaddrport)"
|
||||
o:value("114.114.114.114")
|
||||
o:value("223.5.5.5:53")
|
||||
o.default = o.keylist[1]
|
||||
o:depends({ domain_resolver = "tcp" })
|
||||
o:depends({ domain_resolver = "udp" })
|
||||
|
||||
o = s:option(Value, "domain_resolver_dns_https", "DNS")
|
||||
o:value("https://120.53.53.53/dns-query", "DNSPod")
|
||||
o:value("https://223.5.5.5/dns-query", "AliDNS")
|
||||
o.default = o.keylist[1]
|
||||
o:depends({ domain_resolver = "https" })
|
||||
|
||||
o = s:option(ListValue, "domain_strategy", translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect."))
|
||||
o.default = ""
|
||||
o:value("", translate("Auto"))
|
||||
o:value("prefer_ipv4", translate("Prefer IPv4"))
|
||||
o:value("prefer_ipv6", translate("Prefer IPv6"))
|
||||
o:value("ipv4_only", translate("IPv4 Only"))
|
||||
o:value("ipv6_only", translate("IPv6 Only"))
|
||||
|
||||
|
||||
local protocols = s.fields["protocol"].keylist
|
||||
if #protocols > 0 then
|
||||
for i, v in ipairs(protocols) do
|
||||
if not v:find("^_") then
|
||||
local depends_condition = { protocol = v }
|
||||
if v == "hysteria2" then
|
||||
depends_condition["hysteria2_realms"] = false
|
||||
end
|
||||
s.fields["address"]:depends(depends_condition)
|
||||
s.fields["port"]:depends(depends_condition)
|
||||
s.fields["domain_resolver"]:depends(depends_condition)
|
||||
s.fields["domain_strategy"]:depends(depends_condition)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- [[ Normal single node End ]]
|
||||
|
||||
if not load_shunt_options then
|
||||
o = s:option(ListValue, "chain_proxy", translate("Chain Proxy"))
|
||||
o:value("", translate("Close(Not use)"))
|
||||
if not (load_iface_options or load_urltest_options) then
|
||||
-- Special node cannot be use pre-proxy.
|
||||
o:value("1", translate("Preproxy Node"))
|
||||
o:value("3", translate("Outbound Interface"))
|
||||
end
|
||||
o:value("2", translate("Landing Node"))
|
||||
|
||||
o1 = s:option(ListValue, "preproxy_node", translate("Preproxy Node"), translate("Only support a layer of proxy."))
|
||||
o1:depends({ chain_proxy = "1", hysteria2_realms = false })
|
||||
o1.template = m:template_path("/cbi/nodes_listvalue")
|
||||
o1.group = {}
|
||||
|
||||
o3 = s:option(Value, "outbound_iface", translate("Outbound Interface"))
|
||||
o3:depends({ chain_proxy = "3" })
|
||||
o3:value("", translate("All"))
|
||||
for _, d in ipairs(netdev_list) do
|
||||
o3:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o2 = s:option(ListValue, "to_node", translate("Landing Node"), translate("Only support a layer of proxy."))
|
||||
o2:depends({ chain_proxy = "2", hysteria2_realms = false })
|
||||
o2.template = m:template_path("/cbi/nodes_listvalue")
|
||||
o2.group = {}
|
||||
|
||||
for k1, v1 in pairs(node_list) do
|
||||
if k1 ~= "shunt_list" and k1 ~= "iface_list" then
|
||||
for i, v in ipairs(v1) do
|
||||
if v.id ~= arg[1] then
|
||||
o1:value(v.id, v.remark)
|
||||
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
if k1 == "normal_list" then
|
||||
-- Landing Node not support use special node.
|
||||
o2:value(v.id, v.remark)
|
||||
o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
api.luci_types(s1, s)
|
||||
|
||||
if load_shunt_options then
|
||||
local current_node = m:get(arg[1]) or {}
|
||||
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
|
||||
setfenv(shunt_lua, getfenv(1))(m, s1, {
|
||||
node_id = arg[1],
|
||||
node = current_node,
|
||||
node_list = node_list,
|
||||
})
|
||||
end
|
||||
@@ -0,0 +1,894 @@
|
||||
if not api.finded_com("xray") then
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ Xray ]]
|
||||
local m, s1 = ...
|
||||
local type_name = "Xray"
|
||||
|
||||
s1.fields["type"]:value(type_name, "Xray")
|
||||
if not s1.fields["type"].default then
|
||||
s1.fields["type"].default = type_name
|
||||
end
|
||||
|
||||
if s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local s = NamedSection(m, arg[1], "server")
|
||||
s.type_name = type_name
|
||||
s.option_prefix = "xray_"
|
||||
|
||||
local formvalue_proto = luci.http.formvalue(formvalue_key .. "protocol")
|
||||
|
||||
if formvalue_proto then s1.val["protocol"] = formvalue_proto end
|
||||
|
||||
local arg_select_proto = luci.http.formvalue("select_proto") or ""
|
||||
|
||||
local ss_method_list = {
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
local security_list = { "auto", "aes-128-gcm", "chacha20-poly1305" }
|
||||
|
||||
local header_type_list = {
|
||||
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
|
||||
}
|
||||
|
||||
local xray_version = api.get_app_version("xray")
|
||||
|
||||
o = s:option(ListValue, "protocol", translate("Protocol"))
|
||||
o:value("socks", translate("Socks"))
|
||||
o:value("http", translate("HTTP"))
|
||||
o:value("vmess", translate("Vmess"))
|
||||
o:value("vless", translate("VLESS"))
|
||||
o:value("shadowsocks", translate("Shadowsocks"))
|
||||
o:value("trojan", translate("Trojan"))
|
||||
o:value("wireguard", translate("WireGuard"))
|
||||
if api.compare_versions(xray_version, ">=", "26.1.13") then
|
||||
o:value("hysteria2", translate("Hysteria2"))
|
||||
end
|
||||
if api.compare_versions(xray_version, ">=", "1.8.12") then
|
||||
o:value("_balancing", translate("Balancing"))
|
||||
end
|
||||
o:value("_shunt", translate("Shunt"))
|
||||
o:value("_iface", translate("Custom Interface"))
|
||||
function o.custom_cfgvalue(self, section)
|
||||
if arg_select_proto ~= "" then
|
||||
return arg_select_proto
|
||||
else
|
||||
return m:get(section, self.config_option)
|
||||
end
|
||||
end
|
||||
|
||||
local load_balancing_options = s1.val["protocol"] == "_balancing" or arg_select_proto == "_balancing"
|
||||
local load_shunt_options = s1.val["protocol"] == "_shunt" or arg_select_proto == "_shunt"
|
||||
local load_iface_options = s1.val["protocol"] == "_iface" or arg_select_proto == "_iface"
|
||||
local load_normal_options = true
|
||||
if load_balancing_options or load_shunt_options or load_iface_options then
|
||||
load_normal_options = nil
|
||||
end
|
||||
if not arg_select_proto:find("_") then
|
||||
load_normal_options = true
|
||||
end
|
||||
|
||||
local netdev_list = api.get_network_devices()
|
||||
local node_list = api.get_node_list()
|
||||
local fallback_list = {}
|
||||
local is_balancer = nil
|
||||
for k, e in ipairs(node_list.balancing_list or {}) do
|
||||
if e.id ~= arg[1] then
|
||||
fallback_list[#fallback_list + 1] = {
|
||||
id = e["id"],
|
||||
remark = e["remark"],
|
||||
group = e["group"],
|
||||
fallback = e.o["fallback_node"],
|
||||
}
|
||||
else
|
||||
is_balancer = true
|
||||
end
|
||||
end
|
||||
|
||||
if load_balancing_options then -- [[ Load balancing Start ]]
|
||||
o = s:option(ListValue, "node_add_mode", translate("Node Addition Method"))
|
||||
o:depends({ protocol = "_balancing" })
|
||||
o.default = "manual"
|
||||
o:value("manual", translate("Manual"))
|
||||
o:value("batch", translate("Batch"))
|
||||
|
||||
o = s:option(MultiValue, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://xtls.github.io/config/routing.html#balancerobject'>document</a>"))
|
||||
o:depends({ node_add_mode = "manual" })
|
||||
o.widget = "checkbox"
|
||||
o.template = m:template_path("/cbi/nodes_multivalue")
|
||||
o.group = {}
|
||||
for k1, v1 in pairs(node_list) do
|
||||
if k1 == "socks_list" or k1 == "normal_list" then
|
||||
for i, v in ipairs(v1) do
|
||||
o:value(v.id, v.remark)
|
||||
o.group[#o.group+1] = v.group or ""
|
||||
end
|
||||
end
|
||||
end
|
||||
-- 读取旧 DynamicList
|
||||
function o.custom_cfgvalue(self, section)
|
||||
return table.concat(m:get(section, "balancing_node") or {}, " ")
|
||||
end
|
||||
-- 写入保持 DynamicList
|
||||
function o.custom_write(self, section, value)
|
||||
local old = m:get(section, "balancing_node") or {}
|
||||
local new, set = {}, {}
|
||||
for v in value:gmatch("%S+") do
|
||||
new[#new + 1] = v
|
||||
set[v] = 1
|
||||
end
|
||||
for _, v in ipairs(old) do
|
||||
if not set[v] then
|
||||
m:set(section, "balancing_node", new)
|
||||
return
|
||||
end
|
||||
set[v] = nil
|
||||
end
|
||||
for _ in pairs(set) do
|
||||
m:set(section, "balancing_node", new)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(MultiValue, "node_group", translate("Select Group"))
|
||||
o:depends({ node_add_mode = "batch" })
|
||||
o.widget = "checkbox"
|
||||
o:value("default", translate("default"))
|
||||
for k, v in pairs(groups) do
|
||||
o:value(api.UrlEncode(k), k)
|
||||
end
|
||||
|
||||
o = s:option(Value, "node_match_rule", translate("Node Matching Rules"))
|
||||
o:depends({ node_add_mode = "batch" })
|
||||
local descrStr = "Example: <code>^A && B && !C && D$</code><br>"
|
||||
descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
|
||||
descrStr = descrStr .. "Conditions are joined by <code>&&</code> (AND), and their order does not affect the result.<br>"
|
||||
descrStr = descrStr .. "Multiple groups can be separated by <code>||</code> (OR), matching succeeds if any group matches.<br>"
|
||||
descrStr = descrStr .. "Example: <code>A && B || C && D</code> means (A AND B) OR (C AND D)."
|
||||
o.description = translate(descrStr)
|
||||
|
||||
o = s:option(ListValue, "balancingStrategy", translate("Balancing Strategy"))
|
||||
o:depends({ protocol = "_balancing" })
|
||||
o:value("random")
|
||||
o:value("roundRobin")
|
||||
o:value("leastPing")
|
||||
o:value("leastLoad")
|
||||
o.default = "random"
|
||||
|
||||
-- Fallback Node
|
||||
o = s:option(ListValue, "fallback_node", translate("Fallback Node"))
|
||||
o.group = {"",""}
|
||||
o:value("", translate("Close(Not use)"))
|
||||
o:value("_direct", translate("Direct Connection"))
|
||||
o:depends({ protocol = "_balancing" })
|
||||
o.template = m:template_path("/cbi/nodes_listvalue")
|
||||
-- 最大 fallback 套娃层数
|
||||
local MAX_FALLBACK_DEPTH = 3
|
||||
-- 检查是否会形成回环
|
||||
local function will_loop(start_id, target_id, depth)
|
||||
depth = depth or 0
|
||||
-- 超过最大深度后停止递归
|
||||
if depth >= MAX_FALLBACK_DEPTH then
|
||||
return false
|
||||
end
|
||||
for _, v in ipairs(fallback_list) do
|
||||
if v.id == target_id then
|
||||
local fb = v.fallback
|
||||
-- 没有 fallback
|
||||
if not fb or fb == "" or fb == "_direct" then
|
||||
return false
|
||||
end
|
||||
-- 检测到回环
|
||||
if fb == start_id then
|
||||
return true
|
||||
end
|
||||
-- 继续递归检查
|
||||
return will_loop(start_id, fb, depth + 1)
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
-- 获取 fallback 链深度
|
||||
local function get_fallback_depth(id, depth)
|
||||
depth = depth or 0
|
||||
if depth >= MAX_FALLBACK_DEPTH then
|
||||
return depth
|
||||
end
|
||||
for _, v in ipairs(fallback_list) do
|
||||
if v.id == id then
|
||||
local fb = v.fallback
|
||||
if not fb or fb == "" or fb == "_direct" then
|
||||
return depth
|
||||
end
|
||||
return get_fallback_depth(fb, depth + 1)
|
||||
end
|
||||
end
|
||||
return depth
|
||||
end
|
||||
for _, v in ipairs(fallback_list) do
|
||||
local depth = get_fallback_depth(v.id)
|
||||
-- 超过最大套娃层数后,不允许继续选择 balancer
|
||||
if depth < MAX_FALLBACK_DEPTH
|
||||
and not will_loop(arg[1], v.id)
|
||||
then
|
||||
o:value(v.id, v.remark)
|
||||
o.group[#o.group + 1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
for k1, v1 in pairs(node_list) do
|
||||
if k1 == "socks_list" or k1 == "normal_list" or k1 == "urltest_list" then
|
||||
for i, v in ipairs(v1) do
|
||||
o:value(v.id, v.remark)
|
||||
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 探测地址
|
||||
o = s:option(Flag, "useCustomProbeUrl", translate("Use Custom Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL."))
|
||||
o:depends({ protocol = "_balancing" })
|
||||
|
||||
o = s:option(Value, "probeUrl", translate("Probe URL"))
|
||||
o:depends({ useCustomProbeUrl = true })
|
||||
o:value("https://cp.cloudflare.com/", "Cloudflare")
|
||||
o:value("https://www.gstatic.com/generate_204", "Gstatic")
|
||||
o:value("https://www.google.com/generate_204", "Google")
|
||||
o:value("https://www.youtube.com/generate_204", "YouTube")
|
||||
o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
|
||||
o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
|
||||
o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
|
||||
o.default = o.keylist[3]
|
||||
o.description = translate("The URL used to detect the connection status.")
|
||||
|
||||
-- 探测间隔
|
||||
o = s:option(Value, "probeInterval", translate("Probe Interval"))
|
||||
o:depends({ protocol = "_balancing" })
|
||||
o.default = "1m"
|
||||
o.placeholder = "1m"
|
||||
o.description = translate("The interval between initiating probes.") .. "<br>" ..
|
||||
translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are <code>s</code>, <code>m</code>, <code>h</code>, which correspond to seconds, minutes, and hours, respectively.") .. "<br>" ..
|
||||
translate("When the unit is not filled in, it defaults to seconds.")
|
||||
|
||||
o = s:option(Value, "expected", translate("Preferred Node Count"))
|
||||
o:depends({ balancingStrategy = "leastLoad" })
|
||||
o.datatype = "uinteger"
|
||||
o.default = "2"
|
||||
o.placeholder = "2"
|
||||
o.description = translate("The load balancer selects the optimal number of nodes, and traffic is randomly distributed among them.")
|
||||
|
||||
o = s:option(Value, "tolerance", translate("Failure Tolerance (%)"))
|
||||
o:depends({ balancingStrategy = "leastLoad" })
|
||||
o.datatype = "uinteger"
|
||||
o.default = "10"
|
||||
o.placeholder = "10"
|
||||
o.description = translate("The maximum acceptable speed test failure rate. For example, 1 means allowing a 1% failure rate.")
|
||||
end -- [[ 负载均衡 End ]]
|
||||
|
||||
if load_iface_options then -- [[ 自定义接口 Start ]]
|
||||
o = s:option(Value, "iface", translate("Interface"))
|
||||
o:depends({ protocol = "_iface" })
|
||||
for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
end -- [[ 自定义接口 End ]]
|
||||
|
||||
|
||||
if load_normal_options then
|
||||
|
||||
o = s:option(Value, "address", translate("Address (Support Domain Name)"))
|
||||
|
||||
o = s:option(Value, "port", translate("Port"))
|
||||
o.datatype = "port"
|
||||
|
||||
o = s:option(Value, "uuid", translate("ID"))
|
||||
o.password = true
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless" })
|
||||
|
||||
o = s:option(Value, "username", translate("Username"))
|
||||
o:depends({ protocol = "http" })
|
||||
o:depends({ protocol = "socks" })
|
||||
|
||||
o = s:option(Value, "password", translate("Password"))
|
||||
o.password = true
|
||||
o:depends({ protocol = "http" })
|
||||
o:depends({ protocol = "socks" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
|
||||
o = s:option(ListValue, "security", translate("Encrypt Method"))
|
||||
for a, t in ipairs(security_list) do o:value(t) end
|
||||
o:depends({ protocol = "vmess" })
|
||||
|
||||
o = s:option(Value, "encryption", translate("Encrypt Method") .. " (encryption)")
|
||||
o.default = "none"
|
||||
o.placeholder = "none"
|
||||
o:depends({ protocol = "vless" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
return (value == "" and "none" or value)
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "ss_method", translate("Encrypt Method"))
|
||||
o.rewrite_option = "method"
|
||||
for a, t in ipairs(ss_method_list) do o:value(t) end
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
|
||||
o = s:option(ListValue, "flow", translate("flow"))
|
||||
o.default = ""
|
||||
o:value("", translate("Disable"))
|
||||
o:value("xtls-rprx-vision")
|
||||
o:value("xtls-rprx-vision-udp443")
|
||||
o:depends({ protocol = "vless" })
|
||||
|
||||
---- [[hysteria2]]
|
||||
o = s:option(Value, "hysteria2_hop", translate("Port hopping range"))
|
||||
o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
|
||||
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
|
||||
|
||||
o = s:option(Value, "hysteria2_hop_interval", translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
|
||||
o.datatype = "or(uinteger,portrange)"
|
||||
o.placeholder = "30"
|
||||
o.default = "30"
|
||||
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
|
||||
|
||||
o = s:option(Flag, "hysteria2_realms", translate("Realms"))
|
||||
o.default = "0"
|
||||
if api.compare_versions(xray_version, ">", "26.5.9") then
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
else
|
||||
o:depends({ protocol = "__hide"})
|
||||
end
|
||||
|
||||
o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
|
||||
o = s:option(Value, "hysteria2_auth_password", translate("Auth Password"))
|
||||
o.password = true
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
|
||||
o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
o:value("gecko")
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password"))
|
||||
o:depends({ hysteria2_obfs_type = "salamander" })
|
||||
o:depends({ hysteria2_obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "512"
|
||||
o.default = "512"
|
||||
o:depends({ hysteria2_obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "1200"
|
||||
o.default = "1200"
|
||||
o:depends({ hysteria2_obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps"))
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps"))
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Value, "hysteria2_idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
|
||||
o.datatype = "range(4,120)"
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
|
||||
o = s:option(Value, "hysteria2_keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
|
||||
o.datatype = "range(2,60)"
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
|
||||
o = s:option(Flag, "hysteria2_disable_mtu_discovery", translate("Disable MTU detection"))
|
||||
o.default = "0"
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
---- [[hysteria2 end]]
|
||||
|
||||
o = s:option(Flag, "tls", translate("TLS"))
|
||||
o.default = 0
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
|
||||
o = s:option(Flag, "reality", translate("REALITY"), translate("Only recommend to use with VLESS-TCP-XTLS-Vision."))
|
||||
o.default = 0
|
||||
o:depends({ tls = true, transport = "raw" })
|
||||
o:depends({ tls = true, transport = "ws" })
|
||||
o:depends({ tls = true, transport = "grpc" })
|
||||
o:depends({ tls = true, transport = "httpupgrade" })
|
||||
o:depends({ tls = true, transport = "xhttp" })
|
||||
|
||||
o = s:option(ListValue, "alpn", translate("alpn"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("h3")
|
||||
o:value("h2")
|
||||
o:value("h3,h2")
|
||||
o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
o:depends({ tls = true, reality = false })
|
||||
|
||||
-- o = s:option(Value, "minversion", translate("minversion"))
|
||||
-- o.default = "1.3"
|
||||
-- o:value("1.3")
|
||||
-- o:depends({ tls = true })
|
||||
|
||||
o = s:option(Value, "tls_serverName", "SNI " .. translate("Domain"))
|
||||
o:depends({ tls = true })
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Value, "tls_pinSHA256", translate("TLS Chain Fingerprint (SHA256)"))
|
||||
o:depends({ tls = true, reality = false })
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") ..
|
||||
string.format("<a href='javascript:void(0)' onclick='javascript:fetchCertSha256(this)'>%s</a>", "→ " .. translate("Fetch Manually"))
|
||||
|
||||
o = s:option(Value, "tls_CertByName", translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name."))
|
||||
o:depends({ tls = true, reality = false })
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Flag, "tls_certificate", translate("TLS Certificate (PEM)"))
|
||||
o.default = "0"
|
||||
o:depends({ tls = true, reality = false })
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(TextValue, "tls_certificate_pem", " ", translate("Full certificate (chain), PEM format."))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "off"
|
||||
o:depends({ tls_certificate = true })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
|
||||
return value
|
||||
end
|
||||
|
||||
o = s:option(Flag, "ech", translate("ECH"))
|
||||
o.default = "0"
|
||||
o:depends({ tls = true, reality = false })
|
||||
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
|
||||
|
||||
o = s:option(TextValue, "ech_config", translate("ECH Config"))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "soft"
|
||||
o:depends({ ech = true })
|
||||
o.validate = function(self, value)
|
||||
return api.trim(value:gsub("[\r\n]", ""))
|
||||
end
|
||||
|
||||
-- [[ REALITY部分 ]] --
|
||||
o = s:option(Value, "reality_publicKey", translate("Public Key"))
|
||||
o:depends({ tls = true, reality = true })
|
||||
|
||||
o = s:option(Value, "reality_shortId", translate("Short Id"))
|
||||
o:depends({ tls = true, reality = true })
|
||||
|
||||
o = s:option(Value, "reality_spiderX", translate("Spider X"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ tls = true, reality = true })
|
||||
|
||||
o = s:option(Flag, "utls", translate("uTLS"))
|
||||
o.default = "0"
|
||||
o:depends({ tls = true, reality = false })
|
||||
|
||||
o = s:option(ListValue, "fingerprint", translate("Finger Print"))
|
||||
o:value("chrome")
|
||||
o:value("firefox")
|
||||
o:value("edge")
|
||||
o:value("safari")
|
||||
o:value("360")
|
||||
o:value("qq")
|
||||
o:value("ios")
|
||||
o:value("android")
|
||||
o:value("random")
|
||||
o:value("randomized")
|
||||
o:value("unsafe")
|
||||
o.default = "chrome"
|
||||
o:depends({ tls = true, utls = true })
|
||||
o:depends({ tls = true, reality = true })
|
||||
|
||||
o = s:option(Value, "cipherSuites", translate("Cipher Suites"), '<a href="https://go.dev/src/crypto/tls/cipher_suites.go#L44" target="_blank">***</a>' .. " " .. translate("Configures the list of supported cipher suites, separated by :"))
|
||||
o:depends({ tls = true, reality = false })
|
||||
|
||||
o = s:option(Flag, "use_mldsa65Verify", translate("ML-DSA-65"))
|
||||
o.default = "0"
|
||||
o:depends({ tls = true, reality = true })
|
||||
|
||||
o = s:option(TextValue, "reality_mldsa65Verify", "ML-DSA-65 " .. translate("Public key"))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "soft"
|
||||
o:depends({ use_mldsa65Verify = true })
|
||||
o.validate = function(self, value)
|
||||
return api.trim(value:gsub("[\r\n]", ""))
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "transport", translate("Transport"))
|
||||
o:value("raw", "RAW (TCP)")
|
||||
o:value("mkcp", "mKCP")
|
||||
o:value("ws", "WebSocket")
|
||||
o:value("grpc", "gRPC")
|
||||
o:value("httpupgrade", "HttpUpgrade")
|
||||
o:value("xhttp", "XHTTP")
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
|
||||
o = s:option(Value, "wireguard_public_key", translate("Public Key"))
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(Value, "wireguard_secret_key", translate("Private Key"))
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(Value, "wireguard_preSharedKey", translate("Pre shared key"))
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(Value, "wireguard_mtu", translate("MTU"))
|
||||
o.default = "1420"
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
if api.compare_versions(xray_version, ">=", "1.8.0") then
|
||||
o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
|
||||
o:depends({ protocol = "wireguard" })
|
||||
end
|
||||
|
||||
o = s:option(Value, "wireguard_keepAlive", translate("Keep Alive"))
|
||||
o.default = "0"
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
-- [[ RAW部分 ]]--
|
||||
|
||||
-- TCP伪装
|
||||
o = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
|
||||
o:value("none", "none")
|
||||
o:value("http", "http")
|
||||
o:depends({ transport = "raw" })
|
||||
|
||||
-- HTTP域名
|
||||
o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
|
||||
o:depends({ tcp_guise = "http" })
|
||||
|
||||
-- HTTP路径
|
||||
o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ tcp_guise = "http" })
|
||||
|
||||
-- [[ mKCP部分 ]]--
|
||||
|
||||
o = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
|
||||
for a, t in ipairs(header_type_list) do o:value(t) end
|
||||
o:depends({ transport = "mkcp" })
|
||||
|
||||
o = s:option(Value, "mkcp_domain", translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
|
||||
o:depends({ mkcp_guise = "dns" })
|
||||
|
||||
o = s:option(Value, "mkcp_mtu", translate("KCP MTU"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 1350
|
||||
o:depends({ transport = "mkcp" })
|
||||
|
||||
o = s:option(Value, "mkcp_seed", translate("KCP Seed"))
|
||||
o:depends({ transport = "mkcp" })
|
||||
|
||||
-- [[ WebSocket部分 ]]--
|
||||
o = s:option(Value, "ws_host", translate("WebSocket Host"))
|
||||
o:depends({ transport = "ws" })
|
||||
|
||||
o = s:option(Value, "ws_path", translate("WebSocket Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ transport = "ws" })
|
||||
|
||||
o = s:option(Value, "ws_heartbeatPeriod", translate("HeartbeatPeriod(second)"))
|
||||
o.datatype = "integer"
|
||||
o:depends({ transport = "ws" })
|
||||
|
||||
-- [[ gRPC部分 ]]--
|
||||
o = s:option(Value, "grpc_serviceName", "ServiceName")
|
||||
o:depends({ transport = "grpc" })
|
||||
|
||||
o = s:option(ListValue, "grpc_mode", "gRPC " .. translate("Transfer mode"))
|
||||
o:value("gun")
|
||||
o:value("multi")
|
||||
o:depends({ transport = "grpc" })
|
||||
|
||||
o = s:option(Flag, "grpc_health_check", translate("Health check"))
|
||||
o:depends({ transport = "grpc" })
|
||||
|
||||
o = s:option(Value, "grpc_idle_timeout", translate("Idle timeout"))
|
||||
o.default = "10"
|
||||
o:depends({ grpc_health_check = true })
|
||||
|
||||
o = s:option(Value, "grpc_health_check_timeout", translate("Health check timeout"))
|
||||
o.default = "20"
|
||||
o:depends({ grpc_health_check = true })
|
||||
|
||||
o = s:option(Flag, "grpc_permit_without_stream", translate("Permit without stream"))
|
||||
o.default = "0"
|
||||
o:depends({ grpc_health_check = true })
|
||||
|
||||
o = s:option(Value, "grpc_initial_windows_size", translate("Initial Windows Size"))
|
||||
o.default = "0"
|
||||
o:depends({ transport = "grpc" })
|
||||
|
||||
-- [[ HttpUpgrade部分 ]]--
|
||||
o = s:option(Value, "httpupgrade_host", translate("HttpUpgrade Host"))
|
||||
o:depends({ transport = "httpupgrade" })
|
||||
|
||||
o = s:option(Value, "httpupgrade_path", translate("HttpUpgrade Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ transport = "httpupgrade" })
|
||||
|
||||
-- [[ XHTTP部分 ]]--
|
||||
o = s:option(ListValue, "xhttp_mode", "XHTTP " .. translate("Mode"))
|
||||
o:depends({ transport = "xhttp" })
|
||||
o.default = "auto"
|
||||
o:value("auto")
|
||||
o:value("packet-up")
|
||||
o:value("stream-up")
|
||||
o:value("stream-one")
|
||||
|
||||
o = s:option(Value, "xhttp_host", translate("XHTTP Host"))
|
||||
o:depends({ transport = "xhttp" })
|
||||
|
||||
o = s:option(Value, "xhttp_path", translate("XHTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ transport = "xhttp" })
|
||||
|
||||
o = s:option(Flag, "use_xhttp_extra", translate("XHTTP Extra"))
|
||||
o.default = "0"
|
||||
o:depends({ transport = "xhttp" })
|
||||
|
||||
o = s:option(TextValue, "xhttp_extra", " ", translate("An XHttpObject in JSON format, used for sharing."))
|
||||
o:depends({ use_xhttp_extra = true })
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, "XHTTP Extra " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local raw = m:get(section, "xhttp_extra")
|
||||
if raw then
|
||||
return api.base64Decode(raw)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "xhttp_extra", api.base64Encode(value) or "")
|
||||
local success, data = pcall(api.jsonc.parse, value)
|
||||
if success and data then
|
||||
local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address)
|
||||
or (data.downloadSettings and data.downloadSettings.address)
|
||||
if address and address ~= "" then
|
||||
address = address:gsub("^%[", ""):gsub("%]$", "")
|
||||
m:set(section, "download_address", address)
|
||||
else
|
||||
m:del(section, "download_address")
|
||||
end
|
||||
else
|
||||
m:del(section, "download_address")
|
||||
end
|
||||
end
|
||||
o.custom_remove = function(self, section, value)
|
||||
m:del(section, "xhttp_extra")
|
||||
m:del(section, "download_address")
|
||||
end
|
||||
|
||||
-- [[ User-Agent ]]--
|
||||
o = s:option(Value, "user_agent", translate("User-Agent"))
|
||||
o.default = ""
|
||||
o:value("", translate("default"))
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", "chrome")
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", "firefox")
|
||||
o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", "safari")
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", "edge")
|
||||
o:value("Go-http-client/1.1", "golang")
|
||||
o:value("curl/7.68.0", "curl")
|
||||
o:depends({ tcp_guise = "http" })
|
||||
o:depends({ transport = "ws" })
|
||||
o:depends({ transport = "httpupgrade" })
|
||||
o:depends({ transport = "xhttp" })
|
||||
o:depends({ transport = "grpc" })
|
||||
|
||||
-- [[ Mux.Cool ]]--
|
||||
o = s:option(Flag, "mux", "Mux", translate("Enable Mux.Cool"))
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless", transport = "raw" })
|
||||
o:depends({ protocol = "vless", transport = "ws" })
|
||||
o:depends({ protocol = "vless", transport = "grpc" })
|
||||
o:depends({ protocol = "vless", transport = "httpupgrade" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
|
||||
o = s:option(Value, "mux_concurrency", translate("Mux concurrency"))
|
||||
o.default = -1
|
||||
o:depends({ mux = true })
|
||||
|
||||
o = s:option(Value, "xudp_concurrency", translate("XUDP Mux concurrency"))
|
||||
o.default = 8
|
||||
o:depends({ mux = true })
|
||||
|
||||
--[[FinalMask]]
|
||||
o = s:option(Flag, "use_finalmask", "FinalMask")
|
||||
o.default = "0"
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "wireguard" })
|
||||
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
|
||||
|
||||
o = s:option(TextValue, "finalmask", " ")
|
||||
o:depends({ use_finalmask = true })
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "<br>" ..
|
||||
translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.")
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, "FinalMask " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local raw = m:get(section, "finalmask")
|
||||
if raw then
|
||||
return api.base64Decode(raw)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "finalmask", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
--[[Fast Open]]
|
||||
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
--[[tcpMptcp]]
|
||||
o = s:option(Flag, "tcpMptcp", "tcpMptcp", translate("Enable Multipath TCP, need to be enabled in both server and client configuration."))
|
||||
o.default = 0
|
||||
|
||||
o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve"))
|
||||
o.description = translate("If the node address is a domain name, this DNS will be used for resolution.") .. "<br>" .. string.format('<font color="red">%s</font>',
|
||||
translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead."))
|
||||
o:value("", translate("Auto"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
o:value("https", "DoH")
|
||||
|
||||
o = s:option(Value, "domain_resolver_dns", "DNS")
|
||||
o.datatype = "or(ipaddr,ipaddrport)"
|
||||
o:value("114.114.114.114")
|
||||
o:value("223.5.5.5:53")
|
||||
o.default = o.keylist[1]
|
||||
o:depends({ domain_resolver = "tcp" })
|
||||
o:depends({ domain_resolver = "udp" })
|
||||
|
||||
o = s:option(Value, "domain_resolver_dns_https", "DNS")
|
||||
o:value("https://120.53.53.53/dns-query", "DNSPod")
|
||||
o:value("https://223.5.5.5/dns-query", "AliDNS")
|
||||
o.default = o.keylist[1]
|
||||
o:depends({ domain_resolver = "https" })
|
||||
|
||||
o = s:option(ListValue, "domain_strategy", translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect."))
|
||||
o.default = ""
|
||||
o:value("", translate("Auto"))
|
||||
o:value("UseIPv4v6", translate("Prefer IPv4"))
|
||||
o:value("UseIPv6v4", translate("Prefer IPv6"))
|
||||
o:value("UseIPv4", translate("IPv4 Only"))
|
||||
o:value("UseIPv6", translate("IPv6 Only"))
|
||||
|
||||
o = s:option(Flag, "happy_eyeballs", translate("Enable Happy Eyeballs"), translate("Attempts IPv4 and IPv6 simultaneously; automatically uses the faster connection."))
|
||||
o.default = 0
|
||||
|
||||
local protocols = s.fields["protocol"].keylist
|
||||
if #protocols > 0 then
|
||||
for i, v in ipairs(protocols) do
|
||||
if not v:find("^_") then
|
||||
local depends_condition = { protocol = v }
|
||||
if v == "hysteria2" then
|
||||
depends_condition["hysteria2_realms"] = false
|
||||
end
|
||||
s.fields["address"]:depends(depends_condition)
|
||||
s.fields["port"]:depends(depends_condition)
|
||||
s.fields["domain_resolver"]:depends(depends_condition)
|
||||
s.fields["happy_eyeballs"]:depends(depends_condition)
|
||||
|
||||
local strategy_depends = api.clone(depends_condition)
|
||||
strategy_depends["happy_eyeballs"] = false
|
||||
s.fields["domain_strategy"]:depends(strategy_depends)
|
||||
|
||||
if v ~= "hysteria2" then
|
||||
s.fields["tcp_fast_open"]:depends({ protocol = v })
|
||||
s.fields["tcpMptcp"]:depends({ protocol = v })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- [[ Normal single node End ]]
|
||||
|
||||
if not load_shunt_options then
|
||||
o = s:option(ListValue, "chain_proxy", translate("Chain Proxy"))
|
||||
o:value("", translate("Close(Not use)"))
|
||||
if not (load_iface_options or load_balancing_options) then
|
||||
-- Special node cannot be use pre-proxy.
|
||||
o:value("1", translate("Preproxy Node"))
|
||||
o:value("3", translate("Outbound Interface"))
|
||||
end
|
||||
o:value("2", translate("Landing Node"))
|
||||
|
||||
o1 = s:option(ListValue, "preproxy_node", translate("Preproxy Node"), translate("Only support a layer of proxy."))
|
||||
o1:depends({ chain_proxy = "1", hysteria2_realms = false })
|
||||
o1.template = m:template_path("/cbi/nodes_listvalue")
|
||||
o1.group = {}
|
||||
|
||||
o3 = s:option(Value, "outbound_iface", translate("Outbound Interface"))
|
||||
o3:depends({ chain_proxy = "3" })
|
||||
o3:value("", translate("All"))
|
||||
for _, d in ipairs(netdev_list) do
|
||||
o3:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o2 = s:option(ListValue, "to_node", translate("Landing Node"), translate("Only support a layer of proxy."))
|
||||
o2:depends({ chain_proxy = "2", hysteria2_realms = false })
|
||||
o2.template = m:template_path("/cbi/nodes_listvalue")
|
||||
o2.group = {}
|
||||
|
||||
for k1, v1 in pairs(node_list) do
|
||||
if k1 ~= "shunt_list" and k1 ~= "iface_list" then
|
||||
for i, v in ipairs(v1) do
|
||||
if v.id ~= arg[1] then
|
||||
o1:value(v.id, v.remark)
|
||||
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
if k1 == "normal_list" then
|
||||
-- Landing Node not support use special node.
|
||||
o2:value(v.id, v.remark)
|
||||
o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
api.luci_types(s1, s)
|
||||
|
||||
if load_shunt_options then
|
||||
local current_node = m:get(arg[1]) or {}
|
||||
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
|
||||
setfenv(shunt_lua, getfenv(1))(m, s1, {
|
||||
node_id = arg[1],
|
||||
node = current_node,
|
||||
node_list = node_list,
|
||||
})
|
||||
end
|
||||
@@ -0,0 +1,72 @@
|
||||
if not api.is_finded("sslocal") then
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ Shadowsocks Rust ]]
|
||||
local m, s1 = ...
|
||||
local type_name = "SS-Rust"
|
||||
|
||||
s1.fields["type"]:value(type_name, "Shadowsocks Rust")
|
||||
|
||||
if s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local s = NamedSection(m, arg[1], "server")
|
||||
s.type_name = type_name
|
||||
s.option_prefix = "ssrust_"
|
||||
|
||||
local ssrust_encrypt_method_list = {
|
||||
"none", "plain",
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
|
||||
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
o = s:option(ListValue, "del_protocol") --始终隐藏,用于删除 protocol
|
||||
o:depends({ __hide = "1" })
|
||||
o.rewrite_option = "protocol"
|
||||
|
||||
o = s:option(Value, "address", translate("Address (Support Domain Name)"))
|
||||
|
||||
o = s:option(Value, "port", translate("Port"))
|
||||
o.datatype = "port"
|
||||
|
||||
o = s:option(Value, "password", translate("Password"))
|
||||
o.password = true
|
||||
|
||||
o = s:option(Value, "ss_method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end
|
||||
|
||||
o = s:option(Value, "timeout", translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
|
||||
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
o = s:option(Flag, "plugin_enabled", translate("plugin"))
|
||||
o.default = 0
|
||||
|
||||
o = s:option(Value, "plugin", "SIP003 " .. translate("plugin"), translate("Supports custom SIP003 plugins, Make sure the plugin is installed."))
|
||||
o.default = "none"
|
||||
o:value("none", translate("none"))
|
||||
if api.is_finded("xray-plugin") then o:value("xray-plugin") end
|
||||
if api.is_finded("v2ray-plugin") then o:value("v2ray-plugin") end
|
||||
if api.is_finded("obfs-local") then o:value("obfs-local") end
|
||||
if api.is_finded("shadow-tls") then o:value("shadow-tls") end
|
||||
o:depends({ plugin_enabled = true })
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" and value ~= "none" then
|
||||
if not api.is_finded(value) then
|
||||
return nil, value .. ": " .. translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
o = s:option(Value, "plugin_opts", translate("opts"))
|
||||
o:depends({ plugin_enabled = true })
|
||||
|
||||
api.luci_types(s1, s)
|
||||
+66
-70
@@ -1,70 +1,66 @@
|
||||
local m, s = ...
|
||||
|
||||
if not api.is_finded("ssr-local") and not api.is_finded("ssr-redir")then
|
||||
return
|
||||
end
|
||||
|
||||
type_name = "SSR"
|
||||
|
||||
-- [[ ShadowsocksR Libev ]]
|
||||
|
||||
s.fields["type"]:value(type_name, "ShadowsocksR Libev")
|
||||
|
||||
if s.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local option_prefix = "ssr_"
|
||||
|
||||
local function _n(name)
|
||||
return option_prefix .. name
|
||||
end
|
||||
|
||||
local ssr_encrypt_method_list = {
|
||||
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
|
||||
"aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
|
||||
"bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
|
||||
"cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20",
|
||||
"chacha20-ietf"
|
||||
}
|
||||
|
||||
local ssr_protocol_list = {
|
||||
"origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple",
|
||||
"auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5",
|
||||
"auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c",
|
||||
"auth_chain_d", "auth_chain_e", "auth_chain_f"
|
||||
}
|
||||
local ssr_obfs_list = {
|
||||
"plain", "http_simple", "http_post", "random_head", "tls_simple",
|
||||
"tls1.0_session_auth", "tls1.2_ticket_auth"
|
||||
}
|
||||
|
||||
o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
|
||||
|
||||
o = s:option(Value, _n("port"), translate("Port"))
|
||||
o.datatype = "port"
|
||||
|
||||
o = s:option(Value, _n("password"), translate("Password"))
|
||||
o.password = true
|
||||
|
||||
o = s:option(ListValue, _n("method"), translate("Encrypt Method"))
|
||||
for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end
|
||||
|
||||
o = s:option(ListValue, _n("protocol"), translate("Protocol"))
|
||||
for a, t in ipairs(ssr_protocol_list) do o:value(t) end
|
||||
|
||||
o = s:option(Value, _n("protocol_param"), translate("Protocol_param"))
|
||||
|
||||
o = s:option(ListValue, _n("obfs"), translate("Obfs"))
|
||||
for a, t in ipairs(ssr_obfs_list) do o:value(t) end
|
||||
|
||||
o = s:option(Value, _n("obfs_param"), translate("Obfs_param"))
|
||||
|
||||
o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
api.luci_types(arg[1], m, s, type_name, option_prefix)
|
||||
if not api.is_finded("ssr-local") and not api.is_finded("ssr-redir")then
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ ShadowsocksR Libev ]]
|
||||
local m, s1 = ...
|
||||
local type_name = "SSR"
|
||||
|
||||
s1.fields["type"]:value(type_name, "ShadowsocksR Libev")
|
||||
|
||||
if s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local s = NamedSection(m, arg[1], "server")
|
||||
s.type_name = type_name
|
||||
s.option_prefix = "ssr_"
|
||||
|
||||
local ssr_encrypt_method_list = {
|
||||
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
|
||||
"aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
|
||||
"bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
|
||||
"cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20",
|
||||
"chacha20-ietf"
|
||||
}
|
||||
|
||||
local ssr_protocol_list = {
|
||||
"origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple",
|
||||
"auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5",
|
||||
"auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c",
|
||||
"auth_chain_d", "auth_chain_e", "auth_chain_f"
|
||||
}
|
||||
local ssr_obfs_list = {
|
||||
"plain", "http_simple", "http_post", "random_head", "tls_simple",
|
||||
"tls1.0_session_auth", "tls1.2_ticket_auth"
|
||||
}
|
||||
|
||||
o = s:option(Value, "address", translate("Address (Support Domain Name)"))
|
||||
|
||||
o = s:option(Value, "port", translate("Port"))
|
||||
o.datatype = "port"
|
||||
|
||||
o = s:option(Value, "password", translate("Password"))
|
||||
o.password = true
|
||||
|
||||
o = s:option(ListValue, "method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end
|
||||
|
||||
o = s:option(ListValue, "protocol", translate("Protocol"))
|
||||
for a, t in ipairs(ssr_protocol_list) do o:value(t) end
|
||||
|
||||
o = s:option(Value, "protocol_param", translate("Protocol_param"))
|
||||
|
||||
o = s:option(ListValue, "obfs", translate("Obfs"))
|
||||
for a, t in ipairs(ssr_obfs_list) do o:value(t) end
|
||||
|
||||
o = s:option(Value, "obfs_param", translate("Obfs_param"))
|
||||
|
||||
o = s:option(Value, "timeout", translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
|
||||
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
api.luci_types(s1, s)
|
||||
@@ -0,0 +1,131 @@
|
||||
if not api.finded_com("hysteria") then
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ Hysteria2 ]]
|
||||
local m, s1 = ...
|
||||
local type_name = "Hysteria2"
|
||||
|
||||
s1.fields["type"]:value(type_name, "Hysteria2")
|
||||
|
||||
if s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local s = NamedSection(m, arg[1], "server")
|
||||
s.type_name = type_name
|
||||
s.option_prefix = "hysteria2_"
|
||||
|
||||
local function _n(name)
|
||||
return s.option_prefix .. name
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "protocol", translate("Protocol"))
|
||||
o:value("udp", "UDP")
|
||||
|
||||
o = s:option(Value, "address", translate("Address (Support Domain Name)"))
|
||||
o:depends({ realms = false })
|
||||
|
||||
o = s:option(Value, "port", translate("Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ realms = false })
|
||||
|
||||
o = s:option(Value, "hop", translate("Port hopping range"))
|
||||
o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ realms = false })
|
||||
|
||||
o = s:option(Value, "hop_interval", translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
|
||||
o.datatype = "or(uinteger,portrange)"
|
||||
o.placeholder = "30"
|
||||
o.default = "30"
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ realms = false })
|
||||
|
||||
o = s:option(Flag, "realms", translate("Realms"))
|
||||
o.default = "0"
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(Value, "realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ realms = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, "realm_stun", translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ realms = "1" })
|
||||
|
||||
o = s:option(Value, "auth_password", translate("Auth Password"))
|
||||
o.password = true
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(ListValue, "obfs_type", translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
o:value("gecko")
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(Value, "obfs_password", translate("Obfs Password"))
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ obfs_type = "salamander" })
|
||||
o:depends({ obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Value, "obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "512"
|
||||
o.default = "512"
|
||||
o:depends({ obfs_type = "gecko" })
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(Value, "obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "1200"
|
||||
o.default = "1200"
|
||||
o:depends({ obfs_type = "gecko" })
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(Flag, "fast_open", translate("Fast Open"))
|
||||
o.default = "0"
|
||||
|
||||
o = s:option(Value, "tls_serverName", translate("Domain"))
|
||||
|
||||
o = s:option(Flag, "tls_allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
|
||||
o.default = "0"
|
||||
|
||||
o = s:option(Value, "tls_pinSHA256", translate("PinSHA256"),translate("Certificate fingerprint"))
|
||||
|
||||
o = s:option(Value, "up_mbps", translate("Max upload Mbps"))
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(Value, "down_mbps", translate("Max download Mbps"))
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(Value, "recv_window", translate("QUIC stream receive window"))
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(Value, "recv_window_conn", translate("QUIC connection receive window"))
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(Value, "idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
|
||||
o.datatype = "range(4,120)"
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(Value, "keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
|
||||
o.datatype = "range(2,60)"
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(Flag, "disable_mtu_discovery", translate("Disable MTU detection"))
|
||||
o.default = "0"
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(Flag, "lazy_start", translate("Lazy Start"))
|
||||
o.default = "0"
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
api.luci_types(s1, s)
|
||||
@@ -1,131 +0,0 @@
|
||||
local m, s = ...
|
||||
|
||||
if not api.finded_com("hysteria") then
|
||||
return
|
||||
end
|
||||
|
||||
type_name = "Hysteria2"
|
||||
|
||||
-- [[ Hysteria2 ]]
|
||||
|
||||
s.fields["type"]:value(type_name, "Hysteria2")
|
||||
|
||||
if s.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local option_prefix = "hysteria2_"
|
||||
|
||||
local function _n(name)
|
||||
return option_prefix .. name
|
||||
end
|
||||
|
||||
o = s:option(ListValue, _n("protocol"), translate("Protocol"))
|
||||
o:value("udp", "UDP")
|
||||
|
||||
o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
|
||||
o:depends({ [_n("realms")] = false })
|
||||
|
||||
o = s:option(Value, _n("port"), translate("Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ [_n("realms")] = false })
|
||||
|
||||
o = s:option(Value, _n("hop"), translate("Port hopping range"))
|
||||
o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("realms")] = false })
|
||||
|
||||
o = s:option(Value, _n("hop_interval"), translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
|
||||
o.datatype = "or(uinteger,portrange)"
|
||||
o.placeholder = "30"
|
||||
o.default = "30"
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("realms")] = false })
|
||||
|
||||
o = s:option(Flag, _n("realms"), translate("Realms"))
|
||||
o.default = "0"
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(Value, _n("realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("realms")] = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, _n("realm_stun"), translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("realms")] = "1" })
|
||||
|
||||
o = s:option(Value, _n("auth_password"), translate("Auth Password"))
|
||||
o.password = true
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(ListValue, _n("obfs_type"), translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
o:value("gecko")
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(Value, _n("obfs_password"), translate("Obfs Password"))
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("obfs_type")] = "salamander" })
|
||||
o:depends({ [_n("obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Value, _n("obfs_MinPacketSize"), translate("Gecko Packet Size (min)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "512"
|
||||
o.default = "512"
|
||||
o:depends({ [_n("obfs_type")] = "gecko" })
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(Value, _n("obfs_MaxPacketSize"), translate("Gecko Packet Size (max)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "1200"
|
||||
o.default = "1200"
|
||||
o:depends({ [_n("obfs_type")] = "gecko" })
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(Flag, _n("fast_open"), translate("Fast Open"))
|
||||
o.default = "0"
|
||||
|
||||
o = s:option(Value, _n("tls_serverName"), translate("Domain"))
|
||||
|
||||
o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
|
||||
o.default = "0"
|
||||
|
||||
o = s:option(Value, _n("tls_pinSHA256"), translate("PinSHA256"),translate("Certificate fingerprint"))
|
||||
|
||||
o = s:option(Value, _n("up_mbps"), translate("Max upload Mbps"))
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(Value, _n("down_mbps"), translate("Max download Mbps"))
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(Value, _n("recv_window"), translate("QUIC stream receive window"))
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(Value, _n("recv_window_conn"), translate("QUIC connection receive window"))
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(Value, _n("idle_timeout"), translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
|
||||
o.datatype = "range(4,120)"
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(Value, _n("keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
|
||||
o.datatype = "range(2,60)"
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(Flag, _n("disable_mtu_discovery"), translate("Disable MTU detection"))
|
||||
o.default = "0"
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(Flag, _n("lazy_start"), translate("Lazy Start"))
|
||||
o.default = "0"
|
||||
o.rewrite_option = o.option
|
||||
|
||||
api.luci_types(arg[1], m, s, type_name, option_prefix)
|
||||
@@ -1,900 +0,0 @@
|
||||
local m, s = ...
|
||||
|
||||
if not api.finded_com("xray") then
|
||||
return
|
||||
end
|
||||
|
||||
local jsonc = api.jsonc
|
||||
|
||||
type_name = "Xray"
|
||||
|
||||
-- [[ Xray ]]
|
||||
|
||||
s.fields["type"]:value(type_name, "Xray")
|
||||
if not s.fields["type"].default then
|
||||
s.fields["type"].default = type_name
|
||||
end
|
||||
|
||||
if s.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local option_prefix = "xray_"
|
||||
|
||||
local function _n(name)
|
||||
return option_prefix .. name
|
||||
end
|
||||
|
||||
local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol"))
|
||||
|
||||
if formvalue_proto then s.val["protocol"] = formvalue_proto end
|
||||
|
||||
local arg_select_proto = luci.http.formvalue("select_proto") or ""
|
||||
|
||||
local ss_method_list = {
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
local security_list = { "auto", "aes-128-gcm", "chacha20-poly1305" }
|
||||
|
||||
local header_type_list = {
|
||||
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
|
||||
}
|
||||
|
||||
local xray_version = api.get_app_version("xray")
|
||||
|
||||
o = s:option(ListValue, _n("protocol"), translate("Protocol"))
|
||||
o:value("socks", translate("Socks"))
|
||||
o:value("http", translate("HTTP"))
|
||||
o:value("vmess", translate("Vmess"))
|
||||
o:value("vless", translate("VLESS"))
|
||||
o:value("shadowsocks", translate("Shadowsocks"))
|
||||
o:value("trojan", translate("Trojan"))
|
||||
o:value("wireguard", translate("WireGuard"))
|
||||
if api.compare_versions(xray_version, ">=", "26.1.13") then
|
||||
o:value("hysteria2", translate("Hysteria2"))
|
||||
end
|
||||
if api.compare_versions(xray_version, ">=", "1.8.12") then
|
||||
o:value("_balancing", translate("Balancing"))
|
||||
end
|
||||
o:value("_shunt", translate("Shunt"))
|
||||
o:value("_iface", translate("Custom Interface"))
|
||||
function o.custom_cfgvalue(self, section)
|
||||
if arg_select_proto ~= "" then
|
||||
return arg_select_proto
|
||||
else
|
||||
return m:get(section, self.option:sub(1 + #option_prefix))
|
||||
end
|
||||
end
|
||||
|
||||
local load_balancing_options = s.val["protocol"] == "_balancing" or arg_select_proto == "_balancing"
|
||||
local load_shunt_options = s.val["protocol"] == "_shunt" or arg_select_proto == "_shunt"
|
||||
local load_iface_options = s.val["protocol"] == "_iface" or arg_select_proto == "_iface"
|
||||
local load_normal_options = true
|
||||
if load_balancing_options or load_shunt_options or load_iface_options then
|
||||
load_normal_options = nil
|
||||
end
|
||||
if not arg_select_proto:find("_") then
|
||||
load_normal_options = true
|
||||
end
|
||||
|
||||
local netdev_list = api.get_network_devices()
|
||||
local node_list = api.get_node_list()
|
||||
local fallback_list = {}
|
||||
local is_balancer = nil
|
||||
for k, e in ipairs(node_list.balancing_list or {}) do
|
||||
if e.id ~= arg[1] then
|
||||
fallback_list[#fallback_list + 1] = {
|
||||
id = e["id"],
|
||||
remark = e["remark"],
|
||||
group = e["group"],
|
||||
fallback = e.o["fallback_node"],
|
||||
}
|
||||
else
|
||||
is_balancer = true
|
||||
end
|
||||
end
|
||||
|
||||
if load_balancing_options then -- [[ Load balancing Start ]]
|
||||
o = s:option(ListValue, _n("node_add_mode"), translate("Node Addition Method"))
|
||||
o:depends({ [_n("protocol")] = "_balancing" })
|
||||
o.default = "manual"
|
||||
o:value("manual", translate("Manual"))
|
||||
o:value("batch", translate("Batch"))
|
||||
|
||||
o = s:option(MultiValue, _n("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://xtls.github.io/config/routing.html#balancerobject'>document</a>"))
|
||||
o:depends({ [_n("node_add_mode")] = "manual" })
|
||||
o.widget = "checkbox"
|
||||
o.template = m:template_path("/cbi/nodes_multivalue")
|
||||
o.group = {}
|
||||
for k1, v1 in pairs(node_list) do
|
||||
if k1 == "socks_list" or k1 == "normal_list" then
|
||||
for i, v in ipairs(v1) do
|
||||
o:value(v.id, v.remark)
|
||||
o.group[#o.group+1] = v.group or ""
|
||||
end
|
||||
end
|
||||
end
|
||||
-- 读取旧 DynamicList
|
||||
function o.custom_cfgvalue(self, section)
|
||||
return table.concat(m:get(section, "balancing_node") or {}, " ")
|
||||
end
|
||||
-- 写入保持 DynamicList
|
||||
function o.custom_write(self, section, value)
|
||||
local old = m:get(section, "balancing_node") or {}
|
||||
local new, set = {}, {}
|
||||
for v in value:gmatch("%S+") do
|
||||
new[#new + 1] = v
|
||||
set[v] = 1
|
||||
end
|
||||
for _, v in ipairs(old) do
|
||||
if not set[v] then
|
||||
m:set(section, "balancing_node", new)
|
||||
return
|
||||
end
|
||||
set[v] = nil
|
||||
end
|
||||
for _ in pairs(set) do
|
||||
m:set(section, "balancing_node", new)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(MultiValue, _n("node_group"), translate("Select Group"))
|
||||
o:depends({ [_n("node_add_mode")] = "batch" })
|
||||
o.widget = "checkbox"
|
||||
o:value("default", translate("default"))
|
||||
for k, v in pairs(groups) do
|
||||
o:value(api.UrlEncode(k), k)
|
||||
end
|
||||
|
||||
o = s:option(Value, _n("node_match_rule"), translate("Node Matching Rules"))
|
||||
o:depends({ [_n("node_add_mode")] = "batch" })
|
||||
local descrStr = "Example: <code>^A && B && !C && D$</code><br>"
|
||||
descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
|
||||
descrStr = descrStr .. "Conditions are joined by <code>&&</code> (AND), and their order does not affect the result.<br>"
|
||||
descrStr = descrStr .. "Multiple groups can be separated by <code>||</code> (OR), matching succeeds if any group matches.<br>"
|
||||
descrStr = descrStr .. "Example: <code>A && B || C && D</code> means (A AND B) OR (C AND D)."
|
||||
o.description = translate(descrStr)
|
||||
|
||||
o = s:option(ListValue, _n("balancingStrategy"), translate("Balancing Strategy"))
|
||||
o:depends({ [_n("protocol")] = "_balancing" })
|
||||
o:value("random")
|
||||
o:value("roundRobin")
|
||||
o:value("leastPing")
|
||||
o:value("leastLoad")
|
||||
o.default = "random"
|
||||
|
||||
-- Fallback Node
|
||||
o = s:option(ListValue, _n("fallback_node"), translate("Fallback Node"))
|
||||
o.group = {"",""}
|
||||
o:value("", translate("Close(Not use)"))
|
||||
o:value("_direct", translate("Direct Connection"))
|
||||
o:depends({ [_n("protocol")] = "_balancing" })
|
||||
o.template = m:template_path("/cbi/nodes_listvalue")
|
||||
-- 最大 fallback 套娃层数
|
||||
local MAX_FALLBACK_DEPTH = 3
|
||||
-- 检查是否会形成回环
|
||||
local function will_loop(start_id, target_id, depth)
|
||||
depth = depth or 0
|
||||
-- 超过最大深度后停止递归
|
||||
if depth >= MAX_FALLBACK_DEPTH then
|
||||
return false
|
||||
end
|
||||
for _, v in ipairs(fallback_list) do
|
||||
if v.id == target_id then
|
||||
local fb = v.fallback
|
||||
-- 没有 fallback
|
||||
if not fb or fb == "" or fb == "_direct" then
|
||||
return false
|
||||
end
|
||||
-- 检测到回环
|
||||
if fb == start_id then
|
||||
return true
|
||||
end
|
||||
-- 继续递归检查
|
||||
return will_loop(start_id, fb, depth + 1)
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
-- 获取 fallback 链深度
|
||||
local function get_fallback_depth(id, depth)
|
||||
depth = depth or 0
|
||||
if depth >= MAX_FALLBACK_DEPTH then
|
||||
return depth
|
||||
end
|
||||
for _, v in ipairs(fallback_list) do
|
||||
if v.id == id then
|
||||
local fb = v.fallback
|
||||
if not fb or fb == "" or fb == "_direct" then
|
||||
return depth
|
||||
end
|
||||
return get_fallback_depth(fb, depth + 1)
|
||||
end
|
||||
end
|
||||
return depth
|
||||
end
|
||||
for _, v in ipairs(fallback_list) do
|
||||
local depth = get_fallback_depth(v.id)
|
||||
-- 超过最大套娃层数后,不允许继续选择 balancer
|
||||
if depth < MAX_FALLBACK_DEPTH
|
||||
and not will_loop(arg[1], v.id)
|
||||
then
|
||||
o:value(v.id, v.remark)
|
||||
o.group[#o.group + 1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
for k1, v1 in pairs(node_list) do
|
||||
if k1 == "socks_list" or k1 == "normal_list" or k1 == "urltest_list" then
|
||||
for i, v in ipairs(v1) do
|
||||
o:value(v.id, v.remark)
|
||||
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 探测地址
|
||||
o = s:option(Flag, _n("useCustomProbeUrl"), translate("Use Custom Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL."))
|
||||
o:depends({ [_n("protocol")] = "_balancing" })
|
||||
|
||||
o = s:option(Value, _n("probeUrl"), translate("Probe URL"))
|
||||
o:depends({ [_n("useCustomProbeUrl")] = true })
|
||||
o:value("https://cp.cloudflare.com/", "Cloudflare")
|
||||
o:value("https://www.gstatic.com/generate_204", "Gstatic")
|
||||
o:value("https://www.google.com/generate_204", "Google")
|
||||
o:value("https://www.youtube.com/generate_204", "YouTube")
|
||||
o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
|
||||
o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
|
||||
o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
|
||||
o.default = o.keylist[3]
|
||||
o.description = translate("The URL used to detect the connection status.")
|
||||
|
||||
-- 探测间隔
|
||||
o = s:option(Value, _n("probeInterval"), translate("Probe Interval"))
|
||||
o:depends({ [_n("protocol")] = "_balancing" })
|
||||
o.default = "1m"
|
||||
o.placeholder = "1m"
|
||||
o.description = translate("The interval between initiating probes.") .. "<br>" ..
|
||||
translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are <code>s</code>, <code>m</code>, <code>h</code>, which correspond to seconds, minutes, and hours, respectively.") .. "<br>" ..
|
||||
translate("When the unit is not filled in, it defaults to seconds.")
|
||||
|
||||
o = s:option(Value, _n("expected"), translate("Preferred Node Count"))
|
||||
o:depends({ [_n("balancingStrategy")] = "leastLoad" })
|
||||
o.datatype = "uinteger"
|
||||
o.default = "2"
|
||||
o.placeholder = "2"
|
||||
o.description = translate("The load balancer selects the optimal number of nodes, and traffic is randomly distributed among them.")
|
||||
|
||||
o = s:option(Value, _n("tolerance"), translate("Failure Tolerance (%)"))
|
||||
o:depends({ [_n("balancingStrategy")] = "leastLoad" })
|
||||
o.datatype = "uinteger"
|
||||
o.default = "10"
|
||||
o.placeholder = "10"
|
||||
o.description = translate("The maximum acceptable speed test failure rate. For example, 1 means allowing a 1% failure rate.")
|
||||
end -- [[ 负载均衡 End ]]
|
||||
|
||||
if load_iface_options then -- [[ 自定义接口 Start ]]
|
||||
o = s:option(Value, _n("iface"), translate("Interface"))
|
||||
o:depends({ [_n("protocol")] = "_iface" })
|
||||
for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
end -- [[ 自定义接口 End ]]
|
||||
|
||||
|
||||
if load_normal_options then
|
||||
|
||||
o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
|
||||
|
||||
o = s:option(Value, _n("port"), translate("Port"))
|
||||
o.datatype = "port"
|
||||
|
||||
o = s:option(Value, _n("uuid"), translate("ID"))
|
||||
o.password = true
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
|
||||
o = s:option(Value, _n("username"), translate("Username"))
|
||||
o:depends({ [_n("protocol")] = "http" })
|
||||
o:depends({ [_n("protocol")] = "socks" })
|
||||
|
||||
o = s:option(Value, _n("password"), translate("Password"))
|
||||
o.password = true
|
||||
o:depends({ [_n("protocol")] = "http" })
|
||||
o:depends({ [_n("protocol")] = "socks" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
|
||||
o = s:option(ListValue, _n("security"), translate("Encrypt Method"))
|
||||
for a, t in ipairs(security_list) do o:value(t) end
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
|
||||
o = s:option(Value, _n("encryption"), translate("Encrypt Method") .. " (encryption)")
|
||||
o.default = "none"
|
||||
o.placeholder = "none"
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
return (value == "" and "none" or value)
|
||||
end
|
||||
|
||||
o = s:option(ListValue, _n("ss_method"), translate("Encrypt Method"))
|
||||
o.rewrite_option = "method"
|
||||
for a, t in ipairs(ss_method_list) do o:value(t) end
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
|
||||
o = s:option(ListValue, _n("flow"), translate("flow"))
|
||||
o.default = ""
|
||||
o:value("", translate("Disable"))
|
||||
o:value("xtls-rprx-vision")
|
||||
o:value("xtls-rprx-vision-udp443")
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
|
||||
---- [[hysteria2]]
|
||||
o = s:option(Value, _n("hysteria2_hop"), translate("Port hopping range"))
|
||||
o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
|
||||
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_hop_interval"), translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
|
||||
o.datatype = "or(uinteger,portrange)"
|
||||
o.placeholder = "30"
|
||||
o.default = "30"
|
||||
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
|
||||
|
||||
o = s:option(Flag, _n("hysteria2_realms"), translate("Realms"))
|
||||
o.default = "0"
|
||||
if api.compare_versions(xray_version, ">", "26.5.9") then
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
else
|
||||
o:depends({ [_n("protocol")] = "__hide"})
|
||||
end
|
||||
|
||||
o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o:depends({ [_n("hysteria2_realms")] = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o:depends({ [_n("hysteria2_realms")] = "1" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
|
||||
o.password = true
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
|
||||
o = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
o:value("gecko")
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password"))
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "salamander" })
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_obfs_MinPacketSize"), translate("Gecko Packet Size (min)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "512"
|
||||
o.default = "512"
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_obfs_MaxPacketSize"), translate("Gecko Packet Size (max)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "1200"
|
||||
o.default = "1200"
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_up_mbps"), translate("Max upload Mbps"))
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_down_mbps"), translate("Max download Mbps"))
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_idle_timeout"), translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
|
||||
o.datatype = "range(4,120)"
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
|
||||
o = s:option(Value, _n("hysteria2_keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
|
||||
o.datatype = "range(2,60)"
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
|
||||
o = s:option(Flag, _n("hysteria2_disable_mtu_discovery"), translate("Disable MTU detection"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
---- [[hysteria2 end]]
|
||||
|
||||
o = s:option(Flag, _n("tls"), translate("TLS"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
|
||||
o = s:option(Flag, _n("reality"), translate("REALITY"), translate("Only recommend to use with VLESS-TCP-XTLS-Vision."))
|
||||
o.default = 0
|
||||
o:depends({ [_n("tls")] = true, [_n("transport")] = "raw" })
|
||||
o:depends({ [_n("tls")] = true, [_n("transport")] = "ws" })
|
||||
o:depends({ [_n("tls")] = true, [_n("transport")] = "grpc" })
|
||||
o:depends({ [_n("tls")] = true, [_n("transport")] = "httpupgrade" })
|
||||
o:depends({ [_n("tls")] = true, [_n("transport")] = "xhttp" })
|
||||
|
||||
o = s:option(ListValue, _n("alpn"), translate("alpn"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("h3")
|
||||
o:value("h2")
|
||||
o:value("h3,h2")
|
||||
o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
|
||||
-- o = s:option(Value, _n("minversion"), translate("minversion"))
|
||||
-- o.default = "1.3"
|
||||
-- o:value("1.3")
|
||||
-- o:depends({ [_n("tls")] = true })
|
||||
|
||||
o = s:option(Value, _n("tls_serverName"), "SNI " .. translate("Domain"))
|
||||
o:depends({ [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Value, _n("tls_pinSHA256"), translate("TLS Chain Fingerprint (SHA256)"))
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") ..
|
||||
string.format("<a href='javascript:void(0)' onclick='javascript:fetchCertSha256(this)'>%s</a>", "→ " .. translate("Fetch Manually"))
|
||||
|
||||
o = s:option(Value, _n("tls_CertByName"), translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name."))
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Flag, _n("tls_certificate"), translate("TLS Certificate (PEM)"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(TextValue, _n("tls_certificate_pem"), " ", translate("Full certificate (chain), PEM format."))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("tls_certificate")] = true })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
|
||||
return value
|
||||
end
|
||||
|
||||
o = s:option(Flag, _n("ech"), translate("ECH"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
|
||||
|
||||
o = s:option(TextValue, _n("ech_config"), translate("ECH Config"))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "soft"
|
||||
o:depends({ [_n("ech")] = true })
|
||||
o.validate = function(self, value)
|
||||
return api.trim(value:gsub("[\r\n]", ""))
|
||||
end
|
||||
|
||||
-- [[ REALITY部分 ]] --
|
||||
o = s:option(Value, _n("reality_publicKey"), translate("Public Key"))
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = true })
|
||||
|
||||
o = s:option(Value, _n("reality_shortId"), translate("Short Id"))
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = true })
|
||||
|
||||
o = s:option(Value, _n("reality_spiderX"), translate("Spider X"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = true })
|
||||
|
||||
o = s:option(Flag, _n("utls"), translate("uTLS"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
|
||||
o = s:option(ListValue, _n("fingerprint"), translate("Finger Print"))
|
||||
o:value("chrome")
|
||||
o:value("firefox")
|
||||
o:value("edge")
|
||||
o:value("safari")
|
||||
o:value("360")
|
||||
o:value("qq")
|
||||
o:value("ios")
|
||||
o:value("android")
|
||||
o:value("random")
|
||||
o:value("randomized")
|
||||
o:value("unsafe")
|
||||
o.default = "chrome"
|
||||
o:depends({ [_n("tls")] = true, [_n("utls")] = true })
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = true })
|
||||
|
||||
o = s:option(Value, _n("cipherSuites"), translate("Cipher Suites"), '<a href="https://go.dev/src/crypto/tls/cipher_suites.go#L44" target="_blank">***</a>' .. " " .. translate("Configures the list of supported cipher suites, separated by :"))
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
|
||||
o = s:option(Flag, _n("use_mldsa65Verify"), translate("ML-DSA-65"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = true })
|
||||
|
||||
o = s:option(TextValue, _n("reality_mldsa65Verify"), "ML-DSA-65 " .. translate("Public key"))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "soft"
|
||||
o:depends({ [_n("use_mldsa65Verify")] = true })
|
||||
o.validate = function(self, value)
|
||||
return api.trim(value:gsub("[\r\n]", ""))
|
||||
end
|
||||
|
||||
o = s:option(ListValue, _n("transport"), translate("Transport"))
|
||||
o:value("raw", "RAW (TCP)")
|
||||
o:value("mkcp", "mKCP")
|
||||
o:value("ws", "WebSocket")
|
||||
o:value("grpc", "gRPC")
|
||||
o:value("httpupgrade", "HttpUpgrade")
|
||||
o:value("xhttp", "XHTTP")
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
|
||||
o = s:option(Value, _n("wireguard_public_key"), translate("Public Key"))
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(Value, _n("wireguard_secret_key"), translate("Private Key"))
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(Value, _n("wireguard_preSharedKey"), translate("Pre shared key"))
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(DynamicList, _n("wireguard_local_address"), translate("Local Address"))
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(Value, _n("wireguard_mtu"), translate("MTU"))
|
||||
o.default = "1420"
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
if api.compare_versions(xray_version, ">=", "1.8.0") then
|
||||
o = s:option(Value, _n("wireguard_reserved"), translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
end
|
||||
|
||||
o = s:option(Value, _n("wireguard_keepAlive"), translate("Keep Alive"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
-- [[ RAW部分 ]]--
|
||||
|
||||
-- TCP伪装
|
||||
o = s:option(ListValue, _n("tcp_guise"), translate("Camouflage Type"))
|
||||
o:value("none", "none")
|
||||
o:value("http", "http")
|
||||
o:depends({ [_n("transport")] = "raw" })
|
||||
|
||||
-- HTTP域名
|
||||
o = s:option(DynamicList, _n("tcp_guise_http_host"), translate("HTTP Host"))
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
-- HTTP路径
|
||||
o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
-- [[ mKCP部分 ]]--
|
||||
|
||||
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
|
||||
for a, t in ipairs(header_type_list) do o:value(t) end
|
||||
o:depends({ [_n("transport")] = "mkcp" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
|
||||
o:depends({ [_n("mkcp_guise")] = "dns" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 1350
|
||||
o:depends({ [_n("transport")] = "mkcp" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_seed"), translate("KCP Seed"))
|
||||
o:depends({ [_n("transport")] = "mkcp" })
|
||||
|
||||
-- [[ WebSocket部分 ]]--
|
||||
o = s:option(Value, _n("ws_host"), translate("WebSocket Host"))
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Value, _n("ws_heartbeatPeriod"), translate("HeartbeatPeriod(second)"))
|
||||
o.datatype = "integer"
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
-- [[ gRPC部分 ]]--
|
||||
o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
|
||||
o:depends({ [_n("transport")] = "grpc" })
|
||||
|
||||
o = s:option(ListValue, _n("grpc_mode"), "gRPC " .. translate("Transfer mode"))
|
||||
o:value("gun")
|
||||
o:value("multi")
|
||||
o:depends({ [_n("transport")] = "grpc" })
|
||||
|
||||
o = s:option(Flag, _n("grpc_health_check"), translate("Health check"))
|
||||
o:depends({ [_n("transport")] = "grpc" })
|
||||
|
||||
o = s:option(Value, _n("grpc_idle_timeout"), translate("Idle timeout"))
|
||||
o.default = "10"
|
||||
o:depends({ [_n("grpc_health_check")] = true })
|
||||
|
||||
o = s:option(Value, _n("grpc_health_check_timeout"), translate("Health check timeout"))
|
||||
o.default = "20"
|
||||
o:depends({ [_n("grpc_health_check")] = true })
|
||||
|
||||
o = s:option(Flag, _n("grpc_permit_without_stream"), translate("Permit without stream"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("grpc_health_check")] = true })
|
||||
|
||||
o = s:option(Value, _n("grpc_initial_windows_size"), translate("Initial Windows Size"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("transport")] = "grpc" })
|
||||
|
||||
-- [[ HttpUpgrade部分 ]]--
|
||||
o = s:option(Value, _n("httpupgrade_host"), translate("HttpUpgrade Host"))
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
o = s:option(Value, _n("httpupgrade_path"), translate("HttpUpgrade Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
-- [[ XHTTP部分 ]]--
|
||||
o = s:option(ListValue, _n("xhttp_mode"), "XHTTP " .. translate("Mode"))
|
||||
o:depends({ [_n("transport")] = "xhttp" })
|
||||
o.default = "auto"
|
||||
o:value("auto")
|
||||
o:value("packet-up")
|
||||
o:value("stream-up")
|
||||
o:value("stream-one")
|
||||
|
||||
o = s:option(Value, _n("xhttp_host"), translate("XHTTP Host"))
|
||||
o:depends({ [_n("transport")] = "xhttp" })
|
||||
|
||||
o = s:option(Value, _n("xhttp_path"), translate("XHTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "xhttp" })
|
||||
|
||||
o = s:option(Flag, _n("use_xhttp_extra"), translate("XHTTP Extra"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("transport")] = "xhttp" })
|
||||
|
||||
o = s:option(TextValue, _n("xhttp_extra"), " ", translate("An XHttpObject in JSON format, used for sharing."))
|
||||
o:depends({ [_n("use_xhttp_extra")] = true })
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, "XHTTP Extra " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local raw = m:get(section, "xhttp_extra")
|
||||
if raw then
|
||||
return api.base64Decode(raw)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "xhttp_extra", api.base64Encode(value) or "")
|
||||
local success, data = pcall(jsonc.parse, value)
|
||||
if success and data then
|
||||
local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address)
|
||||
or (data.downloadSettings and data.downloadSettings.address)
|
||||
if address and address ~= "" then
|
||||
address = address:gsub("^%[", ""):gsub("%]$", "")
|
||||
m:set(section, "download_address", address)
|
||||
else
|
||||
m:del(section, "download_address")
|
||||
end
|
||||
else
|
||||
m:del(section, "download_address")
|
||||
end
|
||||
end
|
||||
o.custom_remove = function(self, section, value)
|
||||
m:del(section, "xhttp_extra")
|
||||
m:del(section, "download_address")
|
||||
end
|
||||
|
||||
-- [[ User-Agent ]]--
|
||||
o = s:option(Value, _n("user_agent"), translate("User-Agent"))
|
||||
o.default = ""
|
||||
o:value("", translate("default"))
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", "chrome")
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", "firefox")
|
||||
o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", "safari")
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", "edge")
|
||||
o:value("Go-http-client/1.1", "golang")
|
||||
o:value("curl/7.68.0", "curl")
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
o:depends({ [_n("transport")] = "xhttp" })
|
||||
o:depends({ [_n("transport")] = "grpc" })
|
||||
|
||||
-- [[ Mux.Cool ]]--
|
||||
o = s:option(Flag, _n("mux"), "Mux", translate("Enable Mux.Cool"))
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "raw" })
|
||||
o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "ws" })
|
||||
o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "grpc" })
|
||||
o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "httpupgrade" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
|
||||
o = s:option(Value, _n("mux_concurrency"), translate("Mux concurrency"))
|
||||
o.default = -1
|
||||
o:depends({ [_n("mux")] = true })
|
||||
|
||||
o = s:option(Value, _n("xudp_concurrency"), translate("XUDP Mux concurrency"))
|
||||
o.default = 8
|
||||
o:depends({ [_n("mux")] = true })
|
||||
|
||||
--[[FinalMask]]
|
||||
o = s:option(Flag, _n("use_finalmask"), "FinalMask")
|
||||
o.default = "0"
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
|
||||
|
||||
o = s:option(TextValue, _n("finalmask"), " ")
|
||||
o:depends({ [_n("use_finalmask")] = true })
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "<br>" ..
|
||||
translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.")
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, "FinalMask " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local raw = m:get(section, "finalmask")
|
||||
if raw then
|
||||
return api.base64Decode(raw)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "finalmask", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
--[[Fast Open]]
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
--[[tcpMptcp]]
|
||||
o = s:option(Flag, _n("tcpMptcp"), "tcpMptcp", translate("Enable Multipath TCP, need to be enabled in both server and client configuration."))
|
||||
o.default = 0
|
||||
|
||||
o = s:option(ListValue, _n("domain_resolver"), translate("Domain DNS Resolve"))
|
||||
o.description = translate("If the node address is a domain name, this DNS will be used for resolution.") .. "<br>" .. string.format('<font color="red">%s</font>',
|
||||
translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead."))
|
||||
o:value("", translate("Auto"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
o:value("https", "DoH")
|
||||
|
||||
o = s:option(Value, _n("domain_resolver_dns"), "DNS")
|
||||
o.datatype = "or(ipaddr,ipaddrport)"
|
||||
o:value("114.114.114.114")
|
||||
o:value("223.5.5.5:53")
|
||||
o.default = o.keylist[1]
|
||||
o:depends({ [_n("domain_resolver")] = "tcp" })
|
||||
o:depends({ [_n("domain_resolver")] = "udp" })
|
||||
|
||||
o = s:option(Value, _n("domain_resolver_dns_https"), "DNS")
|
||||
o:value("https://120.53.53.53/dns-query", "DNSPod")
|
||||
o:value("https://223.5.5.5/dns-query", "AliDNS")
|
||||
o.default = o.keylist[1]
|
||||
o:depends({ [_n("domain_resolver")] = "https" })
|
||||
|
||||
o = s:option(ListValue, _n("domain_strategy"), translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect."))
|
||||
o.default = ""
|
||||
o:value("", translate("Auto"))
|
||||
o:value("UseIPv4v6", translate("Prefer IPv4"))
|
||||
o:value("UseIPv6v4", translate("Prefer IPv6"))
|
||||
o:value("UseIPv4", translate("IPv4 Only"))
|
||||
o:value("UseIPv6", translate("IPv6 Only"))
|
||||
|
||||
o = s:option(Flag, _n("happy_eyeballs"), translate("Enable Happy Eyeballs"), translate("Attempts IPv4 and IPv6 simultaneously; automatically uses the faster connection."))
|
||||
o.default = 0
|
||||
|
||||
local protocols = s.fields[_n("protocol")].keylist
|
||||
if #protocols > 0 then
|
||||
for i, v in ipairs(protocols) do
|
||||
if not v:find("^_") then
|
||||
local depends_condition = { [_n("protocol")] = v }
|
||||
if v == "hysteria2" then
|
||||
depends_condition[_n("hysteria2_realms")] = false
|
||||
end
|
||||
s.fields[_n("address")]:depends(depends_condition)
|
||||
s.fields[_n("port")]:depends(depends_condition)
|
||||
s.fields[_n("domain_resolver")]:depends(depends_condition)
|
||||
s.fields[_n("happy_eyeballs")]:depends(depends_condition)
|
||||
|
||||
local strategy_depends = api.clone(depends_condition)
|
||||
strategy_depends[_n("happy_eyeballs")] = false
|
||||
s.fields[_n("domain_strategy")]:depends(strategy_depends)
|
||||
|
||||
if v ~= "hysteria2" then
|
||||
s.fields[_n("tcp_fast_open")]:depends({ [_n("protocol")] = v })
|
||||
s.fields[_n("tcpMptcp")]:depends({ [_n("protocol")] = v })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- [[ Normal single node End ]]
|
||||
|
||||
if not load_shunt_options then
|
||||
o = s:option(ListValue, _n("chain_proxy"), translate("Chain Proxy"))
|
||||
o:value("", translate("Close(Not use)"))
|
||||
if not (load_iface_options or load_balancing_options) then
|
||||
-- Special node cannot be use pre-proxy.
|
||||
o:value("1", translate("Preproxy Node"))
|
||||
o:value("3", translate("Outbound Interface"))
|
||||
end
|
||||
o:value("2", translate("Landing Node"))
|
||||
|
||||
o1 = s:option(ListValue, _n("preproxy_node"), translate("Preproxy Node"), translate("Only support a layer of proxy."))
|
||||
o1:depends({ [_n("chain_proxy")] = "1", [_n("hysteria2_realms")] = false })
|
||||
o1.template = m:template_path("/cbi/nodes_listvalue")
|
||||
o1.group = {}
|
||||
|
||||
o3 = s:option(Value, _n("outbound_iface"), translate("Outbound Interface"))
|
||||
o3:depends({ [_n("chain_proxy")] = "3" })
|
||||
o3:value("", translate("All"))
|
||||
for _, d in ipairs(netdev_list) do
|
||||
o3:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o2 = s:option(ListValue, _n("to_node"), translate("Landing Node"), translate("Only support a layer of proxy."))
|
||||
o2:depends({ [_n("chain_proxy")] = "2", [_n("hysteria2_realms")] = false })
|
||||
o2.template = m:template_path("/cbi/nodes_listvalue")
|
||||
o2.group = {}
|
||||
|
||||
for k1, v1 in pairs(node_list) do
|
||||
if k1 ~= "shunt_list" and k1 ~= "iface_list" then
|
||||
for i, v in ipairs(v1) do
|
||||
if v.id ~= arg[1] then
|
||||
o1:value(v.id, v.remark)
|
||||
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
if k1 == "normal_list" then
|
||||
-- Landing Node not support use special node.
|
||||
o2:value(v.id, v.remark)
|
||||
o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
api.luci_types(arg[1], m, s, type_name, option_prefix)
|
||||
|
||||
if load_shunt_options then
|
||||
local current_node = m:get(arg[1]) or {}
|
||||
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
|
||||
setfenv(shunt_lua, getfenv(1))(m, s, {
|
||||
node_id = arg[1],
|
||||
node = current_node,
|
||||
node_list = node_list,
|
||||
})
|
||||
end
|
||||
@@ -1,921 +0,0 @@
|
||||
local m, s = ...
|
||||
|
||||
local singbox_bin = api.finded_com("sing-box")
|
||||
|
||||
if not singbox_bin then
|
||||
return
|
||||
end
|
||||
|
||||
type_name = "sing-box"
|
||||
|
||||
-- [[ sing-box ]]
|
||||
|
||||
s.fields["type"]:value(type_name, "Sing-Box")
|
||||
if not s.fields["type"].default then
|
||||
s.fields["type"].default = type_name
|
||||
end
|
||||
|
||||
if s.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local option_prefix = "singbox_"
|
||||
|
||||
local function _n(name)
|
||||
return option_prefix .. name
|
||||
end
|
||||
|
||||
local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol"))
|
||||
|
||||
if formvalue_proto then s.val["protocol"] = formvalue_proto end
|
||||
|
||||
local arg_select_proto = luci.http.formvalue("select_proto") or ""
|
||||
|
||||
local ss_method_new_list = {
|
||||
"none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
local ss_method_old_list = {
|
||||
"aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20",
|
||||
}
|
||||
|
||||
local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" }
|
||||
|
||||
local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'")
|
||||
|
||||
local singbox_version = api.get_app_version("sing-box"):match("[^v]+")
|
||||
|
||||
o = s:option(ListValue, _n("protocol"), translate("Protocol"))
|
||||
o:value("socks", "Socks")
|
||||
o:value("http", "HTTP")
|
||||
o:value("shadowsocks", "Shadowsocks")
|
||||
o:value("vmess", "Vmess")
|
||||
o:value("trojan", "Trojan")
|
||||
if singbox_tags:find("with_wireguard") then
|
||||
o:value("wireguard", "WireGuard")
|
||||
end
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("hysteria", "Hysteria")
|
||||
end
|
||||
o:value("vless", "VLESS")
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("tuic", "TUIC")
|
||||
end
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("hysteria2", "Hysteria2")
|
||||
end
|
||||
o:value("anytls", "AnyTLS")
|
||||
o:value("ssh", "SSH")
|
||||
if singbox_tags:find("with_naive_outbound") then
|
||||
o:value("naive", "NaïveProxy")
|
||||
end
|
||||
o:value("_urltest", translate("URLTest"))
|
||||
o:value("_shunt", translate("Shunt"))
|
||||
o:value("_iface", translate("Custom Interface"))
|
||||
function o.custom_cfgvalue(self, section)
|
||||
if arg_select_proto ~= "" then
|
||||
return arg_select_proto
|
||||
else
|
||||
return m:get(section, self.option:sub(1 + #option_prefix))
|
||||
end
|
||||
end
|
||||
|
||||
local load_urltest_options = s.val["protocol"] == "_urltest" or arg_select_proto == "_urltest"
|
||||
local load_shunt_options = s.val["protocol"] == "_shunt" or arg_select_proto == "_shunt"
|
||||
local load_iface_options = s.val["protocol"] == "_iface" or arg_select_proto == "_iface"
|
||||
local load_normal_options = true
|
||||
if load_urltest_options or load_shunt_options or load_iface_options then
|
||||
load_normal_options = nil
|
||||
end
|
||||
if not arg_select_proto:find("_") then
|
||||
load_normal_options = true
|
||||
end
|
||||
|
||||
local netdev_list = api.get_network_devices()
|
||||
local node_list = api.get_node_list()
|
||||
|
||||
if load_urltest_options then -- [[ URLTest Start ]]
|
||||
o = s:option(ListValue, _n("node_add_mode"), translate("Node Addition Method"))
|
||||
o:depends({ [_n("protocol")] = "_urltest" })
|
||||
o.default = "manual"
|
||||
o:value("manual", translate("Manual"))
|
||||
o:value("batch", translate("Batch"))
|
||||
|
||||
o = s:option(MultiValue, _n("urltest_node"), translate("URLTest node list"), translate("List of nodes to test, <a target='_blank' href='https://sing-box.sagernet.org/configuration/outbound/urltest'>document</a>"))
|
||||
o:depends({ [_n("node_add_mode")] = "manual" })
|
||||
o.widget = "checkbox"
|
||||
o.template = m:template_path("/cbi/nodes_multivalue")
|
||||
o.group = {}
|
||||
for k1, v1 in pairs(node_list) do
|
||||
if k1 == "socks_list" or k1 == "normal_list" then
|
||||
for i, v in ipairs(v1) do
|
||||
o:value(v.id, v.remark)
|
||||
o.group[#o.group+1] = v.group or ""
|
||||
end
|
||||
end
|
||||
end
|
||||
-- 读取旧 DynamicList
|
||||
function o.custom_cfgvalue(self, section)
|
||||
return table.concat(m:get(section, "urltest_node") or {}, " ")
|
||||
end
|
||||
-- 写入保持 DynamicList
|
||||
function o.custom_write(self, section, value)
|
||||
local old = m:get(section, "urltest_node") or {}
|
||||
local new, set = {}, {}
|
||||
for v in value:gmatch("%S+") do
|
||||
new[#new + 1] = v
|
||||
set[v] = 1
|
||||
end
|
||||
for _, v in ipairs(old) do
|
||||
if not set[v] then
|
||||
m:set(section, "urltest_node", new)
|
||||
return
|
||||
end
|
||||
set[v] = nil
|
||||
end
|
||||
for _ in pairs(set) do
|
||||
m:set(section, "urltest_node", new)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(MultiValue, _n("node_group"), translate("Select Group"))
|
||||
o:depends({ [_n("node_add_mode")] = "batch" })
|
||||
o.widget = "checkbox"
|
||||
o:value("default", translate("default"))
|
||||
for k, v in pairs(groups) do
|
||||
o:value(api.UrlEncode(k), k)
|
||||
end
|
||||
|
||||
o = s:option(Value, _n("node_match_rule"), translate("Node Matching Rules"))
|
||||
o:depends({ [_n("node_add_mode")] = "batch" })
|
||||
local descrStr = "Example: <code>^A && B && !C && D$</code><br>"
|
||||
descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
|
||||
descrStr = descrStr .. "Conditions are joined by <code>&&</code> (AND), and their order does not affect the result.<br>"
|
||||
descrStr = descrStr .. "Multiple groups can be separated by <code>||</code> (OR), matching succeeds if any group matches.<br>"
|
||||
descrStr = descrStr .. "Example: <code>A && B || C && D</code> means (A AND B) OR (C AND D)."
|
||||
o.description = translate(descrStr)
|
||||
|
||||
o = s:option(Value, _n("urltest_url"), translate("Probe URL"))
|
||||
o:depends({ [_n("protocol")] = "_urltest" })
|
||||
o:value("https://cp.cloudflare.com/", "Cloudflare")
|
||||
o:value("https://www.gstatic.com/generate_204", "Gstatic")
|
||||
o:value("https://www.google.com/generate_204", "Google")
|
||||
o:value("https://www.youtube.com/generate_204", "YouTube")
|
||||
o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
|
||||
o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
|
||||
o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
|
||||
o.default = o.keylist[3]
|
||||
o.description = translate("The URL used to detect the connection status.")
|
||||
|
||||
o = s:option(Value, _n("urltest_interval"), translate("Test interval"))
|
||||
o:depends({ [_n("protocol")] = "_urltest" })
|
||||
o.default = "3m"
|
||||
o.placeholder = "3m"
|
||||
o.description = translate("The interval between initiating probes.") .. "<br>" ..
|
||||
translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are <code>s</code>, <code>m</code>, <code>h</code>, which correspond to seconds, minutes, and hours, respectively.") .. "<br>" ..
|
||||
translate("When the unit is not filled in, it defaults to seconds.") .. "<br>" ..
|
||||
translate("Test interval must be less or equal than idle timeout.")
|
||||
|
||||
o = s:option(Value, _n("urltest_tolerance"), translate("Test tolerance"), translate("The test tolerance in milliseconds."))
|
||||
o:depends({ [_n("protocol")] = "_urltest" })
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "50"
|
||||
o.default = "50"
|
||||
|
||||
o = s:option(Value, _n("urltest_idle_timeout"), translate("Idle timeout"))
|
||||
o:depends({ [_n("protocol")] = "_urltest" })
|
||||
o.placeholder = "30m"
|
||||
o.default = "30m"
|
||||
o.description = translate("The idle timeout.") .. "<br>" ..
|
||||
translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are <code>s</code>, <code>m</code>, <code>h</code>, which correspond to seconds, minutes, and hours, respectively.") .. "<br>" ..
|
||||
translate("When the unit is not filled in, it defaults to seconds.")
|
||||
|
||||
o = s:option(Flag, _n("urltest_interrupt_exist_connections"), translate("Interrupt existing connections"))
|
||||
o:depends({ [_n("protocol")] = "_urltest" })
|
||||
o.default = "0"
|
||||
o.description = translate("Interrupt existing connections when the selected outbound has changed.")
|
||||
end -- [[ URLTest End ]]
|
||||
|
||||
if load_iface_options then -- [[ 自定义接口 Start ]]
|
||||
o = s:option(Value, _n("iface"), translate("Interface"))
|
||||
o:depends({ [_n("protocol")] = "_iface" })
|
||||
for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if load_normal_options then
|
||||
|
||||
o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
|
||||
|
||||
o = s:option(Value, _n("port"), translate("Port"))
|
||||
o.datatype = "port"
|
||||
|
||||
o = s:option(Value, _n("uuid"), translate("ID"))
|
||||
o.password = true
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
|
||||
o = s:option(Value, _n("username"), translate("Username"))
|
||||
o:depends({ [_n("protocol")] = "http" })
|
||||
o:depends({ [_n("protocol")] = "socks" })
|
||||
o:depends({ [_n("protocol")] = "ssh" })
|
||||
o:depends({ [_n("protocol")] = "naive" })
|
||||
|
||||
o = s:option(Value, _n("password"), translate("Password"))
|
||||
o.password = true
|
||||
o:depends({ [_n("protocol")] = "http" })
|
||||
o:depends({ [_n("protocol")] = "socks" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
o:depends({ [_n("protocol")] = "anytls" })
|
||||
o:depends({ [_n("protocol")] = "ssh" })
|
||||
o:depends({ [_n("protocol")] = "naive" })
|
||||
|
||||
o = s:option(ListValue, _n("security"), translate("Encrypt Method"))
|
||||
for a, t in ipairs(security_list) do o:value(t) end
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
|
||||
o = s:option(ListValue, _n("ss_method"), translate("Encrypt Method"))
|
||||
o.rewrite_option = "method"
|
||||
for a, t in ipairs(ss_method_new_list) do o:value(t) end
|
||||
for a, t in ipairs(ss_method_old_list) do o:value(t) end
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
|
||||
o = s:option(Flag, _n("uot"), translate("UDP over TCP"))
|
||||
o:depends({ [_n("protocol")] = "socks" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "naive" })
|
||||
|
||||
o = s:option(Value, _n("alter_id"), "Alter ID")
|
||||
o.datatype = "uinteger"
|
||||
o.default = "0"
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
|
||||
o = s:option(Flag, _n("global_padding"), "global_padding", translate("Protocol parameter. Will waste traffic randomly if enabled."))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
|
||||
o = s:option(Flag, _n("authenticated_length"), "authenticated_length", translate("Protocol parameter. Enable length block encryption."))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
|
||||
o = s:option(ListValue, _n("flow"), translate("flow"))
|
||||
o.default = ""
|
||||
o:value("", translate("Disable"))
|
||||
o:value("xtls-rprx-vision")
|
||||
o:depends({ [_n("protocol")] = "vless", [_n("tls")] = true })
|
||||
|
||||
if singbox_tags:find("with_quic") then
|
||||
o = s:option(Value, _n("hysteria_hop"), translate("Port hopping range"))
|
||||
o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Value, _n("hysteria_hop_interval"), translate("Hop Interval(second)"), translate("Example:") .. "30 (≥5)")
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "30"
|
||||
o.default = "30"
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Value, _n("hysteria_obfs"), translate("Obfs Password"))
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(ListValue, _n("hysteria_auth_type"), translate("Auth Type"))
|
||||
o:value("disable", translate("Disable"))
|
||||
o:value("string", translate("STRING"))
|
||||
o:value("base64", translate("BASE64"))
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Value, _n("hysteria_auth_password"), translate("Auth Password"))
|
||||
o.password = true
|
||||
o:depends({ [_n("protocol")] = "hysteria", [_n("hysteria_auth_type")] = "string"})
|
||||
o:depends({ [_n("protocol")] = "hysteria", [_n("hysteria_auth_type")] = "base64"})
|
||||
|
||||
o = s:option(Value, _n("hysteria_up_mbps"), translate("Max upload Mbps"))
|
||||
o.default = "10"
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Value, _n("hysteria_down_mbps"), translate("Max download Mbps"))
|
||||
o.default = "50"
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Value, _n("hysteria_recv_window_conn"), translate("QUIC stream receive window"))
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Value, _n("hysteria_recv_window"), translate("QUIC connection receive window"))
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Flag, _n("hysteria_disable_mtu_discovery"), translate("Disable MTU detection"))
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
end
|
||||
|
||||
if singbox_tags:find("with_quic") then
|
||||
o = s:option(ListValue, _n("tuic_congestion_control"), translate("Congestion control algorithm"))
|
||||
o.default = "cubic"
|
||||
o:value("bbr", translate("BBR"))
|
||||
o:value("cubic", translate("CUBIC"))
|
||||
o:value("new_reno", translate("New Reno"))
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
|
||||
o = s:option(ListValue, _n("tuic_udp_relay_mode"), translate("UDP relay mode"))
|
||||
o.default = "native"
|
||||
o:value("native", translate("native"))
|
||||
o:value("quic", translate("QUIC"))
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
|
||||
--[[
|
||||
o = s:option(Flag, _n("tuic_udp_over_stream"), translate("UDP over stream"))
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
]]--
|
||||
|
||||
o = s:option(Flag, _n("tuic_zero_rtt_handshake"), translate("Enable 0-RTT QUIC handshake"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
|
||||
o = s:option(Value, _n("tuic_heartbeat"), translate("Heartbeat interval(second)"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = "3"
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
|
||||
o = s:option(ListValue, _n("tuic_alpn"), translate("QUIC TLS ALPN"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("h3")
|
||||
o:value("h2")
|
||||
o:value("h3,h2")
|
||||
o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
o:value("spdy/3.1")
|
||||
o:value("h3,spdy/3.1")
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
end
|
||||
|
||||
if singbox_tags:find("with_quic") then
|
||||
o = s:option(Value, _n("hysteria2_hop"), translate("Port hopping range"))
|
||||
o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
|
||||
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_hop_interval"), translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
|
||||
o.datatype = "or(uinteger,portrange)"
|
||||
o.placeholder = "30"
|
||||
o.default = "30"
|
||||
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
|
||||
|
||||
o = s:option(Flag, _n("hysteria2_realms"), translate("Realms"))
|
||||
o.default = "0"
|
||||
if api.compare_versions(singbox_version, ">=", "1.14.0") then
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
else
|
||||
o:depends({ [_n("protocol")] = "__hide"})
|
||||
end
|
||||
|
||||
o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o:depends({ [_n("hysteria2_realms")] = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o:depends({ [_n("hysteria2_realms")] = "1" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
|
||||
o.password = true
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
|
||||
o = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
o:value("gecko")
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password"))
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "salamander" })
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_obfs_MinPacketSize"), translate("Gecko Packet Size (min)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "512"
|
||||
o.default = "512"
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_obfs_MaxPacketSize"), translate("Gecko Packet Size (max)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "1200"
|
||||
o.default = "1200"
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_up_mbps"), translate("Max upload Mbps"))
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_down_mbps"), translate("Max download Mbps"))
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_idle_timeout"), translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
|
||||
o.datatype = "range(4,120)"
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
|
||||
o = s:option(Value, _n("hysteria2_keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
|
||||
o.datatype = "range(2,60)"
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
|
||||
o = s:option(Flag, _n("hysteria2_disable_mtu_discovery"), translate("Disable MTU detection"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
end
|
||||
|
||||
-- [[ SSH config start ]] --
|
||||
o = s:option(TextValue, _n("ssh_priv_key"), translate("Private Key"))
|
||||
o.rows = 5
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("protocol")] = "ssh" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
|
||||
return value
|
||||
end
|
||||
|
||||
o = s:option(Value, _n("ssh_priv_key_pp"), translate("Private Key Passphrase"))
|
||||
o.password = true
|
||||
o:depends({ [_n("protocol")] = "ssh" })
|
||||
|
||||
o = s:option(DynamicList, _n("ssh_host_key"), translate("Host Key"), translate("Accept any if empty."))
|
||||
o:depends({ [_n("protocol")] = "ssh" })
|
||||
|
||||
o = s:option(DynamicList, _n("ssh_host_key_algo"), translate("Host Key Algorithms"))
|
||||
o:depends({ [_n("protocol")] = "ssh" })
|
||||
|
||||
o = s:option(Value, _n("ssh_client_version"), translate("Client Version"), translate("Random version will be used if empty."))
|
||||
o:depends({ [_n("protocol")] = "ssh" })
|
||||
-- [[ SSH config end ]] --
|
||||
|
||||
-- [[ naive start ]] --
|
||||
o = s:option(Value, _n("naive_insecure_concurrency"), translate("Concurrent Tunnels"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "0"
|
||||
o.default = "0"
|
||||
o:depends({ [_n("protocol")] = "naive" })
|
||||
|
||||
o = s:option(Flag, _n("naive_quic"), translate("QUIC"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("protocol")] = "naive" })
|
||||
|
||||
o = s:option(ListValue, _n("naive_congestion_control"), translate("Congestion control algorithm"))
|
||||
o.default = "bbr"
|
||||
o:value("bbr", translate("BBR"))
|
||||
o:value("bbr2", translate("BBRv2"))
|
||||
o:value("cubic", translate("CUBIC"))
|
||||
o:value("reno", translate("New Reno"))
|
||||
o:depends({ [_n("naive_quic")] = "1" })
|
||||
-- [[ naive end ]] --
|
||||
|
||||
o = s:option(Flag, _n("tls"), translate("TLS"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o:depends({ [_n("protocol")] = "http" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "anytls" })
|
||||
|
||||
o = s:option(ListValue, _n("alpn"), translate("ALPN"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("h3")
|
||||
o:value("h2")
|
||||
o:value("h3,h2")
|
||||
o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
o:depends({ [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Flag, _n("tls_disable_sni"), translate("Disable SNI"), translate("Do not send server name in ClientHello."))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "hysteria"})
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Value, _n("tls_serverName"), "SNI " .. translate("Domain"))
|
||||
o:depends({ [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "hysteria"})
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
o:depends({ [_n("protocol")] = "naive" })
|
||||
|
||||
o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "hysteria"})
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Flag, _n("tls_certificate"), translate("TLS Certificate (PEM)"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "hysteria"})
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
o:depends({ [_n("protocol")] = "naive" })
|
||||
|
||||
o = s:option(TextValue, _n("tls_certificate_pem"), " ", translate("Full certificate (chain), PEM format."))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("tls_certificate")] = true })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
|
||||
return value
|
||||
end
|
||||
|
||||
o = s:option(Value, _n("cipherSuites"), translate("Cipher Suites"), '<a href="https://go.dev/src/crypto/tls/cipher_suites.go#L44" target="_blank">***</a>' .. " " .. translate("Configures the list of supported cipher suites, separated by :"))
|
||||
o:depends({ [_n("tls")] = true })
|
||||
|
||||
o = s:option(Flag, _n("ech"), translate("ECH"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("tls")] = true, [_n("flow")] = "", [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
|
||||
o:depends({ [_n("protocol")] = "naive" })
|
||||
|
||||
o = s:option(TextValue, _n("ech_config"), translate("ECH Config"))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("ech")] = true })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
|
||||
return value
|
||||
end
|
||||
|
||||
o = s:option(Value, _n("ech_query_server_name"), translate("ECH Query Domain"), translate("Overrides the domain name used for ECH HTTPS record queries."))
|
||||
o:depends({ [_n("ech")] = true })
|
||||
|
||||
if singbox_tags:find("with_utls") then
|
||||
o = s:option(Flag, _n("utls"), translate("uTLS"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("tls")] = true })
|
||||
|
||||
o = s:option(ListValue, _n("fingerprint"), translate("Finger Print"))
|
||||
o:value("chrome")
|
||||
o:value("firefox")
|
||||
o:value("edge")
|
||||
o:value("safari")
|
||||
o:value("360")
|
||||
o:value("qq")
|
||||
o:value("ios")
|
||||
o:value("android")
|
||||
o:value("random")
|
||||
o:value("randomized")
|
||||
o.default = "chrome"
|
||||
o:depends({ [_n("utls")] = true })
|
||||
|
||||
-- [[ REALITY部分 ]] --
|
||||
o = s:option(Flag, _n("reality"), translate("REALITY"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("protocol")] = "vless", [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "vmess", [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks", [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "trojan", [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "anytls", [_n("tls")] = true })
|
||||
|
||||
o = s:option(Value, _n("reality_publicKey"), translate("Public Key"))
|
||||
o:depends({ [_n("reality")] = true })
|
||||
|
||||
o = s:option(Value, _n("reality_shortId"), translate("Short Id"))
|
||||
o:depends({ [_n("reality")] = true })
|
||||
end
|
||||
|
||||
o = s:option(ListValue, _n("transport"), translate("Transport"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("http", "HTTP")
|
||||
o:value("ws", "WebSocket")
|
||||
o:value("httpupgrade", "HTTPUpgrade")
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("quic", "QUIC")
|
||||
end
|
||||
if singbox_tags:find("with_grpc") then
|
||||
o:value("grpc", "gRPC")
|
||||
else o:value("grpc", "gRPC-lite")
|
||||
end
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
|
||||
if singbox_tags:find("with_wireguard") then
|
||||
o = s:option(Value, _n("wireguard_public_key"), translate("Public Key"))
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(Value, _n("wireguard_secret_key"), translate("Private Key"))
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(Value, _n("wireguard_preSharedKey"), translate("Pre shared key"))
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(DynamicList, _n("wireguard_local_address"), translate("Local Address"))
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(Value, _n("wireguard_mtu"), translate("MTU"))
|
||||
o.default = "1420"
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(Value, _n("wireguard_reserved"), translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
end
|
||||
|
||||
-- [[ TCP部分(模拟) ]]--
|
||||
o = s:option(ListValue, _n("tcp_guise"), translate("Camouflage Type"))
|
||||
o:value("none", "none")
|
||||
o:value("http", "http")
|
||||
o:depends({ [_n("transport")] = "tcp" })
|
||||
|
||||
o = s:option(DynamicList, _n("tcp_guise_http_host"), translate("HTTP Host"))
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
-- [[ HTTP部分 ]]--
|
||||
o = s:option(DynamicList, _n("http_host"), translate("HTTP Host"))
|
||||
o:depends({ [_n("transport")] = "http" })
|
||||
|
||||
o = s:option(Value, _n("http_path"), translate("HTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "http" })
|
||||
|
||||
o = s:option(Flag, _n("http_h2_health_check"), translate("Health check"))
|
||||
o:depends({ [_n("tls")] = true, [_n("transport")] = "http" })
|
||||
|
||||
o = s:option(Value, _n("http_h2_read_idle_timeout"), translate("Idle timeout"))
|
||||
o.default = "15"
|
||||
o:depends({ [_n("http_h2_health_check")] = true })
|
||||
|
||||
o = s:option(Value, _n("http_h2_health_check_timeout"), translate("Health check timeout"))
|
||||
o.default = "15"
|
||||
o:depends({ [_n("http_h2_health_check")] = true })
|
||||
|
||||
-- [[ WebSocket部分 ]]--
|
||||
o = s:option(Value, _n("ws_host"), translate("WebSocket Host"))
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Flag, _n("ws_enableEarlyData"), translate("Enable early data"))
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Value, _n("ws_maxEarlyData"), translate("Early data length"))
|
||||
o.default = "1024"
|
||||
o:depends({ [_n("ws_enableEarlyData")] = true })
|
||||
|
||||
o = s:option(Value, _n("ws_earlyDataHeaderName"), translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol"))
|
||||
o:depends({ [_n("ws_enableEarlyData")] = true })
|
||||
|
||||
-- [[ HTTPUpgrade部分 ]]--
|
||||
o = s:option(Value, _n("httpupgrade_host"), translate("HTTPUpgrade Host"))
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
o = s:option(Value, _n("httpupgrade_path"), translate("HTTPUpgrade Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
-- [[ gRPC部分 ]]--
|
||||
o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
|
||||
o:depends({ [_n("transport")] = "grpc" })
|
||||
|
||||
o = s:option(Flag, _n("grpc_health_check"), translate("Health check"))
|
||||
o:depends({ [_n("transport")] = "grpc" })
|
||||
|
||||
o = s:option(Value, _n("grpc_idle_timeout"), translate("Idle timeout"))
|
||||
o.default = "15"
|
||||
o:depends({ [_n("grpc_health_check")] = true })
|
||||
|
||||
o = s:option(Value, _n("grpc_health_check_timeout"), translate("Health check timeout"))
|
||||
o.default = "15"
|
||||
o:depends({ [_n("grpc_health_check")] = true })
|
||||
|
||||
o = s:option(Flag, _n("grpc_permit_without_stream"), translate("Permit without stream"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("grpc_health_check")] = true })
|
||||
|
||||
-- [[ User-Agent ]]--
|
||||
o = s:option(Value, _n("user_agent"), translate("User-Agent"))
|
||||
o.default = ""
|
||||
o:value("", translate("default"))
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", "chrome")
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", "firefox")
|
||||
o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", "safari")
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", "edge")
|
||||
o:value("Go-http-client/1.1", "golang")
|
||||
o:value("curl/7.68.0", "curl")
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
o:depends({ [_n("transport")] = "http" })
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
o:depends({ [_n("protocol")] = "naive" })
|
||||
|
||||
-- [[ Mux ]]--
|
||||
o = s:option(Flag, _n("mux"), translate("Mux"))
|
||||
o.rmempty = false
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless", [_n("flow")] = "" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks", [_n("uot")] = "" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
|
||||
o = s:option(ListValue, _n("mux_type"), translate("Mux"))
|
||||
o:value("smux")
|
||||
o:value("yamux")
|
||||
o:value("h2mux")
|
||||
o:depends({ [_n("mux")] = true })
|
||||
|
||||
o = s:option(Value, _n("mux_concurrency"), translate("Mux concurrency"))
|
||||
o.default = 4
|
||||
o:depends({ [_n("mux")] = true, [_n("tcpbrutal")] = false })
|
||||
|
||||
o = s:option(Flag, _n("mux_padding"), translate("Padding"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("mux")] = true })
|
||||
|
||||
-- [[ TCP Brutal ]]--
|
||||
o = s:option(Flag, _n("tcpbrutal"), translate("TCP Brutal"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("mux")] = true })
|
||||
|
||||
o = s:option(Value, _n("tcpbrutal_up_mbps"), translate("Max upload Mbps"))
|
||||
o.default = "10"
|
||||
o:depends({ [_n("tcpbrutal")] = true })
|
||||
|
||||
o = s:option(Value, _n("tcpbrutal_down_mbps"), translate("Max download Mbps"))
|
||||
o.default = "50"
|
||||
o:depends({ [_n("tcpbrutal")] = true })
|
||||
|
||||
o = s:option(Flag, _n("shadowtls"), "ShadowTLS")
|
||||
o.default = 0
|
||||
o:depends({ [_n("protocol")] = "vmess", [_n("tls")] = false })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks", [_n("tls")] = false })
|
||||
|
||||
o = s:option(ListValue, _n("shadowtls_version"), "ShadowTLS " .. translate("Version"))
|
||||
o.default = "1"
|
||||
o:value("1", "ShadowTLS v1")
|
||||
o:value("2", "ShadowTLS v2")
|
||||
o:value("3", "ShadowTLS v3")
|
||||
o:depends({ [_n("shadowtls")] = true })
|
||||
|
||||
o = s:option(Value, _n("shadowtls_password"), "ShadowTLS " .. translate("Password"))
|
||||
o.password = true
|
||||
o:depends({ [_n("shadowtls")] = true, [_n("shadowtls_version")] = "2" })
|
||||
o:depends({ [_n("shadowtls")] = true, [_n("shadowtls_version")] = "3" })
|
||||
|
||||
o = s:option(Value, _n("shadowtls_serverName"), "ShadowTLS " .. translate("Domain"))
|
||||
o:depends({ [_n("shadowtls")] = true })
|
||||
|
||||
if singbox_tags:find("with_utls") then
|
||||
o = s:option(Flag, _n("shadowtls_utls"), "ShadowTLS " .. translate("uTLS"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("shadowtls")] = true })
|
||||
|
||||
o = s:option(ListValue, _n("shadowtls_fingerprint"), "ShadowTLS " .. translate("Finger Print"))
|
||||
o:value("chrome")
|
||||
o:value("firefox")
|
||||
o:value("edge")
|
||||
o:value("safari")
|
||||
-- o:value("360")
|
||||
o:value("qq")
|
||||
o:value("ios")
|
||||
-- o:value("android")
|
||||
o:value("random")
|
||||
-- o:value("randomized")
|
||||
o.default = "chrome"
|
||||
o:depends({ [_n("shadowtls")] = true, [_n("shadowtls_utls")] = true })
|
||||
end
|
||||
|
||||
-- [[ SIP003 plugin ]]--
|
||||
o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
|
||||
o = s:option(ListValue, _n("plugin"), "SIP003 " .. translate("plugin"))
|
||||
o.default = "obfs-local"
|
||||
o:depends({ [_n("plugin_enabled")] = true })
|
||||
o:value("obfs-local")
|
||||
o:value("v2ray-plugin")
|
||||
|
||||
o = s:option(Value, _n("plugin_opts"), translate("opts"))
|
||||
o:depends({ [_n("plugin_enabled")] = true })
|
||||
|
||||
o = s:option(ListValue, _n("domain_resolver"), translate("Domain DNS Resolve"))
|
||||
o.description = translate("If the node address is a domain name, this DNS will be used for resolution.") .. "<br>" .. string.format('<font color="red">%s</font>',
|
||||
translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead."))
|
||||
o:value("", translate("Auto"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
o:value("https", "DoH")
|
||||
|
||||
o = s:option(Value, _n("domain_resolver_dns"), "DNS")
|
||||
o.datatype = "or(ipaddr,ipaddrport)"
|
||||
o:value("114.114.114.114")
|
||||
o:value("223.5.5.5:53")
|
||||
o.default = o.keylist[1]
|
||||
o:depends({ [_n("domain_resolver")] = "tcp" })
|
||||
o:depends({ [_n("domain_resolver")] = "udp" })
|
||||
|
||||
o = s:option(Value, _n("domain_resolver_dns_https"), "DNS")
|
||||
o:value("https://120.53.53.53/dns-query", "DNSPod")
|
||||
o:value("https://223.5.5.5/dns-query", "AliDNS")
|
||||
o.default = o.keylist[1]
|
||||
o:depends({ [_n("domain_resolver")] = "https" })
|
||||
|
||||
o = s:option(ListValue, _n("domain_strategy"), translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect."))
|
||||
o.default = ""
|
||||
o:value("", translate("Auto"))
|
||||
o:value("prefer_ipv4", translate("Prefer IPv4"))
|
||||
o:value("prefer_ipv6", translate("Prefer IPv6"))
|
||||
o:value("ipv4_only", translate("IPv4 Only"))
|
||||
o:value("ipv6_only", translate("IPv6 Only"))
|
||||
|
||||
|
||||
local protocols = s.fields[_n("protocol")].keylist
|
||||
if #protocols > 0 then
|
||||
for i, v in ipairs(protocols) do
|
||||
if not v:find("^_") then
|
||||
local depends_condition = { [_n("protocol")] = v }
|
||||
if v == "hysteria2" then
|
||||
depends_condition[_n("hysteria2_realms")] = false
|
||||
end
|
||||
s.fields[_n("address")]:depends(depends_condition)
|
||||
s.fields[_n("port")]:depends(depends_condition)
|
||||
s.fields[_n("domain_resolver")]:depends(depends_condition)
|
||||
s.fields[_n("domain_strategy")]:depends(depends_condition)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- [[ Normal single node End ]]
|
||||
|
||||
if not load_shunt_options then
|
||||
o = s:option(ListValue, _n("chain_proxy"), translate("Chain Proxy"))
|
||||
o:value("", translate("Close(Not use)"))
|
||||
if not (load_iface_options or load_urltest_options) then
|
||||
-- Special node cannot be use pre-proxy.
|
||||
o:value("1", translate("Preproxy Node"))
|
||||
o:value("3", translate("Outbound Interface"))
|
||||
end
|
||||
o:value("2", translate("Landing Node"))
|
||||
|
||||
o1 = s:option(ListValue, _n("preproxy_node"), translate("Preproxy Node"), translate("Only support a layer of proxy."))
|
||||
o1:depends({ [_n("chain_proxy")] = "1", [_n("hysteria2_realms")] = false })
|
||||
o1.template = m:template_path("/cbi/nodes_listvalue")
|
||||
o1.group = {}
|
||||
|
||||
o3 = s:option(Value, _n("outbound_iface"), translate("Outbound Interface"))
|
||||
o3:depends({ [_n("chain_proxy")] = "3" })
|
||||
o3:value("", translate("All"))
|
||||
for _, d in ipairs(netdev_list) do
|
||||
o3:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o2 = s:option(ListValue, _n("to_node"), translate("Landing Node"), translate("Only support a layer of proxy."))
|
||||
o2:depends({ [_n("chain_proxy")] = "2", [_n("hysteria2_realms")] = false })
|
||||
o2.template = m:template_path("/cbi/nodes_listvalue")
|
||||
o2.group = {}
|
||||
|
||||
for k1, v1 in pairs(node_list) do
|
||||
if k1 ~= "shunt_list" and k1 ~= "iface_list" then
|
||||
for i, v in ipairs(v1) do
|
||||
if v.id ~= arg[1] then
|
||||
o1:value(v.id, v.remark)
|
||||
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
if k1 == "normal_list" then
|
||||
-- Landing Node not support use special node.
|
||||
o2:value(v.id, v.remark)
|
||||
o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
api.luci_types(arg[1], m, s, type_name, option_prefix)
|
||||
|
||||
if load_shunt_options then
|
||||
local current_node = m:get(arg[1]) or {}
|
||||
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
|
||||
setfenv(shunt_lua, getfenv(1))(m, s, {
|
||||
node_id = arg[1],
|
||||
node = current_node,
|
||||
node_list = node_list,
|
||||
})
|
||||
end
|
||||
@@ -1,76 +0,0 @@
|
||||
local m, s = ...
|
||||
|
||||
if not api.is_finded("sslocal") then
|
||||
return
|
||||
end
|
||||
|
||||
type_name = "SS-Rust"
|
||||
|
||||
-- [[ Shadowsocks Rust ]]
|
||||
|
||||
s.fields["type"]:value(type_name, "Shadowsocks Rust")
|
||||
|
||||
if s.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local option_prefix = "ssrust_"
|
||||
|
||||
local function _n(name)
|
||||
return option_prefix .. name
|
||||
end
|
||||
|
||||
local ssrust_encrypt_method_list = {
|
||||
"none", "plain",
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
|
||||
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
o = s:option(ListValue, _n("del_protocol")) --始终隐藏,用于删除 protocol
|
||||
o:depends({ [_n("__hide")] = "1" })
|
||||
o.rewrite_option = "protocol"
|
||||
|
||||
o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
|
||||
|
||||
o = s:option(Value, _n("port"), translate("Port"))
|
||||
o.datatype = "port"
|
||||
|
||||
o = s:option(Value, _n("password"), translate("Password"))
|
||||
o.password = true
|
||||
|
||||
o = s:option(Value, _n("method"), translate("Encrypt Method"))
|
||||
for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end
|
||||
|
||||
o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
o.default = 0
|
||||
|
||||
o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
|
||||
o.default = 0
|
||||
|
||||
o = s:option(Value, _n("plugin"), "SIP003 " .. translate("plugin"), translate("Supports custom SIP003 plugins, Make sure the plugin is installed."))
|
||||
o.default = "none"
|
||||
o:value("none", translate("none"))
|
||||
if api.is_finded("xray-plugin") then o:value("xray-plugin") end
|
||||
if api.is_finded("v2ray-plugin") then o:value("v2ray-plugin") end
|
||||
if api.is_finded("obfs-local") then o:value("obfs-local") end
|
||||
if api.is_finded("shadow-tls") then o:value("shadow-tls") end
|
||||
o:depends({ [_n("plugin_enabled")] = true })
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" and value ~= "none" then
|
||||
if not api.is_finded(value) then
|
||||
return nil, value .. ": " .. translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
o = s:option(Value, _n("plugin_opts"), translate("opts"))
|
||||
o:depends({ [_n("plugin_enabled")] = true })
|
||||
|
||||
api.luci_types(arg[1], m, s, type_name, option_prefix)
|
||||
@@ -17,7 +17,7 @@ m:foreach("user", function(s)
|
||||
user_list[#user_list + 1] = s
|
||||
end)
|
||||
|
||||
s = m:section(NamedSection, arg[1], "server", translate("Server Config"))
|
||||
local s = m:section(NamedSection, arg[1], "server", translate("Server Config"))
|
||||
s.addremove = false
|
||||
s.dynamic = false
|
||||
|
||||
@@ -39,8 +39,6 @@ for filename in api.fs.dir(types_dir) do
|
||||
table.insert(type_table, filename)
|
||||
end
|
||||
table.sort(type_table, function(a, b)
|
||||
if a == "socks.lua" then return true end
|
||||
if b == "socks.lua" then return false end
|
||||
return a < b
|
||||
end)
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
if not api.is_finded("microsocks") then
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ microsocks ]]
|
||||
local m, s1 = ...
|
||||
local type_name = "Socks"
|
||||
|
||||
s1.fields["type"]:value(type_name, "Socks")
|
||||
if not s1.fields["type"].default then
|
||||
s1.fields["type"].default = type_name
|
||||
end
|
||||
|
||||
if s1.val["type"] and s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local s = NamedSection(m, arg[1], "server")
|
||||
s.type_name = type_name
|
||||
s.option_prefix = "socks_"
|
||||
|
||||
o = s:option(Value, "port", translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
|
||||
o = s:option(Flag, "auth", translate("Auth"))
|
||||
|
||||
o = s:option(ListValue, "user", translate("User"))
|
||||
for i, v in ipairs(user_list) do
|
||||
o:value(v[".name"], v.username)
|
||||
end
|
||||
o:depends({ auth = true })
|
||||
|
||||
o = s:option(Flag, "log", translate("Log"))
|
||||
o.default = "1"
|
||||
|
||||
api.luci_types(s1, s)
|
||||
@@ -0,0 +1,522 @@
|
||||
local singbox_bin = api.finded_com("sing-box")
|
||||
|
||||
if not singbox_bin then
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ Sing-Box ]]
|
||||
local m, s1 = ...
|
||||
local type_name = "sing-box"
|
||||
|
||||
s1.fields["type"]:value(type_name, "Sing-Box")
|
||||
if not s1.fields["type"].default then
|
||||
s1.fields["type"].default = type_name
|
||||
end
|
||||
|
||||
if not s1.val["type"] then
|
||||
s1.val["type"] = type_name
|
||||
end
|
||||
|
||||
if s1.val["type"] and s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local s = NamedSection(m, arg[1], "server")
|
||||
s.type_name = type_name
|
||||
s.option_prefix = "singbox_"
|
||||
|
||||
local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'")
|
||||
|
||||
local ss_method_list = {
|
||||
"none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305",
|
||||
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
o = s:option(Flag, "custom", translate("Use Custom Config"))
|
||||
|
||||
o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ custom = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "protocol", translate("Protocol"))
|
||||
o:value("mixed", "Mixed")
|
||||
o:value("socks", "Socks")
|
||||
o:value("http", "HTTP")
|
||||
o:value("shadowsocks", "Shadowsocks")
|
||||
o:value("vmess", "Vmess")
|
||||
o:value("vless", "VLESS")
|
||||
o:value("trojan", "Trojan")
|
||||
o:value("naive", "Naive")
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("hysteria", "Hysteria")
|
||||
end
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("tuic", "TUIC")
|
||||
end
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("hysteria2", "Hysteria2")
|
||||
end
|
||||
o:value("anytls", "AnyTLS")
|
||||
if singbox_tags:find("with_wireguard") then
|
||||
o:value("wireguard", "WireGuard")
|
||||
end
|
||||
o:value("direct", "Direct")
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Value, "port", translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(DynamicList, "users", translate("User"))
|
||||
for i, v in ipairs(user_list) do
|
||||
o:value(v[".name"], v.username)
|
||||
end
|
||||
o:depends({ protocol = "mixed" })
|
||||
o:depends({ protocol = "socks" })
|
||||
o:depends({ protocol = "http" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
o:depends({ protocol = "naive" })
|
||||
o:depends({ protocol = "hysteria" })
|
||||
o:depends({ protocol = "tuic" })
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
o:depends({ protocol = "anytls" })
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
if singbox_tags:find("with_quic") then
|
||||
o = s:option(Value, "hysteria_obfs", translate("Obfs Password"))
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps"))
|
||||
o.default = "100"
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps"))
|
||||
o.default = "100"
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window"))
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Value, "hysteria_recv_window_client", translate("QUIC connection receive window"))
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Value, "hysteria_max_conn_client", translate("QUIC concurrent bidirectional streams"))
|
||||
o.default = "1024"
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
o = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection"))
|
||||
o:depends({ protocol = "hysteria" })
|
||||
end
|
||||
|
||||
if singbox_tags:find("with_quic") then
|
||||
o = s:option(ListValue, "tuic_congestion_control", translate("Congestion control algorithm"))
|
||||
o.default = "cubic"
|
||||
o:value("bbr", translate("BBR"))
|
||||
o:value("cubic", translate("CUBIC"))
|
||||
o:value("new_reno", translate("New Reno"))
|
||||
o:depends({ protocol = "tuic" })
|
||||
|
||||
o = s:option(Flag, "tuic_zero_rtt_handshake", translate("Enable 0-RTT QUIC handshake"))
|
||||
o.default = 0
|
||||
o:depends({ protocol = "tuic" })
|
||||
|
||||
o = s:option(Value, "tuic_heartbeat", translate("Heartbeat interval(second)"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = "3"
|
||||
o:depends({ protocol = "tuic" })
|
||||
|
||||
o = s:option(ListValue, "tuic_alpn", translate("QUIC TLS ALPN"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("h3")
|
||||
o:value("h2")
|
||||
o:value("h3,h2")
|
||||
o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
o:value("spdy/3.1")
|
||||
o:value("h3,spdy/3.1")
|
||||
o:depends({ protocol = "tuic" })
|
||||
end
|
||||
|
||||
if singbox_tags:find("with_quic") then
|
||||
o = s:option(Flag, "hysteria2_realms", translate("Realms"))
|
||||
o.default = "0"
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
|
||||
o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
|
||||
o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
o:value("gecko")
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password"))
|
||||
o:depends({ hysteria2_obfs_type = "salamander" })
|
||||
o:depends({ hysteria2_obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "512"
|
||||
o.default = "512"
|
||||
o:depends({ hysteria2_obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "1200"
|
||||
o.default = "1200"
|
||||
o:depends({ hysteria2_obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Flag, "hysteria2_ignore_client_bandwidth", translate("Client BBR Flow Control"), translate("Commands the client to use the BBR flow control algorithm"))
|
||||
o.default = 0
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps"))
|
||||
o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false })
|
||||
|
||||
o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps"))
|
||||
o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false })
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "d_protocol", translate("Destination protocol"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
o:value("tcp,udp", "TCP,UDP")
|
||||
o:depends({ protocol = "direct" })
|
||||
|
||||
o = s:option(Value, "d_address", translate("Destination address"))
|
||||
o:depends({ protocol = "direct" })
|
||||
|
||||
o = s:option(Value, "d_port", translate("Destination port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ protocol = "direct" })
|
||||
|
||||
o = s:option(Value, "decryption", translate("Encrypt Method"))
|
||||
o.default = "none"
|
||||
o:depends({ protocol = "vless" })
|
||||
|
||||
o = s:option(ListValue, "ss_method", translate("Encrypt Method"))
|
||||
o.rewrite_option = "method"
|
||||
for a, t in ipairs(ss_method_list) do o:value(t) end
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
|
||||
o = s:option(Value, "ss_password", translate("Password"))
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
|
||||
o = s:option(ListValue, "flow", translate("flow"))
|
||||
o.default = ""
|
||||
o:value("", translate("Disable"))
|
||||
o:value("xtls-rprx-vision")
|
||||
o:depends({ protocol = "vless" , tls = true })
|
||||
|
||||
o = s:option(Flag, "tls", translate("TLS"))
|
||||
o.default = 0
|
||||
o.validate = function(self, value, t)
|
||||
if value then
|
||||
local reality = s.fields["reality"] and s.fields["reality"]:formvalue(t) or nil
|
||||
if reality and reality == "1" then return value end
|
||||
if value == "1" then
|
||||
local ca = s.fields["tls_certificateFile"] and s.fields["tls_certificateFile"]:formvalue(t) or ""
|
||||
local key = s.fields["tls_keyFile"] and s.fields["tls_keyFile"]:formvalue(t) or ""
|
||||
if ca == "" or key == "" then
|
||||
return nil, translate("Public key and Private key path can not be empty!")
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
end
|
||||
o:depends({ protocol = "http" })
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
o:depends({ protocol = "anytls" })
|
||||
|
||||
-- https://github.com/SagerNet/sing-box/commit/d2a04c4e41e6cef0937331cb6d10211f431caaab
|
||||
if singbox_tags:find("with_utls") then
|
||||
-- [[ REALITY部分 ]] --
|
||||
o = s:option(Flag, "reality", translate("REALITY"))
|
||||
o.default = 0
|
||||
o:depends({ protocol = "http", tls = true })
|
||||
o:depends({ protocol = "vmess", tls = true })
|
||||
o:depends({ protocol = "vless", tls = true })
|
||||
o:depends({ protocol = "trojan", tls = true })
|
||||
o:depends({ protocol = "anytls", tls = true })
|
||||
|
||||
o = s:option(Value, "reality_private_key", translate("Private Key"))
|
||||
o:depends({ reality = true })
|
||||
|
||||
o = s:option(Value, "reality_shortId", translate("Short Id"))
|
||||
o:depends({ reality = true })
|
||||
|
||||
o = s:option(Value, "reality_handshake_server", translate("Handshake Server"))
|
||||
o.default = "google.com"
|
||||
o:depends({ reality = true })
|
||||
|
||||
o = s:option(Value, "reality_handshake_server_port", translate("Handshake Server Port"))
|
||||
o.datatype = "port"
|
||||
o.default = "443"
|
||||
o:depends({ reality = true })
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "alpn", translate("ALPN"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("h3")
|
||||
o:value("h2")
|
||||
o:value("h3,h2")
|
||||
o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
o:depends({ tls = true, reality = false })
|
||||
o:depends({ protocol = "hysteria" })
|
||||
|
||||
-- [[ TLS部分 ]] --
|
||||
|
||||
o = s:option(FileUpload, "tls_certificateFile", translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
|
||||
o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
|
||||
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
|
||||
o:depends({ tls = true, reality = false })
|
||||
o:depends({ protocol = "naive" })
|
||||
o:depends({ protocol = "hysteria" })
|
||||
o:depends({ protocol = "tuic" })
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
o = s:option(FileUpload, "tls_keyFile", translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
|
||||
o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
|
||||
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
|
||||
o:depends({ tls = true, reality = false })
|
||||
o:depends({ protocol = "naive" })
|
||||
o:depends({ protocol = "hysteria" })
|
||||
o:depends({ protocol = "tuic" })
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
o = s:option(Flag, "ech", translate("ECH"))
|
||||
o.default = "0"
|
||||
o:depends({ tls = true, flow = "", reality = false })
|
||||
o:depends({ protocol = "naive" })
|
||||
o:depends({ protocol = "hysteria" })
|
||||
o:depends({ protocol = "tuic" })
|
||||
o:depends({ protocol = "hysteria2", hysteria2_realms = false })
|
||||
|
||||
o = s:option(TextValue, "ech_key", translate("ECH Key"))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "off"
|
||||
o:depends({ ech = true })
|
||||
o.validate = function(self, value)
|
||||
value = value:gsub("^%s+", ""):gsub("%s+$","\n"):gsub("\r\n","\n"):gsub("[ \t]*\n[ \t]*", "\n")
|
||||
value = value:gsub("^%s*\n", "")
|
||||
if value:sub(-1) == "\n" then
|
||||
value = value:sub(1, -2)
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "transport", translate("Transport"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("http", "HTTP")
|
||||
o:value("ws", "WebSocket")
|
||||
o:value("httpupgrade", "HTTPUpgrade")
|
||||
o:value("quic", "QUIC")
|
||||
o:value("grpc", "gRPC")
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
|
||||
-- [[ HTTP部分 ]]--
|
||||
|
||||
o = s:option(DynamicList, "http_host", translate("HTTP Host"))
|
||||
o:depends({ transport = "http" })
|
||||
|
||||
o = s:option(Value, "http_path", translate("HTTP Path"))
|
||||
o:depends({ transport = "http" })
|
||||
|
||||
-- [[ WebSocket部分 ]]--
|
||||
|
||||
o = s:option(Value, "ws_host", translate("WebSocket Host"))
|
||||
o:depends({ transport = "ws" })
|
||||
|
||||
o = s:option(Value, "ws_path", translate("WebSocket Path"))
|
||||
o:depends({ transport = "ws" })
|
||||
|
||||
-- [[ HTTPUpgrade部分 ]]--
|
||||
|
||||
o = s:option(Value, "httpupgrade_host", translate("HTTPUpgrade Host"))
|
||||
o:depends({ transport = "httpupgrade" })
|
||||
|
||||
o = s:option(Value, "httpupgrade_path", translate("HTTPUpgrade Path"))
|
||||
o:depends({ transport = "httpupgrade" })
|
||||
|
||||
-- [[ gRPC部分 ]]--
|
||||
o = s:option(Value, "grpc_serviceName", "ServiceName")
|
||||
o:depends({ transport = "grpc" })
|
||||
|
||||
-- [[ Mux ]]--
|
||||
o = s:option(Flag, "mux", translate("Mux"))
|
||||
o.rmempty = false
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless", flow = "" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
|
||||
-- [[ TCP Brutal ]]--
|
||||
o = s:option(Flag, "tcpbrutal", translate("TCP Brutal"))
|
||||
o.default = 0
|
||||
o:depends({ mux = true })
|
||||
|
||||
o = s:option(Value, "tcpbrutal_up_mbps", translate("Max upload Mbps"))
|
||||
o.default = "10"
|
||||
o:depends({ tcpbrutal = true })
|
||||
|
||||
o = s:option(Value, "tcpbrutal_down_mbps", translate("Max download Mbps"))
|
||||
o.default = "50"
|
||||
o:depends({ tcpbrutal = true })
|
||||
|
||||
if singbox_tags:find("with_wireguard") then
|
||||
o = s:option(Flag, "wireguard_system_interface", translate("System interface"))
|
||||
o.default = 0
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(Value, "wireguard_mtu", "MTU")
|
||||
o.default = "1408"
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(Value, "wireguard_private_key", translate("Private Key"))
|
||||
o.datatype = "base64"
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(Value, "wireguard_public_key", translate("Public Key"))
|
||||
o.datatype = "base64"
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(DummyValue, "gen_wireguard_key")
|
||||
o.template = m:template_path("/server/gen_wireguard_key")
|
||||
o:depends({ protocol = "wireguard" })
|
||||
end
|
||||
|
||||
o = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
|
||||
o.default = "0"
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
|
||||
o.default = "0"
|
||||
o:depends({ custom = false })
|
||||
|
||||
local nodes_table = {}
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e.node_type == "normal" and e.type == type_name then
|
||||
nodes_table[#nodes_table + 1] = {
|
||||
id = e[".name"],
|
||||
remarks = e["remark"],
|
||||
group = e["group"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "outbound_node", translate("outbound node"))
|
||||
o:value("", translate("Close"))
|
||||
o:value("_socks", translate("Custom Socks"))
|
||||
o:value("_http", translate("Custom HTTP"))
|
||||
o:value("_iface", translate("Custom Interface"))
|
||||
o.template = m:template_path("/cbi/nodes_listvalue")
|
||||
o.group = {"","","",""}
|
||||
for k, v in pairs(nodes_table) do
|
||||
o:value(v.id, v.remarks)
|
||||
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Value, "outbound_node_address", translate("Address (Support Domain Name)"))
|
||||
o:depends({ outbound_node = "_socks" })
|
||||
o:depends({ outbound_node = "_http" })
|
||||
|
||||
o = s:option(Value, "outbound_node_port", translate("Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ outbound_node = "_socks" })
|
||||
o:depends({ outbound_node = "_http" })
|
||||
|
||||
o = s:option(Value, "outbound_node_username", translate("Username"))
|
||||
o:depends({ outbound_node = "_socks" })
|
||||
o:depends({ outbound_node = "_http" })
|
||||
|
||||
o = s:option(Value, "outbound_node_password", translate("Password"))
|
||||
o.password = true
|
||||
o:depends({ outbound_node = "_socks" })
|
||||
o:depends({ outbound_node = "_http" })
|
||||
|
||||
o = s:option(Value, "outbound_node_iface", translate("Interface"))
|
||||
o:depends({ outbound_node = "_iface" })
|
||||
local netdev_list = api.get_network_devices()
|
||||
for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o = s:option(Flag, "log", translate("Log"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, "loglevel", translate("Log Level"))
|
||||
o.default = "info"
|
||||
o:value("debug")
|
||||
o:value("info")
|
||||
o:value("warn")
|
||||
o:value("error")
|
||||
o:depends({ log = true })
|
||||
|
||||
api.luci_types(s1, s)
|
||||
@@ -0,0 +1,536 @@
|
||||
if not api.finded_com("xray") then
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ Xray ]]
|
||||
local m, s1 = ...
|
||||
local type_name = "Xray"
|
||||
|
||||
s1.fields["type"]:value(type_name, "Xray")
|
||||
if not s1.fields["type"].default then
|
||||
s1.fields["type"].default = type_name
|
||||
end
|
||||
|
||||
if s1.val["type"] and s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local s = NamedSection(m, arg[1], "server")
|
||||
s.type_name = type_name
|
||||
s.option_prefix = "xray_"
|
||||
|
||||
local ss_method_list = {
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
local header_type_list = {
|
||||
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
|
||||
}
|
||||
|
||||
o = s:option(Flag, "custom", translate("Use Custom Config"))
|
||||
|
||||
o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ custom = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "protocol", translate("Protocol"))
|
||||
o:value("socks", "Socks")
|
||||
o:value("http", "HTTP")
|
||||
o:value("vmess", "Vmess")
|
||||
o:value("vless", "VLESS")
|
||||
o:value("shadowsocks", "Shadowsocks")
|
||||
o:value("trojan", "Trojan")
|
||||
o:value("hysteria2", "Hysteria2")
|
||||
o:value("wireguard", "WireGuard")
|
||||
o:value("dokodemo-door", "dokodemo-door")
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Value, "port", translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ custom = false })
|
||||
|
||||
|
||||
o = s:option(DynamicList, "users", translate("User"))
|
||||
for i, v in ipairs(user_list) do
|
||||
o:value(v[".name"], v.username)
|
||||
end
|
||||
o:depends({ protocol = "http" })
|
||||
o:depends({ protocol = "socks" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(ListValue, "d_protocol", translate("Destination protocol"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
o:value("tcp,udp", "TCP,UDP")
|
||||
o:depends({ protocol = "dokodemo-door" })
|
||||
|
||||
o = s:option(Value, "d_address", translate("Destination address"))
|
||||
o:depends({ protocol = "dokodemo-door" })
|
||||
|
||||
o = s:option(Value, "d_port", translate("Destination port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ protocol = "dokodemo-door" })
|
||||
|
||||
o = s:option(Value, "decryption", translate("Encrypt Method") .. " (decryption)")
|
||||
o.default = "none"
|
||||
o.placeholder = "none"
|
||||
o:depends({ protocol = "vless" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
return (value == "" and "none" or value)
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "ss_method", translate("Encrypt Method"))
|
||||
o.rewrite_option = "method"
|
||||
for a, t in ipairs(ss_method_list) do o:value(t) end
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
|
||||
o = s:option(Value, "ss_password", translate("Password"))
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
|
||||
o = s:option(ListValue, "ss_network", translate("Transport"))
|
||||
o.default = "tcp,udp"
|
||||
o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
o:value("tcp,udp", "TCP,UDP")
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
|
||||
o = s:option(Flag, "udp_forward", translate("UDP Forward"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
o:depends({ protocol = "socks" })
|
||||
|
||||
o = s:option(ListValue, "flow", translate("flow"))
|
||||
o.default = ""
|
||||
o:value("", translate("Disable"))
|
||||
o:value("xtls-rprx-vision")
|
||||
o:depends({ protocol = "vless" })
|
||||
|
||||
---- [[ hysteria2 ]]
|
||||
o = s:option(Flag, "hysteria2_realms", translate("Realms"))
|
||||
o.default = "0"
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
|
||||
o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o:depends({ hysteria2_realms = "1" })
|
||||
|
||||
o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
o:value("gecko")
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password"))
|
||||
o:depends({ hysteria2_obfs_type = "salamander" })
|
||||
o:depends({ hysteria2_obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "512"
|
||||
o.default = "512"
|
||||
o:depends({ hysteria2_obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "1200"
|
||||
o.default = "1200"
|
||||
o:depends({ hysteria2_obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Flag, "hysteria2_ignore_client_bandwidth", translate("Client BBR Flow Control"))
|
||||
o.default = 0
|
||||
o:depends({ protocol = "hysteria2" })
|
||||
|
||||
o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps"))
|
||||
o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false })
|
||||
|
||||
o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps"))
|
||||
o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false })
|
||||
|
||||
---- [[ TLS ]]
|
||||
o = s:option(Flag, "tls", translate("TLS"))
|
||||
o.default = 0
|
||||
o.validate = function(self, value, t)
|
||||
if value then
|
||||
local reality = s.fields["reality"] and s.fields["reality"]:formvalue(t) or nil
|
||||
if reality and reality == "1" then return value end
|
||||
if value == "1" then
|
||||
local ca = s.fields["tls_certificateFile"] and s.fields["tls_certificateFile"]:formvalue(t) or ""
|
||||
local key = s.fields["tls_keyFile"] and s.fields["tls_keyFile"]:formvalue(t) or ""
|
||||
if ca == "" or key == "" then
|
||||
return nil, translate("Public key and Private key path can not be empty!")
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
end
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
|
||||
-- [[ REALITY部分 ]] --
|
||||
o = s:option(Flag, "reality", translate("REALITY"))
|
||||
o.default = 0
|
||||
o:depends({ tls = true })
|
||||
|
||||
o = s:option(Value, "reality_private_key", translate("Private Key"))
|
||||
o:depends({ reality = true })
|
||||
|
||||
o = s:option(DynamicList, "reality_shortId", translate("Short Id"))
|
||||
o:depends({ reality = true })
|
||||
|
||||
o = s:option(Value, "reality_dest", translate("Dest"))
|
||||
o.default = "google.com:443"
|
||||
o:depends({ reality = true })
|
||||
|
||||
o = s:option(DynamicList, "reality_serverNames", translate("serverNames"))
|
||||
o:depends({ reality = true })
|
||||
|
||||
o = s:option(ListValue, "alpn", translate("alpn"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("h3")
|
||||
o:value("h2")
|
||||
o:value("h3,h2")
|
||||
o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
o:depends({ tls = true, reality = false })
|
||||
|
||||
o = s:option(Flag, "use_mldsa65Seed", translate("ML-DSA-65"))
|
||||
o.default = "0"
|
||||
o:depends({ reality = true })
|
||||
|
||||
o = s:option(TextValue, "reality_mldsa65Seed", "ML-DSA-65 " .. translate("Private Key"))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "soft"
|
||||
o:depends({ use_mldsa65Seed = true })
|
||||
o.validate = function(self, value)
|
||||
return api.trim(value:gsub("[\r\n]", ""))
|
||||
end
|
||||
|
||||
-- o = s:option(Value, "minversion", translate("minversion"))
|
||||
-- o.default = "1.3"
|
||||
-- o:value("1.3")
|
||||
--o:depends({ tls = true })
|
||||
|
||||
-- [[ TLS部分 ]] --
|
||||
|
||||
o = s:option(FileUpload, "tls_certificateFile", translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
|
||||
o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
|
||||
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
|
||||
o:depends({ tls = true, reality = false })
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
o = s:option(FileUpload, "tls_keyFile", translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
|
||||
o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
|
||||
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
|
||||
o:depends({ tls = true, reality = false })
|
||||
o:depends({ protocol = "hysteria2"})
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
o = s:option(Flag, "ech", translate("ECH"))
|
||||
o.default = "0"
|
||||
o:depends({ tls = true, reality = false })
|
||||
|
||||
o = s:option(TextValue, "ech_key", translate("ECH Key"))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "soft"
|
||||
o:depends({ ech = true })
|
||||
o.validate = function(self, value)
|
||||
return api.trim(value:gsub("[\r\n]", ""))
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "transport", translate("Transport"))
|
||||
o:value("raw", "RAW")
|
||||
o:value("mkcp", "mKCP")
|
||||
o:value("ws", "WebSocket")
|
||||
o:value("grpc", "gRPC")
|
||||
o:value("httpupgrade", "HttpUpgrade")
|
||||
o:value("xhttp", "XHTTP")
|
||||
o:depends({ protocol = "vmess" })
|
||||
o:depends({ protocol = "vless" })
|
||||
o:depends({ protocol = "shadowsocks" })
|
||||
o:depends({ protocol = "trojan" })
|
||||
|
||||
-- [[ WebSocket部分 ]]--
|
||||
|
||||
o = s:option(Value, "ws_host", translate("WebSocket Host"))
|
||||
o:depends({ transport = "ws" })
|
||||
|
||||
o = s:option(Value, "ws_path", translate("WebSocket Path"))
|
||||
o:depends({ transport = "ws" })
|
||||
|
||||
-- [[ HttpUpgrade部分 ]]--
|
||||
o = s:option(Value, "httpupgrade_host", translate("HttpUpgrade Host"))
|
||||
o:depends({ transport = "httpupgrade" })
|
||||
|
||||
o = s:option(Value, "httpupgrade_path", translate("HttpUpgrade Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ transport = "httpupgrade" })
|
||||
|
||||
-- [[ XHTTP部分 ]]--
|
||||
o = s:option(Value, "xhttp_host", translate("XHTTP Host"))
|
||||
o:depends({ transport = "xhttp" })
|
||||
|
||||
o = s:option(Value, "xhttp_path", translate("XHTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ transport = "xhttp" })
|
||||
|
||||
o = s:option(Value, "xhttp_maxuploadsize", translate("maxUploadSize"))
|
||||
o.default = "1000000"
|
||||
o:depends({ transport = "xhttp" })
|
||||
|
||||
o = s:option(Value, "xhttp_maxconcurrentuploads", translate("maxConcurrentUploads"))
|
||||
o.default = "10"
|
||||
o:depends({ transport = "xhttp" })
|
||||
|
||||
-- [[ TCP部分 ]]--
|
||||
|
||||
-- TCP伪装
|
||||
o = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
|
||||
o:value("none", "none")
|
||||
o:value("http", "http")
|
||||
o:depends({ transport = "raw" })
|
||||
|
||||
-- HTTP域名
|
||||
o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
|
||||
o:depends({ tcp_guise = "http" })
|
||||
|
||||
-- HTTP路径
|
||||
o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
|
||||
o:depends({ tcp_guise = "http" })
|
||||
|
||||
-- [[ mKCP部分 ]]--
|
||||
|
||||
o = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
|
||||
for a, t in ipairs(header_type_list) do o:value(t) end
|
||||
o:depends({ transport = "mkcp" })
|
||||
|
||||
o = s:option(Value, "mkcp_domain", translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
|
||||
o:depends({ mkcp_guise = "dns" })
|
||||
|
||||
o = s:option(Value, "mkcp_mtu", translate("KCP MTU"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 1350
|
||||
o:depends({ transport = "mkcp" })
|
||||
|
||||
o = s:option(Value, "mkcp_seed", translate("KCP Seed"))
|
||||
o:depends({ transport = "mkcp" })
|
||||
|
||||
-- [[ gRPC部分 ]]--
|
||||
o = s:option(Value, "grpc_serviceName", "ServiceName")
|
||||
o:depends({ transport = "grpc" })
|
||||
|
||||
--[[FinalMask]]
|
||||
o = s:option(Flag, "use_finalmask", "FinalMask")
|
||||
o.default = "0"
|
||||
o:depends({ custom = false, protocol = "vmess" })
|
||||
o:depends({ custom = false, protocol = "vless" })
|
||||
o:depends({ custom = false, protocol = "trojan" })
|
||||
o:depends({ custom = false, protocol = "shadowsocks" })
|
||||
o:depends({ custom = false, protocol = "hysteria2", hysteria2_realms = false })
|
||||
|
||||
o = s:option(TextValue, "finalmask", "FinalMask JSON")
|
||||
o:depends({ use_finalmask = true })
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o.datatype = "json"
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local raw = m:get(section, "finalmask")
|
||||
if raw then
|
||||
return api.base64Decode(raw)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "finalmask", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
--[[acceptProxyProtocol]]
|
||||
o = s:option(Flag, "acceptProxyProtocol", translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used."))
|
||||
o.default = "0"
|
||||
o:depends({ custom = false })
|
||||
|
||||
--[[Fast Open]]
|
||||
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"))
|
||||
o.default = "0"
|
||||
o:depends({ protocol = "vmess", custom = false })
|
||||
o:depends({ protocol = "vless", custom = false })
|
||||
o:depends({ protocol = "shadowsocks", custom = false })
|
||||
o:depends({ protocol = "trojan", custom = false })
|
||||
|
||||
-- [[ Fallback部分 ]]--
|
||||
o = s:option(Flag, "fallback", translate("Fallback"))
|
||||
o:depends({ protocol = "vless", transport = "raw" })
|
||||
o:depends({ protocol = "trojan", transport = "raw" })
|
||||
|
||||
--[[
|
||||
o = s:option(Value, "fallback_alpn", "Fallback alpn")
|
||||
o:depends({ fallback = true })
|
||||
|
||||
o = s:option(Value, "fallback_path", "Fallback path")
|
||||
o:depends({ fallback = true })
|
||||
|
||||
o = s:option(Value, "fallback_dest", "Fallback dest")
|
||||
o:depends({ fallback = true })
|
||||
|
||||
o = s:option(Value, "fallback_xver", "Fallback xver")
|
||||
o.default = 0
|
||||
o:depends({ fallback = true })
|
||||
]]--
|
||||
|
||||
o = s:option(DynamicList, "fallback_list", "Fallback", translate("format: dest,path,xver"))
|
||||
o:depends({ fallback = true })
|
||||
|
||||
-- Not supported at present
|
||||
--[[
|
||||
o = s:option(Flag, "wireguard_system_interface", translate("System interface"))
|
||||
o.default = 0
|
||||
o:depends({ protocol = "wireguard" })
|
||||
]]--
|
||||
|
||||
o = s:option(Value, "wireguard_mtu", "MTU")
|
||||
o.default = "1420"
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
-- Not supported at present
|
||||
--[[
|
||||
o = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
|
||||
o:depends({ protocol = "wireguard" })
|
||||
]]--
|
||||
|
||||
o = s:option(Value, "wireguard_private_key", translate("Private Key"))
|
||||
o.datatype = "base64"
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(Value, "wireguard_public_key", translate("Public Key"))
|
||||
o.datatype = "base64"
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(DummyValue, "gen_wireguard_key")
|
||||
o.template = m:template_path("/server/gen_wireguard_key")
|
||||
o:depends({ protocol = "wireguard" })
|
||||
|
||||
o = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
|
||||
o.default = "0"
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
|
||||
o.default = "0"
|
||||
o:depends({ custom = false })
|
||||
|
||||
local nodes_table = {}
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e.node_type == "normal" and e.type == type_name then
|
||||
nodes_table[#nodes_table + 1] = {
|
||||
id = e[".name"],
|
||||
remarks = e["remark"],
|
||||
group = e["group"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "outbound_node", translate("outbound node"))
|
||||
o:value("", translate("Close"))
|
||||
o:value("_socks", translate("Custom Socks"))
|
||||
o:value("_http", translate("Custom HTTP"))
|
||||
o:value("_iface", translate("Custom Interface"))
|
||||
o.template = m:template_path("/cbi/nodes_listvalue")
|
||||
o.group = {"","","",""}
|
||||
for k, v in pairs(nodes_table) do
|
||||
o:value(v.id, v.remarks)
|
||||
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Value, "outbound_node_address", translate("Address (Support Domain Name)"))
|
||||
o:depends({ outbound_node = "_socks"})
|
||||
o:depends({ outbound_node = "_http"})
|
||||
|
||||
o = s:option(Value, "outbound_node_port", translate("Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ outbound_node = "_socks"})
|
||||
o:depends({ outbound_node = "_http"})
|
||||
|
||||
o = s:option(Value, "outbound_node_username", translate("Username"))
|
||||
o:depends({ outbound_node = "_socks"})
|
||||
o:depends({ outbound_node = "_http"})
|
||||
|
||||
o = s:option(Value, "outbound_node_password", translate("Password"))
|
||||
o.password = true
|
||||
o:depends({ outbound_node = "_socks"})
|
||||
o:depends({ outbound_node = "_http"})
|
||||
|
||||
o = s:option(Value, "outbound_node_iface", translate("Interface"))
|
||||
o:depends({ outbound_node = "_iface"})
|
||||
local netdev_list = api.get_network_devices()
|
||||
for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o = s:option(Flag, "log", translate("Log"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, "loglevel", translate("Log Level"))
|
||||
o.default = "warning"
|
||||
o:value("debug")
|
||||
o:value("info")
|
||||
o:value("warning")
|
||||
o:value("error")
|
||||
o:depends({ log = true })
|
||||
|
||||
api.luci_types(s1, s)
|
||||
+79
-79
@@ -1,79 +1,79 @@
|
||||
local m, s = ...
|
||||
|
||||
if not api.is_finded("ssserver") then
|
||||
return
|
||||
end
|
||||
|
||||
local type_name = "SS-Rust"
|
||||
|
||||
-- [[ Shadowsocks Rust ]]
|
||||
|
||||
s.fields["type"]:value(type_name, translate("Shadowsocks Rust"))
|
||||
|
||||
if s.val["type"] and s.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local option_prefix = "ssrust_"
|
||||
|
||||
local function _n(name)
|
||||
return option_prefix .. name
|
||||
end
|
||||
|
||||
local ssrust_encrypt_method_list = {
|
||||
"plain", "none",
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
|
||||
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
o = s:option(Flag, _n("custom"), translate("Use Custom Config"))
|
||||
|
||||
o = s:option(Value, _n("port"), translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(ListValue, _n("user"), translate("User"))
|
||||
for i, v in ipairs(user_list) do
|
||||
o:value(v[".name"], v.username)
|
||||
end
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(ListValue, _n("method"), translate("Encrypt Method"))
|
||||
for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(Flag, _n("log"), translate("Log"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
api.luci_types(arg[1], m, s, type_name, option_prefix)
|
||||
if not api.is_finded("ssserver") then
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ Shadowsocks Rust ]]
|
||||
local m, s1 = ...
|
||||
local type_name = "SS-Rust"
|
||||
|
||||
s1.fields["type"]:value(type_name, translate("Shadowsocks Rust"))
|
||||
|
||||
if not s1.val["type"] then
|
||||
s1.val["type"] = type_name
|
||||
end
|
||||
|
||||
if s1.val["type"] and s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local s = NamedSection(m, arg[1], "server")
|
||||
s.type_name = type_name
|
||||
s.option_prefix = "ssrust_"
|
||||
|
||||
local ssrust_encrypt_method_list = {
|
||||
"plain", "none",
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
|
||||
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
o = s:option(Flag, "custom", translate("Use Custom Config"))
|
||||
|
||||
o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ custom = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(Value, "port", translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(ListValue, "user", translate("User"))
|
||||
for i, v in ipairs(user_list) do
|
||||
o:value(v[".name"], v.username)
|
||||
end
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(ListValue, "method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Value, "timeout", translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"))
|
||||
o.default = "0"
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Flag, "log", translate("Log"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
api.luci_types(s1, s)
|
||||
+110
-110
@@ -1,110 +1,110 @@
|
||||
local m, s = ...
|
||||
|
||||
if not api.is_finded("ssr-server") then
|
||||
return
|
||||
end
|
||||
|
||||
local type_name = "SSR"
|
||||
|
||||
-- [[ ShadowsocksR ]]
|
||||
|
||||
s.fields["type"]:value(type_name, translate("ShadowsocksR"))
|
||||
|
||||
if s.val["type"] and s.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local option_prefix = "ssr_"
|
||||
|
||||
local function _n(name)
|
||||
return option_prefix .. name
|
||||
end
|
||||
|
||||
local ssr_encrypt_method_list = {
|
||||
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
|
||||
"aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
|
||||
"bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
|
||||
"cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20",
|
||||
"chacha20-ietf"
|
||||
}
|
||||
|
||||
local ssr_protocol_list = {
|
||||
"origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple",
|
||||
"auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5",
|
||||
"auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c",
|
||||
"auth_chain_d", "auth_chain_e", "auth_chain_f"
|
||||
}
|
||||
local ssr_obfs_list = {
|
||||
"plain", "http_simple", "http_post", "random_head", "tls_simple",
|
||||
"tls1.0_session_auth", "tls1.2_ticket_auth"
|
||||
}
|
||||
|
||||
o = s:option(Flag, _n("custom"), translate("Use Custom Config"))
|
||||
|
||||
o = s:option(Value, _n("port"), translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(ListValue, _n("user"), translate("User"))
|
||||
for i, v in ipairs(user_list) do
|
||||
o:value(v[".name"], v.username)
|
||||
end
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(ListValue, _n("method"), translate("Encrypt Method"))
|
||||
for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(ListValue, _n("protocol"), translate("Protocol"))
|
||||
for a, t in ipairs(ssr_protocol_list) do o:value(t) end
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Value, _n("protocol_param"), translate("Protocol_param"))
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(ListValue, _n("obfs"), translate("Obfs"))
|
||||
for a, t in ipairs(ssr_obfs_list) do o:value(t) end
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Value, _n("obfs_param"), translate("Obfs_param"))
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(Flag, _n("udp_forward"), translate("UDP Forward"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Flag, _n("log"), translate("Log"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
api.luci_types(arg[1], m, s, type_name, option_prefix)
|
||||
if not api.is_finded("ssr-server") then
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ ShadowsocksR ]]
|
||||
local m, s1 = ...
|
||||
local type_name = "SSR"
|
||||
|
||||
s1.fields["type"]:value(type_name, translate("ShadowsocksR"))
|
||||
|
||||
if not s1.val["type"] then
|
||||
s1.val["type"] = type_name
|
||||
end
|
||||
|
||||
if s1.val["type"] and s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local s = NamedSection(m, arg[1], "server")
|
||||
s.type_name = type_name
|
||||
s.option_prefix = "ssr_"
|
||||
|
||||
local ssr_encrypt_method_list = {
|
||||
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
|
||||
"aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
|
||||
"bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
|
||||
"cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20",
|
||||
"chacha20-ietf"
|
||||
}
|
||||
|
||||
local ssr_protocol_list = {
|
||||
"origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple",
|
||||
"auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5",
|
||||
"auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c",
|
||||
"auth_chain_d", "auth_chain_e", "auth_chain_f"
|
||||
}
|
||||
local ssr_obfs_list = {
|
||||
"plain", "http_simple", "http_post", "random_head", "tls_simple",
|
||||
"tls1.0_session_auth", "tls1.2_ticket_auth"
|
||||
}
|
||||
|
||||
o = s:option(Flag, "custom", translate("Use Custom Config"))
|
||||
|
||||
o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ custom = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(Value, "port", translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(ListValue, "user", translate("User"))
|
||||
for i, v in ipairs(user_list) do
|
||||
o:value(v[".name"], v.username)
|
||||
end
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(ListValue, "method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(ListValue, "protocol", translate("Protocol"))
|
||||
for a, t in ipairs(ssr_protocol_list) do o:value(t) end
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Value, "protocol_param", translate("Protocol_param"))
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(ListValue, "obfs", translate("Obfs"))
|
||||
for a, t in ipairs(ssr_obfs_list) do o:value(t) end
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Value, "obfs_param", translate("Obfs_param"))
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Value, "timeout", translate("Connection Timeout"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 300
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"))
|
||||
o.default = "0"
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Flag, "udp_forward", translate("UDP Forward"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Flag, "log", translate("Log"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
api.luci_types(s1, s)
|
||||
@@ -0,0 +1,154 @@
|
||||
if not api.finded_com("hysteria") then
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ Hysteria2 ]]
|
||||
local m, s1 = ...
|
||||
local type_name = "Hysteria2"
|
||||
|
||||
s1.fields["type"]:value(type_name, "Hysteria2")
|
||||
|
||||
if s1.val["type"] and s1.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local s = NamedSection(m, arg[1], "server")
|
||||
s.type_name = type_name
|
||||
s.option_prefix = "hysteria2_"
|
||||
|
||||
local function _n(name)
|
||||
return s.option_prefix .. name
|
||||
end
|
||||
|
||||
o = s:option(Flag, "custom", translate("Use Custom Config"))
|
||||
|
||||
o = s:option(TextValue, "custom_config", translate("Custom Config"))
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ custom = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(Value, "port", translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(DynamicList, "users", translate("User"))
|
||||
for i, v in ipairs(user_list) do
|
||||
o:value(v[".name"], v.username)
|
||||
end
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Flag, "realms", translate("Realms"))
|
||||
o.default = "0"
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Value, "realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ realms = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, "realm_stun", translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ realms = "1" })
|
||||
|
||||
o = s:option(ListValue, "obfs_type", translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
o:value("gecko")
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Value, "obfs_password", translate("Obfs Password"))
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ obfs_type = "salamander" })
|
||||
o:depends({ obfs_type = "gecko" })
|
||||
|
||||
o = s:option(Value, "obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "512"
|
||||
o.default = "512"
|
||||
o:depends({ obfs_type = "gecko" })
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(Value, "obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "1200"
|
||||
o.default = "1200"
|
||||
o:depends({ obfs_type = "gecko" })
|
||||
o.rewrite_option = _n(o.option)
|
||||
|
||||
o = s:option(Flag, "udp", translate("UDP"))
|
||||
o.default = "1"
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Value, "up_mbps", translate("Max upload Mbps"))
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Value, "down_mbps", translate("Max download Mbps"))
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Flag, "ignoreClientBandwidth", translate("ignoreClientBandwidth"))
|
||||
o.default = "0"
|
||||
o.rewrite_option = _n(o.option)
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(FileUpload, "tls_certificateFile", translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
|
||||
o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
|
||||
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not api.fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(FileUpload, "tls_keyFile", translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
|
||||
o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
|
||||
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not api.fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
o:depends({ custom = false })
|
||||
|
||||
o = s:option(Flag, "log", translate("Log"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
api.luci_types(s1, s)
|
||||
@@ -1,156 +0,0 @@
|
||||
local m, s = ...
|
||||
|
||||
if not api.finded_com("hysteria") then
|
||||
return
|
||||
end
|
||||
|
||||
local fs = api.fs
|
||||
|
||||
local type_name = "Hysteria2"
|
||||
|
||||
-- [[ Hysteria2 ]]
|
||||
|
||||
s.fields["type"]:value(type_name, "Hysteria2")
|
||||
|
||||
if s.val["type"] and s.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local option_prefix = "hysteria2_"
|
||||
|
||||
local function _n(name)
|
||||
return option_prefix .. name
|
||||
end
|
||||
|
||||
o = s:option(Flag, _n("custom"), translate("Use Custom Config"))
|
||||
|
||||
o = s:option(Value, _n("port"), translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(DynamicList, _n("users"), translate("User"))
|
||||
for i, v in ipairs(user_list) do
|
||||
o:value(v[".name"], v.username)
|
||||
end
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Flag, _n("realms"), translate("Realms"))
|
||||
o.default = "0"
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Value, _n("realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("realms")] = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, _n("realm_stun"), translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("realms")] = "1" })
|
||||
|
||||
o = s:option(ListValue, _n("obfs_type"), translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
o:value("gecko")
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Value, _n("obfs_password"), translate("Obfs Password"))
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("obfs_type")] = "salamander" })
|
||||
o:depends({ [_n("obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Value, _n("obfs_MinPacketSize"), translate("Gecko Packet Size (min)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "512"
|
||||
o.default = "512"
|
||||
o:depends({ [_n("obfs_type")] = "gecko" })
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(Value, _n("obfs_MaxPacketSize"), translate("Gecko Packet Size (max)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "1200"
|
||||
o.default = "1200"
|
||||
o:depends({ [_n("obfs_type")] = "gecko" })
|
||||
o.rewrite_option = o.option
|
||||
|
||||
o = s:option(Flag, _n("udp"), translate("UDP"))
|
||||
o.default = "1"
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Value, _n("up_mbps"), translate("Max upload Mbps"))
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Value, _n("down_mbps"), translate("Max download Mbps"))
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Flag, _n("ignoreClientBandwidth"), translate("ignoreClientBandwidth"))
|
||||
o.default = "0"
|
||||
o.rewrite_option = o.option
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
|
||||
o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
|
||||
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(FileUpload, _n("tls_keyFile"), translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
|
||||
o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
|
||||
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(Flag, _n("log"), translate("Log"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
api.luci_types(arg[1], m, s, type_name, option_prefix)
|
||||
@@ -1,541 +0,0 @@
|
||||
local m, s = ...
|
||||
|
||||
if not api.finded_com("xray") then
|
||||
return
|
||||
end
|
||||
|
||||
local type_name = "Xray"
|
||||
|
||||
-- [[ Xray ]]
|
||||
|
||||
s.fields["type"]:value(type_name, "Xray")
|
||||
if not s.fields["type"].default then
|
||||
s.fields["type"].default = type_name
|
||||
end
|
||||
|
||||
if s.val["type"] and s.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local option_prefix = "xray_"
|
||||
|
||||
local function _n(name)
|
||||
return option_prefix .. name
|
||||
end
|
||||
|
||||
local x_ss_method_list = {
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
local header_type_list = {
|
||||
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
|
||||
}
|
||||
|
||||
o = s:option(Flag, _n("custom"), translate("Use Custom Config"))
|
||||
|
||||
o = s:option(ListValue, _n("protocol"), translate("Protocol"))
|
||||
o:value("socks", "Socks")
|
||||
o:value("http", "HTTP")
|
||||
o:value("vmess", "Vmess")
|
||||
o:value("vless", "VLESS")
|
||||
o:value("shadowsocks", "Shadowsocks")
|
||||
o:value("trojan", "Trojan")
|
||||
o:value("hysteria2", "Hysteria2")
|
||||
o:value("wireguard", "WireGuard")
|
||||
o:value("dokodemo-door", "dokodemo-door")
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Value, _n("port"), translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
|
||||
o = s:option(DynamicList, _n("users"), translate("User"))
|
||||
for i, v in ipairs(user_list) do
|
||||
o:value(v[".name"], v.username)
|
||||
end
|
||||
o:depends({ [_n("protocol")] = "http" })
|
||||
o:depends({ [_n("protocol")] = "socks" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(ListValue, _n("d_protocol"), translate("Destination protocol"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
o:value("tcp,udp", "TCP,UDP")
|
||||
o:depends({ [_n("protocol")] = "dokodemo-door" })
|
||||
|
||||
o = s:option(Value, _n("d_address"), translate("Destination address"))
|
||||
o:depends({ [_n("protocol")] = "dokodemo-door" })
|
||||
|
||||
o = s:option(Value, _n("d_port"), translate("Destination port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ [_n("protocol")] = "dokodemo-door" })
|
||||
|
||||
o = s:option(Value, _n("decryption"), translate("Encrypt Method") .. " (decryption)")
|
||||
o.default = "none"
|
||||
o.placeholder = "none"
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
return (value == "" and "none" or value)
|
||||
end
|
||||
|
||||
o = s:option(ListValue, _n("x_ss_method"), translate("Encrypt Method"))
|
||||
o.rewrite_option = "method"
|
||||
for a, t in ipairs(x_ss_method_list) do o:value(t) end
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
|
||||
o = s:option(Value, _n("ss_password"), translate("Password"))
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
|
||||
o = s:option(ListValue, _n("ss_network"), translate("Transport"))
|
||||
o.default = "tcp,udp"
|
||||
o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
o:value("tcp,udp", "TCP,UDP")
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
|
||||
o = s:option(Flag, _n("udp_forward"), translate("UDP Forward"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
o:depends({ [_n("protocol")] = "socks" })
|
||||
|
||||
o = s:option(ListValue, _n("flow"), translate("flow"))
|
||||
o.default = ""
|
||||
o:value("", translate("Disable"))
|
||||
o:value("xtls-rprx-vision")
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
|
||||
---- [[ hysteria2 ]]
|
||||
o = s:option(Flag, _n("hysteria2_realms"), translate("Realms"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
|
||||
o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o:depends({ [_n("hysteria2_realms")] = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o:depends({ [_n("hysteria2_realms")] = "1" })
|
||||
|
||||
o = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
o:value("gecko")
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password"))
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "salamander" })
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_obfs_MinPacketSize"), translate("Gecko Packet Size (min)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "512"
|
||||
o.default = "512"
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_obfs_MaxPacketSize"), translate("Gecko Packet Size (max)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "1200"
|
||||
o.default = "1200"
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Flag, _n("hysteria2_ignore_client_bandwidth"), translate("Client BBR Flow Control"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_up_mbps"), translate("Max upload Mbps"))
|
||||
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_ignore_client_bandwidth")] = false })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_down_mbps"), translate("Max download Mbps"))
|
||||
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_ignore_client_bandwidth")] = false })
|
||||
|
||||
---- [[ TLS ]]
|
||||
o = s:option(Flag, _n("tls"), translate("TLS"))
|
||||
o.default = 0
|
||||
o.validate = function(self, value, t)
|
||||
if value then
|
||||
local reality = s.fields[_n("reality")] and s.fields[_n("reality")]:formvalue(t) or nil
|
||||
if reality and reality == "1" then return value end
|
||||
if value == "1" then
|
||||
local ca = s.fields[_n("tls_certificateFile")] and s.fields[_n("tls_certificateFile")]:formvalue(t) or ""
|
||||
local key = s.fields[_n("tls_keyFile")] and s.fields[_n("tls_keyFile")]:formvalue(t) or ""
|
||||
if ca == "" or key == "" then
|
||||
return nil, translate("Public key and Private key path can not be empty!")
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
end
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
|
||||
-- [[ REALITY部分 ]] --
|
||||
o = s:option(Flag, _n("reality"), translate("REALITY"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("tls")] = true })
|
||||
|
||||
o = s:option(Value, _n("reality_private_key"), translate("Private Key"))
|
||||
o:depends({ [_n("reality")] = true })
|
||||
|
||||
o = s:option(DynamicList, _n("reality_shortId"), translate("Short Id"))
|
||||
o:depends({ [_n("reality")] = true })
|
||||
|
||||
o = s:option(Value, _n("reality_dest"), translate("Dest"))
|
||||
o.default = "google.com:443"
|
||||
o:depends({ [_n("reality")] = true })
|
||||
|
||||
o = s:option(DynamicList, _n("reality_serverNames"), translate("serverNames"))
|
||||
o:depends({ [_n("reality")] = true })
|
||||
|
||||
o = s:option(ListValue, _n("alpn"), translate("alpn"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("h3")
|
||||
o:value("h2")
|
||||
o:value("h3,h2")
|
||||
o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
|
||||
o = s:option(Flag, _n("use_mldsa65Seed"), translate("ML-DSA-65"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("reality")] = true })
|
||||
|
||||
o = s:option(TextValue, _n("reality_mldsa65Seed"), "ML-DSA-65 " .. translate("Private Key"))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "soft"
|
||||
o:depends({ [_n("use_mldsa65Seed")] = true })
|
||||
o.validate = function(self, value)
|
||||
return api.trim(value:gsub("[\r\n]", ""))
|
||||
end
|
||||
|
||||
-- o = s:option(Value, _n("minversion"), translate("minversion"))
|
||||
-- o.default = "1.3"
|
||||
-- o:value("1.3")
|
||||
--o:depends({ [_n("tls")] = true })
|
||||
|
||||
-- [[ TLS部分 ]] --
|
||||
|
||||
o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
|
||||
o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
|
||||
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
o = s:option(FileUpload, _n("tls_keyFile"), translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
|
||||
o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
|
||||
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
o = s:option(Flag, _n("ech"), translate("ECH"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
|
||||
o = s:option(TextValue, _n("ech_key"), translate("ECH Key"))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "soft"
|
||||
o:depends({ [_n("ech")] = true })
|
||||
o.validate = function(self, value)
|
||||
return api.trim(value:gsub("[\r\n]", ""))
|
||||
end
|
||||
|
||||
o = s:option(ListValue, _n("transport"), translate("Transport"))
|
||||
o:value("raw", "RAW")
|
||||
o:value("mkcp", "mKCP")
|
||||
o:value("ws", "WebSocket")
|
||||
o:value("grpc", "gRPC")
|
||||
o:value("httpupgrade", "HttpUpgrade")
|
||||
o:value("xhttp", "XHTTP")
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
|
||||
-- [[ WebSocket部分 ]]--
|
||||
|
||||
o = s:option(Value, _n("ws_host"), translate("WebSocket Host"))
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
-- [[ HttpUpgrade部分 ]]--
|
||||
o = s:option(Value, _n("httpupgrade_host"), translate("HttpUpgrade Host"))
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
o = s:option(Value, _n("httpupgrade_path"), translate("HttpUpgrade Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
-- [[ XHTTP部分 ]]--
|
||||
o = s:option(Value, _n("xhttp_host"), translate("XHTTP Host"))
|
||||
o:depends({ [_n("transport")] = "xhttp" })
|
||||
|
||||
o = s:option(Value, _n("xhttp_path"), translate("XHTTP Path"))
|
||||
o.placeholder = "/"
|
||||
o:depends({ [_n("transport")] = "xhttp" })
|
||||
|
||||
o = s:option(Value, _n("xhttp_maxuploadsize"), translate("maxUploadSize"))
|
||||
o.default = "1000000"
|
||||
o:depends({ [_n("transport")] = "xhttp" })
|
||||
|
||||
o = s:option(Value, _n("xhttp_maxconcurrentuploads"), translate("maxConcurrentUploads"))
|
||||
o.default = "10"
|
||||
o:depends({ [_n("transport")] = "xhttp" })
|
||||
|
||||
-- [[ TCP部分 ]]--
|
||||
|
||||
-- TCP伪装
|
||||
o = s:option(ListValue, _n("tcp_guise"), translate("Camouflage Type"))
|
||||
o:value("none", "none")
|
||||
o:value("http", "http")
|
||||
o:depends({ [_n("transport")] = "raw" })
|
||||
|
||||
-- HTTP域名
|
||||
o = s:option(DynamicList, _n("tcp_guise_http_host"), translate("HTTP Host"))
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
-- HTTP路径
|
||||
o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path"))
|
||||
o:depends({ [_n("tcp_guise")] = "http" })
|
||||
|
||||
-- [[ mKCP部分 ]]--
|
||||
|
||||
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
|
||||
for a, t in ipairs(header_type_list) do o:value(t) end
|
||||
o:depends({ [_n("transport")] = "mkcp" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
|
||||
o:depends({ [_n("mkcp_guise")] = "dns" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 1350
|
||||
o:depends({ [_n("transport")] = "mkcp" })
|
||||
|
||||
o = s:option(Value, _n("mkcp_seed"), translate("KCP Seed"))
|
||||
o:depends({ [_n("transport")] = "mkcp" })
|
||||
|
||||
-- [[ gRPC部分 ]]--
|
||||
o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
|
||||
o:depends({ [_n("transport")] = "grpc" })
|
||||
|
||||
--[[FinalMask]]
|
||||
o = s:option(Flag, _n("use_finalmask"), "FinalMask")
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false, [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("custom")] = false, [_n("protocol")] = "vless" })
|
||||
o:depends({ [_n("custom")] = false, [_n("protocol")] = "trojan" })
|
||||
o:depends({ [_n("custom")] = false, [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("custom")] = false, [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
|
||||
|
||||
o = s:option(TextValue, _n("finalmask"), "FinalMask JSON")
|
||||
o:depends({ [_n("use_finalmask")] = true })
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o.datatype = "json"
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local raw = m:get(section, "finalmask")
|
||||
if raw then
|
||||
return api.base64Decode(raw)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "finalmask", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
--[[acceptProxyProtocol]]
|
||||
o = s:option(Flag, _n("acceptProxyProtocol"), translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used."))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
--[[Fast Open]]
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("protocol")] = "vmess", [_n("custom")] = false })
|
||||
o:depends({ [_n("protocol")] = "vless", [_n("custom")] = false })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks", [_n("custom")] = false })
|
||||
o:depends({ [_n("protocol")] = "trojan", [_n("custom")] = false })
|
||||
|
||||
-- [[ Fallback部分 ]]--
|
||||
o = s:option(Flag, _n("fallback"), translate("Fallback"))
|
||||
o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "raw" })
|
||||
o:depends({ [_n("protocol")] = "trojan", [_n("transport")] = "raw" })
|
||||
|
||||
--[[
|
||||
o = s:option(Value, _n("fallback_alpn"), "Fallback alpn")
|
||||
o:depends({ [_n("fallback")] = true })
|
||||
|
||||
o = s:option(Value, _n("fallback_path"), "Fallback path")
|
||||
o:depends({ [_n("fallback")] = true })
|
||||
|
||||
o = s:option(Value, _n("fallback_dest"), "Fallback dest")
|
||||
o:depends({ [_n("fallback")] = true })
|
||||
|
||||
o = s:option(Value, _n("fallback_xver"), "Fallback xver")
|
||||
o.default = 0
|
||||
o:depends({ [_n("fallback")] = true })
|
||||
]]--
|
||||
|
||||
o = s:option(DynamicList, _n("fallback_list"), "Fallback", translate("format: dest,path,xver"))
|
||||
o:depends({ [_n("fallback")] = true })
|
||||
|
||||
-- Not supported at present
|
||||
--[[
|
||||
o = s:option(Flag, _n("wireguard_system_interface"), translate("System interface"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
]]--
|
||||
|
||||
o = s:option(Value, _n("wireguard_mtu"), "MTU")
|
||||
o.default = "1420"
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
-- Not supported at present
|
||||
--[[
|
||||
o = s:option(DynamicList, _n("wireguard_local_address"), translate("Local Address"))
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
]]--
|
||||
|
||||
o = s:option(Value, _n("wireguard_private_key"), translate("Private Key"))
|
||||
o.datatype = "base64"
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(Value, _n("wireguard_public_key"), translate("Public Key"))
|
||||
o.datatype = "base64"
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(DummyValue, _n("gen_wireguard_key"))
|
||||
o.prefix = option_prefix
|
||||
o.template = m:template_path("/server/server_wireguard")
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(Flag, _n("bind_local"), translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Flag, _n("accept_lan"), translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
local nodes_table = {}
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e.node_type == "normal" and e.type == type_name then
|
||||
nodes_table[#nodes_table + 1] = {
|
||||
id = e[".name"],
|
||||
remarks = e["remark"],
|
||||
group = e["group"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(ListValue, _n("outbound_node"), translate("outbound node"))
|
||||
o:value("", translate("Close"))
|
||||
o:value("_socks", translate("Custom Socks"))
|
||||
o:value("_http", translate("Custom HTTP"))
|
||||
o:value("_iface", translate("Custom Interface"))
|
||||
o.template = m:template_path("/cbi/nodes_listvalue")
|
||||
o.group = {"","","",""}
|
||||
for k, v in pairs(nodes_table) do
|
||||
o:value(v.id, v.remarks)
|
||||
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Value, _n("outbound_node_address"), translate("Address (Support Domain Name)"))
|
||||
o:depends({ [_n("outbound_node")] = "_socks"})
|
||||
o:depends({ [_n("outbound_node")] = "_http"})
|
||||
|
||||
o = s:option(Value, _n("outbound_node_port"), translate("Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ [_n("outbound_node")] = "_socks"})
|
||||
o:depends({ [_n("outbound_node")] = "_http"})
|
||||
|
||||
o = s:option(Value, _n("outbound_node_username"), translate("Username"))
|
||||
o:depends({ [_n("outbound_node")] = "_socks"})
|
||||
o:depends({ [_n("outbound_node")] = "_http"})
|
||||
|
||||
o = s:option(Value, _n("outbound_node_password"), translate("Password"))
|
||||
o.password = true
|
||||
o:depends({ [_n("outbound_node")] = "_socks"})
|
||||
o:depends({ [_n("outbound_node")] = "_http"})
|
||||
|
||||
o = s:option(Value, _n("outbound_node_iface"), translate("Interface"))
|
||||
o:depends({ [_n("outbound_node")] = "_iface"})
|
||||
local netdev_list = api.get_network_devices()
|
||||
for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(Flag, _n("log"), translate("Log"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, _n("loglevel"), translate("Log Level"))
|
||||
o.default = "warning"
|
||||
o:value("debug")
|
||||
o:value("info")
|
||||
o:value("warning")
|
||||
o:value("error")
|
||||
o:depends({ [_n("log")] = true })
|
||||
|
||||
api.luci_types(arg[1], m, s, type_name, option_prefix)
|
||||
@@ -1,523 +0,0 @@
|
||||
local m, s = ...
|
||||
|
||||
local singbox_bin = api.finded_com("sing-box")
|
||||
|
||||
if not singbox_bin then
|
||||
return
|
||||
end
|
||||
|
||||
local type_name = "sing-box"
|
||||
|
||||
-- [[ Sing-Box ]]
|
||||
|
||||
s.fields["type"]:value(type_name, "Sing-Box")
|
||||
if not s.fields["type"].default then
|
||||
s.fields["type"].default = type_name
|
||||
end
|
||||
|
||||
if s.val["type"] and s.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
local option_prefix = "singbox_"
|
||||
|
||||
local function _n(name)
|
||||
return option_prefix .. name
|
||||
end
|
||||
|
||||
local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'")
|
||||
|
||||
local ss_method_list = {
|
||||
"none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305",
|
||||
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
o = s:option(Flag, _n("custom"), translate("Use Custom Config"))
|
||||
|
||||
o = s:option(ListValue, _n("protocol"), translate("Protocol"))
|
||||
o:value("mixed", "Mixed")
|
||||
o:value("socks", "Socks")
|
||||
o:value("http", "HTTP")
|
||||
o:value("shadowsocks", "Shadowsocks")
|
||||
o:value("vmess", "Vmess")
|
||||
o:value("vless", "VLESS")
|
||||
o:value("trojan", "Trojan")
|
||||
o:value("naive", "Naive")
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("hysteria", "Hysteria")
|
||||
end
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("tuic", "TUIC")
|
||||
end
|
||||
if singbox_tags:find("with_quic") then
|
||||
o:value("hysteria2", "Hysteria2")
|
||||
end
|
||||
o:value("anytls", "AnyTLS")
|
||||
if singbox_tags:find("with_wireguard") then
|
||||
o:value("wireguard", "WireGuard")
|
||||
end
|
||||
o:value("direct", "Direct")
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Value, _n("port"), translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(DynamicList, _n("users"), translate("User"))
|
||||
for i, v in ipairs(user_list) do
|
||||
o:value(v[".name"], v.username)
|
||||
end
|
||||
o:depends({ [_n("protocol")] = "mixed" })
|
||||
o:depends({ [_n("protocol")] = "socks" })
|
||||
o:depends({ [_n("protocol")] = "http" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
o:depends({ [_n("protocol")] = "naive" })
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
o:depends({ [_n("protocol")] = "anytls" })
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
if singbox_tags:find("with_quic") then
|
||||
o = s:option(Value, _n("hysteria_obfs"), translate("Obfs Password"))
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Value, _n("hysteria_up_mbps"), translate("Max upload Mbps"))
|
||||
o.default = "100"
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Value, _n("hysteria_down_mbps"), translate("Max download Mbps"))
|
||||
o.default = "100"
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Value, _n("hysteria_recv_window_conn"), translate("QUIC stream receive window"))
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Value, _n("hysteria_recv_window_client"), translate("QUIC connection receive window"))
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Value, _n("hysteria_max_conn_client"), translate("QUIC concurrent bidirectional streams"))
|
||||
o.default = "1024"
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
o = s:option(Flag, _n("hysteria_disable_mtu_discovery"), translate("Disable MTU detection"))
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
end
|
||||
|
||||
if singbox_tags:find("with_quic") then
|
||||
o = s:option(ListValue, _n("tuic_congestion_control"), translate("Congestion control algorithm"))
|
||||
o.default = "cubic"
|
||||
o:value("bbr", translate("BBR"))
|
||||
o:value("cubic", translate("CUBIC"))
|
||||
o:value("new_reno", translate("New Reno"))
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
|
||||
o = s:option(Flag, _n("tuic_zero_rtt_handshake"), translate("Enable 0-RTT QUIC handshake"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
|
||||
o = s:option(Value, _n("tuic_heartbeat"), translate("Heartbeat interval(second)"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = "3"
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
|
||||
o = s:option(ListValue, _n("tuic_alpn"), translate("QUIC TLS ALPN"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("h3")
|
||||
o:value("h2")
|
||||
o:value("h3,h2")
|
||||
o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
o:value("spdy/3.1")
|
||||
o:value("h3,spdy/3.1")
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
end
|
||||
|
||||
if singbox_tags:find("with_quic") then
|
||||
o = s:option(Flag, _n("hysteria2_realms"), translate("Realms"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("protocol")] = "hysteria2"})
|
||||
|
||||
o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o:depends({ [_n("hysteria2_realms")] = "1" })
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value)
|
||||
local realm = api.parse_realm_uri(value)
|
||||
if realm then return value end
|
||||
return nil, translate("Invalid Realm URL.")
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o:depends({ [_n("hysteria2_realms")] = "1" })
|
||||
|
||||
o = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
o:value("gecko")
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password"))
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "salamander" })
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_obfs_MinPacketSize"), translate("Gecko Packet Size (min)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "512"
|
||||
o.default = "512"
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_obfs_MaxPacketSize"), translate("Gecko Packet Size (max)"))
|
||||
o.datatype = "uinteger"
|
||||
o.placeholder = "1200"
|
||||
o.default = "1200"
|
||||
o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
|
||||
|
||||
o = s:option(Flag, _n("hysteria2_ignore_client_bandwidth"), translate("Client BBR Flow Control"), translate("Commands the client to use the BBR flow control algorithm"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_up_mbps"), translate("Max upload Mbps"))
|
||||
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_ignore_client_bandwidth")] = false })
|
||||
|
||||
o = s:option(Value, _n("hysteria2_down_mbps"), translate("Max download Mbps"))
|
||||
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_ignore_client_bandwidth")] = false })
|
||||
end
|
||||
|
||||
o = s:option(ListValue, _n("d_protocol"), translate("Destination protocol"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
o:value("tcp,udp", "TCP,UDP")
|
||||
o:depends({ [_n("protocol")] = "direct" })
|
||||
|
||||
o = s:option(Value, _n("d_address"), translate("Destination address"))
|
||||
o:depends({ [_n("protocol")] = "direct" })
|
||||
|
||||
o = s:option(Value, _n("d_port"), translate("Destination port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ [_n("protocol")] = "direct" })
|
||||
|
||||
o = s:option(Value, _n("decryption"), translate("Encrypt Method"))
|
||||
o.default = "none"
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
|
||||
o = s:option(ListValue, _n("ss_method"), translate("Encrypt Method"))
|
||||
o.rewrite_option = "method"
|
||||
for a, t in ipairs(ss_method_list) do o:value(t) end
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
|
||||
o = s:option(Value, _n("ss_password"), translate("Password"))
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
|
||||
o = s:option(ListValue, _n("flow"), translate("flow"))
|
||||
o.default = ""
|
||||
o:value("", translate("Disable"))
|
||||
o:value("xtls-rprx-vision")
|
||||
o:depends({ [_n("protocol")] = "vless" , [_n("tls")] = true })
|
||||
|
||||
o = s:option(Flag, _n("tls"), translate("TLS"))
|
||||
o.default = 0
|
||||
o.validate = function(self, value, t)
|
||||
if value then
|
||||
local reality = s.fields[_n("reality")] and s.fields[_n("reality")]:formvalue(t) or nil
|
||||
if reality and reality == "1" then return value end
|
||||
if value == "1" then
|
||||
local ca = s.fields[_n("tls_certificateFile")] and s.fields[_n("tls_certificateFile")]:formvalue(t) or ""
|
||||
local key = s.fields[_n("tls_keyFile")] and s.fields[_n("tls_keyFile")]:formvalue(t) or ""
|
||||
if ca == "" or key == "" then
|
||||
return nil, translate("Public key and Private key path can not be empty!")
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
end
|
||||
o:depends({ [_n("protocol")] = "http" })
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
o:depends({ [_n("protocol")] = "anytls" })
|
||||
|
||||
-- https://github.com/SagerNet/sing-box/commit/d2a04c4e41e6cef0937331cb6d10211f431caaab
|
||||
if singbox_tags:find("with_utls") then
|
||||
-- [[ REALITY部分 ]] --
|
||||
o = s:option(Flag, _n("reality"), translate("REALITY"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("protocol")] = "http", [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "vmess", [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "vless", [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "trojan", [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "anytls", [_n("tls")] = true })
|
||||
|
||||
o = s:option(Value, _n("reality_private_key"), translate("Private Key"))
|
||||
o:depends({ [_n("reality")] = true })
|
||||
|
||||
o = s:option(Value, _n("reality_shortId"), translate("Short Id"))
|
||||
o:depends({ [_n("reality")] = true })
|
||||
|
||||
o = s:option(Value, _n("reality_handshake_server"), translate("Handshake Server"))
|
||||
o.default = "google.com"
|
||||
o:depends({ [_n("reality")] = true })
|
||||
|
||||
o = s:option(Value, _n("reality_handshake_server_port"), translate("Handshake Server Port"))
|
||||
o.datatype = "port"
|
||||
o.default = "443"
|
||||
o:depends({ [_n("reality")] = true })
|
||||
end
|
||||
|
||||
o = s:option(ListValue, _n("alpn"), translate("ALPN"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("h3")
|
||||
o:value("h2")
|
||||
o:value("h3,h2")
|
||||
o:value("http/1.1")
|
||||
o:value("h2,http/1.1")
|
||||
o:value("h3,h2,http/1.1")
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
|
||||
-- [[ TLS部分 ]] --
|
||||
|
||||
o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
|
||||
o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
|
||||
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "naive" })
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
o = s:option(FileUpload, _n("tls_keyFile"), translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
|
||||
o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
|
||||
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "naive" })
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
o.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
o = s:option(Flag, _n("ech"), translate("ECH"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("tls")] = true, [_n("flow")] = "", [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "naive" })
|
||||
o:depends({ [_n("protocol")] = "hysteria" })
|
||||
o:depends({ [_n("protocol")] = "tuic" })
|
||||
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
|
||||
|
||||
o = s:option(TextValue, _n("ech_key"), translate("ECH Key"))
|
||||
o.default = ""
|
||||
o.rows = 5
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("ech")] = true })
|
||||
o.validate = function(self, value)
|
||||
value = value:gsub("^%s+", ""):gsub("%s+$","\n"):gsub("\r\n","\n"):gsub("[ \t]*\n[ \t]*", "\n")
|
||||
value = value:gsub("^%s*\n", "")
|
||||
if value:sub(-1) == "\n" then
|
||||
value = value:sub(1, -2)
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
o = s:option(ListValue, _n("transport"), translate("Transport"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("http", "HTTP")
|
||||
o:value("ws", "WebSocket")
|
||||
o:value("httpupgrade", "HTTPUpgrade")
|
||||
o:value("quic", "QUIC")
|
||||
o:value("grpc", "gRPC")
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
|
||||
-- [[ HTTP部分 ]]--
|
||||
|
||||
o = s:option(DynamicList, _n("http_host"), translate("HTTP Host"))
|
||||
o:depends({ [_n("transport")] = "http" })
|
||||
|
||||
o = s:option(Value, _n("http_path"), translate("HTTP Path"))
|
||||
o:depends({ [_n("transport")] = "http" })
|
||||
|
||||
-- [[ WebSocket部分 ]]--
|
||||
|
||||
o = s:option(Value, _n("ws_host"), translate("WebSocket Host"))
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
|
||||
o:depends({ [_n("transport")] = "ws" })
|
||||
|
||||
-- [[ HTTPUpgrade部分 ]]--
|
||||
|
||||
o = s:option(Value, _n("httpupgrade_host"), translate("HTTPUpgrade Host"))
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
o = s:option(Value, _n("httpupgrade_path"), translate("HTTPUpgrade Path"))
|
||||
o:depends({ [_n("transport")] = "httpupgrade" })
|
||||
|
||||
-- [[ gRPC部分 ]]--
|
||||
o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
|
||||
o:depends({ [_n("transport")] = "grpc" })
|
||||
|
||||
-- [[ Mux ]]--
|
||||
o = s:option(Flag, _n("mux"), translate("Mux"))
|
||||
o.rmempty = false
|
||||
o:depends({ [_n("protocol")] = "vmess" })
|
||||
o:depends({ [_n("protocol")] = "vless", [_n("flow")] = "" })
|
||||
o:depends({ [_n("protocol")] = "shadowsocks" })
|
||||
o:depends({ [_n("protocol")] = "trojan" })
|
||||
|
||||
-- [[ TCP Brutal ]]--
|
||||
o = s:option(Flag, _n("tcpbrutal"), translate("TCP Brutal"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("mux")] = true })
|
||||
|
||||
o = s:option(Value, _n("tcpbrutal_up_mbps"), translate("Max upload Mbps"))
|
||||
o.default = "10"
|
||||
o:depends({ [_n("tcpbrutal")] = true })
|
||||
|
||||
o = s:option(Value, _n("tcpbrutal_down_mbps"), translate("Max download Mbps"))
|
||||
o.default = "50"
|
||||
o:depends({ [_n("tcpbrutal")] = true })
|
||||
|
||||
if singbox_tags:find("with_wireguard") then
|
||||
o = s:option(Flag, _n("wireguard_system_interface"), translate("System interface"))
|
||||
o.default = 0
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(Value, _n("wireguard_mtu"), "MTU")
|
||||
o.default = "1408"
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(DynamicList, _n("wireguard_local_address"), translate("Local Address"))
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(Value, _n("wireguard_private_key"), translate("Private Key"))
|
||||
o.datatype = "base64"
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(Value, _n("wireguard_public_key"), translate("Public Key"))
|
||||
o.datatype = "base64"
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
|
||||
o = s:option(DummyValue, _n("gen_wireguard_key"))
|
||||
o.prefix = option_prefix
|
||||
o.template = m:template_path("/server/server_wireguard")
|
||||
o:depends({ [_n("protocol")] = "wireguard" })
|
||||
end
|
||||
|
||||
o = s:option(Flag, _n("bind_local"), translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Flag, _n("accept_lan"), translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
local nodes_table = {}
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e.node_type == "normal" and e.type == type_name then
|
||||
nodes_table[#nodes_table + 1] = {
|
||||
id = e[".name"],
|
||||
remarks = e["remark"],
|
||||
group = e["group"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(ListValue, _n("outbound_node"), translate("outbound node"))
|
||||
o:value("", translate("Close"))
|
||||
o:value("_socks", translate("Custom Socks"))
|
||||
o:value("_http", translate("Custom HTTP"))
|
||||
o:value("_iface", translate("Custom Interface"))
|
||||
o.template = m:template_path("/cbi/nodes_listvalue")
|
||||
o.group = {"","","",""}
|
||||
for k, v in pairs(nodes_table) do
|
||||
o:value(v.id, v.remarks)
|
||||
o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
o:depends({ [_n("custom")] = false })
|
||||
|
||||
o = s:option(Value, _n("outbound_node_address"), translate("Address (Support Domain Name)"))
|
||||
o:depends({ [_n("outbound_node")] = "_socks" })
|
||||
o:depends({ [_n("outbound_node")] = "_http" })
|
||||
|
||||
o = s:option(Value, _n("outbound_node_port"), translate("Port"))
|
||||
o.datatype = "port"
|
||||
o:depends({ [_n("outbound_node")] = "_socks" })
|
||||
o:depends({ [_n("outbound_node")] = "_http" })
|
||||
|
||||
o = s:option(Value, _n("outbound_node_username"), translate("Username"))
|
||||
o:depends({ [_n("outbound_node")] = "_socks" })
|
||||
o:depends({ [_n("outbound_node")] = "_http" })
|
||||
|
||||
o = s:option(Value, _n("outbound_node_password"), translate("Password"))
|
||||
o.password = true
|
||||
o:depends({ [_n("outbound_node")] = "_socks" })
|
||||
o:depends({ [_n("outbound_node")] = "_http" })
|
||||
|
||||
o = s:option(Value, _n("outbound_node_iface"), translate("Interface"))
|
||||
o:depends({ [_n("outbound_node")] = "_iface" })
|
||||
local netdev_list = api.get_network_devices()
|
||||
for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.datatype = "json"
|
||||
local o_validate = o.validate
|
||||
o.validate = function(self, value)
|
||||
local v = o_validate(self, value)
|
||||
if v then return v end
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local config_str = m:get(section, "config_str")
|
||||
if config_str then
|
||||
return api.base64Decode(config_str)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "config_str", api.base64Encode(value) or "")
|
||||
end
|
||||
|
||||
o = s:option(Flag, _n("log"), translate("Log"))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, _n("loglevel"), translate("Log Level"))
|
||||
o.default = "info"
|
||||
o:value("debug")
|
||||
o:value("info")
|
||||
o:value("warn")
|
||||
o:value("error")
|
||||
o:depends({ [_n("log")] = true })
|
||||
|
||||
api.luci_types(arg[1], m, s, type_name, option_prefix)
|
||||
@@ -1,40 +0,0 @@
|
||||
local m, s = ...
|
||||
|
||||
if not api.is_finded("microsocks") then
|
||||
return
|
||||
end
|
||||
|
||||
local type_name = "Socks"
|
||||
|
||||
local option_prefix = "socks_"
|
||||
|
||||
local function _n(name)
|
||||
return option_prefix .. name
|
||||
end
|
||||
|
||||
-- [[ microsocks ]]
|
||||
|
||||
s.fields["type"]:value(type_name, "Socks")
|
||||
if not s.fields["type"].default then
|
||||
s.fields["type"].default = type_name
|
||||
end
|
||||
|
||||
if s.val["type"] and s.val["type"] ~= type_name then
|
||||
return
|
||||
end
|
||||
|
||||
o = s:option(Value, _n("port"), translate("Listen Port"))
|
||||
o.datatype = "port"
|
||||
|
||||
o = s:option(Flag, _n("auth"), translate("Auth"))
|
||||
|
||||
o = s:option(ListValue, _n("user"), translate("User"))
|
||||
for i, v in ipairs(user_list) do
|
||||
o:value(v[".name"], v.username)
|
||||
end
|
||||
o:depends({ [_n("auth")] = true })
|
||||
|
||||
o = s:option(Flag, _n("log"), translate("Log"))
|
||||
o.default = "1"
|
||||
|
||||
api.luci_types(arg[1], m, s, type_name, option_prefix)
|
||||
@@ -44,7 +44,7 @@ o = s:option(Value, "wireguard_private_key", "Wireguard " .. translate("Private
|
||||
o.datatype = "base64"
|
||||
|
||||
o = s:option(DummyValue, "gen_wireguard_key")
|
||||
o.template = m:template_path("/server/server_wireguard")
|
||||
o.template = m:template_path("/server/gen_wireguard_key")
|
||||
|
||||
o = s:option(Value, "wireguard_pre_shared_key", "Wireguard " .. translate("Pre shared key"))
|
||||
o.datatype = "base64"
|
||||
|
||||
@@ -1534,102 +1534,110 @@ function return_map(map)
|
||||
return map
|
||||
end
|
||||
|
||||
function luci_types(id, m, s, type_name, option_prefix)
|
||||
function luci_types(s, s2)
|
||||
local cbi = require "luci.cbi"
|
||||
local m = s.map
|
||||
local id = s2.section
|
||||
local type_name = s2.type_name
|
||||
local option_prefix = s2.option_prefix
|
||||
local fv_type
|
||||
local field_type = s.fields["type"]
|
||||
if field_type then
|
||||
fv_type = field_type:formvalue(id)
|
||||
end
|
||||
local rewrite_option_table = {}
|
||||
for key, value in pairs(s.fields) do
|
||||
if key:find(option_prefix) == 1 then
|
||||
if not s.fields[key].not_rewrite then
|
||||
if s.fields[key].rewrite_option then
|
||||
if not rewrite_option_table[s.fields[key].rewrite_option] then
|
||||
rewrite_option_table[s.fields[key].rewrite_option] = 1
|
||||
for i, v in ipairs(s2.children) do
|
||||
local o = s2.children[i]
|
||||
o.config_option = o.option
|
||||
o.option_prefix = option_prefix
|
||||
o.option = option_prefix .. o.option
|
||||
if not o.not_rewrite then
|
||||
o.cfgvalue = function(self, section)
|
||||
-- Add a custom `custom_cfgvalue` attribute. If a custom `custom_cfgvalue` function exists, the custom `cfgvalue` logic will be used.
|
||||
if self.custom_cfgvalue then
|
||||
return self:custom_cfgvalue(section)
|
||||
else
|
||||
if self.rewrite_option then
|
||||
return m:get(section, self.rewrite_option)
|
||||
else
|
||||
rewrite_option_table[s.fields[key].rewrite_option] = rewrite_option_table[s.fields[key].rewrite_option] + 1
|
||||
return m:get(section, self.config_option)
|
||||
end
|
||||
end
|
||||
|
||||
s.fields[key].cfgvalue = function(self, section)
|
||||
-- 添加自定义 custom_cfgvalue 属性,如果有自定义的 custom_cfgvalue 函数,则使用自定义的 cfgvalue 逻辑
|
||||
if self.custom_cfgvalue then
|
||||
return self:custom_cfgvalue(section)
|
||||
end
|
||||
o.write = function(self, section, value)
|
||||
if s.fields["type"]:formvalue(id) == type_name then
|
||||
-- Add a custom `custom_write` attribute; if a custom `custom_write` function exists, then use the custom write logic.
|
||||
if self.custom_write then
|
||||
self:custom_write(section, value)
|
||||
else
|
||||
local new_val = value
|
||||
if util.instanceof(self, cbi.DynamicList) then
|
||||
local new_t = {}
|
||||
if type(value) == "table" then
|
||||
new_t = table_remove_duplicates(value)
|
||||
else
|
||||
new_t = { value }
|
||||
end
|
||||
if self.cast == "string" then
|
||||
new_val = table.concat(new_t, " ")
|
||||
else
|
||||
new_val = new_t
|
||||
end
|
||||
end
|
||||
if self.rewrite_option then
|
||||
m:set(section, self.rewrite_option, new_val)
|
||||
else
|
||||
m:set(section, self.config_option, new_val)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
o.remove = function(self, section)
|
||||
if s.fields["type"]:formvalue(id) == type_name then
|
||||
-- Add a custom `custom_remove` attribute; if a custom `custom_remove` function exists, use the custom remove logic.
|
||||
if self.custom_remove then
|
||||
self:custom_remove(section)
|
||||
else
|
||||
if self.rewrite_option then
|
||||
return m:get(section, self.rewrite_option)
|
||||
m:del(section, self.rewrite_option)
|
||||
else
|
||||
if self.option:find(option_prefix) == 1 then
|
||||
return m:get(section, self.option:sub(1 + #option_prefix))
|
||||
end
|
||||
m:del(section, self.config_option)
|
||||
end
|
||||
end
|
||||
end
|
||||
s.fields[key].write = function(self, section, value)
|
||||
if s.fields["type"]:formvalue(id) == type_name then
|
||||
-- 添加自定义 custom_write 属性,如果有自定义的 custom_write 函数,则使用自定义的 write 逻辑
|
||||
if self.custom_write then
|
||||
self:custom_write(section, value)
|
||||
else
|
||||
local new_val = value
|
||||
if util.instanceof(self, cbi.DynamicList) then
|
||||
local new_t = {}
|
||||
if type(value) == "table" then
|
||||
new_t = table_remove_duplicates(value)
|
||||
else
|
||||
new_t = { value }
|
||||
end
|
||||
if self.cast == "string" then
|
||||
new_val = table.concat(new_t, " ")
|
||||
else
|
||||
new_val = new_t
|
||||
end
|
||||
end
|
||||
if self.rewrite_option then
|
||||
m:set(section, self.rewrite_option, new_val)
|
||||
else
|
||||
if self.option:find(option_prefix) == 1 then
|
||||
m:set(section, self.option:sub(1 + #option_prefix), new_val)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
s.fields[key].remove = function(self, section)
|
||||
if s.fields["type"]:formvalue(id) == type_name then
|
||||
-- 添加自定义 custom_remove 属性,如果有自定义的 custom_remove 函数,则使用自定义的 remove 逻辑
|
||||
if self.custom_remove then
|
||||
self:custom_remove(section)
|
||||
else
|
||||
if self.rewrite_option and rewrite_option_table[self.rewrite_option] == 1 then
|
||||
m:del(section, self.rewrite_option)
|
||||
else
|
||||
if self.option:find(option_prefix) == 1 then
|
||||
m:del(section, self.option:sub(1 + #option_prefix))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local deps = s.fields[key].deps
|
||||
if #deps > 0 then
|
||||
for index, value in ipairs(deps) do
|
||||
if not deps[index]['!reverse'] then
|
||||
deps[index]["type"] = type_name
|
||||
end
|
||||
end
|
||||
else
|
||||
s.fields[key]:depends({ type = type_name })
|
||||
end
|
||||
|
||||
if fv_type and fv_type ~= type_name then
|
||||
s.fields[key].rmempty = true
|
||||
end
|
||||
end
|
||||
|
||||
local deps = o.deps
|
||||
if #deps > 0 then
|
||||
local function process_deps(dep)
|
||||
local rewrite_deps = {}
|
||||
for k, v in pairs(dep) do
|
||||
if k:find("!") then
|
||||
rewrite_deps[k] = v
|
||||
else
|
||||
rewrite_deps[option_prefix .. k] = v
|
||||
end
|
||||
end
|
||||
if not rewrite_deps['!reverse'] then
|
||||
rewrite_deps["type"] = type_name
|
||||
end
|
||||
return rewrite_deps
|
||||
end
|
||||
for index, value in ipairs(deps) do
|
||||
local rewrite_deps = process_deps(value)
|
||||
if rewrite_deps then
|
||||
deps[index] = rewrite_deps
|
||||
end
|
||||
end
|
||||
else
|
||||
o:depends({ type = type_name })
|
||||
end
|
||||
|
||||
if fv_type and fv_type ~= type_name then
|
||||
o.rmempty = true
|
||||
end
|
||||
|
||||
s:append(o)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<%
|
||||
local map = self.map
|
||||
local api = map.api
|
||||
local prefix = self.prefix
|
||||
local prefix = self.option_prefix or ""
|
||||
-%>
|
||||
<div class="cbi-value" id="cbi-<%=self.config.."-"..section.."-"..self.option%>" data-index="<%=self.index%>" data-depends="<%=pcdata(self:deplist2json(section))%>">
|
||||
<label class="cbi-value-title"><%:Generate key%></label>
|
||||
@@ -821,6 +821,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
(_method == "xchacha20-poly1305" and "xchacha20-ietf-poly1305") or _method
|
||||
|
||||
result.method = method
|
||||
result.ss_method = method
|
||||
result.password = password
|
||||
result.tcp_fast_open = params.tfo
|
||||
result.use_finalmask = (params.fm and params.fm ~= "") and "1" or nil
|
||||
@@ -1189,6 +1190,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
result.port = content.port
|
||||
result.password = content.password
|
||||
result.method = content.encryption
|
||||
result.ss_method = content.encryption
|
||||
result.plugin = content.plugin
|
||||
result.plugin_opts = content.plugin_options
|
||||
result.group = content.airport
|
||||
|
||||
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_NAME:=luci-app-ssr-plus
|
||||
PKG_VERSION:=196
|
||||
PKG_RELEASE:=58
|
||||
PKG_RELEASE:=59
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
||||
|
||||
@@ -143,40 +143,54 @@ end
|
||||
|
||||
-- 默认的保存并应用行为
|
||||
local function apply_redirect(m)
|
||||
local tmp_uci_file = "/etc/config/" .. "shadowsocksr" .. "_redirect"
|
||||
local tmp_uci_file = "/etc/config/shadowsocksr_redirect"
|
||||
if m.redirect and m.redirect ~= "" then
|
||||
-- 如果存在临时文件,读取 URL 并通过 luci.http.redirect 跳转
|
||||
if nixio.fs.access(tmp_uci_file) then
|
||||
local redirect
|
||||
local redirect_url
|
||||
for line in io.lines(tmp_uci_file) do
|
||||
redirect = line:match("option%s+url%s+['\"]([^'\"]+)['\"]")
|
||||
if redirect and redirect ~= "" then break end
|
||||
redirect_url = line:match("option%s+url%s+['\"]([^'\"]+)['\"]")
|
||||
if redirect_url and redirect_url ~= "" then break end
|
||||
end
|
||||
if redirect and redirect ~= "" then
|
||||
luci.sys.call("/bin/rm -f " .. tmp_uci_file)
|
||||
luci.http.redirect(redirect)
|
||||
if redirect_url and redirect_url ~= "" then
|
||||
-- 使用 nixio 安全删除文件,替代外部 Shell 命令
|
||||
nixio.fs.remove(tmp_uci_file)
|
||||
-- JS 版 LuCI 依赖 luci.http.redirect 向前端重定向
|
||||
luci.http.redirect(redirect_url)
|
||||
return
|
||||
end
|
||||
else
|
||||
-- 创建标记文件
|
||||
nixio.fs.writefile(tmp_uci_file, "config redirect\n")
|
||||
end
|
||||
-- 挂载保存回调
|
||||
local old_on_after_save = m.on_after_save
|
||||
m.on_after_save = function(self)
|
||||
local redirect = self.redirect
|
||||
if redirect and redirect ~= "" then
|
||||
m.uci:set("shadowsocksr" .. "_redirect", "@redirect[0]", "url", redirect)
|
||||
if old_on_after_save then old_on_after_save(self) end
|
||||
local target = self.redirect
|
||||
if target and target ~= "" then
|
||||
-- 使用标准的 nixio.fs 写入文件,保持格式一致
|
||||
local content = string.format("config redirect\n\toption url '%s'\n", target)
|
||||
nixio.fs.writefile(tmp_uci_file, content)
|
||||
end
|
||||
end
|
||||
else
|
||||
luci.sys.call("/bin/rm -f " .. tmp_uci_file)
|
||||
-- 不满足重定向条件时清理文件
|
||||
if nixio.fs.access(tmp_uci_file) then
|
||||
nixio.fs.remove(tmp_uci_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function set_apply_on_parse(map)
|
||||
if not map then return end
|
||||
if is_js_luci() then
|
||||
apply_redirect(map)
|
||||
local old = map.on_after_save
|
||||
map.on_after_save = function(self)
|
||||
if old then old(self) end
|
||||
--map:set("@global[0]", "timestamp", os.time())
|
||||
local function set_apply_on_parse(m)
|
||||
if not m then return end
|
||||
-- is_js_luci 保护
|
||||
if is_js_luci and is_js_luci() then
|
||||
apply_redirect(m)
|
||||
local old_on_after_save = m.on_after_save
|
||||
m.on_after_save = function(self)
|
||||
if old_on_after_save then old_on_after_save(self) end
|
||||
-- m:set("@global[0]", "timestamp", os.time())
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -345,15 +359,12 @@ if not sid or m.uci:get("shadowsocksr", sid) ~= "servers" then
|
||||
return
|
||||
end
|
||||
-- 保存&应用成功后跳转到节点列表
|
||||
set_apply_on_parse(m)
|
||||
local old_after_save = m.on_after_save
|
||||
m.on_after_save = function(self)
|
||||
if old_after_save then old_after_save(self) end
|
||||
set_apply_on_parse(m, function(self)
|
||||
local node_type = self.uci:get("shadowsocksr", sid, "type")
|
||||
if node_type == "clash" then
|
||||
luci.sys.call(string.format("/etc/init.d/shadowsocksr clash_cache %s >/dev/null 2>&1 &", sid))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local server_header = Template("/shadowsocksr/server_header")
|
||||
server_header.section = sid
|
||||
|
||||
@@ -29,42 +29,56 @@ local function is_js_luci()
|
||||
return luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0
|
||||
end
|
||||
|
||||
-- 保存并应用行为
|
||||
-- 默认的保存并应用行为
|
||||
local function apply_redirect(m)
|
||||
local tmp_uci_file = "/etc/config/" .. "shadowsocksr" .. "_redirect"
|
||||
local tmp_uci_file = "/etc/config/shadowsocksr_redirect"
|
||||
if m.redirect and m.redirect ~= "" then
|
||||
-- 如果存在临时文件,读取 URL 并通过 luci.http.redirect 跳转
|
||||
if nixio.fs.access(tmp_uci_file) then
|
||||
local redirect
|
||||
local redirect_url
|
||||
for line in io.lines(tmp_uci_file) do
|
||||
redirect = line:match("option%s+url%s+['\"]([^'\"]+)['\"]")
|
||||
if redirect and redirect ~= "" then break end
|
||||
redirect_url = line:match("option%s+url%s+['\"]([^'\"]+)['\"]")
|
||||
if redirect_url and redirect_url ~= "" then break end
|
||||
end
|
||||
if redirect and redirect ~= "" then
|
||||
luci.sys.call("/bin/rm -f " .. tmp_uci_file)
|
||||
luci.http.redirect(redirect)
|
||||
if redirect_url and redirect_url ~= "" then
|
||||
-- 使用 nixio 安全删除文件,替代外部 Shell 命令
|
||||
nixio.fs.remove(tmp_uci_file)
|
||||
-- JS 版 LuCI 依赖 luci.http.redirect 向前端重定向
|
||||
luci.http.redirect(redirect_url)
|
||||
return
|
||||
end
|
||||
else
|
||||
-- 创建标记文件
|
||||
nixio.fs.writefile(tmp_uci_file, "config redirect\n")
|
||||
end
|
||||
-- 挂载保存回调
|
||||
local old_on_after_save = m.on_after_save
|
||||
m.on_after_save = function(self)
|
||||
local redirect = self.redirect
|
||||
if redirect and redirect ~= "" then
|
||||
m.uci:set("shadowsocksr" .. "_redirect", "@redirect[0]", "url", redirect)
|
||||
if old_on_after_save then old_on_after_save(self) end
|
||||
local target = self.redirect
|
||||
if target and target ~= "" then
|
||||
-- 使用标准的 nixio.fs 写入文件,保持格式一致
|
||||
local content = string.format("config redirect\n\toption url '%s'\n", target)
|
||||
nixio.fs.writefile(tmp_uci_file, content)
|
||||
end
|
||||
end
|
||||
else
|
||||
luci.sys.call("/bin/rm -f " .. tmp_uci_file)
|
||||
-- 不满足重定向条件时清理文件
|
||||
if nixio.fs.access(tmp_uci_file) then
|
||||
nixio.fs.remove(tmp_uci_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function set_apply_on_parse(map)
|
||||
if not map then return end
|
||||
if is_js_luci() then
|
||||
apply_redirect(map)
|
||||
local old = map.on_after_save
|
||||
map.on_after_save = function(self)
|
||||
if old then old(self) end
|
||||
-- map:set("@global[0]", "timestamp", os.time())
|
||||
local function set_apply_on_parse(m)
|
||||
if not m then return end
|
||||
-- is_js_luci 保护
|
||||
if is_js_luci and is_js_luci() then
|
||||
apply_redirect(m)
|
||||
local old_on_after_save = m.on_after_save
|
||||
m.on_after_save = function(self)
|
||||
if old_on_after_save then old_on_after_save(self) end
|
||||
-- m:set("@global[0]", "timestamp", os.time())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -10,8 +10,8 @@ LUCI_TITLE:=Antitethering module for luci-app-firewall
|
||||
#LUCI_DEPENDS:= +luci-app-firewall
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_VERSION:=0.0.6
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=0.0.7
|
||||
PKG_RELEASE:=4
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
|
||||
@@ -12,6 +12,26 @@
|
||||
|
||||
var briefInfo = _('In Method proxy Proxy server must be configured in transparent mode.<br />Default on lan ipaddress interface and port 3128 tcp.<br />Disable masquerade recommended.');
|
||||
|
||||
function packageInstalled(name) {
|
||||
return Promise.all([
|
||||
fs.stat('/usr/lib/opkg/info/' + name + '.list').catch(function() {
|
||||
return false;
|
||||
}),
|
||||
fs.stat('/lib/apk/packages/' + name + '.list').catch(function() {
|
||||
return false;
|
||||
})
|
||||
]).then(function(installed) {
|
||||
return installed[0] || installed[1];
|
||||
});
|
||||
}
|
||||
|
||||
function anyPackageInstalled(packages) {
|
||||
return Promise.all(packages.map(packageInstalled))
|
||||
.then(function(installed) {
|
||||
return installed.some(Boolean);
|
||||
});
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
|
||||
render: function() {
|
||||
@@ -32,46 +52,50 @@ return view.extend({
|
||||
o.nocreate = true;
|
||||
o.optional = true;
|
||||
|
||||
o = s.option(form.ListValue, 'method', _('Method'),
|
||||
_('TTL method outgoing interface<br />Proxy method incoming interface'));
|
||||
o.value('ttl', 'TTL');
|
||||
o.value('proxy', 'Proxy');
|
||||
return anyPackageInstalled(['3proxy', 'squid', 'redsocks']).then(function(proxyInstalled) {
|
||||
o = s.option(form.ListValue, 'method', _('Method'),
|
||||
_('TTL method outgoing interface<br />Proxy method incoming interface'));
|
||||
o.value('ttl', 'TTL');
|
||||
|
||||
o = s.option(form.Flag, 'advanced', _('Advanced Option'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
if (proxyInstalled)
|
||||
o.value('proxy', 'Proxy');
|
||||
|
||||
o = s.option(form.ListValue, 'inet', _('Inet Family'));
|
||||
o.value('ipv4', 'IPv4');
|
||||
o.value('ipv6', 'IPv6');
|
||||
o.value('ipv4v6', _('Both'));
|
||||
o.rmempty = true;
|
||||
o.editable = true;
|
||||
o.depends('advanced', '1');
|
||||
o = s.option(form.Flag, 'advanced', _('Advanced Option'));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.Value, 'ttl', _('TTL Value'),
|
||||
_('Select TTL value. Range 1 - 255'));
|
||||
o.value('64','64')
|
||||
o.value('128','128')
|
||||
o.default = '64';
|
||||
o.rmempty = true;
|
||||
o.editable = true;
|
||||
o.depends({advanced: '1', method: /ttl/});
|
||||
o = s.option(form.ListValue, 'inet', _('Inet Family'));
|
||||
o.value('ipv4', 'IPv4');
|
||||
o.value('ipv6', 'IPv6');
|
||||
o.value('ipv4v6', _('Both'));
|
||||
o.rmempty = true;
|
||||
o.editable = true;
|
||||
o.depends('advanced', '1');
|
||||
|
||||
o = s.option(form.Value, 'ports', _('Ports'),
|
||||
_('Incoming ports route to proxy-server<br />Custom ports range: 0-65535'));
|
||||
o.editable = true;
|
||||
o.rmempty = true;
|
||||
o.value('all', _('ALL Ports'));
|
||||
o.value('http', _('HTTP Ports'));
|
||||
o.default = 'all';
|
||||
o.depends({advanced: '1', method: /proxy/})
|
||||
o = s.option(form.Value, 'ttl', _('TTL Value'),
|
||||
_('Select TTL value. Range 1 - 255'));
|
||||
o.value('64', '64');
|
||||
o.value('128', '128');
|
||||
o.default = '64';
|
||||
o.rmempty = true;
|
||||
o.editable = true;
|
||||
o.depends({ advanced: '1', method: /ttl/ });
|
||||
|
||||
o = s.option(form.Value, 'proxy', _('Proxy Server Address'),
|
||||
_('IP-address proxy<br/>Format: <code>ipaddress:port</code><br />If not defined: use selected interface address.<br />Default: <code>lan</code> interface ipaddress'));
|
||||
o.datatype = 'ipaddrport';
|
||||
o.depends({advanced: '1', method: /proxy/})
|
||||
o = s.option(form.Value, 'ports', _('Ports'),
|
||||
_('Incoming ports route to proxy-server<br />Custom ports range: 0-65535'));
|
||||
o.editable = true;
|
||||
o.rmempty = true;
|
||||
o.value('all', _('ALL Ports'));
|
||||
o.value('http', _('HTTP Ports'));
|
||||
o.default = 'all';
|
||||
o.depends({ advanced: '1', method: /proxy/ });
|
||||
|
||||
return m.render();
|
||||
o = s.option(form.Value, 'proxy', _('Proxy Server Address'),
|
||||
_('IP-address proxy<br/>Format: <code>ipaddress:port</code><br />If not defined: use selected interface address.<br />Default: <code>lan</code> interface ipaddress'));
|
||||
o.datatype = 'ipaddrport';
|
||||
o.depends({ advanced: '1', method: /proxy/ });
|
||||
|
||||
return m.render();
|
||||
});
|
||||
},
|
||||
});
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=mhz
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL=https://github.com/wtarreau/mhz.git
|
||||
PKG_SOURCE_DATE:=2025-07-26
|
||||
PKG_SOURCE_VERSION:=512f63021f6f0cec8e5e13be744dba0b4704cfae
|
||||
PKG_SOURCE_VERSION:=null
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_MAINTAINER:=Robert Marko <robimarko@gmail.com>
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=nexttrace
|
||||
PKG_VERSION:=1.7.2
|
||||
PKG_RELEASE:=13
|
||||
PKG_VERSION:=1.6.2
|
||||
PKG_RELEASE:=14
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/nxtrace/NTrace-core/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
+2
-2
@@ -8,8 +8,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ooniprobe
|
||||
PKG_VERSION:=3.30.0
|
||||
PKG_RELEASE:=6
|
||||
PKG_VERSION:=3.16.5
|
||||
PKG_RELEASE:=7
|
||||
|
||||
PKG_SOURCE:=probe-cli-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/ooni/probe-cli/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
+3
-3
@@ -8,12 +8,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=torrserver
|
||||
PKG_VERSION:=MatriX.142.4
|
||||
PKG_RELEASE:=16
|
||||
PKG_VERSION:=MatriX.143
|
||||
PKG_RELEASE:=17
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/YouROK/TorrServer.git
|
||||
PKG_SOURCE_VERSION:=9b611cf7022b60f4385392ce37c83793235fe5c6
|
||||
PKG_SOURCE_VERSION:=5c812ec1abe4c8e0acee29d820f78205a8f0e7b7
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
||||
|
||||
Reference in new issue
Block a user