mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 13:55:13 +08:00
💐 Sync 2026-03-10 20:23:29
This commit is contained in:
1 parent
62c1cd7501
commit
52bf5fc663
26 files changed
+3712
-3272
No files matched your search
@@ -11,7 +11,7 @@ LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix
|
|||||||
PKG_MAINTAINER:=timsaya
|
PKG_MAINTAINER:=timsaya
|
||||||
|
|
||||||
PKG_VERSION:=0.12.6
|
PKG_VERSION:=0.12.6
|
||||||
PKG_RELEASE:=3
|
PKG_RELEASE:=4
|
||||||
|
|
||||||
include $(TOPDIR)/feeds/luci/luci.mk
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
|
|
||||||
|
|||||||
@@ -3118,8 +3118,8 @@ return view.extend({
|
|||||||
};
|
};
|
||||||
var daysText = days.length > 0 ? days.map(function (d) { return dayNames[d] || d; }).join(', ') : '-';
|
var daysText = days.length > 0 ? days.map(function (d) { return dayNames[d] || d; }).join(', ') : '-';
|
||||||
|
|
||||||
var uploadLimit = rule.w_tx_r_limit || 0;
|
var uploadLimit = rule.wan_tx_rate_limit || 0;
|
||||||
var downloadLimit = rule.w_rx_r_limit || 0;
|
var downloadLimit = rule.wan_rx_rate_limit || 0;
|
||||||
|
|
||||||
// 使用 isRuleActive 函数检查规则是否激活
|
// 使用 isRuleActive 函数检查规则是否激活
|
||||||
var isActive = isRuleActive(rule);
|
var isActive = isRuleActive(rule);
|
||||||
@@ -3746,8 +3746,8 @@ return view.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 设置限速值
|
// 设置限速值
|
||||||
var uploadLimit = editingRule.w_tx_r_limit || 0;
|
var uploadLimit = editingRule.wan_tx_rate_limit || 0;
|
||||||
var downloadLimit = editingRule.w_rx_r_limit || 0;
|
var downloadLimit = editingRule.wan_rx_rate_limit || 0;
|
||||||
|
|
||||||
// 根据限速值选择合适的单位
|
// 根据限速值选择合适的单位
|
||||||
var uploadUnit, uploadValue, downloadUnit, downloadValue;
|
var uploadUnit, uploadValue, downloadUnit, downloadValue;
|
||||||
@@ -4064,8 +4064,8 @@ return view.extend({
|
|||||||
|
|
||||||
var startTime = rule.time_slot && rule.time_slot.start ? rule.time_slot.start : '';
|
var startTime = rule.time_slot && rule.time_slot.start ? rule.time_slot.start : '';
|
||||||
var endTime = rule.time_slot && rule.time_slot.end ? rule.time_slot.end : '';
|
var endTime = rule.time_slot && rule.time_slot.end ? rule.time_slot.end : '';
|
||||||
var uploadLimit = rule.w_tx_r_limit || 0;
|
var uploadLimit = rule.wan_tx_rate_limit || 0;
|
||||||
var downloadLimit = rule.w_rx_r_limit || 0;
|
var downloadLimit = rule.wan_rx_rate_limit || 0;
|
||||||
|
|
||||||
var ruleItem = E('div', { 'class': 'schedule-rule-item' }, [
|
var ruleItem = E('div', { 'class': 'schedule-rule-item' }, [
|
||||||
E('div', { 'class': 'schedule-rule-info' }, [
|
E('div', { 'class': 'schedule-rule-info' }, [
|
||||||
@@ -4262,8 +4262,8 @@ return view.extend({
|
|||||||
var downloadLimits = [];
|
var downloadLimits = [];
|
||||||
|
|
||||||
activeRules.forEach(function (rule) {
|
activeRules.forEach(function (rule) {
|
||||||
var uploadLimit = rule.w_tx_r_limit || 0;
|
var uploadLimit = rule.wan_tx_rate_limit || 0;
|
||||||
var downloadLimit = rule.w_rx_r_limit || 0;
|
var downloadLimit = rule.wan_rx_rate_limit || 0;
|
||||||
|
|
||||||
// 只收集非零的限制值
|
// 只收集非零的限制值
|
||||||
if (uploadLimit > 0) {
|
if (uploadLimit > 0) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ LUCI_DEPENDS:=+ip-full +tc +nftables +bc +kmod-ifb
|
|||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
|
|
||||||
PKG_VERSION:=1.3.0
|
PKG_VERSION:=1.3.0
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
PKG_MAINTAINER:=sirpdboy <herboy2008@gmail.com>
|
PKG_MAINTAINER:=sirpdboy <herboy2008@gmail.com>
|
||||||
|
|
||||||
define Build/Compile
|
define Build/Compile
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Copyright (C) 2006 OpenWrt.org
|
|
||||||
# Copyright 2022-2023 sirpdboy <herboy2008@gmail.com>
|
|
||||||
NAME=eqosplus
|
|
||||||
IDLIST="/var/$NAME.idlist"
|
|
||||||
TMPID="/var/$NAME.tmpid"
|
|
||||||
|
|
||||||
idlist=`uci show $NAME | grep "enable='1'" | grep "device" | grep -oE '\[.*?\]' | grep -o '[0-9]' `
|
|
||||||
|
|
||||||
check_list() {
|
|
||||||
i=$1
|
|
||||||
checki='0'
|
|
||||||
start_time=$(uci -q get $NAME.@device[$i].timestart 2>/dev/null)
|
|
||||||
end_time=$(uci -q get $NAME.@device[$i].timeend 2>/dev/null)
|
|
||||||
wweek=`uci -q get $NAME.@device[$i].week `
|
|
||||||
current_time=$(date +%H:%M)
|
|
||||||
current_weekday=$(date +%u)
|
|
||||||
[ "$start_time" = "$end_time" ] || {
|
|
||||||
[[ "$start_time" < "$end_time" ]] && { [[ "$current_time" > "$start_time" ]] && [[ "$current_time" < "$end_time" ]] || return ; }
|
|
||||||
[[ "$start_time" > "$end_time" ]] && { [[ "$current_time" < "$start_time" ]] && [[ "$current_time" > "$end_time" ]] || return ; }
|
|
||||||
}
|
|
||||||
for ww in `echo $wweek | sed 's/,/ /g' `; do
|
|
||||||
if [ $current_weekday = $ww ] || [ 'x0' = x$ww ] ;then
|
|
||||||
checki='1'
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
return $re
|
|
||||||
}
|
|
||||||
|
|
||||||
idlistusr(){
|
|
||||||
|
|
||||||
checki='0'
|
|
||||||
[ -s $IDLIST ] || return
|
|
||||||
for list in $idlist ;do
|
|
||||||
check_list $list
|
|
||||||
if [ $checki == '1' ] ; then
|
|
||||||
[ `cat $IDLIST 2>/dev/null | grep "!${list}!" | wc -l ` -gt 0 ] || {
|
|
||||||
eqosplus add $list
|
|
||||||
echo "!${list}!" >> $IDLIST ; cat $IDLIST | sort | uniq > $TMPID ;cat $TMPID >$IDLIST ;rm -rf $TMPID
|
|
||||||
}
|
|
||||||
else
|
|
||||||
[ `cat $IDLIST 2>/dev/null | grep "!${list}!" | wc -l ` -gt 0 ] && {
|
|
||||||
eqosplus del $list
|
|
||||||
sed -i "/!$list!/d" $IDLIST >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
while :;do
|
|
||||||
sleep 30
|
|
||||||
idlistusr
|
|
||||||
sleep 30
|
|
||||||
done
|
|
||||||
@@ -124,6 +124,10 @@ const glossary = {
|
|||||||
prefmt: 'rule_%s',
|
prefmt: 'rule_%s',
|
||||||
field: 'rule-providers',
|
field: 'rule-providers',
|
||||||
},
|
},
|
||||||
|
inbound: {
|
||||||
|
prefmt: 'inbound_%s',
|
||||||
|
field: 'listeners',
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
prefmt: 'server_%s',
|
prefmt: 'server_%s',
|
||||||
field: 'listeners',
|
field: 'listeners',
|
||||||
@@ -148,7 +152,8 @@ const inbound_type = [
|
|||||||
['anytls', _('AnyTLS') + ' - ' + _('TCP')],
|
['anytls', _('AnyTLS') + ' - ' + _('TCP')],
|
||||||
['tuic', _('TUIC') + ' - ' + _('UDP')],
|
['tuic', _('TUIC') + ' - ' + _('UDP')],
|
||||||
['hysteria2', _('Hysteria2') + ' - ' + _('UDP')],
|
['hysteria2', _('Hysteria2') + ' - ' + _('UDP')],
|
||||||
//['tunnel', _('Tunnel') + ' - ' + _('TCP/UDP')]
|
['trusttunnel', _('TrustTunnel') + ' - ' + _('TCP/UDP')],
|
||||||
|
['tunnel', _('Tunnel') + ' - ' + _('TCP/UDP')]
|
||||||
];
|
];
|
||||||
|
|
||||||
const ip_version = [
|
const ip_version = [
|
||||||
@@ -183,6 +188,7 @@ const outbound_type = [
|
|||||||
['hysteria2', _('Hysteria2') + ' - ' + _('UDP')],
|
['hysteria2', _('Hysteria2') + ' - ' + _('UDP')],
|
||||||
['tuic', _('TUIC') + ' - ' + _('UDP')],
|
['tuic', _('TUIC') + ' - ' + _('UDP')],
|
||||||
['masque', _('Masque') + ' - ' + _('UDP')], // https://blog.cloudflare.com/post-quantum-warp/
|
['masque', _('Masque') + ' - ' + _('UDP')], // https://blog.cloudflare.com/post-quantum-warp/
|
||||||
|
['trusttunnel', _('TrustTunnel') + ' - ' + _('TCP/UDP')],
|
||||||
['wireguard', _('WireGuard') + ' - ' + _('UDP')],
|
['wireguard', _('WireGuard') + ' - ' + _('UDP')],
|
||||||
['ssh', _('SSH') + ' - ' + _('TCP')]
|
['ssh', _('SSH') + ' - ' + _('TCP')]
|
||||||
];
|
];
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -795,7 +795,7 @@ function renderRules(s, uciconfig) {
|
|||||||
UIEl.setValue(rule.toString('json'));
|
UIEl.setValue(rule.toString('json'));
|
||||||
}
|
}
|
||||||
o.write = function() {};
|
o.write = function() {};
|
||||||
//o.depends('SUB-RULE', '');
|
//o.depends('SUB-RULE', ''); // work on subrules not rules
|
||||||
o.editable = true;
|
o.editable = true;
|
||||||
|
|
||||||
o = s.option(form.Flag, 'src', _('src'),
|
o = s.option(form.Flag, 'src', _('src'),
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
'use strict';
|
||||||
|
'require form';
|
||||||
|
'require uci';
|
||||||
|
'require ui';
|
||||||
|
'require view';
|
||||||
|
|
||||||
|
'require fchomo as hm';
|
||||||
|
'require fchomo.listeners as lsnr'
|
||||||
|
|
||||||
|
return view.extend({
|
||||||
|
load() {
|
||||||
|
return Promise.all([
|
||||||
|
uci.load('fchomo')
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
render(data) {
|
||||||
|
let m, s, o;
|
||||||
|
|
||||||
|
m = new form.Map('fchomo', _('Edit inbound'));
|
||||||
|
|
||||||
|
/* Inbound settings START */
|
||||||
|
s = m.section(hm.GridSection, 'inbound', null);
|
||||||
|
s.addremove = true;
|
||||||
|
s.rowcolors = true;
|
||||||
|
s.sortable = true;
|
||||||
|
s.nodescriptions = true;
|
||||||
|
s.hm_modaltitle = [ _('Inbound'), _('Add a inbound') ];
|
||||||
|
s.hm_prefmt = hm.glossary[s.sectiontype].prefmt;
|
||||||
|
s.hm_field = hm.glossary[s.sectiontype].field;
|
||||||
|
s.hm_lowcase_only = false;
|
||||||
|
|
||||||
|
lsnr.renderListeners(s, data[0], true);
|
||||||
|
/* Inbound settings END */
|
||||||
|
|
||||||
|
return m.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -255,13 +255,13 @@ return view.extend({
|
|||||||
/* hm.validateAuth */
|
/* hm.validateAuth */
|
||||||
so = ss.taboption('field_general', form.Value, 'username', _('Username'));
|
so = ss.taboption('field_general', form.Value, 'username', _('Username'));
|
||||||
so.validate = hm.validateAuthUsername;
|
so.validate = hm.validateAuthUsername;
|
||||||
so.depends({type: /^(http|socks5|mieru|ssh)$/});
|
so.depends({type: /^(http|socks5|mieru|trusttunnel|ssh)$/});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.Value, 'password', _('Password'));
|
so = ss.taboption('field_general', form.Value, 'password', _('Password'));
|
||||||
so.password = true;
|
so.password = true;
|
||||||
so.validate = hm.validateAuthPassword;
|
so.validate = hm.validateAuthPassword;
|
||||||
so.depends({type: /^(http|socks5|mieru|trojan|anytls|hysteria2|tuic|ssh)$/});
|
so.depends({type: /^(http|socks5|mieru|trojan|anytls|hysteria2|tuic|trusttunnel|ssh)$/});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_general', hm.TextValue, 'headers', _('HTTP header'));
|
so = ss.taboption('field_general', hm.TextValue, 'headers', _('HTTP header'));
|
||||||
@@ -375,6 +375,12 @@ return view.extend({
|
|||||||
so.depends('type', 'mieru');
|
so.depends('type', 'mieru');
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
|
so = ss.taboption('field_general', form.Value, 'mieru_traffic_pattern', _('Traffic pattern'),
|
||||||
|
_('A base64 string is used to fine-tune network behavior.<br/>Please refer to the <a target="_blank" href="%s" rel="noreferrer noopener">document</a>.')
|
||||||
|
.format('https://github.com/enfein/mieru/blob/main/docs/traffic-pattern.md'));
|
||||||
|
so.depends('type', 'mieru');
|
||||||
|
so.modalonly = true;
|
||||||
|
|
||||||
/* Sudoku fields */
|
/* Sudoku fields */
|
||||||
so = ss.taboption('field_general', form.Value, 'sudoku_key', _('Key'),
|
so = ss.taboption('field_general', form.Value, 'sudoku_key', _('Key'),
|
||||||
_('The ED25519 available private key or UUID provided by Sudoku server.'));
|
_('The ED25519 available private key or UUID provided by Sudoku server.'));
|
||||||
@@ -448,18 +454,19 @@ return view.extend({
|
|||||||
so.value('stream', _('split-stream') + ' - ' + _('CDN support'));
|
so.value('stream', _('split-stream') + ' - ' + _('CDN support'));
|
||||||
so.value('poll', _('poll') + ' - ' + _('CDN support'));
|
so.value('poll', _('poll') + ' - ' + _('CDN support'));
|
||||||
so.value('auto', _('Auto') + ' - ' + _('CDN support'));
|
so.value('auto', _('Auto') + ' - ' + _('CDN support'));
|
||||||
|
so.value('ws', _('WebSocket') + ' - ' + _('CDN support'));
|
||||||
so.depends('sudoku_http_mask', '1');
|
so.depends('sudoku_http_mask', '1');
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.Flag, 'sudoku_http_mask_tls', _('HTTP mask: %s').format(_('TLS')));
|
so = ss.taboption('field_general', form.Flag, 'sudoku_http_mask_tls', _('HTTP mask: %s').format(_('TLS')));
|
||||||
so.default = so.disabled;
|
so.default = so.disabled;
|
||||||
so.depends({sudoku_http_mask_mode: /^(stream|poll|auto)$/});
|
so.depends({sudoku_http_mask_mode: /^(stream|poll|auto|ws)$/});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.Value, 'sudoku_http_mask_host', _('HTTP mask: %s').format(_('Host/SNI override')));
|
so = ss.taboption('field_general', form.Value, 'sudoku_http_mask_host', _('HTTP mask: %s').format(_('Host/SNI override')));
|
||||||
so.datatype = 'or(hostname, hostport)';
|
so.datatype = 'or(hostname, hostport)';
|
||||||
so.placeholder = 'example.com[:443]';
|
so.placeholder = 'example.com[:443]';
|
||||||
so.depends({sudoku_http_mask_mode: /^(stream|poll|auto)$/});
|
so.depends({sudoku_http_mask_mode: /^(stream|poll|auto|ws)$/});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.Value, 'sudoku_path_root', _('HTTP root path'));
|
so = ss.taboption('field_general', form.Value, 'sudoku_path_root', _('HTTP root path'));
|
||||||
@@ -475,8 +482,10 @@ return view.extend({
|
|||||||
so.validate = function(section_id, value) {
|
so.validate = function(section_id, value) {
|
||||||
const http_mask_mode = this.section.getOption('sudoku_http_mask_mode').formvalue(section_id);
|
const http_mask_mode = this.section.getOption('sudoku_http_mask_mode').formvalue(section_id);
|
||||||
|
|
||||||
|
if (http_mask_mode === 'ws' && value !== 'off')
|
||||||
|
return _('Expecting: %s').format(_('only applies when %s is not %s.').format(_('HTTP mask mode'), _('WebSocket')));
|
||||||
if (value === 'on' && !['stream', 'poll', 'auto'].includes(http_mask_mode))
|
if (value === 'on' && !['stream', 'poll', 'auto'].includes(http_mask_mode))
|
||||||
return _('Expecting: %s').format(_('only applies when %s is stream/poll/auto.').format(_('HTTP mask mode')));
|
return _('Expecting: %s').format(_('only applies when %s is %s.').format(_('HTTP mask mode'), _('stream/poll/auto')));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -512,15 +521,6 @@ return view.extend({
|
|||||||
so.depends('type', 'tuic');
|
so.depends('type', 'tuic');
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.ListValue, 'tuic_congestion_controller', _('Congestion controller'),
|
|
||||||
_('QUIC congestion controller.'));
|
|
||||||
so.default = hm.congestion_controller[0][0];
|
|
||||||
hm.congestion_controller.forEach((res) => {
|
|
||||||
so.value.apply(so, res);
|
|
||||||
})
|
|
||||||
so.depends('type', 'tuic');
|
|
||||||
so.modalonly = true;
|
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.ListValue, 'tuic_udp_relay_mode', _('UDP relay mode'),
|
so = ss.taboption('field_general', form.ListValue, 'tuic_udp_relay_mode', _('UDP relay mode'),
|
||||||
_('UDP packet relay mode.'));
|
_('UDP packet relay mode.'));
|
||||||
so.default = 'native';
|
so.default = 'native';
|
||||||
@@ -714,12 +714,15 @@ return view.extend({
|
|||||||
so.depends('masque_remote_dns_resolve', '1');
|
so.depends('masque_remote_dns_resolve', '1');
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.ListValue, 'masque_congestion_controller', _('Congestion controller'));
|
/* TrustTunnel fields */
|
||||||
so.default = hm.congestion_controller[0][0];
|
so = ss.taboption('field_general', form.Flag, 'trusttunnel_health_check', _('Health check'));
|
||||||
hm.congestion_controller.forEach((res) => {
|
so.default = so.enabled;
|
||||||
so.value.apply(so, res);
|
so.depends('type', 'trusttunnel');
|
||||||
})
|
so.modalonly = true;
|
||||||
so.depends('type', 'masque');
|
|
||||||
|
so = ss.taboption('field_general', form.Flag, 'trusttunnel_quic', _('QUIC'));
|
||||||
|
so.default = so.disabled;
|
||||||
|
so.depends('type', 'trusttunnel');
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
/* WireGuard fields */
|
/* WireGuard fields */
|
||||||
@@ -848,9 +851,17 @@ return view.extend({
|
|||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
/* Extra fields */
|
/* Extra fields */
|
||||||
|
so = ss.taboption('field_general', form.ListValue, 'congestion_controller', _('Congestion controller'));
|
||||||
|
so.default = hm.congestion_controller[0][0];
|
||||||
|
hm.congestion_controller.forEach((res) => {
|
||||||
|
so.value.apply(so, res);
|
||||||
|
})
|
||||||
|
so.depends({type: /^(tuic|masque|trusttunnel)$/});
|
||||||
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.Flag, 'udp', _('UDP'));
|
so = ss.taboption('field_general', form.Flag, 'udp', _('UDP'));
|
||||||
so.default = so.disabled;
|
so.default = so.disabled;
|
||||||
so.depends({type: /^(direct|socks5|ss|mieru|vmess|vless|trojan|anytls|masque|wireguard)$/});
|
so.depends({type: /^(direct|socks5|ss|mieru|vmess|vless|trojan|anytls|trusttunnel|masque|wireguard)$/});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.Flag, 'uot', _('UoT'),
|
so = ss.taboption('field_general', form.Flag, 'uot', _('UoT'),
|
||||||
@@ -941,7 +952,7 @@ return view.extend({
|
|||||||
let tls = this.section.getUIElement(section_id, 'tls').node.querySelector('input');
|
let tls = this.section.getUIElement(section_id, 'tls').node.querySelector('input');
|
||||||
|
|
||||||
// Force enabled
|
// Force enabled
|
||||||
if (['trojan', 'anytls', 'hysteria', 'hysteria2', 'tuic'].includes(type)) {
|
if (['trojan', 'anytls', 'hysteria', 'hysteria2', 'tuic', 'trusttunnel'].includes(type)) {
|
||||||
tls.checked = true;
|
tls.checked = true;
|
||||||
tls.disabled = true;
|
tls.disabled = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -950,7 +961,7 @@ return view.extend({
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
so.depends({type: /^(http|socks5|vmess|vless|trojan|anytls|hysteria|hysteria2|tuic)$/});
|
so.depends({type: /^(http|socks5|vmess|vless|trojan|anytls|hysteria|hysteria2|tuic|trusttunnel)$/});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_tls', form.Flag, 'tls_disable_sni', _('Disable SNI'),
|
so = ss.taboption('field_tls', form.Flag, 'tls_disable_sni', _('Disable SNI'),
|
||||||
@@ -961,7 +972,7 @@ return view.extend({
|
|||||||
|
|
||||||
so = ss.taboption('field_tls', form.Value, 'tls_sni', _('TLS SNI'),
|
so = ss.taboption('field_tls', form.Value, 'tls_sni', _('TLS SNI'),
|
||||||
_('Used to verify the hostname on the returned certificates.'));
|
_('Used to verify the hostname on the returned certificates.'));
|
||||||
so.depends({tls: '1', type: /^(http|vmess|vless|trojan|anytls|hysteria|hysteria2)$/});
|
so.depends({tls: '1', type: /^(http|vmess|vless|trojan|anytls|hysteria|hysteria2|trusttunnel)$/});
|
||||||
so.depends({tls: '1', tls_disable_sni: '0', type: /^(tuic)$/});
|
so.depends({tls: '1', tls_disable_sni: '0', type: /^(tuic)$/});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
@@ -991,6 +1002,9 @@ return view.extend({
|
|||||||
case 'anytls':
|
case 'anytls':
|
||||||
def_alpn = ['h2', 'http/1.1'];
|
def_alpn = ['h2', 'http/1.1'];
|
||||||
break;
|
break;
|
||||||
|
case 'trusttunnel':
|
||||||
|
def_alpn = ['h3', 'h2'];
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
def_alpn = [];
|
def_alpn = [];
|
||||||
}
|
}
|
||||||
@@ -1000,7 +1014,7 @@ return view.extend({
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
so.depends({tls: '1', type: /^(vmess|vless|trojan|anytls|hysteria|hysteria2|tuic)$/});
|
so.depends({tls: '1', type: /^(vmess|vless|trojan|anytls|hysteria|hysteria2|tuic|trusttunnel)$/});
|
||||||
so.depends({type: 'ss', plugin: 'shadow-tls'});
|
so.depends({type: 'ss', plugin: 'shadow-tls'});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
@@ -1022,7 +1036,7 @@ return view.extend({
|
|||||||
'<br/>' +
|
'<br/>' +
|
||||||
_('This is <strong>DANGEROUS</strong>, your traffic is almost like <strong>PLAIN TEXT</strong>! Use at your own risk!'));
|
_('This is <strong>DANGEROUS</strong>, your traffic is almost like <strong>PLAIN TEXT</strong>! Use at your own risk!'));
|
||||||
so.default = so.disabled;
|
so.default = so.disabled;
|
||||||
so.depends({tls: '1', type: /^(http|socks5|vmess|vless|trojan|anytls|hysteria|hysteria2|tuic)$/});
|
so.depends({tls: '1', type: /^(http|socks5|vmess|vless|trojan|anytls|hysteria|hysteria2|tuic|trusttunnel)$/});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_tls', form.Value, 'tls_cert_path', _('Certificate path') + _(' (mTLS)'),
|
so = ss.taboption('field_tls', form.Value, 'tls_cert_path', _('Certificate path') + _(' (mTLS)'),
|
||||||
@@ -1076,7 +1090,7 @@ return view.extend({
|
|||||||
hm.tls_client_fingerprints.forEach((res) => {
|
hm.tls_client_fingerprints.forEach((res) => {
|
||||||
so.value.apply(so, res);
|
so.value.apply(so, res);
|
||||||
})
|
})
|
||||||
so.depends({tls: '1', type: /^(vmess|vless|trojan|anytls)$/});
|
so.depends({tls: '1', type: /^(vmess|vless|trojan|anytls|trusttunnel)$/});
|
||||||
so.depends({type: 'ss', plugin: /^(shadow-tls|restls)$/});
|
so.depends({type: 'ss', plugin: /^(shadow-tls|restls)$/});
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
|
|||||||
@@ -6,125 +6,17 @@
|
|||||||
'require view';
|
'require view';
|
||||||
|
|
||||||
'require fchomo as hm';
|
'require fchomo as hm';
|
||||||
|
'require fchomo.listeners as lsnr'
|
||||||
const CBIDummyCopyValue = hm.CopyValue.extend({
|
|
||||||
__name__: 'CBI.DummyCopyValue',
|
|
||||||
|
|
||||||
renderWidget(/* ... */) {
|
|
||||||
let node = hm.CopyValue.prototype.renderWidget.apply(this, arguments);
|
|
||||||
|
|
||||||
node.firstChild.style.width = '30em';
|
|
||||||
|
|
||||||
return node;
|
|
||||||
},
|
|
||||||
|
|
||||||
write: function() {}
|
|
||||||
});
|
|
||||||
|
|
||||||
class VlessEncryption {
|
|
||||||
// origin:
|
|
||||||
// https://github.com/XTLS/Xray-core/pull/5067
|
|
||||||
// server:
|
|
||||||
// https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/factory.go#L64
|
|
||||||
// https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/server.go#L42
|
|
||||||
// client:
|
|
||||||
// https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/factory.go#L12
|
|
||||||
// https://github.com/muink/mihomo/blob/7917f24f428e40ac20b8b8f953b02cf59d1be334/transport/vless/encryption/client.go#L45
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
"method": "mlkem768x25519plus",
|
|
||||||
"xormode": "native",
|
|
||||||
"ticket": "600s",
|
|
||||||
"rtt": "0rtt",
|
|
||||||
"paddings": [ // Optional
|
|
||||||
"100-111-1111",
|
|
||||||
"75-0-111",
|
|
||||||
"50-0-3333",
|
|
||||||
...
|
|
||||||
],
|
|
||||||
"keypairs": [
|
|
||||||
{
|
|
||||||
"type": "vless-x25519",
|
|
||||||
"server": "cP5Oy9MOpTaBKKE17Pfd56mbb1CIfp5EMpyBYqr2EG8",
|
|
||||||
"client": "khEcQMT8j41xWmGYKpZtQ4vd8_9VWyFVmmCDIhRJ-Uk"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "vless-mlkem768",
|
|
||||||
"server": "UHPx3nf-FVxF95byAw0YG025aQNw9HxKej-MiG5AhTcdW_WFpHlTVYQU5NHmXP6tmljSnB2iPmSQ29fisGxEog",
|
|
||||||
"client": "h4sdZgCc5-ZefvQ8mZmReOWQdxYb0mwngMdl7pKhYEZZpGWHUPKAmxug87Bgj3GqSHs195QeVpxfrMLNB5Jm0Ge71Fc-A3aLpaS3C3pARbGQoquUDUEVDNwEWjQTvFpGTUV3Nddw_LlRmWN6Wqhguti9cpS6GhEmkBvBayFeHgZosuaQ1FMoAqIeQzSSSoguCZtGLUmdQjEs3zc5rwG1rNanbhtyI3QnooYvr3A0vggIkbmddjtjwYaVQdMAj9Moavc12EAUajOV91QA73RWVuhelbe7pLumsHiW-emIdBgVhEgDDYdGaLq1E8QjB0WbIfufnJp-CJa3Ieu9gmDASTlQBeEREeA9gfoZcTpYD8elhJIJxaPJKXchvUVkFhZarcivlKoqVuaFPzsJM7KQCBC8zfS0t_oiBka-uzg3_Hl153nMTDaCAbZULPZGE-p2EazI2eFBCDktdHtDffJNo7i7ZYSkWkqN9ysr2QZRvYG_PYCzcYSo34Gf5WNvHKuz0Ye3kFkckfuirCmzr3knw2azrSOmpTOX_RSlMlse7HgFYwxHPMJnzPS19ymiwKZPgrAMvCmAUZmsxZGDoKeusNEDGSSFhLcTQys20qGBGYasIgKYGjAKGjK7SCxSOCGBQSU496XBkXQEeOB7k9Sh8jdB0pQGAZw9Ntwvrts2DjIUcsQBv-XEGfnHQXoBmDgzwzYEWxeHd0oNbPIlz7CqvNseoKu6uPZl85xynum6aWd6BDDAtwobbqYkuMUfOUhXf_cH13kWSnuJ6QrOxah94JzAnda3tWRDQ3RajOOjk-OXhbOqi8QMJRFdA_C-xMwQalM_rTSTKOqyCcaNSTkVmMlmyOt90tptk7jKUizDmGhGbsSU8WMY5mhdZ3eUd5O6gQitiMHI1EqnlaRNsXnKFoJ5yHV82Wp1dhFONCG_dlpqunVJD5bFgpxtdFDD-KmXQTymAalFjxeVl_xdc5xd4XYCYmk5dhEiQBE0J_S3Z6x0tmFORpWG9lESK_OBRSul9oKZh9Vet-UZ8FSOVtNFwbeokRwWpFuFL1dL3UpJeININ2cgUfDNWQlwItkokiFf_Kdy12y2O_hqJtoTpNttNxTOiclDzKM1KHNOjYJgTgydcid3mmJl3eA6ezyrDAw1RLCHBucIvYRfwbkmpYMvnfAaA2DIiaTNaSxX8BUl92V49UVKWlQSp8ijfmmTRHrBMmxKjvBIgHqC6dSMhVUEOMzCKXAO3giCS3eZzdrNQGhhqTxpYYnFf6uLoKOIiaGY-ByI1YoIVXxX8aCTOOpesFvHjwOKBEoj4Hoxd3iFMUJQazR7P2drnfmS11kgipM7pSUgB7POKwxEF0NQCedM41wVIuoathAqD6N6qalwQ6iOKlZOBUwwMVAMRDJ3aomG37ZeLYhv6fB0-pUUJSN1q4knjtkLFIJSUrih9FZ0XnOll_aeEgOICqQkb4aOMrovjcJEWvgdjUqGPdyIGgkurfqBRHih3dukUcYxt6Y__4KLQ7acqMx0FOFv0ZxFRTCIRGj_GAlFWUi6fpuPKebXUnEn1PRE0iNXwUV_4jESWb0"
|
|
||||||
},
|
|
||||||
...
|
|
||||||
]
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
constructor(payload) {
|
|
||||||
this.input = payload || '';
|
|
||||||
try {
|
|
||||||
let content = JSON.parse(this.input.trim());
|
|
||||||
Object.keys(content).forEach(key => this[key] = content[key]);
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
this.method ||= hm.vless_encryption.methods[0][0];
|
|
||||||
this.xormode ||= hm.vless_encryption.xormodes[0][0];
|
|
||||||
this.ticket ||= hm.vless_encryption.tickets[0][0];
|
|
||||||
this.rtt ||= hm.vless_encryption.rtts[0][0];
|
|
||||||
this.paddings ||= [];
|
|
||||||
this.keypairs ||= [];
|
|
||||||
}
|
|
||||||
|
|
||||||
setKey(key, value) {
|
|
||||||
this[key] = value;
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
_toMihomo(payload, side) {
|
|
||||||
if (!['server', 'client'].includes(side))
|
|
||||||
throw new Error('Unknown side: ' + side); // `Unknown side: '${side}'`
|
|
||||||
|
|
||||||
let required = [
|
|
||||||
payload.method,
|
|
||||||
payload.xormode,
|
|
||||||
side === 'server' ? payload.ticket : side === 'client' ? payload.rtt : null
|
|
||||||
].join('.');
|
|
||||||
|
|
||||||
return required +
|
|
||||||
(hm.isEmpty(payload.paddings) ? '' : '.' + payload.paddings.join('.')) + // Optional
|
|
||||||
(hm.isEmpty(payload.keypairs) ? '' : '.' + payload.keypairs.map(e => e[side]).join('.')); // Required
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(format, side) {
|
|
||||||
format ||= 'json';
|
|
||||||
|
|
||||||
let payload = hm.removeBlankAttrs({
|
|
||||||
method: this.method,
|
|
||||||
xormode: this.xormode,
|
|
||||||
ticket: this.ticket,
|
|
||||||
rtt: this.rtt,
|
|
||||||
paddings: this.paddings || [],
|
|
||||||
keypairs: this.keypairs || []
|
|
||||||
});
|
|
||||||
|
|
||||||
if (format === 'json')
|
|
||||||
return JSON.stringify(payload);
|
|
||||||
else if (format === 'mihomo')
|
|
||||||
return this._toMihomo(payload, side);
|
|
||||||
else
|
|
||||||
throw new Error(`Unknown format: '${format}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
load() {
|
load() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
uci.load('fchomo'),
|
uci.load('fchomo')
|
||||||
hm.getFeatures()
|
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
render(data) {
|
render(data) {
|
||||||
const dashboard_repo = uci.get(data[0], 'api', 'dashboard_repo');
|
const dashboard_repo = uci.get(data[0], 'api', 'dashboard_repo');
|
||||||
const features = data[1];
|
|
||||||
|
|
||||||
let m, s, o;
|
let m, s, o;
|
||||||
|
|
||||||
@@ -168,845 +60,7 @@ return view.extend({
|
|||||||
s.hm_field = hm.glossary[s.sectiontype].field;
|
s.hm_field = hm.glossary[s.sectiontype].field;
|
||||||
s.hm_lowcase_only = false;
|
s.hm_lowcase_only = false;
|
||||||
|
|
||||||
s.tab('field_general', _('General fields'));
|
lsnr.renderListeners(s, data[0], false);
|
||||||
s.tab('field_vless_encryption', _('Vless Encryption fields'));
|
|
||||||
s.tab('field_tls', _('TLS fields'));
|
|
||||||
s.tab('field_transport', _('Transport fields'));
|
|
||||||
s.tab('field_multiplex', _('Multiplex fields'));
|
|
||||||
s.tab('field_listen', _('Listen fields'));
|
|
||||||
|
|
||||||
/* General fields */
|
|
||||||
o = s.taboption('field_general', form.Value, 'label', _('Label'));
|
|
||||||
o.load = hm.loadDefaultLabel;
|
|
||||||
o.validate = hm.validateUniqueValue;
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Flag, 'enabled', _('Enable'));
|
|
||||||
o.default = o.enabled;
|
|
||||||
o.editable = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Flag, 'auto_firewall', _('Firewall'),
|
|
||||||
_('Auto configure firewall'));
|
|
||||||
o.default = o.enabled;
|
|
||||||
o.editable = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.ListValue, 'type', _('Type'));
|
|
||||||
o.default = hm.inbound_type[0][0];
|
|
||||||
hm.inbound_type.forEach((res) => {
|
|
||||||
o.value.apply(o, res);
|
|
||||||
})
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'listen', _('Listen address'));
|
|
||||||
o.datatype = 'ipaddr';
|
|
||||||
o.placeholder = '::';
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'port', _('Listen port') + ' / ' + _('Ports pool'));
|
|
||||||
o.datatype = 'or(port, portrange)';
|
|
||||||
//o.placeholder = '1080,2079-2080,3080'; // @fw4 does not support port lists with commas
|
|
||||||
o.rmempty = false;
|
|
||||||
//o.validate = hm.validateCommonPort; // @fw4 does not support port lists with commas
|
|
||||||
|
|
||||||
// @dev: Features under development
|
|
||||||
// @rule
|
|
||||||
// @proxy
|
|
||||||
|
|
||||||
/* HTTP / SOCKS fields */
|
|
||||||
/* hm.validateAuth */
|
|
||||||
o = s.taboption('field_general', form.Value, 'username', _('Username'));
|
|
||||||
o.validate = hm.validateAuthUsername;
|
|
||||||
o.depends({type: /^(http|socks|mixed|mieru|trojan|anytls|hysteria2)$/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', hm.GenValue, 'password', _('Password'));
|
|
||||||
o.password = true;
|
|
||||||
o.validate = hm.validateAuthPassword;
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends({type: /^(http|socks|mixed|mieru|trojan|anytls|hysteria2)$/, username: /.+/});
|
|
||||||
o.depends({type: /^(tuic)$/, uuid: /.+/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
/* Hysteria2 fields */
|
|
||||||
o = s.taboption('field_general', form.Value, 'hysteria_up_mbps', _('Max upload speed'),
|
|
||||||
_('In Mbps.'));
|
|
||||||
o.datatype = 'uinteger';
|
|
||||||
o.depends('type', 'hysteria2');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'hysteria_down_mbps', _('Max download speed'),
|
|
||||||
_('In Mbps.'));
|
|
||||||
o.datatype = 'uinteger';
|
|
||||||
o.depends('type', 'hysteria2');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Flag, 'hysteria_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.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.ListValue, 'hysteria_obfs_type', _('Obfuscate type'));
|
|
||||||
o.value('', _('Disable'));
|
|
||||||
o.value('salamander', _('Salamander'));
|
|
||||||
o.depends('type', 'hysteria2');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', hm.GenValue, 'hysteria_obfs_password', _('Obfuscate password'),
|
|
||||||
_('Enabling obfuscation will make the server incompatible with standard QUIC connections, losing the ability to masquerade with HTTP/3.'));
|
|
||||||
o.password = true;
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends('type', 'hysteria');
|
|
||||||
o.depends({type: 'hysteria2', hysteria_obfs_type: /.+/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'hysteria_masquerade', _('Masquerade'),
|
|
||||||
_('HTTP3 server behavior when authentication fails.<br/>A 404 page will be returned if empty.'));
|
|
||||||
o.placeholder = 'file:///var/www or http://127.0.0.1:8080'
|
|
||||||
o.depends('type', 'hysteria2');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
/* Shadowsocks fields */
|
|
||||||
o = s.taboption('field_general', form.ListValue, 'shadowsocks_chipher', _('Chipher'));
|
|
||||||
o.default = hm.shadowsocks_cipher_methods[1][0];
|
|
||||||
hm.shadowsocks_cipher_methods.forEach((res) => {
|
|
||||||
o.value.apply(o, res);
|
|
||||||
})
|
|
||||||
o.depends('type', 'shadowsocks');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', hm.GenValue, 'shadowsocks_password', _('Password'));
|
|
||||||
o.password = true;
|
|
||||||
o.validate = function(section_id, value) {
|
|
||||||
const encmode = this.section.getOption('shadowsocks_chipher').formvalue(section_id);
|
|
||||||
return hm.validateShadowsocksPassword.call(this, encmode, section_id, value);
|
|
||||||
}
|
|
||||||
o.depends({type: 'shadowsocks', shadowsocks_chipher: /.+/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
/* Mieru fields */
|
|
||||||
o = s.taboption('field_general', form.ListValue, 'mieru_transport', _('Transport'));
|
|
||||||
o.default = 'TCP';
|
|
||||||
o.value('TCP');
|
|
||||||
o.value('UDP');
|
|
||||||
o.depends('type', 'mieru');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
/* Sudoku fields */
|
|
||||||
const sudoku_keytypes = [
|
|
||||||
['sudoku-keypair', _('sudoku-keypair')],
|
|
||||||
['uuid', _('UUID')]
|
|
||||||
]
|
|
||||||
o = s.taboption('field_general', hm.GenValue, 'sudoku_key', _('Key'),
|
|
||||||
_('The ED25519 master public key or UUID generated by Sudoku.'));
|
|
||||||
o.hm_options = {
|
|
||||||
type: sudoku_keytypes[0][0],
|
|
||||||
callback: function(result) {
|
|
||||||
if (result.uuid)
|
|
||||||
return [
|
|
||||||
[this.option, result.uuid],
|
|
||||||
['sudoku_client_key', result.uuid]
|
|
||||||
]
|
|
||||||
else
|
|
||||||
return [
|
|
||||||
[this.option, result.public_key],
|
|
||||||
['sudoku_client_key', result.private_key]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.renderWidget = function(section_id, option_index, cfgvalue) {
|
|
||||||
let node = form.Value.prototype.renderWidget.call(this, section_id, option_index, cfgvalue);
|
|
||||||
const cbid = this.cbid(section_id) + '._keytype_select';
|
|
||||||
const selected = this.hm_options.type;
|
|
||||||
|
|
||||||
let selectEl = E('select', {
|
|
||||||
id: cbid,
|
|
||||||
class: 'cbi-input-select',
|
|
||||||
style: 'width: 10em',
|
|
||||||
});
|
|
||||||
|
|
||||||
sudoku_keytypes.forEach(([k, v]) => {
|
|
||||||
selectEl.appendChild(E('option', {
|
|
||||||
'value': k,
|
|
||||||
'selected': (k === selected) ? '' : null
|
|
||||||
}, [ v ]));
|
|
||||||
});
|
|
||||||
|
|
||||||
node.appendChild(E('div', { 'class': 'control-group' }, [
|
|
||||||
selectEl,
|
|
||||||
E('button', {
|
|
||||||
class: 'cbi-button cbi-button-add',
|
|
||||||
click: ui.createHandlerFn(this, () => {
|
|
||||||
this.hm_options.type = document.getElementById(cbid).value;
|
|
||||||
|
|
||||||
return hm.handleGenKey.call(this, this.hm_options);
|
|
||||||
})
|
|
||||||
}, [ _('Generate') ])
|
|
||||||
]));
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends('type', 'sudoku');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', hm.CopyValue, 'sudoku_client_key', _('Client key'));
|
|
||||||
o.depends('type', 'sudoku');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.ListValue, 'sudoku_aead_method', _('Chipher'));
|
|
||||||
o.default = hm.sudoku_cipher_methods[0][0];
|
|
||||||
hm.sudoku_cipher_methods.forEach((res) => {
|
|
||||||
o.value.apply(o, res);
|
|
||||||
})
|
|
||||||
o.validate = function(section_id, value) {
|
|
||||||
const pure_downlink = this.section.getUIElement(section_id, 'sudoku_enable_pure_downlink')?.node.querySelector('input').checked;
|
|
||||||
|
|
||||||
if (value === 'none' && pure_downlink === false)
|
|
||||||
return _('Expecting: %s').format(_('Chipher must be enabled if obfuscate downlink is disabled.'));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
o.depends('type', 'sudoku');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.ListValue, 'sudoku_table_type', _('Obfuscate type'));
|
|
||||||
o.value('prefer_ascii', _('Obfuscated as ASCII data stream'));
|
|
||||||
o.value('prefer_entropy', _('Obfuscated as low-entropy data stream'));
|
|
||||||
o.depends('type', 'sudoku');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.DynamicList, 'sudoku_custom_tables', _('Custom byte layout'));
|
|
||||||
o.renderWidget = function(/* ... */) {
|
|
||||||
let node = form.DynamicList.prototype.renderWidget.apply(this, arguments);
|
|
||||||
|
|
||||||
(node.querySelector('.control-group') || node).appendChild(E('button', {
|
|
||||||
class: 'cbi-button cbi-button-positive',
|
|
||||||
title: _('Generate'),
|
|
||||||
click: ui.createHandlerFn(this, hm.handleGenKey, this.hm_options || this.option)
|
|
||||||
}, [ _('Generate') ]));
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
o.validate = hm.validateSudokuCustomTable;
|
|
||||||
o.depends('sudoku_table_type', 'prefer_entropy');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'sudoku_padding_min', _('Minimum padding rate'));
|
|
||||||
o.datatype = 'and(uinteger, range(0, 100))';
|
|
||||||
o.default = 1;
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends('type', 'sudoku');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'sudoku_padding_max', _('Maximum padding rate'));
|
|
||||||
o.datatype = 'and(uinteger, range(0, 100))';
|
|
||||||
o.default = 15;
|
|
||||||
o.rmempty = false;
|
|
||||||
o.validate = function(section_id, value) {
|
|
||||||
const padding_min = this.section.getOption('sudoku_padding_min').formvalue(section_id);
|
|
||||||
|
|
||||||
if (value < padding_min)
|
|
||||||
return _('Expecting: %s').format(_('Maximum padding rate must be greater than or equal to the minimum padding rate.'));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
o.depends('type', 'sudoku');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'sudoku_handshake_timeout', _('Handshake timeout'),
|
|
||||||
_('In seconds.'));
|
|
||||||
o.datatype = 'uinteger';
|
|
||||||
o.placeholder = 5;
|
|
||||||
o.depends('type', 'sudoku');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Flag, 'sudoku_enable_pure_downlink', _('Enable obfuscate for downlink'),
|
|
||||||
_('false = bandwidth optimized downlink; true = pure Sudoku downlink.'));
|
|
||||||
o.default = o.enabled;
|
|
||||||
o.depends('type', 'sudoku');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Flag, 'sudoku_http_mask', _('HTTP mask'));
|
|
||||||
o.default = o.enabled;
|
|
||||||
o.depends('type', 'sudoku');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.ListValue, 'sudoku_http_mask_mode', _('HTTP mask mode'));
|
|
||||||
o.default = 'legacy';
|
|
||||||
o.value('legacy', _('Legacy'));
|
|
||||||
o.value('stream', _('split-stream') + ' - ' + _('CDN support'));
|
|
||||||
o.value('poll', _('poll') + ' - ' + _('CDN support'));
|
|
||||||
o.value('auto', _('Auto') + ' - ' + _('CDN support'));
|
|
||||||
o.depends('sudoku_http_mask', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'sudoku_path_root', _('HTTP root path'));
|
|
||||||
o.depends('sudoku_http_mask', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
/* Tuic fields */
|
|
||||||
o = s.taboption('field_general', hm.GenValue, 'uuid', _('UUID'));
|
|
||||||
o.rmempty = false;
|
|
||||||
o.validate = hm.validateUUID;
|
|
||||||
o.depends('type', 'tuic');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.ListValue, 'tuic_congestion_controller', _('Congestion controller'),
|
|
||||||
_('QUIC congestion controller.'));
|
|
||||||
o.default = hm.congestion_controller[0][0];
|
|
||||||
hm.congestion_controller.forEach((res) => {
|
|
||||||
o.value.apply(o, res);
|
|
||||||
})
|
|
||||||
o.depends('type', 'tuic');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'tuic_max_udp_relay_packet_size', _('Max UDP relay packet size'));
|
|
||||||
o.datatype = 'uinteger';
|
|
||||||
o.default = '1500';
|
|
||||||
o.depends('type', 'tuic');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'tuic_max_idle_time', _('Idle timeout'),
|
|
||||||
_('In seconds.'));
|
|
||||||
o.default = '15000';
|
|
||||||
o.validate = hm.validateTimeDuration;
|
|
||||||
o.depends('type', 'tuic');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'tuic_authentication_timeout', _('Auth timeout'),
|
|
||||||
_('In seconds.'));
|
|
||||||
o.default = '1000';
|
|
||||||
o.validate = hm.validateTimeDuration;
|
|
||||||
o.depends('type', 'tuic');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
/* Trojan fields */
|
|
||||||
o = s.taboption('field_general', form.Flag, 'trojan_ss_enabled', _('Shadowsocks encrypt'));
|
|
||||||
o.default = o.disabled;
|
|
||||||
o.depends('type', 'trojan');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.ListValue, 'trojan_ss_chipher', _('Shadowsocks chipher'));
|
|
||||||
o.default = hm.trojan_cipher_methods[0][0];
|
|
||||||
hm.trojan_cipher_methods.forEach((res) => {
|
|
||||||
o.value.apply(o, res);
|
|
||||||
})
|
|
||||||
o.depends({type: 'trojan', trojan_ss_enabled: '1'});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', hm.GenValue, 'trojan_ss_password', _('Shadowsocks password'));
|
|
||||||
o.password = true;
|
|
||||||
o.validate = function(section_id, value) {
|
|
||||||
const encmode = this.section.getOption('trojan_ss_chipher').formvalue(section_id);
|
|
||||||
return hm.validateShadowsocksPassword.call(this, encmode, section_id, value);
|
|
||||||
}
|
|
||||||
o.depends({type: 'trojan', trojan_ss_enabled: '1'});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
/* AnyTLS fields */
|
|
||||||
o = s.taboption('field_general', form.TextValue, 'anytls_padding_scheme', _('Padding scheme'));
|
|
||||||
o.depends('type', 'anytls');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
/* VMess / VLESS fields */
|
|
||||||
o = s.taboption('field_general', hm.GenValue, 'vmess_uuid', _('UUID'));
|
|
||||||
o.rmempty = false;
|
|
||||||
o.validate = hm.validateUUID;
|
|
||||||
o.depends({type: /^(vmess|vless)$/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.ListValue, 'vless_flow', _('Flow'));
|
|
||||||
o.default = hm.vless_flow[0][0];
|
|
||||||
hm.vless_flow.forEach((res) => {
|
|
||||||
o.value.apply(o, res);
|
|
||||||
})
|
|
||||||
o.depends('type', 'vless');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'vmess_alterid', _('Alter ID'),
|
|
||||||
_('Legacy protocol support (VMess MD5 Authentication) is provided for compatibility purposes only, use of alterId > 1 is not recommended.'));
|
|
||||||
o.datatype = 'uinteger';
|
|
||||||
o.placeholder = '0';
|
|
||||||
o.depends('type', 'vmess');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
/* Plugin fields */
|
|
||||||
o = s.taboption('field_general', form.ListValue, 'plugin', _('Plugin'));
|
|
||||||
o.value('', _('none'));
|
|
||||||
o.value('shadow-tls', _('shadow-tls'));
|
|
||||||
//o.value('kcp-tun', _('kcp-tun'));
|
|
||||||
o.depends('type', 'shadowsocks');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.Value, 'plugin_opts_handshake_dest', _('Plugin: ') + _('Handshake target that supports TLS 1.3'));
|
|
||||||
o.datatype = 'hostport';
|
|
||||||
o.placeholder = 'cloud.tencent.com:443';
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends({plugin: 'shadow-tls'});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', hm.GenValue, 'plugin_opts_thetlspassword', _('Plugin: ') + _('Password'));
|
|
||||||
o.password = true;
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends({plugin: 'shadow-tls'});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_general', form.ListValue, 'plugin_opts_shadowtls_version', _('Plugin: ') + _('Version'));
|
|
||||||
o.value('1', _('v1'));
|
|
||||||
o.value('2', _('v2'));
|
|
||||||
o.value('3', _('v3'));
|
|
||||||
o.default = '3';
|
|
||||||
o.depends({plugin: 'shadow-tls'});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
/* Extra fields */
|
|
||||||
o = s.taboption('field_general', form.Flag, 'udp', _('UDP'));
|
|
||||||
o.default = o.disabled;
|
|
||||||
o.depends({type: /^(socks|mixed|shadowsocks)$/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
/* Vless Encryption fields */
|
|
||||||
o = s.taboption('field_general', form.Flag, 'vless_decryption', _('decryption'));
|
|
||||||
o.default = o.disabled;
|
|
||||||
o.depends('type', 'vless');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
const initVlessEncryptionOption = function(o, key) {
|
|
||||||
o.load = function(section_id) {
|
|
||||||
return new VlessEncryption(uci.get(data[0], section_id, 'vless_encryption_hmpayload'))[key];
|
|
||||||
}
|
|
||||||
o.onchange = function(ev, section_id, value) {
|
|
||||||
let UIEl = this.section.getUIElement(section_id, 'vless_encryption_hmpayload');
|
|
||||||
let newpayload = new VlessEncryption(UIEl.getValue()).setKey(key, value);
|
|
||||||
|
|
||||||
UIEl.setValue(newpayload.toString());
|
|
||||||
|
|
||||||
[
|
|
||||||
['server', '_vless_encryption_decryption'],
|
|
||||||
['client', '_vless_encryption_encryption']
|
|
||||||
].forEach(([side, option]) => {
|
|
||||||
UIEl = this.section.getUIElement(section_id, option);
|
|
||||||
UIEl.setValue(newpayload.toString('mihomo', side));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
o.write = function() {};
|
|
||||||
}
|
|
||||||
|
|
||||||
o = s.taboption('field_vless_encryption', form.Value, 'vless_encryption_hmpayload', _('Payload'));
|
|
||||||
o.readonly = true;
|
|
||||||
o.depends('vless_decryption', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_vless_encryption', CBIDummyCopyValue, '_vless_encryption_decryption', _('decryption'));
|
|
||||||
o.depends('vless_decryption', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_vless_encryption', CBIDummyCopyValue, '_vless_encryption_encryption', _('encryption'));
|
|
||||||
o.depends('vless_decryption', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_vless_encryption', form.ListValue, 'vless_encryption_method', _('Encryption method'));
|
|
||||||
o.default = hm.vless_encryption.methods[0][0];
|
|
||||||
hm.vless_encryption.methods.forEach((res) => {
|
|
||||||
o.value.apply(o, res);
|
|
||||||
})
|
|
||||||
initVlessEncryptionOption(o, 'method');
|
|
||||||
o.depends('vless_decryption', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_vless_encryption', form.RichListValue, 'vless_encryption_xormode', _('XOR mode'));
|
|
||||||
o.default = hm.vless_encryption.xormodes[0][0];
|
|
||||||
hm.vless_encryption.xormodes.forEach((res) => {
|
|
||||||
o.value.apply(o, res);
|
|
||||||
})
|
|
||||||
initVlessEncryptionOption(o, 'xormode');
|
|
||||||
o.depends('vless_decryption', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_vless_encryption', hm.RichValue, 'vless_encryption_ticket', _('Server') +' '+ _('RTT'));
|
|
||||||
o.default = hm.vless_encryption.tickets[0][0];
|
|
||||||
hm.vless_encryption.tickets.forEach((res) => {
|
|
||||||
o.value.apply(o, res);
|
|
||||||
})
|
|
||||||
initVlessEncryptionOption(o, 'ticket');
|
|
||||||
o.validate = function(section_id, value) {
|
|
||||||
if (!value)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (!value.match(/^(\d+-)?\d+s$/))
|
|
||||||
return _('Expecting: %s').format('^(\\d+-)?\\d+s$');
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends('vless_decryption', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_vless_encryption', form.ListValue, 'vless_encryption_rtt', _('Client') +' '+ _('RTT'));
|
|
||||||
o.default = hm.vless_encryption.rtts[0][0];
|
|
||||||
hm.vless_encryption.rtts.forEach((res) => {
|
|
||||||
o.value.apply(o, res);
|
|
||||||
})
|
|
||||||
initVlessEncryptionOption(o, 'rtt');
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends('vless_decryption', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_vless_encryption', hm.less_25_12 ? hm.DynamicList : form.DynamicList, 'vless_encryption_paddings', _('Paddings'), // @less_25_12
|
|
||||||
_('The server and client can set different padding parameters.') + '</br>' +
|
|
||||||
_('In the order of one <code>Padding-Length</code> and one <code>Padding-Interval</code>, infinite concatenation.') + '</br>' +
|
|
||||||
_('The first padding must have a probability of 100% and at least 35 bytes.'));
|
|
||||||
hm.vless_encryption.paddings.forEach((res) => {
|
|
||||||
o.value.apply(o, res);
|
|
||||||
})
|
|
||||||
initVlessEncryptionOption(o, 'paddings');
|
|
||||||
o.validate = function(section_id, value) {
|
|
||||||
if (!value)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (!value.match(/^\d+(-\d+){2}$/))
|
|
||||||
return _('Expecting: %s').format('^\\d+(-\\d+){2}$');
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
o.allowduplicates = true;
|
|
||||||
o.depends('vless_decryption', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_vless_encryption', hm.GenText, 'vless_encryption_keypairs', _('Keypairs'));
|
|
||||||
o.placeholder = '[\n {\n "type": "vless-x25519",\n "server": "cP5Oy9MOpTaBKKE17Pfd56mbb1CIfp5EMpyBYqr2EG8",\n "client": "khEcQMT8j41xWmGYKpZtQ4vd8_9VWyFVmmCDIhRJ-Uk"\n },\n {\n "type": "vless-mlkem768",\n "server": "UHPx3nf-FVxF95byAw0YG025aQNw9HxKej-MiG5AhTcdW_WFpHlTVYQU5NHmXP6tmljSnB2iPmSQ29fisGxEog",\n "client": "h4sdZgCc5-ZefvQ8mZmReOWQdxYb0mwngMdl7pKhYEZZpGWHUPKAmxug87Bgj3GqSHs195QeVpxfrMLNB5J..."\n },\n ...\n]';
|
|
||||||
o.rows = 10;
|
|
||||||
o.hm_options = {
|
|
||||||
type: hm.vless_encryption.keypairs.types[0][0],
|
|
||||||
params: '',
|
|
||||||
callback: function(result) {
|
|
||||||
const section_id = this.section.section;
|
|
||||||
const key_type = this.hm_options.type;
|
|
||||||
|
|
||||||
let keypair = {"type": key_type, "server": "", "client": ""};
|
|
||||||
switch (key_type) {
|
|
||||||
case 'vless-x25519':
|
|
||||||
keypair.server = result.private_key;
|
|
||||||
keypair.client = result.password;
|
|
||||||
break;
|
|
||||||
case 'vless-mlkem768':
|
|
||||||
keypair.server = result.seed;
|
|
||||||
keypair.client = result.client;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let keypairs = [];
|
|
||||||
try {
|
|
||||||
keypairs = JSON.parse(this.formvalue(section_id).trim());
|
|
||||||
} catch {}
|
|
||||||
if (!Array.isArray(keypairs))
|
|
||||||
keypairs = [];
|
|
||||||
|
|
||||||
keypairs.push(keypair);
|
|
||||||
|
|
||||||
return [
|
|
||||||
[this.option, JSON.stringify(keypairs, null, 2)]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.renderWidget = function(section_id, option_index, cfgvalue) {
|
|
||||||
let node = hm.TextValue.prototype.renderWidget.call(this, section_id, option_index, cfgvalue);
|
|
||||||
const cbid = this.cbid(section_id) + '._keytype_select';
|
|
||||||
const selected = this.hm_options.type;
|
|
||||||
|
|
||||||
let selectEl = E('select', {
|
|
||||||
id: cbid,
|
|
||||||
class: 'cbi-input-select',
|
|
||||||
style: 'width: 10em',
|
|
||||||
});
|
|
||||||
|
|
||||||
hm.vless_encryption.keypairs.types.forEach(([k, v]) => {
|
|
||||||
selectEl.appendChild(E('option', {
|
|
||||||
'value': k,
|
|
||||||
'selected': (k === selected) ? '' : null
|
|
||||||
}, [ v ]));
|
|
||||||
});
|
|
||||||
|
|
||||||
node.appendChild(E('div', { 'class': 'control-group' }, [
|
|
||||||
selectEl,
|
|
||||||
E('button', {
|
|
||||||
class: 'cbi-button cbi-button-add',
|
|
||||||
click: ui.createHandlerFn(this, () => {
|
|
||||||
this.hm_options.type = document.getElementById(cbid).value;
|
|
||||||
|
|
||||||
return hm.handleGenKey.call(this, this.hm_options);
|
|
||||||
})
|
|
||||||
}, [ _('Generate') ])
|
|
||||||
]));
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
o.load = function(section_id) {
|
|
||||||
return JSON.stringify(new VlessEncryption(uci.get(data[0], section_id, 'vless_encryption_hmpayload'))['keypairs'], null, 2);
|
|
||||||
}
|
|
||||||
o.validate = function(section_id, value) {
|
|
||||||
let result = hm.validateJson.call(this, section_id, value);
|
|
||||||
|
|
||||||
if (result === true) {
|
|
||||||
let keypairs = JSON.parse(value.trim());
|
|
||||||
|
|
||||||
if (Array.isArray(keypairs) && keypairs.length >= 1) {
|
|
||||||
let UIEl = this.section.getUIElement(section_id, 'vless_encryption_hmpayload');
|
|
||||||
let newpayload = new VlessEncryption(UIEl.getValue()).setKey('keypairs', keypairs);
|
|
||||||
|
|
||||||
UIEl.setValue(newpayload.toString());
|
|
||||||
|
|
||||||
[
|
|
||||||
['server', '_vless_encryption_decryption'],
|
|
||||||
['client', '_vless_encryption_encryption']
|
|
||||||
].forEach(([side, option]) => {
|
|
||||||
UIEl = this.section.getUIElement(section_id, option);
|
|
||||||
UIEl.setValue(newpayload.toString('mihomo', side));
|
|
||||||
});
|
|
||||||
} else
|
|
||||||
return _('Expecting: %s').format(_('least one keypair required'));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends('vless_decryption', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
/* TLS fields */
|
|
||||||
o = s.taboption('field_general', form.Flag, 'tls', _('TLS'));
|
|
||||||
o.default = o.disabled;
|
|
||||||
o.validate = function(section_id, value) {
|
|
||||||
const type = this.section.getOption('type').formvalue(section_id);
|
|
||||||
let tls = this.section.getUIElement(section_id, 'tls').node.querySelector('input');
|
|
||||||
let tls_alpn = this.section.getUIElement(section_id, 'tls_alpn');
|
|
||||||
let tls_reality = this.section.getUIElement(section_id, 'tls_reality').node.querySelector('input');
|
|
||||||
|
|
||||||
// Force enabled
|
|
||||||
if (['trojan', 'anytls', 'tuic', 'hysteria2'].includes(type)) {
|
|
||||||
tls.checked = true;
|
|
||||||
tls.disabled = true;
|
|
||||||
if (['tuic', 'hysteria2'].includes(type) && !`${tls_alpn.getValue()}`)
|
|
||||||
tls_alpn.setValue('h3');
|
|
||||||
} else {
|
|
||||||
tls.removeAttribute('disabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force disabled
|
|
||||||
if (!['vmess', 'vless', 'trojan'].includes(type)) {
|
|
||||||
tls_reality.checked = false;
|
|
||||||
tls_reality.disabled = true;
|
|
||||||
} else {
|
|
||||||
tls_reality.removeAttribute('disabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
o.depends({type: /^(http|socks|mixed|vmess|vless|trojan|anytls|tuic|hysteria2)$/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', form.DynamicList, 'tls_alpn', _('TLS ALPN'),
|
|
||||||
_('List of supported application level protocols, in order of preference.'));
|
|
||||||
o.depends('tls', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
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'});
|
|
||||||
o.rmempty = false;
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', form.Button, '_upload_cert', _('Upload certificate'),
|
|
||||||
_('<strong>Save your configuration before uploading files!</strong>'));
|
|
||||||
o.inputstyle = 'action';
|
|
||||||
o.inputtitle = _('Upload...');
|
|
||||||
o.depends({tls: '1', tls_cert_path: '/etc/fchomo/certs/server_publickey.pem'});
|
|
||||||
o.onclick = L.bind(hm.uploadCertificate, o, _('certificate'), 'server_publickey');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', form.Value, 'tls_key_path', _('Key path'),
|
|
||||||
_('The %s private key, in PEM format.').format(_('Server')));
|
|
||||||
o.value('/etc/fchomo/certs/server_privatekey.pem');
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends({tls: '1', tls_cert_path: /.+/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', form.Button, '_upload_key', _('Upload key'),
|
|
||||||
_('<strong>Save your configuration before uploading files!</strong>'));
|
|
||||||
o.inputstyle = 'action';
|
|
||||||
o.inputtitle = _('Upload...');
|
|
||||||
o.depends({tls: '1', tls_key_path: '/etc/fchomo/certs/server_privatekey.pem'});
|
|
||||||
o.onclick = L.bind(hm.uploadCertificate, o, _('private key'), 'server_privatekey');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', form.ListValue, 'tls_client_auth_type', _('Client Auth type') + _(' (mTLS)'));
|
|
||||||
o.default = hm.tls_client_auth_types[0][0];
|
|
||||||
hm.tls_client_auth_types.forEach((res) => {
|
|
||||||
o.value.apply(o, res);
|
|
||||||
})
|
|
||||||
o.depends({tls: '1', type: /^(http|socks|mixed|vmess|vless|trojan|anytls|hysteria2|tuic)$/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', form.Value, 'tls_client_auth_cert_path', _('Client Auth Certificate path') + _(' (mTLS)'),
|
|
||||||
_('The %s public key, in PEM format.').format(_('Client')));
|
|
||||||
o.value('/etc/fchomo/certs/client_publickey.pem');
|
|
||||||
o.validate = function(/* ... */) {
|
|
||||||
return hm.validateMTLSClientAuth.call(this, 'tls_client_auth_type', ...arguments);
|
|
||||||
}
|
|
||||||
o.depends({tls: '1', type: /^(http|socks|mixed|vmess|vless|trojan|anytls|hysteria2|tuic)$/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', form.Button, '_upload_client_auth_cert', _('Upload certificate') + _(' (mTLS)'),
|
|
||||||
_('<strong>Save your configuration before uploading files!</strong>'));
|
|
||||||
o.inputstyle = 'action';
|
|
||||||
o.inputtitle = _('Upload...');
|
|
||||||
o.depends({tls: '1', tls_client_auth_cert_path: '/etc/fchomo/certs/client_publickey.pem'});
|
|
||||||
o.onclick = L.bind(hm.uploadCertificate, o, _('certificate'), 'client_publickey');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', hm.GenText, 'tls_ech_key', _('ECH key'));
|
|
||||||
o.placeholder = '-----BEGIN ECH KEYS-----\nACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK\nmadSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz\ndC5jb20AAA==\n-----END ECH KEYS-----';
|
|
||||||
o.hm_placeholder = 'outer-sni.any.domain';
|
|
||||||
o.cols = 30;
|
|
||||||
o.rows = 2;
|
|
||||||
o.hm_options = {
|
|
||||||
type: 'ech-keypair',
|
|
||||||
params: '',
|
|
||||||
callback: function(result) {
|
|
||||||
return [
|
|
||||||
[this.option, result.ech_key],
|
|
||||||
['tls_ech_config', result.ech_cfg]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.renderWidget = function(section_id, option_index, cfgvalue) {
|
|
||||||
let node = hm.TextValue.prototype.renderWidget.call(this, section_id, option_index, cfgvalue);
|
|
||||||
const cbid = this.cbid(section_id) + '._outer_sni';
|
|
||||||
|
|
||||||
node.appendChild(E('div', { 'class': 'control-group' }, [
|
|
||||||
E('input', {
|
|
||||||
id: cbid,
|
|
||||||
class: 'cbi-input-text',
|
|
||||||
style: 'width: 10em',
|
|
||||||
placeholder: this.hm_placeholder
|
|
||||||
}),
|
|
||||||
E('button', {
|
|
||||||
class: 'cbi-button cbi-button-add',
|
|
||||||
click: ui.createHandlerFn(this, () => {
|
|
||||||
this.hm_options.params = document.getElementById(cbid).value;
|
|
||||||
|
|
||||||
return hm.handleGenKey.call(this, this.hm_options);
|
|
||||||
})
|
|
||||||
}, [ _('Generate') ])
|
|
||||||
]));
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
o.depends({tls: '1', type: /^(http|socks|mixed|vmess|vless|trojan|anytls|hysteria2|tuic)$/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', hm.CopyValue, 'tls_ech_config', _('ECH config'),
|
|
||||||
_('This ECH parameter needs to be added to the HTTPS record of the domain.'));
|
|
||||||
o.placeholder = 'AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA';
|
|
||||||
o.depends({tls: '1', type: /^(http|socks|mixed|vmess|vless|trojan|anytls|hysteria2|tuic)$/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
// uTLS fields
|
|
||||||
o = s.taboption('field_tls', form.Flag, 'tls_reality', _('REALITY'));
|
|
||||||
o.default = o.disabled;
|
|
||||||
o.depends('tls', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', form.Value, 'tls_reality_dest', _('REALITY handshake server'));
|
|
||||||
o.datatype = 'hostport';
|
|
||||||
o.placeholder = 'cloud.tencent.com:443';
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends('tls_reality', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', hm.GenValue, 'tls_reality_private_key', _('REALITY private key'));
|
|
||||||
o.hm_options = {
|
|
||||||
type: 'reality-keypair',
|
|
||||||
callback: function(result) {
|
|
||||||
return [
|
|
||||||
[this.option, result.private_key],
|
|
||||||
['tls_reality_public_key', result.public_key]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.password = true;
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends('tls_reality', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', hm.CopyValue, 'tls_reality_public_key', _('REALITY public key'));
|
|
||||||
o.depends('tls_reality', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', form.DynamicList, 'tls_reality_short_id', _('REALITY short ID'));
|
|
||||||
//o.value('', '""');
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends('tls_reality', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_tls', form.DynamicList, 'tls_reality_server_names', _('REALITY certificate issued to'));
|
|
||||||
o.datatype = 'list(hostname)';
|
|
||||||
o.placeholder = 'cloud.tencent.com';
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends('tls_reality', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
/* Transport fields */
|
|
||||||
o = s.taboption('field_general', form.Flag, 'transport_enabled', _('Transport'));
|
|
||||||
o.default = o.disabled;
|
|
||||||
o.depends({type: /^(vmess|vless|trojan)$/});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_transport', form.ListValue, 'transport_type', _('Transport type'));
|
|
||||||
o.value('grpc', _('gRPC'));
|
|
||||||
o.value('ws', _('WebSocket'));
|
|
||||||
o.validate = function(section_id, value) {
|
|
||||||
const type = this.section.getOption('type').formvalue(section_id);
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'vmess':
|
|
||||||
case 'vless':
|
|
||||||
if (!['http', 'h2', 'grpc', 'ws'].includes(value))
|
|
||||||
return _('Expecting: only support %s.').format(_('HTTP') +
|
|
||||||
' / ' + _('HTTPUpgrade') +
|
|
||||||
' / ' + _('gRPC') +
|
|
||||||
' / ' + _('WebSocket'));
|
|
||||||
break;
|
|
||||||
case 'trojan':
|
|
||||||
if (!['grpc', 'ws'].includes(value))
|
|
||||||
return _('Expecting: only support %s.').format(_('gRPC') +
|
|
||||||
' / ' + _('WebSocket'));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
o.depends('transport_enabled', '1');
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_transport', form.Value, 'transport_path', _('Request path'));
|
|
||||||
o.placeholder = '/';
|
|
||||||
o.default = '/';
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends({transport_enabled: '1', transport_type: 'ws'});
|
|
||||||
o.modalonly = true;
|
|
||||||
|
|
||||||
o = s.taboption('field_transport', form.Value, 'transport_grpc_servicename', _('gRPC service name'));
|
|
||||||
o.placeholder = 'GunService';
|
|
||||||
o.rmempty = false;
|
|
||||||
o.depends({transport_enabled: '1', transport_type: 'grpc'});
|
|
||||||
o.modalonly = true;
|
|
||||||
/* Server settings END */
|
/* Server settings END */
|
||||||
|
|
||||||
return m.render();
|
return m.render();
|
||||||
|
|||||||
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
@@ -45,6 +45,31 @@ opmc() { # @less_25_12
|
|||||||
$OPM $action "$@"
|
$OPM $action "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# add_firewall
|
||||||
|
add_firewall() {
|
||||||
|
local enabled auto_firewall listen port
|
||||||
|
config_get_bool enabled "$1" "enabled" "1"
|
||||||
|
config_get_bool auto_firewall "$1" "auto_firewall" "1"
|
||||||
|
config_get listen "$1" "listen" "::"
|
||||||
|
config_get port "$1" "port"
|
||||||
|
|
||||||
|
[ "$enabled" = "0" ] && return 0
|
||||||
|
[ "$auto_firewall" = "0" ] && return 0
|
||||||
|
|
||||||
|
json_add_object ''
|
||||||
|
json_add_string type rule
|
||||||
|
json_add_string target ACCEPT
|
||||||
|
json_add_string name "$1"
|
||||||
|
#json_add_string family '' # '' = IPv4 and IPv6
|
||||||
|
json_add_string proto 'tcp udp'
|
||||||
|
json_add_string direction in
|
||||||
|
json_add_string src "*"
|
||||||
|
#json_add_string dest '' # '' = input
|
||||||
|
json_add_string dest_ip "$(echo "$listen" | grep -vE '^(0\.\d+\.\d+\.\d+|::)$')"
|
||||||
|
json_add_string dest_port "$port"
|
||||||
|
json_close_object
|
||||||
|
}
|
||||||
|
|
||||||
config_load "$CONF"
|
config_load "$CONF"
|
||||||
|
|
||||||
# define global var: DEF_WAN DEF_WAN6 NIC_* NIC6_*
|
# define global var: DEF_WAN DEF_WAN6 NIC_* NIC6_*
|
||||||
@@ -333,6 +358,15 @@ start_service() {
|
|||||||
procd_set_param stderr 1
|
procd_set_param stderr 1
|
||||||
procd_set_param respawn
|
procd_set_param respawn
|
||||||
|
|
||||||
|
# add_firewall
|
||||||
|
procd_open_data
|
||||||
|
# configure firewall
|
||||||
|
json_add_array firewall
|
||||||
|
# meta l4proto %s th dport %s counter accept comment "!%s: accept server instance [%s]"
|
||||||
|
config_foreach add_firewall "inbound"
|
||||||
|
json_close_array
|
||||||
|
procd_close_data
|
||||||
|
|
||||||
procd_close_instance
|
procd_close_instance
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -380,30 +414,6 @@ start_service() {
|
|||||||
procd_set_param respawn
|
procd_set_param respawn
|
||||||
|
|
||||||
# add_firewall
|
# add_firewall
|
||||||
add_firewall() {
|
|
||||||
local enabled auto_firewall listen port
|
|
||||||
config_get_bool enabled "$1" "enabled" "1"
|
|
||||||
config_get_bool auto_firewall "$1" "auto_firewall" "1"
|
|
||||||
config_get listen "$1" "listen" "::"
|
|
||||||
config_get port "$1" "port"
|
|
||||||
|
|
||||||
[ "$enabled" = "0" ] && return 0
|
|
||||||
[ "$auto_firewall" = "0" ] && return 0
|
|
||||||
|
|
||||||
json_add_object ''
|
|
||||||
json_add_string type rule
|
|
||||||
json_add_string target ACCEPT
|
|
||||||
json_add_string name "$1"
|
|
||||||
#json_add_string family '' # '' = IPv4 and IPv6
|
|
||||||
json_add_string proto 'tcp udp'
|
|
||||||
json_add_string direction in
|
|
||||||
json_add_string src "*"
|
|
||||||
#json_add_string dest '' # '' = input
|
|
||||||
json_add_string dest_ip "$(echo "$listen" | grep -vE '^(0\.\d+\.\d+\.\d+|::)$')"
|
|
||||||
json_add_string dest_port "$port"
|
|
||||||
json_close_object
|
|
||||||
}
|
|
||||||
#
|
|
||||||
procd_open_data
|
procd_open_data
|
||||||
# configure firewall
|
# configure firewall
|
||||||
json_add_array firewall
|
json_add_array firewall
|
||||||
|
|||||||
@@ -34,6 +34,22 @@ migrate() {
|
|||||||
uci_set "$CONF" "$1" vless_encryption_encryption "$vless_encryption"
|
uci_set "$CONF" "$1" vless_encryption_encryption "$vless_encryption"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# tuic_congestion_controller -> congestion_controller
|
||||||
|
if isDefined "$1" tuic_congestion_controller; then
|
||||||
|
local tuic_congestion_controller
|
||||||
|
config_get tuic_congestion_controller "$1" tuic_congestion_controller ""
|
||||||
|
uci_remove "$CONF" "$1" tuic_congestion_controller
|
||||||
|
uci_set "$CONF" "$1" congestion_controller "$tuic_congestion_controller"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# masque_congestion_controller -> congestion_controller
|
||||||
|
if isDefined "$1" masque_congestion_controller; then
|
||||||
|
local masque_congestion_controller
|
||||||
|
config_get masque_congestion_controller "$1" masque_congestion_controller ""
|
||||||
|
uci_remove "$CONF" "$1" masque_congestion_controller
|
||||||
|
uci_set "$CONF" "$1" congestion_controller "$masque_congestion_controller"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
config_foreach migrate node
|
config_foreach migrate node
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Migration script for fchomo server
|
||||||
|
# Used to migrate LuCI application server option.
|
||||||
|
|
||||||
|
. /lib/functions.sh
|
||||||
|
. /usr/share/libubox/jshn.sh
|
||||||
|
|
||||||
|
CONF=fchomo
|
||||||
|
|
||||||
|
config_load "$CONF"
|
||||||
|
|
||||||
|
# isDefined <section> <option>
|
||||||
|
isDefined() {
|
||||||
|
local opt=CONFIG_${1}_${2};
|
||||||
|
|
||||||
|
eval "[ -n \"\${$opt+x}\" ] && return 0 || return 1"
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate() {
|
||||||
|
# tuic_congestion_controller -> congestion_controller
|
||||||
|
if isDefined "$1" tuic_congestion_controller; then
|
||||||
|
local tuic_congestion_controller
|
||||||
|
config_get tuic_congestion_controller "$1" tuic_congestion_controller ""
|
||||||
|
uci_remove "$CONF" "$1" tuic_congestion_controller
|
||||||
|
uci_set "$CONF" "$1" congestion_controller "$tuic_congestion_controller"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
config_foreach migrate server
|
||||||
|
|
||||||
|
uci_commit "$CONF"
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -11,6 +11,7 @@ import { urldecode, urlencode } from 'luci.http';
|
|||||||
import {
|
import {
|
||||||
isEmpty, strToBool, strToInt, bytesizeToByte, durationToSecond,
|
isEmpty, strToBool, strToInt, bytesizeToByte, durationToSecond,
|
||||||
arrToObj, removeBlankAttrs,
|
arrToObj, removeBlankAttrs,
|
||||||
|
parseListener,
|
||||||
HM_DIR, RUN_DIR, PRESET_OUTBOUND, RULES_LOGICAL_TYPE
|
HM_DIR, RUN_DIR, PRESET_OUTBOUND, RULES_LOGICAL_TYPE
|
||||||
} from 'fchomo';
|
} from 'fchomo';
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ const ucisniff = 'sniff',
|
|||||||
ucidnspoli = 'dns_policy',
|
ucidnspoli = 'dns_policy',
|
||||||
ucidnsnpoli = 'dns_node_policy',
|
ucidnsnpoli = 'dns_node_policy',
|
||||||
ucipgrp = 'proxy_group',
|
ucipgrp = 'proxy_group',
|
||||||
|
uciinbd = 'inbound',
|
||||||
ucinode = 'node',
|
ucinode = 'node',
|
||||||
uciprov = 'provider',
|
uciprov = 'provider',
|
||||||
ucichain = 'dialer_proxy',
|
ucichain = 'dialer_proxy',
|
||||||
@@ -326,29 +328,14 @@ uci.foreach(uciconf, ucisniff, (cfg) => {
|
|||||||
/* Inbound START */
|
/* Inbound START */
|
||||||
const proxy_mode = uci.get(uciconf, uciinbound, 'proxy_mode') || 'redir_tproxy';
|
const proxy_mode = uci.get(uciconf, uciinbound, 'proxy_mode') || 'redir_tproxy';
|
||||||
/* Listen ports */
|
/* Listen ports */
|
||||||
config.listeners = [];
|
config["allow-lan"] = true;
|
||||||
push(config.listeners, {
|
config["bind-address"] = "*";
|
||||||
name: 'mixed-in',
|
config["mixed-port"] = strToInt(uci.get(uciconf, uciinbound, 'mixed_port')) || 7890;
|
||||||
type: 'mixed',
|
|
||||||
port: strToInt(uci.get(uciconf, uciinbound, 'mixed_port')) || 7890,
|
|
||||||
listen: '::',
|
|
||||||
udp: true
|
|
||||||
});
|
|
||||||
if (match(proxy_mode, /redir/))
|
if (match(proxy_mode, /redir/))
|
||||||
push(config.listeners, {
|
config["redir-port"] = strToInt(uci.get(uciconf, uciinbound, 'redir_port')) || 7891;
|
||||||
name: 'redir-in',
|
|
||||||
type: 'redir',
|
|
||||||
port: strToInt(uci.get(uciconf, uciinbound, 'redir_port')) || 7891,
|
|
||||||
listen: '::'
|
|
||||||
});
|
|
||||||
if (match(proxy_mode, /tproxy/))
|
if (match(proxy_mode, /tproxy/))
|
||||||
push(config.listeners, {
|
config["tproxy-port"] = strToInt(uci.get(uciconf, uciinbound, 'tproxy_port')) || 7892;
|
||||||
name: 'tproxy-in',
|
config.listeners = [];
|
||||||
type: 'tproxy',
|
|
||||||
port: strToInt(uci.get(uciconf, uciinbound, 'tproxy_port')) || 7892,
|
|
||||||
listen: '::',
|
|
||||||
udp: true
|
|
||||||
});
|
|
||||||
push(config.listeners, {
|
push(config.listeners, {
|
||||||
name: 'dns-in',
|
name: 'dns-in',
|
||||||
type: 'tunnel',
|
type: 'tunnel',
|
||||||
@@ -357,6 +344,13 @@ push(config.listeners, {
|
|||||||
network: ['tcp', 'udp'],
|
network: ['tcp', 'udp'],
|
||||||
target: '1.1.1.1:53'
|
target: '1.1.1.1:53'
|
||||||
}); // @Not required for v1.19.2+
|
}); // @Not required for v1.19.2+
|
||||||
|
/* Custom Inbound settings */
|
||||||
|
uci.foreach(uciconf, uciinbd, (cfg) => {
|
||||||
|
if (cfg.enabled === '0')
|
||||||
|
return;
|
||||||
|
|
||||||
|
push(config.listeners, parseListener(cfg, true, get_proxygroup(cfg.proxy)));
|
||||||
|
});
|
||||||
/* Tun settings */
|
/* Tun settings */
|
||||||
if (match(proxy_mode, /tun/))
|
if (match(proxy_mode, /tun/))
|
||||||
config.tun = {
|
config.tun = {
|
||||||
@@ -502,7 +496,6 @@ uci.foreach(uciconf, ucinode, (cfg) => {
|
|||||||
cipher: cfg.vmess_chipher || cfg.shadowsocks_chipher,
|
cipher: cfg.vmess_chipher || cfg.shadowsocks_chipher,
|
||||||
password: cfg.shadowsocks_password || cfg.password,
|
password: cfg.shadowsocks_password || cfg.password,
|
||||||
headers: cfg.headers ? json(cfg.headers) : null,
|
headers: cfg.headers ? json(cfg.headers) : null,
|
||||||
"congestion-controller": cfg.tuic_congestion_controller || cfg.masque_congestion_controller,
|
|
||||||
"private-key": cfg.masque_private_key || cfg.wireguard_private_key || cfg.ssh_priv_key,
|
"private-key": cfg.masque_private_key || cfg.wireguard_private_key || cfg.ssh_priv_key,
|
||||||
"public-key": cfg.masque_endpoint_public_key || cfg.wireguard_peer_public_key,
|
"public-key": cfg.masque_endpoint_public_key || cfg.wireguard_peer_public_key,
|
||||||
ip: cfg.masque_ip || cfg.wireguard_ip,
|
ip: cfg.masque_ip || cfg.wireguard_ip,
|
||||||
@@ -531,6 +524,7 @@ uci.foreach(uciconf, ucinode, (cfg) => {
|
|||||||
transport: cfg.mieru_transport,
|
transport: cfg.mieru_transport,
|
||||||
multiplexing: cfg.mieru_multiplexing,
|
multiplexing: cfg.mieru_multiplexing,
|
||||||
"handshake-mode": cfg.mieru_handshake_mode,
|
"handshake-mode": cfg.mieru_handshake_mode,
|
||||||
|
"traffic-pattern": cfg.mieru_traffic_pattern,
|
||||||
|
|
||||||
/* Sudoku */
|
/* Sudoku */
|
||||||
key: cfg.sudoku_key,
|
key: cfg.sudoku_key,
|
||||||
@@ -540,12 +534,16 @@ uci.foreach(uciconf, ucinode, (cfg) => {
|
|||||||
"table-type": cfg.sudoku_table_type,
|
"table-type": cfg.sudoku_table_type,
|
||||||
"custom-tables": cfg.sudoku_custom_tables,
|
"custom-tables": cfg.sudoku_custom_tables,
|
||||||
"enable-pure-downlink": (cfg.sudoku_enable_pure_downlink === '0') ? false : null,
|
"enable-pure-downlink": (cfg.sudoku_enable_pure_downlink === '0') ? false : null,
|
||||||
"http-mask": (cfg.sudoku_http_mask === '0') ? false : true,
|
...(cfg.type === 'sudoku' ? {
|
||||||
"http-mask-mode": cfg.sudoku_http_mask_mode,
|
httpmask: (cfg.sudoku_http_mask === '0') ? { disable: true } : {
|
||||||
"http-mask-tls": strToBool(cfg.sudoku_http_mask_tls),
|
disable: false,
|
||||||
"http-mask-host": cfg.sudoku_http_mask_host,
|
mode: cfg.sudoku_http_mask_mode,
|
||||||
"path-root": cfg.sudoku_path_root,
|
tls: strToBool(cfg.sudoku_http_mask_tls) || false,
|
||||||
"http-mask-multiplex": cfg.sudoku_http_mask_multiplex,
|
host: cfg.sudoku_http_mask_host,
|
||||||
|
path_root: cfg.sudoku_path_root,
|
||||||
|
multiplex: cfg.sudoku_http_mask_multiplex,
|
||||||
|
}
|
||||||
|
} : {}),
|
||||||
|
|
||||||
/* Snell */
|
/* Snell */
|
||||||
psk: cfg.snell_psk,
|
psk: cfg.snell_psk,
|
||||||
@@ -587,6 +585,10 @@ uci.foreach(uciconf, ucinode, (cfg) => {
|
|||||||
"packet-encoding": cfg.vmess_packet_encoding,
|
"packet-encoding": cfg.vmess_packet_encoding,
|
||||||
encryption: cfg.vless_encryption === '1' ? cfg.vless_encryption_encryption : null,
|
encryption: cfg.vless_encryption === '1' ? cfg.vless_encryption_encryption : null,
|
||||||
|
|
||||||
|
/* TrustTunnel */
|
||||||
|
"health-check": strToBool(cfg.trusttunnel_health_check === '0' ? false : true),
|
||||||
|
quic: strToBool(cfg.trusttunnel_quic),
|
||||||
|
|
||||||
/* WireGuard */
|
/* WireGuard */
|
||||||
"pre-shared-key": cfg.wireguard_pre_shared_key,
|
"pre-shared-key": cfg.wireguard_pre_shared_key,
|
||||||
"allowed-ips": cfg.wireguard_allowed_ips,
|
"allowed-ips": cfg.wireguard_allowed_ips,
|
||||||
@@ -605,12 +607,13 @@ uci.foreach(uciconf, ucinode, (cfg) => {
|
|||||||
} : null,
|
} : null,
|
||||||
|
|
||||||
/* Extra fields */
|
/* Extra fields */
|
||||||
|
"congestion-controller": cfg.congestion_controller,
|
||||||
udp: strToBool(cfg.udp),
|
udp: strToBool(cfg.udp),
|
||||||
"udp-over-tcp": strToBool(cfg.uot),
|
"udp-over-tcp": strToBool(cfg.uot),
|
||||||
"udp-over-tcp-version": cfg.uot_version,
|
"udp-over-tcp-version": cfg.uot_version,
|
||||||
|
|
||||||
/* TLS fields */
|
/* TLS fields */
|
||||||
tls: (cfg.type in ['trojan', 'anytls', 'hysteria', 'hysteria2', 'tuic']) ? null : strToBool(cfg.tls),
|
tls: (cfg.type in ['trojan', 'anytls', 'hysteria', 'hysteria2', 'tuic', 'trusttunnel']) ? null : strToBool(cfg.tls),
|
||||||
"disable-sni": strToBool(cfg.tls_disable_sni),
|
"disable-sni": strToBool(cfg.tls_disable_sni),
|
||||||
...arrToObj([[(cfg.type in ['vmess', 'vless']) ? 'servername' : 'sni', cfg.tls_sni]]),
|
...arrToObj([[(cfg.type in ['vmess', 'vless']) ? 'servername' : 'sni', cfg.tls_sni]]),
|
||||||
fingerprint: cfg.tls_fingerprint,
|
fingerprint: cfg.tls_fingerprint,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { cursor } from 'uci';
|
|||||||
import {
|
import {
|
||||||
isEmpty, strToBool, strToInt, durationToSecond,
|
isEmpty, strToBool, strToInt, durationToSecond,
|
||||||
arrToObj, removeBlankAttrs,
|
arrToObj, removeBlankAttrs,
|
||||||
|
parseListener,
|
||||||
HM_DIR, RUN_DIR, PRESET_OUTBOUND
|
HM_DIR, RUN_DIR, PRESET_OUTBOUND
|
||||||
} from 'fchomo';
|
} from 'fchomo';
|
||||||
|
|
||||||
@@ -33,23 +34,6 @@ function parse_users(cfg) {
|
|||||||
|
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse_vless_encryption(payload, side) {
|
|
||||||
if (isEmpty(payload))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
let content = json(trim(payload));
|
|
||||||
|
|
||||||
let required = join('.', [
|
|
||||||
content.method,
|
|
||||||
content.xormode,
|
|
||||||
side === 'server' ? content.ticket : side === 'client' ? content.rtt : null
|
|
||||||
]);
|
|
||||||
|
|
||||||
return required +
|
|
||||||
(isEmpty(content.paddings) ? '' : '.' + join('.', content.paddings)) + // Optional
|
|
||||||
(isEmpty(content.keypairs) ? '' : '.' + join('.', map(content.keypairs, e => e[side]))); // Required
|
|
||||||
}
|
|
||||||
/* Config helper END */
|
/* Config helper END */
|
||||||
|
|
||||||
/* Main */
|
/* Main */
|
||||||
@@ -61,131 +45,7 @@ uci.foreach(uciconf, uciserver, (cfg) => {
|
|||||||
if (cfg.enabled === '0')
|
if (cfg.enabled === '0')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
push(config.listeners, {
|
push(config.listeners, parseListener(cfg, false));
|
||||||
name: cfg['.name'],
|
|
||||||
type: cfg.type,
|
|
||||||
|
|
||||||
listen: cfg.listen || '::',
|
|
||||||
port: cfg.port,
|
|
||||||
proxy: 'DIRECT',
|
|
||||||
|
|
||||||
/* HTTP / SOCKS / VMess / VLESS / Trojan / AnyTLS / Tuic / Hysteria2 */
|
|
||||||
users: (cfg.type in ['http', 'socks', 'mixed', 'vmess', 'vless', 'trojan']) ? [
|
|
||||||
{
|
|
||||||
/* HTTP / SOCKS */
|
|
||||||
username: cfg.username,
|
|
||||||
password: cfg.password,
|
|
||||||
|
|
||||||
/* VMess / VLESS */
|
|
||||||
uuid: cfg.vmess_uuid,
|
|
||||||
flow: cfg.vless_flow,
|
|
||||||
alterId: strToInt(cfg.vmess_alterid)
|
|
||||||
}
|
|
||||||
/*{
|
|
||||||
}*/
|
|
||||||
] : ((cfg.type in ['mieru', 'anytls', 'tuic', 'hysteria2']) ? {
|
|
||||||
/* Mieru / AnyTLS / Hysteria2 */
|
|
||||||
...arrToObj([[cfg.username, cfg.password]]),
|
|
||||||
|
|
||||||
/* Tuic */
|
|
||||||
...arrToObj([[cfg.uuid, cfg.password]])
|
|
||||||
} : null),
|
|
||||||
|
|
||||||
/* 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,
|
|
||||||
masquerade: cfg.hysteria_masquerade,
|
|
||||||
|
|
||||||
/* Shadowsocks */
|
|
||||||
cipher: cfg.shadowsocks_chipher,
|
|
||||||
password: cfg.shadowsocks_password,
|
|
||||||
|
|
||||||
/* Mieru */
|
|
||||||
transport: cfg.mieru_transport,
|
|
||||||
|
|
||||||
/* Sudoku */
|
|
||||||
key: cfg.sudoku_key,
|
|
||||||
"aead-method": replace(cfg.sudoku_aead_method || '', 'chacha20-ietf-poly1305', 'chacha20-poly1305') || null,
|
|
||||||
"padding-min": strToInt(cfg.sudoku_padding_min),
|
|
||||||
"padding-max": strToInt(cfg.sudoku_padding_max),
|
|
||||||
"table-type": cfg.sudoku_table_type,
|
|
||||||
"custom-tables": cfg.sudoku_custom_tables,
|
|
||||||
"handshake-timeout": strToInt(cfg.sudoku_handshake_timeout) ?? null,
|
|
||||||
"enable-pure-downlink": (cfg.sudoku_enable_pure_downlink === '0') ? false : null,
|
|
||||||
"disable-http-mask": (cfg.sudoku_http_mask === '0') ? true : null,
|
|
||||||
"http-mask-mode": cfg.sudoku_http_mask_mode,
|
|
||||||
"path-root": cfg.sudoku_path_root,
|
|
||||||
|
|
||||||
/* Tuic */
|
|
||||||
"congestion-controller": cfg.tuic_congestion_controller,
|
|
||||||
"max-idle-time": durationToSecond(cfg.tuic_max_idle_time),
|
|
||||||
"authentication-timeout": durationToSecond(cfg.tuic_authentication_timeout),
|
|
||||||
"max-udp-relay-packet-size": strToInt(cfg.tuic_max_udp_relay_packet_size),
|
|
||||||
|
|
||||||
/* Trojan */
|
|
||||||
"ss-option": cfg.trojan_ss_enabled === '1' ? {
|
|
||||||
enabled: true,
|
|
||||||
method: cfg.trojan_ss_chipher,
|
|
||||||
password: cfg.trojan_ss_password
|
|
||||||
} : null,
|
|
||||||
|
|
||||||
/* AnyTLS */
|
|
||||||
"padding-scheme": cfg.anytls_padding_scheme,
|
|
||||||
|
|
||||||
/* VMess / VLESS */
|
|
||||||
decryption: cfg.vless_decryption === '1' ? parse_vless_encryption(cfg.vless_encryption_hmpayload, 'server') : null,
|
|
||||||
|
|
||||||
/* Plugin fields */
|
|
||||||
...(cfg.plugin ? {
|
|
||||||
// shadow-tls
|
|
||||||
"shadow-tls": cfg.plugin === 'shadow-tls' ? {
|
|
||||||
enable: true,
|
|
||||||
version: strToInt(cfg.plugin_opts_shadowtls_version),
|
|
||||||
...(strToInt(cfg.plugin_opts_shadowtls_version) >= 3 ? {
|
|
||||||
users: [
|
|
||||||
{
|
|
||||||
name: 1,
|
|
||||||
password: cfg.plugin_opts_thetlspassword
|
|
||||||
}
|
|
||||||
],
|
|
||||||
} : { password: cfg.plugin_opts_thetlspassword }),
|
|
||||||
handshake: {
|
|
||||||
dest: cfg.plugin_opts_handshake_dest
|
|
||||||
},
|
|
||||||
} : null
|
|
||||||
} : {}),
|
|
||||||
|
|
||||||
/* Extra fields */
|
|
||||||
udp: strToBool(cfg.udp),
|
|
||||||
|
|
||||||
/* TLS fields */
|
|
||||||
...(cfg.tls === '1' ? {
|
|
||||||
alpn: cfg.tls_alpn,
|
|
||||||
...(cfg.tls_reality === '1' ? {
|
|
||||||
"reality-config": {
|
|
||||||
dest: cfg.tls_reality_dest,
|
|
||||||
"private-key": cfg.tls_reality_private_key,
|
|
||||||
"short-id": cfg.tls_reality_short_id,
|
|
||||||
"server-names": cfg.tls_reality_server_names
|
|
||||||
}
|
|
||||||
} : {
|
|
||||||
certificate: cfg.tls_cert_path,
|
|
||||||
"private-key": cfg.tls_key_path
|
|
||||||
}),
|
|
||||||
"client-auth-type": cfg.tls_client_auth_type,
|
|
||||||
"client-auth-cert": cfg.tls_client_auth_cert_path,
|
|
||||||
"ech-key": cfg.tls_ech_key,
|
|
||||||
} : {}),
|
|
||||||
|
|
||||||
/* Transport fields */
|
|
||||||
...(cfg.transport_enabled === '1' ? {
|
|
||||||
"grpc-service-name": cfg.transport_grpc_servicename,
|
|
||||||
"ws-path": cfg.transport_path
|
|
||||||
} : {})
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
/* Inbound END */
|
/* Inbound END */
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,14 @@
|
|||||||
"path": "fchomo/hosts"
|
"path": "fchomo/hosts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"admin/services/fchomo/inbound": {
|
||||||
|
"title": "Inbound",
|
||||||
|
"order": 27,
|
||||||
|
"action": {
|
||||||
|
"type": "view",
|
||||||
|
"path": "fchomo/inbound"
|
||||||
|
}
|
||||||
|
},
|
||||||
"admin/services/fchomo/node": {
|
"admin/services/fchomo/node": {
|
||||||
"title": "Node",
|
"title": "Node",
|
||||||
"order": 30,
|
"order": 30,
|
||||||
|
|||||||
@@ -191,3 +191,163 @@ export function removeBlankAttrs(res) {
|
|||||||
return content;
|
return content;
|
||||||
};
|
};
|
||||||
/* String helper end */
|
/* String helper end */
|
||||||
|
|
||||||
|
/* String universal start */
|
||||||
|
export function parseVlessEncryption(payload, side) {
|
||||||
|
if (isEmpty(payload))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
let content = json(trim(payload));
|
||||||
|
|
||||||
|
let required = join('.', [
|
||||||
|
content.method,
|
||||||
|
content.xormode,
|
||||||
|
side === 'server' ? content.ticket : side === 'client' ? content.rtt : null
|
||||||
|
]);
|
||||||
|
|
||||||
|
return required +
|
||||||
|
(isEmpty(content.paddings) ? '' : '.' + join('.', content.paddings)) + // Optional
|
||||||
|
(isEmpty(content.keypairs) ? '' : '.' + join('.', map(content.keypairs, e => e[side]))); // Required
|
||||||
|
};
|
||||||
|
|
||||||
|
export function parseListener(cfg, isClient, label) {
|
||||||
|
return {
|
||||||
|
name: cfg['.name'],
|
||||||
|
type: cfg.type,
|
||||||
|
|
||||||
|
listen: cfg.listen || '::',
|
||||||
|
port: cfg.port,
|
||||||
|
...(isClient ? {
|
||||||
|
rule: cfg.rule,
|
||||||
|
proxy: label,
|
||||||
|
} : {}),
|
||||||
|
|
||||||
|
/* HTTP / SOCKS / VMess / VLESS / Trojan / AnyTLS / Tuic / Hysteria2 */
|
||||||
|
users: (cfg.type in ['http', 'socks', 'mixed', 'vmess', 'vless', 'trojan', 'trusttunnel']) ? [
|
||||||
|
{
|
||||||
|
/* HTTP / SOCKS */
|
||||||
|
username: cfg.username,
|
||||||
|
password: cfg.password,
|
||||||
|
|
||||||
|
/* VMess / VLESS */
|
||||||
|
uuid: cfg.vmess_uuid,
|
||||||
|
flow: cfg.vless_flow,
|
||||||
|
alterId: strToInt(cfg.vmess_alterid)
|
||||||
|
}
|
||||||
|
/*{
|
||||||
|
}*/
|
||||||
|
] : ((cfg.type in ['mieru', 'anytls', 'tuic', 'hysteria2']) ? {
|
||||||
|
/* Mieru / AnyTLS / Hysteria2 */
|
||||||
|
...arrToObj([[cfg.username, cfg.password]]),
|
||||||
|
|
||||||
|
/* Tuic */
|
||||||
|
...arrToObj([[cfg.uuid, cfg.password]])
|
||||||
|
} : null),
|
||||||
|
|
||||||
|
/* 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,
|
||||||
|
masquerade: cfg.hysteria_masquerade,
|
||||||
|
|
||||||
|
/* Shadowsocks */
|
||||||
|
cipher: cfg.shadowsocks_chipher,
|
||||||
|
password: cfg.shadowsocks_password,
|
||||||
|
|
||||||
|
/* Mieru */
|
||||||
|
transport: cfg.mieru_transport,
|
||||||
|
"traffic-pattern": cfg.mieru_traffic_pattern,
|
||||||
|
|
||||||
|
/* Sudoku */
|
||||||
|
key: cfg.sudoku_key,
|
||||||
|
"aead-method": replace(cfg.sudoku_aead_method || '', 'chacha20-ietf-poly1305', 'chacha20-poly1305') || null,
|
||||||
|
"padding-min": strToInt(cfg.sudoku_padding_min),
|
||||||
|
"padding-max": strToInt(cfg.sudoku_padding_max),
|
||||||
|
"table-type": cfg.sudoku_table_type,
|
||||||
|
"custom-tables": cfg.sudoku_custom_tables,
|
||||||
|
"handshake-timeout": strToInt(cfg.sudoku_handshake_timeout) ?? null,
|
||||||
|
"enable-pure-downlink": (cfg.sudoku_enable_pure_downlink === '0') ? false : null,
|
||||||
|
...(cfg.type === 'sudoku' ? {
|
||||||
|
httpmask: (cfg.sudoku_http_mask === '0') ? { disable: true } : {
|
||||||
|
disable: false,
|
||||||
|
mode: cfg.sudoku_http_mask_mode,
|
||||||
|
path_root: cfg.sudoku_path_root,
|
||||||
|
}
|
||||||
|
} : {}),
|
||||||
|
fallback: (cfg.sudoku_http_mask === '0') ? null : cfg.sudoku_fallback,
|
||||||
|
|
||||||
|
/* Tuic */
|
||||||
|
"max-idle-time": durationToSecond(cfg.tuic_max_idle_time),
|
||||||
|
"authentication-timeout": durationToSecond(cfg.tuic_authentication_timeout),
|
||||||
|
"max-udp-relay-packet-size": strToInt(cfg.tuic_max_udp_relay_packet_size),
|
||||||
|
|
||||||
|
/* Trojan */
|
||||||
|
"ss-option": cfg.trojan_ss_enabled === '1' ? {
|
||||||
|
enabled: true,
|
||||||
|
method: cfg.trojan_ss_chipher,
|
||||||
|
password: cfg.trojan_ss_password
|
||||||
|
} : null,
|
||||||
|
|
||||||
|
/* AnyTLS */
|
||||||
|
"padding-scheme": cfg.anytls_padding_scheme,
|
||||||
|
|
||||||
|
/* VMess / VLESS */
|
||||||
|
decryption: cfg.vless_decryption === '1' ? parseVlessEncryption(cfg.vless_encryption_hmpayload, 'server') : null,
|
||||||
|
|
||||||
|
/* Tunnel */
|
||||||
|
target: cfg.tunnel_target,
|
||||||
|
|
||||||
|
/* Plugin fields */
|
||||||
|
...(cfg.plugin ? {
|
||||||
|
// shadow-tls
|
||||||
|
"shadow-tls": cfg.plugin === 'shadow-tls' ? {
|
||||||
|
enable: true,
|
||||||
|
version: strToInt(cfg.plugin_opts_shadowtls_version),
|
||||||
|
...(strToInt(cfg.plugin_opts_shadowtls_version) >= 3 ? {
|
||||||
|
users: [
|
||||||
|
{
|
||||||
|
name: 1,
|
||||||
|
password: cfg.plugin_opts_thetlspassword
|
||||||
|
}
|
||||||
|
],
|
||||||
|
} : { password: cfg.plugin_opts_thetlspassword }),
|
||||||
|
handshake: {
|
||||||
|
dest: cfg.plugin_opts_handshake_dest
|
||||||
|
},
|
||||||
|
} : null
|
||||||
|
} : {}),
|
||||||
|
|
||||||
|
/* Extra fields */
|
||||||
|
"congestion-controller": cfg.congestion_controller,
|
||||||
|
network: cfg.network,
|
||||||
|
udp: strToBool(cfg.udp),
|
||||||
|
|
||||||
|
/* TLS fields */
|
||||||
|
...(cfg.tls === '1' ? {
|
||||||
|
alpn: cfg.tls_alpn,
|
||||||
|
...(cfg.tls_reality === '1' ? {
|
||||||
|
"reality-config": {
|
||||||
|
dest: cfg.tls_reality_dest,
|
||||||
|
"private-key": cfg.tls_reality_private_key,
|
||||||
|
"short-id": cfg.tls_reality_short_id,
|
||||||
|
"server-names": cfg.tls_reality_server_names
|
||||||
|
}
|
||||||
|
} : {
|
||||||
|
certificate: cfg.tls_cert_path,
|
||||||
|
"private-key": cfg.tls_key_path
|
||||||
|
}),
|
||||||
|
"client-auth-type": cfg.tls_client_auth_type,
|
||||||
|
"client-auth-cert": cfg.tls_client_auth_cert_path,
|
||||||
|
"ech-key": cfg.tls_ech_key,
|
||||||
|
} : {}),
|
||||||
|
|
||||||
|
/* Transport fields */
|
||||||
|
...(cfg.transport_enabled === '1' ? {
|
||||||
|
"grpc-service-name": cfg.transport_grpc_servicename,
|
||||||
|
"ws-path": cfg.transport_path
|
||||||
|
} : {})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/* String universal end */
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
LUCI_TITLE:=kai
|
LUCI_TITLE:=kai
|
||||||
PKG_VERSION:=1.0.0
|
PKG_VERSION:=1.0.1
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
LUCI_DEPENDS:=+kai +kai_session +luci-compat
|
LUCI_DEPENDS:=+kai +kai_session +luci-compat
|
||||||
LUCI_MINIFY_CSS:=0
|
LUCI_MINIFY_CSS:=0
|
||||||
LUCI_MINIFY_JS:=0
|
LUCI_MINIFY_JS:=0
|
||||||
|
|||||||
@@ -9,11 +9,20 @@ s.anonymous=true
|
|||||||
|
|
||||||
s:option(Flag, "enabled", translate("Enable")).rmempty=false
|
s:option(Flag, "enabled", translate("Enable")).rmempty=false
|
||||||
|
|
||||||
|
local kai_model = require "luci.model.kai"
|
||||||
|
local blocks = kai_model.blocks()
|
||||||
|
local home = kai_model.home()
|
||||||
|
|
||||||
local data_dir = s:option(Value, "data_dir", translate("Data directory"))
|
local data_dir = s:option(Value, "data_dir", translate("Data directory"))
|
||||||
data_dir.placeholder = ""
|
|
||||||
data_dir.rmempty = false
|
data_dir.rmempty = false
|
||||||
data_dir.description = translate("Required. KAI session will store cwd/cache/data/config/state under this directory (subfolders: cwd, cache, data, config, state).")
|
data_dir.description = translate("Required. KAI session will store cwd/cache/data/config/state under this directory (subfolders: cwd, cache, data, config, state).")
|
||||||
|
|
||||||
|
local paths, default_path = kai_model.find_paths(blocks, home, "Configs")
|
||||||
|
for _, val in pairs(paths) do
|
||||||
|
data_dir:value(val, val)
|
||||||
|
end
|
||||||
|
data_dir.default = default_path
|
||||||
|
|
||||||
local port = s:option(Value, "port", translate("API port"))
|
local port = s:option(Value, "port", translate("API port"))
|
||||||
port.default = "8197"
|
port.default = "8197"
|
||||||
port.rmempty = false
|
port.rmempty = false
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
local jsonc = require "luci.jsonc"
|
||||||
|
|
||||||
|
local kai = {}
|
||||||
|
|
||||||
|
kai.blocks = function()
|
||||||
|
local f = io.popen("lsblk -s -f -b -o NAME,FSSIZE,MOUNTPOINT --json", "r")
|
||||||
|
local vals = {}
|
||||||
|
if f then
|
||||||
|
local ret = f:read("*all")
|
||||||
|
f:close()
|
||||||
|
local obj = jsonc.parse(ret)
|
||||||
|
for _, val in pairs(obj and obj["blockdevices"] or {}) do
|
||||||
|
local fsize = val["fssize"]
|
||||||
|
if fsize ~= nil and string.len(fsize) > 10 and val["mountpoint"] then
|
||||||
|
vals[#vals + 1] = val["mountpoint"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return vals
|
||||||
|
end
|
||||||
|
|
||||||
|
kai.home = function()
|
||||||
|
local uci = require "luci.model.uci".cursor()
|
||||||
|
local home_dirs = {}
|
||||||
|
home_dirs["main_dir"] = uci:get_first("quickstart", "main", "main_dir", "/root")
|
||||||
|
home_dirs["Configs"] = uci:get_first("quickstart", "main", "conf_dir", home_dirs["main_dir"] .. "/Configs")
|
||||||
|
home_dirs["Public"] = uci:get_first("quickstart", "main", "pub_dir", home_dirs["main_dir"] .. "/Public")
|
||||||
|
home_dirs["Downloads"] = uci:get_first("quickstart", "main", "dl_dir", home_dirs["Public"] .. "/Downloads")
|
||||||
|
home_dirs["Caches"] = uci:get_first("quickstart", "main", "tmp_dir", home_dirs["main_dir"] .. "/Caches")
|
||||||
|
return home_dirs
|
||||||
|
end
|
||||||
|
|
||||||
|
kai.find_paths = function(blocks, home_dirs, path_name)
|
||||||
|
local default_path = ""
|
||||||
|
local paths = {}
|
||||||
|
|
||||||
|
default_path = home_dirs[path_name] .. "/KAI"
|
||||||
|
if #blocks == 0 then
|
||||||
|
table.insert(paths, default_path)
|
||||||
|
else
|
||||||
|
for _, val in pairs(blocks) do
|
||||||
|
table.insert(paths, val .. "/" .. path_name .. "/KAI")
|
||||||
|
end
|
||||||
|
local without_conf_dir = "/root/" .. path_name .. "/KAI"
|
||||||
|
if default_path == without_conf_dir then
|
||||||
|
default_path = paths[1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return paths, default_path
|
||||||
|
end
|
||||||
|
|
||||||
|
return kai
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ LUCI_TITLE:=LuCI Support for Automatic Partition Mount
|
|||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
LUCI_DEPENDS:=+fdisk +block-mount +bc +blkid +parted +btrfs-progs +losetup +resize2fs +e2fsprogs +f2fs-tools +kmod-loop
|
LUCI_DEPENDS:=+fdisk +block-mount +bc +blkid +parted +btrfs-progs +losetup +resize2fs +e2fsprogs +f2fs-tools +kmod-loop
|
||||||
PKG_VERSION:=2.0.3
|
PKG_VERSION:=2.0.3
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
PKG_LICENSE:=Apache-2.0
|
PKG_LICENSE:=Apache-2.0
|
||||||
PKG_MAINTAINER:=Sirpdboy <herboy2008@gmail.com>
|
PKG_MAINTAINER:=Sirpdboy <herboy2008@gmail.com>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright (C) 2021-2025 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/luci-app-partexp
|
# Copyright (C) 2021-2026 sirpdboy <herboy2008@gmail.com> https://github.com/sirpdboy/luci-app-partexp
|
||||||
#
|
#
|
||||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||||
#
|
#
|
||||||
@@ -279,8 +279,8 @@ check_part_space() {
|
|||||||
check_free_space() {
|
check_free_space() {
|
||||||
DISK=$1
|
DISK=$1
|
||||||
PARTED_OUTPUT=$(parted -s /dev/$DISK unit GB print free 2>/dev/null)
|
PARTED_OUTPUT=$(parted -s /dev/$DISK unit GB print free 2>/dev/null)
|
||||||
FREE_SPACE=$(echo "$PARTED_OUTPUT" | grep "Free Space" | awk '{print $3}' )
|
FREE_SPACE=$(echo "$PARTED_OUTPUT" | awk '/Free Space/ {last=$3} END {print last}' | sed 's/GB$//')
|
||||||
echo $FREE_SPACE |awk -F '.' '{print $1}' | sed 's/[A-Za-z]//g'
|
echo $FREE_SPACE | cut -d'.' -f1
|
||||||
}
|
}
|
||||||
|
|
||||||
show_partition_info() {
|
show_partition_info() {
|
||||||
@@ -418,12 +418,6 @@ rootfs_resize() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
get_config() {
|
|
||||||
config_get target_function $1 target_function 1
|
|
||||||
config_get target_disk $1 target_disk 1
|
|
||||||
config_get_bool keep_config $1 keep_config 1
|
|
||||||
config_get format_type $1 format_type
|
|
||||||
}
|
|
||||||
|
|
||||||
# 修改 fdiskB 函数,使用环境变量参数
|
# 修改 fdiskB 函数,使用环境变量参数
|
||||||
fdiskB() {
|
fdiskB() {
|
||||||
|
|||||||
+2
-2
@@ -6,12 +6,12 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=redsocks2
|
PKG_NAME:=redsocks2
|
||||||
PKG_VERSION:=0.71
|
PKG_VERSION:=0.71
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_URL:=https://github.com/semigodking/redsocks.git
|
PKG_SOURCE_URL:=https://github.com/semigodking/redsocks.git
|
||||||
PKG_SOURCE_DATE:=2025-12-12
|
PKG_SOURCE_DATE:=2025-12-12
|
||||||
PKG_SOURCE_VERSION:=33c37b37c0524ef01e3f2653a6c47a10f783cc39
|
PKG_SOURCE_VERSION:=43ba8a3cb1b5f8c44b2a827b924375f1de733370
|
||||||
PKG_MIRROR_HASH:=skip
|
PKG_MIRROR_HASH:=skip
|
||||||
|
|
||||||
PKG_MAINTAINER:=semigodking <semigodking@gmail.com>
|
PKG_MAINTAINER:=semigodking <semigodking@gmail.com>
|
||||||
|
|||||||
Reference in new issue
Block a user