mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-27 02:11:19 +08:00
🦄 Sync 2026-07-16 04:24:09
This commit is contained in:
parent
58b9b357a3
commit
ddc3d78236
@ -5,12 +5,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=cloudreve
|
||||
PKG_VERSION:=4.17.0
|
||||
PKG_RELEASE:=13
|
||||
PKG_VERSION:=4.18.0
|
||||
PKG_RELEASE:=14
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/cloudreve/Cloudreve.git
|
||||
PKG_SOURCE_VERSION:=d4a145c3ac68f08b9071dec0f2ac025c6dc7362d
|
||||
PKG_SOURCE_VERSION:=0bb0ab833571d380153edd3529e01a7957b8b4ce
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-3.0-only
|
||||
|
||||
@ -461,25 +461,26 @@ function renderListeners(s, uciconfig, isClient) {
|
||||
o.depends('type', 'tuic');
|
||||
o.modalonly = true;
|
||||
|
||||
/* Hysteria2 fields */
|
||||
o = s.taboption('field_general', form.Value, 'hysteria_up_mbps', _('Max upload speed'),
|
||||
/* Brutal fields */
|
||||
o = s.taboption('field_general', form.Value, 'brutal_up_mbps', _('Max upload speed'),
|
||||
_('In Mbps.'));
|
||||
o.datatype = 'uinteger';
|
||||
o.depends('type', 'hysteria2');
|
||||
o.depends({type: /^(hysteria2|shadowquic)$/});
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('field_general', form.Value, 'hysteria_down_mbps', _('Max download speed'),
|
||||
o = s.taboption('field_general', form.Value, 'brutal_down_mbps', _('Max download speed'),
|
||||
_('In Mbps.'));
|
||||
o.datatype = 'uinteger';
|
||||
o.depends('type', 'hysteria2');
|
||||
o.depends({type: /^(hysteria2|shadowquic)$/});
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('field_general', form.Flag, 'hysteria_ignore_client_bandwidth', _('Ignore client bandwidth'),
|
||||
o = s.taboption('field_general', form.Flag, 'brutal_ignore_client_bandwidth', _('Ignore client bandwidth'),
|
||||
_('Tell the client to use the BBR flow control algorithm instead of Hysteria CC.'));
|
||||
o.default = o.disabled;
|
||||
o.depends({type: 'hysteria2', hysteria_up_mbps: '', hysteria_down_mbps: ''});
|
||||
o.depends({type: /^(hysteria2|shadowquic)$/, brutal_up_mbps: '', brutal_down_mbps: ''});
|
||||
o.modalonly = true;
|
||||
|
||||
/* Hysteria2 fields */
|
||||
o = s.taboption('field_general', form.ListValue, 'hysteria_obfs_type', _('Obfuscate type'));
|
||||
o.value('', _('Disable'));
|
||||
o.value('salamander', _('Salamander'));
|
||||
@ -638,6 +639,7 @@ function renderListeners(s, uciconfig, isClient) {
|
||||
o = s.taboption('field_general', form.Flag, 'plugin', _('Plugin'));
|
||||
o.default = o.disabled;
|
||||
o.depends({type: /^(shadowsocks|snell)$/});
|
||||
o.depends({type: /^(vmess|vless|trojan|anytls)$/});
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('field_plugin', form.ListValue, 'plugin_type', _('Plugin type'));
|
||||
@ -651,9 +653,12 @@ function renderListeners(s, uciconfig, isClient) {
|
||||
|
||||
if (value) {
|
||||
if (type === 'snell' && !['obfs', 'shadow-tls'].includes(value)) {
|
||||
return _('Expecting: only support %s.').format(_('obfs-simple') +
|
||||
return _('Expecting: Only support %s.').format(_('obfs-simple') +
|
||||
' / ' + _('ShadowTLS'));
|
||||
}
|
||||
if (['vmess', 'vless', 'trojan', 'anytls'].includes(type) && !['jls'].includes(value)) {
|
||||
return _('Expecting: Only support %s.').format(_('JLS'));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -1045,6 +1050,10 @@ function renderListeners(s, uciconfig, isClient) {
|
||||
|
||||
switch (type) {
|
||||
case 'shadowsocks':
|
||||
case 'vmess':
|
||||
case 'vless':
|
||||
case 'trojan':
|
||||
case 'anytls':
|
||||
def_alpn = ['h2', 'http/1.1']; // when plugin_type in ['jls']
|
||||
break;
|
||||
case 'tuic':
|
||||
@ -1079,8 +1088,21 @@ function renderListeners(s, uciconfig, isClient) {
|
||||
o = s.taboption('field_tls', form.Value, 'tls_cert_path', _('Certificate path'),
|
||||
_('The %s public key, in PEM format.').format(_('Server')));
|
||||
o.value('/etc/fchomo/certs/server_publickey.pem');
|
||||
o.depends({tls: '1', tls_reality: '0', type: /^(http|socks|mixed|vmess|vless|trojan|anytls|tuic|hysteria2|hysteria2-realm|trusttunnel)$/});
|
||||
o.rmempty = false;
|
||||
o.validate = function(section_id, value) {
|
||||
const plugin_type = this.section.getOption('plugin_type').formvalue(section_id);
|
||||
const tls_reality = this.section.getOption('tls_reality').formvalue(section_id);
|
||||
|
||||
if (plugin_type === 'jls' || tls_reality == 1) {
|
||||
if (value)
|
||||
return _('Expecting: Keep empty when %s is enabled.').format(_('JLS') +
|
||||
' / ' + _('REALITY'));
|
||||
} else if (!value) {
|
||||
return _('Expecting: Cannot be empty.');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
o.depends({tls: '1', type: /^(http|socks|mixed|vmess|vless|trojan|anytls|tuic|hysteria2|hysteria2-realm|trusttunnel)$/});
|
||||
o.modalonly = true;
|
||||
|
||||
o = s.taboption('field_tls', form.Button, '_upload_cert', _('Upload certificate'),
|
||||
@ -1181,6 +1203,15 @@ function renderListeners(s, uciconfig, isClient) {
|
||||
// uTLS fields
|
||||
o = s.taboption('field_tls', form.Flag, 'tls_reality', _('REALITY'));
|
||||
o.default = o.disabled;
|
||||
o.validate = function(section_id, value) {
|
||||
const plugin_type = this.section.getOption('plugin_type').formvalue(section_id);
|
||||
value = this.formvalue(section_id);
|
||||
|
||||
if (value == 1 && plugin_type === 'jls')
|
||||
return _('Expecting: Cannot be enabled when %s is enabled.').format(_('JLS'));
|
||||
|
||||
return true;
|
||||
}
|
||||
o.depends('tls', '1');
|
||||
o.modalonly = true;
|
||||
|
||||
@ -1243,14 +1274,14 @@ function renderListeners(s, uciconfig, isClient) {
|
||||
switch (type) {
|
||||
case 'vless':
|
||||
if (!['grpc', 'ws', 'xhttp'].includes(value))
|
||||
return _('Expecting: only support %s.').format(_('gRPC') +
|
||||
return _('Expecting: Only support %s.').format(_('gRPC') +
|
||||
' / ' + _('WebSocket') +
|
||||
' / ' + _('XHTTP'));
|
||||
break;
|
||||
case 'vmess':
|
||||
case 'trojan':
|
||||
if (!['grpc', 'ws'].includes(value))
|
||||
return _('Expecting: only support %s.').format(_('gRPC') +
|
||||
return _('Expecting: Only support %s.').format(_('gRPC') +
|
||||
' / ' + _('WebSocket'));
|
||||
break;
|
||||
default:
|
||||
|
||||
@ -667,6 +667,19 @@ return view.extend({
|
||||
so.depends('type', 'tuic');
|
||||
so.modalonly = true;
|
||||
|
||||
/* Brutal fields */
|
||||
so = ss.taboption('field_general', form.Value, 'brutal_up_mbps', _('Max upload speed'),
|
||||
_('In Mbps.'));
|
||||
so.datatype = 'uinteger';
|
||||
so.depends({type: /^(hysteria|hysteria2|shadowquic)$/});
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.taboption('field_general', form.Value, 'brutal_down_mbps', _('Max download speed'),
|
||||
_('In Mbps.'));
|
||||
so.datatype = 'uinteger';
|
||||
so.depends({type: /^(hysteria|hysteria2|shadowquic)$/});
|
||||
so.modalonly = true;
|
||||
|
||||
/* Hysteria / Hysteria2 fields */
|
||||
so = ss.taboption('field_general', form.DynamicList, 'hysteria_ports', _('Ports pool'));
|
||||
so.datatype = 'or(port, portrange)';
|
||||
@ -679,18 +692,6 @@ return view.extend({
|
||||
so.depends('type', 'hysteria2');
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.taboption('field_general', form.Value, 'hysteria_up_mbps', _('Max upload speed'),
|
||||
_('In Mbps.'));
|
||||
so.datatype = 'uinteger';
|
||||
so.depends({type: /^(hysteria|hysteria2)$/});
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.taboption('field_general', form.Value, 'hysteria_down_mbps', _('Max download speed'),
|
||||
_('In Mbps.'));
|
||||
so.datatype = 'uinteger';
|
||||
so.depends({type: /^(hysteria|hysteria2)$/});
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.taboption('field_general', form.ListValue, 'hysteria_obfs_type', _('Obfuscate type'));
|
||||
so.value('', _('Disable'));
|
||||
so.value('salamander', _('Salamander'));
|
||||
@ -716,17 +717,6 @@ return view.extend({
|
||||
so.depends('hysteria_obfs_type', 'gecko');
|
||||
so.modalonly = true;
|
||||
|
||||
/* TrustTunnel fields */
|
||||
so = ss.taboption('field_general', form.Flag, 'trusttunnel_health_check', _('Health check'));
|
||||
so.default = so.enabled;
|
||||
so.depends('type', 'trusttunnel');
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.taboption('field_general', form.Flag, 'trusttunnel_quic', _('QUIC'));
|
||||
so.default = so.disabled;
|
||||
so.depends('type', 'trusttunnel');
|
||||
so.modalonly = true;
|
||||
|
||||
/* ShadowQUIC fields */
|
||||
so = ss.taboption('field_general', form.DynamicList, 'shadowquic_quic_versions', _('QUIC versions'),
|
||||
_('Support %s, default %s.').format('v1/v2', 'v1'));
|
||||
@ -752,6 +742,17 @@ return view.extend({
|
||||
so.depends('type', 'shadowquic');
|
||||
so.modalonly = true;
|
||||
|
||||
/* TrustTunnel fields */
|
||||
so = ss.taboption('field_general', form.Flag, 'trusttunnel_health_check', _('Health check'));
|
||||
so.default = so.enabled;
|
||||
so.depends('type', 'trusttunnel');
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.taboption('field_general', form.Flag, 'trusttunnel_quic', _('QUIC'));
|
||||
so.default = so.disabled;
|
||||
so.depends('type', 'trusttunnel');
|
||||
so.modalonly = true;
|
||||
|
||||
/* WireGuard fields */
|
||||
so = ss.taboption('field_general', form.Value, 'wireguard_ip', _('Local address'),
|
||||
_('The %s address used by local machine in the Wireguard network.').format('IPv4'));
|
||||
@ -982,7 +983,7 @@ return view.extend({
|
||||
|
||||
if (value) {
|
||||
if (type === 'snell' && !['obfs', 'shadow-tls'].includes(value)) {
|
||||
return _('Expecting: only support %s.').format(_('obfs-simple') +
|
||||
return _('Expecting: Only support %s.').format(_('obfs-simple') +
|
||||
' / ' + _('ShadowTLS'));
|
||||
}
|
||||
}
|
||||
@ -1166,6 +1167,14 @@ return view.extend({
|
||||
|
||||
so = ss.taboption('field_tls', form.Value, 'tls_sni', _('TLS SNI'),
|
||||
_('Hostname that the client attempts to connect to at the start of the TLS handshake process.'));
|
||||
so.validate = function(section_id, value) {
|
||||
const tls_jls = this.section.getOption('tls_jls').formvalue(section_id);
|
||||
|
||||
if (tls_jls == 1 && !value)
|
||||
return _('Expecting: %s cannot be empty when %s is enabled.').format(_('TLS SNI'), _('JLS'));
|
||||
|
||||
return true;
|
||||
};
|
||||
so.depends({tls: '1', type: /^(http|vmess|vless|trojan|anytls|hysteria|hysteria2|shadowquic|trusttunnel|masque)$/});
|
||||
so.depends({tls: '1', type: /^(tuic)$/, tls_disable_sni: '0'});
|
||||
so.modalonly = true;
|
||||
@ -1300,9 +1309,24 @@ return view.extend({
|
||||
so.depends({type: 'ss', plugin_type: 'jls'});
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.taboption('field_tls', form.Flag, 'tls_jls', _('JLS'));
|
||||
so.default = so.disabled;
|
||||
so.depends({tls: '1', type: /^(vmess|vless|trojan|anytls)$/});
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.taboption('field_tls', form.Value, 'tls_jls_username', _('JLS username'));
|
||||
so.rmempty = false;
|
||||
so.depends('tls_jls', '1');
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.taboption('field_tls', form.Value, 'tls_jls_password', _('JLS password'));
|
||||
so.rmempty = false;
|
||||
so.depends('tls_jls', '1');
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.taboption('field_tls', form.Flag, 'tls_reality', _('REALITY'));
|
||||
so.default = so.disabled;
|
||||
so.depends({tls: '1', type: /^(vmess|vless|trojan)$/});
|
||||
so.depends({tls: '1', tls_jls: '0', type: /^(vmess|vless|trojan)$/});
|
||||
so.modalonly = true;
|
||||
|
||||
so = ss.taboption('field_tls', form.Value, 'tls_reality_public_key', _('REALITY public key'));
|
||||
@ -1344,14 +1368,14 @@ return view.extend({
|
||||
switch (type) {
|
||||
case 'vmess':
|
||||
if (!['http', 'h2', 'grpc', 'ws'].includes(value))
|
||||
return _('Expecting: only support %s.').format(_('HTTP') +
|
||||
return _('Expecting: Only support %s.').format(_('HTTP') +
|
||||
' / ' + _('HTTPUpgrade') +
|
||||
' / ' + _('gRPC') +
|
||||
' / ' + _('WebSocket'));
|
||||
break;
|
||||
case 'vless':
|
||||
if (!['http', 'h2', 'grpc', 'ws', 'xhttp'].includes(value))
|
||||
return _('Expecting: only support %s.').format(_('HTTP') +
|
||||
return _('Expecting: Only support %s.').format(_('HTTP') +
|
||||
' / ' + _('HTTPUpgrade') +
|
||||
' / ' + _('gRPC') +
|
||||
' / ' + _('WebSocket') +
|
||||
@ -1359,7 +1383,7 @@ return view.extend({
|
||||
break;
|
||||
case 'trojan':
|
||||
if (!['grpc', 'ws'].includes(value))
|
||||
return _('Expecting: only support %s.').format(_('gRPC') +
|
||||
return _('Expecting: Only support %s.').format(_('gRPC') +
|
||||
' / ' + _('WebSocket'));
|
||||
break;
|
||||
default:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -25,6 +25,30 @@ migrate() {
|
||||
uci_set "$CONF" "$1" max_idle_time "$tuic_max_idle_time"
|
||||
fi
|
||||
|
||||
# hysteria_up_mbps -> brutal_up_mbps
|
||||
if isDefined "$1" hysteria_up_mbps; then
|
||||
local hysteria_up_mbps
|
||||
config_get hysteria_up_mbps "$1" hysteria_up_mbps ""
|
||||
uci_remove "$CONF" "$1" hysteria_up_mbps
|
||||
uci_set "$CONF" "$1" brutal_up_mbps "$hysteria_up_mbps"
|
||||
fi
|
||||
|
||||
# hysteria_down_mbps -> brutal_down_mbps
|
||||
if isDefined "$1" hysteria_down_mbps; then
|
||||
local hysteria_down_mbps
|
||||
config_get hysteria_down_mbps "$1" hysteria_down_mbps ""
|
||||
uci_remove "$CONF" "$1" hysteria_down_mbps
|
||||
uci_set "$CONF" "$1" brutal_down_mbps "$hysteria_down_mbps"
|
||||
fi
|
||||
|
||||
# hysteria_ignore_client_bandwidth -> brutal_ignore_client_bandwidth
|
||||
if isDefined "$1" hysteria_ignore_client_bandwidth; then
|
||||
local hysteria_ignore_client_bandwidth
|
||||
config_get hysteria_ignore_client_bandwidth "$1" hysteria_ignore_client_bandwidth ""
|
||||
uci_remove "$CONF" "$1" hysteria_ignore_client_bandwidth
|
||||
uci_set "$CONF" "$1" brutal_ignore_client_bandwidth "$hysteria_ignore_client_bandwidth"
|
||||
fi
|
||||
|
||||
# plugin -> plugin_type
|
||||
if isDefined "$1" plugin; then
|
||||
local plugin
|
||||
|
||||
@ -59,6 +59,22 @@ migrate() {
|
||||
uci_set "$CONF" "$1" max_open_streams "$tuic_max_open_streams"
|
||||
fi
|
||||
|
||||
# hysteria_up_mbps -> brutal_up_mbps
|
||||
if isDefined "$1" hysteria_up_mbps; then
|
||||
local hysteria_up_mbps
|
||||
config_get hysteria_up_mbps "$1" hysteria_up_mbps ""
|
||||
uci_remove "$CONF" "$1" hysteria_up_mbps
|
||||
uci_set "$CONF" "$1" brutal_up_mbps "$hysteria_up_mbps"
|
||||
fi
|
||||
|
||||
# hysteria_down_mbps -> brutal_down_mbps
|
||||
if isDefined "$1" hysteria_down_mbps; then
|
||||
local hysteria_down_mbps
|
||||
config_get hysteria_down_mbps "$1" hysteria_down_mbps ""
|
||||
uci_remove "$CONF" "$1" hysteria_down_mbps
|
||||
uci_set "$CONF" "$1" brutal_down_mbps "$hysteria_down_mbps"
|
||||
fi
|
||||
|
||||
# plugin -> plugin_type
|
||||
if isDefined "$1" plugin; then
|
||||
local plugin
|
||||
|
||||
@ -33,6 +33,30 @@ migrate() {
|
||||
uci_set "$CONF" "$1" max_idle_time "$tuic_max_idle_time"
|
||||
fi
|
||||
|
||||
# hysteria_up_mbps -> brutal_up_mbps
|
||||
if isDefined "$1" hysteria_up_mbps; then
|
||||
local hysteria_up_mbps
|
||||
config_get hysteria_up_mbps "$1" hysteria_up_mbps ""
|
||||
uci_remove "$CONF" "$1" hysteria_up_mbps
|
||||
uci_set "$CONF" "$1" brutal_up_mbps "$hysteria_up_mbps"
|
||||
fi
|
||||
|
||||
# hysteria_down_mbps -> brutal_down_mbps
|
||||
if isDefined "$1" hysteria_down_mbps; then
|
||||
local hysteria_down_mbps
|
||||
config_get hysteria_down_mbps "$1" hysteria_down_mbps ""
|
||||
uci_remove "$CONF" "$1" hysteria_down_mbps
|
||||
uci_set "$CONF" "$1" brutal_down_mbps "$hysteria_down_mbps"
|
||||
fi
|
||||
|
||||
# hysteria_ignore_client_bandwidth -> brutal_ignore_client_bandwidth
|
||||
if isDefined "$1" hysteria_ignore_client_bandwidth; then
|
||||
local hysteria_ignore_client_bandwidth
|
||||
config_get hysteria_ignore_client_bandwidth "$1" hysteria_ignore_client_bandwidth ""
|
||||
uci_remove "$CONF" "$1" hysteria_ignore_client_bandwidth
|
||||
uci_set "$CONF" "$1" brutal_ignore_client_bandwidth "$hysteria_ignore_client_bandwidth"
|
||||
fi
|
||||
|
||||
# plugin -> plugin_type
|
||||
if isDefined "$1" plugin; then
|
||||
local plugin
|
||||
|
||||
@ -573,11 +573,13 @@ uci.foreach(uciconf, ucinode, (cfg) => {
|
||||
"reduce-rtt": strToBool(cfg.tuic_reduce_rtt),
|
||||
"request-timeout": strToInt(cfg.tuic_request_timeout) || null,
|
||||
|
||||
/* Brutal */
|
||||
up: strToInt(cfg.brutal_up_mbps),
|
||||
down: strToInt(cfg.brutal_down_mbps),
|
||||
|
||||
/* Hysteria / Hysteria2 */
|
||||
ports: isEmpty(cfg.hysteria_ports) ? null : join(',', cfg.hysteria_ports),
|
||||
"hop-interval": strToInt(cfg.hysteria_hop_interval), // @DEBUG ERROR data type *utils.IntRanges[uint16]
|
||||
up: cfg.hysteria_up_mbps ? cfg.hysteria_up_mbps + ' Mbps' : null,
|
||||
down: cfg.hysteria_down_mbps ? cfg.hysteria_down_mbps + ' Mbps' : null,
|
||||
obfs: cfg.hysteria_obfs_type,
|
||||
"obfs-password": cfg.hysteria_obfs_password,
|
||||
"obfs-min-packet-size": strToInt(cfg.hysteria_obfs_min_packet_size),
|
||||
@ -682,6 +684,10 @@ uci.foreach(uciconf, ucinode, (cfg) => {
|
||||
config: cfg.tls_ech_config,
|
||||
"query-server-name": cfg.tls_ech_query_server_name
|
||||
} : null,
|
||||
"jls-opts": cfg.tls_jls === '1' ? {
|
||||
"username": cfg.tls_jls_username,
|
||||
"password": cfg.tls_jls_password
|
||||
} : null,
|
||||
"reality-opts": cfg.tls_reality === '1' ? {
|
||||
"public-key": cfg.tls_reality_public_key,
|
||||
"short-id": cfg.tls_reality_short_id,
|
||||
|
||||
@ -292,10 +292,12 @@ export function parseListener(cfg) {
|
||||
"authentication-timeout": durationToSecond(cfg.tuic_authentication_timeout),
|
||||
"max-udp-relay-packet-size": strToInt(cfg.tuic_max_udp_relay_packet_size),
|
||||
|
||||
/* Brutal */
|
||||
up: strToInt(cfg.brutal_up_mbps),
|
||||
down: strToInt(cfg.brutal_down_mbps),
|
||||
"ignore-client-bandwidth": strToBool(cfg.brutal_ignore_client_bandwidth),
|
||||
|
||||
/* Hysteria2 */
|
||||
up: strToInt(cfg.hysteria_up_mbps),
|
||||
down: strToInt(cfg.hysteria_down_mbps),
|
||||
"ignore-client-bandwidth": strToBool(cfg.hysteria_ignore_client_bandwidth),
|
||||
obfs: cfg.hysteria_obfs_type,
|
||||
"obfs-password": cfg.hysteria_obfs_password,
|
||||
"obfs-min-packet-size": strToInt(cfg.hysteria_obfs_min_packet_size),
|
||||
@ -417,7 +419,7 @@ export function parseListener(cfg) {
|
||||
|
||||
/* TLS fields */
|
||||
...(cfg.allow_insecure === '1' ? { "allow-insecure": true } : cfg.tls === '1' ? {
|
||||
alpn: cfg.tls_alpn,
|
||||
alpn: cfg.plugin_type in ['jls'] ? null : cfg.tls_alpn,
|
||||
...(cfg.tls_reality === '1' ? {
|
||||
"reality-config": {
|
||||
dest: cfg.tls_reality_dest,
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.7.1
|
||||
PKG_RELEASE:=179
|
||||
PKG_VERSION:=26.7.15
|
||||
PKG_RELEASE:=180
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@ -441,24 +441,15 @@ o = s:option(Value, _n("tls_serverName"), "SNI " .. translate("Domain"))
|
||||
o:depends({ [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
if api.compare_versions(os.date("%Y.%m.%d"), "<", "3026.6.1") then
|
||||
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, [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
end
|
||||
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"))
|
||||
|
||||
if api.compare_versions(xray_version, ">=", "26.1.31") then
|
||||
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" })
|
||||
end
|
||||
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"
|
||||
|
||||
@ -152,21 +152,9 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
security = node.stream_security,
|
||||
tlsSettings = (node.stream_security == "tls") and {
|
||||
serverName = node.tls_serverName,
|
||||
allowInsecure = (function()
|
||||
if node.tls_pinSHA256 and node.tls_pinSHA256 ~= "" then return nil end
|
||||
if node.tls_allowInsecure == "1" then return true end
|
||||
end)(),
|
||||
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
|
||||
pinnedPeerCertSha256 = (function()
|
||||
if api.compare_versions(xray_version, "<", "26.1.31") then return nil end
|
||||
if not node.tls_pinSHA256 then return "" end
|
||||
return node.tls_pinSHA256
|
||||
end)(),
|
||||
verifyPeerCertByName = (function()
|
||||
if api.compare_versions(xray_version, "<", "26.1.31") then return nil end
|
||||
if not node.tls_CertByName then return "" end
|
||||
return node.tls_CertByName
|
||||
end)(),
|
||||
pinnedPeerCertSha256 = node.tls_pinSHA256,
|
||||
verifyPeerCertByName = node.tls_CertByName,
|
||||
echConfigList = (node.ech == "1") and node.ech_config or nil,
|
||||
certificates = (node.tls_certificate == "1" and node.tls_certificate_pem ~= "") and {
|
||||
certificate = api.split(node.tls_certificate_pem, "\n"),
|
||||
@ -820,7 +808,8 @@ function gen_config_server(node)
|
||||
serverNames = node.reality_serverNames or {},
|
||||
privateKey = node.reality_private_key,
|
||||
shortIds = node.reality_shortId or "",
|
||||
mldsa65Seed = (node.use_mldsa65Seed == "1") and node.reality_mldsa65Seed or nil
|
||||
mldsa65Seed = (node.use_mldsa65Seed == "1") and node.reality_mldsa65Seed or nil,
|
||||
minClientVer = "1.0.0"
|
||||
} or nil
|
||||
end
|
||||
end
|
||||
|
||||
@ -1313,6 +1313,8 @@ local current_node = map:get(section)
|
||||
opt.set(dom_prefix + 'utls', true);
|
||||
opt.set(dom_prefix + 'fingerprint', queryParam.fp);
|
||||
}
|
||||
opt.set(dom_prefix + 'ech', !!queryParam.ech);
|
||||
opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
|
||||
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || '');
|
||||
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
|
||||
opt.set(
|
||||
|
||||
@ -1069,6 +1069,10 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
result.reality_publicKey = params.pbk or nil
|
||||
result.reality_shortId = params.sid or nil
|
||||
end
|
||||
if params.ech and params.ech ~= "" then
|
||||
result.ech = "1"
|
||||
result.ech_config = params.ech
|
||||
end
|
||||
result.tls_pinSHA256 = params.pcs
|
||||
result.tls_CertByName = params.vcn
|
||||
result.tls_allowInsecure = params.allowinsecure or params.allowInsecure or params.insecure
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall2
|
||||
PKG_VERSION:=26.7.12
|
||||
PKG_RELEASE:=62
|
||||
PKG_VERSION:=26.7.16
|
||||
PKG_RELEASE:=63
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
function is_json(str) {
|
||||
try {
|
||||
const result = JSON.parse(str);
|
||||
return typeof result === 'object' && result !== null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function is_timehhmm(str) {
|
||||
const match = String(str).match(/^(\d?\d):(\d\d)$/);
|
||||
if (match) {
|
||||
const hour = parseInt(match[1], 10);
|
||||
const minute = parseInt(match[2], 10);
|
||||
if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof cbi_validators !== 'undefined' && cbi_validators !== null) {
|
||||
cbi_validators['json'] = function() {
|
||||
return is_json(this);
|
||||
}
|
||||
cbi_validators['timehhmm'] = function() {
|
||||
return is_timehhmm(this);
|
||||
}
|
||||
} else {
|
||||
L.require('validation').then(function(validation) {
|
||||
validation.types['json'] = function() {
|
||||
return this.assert(is_json(this.value), _('Must be JSON text!'));
|
||||
}
|
||||
validation.types['timehhmm'] = function() {
|
||||
return this.assert(is_timehhmm(this.value), _('valid time (hh:mm)'));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -114,6 +114,8 @@ function index()
|
||||
|
||||
--[[geoview]]
|
||||
entry({"admin", "services", appname, "geo_view"}, call("geo_view")).leaf = true
|
||||
|
||||
entry({"admin", "services", appname, "fetch_certsha256"}, call("fetch_certsha256")).leaf = true
|
||||
end
|
||||
|
||||
function check_site(host, port)
|
||||
@ -995,3 +997,17 @@ function flush_set()
|
||||
http.redirect(api.url("log"))
|
||||
end
|
||||
end
|
||||
|
||||
function fetch_certsha256()
|
||||
local id = http.formvalue("id") or ""
|
||||
local address = (id ~= "") and uci:get(appname, id, "address") or ""
|
||||
local port = (id ~= "") and uci:get(appname, id, "port") or 0
|
||||
local sni = (id ~= "") and uci:get(appname, id, "tls_serverName") or ""
|
||||
sni = (sni ~= "") and sni or address
|
||||
if address == "" or port == 0 then
|
||||
http_write_json_error()
|
||||
return
|
||||
end
|
||||
local data = api.fetch_cert_sha256(address, port, sni, 5)
|
||||
http_write_json(data ~= "" and { code = 1, data = data } or { code = 0 })
|
||||
end
|
||||
|
||||
@ -2,6 +2,8 @@ local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
local sys = api.sys
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@ -13,7 +15,8 @@ o.rmempty = false
|
||||
o.default = false
|
||||
|
||||
-- [[ ACLs Settings ]]--
|
||||
s = m:section(TypedSection, "acl_rule")
|
||||
local cfgname = "acl_rule"
|
||||
s = m:section(TypedSection, cfgname)
|
||||
s.template = "cbi/tblsection"
|
||||
s.sortable = true
|
||||
s.anonymous = true
|
||||
@ -65,4 +68,9 @@ i.cfgvalue = function(t, n)
|
||||
return v
|
||||
end
|
||||
|
||||
return m
|
||||
local sortable = Template(appname .. "/cbi/sortable")
|
||||
sortable.api = api
|
||||
sortable.target_cfgname = cfgname
|
||||
m:append(sortable)
|
||||
|
||||
return api.return_map(m)
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@ -379,4 +381,4 @@ for k, v in pairs(nodes_table) do
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
|
||||
@ -2,6 +2,8 @@ local api = require "luci.passwall2.api"
|
||||
local com = require "luci.passwall2.com"
|
||||
local appname = api.appname
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@ -28,4 +30,4 @@ o.cfgvalue = function(t, n)
|
||||
return string.format('<font color="red">%s</font>', translate("if you want to run from memory, change the path, /tmp beginning then save the application and update it manually."))
|
||||
end
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = "passwall2"
|
||||
local appname = api.appname
|
||||
local fs = api.fs
|
||||
local uci = api.uci
|
||||
|
||||
|
||||
@ -4,6 +4,8 @@ datatypes = api.datatypes
|
||||
has_singbox = api.finded_com("sing-box")
|
||||
has_xray = api.finded_com("xray")
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@ -387,4 +389,4 @@ footer.shunt_list = api.jsonc.stringify(shunt_list)
|
||||
m:append(footer)
|
||||
m:append(Template(appname .. "/global/status_bottom"))
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
|
||||
@ -15,6 +15,8 @@ for k, e in ipairs(api.get_valid_nodes()) do
|
||||
end
|
||||
end
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@ -100,7 +102,8 @@ end
|
||||
o:depends("health_check_type", "passwall_logic")
|
||||
|
||||
-- [[ Balancing Settings ]]--
|
||||
s = m:section(TypedSection, "haproxy_config", translate("Node List"))
|
||||
local cfgname = "haproxy_config"
|
||||
s = m:section(TypedSection, cfgname, translate("Node List"))
|
||||
s.description = "<font color='red'>" ..
|
||||
translate("Add a node, Export Of Multi WAN Only support Multi Wan. Load specific gravity range 1-256. Multiple primary servers can be load balanced, standby will only be enabled when the primary server is offline! Multiple groups can be set, Haproxy port same one for each group.") .. "<br>" ..
|
||||
translate("Note that the node configuration parameters for load balancing must be consistent when use TCP health check type, otherwise it cannot be used normally!") .. "</font>"
|
||||
@ -176,6 +179,11 @@ o:value(0, translate("Primary"))
|
||||
o:value(1, translate("Standby"))
|
||||
o.rmempty = false
|
||||
|
||||
local sortable = Template(appname .. "/cbi/sortable")
|
||||
sortable.api = api
|
||||
sortable.target_cfgname = cfgname
|
||||
m:append(sortable)
|
||||
|
||||
m:append(Template(appname .. "/haproxy/js"))
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
api = require "luci.passwall2.api"
|
||||
appname = api.appname
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname, translate("Node Config"))
|
||||
m.redirect = api.url()
|
||||
api.set_apply_on_parse(m)
|
||||
@ -94,7 +96,6 @@ local footer = Template(appname .. "/node_config/footer")
|
||||
footer.api = api
|
||||
footer.config = m.config
|
||||
footer.section = arg[1]
|
||||
|
||||
m:append(footer)
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
|
||||
@ -3,6 +3,8 @@ local appname = api.appname
|
||||
local datatypes = api.datatypes
|
||||
local sys = api.sys
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@ -33,4 +35,4 @@ s:append(Template(appname .. "/node_list/link_add_node"))
|
||||
|
||||
m:append(Template(appname .. "/node_list/node_list"))
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
|
||||
@ -38,6 +38,8 @@ if has_hysteria2 then
|
||||
table.insert(hysteria2_type, s)
|
||||
end
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@ -124,7 +126,8 @@ o.cfgvalue = function(self, section)
|
||||
translate("Manual subscription All"))
|
||||
end
|
||||
|
||||
s = m:section(TypedSection, "subscribe_list", "", "<font color='red'>" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "</font>")
|
||||
local cfgname = "subscribe_list"
|
||||
s = m:section(TypedSection, cfgname, "", "<font color='red'>" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "</font>")
|
||||
s.addremove = true
|
||||
s.anonymous = true
|
||||
s.sortable = true
|
||||
@ -144,7 +147,7 @@ o.validate = function(self, value, section)
|
||||
return nil, translate("Remark cannot be empty.")
|
||||
end
|
||||
local duplicate = false
|
||||
m.uci:foreach(appname, "subscribe_list", function(e)
|
||||
m.uci:foreach(appname, cfgname, function(e)
|
||||
if e[".name"] ~= section and e["remark"] and e["remark"]:lower() == value:lower() then
|
||||
duplicate = true
|
||||
return false
|
||||
@ -219,6 +222,11 @@ o.cfgvalue = function(self, section)
|
||||
section, translate("Manual subscription"))
|
||||
end
|
||||
|
||||
local sortable = Template(appname .. "/cbi/sortable")
|
||||
sortable.api = api
|
||||
sortable.target_cfgname = cfgname
|
||||
m:append(sortable)
|
||||
|
||||
m:append(Template(appname .. "/node_subscribe/js"))
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
m.redirect = api.url("node_subscribe")
|
||||
api.set_apply_on_parse(m)
|
||||
@ -255,13 +257,7 @@ for t = 0, 23 do
|
||||
end
|
||||
end
|
||||
o.default = "0:00"
|
||||
o.validate = function(self, value)
|
||||
local b = api.is_timehhmm(value)
|
||||
if b then
|
||||
return value
|
||||
end
|
||||
return nil
|
||||
end
|
||||
o.datatype = "timehhmm"
|
||||
o:depends("update_week_mode", "0")
|
||||
o:depends("update_week_mode", "1")
|
||||
o:depends("update_week_mode", "2")
|
||||
@ -336,4 +332,4 @@ o.description = translate("Some subscription providers may require a hardware id
|
||||
translate("Enabling this option will send a hashed HWID when updating subscriptions.") .. "<br>" ..
|
||||
translate("This may reveal limited hardware information. Enable only if you trust your provider.")
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
|
||||
@ -8,6 +8,8 @@ local port_validate = function(self, value, t)
|
||||
return value:gsub("-", ":")
|
||||
end
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@ -53,13 +55,7 @@ for index, value in ipairs({"stop", "start", "restart"}) do
|
||||
end
|
||||
end
|
||||
o.default = "0:00"
|
||||
o.validate = function(self, value)
|
||||
local b = api.is_timehhmm(value)
|
||||
if b then
|
||||
return value
|
||||
end
|
||||
return nil
|
||||
end
|
||||
o.datatype = "timehhmm"
|
||||
o:depends(value .. "_week_mode", "0")
|
||||
o:depends(value .. "_week_mode", "1")
|
||||
o:depends(value .. "_week_mode", "2")
|
||||
@ -312,4 +308,4 @@ if has_singbox then
|
||||
o.default = 0
|
||||
end
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@ -59,13 +61,7 @@ for t = 0, 23 do
|
||||
end
|
||||
end
|
||||
o.default = "0:00"
|
||||
o.validate = function(self, value)
|
||||
local b = api.is_timehhmm(value)
|
||||
if b then
|
||||
return value
|
||||
end
|
||||
return nil
|
||||
end
|
||||
o.datatype = "timehhmm"
|
||||
o:depends("update_week_mode", "0")
|
||||
o:depends("update_week_mode", "1")
|
||||
o:depends("update_week_mode", "2")
|
||||
@ -92,7 +88,8 @@ end
|
||||
|
||||
s:append(Template(appname .. "/rule/rule_version"))
|
||||
|
||||
s = m:section(TypedSection, "shunt_rules", "Sing-Box/Xray " .. translate("Shunt Rule"), "<a style='color: red'>" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "</a>")
|
||||
local cfgname = "shunt_rules"
|
||||
s = m:section(TypedSection, cfgname, "Sing-Box/Xray " .. translate("Shunt Rule"), "<a style='color: red'>" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "</a>")
|
||||
s.template = "cbi/tblsection"
|
||||
s.anonymous = false
|
||||
s.addremove = true
|
||||
@ -113,4 +110,9 @@ end
|
||||
|
||||
o = s:option(DummyValue, "remarks", translate("Remarks"))
|
||||
|
||||
return m
|
||||
local sortable = Template(appname .. "/cbi/sortable")
|
||||
sortable.api = api
|
||||
sortable.target_cfgname = cfgname
|
||||
m:append(sortable)
|
||||
|
||||
return api.return_map(m)
|
||||
|
||||
@ -2,6 +2,8 @@ local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
local datatypes = api.datatypes
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname, "Sing-Box/Xray " .. translate("Shunt Rule"))
|
||||
m.redirect = api.url("rule")
|
||||
api.set_apply_on_parse(m)
|
||||
@ -248,4 +250,4 @@ ip_list.description = "<br /><ul>"
|
||||
|
||||
o = s:option(Flag, "invert", "invert", translate("Invert match result.") .. " " .. translate("Only support Sing-Box."))
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map(appname)
|
||||
api.set_apply_on_parse(m)
|
||||
|
||||
@ -80,23 +82,14 @@ o = s:option(Flag, "enable_autoswitch", translate("Auto Switch"))
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "autoswitch_testing_time", translate("How often to test"), translate("Units:seconds"))
|
||||
o.datatype = "min(10)"
|
||||
o.default = 30
|
||||
o = s:option(ListValue, "backup_node_add_mode", translate("Backup Node Addition Method"))
|
||||
o:depends("enable_autoswitch", true)
|
||||
o.default = "manual"
|
||||
o:value("manual", translate("Manual"))
|
||||
o:value("batch", translate("Batch"))
|
||||
|
||||
o = s:option(Value, "autoswitch_connect_timeout", translate("Timeout seconds"), translate("Units:seconds"))
|
||||
o.datatype = "min(1)"
|
||||
o.default = 3
|
||||
o:depends("enable_autoswitch", true)
|
||||
|
||||
o = s:option(Value, "autoswitch_retry_num", translate("Timeout retry num"))
|
||||
o.datatype = "min(1)"
|
||||
o.default = 1
|
||||
o:depends("enable_autoswitch", true)
|
||||
|
||||
o = s:option(DynamicList, "autoswitch_backup_node", translate("List of backup nodes"))
|
||||
o:depends("enable_autoswitch", true)
|
||||
o:depends("backup_node_add_mode", "manual")
|
||||
o.template = appname .. "/cbi/nodes_dynamiclist"
|
||||
o.group = {}
|
||||
o.write = function(self, section, value)
|
||||
@ -118,6 +111,44 @@ for i, v in pairs(nodes_table) do
|
||||
socks_node.group[#socks_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
|
||||
end
|
||||
|
||||
o = s:option(MultiValue, "backup_node_group", translate("Select Group"))
|
||||
o:depends("backup_node_add_mode", "batch")
|
||||
o.widget = "checkbox"
|
||||
o:value("default", translate("default"))
|
||||
local groups = {}
|
||||
m.uci:foreach(appname, "nodes", function(s)
|
||||
if s.group and s.group ~= "" then
|
||||
groups[s.group] = true
|
||||
end
|
||||
end)
|
||||
for k, v in pairs(groups) do
|
||||
o:value(api.UrlEncode(k), k)
|
||||
end
|
||||
|
||||
o = s:option(Value, "backup_node_match_rule", translate("Node Matching Rules"))
|
||||
o:depends("backup_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, "autoswitch_testing_time", translate("How often to test"), translate("Units:seconds"))
|
||||
o.datatype = "min(10)"
|
||||
o.default = 30
|
||||
o:depends("enable_autoswitch", true)
|
||||
|
||||
o = s:option(Value, "autoswitch_connect_timeout", translate("Timeout seconds"), translate("Units:seconds"))
|
||||
o.datatype = "min(1)"
|
||||
o.default = 3
|
||||
o:depends("enable_autoswitch", true)
|
||||
|
||||
o = s:option(Value, "autoswitch_retry_num", translate("Timeout retry num"))
|
||||
o.datatype = "min(1)"
|
||||
o.default = 1
|
||||
o:depends("enable_autoswitch", true)
|
||||
|
||||
o = s:option(Flag, "autoswitch_restore_switch", translate("Restore Switch"), translate("When detects main node is available, switch back to the main node."))
|
||||
o:depends("enable_autoswitch", true)
|
||||
|
||||
@ -134,6 +165,6 @@ o:depends("enable_autoswitch", true)
|
||||
|
||||
o = s:option(DummyValue, "btn")
|
||||
o.template = appname .. "/socks_auto_switch/btn"
|
||||
o:depends("enable_autoswitch", true)
|
||||
o:depends("backup_node_add_mode", "manual")
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
|
||||
@ -442,22 +442,15 @@ o = s:option(Value, _n("tls_serverName"), "SNI " .. translate("Domain"))
|
||||
o:depends({ [_n("tls")] = true })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
|
||||
if api.compare_versions(os.date("%Y.%m.%d"), "<", "3026.6.1") then
|
||||
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, [_n("reality")] = false })
|
||||
o:depends({ [_n("protocol")] = "hysteria2" })
|
||||
end
|
||||
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"))
|
||||
|
||||
if api.compare_versions(xray_version, ">=", "26.1.31") then
|
||||
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 = 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" })
|
||||
end
|
||||
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"
|
||||
@ -671,6 +664,14 @@ o = s:option(TextValue, _n("xhttp_extra"), " ", translate("An XHttpObject in J
|
||||
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
|
||||
@ -693,14 +694,6 @@ o.custom_write = function(self, section, value)
|
||||
m:del(section, "download_address")
|
||||
end
|
||||
end
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||
if api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, "XHTTP Extra " .. translate("Must be JSON text!")
|
||||
end
|
||||
end
|
||||
o.custom_remove = function(self, section, value)
|
||||
m:del(section, "xhttp_extra")
|
||||
m:del(section, "download_address")
|
||||
@ -758,6 +751,14 @@ 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
|
||||
@ -767,14 +768,6 @@ end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "finalmask", api.base64Encode(value) or "")
|
||||
end
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||
if api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, "FinalMask " .. translate("Must be JSON text!")
|
||||
end
|
||||
end
|
||||
|
||||
--[[Fast Open]]
|
||||
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map("passwall2_server", translate("Server-Side"))
|
||||
api.set_apply_on_parse(m)
|
||||
@ -10,7 +13,8 @@ t.addremove = false
|
||||
e = t:option(Flag, "enable", translate("Enable"))
|
||||
e.rmempty = false
|
||||
|
||||
t = m:section(TypedSection, "user", translate("Users Manager"))
|
||||
local cfgname = "user"
|
||||
t = m:section(TypedSection, cfgname, translate("Users Manager"))
|
||||
t.anonymous = true
|
||||
t.addremove = true
|
||||
t.sortable = true
|
||||
@ -85,8 +89,14 @@ e = t:option(Flag, "log", translate("Log"))
|
||||
e.default = "1"
|
||||
e.rmempty = false
|
||||
|
||||
m:append(Template("passwall2/server/log"))
|
||||
local sortable = Template(appname .. "/cbi/sortable")
|
||||
sortable.api = api
|
||||
sortable.appname = m.config
|
||||
sortable.target_cfgname = cfgname
|
||||
m:append(sortable)
|
||||
|
||||
m:append(Template("passwall2/server/users_list_status"))
|
||||
return m
|
||||
m:append(Template(appname .. "/server/log"))
|
||||
|
||||
m:append(Template(appname .. "/server/users_list_status"))
|
||||
|
||||
return api.return_map(m)
|
||||
|
||||
@ -129,12 +129,12 @@ o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.validate = function(self, value, t)
|
||||
if value and api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
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")
|
||||
|
||||
@ -412,6 +412,7 @@ 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
|
||||
@ -421,14 +422,6 @@ end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, "finalmask", api.base64Encode(value) or "")
|
||||
end
|
||||
o.validate = function(self, value)
|
||||
value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
|
||||
if api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, "FinalMask " .. translate("Must be JSON text!")
|
||||
end
|
||||
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."))
|
||||
@ -523,16 +516,16 @@ for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.validate = function(self, value, t)
|
||||
if value and api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
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")
|
||||
|
||||
@ -489,16 +489,16 @@ for _, d in ipairs(netdev_list) do
|
||||
o:value(d.name, d.label)
|
||||
end
|
||||
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.validate = function(self, value, t)
|
||||
if value and api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
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")
|
||||
|
||||
@ -49,16 +49,16 @@ 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"))
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.validate = function(self, value, t)
|
||||
if value and api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
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")
|
||||
|
||||
@ -76,16 +76,16 @@ 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"))
|
||||
o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o:depends({ [_n("custom")] = true })
|
||||
o.validate = function(self, value, t)
|
||||
if value and api.jsonc.parse(value) then
|
||||
return value
|
||||
else
|
||||
return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
|
||||
end
|
||||
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")
|
||||
|
||||
@ -2,6 +2,8 @@ api = require "luci.passwall2.api"
|
||||
appname = api.appname
|
||||
fs = api.fs
|
||||
|
||||
api.set_default_cbi()
|
||||
|
||||
m = Map("passwall2_server", translate("Server Config"))
|
||||
m.redirect = api.url("server")
|
||||
api.set_apply_on_parse(m)
|
||||
@ -54,4 +56,4 @@ footer.config = m.config
|
||||
footer.section = arg[1]
|
||||
m:append(footer)
|
||||
|
||||
return m
|
||||
return api.return_map(m)
|
||||
|
||||
@ -367,8 +367,16 @@ function strToTable(str)
|
||||
return loadstring("return " .. str)()
|
||||
end
|
||||
|
||||
function is_timehhmm(timeStr)
|
||||
local hour, minute = string.match(timeStr, "^(%d?%d):(%d%d)$")
|
||||
function is_json(str)
|
||||
if str and jsonc.parse(str) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
datatypes.json = is_json
|
||||
|
||||
function is_timehhmm(str)
|
||||
local hour, minute = string.match(str, "^(%d?%d):(%d%d)$")
|
||||
if hour and minute then
|
||||
hour = tonumber(hour)
|
||||
minute = tonumber(minute)
|
||||
@ -378,6 +386,7 @@ function is_timehhmm(timeStr)
|
||||
end
|
||||
return false
|
||||
end
|
||||
datatypes.timehhmm = is_timehhmm
|
||||
|
||||
function is_normal_node(e)
|
||||
if e and e.type and e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface" or e.protocol == "_urltest") then
|
||||
@ -1380,6 +1389,40 @@ function set_apply_on_parse(map)
|
||||
end
|
||||
end
|
||||
|
||||
function set_default_cbi()
|
||||
local cbi = require "luci.cbi"
|
||||
if true then
|
||||
--TextValue
|
||||
local TextValue = cbi.TextValue
|
||||
local original_init = TextValue.__init__
|
||||
function TextValue.__init__(self, ...)
|
||||
original_init(self, ...)
|
||||
self.template = appname .. "/cbi/tvalue"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function return_map(map)
|
||||
local cbi = require "luci.cbi"
|
||||
local api = require "luci.passwall2.api"
|
||||
if true then
|
||||
-- header
|
||||
local header = cbi.Template(appname .. "/cbi/header")
|
||||
header.api = api
|
||||
header.config = map.config
|
||||
table.insert(map.children, 1, header)
|
||||
end
|
||||
if true then
|
||||
-- footer
|
||||
local footer = cbi.Template(appname .. "/cbi/footer")
|
||||
footer.api = api
|
||||
footer.config = map.config
|
||||
map:append(footer)
|
||||
end
|
||||
|
||||
return map
|
||||
end
|
||||
|
||||
function luci_types(id, m, s, type_name, option_prefix)
|
||||
local fv_type
|
||||
local field_type = s.fields["type"]
|
||||
@ -1585,6 +1628,56 @@ function match_node_rule(name, rule)
|
||||
return false
|
||||
end
|
||||
|
||||
local normal_nodes = {}
|
||||
function get_batch_nodes(node)
|
||||
if #normal_nodes == 0 then
|
||||
for k, e in ipairs(get_valid_nodes()) do
|
||||
if e.node_type == "normal" and (not e.chain_proxy or e.chain_proxy == "") then
|
||||
normal_nodes[#normal_nodes + 1] = {
|
||||
id = e[".name"],
|
||||
remarks = e["remarks"],
|
||||
group = e["group"]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
if not node.node_group or node.node_group == "" then return {} end
|
||||
local nodes = {}
|
||||
for g in node.node_group:gmatch("%S+") do
|
||||
g = UrlDecode(g)
|
||||
for k, v in pairs(normal_nodes) do
|
||||
local gn = (v.group and v.group ~= "") and v.group or "default"
|
||||
if gn:lower() == g:lower() and match_node_rule(v.remarks, node.node_match_rule) then
|
||||
nodes[#nodes + 1] = v.id
|
||||
end
|
||||
end
|
||||
end
|
||||
return nodes
|
||||
end
|
||||
|
||||
function get_socks_backup_nodes(id)
|
||||
id = trim(id)
|
||||
if id == "" then return "" end
|
||||
local socks = uci:get_all(appname, id)
|
||||
local nodes
|
||||
if socks.backup_node_add_mode and socks.backup_node_add_mode == "batch" then
|
||||
local node = {}
|
||||
node.node_group = socks.backup_node_group
|
||||
node.node_match_rule = socks.backup_node_match_rule
|
||||
nodes = get_batch_nodes(node)
|
||||
else
|
||||
nodes = socks.autoswitch_backup_node
|
||||
end
|
||||
local backup_nodes, seen = {}, {}
|
||||
for _, v in ipairs(nodes or {}) do
|
||||
if v ~= socks.node and not seen[v] then
|
||||
seen[v] = true
|
||||
table.insert(backup_nodes, v)
|
||||
end
|
||||
end
|
||||
return table.concat(backup_nodes, " ")
|
||||
end
|
||||
|
||||
function get_core(field, candidates)
|
||||
local v = uci:get(appname, "@global_subscribe[0]", field)
|
||||
if v and v ~= "" then
|
||||
@ -1610,6 +1703,26 @@ function cleanEmptyTables(t)
|
||||
return next(t) and t or nil
|
||||
end
|
||||
|
||||
function fetch_cert_sha256(host, port, sni, timeout)
|
||||
if not host then return "" end
|
||||
port = tonumber(port) or 443
|
||||
sni = sni or host
|
||||
timeout = tonumber(timeout) or 5
|
||||
local cmd = string.format(
|
||||
"timeout %d openssl s_client -connect %s:%d -servername %s -showcerts </dev/null 2>/dev/null " ..
|
||||
"| awk 'BEGIN{c=0}/BEGIN CERT/{c++} c==1{print} /END CERT/{if(c==1)exit}' " ..
|
||||
"| openssl x509 -outform der 2>/dev/null " ..
|
||||
"| sha256sum 2>/dev/null",
|
||||
timeout, host, port, sni
|
||||
)
|
||||
local out = trim(sys.exec(cmd))
|
||||
local fp = out:match("^([0-9a-fA-F]+)")
|
||||
if not fp or fp:lower():match("^e3b0c44298fc1c149afbf4c8996fb924") then
|
||||
return ""
|
||||
end
|
||||
return fp:upper()
|
||||
end
|
||||
|
||||
function get_dnsmasq_server_domain()
|
||||
local dnsmasq_server = uci:get("dhcp", "@dnsmasq[0]", "server")
|
||||
local dnsmasq_server_t = {}
|
||||
|
||||
@ -1329,33 +1329,6 @@ function gen_config(var)
|
||||
return result
|
||||
end
|
||||
|
||||
local nodes_list = {}
|
||||
function get_urltest_batch_nodes(_node)
|
||||
if #nodes_list == 0 then
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e.node_type == "normal" and (not e.chain_proxy or e.chain_proxy == "") then
|
||||
nodes_list[#nodes_list + 1] = {
|
||||
id = e[".name"],
|
||||
remarks = e["remarks"],
|
||||
group = e["group"]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
if not _node.node_group or _node.node_group == "" then return {} end
|
||||
local nodes = {}
|
||||
for g in _node.node_group:gmatch("%S+") do
|
||||
g = api.UrlDecode(g)
|
||||
for k, v in pairs(nodes_list) do
|
||||
local gn = (v.group and v.group ~= "") and v.group or "default"
|
||||
if gn:lower() == g:lower() and api.match_node_rule(v.remarks, _node.node_match_rule) then
|
||||
nodes[#nodes + 1] = v.id
|
||||
end
|
||||
end
|
||||
end
|
||||
return nodes
|
||||
end
|
||||
|
||||
function get_node_by_id(node_id)
|
||||
if not node_id or node_id == "" or node_id == "nil" then return nil end
|
||||
if node_id:find("Socks_") then
|
||||
@ -1377,7 +1350,7 @@ function gen_config(var)
|
||||
-- new urltest
|
||||
local ut_nodes
|
||||
if _node.node_add_mode and _node.node_add_mode == "batch" then
|
||||
ut_nodes = get_urltest_batch_nodes(_node)
|
||||
ut_nodes = api.get_batch_nodes(_node)
|
||||
else
|
||||
ut_nodes = _node.urltest_node
|
||||
end
|
||||
|
||||
@ -176,21 +176,9 @@ function gen_outbound(flag, node, tag, proxy_table)
|
||||
security = node.stream_security,
|
||||
tlsSettings = (node.stream_security == "tls") and {
|
||||
serverName = node.tls_serverName,
|
||||
allowInsecure = (function()
|
||||
if node.tls_pinSHA256 and node.tls_pinSHA256 ~= "" then return nil end
|
||||
if node.tls_allowInsecure == "1" then return true end
|
||||
end)(),
|
||||
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
|
||||
pinnedPeerCertSha256 = (function()
|
||||
if api.compare_versions(xray_version, "<", "26.1.31") then return nil end
|
||||
if not node.tls_pinSHA256 then return "" end
|
||||
return node.tls_pinSHA256
|
||||
end)(),
|
||||
verifyPeerCertByName = (function()
|
||||
if api.compare_versions(xray_version, "<", "26.1.31") then return nil end
|
||||
if not node.tls_CertByName then return "" end
|
||||
return node.tls_CertByName
|
||||
end)(),
|
||||
pinnedPeerCertSha256 = node.tls_pinSHA256,
|
||||
verifyPeerCertByName = node.tls_CertByName,
|
||||
echConfigList = (node.ech == "1") and node.ech_config or nil,
|
||||
certificates = (node.tls_certificate == "1" and node.tls_certificate_pem ~= "") and {
|
||||
certificate = api.split(node.tls_certificate_pem, "\n"),
|
||||
@ -839,7 +827,8 @@ function gen_config_server(node)
|
||||
serverNames = node.reality_serverNames or {},
|
||||
privateKey = node.reality_private_key,
|
||||
shortIds = node.reality_shortId or "",
|
||||
mldsa65Seed = (node.use_mldsa65Seed == "1") and node.reality_mldsa65Seed or nil
|
||||
mldsa65Seed = (node.use_mldsa65Seed == "1") and node.reality_mldsa65Seed or nil,
|
||||
minClientVer = "1.0.0"
|
||||
} or nil
|
||||
end
|
||||
end
|
||||
@ -1032,33 +1021,6 @@ function gen_config(var)
|
||||
end
|
||||
end
|
||||
|
||||
local nodes_list = {}
|
||||
function get_balancer_batch_nodes(_node)
|
||||
if #nodes_list == 0 then
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e.node_type == "normal" and (not e.chain_proxy or e.chain_proxy == "") then
|
||||
nodes_list[#nodes_list + 1] = {
|
||||
id = e[".name"],
|
||||
remarks = e["remarks"],
|
||||
group = e["group"]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
if not _node.node_group or _node.node_group == "" then return {} end
|
||||
local nodes = {}
|
||||
for g in _node.node_group:gmatch("%S+") do
|
||||
g = api.UrlDecode(g)
|
||||
for k, v in pairs(nodes_list) do
|
||||
local gn = (v.group and v.group ~= "") and v.group or "default"
|
||||
if gn:lower() == g:lower() and api.match_node_rule(v.remarks, _node.node_match_rule) then
|
||||
nodes[#nodes + 1] = v.id
|
||||
end
|
||||
end
|
||||
end
|
||||
return nodes
|
||||
end
|
||||
|
||||
function gen_loopback(outbound_tag, loopback_dst)
|
||||
if not outbound_tag or outbound_tag == "" then return nil end
|
||||
local inbound_tag = loopback_dst and "lo-to-" .. loopback_dst or outbound_tag .. "-lo"
|
||||
@ -1090,7 +1052,7 @@ function gen_config(var)
|
||||
-- new balancer
|
||||
local blc_nodes
|
||||
if _node.node_add_mode and _node.node_add_mode == "batch" then
|
||||
blc_nodes = get_balancer_batch_nodes(_node)
|
||||
blc_nodes = api.get_batch_nodes(_node)
|
||||
else
|
||||
blc_nodes = _node.balancing_node
|
||||
end
|
||||
|
||||
1
luci-app-passwall2/luasrc/view/passwall2/cbi/footer.htm
Normal file
1
luci-app-passwall2/luasrc/view/passwall2/cbi/footer.htm
Normal file
@ -0,0 +1 @@
|
||||
<!-- map footer -->
|
||||
2
luci-app-passwall2/luasrc/view/passwall2/cbi/header.htm
Normal file
2
luci-app-passwall2/luasrc/view/passwall2/cbi/header.htm
Normal file
@ -0,0 +1,2 @@
|
||||
<!-- map header -->
|
||||
<script src="<%=resource%>/view/<%=self.api.appname%>/cbi.js"></script>
|
||||
159
luci-app-passwall2/luasrc/view/passwall2/cbi/sortable.htm
Normal file
159
luci-app-passwall2/luasrc/view/passwall2/cbi/sortable.htm
Normal file
@ -0,0 +1,159 @@
|
||||
<%
|
||||
local api = self.api
|
||||
local appname = self.appname or api.appname
|
||||
local target_cfgname = self.target_cfgname
|
||||
-%>
|
||||
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js"></script>
|
||||
<style>
|
||||
table .cbi-button-up,
|
||||
table .cbi-button-down,
|
||||
.td.cbi-section-actions .cbi-button-up,
|
||||
.td.cbi-section-actions .cbi-button-down {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
vertical-align: middle;
|
||||
cursor: grab !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: 100;
|
||||
padding: 0 !important;
|
||||
line-height: inherit;
|
||||
user-select: none;
|
||||
align-self: stretch;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.drag-handle:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dragging-row {
|
||||
background-color: rgba(131, 191, 255, 0.7) !important;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1152px) {
|
||||
#cbi-<%=appname%>-<%=target_cfgname%> .cbi-section-table-titles {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
//<![CDATA[
|
||||
//Add drag-and-drop sorting
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function initSortableForTable() {
|
||||
var section = document.getElementById("cbi-<%=appname%>-<%=target_cfgname%>");
|
||||
if (!section) return;
|
||||
|
||||
// === insert drag handle ===
|
||||
var rows = section.querySelectorAll("tr");
|
||||
rows.forEach(function(row) {
|
||||
var btn = row.querySelector(".cbi-button-remove:last-of-type");
|
||||
if (!btn) return;
|
||||
if (btn.nextElementSibling && btn.nextElementSibling.classList.contains("drag-handle")) return;
|
||||
var handle = document.createElement("span");
|
||||
handle.className = "drag-handle center";
|
||||
handle.title = "<%:Drag to reorder%>";
|
||||
handle.innerHTML = "⠿";
|
||||
btn.after(handle);
|
||||
});
|
||||
|
||||
// === init Sortable ===
|
||||
var table = section.getElementsByTagName("table")[0];
|
||||
if (!table) return;
|
||||
var root = table.tBodies[0] || table;
|
||||
if (root._sortable_initialized) return root._sortable_instance;
|
||||
root._sortable_initialized = true;
|
||||
|
||||
// save origin order
|
||||
root._origOrder = getCurrentOrder(root);
|
||||
|
||||
try {
|
||||
root._sortable_instance = Sortable.create(root, {
|
||||
handle: ".drag-handle",
|
||||
draggable: "tr.cbi-section-table-row",
|
||||
animation: 150,
|
||||
ghostClass: "dragging-row",
|
||||
fallbackOnBody: true,
|
||||
forceFallback: false,
|
||||
swapThreshold: 0.65,
|
||||
onEnd: function (evt) {
|
||||
updateHiddenInput(root, section);
|
||||
}
|
||||
});
|
||||
return root._sortable_instance;
|
||||
} catch (e) {
|
||||
root._sortable_initialized = false;
|
||||
console.error("Sortable init failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// get table current line order
|
||||
function getCurrentOrder(root) {
|
||||
var order = [];
|
||||
var rows = root.querySelectorAll("tr.cbi-section-table-row");
|
||||
rows.forEach(function (tr) {
|
||||
var id = tr.id || "";
|
||||
if (id.startsWith("cbi-<%=appname%>-"))
|
||||
id = id.replace("cbi-<%=appname%>-", "");
|
||||
order.push(id);
|
||||
});
|
||||
return order;
|
||||
}
|
||||
|
||||
// update after drag and drop hidden input
|
||||
function updateHiddenInput(root, section) {
|
||||
var newOrder = getCurrentOrder(root);
|
||||
var changed = newOrder.join(" ") !== root._origOrder.join(" ");
|
||||
var hiddenInput = section.querySelector('input[type="hidden"][id^="cbi.sts."]');
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = changed ? newOrder.join(" ") : "";
|
||||
}
|
||||
}
|
||||
|
||||
// hide lua luci version up/down
|
||||
function hideSortColumn(section) {
|
||||
var table = section.getElementsByTagName("table")[0];
|
||||
if(!table) return;
|
||||
var rows = table.querySelectorAll("tr.cbi-section-table-row");
|
||||
if(!rows.length) return;
|
||||
var colCount = rows[0].children.length, sortCol = -1;
|
||||
for(var col=0; col<colCount; col++){
|
||||
var hasSort=false, invalid=false;
|
||||
rows.forEach(function(tr){
|
||||
var td=tr.children[col]; if(!td) return;
|
||||
if(td.querySelector(".cbi-button-edit, .cbi-button-remove")) invalid=true;
|
||||
if(td.querySelector(".cbi-button-up, .cbi-button-down")) hasSort=true;
|
||||
});
|
||||
if(!invalid && hasSort){ sortCol=col; break; }
|
||||
}
|
||||
if(sortCol===-1) return;
|
||||
table.querySelectorAll("tr").forEach(function(tr){
|
||||
var c=tr.children[sortCol]; if(c) c.style.display="none";
|
||||
});
|
||||
}
|
||||
|
||||
// === wait TypedSection ===
|
||||
(function waitStable() {
|
||||
var last = 0, stable = 0;
|
||||
var THRESHOLD = 5;
|
||||
function tick() {
|
||||
var count = document.querySelectorAll("tr.cbi-section-table-row").length;
|
||||
if (count && count === last) stable++;
|
||||
else stable = 0;
|
||||
|
||||
last = count;
|
||||
if (stable >= THRESHOLD)
|
||||
setTimeout(initSortableForTable, 200);
|
||||
else
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
tick();
|
||||
})();
|
||||
});
|
||||
</script>
|
||||
13
luci-app-passwall2/luasrc/view/passwall2/cbi/tvalue.htm
Normal file
13
luci-app-passwall2/luasrc/view/passwall2/cbi/tvalue.htm
Normal file
@ -0,0 +1,13 @@
|
||||
<%+cbi/valueheader%>
|
||||
<textarea class="cbi-input-textarea" <% if not self.size then %> style="width: 100%"<% else %> cols="<%=self.size%>"<% end %>
|
||||
data-update="change"<%=
|
||||
attr("name", cbid) ..
|
||||
attr("id", cbid) ..
|
||||
ifattr(self.rows, "rows") ..
|
||||
ifattr(self.wrap, "wrap") ..
|
||||
ifattr(self.readonly, "readonly") ..
|
||||
ifattr(self.datatype, "data-type", self.datatype)
|
||||
%>>
|
||||
<%-=pcdata(self:cfgvalue(section) or self.default)-%>
|
||||
</textarea>
|
||||
<%+cbi/valuefooter%>
|
||||
@ -2,46 +2,8 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
-%>
|
||||
<script src="<%=resource%>/view/<%=appname%>/Sortable.min.js"></script>
|
||||
|
||||
<style>
|
||||
table .cbi-button-up,
|
||||
table .cbi-button-down,
|
||||
.td.cbi-section-actions .cbi-button-up,
|
||||
.td.cbi-section-actions .cbi-button-down {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
vertical-align: middle;
|
||||
cursor: grab !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: 100;
|
||||
padding: 0 !important;
|
||||
line-height: inherit;
|
||||
user-select: none;
|
||||
align-self: stretch;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.drag-handle:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dragging-row {
|
||||
background-color: rgba(131, 191, 255, 0.7) !important;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1152px) {
|
||||
#cbi-<%=appname%>-haproxy_config .cbi-section-table-titles {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1152px) {
|
||||
#cbi-<%=appname%>-haproxy_config input[id*=".<%=appname%>."][id$=".haproxy_port"],
|
||||
#cbi-<%=appname%>-haproxy_config input[id*=".<%=appname%>."][id$=".lbweight"] {
|
||||
@ -110,129 +72,5 @@ table .cbi-button-down,
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
//节点列表添加拖拽排序
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function initSortableForTable() {
|
||||
var section = document.getElementById("cbi-<%=appname%>-haproxy_config");
|
||||
if (!section) return;
|
||||
|
||||
hideSortColumn(section);
|
||||
|
||||
// === 插入 drag handle ===
|
||||
var rows = section.querySelectorAll("tr");
|
||||
rows.forEach(function(row) {
|
||||
var btn = row.querySelector(".cbi-button-remove:last-of-type");
|
||||
if (!btn) return;
|
||||
if (btn.nextElementSibling && btn.nextElementSibling.classList.contains("drag-handle")) return;
|
||||
var handle = document.createElement("span");
|
||||
handle.className = "drag-handle center";
|
||||
handle.title = "<%:Drag to reorder%>";
|
||||
handle.innerHTML = "⠿";
|
||||
btn.after(handle);
|
||||
});
|
||||
|
||||
// === 初始化 Sortable ===
|
||||
var table = section.getElementsByTagName("table")[0];
|
||||
if (!table) return;
|
||||
var root = table.tBodies[0] || table;
|
||||
if (root._sortable_initialized) return root._sortable_instance;
|
||||
root._sortable_initialized = true;
|
||||
|
||||
// 保存原始顺序
|
||||
root._origOrder = getCurrentOrder(root);
|
||||
|
||||
try {
|
||||
root._sortable_instance = Sortable.create(root, {
|
||||
handle: ".drag-handle",
|
||||
draggable: "tr.cbi-section-table-row",
|
||||
animation: 150,
|
||||
ghostClass: "dragging-row",
|
||||
fallbackOnBody: true,
|
||||
forceFallback: false,
|
||||
swapThreshold: 0.65,
|
||||
onEnd: function (evt) {
|
||||
updateHiddenInput(root, section);
|
||||
}
|
||||
});
|
||||
return root._sortable_instance;
|
||||
} catch (e) {
|
||||
root._sortable_initialized = false;
|
||||
console.error("Sortable init failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 table 当前行顺序
|
||||
function getCurrentOrder(root) {
|
||||
var order = [];
|
||||
var rows = root.querySelectorAll("tr.cbi-section-table-row");
|
||||
rows.forEach(function (tr) {
|
||||
var id = tr.id || "";
|
||||
if (id.startsWith("cbi-<%=appname%>-"))
|
||||
id = id.replace("cbi-<%=appname%>-", "");
|
||||
order.push(id);
|
||||
});
|
||||
return order;
|
||||
}
|
||||
|
||||
// 拖拽完成后更新 hidden input
|
||||
function updateHiddenInput(root, section) {
|
||||
var newOrder = getCurrentOrder(root);
|
||||
var changed = newOrder.join(" ") !== root._origOrder.join(" ");
|
||||
var hiddenInput = section.querySelector('input[type="hidden"][id^="cbi.sts."]');
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = changed ? newOrder.join(" ") : "";
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏18.06 up/down 列
|
||||
function hideSortColumn(section) {
|
||||
var table = section.querySelector("table");
|
||||
if (!table) return;
|
||||
var ths = Array.prototype.slice.call(table.querySelectorAll("tr.cbi-section-table-titles > th"));
|
||||
var dataRows = table.querySelectorAll("tr.cbi-section-table-row");
|
||||
if (!ths.length || !dataRows.length) return;
|
||||
var sortCol = -1;
|
||||
for (var i = 0; i < ths.length; i++) {
|
||||
var hasSort = false, invalid = false;
|
||||
dataRows.forEach(function(tr) {
|
||||
var td = tr.querySelectorAll(":scope > td")[i];
|
||||
if (!td) return;
|
||||
if (td.querySelector(".cbi-button-edit, .cbi-button-remove")) invalid = true;
|
||||
if (td.querySelector(".cbi-button-up, .cbi-button-down")) hasSort = true;
|
||||
});
|
||||
if (!invalid && hasSort) { sortCol = i; break; }
|
||||
}
|
||||
if (sortCol === -1) return;
|
||||
var rows = [table.querySelector("tr.cbi-section-table-titles")].concat(
|
||||
Array.prototype.slice.call(dataRows),
|
||||
Array.prototype.slice.call(table.querySelectorAll("tr.cbi-section-table-descr"))
|
||||
);
|
||||
rows.forEach(function(tr) {
|
||||
var cells = Array.prototype.filter.call(tr.children, function(el) {
|
||||
return el.tagName === "TH" || el.tagName === "TD";
|
||||
});
|
||||
if (cells[sortCol]) cells[sortCol].style.display = "none";
|
||||
});
|
||||
}
|
||||
|
||||
// === 等待 TypedSection 行稳定 ===
|
||||
(function waitStable() {
|
||||
var last = 0, stable = 0;
|
||||
var THRESHOLD = 5;
|
||||
function tick() {
|
||||
var count = document.querySelectorAll("tr.cbi-section-table-row").length;
|
||||
if (count && count === last) stable++;
|
||||
else stable = 0;
|
||||
|
||||
last = count;
|
||||
if (stable >= THRESHOLD)
|
||||
setTimeout(initSortableForTable, 200);
|
||||
else
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
tick();
|
||||
})();
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
@ -52,5 +52,29 @@ local api = self.api
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function fetchCertSha256(el) {
|
||||
if (el.getAttribute("data-loading") === "1") return;
|
||||
el.setAttribute("data-loading", "1");
|
||||
el.style.pointerEvents = "none";
|
||||
el.style.color = "red";
|
||||
XHR.get('<%=api.url("fetch_certsha256")%>', { id: node_id }, function(x, res) {
|
||||
el.setAttribute("data-loading", "0");
|
||||
el.style.pointerEvents = "";
|
||||
el.style.color = "";
|
||||
if (!x || x.status !== 200) {
|
||||
alert("<%:Error%>");
|
||||
return;
|
||||
}
|
||||
if (!res || res.code !== 1 || !res.data) {
|
||||
alert(res && res.msg ? res.msg : "<%:Fetch Failed%> !");
|
||||
return;
|
||||
}
|
||||
const inputs = document.querySelectorAll('input[id$=".xray_tls_pinSHA256"]');
|
||||
inputs.forEach(function(input) {
|
||||
input.value = res.data;
|
||||
});
|
||||
});
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
@ -1314,6 +1314,8 @@ local current_node = map:get(section)
|
||||
opt.set(dom_prefix + 'utls', true);
|
||||
opt.set(dom_prefix + 'fingerprint', queryParam.fp);
|
||||
}
|
||||
opt.set(dom_prefix + 'ech', !!queryParam.ech);
|
||||
opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
|
||||
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || '');
|
||||
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
|
||||
opt.set(
|
||||
|
||||
@ -635,11 +635,15 @@ table td, .table .td {
|
||||
if (cacheData && cacheData.savetime && (now - cacheData.timestamp) < cacheData.savetime) {
|
||||
const a = ping_value[i].firstElementChild;
|
||||
let color = "red";
|
||||
if (cacheData.value < 100)
|
||||
color = "green";
|
||||
else if (cacheData.value < 200)
|
||||
color = "#fb9a05";
|
||||
a.innerText = cacheData.value + " ms";
|
||||
if (cacheData.value >= 0) {
|
||||
a.innerText = cacheData.value + " ms";
|
||||
if (cacheData.value < 100)
|
||||
color = "green";
|
||||
else if (cacheData.value < 200)
|
||||
color = "#fb9a05";
|
||||
} else {
|
||||
a.innerText = "<%:Timeout%>";
|
||||
}
|
||||
a.style.color = color;
|
||||
a.onclick = function () {
|
||||
ping_node(cbi_id, this, auto_detection_time);
|
||||
@ -679,15 +683,23 @@ table td, .table .td {
|
||||
const cache_data = {
|
||||
dom_id: strs[i],
|
||||
timestamp: Date.now(),
|
||||
savetime: 60 * 1000,
|
||||
savetime: 150 * 1000,
|
||||
value: ping
|
||||
};
|
||||
text = result.ping + " ms";
|
||||
localStorage.setItem(auto_detection_time + ":" + dom.address + ":" + dom.port, JSON.stringify(cache_data));
|
||||
text = result.ping + " ms";
|
||||
if (ping < 100)
|
||||
color = "green";
|
||||
else if (ping < 200)
|
||||
color = "#fb9a05";
|
||||
} else {
|
||||
const cache_data = {
|
||||
dom_id: strs[i],
|
||||
timestamp: Date.now(),
|
||||
savetime: 60 * 1000,
|
||||
value: -1
|
||||
};
|
||||
localStorage.setItem(auto_detection_time + ":" + dom.address + ":" + dom.port, JSON.stringify(cache_data));
|
||||
}
|
||||
a.innerText = text;
|
||||
a.style.color = color;
|
||||
@ -1101,6 +1113,15 @@ table td, .table .td {
|
||||
onChange("auto_detection_time", auto_detection_time);
|
||||
});
|
||||
});
|
||||
|
||||
const links = document.querySelectorAll('a');
|
||||
links.forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
if (link.getAttribute('target') !== '_blank') {
|
||||
ajaxAbortAll();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
@ -1,46 +1,6 @@
|
||||
<%
|
||||
local api = require "luci.passwall2.api"
|
||||
-%>
|
||||
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js"></script>
|
||||
|
||||
<style>
|
||||
table .cbi-button-up,
|
||||
table .cbi-button-down,
|
||||
.td.cbi-section-actions .cbi-button-up,
|
||||
.td.cbi-section-actions .cbi-button-down {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
vertical-align: middle;
|
||||
cursor: grab !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: 100;
|
||||
padding: 0 !important;
|
||||
line-height: inherit;
|
||||
user-select: none;
|
||||
align-self: stretch;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.drag-handle:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dragging-row {
|
||||
background-color: rgba(131, 191, 255, 0.7) !important;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1152px) {
|
||||
#cbi-<%=api.appname%>-subscribe_list .cbi-section-table-titles {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
@ -148,129 +108,5 @@ table .cbi-button-down,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//订阅列表添加拖拽排序
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function initSortableForTable() {
|
||||
var section = document.getElementById("cbi-<%=api.appname%>-subscribe_list");
|
||||
if (!section) return;
|
||||
|
||||
hideSortColumn(section);
|
||||
|
||||
// === 插入 drag handle ===
|
||||
var rows = section.querySelectorAll("tr");
|
||||
rows.forEach(function(row) {
|
||||
var btn = row.querySelector(".cbi-button-remove:last-of-type");
|
||||
if (!btn) return;
|
||||
if (btn.nextElementSibling && btn.nextElementSibling.classList.contains("drag-handle")) return;
|
||||
var handle = document.createElement("span");
|
||||
handle.className = "drag-handle center";
|
||||
handle.title = "<%:Drag to reorder%>";
|
||||
handle.innerHTML = "⠿";
|
||||
btn.after(handle);
|
||||
});
|
||||
|
||||
// === 初始化 Sortable ===
|
||||
var table = section.getElementsByTagName("table")[0];
|
||||
if (!table) return;
|
||||
var root = table.tBodies[0] || table;
|
||||
if (root._sortable_initialized) return root._sortable_instance;
|
||||
root._sortable_initialized = true;
|
||||
|
||||
// 保存原始顺序
|
||||
root._origOrder = getCurrentOrder(root);
|
||||
|
||||
try {
|
||||
root._sortable_instance = Sortable.create(root, {
|
||||
handle: ".drag-handle",
|
||||
draggable: "tr.cbi-section-table-row",
|
||||
animation: 150,
|
||||
ghostClass: "dragging-row",
|
||||
fallbackOnBody: true,
|
||||
forceFallback: false,
|
||||
swapThreshold: 0.65,
|
||||
onEnd: function (evt) {
|
||||
updateHiddenInput(root, section);
|
||||
}
|
||||
});
|
||||
return root._sortable_instance;
|
||||
} catch (e) {
|
||||
root._sortable_initialized = false;
|
||||
console.error("Sortable init failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 table 当前行顺序
|
||||
function getCurrentOrder(root) {
|
||||
var order = [];
|
||||
var rows = root.querySelectorAll("tr.cbi-section-table-row");
|
||||
rows.forEach(function (tr) {
|
||||
var id = tr.id || "";
|
||||
if (id.startsWith("cbi-<%=api.appname%>-"))
|
||||
id = id.replace("cbi-<%=api.appname%>-", "");
|
||||
order.push(id);
|
||||
});
|
||||
return order;
|
||||
}
|
||||
|
||||
// 拖拽完成后更新 hidden input
|
||||
function updateHiddenInput(root, section) {
|
||||
var newOrder = getCurrentOrder(root);
|
||||
var changed = newOrder.join(" ") !== root._origOrder.join(" ");
|
||||
var hiddenInput = section.querySelector('input[type="hidden"][id^="cbi.sts."]');
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = changed ? newOrder.join(" ") : "";
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏18.06 up/down 列
|
||||
function hideSortColumn(section) {
|
||||
var table = section.querySelector("table");
|
||||
if (!table) return;
|
||||
var ths = Array.prototype.slice.call(table.querySelectorAll("tr.cbi-section-table-titles > th"));
|
||||
var dataRows = table.querySelectorAll("tr.cbi-section-table-row");
|
||||
if (!ths.length || !dataRows.length) return;
|
||||
var sortCol = -1;
|
||||
for (var i = 0; i < ths.length; i++) {
|
||||
var hasSort = false, invalid = false;
|
||||
dataRows.forEach(function(tr) {
|
||||
var td = tr.querySelectorAll(":scope > td")[i];
|
||||
if (!td) return;
|
||||
if (td.querySelector(".cbi-button-edit, .cbi-button-remove")) invalid = true;
|
||||
if (td.querySelector(".cbi-button-up, .cbi-button-down")) hasSort = true;
|
||||
});
|
||||
if (!invalid && hasSort) { sortCol = i; break; }
|
||||
}
|
||||
if (sortCol === -1) return;
|
||||
var rows = [table.querySelector("tr.cbi-section-table-titles")].concat(
|
||||
Array.prototype.slice.call(dataRows),
|
||||
Array.prototype.slice.call(table.querySelectorAll("tr.cbi-section-table-descr"))
|
||||
);
|
||||
rows.forEach(function(tr) {
|
||||
var cells = Array.prototype.filter.call(tr.children, function(el) {
|
||||
return el.tagName === "TH" || el.tagName === "TD";
|
||||
});
|
||||
if (cells[sortCol]) cells[sortCol].style.display = "none";
|
||||
});
|
||||
}
|
||||
|
||||
// === 等待 TypedSection 行稳定 ===
|
||||
(function waitStable() {
|
||||
var last = 0, stable = 0;
|
||||
var THRESHOLD = 5;
|
||||
function tick() {
|
||||
var count = document.querySelectorAll("tr.cbi-section-table-row").length;
|
||||
if (count && count === last) stable++;
|
||||
else stable = 0;
|
||||
|
||||
last = count;
|
||||
if (stable >= THRESHOLD)
|
||||
setTimeout(initSortableForTable, 200);
|
||||
else
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
tick();
|
||||
})();
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
@ -4,44 +4,12 @@ local fs = api.fs
|
||||
local has_old_geoip = fs.access("/tmp/bak_v2ray/geoip.dat")
|
||||
local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
|
||||
-%>
|
||||
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js"></script>
|
||||
|
||||
<style>
|
||||
table .cbi-button-up,
|
||||
table .cbi-button-down,
|
||||
.td.cbi-section-actions .cbi-button-up,
|
||||
.td.cbi-section-actions .cbi-button-down {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div.cbi-value[id$="-geoip_update"],
|
||||
div.cbi-value[id$="-geosite_update"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
vertical-align: middle;
|
||||
cursor: grab !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: 100;
|
||||
padding: 0 !important;
|
||||
line-height: inherit;
|
||||
user-select: none;
|
||||
align-self: stretch;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.drag-handle:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dragging-row {
|
||||
background-color: rgba(131, 191, 255, 0.7) !important;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="cbi-value" id="_rule_div">
|
||||
@ -157,118 +125,5 @@ local has_old_geosite = fs.access("/tmp/bak_v2ray/geosite.dat")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//分流规则添加拖拽排序
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function initSortableForTable() {
|
||||
var section = document.getElementById("cbi-<%=api.appname%>-shunt_rules");
|
||||
if (!section) return;
|
||||
|
||||
// === 插入 drag handle ===
|
||||
var rows = section.querySelectorAll("tr");
|
||||
rows.forEach(function(row) {
|
||||
var btn = row.querySelector(".cbi-button-remove:last-of-type");
|
||||
if (!btn) return;
|
||||
if (btn.nextElementSibling && btn.nextElementSibling.classList.contains("drag-handle")) return;
|
||||
var handle = document.createElement("span");
|
||||
handle.className = "drag-handle center";
|
||||
handle.title = "<%:Drag to reorder%>";
|
||||
handle.innerHTML = "⠿";
|
||||
btn.after(handle);
|
||||
});
|
||||
|
||||
// === 初始化 Sortable ===
|
||||
var table = section.getElementsByTagName("table")[0];
|
||||
if (!table) return;
|
||||
var root = table.tBodies[0] || table;
|
||||
if (root._sortable_initialized) return root._sortable_instance;
|
||||
root._sortable_initialized = true;
|
||||
|
||||
// 保存原始顺序
|
||||
root._origOrder = getCurrentOrder(root);
|
||||
|
||||
try {
|
||||
root._sortable_instance = Sortable.create(root, {
|
||||
handle: ".drag-handle",
|
||||
draggable: "tr.cbi-section-table-row",
|
||||
animation: 150,
|
||||
ghostClass: "dragging-row",
|
||||
fallbackOnBody: true,
|
||||
forceFallback: false,
|
||||
swapThreshold: 0.65,
|
||||
onEnd: function (evt) {
|
||||
updateHiddenInput(root, section);
|
||||
}
|
||||
});
|
||||
return root._sortable_instance;
|
||||
} catch (e) {
|
||||
root._sortable_initialized = false;
|
||||
console.error("Sortable init failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 table 当前行顺序
|
||||
function getCurrentOrder(root) {
|
||||
var order = [];
|
||||
var rows = root.querySelectorAll("tr.cbi-section-table-row");
|
||||
rows.forEach(function (tr) {
|
||||
var id = tr.id || "";
|
||||
if (id.startsWith("cbi-<%=api.appname%>-"))
|
||||
id = id.replace("cbi-<%=api.appname%>-", "");
|
||||
order.push(id);
|
||||
});
|
||||
return order;
|
||||
}
|
||||
|
||||
// 拖拽完成后更新 hidden input
|
||||
function updateHiddenInput(root, section) {
|
||||
var newOrder = getCurrentOrder(root);
|
||||
var changed = newOrder.join(" ") !== root._origOrder.join(" ");
|
||||
var hiddenInput = section.querySelector('input[type="hidden"][id^="cbi.sts."]');
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = changed ? newOrder.join(" ") : "";
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏18.06 up/down 列
|
||||
function hideSortColumn(section) {
|
||||
var table = section.getElementsByTagName("table")[0];
|
||||
if(!table) return;
|
||||
var rows = table.querySelectorAll("tr.cbi-section-table-row");
|
||||
if(!rows.length) return;
|
||||
var colCount = rows[0].children.length, sortCol = -1;
|
||||
for(var col=0; col<colCount; col++){
|
||||
var hasSort=false, invalid=false;
|
||||
rows.forEach(function(tr){
|
||||
var td=tr.children[col]; if(!td) return;
|
||||
if(td.querySelector(".cbi-button-edit, .cbi-button-remove")) invalid=true;
|
||||
if(td.querySelector(".cbi-button-up, .cbi-button-down")) hasSort=true;
|
||||
});
|
||||
if(!invalid && hasSort){ sortCol=col; break; }
|
||||
}
|
||||
if(sortCol===-1) return;
|
||||
table.querySelectorAll("tr").forEach(function(tr){
|
||||
var c=tr.children[sortCol]; if(c) c.style.display="none";
|
||||
});
|
||||
}
|
||||
|
||||
// === 等待 TypedSection 行稳定 ===
|
||||
(function waitStable() {
|
||||
var last = 0, stable = 0;
|
||||
var THRESHOLD = 5;
|
||||
function tick() {
|
||||
var count = document.querySelectorAll("tr.cbi-section-table-row").length;
|
||||
if (count && count === last) stable++;
|
||||
else stable = 0;
|
||||
|
||||
last = count;
|
||||
if (stable >= THRESHOLD)
|
||||
setTimeout(initSortableForTable, 200);
|
||||
else
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
tick();
|
||||
})();
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
@ -379,6 +379,9 @@ msgstr "توزیع بار"
|
||||
msgid "Node Addition Method"
|
||||
msgstr "روش افزودن گره"
|
||||
|
||||
msgid "Backup Node Addition Method"
|
||||
msgstr "روش افزودن گره پشتیبان"
|
||||
|
||||
msgid "Manual"
|
||||
msgstr "دستی"
|
||||
|
||||
@ -1651,6 +1654,12 @@ msgstr "گواهی TLS (PEM)"
|
||||
msgid "Full certificate (chain), PEM format."
|
||||
msgstr "گواهی کامل (زنجیرهای)، فرمت PEM."
|
||||
|
||||
msgid "Fetch Manually"
|
||||
msgstr "دریافت دستی"
|
||||
|
||||
msgid "Fetch Failed"
|
||||
msgstr "واکشی ناموفق"
|
||||
|
||||
msgid "ECH Config"
|
||||
msgstr "پیکربندی ECH"
|
||||
|
||||
@ -2400,4 +2409,7 @@ msgid "Gecko Packet Size (min)"
|
||||
msgstr "اندازه بسته Gecko (دقیقه)"
|
||||
|
||||
msgid "Gecko Packet Size (max)"
|
||||
msgstr "اندازه بسته Gecko (حداکثر)"
|
||||
msgstr "اندازه بسته Gecko (حداکثر)"
|
||||
|
||||
msgid "valid time (hh:mm)"
|
||||
msgstr "زمان معتبر (ساعت:میلیمتر)"
|
||||
|
||||
@ -376,6 +376,9 @@ msgstr "Балансировка"
|
||||
msgid "Node Addition Method"
|
||||
msgstr "Способ добавления узлов"
|
||||
|
||||
msgid "Backup Node Addition Method"
|
||||
msgstr "Метод добавления резервного узла"
|
||||
|
||||
msgid "Manual"
|
||||
msgstr "Вручную"
|
||||
|
||||
@ -1652,6 +1655,12 @@ msgstr "Сертификат TLS (PEM)"
|
||||
msgid "Full certificate (chain), PEM format."
|
||||
msgstr "Полный сертификат (цепочка), формат PEM."
|
||||
|
||||
msgid "Fetch Manually"
|
||||
msgstr "Получить вручную"
|
||||
|
||||
msgid "Fetch Failed"
|
||||
msgstr "Не удалось получить данные."
|
||||
|
||||
msgid "ECH Config"
|
||||
msgstr "Конфигурация ECH"
|
||||
|
||||
@ -2399,3 +2408,6 @@ msgstr "Размер упаковки Gecko (мин)"
|
||||
|
||||
msgid "Gecko Packet Size (max)"
|
||||
msgstr "Размер упаковки Gecko (макс)"
|
||||
|
||||
msgid "valid time (hh:mm)"
|
||||
msgstr "Время действия (чч:мм)"
|
||||
|
||||
@ -376,6 +376,9 @@ msgstr "负载均衡"
|
||||
msgid "Node Addition Method"
|
||||
msgstr "节点添加方式"
|
||||
|
||||
msgid "Backup Node Addition Method"
|
||||
msgstr "后备节点添加方式"
|
||||
|
||||
msgid "Manual"
|
||||
msgstr "手动"
|
||||
|
||||
@ -1643,6 +1646,12 @@ msgstr "TLS 证书(PEM)"
|
||||
msgid "Full certificate (chain), PEM format."
|
||||
msgstr "完整证书(链),PEM 格式。"
|
||||
|
||||
msgid "Fetch Manually"
|
||||
msgstr "手动获取"
|
||||
|
||||
msgid "Fetch Failed"
|
||||
msgstr "获取失败"
|
||||
|
||||
msgid "ECH Config"
|
||||
msgstr "ECH 配置"
|
||||
|
||||
@ -2387,3 +2396,6 @@ msgstr "Gecko 包大小(最小)"
|
||||
|
||||
msgid "Gecko Packet Size (max)"
|
||||
msgstr "Gecko 包大小(最大)"
|
||||
|
||||
msgid "valid time (hh:mm)"
|
||||
msgstr "有效时间(hh:mm)"
|
||||
|
||||
@ -376,6 +376,9 @@ msgstr "負載均衡"
|
||||
msgid "Node Addition Method"
|
||||
msgstr "節點新增方式"
|
||||
|
||||
msgid "Backup Node Addition Method"
|
||||
msgstr "備用節點新增方式"
|
||||
|
||||
msgid "Manual"
|
||||
msgstr "手動"
|
||||
|
||||
@ -1643,6 +1646,12 @@ msgstr "TLS 憑證(PEM)"
|
||||
msgid "Full certificate (chain), PEM format."
|
||||
msgstr "完整憑證(鏈),PEM 格式。"
|
||||
|
||||
msgid "Fetch Manually"
|
||||
msgstr "手動獲取"
|
||||
|
||||
msgid "Fetch Failed"
|
||||
msgstr "獲取失敗"
|
||||
|
||||
msgid "ECH Config"
|
||||
msgstr "ECH 配置"
|
||||
|
||||
@ -2393,3 +2402,6 @@ msgstr "Gecko 包大小(最小)"
|
||||
|
||||
msgid "Gecko Packet Size (max)"
|
||||
msgstr "Gecko 包大小(最大)"
|
||||
|
||||
msgid "valid time (hh:mm)"
|
||||
msgstr "有效時間(hh:mm)"
|
||||
|
||||
@ -62,6 +62,10 @@ chmod +x /usr/share/passwall2/*.sh
|
||||
[ "$(uci -q get passwall2.@global_xray[0].sniffing)" == "1" ] && [ "$(uci -q get passwall2.@global_xray[0].route_only)" != "1" ] && uci -q set passwall2.@global_xray[0].sniffing_override_dest=1
|
||||
uci -q delete passwall2.@global_xray[0].sniffing
|
||||
uci -q delete passwall2.@global_xray[0].route_only
|
||||
frag_len=$(uci -q get passwall2.@global_xray[0].fragment_length)
|
||||
[ -n "$frag_len" ] && uci -q set passwall2.@global_xray[0].fragment_lengths="$frag_len" && uci -q delete passwall2.@global_xray[0].fragment_length
|
||||
frag_dly=$(uci -q get passwall2.@global_xray[0].fragment_delay)
|
||||
[ -n "$frag_dly" ] && uci -q set passwall2.@global_xray[0].fragment_delays="$frag_dly" && uci -q delete passwall2.@global_xray[0].fragment_delay
|
||||
uci -q commit passwall2
|
||||
|
||||
sed -i "s#add_from#group#g" /etc/config/passwall2 2>/dev/null
|
||||
|
||||
@ -166,9 +166,8 @@ start() {
|
||||
retry_num=$(config_n_get $id autoswitch_retry_num 1)
|
||||
restore_switch=$(config_n_get $id autoswitch_restore_switch 0)
|
||||
probe_url=$(config_n_get $id autoswitch_probe_url "https://www.google.com/generate_204")
|
||||
backup_node=$(config_n_get $id autoswitch_backup_node)
|
||||
backup_node=$(lua_api "get_socks_backup_nodes(\"${id}\")")
|
||||
if [ -n "$backup_node" ]; then
|
||||
backup_node=$(echo "$backup_node" | tr -s ' ' '\n' | uniq | tr -s '\n' ' ')
|
||||
backup_node_num=$(printf "%s\n" "$backup_node" | wc -w)
|
||||
if [ "$backup_node_num" -eq 1 ]; then
|
||||
[ "$main_node" = "$backup_node" ] && return
|
||||
|
||||
@ -1368,6 +1368,10 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
result.reality_publicKey = params.pbk or nil
|
||||
result.reality_shortId = params.sid or nil
|
||||
end
|
||||
if params.ech and params.ech ~= "" then
|
||||
result.ech = "1"
|
||||
result.ech_config = params.ech
|
||||
end
|
||||
result.tls_pinSHA256 = params.pcs
|
||||
result.tls_CertByName = params.vcn
|
||||
local insecure = params.allowinsecure or params.allowInsecure or params.insecure
|
||||
|
||||
@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_NAME:=luci-app-ssr-plus
|
||||
PKG_VERSION:=196
|
||||
PKG_RELEASE:=44
|
||||
PKG_RELEASE:=45
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
||||
|
||||
@ -499,7 +499,10 @@ Xray.outbounds = {
|
||||
fingerprint = server.fingerprint,
|
||||
allowInsecure = (function()
|
||||
if server.tls_CertSha and server.tls_CertSha ~= "" then return nil end
|
||||
return server.insecure == "1"
|
||||
if os.date("%Y.%m.%d") < "2026.06.01" then
|
||||
return server.insecure == "1"
|
||||
end
|
||||
return nil
|
||||
end)(),
|
||||
serverName = server.tls_host,
|
||||
certificates = server.certificate and {
|
||||
|
||||
@ -8,8 +8,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=netdata
|
||||
PKG_VERSION:=2.10.3
|
||||
PKG_RELEASE:=13
|
||||
PKG_VERSION:=2.10.4
|
||||
PKG_RELEASE:=14
|
||||
|
||||
PKG_MAINTAINER:=Josef Schlehofer <pepe.schlehofer@gmail.com>, Daniel Engberg <daniel.engberg.lists@pyret.net>
|
||||
PKG_LICENSE:=GPL-3.0-or-later
|
||||
|
||||
@ -8,12 +8,12 @@ PKG_NAME:=alwaysonline
|
||||
PKG_UPSTREAM_VERSION:=1.2.1
|
||||
PKG_UPSTREAM_GITHASH:=206292ca68e4d6c81b215163a7bbd0cd2eb36860
|
||||
PKG_VERSION:=$(PKG_UPSTREAM_VERSION)~$(call version_abbrev,$(PKG_UPSTREAM_GITHASH))
|
||||
PKG_RELEASE:=8
|
||||
PKG_RELEASE:=9
|
||||
UCI_VERSION:=0.2025.01.25
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/Jamesits/alwaysonline.git
|
||||
PKG_SOURCE_VERSION:=0b7050013224cea1e9d4843817f9baa12b0bfe19
|
||||
PKG_SOURCE_VERSION:=e5f20a72c8cf6a9ce0c5901e84dff18e96df5812
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_UPSTREAM_VERSION)
|
||||
|
||||
@ -10,12 +10,13 @@ include $(TOPDIR)/rules.mk
|
||||
PKG_NAME:=tcptraceroute
|
||||
PKG_REALVERSION:=1.5beta7
|
||||
PKG_VERSION:=1.5_beta7
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_VERSION:=tcptraceroute-$(PKG_REALVERSION)
|
||||
PKG_SOURCE_URL:=https://github.com/mct/tcptraceroute.git
|
||||
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/tcptraceroute
|
||||
|
||||
@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=Xray-core
|
||||
PKG_VERSION:=26.7.11
|
||||
PKG_RELEASE:=19
|
||||
PKG_RELEASE:=20
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/XTLS/Xray-core/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
--- a/infra/conf/transport_internet.go
|
||||
+++ b/infra/conf/transport_internet.go
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
- "time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -699,12 +698,7 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
||||
config.MasterKeyLog = c.MasterKeyLog
|
||||
|
||||
if c.AllowInsecure {
|
||||
- if time.Now().After(time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)) {
|
||||
- return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"`)
|
||||
- } else {
|
||||
- errors.LogWarning(context.Background(), `"allowInsecure" will be removed automatically after 2026-06-01, please use "pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn) instead, PLEASE CONTACT YOUR SERVICE PROVIDER (AIRPORT)`)
|
||||
- config.AllowInsecure = true
|
||||
- }
|
||||
+ config.AllowInsecure = true
|
||||
}
|
||||
if c.PinnedPeerCertSha256 != "" {
|
||||
for v := range strings.SplitSeq(c.PinnedPeerCertSha256, ",") {
|
||||
@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=z8102
|
||||
PKG_VERSION:=0.2
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_MAINTAINER:=Yannick Chabanois <contact@openmptcprouter.com>
|
||||
|
||||
|
||||
@ -12,13 +12,15 @@ log() {
|
||||
modem_reset()
|
||||
{
|
||||
modemnb=$1
|
||||
# sim
|
||||
if [ -e /sys/class/gpio/sim${modemnb}/value ]; then
|
||||
echo "0" > /sys/class/gpio/sim${modemnb}/value
|
||||
elif [ -f /usr/bin/gpiofind ]; then
|
||||
gpioset `gpiofind "sim=${modemnb}"`=0
|
||||
else
|
||||
gpioset -t0 sim${modemnb}=0
|
||||
if [ -z "$(cat /etc/board.json | grep z8802)" ]; then
|
||||
# sim
|
||||
if [ -e /sys/class/gpio/sim${modemnb}/value ]; then
|
||||
echo "0" > /sys/class/gpio/sim${modemnb}/value
|
||||
elif [ -f /usr/bin/gpiofind ]; then
|
||||
gpioset `gpiofind "sim=${modemnb}"`=0
|
||||
else
|
||||
gpioset -t0 sim${modemnb}=0
|
||||
fi
|
||||
fi
|
||||
|
||||
# stop modem
|
||||
|
||||
Loading…
Reference in New Issue
Block a user