mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-28 23:55:13 +08:00
🗽 Sync 2026-06-03 14:05:40
This commit is contained in:
1 parent
82314810a3
commit
50bf1ec00a
61 files changed
+6813
-15676
No files matched your search
+1
-1
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=dns2tcp
|
||||
PKG_VERSION:=1.1.2
|
||||
PKG_RELEASE:=2
|
||||
PKG_RELEASE:=3
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/zfl9/dns2tcp/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
diff --git a/dns2tcp.c b/dns2tcp.c
|
||||
index b289598..542f839 100644
|
||||
--- a/dns2tcp.c
|
||||
+++ b/dns2tcp.c
|
||||
@@ -35,6 +35,10 @@
|
||||
#define PORTSTRLEN 6 /* "65535" (include \0) */
|
||||
|
||||
#define DNS_MSGSZ 1472 /* mtu:1500 - iphdr:20 - udphdr:8 */
|
||||
+#define DNS_HDRLEN 12
|
||||
+
|
||||
+#define DNS_TYPE_A 1
|
||||
+#define DNS_TYPE_AAAA 28
|
||||
|
||||
/* ======================== helper ======================== */
|
||||
|
||||
@@ -128,6 +132,14 @@ typedef struct {
|
||||
union skaddr srcaddr;
|
||||
} ctx_t;
|
||||
|
||||
+typedef struct {
|
||||
+ size_t start;
|
||||
+ size_t end;
|
||||
+ uint16_t type;
|
||||
+ uint8_t section;
|
||||
+ bool drop;
|
||||
+} dns_rr_t;
|
||||
+
|
||||
/* ======================== global-vars ======================== */
|
||||
|
||||
enum {
|
||||
@@ -135,6 +147,7 @@ enum {
|
||||
FLAG_REUSE_PORT = 1 << 1, /* udp listen */
|
||||
FLAG_VERBOSE = 1 << 2, /* logging */
|
||||
FLAG_LOCAL_ADDR = 1 << 3, /* tcp local addr */
|
||||
+ FLAG_FILTER_AAAA = 1 << 4, /* strip all AAAA records from dns response */
|
||||
};
|
||||
|
||||
#define has_flag(flag) (g_flags & (flag))
|
||||
@@ -166,6 +179,126 @@ static void tcp_connect_cb(evloop_t *evloop, evio_t *watcher, int events);
|
||||
static void tcp_sendmsg_cb(evloop_t *evloop, evio_t *watcher, int events);
|
||||
static void tcp_recvmsg_cb(evloop_t *evloop, evio_t *watcher, int events);
|
||||
|
||||
+static uint16_t read_be16(const void *ptr) {
|
||||
+ const uint8_t *bytes = ptr;
|
||||
+ return ((uint16_t)bytes[0] << 8) | bytes[1];
|
||||
+}
|
||||
+
|
||||
+static void write_be16(void *ptr, uint16_t value) {
|
||||
+ uint8_t *bytes = ptr;
|
||||
+ bytes[0] = value >> 8;
|
||||
+ bytes[1] = value & 0xff;
|
||||
+}
|
||||
+
|
||||
+static bool dns_skip_name(const uint8_t *msg, size_t msglen, size_t offset, size_t *next_offset) {
|
||||
+ while (offset < msglen) {
|
||||
+ uint8_t len = msg[offset];
|
||||
+ if (len == 0) {
|
||||
+ *next_offset = offset + 1;
|
||||
+ return true;
|
||||
+ }
|
||||
+ if ((len & 0xc0) == 0xc0) {
|
||||
+ if (offset + 1 >= msglen)
|
||||
+ return false;
|
||||
+ *next_offset = offset + 2;
|
||||
+ return true;
|
||||
+ }
|
||||
+ if ((len & 0xc0) || offset + 1 + len > msglen)
|
||||
+ return false;
|
||||
+ offset += 1 + len;
|
||||
+ }
|
||||
+
|
||||
+ return false;
|
||||
+}
|
||||
+
|
||||
+static size_t filter_aaaa_records(uint8_t *msg, size_t msglen) {
|
||||
+ if (!has_flag(FLAG_FILTER_AAAA) || msglen < DNS_HDRLEN)
|
||||
+ return msglen;
|
||||
+
|
||||
+ uint16_t qdcount = read_be16(msg + 4);
|
||||
+ uint16_t counts[] = {
|
||||
+ read_be16(msg + 6),
|
||||
+ read_be16(msg + 8),
|
||||
+ read_be16(msg + 10),
|
||||
+ };
|
||||
+ uint32_t rrcount = counts[0] + counts[1] + counts[2];
|
||||
+
|
||||
+ if (rrcount == 0 || qdcount > msglen / 5 || rrcount > msglen / 11)
|
||||
+ return msglen;
|
||||
+
|
||||
+ size_t offset = DNS_HDRLEN;
|
||||
+ for (uint16_t i = 0; i < qdcount; i++) {
|
||||
+ if (!dns_skip_name(msg, msglen, offset, &offset) || offset + 4 > msglen)
|
||||
+ return msglen;
|
||||
+ offset += 4;
|
||||
+ }
|
||||
+
|
||||
+ size_t rr_start = offset;
|
||||
+ dns_rr_t *rrs = calloc(rrcount, sizeof(*rrs));
|
||||
+ if (!rrs)
|
||||
+ return msglen;
|
||||
+
|
||||
+ uint32_t rridx = 0;
|
||||
+ for (uint8_t section = 0; section < 3; section++) {
|
||||
+ for (uint16_t i = 0; i < counts[section]; i++) {
|
||||
+ dns_rr_t *rr = &rrs[rridx++];
|
||||
+ rr->start = offset;
|
||||
+ rr->section = section;
|
||||
+
|
||||
+ if (!dns_skip_name(msg, msglen, offset, &offset) || offset + 10 > msglen)
|
||||
+ goto out;
|
||||
+
|
||||
+ rr->type = read_be16(msg + offset);
|
||||
+ uint16_t rdlen = read_be16(msg + offset + 8);
|
||||
+ rr->end = offset + 10 + rdlen;
|
||||
+ if (rr->end > msglen)
|
||||
+ goto out;
|
||||
+
|
||||
+ offset = rr->end;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ size_t rest_start = offset;
|
||||
+ bool changed = false;
|
||||
+ uint16_t keep_counts[] = { counts[0], counts[1], counts[2] };
|
||||
+
|
||||
+ for (uint32_t i = 0; i < rrcount; i++) {
|
||||
+ if (rrs[i].type == DNS_TYPE_AAAA) {
|
||||
+ rrs[i].drop = true;
|
||||
+ keep_counts[rrs[i].section]--;
|
||||
+ changed = true;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (changed) {
|
||||
+ size_t write_offset = rr_start;
|
||||
+
|
||||
+ for (uint32_t i = 0; i < rrcount; i++) {
|
||||
+ if (rrs[i].drop)
|
||||
+ continue;
|
||||
+
|
||||
+ size_t rrlen = rrs[i].end - rrs[i].start;
|
||||
+ if (write_offset != rrs[i].start)
|
||||
+ memmove(msg + write_offset, msg + rrs[i].start, rrlen);
|
||||
+ write_offset += rrlen;
|
||||
+ }
|
||||
+
|
||||
+ if (write_offset != rest_start)
|
||||
+ memmove(msg + write_offset, msg + rest_start, msglen - rest_start);
|
||||
+
|
||||
+ msglen = write_offset + (msglen - rest_start);
|
||||
+ write_be16(msg + 6, keep_counts[0]);
|
||||
+ write_be16(msg + 8, keep_counts[1]);
|
||||
+ write_be16(msg + 10, keep_counts[2]);
|
||||
+ log_verbose("filter AAAA records, answer:%hu -> %hu, authority:%hu -> %hu, additional:%hu -> %hu",
|
||||
+ counts[0], keep_counts[0], counts[1], keep_counts[1], counts[2], keep_counts[2]);
|
||||
+ }
|
||||
+
|
||||
+out:
|
||||
+ free(rrs);
|
||||
+ return msglen;
|
||||
+}
|
||||
+
|
||||
static void print_help(void) {
|
||||
printf("usage: dns2tcp <-L listen> <-R remote> [options...]\n"
|
||||
" -L <ip[#port]> udp listen address, port default to 53\n"
|
||||
@@ -174,6 +307,7 @@ static void print_help(void) {
|
||||
" -s <syncnt> set TCP_SYNCNT option for tcp socket\n"
|
||||
" -6 set IPV6_V6ONLY option for udp socket\n"
|
||||
" -r set SO_REUSEPORT option for udp socket\n"
|
||||
+ " -A strip all AAAA records from dns response\n"
|
||||
" -v print verbose log, used for debugging\n"
|
||||
" -V print version number of dns2tcp and exit\n"
|
||||
" -h print help information of dns2tcp and exit\n"
|
||||
@@ -257,7 +391,7 @@ static void parse_opt(int argc, char *argv[]) {
|
||||
|
||||
opterr = 0;
|
||||
int shortopt;
|
||||
- const char *optstr = "L:R:l:s:6rafvVh";
|
||||
+ const char *optstr = "L:R:l:s:6rAafvVh";
|
||||
while ((shortopt = getopt(argc, argv, optstr)) != -1) {
|
||||
switch (shortopt) {
|
||||
case 'L':
|
||||
@@ -295,6 +429,9 @@ static void parse_opt(int argc, char *argv[]) {
|
||||
case 'r':
|
||||
add_flag(FLAG_REUSE_PORT);
|
||||
break;
|
||||
+ case 'A':
|
||||
+ add_flag(FLAG_FILTER_AAAA);
|
||||
+ break;
|
||||
case 'a':
|
||||
/* nop */
|
||||
break;
|
||||
@@ -399,6 +536,7 @@ int main(int argc, char *argv[]) {
|
||||
if (g_syn_cnt) log_info("enable TCP_SYNCNT:%hhu sockopt", g_syn_cnt);
|
||||
if (has_flag(FLAG_IPV6_V6ONLY)) log_info("enable IPV6_V6ONLY sockopt");
|
||||
if (has_flag(FLAG_REUSE_PORT)) log_info("enable SO_REUSEPORT sockopt");
|
||||
+ if (has_flag(FLAG_FILTER_AAAA)) log_info("enable AAAA record filter");
|
||||
log_verbose("print the verbose log");
|
||||
|
||||
g_listen_fd = create_socket(skaddr_family(&g_listen_skaddr), SOCK_DGRAM);
|
||||
@@ -532,6 +670,9 @@ static void tcp_recvmsg_cb(evloop_t *evloop, evio_t *watcher, int events __unuse
|
||||
uint16_t msglen;
|
||||
if (ctx->nbytes < 2 || ctx->nbytes < 2 + (msglen = ntohs(*(uint16_t *)buffer))) return;
|
||||
|
||||
+ msglen = filter_aaaa_records((uint8_t *)buffer + 2, msglen);
|
||||
+ *(uint16_t *)buffer = htons(msglen);
|
||||
+
|
||||
ssize_t nsend = sendto(g_listen_fd, buffer + 2, msglen, 0, &ctx->srcaddr.sa, skaddr_len(&ctx->srcaddr));
|
||||
if (nsend < 0 || verbose) {
|
||||
char ip[IP6STRLEN];
|
||||
+94
-69
@@ -3,51 +3,77 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=luci-app-ssr-plus
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_NAME:=luci-app-ssr-plus
|
||||
PKG_VERSION:=193
|
||||
PKG_RELEASE:=16
|
||||
PKG_VERSION:=190
|
||||
PKG_RELEASE:=17
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NONE_V2RAY \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Xray \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS_RUST \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_DNS2TCP \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_DNSPROXY \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_MosDNS \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Http_Proxy \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Mihomo \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Tuic_Client \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadow_TLS \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Redsocks2 \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Client \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Server \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Simple_Obfs \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_V2ray_Plugin \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Trojan
|
||||
|
||||
LUCI_TITLE:=SS/SSR/V2Ray/Trojan/NaiveProxy/Tuic/ShadowTLS/Hysteria/Socks5/Clash LuCI interface
|
||||
LUCI_TITLE:=SS/SSR/V2Ray/Trojan/NaiveProxy/Tuic/ShadowTLS/Hysteria/Socks5/Tun LuCI interface
|
||||
LUCI_PKGARCH:=all
|
||||
LUCI_DEPENDS:= \
|
||||
+libuci-lua +lua +luci-compat +coreutils +coreutils-base64 +dns2tcp +dnsmasq-full \
|
||||
+jq +ip-full +lua-neturl +libuci-lua +microsocks +ipt2socks +lyaml \
|
||||
+resolveip +curl +nping +unzip +xz-utils \
|
||||
+coreutils +coreutils-base64 +dns2tcp +dnsmasq-full \
|
||||
+jq +ip-full +lua +lua-neturl +libuci-lua +microsocks \
|
||||
+tcping +resolveip +shadowsocksr-libev-ssr-check +curl +nping \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:curl \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:v2ray-core \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Xray:curl \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Xray:xray-core \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG:chinadns-ng \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS:dns2socks \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS_RUST:dns2socks-rust \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_DNSPROXY:dnsproxy \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_MosDNS:mosdns \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria:hysteria \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Tuic_Client:tuic-client \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadow_TLS:shadow-tls \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks:ipt2socks \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun:kcptun-client \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy:naiveproxy \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Redsocks2:redsocks2 \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client:shadowsocks-libev-ss-local \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client:shadowsocks-libev-ss-redir \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server:shadowsocks-libev-ss-server \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client:shadowsocks-rust-sslocal \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server:shadowsocks-rust-ssserver \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Simple_Obfs:simple-obfs-client \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_V2ray_Plugin:v2ray-plugin \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-local \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-redir \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server:shadowsocksr-libev-ssr-server
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server:shadowsocksr-libev-ssr-server \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Trojan:trojan
|
||||
|
||||
|
||||
define Package/$(PKG_NAME)/config
|
||||
select PACKAGE_luci-lua-runtime if PACKAGE_$(PKG_NAME)
|
||||
|
||||
choice
|
||||
prompt "Transparent Proxy Backend"
|
||||
@@ -77,24 +103,37 @@ endchoice
|
||||
|
||||
choice
|
||||
prompt "Shadowsocks Client Selection"
|
||||
default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Client
|
||||
default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client if aarch64 || x86_64
|
||||
default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Client
|
||||
bool "None"
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client
|
||||
bool "Shadowsocks-libev"
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client
|
||||
bool "Shadowsocks Rust"
|
||||
bool "Shadowsocks-rust"
|
||||
depends on aarch64||arm||i386||mips||mipsel||x86_64
|
||||
depends on !(TARGET_x86_geode||TARGET_x86_legacy)
|
||||
endchoice
|
||||
|
||||
choice
|
||||
prompt "Shadowsocks Server Selection"
|
||||
default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server if aarch64
|
||||
default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server if i386||x86_64||arm
|
||||
default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Server
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Server
|
||||
bool "None"
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server
|
||||
bool "Shadowsocks-libev"
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server
|
||||
bool "Shadowsocks Rust"
|
||||
bool "Shadowsocks-rust"
|
||||
depends on aarch64||arm||i386||mips||mipsel||x86_64
|
||||
depends on !(TARGET_x86_geode||TARGET_x86_legacy)
|
||||
endchoice
|
||||
|
||||
choice
|
||||
@@ -105,35 +144,45 @@ choice
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_NONE_V2RAY
|
||||
bool "None"
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray
|
||||
bool "V2ray-core"
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Xray
|
||||
bool "Xray-core"
|
||||
select PACKAGE_xray-core
|
||||
endchoice
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG
|
||||
bool "Include ChinaDNS-NG"
|
||||
default y if aarch64||arm||i386||x86_64
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS
|
||||
bool "Include DNS2socks"
|
||||
default y
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_DNS2SOCKS_RUST
|
||||
bool "Include DNS2socks-Rust"
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_DNSPROXY
|
||||
bool "Include DNSproxy"
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_MosDNS
|
||||
bool "Include MosDNS"
|
||||
default y if aarch64||i386||x86_64
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria
|
||||
bool "Include Hysteria"
|
||||
select PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Http_Proxy
|
||||
bool "Include HTTP(S) Proxy Server"
|
||||
select PACKAGE_3proxy
|
||||
default y if (aarch64||arm||i386||loongarch64||riscv64||x86_64)
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Mihomo
|
||||
bool "Include Mihomo (Clash Support)"
|
||||
select PACKAGE_mihomo
|
||||
depends on aarch64||arm||i386||loongarch64||riscv64||x86_64
|
||||
default y if aarch64||arm||i386||loongarch64||riscv64||x86_64
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_GeoData
|
||||
bool "Include GeoData (GeoIP and GeoSite)"
|
||||
select PACKAGE_v2ray-geoip
|
||||
select PACKAGE_v2ray-geosite
|
||||
default y if i386||x86_64
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Tuic_Client
|
||||
bool "Include Tuic-Client"
|
||||
select PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG
|
||||
select PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks
|
||||
depends on aarch64||arm||i386||x86_64
|
||||
depends on !(TARGET_x86_geode||TARGET_x86_legacy)
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadow_TLS
|
||||
bool "Include Shadow-TLS"
|
||||
@@ -143,6 +192,10 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_Shadow_TLS
|
||||
depends on !(TARGET_x86_geode||TARGET_x86_legacy)
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks
|
||||
bool "Include IPT2Socks"
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun
|
||||
bool "Include Kcptun"
|
||||
default n
|
||||
@@ -152,9 +205,13 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy
|
||||
depends on !(arc||armeb||mips||mips64||powerpc||TARGET_gemini)
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Redsocks2
|
||||
bool "Include Redsocks2"
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Simple_Obfs
|
||||
bool "Include Shadowsocks Simple Obfs Plugin"
|
||||
default n
|
||||
default y
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_V2ray_Plugin
|
||||
bool "Include Shadowsocks V2ray Plugin"
|
||||
@@ -166,6 +223,11 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server
|
||||
bool "Include ShadowsocksR Libev Server"
|
||||
default y if i386||x86_64||arm
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Trojan
|
||||
bool "Include Trojan"
|
||||
select PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks
|
||||
default n
|
||||
|
||||
endef
|
||||
@@ -178,41 +240,4 @@ endef
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
ifneq ($(wildcard ${CURDIR}/luasrc),)
|
||||
$(INSTALL_DIR) $(1)$(LUCI_LIBRARYDIR)
|
||||
cp -pR $(PKG_BUILD_DIR)/luasrc/* $(1)$(LUCI_LIBRARYDIR)/
|
||||
$(FIND) $(1)$(LUCI_LIBRARYDIR)/ -type f -name '*.luadoc' | $(XARGS) rm
|
||||
$(if $(CONFIG_LUCI_SRCDIET),$(call SrcDiet,$(1)$(LUCI_LIBRARYDIR)/),true)
|
||||
$(call SubstituteVersion,$(1)$(LUCI_LIBRARYDIR)/)
|
||||
endif
|
||||
ifneq ($(wildcard ${CURDIR}/ucode),)
|
||||
$(INSTALL_DIR) $(1)$(UCODE_LIBRARYDIR)
|
||||
cp -pR $(PKG_BUILD_DIR)/ucode/* $(1)$(UCODE_LIBRARYDIR)/
|
||||
$(call SubstituteVersion,$(1)$(UCODE_LIBRARYDIR)/)
|
||||
endif
|
||||
ifneq ($(wildcard ${CURDIR}/htdocs),)
|
||||
$(INSTALL_DIR) $(1)$(HTDOCS)
|
||||
cp -pR $(PKG_BUILD_DIR)/htdocs/* $(1)$(HTDOCS)/
|
||||
$(if $(CONFIG_LUCI_JSMIN),$(call JsMin,$(1)$(HTDOCS)/),true)
|
||||
$(if $(CONFIG_LUCI_CSSTIDY),$(call CssTidy,$(1)$(HTDOCS)/),true)
|
||||
endif
|
||||
ifneq ($(wildcard ${CURDIR}/root),)
|
||||
$(INSTALL_DIR) $(1)/
|
||||
cp -pR $(PKG_BUILD_DIR)/root/* $(1)/
|
||||
endif
|
||||
ifneq ($(wildcard ${CURDIR}/src),)
|
||||
$(call Build/Install/Default)
|
||||
$(CP) $(PKG_INSTALL_DIR)/* $(1)/
|
||||
endif
|
||||
if [ -d "$(1)/usr/bin" ]; then \
|
||||
chmod 0755 "$(1)/usr/bin/ssr-monitor" 2>/dev/null || true; \
|
||||
chmod 0755 "$(1)/usr/bin/ssr-rules" 2>/dev/null || true; \
|
||||
chmod 0755 "$(1)/usr/bin/ssr-switch" 2>/dev/null || true; \
|
||||
fi
|
||||
if [ -f "$(1)/etc/hotplug.d/iface/99-ssrplus-pppoe" ]; then \
|
||||
chmod 0755 "$(1)/etc/hotplug.d/iface/99-ssrplus-pppoe" 2>/dev/null || true; \
|
||||
fi
|
||||
endef
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
/*! Sortable 1.15.7 - MIT | git://github.com/SortableJS/Sortable.git */
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sortable=e()}(this,function(){"use strict";function o(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,o=Array(e);n<e;n++)o[n]=t[n];return o}function i(t,e,n){return(e=function(t){t=function(t,e){if("object"!=typeof t||!t)return t;var n=t[Symbol.toPrimitive];if(void 0===n)return("string"===e?String:Number)(t);e=n.call(t,e||"default");if("object"!=typeof e)return e;throw new TypeError("@@toPrimitive must return a primitive value.")}(t,"string");return"symbol"==typeof t?t:t+""}(e))in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function a(){return(a=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var n,o=arguments[e];for(n in o)!{}.hasOwnProperty.call(o,n)||(t[n]=o[n])}return t}).apply(null,arguments)}function r(e,t){var n,o=Object.keys(e);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(e),t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),o.push.apply(o,n)),o}function I(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach(function(t){i(e,t,n[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))})}return e}function l(t,e){if(null==t)return{};var n,o=function(t,e){if(null==t)return{};var n,o={};for(n in t)if({}.hasOwnProperty.call(t,n)){if(-1!==e.indexOf(n))continue;o[n]=t[n]}return o}(t,e);if(Object.getOwnPropertySymbols)for(var i=Object.getOwnPropertySymbols(t),r=0;r<i.length;r++)n=i[r],-1===e.indexOf(n)&&{}.propertyIsEnumerable.call(t,n)&&(o[n]=t[n]);return o}function e(t){return function(t){if(Array.isArray(t))return o(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(t){if("string"==typeof t)return o(t,e);var n={}.toString.call(t).slice(8,-1);return"Map"===(n="Object"===n&&t.constructor?t.constructor.name:n)||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?o(t,e):void 0}}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function s(t){return(s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function t(t){if("undefined"!=typeof window&&window.navigator)return!!navigator.userAgent.match(t)}var y=t(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),w=t(/Edge/i),c=t(/firefox/i),u=t(/safari/i)&&!t(/chrome/i)&&!t(/android/i),d=t(/iP(ad|od|hone)/i),n=t(/chrome/i)&&t(/android/i),h={capture:!1,passive:!1};function f(t,e,n){t.addEventListener(e,n,!y&&h)}function p(t,e,n){t.removeEventListener(e,n,!y&&h)}function g(t,e){if(e&&(">"===e[0]&&(e=e.substring(1)),t))try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return}}function m(t){return t.host&&t!==document&&t.host.nodeType&&t.host!==t?t.host:t.parentNode}function P(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"!==e[0]||t.parentNode===n)&&g(t,e)||o&&t===n)return t}while(t!==n&&(t=m(t)))}return null}var v,b=/\s+/g;function k(t,e,n){var o;t&&e&&(t.classList?t.classList[n?"add":"remove"](e):(o=(" "+t.className+" ").replace(b," ").replace(" "+e+" "," "),t.className=(o+(n?" "+e:"")).replace(b," ")))}function R(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];o[e=!(e in o||-1!==e.indexOf("webkit"))?"-webkit-"+e:e]=n+("string"==typeof n?"":"px")}}function D(t,e){var n="";if("string"==typeof t)n=t;else do{var o=R(t,"transform")}while(o&&"none"!==o&&(n=o+" "+n),!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function E(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i<r;i++)n(o[i],i);return o}return[]}function O(){var t=document.scrollingElement;return t||document.documentElement}function X(t,e,n,o,i){if(t.getBoundingClientRect||t===window){var r,a,l,s,c,u,d=t!==window&&t.parentNode&&t!==O()?(a=(r=t.getBoundingClientRect()).top,l=r.left,s=r.bottom,c=r.right,u=r.height,r.width):(l=a=0,s=window.innerHeight,c=window.innerWidth,u=window.innerHeight,window.innerWidth);if((e||n)&&t!==window&&(i=i||t.parentNode,!y))do{if(i&&i.getBoundingClientRect&&("none"!==R(i,"transform")||n&&"static"!==R(i,"position"))){var h=i.getBouLine truncated
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,6 @@
|
||||
local m, s, o
|
||||
local cbi = require "luci.cbi"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local URL = require "url"
|
||||
|
||||
-- 获取 LAN IP 地址
|
||||
function lanip()
|
||||
@@ -31,32 +30,16 @@ local lan_ip = lanip()
|
||||
local server_table = {}
|
||||
local type_table = {}
|
||||
local function is_finded(e)
|
||||
return luci.sys.exec(string.format('type -t -p "%s" -p "/usr/libexec/%s" 2>/dev/null', e, e)) ~= ""
|
||||
end
|
||||
|
||||
local function clash_display_name(s)
|
||||
if s.type ~= "clash" or not s.clash_url or s.clash_url == "" then
|
||||
return nil
|
||||
end
|
||||
local ok, parsed = pcall(URL.parse, s.clash_url)
|
||||
if ok and parsed and parsed.host then
|
||||
return "[CLASH]:" .. parsed.host
|
||||
end
|
||||
return "[CLASH]"
|
||||
return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= ""
|
||||
end
|
||||
|
||||
uci:foreach("shadowsocksr", "servers", function(s)
|
||||
if s.type ~= "tun" and s.alias then
|
||||
if s.alias then
|
||||
server_table[s[".name"]] = "[%s]:%s" % {string.upper(s.v2ray_protocol or s.type), s.alias}
|
||||
elseif s.type ~= "tun" and s.server and s.server_port then
|
||||
elseif s.server and s.server_port then
|
||||
server_table[s[".name"]] = "[%s]:%s:%s" % {string.upper(s.v2ray_protocol or s.type), s.server, s.server_port}
|
||||
elseif s.type ~= "tun" then
|
||||
local display_name = clash_display_name(s)
|
||||
if display_name then
|
||||
server_table[s[".name"]] = display_name
|
||||
end
|
||||
end
|
||||
if s.type and s.type ~= "tun" then
|
||||
if s.type then
|
||||
type_table[s[".name"]] = s.type
|
||||
end
|
||||
end)
|
||||
@@ -96,13 +79,6 @@ o.datatype = "uinteger"
|
||||
o:depends("enable_switch", "1")
|
||||
o.default = 3
|
||||
|
||||
s:append(cbi.Template("shadowsocksr/advanced_switch_compact"))
|
||||
|
||||
o = s:option(Value, "default_node_local_port", translate("Default Node Local Port"))
|
||||
o.datatype = "port"
|
||||
o.default = 1234
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "gfwlist_url", translate("gfwlist Update url"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt", translate("v2fly/domain-list-community"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt", translate("Loyalsoldier/v2ray-rules-dat"))
|
||||
@@ -116,6 +92,145 @@ o:value("https://ispip.clang.cn/all_cn_cidr.txt", translate("Clang.CN.CIDR"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt", translate("china-operator-ip"))
|
||||
o.default = "https://ispip.clang.cn/all_cn.txt"
|
||||
|
||||
o = s:option(Flag, "netflix_enable", translate("Enable Netflix Mode"))
|
||||
o.description = translate("When disabled shunt mode, will same time stopped shunt service.")
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "nfip_url", translate("nfip_url"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/QiuSimons/Netflix_IP/NF_only.txt", translate("Netflix IP Only"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/QiuSimons/Netflix_IP/getflix.txt", translate("Netflix and AWS"))
|
||||
o.default = "https://fastly.jsdelivr.net/gh/QiuSimons/Netflix_IP/NF_only.txt"
|
||||
o.description = translate("Customize Netflix IP Url")
|
||||
o:depends("netflix_enable", "1")
|
||||
|
||||
o = s:option(ListValue, "shunt_dns_mode", translate("DNS Query Mode For Shunt Mode"))
|
||||
if is_finded("dns2socks") then
|
||||
o:value("1", translate("Use DNS2SOCKS query and cache"))
|
||||
end
|
||||
if is_finded("dns2socks-rust") then
|
||||
o:value("2", translate("Use DNS2SOCKS-RUST query and cache"))
|
||||
end
|
||||
if is_finded("mosdns") then
|
||||
o:value("3", translate("Use MosDNS query"))
|
||||
end
|
||||
if is_finded("dnsproxy") then
|
||||
o:value("4", translate("Use DNSPROXY query and cache"))
|
||||
end
|
||||
if is_finded("chinadns-ng") then
|
||||
o:value("5", translate("Use ChinaDNS-NG query and cache"))
|
||||
end
|
||||
o:depends("netflix_enable", "1")
|
||||
o.default = 1
|
||||
|
||||
o = s:option(Value, "shunt_dnsserver", translate("Anti-pollution DNS Server For Shunt Mode"))
|
||||
o:value("8.8.4.4:53", translate("Google Public DNS (8.8.4.4)"))
|
||||
o:value("8.8.8.8:53", translate("Google Public DNS (8.8.8.8)"))
|
||||
o:value("208.67.222.222:53", translate("OpenDNS (208.67.222.222)"))
|
||||
o:value("208.67.220.220:53", translate("OpenDNS (208.67.220.220)"))
|
||||
o:value("209.244.0.3:53", translate("Level 3 Public DNS (209.244.0.3)"))
|
||||
o:value("209.244.0.4:53", translate("Level 3 Public DNS (209.244.0.4)"))
|
||||
o:value("4.2.2.1:53", translate("Level 3 Public DNS (4.2.2.1)"))
|
||||
o:value("4.2.2.2:53", translate("Level 3 Public DNS (4.2.2.2)"))
|
||||
o:value("4.2.2.3:53", translate("Level 3 Public DNS (4.2.2.3)"))
|
||||
o:value("4.2.2.4:53", translate("Level 3 Public DNS (4.2.2.4)"))
|
||||
o:value("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)"))
|
||||
o:depends("shunt_dns_mode", "1")
|
||||
o:depends("shunt_dns_mode", "2")
|
||||
o.description = translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)")
|
||||
o.datatype = "ip4addrport"
|
||||
|
||||
o = s:option(Value, "shunt_mosdns_dnsserver", translate("Anti-pollution DNS Server"))
|
||||
o:value("tcp://8.8.4.4:53,tcp://8.8.8.8:53", translate("Google Public DNS"))
|
||||
o:value("tcp://208.67.222.222:53,tcp://208.67.220.220:53", translate("OpenDNS"))
|
||||
o:value("tcp://209.244.0.3:53,tcp://209.244.0.4:53", translate("Level 3 Public DNS-1 (209.244.0.3-4)"))
|
||||
o:value("tcp://4.2.2.1:53,tcp://4.2.2.2:53", translate("Level 3 Public DNS-2 (4.2.2.1-2)"))
|
||||
o:value("tcp://4.2.2.3:53,tcp://4.2.2.4:53", translate("Level 3 Public DNS-3 (4.2.2.3-4)"))
|
||||
o:value("tcp://1.1.1.1:53,tcp://1.0.0.1:53", translate("Cloudflare DNS"))
|
||||
o:depends("shunt_dns_mode", "3")
|
||||
o.description = translate("Custom DNS Server format as tcp://IP:PORT or tls://DOMAIN:PORT (tcp://8.8.8.8 or tls://dns.google:853)")
|
||||
|
||||
o = s:option(Flag, "shunt_mosdns_ipv6", translate("Disable IPv6 In MosDNS Query Mode (Shunt Mode)"))
|
||||
o:depends("shunt_dns_mode", "3")
|
||||
o.rmempty = false
|
||||
o.default = "0"
|
||||
|
||||
if is_finded("dnsproxy") then
|
||||
o = s:option(ListValue, "shunt_parse_method", translate("Select DNS parse Mode"))
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("When use DNS list file, please ensure list file exists and is formatted correctly.") .. "</li>" ..
|
||||
"<li>" .. translate("Tips: Dnsproxy DNS Parse List Path:") ..
|
||||
" <a href='http://" .. lan_ip .. "/cgi-bin/luci/admin/services/shadowsocksr/control' target='_blank'>" ..
|
||||
translate("Click here to view or manage the DNS list file") .. "</a>" .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
o:value("single_dns", translate("Set Single DNS"))
|
||||
o:value("parse_file", translate("Use DNS List File"))
|
||||
o:depends("shunt_dns_mode", "4")
|
||||
o.rmempty = true
|
||||
o.default = "single_dns"
|
||||
|
||||
o = s:option(Value, "dnsproxy_shunt_forward", translate("Anti-pollution DNS Server"))
|
||||
o:value("sdns://AgUAAAAAAAAABzguOC40LjQgsKKKE4EwvtIbNjGjagI2607EdKSVHowYZtyvD9iPrkkHOC44LjQuNAovZG5zLXF1ZXJ5", translate("Google DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAAACC2vD25TAYM7EnyCH8Xw1-0g5OccnTsGH9vQUUH0njRtAxkbnMudHduaWMudHcKL2Rucy1xdWVyeQ", translate("TWNIC-101 DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAADzE4NS4yMjIuMjIyLjIyMiAOp5Svj-oV-Fz-65-8H2VKHLKJ0egmfEgrdPeAQlUFFA8xODUuMjIyLjIyMi4yMjIKL2Rucy1xdWVyeQ", translate("dns.sb DNSCrypt SDNS"))
|
||||
o:value("sdns://AgMAAAAAAAAADTE0OS4xMTIuMTEyLjkgsBkgdEu7dsmrBT4B4Ht-BQ5HPSD3n3vqQ1-v5DydJC8SZG5zOS5xdWFkOS5uZXQ6NDQzCi9kbnMtcXVlcnk", translate("Quad9 DNSCrypt SDNS"))
|
||||
o:value("sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", translate("AdGuard DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAABzEuMC4wLjGgENk8mGSlIfMGXMOlIlCcKvq7AVgcrZxtjon911-ep0cg63Ul-I8NlFj4GplQGb_TTLiczclX57DvMV8Q-JdjgRgSZG5zLmNsb3VkZmxhcmUuY29tCi9kbnMtcXVlcnk", translate("Cloudflare DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAADjEwNC4xNi4yNDkuMjQ5ABJjbG91ZGZsYXJlLWRucy5jb20KL2Rucy1xdWVyeQ", translate("cloudflare-dns.com DNSCrypt SDNS"))
|
||||
o:depends("shunt_parse_method", "single_dns")
|
||||
o.description = translate("Custom DNS Server (support: IP:Port or tls://IP:Port or https://IP/dns-query and other format).")
|
||||
|
||||
o = s:option(ListValue, "shunt_upstreams_logic_mode", translate("Defines the upstreams logic mode"))
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("Defines the upstreams logic mode, possible values: load_balance, parallel, fastest_addr (default: load_balance).") .. "</li>" .. "<li>" .. translate("When two or more DNS servers are deployed, enable this function.") .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
o:value("load_balance", translate("load_balance"))
|
||||
o:value("parallel", translate("parallel"))
|
||||
o:value("fastest_addr", translate("fastest_addr"))
|
||||
o:depends("shunt_parse_method", "parse_file")
|
||||
o.rmempty = true
|
||||
o.default = "load_balance"
|
||||
|
||||
o = s:option(Flag, "shunt_dnsproxy_ipv6", translate("Disable IPv6 query mode"))
|
||||
o.description = translate("When disabled, all AAAA requests are not resolved.")
|
||||
o:depends("shunt_parse_method", "single_dns")
|
||||
o:depends("shunt_parse_method", "parse_file")
|
||||
o.rmempty = false
|
||||
o.default = "1"
|
||||
end
|
||||
|
||||
if is_finded("chinadns-ng") then
|
||||
o = s:option(Value, "chinadns_ng_shunt_dnsserver", translate("Anti-pollution DNS Server For Shunt Mode"))
|
||||
o:value("8.8.4.4:53", translate("Google Public DNS (8.8.4.4)"))
|
||||
o:value("8.8.8.8:53", translate("Google Public DNS (8.8.8.8)"))
|
||||
o:value("208.67.222.222:53", translate("OpenDNS (208.67.222.222)"))
|
||||
o:value("208.67.220.220:53", translate("OpenDNS (208.67.220.220)"))
|
||||
o:value("209.244.0.3:53", translate("Level 3 Public DNS (209.244.0.3)"))
|
||||
o:value("209.244.0.4:53", translate("Level 3 Public DNS (209.244.0.4)"))
|
||||
o:value("4.2.2.1:53", translate("Level 3 Public DNS (4.2.2.1)"))
|
||||
o:value("4.2.2.2:53", translate("Level 3 Public DNS (4.2.2.2)"))
|
||||
o:value("4.2.2.3:53", translate("Level 3 Public DNS (4.2.2.3)"))
|
||||
o:value("4.2.2.4:53", translate("Level 3 Public DNS (4.2.2.4)"))
|
||||
o:value("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)"))
|
||||
o:depends("shunt_dns_mode", "5")
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)") .. "</li>" ..
|
||||
"<li>" .. translate("Muitiple DNS server can saperate with ','") .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
|
||||
o = s:option(ListValue, "chinadns_ng_shunt_proto", translate("ChinaDNS-NG shunt query protocol"))
|
||||
o:value("none", translate("UDP/TCP upstream"))
|
||||
o:value("tcp", translate("TCP upstream"))
|
||||
o:value("udp", translate("UDP upstream"))
|
||||
o:value("tls", translate("DoT upstream (Need use wolfssl version)"))
|
||||
o:depends("shunt_dns_mode", "5")
|
||||
end
|
||||
|
||||
o = s:option(Flag, "apple_optimization", translate("Apple domains optimization"), translate("For Apple domains equipped with Chinese mainland CDN, always responsive to Chinese CDN IP addresses"))
|
||||
o.rmempty = false
|
||||
o.default = "1"
|
||||
@@ -151,13 +266,45 @@ end
|
||||
-- [[ SOCKS5 Proxy ]]--
|
||||
s = m:section(TypedSection, "socks5_proxy", translate("Global SOCKS5 Proxy Server"))
|
||||
s.anonymous = true
|
||||
-- s.description = translate("Only Same as Global Server is supported here. If the current main program supports a built-in SOCKS5 listener, it will be enabled in-process; otherwise SSR Plus will fall back to microsocks.")
|
||||
|
||||
-- Enable/Disable Option
|
||||
o = s:option(Flag, "enabled", translate("Enable"))
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
|
||||
-- Server Selection
|
||||
o = s:option(ListValue, "server", translate("Server"))
|
||||
o:value("same", translate("Same as Global Server"))
|
||||
for _, key in pairs(key_table) do
|
||||
o:value(key, server_table[key])
|
||||
end
|
||||
o.default = "same"
|
||||
o.rmempty = false
|
||||
|
||||
-- Dynamic value handling based on enabled/disabled state
|
||||
o.cfgvalue = function(self, section)
|
||||
local enabled = m:get(section, "enabled")
|
||||
if enabled == "0" then
|
||||
return m:get(section, "old_server")
|
||||
end
|
||||
return Value.cfgvalue(self, section)-- Default to `same` when enabled
|
||||
end
|
||||
|
||||
o.write = function(self, section, value)
|
||||
local enabled = m:get(section, "enabled")
|
||||
if enabled == "0" then
|
||||
local old_server = Value.cfgvalue(self, section)
|
||||
if old_server ~= "nil" then
|
||||
m:set(section, "old_server", old_server)
|
||||
end
|
||||
m:set(section, "server", "nil")
|
||||
else
|
||||
m:del(section, "old_server")
|
||||
-- Write the value normally when enabled
|
||||
Value.write(self, section, value)
|
||||
end
|
||||
end
|
||||
|
||||
-- Socks Auth
|
||||
if is_finded("xray") then
|
||||
o = s:option(ListValue, "socks5_auth", translate("Socks5 Auth Mode"), translate("Socks protocol auth methods, default:noauth."))
|
||||
@@ -165,6 +312,13 @@ o.default = "noauth"
|
||||
o:value("noauth", "NOAUTH")
|
||||
o:value("password", "PASSWORD")
|
||||
o.rmempty = true
|
||||
for key, server_type in pairs(type_table) do
|
||||
if server_type == "v2ray" then
|
||||
-- 如果服务器类型是 v2ray,则设置依赖项显示
|
||||
o:depends("server", key)
|
||||
end
|
||||
end
|
||||
o:depends({server = "same", disable = true})
|
||||
|
||||
-- Socks User
|
||||
o = s:option(Value, "socks5_user", translate("Socks5 User"), translate("Only when Socks5 Auth Mode is password valid, Mandatory."))
|
||||
@@ -181,6 +335,13 @@ o:depends("socks5_auth", "password")
|
||||
o = s:option(Flag, "socks5_mixed", translate("Enabled Mixed"), translate("Mixed as an alias of socks, default:Enabled."))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
for key, server_type in pairs(type_table) do
|
||||
if server_type == "v2ray" then
|
||||
-- 如果服务器类型是 v2ray,则设置依赖项显示
|
||||
o:depends("server", key)
|
||||
end
|
||||
end
|
||||
o:depends({server = "same", disable = true})
|
||||
end
|
||||
|
||||
-- Local Port
|
||||
@@ -189,37 +350,6 @@ o.datatype = "port"
|
||||
o.default = 1080
|
||||
o.rmempty = false
|
||||
|
||||
if is_finded("3proxy") then
|
||||
-- [[ HTTP/HTTPS Proxy ]]--
|
||||
s = m:section(TypedSection, "http_proxy", translate("Global HTTP/HTTPS Proxy Server"))
|
||||
s.anonymous = true
|
||||
-- s.description = translate("Only Same as Global Server is supported here. HTTP/HTTPS proxy service is provided through 3proxy.")
|
||||
|
||||
o = s:option(Flag, "enabled", translate("Enable"))
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, "http_auth", translate("HTTP Auth Mode"), translate("HTTP proxy auth method, default:none."))
|
||||
o.default = "none"
|
||||
o:value("none", "NONE")
|
||||
o:value("password", "PASSWORD")
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "http_user", translate("HTTP User"), translate("Only when HTTP Auth Mode is password valid, Mandatory."))
|
||||
o.rmempty = true
|
||||
o:depends("http_auth", "password")
|
||||
|
||||
o = s:option(Value, "http_pass", translate("HTTP Password"), translate("Only when HTTP Auth Mode is password valid, Not mandatory."))
|
||||
o.password = true
|
||||
o.rmempty = true
|
||||
o:depends("http_auth", "password")
|
||||
|
||||
o = s:option(Value, "local_port", translate("Local Port"))
|
||||
o.datatype = "port"
|
||||
o.default = 3128
|
||||
o.rmempty = false
|
||||
end
|
||||
|
||||
-- [[ fragmen Settings ]]--
|
||||
if is_finded("xray") then
|
||||
s = m:section(TypedSection, "global_xray_fragment", translate("Xray Fragment Settings"))
|
||||
|
||||
@@ -20,7 +20,7 @@ local xray_version_val = 0
|
||||
|
||||
-- 确保正确判断程序是否存在
|
||||
local function is_finded(e)
|
||||
return luci.sys.exec(string.format('type -t -p "%s" -p "/usr/libexec/%s" 2>/dev/null', e, e)) ~= ""
|
||||
return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= ""
|
||||
end
|
||||
|
||||
local function is_installed(e)
|
||||
@@ -142,14 +142,16 @@ local function set_apply_on_parse(map)
|
||||
local old = map.on_after_save
|
||||
map.on_after_save = function(self)
|
||||
if old then old(self) end
|
||||
--map:set("@global[0]", "timestamp", os.time())
|
||||
map:set("@global[0]", "timestamp", os.time())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local has_ss_rust = is_finded("sslocal") or is_finded("ssserver")
|
||||
local has_mihomo = is_finded("mihomo")
|
||||
local has_ss_libev = is_finded("ss-redir") or is_finded("ss-local")
|
||||
local has_trojan = is_finded("trojan")
|
||||
local has_xray = is_finded("xray")
|
||||
local has_hysteria2 = is_finded("hysteria")
|
||||
|
||||
local server_table = {}
|
||||
local encrypt_methods = {
|
||||
@@ -254,56 +256,6 @@ local tls_flows = {
|
||||
"none"
|
||||
}
|
||||
|
||||
local function migrate_xray_protocol_nodes()
|
||||
local changed = false
|
||||
|
||||
uci:foreach("shadowsocksr", "servers", function(section)
|
||||
local sid = section[".name"]
|
||||
local stype = section.type
|
||||
local proto = section.v2ray_protocol
|
||||
local escaped_sid = luci.util.shellquote(sid)
|
||||
|
||||
if stype == "ss" or stype == "ss-libev" then
|
||||
if has_mihomo then
|
||||
luci.sys.call(string.format("uci set shadowsocksr.%s.type='ss'", escaped_sid))
|
||||
changed = true
|
||||
elseif has_ss_rust then
|
||||
luci.sys.call(string.format("uci set shadowsocksr.%s.type='ss-rust'", escaped_sid))
|
||||
changed = true
|
||||
elseif has_xray then
|
||||
luci.sys.call(string.format("uci set shadowsocksr.%s.type='v2ray' && " .. "uci set shadowsocksr.%s.v2ray_protocol='shadowsocks'", escaped_sid, escaped_sid))
|
||||
changed = true
|
||||
end
|
||||
elseif stype == "v2ray" and proto == "shadowsocks" and has_mihomo then
|
||||
luci.sys.call(string.format("uci set shadowsocksr.%s.type='ss' && " .. "uci delete shadowsocksr.%s.v2ray_protocol", escaped_sid, escaped_sid))
|
||||
changed = true
|
||||
end
|
||||
if stype == "hysteria2" then
|
||||
luci.sys.call(string.format("uci set shadowsocksr.%s.type='v2ray' && " .. "uci set shadowsocksr.%s.v2ray_protocol='hysteria2'", escaped_sid, escaped_sid))
|
||||
changed = true
|
||||
elseif stype == "trojan" then
|
||||
luci.sys.call(string.format("uci set shadowsocksr.%s.type='v2ray' && " .. "uci set shadowsocksr.%s.v2ray_protocol='trojan'", escaped_sid, escaped_sid))
|
||||
changed = true
|
||||
end
|
||||
end)
|
||||
local subscribe_sid = uci:get_first("shadowsocksr", "server_subscribe")
|
||||
if subscribe_sid then
|
||||
local old_options = {"xray_hy2_type", "xray_tj_type", "ss_type"}
|
||||
local escaped_sub_sid = luci.util.shellquote(subscribe_sid)
|
||||
for _, opt in ipairs(old_options) do
|
||||
if uci:get("shadowsocksr", subscribe_sid, opt) then
|
||||
luci.sys.call(string.format("uci delete shadowsocksr.%s.%s", escaped_sub_sid, opt))
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
if changed then
|
||||
luci.sys.call("uci commit shadowsocksr")
|
||||
end
|
||||
end
|
||||
|
||||
migrate_xray_protocol_nodes()
|
||||
|
||||
m = Map("shadowsocksr", translate("Edit ShadowSocksR Server"))
|
||||
m.redirect = url("servers")
|
||||
if not sid or m.uci:get("shadowsocksr", sid) ~= "servers" then
|
||||
@@ -312,14 +264,6 @@ if not sid or m.uci:get("shadowsocksr", sid) ~= "servers" then
|
||||
end
|
||||
-- 保存&应用成功后跳转到节点列表
|
||||
set_apply_on_parse(m)
|
||||
local old_after_save = m.on_after_save
|
||||
m.on_after_save = function(self)
|
||||
if old_after_save then old_after_save(self) end
|
||||
local node_type = self.uci:get("shadowsocksr", sid, "type")
|
||||
if node_type == "clash" then
|
||||
luci.sys.call(string.format("/etc/init.d/shadowsocksr clash_cache %s >/dev/null 2>&1 &", sid))
|
||||
end
|
||||
end
|
||||
|
||||
-- [[ Servers Setting ]]--
|
||||
s = m:section(NamedSection, sid, "servers")
|
||||
@@ -331,27 +275,88 @@ o.rawhtml = true
|
||||
o.template = "shadowsocksr/ssrurl"
|
||||
o.value = sid
|
||||
|
||||
-- 新增一个选择框,用于选择 Xray 或 Hysteria2 核心
|
||||
o = s:option(ListValue, "_xray_hy2_type", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Type", "Hysteria2")))
|
||||
o.description = translate("The configured type also applies to the core specified when manually importing nodes.")
|
||||
-- 注意:Auto 选项使用特殊字符串 "__auto__" 而不是空字符串
|
||||
o:value("__auto__", translate("Auto"))
|
||||
if has_hysteria2 then
|
||||
o:value("hysteria2", translate("Hysteria2"))
|
||||
end
|
||||
if has_xray then
|
||||
o:value("v2ray", translate("Xray (Hysteria2)"))
|
||||
end
|
||||
-- 读取全局 xray_hy2_type
|
||||
o.cfgvalue = function(self, section)
|
||||
local val = uci:get("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type")
|
||||
if val == nil or val == "" then
|
||||
return "__auto__" -- 对应 Auto 选项
|
||||
end
|
||||
return val
|
||||
end
|
||||
o.rmempty = true
|
||||
-- 保存时更新全局配置
|
||||
o.write = function(self, section, value)
|
||||
if value == "__auto__" then
|
||||
-- 删除全局配置
|
||||
uci:delete("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type")
|
||||
else
|
||||
-- 设置具体值
|
||||
uci:set("shadowsocksr", "@server_subscribe[0]", "xray_hy2_type", value)
|
||||
end
|
||||
end
|
||||
|
||||
-- 新增一个选择框,用于选择 Xray 或 Trojan 核心
|
||||
o = s:option(ListValue, "_xray_tj_type", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Type", "Trojan")))
|
||||
o.description = translate("The configured type also applies to the core specified when manually importing nodes.")
|
||||
-- 注意:Auto 选项使用特殊字符串 "__auto__" 而不是空字符串
|
||||
o:value("__auto__", translate("Auto"))
|
||||
if has_hysteria2 then
|
||||
o:value("trojan", translate("Trojan"))
|
||||
end
|
||||
if has_xray then
|
||||
o:value("v2ray", translate("Xray (Trojan)"))
|
||||
end
|
||||
-- 读取全局 xray_tj_type
|
||||
o.cfgvalue = function(self, section)
|
||||
local val = uci:get("shadowsocksr", "@server_subscribe[0]", "xray_tj_type")
|
||||
if val == nil or val == "" then
|
||||
return "__auto__" -- 对应 Auto 选项
|
||||
end
|
||||
return val
|
||||
end
|
||||
o.rmempty = true
|
||||
-- 保存时更新全局配置
|
||||
o.write = function(self, section, value)
|
||||
if value == "__auto__" then
|
||||
-- 删除全局配置
|
||||
uci:delete("shadowsocksr", "@server_subscribe[0]", "xray_tj_type")
|
||||
else
|
||||
-- 设置具体值
|
||||
uci:set("shadowsocksr", "@server_subscribe[0]", "xray_tj_type", value)
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "type", translate("Server Node Type"))
|
||||
if is_finded("xray") then
|
||||
if is_finded("xray") or is_finded("v2ray") then
|
||||
o:value("v2ray", translate("V2Ray/XRay"))
|
||||
end
|
||||
if is_finded("ssr-redir") then
|
||||
o:value("ssr", translate("ShadowsocksR"))
|
||||
end
|
||||
if has_mihomo then
|
||||
o:value("ss", translate("ShadowSocks"))
|
||||
if has_ss_rust or has_ss_libev then
|
||||
o:value("ss", translate("ShadowSocks"))
|
||||
end
|
||||
if has_ss_rust then
|
||||
o:value("ss-rust", translate("ShadowSocks"))
|
||||
if is_finded("trojan") then
|
||||
o:value("trojan", translate("Trojan"))
|
||||
end
|
||||
if is_finded("naive") then
|
||||
o:value("naiveproxy", translate("NaiveProxy"))
|
||||
end
|
||||
if is_finded("mihomo") then
|
||||
o:value("clash", translate("Clash/Mihomo"))
|
||||
if is_finded("hysteria") then
|
||||
o:value("hysteria2", translate("Hysteria2"))
|
||||
end
|
||||
if is_finded("mihomo") then
|
||||
if is_finded("tuic-client") then
|
||||
o:value("tuic", translate("TUIC"))
|
||||
end
|
||||
if is_finded("shadow-tls") and is_finded("sslocal") then
|
||||
@@ -360,51 +365,78 @@ end
|
||||
if is_finded("ipt2socks") then
|
||||
o:value("socks5", translate("Socks5"))
|
||||
end
|
||||
if is_finded("redsocks2") then
|
||||
o:value("tun", translate("Network Tunnel"))
|
||||
end
|
||||
local old_cfgvalue = o.cfgvalue
|
||||
o.cfgvalue = function(self, section)
|
||||
local val = self.map.uci:get("shadowsocksr", section, "type")
|
||||
if old_cfgvalue then
|
||||
local val = self.map.uci:get("shadowsocksr", section, "type")
|
||||
if val == "ss-rust" or val == "ss-libev" then
|
||||
return "ss"
|
||||
end
|
||||
if old_cfgvalue then
|
||||
return old_cfgvalue(self, section)
|
||||
end
|
||||
return val
|
||||
end
|
||||
return val
|
||||
end
|
||||
-- 重写 write,当用户选择 "ss" 时不写入(由 _ss_core 负责写入具体核心)
|
||||
local old_write = o.write
|
||||
o.write = function(self, section, value)
|
||||
if value == "ss" then
|
||||
return -- 不做任何写入,等待 _ss_core 写入
|
||||
end
|
||||
if old_write then
|
||||
old_write(self, section, value)
|
||||
else
|
||||
self.map.uci:set("shadowsocksr", section, "type", value)
|
||||
end
|
||||
end
|
||||
|
||||
o.description = translate("Using incorrect encryption mothod may causes service fail to start")
|
||||
|
||||
o = s:option(Value, "alias", translate("Alias(optional)"))
|
||||
|
||||
local function clash_source_formvalue(map, section, option)
|
||||
local value = map:formvalue("cbid." .. map.config .. "." .. section .. "." .. option)
|
||||
if value == nil then
|
||||
value = map.uci:get(map.config, section, option)
|
||||
o = s:option(ListValue, "iface", translate("Network interface to use"))
|
||||
for _, e in ipairs(luci.sys.net.devices()) do
|
||||
if e ~= "lo" then
|
||||
o:value(e)
|
||||
end
|
||||
return trim(value or "")
|
||||
end
|
||||
o:depends("type", "tun")
|
||||
o.description = translate("Redirect traffic to this network interface")
|
||||
|
||||
local function validate_clash_source(self, value, section)
|
||||
local clash_url = clash_source_formvalue(self.map, section, "clash_url")
|
||||
local clash_path = clash_source_formvalue(self.map, section, "clash_path")
|
||||
if clash_url == "" and clash_path == "" then
|
||||
return nil, translate("Please specify either a Clash subscription URL or a local YAML path.")
|
||||
end
|
||||
return value
|
||||
-- 新增一个选择框,用于选择 Shadowsocks 具体版本(仅当节点类型为 ss 或其具体子类型时显示)
|
||||
o = s:option(ListValue, "_ss_core", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Version", "ShadowSocks")))
|
||||
o.description = translate("Selection ShadowSocks Node Use Version.")
|
||||
if has_ss_rust then
|
||||
o:value("ss-rust", translate("ShadowSocks-rust Version"))
|
||||
end
|
||||
|
||||
o = s:option(Value, "clash_url", translate("Clash Subscription URL"))
|
||||
o.placeholder = "https://example.com/config.yaml"
|
||||
if has_ss_libev then
|
||||
o:value("ss-libev", translate("ShadowSocks-libev Version"))
|
||||
end
|
||||
o.cfgvalue = function(self, section)
|
||||
-- 读取当前节点的 type 值,如果已经是具体核心则显示对应的选项
|
||||
local node_type = self.map.uci:get("shadowsocksr", section, "type")
|
||||
if node_type == "ss-rust" or node_type == "ss-libev" then
|
||||
return node_type
|
||||
end
|
||||
-- 如果全局 ss_type 有值且为具体核心则返回该值
|
||||
local ss_type = self.map.uci:get("shadowsocksr", "@server_subscribe[0]", "ss_type")
|
||||
if ss_type == "ss-rust" or ss_type == "ss-libev" then
|
||||
return ss_type
|
||||
end
|
||||
-- 如果节点 type 是旧的 "ss",则返回空,手动选择
|
||||
return nil
|
||||
end
|
||||
-- 显示条件:当节点类型为 "ss" 或其具体核心时显示
|
||||
o:depends("type", "ss")
|
||||
o.rmempty = true
|
||||
o:depends("type", "clash")
|
||||
o.validate = validate_clash_source
|
||||
|
||||
o = s:option(Value, "clash_path", translate("Clash YAML Path"))
|
||||
o.placeholder = "/etc/ssrplus/clash/custom.yaml"
|
||||
o.rmempty = true
|
||||
o:depends("type", "clash")
|
||||
o.validate = validate_clash_source
|
||||
|
||||
o = s:option(Value, "clash_user_agent", translate("Clash User-Agent"))
|
||||
o.default = "clash"
|
||||
o.rmempty = false
|
||||
o:depends("type", "clash")
|
||||
-- 保存时,将选择的值直接写入当前节点的 type 字段
|
||||
o.write = function(self, section, value)
|
||||
if value and value ~= "" then
|
||||
self.map.uci:set("shadowsocksr", section, "type", value)
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "v2ray_protocol", translate("V2Ray/XRay protocol"))
|
||||
o:value("vless", translate("VLESS"))
|
||||
@@ -422,54 +454,30 @@ o:value("http", translate("HTTP"))
|
||||
o:depends("type", "v2ray")
|
||||
|
||||
o = s:option(Value, "server", translate("Server Address"))
|
||||
o.datatype = "or(host,ip6addr)"
|
||||
o.datatype = "host"
|
||||
o.rmempty = false
|
||||
o:depends("type", "ssr")
|
||||
o:depends("type", "ss")
|
||||
o:depends("type", "ss-rust")
|
||||
o:depends("type", "v2ray")
|
||||
o:depends("type", "trojan")
|
||||
o:depends("type", "naiveproxy")
|
||||
o:depends("type", "hysteria2")
|
||||
o:depends("type", "tuic")
|
||||
o:depends("type", "shadowtls")
|
||||
o:depends("type", "socks5")
|
||||
local protocols = s.fields["v2ray_protocol"].keylist
|
||||
if protocols and type(protocols) == "table" and #protocols > 0 then
|
||||
for _, proto in ipairs(protocols) do
|
||||
if not proto:find("^_") then
|
||||
if proto == "hysteria2" then
|
||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", hysteria2_realms = false})
|
||||
else
|
||||
o:depends({type = "v2ray", v2ray_protocol = proto})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(Value, "server_port", translate("Server Port"))
|
||||
o.datatype = "port"
|
||||
o.rmempty = true
|
||||
o:depends("type", "ssr")
|
||||
o:depends("type", "ss")
|
||||
o:depends("type", "ss-rust")
|
||||
o:depends("type", "v2ray")
|
||||
o:depends("type", "trojan")
|
||||
o:depends("type", "naiveproxy")
|
||||
o:depends("type", "hysteria2")
|
||||
o:depends("type", "tuic")
|
||||
o:depends("type", "shadowtls")
|
||||
o:depends("type", "socks5")
|
||||
local protocols = s.fields["v2ray_protocol"].keylist
|
||||
if protocols and type(protocols) == "table" and #protocols > 0 then
|
||||
for _, proto in ipairs(protocols) do
|
||||
if not proto:find("^_") then
|
||||
if proto == "hysteria2" then
|
||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", hysteria2_realms = false})
|
||||
else
|
||||
o:depends({type = "v2ray", v2ray_protocol = proto})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(Flag, "auth_enable", translate("Enable Authentication"))
|
||||
o.rmempty = false
|
||||
@@ -490,7 +498,6 @@ o.password = true
|
||||
o.rmempty = true
|
||||
o:depends("type", "ssr")
|
||||
o:depends("type", "ss")
|
||||
o:depends("type", "ss-rust")
|
||||
o:depends("type", "trojan")
|
||||
o:depends("type", "naiveproxy")
|
||||
o:depends("type", "shadowtls")
|
||||
@@ -517,7 +524,6 @@ for _, v in ipairs(encrypt_methods_ss) do
|
||||
end
|
||||
end
|
||||
o.rmempty = true
|
||||
o:depends("type", "ss-rust")
|
||||
o:depends("type", "ss")
|
||||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||||
|
||||
@@ -536,30 +542,22 @@ o.default = "1"
|
||||
o = s:option(Flag, "enable_plugin", translate("Enable Plugin"))
|
||||
o.rmempty = true
|
||||
o:depends("type", "ss")
|
||||
o:depends("type", "ss-rust")
|
||||
o.default = "0"
|
||||
|
||||
-- Shadowsocks Plugin
|
||||
o = s:option(ListValue, "plugin", translate("Obfs"))
|
||||
o:value("none", translate("None"))
|
||||
if has_mihomo or is_finded("obfs-local") then
|
||||
if is_finded("obfs-local") then
|
||||
o:value("obfs-local", translate("obfs-local"))
|
||||
end
|
||||
if has_mihomo or is_finded("v2ray-plugin") then
|
||||
if is_finded("v2ray-plugin") then
|
||||
o:value("v2ray-plugin", translate("v2ray-plugin"))
|
||||
end
|
||||
if has_mihomo then
|
||||
o:value("gost-plugin", translate("gost-plugin"))
|
||||
end
|
||||
if is_finded("xray-plugin") then
|
||||
o:value("xray-plugin", translate("xray-plugin"))
|
||||
end
|
||||
if has_mihomo or is_finded("shadow-tls") then
|
||||
o:value("shadow-tls", translate("Shadow-TLS"))
|
||||
end
|
||||
if has_mihomo then
|
||||
o:value("restls", translate("restls"))
|
||||
o:value("kcptun", translate("kcptun"))
|
||||
if is_finded("shadow-tls") then
|
||||
o:value("shadow-tls", translate("shadow-tls"))
|
||||
end
|
||||
o:value("custom", translate("Custom"))
|
||||
o.rmempty = true
|
||||
@@ -595,21 +593,6 @@ o:depends("type", "ssr")
|
||||
|
||||
|
||||
-- [[ Hysteria2 ]]--
|
||||
o = s:option(Flag, "hysteria2_realms", translate("Hysteria2 Realms"))
|
||||
o.default = "0"
|
||||
if xray_version_val > 260509 then
|
||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||||
else
|
||||
o:depends({type = "v2ray", v2ray_protocol = "__hide"})
|
||||
end
|
||||
|
||||
o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
|
||||
o:depends("hysteria2_realms", true)
|
||||
|
||||
o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
|
||||
o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
|
||||
o:depends("hysteria2_realms", true)
|
||||
|
||||
o = s:option(Value, "hy2_auth", translate("Users Authentication"))
|
||||
o:depends("type", "hysteria2")
|
||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||||
@@ -618,7 +601,7 @@ o.rmempty = false
|
||||
|
||||
o = s:option(Flag, "flag_port_hopping", translate("Enable Port Hopping"))
|
||||
o:depends("type", "hysteria2")
|
||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", hysteria2_realms = false})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||||
o.rmempty = true
|
||||
o.default = "0"
|
||||
|
||||
@@ -660,30 +643,17 @@ o.rmempty = true
|
||||
o.default = "0"
|
||||
|
||||
o = s:option(Value, "obfs_type", translate("Obfuscation Type"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("salamander")
|
||||
o:value("gecko")
|
||||
o.rmempty = true
|
||||
o:depends({type = "hysteria2", flag_obfs = true})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_obfs = true})
|
||||
o.rmempty = true
|
||||
o.placeholder = "salamander"
|
||||
|
||||
o = s:option(Value, "salamander", translate("Obfuscation Password"))
|
||||
o:depends({type = "hysteria2", flag_obfs = true})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", flag_obfs = true})
|
||||
o.password = true
|
||||
o.rmempty = true
|
||||
o:depends({type = "hysteria2", flag_obfs = true})
|
||||
local obfs = s.fields["obfs_type"].keylist
|
||||
if obfs and type(obfs) == "table" and #obfs > 0 then
|
||||
for _, v in ipairs(obfs) do
|
||||
if v and v ~= "" then
|
||||
o:depends({
|
||||
type = "v2ray",
|
||||
v2ray_protocol = "hysteria2",
|
||||
obfs_type = v,
|
||||
flag_obfs = true
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
o.placeholder = "cry_me_a_r1ver"
|
||||
|
||||
o = s:option(Flag, "flag_quicparam", translate("Hysterir QUIC parameters"))
|
||||
o:depends("type", "hysteria2")
|
||||
@@ -778,13 +748,13 @@ o:depends("type", "shadowtls")
|
||||
if is_finded("sslocal") then
|
||||
o:value("sslocal", translate("ShadowSocks-rust Version"))
|
||||
end
|
||||
if is_finded("xray") then
|
||||
if is_finded("xray") or is_finded("v2ray") then
|
||||
o:value("vmess", translate("Vmess Protocol"))
|
||||
end
|
||||
o.default = "sslocal"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "sslocal_password",translate("Shadowsocks Password"))
|
||||
o = s:option(Value, "sslocal_password",translate("Shadowsocks password"))
|
||||
o:depends({type = "shadowtls", chain_type = "sslocal"})
|
||||
o.rmempty = true
|
||||
|
||||
@@ -818,7 +788,7 @@ o:depends("type", "tuic")
|
||||
--Tuic IP
|
||||
o = s:option(Value, "tuic_ip", translate("TUIC Server IP Address"))
|
||||
o.rmempty = true
|
||||
o.datatype = "ipaddr"
|
||||
o.datatype = "ip4addr"
|
||||
o.default = ""
|
||||
o:depends("type", "tuic")
|
||||
|
||||
@@ -999,7 +969,7 @@ o = s:option(Value, "ws_heartbeatPeriod", translate("HeartbeatPeriod(second)"))
|
||||
o.datatype = "integer"
|
||||
o:depends("transport", "ws")
|
||||
|
||||
if is_finded("xray") then
|
||||
if is_finded("v2ray") then
|
||||
-- WS前置数据
|
||||
o = s:option(Value, "ws_ed", translate("Max Early Data"))
|
||||
o:depends("ws_ed_enable", true)
|
||||
@@ -1240,12 +1210,12 @@ o.default = "0"
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(DynamicList, "local_addresses", translate("Local addresses"))
|
||||
--o.datatype = "cidr"
|
||||
o.datatype = "cidr"
|
||||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(DynamicList, "reserved", translate("Reserved bytes(optional)"))
|
||||
o.description = translate("Supports decimal numbers separated by \",\" or Base64-encoded strings, with a maximum length of 3 bytes.")
|
||||
o.description = translate("Decimal numbers separated by \",\" or Base64-encoded strings.")
|
||||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||||
o.rmempty = true
|
||||
|
||||
@@ -1290,18 +1260,12 @@ o:depends("transport", "grpc")
|
||||
o = s:option(Flag, "enable_finalmask", translate("FinalMask"))
|
||||
o.rmempty = true
|
||||
o.default = "0"
|
||||
local protocols = s.fields["v2ray_protocol"].keylist
|
||||
if protocols and type(protocols) == "table" and #protocols > 0 then
|
||||
for _, proto in ipairs(protocols) do
|
||||
if not proto:find("^_") then
|
||||
if proto == "hysteria2" then
|
||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", hysteria2_realms = false})
|
||||
elseif proto ~= "socks" and proto ~= "http" then
|
||||
o:depends({type = "v2ray", v2ray_protocol = proto})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
o:depends({type = "v2ray", v2ray_protocol = "vless"})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "vmess"})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "trojan"})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||||
|
||||
o = s:option(TextValue, "finalmask", " ")
|
||||
o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "<br>" ..
|
||||
@@ -1398,18 +1362,7 @@ if is_finded("xray") then
|
||||
o = s:option(Flag, "enable_ech", translate("Enable ECH(optional)"))
|
||||
o.rmempty = true
|
||||
o.default = "0"
|
||||
local protocols = s.fields["v2ray_protocol"].keylist
|
||||
if protocols and type(protocols) == "table" and #protocols > 0 then
|
||||
for _, proto in ipairs(protocols) do
|
||||
if not proto:find("^_") then
|
||||
if proto == "hysteria2" then
|
||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", tls = true, hysteria2_realms = false})
|
||||
else
|
||||
o:depends({type = "v2ray", v2ray_protocol = proto, tls = true})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
o:depends({type = "v2ray", tls = true})
|
||||
|
||||
o = s:option(TextValue, "ech_config", translate("ECH Config"))
|
||||
o.description = translate(
|
||||
@@ -1692,41 +1645,100 @@ o.rmempty = true
|
||||
o.default = "0"
|
||||
o:depends("type", "ssr")
|
||||
o:depends("type", "ss")
|
||||
o:depends("type", "ss-rust")
|
||||
o:depends("type", "trojan")
|
||||
o:depends("type", "hysteria2")
|
||||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "xhttp"})
|
||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||||
|
||||
o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve"))
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("If the node address is a domain name, this DNS will be used for resolution.") .. "</li>" ..
|
||||
"<li>" .. string.format('<font style=\'color:red;\'>%s</font>', translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead.")) .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
o:value("", translate("Auto"))
|
||||
o:value("tcp", translate("TCP"))
|
||||
o:value("udp", translate("UDP"))
|
||||
o:value("https", translate("DoH"))
|
||||
o:depends("type", "v2ray")
|
||||
|
||||
o = s:option(Value, "domain_resolver_dns", translate("DNS"))
|
||||
o.datatype = "or(ipaddr,ipaddrport)"
|
||||
o:value("114.114.114.114")
|
||||
o:value("223.5.5.5:53")
|
||||
o.default = "114.114.114.114"
|
||||
o:depends("domain_resolver", "tcp")
|
||||
o:depends("domain_resolver", "udp")
|
||||
|
||||
o = s:option(Value, "domain_resolver_dns_https", translate("DNS"))
|
||||
o:value("https://120.53.53.53/dns-query", translate("DNSPod"))
|
||||
o:value("https://223.5.5.5/dns-query", translate("AliDNS"))
|
||||
o.default = "https://120.53.53.53/dns-query"
|
||||
o:depends("domain_resolver", "https")
|
||||
|
||||
o = s:option(ListValue, "domain_strategy", translate("Domain Strategy"))
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("If is domain name, The requested domain name will be resolved to IP before connect.") .. "</li>" ..
|
||||
"<li>" .. string.format('<font style=\'color:red;\'>%s</font>', translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead.")) .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
o.default = ""
|
||||
o:value("", translate("Auto"))
|
||||
o:value("UseIPv4v6", translate("Prefer IPv4"))
|
||||
o:value("UseIPv6v4", translate("Prefer IPv6"))
|
||||
o:value("UseIPv4", translate("IPv4 Only"))
|
||||
o:value("UseIPv6", translate("IPv6 Only"))
|
||||
o:depends("type", "v2ray")
|
||||
|
||||
local v2ray_protocols = s.fields["v2ray_protocol"]
|
||||
if #v2ray_protocols > 0 then
|
||||
for i, v in ipairs(v2ray_protocols) do
|
||||
if not v:find("^_") then
|
||||
s.fields["server"]:depends({ ["v2ray_protocol"] = v })
|
||||
s.fields["server_port"]:depends({ ["v2ray_protocol"] = v })
|
||||
s.fields["domain_resolver"]:depends({ ["v2ray_protocol"] = v })
|
||||
s.fields["domain_strategy"]:depends({ ["v2ray_protocol"] = v })
|
||||
|
||||
if v ~= "hysteria2" then
|
||||
s.fields["fast_open"]:depends({ ["v2ray_protocol"] = v })
|
||||
s.fields["mptcp"]:depends({ ["v2ray_protocol"] = v })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(Flag, "switch_enable", translate("Enable Auto Switch"))
|
||||
o.rmempty = false
|
||||
o.default = "1"
|
||||
|
||||
o = s:option(Value, "local_port", translate("Local Port"))
|
||||
o.datatype = "port"
|
||||
o.default = 1234
|
||||
o.rmempty = false
|
||||
|
||||
if is_finded("kcptun-client") then
|
||||
o = s:option(Flag, "kcp_enable", translate("KcpTun Enable"))
|
||||
o.rmempty = true
|
||||
o.default = "0"
|
||||
o:depends("type", "ssr")
|
||||
o:depends("type", "ss-rust")
|
||||
o:depends("type", "ss")
|
||||
|
||||
o = s:option(Value, "kcp_port", translate("KcpTun Port"))
|
||||
o.datatype = "portrange"
|
||||
o.default = 4000
|
||||
o:depends("type", "ssr")
|
||||
o:depends("type", "ss-rust")
|
||||
o:depends("type", "ss")
|
||||
|
||||
o = s:option(Value, "kcp_password", translate("KcpTun Password"))
|
||||
o.password = true
|
||||
o:depends("type", "ssr")
|
||||
o:depends("type", "ss-rust")
|
||||
o:depends("type", "ss")
|
||||
|
||||
o = s:option(Value, "kcp_param", translate("KcpTun Param"))
|
||||
o.default = "--nocomp"
|
||||
o:depends("type", "ssr")
|
||||
o:depends("type", "ss-rust")
|
||||
o:depends("type", "ss")
|
||||
end
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
local m, s, sec, o
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local URL = require "url"
|
||||
|
||||
-- 获取 LAN IP 地址
|
||||
function lanip()
|
||||
@@ -32,62 +31,69 @@ end
|
||||
|
||||
local lan_ip = lanip()
|
||||
local validation = require "luci.cbi.datatypes"
|
||||
local clash_nodes = {}
|
||||
local function is_finded(e)
|
||||
return luci.sys.exec(string.format('type -t -p "%s" -p "/usr/libexec/%s" 2>/dev/null', e, e)) ~= ""
|
||||
return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= ""
|
||||
end
|
||||
|
||||
local function clash_display_name(s)
|
||||
if s.type ~= "clash" or not s.clash_url or s.clash_url == "" then
|
||||
return nil
|
||||
end
|
||||
local ok, parsed = pcall(URL.parse, s.clash_url)
|
||||
if ok and parsed and parsed.host then
|
||||
return "[CLASH]:" .. parsed.host
|
||||
end
|
||||
return "[CLASH]"
|
||||
end
|
||||
|
||||
m = Map("shadowsocksr", translate("ShadowSocksR Plus+ Settings"), translate("<h3>Support SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/CLASH etc.</h3>"))
|
||||
m = Map("shadowsocksr", translate("ShadowSocksR Plus+ Settings"), translate("<h3>Support SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/TUN etc.</h3>"))
|
||||
m:section(SimpleSection).template = "shadowsocksr/status"
|
||||
|
||||
local server_table = {}
|
||||
local server_order = {}
|
||||
uci:foreach("shadowsocksr", "servers", function(s)
|
||||
if s.type == "clash" then
|
||||
clash_nodes[s[".name"]] = true
|
||||
end
|
||||
|
||||
if s.type ~= "tun" and s.alias then
|
||||
if s.alias then
|
||||
server_table[s[".name"]] = "[%s]:%s" % {string.upper(s.v2ray_protocol or s.type), s.alias}
|
||||
elseif s.type ~= "tun" and s.server and s.server_port then
|
||||
elseif s.server and s.server_port then
|
||||
server_table[s[".name"]] = "[%s]:%s:%s" % {string.upper(s.v2ray_protocol or s.type), s.server, s.server_port}
|
||||
elseif s.type ~= "tun" then
|
||||
local display_name = clash_display_name(s)
|
||||
if display_name then
|
||||
server_table[s[".name"]] = display_name
|
||||
end
|
||||
end
|
||||
if s.type ~= "tun" and server_table[s[".name"]] then
|
||||
table.insert(server_order, s[".name"])
|
||||
end
|
||||
end)
|
||||
|
||||
local key_table = {}
|
||||
for key, _ in pairs(server_table) do
|
||||
table.insert(key_table, key)
|
||||
end
|
||||
|
||||
table.sort(key_table)
|
||||
|
||||
-- [[ Global Setting ]]--
|
||||
s = m:section(TypedSection, "global")
|
||||
s.anonymous = true
|
||||
|
||||
o = s:option(ListValue, "global_server", translate("Main Server"))
|
||||
o:value("nil", translate("Disable"))
|
||||
for _, key in ipairs(server_order) do
|
||||
for _, key in pairs(key_table) do
|
||||
o:value(key, server_table[key])
|
||||
end
|
||||
o.default = "nil"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(DummyValue, "_clash_panel", translate("Clash Panel"))
|
||||
o.template = "shadowsocksr/clash_main_panel"
|
||||
o.clash_nodes = clash_nodes
|
||||
o = s:option(ListValue, "udp_relay_server", translate("Game Mode UDP Server"))
|
||||
o:value("", translate("Disable"))
|
||||
o:value("same", translate("Same as Global Server"))
|
||||
for _, key in pairs(key_table) do
|
||||
o:value(key, server_table[key])
|
||||
end
|
||||
|
||||
if uci:get_first("shadowsocksr", 'global', 'netflix_enable', '0') == '1' then
|
||||
o = s:option(ListValue, "netflix_server", translate("Netflix Node"))
|
||||
o:value("nil", translate("Disable"))
|
||||
o:value("same", translate("Same as Global Server"))
|
||||
for _, key in pairs(key_table) do
|
||||
o:value(key, server_table[key])
|
||||
end
|
||||
o.default = "nil"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Flag, "netflix_proxy", translate("External Proxy Mode"))
|
||||
o.rmempty = false
|
||||
o.description = translate("Forward Netflix Proxy through Main Proxy")
|
||||
o.default = "0"
|
||||
end
|
||||
|
||||
-- [[ Use nftables/iptables ]]--
|
||||
o = s:option(ListValue, "prefer_nft", translate("Prefer firewall tools"))
|
||||
o.default = "1"
|
||||
o:value("0", "Iptables")
|
||||
o:value("1", "Nftables")
|
||||
|
||||
o = s:option(ListValue, "threads", translate("Multi Threads Option"))
|
||||
o:value("0", translate("Auto Threads"))
|
||||
@@ -106,6 +112,7 @@ o = s:option(ListValue, "run_mode", translate("Running Mode"))
|
||||
o:value("gfw", translate("GFW List Mode"))
|
||||
o:value("router", translate("IP Route Mode"))
|
||||
o:value("all", translate("Global Mode"))
|
||||
o:value("oversea", translate("Oversea Mode"))
|
||||
o.default = gfw
|
||||
|
||||
o = s:option(ListValue, "dports", translate("Proxy Ports"))
|
||||
@@ -121,13 +128,21 @@ o = s:option(ListValue, "pdnsd_enable", translate("Resolve Dns Mode"))
|
||||
if is_finded("dns2tcp") then
|
||||
o:value("1", translate("Use DNS2TCP query"))
|
||||
end
|
||||
if is_finded("dns2socks") then
|
||||
o:value("2", translate("Use DNS2SOCKS query and cache"))
|
||||
end
|
||||
if is_finded("dns2socks-rust") then
|
||||
o:value("3", translate("Use DNS2SOCKS-RUST query and cache"))
|
||||
end
|
||||
if is_finded("mosdns") then
|
||||
o:value("4", translate("Use MosDNS query"))
|
||||
o:value("4", translate("Use MOSDNS query (Not Support Oversea Mode)"))
|
||||
end
|
||||
if is_finded("dnsproxy") then
|
||||
o:value("5", translate("Use DNSPROXY query and cache"))
|
||||
end
|
||||
if is_finded("chinadns-ng") then
|
||||
o:value("6", translate("Use ChinaDNS-NG query and cache"))
|
||||
end
|
||||
o:value("7", translate("Prefer module built-in DNS"))
|
||||
o:value("0", translate("Use Local DNS Service listen port 5335"))
|
||||
o.default = 1
|
||||
|
||||
@@ -143,8 +158,11 @@ o:value("4.2.2.2:53", translate("Level 3 Public DNS (4.2.2.2)"))
|
||||
o:value("4.2.2.3:53", translate("Level 3 Public DNS (4.2.2.3)"))
|
||||
o:value("4.2.2.4:53", translate("Level 3 Public DNS (4.2.2.4)"))
|
||||
o:value("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)"))
|
||||
o:value("114.114.114.114:53", translate("Oversea Mode DNS-1 (114.114.114.114)"))
|
||||
o:value("114.114.115.115:53", translate("Oversea Mode DNS-2 (114.114.115.115)"))
|
||||
o:depends("pdnsd_enable", "1")
|
||||
o:depends("pdnsd_enable", "7")
|
||||
o:depends("pdnsd_enable", "2")
|
||||
o:depends("pdnsd_enable", "3")
|
||||
o.description = translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)")
|
||||
o.datatype = "ip4addrport"
|
||||
o.default = "8.8.4.4:53"
|
||||
@@ -158,15 +176,61 @@ o:value("tcp://4.2.2.3:53,tcp://4.2.2.4:53", translate("Level 3 Public DNS-3 (4.
|
||||
o:value("tcp://1.1.1.1:53,tcp://1.0.0.1:53", translate("Cloudflare DNS"))
|
||||
o:depends("pdnsd_enable", "4")
|
||||
o.description = translate("Custom DNS Server format as tcp://IP:PORT or tls://DOMAIN:PORT (tcp://8.8.8.8 or tls://dns.google:853)")
|
||||
o.default = "tcp://8.8.4.4:53,tcp://8.8.8.8:53"
|
||||
|
||||
o = s:option(Flag, "filter_aaaa", translate("Disable IPv6 for Overseas FQDN"))
|
||||
o:depends("pdnsd_enable", "1")
|
||||
o = s:option(Flag, "mosdns_ipv6", translate("Disable IPv6 in MOSDNS query mode"))
|
||||
o:depends("pdnsd_enable", "4")
|
||||
o:depends("pdnsd_enable", "7")
|
||||
o.rmempty = false
|
||||
o.default = "1"
|
||||
|
||||
if is_finded("dnsproxy") then
|
||||
o = s:option(ListValue, "parse_method", translate("Select DNS parse Mode"))
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("When use DNS list file, please ensure list file exists and is formatted correctly.") .. "</li>" ..
|
||||
"<li>" .. translate("Tips: Dnsproxy DNS Parse List Path:") ..
|
||||
" <a href='http://" .. lan_ip .. "/cgi-bin/luci/admin/services/shadowsocksr/control' target='_blank'>" ..
|
||||
translate("Click here to view or manage the DNS list file") .. "</a>" .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
o:value("single_dns", translate("Set Single DNS"))
|
||||
o:value("parse_file", translate("Use DNS List File"))
|
||||
o:depends("pdnsd_enable", "5")
|
||||
o.rmempty = true
|
||||
o.default = "single_dns"
|
||||
|
||||
o = s:option(Value, "dnsproxy_tunnel_forward", translate("Anti-pollution DNS Server"))
|
||||
o:value("sdns://AgUAAAAAAAAABzguOC40LjQgsKKKE4EwvtIbNjGjagI2607EdKSVHowYZtyvD9iPrkkHOC44LjQuNAovZG5zLXF1ZXJ5", translate("Google DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAAACC2vD25TAYM7EnyCH8Xw1-0g5OccnTsGH9vQUUH0njRtAxkbnMudHduaWMudHcKL2Rucy1xdWVyeQ", translate("TWNIC-101 DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAADzE4NS4yMjIuMjIyLjIyMiAOp5Svj-oV-Fz-65-8H2VKHLKJ0egmfEgrdPeAQlUFFA8xODUuMjIyLjIyMi4yMjIKL2Rucy1xdWVyeQ", translate("dns.sb DNSCrypt SDNS"))
|
||||
o:value("sdns://AgMAAAAAAAAADTE0OS4xMTIuMTEyLjkgsBkgdEu7dsmrBT4B4Ht-BQ5HPSD3n3vqQ1-v5DydJC8SZG5zOS5xdWFkOS5uZXQ6NDQzCi9kbnMtcXVlcnk", translate("Quad9 DNSCrypt SDNS"))
|
||||
o:value("sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", translate("AdGuard DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAABzEuMC4wLjGgENk8mGSlIfMGXMOlIlCcKvq7AVgcrZxtjon911-ep0cg63Ul-I8NlFj4GplQGb_TTLiczclX57DvMV8Q-JdjgRgSZG5zLmNsb3VkZmxhcmUuY29tCi9kbnMtcXVlcnk", translate("Cloudflare DNSCrypt SDNS"))
|
||||
o:value("sdns://AgcAAAAAAAAADjEwNC4xNi4yNDkuMjQ5ABJjbG91ZGZsYXJlLWRucy5jb20KL2Rucy1xdWVyeQ", translate("cloudflare-dns.com DNSCrypt SDNS"))
|
||||
o:depends("parse_method", "single_dns")
|
||||
o.description = translate("Custom DNS Server (support: IP:Port or tls://IP:Port or https://IP/dns-query and other format).")
|
||||
|
||||
o = s:option(ListValue, "upstreams_logic_mode", translate("Defines the upstreams logic mode"))
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("Defines the upstreams logic mode, possible values: load_balance, parallel, fastest_addr (default: load_balance).") .. "</li>" ..
|
||||
"<li>" .. translate("When two or more DNS servers are deployed, enable this function.") .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
o:value("load_balance", translate("load_balance"))
|
||||
o:value("parallel", translate("parallel"))
|
||||
o:value("fastest_addr", translate("fastest_addr"))
|
||||
o:depends("parse_method", "parse_file")
|
||||
o.rmempty = true
|
||||
o.default = "load_balance"
|
||||
|
||||
o = s:option(Flag, "dnsproxy_ipv6", translate("Disable IPv6 query mode"))
|
||||
o.description = translate("When disabled, all AAAA requests are not resolved.")
|
||||
o:depends("parse_method", "single_dns")
|
||||
o:depends("parse_method", "parse_file")
|
||||
o.rmempty = false
|
||||
o.default = "1"
|
||||
end
|
||||
|
||||
if is_finded("chinadns-ng") then
|
||||
o = s:option(Value, "chinadns_ng_tunnel_forward", translate("Anti-pollution DNS Server"))
|
||||
o:value("8.8.4.4:53", translate("Google Public DNS (8.8.4.4)"))
|
||||
@@ -181,7 +245,6 @@ if is_finded("chinadns-ng") then
|
||||
o:value("4.2.2.4:53", translate("Level 3 Public DNS (4.2.2.4)"))
|
||||
o:value("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)"))
|
||||
o:depends("pdnsd_enable", "6")
|
||||
o.default = "8.8.4.4:53"
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)") .. "</li>" ..
|
||||
@@ -207,8 +270,11 @@ if is_finded("chinadns-ng") then
|
||||
o:value("101.226.4.6:53", translate("360 Security DNS (China Telecom) (101.226.4.6)"))
|
||||
o:value("123.125.81.6:53", translate("360 Security DNS (China Unicom) (123.125.81.6)"))
|
||||
o:value("1.2.4.8:53", translate("CNNIC SDNS (1.2.4.8)"))
|
||||
o:depends({pdnsd_enable = "1", run_mode = "router"})
|
||||
o:depends({pdnsd_enable = "2", run_mode = "router"})
|
||||
o:depends({pdnsd_enable = "3", run_mode = "router"})
|
||||
o:depends({pdnsd_enable = "5", run_mode = "router"})
|
||||
o:depends({pdnsd_enable = "6", run_mode = "router"})
|
||||
o.default = "wan"
|
||||
o.description = translate("Custom DNS Server format as IP:PORT (default: disabled)")
|
||||
o.validate = function(self, value, section)
|
||||
if (section and value) then
|
||||
@@ -227,7 +293,5 @@ if is_finded("chinadns-ng") then
|
||||
end
|
||||
end
|
||||
|
||||
m:append(Template("shadowsocksr/client_dns_defaults"))
|
||||
m:append(Template("shadowsocksr/status_bottom"))
|
||||
|
||||
m:section(SimpleSection).template = "shadowsocksr/status_bottom"
|
||||
return m
|
||||
@@ -1,30 +0,0 @@
|
||||
local m, s, o
|
||||
|
||||
m = SimpleForm(
|
||||
"component_update",
|
||||
translate("Component Update"),
|
||||
translate("Check installed component versions and upgrade them online from the upstream release page.")
|
||||
)
|
||||
m.reset = false
|
||||
m.submit = false
|
||||
|
||||
s = m:section(SimpleSection)
|
||||
|
||||
o = m:field(ListValue, "component_mirror", translate("Mirror URL"))
|
||||
o:value("direct", translate("GitHub Direct"))
|
||||
o:value("ghproxy", "mirror.ghproxy.com")
|
||||
o:value("ghproxy_cc", "ghproxy.cc")
|
||||
o:value("ghfast", "ghfast.top")
|
||||
o:value("jsdelivr", "cdn.jsdelivr.net")
|
||||
o.rmempty = false
|
||||
o.cfgvalue = function(self)
|
||||
return m.uci:get_first("shadowsocksr", "global", "component_mirror") or "direct"
|
||||
end
|
||||
o.write = function(self, section, value)
|
||||
m.uci:set("shadowsocksr", "@global[0]", "component_mirror", value)
|
||||
m.uci:commit("shadowsocksr")
|
||||
end
|
||||
|
||||
s.template = "shadowsocksr/component"
|
||||
|
||||
return m
|
||||
@@ -3,6 +3,10 @@ require "nixio.fs"
|
||||
require "luci.sys"
|
||||
local m, s, o
|
||||
|
||||
local function is_finded(e)
|
||||
return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= ""
|
||||
end
|
||||
|
||||
m = Map("shadowsocksr")
|
||||
|
||||
s = m:section(TypedSection, "access_control")
|
||||
@@ -125,6 +129,40 @@ o.remove = function(self, section, value)
|
||||
nixio.fs.writefile(denydomainconf, "")
|
||||
end
|
||||
|
||||
s:tab("netflix", translate("Netflix Domain List"))
|
||||
local netflixconf = "/etc/ssrplus/netflix.list"
|
||||
o = s:taboption("netflix", TextValue, "netflixconf")
|
||||
o.rows = 13
|
||||
o.wrap = "off"
|
||||
o.rmempty = true
|
||||
o.cfgvalue = function(self, section)
|
||||
return nixio.fs.readfile(netflixconf) or " "
|
||||
end
|
||||
o.write = function(self, section, value)
|
||||
nixio.fs.writefile(netflixconf, value:gsub("\r\n", "\n"))
|
||||
end
|
||||
o.remove = function(self, section, value)
|
||||
nixio.fs.writefile(netflixconf, "")
|
||||
end
|
||||
|
||||
if is_finded("dnsproxy") then
|
||||
s:tab("dnsproxy", translate("Dnsproxy Parse List"))
|
||||
local dnsproxyconf = "/etc/ssrplus/dnsproxy_dns.list"
|
||||
o = s:taboption("dnsproxy", TextValue, "dnsproxyconf", "", "<font style=color:red>" .. translate("Specifically for edit dnsproxy DNS parse files.") .. "</font>")
|
||||
o.rows = 13
|
||||
o.wrap = "off"
|
||||
o.rmempty = true
|
||||
o.cfgvalue = function(self, section)
|
||||
return nixio.fs.readfile(dnsproxyconf) or " "
|
||||
end
|
||||
o.write = function(self, section, value)
|
||||
nixio.fs.writefile(dnsproxyconf, value:gsub("\r\n", "\n"))
|
||||
end
|
||||
o.remove = function(self, section, value)
|
||||
nixio.fs.writefile(dnsproxyconf, "")
|
||||
end
|
||||
end
|
||||
|
||||
if luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0 then
|
||||
m.apply_on_parse = true
|
||||
function m.on_apply(self)
|
||||
@@ -132,6 +170,4 @@ if luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0 then
|
||||
end
|
||||
end
|
||||
|
||||
m:append(Template("shadowsocksr/control_layout"))
|
||||
|
||||
return m
|
||||
@@ -50,7 +50,6 @@ luci.http.setfilehandler(function(meta, chunk, eof)
|
||||
luci.sys.call("rm -rf " .. temp_dir)
|
||||
nixio.fs.remove(file_path)
|
||||
fd = nixio.open(file_path, "w")
|
||||
luci.sys.call("ls /tmp/sub_md5_* >/dev/null 2>&1 && rm -f /tmp/sub_md5_*")
|
||||
luci.sys.call("echo '' > /var/log/ssrplus.log")
|
||||
end
|
||||
|
||||
@@ -97,7 +96,6 @@ luci.http.setfilehandler(function(meta, chunk, eof)
|
||||
-- 清理临时文件
|
||||
luci.sys.call("rm -rf " .. temp_dir)
|
||||
nixio.fs.remove(file_path)
|
||||
luci.sys.call("ls /tmp/sub_md5_* >/dev/null 2>&1 && rm -f /tmp/sub_md5_*")
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
@@ -7,39 +7,68 @@ require "nixio.fs"
|
||||
local m, s, o
|
||||
local sid = arg[1]
|
||||
|
||||
-- 加密方式(SS/SSR用)
|
||||
local encrypt_methods = {
|
||||
"rc4-md5", "rc4-md5-6", "rc4", "table",
|
||||
"aes-128-cfb", "aes-192-cfb", "aes-256-cfb",
|
||||
"aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
|
||||
"bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
|
||||
"cast5-cfb", "des-cfb", "idea-cfb", "rc2-cfb", "seed-cfb",
|
||||
"salsa20", "chacha20", "chacha20-ietf"
|
||||
"rc4-md5",
|
||||
"rc4-md5-6",
|
||||
"rc4",
|
||||
"table",
|
||||
"aes-128-cfb",
|
||||
"aes-192-cfb",
|
||||
"aes-256-cfb",
|
||||
"aes-128-ctr",
|
||||
"aes-192-ctr",
|
||||
"aes-256-ctr",
|
||||
"bf-cfb",
|
||||
"camellia-128-cfb",
|
||||
"camellia-192-cfb",
|
||||
"camellia-256-cfb",
|
||||
"cast5-cfb",
|
||||
"des-cfb",
|
||||
"idea-cfb",
|
||||
"rc2-cfb",
|
||||
"seed-cfb",
|
||||
"salsa20",
|
||||
"chacha20",
|
||||
"chacha20-ietf"
|
||||
}
|
||||
|
||||
local encrypt_methods_ss = {
|
||||
"aes-128-gcm", "aes-192-gcm", "aes-256-gcm",
|
||||
"chacha20-ietf-poly1305", "xchacha20-ietf-poly1305",
|
||||
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
-- Shadowsocks 加密方法(Xray 版)
|
||||
local v_ss_encrypt_method_list = {
|
||||
"aes-128-cfb", "aes-256-cfb", "aes-128-gcm", "aes-256-gcm",
|
||||
"chacha20", "chacha20-ietf", "chacha20-poly1305", "chacha20-ietf-poly1305"
|
||||
}
|
||||
|
||||
-- 伪装类型(用于 mKCP/QUIC)
|
||||
local header_type_list = {
|
||||
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard"
|
||||
-- aead
|
||||
"aes-128-gcm",
|
||||
"aes-192-gcm",
|
||||
"aes-256-gcm",
|
||||
"chacha20-ietf-poly1305",
|
||||
"xchacha20-ietf-poly1305",
|
||||
-- aead 2022
|
||||
"2022-blake3-aes-128-gcm",
|
||||
"2022-blake3-aes-256-gcm",
|
||||
"2022-blake3-chacha20-poly1305"
|
||||
--[[ stream
|
||||
"table",
|
||||
"rc4",
|
||||
"rc4-md5",
|
||||
"aes-128-cfb",
|
||||
"aes-192-cfb",
|
||||
"aes-256-cfb",
|
||||
"aes-128-ctr",
|
||||
"aes-192-ctr",
|
||||
"aes-256-ctr",
|
||||
"bf-cfb",
|
||||
"camellia-128-cfb",
|
||||
"camellia-192-cfb",
|
||||
"camellia-256-cfb",
|
||||
"salsa20",
|
||||
"chacha20",
|
||||
"chacha20-ietf" ]]
|
||||
}
|
||||
|
||||
local protocol = {"origin"}
|
||||
|
||||
obfs = {"plain", "http_simple", "http_post"}
|
||||
|
||||
m = Map("shadowsocksr", translate("Edit ShadowSocksR Server"))
|
||||
m.redirect = luci.dispatcher.build_url("admin/services/shadowsocksr/server")
|
||||
|
||||
m.redirect = luci.dispatcher.build_url("admin/services/shadowsocksr/server")
|
||||
if m.uci:get("shadowsocksr", sid) ~= "server_config" then
|
||||
luci.http.redirect(m.redirect)
|
||||
return
|
||||
@@ -50,26 +79,18 @@ s = m:section(NamedSection, sid, "server_config")
|
||||
s.anonymous = true
|
||||
s.addremove = false
|
||||
|
||||
-- ========== 基本设置 ==========
|
||||
o = s:option(Flag, "enable", translate("Enable"))
|
||||
o.default = 1
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, "type", translate("Server Type"))
|
||||
o:value("socks5", translate("Socks5"))
|
||||
if nixio.fs.access("/usr/bin/mihomo") or nixio.fs.access("/usr/libexec/mihomo") or nixio.fs.access("/usr/bin/ssserver") or nixio.fs.access("/usr/libexec/ssserver") then
|
||||
if nixio.fs.access("/usr/bin/ssserver") or nixio.fs.access("/usr/bin/ss-server") then
|
||||
o:value("ss", translate("ShadowSocks"))
|
||||
end
|
||||
if nixio.fs.access("/usr/bin/ssr-server") then
|
||||
o:value("ssr", translate("ShadowsocksR"))
|
||||
end
|
||||
-- Xray 协议支持
|
||||
if nixio.fs.access("/usr/bin/xray") or nixio.fs.access("/usr/libexec/xray") then
|
||||
o:value("vmess", "VMess (Xray)")
|
||||
o:value("vless", "VLESS (Xray)")
|
||||
o:value("trojan", "Trojan (Xray)")
|
||||
o:value("shadowsocks", "Shadowsocks (Xray)")
|
||||
end
|
||||
o.default = "socks5"
|
||||
|
||||
o = s:option(Value, "server_port", translate("Server Port"))
|
||||
@@ -93,30 +114,32 @@ o:depends("type", "socks5")
|
||||
o = s:option(Value, "password", translate("Password"))
|
||||
o.password = true
|
||||
o.rmempty = false
|
||||
o:depends("type", "socks5")
|
||||
o:depends("type", "ss")
|
||||
o:depends("type", "ssr")
|
||||
o:depends("type", "trojan")
|
||||
o:depends("type", "shadowsocks")
|
||||
|
||||
-- ========== SS/SSR 协议特有 ==========
|
||||
o = s:option(ListValue, "encrypt_method", translate("Encrypt Method"))
|
||||
for _, v in ipairs(encrypt_methods) do o:value(v) end
|
||||
for _, v in ipairs(encrypt_methods) do
|
||||
o:value(v)
|
||||
end
|
||||
o.rmempty = false
|
||||
o:depends("type", "ssr")
|
||||
|
||||
o = s:option(ListValue, "encrypt_method_ss", translate("Encrypt Method"))
|
||||
for _, v in ipairs(encrypt_methods_ss) do o:value(v) end
|
||||
for _, v in ipairs(encrypt_methods_ss) do
|
||||
o:value(v)
|
||||
end
|
||||
o.rmempty = false
|
||||
o:depends("type", "ss")
|
||||
|
||||
o = s:option(ListValue, "protocol", translate("Protocol"))
|
||||
for _, v in ipairs(protocol) do o:value(v) end
|
||||
for _, v in ipairs(protocol) do
|
||||
o:value(v)
|
||||
end
|
||||
o.rmempty = false
|
||||
o:depends("type", "ssr")
|
||||
|
||||
o = s:option(ListValue, "obfs", translate("Obfs"))
|
||||
for _, v in ipairs(obfs) do o:value(v) end
|
||||
for _, v in ipairs(obfs) do
|
||||
o:value(v)
|
||||
end
|
||||
o.rmempty = false
|
||||
o:depends("type", "ssr")
|
||||
|
||||
@@ -128,269 +151,4 @@ o.rmempty = false
|
||||
o:depends("type", "ss")
|
||||
o:depends("type", "ssr")
|
||||
|
||||
-- ========== Xray 协议通用字段 ==========
|
||||
-- 用户备注
|
||||
o = s:option(Value, "remarks", translate("Remarks"))
|
||||
o.default = translate("Remarks")
|
||||
o.rmempty = true
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
o:depends("type", "trojan")
|
||||
o:depends("type", "shadowsocks")
|
||||
|
||||
-- 用户等级
|
||||
o = s:option(Value, "level", translate("User Level"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 1
|
||||
o.rmempty = true
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
o:depends("type", "shadowsocks")
|
||||
o:depends("type", "trojan")
|
||||
|
||||
-- ========== Xray 协议认证字段 ==========
|
||||
-- UUID (用于 vmess/vless)
|
||||
o = s:option(Value, "uuid", translate("UUID"))
|
||||
o.description = translate("Required for VMess/VLESS. Generate with: uuidgen")
|
||||
o.rmempty = true
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
|
||||
-- Trojan 密码
|
||||
o = s:option(Value, "trojan_password", translate("Trojan Password"))
|
||||
o.password = true
|
||||
o.rmempty = true
|
||||
o:depends("type", "trojan")
|
||||
|
||||
-- Shadowsocks 密码和加密(Xray 内置)
|
||||
o = s:option(Value, "ss_password", translate("Shadowsocks Password"))
|
||||
o.password = true
|
||||
o.rmempty = true
|
||||
o:depends("type", "shadowsocks")
|
||||
|
||||
o = s:option(ListValue, "ss_method", translate("Encrypt Method"))
|
||||
for _, v in ipairs(v_ss_encrypt_method_list) do o:value(v) end
|
||||
o.default = "chacha20-ietf-poly1305"
|
||||
o.rmempty = true
|
||||
o:depends("type", "shadowsocks")
|
||||
|
||||
o = s:option(ListValue, "ss_network", translate("Transport"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("udp", "UDP")
|
||||
o:value("tcp,udp", "TCP,UDP")
|
||||
o.default = "tcp,udp"
|
||||
o.rmempty = true
|
||||
o:depends("type", "shadowsocks")
|
||||
|
||||
-- VMess alterId
|
||||
o = s:option(Value, "alter_id", translate("Alter ID"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 0
|
||||
o.rmempty = true
|
||||
o:depends("type", "vmess")
|
||||
|
||||
-- VLESS decryption
|
||||
o = s:option(Value, "decryption", translate("Decryption"))
|
||||
o.default = "none"
|
||||
o.rmempty = true
|
||||
o:depends("type", "vless")
|
||||
|
||||
-- ========== TLS / XTLS 设置 ==========
|
||||
o = s:option(Flag, "tls", translate("TLS"))
|
||||
o.default = 0
|
||||
o.rmempty = true
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
o:depends("type", "trojan")
|
||||
o:depends("type", "shadowsocks")
|
||||
|
||||
o = s:option(Flag, "xtls", translate("XTLS"))
|
||||
o.default = 0
|
||||
o.rmempty = true
|
||||
o:depends({ type = "vless", tls = "1" })
|
||||
|
||||
o = s:option(ListValue, "flow", translate("Flow"))
|
||||
o:value("xtls-rprx-origin", "xtls-rprx-origin")
|
||||
o:value("xtls-rprx-origin-udp443", "xtls-rprx-origin-udp443")
|
||||
o:value("xtls-rprx-direct", "xtls-rprx-direct")
|
||||
o:value("xtls-rprx-direct-udp443", "xtls-rprx-direct-udp443")
|
||||
o:value("xtls-rprx-splice", "xtls-rprx-splice")
|
||||
o:value("xtls-rprx-splice-udp443", "xtls-rprx-splice-udp443")
|
||||
o.default = "xtls-rprx-direct"
|
||||
o.rmempty = true
|
||||
o:depends("xtls", "1")
|
||||
|
||||
o = s:option(Value, "tls_serverName", translate("Server Name (SNI)"))
|
||||
o.rmempty = true
|
||||
o:depends("tls", "1")
|
||||
|
||||
o = s:option(Value, "tls_certificateFile", translate("Certificate File Path"))
|
||||
o.description = translate("e.g.: /etc/ssl/fullchain.pem")
|
||||
o.rmempty = true
|
||||
o:depends("tls", "1")
|
||||
|
||||
o = s:option(Value, "tls_keyFile", translate("Private Key File Path"))
|
||||
o.description = translate("e.g.: /etc/ssl/private.key")
|
||||
o.rmempty = true
|
||||
o:depends("tls", "1")
|
||||
|
||||
-- ========== 传输层设置 (Transport) ==========
|
||||
o = s:option(ListValue, "transport", translate("Transport Protocol"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("mkcp", "mKCP")
|
||||
o:value("ws", "WebSocket")
|
||||
o:value("h2", "HTTP/2")
|
||||
o:value("ds", "DomainSocket")
|
||||
o:value("quic", "QUIC")
|
||||
o.default = "tcp"
|
||||
o.rmempty = true
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
o:depends("type", "trojan")
|
||||
|
||||
-- ----- WebSocket 设置 -----
|
||||
o = s:option(Value, "ws_host", translate("WebSocket Host"))
|
||||
o.rmempty = true
|
||||
o:depends("transport", "ws")
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
o:depends("type", "trojan")
|
||||
|
||||
o = s:option(Value, "ws_path", translate("WebSocket Path"))
|
||||
o.default = "/"
|
||||
o.rmempty = true
|
||||
o:depends("transport", "ws")
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
o:depends("type", "trojan")
|
||||
|
||||
-- ----- HTTP/2 设置 -----
|
||||
o = s:option(Value, "h2_host", translate("HTTP/2 Host"))
|
||||
o.rmempty = true
|
||||
o:depends("transport", "h2")
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
|
||||
o = s:option(Value, "h2_path", translate("HTTP/2 Path"))
|
||||
o.default = "/"
|
||||
o.rmempty = true
|
||||
o:depends("transport", "h2")
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
|
||||
-- ----- TCP 伪装设置 -----
|
||||
o = s:option(ListValue, "tcp_guise", translate("TCP Camouflage Type"))
|
||||
o:value("none", "none")
|
||||
o:value("http", "http")
|
||||
o.default = "none"
|
||||
o.rmempty = true
|
||||
o:depends("transport", "tcp")
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
|
||||
o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
|
||||
o.rmempty = true
|
||||
o:depends("tcp_guise", "http")
|
||||
|
||||
o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
|
||||
o.rmempty = true
|
||||
o:depends("tcp_guise", "http")
|
||||
|
||||
-- ----- mKCP 设置 -----
|
||||
o = s:option(ListValue, "mkcp_guise", translate("mKCP Camouflage Type"))
|
||||
for _, v in ipairs(header_type_list) do o:value(v) end
|
||||
o.default = "none"
|
||||
o.rmempty = true
|
||||
o:depends("transport", "mkcp")
|
||||
|
||||
o = s:option(Value, "mkcp_mtu", translate("KCP MTU"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 1350
|
||||
o.rmempty = true
|
||||
o:depends("transport", "mkcp")
|
||||
|
||||
o = s:option(Value, "mkcp_tti", translate("KCP TTI"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 20
|
||||
o.rmempty = true
|
||||
o:depends("transport", "mkcp")
|
||||
|
||||
o = s:option(Value, "mkcp_uplinkCapacity", translate("KCP Uplink Capacity"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 5
|
||||
o.rmempty = true
|
||||
o:depends("transport", "mkcp")
|
||||
|
||||
o = s:option(Value, "mkcp_downlinkCapacity", translate("KCP Downlink Capacity"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 20
|
||||
o.rmempty = true
|
||||
o:depends("transport", "mkcp")
|
||||
|
||||
o = s:option(Flag, "mkcp_congestion", translate("KCP Congestion Control"))
|
||||
o.default = 0
|
||||
o.rmempty = true
|
||||
o:depends("transport", "mkcp")
|
||||
|
||||
o = s:option(Value, "mkcp_readBufferSize", translate("KCP Read Buffer Size"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 1
|
||||
o.rmempty = true
|
||||
o:depends("transport", "mkcp")
|
||||
|
||||
o = s:option(Value, "mkcp_writeBufferSize", translate("KCP Write Buffer Size"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 1
|
||||
o.rmempty = true
|
||||
o:depends("transport", "mkcp")
|
||||
|
||||
o = s:option(Value, "mkcp_seed", translate("KCP Seed"))
|
||||
o.datatype = "uinteger"
|
||||
o.rmempty = true
|
||||
o:depends("transport", "mkcp")
|
||||
|
||||
-- ----- DomainSocket 设置 -----
|
||||
o = s:option(Value, "ds_path", translate("DomainSocket Path"))
|
||||
o.description = translate("A legal file path. This file must not exist before running.")
|
||||
o.rmempty = true
|
||||
o:depends("transport", "ds")
|
||||
|
||||
-- ----- QUIC 设置 -----
|
||||
o = s:option(ListValue, "quic_security", translate("QUIC Security"))
|
||||
o:value("none", "none")
|
||||
o:value("aes-128-gcm", "aes-128-gcm")
|
||||
o:value("chacha20-poly1305", "chacha20-poly1305")
|
||||
o.default = "none"
|
||||
o.rmempty = true
|
||||
o:depends("transport", "quic")
|
||||
|
||||
o = s:option(Value, "quic_key", translate("QUIC Key"))
|
||||
o.rmempty = true
|
||||
o:depends("transport", "quic")
|
||||
|
||||
o = s:option(ListValue, "quic_guise", translate("QUIC Camouflage Type"))
|
||||
for _, v in ipairs(header_type_list) do o:value(v) end
|
||||
o.default = "none"
|
||||
o.rmempty = true
|
||||
o:depends("transport", "quic")
|
||||
|
||||
-- ========== 访问控制 ==========
|
||||
o = s:option(Flag, "bind_local", translate("Bind Local Only"))
|
||||
o.description = translate("When selected, it can only be accessed locally. Recommended when using reverse proxies.")
|
||||
o.default = 0
|
||||
o.rmempty = true
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
o:depends("type", "trojan")
|
||||
o:depends("type", "shadowsocks")
|
||||
|
||||
o = s:option(Flag, "accept_lan", translate("Accept LAN Access"))
|
||||
o.description = translate("When selected, it can be accessed from LAN. This may not be safe!")
|
||||
o.default = 0
|
||||
o.rmempty = true
|
||||
o:depends("type", "vmess")
|
||||
o:depends("type", "vless")
|
||||
o:depends("type", "trojan")
|
||||
o:depends("type", "shadowsocks")
|
||||
|
||||
return m
|
||||
return m
|
||||
@@ -105,13 +105,8 @@ end
|
||||
o.rmempty = false
|
||||
|
||||
o = sec:option(DummyValue, "type", translate("Server Type"))
|
||||
function o.cfgvalue(self, section)
|
||||
local val = Value.cfgvalue(self, section) or "ss"
|
||||
if val == "vmess" then return "VMess (Xray)"
|
||||
elseif val == "vless" then return "VLESS (Xray)"
|
||||
elseif val == "trojan" then return "Trojan (Xray)"
|
||||
elseif val == "shadowsocks" then return "Shadowsocks (Xray)"
|
||||
else return val end
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or "ss"
|
||||
end
|
||||
|
||||
o = sec:option(DummyValue, "server_port", translate("Server Port"))
|
||||
@@ -140,4 +135,4 @@ function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or "-"
|
||||
end
|
||||
|
||||
return m
|
||||
return m
|
||||
@@ -1,35 +1,36 @@
|
||||
-- Licensed to the public under the GNU General Public License v3.
|
||||
require "luci.http"
|
||||
require "luci.sys"
|
||||
require "luci.util"
|
||||
require "nixio.fs"
|
||||
require "luci.dispatcher"
|
||||
require "luci.model.uci"
|
||||
local nixio = require "nixio"
|
||||
local json = require "luci.jsonc"
|
||||
local cbi = require "luci.cbi"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local URL = require "url"
|
||||
|
||||
local m, s, o, node
|
||||
local server_count = 0
|
||||
local server_cache = {}
|
||||
local server_sections = {}
|
||||
local detect_cache = {}
|
||||
local CLASH_YAML_DIR = "/etc/ssrplus/clash"
|
||||
local CLASH_YAML_HELPER = "/usr/share/shadowsocksr/clash_yaml.lua"
|
||||
local DEFAULT_SERVER_PAGE_SIZE = 50
|
||||
local SERVER_PAGE_SIZE_OPTIONS = {25, 50, 100, 200, 0}
|
||||
|
||||
-- 确保正确判断程序是否存在
|
||||
local function is_finded(e)
|
||||
return luci.sys.exec(string.format('type -t -p "%s" -p "/usr/libexec/%s" 2>/dev/null', e, e)) ~= ""
|
||||
return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= ""
|
||||
end
|
||||
|
||||
local function is_js_luci()
|
||||
return luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0
|
||||
end
|
||||
|
||||
-- 保存并应用行为
|
||||
local function url(...)
|
||||
local url = string.format("admin/services/%s", "shadowsocksr")
|
||||
local args = { ... }
|
||||
for i, v in ipairs(args) do
|
||||
if v and v ~= "" then
|
||||
url = url .. "/" .. v
|
||||
end
|
||||
end
|
||||
return require "luci.dispatcher".build_url(url)
|
||||
end
|
||||
|
||||
-- 默认的保存并应用行为
|
||||
local function apply_redirect(m)
|
||||
local tmp_uci_file = "/etc/config/" .. "shadowsocksr" .. "_redirect"
|
||||
if m.redirect and m.redirect ~= "" then
|
||||
@@ -64,341 +65,50 @@ local function set_apply_on_parse(map)
|
||||
local old = map.on_after_save
|
||||
map.on_after_save = function(self)
|
||||
if old then old(self) end
|
||||
-- map:set("@global[0]", "timestamp", os.time())
|
||||
map:set("@global[0]", "timestamp", os.time())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function trim(text)
|
||||
if not text or text == "" then
|
||||
return ""
|
||||
end
|
||||
return (text:gsub("^%s*(.-)%s*$", "%1"))
|
||||
local has_ss_rust = is_finded("sslocal") or is_finded("ssserver")
|
||||
local has_ss_libev = is_finded("ss-redir") or is_finded("ss-local")
|
||||
local has_trojan = is_finded("trojan")
|
||||
local has_xray = is_finded("xray")
|
||||
local has_hysteria2 = is_finded("hysteria")
|
||||
|
||||
local ss_type_list = {}
|
||||
local tj_type_list = {}
|
||||
local hy2_type_list = {}
|
||||
|
||||
if has_hysteria2 then
|
||||
table.insert(hy2_type_list, { id = "hysteria2", name = translate("Hysteria2") })
|
||||
end
|
||||
if has_xray then
|
||||
table.insert(hy2_type_list, { id = "v2ray", name = translate("Xray (Hysteria2)") })
|
||||
end
|
||||
|
||||
local function parse_nonnegative_int(value)
|
||||
local number = tonumber(value)
|
||||
if not number then
|
||||
return nil
|
||||
end
|
||||
number = math.floor(number)
|
||||
if number < 0 then
|
||||
return nil
|
||||
end
|
||||
return number
|
||||
if has_trojan then
|
||||
table.insert(tj_type_list, { id = "trojan", name = translate("Trojan") })
|
||||
end
|
||||
if has_xray then
|
||||
table.insert(tj_type_list, { id = "v2ray", name = translate("Xray (Trojan)") })
|
||||
end
|
||||
|
||||
local function in_list(list, target)
|
||||
for _, item in ipairs(list) do
|
||||
if item == target then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
if has_ss_rust then
|
||||
table.insert(ss_type_list, { id = "ss-rust", name = translate("ShadowSocks-rust Version") })
|
||||
end
|
||||
|
||||
local function upload_alias(filename)
|
||||
local stem = tostring(filename or ""):gsub("\\", "/"):match("([^/]+)$") or "custom"
|
||||
stem = stem:gsub("%.%w+$", "")
|
||||
stem = trim(stem):gsub("[%c\r\n]+", " "):gsub("%s+", " ")
|
||||
if stem == "" then
|
||||
stem = "custom"
|
||||
end
|
||||
return "Clash_" .. stem
|
||||
if has_ss_libev then
|
||||
table.insert(ss_type_list, { id = "ss-libev", name = translate("ShadowSocks-libev Version") })
|
||||
end
|
||||
|
||||
local function hash_file(path)
|
||||
local cmd = "md5sum " .. luci.util.shellquote(path) .. " 2>/dev/null | awk '{print $1}'"
|
||||
return trim(luci.sys.exec(cmd))
|
||||
if has_xray then
|
||||
table.insert(ss_type_list, { id = "v2ray", name = translate("Xray (ShadowSocks)") })
|
||||
end
|
||||
|
||||
local function preprocess_clash_yaml(input_path, output_path)
|
||||
local cmd = string.format(
|
||||
"/usr/bin/lua %s prepare %s %s >/dev/null 2>&1",
|
||||
luci.util.shellquote(CLASH_YAML_HELPER),
|
||||
luci.util.shellquote(input_path),
|
||||
luci.util.shellquote(output_path)
|
||||
)
|
||||
return luci.sys.call(cmd) == 0
|
||||
end
|
||||
|
||||
local function clash_path_in_use(path, exclude_sid)
|
||||
local in_use = false
|
||||
|
||||
uci:foreach("shadowsocksr", "servers", function(section)
|
||||
if section[".name"] ~= exclude_sid and section.clash_path == path then
|
||||
in_use = true
|
||||
return false
|
||||
end
|
||||
end)
|
||||
|
||||
return in_use
|
||||
end
|
||||
|
||||
local function cleanup_old_clash_path(old_path, new_path, sid)
|
||||
if not old_path or old_path == "" or old_path == new_path then
|
||||
return
|
||||
end
|
||||
if old_path:sub(1, #CLASH_YAML_DIR + 1) ~= CLASH_YAML_DIR .. "/" then
|
||||
return
|
||||
end
|
||||
if clash_path_in_use(old_path, sid) then
|
||||
return
|
||||
end
|
||||
nixio.fs.remove(old_path)
|
||||
end
|
||||
|
||||
local function find_uploaded_clash_section(upload_name, final_path)
|
||||
local sid
|
||||
|
||||
uci:foreach("shadowsocksr", "servers", function(section)
|
||||
if section.type ~= "clash" or section.yaml_upload ~= "1" then
|
||||
return
|
||||
end
|
||||
if upload_name ~= "" and section.yaml_upload_name == upload_name then
|
||||
sid = section[".name"]
|
||||
return false
|
||||
end
|
||||
if final_path ~= "" and section.clash_path == final_path then
|
||||
sid = section[".name"]
|
||||
return false
|
||||
end
|
||||
end)
|
||||
|
||||
return sid
|
||||
end
|
||||
|
||||
local function save_uploaded_clash_node(upload_name, final_path)
|
||||
local sid = find_uploaded_clash_section(upload_name, final_path)
|
||||
local old_path
|
||||
local alias
|
||||
|
||||
if not sid then
|
||||
sid = uci:add("shadowsocksr", "servers")
|
||||
end
|
||||
if not sid then
|
||||
return nil
|
||||
end
|
||||
|
||||
old_path = uci:get("shadowsocksr", sid, "clash_path")
|
||||
alias = uci:get("shadowsocksr", sid, "alias")
|
||||
if not alias or alias == "" then
|
||||
alias = upload_alias(upload_name)
|
||||
end
|
||||
|
||||
uci:set("shadowsocksr", sid, "type", "clash")
|
||||
uci:set("shadowsocksr", sid, "alias", alias)
|
||||
uci:set("shadowsocksr", sid, "server", "127.0.0.1")
|
||||
uci:set("shadowsocksr", sid, "server_port", "0")
|
||||
uci:delete("shadowsocksr", sid, "clash_url")
|
||||
uci:set("shadowsocksr", sid, "clash_path", final_path)
|
||||
uci:set("shadowsocksr", sid, "clash_user_agent", uci:get("shadowsocksr", sid, "clash_user_agent") or "clash")
|
||||
if not uci:get("shadowsocksr", sid, "switch_enable") then
|
||||
uci:set("shadowsocksr", sid, "switch_enable", uci:get_first("shadowsocksr", "server_subscribe", "switch", "1") or "1")
|
||||
end
|
||||
uci:set("shadowsocksr", sid, "yaml_upload", "1")
|
||||
uci:set("shadowsocksr", sid, "yaml_upload_name", upload_name)
|
||||
uci:save("shadowsocksr")
|
||||
uci:commit("shadowsocksr")
|
||||
|
||||
cleanup_old_clash_path(old_path, final_path, sid)
|
||||
luci.sys.call(string.format("/etc/init.d/shadowsocksr clash_cache %s >/dev/null 2>&1 &", luci.util.shellquote(sid)))
|
||||
return sid, alias
|
||||
end
|
||||
|
||||
local has_mihomo = is_finded("mihomo")
|
||||
local upload_fd
|
||||
local upload_tmp_path
|
||||
local upload_filename
|
||||
local upload_message
|
||||
local upload_errmessage
|
||||
|
||||
if has_mihomo then
|
||||
luci.http.setfilehandler(function(meta, chunk, eof)
|
||||
if not meta or meta.name ~= "clash_yaml_file" then
|
||||
return
|
||||
end
|
||||
|
||||
if not upload_fd then
|
||||
if not meta.file or meta.file == "" then
|
||||
return
|
||||
end
|
||||
upload_filename = tostring(meta.file):gsub("[\r\n]", "")
|
||||
upload_tmp_path = string.format("/tmp/ssrplus-clash-upload-%d-%d.yaml", nixio.getpid(), os.time())
|
||||
upload_fd = nixio.open(upload_tmp_path, "w")
|
||||
if not upload_fd then
|
||||
upload_errmessage = translate("Failed to create temporary YAML upload file.")
|
||||
upload_tmp_path = nil
|
||||
upload_filename = nil
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if chunk and upload_fd then
|
||||
upload_fd:write(chunk)
|
||||
end
|
||||
|
||||
if eof and upload_fd then
|
||||
upload_fd:close()
|
||||
upload_fd = nil
|
||||
end
|
||||
end)
|
||||
|
||||
if luci.http.formvalue("upload_clash_yaml") then
|
||||
if not upload_tmp_path or not upload_filename or not nixio.fs.access(upload_tmp_path) then
|
||||
upload_errmessage = upload_errmessage or translate("No custom YAML file was selected.")
|
||||
else
|
||||
local hash
|
||||
local final_path
|
||||
local tmp_output = string.format("%s/.upload-%d-%d.yaml", CLASH_YAML_DIR, nixio.getpid(), os.time())
|
||||
local sid
|
||||
local alias
|
||||
|
||||
nixio.fs.mkdirr(CLASH_YAML_DIR)
|
||||
nixio.fs.remove(tmp_output)
|
||||
if preprocess_clash_yaml(upload_tmp_path, tmp_output) then
|
||||
hash = hash_file(tmp_output)
|
||||
if hash == "" then
|
||||
nixio.fs.remove(tmp_output)
|
||||
upload_errmessage = translate("Uploaded YAML validation or preprocessing failed.")
|
||||
else
|
||||
final_path = string.format("%s/%s.yaml", CLASH_YAML_DIR, hash)
|
||||
luci.sys.call(string.format("mv -f %s %s", luci.util.shellquote(tmp_output), luci.util.shellquote(final_path)))
|
||||
sid, alias = save_uploaded_clash_node(upload_filename, final_path)
|
||||
if sid then
|
||||
upload_message = string.format(translate("Custom YAML imported successfully: %s"), alias or sid)
|
||||
else
|
||||
upload_errmessage = translate("Uploaded YAML validation or preprocessing failed.")
|
||||
end
|
||||
end
|
||||
else
|
||||
nixio.fs.remove(tmp_output)
|
||||
upload_errmessage = translate("Uploaded YAML validation or preprocessing failed.")
|
||||
end
|
||||
end
|
||||
|
||||
if upload_tmp_path then
|
||||
nixio.fs.remove(upload_tmp_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function preserve_when_hidden(opt, controller, enabled_value)
|
||||
local original_parse = opt.parse
|
||||
|
||||
opt.parse = function(self, section, novld)
|
||||
local current = self.map:get(section, controller)
|
||||
if current == nil then
|
||||
current = self.map:formvalue("cbid." .. self.map.config .. "." .. section .. "." .. controller)
|
||||
end
|
||||
if tostring(current or "") ~= tostring(enabled_value) then
|
||||
return
|
||||
end
|
||||
return original_parse(self, section, novld)
|
||||
end
|
||||
end
|
||||
|
||||
local function migrate_legacy_subscribe_urls()
|
||||
local subscribe_sid = uci:get_first("shadowsocksr", "server_subscribe")
|
||||
if not subscribe_sid then
|
||||
return
|
||||
end
|
||||
|
||||
local legacy_urls = uci:get_list("shadowsocksr", subscribe_sid, "subscribe_url") or {}
|
||||
if #legacy_urls == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local has_items = false
|
||||
uci:foreach("shadowsocksr", "server_subscribe_item", function()
|
||||
has_items = true
|
||||
return false
|
||||
end)
|
||||
if has_items then
|
||||
return
|
||||
end
|
||||
|
||||
for index, url in ipairs(legacy_urls) do
|
||||
local trimmed = trim(url)
|
||||
if trimmed ~= "" then
|
||||
local sid_output = luci.sys.exec("uci add shadowsocksr server_subscribe_item")
|
||||
local sid = sid_output:match("%S+")
|
||||
if sid and sid ~= "" then
|
||||
local escaped_sid = luci.util.shellquote(sid)
|
||||
local alias = string.format("Subscribe %d", index)
|
||||
local escaped_alias = luci.util.shellquote(alias)
|
||||
local escaped_url = luci.util.shellquote(trimmed)
|
||||
luci.sys.call("uci set shadowsocksr." .. escaped_sid .. ".enabled=1")
|
||||
luci.sys.call("uci set shadowsocksr." .. escaped_sid .. ".alias=" .. escaped_alias)
|
||||
luci.sys.call("uci set shadowsocksr." .. escaped_sid .. ".url=" .. escaped_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
luci.sys.call("uci delete shadowsocksr." .. subscribe_sid .. ".subscribe_url")
|
||||
luci.sys.call("uci commit shadowsocksr")
|
||||
end
|
||||
|
||||
local function clash_host_port(clash_url)
|
||||
if not clash_url or clash_url == "" then
|
||||
return nil, nil
|
||||
end
|
||||
local ok, parsed = pcall(URL.parse, clash_url)
|
||||
if not ok or not parsed then
|
||||
return nil, nil
|
||||
end
|
||||
local host = parsed.host
|
||||
local port = parsed.port
|
||||
if not port or port == "" then
|
||||
port = (parsed.scheme == "http") and "80" or "443"
|
||||
end
|
||||
return host, port
|
||||
end
|
||||
|
||||
migrate_legacy_subscribe_urls()
|
||||
|
||||
uci:foreach("shadowsocksr", "servers", function(s)
|
||||
server_count = server_count + 1
|
||||
server_sections[#server_sections + 1] = s[".name"]
|
||||
server_cache[s[".name"]] = {
|
||||
type = s.type,
|
||||
v2ray_protocol = s.v2ray_protocol,
|
||||
alias = s.alias,
|
||||
server_port = s.server_port,
|
||||
server = s.server,
|
||||
transport = s.transport,
|
||||
ws_path = s.ws_path,
|
||||
ws_host = s.ws_host,
|
||||
tls_host = s.tls_host,
|
||||
tls = s.tls,
|
||||
reality = s.reality,
|
||||
clash_url = s.clash_url
|
||||
}
|
||||
end)
|
||||
|
||||
local function get_server(section)
|
||||
return server_cache[section] or {}
|
||||
end
|
||||
|
||||
do
|
||||
local raw = nixio.fs.readfile("/tmp/ssrplus_server_detect.json")
|
||||
if raw and raw ~= "" then
|
||||
local parsed = json.parse(raw)
|
||||
if type(parsed) == "table" then
|
||||
detect_cache = parsed
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
m = Map("shadowsocksr", translate("Servers subscription and manage"))
|
||||
if upload_errmessage then
|
||||
m.errmessage = upload_errmessage
|
||||
elseif upload_message then
|
||||
m.message = upload_message
|
||||
end
|
||||
|
||||
local style_section = m:section(SimpleSection)
|
||||
style_section.template = "shadowsocksr/servers_subscribe_url_style"
|
||||
|
||||
-- Server Subscribe
|
||||
s = m:section(TypedSection, "server_subscribe")
|
||||
@@ -408,13 +118,6 @@ o = s:option(Flag, "auto_update", translate("Auto Update"))
|
||||
o.rmempty = false
|
||||
o.description = translate("Auto Update Server subscription, GFW list and CHN route")
|
||||
|
||||
o = s:option(ListValue, "config_auto_update_mode", translate("Update Mode"))
|
||||
o:value("0", translate("Appointment Mode"))
|
||||
o:value("1", translate("Loop Mode"))
|
||||
o.default = "0"
|
||||
o.rmempty = true
|
||||
o:depends("auto_update", "1")
|
||||
|
||||
o = s:option(ListValue, "auto_update_week_time", translate("Update cycle (Day/Week)"))
|
||||
o:value('*', translate("Every Day"))
|
||||
o:value("1", translate("Every Monday"))
|
||||
@@ -426,7 +129,7 @@ o:value("6", translate("Every Saturday"))
|
||||
o:value("0", translate("Every Sunday"))
|
||||
o.default = "*"
|
||||
o.rmempty = true
|
||||
o:depends({auto_update = "1", config_auto_update_mode = "0"})
|
||||
o:depends("auto_update", "1")
|
||||
|
||||
o = s:option(ListValue, "auto_update_day_time", translate("Regular update (Hour)"))
|
||||
for t = 0, 23 do
|
||||
@@ -434,7 +137,7 @@ for t = 0, 23 do
|
||||
end
|
||||
o.default = 2
|
||||
o.rmempty = true
|
||||
o:depends({auto_update = "1", config_auto_update_mode = "0"})
|
||||
o:depends("auto_update", "1")
|
||||
|
||||
o = s:option(ListValue, "auto_update_min_time", translate("Regular update (Min)"))
|
||||
for i = 0, 59 do
|
||||
@@ -442,83 +145,178 @@ for i = 0, 59 do
|
||||
end
|
||||
o.default = 30
|
||||
o.rmempty = true
|
||||
o:depends({auto_update = "1", config_auto_update_mode = "0"})
|
||||
o:depends("auto_update", "1")
|
||||
|
||||
o = s:option(Value, "config_update_interval", translate("Update Interval(min)"))
|
||||
o.default = "60"
|
||||
o.datatype = "uinteger"
|
||||
-- 确保 hy2_type_list 不为空
|
||||
if #hy2_type_list > 0 then
|
||||
local sid = uci:get_first("shadowsocksr", "server_subscribe")
|
||||
if not sid then
|
||||
uci:foreach("shadowsocksr", "server_subscribe", function(section)
|
||||
sid = section[".name"]
|
||||
return false
|
||||
end)
|
||||
end
|
||||
if sid then
|
||||
local old_val = uci:get("shadowsocksr", sid, "xray_hy2_type")
|
||||
if old_val and old_val ~= "" then
|
||||
if (old_val == "hysteria2" and not has_hysteria2) or
|
||||
(old_val == "v2ray" and not has_xray) then
|
||||
-- 核心不可用,设置为空(删除配置)
|
||||
uci:set("shadowsocksr", sid, "xray_hy2_type", "")
|
||||
uci:commit("shadowsocksr")
|
||||
end
|
||||
end
|
||||
end
|
||||
o = s:option(ListValue, "xray_hy2_type", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Type", "Hysteria2")))
|
||||
o.description = translate("The configured type also applies to the core specified when manually importing nodes.")
|
||||
o:value("", translate("Auto"))
|
||||
for _, v in ipairs(hy2_type_list) do
|
||||
o:value(v.id, v.name) -- 存储 "Xray" / "Hysteria2",但 UI 显示完整名称
|
||||
end
|
||||
end
|
||||
|
||||
-- 确保 tj_type_list 不为空
|
||||
if #tj_type_list > 0 then
|
||||
local sid = uci:get_first("shadowsocksr", "server_subscribe")
|
||||
if not sid then
|
||||
uci:foreach("shadowsocksr", "server_subscribe", function(section)
|
||||
sid = section[".name"]
|
||||
return false
|
||||
end)
|
||||
end
|
||||
if sid then
|
||||
local old_val = uci:get("shadowsocksr", sid, "xray_tj_type")
|
||||
if old_val and old_val ~= "" then
|
||||
if (old_val == "trojan" and not has_trojan) or
|
||||
(old_val == "v2ray" and not has_xray) then
|
||||
-- 核心不可用,设置为空(删除配置)
|
||||
uci:set("shadowsocksr", sid, "xray_tj_type", "")
|
||||
uci:commit("shadowsocksr")
|
||||
end
|
||||
end
|
||||
end
|
||||
o = s:option(ListValue, "xray_tj_type", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Type", "Trojan")))
|
||||
o.description = translate("The configured type also applies to the core specified when manually importing nodes.")
|
||||
o:value("", translate("Auto"))
|
||||
for _, v in ipairs(tj_type_list) do
|
||||
o:value(v.id, v.name) -- 存储 "Xray" / "Trojan",但 UI 显示完整名称
|
||||
end
|
||||
end
|
||||
|
||||
-- 确保 ss_type_list 不为空
|
||||
if #ss_type_list > 0 then
|
||||
local sid = uci:get_first("shadowsocksr", "server_subscribe")
|
||||
if not sid then
|
||||
uci:foreach("shadowsocksr", "server_subscribe", function(section)
|
||||
sid = section[".name"]
|
||||
return false
|
||||
end)
|
||||
end
|
||||
if sid then
|
||||
local old_val = uci:get("shadowsocksr", sid, "ss_type")
|
||||
if old_val and old_val ~= "" then
|
||||
if (old_val == "ss-rust" and not has_ss_rust) or
|
||||
(old_val == "ss-libev" and not has_ss_libev) or
|
||||
(old_val == "v2ray" and not has_xray) then
|
||||
-- 核心不可用,设置为空(删除配置)
|
||||
uci:set("shadowsocksr", sid, "ss_type", "")
|
||||
uci:commit("shadowsocksr")
|
||||
end
|
||||
end
|
||||
end
|
||||
o = s:option(ListValue, "ss_type", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Version", "ShadowSocks")))
|
||||
o.description = translate("Selection ShadowSocks Node Use Version.")
|
||||
o:value("", translate("Auto"))
|
||||
for _, v in ipairs(ss_type_list) do
|
||||
o:value(v.id, v.name) -- 存储 "ss-libev" / "ss-rust",但 UI 显示完整名称
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, "subscribe_url", translate("Subscribe URL"))
|
||||
o.rmempty = true
|
||||
o:depends({auto_update = "1", config_auto_update_mode = "1"})
|
||||
|
||||
o = s:option(Flag, "_subscribe_advanced_toggle", translate("Subscribe Advanced Settings"))
|
||||
o.rmempty = false
|
||||
o.default = "0"
|
||||
o.write = function() end
|
||||
o.remove = function() end
|
||||
o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve"))
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("If the node address is a domain name, this DNS will be used for resolution.") .. "</li>" ..
|
||||
"<li>" .. string.format('<font style=\'color:red;\'>%s</font>', translate("Supports only Xray node types.")) .. "</li>" ..
|
||||
"<li>" .. string.format('<font style=\'color:red;\'>%s</font>', translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead.")) .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
o:value("", translate("Auto"))
|
||||
o:value("tcp", translate("TCP"))
|
||||
o:value("udp", translate("UDP"))
|
||||
o:value("https", translate("DoH"))
|
||||
|
||||
o = s:option(Value, "domain_resolver_dns", translate("DNS"))
|
||||
o.datatype = "or(ipaddr,ipaddrport)"
|
||||
o:value("114.114.114.114")
|
||||
o:value("223.5.5.5:53")
|
||||
o.default = "114.114.114.114"
|
||||
o:depends("domain_resolver", "tcp")
|
||||
o:depends("domain_resolver", "udp")
|
||||
|
||||
o = s:option(Value, "domain_resolver_dns_https", translate("DNS"))
|
||||
o:value("https://120.53.53.53/dns-query", "DNSPod")
|
||||
o:value("https://223.5.5.5/dns-query", "AliDNS")
|
||||
o.default = o.keylist[1]
|
||||
o:depends("domain_resolver", "https")
|
||||
|
||||
o = s:option(ListValue, "domain_strategy", translate("Domain Strategy"))
|
||||
o.description = translate(
|
||||
"<ul>" ..
|
||||
"<li>" .. translate("If is domain name, The requested domain name will be resolved to IP before connect.") .. "</li>" ..
|
||||
"<li>" .. string.format('<font style=\'color:red;\'>%s</font>', translate("Supports only Xray node types.")) .. "</li>" ..
|
||||
"<li>" .. string.format('<font style=\'color:red;\'>%s</font>', translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead.")) .. "</li>" ..
|
||||
"</ul>"
|
||||
)
|
||||
o.default = ""
|
||||
o:value("", translate("Auto"))
|
||||
o:value("UseIPv4v6", translate("Prefer IPv4"))
|
||||
o:value("UseIPv6v4", translate("Prefer IPv6"))
|
||||
o:value("UseIPv4", translate("IPv4 Only"))
|
||||
o:value("UseIPv6", translate("IPv6 Only"))
|
||||
|
||||
o = s:option(Value, "filter_words", translate("Subscribe Filter Words"))
|
||||
o.rmempty = true
|
||||
o.description = translate("Filter Words splited by /")
|
||||
o:depends("_subscribe_advanced_toggle", "1")
|
||||
preserve_when_hidden(o, "_subscribe_advanced_toggle", "1")
|
||||
|
||||
o = s:option(Value, "save_words", translate("Subscribe Save Words"))
|
||||
o.rmempty = true
|
||||
o.description = translate("Save Words splited by /")
|
||||
o:depends("_subscribe_advanced_toggle", "1")
|
||||
preserve_when_hidden(o, "_subscribe_advanced_toggle", "1")
|
||||
|
||||
o = s:option(Button, "update_Sub", translate("Update Subscribe List"))
|
||||
o.inputstyle = "reload"
|
||||
o.description = translate("Update subscribe url list first")
|
||||
o.write = function()
|
||||
uci:commit("shadowsocksr")
|
||||
luci.sys.exec("rm -rf /tmp/sub_md5_*")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers"))
|
||||
end
|
||||
|
||||
o = s:option(Flag, "allow_insecure", translate("Allow subscribe Insecure nodes By default"))
|
||||
o.rmempty = false
|
||||
o.description = translate("Subscribe nodes allows insecure connection as TLS client (insecure)")
|
||||
o.default = "0"
|
||||
o:depends("_subscribe_advanced_toggle", "1")
|
||||
preserve_when_hidden(o, "_subscribe_advanced_toggle", "1")
|
||||
|
||||
o = s:option(Flag, "switch", translate("Subscribe Default Auto-Switch"))
|
||||
o.rmempty = false
|
||||
o.description = translate("Subscribe new add server default Auto-Switch on")
|
||||
o.default = "1"
|
||||
o:depends("_subscribe_advanced_toggle", "1")
|
||||
preserve_when_hidden(o, "_subscribe_advanced_toggle", "1")
|
||||
|
||||
o = s:option(Flag, "proxy", translate("Through proxy update"))
|
||||
o.rmempty = false
|
||||
o.description = translate("Through proxy update list, Not Recommended ")
|
||||
o.default = "1"
|
||||
o:depends("_subscribe_advanced_toggle", "1")
|
||||
preserve_when_hidden(o, "_subscribe_advanced_toggle", "1")
|
||||
|
||||
o = s:option(Button, "_save_subscribe_settings", translate("Save Subscribe Settings"))
|
||||
o.inputstyle = "save"
|
||||
o.description = translate("Save current subscribe settings")
|
||||
o.write = function() end
|
||||
|
||||
o = s:option(Button, "subscribe", translate("Update All Subscribe Servers"))
|
||||
o.rawhtml = true
|
||||
o.template = "shadowsocksr/subscribe"
|
||||
o.write = function(self, section)
|
||||
self.map.ssr_subscribe_requested = true
|
||||
end
|
||||
|
||||
o = s:option(Button, "delete", translate("Delete All Subscribe Servers"))
|
||||
o.inputstyle = "reset"
|
||||
o.description = string.format(translate("Server Count") .. ": %d", server_count)
|
||||
o.write = function()
|
||||
uci:delete_all("shadowsocksr", "servers", function(s)
|
||||
if s.hashkey or s.isSubscribe then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end)
|
||||
uci:save("shadowsocksr")
|
||||
uci:commit("shadowsocksr")
|
||||
for file in nixio.fs.glob("/tmp/sub_md5_*") do
|
||||
nixio.fs.remove(file)
|
||||
end
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "delete"))
|
||||
return
|
||||
luci.http.redirect(url("delete"))
|
||||
end
|
||||
|
||||
o = s:option(Value, "url_test_url", translate("URL Test Address"))
|
||||
@@ -537,187 +335,57 @@ o:value("curl", "Curl")
|
||||
o:value("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Linux")
|
||||
o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Windows")
|
||||
o:value("v2rayN/9.99", "v2rayN")
|
||||
o:depends("_subscribe_advanced_toggle", "1")
|
||||
preserve_when_hidden(o, "_subscribe_advanced_toggle", "1")
|
||||
|
||||
if has_mihomo then
|
||||
o = s:option(DummyValue, "_upload_clash_yaml", translate("Upload Custom YAML File"))
|
||||
o.template = "shadowsocksr/clash_yaml_upload"
|
||||
o.description = translate("Upload a custom Clash/Mihomo YAML file. The file will be preprocessed and saved as a local Clash node.")
|
||||
end
|
||||
|
||||
s:append(cbi.Template("shadowsocksr/subscribe_schedule_compact"))
|
||||
|
||||
s = m:section(TypedSection, "server_subscribe_item", translate("Subscribe URL"))
|
||||
s.anonymous = true
|
||||
s.addremove = true
|
||||
s.sortable = true
|
||||
s.template = "shadowsocksr/subscribe_actions_footer"
|
||||
--s.template = "cbi/tblsection"
|
||||
--s.template_addremove = "shadowsocksr/subscribe_actions_footer"
|
||||
s.description = translate("Manage multiple subscribe URLs, including Clash subscriptions. Only enabled entries are included when updating all subscriptions.")
|
||||
|
||||
o = s:option(Flag, "enabled", translate("Enable"))
|
||||
o.rmempty = false
|
||||
o.default = "1"
|
||||
o.width = "1%"
|
||||
function o.cfgvalue(...)
|
||||
return Flag.cfgvalue(...) or "1"
|
||||
end
|
||||
|
||||
o = s:option(Value, "alias", translate("Alias"))
|
||||
o.rmempty = true
|
||||
o.width = "7.5rem"
|
||||
function o.cfgvalue(self, section)
|
||||
return Value.cfgvalue(self, section) or string.format("Subscribe %s", section:sub(-4))
|
||||
end
|
||||
|
||||
o = s:option(Value, "url", translate("Subscribe URL"))
|
||||
o.rmempty = false
|
||||
|
||||
-- [[ Servers Manage ]]--
|
||||
s = m:section(TypedSection, "servers")
|
||||
s.anonymous = true
|
||||
s.addremove = true
|
||||
s.description = translate("Node order can be dragged with the mouse and takes effect immediately. The automatic switch order of server nodes is consistent with the node order in the table.")
|
||||
s.template = "shadowsocksr/server_table"
|
||||
s.template = "cbi/tblsection"
|
||||
set_apply_on_parse(m)
|
||||
s:append(cbi.Template("shadowsocksr/optimize_cbi_ui"))
|
||||
s.extedit = luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers", "%s")
|
||||
|
||||
local server_page_size = parse_nonnegative_int(luci.http.formvalue("server_page_size"))
|
||||
if not server_page_size or not in_list(SERVER_PAGE_SIZE_OPTIONS, server_page_size) then
|
||||
server_page_size = DEFAULT_SERVER_PAGE_SIZE
|
||||
end
|
||||
|
||||
local server_page_count = 1
|
||||
if server_page_size > 0 and server_count > 0 then
|
||||
server_page_count = math.ceil(server_count / server_page_size)
|
||||
end
|
||||
|
||||
local server_page = parse_nonnegative_int(luci.http.formvalue("server_page")) or 1
|
||||
if server_page < 1 then
|
||||
server_page = 1
|
||||
end
|
||||
if server_page > server_page_count then
|
||||
server_page = server_page_count
|
||||
end
|
||||
|
||||
local visible_server_sections = server_sections
|
||||
local server_first_index = server_count > 0 and 1 or 0
|
||||
local server_last_index = server_count
|
||||
|
||||
if server_page_size > 0 then
|
||||
local first = ((server_page - 1) * server_page_size) + 1
|
||||
local last = math.min(first + server_page_size - 1, server_count)
|
||||
visible_server_sections = {}
|
||||
server_first_index = server_count > 0 and first or 0
|
||||
server_last_index = server_count > 0 and last or 0
|
||||
|
||||
for index = first, last do
|
||||
visible_server_sections[#visible_server_sections + 1] = server_sections[index]
|
||||
end
|
||||
end
|
||||
|
||||
s.sortable = true
|
||||
|
||||
function s.cfgsections(self)
|
||||
return visible_server_sections
|
||||
end
|
||||
|
||||
s.server_page = server_page
|
||||
s.server_page_size = server_page_size
|
||||
s.server_page_count = server_page_count
|
||||
s.server_page_sizes = SERVER_PAGE_SIZE_OPTIONS
|
||||
s.server_total = server_count
|
||||
s.server_first_index = server_first_index
|
||||
s.server_last_index = server_last_index
|
||||
s.server_base_url = luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers")
|
||||
|
||||
--[[
|
||||
s.extedit = url("servers", "%s")
|
||||
function s.create(self, ...)
|
||||
local used_sid = {}
|
||||
local next_sid = 1
|
||||
|
||||
self.map.uci:foreach(self.config, self.sectiontype, function(s)
|
||||
local num = s[".name"]:match("^cfg(%x%x)")
|
||||
if num then
|
||||
local n = tonumber(num, 16)
|
||||
if n then
|
||||
used_sid[n] = true
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local function get_next_sid()
|
||||
while used_sid[next_sid] do
|
||||
next_sid = next_sid + 1
|
||||
end
|
||||
used_sid[next_sid] = true
|
||||
return next_sid
|
||||
end
|
||||
|
||||
local sid = TypedSection.create(self, ...)
|
||||
if sid then
|
||||
local suffix = sid:sub(-4)
|
||||
local sid = TypedSection.create(self, ...)
|
||||
if sid then
|
||||
local newsid = "cfg" .. sid:sub(-6)
|
||||
-- 删除匿名
|
||||
self.map.uci:delete(self.config, sid)
|
||||
local id = get_next_sid()
|
||||
local newsid = string.format("cfg%02x%s", id, suffix)
|
||||
local success = self.map.uci:section(self.config, self.sectiontype, newsid)
|
||||
if success then
|
||||
--self.map.uci:save(self.config)
|
||||
luci.http.redirect(self.extedit % newsid)
|
||||
return
|
||||
end
|
||||
end
|
||||
-- 重命名 section
|
||||
self.map.uci:section(self.config, self.sectiontype, newsid)
|
||||
luci.http.redirect(self.extedit % newsid)
|
||||
return
|
||||
end
|
||||
end
|
||||
]]--
|
||||
|
||||
o = s:option(DummyValue, "type", translate("Type"))
|
||||
function o.cfgvalue(self, section)
|
||||
local cfg = get_server(section)
|
||||
return cfg.v2ray_protocol or cfg.type or translate("None")
|
||||
return m:get(section, "v2ray_protocol") or Value.cfgvalue(self, section) or translate("None")
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "alias", translate("Alias"))
|
||||
function o.cfgvalue(self, section)
|
||||
return get_server(section).alias or translate("None")
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("None")
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "server_port", translate("Server Port"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or "N/A"
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "server_port", translate("Socket Connected"))
|
||||
o.template = "shadowsocksr/socket"
|
||||
o.width = "10%"
|
||||
function o.cfgvalue(self, section)
|
||||
self.detect_cache = detect_cache[section]
|
||||
local cfg = get_server(section)
|
||||
local stype = cfg.type
|
||||
if stype == "clash" then
|
||||
return "N/A"
|
||||
end
|
||||
return cfg.server_port
|
||||
end
|
||||
o.render = function(self, section, scope)
|
||||
local cfg = get_server(section)
|
||||
local stype = cfg.type
|
||||
self.type = stype or ""
|
||||
self.proto = cfg.v2ray_protocol or ""
|
||||
self.reality = cfg.reality or ""
|
||||
if stype == "clash" then
|
||||
self.transport = ""
|
||||
self.ws_path = ""
|
||||
self.ws_host = ""
|
||||
self.tls_host = ""
|
||||
self.tls = ""
|
||||
self.reality = ""
|
||||
else
|
||||
self.transport = cfg.transport or ""
|
||||
self.ws_host = cfg.ws_host or ""
|
||||
self.tls_host = cfg.tls_host or ""
|
||||
if self.transport == 'ws' then
|
||||
self.ws_path = cfg.ws_path or ""
|
||||
self.tls = cfg.tls or ""
|
||||
else
|
||||
self.ws_path = ""
|
||||
self.tls = ""
|
||||
end
|
||||
local cfg = s:cfgvalue(section) or {}
|
||||
self.transport = cfg.transport
|
||||
self.type = cfg.type
|
||||
self.v2ray_protocol = cfg.v2ray_protocol
|
||||
if self.transport == 'ws' then
|
||||
self.ws_path = cfg.ws_path
|
||||
self.tls = cfg.tls
|
||||
self.tls_host = cfg.tls_host
|
||||
end
|
||||
DummyValue.render(self, section, scope)
|
||||
end
|
||||
@@ -725,15 +393,6 @@ end
|
||||
o = s:option(DummyValue, "server", translate("Ping Latency"))
|
||||
o.template = "shadowsocksr/ping"
|
||||
o.width = "10%"
|
||||
function o.cfgvalue(self, section)
|
||||
self.detect_cache = detect_cache[section]
|
||||
local cfg = get_server(section)
|
||||
self.type = cfg.type or ""
|
||||
if cfg.type == "clash" then
|
||||
return "N/A"
|
||||
end
|
||||
return cfg.server or "N/A"
|
||||
end
|
||||
|
||||
local global_server = uci:get_first('shadowsocksr', 'global', 'global_server')
|
||||
|
||||
@@ -748,10 +407,11 @@ node.render = function(self, section, scope)
|
||||
Button.render(self, section, scope)
|
||||
end
|
||||
node.write = function(self, section)
|
||||
local safe_section = luci.util.shellquote(section)
|
||||
local cmd = string.format("uci set shadowsocksr.@global[0].global_server=%s && uci commit shadowsocksr", safe_section)
|
||||
luci.sys.call(cmd)
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "restart"))
|
||||
uci:set("shadowsocksr", '@global[0]', 'global_server', section)
|
||||
uci:save("shadowsocksr")
|
||||
uci:commit("shadowsocksr")
|
||||
luci.sys.call("/etc/init.d/shadowsocksr restart >/dev/null 2>&1 &")
|
||||
luci.http.redirect(url("restart"))
|
||||
end
|
||||
|
||||
o = s:option(Flag, "switch_enable", translate("Auto Switch"))
|
||||
@@ -762,15 +422,4 @@ end
|
||||
|
||||
m:append(cbi.Template("shadowsocksr/server_list"))
|
||||
|
||||
m.commit_handler = function(self)
|
||||
if not self.ssr_subscribe_requested then
|
||||
return
|
||||
end
|
||||
|
||||
for _, config in ipairs(self.parsechain or {}) do
|
||||
self.uci:commit(config)
|
||||
end
|
||||
self.ssr_subscribe_autostart = true
|
||||
end
|
||||
|
||||
return m
|
||||
@@ -7,22 +7,15 @@ local m, s, o
|
||||
local redir_run = 0
|
||||
local reudp_run = 0
|
||||
local sock5_run = 0
|
||||
local http_run = 0
|
||||
local server_run = 0
|
||||
local kcptun_run = 0
|
||||
local tunnel_run = 0
|
||||
local gfw_count = 0
|
||||
local ad_count = 0
|
||||
local ip_count = 0
|
||||
local Process_list = luci.sys.exec("busybox ps -w 2>/dev/null || busybox ps")
|
||||
local nfip_count = 0
|
||||
local Process_list = luci.sys.exec("busybox ps -w")
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local global_server = uci:get_first("shadowsocksr", "global", "global_server", "nil")
|
||||
local global_type = global_server ~= "nil" and (uci:get("shadowsocksr", global_server, "type") or "") or ""
|
||||
local global_socks_enabled = uci:get_first("shadowsocksr", "socks5_proxy", "enabled", "0") == "1"
|
||||
local global_socks_server = uci:get_first("shadowsocksr", "socks5_proxy", "server", "nil")
|
||||
local global_http_enabled = uci:get_first("shadowsocksr", "http_proxy", "enabled", "0") == "1"
|
||||
local has_3proxy = nixio.fs.access("/usr/bin/3proxy") or nixio.fs.access("/usr/libexec/3proxy") or nixio.fs.access("/bin/3proxy")
|
||||
local pdnsd_mode = uci:get_first("shadowsocksr", 'global', 'pdnsd_enable', '0')
|
||||
-- html constants
|
||||
font_blue = [[<b style=color:green>]]
|
||||
style_blue = [[<b style=color:red>]]
|
||||
@@ -59,6 +52,10 @@ if nixio.fs.access("/etc/ssrplus/applechina.conf") then
|
||||
apple_count = tonumber(luci.sys.exec("cat /etc/ssrplus/applechina.conf | wc -l"))
|
||||
end
|
||||
|
||||
if nixio.fs.access("/etc/ssrplus/netflixip.list") then
|
||||
nfip_count = tonumber(luci.sys.exec("cat /etc/ssrplus/netflixip.list | wc -l"))
|
||||
end
|
||||
|
||||
if Process_list:find("udp.only.ssr.reudp") then
|
||||
reudp_run = 1
|
||||
end
|
||||
@@ -77,10 +74,6 @@ if Process_list:find("tcp.udp.ssr.local") then
|
||||
sock5_run = 1
|
||||
end
|
||||
|
||||
if has_3proxy and Process_list:find("3proxy%-ssr%-http%.cfg") then
|
||||
http_run = 1
|
||||
end
|
||||
|
||||
if Process_list:find("tcp.udp.ssr.retcp") then
|
||||
redir_run = 1
|
||||
reudp_run = 1
|
||||
@@ -110,38 +103,6 @@ if Process_list:find("local.udp.ssr.retcp") then
|
||||
sock5_run = 1
|
||||
end
|
||||
|
||||
if global_type == "socks5" and Process_list:find("ipt2socks") then
|
||||
if Process_list:find("%-T") or Process_list:find("%-%-tcp%-only") then
|
||||
redir_run = 1
|
||||
end
|
||||
if Process_list:find("%-U") or Process_list:find("%-%-udp%-only") then
|
||||
reudp_run = 1
|
||||
end
|
||||
if global_socks_enabled and (global_socks_server == "same" and global_socks_server == global_server) then
|
||||
sock5_run = 1
|
||||
end
|
||||
end
|
||||
|
||||
if (global_type == "clash" or global_type == "tuic" or global_type == "ss") and Process_list:find("ssr%-retcp") then
|
||||
redir_run = 1
|
||||
reudp_run = 1
|
||||
if global_socks_enabled and (global_socks_server == "same" and global_socks_server == global_server) then
|
||||
sock5_run = 1
|
||||
end
|
||||
end
|
||||
|
||||
if (global_type == "clash" or global_type == "tuic" or global_type == "ss") and Process_list:find("mihomo") and (Process_list:find("/clash%-") or Process_list:find("/tuic%-") or Process_list:find("/ss%-")) then
|
||||
redir_run = 1
|
||||
reudp_run = 1
|
||||
if global_socks_enabled and (global_socks_server == "same" and global_socks_server == global_server) then
|
||||
sock5_run = 1
|
||||
end
|
||||
end
|
||||
|
||||
if has_3proxy and global_http_enabled and http_run == 0 and Process_list:find("3proxy%-ssr%-http%.cfg") then
|
||||
http_run = 1
|
||||
end
|
||||
|
||||
if Process_list:find("kcptun.client") then
|
||||
kcptun_run = 1
|
||||
end
|
||||
@@ -150,21 +111,11 @@ if Process_list:find("ssr.server") then
|
||||
server_run = 1
|
||||
end
|
||||
|
||||
if Process_list:find("mihomo") and Process_list:find("/ss%-server%-") then
|
||||
server_run = 1
|
||||
end
|
||||
|
||||
if Process_list:find("ssrplus/bin/dns2tcp") or
|
||||
Process_list:find("ssrplus/bin/mosdns") or
|
||||
Process_list:find("chinadns.*127.0.0.1.*5335") then
|
||||
pdnsd_run = 1
|
||||
end
|
||||
|
||||
if pdnsd_mode == "7" and (global_type == "clash" or global_type == "tuic" or global_type == "ss") and Process_list:find("ssr%-retcp") then
|
||||
pdnsd_run = 1
|
||||
end
|
||||
|
||||
if pdnsd_mode == "7" and global_type == "v2ray" and Process_list:find("ssr%-retcp%.json") then
|
||||
Process_list:find("dnsproxy.*127.0.0.1.*5335") or
|
||||
Process_list:find("chinadns.*127.0.0.1.*5335") or
|
||||
(Process_list:find("ssrplus.dns") and Process_list:find("dns2socks.*127.0.0.1.*127.0.0.1.5335")) then
|
||||
pdnsd_run = 1
|
||||
end
|
||||
|
||||
@@ -206,16 +157,6 @@ else
|
||||
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
|
||||
end
|
||||
|
||||
if has_3proxy then
|
||||
s = m:field(DummyValue, "http_run", translate("Global HTTP/HTTPS Proxy Server"))
|
||||
s.rawhtml = true
|
||||
if http_run == 1 then
|
||||
s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off
|
||||
else
|
||||
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
|
||||
end
|
||||
end
|
||||
|
||||
s = m:field(DummyValue, "server_run", translate("Local Servers"))
|
||||
s.rawhtml = true
|
||||
if server_run == 1 then
|
||||
@@ -270,6 +211,13 @@ if uci:get_first("shadowsocksr", 'global', 'apple_optimization', '0') ~= '0' the
|
||||
s.value = apple_count .. " " .. translate("Records")
|
||||
end
|
||||
|
||||
if uci:get_first("shadowsocksr", 'global', 'netflix_enable', '0') ~= '0' then
|
||||
s = m:field(DummyValue, "nfip_data", translate("Netflix IP Data"))
|
||||
s.rawhtml = true
|
||||
s.template = "shadowsocksr/refresh"
|
||||
s.value = nfip_count .. " " .. translate("Records")
|
||||
end
|
||||
|
||||
if uci:get_first("shadowsocksr", 'global', 'adblock', '0') == '1' then
|
||||
s = m:field(DummyValue, "ad_data", translate("Advertising Data"))
|
||||
s.rawhtml = true
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
<style type="text/css">
|
||||
.ssr-advanced-switch-inline {
|
||||
display: flex;
|
||||
gap: 2%;
|
||||
align-items: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.ssr-advanced-switch-item {
|
||||
width: 30%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ssr-advanced-switch-item input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ssr-advanced-switch-subtitle {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.ssr-advanced-switch-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function ssrCompactAdvancedSwitch() {
|
||||
function getInputByName(suffix) {
|
||||
return document.querySelector('input[name$=".' + suffix + '"]');
|
||||
}
|
||||
|
||||
function getRowByName(suffix) {
|
||||
var el = getInputByName(suffix);
|
||||
return el ? el.closest('.cbi-value') : null;
|
||||
}
|
||||
|
||||
function compactSwitchRow() {
|
||||
var timeRow = getRowByName('switch_time');
|
||||
var timeoutRow = getRowByName('switch_timeout');
|
||||
var tryRow = getRowByName('switch_try_count');
|
||||
if (!timeRow || !timeoutRow || !tryRow) return;
|
||||
|
||||
var timeField = timeRow.querySelector('.cbi-value-field');
|
||||
var timeoutField = timeoutRow.querySelector('.cbi-value-field');
|
||||
var tryField = tryRow.querySelector('.cbi-value-field');
|
||||
if (!timeField || !timeoutField || !tryField) return;
|
||||
|
||||
if (timeField.querySelector('.ssr-advanced-switch-inline')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var timeInput = timeField.querySelector('input');
|
||||
var timeoutInput = timeoutField.querySelector('input');
|
||||
var tryInput = tryField.querySelector('input');
|
||||
if (!timeInput || !timeoutInput || !tryInput) return;
|
||||
|
||||
var wrap = document.createElement('div');
|
||||
wrap.className = 'ssr-advanced-switch-inline';
|
||||
|
||||
function makeItem(title, input) {
|
||||
var item = document.createElement('div');
|
||||
item.className = 'ssr-advanced-switch-item';
|
||||
var subtitle = document.createElement('div');
|
||||
subtitle.className = 'ssr-advanced-switch-subtitle';
|
||||
subtitle.textContent = title;
|
||||
item.appendChild(subtitle);
|
||||
item.appendChild(input);
|
||||
return item;
|
||||
}
|
||||
|
||||
wrap.appendChild(makeItem('<%:Second%>', timeInput));
|
||||
wrap.appendChild(makeItem('<%:Timeout%>', timeoutInput));
|
||||
wrap.appendChild(makeItem('<%:Try Count%>', tryInput));
|
||||
|
||||
timeField.innerHTML = '';
|
||||
timeField.appendChild(wrap);
|
||||
|
||||
timeoutRow.classList.add('ssr-advanced-switch-hidden');
|
||||
tryRow.classList.add('ssr-advanced-switch-hidden');
|
||||
}
|
||||
|
||||
compactSwitchRow();
|
||||
}
|
||||
|
||||
(function() {
|
||||
var retryCount = 0;
|
||||
var maxRetries = 30;
|
||||
var retryTimer = null;
|
||||
var observer = null;
|
||||
|
||||
function hasCompactLayout() {
|
||||
return !!document.querySelector('.ssr-advanced-switch-inline');
|
||||
}
|
||||
|
||||
function tryCompact() {
|
||||
ssrCompactAdvancedSwitch();
|
||||
if (hasCompactLayout()) {
|
||||
if (retryTimer) {
|
||||
window.clearTimeout(retryTimer);
|
||||
retryTimer = null;
|
||||
}
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
observer = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (retryCount >= maxRetries) {
|
||||
return;
|
||||
}
|
||||
|
||||
retryCount = retryCount + 1;
|
||||
retryTimer = window.setTimeout(tryCompact, 200);
|
||||
}
|
||||
|
||||
function startWatcher() {
|
||||
tryCompact();
|
||||
if (window.MutationObserver) {
|
||||
observer = new MutationObserver(function() {
|
||||
if (!hasCompactLayout()) {
|
||||
tryCompact();
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', startWatcher);
|
||||
window.addEventListener('load', tryCompact);
|
||||
} else {
|
||||
startWatcher();
|
||||
}
|
||||
})();
|
||||
//]]>
|
||||
</script>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,104 +0,0 @@
|
||||
<%+header%>
|
||||
|
||||
<h2><%:Clash Panel%> - <%=alias%></h2>
|
||||
|
||||
<div class="cbi-map-descr">
|
||||
<%:This panel is available only when the current Clash total node is active.%>
|
||||
</div>
|
||||
|
||||
<div class="cbi-section-actions" style="margin: 1em 0;">
|
||||
<input type="button" id="clash-panel-refresh" class="btn cbi-button cbi-button-apply" value="<%:Refresh%>" />
|
||||
</div>
|
||||
|
||||
<div id="clash-panel-status" style="margin: 1em 0; color: #666;"></div>
|
||||
<div id="clash-panel-groups"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
(function() {
|
||||
const sid = <%=string.format("%q", sid)%>;
|
||||
const statusEl = document.getElementById('clash-panel-status');
|
||||
const groupsEl = document.getElementById('clash-panel-groups');
|
||||
const refreshBtn = document.getElementById('clash-panel-refresh');
|
||||
|
||||
function setStatus(text, color) {
|
||||
statusEl.style.color = color || '#666';
|
||||
statusEl.textContent = text;
|
||||
}
|
||||
|
||||
function renderGroups(groups) {
|
||||
groupsEl.innerHTML = '';
|
||||
if (!groups || !groups.length) {
|
||||
groupsEl.innerHTML = '<p><%:No switchable proxy groups found.%></p>';
|
||||
return;
|
||||
}
|
||||
|
||||
groups.forEach(function(group) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'cbi-value';
|
||||
wrapper.style.marginBottom = '1em';
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.className = 'cbi-value-title';
|
||||
label.textContent = group.name + (group.type ? ' (' + group.type + ')' : '');
|
||||
|
||||
const field = document.createElement('div');
|
||||
field.className = 'cbi-value-field';
|
||||
|
||||
const select = document.createElement('select');
|
||||
select.className = 'cbi-input-select';
|
||||
group.all.forEach(function(name) {
|
||||
const option = document.createElement('option');
|
||||
option.value = name;
|
||||
option.textContent = name;
|
||||
if (name === group.now) {
|
||||
option.selected = true;
|
||||
}
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
select.onchange = function() {
|
||||
setStatus('<%:Switching...%>', '#0072c3');
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "clash_switch")%>', {
|
||||
sid: sid,
|
||||
group: group.name,
|
||||
name: select.value
|
||||
}, function(x, result) {
|
||||
if (result && result.success) {
|
||||
setStatus('<%:Switched successfully.%>', '#249400');
|
||||
loadGroups();
|
||||
} else {
|
||||
setStatus('<%:Switch failed.%>', '#ff0000');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
field.appendChild(select);
|
||||
wrapper.appendChild(label);
|
||||
wrapper.appendChild(field);
|
||||
groupsEl.appendChild(wrapper);
|
||||
});
|
||||
}
|
||||
|
||||
function loadGroups() {
|
||||
setStatus('<%:Loading...%>', '#666');
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "clash_groups")%>', {
|
||||
sid: sid
|
||||
}, function(x, result) {
|
||||
if (!result || !result.active) {
|
||||
setStatus('<%:This Clash total node is not active. Apply this node first.%>', '#ff7700');
|
||||
groupsEl.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
setStatus('<%:Ready.%>', '#249400');
|
||||
renderGroups(result.groups || []);
|
||||
});
|
||||
}
|
||||
|
||||
refreshBtn.onclick = loadGroups;
|
||||
loadGroups();
|
||||
})();
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<%+footer%>
|
||||
@@ -1,74 +0,0 @@
|
||||
<%+cbi/valueheader%>
|
||||
<%
|
||||
local stype = self.map:get(section, "type")
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
if (typeof(window.ssrClashRefreshSubscription) !== "function") {
|
||||
window.ssrClashRefreshSubscription = function(btn, sid) {
|
||||
var original = btn.value;
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Reloading YAML...%>';
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "clash_refresh")%>', {
|
||||
sid: sid
|
||||
}, function(x, result) {
|
||||
btn.disabled = false;
|
||||
btn.value = original;
|
||||
if (result && result.success) {
|
||||
btn.value = result.reapplied ? '<%:Reapply%>' : '<%:YAML Reloaded!%>';
|
||||
setTimeout(function() { btn.value = original; }, 1200);
|
||||
} else {
|
||||
btn.value = '<%:YAML Reload Failed!%>';
|
||||
setTimeout(function() { btn.value = original; }, 1500);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (typeof(window.ssrClashResetDefaults) !== "function") {
|
||||
window.ssrClashResetDefaults = function(btn, sid) {
|
||||
var original = btn.value;
|
||||
if (!window.confirm('<%:Reset saved Mihomo proxy-group selections and restore YAML defaults?%>')) {
|
||||
return false;
|
||||
}
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Resetting Defaults...%>';
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "clash_reset_defaults")%>', {
|
||||
sid: sid
|
||||
}, function(x, result) {
|
||||
btn.disabled = false;
|
||||
btn.value = original;
|
||||
if (result && result.success) {
|
||||
btn.value = result.reapplied ? '<%:Defaults Restored!%>' : '<%:Defaults Cleared!%>';
|
||||
setTimeout(function() { btn.value = original; }, 1500);
|
||||
} else {
|
||||
btn.value = '<%:Reset Defaults Failed!%>';
|
||||
setTimeout(function() { btn.value = original; }, 1600);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<% if stype == "clash" then %>
|
||||
<input
|
||||
type="button"
|
||||
class="btn cbi-button"
|
||||
value="<%:Reload YAML%>"
|
||||
title="<%:Reload YAML%>"
|
||||
onclick="return ssrClashRefreshSubscription(this, '<%=section%>');" />
|
||||
<input
|
||||
type="button"
|
||||
class="btn cbi-button"
|
||||
value="<%:Reset Default Proxies Rules%>"
|
||||
title="<%:Reset Default Proxies Rules%>"
|
||||
onclick="return ssrClashResetDefaults(this, '<%=section%>');" />
|
||||
<input
|
||||
type="button"
|
||||
class="btn cbi-button cbi-button-apply"
|
||||
value="<%:Panel%>"
|
||||
title="<%:Panel%>"
|
||||
onclick="window.open('<%=luci.dispatcher.build_url('admin', 'services', 'shadowsocksr', 'clash_panel')%>?sid=<%=section%>', '_blank');" />
|
||||
<% else %>
|
||||
-
|
||||
<% end %>
|
||||
<%+cbi/valuefooter%>
|
||||
@@ -1,4 +0,0 @@
|
||||
<%+cbi/valueheader%>
|
||||
<input class="cbi-input-file" style="width: 400px" type="file" id="clash_yaml_file" name="clash_yaml_file" accept=".yaml,.yml" />
|
||||
<input type="submit" class="cbi-button cbi-input-apply" name="upload_clash_yaml" value="<%:Upload%>" />
|
||||
<%+cbi/valuefooter%>
|
||||
@@ -1,78 +0,0 @@
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
(function() {
|
||||
function getField(name) {
|
||||
return document.querySelector('select[name$=".' + name + '"], input[name$=".' + name + '"]');
|
||||
}
|
||||
|
||||
function ensureDefaultValue(modeValue, fieldName) {
|
||||
var modeEl = getField('pdnsd_enable');
|
||||
var dnsEl = getField(fieldName);
|
||||
if (!modeEl || !dnsEl || modeEl.value !== modeValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
var current = (dnsEl.value || '').trim();
|
||||
var placeholder = (dnsEl.options && dnsEl.selectedIndex >= 0 && dnsEl.options[dnsEl.selectedIndex])
|
||||
? (dnsEl.options[dnsEl.selectedIndex].text || '').trim()
|
||||
: '';
|
||||
if (current !== '' && current !== '-- 请选择 --' && current !== '-- Please choose --' &&
|
||||
placeholder !== '-- 请选择 --' && placeholder !== '-- Please choose --') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dnsEl.tagName === 'SELECT') {
|
||||
var firstValue = '';
|
||||
for (var i = 0; i < dnsEl.options.length; i++) {
|
||||
var option = dnsEl.options[i];
|
||||
if (option && option.value) {
|
||||
firstValue = option.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (firstValue) {
|
||||
dnsEl.value = firstValue;
|
||||
}
|
||||
} else if (dnsEl.dataset && dnsEl.dataset.default) {
|
||||
dnsEl.value = dnsEl.dataset.default;
|
||||
}
|
||||
}
|
||||
|
||||
function ensureMosdnsDefault() {
|
||||
ensureDefaultValue('4', 'tunnel_forward_mosdns');
|
||||
}
|
||||
|
||||
function ensureChinaDnsNgDefault() {
|
||||
ensureDefaultValue('6', 'chinadns_ng_tunnel_forward');
|
||||
}
|
||||
|
||||
function bindModeChange() {
|
||||
var modeEl = getField('pdnsd_enable');
|
||||
if (!modeEl || modeEl.dataset.ssrDnsDefaultBound) {
|
||||
return;
|
||||
}
|
||||
|
||||
modeEl.dataset.ssrDnsDefaultBound = '1';
|
||||
modeEl.addEventListener('change', function() {
|
||||
window.setTimeout(function() {
|
||||
ensureMosdnsDefault();
|
||||
ensureChinaDnsNgDefault();
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
function initMosdnsDefault() {
|
||||
bindModeChange();
|
||||
ensureMosdnsDefault();
|
||||
ensureChinaDnsNgDefault();
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initMosdnsDefault);
|
||||
window.addEventListener('load', initMosdnsDefault);
|
||||
} else {
|
||||
initMosdnsDefault();
|
||||
}
|
||||
})();
|
||||
//]]>
|
||||
</script>
|
||||
@@ -1,446 +0,0 @@
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
var componentMeta = {
|
||||
xray: {
|
||||
label: '<%:Xray-core%>',
|
||||
help: '<%:The online upgrade downloads the matching Xray-core linux archive for the current ARCH from the official GitHub release page.%>'
|
||||
},
|
||||
mihomo: {
|
||||
label: '<%:Mihomo%>',
|
||||
help: '<%:The online upgrade only tracks the latest stable Mihomo release and downloads the matching linux archive for the current ARCH from the official GitHub release page.%>'
|
||||
},
|
||||
naiveproxy: {
|
||||
label: '<%:NaiveProxy%>',
|
||||
help: '<%:The online upgrade tracks the latest NaiveProxy release and prefers OpenWrt static archives matching the current ARCH before falling back to other release assets.%>'
|
||||
}
|
||||
};
|
||||
|
||||
var geoMeta = {
|
||||
country_mmdb: {
|
||||
label: '<%:Country MMDB%>',
|
||||
help: '<%:Update the Country.mmdb database used by Mihomo/Clash runtime when available.%>'
|
||||
},
|
||||
geosite: {
|
||||
label: '<%:GeoSite Database%>',
|
||||
help: '<%:Update the GeoSite.dat database used by Mihomo/Clash runtime when available.%>'
|
||||
},
|
||||
v2ray_geo: {
|
||||
label: '<%:V2Ray GEO Databases%>',
|
||||
help: '<%:Update geoip.dat and geosite.dat used by Xray/V2Ray runtime when available.%>'
|
||||
}
|
||||
};
|
||||
|
||||
var componentMessageMap = {
|
||||
'Already up to date': '<%:Already up to date%>',
|
||||
'Upgrade completed': '<%:Upgrade completed%>',
|
||||
'Unsupported ARCH': '<%:Unsupported ARCH%>',
|
||||
'Failed to fetch release metadata': '<%:Failed to fetch release metadata%>',
|
||||
'Matching release asset not found': '<%:Matching release asset not found%>',
|
||||
'Missing unzip support': '<%:Missing unzip support%>',
|
||||
'Missing gzip support': '<%:Missing gzip support%>',
|
||||
'Missing xz support': '<%:Missing xz support%>',
|
||||
'Failed to create temp directory': '<%:Failed to create temp directory%>',
|
||||
'Download failed': '<%:Download failed%>',
|
||||
'Extract failed': '<%:Extract failed%>',
|
||||
'xray binary not found in archive': '<%:xray binary not found in archive%>',
|
||||
'mihomo binary not found in archive': '<%:mihomo binary not found in archive%>',
|
||||
'naive binary not found in archive': '<%:naive binary not found in archive%>',
|
||||
'Install failed': '<%:Install failed%>',
|
||||
'Installed binary failed to run': '<%:Installed binary failed to run%>'
|
||||
};
|
||||
|
||||
function selectedComponent() {
|
||||
var selector = document.getElementById('component-selector');
|
||||
return selector ? selector.value : 'xray';
|
||||
}
|
||||
|
||||
function selectedMirror() {
|
||||
var selector = document.getElementById('component-mirror');
|
||||
return selector ? selector.value : 'direct';
|
||||
}
|
||||
|
||||
function componentHelp(component) {
|
||||
return componentMeta[component] ? componentMeta[component].help : '';
|
||||
}
|
||||
|
||||
function displayMessage(message) {
|
||||
return componentMessageMap[message] || message || '';
|
||||
}
|
||||
|
||||
function displayError(error) {
|
||||
if (!error) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var map = {
|
||||
unsupported_arch: '<%:Unsupported ARCH%>',
|
||||
fetch_failed: '<%:Failed to fetch release metadata%>',
|
||||
asset_not_found: '<%:Matching release asset not found%>',
|
||||
unsupported_component: '<%:Unsupported component%>',
|
||||
unknown_error: '<%:Unknown error%>'
|
||||
};
|
||||
|
||||
return map[error] || error;
|
||||
}
|
||||
|
||||
function renderComponentInfo(data, checkedRemote) {
|
||||
var panel = document.getElementById('component-status');
|
||||
var upgradeButton = document.getElementById('component-upgrade');
|
||||
var action = document.getElementById('component-action');
|
||||
var help = document.getElementById('component-help');
|
||||
var html = [];
|
||||
var component = data && data.component ? data.component : selectedComponent();
|
||||
|
||||
if (!panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
panel.innerHTML = '<span style="color:red"><%:Failed to query component information.%></span>';
|
||||
if (upgradeButton) upgradeButton.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (help) {
|
||||
help.innerHTML = componentHelp(component);
|
||||
}
|
||||
|
||||
html.push('<div><strong><%:Installed Version%>:</strong> ' + (data.current_version || '<%:Not installed%>') + '</div>');
|
||||
html.push('<div><strong><%:Latest Version%>:</strong> ' + (data.latest_version || '<%:Unknown%>') + '</div>');
|
||||
html.push('<div><strong><%:Current ARCH%>:</strong> ' + (data.arch || '<%:Unknown%>') + '</div>');
|
||||
html.push('<div><strong><%:Release Asset%>:</strong> ' + (data.asset || '<%:Unknown%>') + '</div>');
|
||||
|
||||
if (data.error) {
|
||||
html.push('<div style="color:red"><%:Status%>: ' + displayError(data.error) + '</div>');
|
||||
} else if (!checkedRemote) {
|
||||
html.push('<div style="color:#666"><%:Status%>: <%:No Check%></div>');
|
||||
} else if (data.can_upgrade) {
|
||||
html.push('<div style="color:#d48806"><%:Status%>: <%:Upgrade available%></div>');
|
||||
} else {
|
||||
html.push('<div style="color:green"><%:Status%>: <%:Already up to date%></div>');
|
||||
}
|
||||
|
||||
panel.innerHTML = html.join('');
|
||||
|
||||
if (upgradeButton) {
|
||||
upgradeButton.disabled = !!data.error || !checkedRemote || !data.latest_version || !data.can_upgrade;
|
||||
}
|
||||
|
||||
if (action) {
|
||||
action.innerHTML = data.message
|
||||
? '<span style="color:' + (data.success ? 'green' : 'red') + '">' + displayMessage(data.message) + '</span>'
|
||||
: '';
|
||||
}
|
||||
}
|
||||
|
||||
function queryComponentInfo(button, doUpgrade) {
|
||||
var component = selectedComponent();
|
||||
var mirror = selectedMirror();
|
||||
var action = document.getElementById('component-action');
|
||||
var url = doUpgrade
|
||||
? '<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "component_upgrade")%>'
|
||||
: '<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "component_status")%>';
|
||||
|
||||
if (button) {
|
||||
button.disabled = true;
|
||||
button.value = doUpgrade ? '<%:Upgrading...%>' : '<%:Checking...%>';
|
||||
}
|
||||
|
||||
if (action) {
|
||||
action.innerHTML = doUpgrade ? '<%:Processing...%>' : '';
|
||||
}
|
||||
|
||||
XHR.get(url, { component: component, mirror: mirror }, function(x, rv) {
|
||||
renderComponentInfo(rv, true);
|
||||
if (button) {
|
||||
button.disabled = false;
|
||||
button.value = doUpgrade ? '<%:Upgrade%>' : '<%:Check Update%>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function queryLocalComponentInfo() {
|
||||
var action = document.getElementById('component-action');
|
||||
if (action) {
|
||||
action.innerHTML = '';
|
||||
}
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "component_local_status")%>', {
|
||||
component: selectedComponent(),
|
||||
mirror: selectedMirror()
|
||||
}, function(x, rv) {
|
||||
renderComponentInfo(rv, false);
|
||||
});
|
||||
}
|
||||
|
||||
function onComponentChange() {
|
||||
queryLocalComponentInfo();
|
||||
}
|
||||
|
||||
function onMirrorChange() {
|
||||
var mirror = selectedMirror();
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "component_set_mirror")%>', {
|
||||
mirror: mirror
|
||||
}, function() {
|
||||
queryLocalComponentInfo();
|
||||
});
|
||||
}
|
||||
|
||||
function selectedGeoComponent() {
|
||||
var selector = document.getElementById('geo-selector');
|
||||
return selector ? selector.value : 'country_mmdb';
|
||||
}
|
||||
|
||||
function geoHelp(component) {
|
||||
return geoMeta[component] ? geoMeta[component].help : '';
|
||||
}
|
||||
|
||||
function renderGeoInfo(data) {
|
||||
var panel = document.getElementById('geo-status');
|
||||
var upgradeButton = document.getElementById('geo-upgrade');
|
||||
var action = document.getElementById('geo-action');
|
||||
var help = document.getElementById('geo-help');
|
||||
var html = [];
|
||||
var component = data && data.component ? data.component : selectedGeoComponent();
|
||||
|
||||
if (!panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
panel.innerHTML = '<span style="color:red"><%:Failed to query geo database information.%></span>';
|
||||
if (upgradeButton) upgradeButton.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (help) {
|
||||
help.innerHTML = geoHelp(component);
|
||||
}
|
||||
|
||||
html.push('<div><strong><%:Current Version%>:</strong> ' + (data.current_version || '<%:File Not Exist%>') + '</div>');
|
||||
if (data.component !== 'v2ray_geo' && data.current_version_extra) {
|
||||
html.push('<div><strong><%:Additional Version%>:</strong> ' + data.current_version_extra + '</div>');
|
||||
}
|
||||
if (data.error) {
|
||||
html.push('<div style="color:red"><%:Status%>: ' + displayError(data.error) + '</div>');
|
||||
} else if (data.message) {
|
||||
html.push('<div style="color:green"><%:Status%>: ' + (data.success ? displayMessage(data.message) : (data.message || '<%:Unknown%>')) + '</div>');
|
||||
} else {
|
||||
html.push('<div style="color:#666"><%:Status%>: <%:Ready.%></div>');
|
||||
}
|
||||
|
||||
panel.innerHTML = html.join('');
|
||||
if (upgradeButton) {
|
||||
upgradeButton.disabled = false;
|
||||
}
|
||||
if (action) {
|
||||
action.innerHTML = data.message
|
||||
? '<span style="color:' + (data.success ? 'green' : 'red') + '">' + displayMessage(data.message) + '</span>'
|
||||
: '';
|
||||
}
|
||||
}
|
||||
|
||||
function queryGeoInfo(button) {
|
||||
var component = selectedGeoComponent();
|
||||
var mirror = selectedMirror();
|
||||
var action = document.getElementById('geo-action');
|
||||
var url = '<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "geo_upgrade")%>';
|
||||
|
||||
if (button) {
|
||||
button.disabled = true;
|
||||
button.value = '<%:Checking...%>';
|
||||
}
|
||||
|
||||
if (action) {
|
||||
action.innerHTML = '<%:Processing...%>';
|
||||
}
|
||||
|
||||
XHR.get(url, { component: component, mirror: mirror }, function(x, rv) {
|
||||
renderGeoInfo(rv);
|
||||
if (button) {
|
||||
button.disabled = false;
|
||||
button.value = '<%:CHECK AND UPDATE%>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function queryLocalGeoInfo() {
|
||||
var action = document.getElementById('geo-action');
|
||||
if (action) {
|
||||
action.innerHTML = '';
|
||||
}
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "geo_local_status")%>', {
|
||||
component: selectedGeoComponent(),
|
||||
mirror: selectedMirror()
|
||||
}, function(x, rv) {
|
||||
renderGeoInfo(rv);
|
||||
});
|
||||
}
|
||||
|
||||
function onGeoChange() {
|
||||
queryLocalGeoInfo();
|
||||
}
|
||||
|
||||
window.setTimeout(function() {
|
||||
onComponentChange();
|
||||
onGeoChange();
|
||||
}, 0);
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.component-upgrade-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.component-upgrade-card {
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.component-upgrade-help {
|
||||
margin-top: 6px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.component-upgrade-result {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.component-selector {
|
||||
min-width: 180px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.component-layout {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.component-left {
|
||||
width: 320px;
|
||||
min-width: 260px;
|
||||
}
|
||||
|
||||
.component-right {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.component-select-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.component-select-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.component-select-label {
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.component-select-label strong {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.component-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.component-left {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<fieldset class="cbi-section">
|
||||
<legend><%:Core Components%></legend>
|
||||
<div class="cbi-value">
|
||||
<div class="cbi-value-field component-upgrade-card">
|
||||
<div class="component-layout">
|
||||
<div class="component-left">
|
||||
<div class="component-select-stack">
|
||||
<div class="component-select-item">
|
||||
<div class="component-select-label">
|
||||
<strong><%:Download Source%></strong>
|
||||
<span><%:Address%></span>
|
||||
</div>
|
||||
<select id="component-mirror" class="cbi-input-select component-selector" onchange="onMirrorChange()">
|
||||
<option value="direct" selected="selected"><%:GitHub Direct%></option>
|
||||
<option value="ghproxy">mirror.ghproxy.com</option>
|
||||
<option value="ghproxy_cc">ghproxy.cc</option>
|
||||
<option value="ghfast">ghfast.top</option>
|
||||
<option value="jsdelivr">cdn.jsdelivr.net</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="component-select-item">
|
||||
<div class="component-select-label">
|
||||
<strong><%:Component%></strong>
|
||||
<span><%:Package Name%></span>
|
||||
</div>
|
||||
<select id="component-selector" class="cbi-input-select component-selector" onchange="onComponentChange()">
|
||||
<option value="xray"><%:Xray-core%></option>
|
||||
<option value="mihomo"><%:Mihomo%></option>
|
||||
<option value="naiveproxy"><%:NaiveProxy%></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-right">
|
||||
<div id="component-status"><em><%:Collecting data...%></em></div>
|
||||
<div class="component-upgrade-help" id="component-help"></div>
|
||||
<div class="component-upgrade-actions">
|
||||
<input type="button" class="btn cbi-button cbi-button-reload" value="<%:Check Update%>" onclick="queryComponentInfo(this, false)" />
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" id="component-upgrade" value="<%:Upgrade%>" onclick="queryComponentInfo(this, true)" />
|
||||
</div>
|
||||
<div class="component-upgrade-result" id="component-action"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="cbi-section">
|
||||
<legend><%:Geo Database Update%></legend>
|
||||
<div class="cbi-value">
|
||||
<div class="cbi-value-field component-upgrade-card">
|
||||
<div class="component-layout">
|
||||
<div class="component-left">
|
||||
<div class="component-select-stack">
|
||||
<div class="component-select-item">
|
||||
<div class="component-select-label">
|
||||
<strong><%:Database%></strong>
|
||||
<span><%:Geo Resource%></span>
|
||||
</div>
|
||||
<select id="geo-selector" class="cbi-input-select component-selector" onchange="onGeoChange()">
|
||||
<option value="country_mmdb"><%:Country MMDB%></option>
|
||||
<option value="geosite"><%:GeoSite Database%></option>
|
||||
<option value="v2ray_geo"><%:V2Ray GEO Databases%></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-right">
|
||||
<div id="geo-status"><em><%:Collecting data...%></em></div>
|
||||
<div class="component-upgrade-help" id="geo-help"></div>
|
||||
<div class="component-upgrade-actions">
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" id="geo-upgrade" value="<%:CHECK AND UPDATE%>" onclick="queryGeoInfo(this)" />
|
||||
</div>
|
||||
<div class="component-upgrade-result" id="geo-action"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -1,9 +0,0 @@
|
||||
<style type="text/css">
|
||||
[data-page="admin-services-shadowsocksr-control"] .cbi-section-node-tabbed {
|
||||
min-height: 20rem;
|
||||
}
|
||||
|
||||
[data-page="admin-services-shadowsocksr-control"] .cbi-tabcontainer[data-tab-active="true"] {
|
||||
min-height: 20rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,17 +1,3 @@
|
||||
<%+cbi/valueheader%>
|
||||
<%
|
||||
local hint = self:cfgvalue(section)
|
||||
local cache = self.detect_cache
|
||||
local is_clash = self.type == "clash"
|
||||
local text = is_clash and "N/A" or "--- ms"
|
||||
local color = is_clash and "#999999" or "#ff0000"
|
||||
if not is_clash and cache and tonumber(cache.ping or 0) > 0 then
|
||||
local ping = tonumber(cache.ping)
|
||||
text = tostring(ping) .. " ms"
|
||||
if ping < 300 then color = "#ff3300" end
|
||||
if ping < 200 then color = "#ff7700" end
|
||||
if ping < 100 then color = "#249400" end
|
||||
end
|
||||
%>
|
||||
<span class="pingtime" hint="<%=hint%>" data-sid="<%=section%>"><font style="color:<%=color%>"><%=text%></font></span>
|
||||
<span class="pingtime" hint="<%=self:cfgvalue(section)%>">-- ms</span>
|
||||
<%+cbi/valuefooter%>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,304 +0,0 @@
|
||||
<%-
|
||||
local rowcnt = 0
|
||||
|
||||
function rowstyle()
|
||||
rowcnt = rowcnt + 1
|
||||
if rowcnt % 2 == 0 then
|
||||
return " cbi-rowstyle-1"
|
||||
else
|
||||
return " cbi-rowstyle-2"
|
||||
end
|
||||
end
|
||||
|
||||
function width(o)
|
||||
if o.width then
|
||||
if type(o.width) == "number" then
|
||||
return ' style="width:%dpx"' % o.width
|
||||
end
|
||||
return ' style="width:%s"' % o.width
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
local has_titles = false
|
||||
local has_descriptions = false
|
||||
|
||||
local anonclass = (not self.anonymous or self.sectiontitle) and "named" or "anonymous"
|
||||
local titlename = ifattr(not self.anonymous or self.sectiontitle, "data-title", translate("Name"))
|
||||
|
||||
local i, k
|
||||
for i, k in pairs(self.children) do
|
||||
if not k.typename then
|
||||
k.typename = k.template and k.template:gsub("^.+/", "") or ""
|
||||
end
|
||||
|
||||
if not has_titles and k.title and #k.title > 0 then
|
||||
has_titles = true
|
||||
end
|
||||
|
||||
if not has_descriptions and k.description and #k.description > 0 then
|
||||
has_descriptions = true
|
||||
end
|
||||
end
|
||||
|
||||
local total = tonumber(self.server_total) or 0
|
||||
local page = tonumber(self.server_page) or 1
|
||||
local page_size = tonumber(self.server_page_size) or 0
|
||||
local page_count = tonumber(self.server_page_count) or 1
|
||||
local first_index = tonumber(self.server_first_index) or 0
|
||||
local last_index = tonumber(self.server_last_index) or 0
|
||||
local page_sizes = self.server_page_sizes or {}
|
||||
local base_url = self.server_base_url or ""
|
||||
|
||||
local function page_url(target_page, target_size)
|
||||
return string.format(
|
||||
"%s?server_page=%d&server_page_size=%d",
|
||||
base_url,
|
||||
target_page,
|
||||
target_size
|
||||
)
|
||||
end
|
||||
|
||||
function render_titles()
|
||||
if not has_titles then
|
||||
return
|
||||
end
|
||||
|
||||
%><tr class="tr cbi-section-table-titles <%=anonclass%>"<%=titlename%>><%
|
||||
|
||||
local i, k
|
||||
for i, k in ipairs(self.children) do
|
||||
if not k.optional then
|
||||
%><th class="th cbi-section-table-cell"<%=
|
||||
width(k) .. attr("data-widget", k.typename) %>><%
|
||||
|
||||
if k.titleref then
|
||||
%><a title="<%=self.titledesc or translate("Go to relevant configuration page")%>" class="cbi-title-ref" href="<%=k.titleref%>"><%
|
||||
end
|
||||
|
||||
write(k.title)
|
||||
|
||||
if k.titleref then
|
||||
%></a><%
|
||||
end
|
||||
|
||||
%></th><%
|
||||
end
|
||||
end
|
||||
|
||||
if self.sortable or self.extedit or self.addremove then
|
||||
%><th class="th cbi-section-table-cell cbi-section-actions"></th><%
|
||||
end
|
||||
|
||||
%></tr><%
|
||||
|
||||
rowcnt = rowcnt + 1
|
||||
end
|
||||
|
||||
function render_descriptions()
|
||||
if not has_descriptions then
|
||||
return
|
||||
end
|
||||
|
||||
%><tr class="tr cbi-section-table-descr <%=anonclass%>"><%
|
||||
|
||||
local i, k
|
||||
for i, k in ipairs(self.children) do
|
||||
if not k.optional then
|
||||
%><th class="th cbi-section-table-cell"<%=
|
||||
width(k) .. attr("data-widget", k.typename) %>><%
|
||||
|
||||
write(k.description)
|
||||
|
||||
%></th><%
|
||||
end
|
||||
end
|
||||
|
||||
if self.sortable or self.extedit or self.addremove then
|
||||
%><th class="th cbi-section-table-cell cbi-section-actions"></th><%
|
||||
end
|
||||
|
||||
%></tr><%
|
||||
|
||||
rowcnt = rowcnt + 1
|
||||
end
|
||||
|
||||
function render_pager()
|
||||
if total == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
%>
|
||||
<div class="ssr-server-pager">
|
||||
<span class="ssr-server-pager-summary">
|
||||
<%=string.format(translate("Showing %d-%d of %d nodes"), first_index, last_index, total)%>
|
||||
</span>
|
||||
<span class="ssr-server-pager-pages">
|
||||
<% if page_count > 1 then %>
|
||||
<% if page > 1 then %>
|
||||
<a class="cbi-button cbi-button-link" href="<%=page_url(page - 1, page_size)%>"><%:Prev%></a>
|
||||
<% end %>
|
||||
<% for current = 1, page_count do %>
|
||||
<% if current == page then %>
|
||||
<strong><%=current%></strong>
|
||||
<% else %>
|
||||
<a class="cbi-button cbi-button-link" href="<%=page_url(current, page_size)%>"><%=current%></a>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if page < page_count then %>
|
||||
<a class="cbi-button cbi-button-link" href="<%=page_url(page + 1, page_size)%>"><%:Next%></a>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</span>
|
||||
<span class="ssr-server-pager-sizes">
|
||||
<%:Per page%>:
|
||||
<% for _, size in ipairs(page_sizes) do %>
|
||||
<%
|
||||
local label = size == 0 and translate("All") or tostring(size)
|
||||
local selected = size == page_size
|
||||
%>
|
||||
<% if selected then %>
|
||||
<strong><%=label%></strong>
|
||||
<% else %>
|
||||
<a class="cbi-button cbi-button-link" href="<%=page_url(1, size)%>"><%=label%></a>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</span>
|
||||
</div>
|
||||
<input type="hidden" id="cbi-server-page" value="<%=page%>" />
|
||||
<input type="hidden" id="cbi-server-page-size" value="<%=page_size%>" />
|
||||
<%
|
||||
end
|
||||
-%>
|
||||
|
||||
<style type="text/css">
|
||||
.ssr-server-pager {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem 0.75rem;
|
||||
align-items: center;
|
||||
margin: 0.5rem 0 0.75rem;
|
||||
}
|
||||
|
||||
.ssr-server-pager-pages,
|
||||
.ssr-server-pager-sizes {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.ssr-server-pager .cbi-button-link {
|
||||
padding: 0 0.45rem;
|
||||
min-width: 1.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- tblsection -->
|
||||
<div class="cbi-section cbi-tblsection" id="cbi-<%=self.config%>-<%=self.sectiontype%>">
|
||||
<% if self.title and #self.title > 0 then -%>
|
||||
<h3><%=self.title%></h3>
|
||||
<%- end %>
|
||||
<%- if self.sortable then -%>
|
||||
<input type="hidden" id="cbi.sts.<%=self.config%>.<%=self.sectiontype%>" name="cbi.sts.<%=self.config%>.<%=self.sectiontype%>" value="" />
|
||||
<%- end -%>
|
||||
<div class="cbi-section-descr"><%=self.description%></div>
|
||||
<% render_pager() %>
|
||||
<table class="table cbi-section-table">
|
||||
<%-
|
||||
render_titles()
|
||||
render_descriptions()
|
||||
|
||||
local isempty, section, i, k = true, nil, nil
|
||||
for i, k in ipairs(self:cfgsections()) do
|
||||
isempty = false
|
||||
section = k
|
||||
|
||||
local sectionname = striptags((type(self.sectiontitle) == "function") and self:sectiontitle(section) or k)
|
||||
local sectiontitle = ifattr(sectionname and (not self.anonymous or self.sectiontitle), "data-title", sectionname, true)
|
||||
local colorclass = (self.extedit or self.rowcolors) and rowstyle() or ""
|
||||
local scope = {
|
||||
valueheader = "cbi/cell_valueheader",
|
||||
valuefooter = "cbi/cell_valuefooter"
|
||||
}
|
||||
-%>
|
||||
<tr class="tr cbi-section-table-row<%=colorclass%>" id="cbi-<%=self.config%>-<%=section%>"<%=sectiontitle%>>
|
||||
<%-
|
||||
local node
|
||||
for k, node in ipairs(self.children) do
|
||||
if not node.optional then
|
||||
node:render(section, scope or {})
|
||||
end
|
||||
end
|
||||
-%>
|
||||
|
||||
<%- if self.sortable or self.extedit or self.addremove then -%>
|
||||
<td class="td cbi-section-table-cell nowrap cbi-section-actions">
|
||||
<div>
|
||||
<% if self.extedit then -%>
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:Edit%>"
|
||||
<%- if type(self.extedit) == "string" then
|
||||
%> onclick="location.href='<%=self.extedit:format(section)%>'"
|
||||
<%- elseif type(self.extedit) == "function" then
|
||||
%> onclick="location.href='<%=self:extedit(section)%>'"
|
||||
<%- end
|
||||
%> alt="<%:Edit%>" title="<%:Edit%>" />
|
||||
<% end; if self.addremove then %>
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" value="<%:Delete%>"
|
||||
onclick="
|
||||
var sid = '<%=k%>';
|
||||
XHR.post('<%=luci.dispatcher.build_url("admin/services/shadowsocksr/delete_node")%>', {
|
||||
sid: sid
|
||||
}, function(x, result) {
|
||||
result = result || {};
|
||||
if (result.ret == 1) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
});
|
||||
return false;
|
||||
"
|
||||
/>
|
||||
<%- end -%>
|
||||
</div>
|
||||
</td>
|
||||
<%- end -%>
|
||||
</tr>
|
||||
<%- end -%>
|
||||
|
||||
<%- if isempty then -%>
|
||||
<tr class="tr cbi-section-table-row placeholder">
|
||||
<td class="td"><em><%:This section contains no values yet%></em></td>
|
||||
</tr>
|
||||
<%- end -%>
|
||||
</table>
|
||||
<% render_pager() %>
|
||||
|
||||
<% if self.error then %>
|
||||
<div class="cbi-section-error">
|
||||
<ul><% for _, c in pairs(self.error) do for _, e in ipairs(c) do -%>
|
||||
<li><%=pcdata(e):gsub("\n", "<br />")%></li>
|
||||
<%- end end %></ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%- if self.addremove then -%>
|
||||
<% if self.template_addremove then include(self.template_addremove) else -%>
|
||||
<div class="cbi-section-create cbi-tblsection-create">
|
||||
<% if self.anonymous then %>
|
||||
<input class="btn cbi-button cbi-button-add" type="submit" value="<%:Add%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" title="<%:Add%>" />
|
||||
<% else %>
|
||||
<% if self.invalid_cts then -%>
|
||||
<div class="cbi-section-error"><%:Invalid%></div>
|
||||
<%- end %>
|
||||
<div>
|
||||
<input type="text" class="cbi-section-create-name" id="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" data-type="uciname" data-optional="true" onkeyup="cbi_validate_named_section_add(this)" />
|
||||
</div>
|
||||
<input class="btn cbi-button cbi-button-add" type="submit" onclick="this.form.cbi_state='add-section'; return true" value="<%:Add%>" title="<%:Add%>" disabled="" />
|
||||
<% end %>
|
||||
</div>
|
||||
<%- end %>
|
||||
<%- end -%>
|
||||
</div>
|
||||
<!-- /tblsection -->
|
||||
@@ -1,166 +0,0 @@
|
||||
<style type="text/css">
|
||||
#cbi-shadowsocksr-server_subscribe_item > h3 {
|
||||
font-weight: 800 !important;
|
||||
margin-bottom: 0.45rem !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item > .cbi-section-descr {
|
||||
margin-bottom: 0.7rem !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item > .table {
|
||||
table-layout: fixed !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .th,
|
||||
#cbi-shadowsocksr-server_subscribe_item .td {
|
||||
vertical-align: middle !important;
|
||||
line-height: 1 !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .table.cbi-section-table .th,
|
||||
#cbi-shadowsocksr-server_subscribe_item .table.cbi-section-table .td {
|
||||
padding: 4px 6px !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .cbi-section-table-row {
|
||||
text-align: left !important;
|
||||
height: auto !important;
|
||||
min-height: 0 !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .cbi-section-table-row .td {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .cbi-section-table-row.placeholder {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .cbi-value {
|
||||
margin-bottom: 0;
|
||||
min-height: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-value-field {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item input[type="text"] {
|
||||
width: 100%;
|
||||
height: 2.1rem !important;
|
||||
line-height: 1.15 !important;
|
||||
font-size: 1.08rem !important;
|
||||
padding-top: 0.22rem !important;
|
||||
padding-bottom: 0.22rem !important;
|
||||
padding-left: 0.5rem !important;
|
||||
padding-right: 0.5rem !important;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .cbi-value-field {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .cbi-value label.cbi-value-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-value-field[data-name="enabled"] > div {
|
||||
display: flex;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
min-height: 1.7rem;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-value-field[data-name="enabled"] [data-ui-widget*="Checkbox"] {
|
||||
display: flex !important;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-value-field[data-name="enabled"] input[type="checkbox"] {
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .th:nth-child(1),
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-value-field[data-name="enabled"] {
|
||||
width: 4.2rem !important;
|
||||
min-width: 4.2rem !important;
|
||||
max-width: 4.2rem !important;
|
||||
text-align: center !important;
|
||||
white-space: nowrap;
|
||||
padding-left: 4px !important;
|
||||
padding-right: 2px !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .th:nth-child(2),
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-value-field[data-name="alias"] {
|
||||
width: 10.2rem !important;
|
||||
min-width: 10.2rem !important;
|
||||
max-width: 10.2rem !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .th:nth-child(3),
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-value-field[data-name="url"] {
|
||||
width: auto !important;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .th.cbi-section-actions,
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-section-actions {
|
||||
width: 7.4rem !important;
|
||||
min-width: 7.4rem !important;
|
||||
max-width: 7.4rem !important;
|
||||
text-align: right !important;
|
||||
white-space: nowrap;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-section-actions > div {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-section-actions .btn {
|
||||
height: 2.1rem !important;
|
||||
line-height: 1.1 !important;
|
||||
padding-top: 0.22rem !important;
|
||||
padding-bottom: 0.22rem !important;
|
||||
padding-left: 0.9rem !important;
|
||||
padding-right: 0.9rem !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-value-field[data-name="url"] input[type="text"] {
|
||||
font-family: "Menlo", "Monaco", "Consolas", monospace;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-value-field[data-name="alias"] .cbi-input-text,
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-value-field[data-name="url"] .cbi-input-text {
|
||||
width: 100% !important;
|
||||
max-width: none !important;
|
||||
min-width: 0 !important;
|
||||
display: block !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
#cbi-shadowsocksr-server_subscribe_item .td.cbi-value-field[data-name="alias"] .cbi-input-text {
|
||||
font-size: 0.96rem !important;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
#cbi-shadowsocksr-server_subscribe_item > .table {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,26 +1,9 @@
|
||||
<%+cbi/valueheader%>
|
||||
<%
|
||||
local hint = self:cfgvalue(section)
|
||||
local cache = self.detect_cache
|
||||
local text = "N/A"
|
||||
local color = "#999999"
|
||||
if cache then
|
||||
if cache.socket then
|
||||
text = "ok"
|
||||
color = "#249400"
|
||||
else
|
||||
text = "fail"
|
||||
color = "#ff0000"
|
||||
end
|
||||
end
|
||||
%>
|
||||
<span class="socket-connected" hint="<%=hint%>"><font style="color:<%=color%>"><%=text%></font></span>
|
||||
<span class="socket-connected" hint="<%=self:cfgvalue(section)%>">wait</span>
|
||||
<span class="transport" hint="<%=self.transport%>"></span>
|
||||
<span class="wsPath" hint="<%=self.ws_path%>"></span>
|
||||
<span class="wsHost" hint="<%=self.ws_host%>"></span>
|
||||
<span class="tlsHost" hint="<%=self.tls_host%>"></span>
|
||||
<span class="tls" hint="<%=self.tls%>"></span>
|
||||
<span class="type" hint="<%=self.type%>"></span>
|
||||
<span class="proto" hint="<%=self.proto%>"></span>
|
||||
<span class="reality" hint="<%=self.reality%>"></span>
|
||||
<span class="tlshost" hint="<%=self.tls_host%>"></span>
|
||||
<span class="hy2type" hint="<%=self.type%>"></span>
|
||||
<span class="hy2proto" hint="<%=self.v2ray_protocol%>"></span>
|
||||
<%+cbi/valuefooter%>
|
||||
@@ -1,15 +1,25 @@
|
||||
<%+cbi/valueheader%>
|
||||
<%
|
||||
local map = self.map
|
||||
local ss_type = map:get("@server_subscribe[0]", "ss_type")
|
||||
local xray_hy2_type = map:get("@server_subscribe[0]", "xray_hy2_type")
|
||||
local xray_tj_type = map:get("@server_subscribe[0]", "xray_tj_type")
|
||||
|
||||
local has_ss_rust = luci.sys.exec('type -t -p sslocal 2>/dev/null || type -t -p ssserver 2>/dev/null') ~= ""
|
||||
local has_mihomo = luci.sys.exec('type -t -p mihomo -p /usr/libexec/mihomo 2>/dev/null') ~= ""
|
||||
local has_ss_libev = luci.sys.exec('type -t -p ss-redir 2>/dev/null || type -t -p ss-local 2>/dev/null') ~= ""
|
||||
local has_hysteria = luci.sys.exec('type -t -p hysteria 2>/dev/null') ~= ""
|
||||
local has_trojan = luci.sys.exec('type -t -p trojan 2>/dev/null') ~= ""
|
||||
local has_xray = luci.sys.exec('type -t -p xray 2>/dev/null') ~= ""
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
let ss_type = "<%=ss_type%>"
|
||||
let xray_hy2_type = "<%=xray_hy2_type%>"
|
||||
let xray_tj_type = "<%=xray_tj_type%>"
|
||||
let has_ss_rust = "<%=has_ss_rust%>"
|
||||
let has_mihomo = "<%=has_mihomo%>"
|
||||
let has_ss_libev = "<%=has_ss_libev%>"
|
||||
let has_hysteria = "<%=has_hysteria%>"
|
||||
let has_trojan = "<%=has_trojan%>"
|
||||
let has_xray = "<%=has_xray%>"
|
||||
|
||||
function padright(str, cnt, pad) {
|
||||
@@ -32,13 +42,6 @@ function b64DecodeUnicode(str) {
|
||||
}).join(''));
|
||||
}
|
||||
|
||||
function normalizeHost(host) {
|
||||
if (host && host[0] === "[" && host[host.length - 1] === "]") {
|
||||
return host.slice(1, -1);
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
function b64decutf8safe(str) {
|
||||
var l;
|
||||
str = str.replace(/-/g, "+").replace(/_/g, "/");
|
||||
@@ -122,9 +125,38 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Xray Hysteria2 导入函数
|
||||
function importAsXrayHy2() {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray"
|
||||
// 普通 Hysteria2 导入函数
|
||||
function importAsNormalHy2() {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = (ssu[0] === "hy2") ? "hysteria2" : ssu[0];
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
if (params.get("protocol") && params.get("protocol").trim() !== "") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_transport')[0].checked = true; // 设置 flag_transport 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.flag_transport')[0].dispatchEvent(event); // 触发事件
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport_protocol')[0].value = params.get("protocol") || "udp";
|
||||
}
|
||||
if (params.get("lazy") === "1") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.lazy_mode')[0].checked = true;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.lazy_mode')[0].dispatchEvent(event);
|
||||
}
|
||||
if (params.get("sni") || params.get("alpn")) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true; // 设置 tls 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event); // 触发事件
|
||||
if (params.get("sni")) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("sni") || "";
|
||||
}
|
||||
if (params.get("alpn") && params.get("alpn").trim() !== "") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_alpn')[0].value = params.get("alpn") || "";
|
||||
}
|
||||
}
|
||||
if (params.get("pinSHA256") && params.get("pinSHA256").trim() !== "") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.pinsha256')[0].value = params.get("pinSHA256") || "";
|
||||
}
|
||||
}
|
||||
|
||||
// Xray Hysteria2 导入函数
|
||||
function importAsXrayHy2() {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray"
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].value = (ssu[0] === "hy2") ? "hysteria2" : ssu[0];
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].dispatchEvent(event);
|
||||
@@ -152,14 +184,36 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
}
|
||||
}
|
||||
|
||||
if (has_xray !== "true") {
|
||||
s.innerHTML = "<font style=\'color:red\'><%:No available Xray core to import this Hysteria2 node.%></font>";
|
||||
return false;
|
||||
}
|
||||
importAsXrayHy2();
|
||||
// 主逻辑:智能核心选择(支持回退)
|
||||
function hasCore(core) {
|
||||
if (core === "hysteria") return has_hysteria === "true";
|
||||
if (core === "xray") return has_xray === "true";
|
||||
return false;
|
||||
}
|
||||
let finalMode = null; // true=Xray, false=普通, null=无核心
|
||||
if (xray_hy2_type === "v2ray") {
|
||||
if (hasCore("xray")) finalMode = true;
|
||||
else if (hasCore("hysteria")) finalMode = false;
|
||||
else finalMode = null;
|
||||
} else if (xray_hy2_type === "hysteria2") {
|
||||
if (hasCore("hysteria")) finalMode = false;
|
||||
else if (hasCore("xray")) finalMode = true;
|
||||
else finalMode = null;
|
||||
} else {
|
||||
// auto 或空:Hysteria2 无 type 参数,优先普通 Hysteria2
|
||||
if (hasCore("hysteria")) finalMode = false;
|
||||
else if (hasCore("xray")) finalMode = true;
|
||||
else finalMode = null;
|
||||
}
|
||||
if (finalMode === null) {
|
||||
s.innerHTML = "<font style=\'color:red\'><%:No available core (Hysteria2 or Xray) to import this node.%></font>";
|
||||
return false;
|
||||
}
|
||||
if (finalMode === true) importAsXrayHy2();
|
||||
else importAsNormalHy2();
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = normalizeHost(url.hostname);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port || "443";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.hy2_auth')[0].value = decodeURIComponent(url.username);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.hy2_auth')[0].dispatchEvent(event);
|
||||
@@ -194,9 +248,9 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
|
||||
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
|
||||
return false;
|
||||
case "ss":
|
||||
var url0 = (ssu[1] || "");
|
||||
var param = "";
|
||||
case "ss":
|
||||
var url0 = (ssu[1] || "");
|
||||
var param = "";
|
||||
|
||||
// 先分离 #(alias)
|
||||
var hashIndex = url0.indexOf("#");
|
||||
@@ -213,346 +267,374 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
url0 = url0.substring(0, queryIndex);
|
||||
}
|
||||
|
||||
var params = Object.fromEntries(new URLSearchParams(query));
|
||||
var params = Object.fromEntries(new URLSearchParams(query));
|
||||
|
||||
function decodeURIComponentSafe(value) {
|
||||
try {
|
||||
return decodeURIComponent(value);
|
||||
} catch (e) {
|
||||
return value;
|
||||
function importAsNormalSS() {
|
||||
// 普通 SS 导入逻辑
|
||||
// 判断是否 SIP002 格式(即含 @)
|
||||
if (url0.indexOf("@") !== -1) {
|
||||
// === SIP002 格式 ===
|
||||
var sipIndex = url0.indexOf("@");
|
||||
// 先 URL 解码 base64 再解码
|
||||
var userInfoB64 = decodeURIComponent(url0.substring(0, sipIndex));
|
||||
// console.log("userInfoB64:", userInfoB64);
|
||||
var userInfo = b64decsafe(userInfoB64);
|
||||
if (userInfo && userInfo.indexOf(":") !== -1) {
|
||||
var userInfo = userInfo;
|
||||
} else {
|
||||
var userInfo = userInfoB64;
|
||||
}
|
||||
}
|
||||
|
||||
function decodeBase64Safe(value) {
|
||||
try {
|
||||
return b64decsafe(value);
|
||||
} catch (e) {
|
||||
return "";
|
||||
// 如果没有冒号,且不是 base64 格式,我们可以尝试补齐它
|
||||
if (userInfo.indexOf(":") === -1) {
|
||||
userInfo = "none:" + userInfo;
|
||||
}
|
||||
}
|
||||
|
||||
function parseSSHostPort(serverPart) {
|
||||
serverPart = (serverPart || "").trim();
|
||||
var ipv6Match = serverPart.match(/^\[([^\]]+)\]:(\d+)$/);
|
||||
if (ipv6Match) {
|
||||
return { server: ipv6Match[1], port: ipv6Match[2] };
|
||||
// console.log("userInfo after decode:", userInfo);
|
||||
var userInfoSplitIndex = userInfo.indexOf(":");
|
||||
if(userInfoSplitIndex < 0) {
|
||||
// 格式错误
|
||||
s.innerHTML = "<font style='color:red'><%:Userinfo format error.%></font>";
|
||||
return false;
|
||||
}
|
||||
var method = userInfo.substring(0, userInfoSplitIndex);
|
||||
// console.log("method:", method);
|
||||
var password = userInfo.substring(userInfoSplitIndex + 1);
|
||||
// console.log("password:", password);
|
||||
var serverPart = url0.substring(sipIndex + 1);
|
||||
var serverInfo = serverPart.split(":");
|
||||
|
||||
var colonIndex = serverPart.lastIndexOf(":");
|
||||
if (colonIndex > 0 && serverPart.indexOf(":") === colonIndex) {
|
||||
return {
|
||||
server: serverPart.substring(0, colonIndex),
|
||||
port: serverPart.substring(colonIndex + 1)
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseSSPluginInfo() {
|
||||
var plugin = "";
|
||||
var pluginOpts = "";
|
||||
var server = serverInfo[0];
|
||||
var port = serverInfo[1];
|
||||
|
||||
var plugin = "", pluginOpts = "";
|
||||
if (params.plugin) {
|
||||
var pluginInfo = decodeURIComponentSafe(params.plugin);
|
||||
var pluginParts = pluginInfo.split(";");
|
||||
plugin = pluginParts.shift() || "";
|
||||
pluginOpts = pluginParts.join(";");
|
||||
if (plugin === "simple-obfs") {
|
||||
plugin = "obfs-local";
|
||||
}
|
||||
} else if (params["shadow-tls"]) {
|
||||
var pluginParams = decodeURIComponent(params.plugin).split(";");
|
||||
plugin = pluginParams.shift();
|
||||
pluginOpts = pluginParams.join(";");
|
||||
}
|
||||
} else {
|
||||
// === Base64 SS2022 / 普通格式 的整体编码格式 ===
|
||||
// 先 URL 解码整个字符串
|
||||
var decodedUrl0 = decodeURIComponent(url0);
|
||||
var sstr = b64decsafe(decodedUrl0);
|
||||
if (!sstr) {
|
||||
s.innerHTML = "<font style='color:red'><%:Base64 sstr failed.%></font>";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 支持 SS2022 / 普通格式
|
||||
var regex2022 = /^([^:]+):([^:]+):([^@]+)@([^:]+):(\d+)$/;
|
||||
var regexNormal = /^([^:]+):([^@]+)@([^:]+):(\d+)$/;
|
||||
|
||||
var m2022 = sstr.match(regex2022);
|
||||
var mNormal = sstr.match(regexNormal);
|
||||
|
||||
if (m2022) {
|
||||
var method = m2022[1];
|
||||
var password = m2022[2] + ":" + m2022[3];
|
||||
var server = m2022[4];
|
||||
var port = m2022[5];
|
||||
} else if (mNormal) {
|
||||
var method = mNormal[1];
|
||||
var password = mNormal[2];
|
||||
var server = mNormal[3];
|
||||
var port = mNormal[4];
|
||||
} else {
|
||||
s.innerHTML = "<font style='color:red'><%:SS URL base64 sstr format not recognized.%></font>";
|
||||
return false;
|
||||
}
|
||||
|
||||
var plugin = "", pluginOpts = "";
|
||||
if (params["shadow-tls"]) {
|
||||
try {
|
||||
var decodedTls = JSON.parse(b64decsafe(decodeURIComponentSafe((params["shadow-tls"] || "").replace(/ /g, "+"))));
|
||||
var decoded_tls = JSON.parse(atob(decodeURIComponent(params["shadow-tls"])));
|
||||
plugin = "shadow-tls";
|
||||
var versionFlag = "";
|
||||
if (decodedTls.version && !isNaN(decodedTls.version)) {
|
||||
versionFlag = "v" + decodedTls.version + "=1;";
|
||||
if (decoded_tls.version && !isNaN(decoded_tls.version)) {
|
||||
versionFlag = "v" + decoded_tls.version + "=1;";
|
||||
}
|
||||
pluginOpts = versionFlag + "host=" + (decodedTls.host || "") + ";passwd=" + (decodedTls.password || "");
|
||||
pluginOpts = versionFlag + "host=" + (decoded_tls.host || "") + ";passwd=" + (decoded_tls.password || "");
|
||||
} catch (e) {
|
||||
console.log("shadow-tls decode failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
plugin: plugin,
|
||||
pluginOpts: pluginOpts
|
||||
};
|
||||
}
|
||||
|
||||
function parseSSNode() {
|
||||
var userInfo = "";
|
||||
var serverPart = "";
|
||||
var atIndex = url0.lastIndexOf("@");
|
||||
|
||||
if (atIndex !== -1) {
|
||||
var rawUserInfo = decodeURIComponentSafe(url0.substring(0, atIndex));
|
||||
var decodedUserInfo = decodeBase64Safe(rawUserInfo);
|
||||
userInfo = decodedUserInfo && decodedUserInfo.indexOf(":") !== -1 ? decodedUserInfo : rawUserInfo;
|
||||
serverPart = url0.substring(atIndex + 1);
|
||||
} else {
|
||||
var decodedUrl0 = decodeBase64Safe(decodeURIComponentSafe(url0));
|
||||
if (!decodedUrl0) {
|
||||
return { error: "<%:Base64 sstr failed.%>" };
|
||||
}
|
||||
atIndex = decodedUrl0.lastIndexOf("@");
|
||||
if (atIndex === -1) {
|
||||
return { error: "<%:SS URL base64 sstr format not recognized.%>" };
|
||||
}
|
||||
userInfo = decodedUrl0.substring(0, atIndex);
|
||||
serverPart = decodedUrl0.substring(atIndex + 1);
|
||||
// 判断密码是否经过url编码
|
||||
const isURLEncodedPassword = function(pwd) {
|
||||
if (!/%[0-9A-Fa-f]{2}/.test(pwd)) return false;
|
||||
try {
|
||||
const decoded = decodeURIComponent(pwd.replace(/\+/g, "%20"));
|
||||
const reencoded = encodeURIComponent(decoded);
|
||||
return reencoded === pwd;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (userInfo.indexOf(":") === -1) {
|
||||
userInfo = "none:" + userInfo;
|
||||
}
|
||||
|
||||
var userInfoSplitIndex = userInfo.indexOf(":");
|
||||
if (userInfoSplitIndex < 0) {
|
||||
return { error: "<%:Userinfo format error.%>" };
|
||||
}
|
||||
|
||||
var method = userInfo.substring(0, userInfoSplitIndex);
|
||||
var password = userInfo.substring(userInfoSplitIndex + 1);
|
||||
var serverInfo = parseSSHostPort(serverPart);
|
||||
if (!serverInfo) {
|
||||
return { error: "SS server address format error." };
|
||||
}
|
||||
|
||||
const isURLEncodedPassword = function(pwd) {
|
||||
if (!/%[0-9A-Fa-f]{2}/.test(pwd)) return false;
|
||||
try {
|
||||
const decoded = decodeURIComponent(pwd.replace(/\+/g, "%20"));
|
||||
const reencoded = encodeURIComponent(decoded);
|
||||
return reencoded === pwd;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isURLEncodedPassword(password)) {
|
||||
password = decodeURIComponent(password);
|
||||
}
|
||||
|
||||
var pluginInfo = parseSSPluginInfo();
|
||||
return {
|
||||
method: method,
|
||||
password: password,
|
||||
server: normalizeHost(serverInfo.server),
|
||||
port: serverInfo.port,
|
||||
plugin: pluginInfo.plugin,
|
||||
pluginOpts: pluginInfo.pluginOpts,
|
||||
alias: param != undefined ? decodeURIComponentSafe(param) : ""
|
||||
};
|
||||
}
|
||||
|
||||
function importAsNormalSS(ssNode) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = has_mihomo === "true" ? "ss" : "ss-rust";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = ssNode.server;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = ssNode.port;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = ssNode.password || "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.encrypt_method_ss')[0].value = ssNode.method;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.encrypt_method_ss')[0].dispatchEvent(event);
|
||||
|
||||
if (ssNode.plugin && ssNode.plugin !== "none") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].checked = true;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin')[0].value = ssNode.plugin;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin_opts')[0].value = ssNode.pluginOpts || "";
|
||||
} else {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].checked = false;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].dispatchEvent(event);
|
||||
}
|
||||
|
||||
if (params.tfo === "1") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.fast_open')[0].checked = true;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.fast_open')[0].dispatchEvent(event);
|
||||
}
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = ssNode.alias;
|
||||
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
|
||||
return true;
|
||||
}
|
||||
|
||||
function importAsXraySS(ssNode) {
|
||||
function setElementValue(name, value) {
|
||||
const element = document.getElementsByName(name)[0];
|
||||
if (element) {
|
||||
if (typeof value === 'boolean') {
|
||||
element.checked = value;
|
||||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
function dispatchEventIfExists(name, event) {
|
||||
const element = document.getElementsByName(name)[0];
|
||||
if (element) {
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.alias', ssNode.alias);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.type', "v2ray");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.type', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.v2ray_protocol', "shadowsocks");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.v2ray_protocol', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.server', ssNode.server);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.server_port', ssNode.port || "80");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.password', ssNode.password || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.transport',
|
||||
params.type === "http" ? "h2" :
|
||||
(["xhttp", "splithttp"].includes(params.type) ? "xhttp" :
|
||||
(["tcp", "raw"].includes(params.type) ? "raw" :
|
||||
(params.type || "raw")))
|
||||
);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.transport', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.encrypt_method_ss', ssNode.method || params.encryption || "none");
|
||||
if (params.udp === "1") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.uot', true);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.uot', event);
|
||||
}
|
||||
if (params.fm && params.fm.trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_finalmask', true);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_finalmask', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.finalmask', params.fm || "");
|
||||
}
|
||||
if (params.tfo === "1") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.fast_open', true);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.fast_open', event);
|
||||
}
|
||||
if ([ "tls", "xtls", "reality" ].includes(params.security)) {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.' + params.security, true);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.' + params.security, event);
|
||||
|
||||
if (params.security === "tls") {
|
||||
if (params.ech && params.ech.trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_ech', true);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_ech', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ech_config', params.ech || "");
|
||||
}
|
||||
var allowInsecureValue = params.allowInsecure || params.insecure;
|
||||
if (allowInsecureValue === "1" || allowInsecureValue === "true") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.insecure', true);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.insecure', event);
|
||||
}
|
||||
if (params.pcs && params.pcs.trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_CertSha', params.pcs);
|
||||
}
|
||||
if (params.vcn && params.vcn.trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_CertByName', params.vcn);
|
||||
}
|
||||
}
|
||||
if (params.security === "reality") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_publickey', params.pbk ? decodeURIComponentSafe(params.pbk) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_shortid', params.sid || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_spiderx', params.spx ? decodeURIComponentSafe(params.spx) : "");
|
||||
if (params.pqv && params.pqv.trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_mldsa65verify', true);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_mldsa65verify', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_mldsa65verify', params.pqv || "");
|
||||
}
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_flow', params.flow || "none");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tls_flow', event);
|
||||
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_alpn', params.alpn || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.fingerprint', params.fp || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_host', params.sni || "");
|
||||
}
|
||||
switch (params.type) {
|
||||
case "ws":
|
||||
if (params.security !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ws_host', params.host ? decodeURIComponentSafe(params.host) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ws_path', params.path ? decodeURIComponentSafe(params.path) : "/");
|
||||
break;
|
||||
case "httpupgrade":
|
||||
if (params.security !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_host', params.host ? decodeURIComponentSafe(params.host) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_path', params.path ? decodeURIComponentSafe(params.path) : "/");
|
||||
break;
|
||||
case "xhttp":
|
||||
case "splithttp":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_mode', params.mode || "auto");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_host', params.host ? decodeURIComponentSafe(params.host) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_path', params.path ? decodeURIComponentSafe(params.path) : "/");
|
||||
if (params.extra && params.extra.trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', true);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_extra', params.extra || "");
|
||||
}
|
||||
break;
|
||||
case "kcp":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.kcp_guise', params.headerType || "none");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.kcp_guise', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.kcp_domain', params.host || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.seed', params.seed || "");
|
||||
break;
|
||||
case "http":
|
||||
case "h2":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.h2_host', params.host ? decodeURIComponentSafe(params.host) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.h2_path', params.path ? decodeURIComponentSafe(params.path) : "");
|
||||
break;
|
||||
case "quic":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_guise', params.headerType || "none");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_security', params.quicSecurity || "none");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_key', params.key || "");
|
||||
break;
|
||||
case "grpc":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.serviceName', params.serviceName || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.grpc_mode', params.mode || "gun");
|
||||
break;
|
||||
case "tcp":
|
||||
case "raw":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tcp_guise', params.headerType || "none");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tcp_guise', event);
|
||||
if (params.headerType === "http") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.http_host', params.host ? decodeURIComponentSafe(params.host) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.http_path', params.path ? decodeURIComponentSafe(params.path) : "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
|
||||
return true;
|
||||
}
|
||||
|
||||
function preferredSSBackend() {
|
||||
if (has_mihomo === "true") return "ss";
|
||||
if (has_ss_rust === "true") return "ss-rust";
|
||||
if (has_xray === "true") return "v2ray";
|
||||
return null;
|
||||
}
|
||||
|
||||
var ssNode = parseSSNode();
|
||||
if (ssNode.error) {
|
||||
s.innerHTML = "<font style='color:red'>" + ssNode.error + "</font>";
|
||||
return false;
|
||||
}
|
||||
|
||||
let finalSSBackend;
|
||||
const finalXray = (params.security && params.security !== "none") || // TLS/XTLS/Reality
|
||||
(params.type && ["ws", "httpupgrade", "xhttp", "splithttp", "grpc", "quic", "kcp", "http", "h2"].includes(params.type));
|
||||
if (finalXray && has_xray === "true") {
|
||||
finalSSBackend = "v2ray";
|
||||
if (isURLEncodedPassword(password)) {
|
||||
password = decodeURIComponent(password); // 解码URL编码
|
||||
} else {
|
||||
finalSSBackend = preferredSSBackend();
|
||||
password = password; // 保持原始值
|
||||
}
|
||||
if (finalSSBackend === null) {
|
||||
s.innerHTML = "<font style=\'color:red\'><%:No available core (Shadowsocks or Xray) to import this node.%></font>";
|
||||
|
||||
// === 填充配置项 ===
|
||||
//var has_ss_type = (ss_type === "ss-rust") ? "ss-rust" : "ss-libev";
|
||||
// 设置 _ss_core 的值
|
||||
var ssCore = (ss_type === "ss-rust" || ss_type === "ss-libev")
|
||||
? ss_type
|
||||
: (has_ss_rust ? "ss-rust" : (has_ss_libev ? "ss-libev" : "ss-libev"));
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = ssu[0];
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '._ss_core')[0].value = ssCore;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '._ss_core')[0].dispatchEvent(event);
|
||||
|
||||
//document.getElementsByName('cbid.shadowsocksr.' + sid + '.has_ss_type')[0].value = has_ss_type;
|
||||
//document.getElementsByName('cbid.shadowsocksr.' + sid + '.has_ss_type')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = server;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = port;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = password || "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.encrypt_method_ss')[0].value = method;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.encrypt_method_ss')[0].dispatchEvent(event);
|
||||
|
||||
if (plugin && plugin !== "none") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].checked = true; // 设置 enable_plugin 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].dispatchEvent(event); // 触发事件
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin')[0].value = plugin;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin')[0].dispatchEvent(event);
|
||||
if (plugin !== undefined) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin_opts')[0].value = pluginOpts || "";
|
||||
}
|
||||
} else {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].checked = false;
|
||||
}
|
||||
|
||||
//if (params.udp === "1") {
|
||||
// document.getElementsByName('cbid.shadowsocksr.' + sid + '.uot')[0].checked = true; // 设置 uot 为 true
|
||||
// document.getElementsByName('cbid.shadowsocksr.' + sid + '.uot')[0].dispatchEvent(event); // 触发事件
|
||||
//}
|
||||
|
||||
if (params.tfo === "1") {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.fast_open')[0].checked = true; // 设置 fast_open 为 true
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.fast_open')[0].dispatchEvent(event); // 触发事件
|
||||
}
|
||||
|
||||
if (param != undefined) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = decodeURIComponent(param);
|
||||
}
|
||||
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
|
||||
return true;
|
||||
}
|
||||
|
||||
function importAsXraySS() {
|
||||
try {
|
||||
// Xray SS 导入逻辑
|
||||
// 拆分 @,判断是否是 base64 userinfo 的格式
|
||||
var parts = url0.split("@");
|
||||
if (parts.length > 1) {
|
||||
// @ 前是 base64(method:password),后面是 server:port?params
|
||||
var userinfo = b64decsafe(parts[0]);
|
||||
var sepIndex = userinfo.indexOf(":");
|
||||
if (sepIndex > -1) {
|
||||
method = userinfo.slice(0, sepIndex);
|
||||
password = userinfo.slice(sepIndex + 1); //一些链接用明文uuid做密码
|
||||
}
|
||||
}
|
||||
// 已编码的别名
|
||||
// var url = new URL("http://" + url0 + (param ? "#" + encodeURIComponent(param) : ""));
|
||||
// 未编码的别名
|
||||
// var url = new URL("http://" + url0 + (param ? "#" + param : ""));
|
||||
// 兼容已编码和未编码的别名
|
||||
var remarks = param ? decodeURIComponent(param) : "";
|
||||
var encodedremarks = encodeURIComponent(remarks);
|
||||
var url = new URL("http://" + url0 + (encodedremarks ? "#" + encodedremarks : ""));
|
||||
|
||||
} catch(e) {
|
||||
alert(e);
|
||||
return false;
|
||||
}
|
||||
if (finalSSBackend === "v2ray" && ssNode.plugin && ssNode.plugin !== "none") {
|
||||
s.innerHTML = "<font style='color:red'>Xray fallback does not support importing Shadowsocks links with SIP003 plugins.</font>";
|
||||
return false;
|
||||
// Check if the elements exist before trying to modify them
|
||||
function setElementValue(name, value) {
|
||||
const element = document.getElementsByName(name)[0];
|
||||
if (element) {
|
||||
if (typeof value === 'boolean') {
|
||||
element.checked = value;
|
||||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (finalSSBackend === "v2ray") importAsXraySS(ssNode);
|
||||
else importAsNormalSS(ssNode);
|
||||
function dispatchEventIfExists(name, event) {
|
||||
const element = document.getElementsByName(name)[0];
|
||||
if (element) {
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.alias', url.hash ? decodeURIComponent(url.hash.slice(1)) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.type', "v2ray");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.type', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.v2ray_protocol', "shadowsocks");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.v2ray_protocol', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.server', url.hostname);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.server_port', url.port || "80");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.password', password || url.username);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.transport',
|
||||
params.type === "http" ? "h2" :
|
||||
(["xhttp", "splithttp"].includes(params.type) ? "xhttp" :
|
||||
(["tcp", "raw"].includes(params.type) ? "raw" :
|
||||
(params.type || "raw")))
|
||||
);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.transport', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.encrypt_method_ss', method || params.encryption || "none");
|
||||
if (params.udp === "1") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.uot', true); // 设置 uot 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.uot', event); // 触发事件
|
||||
}
|
||||
if (params.fm && params.fm.trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_finalmask', true); // 设置 enable_finalmask 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_finalmask', event); // 触发事件
|
||||
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.finalmask', params.fm || "");
|
||||
}
|
||||
if (params.tfo === "1") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.fast_open', true); // 设置 fast_open 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.fast_open', event); // 触发事件
|
||||
}
|
||||
if ([ "tls", "xtls", "reality" ].includes(params.security)) {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.' + params.security, true);
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.' + params.security, event);
|
||||
|
||||
if (params.security === "tls") {
|
||||
if (params.ech && params.ech.trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_ech', true); // 设置 enable_ech 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_ech', event); // 触发事件
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ech_config', params.ech || "");
|
||||
}
|
||||
var allowInsecureValue = params.allowInsecure || params.insecure;
|
||||
if (allowInsecureValue === "1" || allowInsecureValue === "true") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.insecure', true); // 设置 insecure 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.insecure', event); // 触发事件
|
||||
}
|
||||
if (params.pcs && params.pcs.trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_CertSha', params.pcs);
|
||||
}
|
||||
if (params.vcn && params.vcn.trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_CertByName', params.vcn);
|
||||
}
|
||||
}
|
||||
if (params.security === "reality") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_publickey', params.pbk ? decodeURIComponent(params.pbk) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_shortid', params.sid || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_spiderx', params.spx ? decodeURIComponent(params.spx) : "");
|
||||
if (params.pqv && params.pqv.trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_mldsa65verify', true); // 设置 enable_mldsa65verify 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_mldsa65verify', event); // 触发事件
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.reality_mldsa65verify', params.pqv || "");
|
||||
}
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_flow', params.flow || "none");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tls_flow', event);
|
||||
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_alpn', params.alpn || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.fingerprint', params.fp || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tls_host', params.sni || "");
|
||||
}
|
||||
switch (params.type) {
|
||||
case "ws":
|
||||
if (params.security !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ws_host', params.host ? decodeURIComponent(params.host) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.ws_path', params.path ? decodeURIComponent(params.path) : "/");
|
||||
break;
|
||||
case "httpupgrade":
|
||||
if (params.security !== "tls") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_host', params.host ? decodeURIComponent(params.host) : "");
|
||||
}
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_path', params.path ? decodeURIComponent(params.path) : "/");
|
||||
break;
|
||||
case "xhttp":
|
||||
case "splithttp":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_mode', params.mode || "auto");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_host', params.host ? decodeURIComponent(params.host) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_path', params.path ? decodeURIComponent(params.path) : "/");
|
||||
if (params.extra && params.extra.trim() !== "") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', true); // 设置 enable_xhttp_extra 为 true
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', event); // 触发事件
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_extra', params.extra || "");
|
||||
}
|
||||
break;
|
||||
case "kcp":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.kcp_guise', params.headerType || "none");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.kcp_guise', event);
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.kcp_domain', params.host || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.seed', params.seed || "");
|
||||
break;
|
||||
case "http":
|
||||
/* this is non-standard, bullshit */
|
||||
case "h2":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.h2_host', params.host ? decodeURIComponent(params.host) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.h2_path', params.path ? decodeURIComponent(params.path) : "");
|
||||
break;
|
||||
case "quic":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_guise', params.headerType || "none");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_security', params.quicSecurity || "none");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.quic_key', params.key || "");
|
||||
break;
|
||||
case "grpc":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.serviceName', params.serviceName || "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.grpc_mode', params.mode || "gun");
|
||||
break;
|
||||
case "tcp":
|
||||
case "raw":
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.tcp_guise', params.headerType || "none");
|
||||
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tcp_guise', event);
|
||||
if (params.headerType === "http") {
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.http_host', params.host ? decodeURIComponent(params.host) : "");
|
||||
setElementValue('cbid.shadowsocksr.' + sid + '.http_path', params.path ? decodeURIComponent(params.path) : "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
|
||||
return true;
|
||||
}
|
||||
|
||||
// 主逻辑:智能核心选择(支持回退)
|
||||
function hasSSCore() { return has_ss_rust === "true" || has_ss_libev === "true"; }
|
||||
function hasXrayCore() { return has_xray === "true"; }
|
||||
let finalSSType = null; // true=Xray, false=普通SS, null=无核心
|
||||
if (ss_type === "v2ray") {
|
||||
if (hasXrayCore()) finalSSType = true;
|
||||
else if (hasSSCore()) finalSSType = false;
|
||||
else finalSSType = null;
|
||||
} else if (ss_type === "ss-rust" || ss_type === "ss-libev") {
|
||||
let userSSCore = (ss_type === "ss-rust" && has_ss_rust === "true") || (ss_type === "ss-libev" && has_ss_libev === "true");
|
||||
if (userSSCore) finalSSType = false;
|
||||
else {
|
||||
let otherSSCore = (ss_type === "ss-rust" && has_ss_libev === "true") || (ss_type === "ss-libev" && has_ss_rust === "true");
|
||||
if (otherSSCore) finalSSType = false;
|
||||
else if (hasXrayCore()) finalSSType = true;
|
||||
else finalSSType = null;
|
||||
}
|
||||
} else {
|
||||
// auto 或空:根据 type 参数决定
|
||||
let hasType = params.type && params.type !== "";
|
||||
if (hasType) {
|
||||
if (hasXrayCore()) finalSSType = true;
|
||||
else if (hasSSCore()) finalSSType = false;
|
||||
else finalSSType = null;
|
||||
} else {
|
||||
if (hasSSCore()) finalSSType = false;
|
||||
else if (hasXrayCore()) finalSSType = true;
|
||||
else finalSSType = null;
|
||||
}
|
||||
}
|
||||
if (finalSSType === null) {
|
||||
s.innerHTML = "<font style=\'color:red\'><%:No available core (Shadowsocks or Xray) to import this node.%></font>";
|
||||
return false;
|
||||
}
|
||||
if (finalSSType === true) importAsXraySS();
|
||||
else importAsNormalSS();
|
||||
return false;
|
||||
case "ssr":
|
||||
var sstr = b64decsafe((ssu[1] || "").replace(/#.*/, "").trim());
|
||||
var ploc = sstr.indexOf("/?");
|
||||
@@ -603,7 +685,7 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "trojan";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = normalizeHost(url.hostname);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port || "80";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = decodeURIComponent(url.username);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
|
||||
@@ -632,7 +714,7 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].value = "trojan";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = normalizeHost(url.hostname);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port || "80";
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = decodeURIComponent(url.username);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
|
||||
@@ -732,13 +814,36 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 主逻辑:Trojan 统一使用 Xray
|
||||
// 主逻辑:智能核心选择(支持回退)
|
||||
function hasTrojanCore() { return has_trojan === "true"; }
|
||||
function hasXrayCore() { return has_xray === "true"; }
|
||||
if (!hasXrayCore()) {
|
||||
s.innerHTML = "<font style=\'color:red\'><%:No available core (Xray) to import this node.%></font>";
|
||||
let finalTrojanMode = null;
|
||||
if (xray_tj_type === "v2ray") {
|
||||
if (hasXrayCore()) finalTrojanMode = true;
|
||||
else if (hasTrojanCore()) finalTrojanMode = false;
|
||||
else finalTrojanMode = null;
|
||||
} else if (xray_tj_type === "trojan") {
|
||||
if (hasTrojanCore()) finalTrojanMode = false;
|
||||
else if (hasXrayCore()) finalTrojanMode = true;
|
||||
else finalTrojanMode = null;
|
||||
} else {
|
||||
let hasType = params.get("type") && params.get("type") !== "";
|
||||
if (hasType) {
|
||||
if (hasXrayCore()) finalTrojanMode = true;
|
||||
else if (hasTrojanCore()) finalTrojanMode = false;
|
||||
else finalTrojanMode = null;
|
||||
} else {
|
||||
if (hasTrojanCore()) finalTrojanMode = false;
|
||||
else if (hasXrayCore()) finalTrojanMode = true;
|
||||
else finalTrojanMode = null;
|
||||
}
|
||||
}
|
||||
if (finalTrojanMode === null) {
|
||||
s.innerHTML = "<font style=\'color:red\'><%:No available core (Trojan or Xray) to import this node.%></font>";
|
||||
return false;
|
||||
}
|
||||
importAsXrayTrojan();
|
||||
if (finalTrojanMode === true) importAsXrayTrojan();
|
||||
else importAsNormalTrojan();
|
||||
return false;
|
||||
case "vmess":
|
||||
var sstr = b64DecodeUnicode((ssu[1] || "").replace(/#.*/, "").trim());
|
||||
@@ -871,7 +976,7 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
var server = normalizeHost(url.hostname);
|
||||
var server = url.hostname;
|
||||
/* 如果带 [],再去掉 [] */
|
||||
if (server[0] === "[" && server[server.length - 1] === "]") {
|
||||
server = server.slice(1, -1);
|
||||
@@ -1036,7 +1141,7 @@ function import_ssr_url(btn, urlname, sid) {
|
||||
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = ssu[0];
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = normalizeHost(url.hostname);
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_uuid')[0].value = method;
|
||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_ip')[0].value = params.ip || "";
|
||||
|
||||
@@ -4,8 +4,7 @@ XHR.poll(3, '<%=url([[admin]], [[services]], [[shadowsocksr]], [[run]])%>', null
|
||||
var tb = document.getElementById('shadowsocksr_status');
|
||||
if (data && tb) {
|
||||
if (data.running) {
|
||||
var text = data.status_text || '<%:RUNNING%>';
|
||||
var links = '<em><b style=color:green>ShadowsocksR Plus+ ' + text + '</b></em>';
|
||||
var links = '<em><b style=color:green>ShadowsocksR Plus+ <%:RUNNING%></b></em>';
|
||||
tb.innerHTML = links;
|
||||
} else {
|
||||
tb.innerHTML = '<em><b style=color:red>ShadowsocksR Plus+ <%:NOT RUNNING%></b></em>';
|
||||
|
||||
@@ -1,257 +1,18 @@
|
||||
<%+cbi/valueheader%>
|
||||
<%
|
||||
local dsp = require "luci.dispatcher"
|
||||
local subscribe_url = dsp.build_url("admin", "services", "shadowsocksr", "subscribe")
|
||||
local get_log_url = dsp.build_url("admin", "services", "shadowsocksr", "get_log")
|
||||
local servers_url = dsp.build_url("admin", "services", "shadowsocksr", "servers")
|
||||
local autostart = self.map and self.map.ssr_subscribe_autostart
|
||||
-%>
|
||||
<div id="ssr-subscribe-modal-wrap">
|
||||
<style type="text/css">
|
||||
#ssr-subscribe-modal-mask {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
#ssr-subscribe-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: min(900px, calc(100vw - 32px));
|
||||
max-height: min(720px, calc(100vh - 80px));
|
||||
overflow: auto;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.18);
|
||||
z-index: 2001;
|
||||
padding: 12px 16px 14px;
|
||||
}
|
||||
|
||||
#ssr-subscribe-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#ssr-subscribe-modal-title {
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#ssr-subscribe-modal-status {
|
||||
margin: 0 0 8px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#ssr-subscribe-modal-status.ssr-subscribe-highlight {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#ssr-subscribe-log {
|
||||
margin: 0;
|
||||
padding: 14px 16px;
|
||||
min-height: 420px;
|
||||
max-height: min(520px, calc(100vh - 220px));
|
||||
overflow: auto;
|
||||
background: #111827;
|
||||
color: #e5e7eb;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font: 13px/1.5 Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
#ssr-subscribe-modal {
|
||||
width: calc(100vw - 16px);
|
||||
max-height: calc(100vh - 24px);
|
||||
padding: 10px 12px 12px;
|
||||
}
|
||||
|
||||
#ssr-subscribe-modal-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#ssr-subscribe-log {
|
||||
max-height: calc(100vh - 260px);
|
||||
min-height: 280px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
(function() {
|
||||
var pollTimer = null;
|
||||
var running = false;
|
||||
var autoStart = <%=autostart and "true" or "false"%>;
|
||||
|
||||
function getEl(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function setStatus(text, color) {
|
||||
var status = getEl('ssr-subscribe-modal-status');
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
status.className = 'ssr-subscribe-highlight';
|
||||
status.style.color = color || '#666';
|
||||
status.textContent = text;
|
||||
}
|
||||
|
||||
function updateLog(text) {
|
||||
var log = getEl('ssr-subscribe-log');
|
||||
if (!log) {
|
||||
return;
|
||||
}
|
||||
log.textContent = text || '';
|
||||
log.scrollTop = log.scrollHeight;
|
||||
}
|
||||
|
||||
function appendLog(text) {
|
||||
var log = getEl('ssr-subscribe-log');
|
||||
if (!log || !text) {
|
||||
return;
|
||||
}
|
||||
log.textContent = (log.textContent ? log.textContent.replace(/\s*$/, '') + '\n' : '') + text;
|
||||
log.scrollTop = log.scrollHeight;
|
||||
}
|
||||
|
||||
function clearPoll() {
|
||||
if (pollTimer) {
|
||||
window.clearInterval(pollTimer);
|
||||
pollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function fetchLog() {
|
||||
XHR.get('<%=get_log_url%>', null, function(x) {
|
||||
if (x && x.status === 200) {
|
||||
updateLog(x.responseText || '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
var mask = getEl('ssr-subscribe-modal-mask');
|
||||
var modal = getEl('ssr-subscribe-modal');
|
||||
if (mask) {
|
||||
mask.style.display = 'block';
|
||||
}
|
||||
if (modal) {
|
||||
modal.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal(force) {
|
||||
if (running && !force) {
|
||||
return false;
|
||||
}
|
||||
clearPoll();
|
||||
var mask = getEl('ssr-subscribe-modal-mask');
|
||||
var modal = getEl('ssr-subscribe-modal');
|
||||
if (mask) {
|
||||
mask.style.display = 'none';
|
||||
}
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function finishSubscribe(success) {
|
||||
running = false;
|
||||
clearPoll();
|
||||
fetchLog();
|
||||
if (success) {
|
||||
var remain = 5;
|
||||
appendLog('订阅已结束,将在倒数5秒后返回页面');
|
||||
setStatus('订阅已结束,将在倒数' + remain + '秒后返回页面', '#249400');
|
||||
var countdown = window.setInterval(function() {
|
||||
remain = remain - 1;
|
||||
if (remain <= 0) {
|
||||
window.clearInterval(countdown);
|
||||
closeModal(true);
|
||||
window.location.href = '<%=servers_url%>';
|
||||
return;
|
||||
}
|
||||
setStatus('订阅已结束,将在倒数' + remain + '秒后返回页面', '#249400');
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
setStatus('<%:Subscribe failed, refreshing page...%>', '#ff0000');
|
||||
window.setTimeout(function() {
|
||||
closeModal(true);
|
||||
window.location.href = '<%=servers_url%>';
|
||||
}, 1200);
|
||||
}
|
||||
|
||||
function startSubscribe(options, done) {
|
||||
options = options || {};
|
||||
running = true;
|
||||
updateLog('');
|
||||
setStatus(options.title || '<%:Starting subscription update...%>', '#0072c3');
|
||||
openModal();
|
||||
|
||||
clearPoll();
|
||||
fetchLog();
|
||||
pollTimer = window.setInterval(fetchLog, 1000);
|
||||
|
||||
XHR.get('<%=subscribe_url%>', options.sid ? { sid: options.sid } : null, function(x, rv) {
|
||||
var ok = !!(rv && typeof rv.ret !== 'undefined' && Number(rv.ret) === 0);
|
||||
if (typeof done === 'function') {
|
||||
done(ok);
|
||||
}
|
||||
finishSubscribe(ok);
|
||||
});
|
||||
}
|
||||
|
||||
window.ssrSubscribeRun = startSubscribe;
|
||||
|
||||
window.setTimeout(function() {
|
||||
var closeBtn = getEl('ssr-subscribe-modal-close');
|
||||
var mask = getEl('ssr-subscribe-modal-mask');
|
||||
|
||||
if (closeBtn) {
|
||||
closeBtn.onclick = function() {
|
||||
return closeModal(false);
|
||||
};
|
||||
}
|
||||
if (mask) {
|
||||
mask.onclick = function() {
|
||||
return closeModal(false);
|
||||
};
|
||||
}
|
||||
|
||||
if (autoStart) {
|
||||
startSubscribe();
|
||||
}
|
||||
}, 0);
|
||||
})();
|
||||
//]]></script>
|
||||
|
||||
<input class="btn cbi-button cbi-button-apply" type="submit"<%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> />
|
||||
|
||||
<div id="ssr-subscribe-modal-mask"></div>
|
||||
<div id="ssr-subscribe-modal" role="dialog" aria-modal="true" aria-labelledby="ssr-subscribe-modal-title">
|
||||
<div id="ssr-subscribe-modal-header">
|
||||
<div id="ssr-subscribe-modal-title"><%:Subscribe Log%></div>
|
||||
<input type="button" id="ssr-subscribe-modal-close" class="btn cbi-button" value="<%:Close%>" />
|
||||
</div>
|
||||
<div id="ssr-subscribe-modal-status"><%:Ready.%></div>
|
||||
<pre id="ssr-subscribe-log"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
function subscribe(btn,dataname) {
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Refresh...%> ';
|
||||
murl=dataname;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr","subscribe")%>', { set:murl }, function(x,rv) {
|
||||
// 先简单刷新,后期如果重构会考虑下如何组织lua shell JavaScript之间的代码逻辑和各自的调用逻辑
|
||||
window.location.reload()
|
||||
// btn.disabled = false;
|
||||
// btn.value = '<%:Refresh Data %>';
|
||||
});
|
||||
return false;
|
||||
}
|
||||
//]]></script>
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Update All Subscribe Servers%> " onclick="return subscribe(this,'<%=self.option%>')" />
|
||||
<!-- <span id="<%=self.option%>-status"><em><%=self.value%></em></span> -->
|
||||
<%+cbi/valuefooter%>
|
||||
@@ -1,490 +0,0 @@
|
||||
<%-
|
||||
local rowcnt = 0
|
||||
|
||||
local function rowstyle()
|
||||
rowcnt = rowcnt + 1
|
||||
if rowcnt % 2 == 0 then
|
||||
return " cbi-rowstyle-1"
|
||||
end
|
||||
return " cbi-rowstyle-2"
|
||||
end
|
||||
|
||||
local function width(o)
|
||||
if o.width then
|
||||
if type(o.width) == "number" then
|
||||
return ' style="width:%dpx"' % o.width
|
||||
end
|
||||
return ' style="width:%s"' % o.width
|
||||
end
|
||||
return ""
|
||||
end
|
||||
-%>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
window.ssrUpdateSubscribeItem = window.ssrUpdateSubscribeItem || function(btn) {
|
||||
if (!btn) return false;
|
||||
|
||||
var sid = btn.getAttribute('data-sid') || '';
|
||||
var title = btn.getAttribute('data-title') || '';
|
||||
var original = btn.getAttribute('data-original-value') || btn.value || 'Update';
|
||||
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Updating...%>';
|
||||
window.ssrSubscribeRun = window.ssrSubscribeRun || function(_, done) {
|
||||
if (done) done();
|
||||
};
|
||||
window.ssrSubscribeRun({
|
||||
sid: sid,
|
||||
title: title
|
||||
}, function() {
|
||||
btn.disabled = false;
|
||||
btn.value = original;
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
function saveAllSubscribeItems(btn) {
|
||||
// var btn = document.querySelector('#ssr-subscribe-toolbar .cbi-button-save');
|
||||
if (!btn) return;
|
||||
|
||||
var originalText = btn.value;
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Saving...%>';
|
||||
|
||||
var fieldsMap = {};
|
||||
var allInputs = document.querySelectorAll('[name^="cbid.shadowsocksr."]');
|
||||
allInputs.forEach(function(input) {
|
||||
var match = input.name.match(/^cbid\.shadowsocksr\.([^.]+)\.(url|alias|enabled)$/);
|
||||
if (!match) return;
|
||||
|
||||
var sid = match[1];
|
||||
var field = match[2];
|
||||
if (!fieldsMap[sid]) fieldsMap[sid] = {};
|
||||
if (field === 'enabled') {
|
||||
fieldsMap[sid][field] = input.checked ? '1' : '0';
|
||||
} else {
|
||||
fieldsMap[sid][field] = input.value;
|
||||
}
|
||||
});
|
||||
|
||||
var promises = [];
|
||||
for (var sid in fieldsMap) {
|
||||
var fields = fieldsMap[sid];
|
||||
for (var field in fields) {
|
||||
var value = fields[field];
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
XHR.post(
|
||||
'<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "toggle_subscribe_item_enabled")%>',
|
||||
{ sid: sid, field: field, value: value },
|
||||
function(x, rv) {
|
||||
if (rv && Number(rv.ret) === 1) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error('<%:Save failed:%> ' + sid + '.' + field + ': ' + (rv && rv.error ? rv.error : '<%:Unknown error%>')));
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
promises.push(promise);
|
||||
}
|
||||
}
|
||||
|
||||
if (promises.length === 0) {
|
||||
btn.disabled = false;
|
||||
btn.value = originalText;
|
||||
alert('<%:No subscription items found%>');
|
||||
return;
|
||||
}
|
||||
|
||||
Promise.all(promises).then(function() {
|
||||
btn.disabled = false;
|
||||
btn.value = originalText;
|
||||
alert('<%:All settings saved successfully.%>');
|
||||
}).catch(function(err) {
|
||||
btn.disabled = false;
|
||||
btn.value = originalText;
|
||||
alert('<%:Save failed:%>' + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteSubscribeItem(btn) {
|
||||
var sid = btn.getAttribute('data-sid');
|
||||
if (!sid) return;
|
||||
if (!confirm('<%:Are you sure you want to delete subscribe link: %s ?%>'.replace('%s', sid))) return;
|
||||
|
||||
btn.disabled = true;
|
||||
XHR.post('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "delete_subscribe_item")%>',
|
||||
{ sid: sid },
|
||||
function(x, rv) {
|
||||
btn.disabled = false;
|
||||
if (rv && rv.ret === 1) {
|
||||
var row = btn.closest('tr');
|
||||
if (row) row.remove();
|
||||
var table = document.querySelector('#cbi-shadowsocksr-server_subscribe_item .cbi-section-table');
|
||||
if (table.querySelectorAll('.cbi-section-table-row:not(.cbi-section-table-titles)').length === 0) {
|
||||
var placeholder = document.createElement('tr');
|
||||
placeholder.className = 'tr cbi-section-table-row placeholder';
|
||||
placeholder.innerHTML = '<td class="td" colspan="4"><em><%:This section contains no values yet%></em></td>';
|
||||
table.appendChild(placeholder);
|
||||
}
|
||||
} else {
|
||||
alert('<%:Delete failed:%> ' + (rv ? rv.error : '<%:Unknown error%>'));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createEmptyRow(sid) {
|
||||
var templateRow = document.querySelector('#cbi-shadowsocksr-server_subscribe_item .cbi-section-table-row:not(.cbi-section-table-titles):not(.placeholder)');
|
||||
if (!templateRow) {
|
||||
console.error('No template row found');
|
||||
return null;
|
||||
}
|
||||
|
||||
var newRow = templateRow.cloneNode(true);
|
||||
newRow.id = 'cbi-shadowsocksr-server_subscribe_item-' + sid;
|
||||
|
||||
var inputs = newRow.querySelectorAll('input');
|
||||
inputs.forEach(function(input) {
|
||||
if (input.name) {
|
||||
input.name = input.name.replace(/cbid\.shadowsocksr\.[^.]+\./, 'cbid.shadowsocksr.' + sid + '.');
|
||||
}
|
||||
if (input.id) {
|
||||
input.id = input.id.replace(/cfg[^.]+/, sid);
|
||||
}
|
||||
if (input.type === 'checkbox') {
|
||||
input.checked = false;
|
||||
input.defaultChecked = false;
|
||||
} else if (input.type === 'text' || input.type === 'url') {
|
||||
input.value = '';
|
||||
input.defaultValue = '';
|
||||
}
|
||||
input.disabled = false;
|
||||
});
|
||||
|
||||
var delBtn = newRow.querySelector('.cbi-button-remove');
|
||||
if (delBtn) {
|
||||
delBtn.setAttribute('data-sid', sid);
|
||||
delBtn.removeAttribute('onclick');
|
||||
delBtn.onclick = function(e) { deleteSubscribeItem(this); return false; };
|
||||
}
|
||||
var updateBtn = newRow.querySelector('.cbi-button-apply');
|
||||
if (updateBtn) {
|
||||
updateBtn.setAttribute('data-sid', sid);
|
||||
}
|
||||
return newRow;
|
||||
}
|
||||
|
||||
function addSubscribeItem() {
|
||||
var btn = document.getElementById('ssr-add-btn');
|
||||
if (!btn) return;
|
||||
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Adding...%>';
|
||||
|
||||
XHR.post(
|
||||
'<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "add_subscribe_item")%>',
|
||||
{},
|
||||
function(x, rv) {
|
||||
btn.disabled = false;
|
||||
btn.value = '<%:Add%>';
|
||||
if (rv && rv.ret === 1 && rv.sid) {
|
||||
var newRow = createEmptyRow(rv.sid);
|
||||
if (newRow) {
|
||||
var aliasInput = newRow.querySelector('input[name$=".alias"]');
|
||||
if (aliasInput) {
|
||||
aliasInput.value = rv.alias || '';
|
||||
aliasInput.defaultValue = rv.alias || '';
|
||||
}
|
||||
var urlInput = newRow.querySelector('input[name$=".url"]');
|
||||
if (urlInput) {
|
||||
urlInput.value = rv.url || '';
|
||||
urlInput.defaultValue = rv.url || '';
|
||||
}
|
||||
var enabledInput = newRow.querySelector('input[type="checkbox"]');
|
||||
if (enabledInput) {
|
||||
var checked = (rv.enabled === '1');
|
||||
enabledInput.checked = checked;
|
||||
enabledInput.defaultChecked = checked;
|
||||
}
|
||||
var table = document.querySelector('#cbi-shadowsocksr-server_subscribe_item .cbi-section-table');
|
||||
var placeholder = table.querySelector('.placeholder');
|
||||
if (placeholder) placeholder.remove();
|
||||
table.appendChild(newRow);
|
||||
}
|
||||
} else {
|
||||
alert('<%:Add failed:%> ' + (rv && rv.error ? rv.error : '<%:Unknown error%>'));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//]]></script>
|
||||
<div class="cbi-section cbi-tblsection" id="cbi-<%=self.config%>-<%=self.sectiontype%>">
|
||||
<% if self.title and #self.title > 0 then -%>
|
||||
<h3><%=self.title%></h3>
|
||||
<%- end %>
|
||||
<%- if self.sortable then -%>
|
||||
<input type="hidden" id="cbi.sts.<%=self.config%>.<%=self.sectiontype%>" name="cbi.sts.<%=self.config%>.<%=self.sectiontype%>" value="" />
|
||||
<%- end -%>
|
||||
<div class="cbi-section-descr"><%=self.description%></div>
|
||||
<table class="table cbi-section-table">
|
||||
<tr class="tr cbi-section-table-titles anonymous">
|
||||
<% for _, node in ipairs(self.children) do if not node.optional and node.option ~= "_update" then -%>
|
||||
<th class="th cbi-section-table-cell"<%=width(node) .. attr("data-widget", node.typename or "")%>><%=node.title%></th>
|
||||
<% end end -%>
|
||||
<th class="th cbi-section-table-cell cbi-section-actions" style="width:1%"><%:Actions%></th>
|
||||
</tr>
|
||||
|
||||
<%
|
||||
local isempty = true
|
||||
for i, section in ipairs(self:cfgsections()) do
|
||||
isempty = false
|
||||
local colorclass = rowstyle()
|
||||
local scope = {
|
||||
valueheader = "cbi/cell_valueheader",
|
||||
valuefooter = "cbi/cell_valuefooter"
|
||||
}
|
||||
-%>
|
||||
<tr class="tr cbi-section-table-row<%=colorclass%>" id="cbi-<%=self.config%>-<%=section%>">
|
||||
<%
|
||||
for _, node in ipairs(self.children) do
|
||||
if not node.optional and node.option ~= "_update" then
|
||||
node:render(section, scope)
|
||||
end
|
||||
end
|
||||
local alias = self.map:get(section, "alias") or string.format("Subscribe %s", section:sub(-4))
|
||||
local update_title = "Subscribe: " .. tostring(alias)
|
||||
-%>
|
||||
<td class="td cbi-section-table-cell nowrap cbi-section-actions">
|
||||
<div class="ssr-subscribe-actions">
|
||||
<input class="btn cbi-button cbi-button-save" type="button" value="<%:Save%>" title="<%:Save%>" onclick="saveAllSubscribeItems(this)" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" value="<%:Delete%>" data-sid="<%=section%>" onclick="deleteSubscribeItem(this); return false;" />
|
||||
<input
|
||||
type="button"
|
||||
class="btn cbi-button cbi-button-apply"
|
||||
value="<%:Update%>"
|
||||
data-original-value="<%:Update%>"
|
||||
<%=attr("data-sid", section) .. attr("data-title", update_title)%>
|
||||
onclick="return window.ssrUpdateSubscribeItem(this)"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<%- end -%>
|
||||
|
||||
<%- if isempty then -%>
|
||||
<tr class="tr cbi-section-table-row placeholder">
|
||||
<td class="td" colspan="4"><em><%:This section contains no values yet%></em></td>
|
||||
</tr>
|
||||
<%- end -%>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="cbi-section-create cbi-tblsection-create ssr-subscribe-toolbar" id="ssr-subscribe-toolbar">
|
||||
<input class="btn cbi-button cbi-button-add" type="button" value="<%:Add%>" id="ssr-add-btn" title="<%:Add%>" />
|
||||
<input class="btn cbi-button cbi-button-save" type="button" value="<%:Save%>" title="<%:Save%>" onclick="saveAllSubscribeItems(this)" />
|
||||
<!-- <input class="btn cbi-button cbi-button-save ssr-subscribe-save-settings" type="submit" value="<%:Save%>" title="<%:Save%>" name="cbi.apply" /> -->
|
||||
<div class="ssr-subscribe-toolbar-slot ssr-subscribe-toolbar-slot-delete"></div>
|
||||
<div class="ssr-subscribe-toolbar-slot ssr-subscribe-toolbar-slot-update"></div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
(function() {
|
||||
function styleActionButton(btn, extraClass) {
|
||||
if (!btn) return;
|
||||
btn.classList.remove('cbi-button-apply', 'cbi-button-reset');
|
||||
btn.classList.add('cbi-button-add');
|
||||
if (extraClass) btn.classList.add(extraClass);
|
||||
}
|
||||
|
||||
function mountSubscribeActions() {
|
||||
var toolbar = document.getElementById('ssr-subscribe-toolbar');
|
||||
var subscribeSection = document.getElementById('cbi-shadowsocksr-server_subscribe');
|
||||
if (!toolbar || !subscribeSection) return;
|
||||
if (toolbar.getAttribute('data-ssr-subscribe-actions-ready') === '1') return;
|
||||
|
||||
var deleteSlot = toolbar.querySelector('.ssr-subscribe-toolbar-slot-delete');
|
||||
var updateSlot = toolbar.querySelector('.ssr-subscribe-toolbar-slot-update');
|
||||
var updateRow = subscribeSection.querySelector('.cbi-value[id$="-subscribe"]');
|
||||
var deleteRow = subscribeSection.querySelector('.cbi-value[id$="-delete"]');
|
||||
if (!deleteSlot || !updateSlot || !updateRow || !deleteRow) return;
|
||||
|
||||
var modalWrap = updateRow.querySelector('#ssr-subscribe-modal-wrap');
|
||||
if (modalWrap) {
|
||||
var updateBtn = modalWrap.querySelector('input[type="submit"]');
|
||||
styleActionButton(updateBtn, 'ssr-subscribe-update-all');
|
||||
updateSlot.appendChild(modalWrap);
|
||||
}
|
||||
|
||||
var deleteBtn = deleteRow.querySelector('input[type="submit"]');
|
||||
if (deleteBtn) {
|
||||
styleActionButton(deleteBtn, 'ssr-subscribe-delete-all');
|
||||
deleteSlot.appendChild(deleteBtn);
|
||||
}
|
||||
|
||||
updateRow.style.display = 'none';
|
||||
deleteRow.style.display = 'none';
|
||||
toolbar.setAttribute('data-ssr-subscribe-actions-ready', '1');
|
||||
}
|
||||
|
||||
function bindAddButton() {
|
||||
var addBtn = document.getElementById('ssr-add-btn');
|
||||
if (addBtn && !addBtn.getAttribute('data-bound')) {
|
||||
addBtn.addEventListener('click', addSubscribeItem);
|
||||
addBtn.setAttribute('data-bound', '1');
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
mountSubscribeActions();
|
||||
bindAddButton();
|
||||
});
|
||||
window.addEventListener('load', function() {
|
||||
mountSubscribeActions();
|
||||
bindAddButton();
|
||||
});
|
||||
} else {
|
||||
window.setTimeout(mountSubscribeActions, 0);
|
||||
window.setTimeout(bindAddButton, 0);
|
||||
}
|
||||
})();
|
||||
//]]></script>
|
||||
|
||||
<style type="text/css">
|
||||
#ssr-subscribe-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#ssr-subscribe-toolbar > .btn.cbi-button-add,
|
||||
#ssr-subscribe-toolbar > .btn.cbi-button-save {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#ssr-subscribe-toolbar .ssr-subscribe-toolbar-slot-delete,
|
||||
#ssr-subscribe-toolbar .ssr-subscribe-toolbar-slot-update {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#ssr-subscribe-toolbar .ssr-subscribe-toolbar-slot-delete {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#ssr-subscribe-toolbar #ssr-subscribe-modal-wrap,
|
||||
#ssr-subscribe-toolbar .ssr-subscribe-toolbar-slot input.btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#ssr-subscribe-toolbar #ssr-subscribe-modal-wrap {
|
||||
margin: 0 0 0 12px;
|
||||
}
|
||||
|
||||
#ssr-subscribe-toolbar .ssr-subscribe-delete-all {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
#ssr-subscribe-toolbar > .btn.cbi-button-add,
|
||||
#ssr-subscribe-toolbar > .btn.cbi-button-save,
|
||||
#ssr-subscribe-toolbar .ssr-subscribe-toolbar-slot .btn {
|
||||
min-height: 2.1rem;
|
||||
padding-top: 0.18rem;
|
||||
padding-bottom: 0.18rem;
|
||||
}
|
||||
|
||||
#ssr-subscribe-toolbar .ssr-subscribe-toolbar-slot .btn.cbi-button-add {
|
||||
margin: 0;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#ssr-subscribe-toolbar .ssr-subscribe-delete-all {
|
||||
background: #5f6874 !important;
|
||||
border-color: #5f6874 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* 电脑端按钮间距 */
|
||||
#cbi-shadowsocksr-server_subscribe_item .ssr-subscribe-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2px; /* 按钮之间增加 8px 间距 */
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 移除电脑端默认外边距,统一由 gap 控制间距 */
|
||||
#cbi-shadowsocksr-server_subscribe_item .ssr-subscribe-actions .btn {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* 清除手机端伪元素问题(出现断层显示)*/
|
||||
@media screen and (max-width: 1152px) {
|
||||
td[data-title]::after {
|
||||
content: none !important;
|
||||
display: none !important;
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
#ssr-subscribe-toolbar {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#ssr-subscribe-toolbar > .btn.cbi-button-add,
|
||||
#ssr-subscribe-toolbar > .btn.cbi-button-save,
|
||||
#ssr-subscribe-toolbar .ssr-subscribe-toolbar-slot-delete,
|
||||
#ssr-subscribe-toolbar .ssr-subscribe-toolbar-slot-update {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
/* 订阅列表 table 卡片化 */
|
||||
#cbi-shadowsocksr-server_subscribe_item .cbi-section-table,
|
||||
#cbi-shadowsocksr-server_subscribe_item .cbi-section-table tbody,
|
||||
#cbi-shadowsocksr-server_subscribe_item .cbi-section-table tr,
|
||||
#cbi-shadowsocksr-server_subscribe_item .cbi-section-table td {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 隐藏表头 */
|
||||
#cbi-shadowsocksr-server_subscribe_item .cbi-section-table-titles {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 操作列,将 td 容器调整为 100% */
|
||||
#cbi-shadowsocksr-server_subscribe_item .cbi-section-table td.cbi-section-actions {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
display: block !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 按钮区域 */
|
||||
#cbi-shadowsocksr-server_subscribe_item .ssr-subscribe-actions {
|
||||
display: flex !important;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 保存、删除、更新 三个按钮均分 */
|
||||
#cbi-shadowsocksr-server_subscribe_item .ssr-subscribe-actions .btn {
|
||||
flex: 1;
|
||||
margin: 0 !important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 修复手机端卡片化布局中 placeholder 行的显示问题 */
|
||||
#cbi-shadowsocksr-server_subscribe_item .placeholder td {
|
||||
display: table-cell !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,47 +0,0 @@
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
(function() {
|
||||
function bindSubscribeItemEnabledAutoSave() {
|
||||
var root = document.getElementById('cbi-shadowsocksr-server_subscribe_item');
|
||||
if (!root || root.getAttribute('data-enabled-autosave-bound') === '1') {
|
||||
return;
|
||||
}
|
||||
|
||||
root.addEventListener('change', function(ev) {
|
||||
var target = ev.target;
|
||||
if (!target || target.type !== 'checkbox' || !target.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
var matched = target.name.match(/^cbid\.shadowsocksr\.([^.]+)\.enabled$/);
|
||||
if (!matched) {
|
||||
return;
|
||||
}
|
||||
|
||||
var sid = matched[1];
|
||||
var original = target.checked;
|
||||
target.disabled = true;
|
||||
|
||||
XHR.post(
|
||||
'<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr", "toggle_subscribe_item_enabled")%>',
|
||||
{ sid: sid, enabled: original ? '1' : '0' },
|
||||
function(x, rv) {
|
||||
target.disabled = false;
|
||||
if (!(rv && Number(rv.ret) === 1)) {
|
||||
target.checked = !original;
|
||||
alert('Failed to save subscription enable state.');
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
root.setAttribute('data-enabled-autosave-bound', '1');
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', bindSubscribeItemEnabledAutoSave);
|
||||
window.addEventListener('load', bindSubscribeItemEnabledAutoSave);
|
||||
} else {
|
||||
window.setTimeout(bindSubscribeItemEnabledAutoSave, 0);
|
||||
}
|
||||
})();
|
||||
//]]></script>
|
||||
@@ -1,182 +0,0 @@
|
||||
<style type="text/css">
|
||||
.ssr-schedule-inline {
|
||||
display: flex;
|
||||
gap: 2%;
|
||||
align-items: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.ssr-schedule-item {
|
||||
width: 30%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ssr-schedule-item select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ssr-schedule-subtitle {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.ssr-schedule-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function ssrCompactSubscribeSchedule() {
|
||||
function getSelectByName(suffix) {
|
||||
return document.querySelector('select[name$=".' + suffix + '"], input[name$=".' + suffix + '"]');
|
||||
}
|
||||
|
||||
function shortLabel(row, fallback) {
|
||||
if (!row) return fallback;
|
||||
var label = row.querySelector('.cbi-value-title');
|
||||
if (!label) return fallback;
|
||||
var text = (label.textContent || '').trim();
|
||||
var matched = text.match(/\(([^)]+)\)/);
|
||||
return matched ? matched[1] : fallback;
|
||||
}
|
||||
|
||||
function getRowByName(suffix) {
|
||||
var el = getSelectByName(suffix);
|
||||
return el ? el.closest('.cbi-value') : null;
|
||||
}
|
||||
|
||||
function compactScheduleRow() {
|
||||
var modeRow = getRowByName('config_auto_update_mode');
|
||||
var weekRow = getRowByName('auto_update_week_time');
|
||||
var hourRow = getRowByName('auto_update_day_time');
|
||||
var minRow = getRowByName('auto_update_min_time');
|
||||
var intervalRow = getRowByName('config_update_interval');
|
||||
if (!weekRow || !hourRow || !minRow) return;
|
||||
|
||||
var weekField = weekRow.querySelector('.cbi-value-field');
|
||||
var hourField = hourRow.querySelector('.cbi-value-field');
|
||||
var minField = minRow.querySelector('.cbi-value-field');
|
||||
if (!weekField || !hourField || !minField) return;
|
||||
|
||||
if (weekField.querySelector('.ssr-schedule-inline')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var weekSelect = weekField.querySelector('select');
|
||||
var hourSelect = hourField.querySelector('select');
|
||||
var minSelect = minField.querySelector('select');
|
||||
if (!weekSelect || !hourSelect || !minSelect) return;
|
||||
|
||||
var wrap = document.createElement('div');
|
||||
wrap.className = 'ssr-schedule-inline';
|
||||
|
||||
function makeItem(title, select) {
|
||||
var item = document.createElement('div');
|
||||
item.className = 'ssr-schedule-item';
|
||||
var subtitle = document.createElement('div');
|
||||
subtitle.className = 'ssr-schedule-subtitle';
|
||||
subtitle.textContent = title;
|
||||
item.appendChild(subtitle);
|
||||
item.appendChild(select);
|
||||
return item;
|
||||
}
|
||||
|
||||
wrap.appendChild(makeItem(shortLabel(weekRow, '<%:Day/Week%>'), weekSelect));
|
||||
wrap.appendChild(makeItem(shortLabel(hourRow, '<%:Hour%>'), hourSelect));
|
||||
wrap.appendChild(makeItem(shortLabel(minRow, '<%:Min%>'), minSelect));
|
||||
|
||||
weekField.innerHTML = '';
|
||||
weekField.appendChild(wrap);
|
||||
|
||||
hourRow.classList.add('ssr-schedule-hidden');
|
||||
minRow.classList.add('ssr-schedule-hidden');
|
||||
|
||||
function applyModeVisibility() {
|
||||
var modeEl = getSelectByName('config_auto_update_mode');
|
||||
var loopMode = modeEl && modeEl.value === '1';
|
||||
|
||||
if (weekRow) {
|
||||
weekRow.classList.toggle('ssr-schedule-hidden', loopMode);
|
||||
}
|
||||
if (hourRow) {
|
||||
hourRow.classList.add('ssr-schedule-hidden');
|
||||
}
|
||||
if (minRow) {
|
||||
minRow.classList.add('ssr-schedule-hidden');
|
||||
}
|
||||
if (intervalRow) {
|
||||
intervalRow.classList.toggle('ssr-schedule-hidden', !loopMode);
|
||||
}
|
||||
}
|
||||
|
||||
if (modeRow) {
|
||||
var modeSelect = modeRow.querySelector('select');
|
||||
if (modeSelect && !modeSelect.dataset.ssrLoopBound) {
|
||||
modeSelect.dataset.ssrLoopBound = '1';
|
||||
modeSelect.addEventListener('change', applyModeVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
applyModeVisibility();
|
||||
}
|
||||
|
||||
compactScheduleRow();
|
||||
}
|
||||
|
||||
(function() {
|
||||
var retryCount = 0;
|
||||
var maxRetries = 30;
|
||||
var retryTimer = null;
|
||||
var observer = null;
|
||||
|
||||
function hasCompactLayout() {
|
||||
return !!document.querySelector('.ssr-schedule-inline');
|
||||
}
|
||||
|
||||
function tryCompactSchedule() {
|
||||
ssrCompactSubscribeSchedule();
|
||||
if (hasCompactLayout()) {
|
||||
if (retryTimer) {
|
||||
window.clearTimeout(retryTimer);
|
||||
retryTimer = null;
|
||||
}
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
observer = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (retryCount >= maxRetries) {
|
||||
return;
|
||||
}
|
||||
|
||||
retryCount = retryCount + 1;
|
||||
retryTimer = window.setTimeout(tryCompactSchedule, 200);
|
||||
}
|
||||
|
||||
function startCompactWatcher() {
|
||||
tryCompactSchedule();
|
||||
|
||||
if (window.MutationObserver) {
|
||||
observer = new MutationObserver(function() {
|
||||
if (!hasCompactLayout()) {
|
||||
tryCompactSchedule();
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', startCompactWatcher);
|
||||
window.addEventListener('load', tryCompactSchedule);
|
||||
} else {
|
||||
startCompactWatcher();
|
||||
}
|
||||
})();
|
||||
//]]>
|
||||
</script>
|
||||
+1039
-1503
File diff suppressed because it is too large.
Load diff
+1086
-1783
File diff suppressed because it is too large.
Load diff
@@ -1,22 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ "$ACTION" = "ifup" ] || exit 0
|
||||
[ "$INTERFACE" = "wan" ] || exit 0
|
||||
|
||||
WAN_PROTO="$(uci -q get network.wan.proto 2>/dev/null)"
|
||||
[ "$WAN_PROTO" = "pppoe" ] || exit 0
|
||||
|
||||
LOCK_FILE="/var/run/ssrplus-pppoe-hotplug.lock"
|
||||
|
||||
(
|
||||
if ! mkdir "$LOCK_FILE" 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
trap 'rmdir "$LOCK_FILE" 2>/dev/null' EXIT INT TERM
|
||||
|
||||
sleep 10
|
||||
/etc/init.d/shadowsocksr restart >/dev/null 2>&1
|
||||
) >/dev/null 2>&1 &
|
||||
|
||||
exit 0
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,25 @@
|
||||
amazonaws.com
|
||||
aws.amazon.com
|
||||
awsstatic.com
|
||||
fast.com
|
||||
netflix.com
|
||||
netflix.net
|
||||
nflxext.com
|
||||
nflximg.net
|
||||
nflxso.net
|
||||
nflxvideo.net
|
||||
netflixdnstest0.com
|
||||
netflixdnstest1.com
|
||||
netflixdnstest2.com
|
||||
netflixdnstest3.com
|
||||
netflixdnstest4.com
|
||||
netflixdnstest5.com
|
||||
netflixdnstest6.com
|
||||
netflixdnstest7.com
|
||||
netflixdnstest8.com
|
||||
netflixdnstest9.com
|
||||
hulu.com
|
||||
huluim.com
|
||||
hbonow.com
|
||||
hbogo.com
|
||||
hbo.com
|
||||
@@ -0,0 +1,192 @@
|
||||
server=/v.youku.com/127.0.0.1#5335
|
||||
server=/api.youku.com/127.0.0.1#5335
|
||||
server=/v2.tudou.com/127.0.0.1#5335
|
||||
server=/www.tudou.com/127.0.0.1#5335
|
||||
server=/s.plcloud.music.qq.com/127.0.0.1#5335
|
||||
server=/i.y.qq.com/127.0.0.1#5335
|
||||
server=/hot.vrs.sohu.com/127.0.0.1#5335
|
||||
server=/live.tv.sohu.com/127.0.0.1#5335
|
||||
server=/pad.tv.sohu.com/127.0.0.1#5335
|
||||
server=/my.tv.sohu.com/127.0.0.1#5335
|
||||
server=/hot.vrs.letv.com/127.0.0.1#5335
|
||||
server=/data.video.qiyi.com/127.0.0.1#5335
|
||||
server=/cache.video.qiyi.com/127.0.0.1#5335
|
||||
server=/cache.vip.qiyi.com/127.0.0.1#5335
|
||||
server=/vv.video.qq.com/127.0.0.1#5335
|
||||
server=/tt.video.qq.com/127.0.0.1#5335
|
||||
server=/ice.video.qq.com/127.0.0.1#5335
|
||||
server=/tjsa.video.qq.com/127.0.0.1#5335
|
||||
server=/a10.video.qq.com/127.0.0.1#5335
|
||||
server=/xyy.video.qq.com/127.0.0.1#5335
|
||||
server=/vcq.video.qq.com/127.0.0.1#5335
|
||||
server=/vsh.video.qq.com/127.0.0.1#5335
|
||||
server=/vbj.video.qq.com/127.0.0.1#5335
|
||||
server=/bobo.video.qq.com/127.0.0.1#5335
|
||||
server=/flvs.video.qq.com/127.0.0.1#5335
|
||||
server=/bkvv.video.qq.com/127.0.0.1#5335
|
||||
server=/info.zb.qq.com/127.0.0.1#5335
|
||||
server=/geo.js.kankan.xunlei.com/127.0.0.1#5335
|
||||
server=/web-play.pptv.com/127.0.0.1#5335
|
||||
server=/web-play.pplive.cn/127.0.0.1#5335
|
||||
server=/dyn.ugc.pps.tv/127.0.0.1#5335
|
||||
server=/v.pps.tv/127.0.0.1#5335
|
||||
server=/inner.kandian.com/127.0.0.1#5335
|
||||
server=/ipservice.163.com/127.0.0.1#5335
|
||||
server=/so.open.163.com/127.0.0.1#5335
|
||||
server=/zb.s.qq.com/127.0.0.1#5335
|
||||
server=/ip.kankan.xunlei.com/127.0.0.1#5335
|
||||
server=/vxml.56.com/127.0.0.1#5335
|
||||
server=/music.sina.com.cn/127.0.0.1#5335
|
||||
server=/play.baidu.com/127.0.0.1#5335
|
||||
server=/v.iask.com/127.0.0.1#5335
|
||||
server=/tv.weibo.com/127.0.0.1#5335
|
||||
server=/wtv.v.iask.com/127.0.0.1#5335
|
||||
server=/video.sina.com.cn/127.0.0.1#5335
|
||||
server=/www.yinyuetai.com/127.0.0.1#5335
|
||||
server=/api.letv.com/127.0.0.1#5335
|
||||
server=/live.gslb.letv.com/127.0.0.1#5335
|
||||
server=/static.itv.letv.com/127.0.0.1#5335
|
||||
server=/ip.apps.cntv.cn/127.0.0.1#5335
|
||||
server=/vdn.apps.cntv.cn/127.0.0.1#5335
|
||||
server=/vdn.live.cntv.cn/127.0.0.1#5335
|
||||
server=/vip.sports.cntv.cn/127.0.0.1#5335
|
||||
server=/a.play.api.3g.youku.com/127.0.0.1#5335
|
||||
server=/i.play.api.3g.youku.com/127.0.0.1#5335
|
||||
server=/api.3g.youku.com/127.0.0.1#5335
|
||||
server=/tv.api.3g.youku.com/127.0.0.1#5335
|
||||
server=/play.api.3g.youku.com/127.0.0.1#5335
|
||||
server=/play.api.3g.tudou.com/127.0.0.1#5335
|
||||
server=/tv.api.3g.tudou.com/127.0.0.1#5335
|
||||
server=/api.3g.tudou.com/127.0.0.1#5335
|
||||
server=/api.tv.sohu.com/127.0.0.1#5335
|
||||
server=/access.tv.sohu.com/127.0.0.1#5335
|
||||
server=/iface.iqiyi.com/127.0.0.1#5335
|
||||
server=/iface2.iqiyi.com/127.0.0.1#5335
|
||||
server=/cache.m.iqiyi.com/127.0.0.1#5335
|
||||
server=/dynamic.app.m.letv.com/127.0.0.1#5335
|
||||
server=/dynamic.meizi.app.m.letv.com/127.0.0.1#5335
|
||||
server=/dynamic.search.app.m.letv.com/127.0.0.1#5335
|
||||
server=/dynamic.live.app.m.letv.com/127.0.0.1#5335
|
||||
server=/listso.m.areainfo.ppstream.com/127.0.0.1#5335
|
||||
server=/epg.api.pptv.com/127.0.0.1#5335
|
||||
server=/play.api.pptv.com/127.0.0.1#5335
|
||||
server=/m.letv.com/127.0.0.1#5335
|
||||
server=/interface.bilibili.com/127.0.0.1#5335
|
||||
server=/3g.music.qq.com/127.0.0.1#5335
|
||||
server=/mqqplayer.3g.qq.com/127.0.0.1#5335
|
||||
server=/proxy.music.qq.com/127.0.0.1#5335
|
||||
server=/proxymc.qq.com/127.0.0.1#5335
|
||||
server=/ip2.kugou.com/127.0.0.1#5335
|
||||
server=/ip.kugou.com/127.0.0.1#5335
|
||||
server=/client.api.ttpod.com/127.0.0.1#5335
|
||||
server=/mobi.kuwo.cn/127.0.0.1#5335
|
||||
server=/mobilefeedback.kugou.com/127.0.0.1#5335
|
||||
server=/tingapi.ting.baidu.com/127.0.0.1#5335
|
||||
server=/music.baidu.com/127.0.0.1#5335
|
||||
server=/serviceinfo.sdk.duomi.com/127.0.0.1#5335
|
||||
server=/music.163.com/127.0.0.1#5335
|
||||
server=/www.xiami.com/127.0.0.1#5335
|
||||
server=/spark.api.xiami.com/127.0.0.1#5335
|
||||
server=/iplocation.geo.qiyi.com/127.0.0.1#5335
|
||||
server=/sns.video.qq.com/127.0.0.1#5335
|
||||
server=/v5.pc.duomi.com/127.0.0.1#5335
|
||||
server=/tms.is.ysten.com/127.0.0.1#5335
|
||||
server=/internal.check.duokanbox.com/127.0.0.1#5335
|
||||
server=/openapi.youku.com/127.0.0.1#5335
|
||||
server=/y.qq.com/127.0.0.1#5335
|
||||
ipset=/v.youku.com/oversea
|
||||
ipset=/api.youku.com/oversea
|
||||
ipset=/v2.tudou.com/oversea
|
||||
ipset=/www.tudou.com/oversea
|
||||
ipset=/s.plcloud.music.qq.com/oversea
|
||||
ipset=/i.y.qq.com/oversea
|
||||
ipset=/hot.vrs.sohu.com/oversea
|
||||
ipset=/live.tv.sohu.com/oversea
|
||||
ipset=/pad.tv.sohu.com/oversea
|
||||
ipset=/my.tv.sohu.com/oversea
|
||||
ipset=/hot.vrs.letv.com/oversea
|
||||
ipset=/data.video.qiyi.com/oversea
|
||||
ipset=/cache.video.qiyi.com/oversea
|
||||
ipset=/cache.vip.qiyi.com/oversea
|
||||
ipset=/vv.video.qq.com/oversea
|
||||
ipset=/tt.video.qq.com/oversea
|
||||
ipset=/ice.video.qq.com/oversea
|
||||
ipset=/tjsa.video.qq.com/oversea
|
||||
ipset=/a10.video.qq.com/oversea
|
||||
ipset=/xyy.video.qq.com/oversea
|
||||
ipset=/vcq.video.qq.com/oversea
|
||||
ipset=/vsh.video.qq.com/oversea
|
||||
ipset=/vbj.video.qq.com/oversea
|
||||
ipset=/bobo.video.qq.com/oversea
|
||||
ipset=/flvs.video.qq.com/oversea
|
||||
ipset=/bkvv.video.qq.com/oversea
|
||||
ipset=/info.zb.qq.com/oversea
|
||||
ipset=/geo.js.kankan.xunlei.com/oversea
|
||||
ipset=/web-play.pptv.com/oversea
|
||||
ipset=/web-play.pplive.cn/oversea
|
||||
ipset=/dyn.ugc.pps.tv/oversea
|
||||
ipset=/v.pps.tv/oversea
|
||||
ipset=/inner.kandian.com/oversea
|
||||
ipset=/ipservice.163.com/oversea
|
||||
ipset=/so.open.163.com/oversea
|
||||
ipset=/zb.s.qq.com/oversea
|
||||
ipset=/ip.kankan.xunlei.com/oversea
|
||||
ipset=/vxml.56.com/oversea
|
||||
ipset=/music.sina.com.cn/oversea
|
||||
ipset=/play.baidu.com/oversea
|
||||
ipset=/v.iask.com/oversea
|
||||
ipset=/tv.weibo.com/oversea
|
||||
ipset=/wtv.v.iask.com/oversea
|
||||
ipset=/video.sina.com.cn/oversea
|
||||
ipset=/www.yinyuetai.com/oversea
|
||||
ipset=/api.letv.com/oversea
|
||||
ipset=/live.gslb.letv.com/oversea
|
||||
ipset=/static.itv.letv.com/oversea
|
||||
ipset=/ip.apps.cntv.cn/oversea
|
||||
ipset=/vdn.apps.cntv.cn/oversea
|
||||
ipset=/vdn.live.cntv.cn/oversea
|
||||
ipset=/vip.sports.cntv.cn/oversea
|
||||
ipset=/a.play.api.3g.youku.com/oversea
|
||||
ipset=/i.play.api.3g.youku.com/oversea
|
||||
ipset=/api.3g.youku.com/oversea
|
||||
ipset=/tv.api.3g.youku.com/oversea
|
||||
ipset=/play.api.3g.youku.com/oversea
|
||||
ipset=/play.api.3g.tudou.com/oversea
|
||||
ipset=/tv.api.3g.tudou.com/oversea
|
||||
ipset=/api.3g.tudou.com/oversea
|
||||
ipset=/api.tv.sohu.com/oversea
|
||||
ipset=/access.tv.sohu.com/oversea
|
||||
ipset=/iface.iqiyi.com/oversea
|
||||
ipset=/iface2.iqiyi.com/oversea
|
||||
ipset=/cache.m.iqiyi.com/oversea
|
||||
ipset=/dynamic.app.m.letv.com/oversea
|
||||
ipset=/dynamic.meizi.app.m.letv.com/oversea
|
||||
ipset=/dynamic.search.app.m.letv.com/oversea
|
||||
ipset=/dynamic.live.app.m.letv.com/oversea
|
||||
ipset=/listso.m.areainfo.ppstream.com/oversea
|
||||
ipset=/epg.api.pptv.com/oversea
|
||||
ipset=/play.api.pptv.com/oversea
|
||||
ipset=/m.letv.com/oversea
|
||||
ipset=/interface.bilibili.com/oversea
|
||||
ipset=/3g.music.qq.com/oversea
|
||||
ipset=/mqqplayer.3g.qq.com/oversea
|
||||
ipset=/proxy.music.qq.com/oversea
|
||||
ipset=/proxymc.qq.com/oversea
|
||||
ipset=/ip2.kugou.com/oversea
|
||||
ipset=/ip.kugou.com/oversea
|
||||
ipset=/client.api.ttpod.com/oversea
|
||||
ipset=/mobi.kuwo.cn/oversea
|
||||
ipset=/mobilefeedback.kugou.com/oversea
|
||||
ipset=/tingapi.ting.baidu.com/oversea
|
||||
ipset=/music.baidu.com/oversea
|
||||
ipset=/serviceinfo.sdk.duomi.com/oversea
|
||||
ipset=/music.163.com/oversea
|
||||
ipset=/www.xiami.com/oversea
|
||||
ipset=/spark.api.xiami.com/oversea
|
||||
ipset=/iplocation.geo.qiyi.com/oversea
|
||||
ipset=/sns.video.qq.com/oversea
|
||||
ipset=/v5.pc.duomi.com/oversea
|
||||
ipset=/tms.is.ysten.com/oversea
|
||||
ipset=/internal.check.duokanbox.com/oversea
|
||||
ipset=/openapi.youku.com/oversea
|
||||
ipset=/y.qq.com/oversea
|
||||
@@ -31,8 +31,11 @@ touch /etc/ssrplus/china_ssr.txt
|
||||
touch /etc/ssrplus/deny.list
|
||||
touch /etc/ssrplus/white.list
|
||||
touch /etc/ssrplus/black.list
|
||||
touch /etc/ssrplus/netflix.list
|
||||
touch /etc/ssrplus/netflixip.list
|
||||
touch /etc/ssrplus/gfw_base.conf
|
||||
touch /etc/ssrplus/gfw_list.conf
|
||||
touch /etc/ssrplus/oversea_list.conf
|
||||
touch /etc/ssrplus/ad.conf
|
||||
touch /etc/config/shadowsocksr
|
||||
|
||||
@@ -47,20 +50,16 @@ if [ -s "/etc/config/shadowsocksr" ]; then
|
||||
uci -q set shadowsocksr.@server_subscribe[0].auto_update_min_time='0'
|
||||
fi
|
||||
|
||||
if ! uci -q get shadowsocksr.@server_subscribe[0].config_auto_update_mode > /dev/null; then
|
||||
uci -q set shadowsocksr.@server_subscribe[0].config_auto_update_mode='0'
|
||||
fi
|
||||
|
||||
if ! uci -q get shadowsocksr.@server_subscribe[0].config_update_interval > /dev/null; then
|
||||
uci -q set shadowsocksr.@server_subscribe[0].config_update_interval='60'
|
||||
if ! uci -q get shadowsocksr.@server_subscribe[0].ss_type > /dev/null; then
|
||||
uci -q set shadowsocksr.@server_subscribe[0].ss_type='ss-rust'
|
||||
fi
|
||||
|
||||
if ! uci -q get shadowsocksr.@server_subscribe[0].user_agent > /dev/null; then
|
||||
uci -q set shadowsocksr.@server_subscribe[0].user_agent='v2rayN/9.99'
|
||||
fi
|
||||
|
||||
if ! uci -q get shadowsocksr.@server_subscribe[0].proxy > /dev/null; then
|
||||
uci -q set shadowsocksr.@server_subscribe[0].proxy='1'
|
||||
if ! uci -q get shadowsocksr.@server_subscribe[0].xray_hy2_type > /dev/null; then
|
||||
uci -q set shadowsocksr.@server_subscribe[0].xray_hy2_type='hysteria2'
|
||||
fi
|
||||
|
||||
if ! uci -q get shadowsocksr.@global_xray_fragment[0] > /dev/null; then
|
||||
@@ -69,61 +68,8 @@ if [ -s "/etc/config/shadowsocksr" ]; then
|
||||
uci -q set shadowsocksr.@global_xray_fragment[0].noise='0'
|
||||
fi
|
||||
|
||||
if ! uci -q get shadowsocksr.@global[0].component_mirror > /dev/null; then
|
||||
uci -q set shadowsocksr.@global[0].component_mirror='direct'
|
||||
fi
|
||||
|
||||
if ! uci -q get shadowsocksr.@global[0].filter_aaaa > /dev/null; then
|
||||
legacy_filter_aaaa="$(uci -q get shadowsocksr.@global[0].mosdns_ipv6)"
|
||||
uci -q set shadowsocksr.@global[0].filter_aaaa="${legacy_filter_aaaa:-1}"
|
||||
fi
|
||||
|
||||
legacy_dns_mode="$(uci -q get shadowsocksr.@global[0].pdnsd_enable)"
|
||||
if [ "$legacy_dns_mode" = "2" ] || [ "$legacy_dns_mode" = "3" ]; then
|
||||
uci -q set shadowsocksr.@global[0].pdnsd_enable='1'
|
||||
fi
|
||||
|
||||
if uci -q get shadowsocksr.@global[0].mosdns_ipv6 > /dev/null; then
|
||||
uci -q delete shadowsocksr.@global[0].mosdns_ipv6
|
||||
fi
|
||||
|
||||
if ! uci -q get shadowsocksr.@http_proxy[0] > /dev/null; then
|
||||
uci -q add shadowsocksr http_proxy
|
||||
uci -q set shadowsocksr.@http_proxy[0].server='nil'
|
||||
uci -q set shadowsocksr.@http_proxy[0].local_port='3128'
|
||||
uci -q set shadowsocksr.@http_proxy[0].http_auth='none'
|
||||
fi
|
||||
|
||||
ss_rust_bin="$(command -v ssserver 2>/dev/null)"
|
||||
[ -n "$ss_rust_bin" ] || [ ! -x /usr/libexec/ssserver ] || ss_rust_bin="/usr/libexec/ssserver"
|
||||
mihomo_bin="$(command -v mihomo 2>/dev/null)"
|
||||
[ -n "$mihomo_bin" ] || [ ! -x /usr/libexec/mihomo ] || mihomo_bin="/usr/libexec/mihomo"
|
||||
xray_bin="$(command -v xray 2>/dev/null)"
|
||||
[ -n "$xray_bin" ] || [ ! -x /usr/libexec/xray ] || xray_bin="/usr/libexec/xray"
|
||||
for section in $(uci -q show shadowsocksr | sed -n "s/^shadowsocksr\\.\\([^.=][^.=]*\\)=servers$/\\1/p"); do
|
||||
node_type="$(uci -q get shadowsocksr.${section}.type)"
|
||||
case "$node_type" in
|
||||
ss|ss-libev)
|
||||
if [ -n "$mihomo_bin" ]; then
|
||||
uci -q set shadowsocksr.${section}.type='ss'
|
||||
elif [ -n "$ss_rust_bin" ]; then
|
||||
uci -q set shadowsocksr.${section}.type='ss-rust'
|
||||
elif [ -n "$xray_bin" ]; then
|
||||
uci -q set shadowsocksr.${section}.type='v2ray'
|
||||
uci -q set shadowsocksr.${section}.v2ray_protocol='shadowsocks'
|
||||
fi
|
||||
;;
|
||||
v2ray)
|
||||
if [ "$(uci -q get shadowsocksr.${section}.v2ray_protocol)" = "shadowsocks" ] && [ -n "$mihomo_bin" ]; then
|
||||
uci -q set shadowsocksr.${section}.type='ss'
|
||||
uci -q delete shadowsocksr.${section}.v2ray_protocol
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
uci -q commit shadowsocksr
|
||||
fi
|
||||
uci -q commit shadowsocksr
|
||||
fi
|
||||
|
||||
[ -s "/etc/config/shadowsocksr" ] || /etc/init.d/shadowsocksr reset
|
||||
|
||||
|
||||
@@ -10,58 +10,6 @@
|
||||
LOCK_FILE="/var/lock/ssr-monitor.lock"
|
||||
[ -f "$LOCK_FILE" ] && exit 2
|
||||
touch "$LOCK_FILE"
|
||||
|
||||
ps_list() {
|
||||
if busybox ps -w >/dev/null 2>&1; then
|
||||
busybox ps -w
|
||||
else
|
||||
busybox ps
|
||||
fi
|
||||
}
|
||||
|
||||
match_proc_count() {
|
||||
local pattern="$1"
|
||||
ps_list | grep -E "$pattern" | grep -v grep | wc -l
|
||||
}
|
||||
|
||||
redir_tcp_running() {
|
||||
local ssr_count
|
||||
local ipt_count
|
||||
ssr_count=$(match_proc_count 'ssr-retcp')
|
||||
ipt_count=$(ps_list | grep 'ipt2socks' | grep -v grep | grep -E ' -T( |$)| --tcp-only( |$)' | wc -l)
|
||||
echo $((ssr_count + ipt_count))
|
||||
}
|
||||
|
||||
redir_udp_running() {
|
||||
local ssr_count
|
||||
local ipt_count
|
||||
ssr_count=$(match_proc_count 'ssr-reudp')
|
||||
ipt_count=$(ps_list | grep 'ipt2socks' | grep -v grep | grep -E ' -U( |$)| --udp-only( |$)' | wc -l)
|
||||
echo $((ssr_count + ipt_count))
|
||||
}
|
||||
|
||||
kill_matched_procs() {
|
||||
local pattern="$1"
|
||||
ps_list | grep -E "$pattern" | grep -v grep | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1
|
||||
}
|
||||
|
||||
get_expected_threads() {
|
||||
local threads
|
||||
threads=$(uci_get_by_type global threads 0)
|
||||
if [ "$threads" = "0" ] || [ -z "$threads" ]; then
|
||||
threads=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null)
|
||||
fi
|
||||
|
||||
case "$threads" in
|
||||
''|*[!0-9]*)
|
||||
threads=1
|
||||
;;
|
||||
esac
|
||||
|
||||
[ "$threads" -lt 1 ] && threads=1
|
||||
echo "$threads"
|
||||
}
|
||||
|
||||
server_process_count=$1
|
||||
redir_tcp_process=$2
|
||||
redir_udp_process=$3
|
||||
@@ -73,8 +21,6 @@ if [ -z "$pdnsd_process" ]; then
|
||||
fi
|
||||
i=0
|
||||
GLOBAL_SERVER=$(uci_get_by_type global global_server)
|
||||
GLOBAL_TYPE=$(uci_get_by_name $GLOBAL_SERVER type)
|
||||
EXPECTED_THREADS=$(get_expected_threads)
|
||||
server=$(uci_get_by_name $GLOBAL_SERVER server)
|
||||
kcp_port=$(uci_get_by_name $GLOBAL_SERVER kcp_port)
|
||||
server_port=$(uci_get_by_name $GLOBAL_SERVER server_port)
|
||||
@@ -86,12 +32,8 @@ while [ "1" == "1" ]; do #死循环
|
||||
sleep 000030s
|
||||
#redir tcp
|
||||
if [ "$redir_tcp_process" -gt 0 ]; then
|
||||
icount=$(redir_tcp_running)
|
||||
local tcp_expected="$redir_tcp_process"
|
||||
if [ "$GLOBAL_TYPE" = "socks5" ]; then
|
||||
tcp_expected="$EXPECTED_THREADS"
|
||||
fi
|
||||
if [ "$icount" -lt "$tcp_expected" ]; then
|
||||
icount=$(busybox ps -w | grep ssr-retcp | grep -v grep | wc -l)
|
||||
if [ "$icount" == 0 ]; then
|
||||
logger -t "$NAME" "ssrplus redir tcp error.restart!"
|
||||
echolog "ssrplus redir tcp error.restart!"
|
||||
/etc/init.d/shadowsocksr restart
|
||||
@@ -100,12 +42,8 @@ while [ "1" == "1" ]; do #死循环
|
||||
fi
|
||||
#redir udp
|
||||
if [ "$redir_udp_process" -gt 0 ]; then
|
||||
icount=$(redir_udp_running)
|
||||
local udp_expected="$redir_udp_process"
|
||||
if [ "$GLOBAL_TYPE" = "socks5" ]; then
|
||||
udp_expected="$EXPECTED_THREADS"
|
||||
fi
|
||||
if [ "$icount" -lt "$udp_expected" ]; then
|
||||
icount=$(busybox ps -w | grep ssr-reudp | grep -v grep | wc -l)
|
||||
if [ "$icount" == 0 ]; then
|
||||
logger -t "$NAME" "ssrplus redir udp error.restart!"
|
||||
echolog "ssrplus redir udp error.restart!"
|
||||
/etc/init.d/shadowsocksr restart
|
||||
@@ -114,18 +52,18 @@ while [ "1" == "1" ]; do #死循环
|
||||
fi
|
||||
#server
|
||||
if [ "$server_process_count" -gt 0 ]; then
|
||||
icount=$(match_proc_count 'ssr-server|ss-server-|xray-server-')
|
||||
icount=$(busybox ps -w | grep ssr-server | grep -v grep | wc -l)
|
||||
if [ "$icount" -lt "$server_process_count" ]; then #如果进程挂掉就重启它
|
||||
logger -t "$NAME" "ssrplus server error.restart!"
|
||||
echolog "ssrplus server error.restart!"
|
||||
kill_matched_procs 'ssr-server|ss-server-'
|
||||
kill -9 $(busybox ps -w | grep ssr-server | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
/etc/init.d/shadowsocksr restart
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
#kcptun
|
||||
if [ "$kcp_process" -gt 0 ]; then
|
||||
icount=$(ps_list | grep kcptun-client | grep -v grep | wc -l)
|
||||
icount=$(busybox ps -w | grep kcptun-client | grep -v grep | wc -l)
|
||||
if [ "$icount" -lt "$kcp_process" ]; then #如果进程挂掉就重启它
|
||||
logger -t "$NAME" "ssrplus kcptun error.restart!"
|
||||
echolog "ssrplus kcptun error.restart!"
|
||||
@@ -135,43 +73,132 @@ while [ "1" == "1" ]; do #死循环
|
||||
fi
|
||||
#localsocks
|
||||
if [ "$local_process" -gt 0 ]; then
|
||||
icount=$(match_proc_count 'ssr-local|ss-local|tuic-local')
|
||||
icount=$(busybox ps -w | grep ssr-local | grep -v grep | wc -l)
|
||||
if [ "$icount" -lt "$local_process" ]; then #如果进程挂掉就重启它
|
||||
logger -t "$NAME" "global socks server error.restart!"
|
||||
echolog "global socks server error.restart!"
|
||||
kill_matched_procs 'ssr-local|ss-local|tuic-local'
|
||||
kill -9 $(busybox ps -w | grep ssr-local | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
/etc/init.d/shadowsocksr restart
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
#dns2tcp
|
||||
if [ "$pdnsd_process" -eq 1 ]; then
|
||||
icount=$(ps_list | grep $TMP_BIN_PATH/dns2tcp | grep -v grep | wc -l)
|
||||
icount=$(busybox ps -w | grep $TMP_BIN_PATH/dns2tcp | grep -v grep | wc -l)
|
||||
if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它
|
||||
logger -t "$NAME" "dns2tcp tunnel error.restart!"
|
||||
echolog "dns2tcp tunnel error.restart!"
|
||||
dnsserver=$(uci_get_by_type global tunnel_forward 8.8.4.4:53)
|
||||
kill -9 $(ps_list | grep $TMP_BIN_PATH/dns2tcp | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
start_dns2tcp "$dnsserver"
|
||||
kill -9 $(busybox ps -w | grep $TMP_BIN_PATH/dns2tcp | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
ln_start_bin $(first_type dns2tcp) dns2tcp -L "127.0.0.1#$dns_port" -R "${dnsserver/:/#}"
|
||||
fi
|
||||
#dns2socks
|
||||
elif [ "$pdnsd_process" -eq 2 ]; then
|
||||
icount=$(busybox ps -w | grep -e ssrplus-dns -e "dns2socks 127.0.0.1 $tmp_dns_port" | grep -v grep | wc -l)
|
||||
if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它
|
||||
logger -t "$NAME" "dns2socks $dnsserver tunnel error.restart!"
|
||||
echolog "dns2socks $dnsserver tunnel error.restart!"
|
||||
dnsserver=$(uci_get_by_type global tunnel_forward 8.8.4.4:53)
|
||||
kill -9 $(busybox ps -w | grep ssrplus-dns | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
kill -9 $(busybox ps -w | grep "dns2socks 127.0.0.1 $tmp_dns_port" | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
ln_start_bin $(first_type microsocks) microsocks -i 127.0.0.1 -p $tmp_dns_port ssrplus-dns
|
||||
ln_start_bin $(first_type dns2socks) dns2socks 127.0.0.1:$tmp_dns_port $dnsserver 127.0.0.1:$dns_port -q
|
||||
fi
|
||||
#dns2socks-rust
|
||||
elif [ "$pdnsd_process" -eq 3 ]; then
|
||||
icount=$(busybox ps -w | grep -e ssrplus-dns -e "dns2socks-rust -s socks5://127.0.0.1 $tmp_dns_port" | grep -v grep | wc -l)
|
||||
if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它
|
||||
logger -t "$NAME" "dns2socks-rust $dnsserver tunnel error.restart!"
|
||||
echolog "dns2socks-rust $dnsserver tunnel error.restart!"
|
||||
dnsserver=$(uci_get_by_type global tunnel_forward 8.8.4.4:53)
|
||||
kill -9 $(busybox ps -w | grep ssrplus-dns | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
kill -9 $(busybox ps -w | grep "dns2socks-rust -s socks5://127.0.0.1 $tmp_dns_port" | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
ln_start_bin $(first_type microsocks) microsocks -i 127.0.0.1 -p $tmp_dns_port ssrplus-dns
|
||||
ln_start_bin $(first_type dns2socks) dns2socks-rust -s socks5://127.0.0.1:$tmp_dns_port -d $dnsserver -l 127.0.0.1:$dns_port -f -c
|
||||
fi
|
||||
#mosdns
|
||||
elif [ "$pdnsd_process" -eq 4 ]; then
|
||||
icount=$(ps_list | grep $TMP_BIN_PATH/mosdns | grep -v grep | wc -l)
|
||||
icount=$(busybox ps -w | grep $TMP_BIN_PATH/mosdns | grep -v grep | wc -l)
|
||||
if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它
|
||||
logger -t "$NAME" "mosdns tunnel error.restart!"
|
||||
echolog "mosdns tunnel error.restart!"
|
||||
dnsserver=$(uci_get_by_type global tunnel_forward 8.8.4.4:53)
|
||||
kill -9 $(ps_list | grep $TMP_BIN_PATH/mosdns | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
kill -9 $(busybox ps -w | grep $TMP_BIN_PATH/mosdns | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
ln_start_bin $(first_type mosdns) mosdns start -c /etc/mosdns/config.yaml
|
||||
#dnsproxy
|
||||
elif [ "$pdnsd_process" -eq 5 ]; then
|
||||
icount=$(busybox ps -w | grep -e ssrplus-dns -e "dnsproxy -l 127.0.0.1 -p $tmp_dns_port" | grep -v grep | wc -l)
|
||||
if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它
|
||||
logger -t "$NAME" "dnsproxy $dnsserver tunnel error.restart!"
|
||||
echolog "dnsproxy $dnsserver tunnel error.restart!"
|
||||
local dnsproxy_dnsserver="$(uci_get_by_type global parse_method)"
|
||||
if [ -n "$dnsproxy_dnsserver" ] && [ "$dnsproxy_dnsserver" != "parse_file" ]; then
|
||||
dnsserver="$(uci_get_by_type global dnsproxy_tunnel_forward 8.8.4.4:53)"
|
||||
fi
|
||||
kill -9 $(busybox ps -w | grep "dnsproxy -l 127.0.0.1 -p $tmp_dns_port" | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
dnsproxy_ipv6="$(uci_get_by_type global dnsproxy_ipv6)"
|
||||
disabled_ipv6="--ipv6-disabled"
|
||||
fi
|
||||
if [ "$dnsproxy_dnsserver" != "parse_file" ]; then
|
||||
ln_start_bin $(first_type dnsproxy) dnsproxy -l 127.0.0.1 -p $tmp_dns_port -p $dns_port -u $dnsserver $disabled_ipv6 --cache --cache-min-ttl=3600
|
||||
else
|
||||
dnsproxy_dnsserver_file="$TMP_PATH/dnsproxy_dns.list"
|
||||
cleaned_file="$TMP_PATH/cleaned_dns.list"
|
||||
temp_file="$TMP_PATH/temp_dns.list"
|
||||
> "$cleaned_file"
|
||||
# 清理输入文件并去重
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
line=$(echo "$line" | sed -E 's/^[ \t\r]+//; s/[ \t\r]+$//')
|
||||
[ -z "$line" ] && continue
|
||||
echo "$line" | grep -qE '^#' && continue
|
||||
echo "$line" >> "$cleaned_file"
|
||||
done < "/etc/ssrplus/dnsproxy_dns.list"
|
||||
# 获取清理后文件的MD5
|
||||
cleaned_md5=$(md5sum "$cleaned_file" | awk '{print $1}')
|
||||
if [ ! -f "$dnsproxy_dnsserver_file" ]; then
|
||||
cp "$cleaned_file" "$dnsproxy_dnsserver_file"
|
||||
else
|
||||
target_md5=$(md5sum "$dnsproxy_dnsserver_file" | awk '{print $1}')
|
||||
if [ "$cleaned_md5" != "$target_md5" ]; then
|
||||
> "$temp_file"
|
||||
# 保留目标文件中也存在于清理文件的记录(去重)
|
||||
while IFS= read -r line; do
|
||||
line=$(echo "$line" | sed -E 's/^[ \t\r]+//; s/[ \t\r]+$//')
|
||||
if grep -qixF "$line" "$cleaned_file" && ! grep -qixF "$line" "$temp_file"; then
|
||||
echo "$line" >> "$temp_file"
|
||||
fi
|
||||
done < "$dnsproxy_dnsserver_file"
|
||||
# 添加清理文件中有但目标文件没有的记录(去重)
|
||||
while IFS= read -r line; do
|
||||
line=$(echo "$line" | sed -E 's/^[ \t\r]+//; s/[ \t\r]+$//')
|
||||
if ! grep -qixF "$line" "$temp_file"; then
|
||||
echo "$line" >> "$temp_file"
|
||||
fi
|
||||
done < "$cleaned_file"
|
||||
temp_md5=$(md5sum "$temp_file" | awk '{print $1}')
|
||||
if [ "$temp_md5" != "$target_md5" ]; then
|
||||
mv "$temp_file" "$dnsproxy_dnsserver_file"
|
||||
else
|
||||
rm -f "$temp_file"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
rm -f "$cleaned_file"
|
||||
|
||||
if [ -n "$dnsproxy_dnsserver_file" ] && [ -s "$dnsproxy_dnsserver_file" ]; then
|
||||
local upstreams_logic_mode="$(uci_get_by_type global upstreams_logic_mode)"
|
||||
ln_start_bin $(first_type dnsproxy) dnsproxy -l 127.0.0.1 -p $tmp_dns_port -p $dns_port -u $dnsproxy_dnsserver_file $disabled_ipv6 --cache --cache-min-ttl=3600 --upstream-mode=$upstreams_logic_mode
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
#chinadns-ng(proxy)
|
||||
elif [ "$pdnsd_process" -eq 6 ]; then
|
||||
icount=$(ps_list | grep -e ssrplus-dns -e "chinadns-ng -b 127.0.0.1 -l $tmp_dns_port" | grep -v grep | wc -l)
|
||||
icount=$(busybox ps -w | grep -e ssrplus-dns -e "chinadns-ng -b 127.0.0.1 -l $tmp_dns_port" | grep -v grep | wc -l)
|
||||
if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它
|
||||
logger -t "$NAME" "chinadns-ng $dnsserver tunnel error.restart!"
|
||||
echolog "chinadns-ng $dnsserver tunnel error.restart!"
|
||||
dnsserver=$(uci_get_by_type global chinadns_ng_tunnel_forward 8.8.4.4:53)
|
||||
kill -9 $(ps_list | grep "chinadns-ng -b 127.0.0.1 -l $tmp_dns_port" | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
kill -9 $(busybox ps -w | grep "chinadns-ng -b 127.0.0.1 -l $tmp_dns_port" | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
local chinadns_ng_proto="$(uci_get_by_type global chinadns_ng_proto)"
|
||||
local chinadns_ng_dns=""
|
||||
IFS=','
|
||||
@@ -197,18 +224,10 @@ while [ "1" == "1" ]; do #死循环
|
||||
dnsserver="$chinadns_ng_dns"
|
||||
ln_start_bin $(first_type chinadns-ng) chinadns-ng -b 127.0.0.1 -l $tmp_dns_port -l $dns_port -p 3 -d gfw $dnsserver -N --filter-qtype 64,65 -f -r --cache 4096 --cache-stale 86400 --cache-refresh 20
|
||||
fi
|
||||
#built-in dns
|
||||
elif [ "$pdnsd_process" -eq 7 ]; then
|
||||
if ! is_builtin_dns_active; then
|
||||
logger -t "$NAME" "builtin dns error.restart!"
|
||||
echolog "builtin dns error.restart!"
|
||||
/etc/init.d/shadowsocksr restart
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
#chinadns-ng(china)
|
||||
if [ "$(uci -q get "dhcp.@dnsmasq[0]._unused_ssrp_changed")" = "1" ]; then
|
||||
icount=$(ps_list | grep $TMP_BIN_PATH/chinadns-ng | grep -v grep | wc -l)
|
||||
icount=$(busybox ps -w | grep $TMP_BIN_PATH/chinadns-ng | grep -v grep | wc -l)
|
||||
if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它
|
||||
logger -t "$NAME" "chinadns-ng tunnel error.restart!"
|
||||
echolog "chinadns-ng tunnel error.restart!"
|
||||
@@ -218,7 +237,7 @@ while [ "1" == "1" ]; do #死循环
|
||||
"wan") chinadns="$wandns" ;;
|
||||
""|"wan_114") chinadns="$wandns,114.114.114.114" ;;
|
||||
esac
|
||||
kill -9 $(ps_list | grep $TMP_BIN_PATH/chinadns-ng | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
kill -9 $(busybox ps -w | grep $TMP_BIN_PATH/chinadns-ng | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
ln_start_bin $(first_type chinadns-ng) chinadns-ng -l $china_dns_port -4 china -p 3 -c ${chinadns/:/#} -t 127.0.0.1#$dns_port -N -f -r
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -85,14 +85,13 @@ usage() {
|
||||
-e <extra_options> extra options for iptables
|
||||
-o apply the rules to the OUTPUT chain
|
||||
-O apply the global rules to the OUTPUT chain
|
||||
-u enable udprelay mode, TPROXY is required
|
||||
-U enable udprelay mode, using different IP
|
||||
and ports for TCP and UDP
|
||||
-y disable all auxiliary UDP rules when UDP
|
||||
transparent proxy is unavailable
|
||||
-f flush the rules
|
||||
-u enable udprelay mode, TPROXY is required
|
||||
-U enable udprelay mode, using different IP
|
||||
and ports for TCP and UDP
|
||||
-f flush the rules
|
||||
-g gfwlist mode
|
||||
-r router mode
|
||||
-c oversea mode
|
||||
-z all mode
|
||||
|
||||
# New persistence management options (use different letters to avoid conflicts)
|
||||
@@ -275,7 +274,7 @@ flush_nftables() {
|
||||
|
||||
# Optional: force delete all ss_spec related sets (even if table was accidentally deleted)
|
||||
for setname in ss_spec_lan_ac ss_spec_wan_ac ssr_gen_router \
|
||||
china fplan bplan gmlan whitelist blacklist gfwlist music; do
|
||||
china fplan bplan gmlan oversea whitelist blacklist netflix gfwlist music; do
|
||||
$NFT delete set inet ss_spec $setname 2>/dev/null
|
||||
$NFT delete set ip ss_spec_mangle $setname 2>/dev/null
|
||||
done
|
||||
@@ -311,7 +310,7 @@ flush_iptables_legacy() {
|
||||
ip route del local 0.0.0.0/0 dev lo table 999 2>/dev/null
|
||||
fi
|
||||
for setname in ss_spec_lan_ac ss_spec_wan_ac ssr_gen_router \
|
||||
china fplan bplan gmlan whitelist blacklist gfwlist music; do
|
||||
china fplan bplan gmlan oversea whitelist blacklist netflix gfwlist music; do
|
||||
ipset -X $setname 2>/dev/null
|
||||
done
|
||||
[ -n "$FWI" ] && echo '#!/bin/sh' >$FWI
|
||||
@@ -334,7 +333,7 @@ ipset_nft() {
|
||||
fi
|
||||
|
||||
# Create necessary collections
|
||||
for setname in china gmlan fplan bplan whitelist blacklist music; do
|
||||
for setname in china gmlan fplan bplan whitelist blacklist netflix music; do
|
||||
if ! $NFT list set inet ss_spec $setname >/dev/null 2>&1; then
|
||||
$NFT add set inet ss_spec $setname '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
|
||||
else
|
||||
@@ -393,6 +392,13 @@ ipset_nft() {
|
||||
$NFT add rule inet ss_spec ss_spec_wan_ac meta l4proto tcp ip daddr @music return
|
||||
fi
|
||||
|
||||
# Shunt/Netflix rules
|
||||
if [ -f "$SHUNT_LIST" ]; then
|
||||
for ip in $(cat "$SHUNT_LIST" 2>/dev/null); do
|
||||
[ -n "$ip" ] && $NFT add element inet ss_spec netflix "{ $ip }" 2>/dev/null
|
||||
done
|
||||
fi
|
||||
|
||||
# Set up mode-specific rules
|
||||
case "$RUNMODE" in
|
||||
router)
|
||||
@@ -421,6 +427,14 @@ ipset_nft() {
|
||||
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @gfwlist jump ss_spec_wan_fw
|
||||
$NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw
|
||||
;;
|
||||
oversea)
|
||||
if ! $NFT list set inet ss_spec oversea >/dev/null 2>&1; then
|
||||
$NFT add set inet ss_spec oversea '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
|
||||
fi
|
||||
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @oversea jump ss_spec_wan_fw
|
||||
$NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan jump ss_spec_wan_fw
|
||||
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @china jump ss_spec_wan_fw
|
||||
;;
|
||||
all)
|
||||
if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then
|
||||
$NFT add rule inet ss_spec ss_spec_wan_ac jump ss_spec_wan_fw
|
||||
@@ -436,10 +450,9 @@ ipset_iptables() {
|
||||
|
||||
$IPT -N SS_SPEC_WAN_AC 2>/dev/null
|
||||
$IPT -F SS_SPEC_WAN_AC
|
||||
$IPT -I SS_SPEC_WAN_AC -m mark --mark 255 -j RETURN
|
||||
|
||||
$IPT -I SS_SPEC_WAN_AC -p tcp --dport 53 -d 127.0.0.0/8 -j RETURN
|
||||
[ -n "$server" ] && $IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d "$server" -j RETURN
|
||||
$IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d "$server" -j RETURN
|
||||
|
||||
ipset -N gmlan hash:net 2>/dev/null
|
||||
for ip in $LAN_GM_IP; do ipset -! add gmlan "$ip"; done
|
||||
@@ -461,6 +474,12 @@ ipset_iptables() {
|
||||
$IPT -A SS_SPEC_WAN_AC -m set --match-set gfwlist dst -j SS_SPEC_WAN_FW
|
||||
$IPT -A SS_SPEC_WAN_AC -m set --match-set gmlan src -m set ! --match-set china dst -j SS_SPEC_WAN_FW
|
||||
;;
|
||||
oversea)
|
||||
ipset -N oversea hash:net 2>/dev/null
|
||||
$IPT -I SS_SPEC_WAN_AC -m set --match-set oversea dst -j SS_SPEC_WAN_FW
|
||||
$IPT -A SS_SPEC_WAN_AC -m set --match-set gmlan src -j SS_SPEC_WAN_FW
|
||||
$IPT -A SS_SPEC_WAN_AC -m set --match-set china dst -j SS_SPEC_WAN_FW
|
||||
;;
|
||||
all)
|
||||
$IPT -A SS_SPEC_WAN_AC -j SS_SPEC_WAN_FW
|
||||
;;
|
||||
@@ -491,6 +510,25 @@ ipset_iptables() {
|
||||
|
||||
for ip in $WAN_BP_IP; do ipset -! add whitelist "$ip"; done
|
||||
for ip in $WAN_FW_IP; do ipset -! add blacklist "$ip"; done
|
||||
|
||||
if [ "$SHUNT_PORT" != "0" ]; then
|
||||
ipset -N netflix hash:net 2>/dev/null
|
||||
for ip in $(cat "${SHUNT_LIST:=/dev/null}" 2>/dev/null); do ipset -! add netflix "$ip"; done
|
||||
case "$SHUNT_PORT" in
|
||||
0) ;;
|
||||
1)
|
||||
$IPT -I SS_SPEC_WAN_AC -p tcp -m set --match-set netflix dst -j REDIRECT --to-ports "$local_port"
|
||||
;;
|
||||
*)
|
||||
$IPT -I SS_SPEC_WAN_AC -p tcp -m set --match-set netflix dst -j REDIRECT --to-ports "$SHUNT_PORT"
|
||||
if [ "$SHUNT_PROXY" = "1" ]; then
|
||||
$IPT -I SS_SPEC_WAN_AC -p tcp -d "$SHUNT_IP" -j REDIRECT --to-ports "$local_port"
|
||||
else
|
||||
ipset -! add whitelist "$SHUNT_IP"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
return $?
|
||||
}
|
||||
|
||||
@@ -524,6 +562,22 @@ fw_rule_nft() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$SHUNT_PORT" != "0" ] && [ -f "$SHUNT_LIST" ]; then
|
||||
case "$SHUNT_PORT" in
|
||||
1)
|
||||
$NFT add rule inet ss_spec ss_spec_wan_ac $TCP_EXT_ARGS ip daddr @netflix counter redirect to :$local_port
|
||||
;;
|
||||
*)
|
||||
$NFT add rule inet ss_spec ss_spec_wan_ac $TCP_EXT_ARGS ip daddr @netflix counter redirect to :$SHUNT_PORT
|
||||
if [ "$SHUNT_PROXY" = "1" ]; then
|
||||
$NFT add rule inet ss_spec ss_spec_wan_ac $TCP_EXT_ARGS ip daddr $SHUNT_IP counter redirect to :$local_port
|
||||
else
|
||||
[ -n "$SHUNT_IP" ] && $NFT add element inet ss_spec whitelist "{ $SHUNT_IP }" 2>/dev/null
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
return $?
|
||||
}
|
||||
|
||||
@@ -616,7 +670,7 @@ ac_rule_nft() {
|
||||
fi
|
||||
|
||||
# Block UDP port 443 when TPROXY not Enable
|
||||
if [ -z "$TPROXY" ] && [ "$DISABLE_UDP_RULES" != "1" ]; then
|
||||
if [ -z "$TPROXY" ]; then
|
||||
# Add UDP 443 block rule
|
||||
if [ -z "$Interface" ]; then
|
||||
if [ -n "$MATCH_SET" ]; then
|
||||
@@ -663,11 +717,10 @@ ac_rule_nft() {
|
||||
case "$OUTPUT" in
|
||||
1)
|
||||
# Create ss_spec_output tcp chain
|
||||
if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
|
||||
$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }'
|
||||
fi
|
||||
$NFT flush chain inet ss_spec ss_spec_output 2>/dev/null
|
||||
$NFT add rule inet ss_spec ss_spec_output meta mark 255 return 2>/dev/null
|
||||
if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
|
||||
$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }'
|
||||
fi
|
||||
$NFT flush chain inet ss_spec ss_spec_output 2>/dev/null
|
||||
|
||||
# Exclude special local addresses
|
||||
if $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
|
||||
@@ -688,11 +741,10 @@ ac_rule_nft() {
|
||||
;;
|
||||
2)
|
||||
# Create ss_spec_output tcp chain
|
||||
if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
|
||||
$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }'
|
||||
fi
|
||||
$NFT flush chain inet ss_spec ss_spec_output 2>/dev/null
|
||||
$NFT add rule inet ss_spec ss_spec_output meta mark 255 return 2>/dev/null
|
||||
if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
|
||||
$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }'
|
||||
fi
|
||||
$NFT flush chain inet ss_spec ss_spec_output 2>/dev/null
|
||||
|
||||
# Exclude special local addresses
|
||||
if $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
|
||||
@@ -706,12 +758,11 @@ ac_rule_nft() {
|
||||
for ip in $(gen_spec_iplist); do
|
||||
[ -n "$ip" ] && $NFT add element inet ss_spec ssr_gen_router "{ $ip }" 2>/dev/null
|
||||
done
|
||||
if ! $NFT list chain inet ss_spec ss_spec_router >/dev/null 2>&1; then
|
||||
$NFT add chain inet ss_spec ss_spec_router 2>/dev/null
|
||||
fi
|
||||
$NFT flush chain inet ss_spec ss_spec_router 2>/dev/null
|
||||
$NFT add rule inet ss_spec ss_spec_router meta mark 255 return 2>/dev/null
|
||||
$NFT add rule inet ss_spec ss_spec_router ip daddr @ssr_gen_router return 2>/dev/null
|
||||
if ! $NFT list chain inet ss_spec ss_spec_router >/dev/null 2>&1; then
|
||||
$NFT add chain inet ss_spec ss_spec_router 2>/dev/null
|
||||
fi
|
||||
$NFT flush chain inet ss_spec ss_spec_router 2>/dev/null
|
||||
$NFT add rule inet ss_spec ss_spec_router ip daddr @ssr_gen_router return 2>/dev/null
|
||||
$NFT add rule inet ss_spec ss_spec_router jump ss_spec_wan_fw 2>/dev/null
|
||||
$NFT add rule inet ss_spec ss_spec_output $TCP_EXT_ARGS jump ss_spec_router comment "\"$TAG\"" 2>/dev/null
|
||||
;;
|
||||
@@ -741,7 +792,7 @@ ac_rule_iptables() {
|
||||
EOF
|
||||
|
||||
# Block UDP port 443 when TPROXY not Enable
|
||||
if [ -z "$TPROXY" ] && [ "$DISABLE_UDP_RULES" != "1" ]; then
|
||||
if [ -z "$TPROXY" ]; then
|
||||
# Add UDP 443 block rule
|
||||
if [ -z "$Interface" ]; then
|
||||
# Global rules
|
||||
@@ -794,11 +845,10 @@ ac_rule_iptables() {
|
||||
create ssr_gen_router hash:net
|
||||
$(gen_spec_iplist | sed -e "s/^/add ssr_gen_router /")
|
||||
EOF
|
||||
$IPT -N SS_SPEC_ROUTER 2>/dev/null
|
||||
$IPT -F SS_SPEC_ROUTER 2>/dev/null
|
||||
$IPT -A SS_SPEC_ROUTER -m mark --mark 255 -j RETURN && \
|
||||
$IPT -A SS_SPEC_ROUTER -m set --match-set ssr_gen_router dst -j RETURN && \
|
||||
$IPT -A SS_SPEC_ROUTER -j SS_SPEC_WAN_FW
|
||||
$IPT -N SS_SPEC_ROUTER 2>/dev/null
|
||||
$IPT -F SS_SPEC_ROUTER 2>/dev/null
|
||||
$IPT -A SS_SPEC_ROUTER -m set --match-set ssr_gen_router dst -j RETURN && \
|
||||
$IPT -A SS_SPEC_ROUTER -j SS_SPEC_WAN_FW
|
||||
$IPT -I OUTPUT 1 -p tcp -m comment --comment "$TAG" -j SS_SPEC_ROUTER
|
||||
;;
|
||||
esac
|
||||
@@ -956,6 +1006,14 @@ tp_rule_nft() {
|
||||
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr @gfwlist counter tproxy ip to :"$LOCAL_PORT" meta mark set ${FWMARK} 2>/dev/null
|
||||
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan ip daddr != @china counter tproxy ip to :"$LOCAL_PORT" meta mark set ${FWMARK} 2>/dev/null
|
||||
;;
|
||||
oversea)
|
||||
if ! $NFT list set ip ss_spec_mangle oversea >/dev/null 2>&1; then
|
||||
$NFT add set ip ss_spec_mangle oversea '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
|
||||
fi
|
||||
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip saddr @oversea counter tproxy ip to :"$LOCAL_PORT" meta mark set ${FWMARK} 2>/dev/null
|
||||
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr @china counter tproxy ip to :"$LOCAL_PORT" meta mark set ${FWMARK} 2>/dev/null
|
||||
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan counter tproxy ip to :"$LOCAL_PORT" meta mark set ${FWMARK} 2>/dev/null
|
||||
;;
|
||||
all)
|
||||
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS counter tproxy ip to :"$LOCAL_PORT" meta mark set ${FWMARK} 2>/dev/null
|
||||
;;
|
||||
@@ -1032,8 +1090,8 @@ tp_rule_iptables() {
|
||||
do
|
||||
$ipt -A SS_SPEC_TPROXY -p udp -d "$net" -j RETURN
|
||||
done
|
||||
[ -n "$SERVER" ] && $ipt -A SS_SPEC_TPROXY -p udp ! --dport 53 -d "$SERVER" -j RETURN
|
||||
[ -n "$SERVER" ] && [ "$server" != "$SERVER" ] && ipset -! add whitelist "$SERVER"
|
||||
$ipt -A SS_SPEC_TPROXY -p udp ! --dport 53 -d "$SERVER" -j RETURN
|
||||
[ "$server" != "$SERVER" ] && ipset -! add whitelist "$SERVER"
|
||||
if [ -f "${xhttp_ip:=/etc/ssrplus/xhttp_address.txt}" ]; then
|
||||
while IFS= read -r ip; do
|
||||
[ -n "$ip" ] && ipset add whitelist "$ip" -exist
|
||||
@@ -1061,6 +1119,11 @@ tp_rule_iptables() {
|
||||
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set gfwlist dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK}
|
||||
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set gmlan src -m set ! --match-set china dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK}
|
||||
;;
|
||||
oversea)
|
||||
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set oversea src -m dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK}
|
||||
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set gmlan src -m set -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK}
|
||||
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set china dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK}
|
||||
;;
|
||||
all)
|
||||
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK}
|
||||
;;
|
||||
@@ -1432,7 +1495,7 @@ restore_from_persistence() {
|
||||
fi
|
||||
}
|
||||
|
||||
while getopts ":m:s:l:S:L:i:e:a:B:b:w:p:G:D:F:N:M:I:oOuUyfgrzAKPCRXh" arg; do
|
||||
while getopts ":m:s:l:S:L:i:e:a:B:b:w:p:G:D:F:N:M:I:oOuUfgrczAKPCRXh" arg; do
|
||||
case "$arg" in
|
||||
m)
|
||||
Interface=$OPTARG
|
||||
@@ -1500,15 +1563,15 @@ while getopts ":m:s:l:S:L:i:e:a:B:b:w:p:G:D:F:N:M:I:oOuUyfgrzAKPCRXh" arg; do
|
||||
U)
|
||||
TPROXY=2
|
||||
;;
|
||||
y)
|
||||
DISABLE_UDP_RULES=1
|
||||
;;
|
||||
g)
|
||||
RUNMODE=gfw
|
||||
;;
|
||||
r)
|
||||
RUNMODE=router
|
||||
;;
|
||||
c)
|
||||
RUNMODE=oversea
|
||||
;;
|
||||
z)
|
||||
RUNMODE=all
|
||||
;;
|
||||
@@ -1640,7 +1703,7 @@ runmode_change() {
|
||||
}
|
||||
|
||||
# Main process
|
||||
if [ -n "$local_port" ]; then
|
||||
if [ -n "$server" ] && [ -n "$local_port" ]; then
|
||||
if ! echo "$local_port" | grep -qE '^[0-9]+$'; then
|
||||
loger 3 "Invalid local port: $local_port"
|
||||
exit 1
|
||||
@@ -1652,6 +1715,7 @@ if [ -n "$local_port" ]; then
|
||||
LOCAL_PORT=$local_port
|
||||
;;
|
||||
2)
|
||||
: ${SERVER:?"You must assign an ip for the udp relay server."}
|
||||
: ${LOCAL_PORT:?"You must assign a port for the udp relay server."}
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/bin/sh /etc/rc.common
|
||||
#
|
||||
# Copyright (C) 2017 openwrt-ssr
|
||||
# Copyright (C) 2017 yushi studio <ywb94@qq.com>
|
||||
@@ -7,276 +7,149 @@
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
|
||||
ORIG_ARG1="${1:-}"
|
||||
ORIG_ARG2="${2:-}"
|
||||
ORIG_ARG3="${3:-}"
|
||||
[ -f /lib/functions.sh ] && . /lib/functions.sh
|
||||
. $IPKG_INSTROOT/etc/init.d/shadowsocksr
|
||||
LOCK_FILE="/var/lock/ssr-switch.lock"
|
||||
PROBE_SID=""
|
||||
[ -f "$LOCK_FILE" ] && exit 2
|
||||
touch "$LOCK_FILE"
|
||||
LOG_FILE=/var/log/ssrplus.log
|
||||
|
||||
cycle_time=60
|
||||
switch_time=3
|
||||
probe_socks_port=10800
|
||||
direct_fail_count=0
|
||||
direct_fail_limit=3
|
||||
probe_url="$(uci_get_by_type server_subscribe url_test_url)"
|
||||
[ -n "$probe_url" ] || probe_url="https://www.google.com/generate_204"
|
||||
PROBE_INSTANCE_KEY="ssr-switch-probe-local"
|
||||
normal_flag=0
|
||||
server_locate=0
|
||||
server_count=0
|
||||
ENABLE_SERVER=nil
|
||||
[ -n "$1" ] && cycle_time=$1
|
||||
[ -n "$2" ] && switch_time=$2
|
||||
DEFAULT_SERVER=$(uci_get_by_type global global_server)
|
||||
CURRENT_SERVER=$DEFAULT_SERVER
|
||||
|
||||
DEFAULT_SERVER="$(uci_get_by_type global global_server)"
|
||||
CURRENT_SERVER="$DEFAULT_SERVER"
|
||||
ENABLE_SERVER="nil"
|
||||
|
||||
cleanup_probe_socks() {
|
||||
ps_list | grep -v "grep" | grep -E "tcp-http-ssr-switch-local\\.json|ss-${PROBE_INSTANCE_KEY}/config\\.yaml|tuic-${PROBE_INSTANCE_KEY}/config\\.yaml" | awk '{print $1}' | xargs kill >/dev/null 2>&1
|
||||
sleep 1
|
||||
ps_list | grep -v "grep" | grep -E "tcp-http-ssr-switch-local\\.json|ss-${PROBE_INSTANCE_KEY}/config\\.yaml|tuic-${PROBE_INSTANCE_KEY}/config\\.yaml" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1
|
||||
rm -f "$TMP_PATH/tcp-http-ssr-switch-local.json" 2>/dev/null
|
||||
rm -rf "$TMP_PATH/ss-${PROBE_INSTANCE_KEY}" "$TMP_PATH/tuic-${PROBE_INSTANCE_KEY}" 2>/dev/null
|
||||
rm -f /tmp/ssr-switch-probe.log 2>/dev/null
|
||||
}
|
||||
|
||||
start_probe_socks() {
|
||||
local sid="$1"
|
||||
local server_type
|
||||
|
||||
server_type="$(uci_get_by_name "$sid" type)"
|
||||
[ "$server_type" = "clash" ] && return 1
|
||||
[ "$server_type" = "tuic" ] && return 1
|
||||
[ "$server_type" = "socks5" ] && return 1
|
||||
|
||||
cleanup_probe_socks
|
||||
LOCAL_SERVER="$sid"
|
||||
local_config_file="$TMP_PATH/tcp-http-ssr-switch-local.json"
|
||||
_local="2"
|
||||
local_enable=0
|
||||
tmp_local_port=
|
||||
mode="tcp,udp"
|
||||
|
||||
if [ "$server_type" = "v2ray" ]; then
|
||||
export SSR_SWITCH_PROBE=1
|
||||
gen_config_file "$LOCAL_SERVER" "$server_type" 4 0 "$probe_socks_port" >/tmp/ssr-switch-probe.log 2>&1 || return 1
|
||||
unset SSR_SWITCH_PROBE
|
||||
local xray_bin
|
||||
xray_bin="$(first_type xray)"
|
||||
[ -x "$xray_bin" ] || return 1
|
||||
"$xray_bin" run -c "$local_config_file" >>/tmp/ssr-switch-probe.log 2>&1 &
|
||||
else
|
||||
export SSR_SWITCH_PROBE=1
|
||||
export SSR_SWITCH_PROBE_INSTANCE_KEY="$PROBE_INSTANCE_KEY"
|
||||
start_local_with_port "$probe_socks_port" >/tmp/ssr-switch-probe.log 2>&1 || return 1
|
||||
unset SSR_SWITCH_PROBE_INSTANCE_KEY
|
||||
unset SSR_SWITCH_PROBE
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
return 0
|
||||
}
|
||||
|
||||
probe_http_ok() {
|
||||
case "$1" in
|
||||
[1-5][0-9][0-9])
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
check_direct_probe() {
|
||||
local code
|
||||
|
||||
code="$(curl -k -sS -L -o /dev/null \
|
||||
--connect-timeout "$switch_time" \
|
||||
--max-time "$switch_time" \
|
||||
-w '%{http_code}' \
|
||||
"$probe_url" 2>/dev/null || true)"
|
||||
probe_http_ok "$code"
|
||||
}
|
||||
|
||||
check_proxy_via_socks() {
|
||||
local try_count
|
||||
local code
|
||||
local proxy_host
|
||||
|
||||
try_count="$(uci_get_by_type global switch_try_count 3)"
|
||||
for i in $(seq 1 "$try_count"); do
|
||||
for proxy_host in "127.0.0.1:$probe_socks_port" "[::1]:$probe_socks_port"; do
|
||||
code="$(curl -k -sS -L -o /dev/null \
|
||||
--connect-timeout "$switch_time" \
|
||||
--max-time "$switch_time" \
|
||||
--socks5-hostname "$proxy_host" \
|
||||
-w '%{http_code}' \
|
||||
"$probe_url" 2>/dev/null || true)"
|
||||
probe_http_ok "$code" && return 0
|
||||
done
|
||||
#判断代理是否正常
|
||||
check_proxy() {
|
||||
local result=0
|
||||
local try_count=$(uci_get_by_type global switch_try_count 3)
|
||||
for i in $(seq 1 $try_count); do
|
||||
/usr/bin/ssr-check www.google.com 80 $switch_time 1
|
||||
if [ "$?" == "0" ]; then
|
||||
# echolog "Check Google Proxy Success, count=$i"
|
||||
result=0
|
||||
break
|
||||
else
|
||||
# echolog "Check Google Proxy Fail, count=$i"
|
||||
/usr/bin/ssr-check www.baidu.com 80 $switch_time 1
|
||||
if [ "$?" == "0" ]; then
|
||||
result=1
|
||||
else
|
||||
result=2
|
||||
fi
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
return 1
|
||||
return $result
|
||||
}
|
||||
|
||||
test_proxy() {
|
||||
local sid="$1"
|
||||
[ "$(uci_get_by_name "$sid" type)" = "clash" ] && return 1
|
||||
[ "$(uci_get_by_name "$sid" type)" = "tuic" ] && return 1
|
||||
start_probe_socks "$sid" || {
|
||||
cleanup_probe_socks
|
||||
local servername=$(uci_get_by_name $1 server)
|
||||
local serverport=$(uci_get_by_name $1 server_port)
|
||||
ipset add whitelist $servername 2>/dev/null
|
||||
tcping -q -c 3 -i 1 -t 2 -p $serverport $servername
|
||||
if [ "$?" -gt "0" ]; then
|
||||
ipset del whitelist $servername 2>/dev/null
|
||||
return 1
|
||||
}
|
||||
check_proxy_via_socks
|
||||
fi
|
||||
/usr/bin/ssr-check $servername $serverport $switch_time
|
||||
local ret=$?
|
||||
cleanup_probe_socks
|
||||
return $ret
|
||||
ipset del whitelist $servername 2>/dev/null
|
||||
if [ "$ret" == "0" ]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
search_proxy() {
|
||||
local sid="$1"
|
||||
[ "$(uci_get_by_name "$sid" switch_enable 0)" = "1" ] || return 1
|
||||
[ "$(uci_get_by_name "$sid" type)" = "clash" ] && return 1
|
||||
[ "$(uci_get_by_name "$sid" type)" = "tuic" ] && return 1
|
||||
[ "$sid" = "$CURRENT_SERVER" ] && return 1
|
||||
[ "$ENABLE_SERVER" != "nil" ] && return 0
|
||||
|
||||
if test_proxy "$sid"; then
|
||||
ENABLE_SERVER="$sid"
|
||||
let server_count=server_count+1
|
||||
[ "$normal_flag" == "1" -a "$server_count" -le "$server_locate" ] && return 0
|
||||
[ "$(uci_get_by_name $1 switch_enable 0)" != "1" ] && return 1
|
||||
[ $ENABLE_SERVER != nil ] && return 0
|
||||
[ "$1" == "$CURRENT_SERVER" ] && return 0
|
||||
local servername=$(uci_get_by_name $1 server)
|
||||
local serverport=$(uci_get_by_name $1 server_port)
|
||||
ipset add whitelist $servername 2>/dev/null
|
||||
/usr/bin/ssr-check $servername $serverport $switch_time
|
||||
local ret=$?
|
||||
ipset del whitelist $servername 2>/dev/null
|
||||
if [ "$ret" == "0" ]; then
|
||||
server_locate=$server_count
|
||||
ENABLE_SERVER=$1
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
#选择可用的代理
|
||||
select_proxy() {
|
||||
config_load "$NAME"
|
||||
ENABLE_SERVER="nil"
|
||||
config_load $NAME
|
||||
ENABLE_SERVER=nil
|
||||
mkdir -p /var/run /var/etc
|
||||
server_count=0
|
||||
config_foreach search_proxy servers
|
||||
}
|
||||
|
||||
set_main_server() {
|
||||
local sid="$1"
|
||||
[ -n "$sid" ] || return 1
|
||||
uci set shadowsocksr.@global[0].global_server="$sid"
|
||||
uci commit shadowsocksr
|
||||
return 0
|
||||
}
|
||||
|
||||
#切换代理
|
||||
switch_proxy() {
|
||||
set_main_server "$1" || return 1
|
||||
/etc/init.d/shadowsocksr restart "$1"
|
||||
/etc/init.d/shadowsocksr restart $1
|
||||
return 0
|
||||
}
|
||||
|
||||
start() {
|
||||
while [ "1" = "1" ]; do
|
||||
sleep "0000$cycle_time"
|
||||
run_once
|
||||
#不支持kcptun启用时的切换
|
||||
[ $(uci_get_by_name $DEFAULT_SERVER kcp_enable) = "1" ] && return 1
|
||||
while [ "1" == "1" ]; do #死循环
|
||||
sleep 0000$cycle_time
|
||||
LOGTIME=$(date "+%Y-%m-%d %H:%M:%S")
|
||||
#判断当前代理是否为缺省服务器
|
||||
if [ "$CURRENT_SERVER" != "$DEFAULT_SERVER" ]; then
|
||||
#echo "not default proxy"
|
||||
echolog "Current server is not default Main server, try to switch back."
|
||||
#检查缺省服务器是否正常
|
||||
if test_proxy $DEFAULT_SERVER; then
|
||||
#echo "switch to default proxy"
|
||||
echolog "Main server is avilable."
|
||||
#缺省服务器正常,切换回来
|
||||
CURRENT_SERVER=$DEFAULT_SERVER
|
||||
switch_proxy $CURRENT_SERVER
|
||||
echolog "switch to default "$(uci_get_by_name $CURRENT_SERVER alias)" proxy!"
|
||||
else
|
||||
echolog "Main server is NOT avilable.Continue using current server."
|
||||
fi
|
||||
fi
|
||||
#判断当前代理是否正常
|
||||
#echolog "Start checking if the current server is available."
|
||||
check_proxy
|
||||
current_ret=$?
|
||||
if [ "$current_ret" == "1" ]; then
|
||||
#当前代理错误,判断有无可用的服务器
|
||||
#echo "current error"
|
||||
echolog "Current server error, try to switch another server."
|
||||
select_proxy
|
||||
if [ "$ENABLE_SERVER" != nil ]; then
|
||||
#有其他服务器可用,进行切换
|
||||
#echo $(uci_get_by_name $new_proxy server)
|
||||
echolog "Another server is avilable, now switching server."
|
||||
CURRENT_SERVER=$ENABLE_SERVER
|
||||
switch_proxy $CURRENT_SERVER
|
||||
normal_flag=1
|
||||
echolog "Switch to "$(uci_get_by_name $CURRENT_SERVER alias)" proxy!"
|
||||
else
|
||||
switch_proxy $CURRENT_SERVER
|
||||
normal_flag=1
|
||||
echolog "Try restart current server."
|
||||
fi
|
||||
else
|
||||
normal_flag=0
|
||||
# echolog "ShadowsocksR No Problem."
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
run_once() {
|
||||
DEFAULT_SERVER="$(uci_get_by_type global global_server)"
|
||||
[ "$DEFAULT_SERVER" = "nil" ] && {
|
||||
direct_fail_count=0
|
||||
return 0
|
||||
}
|
||||
[ "$(uci_get_by_name "$DEFAULT_SERVER" type)" = "clash" ] && {
|
||||
direct_fail_count=0
|
||||
cleanup_probe_socks
|
||||
return 0
|
||||
}
|
||||
[ "$(uci_get_by_name "$DEFAULT_SERVER" type)" = "tuic" ] && {
|
||||
direct_fail_count=0
|
||||
cleanup_probe_socks
|
||||
return 0
|
||||
}
|
||||
[ "$(uci_get_by_name "$DEFAULT_SERVER" kcp_enable 0)" = "1" ] && {
|
||||
direct_fail_count=0
|
||||
cleanup_probe_socks
|
||||
return 1
|
||||
}
|
||||
|
||||
CURRENT_SERVER="$DEFAULT_SERVER"
|
||||
|
||||
if check_direct_probe; then
|
||||
direct_fail_count=0
|
||||
return 0
|
||||
fi
|
||||
|
||||
direct_fail_count=$((direct_fail_count + 1))
|
||||
[ "$direct_fail_count" -lt "$direct_fail_limit" ] && return 0
|
||||
direct_fail_count=0
|
||||
|
||||
if test_proxy "$CURRENT_SERVER"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echolog "Current server error, try to switch another server."
|
||||
select_proxy
|
||||
if [ "$ENABLE_SERVER" != "nil" ]; then
|
||||
CURRENT_SERVER="$ENABLE_SERVER"
|
||||
switch_proxy "$CURRENT_SERVER"
|
||||
echolog "Switch to $(uci_get_by_name "$CURRENT_SERVER" alias) proxy!"
|
||||
else
|
||||
switch_proxy "$CURRENT_SERVER"
|
||||
echolog "Try restart current server."
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
probe_once() {
|
||||
local sid="$1"
|
||||
[ -n "$sid" ] || {
|
||||
echo "missing_sid"
|
||||
return 1
|
||||
}
|
||||
cleanup_probe_socks
|
||||
if ! start_probe_socks "$sid"; then
|
||||
echo "probe_start:fail"
|
||||
return 1
|
||||
fi
|
||||
echo "probe_start:ok"
|
||||
echo "probe:curl_ipv4"
|
||||
curl -k -sS -L -o /dev/null \
|
||||
--connect-timeout "$switch_time" \
|
||||
--max-time "$switch_time" \
|
||||
--socks5-hostname "127.0.0.1:$probe_socks_port" \
|
||||
-w 'code=%{http_code}\n' \
|
||||
"$probe_url" 2>/dev/null || true
|
||||
echo "probe:curl_ipv6"
|
||||
curl -g -k -sS -L -o /dev/null \
|
||||
--connect-timeout "$switch_time" \
|
||||
--max-time "$switch_time" \
|
||||
--socks5-hostname "[::1]:$probe_socks_port" \
|
||||
-w 'code=%{http_code}\n' \
|
||||
"$probe_url" 2>/dev/null || true
|
||||
cleanup_probe_socks
|
||||
return 0
|
||||
}
|
||||
|
||||
main() {
|
||||
case "${ORIG_ARG1:-}" in
|
||||
probe)
|
||||
PROBE_SID="${ORIG_ARG2:-}"
|
||||
probe_once "$PROBE_SID"
|
||||
;;
|
||||
once)
|
||||
run_once
|
||||
;;
|
||||
start)
|
||||
[ -n "$ORIG_ARG2" ] && cycle_time="$ORIG_ARG2"
|
||||
[ -n "$ORIG_ARG3" ] && switch_time="$ORIG_ARG3"
|
||||
[ -f "$LOCK_FILE" ] && exit 2
|
||||
touch "$LOCK_FILE"
|
||||
start
|
||||
;;
|
||||
*)
|
||||
[ -n "$ORIG_ARG1" ] && cycle_time="$ORIG_ARG1"
|
||||
[ -n "$ORIG_ARG2" ] && switch_time="$ORIG_ARG2"
|
||||
[ -f "$LOCK_FILE" ] && exit 2
|
||||
touch "$LOCK_FILE"
|
||||
start
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [ "${0##*/}" = "ssr-switch" ]; then
|
||||
main "$@"
|
||||
fi
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 215 KiB |
@@ -1,939 +0,0 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
require "nixio.fs"
|
||||
require "luci.model.uci"
|
||||
|
||||
local ok_lyaml, lyaml = pcall(require, "lyaml")
|
||||
if not ok_lyaml then
|
||||
io.stderr:write("lyaml_not_found\n")
|
||||
os.exit(2)
|
||||
end
|
||||
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local ok_jsonc, jsonc = pcall(require, "luci.jsonc")
|
||||
|
||||
local function read_file(path)
|
||||
local data = nixio.fs.readfile(path)
|
||||
if not data or data == "" then
|
||||
return nil
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
local function write_file(path, data)
|
||||
return nixio.fs.writefile(path, data)
|
||||
end
|
||||
|
||||
local function load_yaml(path)
|
||||
local raw = read_file(path)
|
||||
if not raw then
|
||||
return nil, "read_failed"
|
||||
end
|
||||
|
||||
local ok, parsed = pcall(lyaml.load, raw)
|
||||
if not ok or type(parsed) ~= "table" then
|
||||
return nil, "parse_failed"
|
||||
end
|
||||
|
||||
return parsed
|
||||
end
|
||||
|
||||
local function dump_yaml(path, data)
|
||||
local ok, rendered = pcall(lyaml.dump, { data })
|
||||
if not ok or not rendered then
|
||||
return nil, "dump_failed"
|
||||
end
|
||||
|
||||
write_file(path, rendered)
|
||||
return true
|
||||
end
|
||||
|
||||
local function split_filter_words(text)
|
||||
local items = {}
|
||||
for part in tostring(text or ""):gmatch("[^/]+") do
|
||||
if part ~= "" then
|
||||
items[#items + 1] = part
|
||||
end
|
||||
end
|
||||
return items
|
||||
end
|
||||
|
||||
local function trim(value)
|
||||
return tostring(value or ""):gsub("^%s+", ""):gsub("%s+$", "")
|
||||
end
|
||||
|
||||
local function parse_csv_line(line)
|
||||
local cols = {}
|
||||
local cur = ""
|
||||
local in_quote = false
|
||||
local i = 1
|
||||
|
||||
while i <= #line do
|
||||
local ch = line:sub(i, i)
|
||||
if ch == '"' then
|
||||
if in_quote and line:sub(i + 1, i + 1) == '"' then
|
||||
cur = cur .. '"'
|
||||
i = i + 1
|
||||
else
|
||||
in_quote = not in_quote
|
||||
end
|
||||
elseif ch == "," and not in_quote then
|
||||
cols[#cols + 1] = cur
|
||||
cur = ""
|
||||
else
|
||||
cur = cur .. ch
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
cols[#cols + 1] = cur
|
||||
return cols
|
||||
end
|
||||
|
||||
local function read_clash_client_rules_csv(sid)
|
||||
local rows = {}
|
||||
sid = trim(sid)
|
||||
if sid == "" then
|
||||
return rows
|
||||
end
|
||||
|
||||
local csv_path = string.format("/etc/ssrplus/clash/%s.csv", sid)
|
||||
local raw = read_file(csv_path)
|
||||
if not raw or raw == "" then
|
||||
return rows
|
||||
end
|
||||
|
||||
local first = true
|
||||
for line in tostring(raw):gsub("\r", ""):gmatch("[^\n]+") do
|
||||
local text = trim(line)
|
||||
if text ~= "" then
|
||||
if first and text:lower() == "enabled,client,policy,remarks,client_mac" then
|
||||
first = false
|
||||
else
|
||||
local cols = parse_csv_line(line)
|
||||
if #cols >= 4 then
|
||||
rows[#rows + 1] = {
|
||||
enabled = cols[1],
|
||||
ip_addr = trim(cols[2] or ""),
|
||||
policy_group = trim(cols[3] or ""),
|
||||
remarks = trim(cols[4] or ""),
|
||||
client_mac = trim(cols[5] or "")
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return rows
|
||||
end
|
||||
|
||||
local function has_proxy_sections(doc)
|
||||
return type(doc.proxies) == "table" or type(doc["proxy-providers"]) == "table"
|
||||
end
|
||||
|
||||
local function validate(path)
|
||||
local doc = load_yaml(path)
|
||||
if not doc then
|
||||
return false
|
||||
end
|
||||
return has_proxy_sections(doc)
|
||||
end
|
||||
|
||||
local function filter(path, filter_words)
|
||||
local doc, err = load_yaml(path)
|
||||
if not doc then
|
||||
io.stderr:write(err or "parse_failed", "\n")
|
||||
return false
|
||||
end
|
||||
|
||||
local words = split_filter_words(filter_words)
|
||||
if #words == 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
local removed = {}
|
||||
local proxies = {}
|
||||
for _, proxy in ipairs(doc.proxies or {}) do
|
||||
local name = tostring(proxy.name or "")
|
||||
local matched = false
|
||||
for _, word in ipairs(words) do
|
||||
if name:find(word, 1, true) then
|
||||
matched = true
|
||||
removed[name] = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not matched then
|
||||
proxies[#proxies + 1] = proxy
|
||||
end
|
||||
end
|
||||
doc.proxies = proxies
|
||||
|
||||
for _, group in ipairs(doc["proxy-groups"] or {}) do
|
||||
if type(group.proxies) == "table" then
|
||||
local kept = {}
|
||||
for _, name in ipairs(group.proxies) do
|
||||
if not removed[tostring(name)] then
|
||||
kept[#kept + 1] = name
|
||||
end
|
||||
end
|
||||
group.proxies = kept
|
||||
end
|
||||
end
|
||||
|
||||
local count = 0
|
||||
for _ in pairs(removed) do
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
dump_yaml(path, doc)
|
||||
io.stdout:write(tostring(count), "\n")
|
||||
return true
|
||||
end
|
||||
|
||||
local function deep_merge(dst, src)
|
||||
if type(dst) ~= "table" or type(src) ~= "table" then
|
||||
return src
|
||||
end
|
||||
|
||||
for k, v in pairs(src) do
|
||||
if type(v) == "table" and type(dst[k]) == "table" then
|
||||
dst[k] = deep_merge(dst[k], v)
|
||||
else
|
||||
dst[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
return dst
|
||||
end
|
||||
|
||||
local function strip_runtime_conflicts(doc)
|
||||
doc.tun = nil
|
||||
doc.listeners = nil
|
||||
doc["redir-port"] = nil
|
||||
doc["tproxy-port"] = nil
|
||||
doc["socks-port"] = nil
|
||||
doc["mixed-port"] = nil
|
||||
doc.port = nil
|
||||
doc["external-controller"] = nil
|
||||
doc.secret = nil
|
||||
doc["allow-lan"] = nil
|
||||
if type(doc.dns) == "table" then
|
||||
doc.dns["fake-ip-range"] = nil
|
||||
doc.dns["fake-ip-filter"] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function group_requires_candidates(group)
|
||||
local gtype = tostring(group and group.type or ""):lower()
|
||||
return gtype == "select"
|
||||
or gtype == "fallback"
|
||||
or gtype == "load-balance"
|
||||
or gtype == "url-test"
|
||||
or gtype == "relay"
|
||||
end
|
||||
|
||||
local function has_nonempty_sequence(value)
|
||||
return type(value) == "table" and next(value) ~= nil
|
||||
end
|
||||
|
||||
local function fill_empty_proxy_groups(doc)
|
||||
local changed = 0
|
||||
for _, group in ipairs(doc["proxy-groups"] or {}) do
|
||||
if type(group) == "table"
|
||||
and group_requires_candidates(group)
|
||||
and not has_nonempty_sequence(group.proxies)
|
||||
and not has_nonempty_sequence(group.use)
|
||||
then
|
||||
group.proxies = { "DIRECT" }
|
||||
changed = changed + 1
|
||||
end
|
||||
end
|
||||
return changed
|
||||
end
|
||||
|
||||
local function strip_incompatible_script_rules(doc)
|
||||
local kept = {}
|
||||
local removed = 0
|
||||
local has_script_rule = false
|
||||
|
||||
for _, rule in ipairs(doc.rules or {}) do
|
||||
local text = tostring(rule or "")
|
||||
if text:match("^SCRIPT,") then
|
||||
removed = removed + 1
|
||||
else
|
||||
kept[#kept + 1] = rule
|
||||
if text:match("^SCRIPT,") then
|
||||
has_script_rule = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if removed > 0 then
|
||||
doc.rules = kept
|
||||
end
|
||||
|
||||
if not has_script_rule then
|
||||
doc.script = nil
|
||||
end
|
||||
|
||||
return removed
|
||||
end
|
||||
|
||||
local function prepare(input_path, output_path)
|
||||
local doc, err = load_yaml(input_path)
|
||||
if not doc then
|
||||
io.stderr:write(err or "parse_failed", "\n")
|
||||
return false
|
||||
end
|
||||
if not has_proxy_sections(doc) then
|
||||
io.stderr:write("missing_proxy_sections\n")
|
||||
return false
|
||||
end
|
||||
|
||||
strip_runtime_conflicts(doc)
|
||||
local filled_groups = fill_empty_proxy_groups(doc)
|
||||
local stripped_rules = strip_incompatible_script_rules(doc)
|
||||
local ok, rendered = pcall(lyaml.dump, { doc })
|
||||
if not ok or not rendered then
|
||||
io.stderr:write("dump_failed\n")
|
||||
return false
|
||||
end
|
||||
|
||||
write_file(output_path, rendered)
|
||||
io.stdout:write(string.format("filled_groups=%d stripped_script_rules=%d\n", filled_groups, stripped_rules))
|
||||
return true
|
||||
end
|
||||
|
||||
local function merge(raw_path, overlay_path, output_path)
|
||||
local raw_doc, raw_err = load_yaml(raw_path)
|
||||
if not raw_doc then
|
||||
io.stderr:write(raw_err or "parse_failed", "\n")
|
||||
return false
|
||||
end
|
||||
|
||||
local overlay_doc, overlay_err = load_yaml(overlay_path)
|
||||
if not overlay_doc then
|
||||
io.stderr:write(overlay_err or "parse_failed", "\n")
|
||||
return false
|
||||
end
|
||||
|
||||
strip_runtime_conflicts(raw_doc)
|
||||
local filled_groups = fill_empty_proxy_groups(raw_doc)
|
||||
local stripped_rules = strip_incompatible_script_rules(raw_doc)
|
||||
local merged = deep_merge(raw_doc, overlay_doc)
|
||||
local ok, rendered = pcall(lyaml.dump, { merged })
|
||||
if not ok or not rendered then
|
||||
io.stderr:write("dump_failed\n")
|
||||
return false
|
||||
end
|
||||
|
||||
write_file(output_path, rendered)
|
||||
io.stdout:write(string.format("filled_groups=%d stripped_script_rules=%d\n", filled_groups, stripped_rules))
|
||||
return true
|
||||
end
|
||||
|
||||
local function append_client_policy_rules(runtime_path, sid)
|
||||
local doc, err = load_yaml(runtime_path)
|
||||
if not doc then
|
||||
io.stderr:write(err or "parse_failed", "\n")
|
||||
return false
|
||||
end
|
||||
|
||||
local valid_policies = {}
|
||||
for _, proxy in ipairs(doc.proxies or {}) do
|
||||
if type(proxy) == "table" and proxy.name and proxy.name ~= "" then
|
||||
valid_policies[tostring(proxy.name)] = true
|
||||
end
|
||||
end
|
||||
for _, group in ipairs(doc["proxy-groups"] or {}) do
|
||||
if type(group) == "table" and group.name and group.name ~= "" then
|
||||
valid_policies[tostring(group.name)] = true
|
||||
end
|
||||
end
|
||||
|
||||
local custom_rules = {}
|
||||
for _, section in ipairs(read_clash_client_rules_csv(sid)) do
|
||||
if tostring(section.enabled or "0") == "1" then
|
||||
local ip_addr = tostring(section.ip_addr or "")
|
||||
local policy_group = tostring(section.policy_group or "")
|
||||
if ip_addr ~= "" and policy_group ~= "" and valid_policies[policy_group] then
|
||||
if not ip_addr:find("/", 1, true) then
|
||||
ip_addr = ip_addr .. "/32"
|
||||
end
|
||||
custom_rules[#custom_rules + 1] = string.format("SRC-IP-CIDR,%s,%s", ip_addr, policy_group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #custom_rules == 0 then
|
||||
io.stdout:write("client_rules=0\n")
|
||||
return true
|
||||
end
|
||||
|
||||
local existing_rules = {}
|
||||
for _, rule in ipairs(doc.rules or {}) do
|
||||
local text = tostring(rule or "")
|
||||
if not text:match("^SRC%-IP%-CIDR,") then
|
||||
existing_rules[#existing_rules + 1] = rule
|
||||
end
|
||||
end
|
||||
|
||||
doc.rules = {}
|
||||
for _, rule in ipairs(custom_rules) do
|
||||
doc.rules[#doc.rules + 1] = rule
|
||||
end
|
||||
for _, rule in ipairs(existing_rules) do
|
||||
doc.rules[#doc.rules + 1] = rule
|
||||
end
|
||||
|
||||
local ok, rendered = pcall(lyaml.dump, { doc })
|
||||
if not ok or not rendered then
|
||||
io.stderr:write("dump_failed\n")
|
||||
return false
|
||||
end
|
||||
|
||||
write_file(runtime_path, rendered)
|
||||
io.stdout:write(string.format("client_rules=%d\n", #custom_rules))
|
||||
return true
|
||||
end
|
||||
|
||||
local function bool_enabled(value)
|
||||
return value == "1" or value == 1 or value == true or value == "true"
|
||||
end
|
||||
|
||||
local function split_csv(value)
|
||||
local items = {}
|
||||
for part in tostring(value or ""):gmatch("[^,%s]+") do
|
||||
items[#items + 1] = part
|
||||
end
|
||||
return items
|
||||
end
|
||||
|
||||
local function get_server_field(sid, option, default)
|
||||
local value = uci:get("shadowsocksr", sid, option)
|
||||
if value == nil or value == "" then
|
||||
return default
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
local function get_filter_aaaa()
|
||||
local value = uci:get_first("shadowsocksr", "global", "filter_aaaa", "1")
|
||||
if value == nil or value == "" then
|
||||
value = uci:get_first("shadowsocksr", "global", "mosdns_ipv6", "1")
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
local function build_tuic_runtime_doc(sid, local_port, socks_port, mode)
|
||||
local server = get_server_field(sid, "server", "")
|
||||
local server_port = tonumber(get_server_field(sid, "server_port", "0")) or 0
|
||||
local tuic_ip = get_server_field(sid, "tuic_ip", "")
|
||||
local tls_host = get_server_field(sid, "tls_host", "")
|
||||
local ipstack_prefer = get_server_field(sid, "ipstack_prefer", "")
|
||||
local dns_mode = uci:get_first("shadowsocksr", "global", "pdnsd_enable", "0")
|
||||
|
||||
local proxy = {
|
||||
name = sid,
|
||||
type = "tuic",
|
||||
server = server,
|
||||
port = server_port,
|
||||
uuid = get_server_field(sid, "tuic_uuid", ""),
|
||||
password = get_server_field(sid, "tuic_passwd", ""),
|
||||
["udp-relay-mode"] = get_server_field(sid, "udp_relay_mode", "native"),
|
||||
["congestion-controller"] = get_server_field(sid, "congestion_control", "cubic"),
|
||||
["skip-cert-verify"] = bool_enabled(get_server_field(sid, "insecure", "0")),
|
||||
["disable-sni"] = bool_enabled(get_server_field(sid, "disable_sni", "0")),
|
||||
["reduce-rtt"] = bool_enabled(get_server_field(sid, "zero_rtt_handshake", "0"))
|
||||
}
|
||||
|
||||
if tuic_ip ~= "" then
|
||||
proxy.ip = tuic_ip
|
||||
end
|
||||
if tls_host ~= "" then
|
||||
proxy.sni = tls_host
|
||||
end
|
||||
|
||||
local alpn = split_csv(get_server_field(sid, "tuic_alpn", ""))
|
||||
if #alpn > 0 then
|
||||
proxy.alpn = alpn
|
||||
end
|
||||
|
||||
local heartbeat = tonumber(get_server_field(sid, "heartbeat", "0"))
|
||||
if heartbeat and heartbeat > 0 then
|
||||
proxy["heartbeat-interval"] = heartbeat * 1000
|
||||
end
|
||||
|
||||
local timeout = tonumber(get_server_field(sid, "timeout", "0"))
|
||||
if timeout and timeout > 0 then
|
||||
proxy["request-timeout"] = timeout * 1000
|
||||
end
|
||||
|
||||
local max_udp_packet_size = tonumber(get_server_field(sid, "tuic_max_package_size", "0"))
|
||||
if max_udp_packet_size and max_udp_packet_size > 0 then
|
||||
proxy["max-udp-relay-packet-size"] = max_udp_packet_size
|
||||
end
|
||||
|
||||
if ipstack_prefer ~= "" then
|
||||
proxy["ip-version"] = ipstack_prefer == "v6first" and "ipv6-prefer" or "ipv4-prefer"
|
||||
end
|
||||
|
||||
local listen_port = tonumber(local_port)
|
||||
local socks_listen = tonumber(socks_port)
|
||||
|
||||
local doc = {
|
||||
["allow-lan"] = true,
|
||||
["bind-address"] = "0.0.0.0",
|
||||
mode = "rule",
|
||||
["log-level"] = "silent",
|
||||
["find-process-mode"] = "off",
|
||||
["unified-delay"] = true,
|
||||
["tcp-concurrent"] = true,
|
||||
["routing-mark"] = 255,
|
||||
proxies = { proxy },
|
||||
["proxy-groups"] = {
|
||||
{
|
||||
name = "PROXY",
|
||||
type = "select",
|
||||
proxies = { sid }
|
||||
}
|
||||
},
|
||||
rules = { "MATCH,PROXY" },
|
||||
tun = { enable = false },
|
||||
profile = { ["store-selected"] = true },
|
||||
dns = {
|
||||
enable = dns_mode == "7",
|
||||
["enhanced-mode"] = "redir-host",
|
||||
listen = "127.0.0.1:5335",
|
||||
ipv6 = get_filter_aaaa() ~= "1"
|
||||
}
|
||||
}
|
||||
|
||||
if mode == "socks" then
|
||||
doc["socks-port"] = listen_port
|
||||
else
|
||||
doc["redir-port"] = listen_port
|
||||
doc["tproxy-port"] = listen_port
|
||||
if socks_listen and socks_listen > 0 then
|
||||
doc["socks-port"] = socks_listen
|
||||
end
|
||||
end
|
||||
|
||||
return doc
|
||||
end
|
||||
|
||||
local function generate_tuic_runtime(sid, output_path, local_port, socks_port, mode)
|
||||
local doc = build_tuic_runtime_doc(sid, local_port, socks_port, mode)
|
||||
local ok, rendered = pcall(lyaml.dump, { doc })
|
||||
if not ok or not rendered then
|
||||
io.stderr:write("dump_failed\n")
|
||||
return false
|
||||
end
|
||||
write_file(output_path, rendered)
|
||||
return true
|
||||
end
|
||||
|
||||
local function parse_plugin_opts(value)
|
||||
local result = {}
|
||||
for part in tostring(value or ""):gmatch("[^;]+") do
|
||||
local key, val = part:match("^%s*([^=]+)=?(.*)%s*$")
|
||||
if key and key ~= "" then
|
||||
result[key] = val or ""
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function split_host_port(value)
|
||||
local text = tostring(value or "")
|
||||
if text == "" then
|
||||
return "", ""
|
||||
end
|
||||
local host, port = text:match("^%[(.-)%]:(%d+)$")
|
||||
if host and port then
|
||||
return host, port
|
||||
end
|
||||
host, port = text:match("^(.-):(%d+)$")
|
||||
if host and port then
|
||||
return host, port
|
||||
end
|
||||
return text, ""
|
||||
end
|
||||
|
||||
local function bool_default(value, default)
|
||||
if value == nil or value == "" then
|
||||
return default
|
||||
end
|
||||
return bool_enabled(value)
|
||||
end
|
||||
|
||||
local function number_or_nil(value)
|
||||
if value == nil or value == "" then
|
||||
return nil
|
||||
end
|
||||
return tonumber(value)
|
||||
end
|
||||
|
||||
local function string_or_nil(value)
|
||||
if value == nil or value == "" then
|
||||
return nil
|
||||
end
|
||||
return tostring(value)
|
||||
end
|
||||
|
||||
local function pick_plugin_opt(plugin_opts, ...)
|
||||
for i = 1, select("#", ...) do
|
||||
local key = select(i, ...)
|
||||
local value = plugin_opts[key]
|
||||
if value ~= nil and value ~= "" then
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function parse_plugin_headers(plugin_opts)
|
||||
local headers = {}
|
||||
local raw_headers = pick_plugin_opt(plugin_opts, "headers", "header")
|
||||
|
||||
if raw_headers and ok_jsonc and jsonc then
|
||||
local decoded = jsonc.parse(raw_headers)
|
||||
if type(decoded) == "table" then
|
||||
for key, value in pairs(decoded) do
|
||||
headers[tostring(key)] = tostring(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if raw_headers and next(headers) == nil then
|
||||
for part in tostring(raw_headers):gmatch("[^|,]+") do
|
||||
local key, value = part:match("^%s*([^=:]+)%s*[:=]%s*(.-)%s*$")
|
||||
if key and key ~= "" and value and value ~= "" then
|
||||
headers[key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for key, value in pairs(plugin_opts) do
|
||||
local header_name = key:match("^headers[%.:](.+)$")
|
||||
or key:match("^header[%.:](.+)$")
|
||||
or key:match("^header_(.+)$")
|
||||
if header_name and header_name ~= "" and value ~= "" then
|
||||
headers[header_name] = value
|
||||
end
|
||||
end
|
||||
|
||||
return next(headers) and headers or nil
|
||||
end
|
||||
|
||||
local function get_plugin_client_fingerprint(sid, plugin_opts)
|
||||
return string_or_nil(
|
||||
pick_plugin_opt(
|
||||
plugin_opts,
|
||||
"client-fingerprint",
|
||||
"client_fingerprint",
|
||||
"fingerprint"
|
||||
) or get_server_field(sid, "fingerprint", "")
|
||||
)
|
||||
end
|
||||
|
||||
local function normalize_plugin_name(plugin)
|
||||
local value = tostring(plugin or ""):lower()
|
||||
if value == "" or value == "none" then
|
||||
return ""
|
||||
end
|
||||
if value == "simple-obfs" then
|
||||
return "obfs-local"
|
||||
end
|
||||
if value == "obfs" then
|
||||
return "obfs-local"
|
||||
end
|
||||
if value == "shadowtls" then
|
||||
return "shadow-tls"
|
||||
end
|
||||
if value == "gost" then
|
||||
return "gost-plugin"
|
||||
end
|
||||
if value == "kcp-tun" then
|
||||
return "kcptun"
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
local function build_shadowsocks_plugin(proxy, sid)
|
||||
local plugin = normalize_plugin_name(get_server_field(sid, "plugin", ""))
|
||||
local plugin_opts = parse_plugin_opts(get_server_field(sid, "plugin_opts", ""))
|
||||
|
||||
if plugin == "" then
|
||||
return
|
||||
end
|
||||
|
||||
if plugin == "obfs-local" then
|
||||
proxy.plugin = "obfs"
|
||||
proxy["plugin-opts"] = {
|
||||
mode = pick_plugin_opt(plugin_opts, "obfs", "mode") or "http",
|
||||
host = string_or_nil(pick_plugin_opt(plugin_opts, "obfs-host", "obfs_host", "host"))
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
if plugin == "v2ray-plugin" or plugin == "xray-plugin" then
|
||||
proxy.plugin = "v2ray-plugin"
|
||||
proxy["plugin-opts"] = {
|
||||
mode = pick_plugin_opt(plugin_opts, "mode") or "websocket",
|
||||
tls = bool_default(pick_plugin_opt(plugin_opts, "tls"), false),
|
||||
fingerprint = string_or_nil(pick_plugin_opt(plugin_opts, "fingerprint")),
|
||||
["skip-cert-verify"] = bool_default(pick_plugin_opt(plugin_opts, "skip-cert-verify", "skip_cert_verify", "insecure"), false),
|
||||
host = string_or_nil(pick_plugin_opt(plugin_opts, "host")),
|
||||
path = string_or_nil(pick_plugin_opt(plugin_opts, "path")),
|
||||
mux = bool_default(pick_plugin_opt(plugin_opts, "mux"), false),
|
||||
headers = parse_plugin_headers(plugin_opts),
|
||||
["v2ray-http-upgrade"] = bool_default(pick_plugin_opt(plugin_opts, "v2ray-http-upgrade", "v2ray_http_upgrade"), false)
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
if plugin == "gost-plugin" then
|
||||
proxy.plugin = "gost-plugin"
|
||||
proxy["plugin-opts"] = {
|
||||
mode = pick_plugin_opt(plugin_opts, "mode") or "websocket",
|
||||
tls = bool_default(pick_plugin_opt(plugin_opts, "tls"), false),
|
||||
fingerprint = string_or_nil(pick_plugin_opt(plugin_opts, "fingerprint")),
|
||||
["skip-cert-verify"] = bool_default(pick_plugin_opt(plugin_opts, "skip-cert-verify", "skip_cert_verify", "insecure"), false),
|
||||
host = string_or_nil(pick_plugin_opt(plugin_opts, "host")),
|
||||
path = string_or_nil(pick_plugin_opt(plugin_opts, "path")),
|
||||
mux = bool_default(pick_plugin_opt(plugin_opts, "mux"), false),
|
||||
headers = parse_plugin_headers(plugin_opts)
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
if plugin == "shadow-tls" then
|
||||
local host, port = split_host_port(pick_plugin_opt(plugin_opts, "host") or "")
|
||||
local version
|
||||
if plugin_opts.v3 == "1" or plugin_opts.version == "3" then
|
||||
version = 3
|
||||
elseif plugin_opts.v2 == "1" or plugin_opts.version == "2" then
|
||||
version = 2
|
||||
elseif plugin_opts.v1 == "1" or plugin_opts.version == "1" then
|
||||
version = 1
|
||||
end
|
||||
proxy.plugin = "shadow-tls"
|
||||
proxy["client-fingerprint"] = get_plugin_client_fingerprint(sid, plugin_opts)
|
||||
proxy["plugin-opts"] = {
|
||||
host = host ~= "" and host or nil,
|
||||
port = number_or_nil(port),
|
||||
password = string_or_nil(pick_plugin_opt(plugin_opts, "passwd", "password")),
|
||||
version = version
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
if plugin == "restls" then
|
||||
proxy.plugin = "restls"
|
||||
proxy["client-fingerprint"] = get_plugin_client_fingerprint(sid, plugin_opts)
|
||||
proxy["plugin-opts"] = {
|
||||
host = string_or_nil(pick_plugin_opt(plugin_opts, "host")),
|
||||
password = string_or_nil(pick_plugin_opt(plugin_opts, "passwd", "password")),
|
||||
["version-hint"] = string_or_nil(pick_plugin_opt(plugin_opts, "version-hint", "version_hint")),
|
||||
["restls-script"] = string_or_nil(pick_plugin_opt(plugin_opts, "restls-script", "restls_script"))
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
if plugin == "kcptun" then
|
||||
proxy.plugin = "kcptun"
|
||||
proxy["plugin-opts"] = {
|
||||
key = string_or_nil(pick_plugin_opt(plugin_opts, "key", "passwd", "password")),
|
||||
crypt = string_or_nil(pick_plugin_opt(plugin_opts, "crypt")),
|
||||
mode = string_or_nil(pick_plugin_opt(plugin_opts, "mode")),
|
||||
conn = number_or_nil(pick_plugin_opt(plugin_opts, "conn")),
|
||||
autoexpire = number_or_nil(pick_plugin_opt(plugin_opts, "autoexpire")),
|
||||
scavengettl = number_or_nil(pick_plugin_opt(plugin_opts, "scavengettl")),
|
||||
mtu = number_or_nil(pick_plugin_opt(plugin_opts, "mtu")),
|
||||
ratelimit = number_or_nil(pick_plugin_opt(plugin_opts, "ratelimit")),
|
||||
sndwnd = number_or_nil(pick_plugin_opt(plugin_opts, "sndwnd")),
|
||||
rcvwnd = number_or_nil(pick_plugin_opt(plugin_opts, "rcvwnd")),
|
||||
datashard = number_or_nil(pick_plugin_opt(plugin_opts, "datashard")),
|
||||
parityshard = number_or_nil(pick_plugin_opt(plugin_opts, "parityshard")),
|
||||
dscp = number_or_nil(pick_plugin_opt(plugin_opts, "dscp")),
|
||||
nocomp = bool_default(pick_plugin_opt(plugin_opts, "nocomp"), false),
|
||||
acknodelay = bool_default(pick_plugin_opt(plugin_opts, "acknodelay"), false),
|
||||
nodelay = number_or_nil(pick_plugin_opt(plugin_opts, "nodelay")),
|
||||
interval = number_or_nil(pick_plugin_opt(plugin_opts, "interval")),
|
||||
resend = number_or_nil(pick_plugin_opt(plugin_opts, "resend")),
|
||||
sockbuf = number_or_nil(pick_plugin_opt(plugin_opts, "sockbuf")),
|
||||
smuxver = number_or_nil(pick_plugin_opt(plugin_opts, "smuxver")),
|
||||
smuxbuf = number_or_nil(pick_plugin_opt(plugin_opts, "smuxbuf")),
|
||||
framesize = number_or_nil(pick_plugin_opt(plugin_opts, "framesize")),
|
||||
streambuf = number_or_nil(pick_plugin_opt(plugin_opts, "streambuf")),
|
||||
keepalive = number_or_nil(pick_plugin_opt(plugin_opts, "keepalive"))
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
proxy.plugin = plugin
|
||||
if next(plugin_opts) then
|
||||
proxy["plugin-opts"] = plugin_opts
|
||||
end
|
||||
end
|
||||
|
||||
local function build_kcptun_plugin(proxy, sid)
|
||||
if not bool_enabled(get_server_field(sid, "kcp_enable", "0")) then
|
||||
return
|
||||
end
|
||||
|
||||
proxy.plugin = "kcptun"
|
||||
proxy.port = tonumber(get_server_field(sid, "kcp_port", "0")) or proxy.port
|
||||
proxy["plugin-opts"] = {
|
||||
key = get_server_field(sid, "kcp_password", ""),
|
||||
mode = "fast",
|
||||
mtu = 1350
|
||||
}
|
||||
end
|
||||
|
||||
local function build_shadowsocks_runtime_doc(sid, local_port, socks_port, mode)
|
||||
local dns_mode = uci:get_first("shadowsocksr", "global", "pdnsd_enable", "0")
|
||||
local server = get_server_field(sid, "server", "")
|
||||
local server_port = tonumber(get_server_field(sid, "server_port", "0")) or 0
|
||||
local method = get_server_field(sid, "encrypt_method_ss", "none")
|
||||
local password = get_server_field(sid, "password", "")
|
||||
local proxy = {
|
||||
name = sid,
|
||||
type = "ss",
|
||||
server = server,
|
||||
port = server_port,
|
||||
cipher = method,
|
||||
password = password,
|
||||
udp = true,
|
||||
tfo = bool_enabled(get_server_field(sid, "fast_open", "0"))
|
||||
}
|
||||
|
||||
if get_server_field(sid, "type", "") == "ss" then
|
||||
build_kcptun_plugin(proxy, sid)
|
||||
end
|
||||
if proxy.plugin == nil then
|
||||
build_shadowsocks_plugin(proxy, sid)
|
||||
end
|
||||
|
||||
local doc = {
|
||||
["allow-lan"] = true,
|
||||
["bind-address"] = "0.0.0.0",
|
||||
mode = "rule",
|
||||
["log-level"] = "silent",
|
||||
["find-process-mode"] = "off",
|
||||
["unified-delay"] = true,
|
||||
["tcp-concurrent"] = true,
|
||||
["routing-mark"] = 255,
|
||||
proxies = { proxy },
|
||||
["proxy-groups"] = {
|
||||
{
|
||||
name = "PROXY",
|
||||
type = "select",
|
||||
proxies = { sid }
|
||||
}
|
||||
},
|
||||
rules = { "MATCH,PROXY" },
|
||||
tun = { enable = false },
|
||||
profile = { ["store-selected"] = true },
|
||||
dns = {
|
||||
enable = dns_mode == "7",
|
||||
["enhanced-mode"] = "redir-host",
|
||||
listen = "127.0.0.1:5335",
|
||||
ipv6 = get_filter_aaaa() ~= "1"
|
||||
}
|
||||
}
|
||||
|
||||
local listen_port = tonumber(local_port)
|
||||
local socks_listen = tonumber(socks_port)
|
||||
if mode == "socks" then
|
||||
doc["socks-port"] = listen_port
|
||||
else
|
||||
doc["redir-port"] = listen_port
|
||||
doc["tproxy-port"] = listen_port
|
||||
if socks_listen and socks_listen > 0 then
|
||||
doc["socks-port"] = socks_listen
|
||||
end
|
||||
end
|
||||
|
||||
return doc
|
||||
end
|
||||
|
||||
local function generate_shadowsocks_runtime(sid, output_path, local_port, socks_port, mode)
|
||||
local doc = build_shadowsocks_runtime_doc(sid, local_port, socks_port, mode)
|
||||
local ok, rendered = pcall(lyaml.dump, { doc })
|
||||
if not ok or not rendered then
|
||||
io.stderr:write("dump_failed\n")
|
||||
return false
|
||||
end
|
||||
write_file(output_path, rendered)
|
||||
return true
|
||||
end
|
||||
|
||||
local function build_shadowsocks_server_doc(sid)
|
||||
local server_port = tonumber(get_server_field(sid, "server_port", "0")) or 0
|
||||
local method = get_server_field(sid, "encrypt_method_ss", "aes-128-gcm")
|
||||
local password = get_server_field(sid, "password", "")
|
||||
local listener = {
|
||||
name = sid,
|
||||
type = "shadowsocks",
|
||||
listen = "::",
|
||||
port = server_port,
|
||||
cipher = method,
|
||||
password = password,
|
||||
udp = true,
|
||||
tfo = bool_enabled(get_server_field(sid, "fast_open", "0"))
|
||||
}
|
||||
|
||||
local plugin = normalize_plugin_name(get_server_field(sid, "plugin", ""))
|
||||
if plugin == "obfs-local" then
|
||||
local plugin_opts = parse_plugin_opts(get_server_field(sid, "plugin_opts", ""))
|
||||
listener.obfs = plugin_opts.obfs or plugin_opts.mode or "http"
|
||||
listener.obfs_opts = {
|
||||
mode = plugin_opts.obfs or plugin_opts.mode or "http",
|
||||
host = plugin_opts["obfs-host"] or plugin_opts.obfs_host or plugin_opts.host or nil
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
["allow-lan"] = true,
|
||||
["bind-address"] = "*",
|
||||
["log-level"] = "silent",
|
||||
["find-process-mode"] = "off",
|
||||
listeners = { listener }
|
||||
}
|
||||
end
|
||||
|
||||
local function generate_shadowsocks_server(sid, output_path)
|
||||
local doc = build_shadowsocks_server_doc(sid)
|
||||
local ok, rendered = pcall(lyaml.dump, { doc })
|
||||
if not ok or not rendered then
|
||||
io.stderr:write("dump_failed\n")
|
||||
return false
|
||||
end
|
||||
write_file(output_path, rendered)
|
||||
return true
|
||||
end
|
||||
|
||||
local action = arg[1]
|
||||
if action == "validate" then
|
||||
os.exit(validate(arg[2]) and 0 or 1)
|
||||
elseif action == "filter" then
|
||||
os.exit(filter(arg[2], arg[3]) and 0 or 1)
|
||||
elseif action == "prepare" then
|
||||
os.exit(prepare(arg[2], arg[3]) and 0 or 1)
|
||||
elseif action == "merge" then
|
||||
os.exit(merge(arg[2], arg[3], arg[4]) and 0 or 1)
|
||||
elseif action == "append_client_policy_rules" then
|
||||
os.exit(append_client_policy_rules(arg[2], arg[3]) and 0 or 1)
|
||||
elseif action == "tuic" then
|
||||
os.exit(generate_tuic_runtime(arg[2], arg[3], arg[4], arg[5], arg[6]) and 0 or 1)
|
||||
elseif action == "ss" then
|
||||
os.exit(generate_shadowsocks_runtime(arg[2], arg[3], arg[4], arg[5], arg[6]) and 0 or 1)
|
||||
elseif action == "ss_server" then
|
||||
os.exit(generate_shadowsocks_server(arg[2], arg[3]) and 0 or 1)
|
||||
else
|
||||
io.stderr:write("usage: clash_yaml.lua validate <yaml> | filter <yaml> <words> | prepare <input> <output> | merge <raw> <overlay> <output> | append_client_policy_rules <runtime_yaml> <sid> | tuic <sid> <output> <local_port> [socks_port] [mode] | ss <sid> <output> <local_port> [socks_port] [mode] | ss_server <sid> <output>\n")
|
||||
os.exit(1)
|
||||
end
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
require "luci.sys"
|
||||
local ucursor = require "luci.model.uci".cursor()
|
||||
local datatypes = require "luci.cbi.datatypes"
|
||||
local json = require "luci.jsonc"
|
||||
|
||||
local server_section = arg[1]
|
||||
@@ -11,6 +12,12 @@ local socks_port = arg[4] or "0"
|
||||
|
||||
local chain = arg[5] or "0"
|
||||
|
||||
local GLOBAL = {
|
||||
DNS_SERVER = {},
|
||||
DNS_HOSTNAME = {},
|
||||
VPS_EXCLUDE = {}
|
||||
}
|
||||
|
||||
-- 辅助函数:拆分字符串(若 luci.util 未加载则定义)
|
||||
local function split(str, pat)
|
||||
local t = {}
|
||||
@@ -31,52 +38,27 @@ local server = ucursor:get_all("shadowsocksr", server_section)
|
||||
local socks_server = ucursor:get_all("shadowsocksr", "@socks5_proxy[0]") or {}
|
||||
local xray_fragment = ucursor:get_all("shadowsocksr", "@global_xray_fragment[0]") or {}
|
||||
local xray_noise = ucursor:get_all("shadowsocksr", "@xray_noise_packets[0]") or {}
|
||||
local default_node_local_port = ucursor:get_first("shadowsocksr", "global", "default_node_local_port", "1234")
|
||||
local dns_mode = ucursor:get_first("shadowsocksr", "global", "pdnsd_enable", "0")
|
||||
local dns_ipv4_only = ucursor:get_first("shadowsocksr", "global", "filter_aaaa")
|
||||
if not dns_ipv4_only or dns_ipv4_only == "" then
|
||||
dns_ipv4_only = ucursor:get_first("shadowsocksr", "global", "mosdns_ipv6", "1")
|
||||
end
|
||||
local builtin_dns_server = ucursor:get_first("shadowsocksr", "global", "tunnel_forward", "8.8.4.4:53")
|
||||
local outbound_settings = nil
|
||||
local xray_version = nil
|
||||
local xray_version_val = 0
|
||||
local xray_builtin_dns = nil
|
||||
|
||||
local node_id = server_section
|
||||
local remarks = server.alias or ""
|
||||
local b64decode = nixio.bin.b64decode
|
||||
local b64encode = nixio.bin.b64encode
|
||||
local effective_node_local_port = tonumber(server.local_port) or tonumber(default_node_local_port) or 1234
|
||||
|
||||
if server.type == "ss-rust" then
|
||||
server.type = "ss"
|
||||
-- 解析 URL(简单实现,仅用于 DoH)
|
||||
local function parseURL(url)
|
||||
if not url then return nil end
|
||||
local schema, rest = url:match("^(https?)://(.*)$")
|
||||
if not schema then return nil end
|
||||
local host, port_str = rest:match("^([^:]+):?(%d*)/?.*$")
|
||||
local port = tonumber(port_str) or (schema == "https" and 443 or 80)
|
||||
return { host = host, port = port, schema = schema }
|
||||
end
|
||||
|
||||
local function parse_realm_uri(uri)
|
||||
if type(uri) ~= "string" then return nil end
|
||||
-- realm://token@server/realm_id?query
|
||||
local token, server_url, realm_id, query = trim(uri):match("^realm://([^@]+)@([^/]+)/([^?]*)%??(.*)$")
|
||||
if not token or not server_url or not realm_id then return nil end
|
||||
realm_id = realm_id:gsub("/+$", "")
|
||||
local realm = {
|
||||
token = token,
|
||||
server_url = server_url,
|
||||
realm_id = realm_id
|
||||
}
|
||||
-- 解析 query 中的 stun=
|
||||
if query and query ~= "" then
|
||||
local stun_servers = {}
|
||||
for key, value in query:gmatch("([^&=?]+)=([^&]+)") do
|
||||
if key == "stun" and value ~= "" then
|
||||
stun_servers[#stun_servers + 1] = value
|
||||
end
|
||||
end
|
||||
if #stun_servers > 0 then
|
||||
realm.stun_servers = stun_servers
|
||||
end
|
||||
end
|
||||
return realm
|
||||
if server.type == "ss-rust" or server.type == "ss-libev" then
|
||||
server.type = "ss"
|
||||
end
|
||||
|
||||
-- base64 解码
|
||||
@@ -138,25 +120,9 @@ local function cleanEmptyTables(t)
|
||||
return next(t) and t or nil
|
||||
end
|
||||
|
||||
local function format_host(host)
|
||||
host = tostring(host or "")
|
||||
if host ~= "" and host:find(":", 1, true) and not host:match("^%[.*%]$") then
|
||||
return "[" .. host .. "]"
|
||||
end
|
||||
return host
|
||||
end
|
||||
|
||||
local function format_host_port(host, port)
|
||||
host = format_host(host)
|
||||
if port == nil or port == "" then
|
||||
return host
|
||||
end
|
||||
return host .. ":" .. tostring(port)
|
||||
end
|
||||
|
||||
-- 确保正确判断程序是否存在
|
||||
local function is_finded(e)
|
||||
return luci.sys.exec(string.format('type -t -p "%s" -p "/usr/libexec/%s" 2>/dev/null', e, e)) ~= ""
|
||||
return luci.sys.exec(string.format('type -t -p "%s" 2>/dev/null', e)) ~= ""
|
||||
end
|
||||
|
||||
-- 获取 Xray 版本号
|
||||
@@ -237,33 +203,20 @@ function wireguard()
|
||||
-- 处理 reserved 字段,支持逗号分隔的数字或 Base64 编码
|
||||
local reserved = nil
|
||||
if server.reserved then
|
||||
local all_bytes = {}
|
||||
local reserved_values = server.reserved
|
||||
|
||||
-- 确保是 table 类型
|
||||
if type(reserved_values) ~= "table" then
|
||||
reserved_values = {reserved_values}
|
||||
end
|
||||
|
||||
for _, reserved_str in ipairs(reserved_values) do
|
||||
if type(reserved_str) == "string" then
|
||||
if not reserved_str:match("[^%d,]+") then
|
||||
-- 数字和逗号格式
|
||||
reserved_str:gsub("%d+", function(b)
|
||||
all_bytes[#all_bytes + 1] = tonumber(b)
|
||||
end)
|
||||
else
|
||||
-- Base64 格式
|
||||
local result = base64Decode(reserved_str)
|
||||
if result then
|
||||
for i = 1, #result do
|
||||
all_bytes[#all_bytes + 1] = result:byte(i)
|
||||
end
|
||||
end
|
||||
end
|
||||
local bytes = {}
|
||||
if not server.reserved:match("[^%d,]+") then
|
||||
-- 纯数字和逗号,解析为数字列表
|
||||
server.reserved:gsub("%d+", function(b)
|
||||
bytes[#bytes + 1] = tonumber(b)
|
||||
end)
|
||||
else
|
||||
-- Base64 编码的二进制数据
|
||||
local result = base64Decode(server.reserved)
|
||||
for i = 1, #result do
|
||||
bytes[i] = result:byte(i)
|
||||
end
|
||||
end
|
||||
reserved = #all_bytes > 0 and all_bytes or nil
|
||||
reserved = #bytes > 0 and bytes or nil
|
||||
end
|
||||
|
||||
outbound_settings = {
|
||||
@@ -273,22 +226,15 @@ function wireguard()
|
||||
{
|
||||
publicKey = server.peer_pubkey,
|
||||
preSharedKey = server.preshared_key,
|
||||
endpoint = format_host_port(server.server, server.server_port),
|
||||
endpoint = server.server .. ":" .. server.server_port,
|
||||
keepAlive = tonumber(server.keepaliveperiod),
|
||||
allowedIPs = (server.allowedips) or nil,
|
||||
}
|
||||
},
|
||||
kernelMode = (server.kernelmode == "1") and true or false,
|
||||
noKernelTun = (server.kernelmode == "1") and true or false,
|
||||
reserved = reserved,
|
||||
mtu = tonumber(server.mtu)
|
||||
}
|
||||
if server.finalmask and server.finalmask ~= "" then
|
||||
local ok, fm = pcall(json.parse, base64Decode(server.finalmask))
|
||||
if ok and type(fm) == "table" then
|
||||
outbound_settings.streamSettings = outbound_settings.streamSettings or {}
|
||||
outbound_settings.streamSettings.finalmask = fm
|
||||
end
|
||||
end
|
||||
end
|
||||
function xray_hysteria2()
|
||||
outbound_settings = {
|
||||
@@ -353,35 +299,6 @@ local Xray = {
|
||||
-- 初始化 outbounds 表
|
||||
outbounds = {},
|
||||
}
|
||||
|
||||
if server.type == "v2ray" and dns_mode == "7" and os.getenv("SSR_SWITCH_PROBE") ~= "1" then
|
||||
local dns_host = builtin_dns_server:match("^([^:]+)") or "8.8.4.4"
|
||||
local dns_port = tonumber(builtin_dns_server:match(":(%d+)$")) or 53
|
||||
|
||||
Xray.dns = {
|
||||
queryStrategy = (dns_ipv4_only == "1") and "UseIPv4" or "UseIP",
|
||||
servers = {
|
||||
string.format("tcp://%s:%d", dns_host, dns_port)
|
||||
}
|
||||
}
|
||||
|
||||
table.insert(Xray.inbounds, {
|
||||
listen = "127.0.0.1",
|
||||
port = 5335,
|
||||
protocol = "dokodemo-door",
|
||||
settings = {
|
||||
address = dns_host,
|
||||
port = dns_port,
|
||||
network = "tcp,udp"
|
||||
},
|
||||
tag = "builtin-dns-in"
|
||||
})
|
||||
|
||||
xray_builtin_dns = {
|
||||
address = dns_host,
|
||||
port = dns_port
|
||||
}
|
||||
end
|
||||
-- 传入连接
|
||||
-- 添加 dokodemo-door 配置,如果 local_port 不为 0
|
||||
if local_port ~= "0" then
|
||||
@@ -432,11 +349,11 @@ if proto and proto:find("tcp") and socks_port ~= "0" then
|
||||
-- socks
|
||||
protocol = "socks",
|
||||
port = tonumber(socks_port),
|
||||
settings = {
|
||||
settings = {
|
||||
auth = socks_server.socks5_auth or "noauth",
|
||||
udp = true,
|
||||
mixed = ((socks_server.socks5_mixed == '1') and true or false) or nil,
|
||||
accounts = (socks_server.socks5_auth and socks_server.socks5_auth ~= "noauth") and {
|
||||
mixed = ((socks_server.socks5_mixed == '1') and true or false) or (socks_server.server == 'same') and nil,
|
||||
accounts = (socks_server.server ~= "same" and (socks_server.socks5_auth and socks_server.socks5_auth ~= "noauth")) and {
|
||||
{
|
||||
user = socks_server.socks5_user,
|
||||
pass = socks_server.socks5_pass
|
||||
@@ -615,7 +532,7 @@ Xray.outbounds = {
|
||||
local finalmask = {}
|
||||
local PT = server.v2ray_protocol
|
||||
local TP = server.transport
|
||||
if TP == "kcp" then
|
||||
if server.transport == "kcp" then
|
||||
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
|
||||
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
|
||||
local udp = {}
|
||||
@@ -633,36 +550,14 @@ Xray.outbounds = {
|
||||
udp[#udp+1] = c
|
||||
finalmask.udp = udp
|
||||
elseif PT == "hysteria2" then
|
||||
local udp = {}
|
||||
if (server.flag_obfs == "1" and (server.obfs_type and server.obfs_type ~= "")) then
|
||||
local o = {
|
||||
type = "salamander",
|
||||
finalmask.udp = {{
|
||||
type = server.obfs_type,
|
||||
settings = server.salamander and {
|
||||
password = server.salamander,
|
||||
packetSize = server.obfs_type == "gecko" and "512-1200" or nil
|
||||
password = server.salamander
|
||||
} or nil
|
||||
}
|
||||
udp[#udp+1] = o
|
||||
}}
|
||||
end
|
||||
if server.hysteria2_realms then
|
||||
local realm = parse_realm_uri(server.hysteria2_realm_url)
|
||||
local url, stun
|
||||
if realm then
|
||||
if realm.token and realm.server_url and realm.realm_id then
|
||||
url = "realm://" .. realm.token .. "@" .. realm.server_url .. "/" .. realm.realm_id
|
||||
end
|
||||
stun = realm.stun_servers or server.hysteria2_realm_stun
|
||||
end
|
||||
local r = {
|
||||
type = "realm",
|
||||
settings = {
|
||||
url = url,
|
||||
stunServers = stun
|
||||
}
|
||||
}
|
||||
udp[#udp+1] = r
|
||||
end
|
||||
finalmask.udp = udp
|
||||
local up = tonumber(server.uplink_capacity) or 0
|
||||
local down = tonumber(server.downlink_capacity) or 0
|
||||
finalmask.quicParams = {
|
||||
@@ -750,6 +645,7 @@ Xray.outbounds = {
|
||||
end)(),
|
||||
sockopt = {
|
||||
mark = 255,
|
||||
domainStrategy = server.domain_strategy or "UseIP",
|
||||
tcpFastOpen = (function()
|
||||
if server.transport == "xhttp" then
|
||||
return (server.fast_open == "1") and true or false
|
||||
@@ -777,24 +673,18 @@ Xray.outbounds = {
|
||||
}
|
||||
}
|
||||
|
||||
if xray_builtin_dns then
|
||||
table.insert(Xray.outbounds, {
|
||||
protocol = "dns",
|
||||
tag = "builtin-dns-out",
|
||||
settings = {
|
||||
network = "tcp",
|
||||
address = xray_builtin_dns.address,
|
||||
port = xray_builtin_dns.port
|
||||
table.insert(Xray.outbounds, {
|
||||
protocol = "freedom",
|
||||
tag = "direct",
|
||||
settings = {
|
||||
domainStrategy = server.domain_strategy or "UseIP" -- 可根据需要改为 direct_dns_query_strategy
|
||||
},
|
||||
streamSettings = {
|
||||
sockopt = {
|
||||
mark = 255
|
||||
}
|
||||
})
|
||||
Xray.routing = Xray.routing or {}
|
||||
Xray.routing.rules = Xray.routing.rules or {}
|
||||
table.insert(Xray.routing.rules, {
|
||||
type = "field",
|
||||
inboundTag = { "builtin-dns-in" },
|
||||
outboundTag = "builtin-dns-out"
|
||||
})
|
||||
end
|
||||
}
|
||||
})
|
||||
|
||||
-- 添加带有 fragment 设置的 dialerproxy 配置
|
||||
if xray_fragment.fragment ~= "0" or (xray_fragment.noise ~= "0" and xray_noise.enabled ~= "0") then
|
||||
@@ -825,6 +715,121 @@ if xray_fragment.fragment ~= "0" or (xray_fragment.noise ~= "0" and xray_noise.e
|
||||
})
|
||||
end
|
||||
|
||||
-- Xray DNS 解析配置
|
||||
if datatypes.hostname(server.server) and server.domain_resolver and (server.domain_resolver_dns or server.domain_resolver_dns_https) then
|
||||
-- 解析 DNS 服务器配置
|
||||
local dns_proto = server.domain_resolver
|
||||
local config_address
|
||||
local config_port
|
||||
if dns_proto == "https" then
|
||||
local _a = parseURL(server.domain_resolver_dns_https)
|
||||
if _a then
|
||||
config_address = server.domain_resolver_dns_https
|
||||
config_port = _a.port or 443
|
||||
if _a.hostname and datatypes.hostname(_a.hostname) then
|
||||
GLOBAL.DNS_HOSTNAME[_a.hostname] = true
|
||||
end
|
||||
end
|
||||
else
|
||||
local server_address = server.domain_resolver_dns
|
||||
config_port = 53
|
||||
local parts = split(server_address, ":")
|
||||
if #parts > 1 then
|
||||
server_address = parts[1]
|
||||
config_port = tonumber(parts[#parts])
|
||||
end
|
||||
config_address = server_address
|
||||
if dns_proto == "tcp" then
|
||||
config_address = dns_proto .. "://" .. server_address .. ":" .. config_port
|
||||
end
|
||||
end
|
||||
|
||||
-- 存入 GLOBAL.DNS_SERVER(去重)
|
||||
local dns_key = dns_proto .. "|" .. config_address .. "|" .. tostring(config_port)
|
||||
if not GLOBAL.DNS_SERVER[dns_key] then
|
||||
GLOBAL.DNS_SERVER[dns_key] = {
|
||||
tag = "dns-node-" .. node_id,
|
||||
address = config_address,
|
||||
port = config_port,
|
||||
finalQuery = true,
|
||||
disableCache = false,
|
||||
serveStale = true,
|
||||
serveExpiredTTL = 30,
|
||||
domains = {}
|
||||
}
|
||||
end
|
||||
|
||||
-- 添加当前节点域名到该 DNS 服务器的 domains 列表
|
||||
local domain = "full:" .. server.server
|
||||
local exists
|
||||
for _, d in ipairs(GLOBAL.DNS_SERVER[dns_key].domains) do
|
||||
if d == domain then exists = true; break end
|
||||
end
|
||||
if not exists then
|
||||
table.insert(GLOBAL.DNS_SERVER[dns_key].domains, domain)
|
||||
end
|
||||
GLOBAL.VPS_EXCLUDE[server.server] = true
|
||||
|
||||
-- 构建 Xray.dns
|
||||
local dns_servers = { "localhost" }
|
||||
for key, dns_server in pairs(GLOBAL.DNS_SERVER) do
|
||||
table.insert(dns_servers, {
|
||||
tag = dns_server.tag,
|
||||
address = dns_server.address,
|
||||
port = dns_server.port,
|
||||
domains = dns_server.domains,
|
||||
finalQuery = dns_server.finalQuery,
|
||||
serveStale = dns_server.serveStale,
|
||||
serveExpiredTTL = dns_server.serveExpiredTTL,
|
||||
disableCache = dns_server.disableCache
|
||||
})
|
||||
end
|
||||
Xray.dns = {
|
||||
servers = dns_servers,
|
||||
disableFallback = true,
|
||||
disableFallbackIfMatch = true,
|
||||
useSystemHosts = true,
|
||||
queryStrategy = "UseIP",
|
||||
disableCache = false,
|
||||
tag = "dns-global" -- 用于 routing
|
||||
}
|
||||
end
|
||||
|
||||
-- 代理出站的 tag(与 outbound 中的 tag 保持一致)
|
||||
local proxy_tag = (remarks ~= nil and remarks ~= "") and (node_id .. ":" .. remarks) or node_id
|
||||
-- 构建 routing 规则表
|
||||
local routing_rules = {}
|
||||
-- 1. 为每个自定义 DNS 服务器添加直连规则(inboundTag 匹配 dns-node-xxx)
|
||||
if GLOBAL.DNS_SERVER and next(GLOBAL.DNS_SERVER) then
|
||||
for _, dns_server in pairs(GLOBAL.DNS_SERVER) do
|
||||
table.insert(routing_rules, {
|
||||
inboundTag = { dns_server.tag },
|
||||
outboundTag = "direct"
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- 2. 添加全局 DNS 直连规则(需要 Xray.dns 中已设置 tag = "dns-global")
|
||||
if Xray.dns and Xray.dns.tag then
|
||||
table.insert(routing_rules, {
|
||||
inboundTag = { "dns-global" },
|
||||
outboundTag = "direct"
|
||||
})
|
||||
end
|
||||
|
||||
-- 3. 添加默认规则:所有 TCP/UDP 流量走代理
|
||||
table.insert(routing_rules, {
|
||||
network = "tcp,udp",
|
||||
ruleTag = "default",
|
||||
outboundTag = proxy_tag
|
||||
})
|
||||
|
||||
-- 构建 routing 对象
|
||||
Xray.routing = {
|
||||
rules = routing_rules,
|
||||
domainStrategy = "AsIs"
|
||||
}
|
||||
|
||||
local cipher = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA"
|
||||
local cipher13 = "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384"
|
||||
local trojan = {
|
||||
@@ -872,7 +877,7 @@ local trojan = {
|
||||
}
|
||||
}
|
||||
local naiveproxy = {
|
||||
proxy = (server.username and server.password and server.server and server.server_port) and "https://" .. server.username .. ":" .. server.password .. "@" .. format_host_port(server.server, server.server_port),
|
||||
proxy = (server.username and server.password and server.server and server.server_port) and "https://" .. server.username .. ":" .. server.password .. "@" .. server.server .. ":" .. server.server_port,
|
||||
listen = (proto == "redir") and "redir" .. "://0.0.0.0:" .. tonumber(local_port) or "socks" .. "://0.0.0.0:" .. tonumber(local_port),
|
||||
["insecure-concurrency"] = tonumber(server.concurrency) or 1
|
||||
}
|
||||
@@ -893,16 +898,16 @@ local hysteria2 = {
|
||||
server.server_port and
|
||||
(
|
||||
server.port_range and
|
||||
(format_host_port(server.server, server.server_port) .. "," .. string.gsub(server.port_range, ":", "-"))
|
||||
(server.server .. ":" .. server.server_port .. "," .. string.gsub(server.port_range, ":", "-"))
|
||||
or
|
||||
(format_host_port(server.server, server.server_port))
|
||||
(server.server .. ":" .. server.server_port)
|
||||
)
|
||||
or
|
||||
(
|
||||
server.port_range and
|
||||
format_host(server.server) .. ":" .. string.gsub(server.port_range, ":", "-")
|
||||
server.server .. ":" .. string.gsub(server.port_range, ":", "-")
|
||||
or
|
||||
server.server and format_host_port(server.server, "443")
|
||||
server.server and server.server .. ":443"
|
||||
)
|
||||
),
|
||||
bandwidth = (server.uplink_capacity or server.downlink_capacity) and {
|
||||
@@ -1001,7 +1006,7 @@ local hysteria2 = {
|
||||
}
|
||||
local shadowtls = {
|
||||
client = {
|
||||
server_addr = server.server_port and format_host_port(server.server, server.server_port) or nil,
|
||||
server_addr = server.server_port and server.server .. ":" .. server.server_port or nil,
|
||||
listen = "127.0.0.1:" .. tonumber(local_port),
|
||||
tls_names = server.shadowtls_sni,
|
||||
password = server.password
|
||||
@@ -1015,7 +1020,7 @@ local chain_sslocal = {
|
||||
locals = local_port ~= "0" and {
|
||||
{
|
||||
local_address = "0.0.0.0",
|
||||
local_port = (chain_local_port == "0" and effective_node_local_port or tonumber(chain_local_port)),
|
||||
local_port = (chain_local_port == "0" and tonumber(server.local_port) or tonumber(chain_local_port)),
|
||||
mode = (proto:find("tcp,udp") and "tcp_and_udp") or proto .. "_only",
|
||||
protocol = "redir",
|
||||
tcp_redir = "redirect",
|
||||
@@ -1044,7 +1049,7 @@ local chain_sslocal = {
|
||||
local chain_vmess = {
|
||||
inbounds = (local_port ~= "0") and {
|
||||
{
|
||||
port = (chain_local_port == "0" and effective_node_local_port or tonumber(chain_local_port)),
|
||||
port = (chain_local_port == "0" and tonumber(server.local_port) or tonumber(chain_local_port)),
|
||||
protocol = "dokodemo-door",
|
||||
settings = {
|
||||
network = proto,
|
||||
@@ -1080,7 +1085,7 @@ local chain_vmess = {
|
||||
}
|
||||
local tuic = {
|
||||
relay = {
|
||||
server = server.server_port and format_host_port(server.server, server.server_port),
|
||||
server = server.server_port and server.server .. ":" .. server.server_port,
|
||||
ip = server.tuic_ip,
|
||||
uuid = server.tuic_uuid,
|
||||
password = server.tuic_passwd,
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/bin/sh
|
||||
argv1=$1
|
||||
argv2=$2
|
||||
argv3=$3
|
||||
argv4=$4
|
||||
argv5=$5
|
||||
argv6=$6
|
||||
argv7=$7
|
||||
argv8=$8
|
||||
argv9=$9
|
||||
cat <<-EOF >$argv1
|
||||
base {
|
||||
log_debug = off;
|
||||
log_info = off;
|
||||
log = stderr;
|
||||
daemon = on;
|
||||
redirector = iptables;
|
||||
reuseport = on;
|
||||
}
|
||||
EOF
|
||||
tcp() {
|
||||
if [ "$argv7" == "0" ]; then
|
||||
cat <<-EOF >>$argv1
|
||||
redsocks {
|
||||
bind = "0.0.0.0:$argv4";
|
||||
relay = "$argv5:$argv6";
|
||||
type = socks5;
|
||||
autoproxy = 0;
|
||||
timeout = 10;
|
||||
}
|
||||
EOF
|
||||
else
|
||||
cat <<-EOF >>$argv1
|
||||
redsocks {
|
||||
bind = "0.0.0.0:$argv4";
|
||||
relay = "$argv5:$argv6";
|
||||
type = socks5;
|
||||
autoproxy = 0;
|
||||
timeout = 10;
|
||||
login = "$argv8";
|
||||
password = "$argv9";
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
udp() {
|
||||
if [ "$argv7" == "0" ]; then
|
||||
cat <<-EOF >>$argv1
|
||||
redudp {
|
||||
bind = "0.0.0.0:$argv4";
|
||||
relay = "$argv5:$argv6";
|
||||
type = socks5;
|
||||
udp_timeout = 10;
|
||||
}
|
||||
EOF
|
||||
else
|
||||
cat <<-EOF >>$argv1
|
||||
redudp {
|
||||
bind = "0.0.0.0:$argv4";
|
||||
relay = "$argv5:$argv6";
|
||||
type = socks5;
|
||||
udp_timeout = 10;
|
||||
login = "$argv8";
|
||||
password = "$argv9";
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
case "$argv2" in
|
||||
socks5)
|
||||
case "$argv3" in
|
||||
tcp)
|
||||
tcp
|
||||
;;
|
||||
udp)
|
||||
udp
|
||||
;;
|
||||
*)
|
||||
tcp
|
||||
udp
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
cat <<-EOF >>$argv1
|
||||
redsocks {
|
||||
bind = "0.0.0.0:$argv4";
|
||||
type = direct;
|
||||
interface = $argv3;
|
||||
autoproxy = 0;
|
||||
timeout = 10;
|
||||
}
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
@@ -18,12 +18,38 @@ case "$USE_TABLES" in
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
netflix() {
|
||||
local port="$1"
|
||||
if [ -f "$TMP_DNSMASQ_PATH/gfw_list.conf" ] && [ -s /etc/ssrplus/netflix.list ]; then
|
||||
grep -vE '^\s*#|^\s*$' /etc/ssrplus/netflix.list > /tmp/ssrplus_netflix.list.clean
|
||||
if [ -s /tmp/ssrplus_netflix.list.clean ]; then
|
||||
grep -v -F -f /tmp/ssrplus_netflix.list.clean "$TMP_DNSMASQ_PATH/gfw_list.conf" > "$TMP_DNSMASQ_PATH/gfw_list.conf.tmp"
|
||||
mv "$TMP_DNSMASQ_PATH/gfw_list.conf.tmp" "$TMP_DNSMASQ_PATH/gfw_list.conf"
|
||||
if [ -f "$TMP_DNSMASQ_PATH/gfw_base.conf" ]; then
|
||||
grep -v -F -f /tmp/ssrplus_netflix.list.clean "$TMP_DNSMASQ_PATH/gfw_base.conf" > "$TMP_DNSMASQ_PATH/gfw_base.conf.tmp"
|
||||
mv "$TMP_DNSMASQ_PATH/gfw_base.conf.tmp" "$TMP_DNSMASQ_PATH/gfw_base.conf"
|
||||
fi
|
||||
fi
|
||||
rm -f /tmp/ssrplus_netflix.list.clean
|
||||
fi
|
||||
if [ "$nft_support" = "1" ]; then
|
||||
# 移除 ipset
|
||||
cat /etc/ssrplus/netflix.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/server=\/&\/127.0.0.1#$port\nnftset=\/&\/inet#ss_spec#netflix/" >$TMP_DNSMASQ_PATH/netflix_forward.conf
|
||||
elif [ "$nft_support" = "0" ]; then
|
||||
cat /etc/ssrplus/netflix.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/server=\/&\/127.0.0.1#$port\nipset=\/&\/netflix/" >$TMP_DNSMASQ_PATH/netflix_forward.conf
|
||||
fi
|
||||
}
|
||||
mkdir -p $TMP_DNSMASQ_PATH
|
||||
|
||||
run_mode=$(normalize_run_mode)
|
||||
run_mode=$(uci_get_by_type global run_mode router)
|
||||
|
||||
cp -rf /etc/ssrplus/gfw_list.conf $TMP_DNSMASQ_PATH/
|
||||
cp -rf /etc/ssrplus/gfw_base.conf $TMP_DNSMASQ_PATH/
|
||||
if [ "$run_mode" = "oversea" ]; then
|
||||
cp -rf /etc/ssrplus/oversea_list.conf $TMP_DNSMASQ_PATH/
|
||||
else
|
||||
cp -rf /etc/ssrplus/gfw_list.conf $TMP_DNSMASQ_PATH/
|
||||
cp -rf /etc/ssrplus/gfw_base.conf $TMP_DNSMASQ_PATH/
|
||||
fi
|
||||
|
||||
for conf_file in gfw_base.conf gfw_list.conf; do
|
||||
conf="$TMP_DNSMASQ_PATH/$conf_file"
|
||||
@@ -41,41 +67,36 @@ for conf_file in gfw_base.conf gfw_list.conf; do
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$(uci_get_by_type global netflix_enable 0)" == "1" ]; then
|
||||
# 只有开启 NetFlix分流 才需要取值
|
||||
SHUNT_SERVER=$(uci_get_by_type global netflix_server nil)
|
||||
else
|
||||
# 没有开启 设置为 nil
|
||||
SHUNT_SERVER=nil
|
||||
fi
|
||||
case "$SHUNT_SERVER" in
|
||||
nil)
|
||||
rm -f $TMP_DNSMASQ_PATH/netflix_forward.conf
|
||||
;;
|
||||
$(uci_get_by_type global global_server nil) | $switch_server | same)
|
||||
netflix $dns_port
|
||||
;;
|
||||
*)
|
||||
netflix $tmp_shunt_dns_port
|
||||
;;
|
||||
esac
|
||||
|
||||
# 此处使用 for 方式读取 防止 /etc/ssrplus/ 目录下的 black.list white.list deny.list 等2个或多个文件一行中存在空格 比如:# abc.com 而丢失:server
|
||||
# Optimize: Batch filter using grep
|
||||
for list_file in /etc/ssrplus/black.list /etc/ssrplus/white.list /etc/ssrplus/deny.list; do
|
||||
if [ -s "$list_file" ]; then
|
||||
# 清理注释和空行
|
||||
grep -vE '^\s*#|^\s*$' "$list_file" | sed 's/\r//g' > "${list_file}.clean"
|
||||
grep -vE '^\s*#|^\s*$' "$list_file" > "${list_file}.clean"
|
||||
if [ -s "${list_file}.clean" ]; then
|
||||
for target_file in "$TMP_DNSMASQ_PATH/gfw_list.conf" "$TMP_DNSMASQ_PATH/gfw_base.conf"; do
|
||||
[ -f "$target_file" ] || continue
|
||||
tmp_file="${target_file}.tmp"
|
||||
awk -v list="${list_file}.clean" '
|
||||
BEGIN {
|
||||
while ((getline line < list) > 0) {
|
||||
gsub(/\r/, "", line)
|
||||
if (line != "") {
|
||||
domain[line] = 1
|
||||
# 同时支持 *.domain
|
||||
domain["*." line] = 1
|
||||
}
|
||||
}
|
||||
close(list)
|
||||
}
|
||||
{
|
||||
# 提取 server=/domain/xxx
|
||||
if (match($0, /^server=\/([^\/]+)\//, m)) {
|
||||
if (m[1] in domain) next
|
||||
}
|
||||
# 提取 ipset=/domain/xxx
|
||||
if (match($0, /^ipset=\/([^\/]+)\//, m)) {
|
||||
if (m[1] in domain) next
|
||||
}
|
||||
print
|
||||
}
|
||||
' "$target_file" > "$tmp_file"
|
||||
mv "$tmp_file" "$target_file"
|
||||
if [ -f "$target_file" ]; then
|
||||
grep -v -F -f "${list_file}.clean" "$target_file" > "${target_file}.tmp"
|
||||
mv "${target_file}.tmp" "$target_file"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
rm -f "${list_file}.clean"
|
||||
@@ -93,45 +114,19 @@ fi
|
||||
cat /etc/ssrplus/deny.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/address=\/&\//" >$TMP_DNSMASQ_PATH/denylist.conf
|
||||
|
||||
if [ "$(uci_get_by_type global adblock 0)" == "1" ]; then
|
||||
cp -f /etc/ssrplus/ad.conf "$TMP_DNSMASQ_PATH/"
|
||||
cp -f /etc/ssrplus/ad.conf $TMP_DNSMASQ_PATH/
|
||||
if [ -f "$TMP_DNSMASQ_PATH/ad.conf" ]; then
|
||||
for list_file in /etc/ssrplus/black.list /etc/ssrplus/white.list /etc/ssrplus/deny.list; do
|
||||
for list_file in /etc/ssrplus/black.list /etc/ssrplus/white.list /etc/ssrplus/deny.list /etc/ssrplus/netflix.list; do
|
||||
if [ -s "$list_file" ]; then
|
||||
# 清理注释 & 空行
|
||||
grep -vE '^\s*#|^\s*$' "$list_file" | sed 's/\r//g' > "${list_file}.clean"
|
||||
grep -vE '^\s*#|^\s*$' "$list_file" > "${list_file}.clean"
|
||||
if [ -s "${list_file}.clean" ]; then
|
||||
tmp_file="$TMP_DNSMASQ_PATH/ad.conf.tmp"
|
||||
awk -v list="${list_file}.clean" '
|
||||
BEGIN {
|
||||
while ((getline line < list) > 0) {
|
||||
gsub(/\r/, "", line)
|
||||
if (line != "") {
|
||||
domain[line] = 1
|
||||
# 支持泛域名
|
||||
domain["*." line] = 1
|
||||
}
|
||||
}
|
||||
close(list)
|
||||
}
|
||||
{
|
||||
keep = 1
|
||||
# 精确匹配 server=/domain/
|
||||
if (match($0, /^server=\/([^\/]+)\//, m)) {
|
||||
if (m[1] in domain) keep = 0
|
||||
}
|
||||
# 精确匹配 ipset=/domain/
|
||||
if (match($0, /^ipset=\/([^\/]+)\//, m)) {
|
||||
if (m[1] in domain) keep = 0
|
||||
}
|
||||
if (keep) print
|
||||
}
|
||||
' "$TMP_DNSMASQ_PATH/ad.conf" > "$tmp_file"
|
||||
mv "$tmp_file" "$TMP_DNSMASQ_PATH/ad.conf"
|
||||
grep -v -F -f "${list_file}.clean" "$TMP_DNSMASQ_PATH/ad.conf" > "$TMP_DNSMASQ_PATH/ad.conf.tmp"
|
||||
mv "$TMP_DNSMASQ_PATH/ad.conf.tmp" "$TMP_DNSMASQ_PATH/ad.conf"
|
||||
fi
|
||||
rm -f "${list_file}.clean"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
else
|
||||
rm -f "$TMP_DNSMASQ_PATH/ad.conf"
|
||||
rm -f $TMP_DNSMASQ_PATH/ad.conf
|
||||
fi
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
#!/bin/sh
|
||||
# /usr/share/shadowsocksr/test.sh
|
||||
|
||||
CONFIG="shadowsocksr"
|
||||
LOCK_PATH=/tmp/lock
|
||||
TMP_PATH=/var/etc/ssrplus
|
||||
|
||||
uci_get_by_name() {
|
||||
local ret=$(uci -q get $CONFIG.$1.$2 2>/dev/null)
|
||||
echo "${ret:=$3}"
|
||||
}
|
||||
|
||||
uci_get_by_type() {
|
||||
local ret=$(uci -q get $CONFIG.@$1[0].$2 2>/dev/null)
|
||||
echo "${ret:=$3}"
|
||||
}
|
||||
|
||||
check_port_exists() {
|
||||
local port=$1
|
||||
local protocol=$2
|
||||
[ -n "$protocol" ] || protocol="tcp,udp"
|
||||
local result=
|
||||
if [ "$protocol" = "tcp" ]; then
|
||||
result=$(netstat -tln | grep -c ":$port ")
|
||||
elif [ "$protocol" = "udp" ]; then
|
||||
result=$(netstat -uln | grep -c ":$port ")
|
||||
elif [ "$protocol" = "tcp,udp" ]; then
|
||||
result=$(netstat -tuln | grep -c ":$port ")
|
||||
fi
|
||||
echo "${result}"
|
||||
}
|
||||
|
||||
set_cache_var() {
|
||||
local key="${1}"
|
||||
shift 1
|
||||
local val="$@"
|
||||
[ -n "${key}" ] && [ -n "${val}" ] && {
|
||||
sed -i "/${key}=/d" $TMP_PATH/var >/dev/null 2>&1
|
||||
echo "${key}=\"${val}\"" >> $TMP_PATH/var
|
||||
eval ${key}=\"${val}\"
|
||||
}
|
||||
}
|
||||
|
||||
get_cache_var() {
|
||||
local key="${1}"
|
||||
[ -n "${key}" ] && [ -s "$TMP_PATH/var" ] && {
|
||||
echo $(cat $TMP_PATH/var | grep "^${key}=" | awk -F '=' '{print $2}' | tail -n 1 | awk -F'"' '{print $2}')
|
||||
}
|
||||
}
|
||||
|
||||
#uci_get_by_port() {
|
||||
# local port=$1
|
||||
# while netstat -tuln 2>/dev/null | grep -q ":${port} "; do
|
||||
# port=$((port + 1))
|
||||
# done
|
||||
# echo $port
|
||||
|
||||
uci_get_by_port() {
|
||||
local default_start_port=2001
|
||||
local min_port=1025
|
||||
local max_port=49151
|
||||
local port="$1"
|
||||
local protocol=$(echo "$2" | tr 'A-Z' 'a-z')
|
||||
local LOCK_FILE="${LOCK_PATH}/${CONFIG}_get_prot.lock"
|
||||
while ! mkdir "$LOCK_FILE" 2>/dev/null; do
|
||||
sleep 1
|
||||
done
|
||||
if [ "$port" = "auto" ]; then
|
||||
local now last_time diff last_port
|
||||
now=$(date +%s 2>/dev/null)
|
||||
last_time=$(get_cache_var "last_get_new_port_time")
|
||||
if [ -n "$now" ] && [ -n "$last_time" ]; then
|
||||
diff=$(expr "$now" - "$last_time")
|
||||
[ "$diff" -lt 0 ] && diff=$(expr 0 - "$diff")
|
||||
else
|
||||
diff=999
|
||||
fi
|
||||
if [ "$diff" -gt 10 ]; then
|
||||
port=$default_start_port
|
||||
else
|
||||
last_port=$(get_cache_var "last_get_new_port_auto")
|
||||
if [ -n "$last_port" ]; then
|
||||
port=$(expr "$last_port" + 1)
|
||||
else
|
||||
port=$default_start_port
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
[ "$port" -lt $min_port -o "$port" -gt $max_port ] && port=$default_start_port
|
||||
local start_port="$port"
|
||||
while :; do
|
||||
if [ "$(check_port_exists "$port" "$protocol")" = 0 ]; then
|
||||
break
|
||||
fi
|
||||
port=$(expr "$port" + 1)
|
||||
if [ "$port" -gt $max_port ]; then
|
||||
port=$min_port
|
||||
fi
|
||||
[ "$port" = "$start_port" ] && {
|
||||
rmdir "$LOCK_FILE" 2>/dev/null
|
||||
return 1
|
||||
}
|
||||
done
|
||||
if [ "$1" = "auto" ]; then
|
||||
set_cache_var "last_get_new_port_auto" "$port"
|
||||
[ -n "$now" ] && set_cache_var "last_get_new_port_time" "$now"
|
||||
fi
|
||||
rmdir "$LOCK_FILE" 2>/dev/null
|
||||
echo "$port"
|
||||
}
|
||||
|
||||
url_test_hy2() {
|
||||
local node_id=$1
|
||||
|
||||
# 读取配置
|
||||
local server=$(uci_get_by_name ${node_id} server)
|
||||
local port=$(uci_get_by_name ${node_id} server_port)
|
||||
local auth=$(uci_get_by_name ${node_id} hy2_auth)
|
||||
local tls=$(uci_get_by_name ${node_id} tls)
|
||||
local insecure=$(uci_get_by_name ${node_id} insecure)
|
||||
local tls_host=$(uci_get_by_name ${node_id} tls_host)
|
||||
|
||||
# 获取本地端口
|
||||
# local tmp_port=$(uci_get_by_port 48900 tcp,udp)
|
||||
local tmp_port=$(uci_get_by_port auto tcp,udp)
|
||||
|
||||
# 生成Hysteria2配置文件
|
||||
local config_file="/tmp/hy2_test_${node_id}.yaml"
|
||||
cat > "$config_file" <<-EOF
|
||||
server: ${server}:${port}
|
||||
auth: "${auth}"
|
||||
tls:
|
||||
insecure: true
|
||||
EOF
|
||||
|
||||
# 如果 tls_host 非空,动态添加 sni 行
|
||||
[ -n "$tls_host" ] && echo " sni: \"${tls_host}\"" >> "$config_file"
|
||||
|
||||
# 追加 socks5 监听配置
|
||||
cat >> "$config_file" <<-EOF
|
||||
socks5:
|
||||
listen: 127.0.0.1:${tmp_port}
|
||||
EOF
|
||||
|
||||
# echo "Debug: 配置文件已生成: $config_file" >&2
|
||||
|
||||
# 启动Hysteria2客户端
|
||||
hysteria client --disable-update-check -c "$config_file" >/dev/null 2>&1 &
|
||||
local pid=$!
|
||||
echo $pid > "/tmp/hy2_test_${node_id}.pid"
|
||||
|
||||
# 等待端口启动
|
||||
sleep 1
|
||||
|
||||
# 测试代理
|
||||
# local result=$(curl --connect-timeout 3 --max-time 3 -s -o /dev/null -I -w "%{http_code}:%{time_pretransfer}" --socks5 127.0.0.1:${tmp_port} "${probeUrl}" 2>/dev/null)
|
||||
local curlx="socks5h://127.0.0.1:${tmp_port}"
|
||||
local probeUrl=$(uci_get_by_type server_subscribe url_test_url https://www.google.com/generate_204)
|
||||
local result=$(curl --connect-timeout 3 --max-time 5 -o /dev/null -I -skL -w "%{http_code}:%{time_pretransfer}" -x ${curlx} "${probeUrl}" 2>/dev/null)
|
||||
|
||||
# 清理
|
||||
# kill -9 $pid 2>/dev/null
|
||||
local pid_file="/tmp/hy2_test_${node_id}.pid"
|
||||
[ -s "$pid_file" ] && kill -9 "$(head -n 1 "$pid_file")" >/dev/null 2>&1
|
||||
pgrep -af "hysteria.*${config_file}" | awk '! /test\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1
|
||||
rm -f "$config_file" "$pid_file"
|
||||
|
||||
echo $result
|
||||
}
|
||||
|
||||
case $1 in
|
||||
url_test_hy2)
|
||||
url_test_hy2 $2
|
||||
;;
|
||||
esac
|
||||
@@ -1,35 +1,35 @@
|
||||
|
||||
config global
|
||||
option global_server 'nil'
|
||||
option netflix_server 'nil'
|
||||
option netflix_proxy '0'
|
||||
option threads '0'
|
||||
option run_mode 'router'
|
||||
option dports '2'
|
||||
option custom_ports '80,443'
|
||||
option pdnsd_enable '1'
|
||||
option filter_aaaa '1'
|
||||
option prefer_nft '1'
|
||||
option tunnel_forward '8.8.4.4:53'
|
||||
option monitor_enable '1'
|
||||
option enable_switch '1'
|
||||
option switch_time '667'
|
||||
option switch_timeout '5'
|
||||
option switch_try_count '3'
|
||||
option default_node_local_port '1234'
|
||||
option component_mirror 'direct'
|
||||
option shunt_dns '1'
|
||||
option gfwlist_url 'https://fastly.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt'
|
||||
option chnroute_url 'https://ispip.clang.cn/all_cn.txt'
|
||||
option nfip_url 'https://fastly.jsdelivr.net/gh/QiuSimons/Netflix_IP/NF_only.txt'
|
||||
option adblock_url 'https://anti-ad.net/anti-ad-for-dnsmasq.conf'
|
||||
|
||||
config server_subscribe
|
||||
option proxy '1'
|
||||
option proxy '0'
|
||||
option auto_update '1'
|
||||
option config_auto_update_mode '0'
|
||||
option config_update_interval '60'
|
||||
option auto_update_week_time '*'
|
||||
option auto_update_day_time '2'
|
||||
option auto_update_min_time '0'
|
||||
option url_test_url 'https://www.google.com/generate_204'
|
||||
option user_agent 'v2rayN/9.99'
|
||||
option filter_words '过期/套餐/剩余/网址/QQ群/官网/防失联/回国'
|
||||
option filter_words '过期/套餐/剩余/QQ群/官网/防失联/回国'
|
||||
|
||||
config access_control
|
||||
option lan_ac_mode '0'
|
||||
@@ -47,11 +47,6 @@ config socks5_proxy
|
||||
option server 'nil'
|
||||
option local_port '1080'
|
||||
|
||||
config http_proxy
|
||||
option server 'nil'
|
||||
option local_port '3128'
|
||||
option http_auth 'none'
|
||||
|
||||
config server_global
|
||||
option enable_server '0'
|
||||
|
||||
@@ -59,9 +54,3 @@ config global_xray_fragment
|
||||
option fragment '0'
|
||||
option noise '0'
|
||||
|
||||
config clash_client_group
|
||||
option enabled '0'
|
||||
option remarks ''
|
||||
option ip_addr ''
|
||||
option client_mac ''
|
||||
option policy_group ''
|
||||
@@ -1,43 +1,6 @@
|
||||
#!/bin/sh
|
||||
LOCK_DIR="/var/run/ssrplusupdate.lock"
|
||||
LOOP_STAMP="/var/run/ssrplusupdate.loop"
|
||||
MODE="$(uci -q get shadowsocksr.@server_subscribe[0].config_auto_update_mode 2>/dev/null || echo 0)"
|
||||
|
||||
mkdir "$LOCK_DIR" 2>/dev/null || exit 0
|
||||
trap 'rmdir "$LOCK_DIR" 2>/dev/null' EXIT INT TERM
|
||||
|
||||
if [ "$1" = "loop" ]; then
|
||||
[ "$(uci -q get shadowsocksr.@server_subscribe[0].auto_update 2>/dev/null || echo 0)" = "1" ] || exit 0
|
||||
[ "$MODE" = "1" ] || exit 0
|
||||
|
||||
INTERVAL="$(uci -q get shadowsocksr.@server_subscribe[0].config_update_interval 2>/dev/null || echo 60)"
|
||||
case "$INTERVAL" in
|
||||
''|*[!0-9]*)
|
||||
INTERVAL=60
|
||||
;;
|
||||
esac
|
||||
[ "$INTERVAL" -gt 0 ] 2>/dev/null || INTERVAL=60
|
||||
|
||||
NOW="$(date +%s)"
|
||||
LAST_RUN=0
|
||||
[ -f "$LOOP_STAMP" ] && LAST_RUN="$(cat "$LOOP_STAMP" 2>/dev/null || echo 0)"
|
||||
case "$LAST_RUN" in
|
||||
''|*[!0-9]*)
|
||||
LAST_RUN=0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ $((NOW - LAST_RUN)) -lt $((INTERVAL * 60)) ]; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
/usr/bin/lua /usr/share/shadowsocksr/update.lua
|
||||
sleep 2s
|
||||
/usr/share/shadowsocksr/chinaipset.sh /var/etc/ssrplus/china_ssr.txt
|
||||
sleep 2s
|
||||
/usr/bin/lua /usr/share/shadowsocksr/subscribe.lua
|
||||
|
||||
if [ "$1" = "loop" ]; then
|
||||
date +%s > "$LOOP_STAMP"
|
||||
fi
|
||||
File diff suppressed because it is too large.
Load diff
@@ -250,6 +250,10 @@ if args then
|
||||
update(uci:get_first("shadowsocksr", "global", "adblock_url"), "/etc/ssrplus/ad.conf", args, TMP_DNSMASQ_PATH .. "/ad.conf")
|
||||
os.exit(0)
|
||||
end
|
||||
if args == "nfip_data" then
|
||||
update(uci:get_first("shadowsocksr", "global", "nfip_url"), "/etc/ssrplus/netflixip.list", args, TMP_DNSMASQ_PATH .. "/netflixip.list")
|
||||
os.exit(0)
|
||||
end
|
||||
else
|
||||
log("正在更新【GFW列表】数据库")
|
||||
update(uci:get_first("shadowsocksr", "global", "gfwlist_url"), "/etc/ssrplus/gfw_list.conf", "gfw_data", TMP_DNSMASQ_PATH .. "/gfw_list.conf")
|
||||
@@ -263,4 +267,10 @@ else
|
||||
log("正在更新【广告屏蔽】数据库")
|
||||
update(uci:get_first("shadowsocksr", "global", "adblock_url"), "/etc/ssrplus/ad.conf", "ad_data", TMP_DNSMASQ_PATH .. "/ad.conf")
|
||||
end
|
||||
if uci:get_first("shadowsocksr", "global", "netflix_enable", "0") == "1" then
|
||||
log("正在更新【Netflix IP段】数据库")
|
||||
update(uci:get_first("shadowsocksr", "global", "nfip_url"), "/etc/ssrplus/netflixip.list", "nfip_data", TMP_DNSMASQ_PATH .. "/netflixip.list")
|
||||
end
|
||||
-- log("正在更新【Netflix IP段】数据库")
|
||||
-- update(uci:get_first("shadowsocksr", "global", "nfip_url"), "/etc/ssrplus/netflixip.list", "nfip_data")
|
||||
end
|
||||
File diff suppressed because it is too large.
Load diff
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
VERSION="$1"
|
||||
CURDIR="$2"
|
||||
BIN_PATH="$3"
|
||||
|
||||
if [ -d "$CURDIR/.git" ]; then
|
||||
config="$CURDIR/.git/config"
|
||||
else
|
||||
config="$(sed "s|^gitdir:\s*|$CURDIR/|;s|$|/config|" "$CURDIR/.git")"
|
||||
fi
|
||||
[ -n "$(sed -En '/^\[remote /{h;:top;n;/^\[/b;s,(https?://gitcode\.(com|net)),\1,;T top;H;x;s|\n\s*|: |;p;}' "$config")" ] && {
|
||||
echo -e "#!/bin/sh\necho $VERSION" > "$BIN_PATH"
|
||||
}
|
||||
exit 0
|
||||
+30
-25
@@ -1,30 +1,32 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Copyright (C) 2024-2026 Anya Lin <hukk1996@gmail.com>
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=mihomo
|
||||
PKG_VERSION:=1.19.26
|
||||
PKG_RELEASE:=11
|
||||
PKG_RELEASE:=12
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git
|
||||
PKG_SOURCE_VERSION:=v$(PKG_VERSION)
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_MIRROR_HASH:=skip
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/metacubex/mihomo/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL3.0+
|
||||
PKG_MAINTAINER:=Joseph Mory <morytyann@gmail.com>
|
||||
PKG_MAINTAINER:=Anya Lin <hukk1996@gmail.com>
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
PKG_BUILD_DEPENDS:=golang/host
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_BUILD_FLAGS:=no-mips16
|
||||
|
||||
PKG_BUILD_VERSION:=v$(PKG_VERSION)
|
||||
PKG_BUILD_TIME:=$(shell date -u -Iseconds)
|
||||
|
||||
GO_PKG:=github.com/metacubex/mihomo
|
||||
GO_PKG_LDFLAGS_X:=$(GO_PKG)/constant.Version=$(PKG_BUILD_VERSION) $(GO_PKG)/constant.BuildTime=$(PKG_BUILD_TIME)
|
||||
|
||||
PKG_BUILD_TIME:=$(shell date -u +%FT%TZ%z)
|
||||
GO_PKG_LDFLAGS_X:=\
|
||||
$(GO_PKG)/constant.Version=v$(PKG_VERSION) \
|
||||
$(GO_PKG)/constant.BuildTime=$(PKG_BUILD_TIME)
|
||||
GO_PKG_TAGS:=with_gvisor
|
||||
GO_PKG_INSTALL_BIN_PATH:=/usr/libexec
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||
@@ -32,24 +34,27 @@ include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
|
||||
define Package/mihomo
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=A rule based proxy in Go.
|
||||
TITLE:=Another Mihomo Kernel.
|
||||
URL:=https://wiki.metacubex.one
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
||||
DEPENDS:=$(GO_ARCH_DEPENDS)
|
||||
PROVIDES:=mihomo
|
||||
ALTERNATIVES:=\
|
||||
300:/usr/bin/mihomo:/usr/libexec/mihomo
|
||||
endef
|
||||
|
||||
define Package/mihomo/description
|
||||
Mihomo is a rule based proxy in Go.
|
||||
endef
|
||||
|
||||
define Package/mihomo/install
|
||||
$(call GoPackage/Package/Install/Bin,$(1))
|
||||
100:/usr/bin/mihomo:/usr/libexec/mihomo-core
|
||||
USERID:=mihomo=7890:mihomo=7890
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
$(Build/Prepare/Default)
|
||||
$(RM) -r $(PKG_BUILD_DIR)/rules/logic_test
|
||||
# rm unit test
|
||||
rm -f $(PKG_BUILD_DIR)/rules/logic_test/logic_test.go
|
||||
endef
|
||||
|
||||
define Package/mihomo/install
|
||||
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
|
||||
$(CURDIR)/.prepare.sh $(VERSION) $(CURDIR) $(PKG_INSTALL_DIR)/usr/bin/$(PKG_NAME)
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/libexec/
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/$(PKG_NAME) $(1)/usr/libexec/mihomo-core
|
||||
endef
|
||||
|
||||
$(eval $(call GoBinPackage,mihomo))
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#
|
||||
# Copyright (C) 2018-2019 wongsyrone
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v3.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=trojan
|
||||
PKG_VERSION:=1.16.0
|
||||
PKG_RELEASE:=3
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/trojan-gfw/trojan/tar.gz/v$(PKG_VERSION)?
|
||||
PKG_HASH:=skip
|
||||
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_BUILD_DEPENDS:=openssl
|
||||
|
||||
PKG_LICENSE:=GPL-3.0
|
||||
PKG_LICENSE_FILE:=LICENSE
|
||||
PKG_MAINTAINER:=GreaterFire
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(INCLUDE_DIR)/cmake.mk
|
||||
include ./boost-version.mk
|
||||
|
||||
TARGET_CXXFLAGS += -Wall -Wextra
|
||||
TARGET_CXXFLAGS += $(FPIC)
|
||||
|
||||
# LTO
|
||||
TARGET_CXXFLAGS += -flto
|
||||
TARGET_LDFLAGS += -flto
|
||||
|
||||
# CXX standard
|
||||
TARGET_CXXFLAGS += -std=c++11
|
||||
TARGET_CXXFLAGS := $(filter-out -O%,$(TARGET_CXXFLAGS)) -O3
|
||||
TARGET_CXXFLAGS += -ffunction-sections -fdata-sections
|
||||
TARGET_LDFLAGS += -Wl,--gc-sections
|
||||
|
||||
CMAKE_OPTIONS += \
|
||||
-DENABLE_MYSQL=OFF \
|
||||
-DENABLE_NAT=ON \
|
||||
-DENABLE_REUSE_PORT=ON \
|
||||
-DENABLE_SSL_KEYLOG=ON \
|
||||
-DENABLE_TLS13_CIPHERSUITES=ON \
|
||||
-DFORCE_TCP_FASTOPEN=OFF \
|
||||
-DSYSTEMD_SERVICE=OFF \
|
||||
-DOPENSSL_USE_STATIC_LIBS=FALSE \
|
||||
-DBoost_DEBUG=ON \
|
||||
-DBoost_NO_BOOST_CMAKE=ON
|
||||
|
||||
define Package/trojan
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=Web Servers/Proxies
|
||||
TITLE:=An unidentifiable mechanism that helps you bypass GFW
|
||||
URL:=https://github.com/trojan-gfw/trojan
|
||||
DEPENDS:= \
|
||||
+libpthread +libstdcpp +libopenssl \
|
||||
+boost +boost-program_options +boost-date_time \
|
||||
$(if $(filter y,$(NEED_BOOST_SYSTEM)),,+boost-system)
|
||||
endef
|
||||
|
||||
define Package/trojan/install
|
||||
$(INSTALL_DIR) $(1)/usr/sbin
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/trojan $(1)/usr/sbin/trojan
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,trojan))
|
||||
@@ -0,0 +1,12 @@
|
||||
# boost-version.mk
|
||||
BOOST_MAKEFILE := $(firstword $(shell find -L $(TOPDIR) -type f -path "*/boost/Makefile"))
|
||||
|
||||
BOOST_PKG_VERSION := $(shell grep '^PKG_VERSION:=' $(BOOST_MAKEFILE) | head -n1 | cut -d= -f2)
|
||||
|
||||
BOOST_VER_MAJOR := $(word 1,$(subst ., ,$(BOOST_PKG_VERSION)))
|
||||
BOOST_VER_MINOR := $(word 2,$(subst ., ,$(BOOST_PKG_VERSION)))
|
||||
BOOST_VER_PATCH := $(word 3,$(subst ., ,$(BOOST_PKG_VERSION)))
|
||||
|
||||
BOOST_VERSION_CODE := $(shell echo $$(($(BOOST_VER_MAJOR)*100000 + $(BOOST_VER_MINOR)*100 + $(BOOST_VER_PATCH))))
|
||||
|
||||
NEED_BOOST_SYSTEM := $(if $(shell [ $(BOOST_VERSION_CODE) -ge 108900 ] && echo y),y,n)
|
||||
@@ -0,0 +1,11 @@
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -43,7 +43,7 @@ if(MSVC)
|
||||
add_definitions(-DBOOST_DATE_TIME_NO_LIB)
|
||||
endif()
|
||||
|
||||
-find_package(OpenSSL 1.1.0 REQUIRED)
|
||||
+find_package(OpenSSL 1.1.1 REQUIRED)
|
||||
include_directories(${OPENSSL_INCLUDE_DIR})
|
||||
target_link_libraries(trojan ${OPENSSL_LIBRARIES})
|
||||
if(OPENSSL_VERSION VERSION_GREATER_EQUAL 1.1.1)
|
||||
@@ -0,0 +1,16 @@
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -36,7 +36,12 @@ set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(trojan ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
-find_package(Boost 1.66.0 REQUIRED COMPONENTS system program_options)
|
||||
+find_package(Boost 1.66.0 REQUIRED)
|
||||
+if (Boost_MAJOR_VERSION LESS_EQUAL 1 AND Boost_MINOR_VERSION LESS 89)
|
||||
+ find_package(Boost 1.66.0 REQUIRED COMPONENTS system program_options)
|
||||
+else()
|
||||
+ find_package(Boost 1.66.0 REQUIRED COMPONENTS program_options)
|
||||
+endif()
|
||||
include_directories(${Boost_INCLUDE_DIR})
|
||||
target_link_libraries(trojan ${Boost_LIBRARIES})
|
||||
if(MSVC)
|
||||
Reference in new issue
Block a user