🍓 Sync 2026-09-15 02:13:06

This commit is contained in:
github-actions[bot] committed 2026-09-15 02:13:06 +08:00
1 parent 0e5393594c
commit 1dd96ac2bb
146 files changed
+26541 -508

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-ssserver-next
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_MAINTAINER:=Dante <dante@example.com>
PKG_LICENSE:=Apache-2.0
LUCI_TITLE:=LuCI support for Shadowsocks-rust Server (Next)
LUCI_DEPENDS:=+shadowsocks-rust-ssserver-next +luci-base +ucode +ucode-mod-fs +ucode-mod-ubus
define Package/luci-app-ssserver/postinst
#!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] || {
chmod +x /etc/init.d/ssserver
/etc/init.d/rpcd restart
}
exit 0
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
@@ -0,0 +1,106 @@
'use strict';
'require view';
'require form';
'require rpc';
var callGetCiphers = rpc.declare({
object: 'luci.ssserver',
method: 'get_ciphers',
expect: {}
});
var callGetTFOStatus = rpc.declare({
object: 'luci.ssserver',
method: 'get_tfo_status',
expect: {}
});
var callGetLanIP = rpc.declare({
object: 'luci.ssserver',
method: 'get_lan_ip',
expect: {}
});
return view.extend({
load: function() {
return Promise.all([
callGetCiphers(),
callGetTFOStatus(),
callGetLanIP()
]);
},
render: function(data) {
var m, s, o;
var ciphers = data[0].ciphers || [];
var tfo_val = data[1].value || 0;
var lan_ip = data[2].ip || '192.168.1.1';
m = new form.Map('ssserver', _('Shadowsocks Server Title') + ' - ' + _('Settings'),
_('A fast and secure tunnel proxy that helps you securely access your local network. ' +
'This version uses shadowsocks-rust.'));
s = m.section(form.TypedSection, 'ssserver', _('Settings'));
s.anonymous = true;
s.addremove = false;
o = s.option(form.Flag, 'enabled', _('Enable'));
o.rmempty = false;
o = s.option(form.Value, 'server', _('Server Address'),
_('The address the server will bind to. Use 0.0.0.0 for all interfaces.'));
o.datatype = 'ipaddr';
o.placeholder = '0.0.0.0';
o = s.option(form.Value, 'server_port', _('Server Port'),
_('The port the server will listen on.'));
o.datatype = 'port';
o.placeholder = '8388';
o = s.option(form.Value, 'password', _('Password'));
o.password = true;
o = s.option(form.ListValue, 'method', _('Encryption Method'));
for (var i = 0; i < ciphers.length; i++) {
o.value(ciphers[i]);
}
if (ciphers.indexOf('chacha20-ietf-poly1305') === -1 && ciphers.length === 0) {
o.value('chacha20-ietf-poly1305');
o.value('aes-256-gcm');
o.value('aes-128-gcm');
}
o = s.option(form.Value, 'timeout', _('ssserver timeout'), _('Connection timeout in seconds.'));
o.datatype = 'uinteger';
o.placeholder = '300';
o = s.option(form.ListValue, 'mode', _('Network Mode'));
o.value('tcp_and_udp', _('TCP and UDP'));
o.value('tcp_only', _('TCP only'));
o.value('udp_only', _('UDP only'));
o.default = 'tcp_and_udp';
o = s.option(form.Value, 'dns_resolver', _('DNS Resolver'),
_('DNS server for the shadowsocks server to resolve addresses.'));
o.datatype = 'ipaddr';
o.placeholder = lan_ip;
o.default = lan_ip;
o = s.option(form.Flag, 'open_firewall', _('Open Firewall'),
_('Automatically open the server port in the firewall (WAN).'));
var tfo_status_msg = '';
if (tfo_val === 0) {
tfo_status_msg = '<span style="color:red"> (' + _('Not supported or disabled in kernel') + ')</span>';
} else if (tfo_val === 1) {
tfo_status_msg = '<span style="color:orange"> (' + _('Enabled for client only') + ')</span>';
} else if (tfo_val === 2 || tfo_val === 3) {
tfo_status_msg = '<span style="color:green"> (' + _('Enabled and supported') + ')</span>';
}
o = s.option(form.Flag, 'fast_open', _('TCP Fast Open'),
_('Enable TCP Fast Open (requires kernel support).') + tfo_status_msg);
return m.render();
}
});
@@ -0,0 +1,47 @@
'use strict';
'require view';
'require rpc';
'require poll';
var callGetLog = rpc.declare({
object: 'luci.ssserver',
method: 'get_log',
expect: {}
});
return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
render: function() {
var log_view = E('textarea', {
id: 'ssserver_log_content',
style: 'width:100%; height:500px; background:#f4f4f4; color:#333; padding:10px; border:1px solid #ccc; border-radius:3px; font-family:monospace; font-size:12px; overflow-y:auto; white-space:pre-wrap; word-break:break-all; margin-top:10px;',
readonly: 'readonly'
}, _('Loading logs...'));
poll.add(function() {
return callGetLog().then(function(res) {
var area = document.getElementById('ssserver_log_content');
if (area) {
if (res && res.log) {
area.value = res.log;
area.scrollTop = area.scrollHeight;
} else {
area.value = _('No log entries found.');
}
}
});
}, 5);
return E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('Shadowsocks Server Title') + ' - ' + _('Log')),
E('div', { 'class': 'cbi-map-descr' }, _('Logs are updated every 5 seconds.')),
E('fieldset', { 'class': 'cbi-section' }, [
E('legend', {}, _('Log')),
E('div', { 'class': 'cbi-section-node' }, log_view)
])
]);
}
});
@@ -0,0 +1,189 @@
'use strict';
'require view';
'require rpc';
'require poll';
'require ui';
'require uci';
const callSsserverStatus = rpc.declare({
object: 'luci.ssserver',
method: 'get_status'
});
const callServiceAction = rpc.declare({
object: 'luci.ssserver',
method: 'service_action',
params: [ 'action' ]
});
const callSsserverLanIp = rpc.declare({
object: 'luci.ssserver',
method: 'get_lan_ip'
});
function renderState(status) {
let text = _('Stopped');
let color = 'red';
if (status.running) {
text = _('Running');
if (status.pid) {
text += ' [PID: ' + status.pid + ']';
}
color = 'green';
}
return { text, color };
}
return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
load: function() {
return Promise.all([
callSsserverStatus(),
uci.load('ssserver'),
callSsserverLanIp().catch(function() { return { ip: '' }; })
]);
},
render: function(data) {
const status = data && data[0] ? data[0] : {};
const stateInfo = renderState(status);
const lanIpInfo = data && data[2] ? data[2] : { ip: '' };
const lanIp = lanIpInfo.ip || '8.8.8.8';
// Read configurations from UCI
const ssserverConfig = uci.get('ssserver', 'main') || {};
const server = ssserverConfig.server || '0.0.0.0';
const port = ssserverConfig.server_port || '8388';
const method = ssserverConfig.method || 'chacha20-ietf-poly1305';
const timeout = ssserverConfig.timeout || '300';
let modeText = _('TCP and UDP');
if (ssserverConfig.mode === 'tcp_only') modeText = _('TCP only');
if (ssserverConfig.mode === 'udp_only') modeText = _('UDP only');
const dns = ssserverConfig.dns_resolver || lanIp;
const tfo = (ssserverConfig.fast_open === '1') ? _('Enabled') : _('Disabled');
const firewall = (ssserverConfig.open_firewall === '1') ? _('Enabled') : _('Disabled');
const container = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('Shadowsocks Server Title') + ' - ' + _('Overview')),
E('div', { 'class': 'cbi-map-descr' }, _('Overview of ssserver running status and actual runtime configurations.'))
]);
// Service Status Section (Aligned with lucky style)
const statusSection = E('fieldset', { 'class': 'cbi-section' }, [
E('legend', {}, _('Service Status')),
E('div', { 'class': 'cbi-section-node' }, [
E('table', { 'class': 'table cbi-section-table' }, [
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'width': '33%' }, _('Current Status')),
E('td', { 'class': 'td left' }, [
E('span', {
id: 'ssserver-status-text',
'style': 'font-weight:bold; color:' + stateInfo.color + '; margin-right:15px;'
}, stateInfo.text),
E('button', {
id: 'ssserver-restart-btn',
'class': 'btn cbi-button cbi-button-apply',
'style': 'display:' + (status.running ? 'inline-block' : 'none') + ';',
click: ui.createHandlerFn(this, function(ev) {
if (!ev || !ev.currentTarget) return;
const btn = ev.currentTarget;
btn.disabled = true;
ui.addNotification(null, E('p', _('Restarting Shadowsocks Server, please wait...')), 'info');
return callServiceAction('restart').then(function(res) {
btn.disabled = false;
if (res && res.success) {
ui.addNotification(null, E('p', _('Service restarted successfully.')), 'info');
} else {
ui.addNotification(null, E('p', _('Failed to restart service.')), 'error');
}
}).catch(function(err) {
btn.disabled = false;
ui.addNotification(null, E('p', _('An error occurred: ') + err.message), 'error');
});
})
}, _('Restart'))
])
])
])
])
]);
container.appendChild(statusSection);
// Runtime Configuration Section (Aligned with lucky style)
const configSection = E('fieldset', { 'class': 'cbi-section' }, [
E('legend', {}, _('Runtime Configurations')),
E('div', { 'class': 'cbi-section-node' }, [
E('table', { 'class': 'table cbi-section-table' }, [
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left', 'width': '33%' }, _('Server Address')),
E('td', { 'class': 'td left' }, E('code', {}, server))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('Server Port')),
E('td', { 'class': 'td left' }, E('code', {}, port))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('Encryption Method')),
E('td', { 'class': 'td left' }, E('code', {}, method))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('Network Mode')),
E('td', { 'class': 'td left' }, modeText)
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('ssserver timeout')),
E('td', { 'class': 'td left' }, timeout + ' ' + _('seconds'))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('DNS Resolver')),
E('td', { 'class': 'td left' }, E('code', {}, dns))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('TCP Fast Open')),
E('td', { 'class': 'td left' }, tfo)
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td left' }, _('Open Firewall')),
E('td', { 'class': 'td left' }, firewall)
])
])
])
]);
container.appendChild(configSection);
// Add live status polling
poll.add(function() {
return callSsserverStatus().then(function(status) {
const textNode = document.getElementById('ssserver-status-text');
const restartBtn = document.getElementById('ssserver-restart-btn');
if (textNode && restartBtn) {
const stateInfo = renderState(status || {});
textNode.textContent = stateInfo.text;
textNode.style.color = stateInfo.color;
// Toggle button visibility based on runtime state
if (status.running) {
restartBtn.style.display = 'inline-block';
} else {
restartBtn.style.display = 'none';
}
}
});
}, 5);
return container;
}
});
+1
View File
@@ -0,0 +1 @@
zh_Hans
@@ -0,0 +1,146 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Shadowsocks Server"
msgstr "Shadowsocks服务端"
msgid "Overview"
msgstr "概览"
msgid "Service Status"
msgstr "服务状态"
msgid "Current Status"
msgstr "当前状态"
msgid "Running"
msgstr "运行中"
msgid "Stopped"
msgstr "已停止"
msgid "Restart"
msgstr "重启"
msgid "Unknown"
msgstr "未知"
msgid "A fast and secure tunnel proxy that helps you securely access your local network. This version uses shadowsocks-rust."
msgstr "一个快速、安全的隧道代理,可以帮助您安全地访问局域网。此版本使用 shadowsocks-rust。"
msgid "Settings"
msgstr "设置"
msgid "Log"
msgstr "日志"
msgid "Enable"
msgstr "启用"
msgid "Server Address"
msgstr "服务器地址"
msgid "The address the server will bind to. Use 0.0.0.0 for all interfaces."
msgstr "服务器绑定的地址。使用 0.0.0.0 以监听所有接口。"
msgid "Server Port"
msgstr "服务器端口"
msgid "The port the server will listen on."
msgstr "服务器监听的端口。"
msgid "Password"
msgstr "密码"
msgid "Encryption Method"
msgstr "加密方式"
msgid "ssserver timeout"
msgstr "超时时间"
msgid "Connection timeout in seconds."
msgstr "连接超时时间(秒)。"
msgid "Network Mode"
msgstr "网络模式"
msgid "TCP and UDP"
msgstr "TCP 和 UDP"
msgid "TCP only"
msgstr "仅 TCP"
msgid "UDP only"
msgstr "仅 UDP"
msgid "TCP Fast Open"
msgstr "TCP 快速打开"
msgid "Enable TCP Fast Open (requires kernel support)."
msgstr "启用 TCP 快速打开(需要内核支持)。"
msgid "Not supported or disabled in kernel"
msgstr "内核不支持或已禁用"
msgid "Enabled for client only"
msgstr "仅对客户端开启"
msgid "Enabled and supported"
msgstr "已开启并支持"
msgid "DNS Resolver"
msgstr "DNS 解析服务器"
msgid "DNS server for the shadowsocks server to resolve addresses."
msgstr "Shadowsocks 服务端用于解析地址的 DNS 服务器。"
msgid "Open Firewall"
msgstr "自动开放防火墙"
msgid "Automatically open the server port in the firewall (WAN)."
msgstr "自动在防火墙 (WAN) 中开放服务器端口。"
msgid "Shadowsocks Server Log"
msgstr "Shadowsocks服务端日志"
msgid "Logs are updated every 5 seconds."
msgstr "日志每 5 秒更新一次。"
msgid "No log entries found."
msgstr "未找到日志条目。"
msgid "Default"
msgstr "默认"
msgid "Enabled"
msgstr "已启用"
msgid "Disabled"
msgstr "已禁用"
msgid "Overview of ssserver running status and actual runtime configurations."
msgstr "Shadowsocks服务端运行状态及当前运行配置概览。"
msgid "Restarting Shadowsocks Server, please wait..."
msgstr "正在重启Shadowsocks服务端,请稍候..."
msgid "Service restarted successfully."
msgstr "服务重启成功。"
msgid "Failed to restart service."
msgstr "服务重启失败。"
msgid "An error occurred: "
msgstr "发生错误:"
msgid "Runtime Configurations"
msgstr "运行配置"
msgid "seconds"
msgstr "秒"
msgid "Loading logs..."
msgstr "正在加载日志..."
msgid "Shadowsocks Server Title"
msgstr "Shadowsocks 服务端"
@@ -0,0 +1,11 @@
config ssserver 'main'
option enabled '0'
option server '0.0.0.0'
option server_port '8388'
option password 'password'
option method 'chacha20-ietf-poly1305'
option timeout '300'
option mode 'tcp_and_udp'
option fast_open '0'
option open_firewall '1'
+104
View File
@@ -0,0 +1,104 @@
#!/bin/sh /etc/rc.common
USE_PROCD=1
START=95
CONF_FILE="/var/etc/ssserver.json"
ss_server_generate_json() {
local server server_port password method timeout fast_open dns_resolver lan_ip mode
config_get server "$1" server "0.0.0.0"
config_get server_port "$1" server_port "8388"
config_get password "$1" password "password"
config_get method "$1" method "chacha20-ietf-poly1305"
config_get timeout "$1" timeout "300"
config_get mode "$1" mode "tcp_and_udp"
config_get_bool fast_open "$1" fast_open 0
config_get dns_resolver "$1" dns_resolver
# If DNS is empty, try to get LAN IP
if [ -z "$dns_resolver" ]; then
lan_ip=$(ubus call network.interface.lan status | jsonfilter -e '@["ipv4-address"][0].address')
dns_resolver="${lan_ip:-8.8.8.8}"
fi
mkdir -p /var/etc
cat > "$CONF_FILE" <<EOF
{
"server": "$server",
"server_port": $server_port,
"password": "$password",
"method": "$method",
"timeout": $timeout,
"fast_open": $([ "$fast_open" = "1" ] && echo "true" || echo "false"),
"dns": "$dns_resolver",
"mode": "$mode"
}
EOF
}
handle_firewall() {
local enabled port mode proto
config_get_bool enabled "$1" open_firewall 1
config_get port "$1" server_port 8388
config_get mode "$1" mode "tcp_and_udp"
case "$mode" in
tcp_only) proto="tcp" ;;
udp_only) proto="udp" ;;
*) proto="tcp udp" ;;
esac
# Remove existing rule if any
uci -q delete firewall.ssserver_wan
if [ "$enabled" -eq 1 ] && [ "$2" = "start" ]; then
uci -q batch <<EOF
set firewall.ssserver_wan=rule
set firewall.ssserver_wan.name='Allow-Shadowsocks-WAN'
set firewall.ssserver_wan.src='wan'
set firewall.ssserver_wan.dest_port="$port"
set firewall.ssserver_wan.proto="$proto"
set firewall.ssserver_wan.target='ACCEPT'
commit firewall
EOF
/etc/init.d/firewall reload
else
uci -q commit firewall
/etc/init.d/firewall reload
fi
}
start_service() {
config_load ssserver
local enabled
config_get_bool enabled main enabled 0
[ "$enabled" -eq 0 ] && return
ss_server_generate_json main
handle_firewall main start
procd_open_instance
procd_set_param command /usr/bin/ssserver -c "$CONF_FILE"
procd_set_param respawn
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param user root
procd_close_instance
}
stop_service() {
config_load ssserver
handle_firewall main stop
}
reload_service() {
stop
start
}
service_triggers() {
procd_add_reload_trigger "ssserver"
}
@@ -0,0 +1,33 @@
{
"admin/services/ssserver": {
"title": "Shadowsocks Server",
"action": {
"type": "firstchild"
},
"order": 50
},
"admin/services/ssserver/overview": {
"title": "Overview",
"action": {
"type": "view",
"path": "ssserver/overview"
},
"order": 10
},
"admin/services/ssserver/config": {
"title": "Settings",
"action": {
"type": "view",
"path": "ssserver/config"
},
"order": 20
},
"admin/services/ssserver/log": {
"title": "Log",
"action": {
"type": "view",
"path": "ssserver/log"
},
"order": 30
}
}
@@ -0,0 +1,17 @@
{
"luci-app-ssserver": {
"description": "Grant access to ssserver configuration and status",
"read": {
"ubus": {
"luci.ssserver": [ "get_log", "get_ciphers", "get_tfo_status", "get_lan_ip", "get_status" ]
},
"uci": [ "ssserver", "firewall" ]
},
"write": {
"ubus": {
"luci.ssserver": [ "service_action" ]
},
"uci": [ "ssserver", "firewall" ]
}
}
}
@@ -0,0 +1,110 @@
'use strict';
import { connect } from 'ubus';
import * as fs from 'fs';
const methods = {
get_log: {
call: function() {
let p = fs.popen('logread -e ssserver');
let log = '';
if (p) {
log = p.read('all');
p.close();
}
return { log: log };
}
},
get_ciphers: {
call: function() {
let ciphers = [];
let p = fs.popen('/usr/bin/ssserver --help 2>&1');
if (p) {
let output = p.read('all');
p.close();
let start = index(output, '--encrypt-method');
if (start >= 0) {
let sub = substr(output, start);
let res = match(sub, /possible values: (([^\]\r\n]|[\r\n])+)/);
if (res) {
let list = split(res[1], /,\s*/);
for (let c in list) {
let val = rtrim(ltrim(c));
// Clean up possible newlines and extra spaces
val = replace(val, /\s+/g, "");
if (val) push(ciphers, val);
}
}
}
}
// Fallback if parsing fails or returns nothing
if (length(ciphers) == 0) {
ciphers = [
'chacha20-ietf-poly1305',
'aes-256-gcm',
'aes-128-gcm',
'2022-blake3-aes-128-gcm',
'2022-blake3-aes-256-gcm',
'2022-blake3-chacha20-poly1305'
];
}
return { ciphers: ciphers };
}
},
get_tfo_status: {
call: function() {
let val = rtrim(fs.readfile('/proc/sys/net/ipv4/tcp_fastopen') || '0');
return { value: +val };
}
},
get_lan_ip: {
call: function() {
let u = connect();
let ip = '';
if (u) {
let status = u.call('network.interface.lan', 'status');
if (status && type(status['ipv4-address']) === 'array' && length(status['ipv4-address']) > 0) {
ip = status['ipv4-address'][0].address;
}
u.disconnect();
}
return { ip: ip };
}
},
get_status: {
call: function() {
let u = connect();
let running = false;
let pid = null;
if (u) {
let res = u.call('service', 'list', { name: 'ssserver' });
if (res && res.ssserver && res.ssserver.instances) {
let inst = res.ssserver.instances.instance1;
if (inst && inst.running) {
running = true;
pid = inst.pid;
}
}
u.disconnect();
}
return { running: running, pid: pid };
}
},
service_action: {
args: { action: '' },
call: function(req) {
let action = req.args?.action ?? '';
if (action != 'start' && action != 'stop' && action != 'restart' && action != 'reload')
return { success: false, error: 'invalid action' };
let rc = system('/etc/init.d/ssserver ' + action + ' >/dev/null 2>&1');
return { success: (rc === 0), applied: (rc === 0) };
}
}
};
return { 'luci.ssserver': methods };