mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 01: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_NAME:=dns2tcp
|
||||||
PKG_VERSION:=1.1.2
|
PKG_VERSION:=1.1.2
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/zfl9/dns2tcp/tar.gz/v$(PKG_VERSION)?
|
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_TITLE:=luci-app-ssr-plus
|
||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
PKG_NAME:=luci-app-ssr-plus
|
PKG_NAME:=luci-app-ssr-plus
|
||||||
PKG_VERSION:=193
|
PKG_VERSION:=190
|
||||||
PKG_RELEASE:=16
|
PKG_RELEASE:=17
|
||||||
|
|
||||||
PKG_CONFIG_DEPENDS:= \
|
PKG_CONFIG_DEPENDS:= \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy \
|
CONFIG_PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NONE_V2RAY \
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NONE_V2RAY \
|
||||||
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Xray \
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Xray \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG \
|
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_DNS2TCP \
|
||||||
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_DNSPROXY \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_MosDNS \
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_MosDNS \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Http_Proxy \
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Mihomo \
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Tuic_Client \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadow_TLS \
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadow_TLS \
|
||||||
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun \
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy \
|
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_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_NONE_Server \
|
||||||
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server \
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Simple_Obfs \
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Simple_Obfs \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_V2ray_Plugin \
|
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_V2ray_Plugin \
|
||||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client \
|
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_PKGARCH:=all
|
||||||
LUCI_DEPENDS:= \
|
LUCI_DEPENDS:= \
|
||||||
+libuci-lua +lua +luci-compat +coreutils +coreutils-base64 +dns2tcp +dnsmasq-full \
|
+coreutils +coreutils-base64 +dns2tcp +dnsmasq-full \
|
||||||
+jq +ip-full +lua-neturl +libuci-lua +microsocks +ipt2socks +lyaml \
|
+jq +ip-full +lua +lua-neturl +libuci-lua +microsocks \
|
||||||
+resolveip +curl +nping +unzip +xz-utils \
|
+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_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_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_Shadow_TLS:shadow-tls \
|
||||||
|
+PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks:ipt2socks \
|
||||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun:kcptun-client \
|
+PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun:kcptun-client \
|
||||||
+PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy:naiveproxy \
|
+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_Client:shadowsocks-rust-sslocal \
|
||||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server:shadowsocks-rust-ssserver \
|
+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_Simple_Obfs:simple-obfs-client \
|
||||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_V2ray_Plugin:v2ray-plugin \
|
+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-local \
|
||||||
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-redir \
|
+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
|
define Package/$(PKG_NAME)/config
|
||||||
|
select PACKAGE_luci-lua-runtime if PACKAGE_$(PKG_NAME)
|
||||||
|
|
||||||
choice
|
choice
|
||||||
prompt "Transparent Proxy Backend"
|
prompt "Transparent Proxy Backend"
|
||||||
@@ -77,24 +103,37 @@ endchoice
|
|||||||
|
|
||||||
choice
|
choice
|
||||||
prompt "Shadowsocks Client Selection"
|
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
|
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Client
|
||||||
bool "None"
|
bool "None"
|
||||||
|
|
||||||
|
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client
|
||||||
|
bool "Shadowsocks-libev"
|
||||||
|
|
||||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client
|
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
|
endchoice
|
||||||
|
|
||||||
choice
|
choice
|
||||||
prompt "Shadowsocks Server Selection"
|
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
|
default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Server
|
||||||
|
|
||||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Server
|
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_NONE_Server
|
||||||
bool "None"
|
bool "None"
|
||||||
|
|
||||||
|
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server
|
||||||
|
bool "Shadowsocks-libev"
|
||||||
|
|
||||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server
|
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
|
endchoice
|
||||||
|
|
||||||
choice
|
choice
|
||||||
@@ -105,35 +144,45 @@ choice
|
|||||||
config PACKAGE_$(PKG_NAME)_INCLUDE_NONE_V2RAY
|
config PACKAGE_$(PKG_NAME)_INCLUDE_NONE_V2RAY
|
||||||
bool "None"
|
bool "None"
|
||||||
|
|
||||||
|
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray
|
||||||
|
bool "V2ray-core"
|
||||||
|
|
||||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Xray
|
config PACKAGE_$(PKG_NAME)_INCLUDE_Xray
|
||||||
bool "Xray-core"
|
bool "Xray-core"
|
||||||
select PACKAGE_xray-core
|
|
||||||
endchoice
|
endchoice
|
||||||
|
|
||||||
config PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG
|
config PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG
|
||||||
bool "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
|
config PACKAGE_$(PKG_NAME)_INCLUDE_MosDNS
|
||||||
bool "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
|
default n
|
||||||
|
|
||||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Http_Proxy
|
config PACKAGE_$(PKG_NAME)_INCLUDE_Tuic_Client
|
||||||
bool "Include HTTP(S) Proxy Server"
|
bool "Include Tuic-Client"
|
||||||
select PACKAGE_3proxy
|
select PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG
|
||||||
default y if (aarch64||arm||i386||loongarch64||riscv64||x86_64)
|
select PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks
|
||||||
|
depends on aarch64||arm||i386||x86_64
|
||||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Mihomo
|
depends on !(TARGET_x86_geode||TARGET_x86_legacy)
|
||||||
bool "Include Mihomo (Clash Support)"
|
default n
|
||||||
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_Shadow_TLS
|
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadow_TLS
|
||||||
bool "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)
|
depends on !(TARGET_x86_geode||TARGET_x86_legacy)
|
||||||
default n
|
default n
|
||||||
|
|
||||||
|
config PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks
|
||||||
|
bool "Include IPT2Socks"
|
||||||
|
default n
|
||||||
|
|
||||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun
|
config PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun
|
||||||
bool "Include Kcptun"
|
bool "Include Kcptun"
|
||||||
default n
|
default n
|
||||||
@@ -152,9 +205,13 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy
|
|||||||
depends on !(arc||armeb||mips||mips64||powerpc||TARGET_gemini)
|
depends on !(arc||armeb||mips||mips64||powerpc||TARGET_gemini)
|
||||||
default n
|
default n
|
||||||
|
|
||||||
|
config PACKAGE_$(PKG_NAME)_INCLUDE_Redsocks2
|
||||||
|
bool "Include Redsocks2"
|
||||||
|
default n
|
||||||
|
|
||||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Simple_Obfs
|
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Simple_Obfs
|
||||||
bool "Include Shadowsocks Simple Obfs Plugin"
|
bool "Include Shadowsocks Simple Obfs Plugin"
|
||||||
default n
|
default y
|
||||||
|
|
||||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_V2ray_Plugin
|
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_V2ray_Plugin
|
||||||
bool "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
|
config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server
|
||||||
bool "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
|
default n
|
||||||
|
|
||||||
endef
|
endef
|
||||||
@@ -178,41 +240,4 @@ endef
|
|||||||
|
|
||||||
include $(TOPDIR)/feeds/luci/luci.mk
|
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
|
# 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 m, s, o
|
||||||
local cbi = require "luci.cbi"
|
local cbi = require "luci.cbi"
|
||||||
local uci = require "luci.model.uci".cursor()
|
local uci = require "luci.model.uci".cursor()
|
||||||
local URL = require "url"
|
|
||||||
|
|
||||||
-- 获取 LAN IP 地址
|
-- 获取 LAN IP 地址
|
||||||
function lanip()
|
function lanip()
|
||||||
@@ -31,32 +30,16 @@ local lan_ip = lanip()
|
|||||||
local server_table = {}
|
local server_table = {}
|
||||||
local type_table = {}
|
local type_table = {}
|
||||||
local function is_finded(e)
|
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
|
end
|
||||||
|
|
||||||
uci:foreach("shadowsocksr", "servers", function(s)
|
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}
|
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}
|
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
|
end
|
||||||
if s.type and s.type ~= "tun" then
|
if s.type then
|
||||||
type_table[s[".name"]] = s.type
|
type_table[s[".name"]] = s.type
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
@@ -96,13 +79,6 @@ o.datatype = "uinteger"
|
|||||||
o:depends("enable_switch", "1")
|
o:depends("enable_switch", "1")
|
||||||
o.default = 3
|
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 = 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/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"))
|
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: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.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 = 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.rmempty = false
|
||||||
o.default = "1"
|
o.default = "1"
|
||||||
@@ -151,13 +266,45 @@ end
|
|||||||
-- [[ SOCKS5 Proxy ]]--
|
-- [[ SOCKS5 Proxy ]]--
|
||||||
s = m:section(TypedSection, "socks5_proxy", translate("Global SOCKS5 Proxy Server"))
|
s = m:section(TypedSection, "socks5_proxy", translate("Global SOCKS5 Proxy Server"))
|
||||||
s.anonymous = true
|
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
|
-- Enable/Disable Option
|
||||||
o = s:option(Flag, "enabled", translate("Enable"))
|
o = s:option(Flag, "enabled", translate("Enable"))
|
||||||
o.default = 0
|
o.default = 0
|
||||||
o.rmempty = false
|
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
|
-- Socks Auth
|
||||||
if is_finded("xray") then
|
if is_finded("xray") then
|
||||||
o = s:option(ListValue, "socks5_auth", translate("Socks5 Auth Mode"), translate("Socks protocol auth methods, default:noauth."))
|
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("noauth", "NOAUTH")
|
||||||
o:value("password", "PASSWORD")
|
o:value("password", "PASSWORD")
|
||||||
o.rmempty = true
|
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
|
-- Socks User
|
||||||
o = s:option(Value, "socks5_user", translate("Socks5 User"), translate("Only when Socks5 Auth Mode is password valid, Mandatory."))
|
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 = s:option(Flag, "socks5_mixed", translate("Enabled Mixed"), translate("Mixed as an alias of socks, default:Enabled."))
|
||||||
o.default = "1"
|
o.default = "1"
|
||||||
o.rmempty = false
|
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
|
end
|
||||||
|
|
||||||
-- Local Port
|
-- Local Port
|
||||||
@@ -189,37 +350,6 @@ o.datatype = "port"
|
|||||||
o.default = 1080
|
o.default = 1080
|
||||||
o.rmempty = false
|
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 ]]--
|
-- [[ fragmen Settings ]]--
|
||||||
if is_finded("xray") then
|
if is_finded("xray") then
|
||||||
s = m:section(TypedSection, "global_xray_fragment", translate("Xray Fragment Settings"))
|
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)
|
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
|
end
|
||||||
|
|
||||||
local function is_installed(e)
|
local function is_installed(e)
|
||||||
@@ -142,14 +142,16 @@ local function set_apply_on_parse(map)
|
|||||||
local old = map.on_after_save
|
local old = map.on_after_save
|
||||||
map.on_after_save = function(self)
|
map.on_after_save = function(self)
|
||||||
if old then old(self) end
|
if old then old(self) end
|
||||||
--map:set("@global[0]", "timestamp", os.time())
|
map:set("@global[0]", "timestamp", os.time())
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local has_ss_rust = is_finded("sslocal") or is_finded("ssserver")
|
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_xray = is_finded("xray")
|
||||||
|
local has_hysteria2 = is_finded("hysteria")
|
||||||
|
|
||||||
local server_table = {}
|
local server_table = {}
|
||||||
local encrypt_methods = {
|
local encrypt_methods = {
|
||||||
@@ -254,56 +256,6 @@ local tls_flows = {
|
|||||||
"none"
|
"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 = Map("shadowsocksr", translate("Edit ShadowSocksR Server"))
|
||||||
m.redirect = url("servers")
|
m.redirect = url("servers")
|
||||||
if not sid or m.uci:get("shadowsocksr", sid) ~= "servers" then
|
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
|
end
|
||||||
-- 保存&应用成功后跳转到节点列表
|
-- 保存&应用成功后跳转到节点列表
|
||||||
set_apply_on_parse(m)
|
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 ]]--
|
-- [[ Servers Setting ]]--
|
||||||
s = m:section(NamedSection, sid, "servers")
|
s = m:section(NamedSection, sid, "servers")
|
||||||
@@ -331,27 +275,88 @@ o.rawhtml = true
|
|||||||
o.template = "shadowsocksr/ssrurl"
|
o.template = "shadowsocksr/ssrurl"
|
||||||
o.value = sid
|
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 核心
|
-- 新增一个选择框,用于选择 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"))
|
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"))
|
o:value("v2ray", translate("V2Ray/XRay"))
|
||||||
end
|
end
|
||||||
if is_finded("ssr-redir") then
|
if is_finded("ssr-redir") then
|
||||||
o:value("ssr", translate("ShadowsocksR"))
|
o:value("ssr", translate("ShadowsocksR"))
|
||||||
end
|
end
|
||||||
if has_mihomo then
|
if has_ss_rust or has_ss_libev then
|
||||||
o:value("ss", translate("ShadowSocks"))
|
o:value("ss", translate("ShadowSocks"))
|
||||||
end
|
end
|
||||||
if has_ss_rust then
|
if is_finded("trojan") then
|
||||||
o:value("ss-rust", translate("ShadowSocks"))
|
o:value("trojan", translate("Trojan"))
|
||||||
end
|
end
|
||||||
if is_finded("naive") then
|
if is_finded("naive") then
|
||||||
o:value("naiveproxy", translate("NaiveProxy"))
|
o:value("naiveproxy", translate("NaiveProxy"))
|
||||||
end
|
end
|
||||||
if is_finded("mihomo") then
|
if is_finded("hysteria") then
|
||||||
o:value("clash", translate("Clash/Mihomo"))
|
o:value("hysteria2", translate("Hysteria2"))
|
||||||
end
|
end
|
||||||
if is_finded("mihomo") then
|
if is_finded("tuic-client") then
|
||||||
o:value("tuic", translate("TUIC"))
|
o:value("tuic", translate("TUIC"))
|
||||||
end
|
end
|
||||||
if is_finded("shadow-tls") and is_finded("sslocal") then
|
if is_finded("shadow-tls") and is_finded("sslocal") then
|
||||||
@@ -360,51 +365,78 @@ end
|
|||||||
if is_finded("ipt2socks") then
|
if is_finded("ipt2socks") then
|
||||||
o:value("socks5", translate("Socks5"))
|
o:value("socks5", translate("Socks5"))
|
||||||
end
|
end
|
||||||
|
if is_finded("redsocks2") then
|
||||||
|
o:value("tun", translate("Network Tunnel"))
|
||||||
|
end
|
||||||
local old_cfgvalue = o.cfgvalue
|
local old_cfgvalue = o.cfgvalue
|
||||||
o.cfgvalue = function(self, section)
|
o.cfgvalue = function(self, section)
|
||||||
local val = self.map.uci:get("shadowsocksr", section, "type")
|
local val = self.map.uci:get("shadowsocksr", section, "type")
|
||||||
if old_cfgvalue then
|
if val == "ss-rust" or val == "ss-libev" then
|
||||||
|
return "ss"
|
||||||
|
end
|
||||||
|
if old_cfgvalue then
|
||||||
return old_cfgvalue(self, section)
|
return old_cfgvalue(self, section)
|
||||||
end
|
end
|
||||||
return val
|
return val
|
||||||
end
|
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.description = translate("Using incorrect encryption mothod may causes service fail to start")
|
||||||
|
|
||||||
o = s:option(Value, "alias", translate("Alias(optional)"))
|
o = s:option(Value, "alias", translate("Alias(optional)"))
|
||||||
|
|
||||||
local function clash_source_formvalue(map, section, option)
|
o = s:option(ListValue, "iface", translate("Network interface to use"))
|
||||||
local value = map:formvalue("cbid." .. map.config .. "." .. section .. "." .. option)
|
for _, e in ipairs(luci.sys.net.devices()) do
|
||||||
if value == nil then
|
if e ~= "lo" then
|
||||||
value = map.uci:get(map.config, section, option)
|
o:value(e)
|
||||||
end
|
end
|
||||||
return trim(value or "")
|
|
||||||
end
|
end
|
||||||
|
o:depends("type", "tun")
|
||||||
|
o.description = translate("Redirect traffic to this network interface")
|
||||||
|
|
||||||
local function validate_clash_source(self, value, section)
|
-- 新增一个选择框,用于选择 Shadowsocks 具体版本(仅当节点类型为 ss 或其具体子类型时显示)
|
||||||
local clash_url = clash_source_formvalue(self.map, section, "clash_url")
|
o = s:option(ListValue, "_ss_core", string.format("<b><span style='color:red;'>%s</span></b>", translatef("%s Node Use Version", "ShadowSocks")))
|
||||||
local clash_path = clash_source_formvalue(self.map, section, "clash_path")
|
o.description = translate("Selection ShadowSocks Node Use Version.")
|
||||||
if clash_url == "" and clash_path == "" then
|
if has_ss_rust then
|
||||||
return nil, translate("Please specify either a Clash subscription URL or a local YAML path.")
|
o:value("ss-rust", translate("ShadowSocks-rust Version"))
|
||||||
end
|
|
||||||
return value
|
|
||||||
end
|
end
|
||||||
|
if has_ss_libev then
|
||||||
o = s:option(Value, "clash_url", translate("Clash Subscription URL"))
|
o:value("ss-libev", translate("ShadowSocks-libev Version"))
|
||||||
o.placeholder = "https://example.com/config.yaml"
|
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.rmempty = true
|
||||||
o:depends("type", "clash")
|
-- 保存时,将选择的值直接写入当前节点的 type 字段
|
||||||
o.validate = validate_clash_source
|
o.write = function(self, section, value)
|
||||||
|
if value and value ~= "" then
|
||||||
o = s:option(Value, "clash_path", translate("Clash YAML Path"))
|
self.map.uci:set("shadowsocksr", section, "type", value)
|
||||||
o.placeholder = "/etc/ssrplus/clash/custom.yaml"
|
end
|
||||||
o.rmempty = true
|
end
|
||||||
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")
|
|
||||||
|
|
||||||
o = s:option(ListValue, "v2ray_protocol", translate("V2Ray/XRay protocol"))
|
o = s:option(ListValue, "v2ray_protocol", translate("V2Ray/XRay protocol"))
|
||||||
o:value("vless", translate("VLESS"))
|
o:value("vless", translate("VLESS"))
|
||||||
@@ -422,54 +454,30 @@ o:value("http", translate("HTTP"))
|
|||||||
o:depends("type", "v2ray")
|
o:depends("type", "v2ray")
|
||||||
|
|
||||||
o = s:option(Value, "server", translate("Server Address"))
|
o = s:option(Value, "server", translate("Server Address"))
|
||||||
o.datatype = "or(host,ip6addr)"
|
o.datatype = "host"
|
||||||
o.rmempty = false
|
o.rmempty = false
|
||||||
o:depends("type", "ssr")
|
o:depends("type", "ssr")
|
||||||
o:depends("type", "ss")
|
o:depends("type", "ss")
|
||||||
o:depends("type", "ss-rust")
|
o:depends("type", "v2ray")
|
||||||
o:depends("type", "trojan")
|
o:depends("type", "trojan")
|
||||||
o:depends("type", "naiveproxy")
|
o:depends("type", "naiveproxy")
|
||||||
o:depends("type", "hysteria2")
|
o:depends("type", "hysteria2")
|
||||||
o:depends("type", "tuic")
|
o:depends("type", "tuic")
|
||||||
o:depends("type", "shadowtls")
|
o:depends("type", "shadowtls")
|
||||||
o:depends("type", "socks5")
|
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 = s:option(Value, "server_port", translate("Server Port"))
|
||||||
o.datatype = "port"
|
o.datatype = "port"
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
o:depends("type", "ssr")
|
o:depends("type", "ssr")
|
||||||
o:depends("type", "ss")
|
o:depends("type", "ss")
|
||||||
o:depends("type", "ss-rust")
|
o:depends("type", "v2ray")
|
||||||
o:depends("type", "trojan")
|
o:depends("type", "trojan")
|
||||||
o:depends("type", "naiveproxy")
|
o:depends("type", "naiveproxy")
|
||||||
o:depends("type", "hysteria2")
|
o:depends("type", "hysteria2")
|
||||||
o:depends("type", "tuic")
|
o:depends("type", "tuic")
|
||||||
o:depends("type", "shadowtls")
|
o:depends("type", "shadowtls")
|
||||||
o:depends("type", "socks5")
|
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 = s:option(Flag, "auth_enable", translate("Enable Authentication"))
|
||||||
o.rmempty = false
|
o.rmempty = false
|
||||||
@@ -490,7 +498,6 @@ o.password = true
|
|||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
o:depends("type", "ssr")
|
o:depends("type", "ssr")
|
||||||
o:depends("type", "ss")
|
o:depends("type", "ss")
|
||||||
o:depends("type", "ss-rust")
|
|
||||||
o:depends("type", "trojan")
|
o:depends("type", "trojan")
|
||||||
o:depends("type", "naiveproxy")
|
o:depends("type", "naiveproxy")
|
||||||
o:depends("type", "shadowtls")
|
o:depends("type", "shadowtls")
|
||||||
@@ -517,7 +524,6 @@ for _, v in ipairs(encrypt_methods_ss) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
o:depends("type", "ss-rust")
|
|
||||||
o:depends("type", "ss")
|
o:depends("type", "ss")
|
||||||
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||||||
|
|
||||||
@@ -536,30 +542,22 @@ o.default = "1"
|
|||||||
o = s:option(Flag, "enable_plugin", translate("Enable Plugin"))
|
o = s:option(Flag, "enable_plugin", translate("Enable Plugin"))
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
o:depends("type", "ss")
|
o:depends("type", "ss")
|
||||||
o:depends("type", "ss-rust")
|
|
||||||
o.default = "0"
|
o.default = "0"
|
||||||
|
|
||||||
-- Shadowsocks Plugin
|
-- Shadowsocks Plugin
|
||||||
o = s:option(ListValue, "plugin", translate("Obfs"))
|
o = s:option(ListValue, "plugin", translate("Obfs"))
|
||||||
o:value("none", translate("None"))
|
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"))
|
o:value("obfs-local", translate("obfs-local"))
|
||||||
end
|
end
|
||||||
if has_mihomo or is_finded("v2ray-plugin") then
|
if is_finded("v2ray-plugin") then
|
||||||
o:value("v2ray-plugin", translate("v2ray-plugin"))
|
o:value("v2ray-plugin", translate("v2ray-plugin"))
|
||||||
end
|
end
|
||||||
if has_mihomo then
|
|
||||||
o:value("gost-plugin", translate("gost-plugin"))
|
|
||||||
end
|
|
||||||
if is_finded("xray-plugin") then
|
if is_finded("xray-plugin") then
|
||||||
o:value("xray-plugin", translate("xray-plugin"))
|
o:value("xray-plugin", translate("xray-plugin"))
|
||||||
end
|
end
|
||||||
if has_mihomo or is_finded("shadow-tls") then
|
if is_finded("shadow-tls") then
|
||||||
o:value("shadow-tls", translate("Shadow-TLS"))
|
o:value("shadow-tls", translate("shadow-tls"))
|
||||||
end
|
|
||||||
if has_mihomo then
|
|
||||||
o:value("restls", translate("restls"))
|
|
||||||
o:value("kcptun", translate("kcptun"))
|
|
||||||
end
|
end
|
||||||
o:value("custom", translate("Custom"))
|
o:value("custom", translate("Custom"))
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
@@ -595,21 +593,6 @@ o:depends("type", "ssr")
|
|||||||
|
|
||||||
|
|
||||||
-- [[ Hysteria2 ]]--
|
-- [[ 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 = s:option(Value, "hy2_auth", translate("Users Authentication"))
|
||||||
o:depends("type", "hysteria2")
|
o:depends("type", "hysteria2")
|
||||||
o:depends({type = "v2ray", v2ray_protocol = "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 = s:option(Flag, "flag_port_hopping", translate("Enable Port Hopping"))
|
||||||
o:depends("type", "hysteria2")
|
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.rmempty = true
|
||||||
o.default = "0"
|
o.default = "0"
|
||||||
|
|
||||||
@@ -660,30 +643,17 @@ o.rmempty = true
|
|||||||
o.default = "0"
|
o.default = "0"
|
||||||
|
|
||||||
o = s:option(Value, "obfs_type", translate("Obfuscation Type"))
|
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 = "hysteria2", flag_obfs = true})
|
||||||
o:depends({type = "v2ray", v2ray_protocol = "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 = 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.password = true
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
o:depends({type = "hysteria2", flag_obfs = true})
|
o.placeholder = "cry_me_a_r1ver"
|
||||||
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 = s:option(Flag, "flag_quicparam", translate("Hysterir QUIC parameters"))
|
o = s:option(Flag, "flag_quicparam", translate("Hysterir QUIC parameters"))
|
||||||
o:depends("type", "hysteria2")
|
o:depends("type", "hysteria2")
|
||||||
@@ -778,13 +748,13 @@ o:depends("type", "shadowtls")
|
|||||||
if is_finded("sslocal") then
|
if is_finded("sslocal") then
|
||||||
o:value("sslocal", translate("ShadowSocks-rust Version"))
|
o:value("sslocal", translate("ShadowSocks-rust Version"))
|
||||||
end
|
end
|
||||||
if is_finded("xray") then
|
if is_finded("xray") or is_finded("v2ray") then
|
||||||
o:value("vmess", translate("Vmess Protocol"))
|
o:value("vmess", translate("Vmess Protocol"))
|
||||||
end
|
end
|
||||||
o.default = "sslocal"
|
o.default = "sslocal"
|
||||||
o.rmempty = false
|
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:depends({type = "shadowtls", chain_type = "sslocal"})
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
|
|
||||||
@@ -818,7 +788,7 @@ o:depends("type", "tuic")
|
|||||||
--Tuic IP
|
--Tuic IP
|
||||||
o = s:option(Value, "tuic_ip", translate("TUIC Server IP Address"))
|
o = s:option(Value, "tuic_ip", translate("TUIC Server IP Address"))
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
o.datatype = "ipaddr"
|
o.datatype = "ip4addr"
|
||||||
o.default = ""
|
o.default = ""
|
||||||
o:depends("type", "tuic")
|
o:depends("type", "tuic")
|
||||||
|
|
||||||
@@ -999,7 +969,7 @@ o = s:option(Value, "ws_heartbeatPeriod", translate("HeartbeatPeriod(second)"))
|
|||||||
o.datatype = "integer"
|
o.datatype = "integer"
|
||||||
o:depends("transport", "ws")
|
o:depends("transport", "ws")
|
||||||
|
|
||||||
if is_finded("xray") then
|
if is_finded("v2ray") then
|
||||||
-- WS前置数据
|
-- WS前置数据
|
||||||
o = s:option(Value, "ws_ed", translate("Max Early Data"))
|
o = s:option(Value, "ws_ed", translate("Max Early Data"))
|
||||||
o:depends("ws_ed_enable", true)
|
o:depends("ws_ed_enable", true)
|
||||||
@@ -1240,12 +1210,12 @@ o.default = "0"
|
|||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
|
|
||||||
o = s:option(DynamicList, "local_addresses", translate("Local addresses"))
|
o = s:option(DynamicList, "local_addresses", translate("Local addresses"))
|
||||||
--o.datatype = "cidr"
|
o.datatype = "cidr"
|
||||||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
|
|
||||||
o = s:option(DynamicList, "reserved", translate("Reserved bytes(optional)"))
|
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:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
|
|
||||||
@@ -1290,18 +1260,12 @@ o:depends("transport", "grpc")
|
|||||||
o = s:option(Flag, "enable_finalmask", translate("FinalMask"))
|
o = s:option(Flag, "enable_finalmask", translate("FinalMask"))
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
o.default = "0"
|
o.default = "0"
|
||||||
local protocols = s.fields["v2ray_protocol"].keylist
|
o:depends({type = "v2ray", v2ray_protocol = "vless"})
|
||||||
if protocols and type(protocols) == "table" and #protocols > 0 then
|
o:depends({type = "v2ray", v2ray_protocol = "vmess"})
|
||||||
for _, proto in ipairs(protocols) do
|
o:depends({type = "v2ray", v2ray_protocol = "trojan"})
|
||||||
if not proto:find("^_") then
|
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
|
||||||
if proto == "hysteria2" then
|
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2", hysteria2_realms = false})
|
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
||||||
elseif proto ~= "socks" and proto ~= "http" then
|
|
||||||
o:depends({type = "v2ray", v2ray_protocol = proto})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
o = s:option(TextValue, "finalmask", " ")
|
o = s:option(TextValue, "finalmask", " ")
|
||||||
o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "<br>" ..
|
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 = s:option(Flag, "enable_ech", translate("Enable ECH(optional)"))
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
o.default = "0"
|
o.default = "0"
|
||||||
local protocols = s.fields["v2ray_protocol"].keylist
|
o:depends({type = "v2ray", tls = true})
|
||||||
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 = s:option(TextValue, "ech_config", translate("ECH Config"))
|
o = s:option(TextValue, "ech_config", translate("ECH Config"))
|
||||||
o.description = translate(
|
o.description = translate(
|
||||||
@@ -1692,41 +1645,100 @@ o.rmempty = true
|
|||||||
o.default = "0"
|
o.default = "0"
|
||||||
o:depends("type", "ssr")
|
o:depends("type", "ssr")
|
||||||
o:depends("type", "ss")
|
o:depends("type", "ss")
|
||||||
o:depends("type", "ss-rust")
|
|
||||||
o:depends("type", "trojan")
|
o:depends("type", "trojan")
|
||||||
o:depends("type", "hysteria2")
|
o:depends("type", "hysteria2")
|
||||||
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "xhttp"})
|
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "xhttp"})
|
||||||
o:depends({type = "v2ray", v2ray_protocol = "hysteria2"})
|
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 = s:option(Flag, "switch_enable", translate("Enable Auto Switch"))
|
||||||
o.rmempty = false
|
o.rmempty = false
|
||||||
o.default = "1"
|
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
|
if is_finded("kcptun-client") then
|
||||||
o = s:option(Flag, "kcp_enable", translate("KcpTun Enable"))
|
o = s:option(Flag, "kcp_enable", translate("KcpTun Enable"))
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
o.default = "0"
|
o.default = "0"
|
||||||
o:depends("type", "ssr")
|
o:depends("type", "ssr")
|
||||||
o:depends("type", "ss-rust")
|
|
||||||
o:depends("type", "ss")
|
o:depends("type", "ss")
|
||||||
|
|
||||||
o = s:option(Value, "kcp_port", translate("KcpTun Port"))
|
o = s:option(Value, "kcp_port", translate("KcpTun Port"))
|
||||||
o.datatype = "portrange"
|
o.datatype = "portrange"
|
||||||
o.default = 4000
|
o.default = 4000
|
||||||
o:depends("type", "ssr")
|
o:depends("type", "ssr")
|
||||||
o:depends("type", "ss-rust")
|
|
||||||
o:depends("type", "ss")
|
o:depends("type", "ss")
|
||||||
|
|
||||||
o = s:option(Value, "kcp_password", translate("KcpTun Password"))
|
o = s:option(Value, "kcp_password", translate("KcpTun Password"))
|
||||||
o.password = true
|
o.password = true
|
||||||
o:depends("type", "ssr")
|
o:depends("type", "ssr")
|
||||||
o:depends("type", "ss-rust")
|
|
||||||
o:depends("type", "ss")
|
o:depends("type", "ss")
|
||||||
|
|
||||||
o = s:option(Value, "kcp_param", translate("KcpTun Param"))
|
o = s:option(Value, "kcp_param", translate("KcpTun Param"))
|
||||||
o.default = "--nocomp"
|
o.default = "--nocomp"
|
||||||
o:depends("type", "ssr")
|
o:depends("type", "ssr")
|
||||||
o:depends("type", "ss-rust")
|
|
||||||
o:depends("type", "ss")
|
o:depends("type", "ss")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
local m, s, sec, o
|
local m, s, sec, o
|
||||||
local uci = require "luci.model.uci".cursor()
|
local uci = require "luci.model.uci".cursor()
|
||||||
local URL = require "url"
|
|
||||||
|
|
||||||
-- 获取 LAN IP 地址
|
-- 获取 LAN IP 地址
|
||||||
function lanip()
|
function lanip()
|
||||||
@@ -32,62 +31,69 @@ end
|
|||||||
|
|
||||||
local lan_ip = lanip()
|
local lan_ip = lanip()
|
||||||
local validation = require "luci.cbi.datatypes"
|
local validation = require "luci.cbi.datatypes"
|
||||||
local clash_nodes = {}
|
|
||||||
local function is_finded(e)
|
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
|
end
|
||||||
|
|
||||||
local function clash_display_name(s)
|
m = Map("shadowsocksr", translate("ShadowSocksR Plus+ Settings"), translate("<h3>Support SS/SSR/V2RAY/XRAY/TROJAN/TUIC/HYSTERIA2/NAIVEPROXY/SOCKS5/TUN etc.</h3>"))
|
||||||
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:section(SimpleSection).template = "shadowsocksr/status"
|
m:section(SimpleSection).template = "shadowsocksr/status"
|
||||||
|
|
||||||
local server_table = {}
|
local server_table = {}
|
||||||
local server_order = {}
|
|
||||||
uci:foreach("shadowsocksr", "servers", function(s)
|
uci:foreach("shadowsocksr", "servers", function(s)
|
||||||
if s.type == "clash" then
|
if s.alias then
|
||||||
clash_nodes[s[".name"]] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if s.type ~= "tun" and s.alias then
|
|
||||||
server_table[s[".name"]] = "[%s]:%s" % {string.upper(s.v2ray_protocol or s.type), s.alias}
|
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}
|
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
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
local key_table = {}
|
||||||
|
for key, _ in pairs(server_table) do
|
||||||
|
table.insert(key_table, key)
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(key_table)
|
||||||
|
|
||||||
-- [[ Global Setting ]]--
|
-- [[ Global Setting ]]--
|
||||||
s = m:section(TypedSection, "global")
|
s = m:section(TypedSection, "global")
|
||||||
s.anonymous = true
|
s.anonymous = true
|
||||||
|
|
||||||
o = s:option(ListValue, "global_server", translate("Main Server"))
|
o = s:option(ListValue, "global_server", translate("Main Server"))
|
||||||
o:value("nil", translate("Disable"))
|
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])
|
o:value(key, server_table[key])
|
||||||
end
|
end
|
||||||
o.default = "nil"
|
o.default = "nil"
|
||||||
o.rmempty = false
|
o.rmempty = false
|
||||||
|
|
||||||
o = s:option(DummyValue, "_clash_panel", translate("Clash Panel"))
|
o = s:option(ListValue, "udp_relay_server", translate("Game Mode UDP Server"))
|
||||||
o.template = "shadowsocksr/clash_main_panel"
|
o:value("", translate("Disable"))
|
||||||
o.clash_nodes = clash_nodes
|
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 = s:option(ListValue, "threads", translate("Multi Threads Option"))
|
||||||
o:value("0", translate("Auto Threads"))
|
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("gfw", translate("GFW List Mode"))
|
||||||
o:value("router", translate("IP Route Mode"))
|
o:value("router", translate("IP Route Mode"))
|
||||||
o:value("all", translate("Global Mode"))
|
o:value("all", translate("Global Mode"))
|
||||||
|
o:value("oversea", translate("Oversea Mode"))
|
||||||
o.default = gfw
|
o.default = gfw
|
||||||
|
|
||||||
o = s:option(ListValue, "dports", translate("Proxy Ports"))
|
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
|
if is_finded("dns2tcp") then
|
||||||
o:value("1", translate("Use DNS2TCP query"))
|
o:value("1", translate("Use DNS2TCP query"))
|
||||||
end
|
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
|
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
|
end
|
||||||
if is_finded("chinadns-ng") then
|
if is_finded("chinadns-ng") then
|
||||||
o:value("6", translate("Use ChinaDNS-NG query and cache"))
|
o:value("6", translate("Use ChinaDNS-NG query and cache"))
|
||||||
end
|
end
|
||||||
o:value("7", translate("Prefer module built-in DNS"))
|
|
||||||
o:value("0", translate("Use Local DNS Service listen port 5335"))
|
o:value("0", translate("Use Local DNS Service listen port 5335"))
|
||||||
o.default = 1
|
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.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("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("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", "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.description = translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)")
|
||||||
o.datatype = "ip4addrport"
|
o.datatype = "ip4addrport"
|
||||||
o.default = "8.8.4.4:53"
|
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:value("tcp://1.1.1.1:53,tcp://1.0.0.1:53", translate("Cloudflare DNS"))
|
||||||
o:depends("pdnsd_enable", "4")
|
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.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 = s:option(Flag, "mosdns_ipv6", translate("Disable IPv6 in MOSDNS query mode"))
|
||||||
o:depends("pdnsd_enable", "1")
|
|
||||||
o:depends("pdnsd_enable", "4")
|
o:depends("pdnsd_enable", "4")
|
||||||
o:depends("pdnsd_enable", "7")
|
|
||||||
o.rmempty = false
|
o.rmempty = false
|
||||||
o.default = "1"
|
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
|
if is_finded("chinadns-ng") then
|
||||||
o = s:option(Value, "chinadns_ng_tunnel_forward", translate("Anti-pollution DNS Server"))
|
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)"))
|
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("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("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)"))
|
||||||
o:depends("pdnsd_enable", "6")
|
o:depends("pdnsd_enable", "6")
|
||||||
o.default = "8.8.4.4:53"
|
|
||||||
o.description = translate(
|
o.description = translate(
|
||||||
"<ul>" ..
|
"<ul>" ..
|
||||||
"<li>" .. translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)") .. "</li>" ..
|
"<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("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("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: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:depends({pdnsd_enable = "6", run_mode = "router"})
|
||||||
o.default = "wan"
|
|
||||||
o.description = translate("Custom DNS Server format as IP:PORT (default: disabled)")
|
o.description = translate("Custom DNS Server format as IP:PORT (default: disabled)")
|
||||||
o.validate = function(self, value, section)
|
o.validate = function(self, value, section)
|
||||||
if (section and value) then
|
if (section and value) then
|
||||||
@@ -227,7 +293,5 @@ if is_finded("chinadns-ng") then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
m:append(Template("shadowsocksr/client_dns_defaults"))
|
m:section(SimpleSection).template = "shadowsocksr/status_bottom"
|
||||||
m:append(Template("shadowsocksr/status_bottom"))
|
|
||||||
|
|
||||||
return m
|
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"
|
require "luci.sys"
|
||||||
local m, s, o
|
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")
|
m = Map("shadowsocksr")
|
||||||
|
|
||||||
s = m:section(TypedSection, "access_control")
|
s = m:section(TypedSection, "access_control")
|
||||||
@@ -125,6 +129,40 @@ o.remove = function(self, section, value)
|
|||||||
nixio.fs.writefile(denydomainconf, "")
|
nixio.fs.writefile(denydomainconf, "")
|
||||||
end
|
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
|
if luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0 then
|
||||||
m.apply_on_parse = true
|
m.apply_on_parse = true
|
||||||
function m.on_apply(self)
|
function m.on_apply(self)
|
||||||
@@ -132,6 +170,4 @@ if luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0 then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
m:append(Template("shadowsocksr/control_layout"))
|
|
||||||
|
|
||||||
return m
|
return m
|
||||||
@@ -50,7 +50,6 @@ luci.http.setfilehandler(function(meta, chunk, eof)
|
|||||||
luci.sys.call("rm -rf " .. temp_dir)
|
luci.sys.call("rm -rf " .. temp_dir)
|
||||||
nixio.fs.remove(file_path)
|
nixio.fs.remove(file_path)
|
||||||
fd = nixio.open(file_path, "w")
|
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")
|
luci.sys.call("echo '' > /var/log/ssrplus.log")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -97,7 +96,6 @@ luci.http.setfilehandler(function(meta, chunk, eof)
|
|||||||
-- 清理临时文件
|
-- 清理临时文件
|
||||||
luci.sys.call("rm -rf " .. temp_dir)
|
luci.sys.call("rm -rf " .. temp_dir)
|
||||||
nixio.fs.remove(file_path)
|
nixio.fs.remove(file_path)
|
||||||
luci.sys.call("ls /tmp/sub_md5_* >/dev/null 2>&1 && rm -f /tmp/sub_md5_*")
|
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|||||||
@@ -7,39 +7,68 @@ require "nixio.fs"
|
|||||||
local m, s, o
|
local m, s, o
|
||||||
local sid = arg[1]
|
local sid = arg[1]
|
||||||
|
|
||||||
-- 加密方式(SS/SSR用)
|
|
||||||
local encrypt_methods = {
|
local encrypt_methods = {
|
||||||
"rc4-md5", "rc4-md5-6", "rc4", "table",
|
"rc4-md5",
|
||||||
"aes-128-cfb", "aes-192-cfb", "aes-256-cfb",
|
"rc4-md5-6",
|
||||||
"aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
|
"rc4",
|
||||||
"bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
|
"table",
|
||||||
"cast5-cfb", "des-cfb", "idea-cfb", "rc2-cfb", "seed-cfb",
|
"aes-128-cfb",
|
||||||
"salsa20", "chacha20", "chacha20-ietf"
|
"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 = {
|
local encrypt_methods_ss = {
|
||||||
"aes-128-gcm", "aes-192-gcm", "aes-256-gcm",
|
-- aead
|
||||||
"chacha20-ietf-poly1305", "xchacha20-ietf-poly1305",
|
"aes-128-gcm",
|
||||||
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
"aes-192-gcm",
|
||||||
}
|
"aes-256-gcm",
|
||||||
|
"chacha20-ietf-poly1305",
|
||||||
-- Shadowsocks 加密方法(Xray 版)
|
"xchacha20-ietf-poly1305",
|
||||||
local v_ss_encrypt_method_list = {
|
-- aead 2022
|
||||||
"aes-128-cfb", "aes-256-cfb", "aes-128-gcm", "aes-256-gcm",
|
"2022-blake3-aes-128-gcm",
|
||||||
"chacha20", "chacha20-ietf", "chacha20-poly1305", "chacha20-ietf-poly1305"
|
"2022-blake3-aes-256-gcm",
|
||||||
}
|
"2022-blake3-chacha20-poly1305"
|
||||||
|
--[[ stream
|
||||||
-- 伪装类型(用于 mKCP/QUIC)
|
"table",
|
||||||
local header_type_list = {
|
"rc4",
|
||||||
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard"
|
"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"}
|
local protocol = {"origin"}
|
||||||
|
|
||||||
obfs = {"plain", "http_simple", "http_post"}
|
obfs = {"plain", "http_simple", "http_post"}
|
||||||
|
|
||||||
m = Map("shadowsocksr", translate("Edit ShadowSocksR Server"))
|
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
|
if m.uci:get("shadowsocksr", sid) ~= "server_config" then
|
||||||
luci.http.redirect(m.redirect)
|
luci.http.redirect(m.redirect)
|
||||||
return
|
return
|
||||||
@@ -50,26 +79,18 @@ s = m:section(NamedSection, sid, "server_config")
|
|||||||
s.anonymous = true
|
s.anonymous = true
|
||||||
s.addremove = false
|
s.addremove = false
|
||||||
|
|
||||||
-- ========== 基本设置 ==========
|
|
||||||
o = s:option(Flag, "enable", translate("Enable"))
|
o = s:option(Flag, "enable", translate("Enable"))
|
||||||
o.default = 1
|
o.default = 1
|
||||||
o.rmempty = false
|
o.rmempty = false
|
||||||
|
|
||||||
o = s:option(ListValue, "type", translate("Server Type"))
|
o = s:option(ListValue, "type", translate("Server Type"))
|
||||||
o:value("socks5", translate("Socks5"))
|
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"))
|
o:value("ss", translate("ShadowSocks"))
|
||||||
end
|
end
|
||||||
if nixio.fs.access("/usr/bin/ssr-server") then
|
if nixio.fs.access("/usr/bin/ssr-server") then
|
||||||
o:value("ssr", translate("ShadowsocksR"))
|
o:value("ssr", translate("ShadowsocksR"))
|
||||||
end
|
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.default = "socks5"
|
||||||
|
|
||||||
o = s:option(Value, "server_port", translate("Server Port"))
|
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 = s:option(Value, "password", translate("Password"))
|
||||||
o.password = true
|
o.password = true
|
||||||
o.rmempty = false
|
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"))
|
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.rmempty = false
|
||||||
o:depends("type", "ssr")
|
o:depends("type", "ssr")
|
||||||
|
|
||||||
o = s:option(ListValue, "encrypt_method_ss", translate("Encrypt Method"))
|
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.rmempty = false
|
||||||
o:depends("type", "ss")
|
o:depends("type", "ss")
|
||||||
|
|
||||||
o = s:option(ListValue, "protocol", translate("Protocol"))
|
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.rmempty = false
|
||||||
o:depends("type", "ssr")
|
o:depends("type", "ssr")
|
||||||
|
|
||||||
o = s:option(ListValue, "obfs", translate("Obfs"))
|
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.rmempty = false
|
||||||
o:depends("type", "ssr")
|
o:depends("type", "ssr")
|
||||||
|
|
||||||
@@ -128,269 +151,4 @@ o.rmempty = false
|
|||||||
o:depends("type", "ss")
|
o:depends("type", "ss")
|
||||||
o:depends("type", "ssr")
|
o:depends("type", "ssr")
|
||||||
|
|
||||||
-- ========== Xray 协议通用字段 ==========
|
return m
|
||||||
-- 用户备注
|
|
||||||
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
|
|
||||||
@@ -105,13 +105,8 @@ end
|
|||||||
o.rmempty = false
|
o.rmempty = false
|
||||||
|
|
||||||
o = sec:option(DummyValue, "type", translate("Server Type"))
|
o = sec:option(DummyValue, "type", translate("Server Type"))
|
||||||
function o.cfgvalue(self, section)
|
function o.cfgvalue(...)
|
||||||
local val = Value.cfgvalue(self, section) or "ss"
|
return Value.cfgvalue(...) 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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
o = sec:option(DummyValue, "server_port", translate("Server Port"))
|
o = sec:option(DummyValue, "server_port", translate("Server Port"))
|
||||||
@@ -140,4 +135,4 @@ function o.cfgvalue(...)
|
|||||||
return Value.cfgvalue(...) or "-"
|
return Value.cfgvalue(...) or "-"
|
||||||
end
|
end
|
||||||
|
|
||||||
return m
|
return m
|
||||||
@@ -1,35 +1,36 @@
|
|||||||
-- Licensed to the public under the GNU General Public License v3.
|
-- Licensed to the public under the GNU General Public License v3.
|
||||||
require "luci.http"
|
require "luci.http"
|
||||||
require "luci.sys"
|
require "luci.sys"
|
||||||
require "luci.util"
|
|
||||||
require "nixio.fs"
|
require "nixio.fs"
|
||||||
require "luci.dispatcher"
|
require "luci.dispatcher"
|
||||||
require "luci.model.uci"
|
require "luci.model.uci"
|
||||||
local nixio = require "nixio"
|
|
||||||
local json = require "luci.jsonc"
|
|
||||||
local cbi = require "luci.cbi"
|
local cbi = require "luci.cbi"
|
||||||
local uci = require "luci.model.uci".cursor()
|
local uci = require "luci.model.uci".cursor()
|
||||||
local URL = require "url"
|
|
||||||
|
|
||||||
local m, s, o, node
|
local m, s, o, node
|
||||||
local server_count = 0
|
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)
|
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
|
end
|
||||||
|
|
||||||
local function is_js_luci()
|
local function is_js_luci()
|
||||||
return luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0
|
return luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0
|
||||||
end
|
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 function apply_redirect(m)
|
||||||
local tmp_uci_file = "/etc/config/" .. "shadowsocksr" .. "_redirect"
|
local tmp_uci_file = "/etc/config/" .. "shadowsocksr" .. "_redirect"
|
||||||
if m.redirect and m.redirect ~= "" then
|
if m.redirect and m.redirect ~= "" then
|
||||||
@@ -64,341 +65,50 @@ local function set_apply_on_parse(map)
|
|||||||
local old = map.on_after_save
|
local old = map.on_after_save
|
||||||
map.on_after_save = function(self)
|
map.on_after_save = function(self)
|
||||||
if old then old(self) end
|
if old then old(self) end
|
||||||
-- map:set("@global[0]", "timestamp", os.time())
|
map:set("@global[0]", "timestamp", os.time())
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function trim(text)
|
local has_ss_rust = is_finded("sslocal") or is_finded("ssserver")
|
||||||
if not text or text == "" then
|
local has_ss_libev = is_finded("ss-redir") or is_finded("ss-local")
|
||||||
return ""
|
local has_trojan = is_finded("trojan")
|
||||||
end
|
local has_xray = is_finded("xray")
|
||||||
return (text:gsub("^%s*(.-)%s*$", "%1"))
|
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
|
end
|
||||||
|
|
||||||
local function parse_nonnegative_int(value)
|
if has_trojan then
|
||||||
local number = tonumber(value)
|
table.insert(tj_type_list, { id = "trojan", name = translate("Trojan") })
|
||||||
if not number then
|
end
|
||||||
return nil
|
if has_xray then
|
||||||
end
|
table.insert(tj_type_list, { id = "v2ray", name = translate("Xray (Trojan)") })
|
||||||
number = math.floor(number)
|
|
||||||
if number < 0 then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
return number
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function in_list(list, target)
|
if has_ss_rust then
|
||||||
for _, item in ipairs(list) do
|
table.insert(ss_type_list, { id = "ss-rust", name = translate("ShadowSocks-rust Version") })
|
||||||
if item == target then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
|
if has_ss_libev then
|
||||||
local function upload_alias(filename)
|
table.insert(ss_type_list, { id = "ss-libev", name = translate("ShadowSocks-libev Version") })
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
if has_xray then
|
||||||
local function hash_file(path)
|
table.insert(ss_type_list, { id = "v2ray", name = translate("Xray (ShadowSocks)") })
|
||||||
local cmd = "md5sum " .. luci.util.shellquote(path) .. " 2>/dev/null | awk '{print $1}'"
|
|
||||||
return trim(luci.sys.exec(cmd))
|
|
||||||
end
|
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)
|
uci:foreach("shadowsocksr", "servers", function(s)
|
||||||
server_count = server_count + 1
|
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)
|
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"))
|
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
|
-- Server Subscribe
|
||||||
s = m:section(TypedSection, "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.rmempty = false
|
||||||
o.description = translate("Auto Update Server subscription, GFW list and CHN route")
|
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 = s:option(ListValue, "auto_update_week_time", translate("Update cycle (Day/Week)"))
|
||||||
o:value('*', translate("Every Day"))
|
o:value('*', translate("Every Day"))
|
||||||
o:value("1", translate("Every Monday"))
|
o:value("1", translate("Every Monday"))
|
||||||
@@ -426,7 +129,7 @@ o:value("6", translate("Every Saturday"))
|
|||||||
o:value("0", translate("Every Sunday"))
|
o:value("0", translate("Every Sunday"))
|
||||||
o.default = "*"
|
o.default = "*"
|
||||||
o.rmempty = true
|
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)"))
|
o = s:option(ListValue, "auto_update_day_time", translate("Regular update (Hour)"))
|
||||||
for t = 0, 23 do
|
for t = 0, 23 do
|
||||||
@@ -434,7 +137,7 @@ for t = 0, 23 do
|
|||||||
end
|
end
|
||||||
o.default = 2
|
o.default = 2
|
||||||
o.rmempty = true
|
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)"))
|
o = s:option(ListValue, "auto_update_min_time", translate("Regular update (Min)"))
|
||||||
for i = 0, 59 do
|
for i = 0, 59 do
|
||||||
@@ -442,83 +145,178 @@ for i = 0, 59 do
|
|||||||
end
|
end
|
||||||
o.default = 30
|
o.default = 30
|
||||||
o.rmempty = true
|
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)"))
|
-- 确保 hy2_type_list 不为空
|
||||||
o.default = "60"
|
if #hy2_type_list > 0 then
|
||||||
o.datatype = "uinteger"
|
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.rmempty = true
|
||||||
o:depends({auto_update = "1", config_auto_update_mode = "1"})
|
|
||||||
|
|
||||||
o = s:option(Flag, "_subscribe_advanced_toggle", translate("Subscribe Advanced Settings"))
|
o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve"))
|
||||||
o.rmempty = false
|
o.description = translate(
|
||||||
o.default = "0"
|
"<ul>" ..
|
||||||
o.write = function() end
|
"<li>" .. translate("If the node address is a domain name, this DNS will be used for resolution.") .. "</li>" ..
|
||||||
o.remove = function() end
|
"<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 = s:option(Value, "filter_words", translate("Subscribe Filter Words"))
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
o.description = translate("Filter Words splited by /")
|
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 = s:option(Value, "save_words", translate("Subscribe Save Words"))
|
||||||
o.rmempty = true
|
o.rmempty = true
|
||||||
o.description = translate("Save Words splited by /")
|
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 = s:option(Flag, "allow_insecure", translate("Allow subscribe Insecure nodes By default"))
|
||||||
o.rmempty = false
|
o.rmempty = false
|
||||||
o.description = translate("Subscribe nodes allows insecure connection as TLS client (insecure)")
|
o.description = translate("Subscribe nodes allows insecure connection as TLS client (insecure)")
|
||||||
o.default = "0"
|
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 = s:option(Flag, "switch", translate("Subscribe Default Auto-Switch"))
|
||||||
o.rmempty = false
|
o.rmempty = false
|
||||||
o.description = translate("Subscribe new add server default Auto-Switch on")
|
o.description = translate("Subscribe new add server default Auto-Switch on")
|
||||||
o.default = "1"
|
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 = s:option(Flag, "proxy", translate("Through proxy update"))
|
||||||
o.rmempty = false
|
o.rmempty = false
|
||||||
o.description = translate("Through proxy update list, Not Recommended ")
|
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 = s:option(Button, "subscribe", translate("Update All Subscribe Servers"))
|
||||||
o.rawhtml = true
|
o.rawhtml = true
|
||||||
o.template = "shadowsocksr/subscribe"
|
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 = s:option(Button, "delete", translate("Delete All Subscribe Servers"))
|
||||||
o.inputstyle = "reset"
|
o.inputstyle = "reset"
|
||||||
o.description = string.format(translate("Server Count") .. ": %d", server_count)
|
o.description = string.format(translate("Server Count") .. ": %d", server_count)
|
||||||
o.write = function()
|
o.write = function()
|
||||||
uci:delete_all("shadowsocksr", "servers", function(s)
|
luci.http.redirect(url("delete"))
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
o = s:option(Value, "url_test_url", translate("URL Test Address"))
|
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 (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("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: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 ]]--
|
-- [[ Servers Manage ]]--
|
||||||
s = m:section(TypedSection, "servers")
|
s = m:section(TypedSection, "servers")
|
||||||
s.anonymous = true
|
s.anonymous = true
|
||||||
s.addremove = 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 = "cbi/tblsection"
|
||||||
s.template = "shadowsocksr/server_table"
|
|
||||||
set_apply_on_parse(m)
|
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
|
s.sortable = true
|
||||||
|
--[[
|
||||||
function s.cfgsections(self)
|
s.extedit = url("servers", "%s")
|
||||||
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")
|
|
||||||
|
|
||||||
function s.create(self, ...)
|
function s.create(self, ...)
|
||||||
local used_sid = {}
|
local sid = TypedSection.create(self, ...)
|
||||||
local next_sid = 1
|
if sid then
|
||||||
|
local newsid = "cfg" .. sid:sub(-6)
|
||||||
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)
|
|
||||||
self.map.uci:delete(self.config, sid)
|
self.map.uci:delete(self.config, sid)
|
||||||
local id = get_next_sid()
|
-- 重命名 section
|
||||||
local newsid = string.format("cfg%02x%s", id, suffix)
|
self.map.uci:section(self.config, self.sectiontype, newsid)
|
||||||
local success = self.map.uci:section(self.config, self.sectiontype, newsid)
|
luci.http.redirect(self.extedit % newsid)
|
||||||
if success then
|
return
|
||||||
--self.map.uci:save(self.config)
|
end
|
||||||
luci.http.redirect(self.extedit % newsid)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
]]--
|
||||||
|
|
||||||
o = s:option(DummyValue, "type", translate("Type"))
|
o = s:option(DummyValue, "type", translate("Type"))
|
||||||
function o.cfgvalue(self, section)
|
function o.cfgvalue(self, section)
|
||||||
local cfg = get_server(section)
|
return m:get(section, "v2ray_protocol") or Value.cfgvalue(self, section) or translate("None")
|
||||||
return cfg.v2ray_protocol or cfg.type or translate("None")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
o = s:option(DummyValue, "alias", translate("Alias"))
|
o = s:option(DummyValue, "alias", translate("Alias"))
|
||||||
function o.cfgvalue(self, section)
|
function o.cfgvalue(...)
|
||||||
return get_server(section).alias or translate("None")
|
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
|
end
|
||||||
|
|
||||||
o = s:option(DummyValue, "server_port", translate("Socket Connected"))
|
o = s:option(DummyValue, "server_port", translate("Socket Connected"))
|
||||||
o.template = "shadowsocksr/socket"
|
o.template = "shadowsocksr/socket"
|
||||||
o.width = "10%"
|
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)
|
o.render = function(self, section, scope)
|
||||||
local cfg = get_server(section)
|
local cfg = s:cfgvalue(section) or {}
|
||||||
local stype = cfg.type
|
self.transport = cfg.transport
|
||||||
self.type = stype or ""
|
self.type = cfg.type
|
||||||
self.proto = cfg.v2ray_protocol or ""
|
self.v2ray_protocol = cfg.v2ray_protocol
|
||||||
self.reality = cfg.reality or ""
|
if self.transport == 'ws' then
|
||||||
if stype == "clash" then
|
self.ws_path = cfg.ws_path
|
||||||
self.transport = ""
|
self.tls = cfg.tls
|
||||||
self.ws_path = ""
|
self.tls_host = cfg.tls_host
|
||||||
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
|
|
||||||
end
|
end
|
||||||
DummyValue.render(self, section, scope)
|
DummyValue.render(self, section, scope)
|
||||||
end
|
end
|
||||||
@@ -725,15 +393,6 @@ end
|
|||||||
o = s:option(DummyValue, "server", translate("Ping Latency"))
|
o = s:option(DummyValue, "server", translate("Ping Latency"))
|
||||||
o.template = "shadowsocksr/ping"
|
o.template = "shadowsocksr/ping"
|
||||||
o.width = "10%"
|
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')
|
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)
|
Button.render(self, section, scope)
|
||||||
end
|
end
|
||||||
node.write = function(self, section)
|
node.write = function(self, section)
|
||||||
local safe_section = luci.util.shellquote(section)
|
uci:set("shadowsocksr", '@global[0]', 'global_server', section)
|
||||||
local cmd = string.format("uci set shadowsocksr.@global[0].global_server=%s && uci commit shadowsocksr", safe_section)
|
uci:save("shadowsocksr")
|
||||||
luci.sys.call(cmd)
|
uci:commit("shadowsocksr")
|
||||||
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "restart"))
|
luci.sys.call("/etc/init.d/shadowsocksr restart >/dev/null 2>&1 &")
|
||||||
|
luci.http.redirect(url("restart"))
|
||||||
end
|
end
|
||||||
|
|
||||||
o = s:option(Flag, "switch_enable", translate("Auto Switch"))
|
o = s:option(Flag, "switch_enable", translate("Auto Switch"))
|
||||||
@@ -762,15 +422,4 @@ end
|
|||||||
|
|
||||||
m:append(cbi.Template("shadowsocksr/server_list"))
|
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
|
return m
|
||||||
@@ -7,22 +7,15 @@ local m, s, o
|
|||||||
local redir_run = 0
|
local redir_run = 0
|
||||||
local reudp_run = 0
|
local reudp_run = 0
|
||||||
local sock5_run = 0
|
local sock5_run = 0
|
||||||
local http_run = 0
|
|
||||||
local server_run = 0
|
local server_run = 0
|
||||||
local kcptun_run = 0
|
local kcptun_run = 0
|
||||||
local tunnel_run = 0
|
local tunnel_run = 0
|
||||||
local gfw_count = 0
|
local gfw_count = 0
|
||||||
local ad_count = 0
|
local ad_count = 0
|
||||||
local ip_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 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
|
-- html constants
|
||||||
font_blue = [[<b style=color:green>]]
|
font_blue = [[<b style=color:green>]]
|
||||||
style_blue = [[<b style=color:red>]]
|
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"))
|
apple_count = tonumber(luci.sys.exec("cat /etc/ssrplus/applechina.conf | wc -l"))
|
||||||
end
|
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
|
if Process_list:find("udp.only.ssr.reudp") then
|
||||||
reudp_run = 1
|
reudp_run = 1
|
||||||
end
|
end
|
||||||
@@ -77,10 +74,6 @@ if Process_list:find("tcp.udp.ssr.local") then
|
|||||||
sock5_run = 1
|
sock5_run = 1
|
||||||
end
|
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
|
if Process_list:find("tcp.udp.ssr.retcp") then
|
||||||
redir_run = 1
|
redir_run = 1
|
||||||
reudp_run = 1
|
reudp_run = 1
|
||||||
@@ -110,38 +103,6 @@ if Process_list:find("local.udp.ssr.retcp") then
|
|||||||
sock5_run = 1
|
sock5_run = 1
|
||||||
end
|
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
|
if Process_list:find("kcptun.client") then
|
||||||
kcptun_run = 1
|
kcptun_run = 1
|
||||||
end
|
end
|
||||||
@@ -150,21 +111,11 @@ if Process_list:find("ssr.server") then
|
|||||||
server_run = 1
|
server_run = 1
|
||||||
end
|
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
|
if Process_list:find("ssrplus/bin/dns2tcp") or
|
||||||
Process_list:find("ssrplus/bin/mosdns") or
|
Process_list:find("ssrplus/bin/mosdns") or
|
||||||
Process_list:find("chinadns.*127.0.0.1.*5335") then
|
Process_list:find("dnsproxy.*127.0.0.1.*5335") or
|
||||||
pdnsd_run = 1
|
Process_list:find("chinadns.*127.0.0.1.*5335") or
|
||||||
end
|
(Process_list:find("ssrplus.dns") and Process_list:find("dns2socks.*127.0.0.1.*127.0.0.1.5335")) then
|
||||||
|
|
||||||
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
|
|
||||||
pdnsd_run = 1
|
pdnsd_run = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -206,16 +157,6 @@ else
|
|||||||
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
|
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
|
||||||
end
|
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 = m:field(DummyValue, "server_run", translate("Local Servers"))
|
||||||
s.rawhtml = true
|
s.rawhtml = true
|
||||||
if server_run == 1 then
|
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")
|
s.value = apple_count .. " " .. translate("Records")
|
||||||
end
|
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
|
if uci:get_first("shadowsocksr", 'global', 'adblock', '0') == '1' then
|
||||||
s = m:field(DummyValue, "ad_data", translate("Advertising Data"))
|
s = m:field(DummyValue, "ad_data", translate("Advertising Data"))
|
||||||
s.rawhtml = true
|
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%>
|
<%+cbi/valueheader%>
|
||||||
<%
|
<span class="pingtime" hint="<%=self:cfgvalue(section)%>">-- ms</span>
|
||||||
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>
|
|
||||||
<%+cbi/valuefooter%>
|
<%+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%>
|
<%+cbi/valueheader%>
|
||||||
<%
|
<span class="socket-connected" hint="<%=self:cfgvalue(section)%>">wait</span>
|
||||||
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="transport" hint="<%=self.transport%>"></span>
|
<span class="transport" hint="<%=self.transport%>"></span>
|
||||||
<span class="wsPath" hint="<%=self.ws_path%>"></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="tls" hint="<%=self.tls%>"></span>
|
||||||
<span class="type" hint="<%=self.type%>"></span>
|
<span class="tlshost" hint="<%=self.tls_host%>"></span>
|
||||||
<span class="proto" hint="<%=self.proto%>"></span>
|
<span class="hy2type" hint="<%=self.type%>"></span>
|
||||||
<span class="reality" hint="<%=self.reality%>"></span>
|
<span class="hy2proto" hint="<%=self.v2ray_protocol%>"></span>
|
||||||
<%+cbi/valuefooter%>
|
<%+cbi/valuefooter%>
|
||||||
@@ -1,15 +1,25 @@
|
|||||||
<%+cbi/valueheader%>
|
<%+cbi/valueheader%>
|
||||||
<%
|
<%
|
||||||
local map = self.map
|
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_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') ~= ""
|
local has_xray = luci.sys.exec('type -t -p xray 2>/dev/null') ~= ""
|
||||||
-%>
|
-%>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
//<![CDATA[
|
//<![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_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%>"
|
let has_xray = "<%=has_xray%>"
|
||||||
|
|
||||||
function padright(str, cnt, pad) {
|
function padright(str, cnt, pad) {
|
||||||
@@ -32,13 +42,6 @@ function b64DecodeUnicode(str) {
|
|||||||
}).join(''));
|
}).join(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeHost(host) {
|
|
||||||
if (host && host[0] === "[" && host[host.length - 1] === "]") {
|
|
||||||
return host.slice(1, -1);
|
|
||||||
}
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
function b64decutf8safe(str) {
|
function b64decutf8safe(str) {
|
||||||
var l;
|
var l;
|
||||||
str = str.replace(/-/g, "+").replace(/_/g, "/");
|
str = str.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
@@ -122,9 +125,38 @@ function import_ssr_url(btn, urlname, sid) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Xray Hysteria2 导入函数
|
// 普通 Hysteria2 导入函数
|
||||||
function importAsXrayHy2() {
|
function importAsNormalHy2() {
|
||||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray"
|
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 + '.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].value = (ssu[0] === "hy2") ? "hysteria2" : ssu[0];
|
||||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].dispatchEvent(event);
|
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>";
|
function hasCore(core) {
|
||||||
return false;
|
if (core === "hysteria") return has_hysteria === "true";
|
||||||
}
|
if (core === "xray") return has_xray === "true";
|
||||||
importAsXrayHy2();
|
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 + '.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 + '.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].value = decodeURIComponent(url.username);
|
||||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.hy2_auth')[0].dispatchEvent(event);
|
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>";
|
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
|
||||||
return false;
|
return false;
|
||||||
case "ss":
|
case "ss":
|
||||||
var url0 = (ssu[1] || "");
|
var url0 = (ssu[1] || "");
|
||||||
var param = "";
|
var param = "";
|
||||||
|
|
||||||
// 先分离 #(alias)
|
// 先分离 #(alias)
|
||||||
var hashIndex = url0.indexOf("#");
|
var hashIndex = url0.indexOf("#");
|
||||||
@@ -213,346 +267,374 @@ function import_ssr_url(btn, urlname, sid) {
|
|||||||
url0 = url0.substring(0, queryIndex);
|
url0 = url0.substring(0, queryIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
var params = Object.fromEntries(new URLSearchParams(query));
|
var params = Object.fromEntries(new URLSearchParams(query));
|
||||||
|
|
||||||
function decodeURIComponentSafe(value) {
|
function importAsNormalSS() {
|
||||||
try {
|
// 普通 SS 导入逻辑
|
||||||
return decodeURIComponent(value);
|
// 判断是否 SIP002 格式(即含 @)
|
||||||
} catch (e) {
|
if (url0.indexOf("@") !== -1) {
|
||||||
return value;
|
// === 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;
|
||||||
}
|
}
|
||||||
}
|
// 如果没有冒号,且不是 base64 格式,我们可以尝试补齐它
|
||||||
|
if (userInfo.indexOf(":") === -1) {
|
||||||
function decodeBase64Safe(value) {
|
userInfo = "none:" + userInfo;
|
||||||
try {
|
|
||||||
return b64decsafe(value);
|
|
||||||
} catch (e) {
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
}
|
// console.log("userInfo after decode:", userInfo);
|
||||||
|
var userInfoSplitIndex = userInfo.indexOf(":");
|
||||||
function parseSSHostPort(serverPart) {
|
if(userInfoSplitIndex < 0) {
|
||||||
serverPart = (serverPart || "").trim();
|
// 格式错误
|
||||||
var ipv6Match = serverPart.match(/^\[([^\]]+)\]:(\d+)$/);
|
s.innerHTML = "<font style='color:red'><%:Userinfo format error.%></font>";
|
||||||
if (ipv6Match) {
|
return false;
|
||||||
return { server: ipv6Match[1], port: ipv6Match[2] };
|
|
||||||
}
|
}
|
||||||
|
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(":");
|
var server = serverInfo[0];
|
||||||
if (colonIndex > 0 && serverPart.indexOf(":") === colonIndex) {
|
var port = serverInfo[1];
|
||||||
return {
|
|
||||||
server: serverPart.substring(0, colonIndex),
|
|
||||||
port: serverPart.substring(colonIndex + 1)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseSSPluginInfo() {
|
|
||||||
var plugin = "";
|
|
||||||
var pluginOpts = "";
|
|
||||||
|
|
||||||
|
var plugin = "", pluginOpts = "";
|
||||||
if (params.plugin) {
|
if (params.plugin) {
|
||||||
var pluginInfo = decodeURIComponentSafe(params.plugin);
|
var pluginParams = decodeURIComponent(params.plugin).split(";");
|
||||||
var pluginParts = pluginInfo.split(";");
|
plugin = pluginParams.shift();
|
||||||
plugin = pluginParts.shift() || "";
|
pluginOpts = pluginParams.join(";");
|
||||||
pluginOpts = pluginParts.join(";");
|
}
|
||||||
if (plugin === "simple-obfs") {
|
} else {
|
||||||
plugin = "obfs-local";
|
// === Base64 SS2022 / 普通格式 的整体编码格式 ===
|
||||||
}
|
// 先 URL 解码整个字符串
|
||||||
} else if (params["shadow-tls"]) {
|
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 {
|
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";
|
plugin = "shadow-tls";
|
||||||
var versionFlag = "";
|
var versionFlag = "";
|
||||||
if (decodedTls.version && !isNaN(decodedTls.version)) {
|
if (decoded_tls.version && !isNaN(decoded_tls.version)) {
|
||||||
versionFlag = "v" + decodedTls.version + "=1;";
|
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) {
|
} catch (e) {
|
||||||
console.log("shadow-tls decode failed:", e);
|
console.log("shadow-tls decode failed:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
plugin: plugin,
|
|
||||||
pluginOpts: pluginOpts
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSSNode() {
|
// 判断密码是否经过url编码
|
||||||
var userInfo = "";
|
const isURLEncodedPassword = function(pwd) {
|
||||||
var serverPart = "";
|
if (!/%[0-9A-Fa-f]{2}/.test(pwd)) return false;
|
||||||
var atIndex = url0.lastIndexOf("@");
|
try {
|
||||||
|
const decoded = decodeURIComponent(pwd.replace(/\+/g, "%20"));
|
||||||
if (atIndex !== -1) {
|
const reencoded = encodeURIComponent(decoded);
|
||||||
var rawUserInfo = decodeURIComponentSafe(url0.substring(0, atIndex));
|
return reencoded === pwd;
|
||||||
var decodedUserInfo = decodeBase64Safe(rawUserInfo);
|
} catch (e) {
|
||||||
userInfo = decodedUserInfo && decodedUserInfo.indexOf(":") !== -1 ? decodedUserInfo : rawUserInfo;
|
return false;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) : ""
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
if (isURLEncodedPassword(password)) {
|
||||||
function importAsNormalSS(ssNode) {
|
password = decodeURIComponent(password); // 解码URL编码
|
||||||
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";
|
|
||||||
} else {
|
} 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;
|
return false;
|
||||||
}
|
}
|
||||||
if (finalSSBackend === "v2ray" && ssNode.plugin && ssNode.plugin !== "none") {
|
// Check if the elements exist before trying to modify them
|
||||||
s.innerHTML = "<font style='color:red'>Xray fallback does not support importing Shadowsocks links with SIP003 plugins.</font>";
|
function setElementValue(name, value) {
|
||||||
return false;
|
const element = document.getElementsByName(name)[0];
|
||||||
|
if (element) {
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
element.checked = value;
|
||||||
|
} else {
|
||||||
|
element.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (finalSSBackend === "v2ray") importAsXraySS(ssNode);
|
function dispatchEventIfExists(name, event) {
|
||||||
else importAsNormalSS(ssNode);
|
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;
|
return false;
|
||||||
|
}
|
||||||
|
if (finalSSType === true) importAsXraySS();
|
||||||
|
else importAsNormalSS();
|
||||||
|
return false;
|
||||||
case "ssr":
|
case "ssr":
|
||||||
var sstr = b64decsafe((ssu[1] || "").replace(/#.*/, "").trim());
|
var sstr = b64decsafe((ssu[1] || "").replace(/#.*/, "").trim());
|
||||||
var ploc = sstr.indexOf("/?");
|
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 + '.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].value = "trojan";
|
||||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
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 + '.server_port')[0].value = url.port || "80";
|
||||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = decodeURIComponent(url.username);
|
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = decodeURIComponent(url.username);
|
||||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
|
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 + '.type')[0].dispatchEvent(event);
|
||||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].value = "trojan";
|
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 + '.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 + '.server_port')[0].value = url.port || "80";
|
||||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = decodeURIComponent(url.username);
|
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = decodeURIComponent(url.username);
|
||||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
|
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
|
||||||
@@ -732,13 +814,36 @@ function import_ssr_url(btn, urlname, sid) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主逻辑:Trojan 统一使用 Xray
|
// 主逻辑:智能核心选择(支持回退)
|
||||||
|
function hasTrojanCore() { return has_trojan === "true"; }
|
||||||
function hasXrayCore() { return has_xray === "true"; }
|
function hasXrayCore() { return has_xray === "true"; }
|
||||||
if (!hasXrayCore()) {
|
let finalTrojanMode = null;
|
||||||
s.innerHTML = "<font style=\'color:red\'><%:No available core (Xray) to import this node.%></font>";
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
importAsXrayTrojan();
|
if (finalTrojanMode === true) importAsXrayTrojan();
|
||||||
|
else importAsNormalTrojan();
|
||||||
return false;
|
return false;
|
||||||
case "vmess":
|
case "vmess":
|
||||||
var sstr = b64DecodeUnicode((ssu[1] || "").replace(/#.*/, "").trim());
|
var sstr = b64DecodeUnicode((ssu[1] || "").replace(/#.*/, "").trim());
|
||||||
@@ -871,7 +976,7 @@ function import_ssr_url(btn, urlname, sid) {
|
|||||||
element.dispatchEvent(event);
|
element.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var server = normalizeHost(url.hostname);
|
var server = url.hostname;
|
||||||
/* 如果带 [],再去掉 [] */
|
/* 如果带 [],再去掉 [] */
|
||||||
if (server[0] === "[" && server[server.length - 1] === "]") {
|
if (server[0] === "[" && server[server.length - 1] === "]") {
|
||||||
server = server.slice(1, -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].value = ssu[0];
|
||||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
|
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 + '.server_port')[0].value = url.port;
|
||||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_uuid')[0].value = method;
|
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_uuid')[0].value = method;
|
||||||
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_ip')[0].value = params.ip || "";
|
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');
|
var tb = document.getElementById('shadowsocksr_status');
|
||||||
if (data && tb) {
|
if (data && tb) {
|
||||||
if (data.running) {
|
if (data.running) {
|
||||||
var text = data.status_text || '<%:RUNNING%>';
|
var links = '<em><b style=color:green>ShadowsocksR Plus+ <%:RUNNING%></b></em>';
|
||||||
var links = '<em><b style=color:green>ShadowsocksR Plus+ ' + text + '</b></em>';
|
|
||||||
tb.innerHTML = links;
|
tb.innerHTML = links;
|
||||||
} else {
|
} else {
|
||||||
tb.innerHTML = '<em><b style=color:red>ShadowsocksR Plus+ <%:NOT RUNNING%></b></em>';
|
tb.innerHTML = '<em><b style=color:red>ShadowsocksR Plus+ <%:NOT RUNNING%></b></em>';
|
||||||
|
|||||||
@@ -1,257 +1,18 @@
|
|||||||
<%+cbi/valueheader%>
|
<%+cbi/valueheader%>
|
||||||
<%
|
<script type="text/javascript">//<![CDATA[
|
||||||
local dsp = require "luci.dispatcher"
|
function subscribe(btn,dataname) {
|
||||||
local subscribe_url = dsp.build_url("admin", "services", "shadowsocksr", "subscribe")
|
btn.disabled = true;
|
||||||
local get_log_url = dsp.build_url("admin", "services", "shadowsocksr", "get_log")
|
btn.value = '<%:Refresh...%> ';
|
||||||
local servers_url = dsp.build_url("admin", "services", "shadowsocksr", "servers")
|
murl=dataname;
|
||||||
local autostart = self.map and self.map.ssr_subscribe_autostart
|
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr","subscribe")%>', { set:murl }, function(x,rv) {
|
||||||
-%>
|
// 先简单刷新,后期如果重构会考虑下如何组织lua shell JavaScript之间的代码逻辑和各自的调用逻辑
|
||||||
<div id="ssr-subscribe-modal-wrap">
|
window.location.reload()
|
||||||
<style type="text/css">
|
// btn.disabled = false;
|
||||||
#ssr-subscribe-modal-mask {
|
// btn.value = '<%:Refresh Data %>';
|
||||||
display: none;
|
});
|
||||||
position: fixed;
|
return false;
|
||||||
inset: 0;
|
}
|
||||||
background: rgba(0, 0, 0, 0.35);
|
//]]></script>
|
||||||
z-index: 2000;
|
<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> -->
|
||||||
|
|
||||||
#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>
|
|
||||||
<%+cbi/valuefooter%>
|
<%+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/deny.list
|
||||||
touch /etc/ssrplus/white.list
|
touch /etc/ssrplus/white.list
|
||||||
touch /etc/ssrplus/black.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_base.conf
|
||||||
touch /etc/ssrplus/gfw_list.conf
|
touch /etc/ssrplus/gfw_list.conf
|
||||||
|
touch /etc/ssrplus/oversea_list.conf
|
||||||
touch /etc/ssrplus/ad.conf
|
touch /etc/ssrplus/ad.conf
|
||||||
touch /etc/config/shadowsocksr
|
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'
|
uci -q set shadowsocksr.@server_subscribe[0].auto_update_min_time='0'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! uci -q get shadowsocksr.@server_subscribe[0].config_auto_update_mode > /dev/null; then
|
if ! uci -q get shadowsocksr.@server_subscribe[0].ss_type > /dev/null; then
|
||||||
uci -q set shadowsocksr.@server_subscribe[0].config_auto_update_mode='0'
|
uci -q set shadowsocksr.@server_subscribe[0].ss_type='ss-rust'
|
||||||
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'
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! uci -q get shadowsocksr.@server_subscribe[0].user_agent > /dev/null; then
|
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'
|
uci -q set shadowsocksr.@server_subscribe[0].user_agent='v2rayN/9.99'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! uci -q get shadowsocksr.@server_subscribe[0].proxy > /dev/null; then
|
if ! uci -q get shadowsocksr.@server_subscribe[0].xray_hy2_type > /dev/null; then
|
||||||
uci -q set shadowsocksr.@server_subscribe[0].proxy='1'
|
uci -q set shadowsocksr.@server_subscribe[0].xray_hy2_type='hysteria2'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! uci -q get shadowsocksr.@global_xray_fragment[0] > /dev/null; then
|
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'
|
uci -q set shadowsocksr.@global_xray_fragment[0].noise='0'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! uci -q get shadowsocksr.@global[0].component_mirror > /dev/null; then
|
uci -q commit shadowsocksr
|
||||||
uci -q set shadowsocksr.@global[0].component_mirror='direct'
|
fi
|
||||||
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
|
|
||||||
|
|
||||||
[ -s "/etc/config/shadowsocksr" ] || /etc/init.d/shadowsocksr reset
|
[ -s "/etc/config/shadowsocksr" ] || /etc/init.d/shadowsocksr reset
|
||||||
|
|
||||||
|
|||||||
@@ -10,58 +10,6 @@
|
|||||||
LOCK_FILE="/var/lock/ssr-monitor.lock"
|
LOCK_FILE="/var/lock/ssr-monitor.lock"
|
||||||
[ -f "$LOCK_FILE" ] && exit 2
|
[ -f "$LOCK_FILE" ] && exit 2
|
||||||
touch "$LOCK_FILE"
|
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
|
server_process_count=$1
|
||||||
redir_tcp_process=$2
|
redir_tcp_process=$2
|
||||||
redir_udp_process=$3
|
redir_udp_process=$3
|
||||||
@@ -73,8 +21,6 @@ if [ -z "$pdnsd_process" ]; then
|
|||||||
fi
|
fi
|
||||||
i=0
|
i=0
|
||||||
GLOBAL_SERVER=$(uci_get_by_type global global_server)
|
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)
|
server=$(uci_get_by_name $GLOBAL_SERVER server)
|
||||||
kcp_port=$(uci_get_by_name $GLOBAL_SERVER kcp_port)
|
kcp_port=$(uci_get_by_name $GLOBAL_SERVER kcp_port)
|
||||||
server_port=$(uci_get_by_name $GLOBAL_SERVER server_port)
|
server_port=$(uci_get_by_name $GLOBAL_SERVER server_port)
|
||||||
@@ -86,12 +32,8 @@ while [ "1" == "1" ]; do #死循环
|
|||||||
sleep 000030s
|
sleep 000030s
|
||||||
#redir tcp
|
#redir tcp
|
||||||
if [ "$redir_tcp_process" -gt 0 ]; then
|
if [ "$redir_tcp_process" -gt 0 ]; then
|
||||||
icount=$(redir_tcp_running)
|
icount=$(busybox ps -w | grep ssr-retcp | grep -v grep | wc -l)
|
||||||
local tcp_expected="$redir_tcp_process"
|
if [ "$icount" == 0 ]; then
|
||||||
if [ "$GLOBAL_TYPE" = "socks5" ]; then
|
|
||||||
tcp_expected="$EXPECTED_THREADS"
|
|
||||||
fi
|
|
||||||
if [ "$icount" -lt "$tcp_expected" ]; then
|
|
||||||
logger -t "$NAME" "ssrplus redir tcp error.restart!"
|
logger -t "$NAME" "ssrplus redir tcp error.restart!"
|
||||||
echolog "ssrplus redir tcp error.restart!"
|
echolog "ssrplus redir tcp error.restart!"
|
||||||
/etc/init.d/shadowsocksr restart
|
/etc/init.d/shadowsocksr restart
|
||||||
@@ -100,12 +42,8 @@ while [ "1" == "1" ]; do #死循环
|
|||||||
fi
|
fi
|
||||||
#redir udp
|
#redir udp
|
||||||
if [ "$redir_udp_process" -gt 0 ]; then
|
if [ "$redir_udp_process" -gt 0 ]; then
|
||||||
icount=$(redir_udp_running)
|
icount=$(busybox ps -w | grep ssr-reudp | grep -v grep | wc -l)
|
||||||
local udp_expected="$redir_udp_process"
|
if [ "$icount" == 0 ]; then
|
||||||
if [ "$GLOBAL_TYPE" = "socks5" ]; then
|
|
||||||
udp_expected="$EXPECTED_THREADS"
|
|
||||||
fi
|
|
||||||
if [ "$icount" -lt "$udp_expected" ]; then
|
|
||||||
logger -t "$NAME" "ssrplus redir udp error.restart!"
|
logger -t "$NAME" "ssrplus redir udp error.restart!"
|
||||||
echolog "ssrplus redir udp error.restart!"
|
echolog "ssrplus redir udp error.restart!"
|
||||||
/etc/init.d/shadowsocksr restart
|
/etc/init.d/shadowsocksr restart
|
||||||
@@ -114,18 +52,18 @@ while [ "1" == "1" ]; do #死循环
|
|||||||
fi
|
fi
|
||||||
#server
|
#server
|
||||||
if [ "$server_process_count" -gt 0 ]; then
|
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 #如果进程挂掉就重启它
|
if [ "$icount" -lt "$server_process_count" ]; then #如果进程挂掉就重启它
|
||||||
logger -t "$NAME" "ssrplus server error.restart!"
|
logger -t "$NAME" "ssrplus server error.restart!"
|
||||||
echolog "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
|
/etc/init.d/shadowsocksr restart
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
#kcptun
|
#kcptun
|
||||||
if [ "$kcp_process" -gt 0 ]; then
|
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 #如果进程挂掉就重启它
|
if [ "$icount" -lt "$kcp_process" ]; then #如果进程挂掉就重启它
|
||||||
logger -t "$NAME" "ssrplus kcptun error.restart!"
|
logger -t "$NAME" "ssrplus kcptun error.restart!"
|
||||||
echolog "ssrplus kcptun error.restart!"
|
echolog "ssrplus kcptun error.restart!"
|
||||||
@@ -135,43 +73,132 @@ while [ "1" == "1" ]; do #死循环
|
|||||||
fi
|
fi
|
||||||
#localsocks
|
#localsocks
|
||||||
if [ "$local_process" -gt 0 ]; then
|
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 #如果进程挂掉就重启它
|
if [ "$icount" -lt "$local_process" ]; then #如果进程挂掉就重启它
|
||||||
logger -t "$NAME" "global socks server error.restart!"
|
logger -t "$NAME" "global socks server error.restart!"
|
||||||
echolog "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
|
/etc/init.d/shadowsocksr restart
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
#dns2tcp
|
#dns2tcp
|
||||||
if [ "$pdnsd_process" -eq 1 ]; then
|
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 #如果进程挂掉就重启它
|
if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它
|
||||||
logger -t "$NAME" "dns2tcp tunnel error.restart!"
|
logger -t "$NAME" "dns2tcp tunnel error.restart!"
|
||||||
echolog "dns2tcp tunnel error.restart!"
|
echolog "dns2tcp tunnel error.restart!"
|
||||||
dnsserver=$(uci_get_by_type global tunnel_forward 8.8.4.4:53)
|
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
|
kill -9 $(busybox ps -w | grep $TMP_BIN_PATH/dns2tcp | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||||
start_dns2tcp "$dnsserver"
|
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
|
fi
|
||||||
#mosdns
|
#mosdns
|
||||||
elif [ "$pdnsd_process" -eq 4 ]; then
|
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 #如果进程挂掉就重启它
|
if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它
|
||||||
logger -t "$NAME" "mosdns tunnel error.restart!"
|
logger -t "$NAME" "mosdns tunnel error.restart!"
|
||||||
echolog "mosdns tunnel error.restart!"
|
echolog "mosdns tunnel error.restart!"
|
||||||
dnsserver=$(uci_get_by_type global tunnel_forward 8.8.4.4:53)
|
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
|
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
|
fi
|
||||||
#chinadns-ng(proxy)
|
#chinadns-ng(proxy)
|
||||||
elif [ "$pdnsd_process" -eq 6 ]; then
|
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 #如果进程挂掉就重启它
|
if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它
|
||||||
logger -t "$NAME" "chinadns-ng $dnsserver tunnel error.restart!"
|
logger -t "$NAME" "chinadns-ng $dnsserver tunnel error.restart!"
|
||||||
echolog "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)
|
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_proto="$(uci_get_by_type global chinadns_ng_proto)"
|
||||||
local chinadns_ng_dns=""
|
local chinadns_ng_dns=""
|
||||||
IFS=','
|
IFS=','
|
||||||
@@ -197,18 +224,10 @@ while [ "1" == "1" ]; do #死循环
|
|||||||
dnsserver="$chinadns_ng_dns"
|
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
|
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
|
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
|
fi
|
||||||
#chinadns-ng(china)
|
#chinadns-ng(china)
|
||||||
if [ "$(uci -q get "dhcp.@dnsmasq[0]._unused_ssrp_changed")" = "1" ]; then
|
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 #如果进程挂掉就重启它
|
if [ "$icount" -lt 1 ]; then #如果进程挂掉就重启它
|
||||||
logger -t "$NAME" "chinadns-ng tunnel error.restart!"
|
logger -t "$NAME" "chinadns-ng tunnel error.restart!"
|
||||||
echolog "chinadns-ng tunnel error.restart!"
|
echolog "chinadns-ng tunnel error.restart!"
|
||||||
@@ -218,7 +237,7 @@ while [ "1" == "1" ]; do #死循环
|
|||||||
"wan") chinadns="$wandns" ;;
|
"wan") chinadns="$wandns" ;;
|
||||||
""|"wan_114") chinadns="$wandns,114.114.114.114" ;;
|
""|"wan_114") chinadns="$wandns,114.114.114.114" ;;
|
||||||
esac
|
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
|
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
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -85,14 +85,13 @@ usage() {
|
|||||||
-e <extra_options> extra options for iptables
|
-e <extra_options> extra options for iptables
|
||||||
-o apply the rules to the OUTPUT chain
|
-o apply the rules to the OUTPUT chain
|
||||||
-O apply the global 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, TPROXY is required
|
||||||
-U enable udprelay mode, using different IP
|
-U enable udprelay mode, using different IP
|
||||||
and ports for TCP and UDP
|
and ports for TCP and UDP
|
||||||
-y disable all auxiliary UDP rules when UDP
|
-f flush the rules
|
||||||
transparent proxy is unavailable
|
|
||||||
-f flush the rules
|
|
||||||
-g gfwlist mode
|
-g gfwlist mode
|
||||||
-r router mode
|
-r router mode
|
||||||
|
-c oversea mode
|
||||||
-z all mode
|
-z all mode
|
||||||
|
|
||||||
# New persistence management options (use different letters to avoid conflicts)
|
# 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)
|
# 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 \
|
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 inet ss_spec $setname 2>/dev/null
|
||||||
$NFT delete set ip ss_spec_mangle $setname 2>/dev/null
|
$NFT delete set ip ss_spec_mangle $setname 2>/dev/null
|
||||||
done
|
done
|
||||||
@@ -311,7 +310,7 @@ flush_iptables_legacy() {
|
|||||||
ip route del local 0.0.0.0/0 dev lo table 999 2>/dev/null
|
ip route del local 0.0.0.0/0 dev lo table 999 2>/dev/null
|
||||||
fi
|
fi
|
||||||
for setname in ss_spec_lan_ac ss_spec_wan_ac ssr_gen_router \
|
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
|
ipset -X $setname 2>/dev/null
|
||||||
done
|
done
|
||||||
[ -n "$FWI" ] && echo '#!/bin/sh' >$FWI
|
[ -n "$FWI" ] && echo '#!/bin/sh' >$FWI
|
||||||
@@ -334,7 +333,7 @@ ipset_nft() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Create necessary collections
|
# 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
|
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
|
$NFT add set inet ss_spec $setname '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
|
||||||
else
|
else
|
||||||
@@ -393,6 +392,13 @@ ipset_nft() {
|
|||||||
$NFT add rule inet ss_spec ss_spec_wan_ac meta l4proto tcp ip daddr @music return
|
$NFT add rule inet ss_spec ss_spec_wan_ac meta l4proto tcp ip daddr @music return
|
||||||
fi
|
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
|
# Set up mode-specific rules
|
||||||
case "$RUNMODE" in
|
case "$RUNMODE" in
|
||||||
router)
|
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 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
|
$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)
|
all)
|
||||||
if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then
|
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
|
$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 -N SS_SPEC_WAN_AC 2>/dev/null
|
||||||
$IPT -F SS_SPEC_WAN_AC
|
$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
|
$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
|
ipset -N gmlan hash:net 2>/dev/null
|
||||||
for ip in $LAN_GM_IP; do ipset -! add gmlan "$ip"; done
|
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 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
|
$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)
|
all)
|
||||||
$IPT -A SS_SPEC_WAN_AC -j SS_SPEC_WAN_FW
|
$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_BP_IP; do ipset -! add whitelist "$ip"; done
|
||||||
for ip in $WAN_FW_IP; do ipset -! add blacklist "$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 $?
|
return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,6 +562,22 @@ fw_rule_nft() {
|
|||||||
fi
|
fi
|
||||||
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 $?
|
return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,7 +670,7 @@ ac_rule_nft() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Block UDP port 443 when TPROXY not Enable
|
# 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
|
# Add UDP 443 block rule
|
||||||
if [ -z "$Interface" ]; then
|
if [ -z "$Interface" ]; then
|
||||||
if [ -n "$MATCH_SET" ]; then
|
if [ -n "$MATCH_SET" ]; then
|
||||||
@@ -663,11 +717,10 @@ ac_rule_nft() {
|
|||||||
case "$OUTPUT" in
|
case "$OUTPUT" in
|
||||||
1)
|
1)
|
||||||
# Create ss_spec_output tcp chain
|
# Create ss_spec_output tcp chain
|
||||||
if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
|
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; }'
|
$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }'
|
||||||
fi
|
fi
|
||||||
$NFT flush chain inet ss_spec ss_spec_output 2>/dev/null
|
$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
|
|
||||||
|
|
||||||
# Exclude special local addresses
|
# Exclude special local addresses
|
||||||
if $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
|
if $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
|
||||||
@@ -688,11 +741,10 @@ ac_rule_nft() {
|
|||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
# Create ss_spec_output tcp chain
|
# Create ss_spec_output tcp chain
|
||||||
if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
|
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; }'
|
$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }'
|
||||||
fi
|
fi
|
||||||
$NFT flush chain inet ss_spec ss_spec_output 2>/dev/null
|
$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
|
|
||||||
|
|
||||||
# Exclude special local addresses
|
# Exclude special local addresses
|
||||||
if $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
|
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
|
for ip in $(gen_spec_iplist); do
|
||||||
[ -n "$ip" ] && $NFT add element inet ss_spec ssr_gen_router "{ $ip }" 2>/dev/null
|
[ -n "$ip" ] && $NFT add element inet ss_spec ssr_gen_router "{ $ip }" 2>/dev/null
|
||||||
done
|
done
|
||||||
if ! $NFT list chain inet ss_spec ss_spec_router >/dev/null 2>&1; then
|
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
|
$NFT add chain inet ss_spec ss_spec_router 2>/dev/null
|
||||||
fi
|
fi
|
||||||
$NFT flush chain inet ss_spec ss_spec_router 2>/dev/null
|
$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
|
||||||
$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_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
|
$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
|
EOF
|
||||||
|
|
||||||
# Block UDP port 443 when TPROXY not Enable
|
# 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
|
# Add UDP 443 block rule
|
||||||
if [ -z "$Interface" ]; then
|
if [ -z "$Interface" ]; then
|
||||||
# Global rules
|
# Global rules
|
||||||
@@ -794,11 +845,10 @@ ac_rule_iptables() {
|
|||||||
create ssr_gen_router hash:net
|
create ssr_gen_router hash:net
|
||||||
$(gen_spec_iplist | sed -e "s/^/add ssr_gen_router /")
|
$(gen_spec_iplist | sed -e "s/^/add ssr_gen_router /")
|
||||||
EOF
|
EOF
|
||||||
$IPT -N SS_SPEC_ROUTER 2>/dev/null
|
$IPT -N SS_SPEC_ROUTER 2>/dev/null
|
||||||
$IPT -F 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 -m set --match-set ssr_gen_router dst -j RETURN && \
|
$IPT -A SS_SPEC_ROUTER -j SS_SPEC_WAN_FW
|
||||||
$IPT -A SS_SPEC_ROUTER -j SS_SPEC_WAN_FW
|
|
||||||
$IPT -I OUTPUT 1 -p tcp -m comment --comment "$TAG" -j SS_SPEC_ROUTER
|
$IPT -I OUTPUT 1 -p tcp -m comment --comment "$TAG" -j SS_SPEC_ROUTER
|
||||||
;;
|
;;
|
||||||
esac
|
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 $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
|
$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)
|
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
|
$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
|
do
|
||||||
$ipt -A SS_SPEC_TPROXY -p udp -d "$net" -j RETURN
|
$ipt -A SS_SPEC_TPROXY -p udp -d "$net" -j RETURN
|
||||||
done
|
done
|
||||||
[ -n "$SERVER" ] && $ipt -A SS_SPEC_TPROXY -p udp ! --dport 53 -d "$SERVER" -j RETURN
|
$ipt -A SS_SPEC_TPROXY -p udp ! --dport 53 -d "$SERVER" -j RETURN
|
||||||
[ -n "$SERVER" ] && [ "$server" != "$SERVER" ] && ipset -! add whitelist "$SERVER"
|
[ "$server" != "$SERVER" ] && ipset -! add whitelist "$SERVER"
|
||||||
if [ -f "${xhttp_ip:=/etc/ssrplus/xhttp_address.txt}" ]; then
|
if [ -f "${xhttp_ip:=/etc/ssrplus/xhttp_address.txt}" ]; then
|
||||||
while IFS= read -r ip; do
|
while IFS= read -r ip; do
|
||||||
[ -n "$ip" ] && ipset add whitelist "$ip" -exist
|
[ -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 $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}
|
$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)
|
all)
|
||||||
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK}
|
$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
|
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
|
case "$arg" in
|
||||||
m)
|
m)
|
||||||
Interface=$OPTARG
|
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)
|
U)
|
||||||
TPROXY=2
|
TPROXY=2
|
||||||
;;
|
;;
|
||||||
y)
|
|
||||||
DISABLE_UDP_RULES=1
|
|
||||||
;;
|
|
||||||
g)
|
g)
|
||||||
RUNMODE=gfw
|
RUNMODE=gfw
|
||||||
;;
|
;;
|
||||||
r)
|
r)
|
||||||
RUNMODE=router
|
RUNMODE=router
|
||||||
;;
|
;;
|
||||||
|
c)
|
||||||
|
RUNMODE=oversea
|
||||||
|
;;
|
||||||
z)
|
z)
|
||||||
RUNMODE=all
|
RUNMODE=all
|
||||||
;;
|
;;
|
||||||
@@ -1640,7 +1703,7 @@ runmode_change() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Main process
|
# Main process
|
||||||
if [ -n "$local_port" ]; then
|
if [ -n "$server" ] && [ -n "$local_port" ]; then
|
||||||
if ! echo "$local_port" | grep -qE '^[0-9]+$'; then
|
if ! echo "$local_port" | grep -qE '^[0-9]+$'; then
|
||||||
loger 3 "Invalid local port: $local_port"
|
loger 3 "Invalid local port: $local_port"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -1652,6 +1715,7 @@ if [ -n "$local_port" ]; then
|
|||||||
LOCAL_PORT=$local_port
|
LOCAL_PORT=$local_port
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
|
: ${SERVER:?"You must assign an ip for the udp relay server."}
|
||||||
: ${LOCAL_PORT:?"You must assign a port for the udp relay server."}
|
: ${LOCAL_PORT:?"You must assign a port for the udp relay server."}
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh /etc/rc.common
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 openwrt-ssr
|
# Copyright (C) 2017 openwrt-ssr
|
||||||
# Copyright (C) 2017 yushi studio <ywb94@qq.com>
|
# Copyright (C) 2017 yushi studio <ywb94@qq.com>
|
||||||
@@ -7,276 +7,149 @@
|
|||||||
# See /LICENSE for more information.
|
# 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
|
. $IPKG_INSTROOT/etc/init.d/shadowsocksr
|
||||||
LOCK_FILE="/var/lock/ssr-switch.lock"
|
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
|
cycle_time=60
|
||||||
switch_time=3
|
switch_time=3
|
||||||
probe_socks_port=10800
|
normal_flag=0
|
||||||
direct_fail_count=0
|
server_locate=0
|
||||||
direct_fail_limit=3
|
server_count=0
|
||||||
probe_url="$(uci_get_by_type server_subscribe url_test_url)"
|
ENABLE_SERVER=nil
|
||||||
[ -n "$probe_url" ] || probe_url="https://www.google.com/generate_204"
|
[ -n "$1" ] && cycle_time=$1
|
||||||
PROBE_INSTANCE_KEY="ssr-switch-probe-local"
|
[ -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"
|
check_proxy() {
|
||||||
ENABLE_SERVER="nil"
|
local result=0
|
||||||
|
local try_count=$(uci_get_by_type global switch_try_count 3)
|
||||||
cleanup_probe_socks() {
|
for i in $(seq 1 $try_count); do
|
||||||
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
|
/usr/bin/ssr-check www.google.com 80 $switch_time 1
|
||||||
sleep 1
|
if [ "$?" == "0" ]; then
|
||||||
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
|
# echolog "Check Google Proxy Success, count=$i"
|
||||||
rm -f "$TMP_PATH/tcp-http-ssr-switch-local.json" 2>/dev/null
|
result=0
|
||||||
rm -rf "$TMP_PATH/ss-${PROBE_INSTANCE_KEY}" "$TMP_PATH/tuic-${PROBE_INSTANCE_KEY}" 2>/dev/null
|
break
|
||||||
rm -f /tmp/ssr-switch-probe.log 2>/dev/null
|
else
|
||||||
}
|
# echolog "Check Google Proxy Fail, count=$i"
|
||||||
|
/usr/bin/ssr-check www.baidu.com 80 $switch_time 1
|
||||||
start_probe_socks() {
|
if [ "$?" == "0" ]; then
|
||||||
local sid="$1"
|
result=1
|
||||||
local server_type
|
else
|
||||||
|
result=2
|
||||||
server_type="$(uci_get_by_name "$sid" type)"
|
fi
|
||||||
[ "$server_type" = "clash" ] && return 1
|
fi
|
||||||
[ "$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
|
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
return 1
|
return $result
|
||||||
}
|
}
|
||||||
|
|
||||||
test_proxy() {
|
test_proxy() {
|
||||||
local sid="$1"
|
local servername=$(uci_get_by_name $1 server)
|
||||||
[ "$(uci_get_by_name "$sid" type)" = "clash" ] && return 1
|
local serverport=$(uci_get_by_name $1 server_port)
|
||||||
[ "$(uci_get_by_name "$sid" type)" = "tuic" ] && return 1
|
ipset add whitelist $servername 2>/dev/null
|
||||||
start_probe_socks "$sid" || {
|
tcping -q -c 3 -i 1 -t 2 -p $serverport $servername
|
||||||
cleanup_probe_socks
|
if [ "$?" -gt "0" ]; then
|
||||||
|
ipset del whitelist $servername 2>/dev/null
|
||||||
return 1
|
return 1
|
||||||
}
|
fi
|
||||||
check_proxy_via_socks
|
/usr/bin/ssr-check $servername $serverport $switch_time
|
||||||
local ret=$?
|
local ret=$?
|
||||||
cleanup_probe_socks
|
ipset del whitelist $servername 2>/dev/null
|
||||||
return $ret
|
if [ "$ret" == "0" ]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
search_proxy() {
|
search_proxy() {
|
||||||
local sid="$1"
|
let server_count=server_count+1
|
||||||
[ "$(uci_get_by_name "$sid" switch_enable 0)" = "1" ] || return 1
|
[ "$normal_flag" == "1" -a "$server_count" -le "$server_locate" ] && return 0
|
||||||
[ "$(uci_get_by_name "$sid" type)" = "clash" ] && return 1
|
[ "$(uci_get_by_name $1 switch_enable 0)" != "1" ] && return 1
|
||||||
[ "$(uci_get_by_name "$sid" type)" = "tuic" ] && return 1
|
[ $ENABLE_SERVER != nil ] && return 0
|
||||||
[ "$sid" = "$CURRENT_SERVER" ] && return 1
|
[ "$1" == "$CURRENT_SERVER" ] && return 0
|
||||||
[ "$ENABLE_SERVER" != "nil" ] && return 0
|
local servername=$(uci_get_by_name $1 server)
|
||||||
|
local serverport=$(uci_get_by_name $1 server_port)
|
||||||
if test_proxy "$sid"; then
|
ipset add whitelist $servername 2>/dev/null
|
||||||
ENABLE_SERVER="$sid"
|
/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
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#选择可用的代理
|
||||||
select_proxy() {
|
select_proxy() {
|
||||||
config_load "$NAME"
|
config_load $NAME
|
||||||
ENABLE_SERVER="nil"
|
ENABLE_SERVER=nil
|
||||||
|
mkdir -p /var/run /var/etc
|
||||||
|
server_count=0
|
||||||
config_foreach search_proxy servers
|
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() {
|
switch_proxy() {
|
||||||
set_main_server "$1" || return 1
|
/etc/init.d/shadowsocksr restart $1
|
||||||
/etc/init.d/shadowsocksr restart "$1"
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
while [ "1" = "1" ]; do
|
#不支持kcptun启用时的切换
|
||||||
sleep "0000$cycle_time"
|
[ $(uci_get_by_name $DEFAULT_SERVER kcp_enable) = "1" ] && return 1
|
||||||
run_once
|
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
|
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"
|
require "luci.sys"
|
||||||
local ucursor = require "luci.model.uci".cursor()
|
local ucursor = require "luci.model.uci".cursor()
|
||||||
|
local datatypes = require "luci.cbi.datatypes"
|
||||||
local json = require "luci.jsonc"
|
local json = require "luci.jsonc"
|
||||||
|
|
||||||
local server_section = arg[1]
|
local server_section = arg[1]
|
||||||
@@ -11,6 +12,12 @@ local socks_port = arg[4] or "0"
|
|||||||
|
|
||||||
local chain = arg[5] or "0"
|
local chain = arg[5] or "0"
|
||||||
|
|
||||||
|
local GLOBAL = {
|
||||||
|
DNS_SERVER = {},
|
||||||
|
DNS_HOSTNAME = {},
|
||||||
|
VPS_EXCLUDE = {}
|
||||||
|
}
|
||||||
|
|
||||||
-- 辅助函数:拆分字符串(若 luci.util 未加载则定义)
|
-- 辅助函数:拆分字符串(若 luci.util 未加载则定义)
|
||||||
local function split(str, pat)
|
local function split(str, pat)
|
||||||
local t = {}
|
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 socks_server = ucursor:get_all("shadowsocksr", "@socks5_proxy[0]") or {}
|
||||||
local xray_fragment = ucursor:get_all("shadowsocksr", "@global_xray_fragment[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 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 outbound_settings = nil
|
||||||
local xray_version = nil
|
local xray_version = nil
|
||||||
local xray_version_val = 0
|
local xray_version_val = 0
|
||||||
local xray_builtin_dns = nil
|
|
||||||
|
|
||||||
local node_id = server_section
|
local node_id = server_section
|
||||||
local remarks = server.alias or ""
|
local remarks = server.alias or ""
|
||||||
local b64decode = nixio.bin.b64decode
|
local b64decode = nixio.bin.b64decode
|
||||||
local b64encode = nixio.bin.b64encode
|
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
|
-- 解析 URL(简单实现,仅用于 DoH)
|
||||||
server.type = "ss"
|
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
|
end
|
||||||
|
|
||||||
local function parse_realm_uri(uri)
|
if server.type == "ss-rust" or server.type == "ss-libev" then
|
||||||
if type(uri) ~= "string" then return nil end
|
server.type = "ss"
|
||||||
-- 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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- base64 解码
|
-- base64 解码
|
||||||
@@ -138,25 +120,9 @@ local function cleanEmptyTables(t)
|
|||||||
return next(t) and t or nil
|
return next(t) and t or nil
|
||||||
end
|
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)
|
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
|
end
|
||||||
|
|
||||||
-- 获取 Xray 版本号
|
-- 获取 Xray 版本号
|
||||||
@@ -237,33 +203,20 @@ function wireguard()
|
|||||||
-- 处理 reserved 字段,支持逗号分隔的数字或 Base64 编码
|
-- 处理 reserved 字段,支持逗号分隔的数字或 Base64 编码
|
||||||
local reserved = nil
|
local reserved = nil
|
||||||
if server.reserved then
|
if server.reserved then
|
||||||
local all_bytes = {}
|
local bytes = {}
|
||||||
local reserved_values = server.reserved
|
if not server.reserved:match("[^%d,]+") then
|
||||||
|
-- 纯数字和逗号,解析为数字列表
|
||||||
-- 确保是 table 类型
|
server.reserved:gsub("%d+", function(b)
|
||||||
if type(reserved_values) ~= "table" then
|
bytes[#bytes + 1] = tonumber(b)
|
||||||
reserved_values = {reserved_values}
|
end)
|
||||||
end
|
else
|
||||||
|
-- Base64 编码的二进制数据
|
||||||
for _, reserved_str in ipairs(reserved_values) do
|
local result = base64Decode(server.reserved)
|
||||||
if type(reserved_str) == "string" then
|
for i = 1, #result do
|
||||||
if not reserved_str:match("[^%d,]+") then
|
bytes[i] = result:byte(i)
|
||||||
-- 数字和逗号格式
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
reserved = #all_bytes > 0 and all_bytes or nil
|
reserved = #bytes > 0 and bytes or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
outbound_settings = {
|
outbound_settings = {
|
||||||
@@ -273,22 +226,15 @@ function wireguard()
|
|||||||
{
|
{
|
||||||
publicKey = server.peer_pubkey,
|
publicKey = server.peer_pubkey,
|
||||||
preSharedKey = server.preshared_key,
|
preSharedKey = server.preshared_key,
|
||||||
endpoint = format_host_port(server.server, server.server_port),
|
endpoint = server.server .. ":" .. server.server_port,
|
||||||
keepAlive = tonumber(server.keepaliveperiod),
|
keepAlive = tonumber(server.keepaliveperiod),
|
||||||
allowedIPs = (server.allowedips) or nil,
|
allowedIPs = (server.allowedips) or nil,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
kernelMode = (server.kernelmode == "1") and true or false,
|
noKernelTun = (server.kernelmode == "1") and true or false,
|
||||||
reserved = reserved,
|
reserved = reserved,
|
||||||
mtu = tonumber(server.mtu)
|
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
|
end
|
||||||
function xray_hysteria2()
|
function xray_hysteria2()
|
||||||
outbound_settings = {
|
outbound_settings = {
|
||||||
@@ -353,35 +299,6 @@ local Xray = {
|
|||||||
-- 初始化 outbounds 表
|
-- 初始化 outbounds 表
|
||||||
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
|
-- 添加 dokodemo-door 配置,如果 local_port 不为 0
|
||||||
if local_port ~= "0" then
|
if local_port ~= "0" then
|
||||||
@@ -432,11 +349,11 @@ if proto and proto:find("tcp") and socks_port ~= "0" then
|
|||||||
-- socks
|
-- socks
|
||||||
protocol = "socks",
|
protocol = "socks",
|
||||||
port = tonumber(socks_port),
|
port = tonumber(socks_port),
|
||||||
settings = {
|
settings = {
|
||||||
auth = socks_server.socks5_auth or "noauth",
|
auth = socks_server.socks5_auth or "noauth",
|
||||||
udp = true,
|
udp = true,
|
||||||
mixed = ((socks_server.socks5_mixed == '1') and true or false) or nil,
|
mixed = ((socks_server.socks5_mixed == '1') and true or false) or (socks_server.server == 'same') and nil,
|
||||||
accounts = (socks_server.socks5_auth and socks_server.socks5_auth ~= "noauth") and {
|
accounts = (socks_server.server ~= "same" and (socks_server.socks5_auth and socks_server.socks5_auth ~= "noauth")) and {
|
||||||
{
|
{
|
||||||
user = socks_server.socks5_user,
|
user = socks_server.socks5_user,
|
||||||
pass = socks_server.socks5_pass
|
pass = socks_server.socks5_pass
|
||||||
@@ -615,7 +532,7 @@ Xray.outbounds = {
|
|||||||
local finalmask = {}
|
local finalmask = {}
|
||||||
local PT = server.v2ray_protocol
|
local PT = server.v2ray_protocol
|
||||||
local TP = server.transport
|
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",
|
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
|
||||||
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
|
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
|
||||||
local udp = {}
|
local udp = {}
|
||||||
@@ -633,36 +550,14 @@ Xray.outbounds = {
|
|||||||
udp[#udp+1] = c
|
udp[#udp+1] = c
|
||||||
finalmask.udp = udp
|
finalmask.udp = udp
|
||||||
elseif PT == "hysteria2" then
|
elseif PT == "hysteria2" then
|
||||||
local udp = {}
|
|
||||||
if (server.flag_obfs == "1" and (server.obfs_type and server.obfs_type ~= "")) then
|
if (server.flag_obfs == "1" and (server.obfs_type and server.obfs_type ~= "")) then
|
||||||
local o = {
|
finalmask.udp = {{
|
||||||
type = "salamander",
|
type = server.obfs_type,
|
||||||
settings = server.salamander and {
|
settings = server.salamander and {
|
||||||
password = server.salamander,
|
password = server.salamander
|
||||||
packetSize = server.obfs_type == "gecko" and "512-1200" or nil
|
|
||||||
} or nil
|
} or nil
|
||||||
}
|
}}
|
||||||
udp[#udp+1] = o
|
|
||||||
end
|
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 up = tonumber(server.uplink_capacity) or 0
|
||||||
local down = tonumber(server.downlink_capacity) or 0
|
local down = tonumber(server.downlink_capacity) or 0
|
||||||
finalmask.quicParams = {
|
finalmask.quicParams = {
|
||||||
@@ -750,6 +645,7 @@ Xray.outbounds = {
|
|||||||
end)(),
|
end)(),
|
||||||
sockopt = {
|
sockopt = {
|
||||||
mark = 255,
|
mark = 255,
|
||||||
|
domainStrategy = server.domain_strategy or "UseIP",
|
||||||
tcpFastOpen = (function()
|
tcpFastOpen = (function()
|
||||||
if server.transport == "xhttp" then
|
if server.transport == "xhttp" then
|
||||||
return (server.fast_open == "1") and true or false
|
return (server.fast_open == "1") and true or false
|
||||||
@@ -777,24 +673,18 @@ Xray.outbounds = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if xray_builtin_dns then
|
table.insert(Xray.outbounds, {
|
||||||
table.insert(Xray.outbounds, {
|
protocol = "freedom",
|
||||||
protocol = "dns",
|
tag = "direct",
|
||||||
tag = "builtin-dns-out",
|
settings = {
|
||||||
settings = {
|
domainStrategy = server.domain_strategy or "UseIP" -- 可根据需要改为 direct_dns_query_strategy
|
||||||
network = "tcp",
|
},
|
||||||
address = xray_builtin_dns.address,
|
streamSettings = {
|
||||||
port = xray_builtin_dns.port
|
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 配置
|
-- 添加带有 fragment 设置的 dialerproxy 配置
|
||||||
if xray_fragment.fragment ~= "0" or (xray_fragment.noise ~= "0" and xray_noise.enabled ~= "0") then
|
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
|
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 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 cipher13 = "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384"
|
||||||
local trojan = {
|
local trojan = {
|
||||||
@@ -872,7 +877,7 @@ local trojan = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
local naiveproxy = {
|
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),
|
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
|
["insecure-concurrency"] = tonumber(server.concurrency) or 1
|
||||||
}
|
}
|
||||||
@@ -893,16 +898,16 @@ local hysteria2 = {
|
|||||||
server.server_port and
|
server.server_port and
|
||||||
(
|
(
|
||||||
server.port_range 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
|
or
|
||||||
(format_host_port(server.server, server.server_port))
|
(server.server .. ":" .. server.server_port)
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
(
|
(
|
||||||
server.port_range and
|
server.port_range and
|
||||||
format_host(server.server) .. ":" .. string.gsub(server.port_range, ":", "-")
|
server.server .. ":" .. string.gsub(server.port_range, ":", "-")
|
||||||
or
|
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 {
|
bandwidth = (server.uplink_capacity or server.downlink_capacity) and {
|
||||||
@@ -1001,7 +1006,7 @@ local hysteria2 = {
|
|||||||
}
|
}
|
||||||
local shadowtls = {
|
local shadowtls = {
|
||||||
client = {
|
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),
|
listen = "127.0.0.1:" .. tonumber(local_port),
|
||||||
tls_names = server.shadowtls_sni,
|
tls_names = server.shadowtls_sni,
|
||||||
password = server.password
|
password = server.password
|
||||||
@@ -1015,7 +1020,7 @@ local chain_sslocal = {
|
|||||||
locals = local_port ~= "0" and {
|
locals = local_port ~= "0" and {
|
||||||
{
|
{
|
||||||
local_address = "0.0.0.0",
|
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",
|
mode = (proto:find("tcp,udp") and "tcp_and_udp") or proto .. "_only",
|
||||||
protocol = "redir",
|
protocol = "redir",
|
||||||
tcp_redir = "redirect",
|
tcp_redir = "redirect",
|
||||||
@@ -1044,7 +1049,7 @@ local chain_sslocal = {
|
|||||||
local chain_vmess = {
|
local chain_vmess = {
|
||||||
inbounds = (local_port ~= "0") and {
|
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",
|
protocol = "dokodemo-door",
|
||||||
settings = {
|
settings = {
|
||||||
network = proto,
|
network = proto,
|
||||||
@@ -1080,7 +1085,7 @@ local chain_vmess = {
|
|||||||
}
|
}
|
||||||
local tuic = {
|
local tuic = {
|
||||||
relay = {
|
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,
|
ip = server.tuic_ip,
|
||||||
uuid = server.tuic_uuid,
|
uuid = server.tuic_uuid,
|
||||||
password = server.tuic_passwd,
|
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
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
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
|
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/
|
if [ "$run_mode" = "oversea" ]; then
|
||||||
cp -rf /etc/ssrplus/gfw_base.conf $TMP_DNSMASQ_PATH/
|
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
|
for conf_file in gfw_base.conf gfw_list.conf; do
|
||||||
conf="$TMP_DNSMASQ_PATH/$conf_file"
|
conf="$TMP_DNSMASQ_PATH/$conf_file"
|
||||||
@@ -41,41 +67,36 @@ for conf_file in gfw_base.conf gfw_list.conf; do
|
|||||||
fi
|
fi
|
||||||
done
|
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
|
# 此处使用 for 方式读取 防止 /etc/ssrplus/ 目录下的 black.list white.list deny.list 等2个或多个文件一行中存在空格 比如:# abc.com 而丢失:server
|
||||||
# Optimize: Batch filter using grep
|
# Optimize: Batch filter using grep
|
||||||
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; do
|
||||||
if [ -s "$list_file" ]; then
|
if [ -s "$list_file" ]; then
|
||||||
# 清理注释和空行
|
grep -vE '^\s*#|^\s*$' "$list_file" > "${list_file}.clean"
|
||||||
grep -vE '^\s*#|^\s*$' "$list_file" | sed 's/\r//g' > "${list_file}.clean"
|
|
||||||
if [ -s "${list_file}.clean" ]; then
|
if [ -s "${list_file}.clean" ]; then
|
||||||
for target_file in "$TMP_DNSMASQ_PATH/gfw_list.conf" "$TMP_DNSMASQ_PATH/gfw_base.conf"; do
|
for target_file in "$TMP_DNSMASQ_PATH/gfw_list.conf" "$TMP_DNSMASQ_PATH/gfw_base.conf"; do
|
||||||
[ -f "$target_file" ] || continue
|
if [ -f "$target_file" ]; then
|
||||||
tmp_file="${target_file}.tmp"
|
grep -v -F -f "${list_file}.clean" "$target_file" > "${target_file}.tmp"
|
||||||
awk -v list="${list_file}.clean" '
|
mv "${target_file}.tmp" "$target_file"
|
||||||
BEGIN {
|
fi
|
||||||
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"
|
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
rm -f "${list_file}.clean"
|
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
|
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
|
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
|
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
|
if [ -s "$list_file" ]; then
|
||||||
# 清理注释 & 空行
|
grep -vE '^\s*#|^\s*$' "$list_file" > "${list_file}.clean"
|
||||||
grep -vE '^\s*#|^\s*$' "$list_file" | sed 's/\r//g' > "${list_file}.clean"
|
|
||||||
if [ -s "${list_file}.clean" ]; then
|
if [ -s "${list_file}.clean" ]; then
|
||||||
tmp_file="$TMP_DNSMASQ_PATH/ad.conf.tmp"
|
grep -v -F -f "${list_file}.clean" "$TMP_DNSMASQ_PATH/ad.conf" > "$TMP_DNSMASQ_PATH/ad.conf.tmp"
|
||||||
awk -v list="${list_file}.clean" '
|
mv "$TMP_DNSMASQ_PATH/ad.conf.tmp" "$TMP_DNSMASQ_PATH/ad.conf"
|
||||||
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"
|
|
||||||
fi
|
fi
|
||||||
rm -f "${list_file}.clean"
|
rm -f "${list_file}.clean"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
rm -f "$TMP_DNSMASQ_PATH/ad.conf"
|
rm -f $TMP_DNSMASQ_PATH/ad.conf
|
||||||
fi
|
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
|
config global
|
||||||
option global_server 'nil'
|
option global_server 'nil'
|
||||||
|
option netflix_server 'nil'
|
||||||
|
option netflix_proxy '0'
|
||||||
option threads '0'
|
option threads '0'
|
||||||
option run_mode 'router'
|
option run_mode 'router'
|
||||||
option dports '2'
|
option dports '2'
|
||||||
option custom_ports '80,443'
|
option custom_ports '80,443'
|
||||||
option pdnsd_enable '1'
|
option pdnsd_enable '1'
|
||||||
option filter_aaaa '1'
|
option prefer_nft '1'
|
||||||
option tunnel_forward '8.8.4.4:53'
|
option tunnel_forward '8.8.4.4:53'
|
||||||
option monitor_enable '1'
|
option monitor_enable '1'
|
||||||
option enable_switch '1'
|
option enable_switch '1'
|
||||||
option switch_time '667'
|
option switch_time '667'
|
||||||
option switch_timeout '5'
|
option switch_timeout '5'
|
||||||
option switch_try_count '3'
|
option switch_try_count '3'
|
||||||
option default_node_local_port '1234'
|
option shunt_dns '1'
|
||||||
option component_mirror 'direct'
|
|
||||||
option gfwlist_url 'https://fastly.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt'
|
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 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'
|
option adblock_url 'https://anti-ad.net/anti-ad-for-dnsmasq.conf'
|
||||||
|
|
||||||
config server_subscribe
|
config server_subscribe
|
||||||
option proxy '1'
|
option proxy '0'
|
||||||
option auto_update '1'
|
option auto_update '1'
|
||||||
option config_auto_update_mode '0'
|
|
||||||
option config_update_interval '60'
|
|
||||||
option auto_update_week_time '*'
|
option auto_update_week_time '*'
|
||||||
option auto_update_day_time '2'
|
option auto_update_day_time '2'
|
||||||
option auto_update_min_time '0'
|
option auto_update_min_time '0'
|
||||||
option url_test_url 'https://www.google.com/generate_204'
|
option url_test_url 'https://www.google.com/generate_204'
|
||||||
option user_agent 'v2rayN/9.99'
|
option user_agent 'v2rayN/9.99'
|
||||||
option filter_words '过期/套餐/剩余/网址/QQ群/官网/防失联/回国'
|
option filter_words '过期/套餐/剩余/QQ群/官网/防失联/回国'
|
||||||
|
|
||||||
config access_control
|
config access_control
|
||||||
option lan_ac_mode '0'
|
option lan_ac_mode '0'
|
||||||
@@ -47,11 +47,6 @@ config socks5_proxy
|
|||||||
option server 'nil'
|
option server 'nil'
|
||||||
option local_port '1080'
|
option local_port '1080'
|
||||||
|
|
||||||
config http_proxy
|
|
||||||
option server 'nil'
|
|
||||||
option local_port '3128'
|
|
||||||
option http_auth 'none'
|
|
||||||
|
|
||||||
config server_global
|
config server_global
|
||||||
option enable_server '0'
|
option enable_server '0'
|
||||||
|
|
||||||
@@ -59,9 +54,3 @@ config global_xray_fragment
|
|||||||
option fragment '0'
|
option fragment '0'
|
||||||
option noise '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
|
#!/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
|
/usr/bin/lua /usr/share/shadowsocksr/update.lua
|
||||||
sleep 2s
|
sleep 2s
|
||||||
/usr/share/shadowsocksr/chinaipset.sh /var/etc/ssrplus/china_ssr.txt
|
/usr/share/shadowsocksr/chinaipset.sh /var/etc/ssrplus/china_ssr.txt
|
||||||
sleep 2s
|
sleep 2s
|
||||||
/usr/bin/lua /usr/share/shadowsocksr/subscribe.lua
|
/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")
|
update(uci:get_first("shadowsocksr", "global", "adblock_url"), "/etc/ssrplus/ad.conf", args, TMP_DNSMASQ_PATH .. "/ad.conf")
|
||||||
os.exit(0)
|
os.exit(0)
|
||||||
end
|
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
|
else
|
||||||
log("正在更新【GFW列表】数据库")
|
log("正在更新【GFW列表】数据库")
|
||||||
update(uci:get_first("shadowsocksr", "global", "gfwlist_url"), "/etc/ssrplus/gfw_list.conf", "gfw_data", TMP_DNSMASQ_PATH .. "/gfw_list.conf")
|
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("正在更新【广告屏蔽】数据库")
|
log("正在更新【广告屏蔽】数据库")
|
||||||
update(uci:get_first("shadowsocksr", "global", "adblock_url"), "/etc/ssrplus/ad.conf", "ad_data", TMP_DNSMASQ_PATH .. "/ad.conf")
|
update(uci:get_first("shadowsocksr", "global", "adblock_url"), "/etc/ssrplus/ad.conf", "ad_data", TMP_DNSMASQ_PATH .. "/ad.conf")
|
||||||
end
|
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
|
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
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=mihomo
|
PKG_NAME:=mihomo
|
||||||
PKG_VERSION:=1.19.26
|
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:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
PKG_SOURCE_URL:=https://codeload.github.com/metacubex/mihomo/tar.gz/v$(PKG_VERSION)?
|
||||||
PKG_MIRROR_HASH:=skip
|
PKG_HASH:=skip
|
||||||
|
|
||||||
PKG_LICENSE:=GPL3.0+
|
PKG_MAINTAINER:=Anya Lin <hukk1996@gmail.com>
|
||||||
PKG_MAINTAINER:=Joseph Mory <morytyann@gmail.com>
|
PKG_LICENSE:=GPL-2.0
|
||||||
|
PKG_LICENSE_FILES:=LICENSE
|
||||||
|
|
||||||
PKG_BUILD_DEPENDS:=golang/host
|
PKG_BUILD_DEPENDS:=golang/host
|
||||||
PKG_BUILD_PARALLEL:=1
|
PKG_BUILD_PARALLEL:=1
|
||||||
PKG_BUILD_FLAGS:=no-mips16
|
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:=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_TAGS:=with_gvisor
|
||||||
GO_PKG_INSTALL_BIN_PATH:=/usr/libexec
|
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
include $(TOPDIR)/feeds/packages/lang/golang/golang-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
|
define Package/mihomo
|
||||||
SECTION:=net
|
SECTION:=net
|
||||||
CATEGORY:=Network
|
CATEGORY:=Network
|
||||||
TITLE:=A rule based proxy in Go.
|
TITLE:=Another Mihomo Kernel.
|
||||||
URL:=https://wiki.metacubex.one
|
URL:=https://wiki.metacubex.one
|
||||||
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
|
DEPENDS:=$(GO_ARCH_DEPENDS)
|
||||||
|
PROVIDES:=mihomo
|
||||||
ALTERNATIVES:=\
|
ALTERNATIVES:=\
|
||||||
300:/usr/bin/mihomo:/usr/libexec/mihomo
|
100:/usr/bin/mihomo:/usr/libexec/mihomo-core
|
||||||
endef
|
USERID:=mihomo=7890:mihomo=7890
|
||||||
|
|
||||||
define Package/mihomo/description
|
|
||||||
Mihomo is a rule based proxy in Go.
|
|
||||||
endef
|
|
||||||
|
|
||||||
define Package/mihomo/install
|
|
||||||
$(call GoPackage/Package/Install/Bin,$(1))
|
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define Build/Prepare
|
define Build/Prepare
|
||||||
$(Build/Prepare/Default)
|
$(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
|
endef
|
||||||
|
|
||||||
$(eval $(call GoBinPackage,mihomo))
|
$(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