├── Docker
├── Caddy_V2ray
│ ├── Caddyfile
│ ├── Dockerfile
│ ├── docker-compose.yml
│ └── index.html
├── V2ray
│ └── docker-compose.yml
├── alpine
│ ├── Dockerfile
│ ├── config.json
│ └── runV2ray.sh
└── arm
│ └── Dockerfile
├── Manager
└── Manager.go
├── README.md
├── client
├── gRPCClient.go
├── handlerServiceClient.go
├── statsServiceClient.go
└── userRuleServerClient.go
├── config
├── cipher.go
└── config.go
├── db
├── db.go
├── mysql.go
├── sspanel.go
├── sspaneltabel.go
├── ssrpanel.go
├── ssrpaneltabel.go
└── webapi.go
├── go-panel.go
├── install-release.sh
├── install.sh
├── model
└── model.go
├── panel.go
├── plugin.go
├── speedtest
├── client.go
├── config.go
├── coordinates.go
├── download.go
├── latency.go
├── latency_test.go
├── opts.go
├── server.go
├── speedtest_thread.go
├── upload.go
├── upload_test.go
└── version.go
├── utility
└── utility.go
└── webapi
└── webapi.go
/Docker/Caddy_V2ray/Caddyfile:
--------------------------------------------------------------------------------
1 | {$V2RAY_DOMAIN}:{$V2RAY_OUTSIDE_PORT}
2 | {
3 | root /srv/www
4 | log ./caddy.log
5 | proxy {$V2RAY_PATH} 127.0.0.1:{$V2RAY_PORT} {
6 | websocket
7 | header_upstream -Origin
8 | }
9 | gzip
10 | tls {$V2RAY_EMAIL} {
11 | protocols tls1.0 tls1.2
12 | # remove comment if u want to use cloudflare (for DNS challenge authentication)
13 | # dns cloudflare
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Docker/Caddy_V2ray/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM jessestuart/caddy-cloudflare:v0.11.0
2 |
3 | RUN mkdir /srv/www
4 | COPY index.html /srv/www/index.html
5 | EXPOSE 80 443 2015
6 | VOLUME /root/.caddy /srv
7 | WORKDIR /srv
8 |
9 | ENTRYPOINT ["/bin/parent", "caddy"]
10 | CMD ["--conf", "/etc/Caddyfile", "--log", "stdout", "--agree=$ACME_AGREE"]
--------------------------------------------------------------------------------
/Docker/Caddy_V2ray/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | v2ray:
5 | image: githubphone/v2ray_v3:go_pay
6 | restart: always
7 | network_mode: "host"
8 | environment:
9 | sspanel_url: "https://xxxx"
10 | key: "xxxx"
11 | docker: "true"
12 | speedtest: 6
13 | node_id: 10
14 | api_port: 2333
15 | downWithPanel: 1
16 | TZ: "Asia/Shanghai"
17 | MYSQLHOST: "https://bing.com"
18 | MYSQLDBNAME: "demo_dbname"
19 | MYSQLUSR: "demo_user"
20 | MYSQLPASSWD: "demo_dbpassword"
21 | MYSQLPORT: 3306
22 | PANELTYPE: 0
23 | usemysql: 0
24 | volumes:
25 | - /etc/localtime:/etc/localtime:ro
26 | logging:
27 | options:
28 | max-size: "10m"
29 | max-file: "3"
30 |
31 | caddy:
32 | image: githubphone/v2ray_v3:caddy
33 | restart: always
34 | environment:
35 | - ACME_AGREE=true
36 | # if u want to use cloudflare (for DNS challenge authentication)
37 | # - CLOUDFLARE_EMAIL=xxxxxx@out.look.com
38 | # - CLOUDFLARE_API_KEY=xxxxxxx
39 | - V2RAY_DOMAIN=xxxx.com
40 | - V2RAY_PATH=/v2ray
41 | - V2RAY_EMAIL=xxxx@outlook.com
42 | - V2RAY_PORT=10550
43 | - V2RAY_OUTSIDE_PORT=443
44 | network_mode: "host"
45 | volumes:
46 | - ./.caddy:/root/.caddy
47 | - ./Caddyfile:/etc/Caddyfile
48 |
--------------------------------------------------------------------------------
/Docker/Caddy_V2ray/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | /
8 |
35 |
36 |
37 | Across the Great Wall we can reach every corner in the world...
38 |
39 |
--------------------------------------------------------------------------------
/Docker/V2ray/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | v2ray:
5 | image: githubphone/v2ray_v3:go_pay
6 | restart: always
7 | network_mode: "host"
8 | environment:
9 | sspanel_url: "https://xxxx"
10 | key: "xxxx"
11 | speedtest: 6
12 | node_id: 10
13 | api_port: 2333
14 | downWithPanel: 1
15 | TZ: "Asia/Shanghai"
16 | MYSQLHOST: "https://bing.com"
17 | MYSQLDBNAME: "demo_dbname"
18 | MYSQLUSR: "demo_user"
19 | MYSQLPASSWD: "demo_dbpassword"
20 | MYSQLPORT: 3306
21 | PANELTYPE: 0
22 | usemysql: 0
23 | volumes:
24 | - /etc/localtime:/etc/localtime:ro
25 | logging:
26 | options:
27 | max-size: "10m"
28 | max-file: "3"
--------------------------------------------------------------------------------
/Docker/alpine/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:latest as builder
2 |
3 | LABEL maintainer="Rico "
4 |
5 | ENV v2ray_version=4.18.4
6 |
7 | RUN apt-get update
8 | RUN apt-get install curl -y
9 | RUN curl -L -o /tmp/go.sh https://raw.githubusercontent.com/githubphone/pay-v2ray-sspanel-v3-mod_Uim-plugin/master/install-release.sh
10 | RUN chmod +x /tmp/go.sh
11 | RUN /tmp/go.sh --version ${v2ray_version} --panelurl https://google.com --panelkey 55fUxDGFzH3n --nodeid 123456 --downwithpanel 1 --mysqlhost https://bing.com --mysqldbname demo_dbname --mysqluser demo_user --mysqlpasswd demo_dbpassword --mysqlport 3306 --speedtestrate 6 --paneltype 0 --usemysql 0
12 |
13 | FROM alpine:latest
14 |
15 | COPY --from=builder /usr/bin/v2ray/v2ray /usr/bin/v2ray/
16 | COPY --from=builder /usr/bin/v2ray/v2ctl /usr/bin/v2ray/
17 | COPY --from=builder /usr/bin/v2ray/geoip.dat /usr/bin/v2ray/
18 | COPY --from=builder /usr/bin/v2ray/geosite.dat /usr/bin/v2ray/
19 | COPY config.json /etc/v2ray/config.json
20 | COPY runV2ray.sh /usr/bin/v2ray/runV2ray.sh
21 | RUN set -ex && \
22 | apk --no-cache add dcron ca-certificates openssl coreutils bind-tools curl socat && \
23 | update-ca-certificates && \
24 | apk add --update tzdata && \
25 | mkdir /var/log/v2ray/ && \
26 | chmod +x /usr/bin/v2ray/v2ctl && \
27 | chmod +x /usr/bin/v2ray/v2ray && \
28 | chmod +x /usr/bin/v2ray/runV2ray.sh && \
29 | rm -rf /var/cache/apk/*
30 |
31 | RUN mkdir -p /var/log/cron && mkdir -m 0644 -p /var/spool/cron/crontabs && touch /var/log/cron/cron.log && mkdir -m 0644 -p /etc/cron.d
32 |
33 |
34 | #Install
35 |
36 | RUN curl https://get.acme.sh | sh
37 |
38 |
39 | RUN ln -s /root/.acme.sh/acme.sh /usr/local/bin/acme.sh && crontab -l | grep acme.sh | sed 's#> /dev/null##' | crontab -
40 |
41 | ENV TZ=Asia/Shanghai
42 | ENV PATH /usr/bin/v2ray:$PATH
43 | VOLUME /var/log/v2ray/ /root/.acme.sh/
44 | WORKDIR /var/log/v2ray/
45 |
46 | CMD sh /usr/bin/v2ray/runV2ray.sh
47 |
--------------------------------------------------------------------------------
/Docker/alpine/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "api": {
3 | "services": [
4 | "HandlerService",
5 | "LoggerService",
6 | "StatsService",
7 | "RuleService"
8 | ],
9 | "tag": "api"
10 | },
11 | "inbounds": [{
12 | "listen": "127.0.0.1",
13 | "port": 2333,
14 | "protocol": "dokodemo-door",
15 | "settings": {
16 | "address": "127.0.0.1"
17 | },
18 | "tag": "api"
19 | }
20 | ],
21 | "log": {
22 | "loglevel": "info"
23 | },
24 | "outbounds": [{
25 | "protocol": "freedom",
26 | "settings": {}
27 | },
28 | {
29 | "protocol": "blackhole",
30 | "settings": {},
31 | "tag": "blocked"
32 | }
33 | ],
34 | "policy": {
35 | "levels": {
36 | "0": {
37 | "connIdle": 300,
38 | "downlinkOnly": 5,
39 | "handshake": 4,
40 | "statsUserDownlink": true,
41 | "statsUserUplink": true,
42 | "uplinkOnly": 2
43 | }
44 | },
45 | "system": {
46 | "statsInboundDownlink": false,
47 | "statsInboundUplink": false
48 | }
49 | },
50 | "reverse": {},
51 | "routing": {
52 | "settings": {
53 | "rules": [{
54 | "ip": [
55 | "0.0.0.0/8",
56 | "10.0.0.0/8",
57 | "100.64.0.0/10",
58 | "127.0.0.0/8",
59 | "169.254.0.0/16",
60 | "172.16.0.0/12",
61 | "192.0.0.0/24",
62 | "192.0.2.0/24",
63 | "192.168.0.0/16",
64 | "198.18.0.0/15",
65 | "198.51.100.0/24",
66 | "203.0.113.0/24",
67 | "::1/128",
68 | "fc00::/7",
69 | "fe80::/10"
70 | ],
71 | "outboundTag": "blocked",
72 | "protocol": [
73 | "bittorrent"
74 | ],
75 | "type": "field"
76 | },
77 | {
78 | "inboundTag": [
79 | "api"
80 | ],
81 | "outboundTag": "api",
82 | "type": "field"
83 | },
84 | {
85 | "domain": [
86 | "regexp:(api|ps|sv|offnavi|newvector|ulog\\.imap|newloc)(\\.map|)\\.(baidu|n\\.shifen)\\.com",
87 | "regexp:(.+\\.|^)(360|so)\\.(cn|com)",
88 | "regexp:(.?)(xunlei|sandai|Thunder|XLLiveUD)(.)"
89 | ],
90 | "outboundTag": "blocked",
91 | "type": "field"
92 | }
93 | ]
94 | },
95 | "strategy": "rules"
96 | },
97 | "stats": {},
98 | "sspanel": {
99 | "nodeid": 123456,
100 | "checkRate": 60,
101 | "SpeedTestCheckRate": 6,
102 | "panelUrl": "https://google.com",
103 | "panelKey": "55fUxDGFzH3n",
104 | "downWithPanel": 1,
105 | "mu_regex": "%5m%id.%suffix",
106 | "mu_suffix": "microsoft.com",
107 | "mysql": {
108 | "host": "https://bing.com",
109 | "port": 3306,
110 | "user": "demo_user",
111 | "password": "demo_dbpassword",
112 | "dbname": "demo_dbname"
113 | },
114 | "paneltype": 0,
115 | "usemysql": 0,
116 | "go_panel_host": "bwg.com",
117 | "go_panel_key": "bbbbaaaaaaa"
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/Docker/alpine/runV2ray.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if [ ! -z "${api_port}" ]
4 | then
5 | sed -i "s|\"port\": 2333,|\"port\": ${api_port},|" "/etc/v2ray/config.json"
6 | fi
7 | if [ ! -z "${sspanel_url}" ]
8 | then
9 | sed -i "s|\"https://google.com\"|\"${sspanel_url}\"|g" "/etc/v2ray/config.json"
10 | fi
11 | if [ ! -z "${key}" ]
12 | then
13 | sed -i "s/\"55fUxDGFzH3n\"/\"${key}\"/g" "/etc/v2ray/config.json"
14 | fi
15 |
16 | if [ ! -z "${node_id}" ]
17 | then
18 | sed -i "s/123456/${node_id}/g" "/etc/v2ray/config.json"
19 | fi
20 |
21 | if [ ! -z "${speedtest}" ]
22 | then
23 | sed -i "s/\"SpeedTestCheckRate\": 6/\"SpeedTestCheckRate\": ${speedtest}/g" "/etc/v2ray/config.json"
24 | fi
25 | if [ ! -z "${downWithPanel}" ]
26 | then
27 | sed -i "s/\"downWithPanel\": 6/\"downWithPanel\": ${downWithPanel}/g" "/etc/v2ray/config.json"
28 | fi
29 |
30 | if [ ! -z "${MYSQLHOST}" ]
31 | then
32 | sed -i "s|"https://bing.com"|"${MYSQLHOST}"|g" "/etc/v2ray/config.json"
33 | fi
34 |
35 | if [ ! -z "${MYSQLDBNAME}" ]
36 | then
37 | sed -i "s/"demo_dbname"/"${MYSQLDBNAME}"/g" "/etc/v2ray/config.json"
38 | fi
39 |
40 | if [ ! -z "${MYSQLUSR}" ]
41 | then
42 | sed -i "s|\"demo_user\"|\"${MYSQLUSR}\"|g" "/etc/v2ray/config.json"
43 | fi
44 | if [ ! -z "${MYSQLPASSWD}" ]
45 | then
46 | sed -i "s/"demo_dbpassword"/"${MYSQLPASSWD}"/g" "/etc/v2ray/config.json"
47 | fi
48 | if [ ! -z "${MYSQLPORT}" ]
49 | then
50 | sed -i "s/3306,/${MYSQLPORT},/g" "/etc/v2ray/config.json"
51 | fi
52 |
53 | if [ ! -z "${PANELTYPE}" ]
54 | then
55 | sed -i "s|\"paneltype\": 0|\"paneltype\": ${PANELTYPE}|g" "/etc/v2ray/config.json"
56 | fi
57 |
58 | if [ ! -z "${usemysql}" ]
59 | then
60 | sed -i "s|\"usemysql\": 0|\"usemysql\": ${usemysql}|g" "/etc/v2ray/config.json"
61 | fi
62 | cat /etc/v2ray/config.json
63 | v2ray -config=/etc/v2ray/config.json
--------------------------------------------------------------------------------
/Docker/arm/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM armv7/armhf-ubuntu:latest as builder
2 | ENV v2ray_version=4.12.0
3 | RUN apt-get install curl -y
4 | RUN curl -L -o /tmp/go.sh https://install.direct/go.sh
5 | RUN chmod +x /tmp/go.sh
6 | RUN /tmp/go.sh --version ${v2ray_version}
7 |
8 | FROM arm32v6/alpine:latest
9 |
10 | COPY --from=builder /usr/bin/v2ray/v2ray /usr/bin/v2ray/
11 | COPY --from=builder /usr/bin/v2ray/v2ctl /usr/bin/v2ray/
12 | COPY --from=builder /usr/bin/v2ray/geoip.dat /usr/bin/v2ray/
13 | COPY --from=builder /usr/bin/v2ray/geosite.dat /usr/bin/v2ray/
14 | COPY --from=builder /etc/v2ray/config.json /etc/v2ray/config.json
15 |
16 | RUN set -ex && \
17 | apk --no-cache add ca-certificates && \
18 | mkdir /var/log/v2ray/ &&\
19 | chmod +x /usr/bin/v2ray/v2ctl && \
20 | chmod +x /usr/bin/v2ray/v2ray
21 |
22 | ENV PATH /usr/bin/v2ray:$PATH
23 |
24 | CMD ["v2ray", "-config=/etc/v2ray/config.json"]
--------------------------------------------------------------------------------
/Manager/Manager.go:
--------------------------------------------------------------------------------
1 | package Manager
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/client"
8 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/model"
9 | "os"
10 | "os/exec"
11 | "os/user"
12 | "strconv"
13 | "v2ray.com/core/common/errors"
14 | "v2ray.com/core/common/net"
15 | "v2ray.com/core/common/serial"
16 | "v2ray.com/core/infra/conf"
17 | "v2ray.com/core/transport/internet"
18 | )
19 |
20 | type Manager struct {
21 | HandlerServiceClient *client.HandlerServiceClient
22 | StatsServiceClient *client.StatsServiceClient
23 | RuleServiceClient *client.RuleServerClient
24 | CurrentNodeInfo *model.NodeInfo
25 | NextNodeInfo *model.NodeInfo
26 | UserChanged bool
27 | UserToBeMoved map[string]model.UserModel
28 | UserToBeAdd map[string]model.UserModel
29 | Users map[string]model.UserModel
30 | Id2PrefixedIdmap map[uint]string
31 | Id2DisServer map[uint]string
32 | MainAddress string
33 | MainListenPort uint16
34 | NodeID uint
35 | CheckRate int
36 | SpeedTestCheckRate int
37 | }
38 |
39 | func homeDir() string {
40 | usr, err := user.Current()
41 | if err != nil {
42 | os.Exit(1)
43 | }
44 | return usr.HomeDir
45 | }
46 |
47 | func (manager *Manager) GetUsers() map[string]model.UserModel {
48 | return manager.Users
49 | }
50 |
51 | func (manager *Manager) Add(user model.UserModel) {
52 | manager.UserChanged = true
53 | manager.UserToBeAdd[user.PrefixedId] = user
54 | }
55 |
56 | func (manager *Manager) Remove(prefixedId string) bool {
57 | user, ok := manager.Users[prefixedId]
58 | if ok {
59 | manager.UserChanged = true
60 | manager.UserToBeMoved[user.PrefixedId] = user
61 | return true
62 | }
63 | return false
64 | }
65 |
66 | func (manager *Manager) UpdataUsers() {
67 | var successfully_removed, successfully_add []string
68 | if manager.CurrentNodeInfo.Server_raw != "" {
69 | if manager.CurrentNodeInfo.Sort == 0 || manager.CurrentNodeInfo.Sort == 13 {
70 | // SS server
71 | /// remove inbounds
72 | for key, value := range manager.UserToBeMoved {
73 | if err := manager.HandlerServiceClient.RemoveInbound(value.PrefixedId); err == nil {
74 | newErrorf("Successfully remove user %s", key).AtInfo().WriteToLog()
75 | successfully_removed = append(successfully_removed, key)
76 | } else {
77 | newError(err).AtDebug().WriteToLog()
78 | successfully_removed = append(successfully_removed, key)
79 | }
80 | if manager.CurrentNodeInfo.Sort == 13 {
81 | newErrorf("Remove AttrMachter %s", value.Muhost).AtInfo().WriteToLog()
82 | manager.HandlerServiceClient.RemoveOutbound("out_" + value.Muhost)
83 | manager.RuleServiceClient.RemveUserAttrMachter("out_" + value.Muhost)
84 | manager.HandlerServiceClient.DelUser(value.Muhost)
85 | }
86 | }
87 | } else if manager.CurrentNodeInfo.Sort == 11 || manager.CurrentNodeInfo.Sort == 12 {
88 | // VMESS
89 | // Remove users
90 | for key, value := range manager.UserToBeMoved {
91 | if err := manager.HandlerServiceClient.DelUser(value.Email); err == nil {
92 | newErrorf("Successfully remove user %s ", key).AtInfo().WriteToLog()
93 | successfully_removed = append(successfully_removed, key)
94 | } else {
95 | newError(err).AtDebug().WriteToLog()
96 | successfully_removed = append(successfully_removed, key)
97 | }
98 | }
99 | }
100 |
101 | }
102 | if manager.NextNodeInfo.Server_raw != "" {
103 | if manager.NextNodeInfo.Sort == 0 || manager.NextNodeInfo.Sort == 13 {
104 | // SS server
105 | /// add inbounds
106 | for key, value := range manager.UserToBeAdd {
107 | var streamsetting *internet.StreamConfig
108 | if manager.NextNodeInfo.Sort == 13 {
109 | newErrorf("ADD WS+SS %s %s", key, value.Muhost).AtInfo().WriteToLog()
110 | streamsetting = client.GetDomainsocketStreamConfig(fmt.Sprintf("/etc/v2ray/%s.sock", value.Muhost))
111 | manager.RuleServiceClient.AddUserAttrMachter("out_"+value.Muhost, fmt.Sprintf("attrs['host'] == '%s'", value.Muhost))
112 | manager.HandlerServiceClient.AddFreedomOutbound("out_"+value.Muhost, streamsetting)
113 | manager.HandlerServiceClient.AddDokodemoUser(value)
114 | }
115 | if err := manager.HandlerServiceClient.AddSSInbound(value, "0.0.0.0", streamsetting); err == nil {
116 | newErrorf("Successfully add user %s ", key).AtInfo().WriteToLog()
117 | successfully_add = append(successfully_add, key)
118 | } else {
119 | newError(err).AtDebug().WriteToLog()
120 | successfully_add = append(successfully_add, key)
121 | }
122 | }
123 | } else if manager.NextNodeInfo.Sort == 11 || manager.NextNodeInfo.Sort == 12 {
124 | // VMESS
125 | // add users
126 | for key, value := range manager.UserToBeAdd {
127 | if err := manager.HandlerServiceClient.AddVmessUser(value); err == nil {
128 | newErrorf("Successfully add user %s ", key).AtInfo().WriteToLog()
129 | successfully_add = append(successfully_add, key)
130 | } else {
131 | newError(err).AtDebug().WriteToLog()
132 | successfully_add = append(successfully_add, key)
133 | }
134 | }
135 | }
136 | }
137 | for index := range successfully_removed {
138 | delete(manager.Users, successfully_removed[index])
139 | delete(manager.UserToBeMoved, successfully_removed[index])
140 | }
141 | for index := range successfully_add {
142 | manager.Users[successfully_add[index]] = manager.UserToBeAdd[successfully_add[index]]
143 | delete(manager.UserToBeAdd, successfully_add[index])
144 | }
145 | manager.Id2PrefixedIdmap = map[uint]string{}
146 | for key, value := range manager.Users {
147 | manager.Id2PrefixedIdmap[value.UserID] = key
148 | }
149 | }
150 |
151 | func (manager *Manager) UpdateMainAddressAndProt(node_info *model.NodeInfo) {
152 | if node_info.Sort == 11 || node_info.Sort == 12 || node_info.Sort == 13 {
153 | if node_info.Server["port"] == "0" || node_info.Server["port"] == "" {
154 | manager.MainAddress = "127.0.0.1"
155 | manager.MainListenPort = 10550
156 | if node_info.Server["inside_port"] != "" {
157 | port, err := strconv.ParseUint(node_info.Server["inside_port"].(string), 10, 0)
158 | if err == nil {
159 | manager.MainListenPort = uint16(port)
160 | }
161 | }
162 | } else {
163 | manager.MainAddress = "0.0.0.0"
164 | manager.MainListenPort = 10550
165 | if node_info.Server["port"] != "" {
166 | port, err := strconv.ParseUint(node_info.Server["port"].(string), 10, 0)
167 | if err == nil {
168 | manager.MainListenPort = uint16(port)
169 | }
170 | }
171 | }
172 | }
173 | }
174 | func (m *Manager) AddCert(server string) (*serial.TypedMessage, error) {
175 | var tlsconfig *conf.TLSConfig
176 | newError("Starting Issuing Tls Cert, please make sure 80 is free").AtInfo().WriteToLog()
177 | //cmd := exec.Command(fmt.Sprintf("command: %s %s %s %s", fmt.Sprintf("%s/.acme.sh/acme.sh", homeDir()), "--issue", fmt.Sprintf("-d %s", server), "--standalone"))
178 | cmd := exec.Command("sh", "-c", fmt.Sprintf("%s/.acme.sh/acme.sh --issue -d %s --standalone", homeDir(), server))
179 | var out bytes.Buffer
180 | var stderr bytes.Buffer
181 | cmd.Stdout = &out
182 | cmd.Stderr = &stderr
183 | cmd.Run()
184 | newError(out.String()).AtInfo().WriteToLog()
185 | newError(stderr.String()).AtInfo().WriteToLog()
186 | tlsconfig = &conf.TLSConfig{
187 | Certs: []*conf.TLSCertConfig{&conf.TLSCertConfig{
188 | CertFile: fmt.Sprintf("%s/.acme.sh/%s/fullchain.cer", homeDir(), server),
189 | KeyFile: fmt.Sprintf("%[1]s/.acme.sh/%[2]s/%[2]s.key", homeDir(), server),
190 | }},
191 | InsecureCiphers: true,
192 | }
193 | cert, err := tlsconfig.Build()
194 | if err != nil {
195 | return nil, newError("Failed to build TLS config.").Base(err)
196 | }
197 | tm := serial.ToTypedMessage(cert)
198 | return tm, nil
199 |
200 | }
201 | func (m *Manager) StopCert(server string) error {
202 | newErrorf("Starting to remove %s from renew list", server).AtInfo().WriteToLog()
203 | cmd := exec.Command("sh", "-c", fmt.Sprintf("%s/.acme.sh/acme.sh --remove -d %s", homeDir(), server))
204 | var out bytes.Buffer
205 | var stderr bytes.Buffer
206 | cmd.Stdout = &out
207 | cmd.Stderr = &stderr
208 | cmd.Run()
209 | newError(out.String()).AtInfo().WriteToLog()
210 | newError(stderr.String()).AtInfo().WriteToLog()
211 | //newErrorf("Starting Remove %s certs", server).AtInfo().WriteToLog()
212 | //cmd = exec.Command("rm", "-rf", fmt.Sprintf("%s/.acme.sh/%s/fullchain.cer", homeDir(), server), fmt.Sprintf("%[1]s/.acme.sh/%[2]s/%[2]s.key", homeDir(), server))
213 | //cmd.Stdout = &out
214 | //cmd.Stderr = &stderr
215 | //cmd.Run()
216 | //newError(out.String()).AtInfo().WriteToLog()
217 | //newError(stderr.String()).AtInfo().WriteToLog()
218 | return nil
219 | }
220 | func (m *Manager) AddMainInbound() error {
221 | if m.NextNodeInfo.Server_raw != "" {
222 | if m.NextNodeInfo.Sort == 13 {
223 | m.UpdateMainAddressAndProt(m.NextNodeInfo)
224 | var streamsetting *internet.StreamConfig
225 | var tm *serial.TypedMessage
226 | if m.NextNodeInfo.Server["protocol"] == "ws" {
227 | host := "www.bing.com"
228 | path := "/"
229 | if m.NextNodeInfo.Server["path"] != "" {
230 | path = m.NextNodeInfo.Server["path"].(string)
231 | }
232 | if m.NextNodeInfo.Server["host"] != "" {
233 | host = m.NextNodeInfo.Server["host"].(string)
234 | }
235 | if m.NextNodeInfo.Server["protocol_param"] == "tls" && m.MainAddress == "0.0.0.0" {
236 | if m.NextNodeInfo.Server["server"] != "" {
237 | tm, _ = m.AddCert(m.NextNodeInfo.Server["server"].(string))
238 | } else if net.ParseAddress(m.NextNodeInfo.Server["server_address"].(string)).Family() == net.AddressFamilyDomain {
239 | tm, _ = m.AddCert(m.NextNodeInfo.Server["server_address"].(string))
240 | }
241 | }
242 | streamsetting = client.GetWebSocketStreamConfig(path, host, tm)
243 | }
244 | if err := m.HandlerServiceClient.AddDokodemoInbound(m.MainListenPort, m.MainAddress, streamsetting); err != nil {
245 | return err
246 | } else {
247 | newErrorf("Successfully add MAIN DokodemoInbound %s port %d", m.MainAddress, m.MainListenPort).AtInfo().WriteToLog()
248 | }
249 | } else if m.NextNodeInfo.Sort == 11 || m.NextNodeInfo.Sort == 12 {
250 | m.UpdateMainAddressAndProt(m.NextNodeInfo)
251 | var streamsetting *internet.StreamConfig
252 | var tm *serial.TypedMessage
253 | //var err error
254 | streamsetting = &internet.StreamConfig{}
255 |
256 | if m.NextNodeInfo.Server["protocol"] == "ws" {
257 | host := "www.bing.com"
258 | path := "/"
259 | if m.NextNodeInfo.Server["path"] != "" {
260 | path = m.NextNodeInfo.Server["path"].(string)
261 | }
262 | if m.NextNodeInfo.Server["host"] != "" {
263 | host = m.NextNodeInfo.Server["host"].(string)
264 | }
265 | if m.NextNodeInfo.Server["protocol_param"] == "tls" && m.MainAddress == "0.0.0.0" {
266 | if m.NextNodeInfo.Server["server"] != "" {
267 | tm, _ = m.AddCert(m.NextNodeInfo.Server["server"].(string))
268 | } else if net.ParseAddress(m.NextNodeInfo.Server["server_address"].(string)).Family() == net.AddressFamilyDomain {
269 | tm, _ = m.AddCert(m.NextNodeInfo.Server["server_address"].(string))
270 | }
271 | }
272 | streamsetting = client.GetWebSocketStreamConfig(path, host, tm)
273 | } else if m.NextNodeInfo.Server["protocol"] == "kcp" || m.NextNodeInfo.Server["protocol"] == "mkcp" {
274 | header_key := "noop"
275 | if m.NextNodeInfo.Server["protocol_param"] != "" {
276 | header_key = m.NextNodeInfo.Server["protocol_param"].(string)
277 | }
278 | streamsetting = client.GetKcpStreamConfig(header_key)
279 | }
280 | if err := m.HandlerServiceClient.AddVmessInbound(m.MainListenPort, m.MainAddress, streamsetting); err != nil {
281 | return err
282 | } else {
283 | newErrorf("Successfully add MAIN INBOUND %s port %d", m.MainAddress, m.MainListenPort).AtInfo().WriteToLog()
284 | }
285 | }
286 |
287 | }
288 | return nil
289 | }
290 | func (m *Manager) AddOuntBound(disnodeinfo *model.DisNodeInfo) error {
291 | if disnodeinfo.Server_raw != "" {
292 | if disnodeinfo.Sort == 11 || disnodeinfo.Sort == 12 {
293 | var streamsetting *internet.StreamConfig
294 | var tm *serial.TypedMessage
295 | streamsetting = &internet.StreamConfig{}
296 |
297 | if disnodeinfo.Server["protocol"] == "ws" {
298 | host := "www.bing.com"
299 | path := "/"
300 | if disnodeinfo.Server["path"] != "" {
301 | path = disnodeinfo.Server["path"].(string)
302 | }
303 | if disnodeinfo.Server["host"] != "" {
304 | host = disnodeinfo.Server["host"].(string)
305 | }
306 | if disnodeinfo.Server["protocol_param"] == "tls" {
307 | tlsconfig := &conf.TLSConfig{
308 | InsecureCiphers: true,
309 | }
310 | cert, _ := tlsconfig.Build()
311 | tm = serial.ToTypedMessage(cert)
312 | }
313 | streamsetting = client.GetWebSocketStreamConfig(path, host, tm)
314 | } else if disnodeinfo.Server["protocol"] == "kcp" || disnodeinfo.Server["protocol"] == "mkcp" {
315 | header_key := "noop"
316 | if disnodeinfo.Server["protocol_param"] != "" {
317 | header_key = disnodeinfo.Server["protocol_param"].(string)
318 | }
319 | streamsetting = client.GetKcpStreamConfig(header_key)
320 | }
321 | if err := m.HandlerServiceClient.AddVmessOutbound(disnodeinfo.Server_raw+fmt.Sprintf("%d", disnodeinfo.UserId), disnodeinfo.Port, disnodeinfo.Server["server_address"].(string), streamsetting, m.HandlerServiceClient.ConvertVmessUser(
322 | m.Users[m.Id2PrefixedIdmap[disnodeinfo.UserId]])); err != nil {
323 | return err
324 | } else {
325 | newErrorf("Successfully add Outbound %s port %d", disnodeinfo.Server_raw+fmt.Sprintf("%d", disnodeinfo.UserId), disnodeinfo.Port).AtInfo().WriteToLog()
326 | }
327 | }
328 | if disnodeinfo.Sort == 0 {
329 | if err := m.HandlerServiceClient.AddSSOutbound(m.Users[m.Id2PrefixedIdmap[disnodeinfo.UserId]], disnodeinfo); err != nil {
330 | return newError("User Chipter %S", m.Users[m.Id2PrefixedIdmap[disnodeinfo.UserId]].Method).Base(err)
331 | } else {
332 | newErrorf("Successfully add user %s outbound %s ", m.Id2PrefixedIdmap[disnodeinfo.UserId], disnodeinfo.Server_raw).AtInfo().WriteToLog()
333 |
334 | }
335 | }
336 | m.AddUserRule(disnodeinfo.Server_raw+fmt.Sprintf("%d", disnodeinfo.UserId), m.Users[m.Id2PrefixedIdmap[disnodeinfo.UserId]].Email)
337 | }
338 |
339 | return nil
340 | }
341 | func (m *Manager) AddUserRule(tag, email string) {
342 | if err := m.RuleServiceClient.AddUserRelyRule(tag, []string{email}); err == nil {
343 | newErrorf("Successfully add user %s %s server rule ", email, tag).AtInfo().WriteToLog()
344 | } else {
345 | newError(err).AtDebug().WriteToLog()
346 | }
347 | }
348 | func (m *Manager) RemoveUserRule(email string) {
349 |
350 | if err := m.RuleServiceClient.RemveUserRelayRule([]string{email}); err == nil {
351 | newErrorf("Successfully remove user %s rule", email).AtInfo().WriteToLog()
352 | } else {
353 | newError(err).AtDebug().WriteToLog()
354 | }
355 | }
356 |
357 | func (m *Manager) RemoveInbound() {
358 | if m.CurrentNodeInfo.Server_raw != "" {
359 | if m.CurrentNodeInfo.Sort == 11 || m.CurrentNodeInfo.Sort == 12 || m.CurrentNodeInfo.Sort == 13 {
360 | m.UpdateMainAddressAndProt(m.CurrentNodeInfo)
361 | if err := m.HandlerServiceClient.RemoveInbound(m.HandlerServiceClient.InboundTag); err != nil {
362 | newError(err).AtWarning().WriteToLog()
363 | } else {
364 | newErrorf("Successfully remove main inbound %s", m.HandlerServiceClient.InboundTag).AtInfo().WriteToLog()
365 | }
366 | if m.CurrentNodeInfo.Server["protocol_param"] == "tls" && m.MainAddress == "0.0.0.0" {
367 | newError("Starting to Rmove Cert").AtInfo().WriteToLog()
368 | if m.CurrentNodeInfo.Server["server"] != "" {
369 | m.StopCert(m.CurrentNodeInfo.Server["server"].(string))
370 | } else if net.ParseAddress(m.CurrentNodeInfo.Server["server_address"].(string)).Family() == net.AddressFamilyDomain {
371 | m.StopCert(m.CurrentNodeInfo.Server["server_address"].(string))
372 | }
373 |
374 | }
375 | }
376 | }
377 | }
378 | func (m *Manager) RemoveOutBound(tag string, userid uint) {
379 | if err := m.HandlerServiceClient.RemoveOutbound(tag); err != nil {
380 | newError(err).AtWarning().WriteToLog()
381 | } else {
382 | newErrorf("Successfully remove outbound %s", tag).AtInfo().WriteToLog()
383 | }
384 | m.RemoveUserRule(m.Users[m.Id2PrefixedIdmap[userid]].Email)
385 | }
386 | func (m *Manager) RemoveAllUserOutBound() {
387 | for id, server := range m.Id2DisServer {
388 | m.RemoveOutBound(server+fmt.Sprintf("%d", id), id)
389 | }
390 | }
391 | func (m *Manager) CopyUsers() {
392 | jsonString, err := json.Marshal(m.Users)
393 | if err != nil {
394 | newError(err).AtWarning().WriteToLog()
395 | }
396 | var usertobemoved map[string]model.UserModel
397 | err = json.Unmarshal(jsonString, &usertobemoved)
398 | if err != nil {
399 | newError(err).AtWarning().WriteToLog()
400 | }
401 | m.UserToBeMoved = usertobemoved
402 | m.UserToBeAdd = map[string]model.UserModel{}
403 | }
404 | func (m *Manager) CopyNodeinfo() {
405 | jsonString, err := json.Marshal(m.NextNodeInfo)
406 | if err != nil {
407 | newError(err).AtWarning().WriteToLog()
408 | }
409 | currentnodeinfo := model.NodeInfo{}
410 | err = json.Unmarshal(jsonString, ¤tnodeinfo)
411 | if err != nil {
412 | newError(err).AtWarning().WriteToLog()
413 | }
414 | m.CurrentNodeInfo = ¤tnodeinfo
415 |
416 | }
417 | func (m *Manager) UpdateServer() error {
418 | m.CopyUsers()
419 | m.UpdataUsers()
420 | m.RemoveInbound()
421 | err := m.AddMainInbound()
422 | m.Users = map[string]model.UserModel{}
423 | return err
424 | }
425 |
426 | func newErrorf(format string, a ...interface{}) *errors.Error {
427 | return newError(fmt.Sprintf(format, a...))
428 | }
429 |
430 | func newError(values ...interface{}) *errors.Error {
431 | values = append([]interface{}{"SSPanelPlugin: "}, values...)
432 | return errors.New(values...)
433 | }
434 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # v2ray-sspanel-v3-mod_Uim-plugin
2 |
3 | ## 公告
4 | 1. 在完成限速功能后,该功能开始将会是收费版本(不在提供源码,只提供二进制文件,需要联系tg @ricobb 获取授权码)
5 | ## Thanks
6 | 1. 感恩的 [ColetteContreras's repo](https://github.com/ColetteContreras/v2ray-ssrpanel-plugin). 让我一个go小白有了下手地。主要起始框架来源于这里
7 | 2. 感恩 [eycorsican](https://github.com/eycorsican) 在v2ray-core [issue](https://github.com/v2ray/v2ray-core/issues/1514), 促成了go版本提上日程
8 |
9 |
10 | # 划重点
11 | 1. 用户务必保证,host 务必填写没有被墙的地址
12 | 2. 目前端口设置为0,才会监听本地,不再是443
13 | 3. 已经适配了中转,必须用我自己维护的[panel](https://github.com/githubphone/ss-panel-v3-mod_Uim)
14 |
15 | ## 项目状态
16 |
17 | 支持 [ss-panel-v3-mod_Uim](https://github.com/NimaQu/ss-panel-v3-mod_Uim) 的 webapi。 目前自己也尝试维护了一个版本, [panel](https://github.com/githubphone/ss-panel-v3-mod_Uim)
18 |
19 | 目前只适配了流量记录、服务器是否在线、在线人数,在线ip上报、负载、中转,后端根据前端的设定自动调用 API 增加用户。
20 |
21 | v2ray 后端 kcp、tcp、ws 都是多用户共用一个端口。
22 |
23 | 也可作为 ss 后端一个用户一个端口。
24 |
25 | ## 已知 Bug
26 |
27 | ## 作为 ss 后端
28 |
29 | 面板配置是节点类型为 Shadowsocks,普通端口。
30 |
31 | 加密方式只支持:
32 |
33 | - [x] aes-256-cfb
34 | - [x] aes-128-cfb
35 | - [x] chacha20
36 | - [x] chacha20-ietf
37 | - [x] aes-256-gcm
38 | - [x] aes-128-gcm
39 | - [x] chacha20-poly1305 或称 chacha20-ietf-poly1305
40 |
41 | ## 作为 V2ray 后端
42 |
43 | 这里面板设置是节点类型v2ray, 普通端口。 v2ray的API接口默认是2333
44 |
45 | 支持 tcp,kcp、ws+(tls 由镜像 Caddy或者ngnix 提供,默认是443接口哦)。或者自己调整。
46 |
47 | [面板设置说明 主要是这个](https://github.com/NimaQu/ss-panel-v3-mod_Uim/wiki/v2ray-%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B)
48 |
49 | ~~~
50 | 没有CDN的域名或者ip;端口(外部链接的);AlterId;协议层;;额外参数(path=/v2ray|host=xxxx.win|inside_port=10550这个端口内部监听))
51 |
52 | // ws 示例
53 | xxxxx.com;10550;4;ws;;path=/v2ray|host=oxxxx.com
54 |
55 | // ws + tls (Caddy 提供)
56 | xxxxx.com;0;4;tls;ws;path=/v2ray|host=oxxxx.com|inside_port=10550
57 | xxxxx.com;;4;tls;ws;path=/v2ray|host=oxxxx.com|inside_port=10550
58 |
59 |
60 |
61 | // nat🐔 ws 示例
62 | xxxxx.com;11120;4;ws;;path=/v2ray|host=oxxxx.com
63 |
64 | // nat🐔 ws + tls (Caddy 提供)
65 | xxxxx.com;0;4;tls;ws;path=/v2ray|host=oxxxx.com|inside_port=10550|outside_port=11120
66 | xxxxx.com;;4;tls;ws;path=/v2ray|host=oxxxx.com|inside_port=10550|outside_port=11120
67 | ~~~
68 |
69 | 目前的逻辑是
70 |
71 | - 如果为外部链接的端口是0或者不填,则默认监听本地127.0.0.1:inside_port
72 | - 如果外部端口设定不是 0或者空,则监听 0.0.0.0:外部设定端口,此端口为所有用户的单端口,此时 inside_port 弃用。
73 | - 默认使用 Caddy 镜像来提供 tls,控制代码不会生成 tls 相关的配置。Caddyfile 可以在Docker/Caddy_V2ray文件夹里面找到。
74 | - Nat🐔,如果要用ws+tls,则需要使用outside_port=xxx,php后端会生成订阅时候,使用outside_port覆盖port部分。 outside_port是内部映射端口,
75 | 建议内网和外网的两个端口数值一致。
76 |
77 | tcp 配置:
78 |
79 | ~~~
80 | xxxxx.com;非0;4;tcp;;
81 | ~~~
82 |
83 | kcp 支持所有 v2ray 的 type:
84 |
85 | - none: 默认值,不进行伪装,发送的数据是没有特征的数据包。
86 |
87 | ~~~
88 | xxxxx.com;非0;4;kcp;noop;
89 | ~~~
90 |
91 | - srtp: 伪装成 SRTP 数据包,会被识别为视频通话数据(如 FaceTime)。
92 |
93 | ~~~
94 | xxxxx.com;非0;4;kcp;srtp;
95 | ~~~
96 |
97 | - utp: 伪装成 uTP 数据包,会被识别为 BT 下载数据。
98 |
99 | ~~~
100 | xxxxx.com;非0;4;kcp;utp;
101 | ~~~
102 |
103 | - wechat-video: 伪装成微信视频通话的数据包。
104 |
105 | ~~~
106 | xxxxx.com;非0;4;kcp;wechat-video;
107 | ~~~
108 |
109 | - dtls: 伪装成 DTLS 1.2 数据包。
110 |
111 | ~~~
112 | xxxxx.com;非0;4;kcp;dtls;
113 | ~~~
114 |
115 | - wireguard: 伪装成 WireGuard 数据包(并不是真正的 WireGuard 协议) 。
116 |
117 | ~~~
118 | xxxxx.com;非0;4;kcp;wireguard;
119 | ~~~
120 |
121 | ### [可选] 安装 BBR
122 |
123 | 看 [Rat的](https://www.moerats.com/archives/387/)
124 | OpenVZ 看这里 [南琴浪](https://github.com/tcp-nanqinlang/wiki/wiki/lkl-haproxy)
125 |
126 | ~~~
127 | wget -N --no-check-certificate "https://raw.githubusercontent.com/chiakge/Linux-NetSpeed/master/tcp.sh" && chmod +x tcp.sh && ./tcp.sh
128 | ~~~
129 |
130 | Ubuntu 18.04 魔改 BBR 暂时有点问题,可使用以下命令安装:
131 |
132 | ~~~
133 | wget -N --no-check-certificate "https://raw.githubusercontent.com/chiakge/Linux-NetSpeed/master/tcp.sh"
134 | apt install make gcc -y
135 | sed -i 's#/usr/bin/gcc-4.9#/usr/bin/gcc#g' '/root/tcp.sh'
136 | chmod +x tcp.sh && ./tcp.sh
137 | ~~~
138 | ### [可选] 增加swap
139 | 整数是M
140 | ~~~
141 | wget https://www.moerats.com/usr/shell/swap.sh && bash swap.sh
142 | ~~~
143 |
144 | ### [推荐] 脚本部署
145 |
146 | #### Docker-compose 安装
147 | 这里一直保持最新版
148 | ~~~
149 | mkdir v2ray-agent && \
150 | cd v2ray-agent && \
151 | curl https://raw.githubusercontent.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/master/install.sh -o install.sh && \
152 | chmod +x install.sh && \
153 | bash install.sh
154 | ~~~
155 |
156 |
157 | #### 普通安装
158 | ##### 安装v2ray
159 | 修改了官方安装脚本
160 | 用脚本指定面板信息,请务必删除原有的config.json, 否则不会更新config.json
161 |
162 | 安装(这里保持最新版本)
163 | ~~~
164 | bash <(curl -L -s https://raw.githubusercontent.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/master/install-release.sh) --panelurl https://xxxx --panelkey xxxx --nodeid 21
165 | ~~~
166 |
167 | 后续升级(如果要更新到最新版本)
168 | ~~~
169 | bash <(curl -L -s https://raw.githubusercontent.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/master/install-release.sh)
170 | ~~~
171 |
172 |
173 | 如果要强制安装某个版本
174 |
175 | ~~~
176 | bash <(curl -L -s https://raw.githubusercontent.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/master/install-release.sh) -f --version 4.12.0
177 | ~~~
178 |
179 |
180 | config.json Example
181 |
182 | ~~~
183 | {
184 | "api": {
185 | "services": [
186 | "HandlerService",
187 | "LoggerService",
188 | "StatsService",
189 | "RuleService"
190 | ],
191 | "tag": "api"
192 | },
193 | "inbounds": [{
194 | "listen": "127.0.0.1",
195 | "port": 2333,
196 | "protocol": "dokodemo-door",
197 | "settings": {
198 | "address": "127.0.0.1"
199 | },
200 | "tag": "api"
201 | }
202 | ],
203 | "log": {
204 | "access": "/var/log/v2ray/access.log",
205 | "error": "/var/log/v2ray/error.log",
206 | "loglevel": "info"
207 | },
208 | "outbounds": [{
209 | "protocol": "freedom",
210 | "settings": {}
211 | },
212 | {
213 | "protocol": "blackhole",
214 | "settings": {},
215 | "tag": "blocked"
216 | }
217 | ],
218 | "policy": {
219 | "levels": {
220 | "0": {
221 | "connIdle": 300,
222 | "downlinkOnly": 5,
223 | "handshake": 4,
224 | "statsUserDownlink": true,
225 | "statsUserUplink": true,
226 | "uplinkOnly": 2
227 | }
228 | },
229 | "system": {
230 | "statsInboundDownlink": false,
231 | "statsInboundUplink": false
232 | }
233 | },
234 | "reverse": {},
235 | "routing": {
236 | "settings": {
237 | "rules": [{
238 | "ip": [
239 | "0.0.0.0/8",
240 | "10.0.0.0/8",
241 | "100.64.0.0/10",
242 | "127.0.0.0/8",
243 | "169.254.0.0/16",
244 | "172.16.0.0/12",
245 | "192.0.0.0/24",
246 | "192.0.2.0/24",
247 | "192.168.0.0/16",
248 | "198.18.0.0/15",
249 | "198.51.100.0/24",
250 | "203.0.113.0/24",
251 | "::1/128",
252 | "fc00::/7",
253 | "fe80::/10"
254 | ],
255 | "outboundTag": "blocked",
256 | "protocol": [
257 | "bittorrent"
258 | ],
259 | "type": "field"
260 | },
261 | {
262 | "inboundTag": [
263 | "api"
264 | ],
265 | "outboundTag": "api",
266 | "type": "field"
267 | },
268 | {
269 | "domain": [
270 | "regexp:(api|ps|sv|offnavi|newvector|ulog\\.imap|newloc)(\\.map|)\\.(baidu|n\\.shifen)\\.com",
271 | "regexp:(.+\\.|^)(360|so)\\.(cn|com)",
272 | "regexp:(.?)(xunlei|sandai|Thunder|XLLiveUD)(.)"
273 | ],
274 | "outboundTag": "blocked",
275 | "type": "field"
276 | }
277 | ]
278 | },
279 | "strategy": "rules"
280 | },
281 | "stats": {},
282 | "sspanel": {
283 | "nodeid": 123456,
284 | "checkRate": 60,
285 | "SpeedTestCheckRate": 6,
286 | "panelUrl": "https://google.com",
287 | "panelKey": "55fUxDGFzH3n",
288 | "downWithPanel": 1,
289 | "mysql": {
290 | "host": "https://bing.com",
291 | "port": 3306,
292 | "user": "demo_user",
293 | "password": "demo_dbpassword",
294 | "dbname": "demo_dbname"
295 | },
296 | "paneltype": 0
297 | }
298 | }
299 |
300 | ~~~
301 | ##### 安装caddy
302 |
303 | 一键安装 caddy 和cf ddns tls插件
304 |
305 | ~~~
306 | curl https://getcaddy.com | bash -s dyndns,tls.dns.cloudflare
307 | ~~~
308 |
309 | Caddyfile
310 |
311 | 自行修改,或者设置对应环境变量
312 |
313 | ~~~
314 | {$V2RAY_DOMAIN}:{$V2RAY_OUTSIDE_PORT}
315 | {
316 | root /srv/www
317 | log ./caddy.log
318 | proxy {$V2RAY_PATH} 127.0.0.1:{$V2RAY_PORT} {
319 | websocket
320 | header_upstream -Origin
321 | }
322 | gzip
323 | tls {$V2RAY_EMAIL} {
324 | protocols tls1.0 tls1.2
325 | # remove comment if u want to use cloudflare ddns
326 | # dns cloudflare
327 | }
328 | }
329 | ~~~
330 |
--------------------------------------------------------------------------------
/client/gRPCClient.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "google.golang.org/grpc"
5 | "time"
6 | )
7 |
8 | func ConnectGRPC(address string, timeoutDuration time.Duration) (conn *grpc.ClientConn, err error) {
9 | timeout := time.After(timeoutDuration)
10 | tick := time.Tick(500 * time.Millisecond)
11 |
12 | for {
13 | select {
14 | case <-timeout:
15 | return
16 | case <-tick:
17 | conn, err = grpc.Dial(address, grpc.WithInsecure())
18 | if err == nil {
19 | return
20 | }
21 | }
22 | }
23 |
24 | return
25 | }
26 |
--------------------------------------------------------------------------------
/client/handlerServiceClient.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/model"
7 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/utility"
8 | "google.golang.org/grpc"
9 | "strings"
10 | "v2ray.com/core"
11 | "v2ray.com/core/app/proxyman"
12 | "v2ray.com/core/app/proxyman/command"
13 | "v2ray.com/core/common/net"
14 | "v2ray.com/core/common/protocol"
15 | "v2ray.com/core/common/serial"
16 | "v2ray.com/core/common/uuid"
17 | "v2ray.com/core/proxy/dokodemo"
18 | "v2ray.com/core/proxy/freedom"
19 | "v2ray.com/core/proxy/mtproto"
20 | "v2ray.com/core/proxy/shadowsocks"
21 | "v2ray.com/core/proxy/vmess"
22 | "v2ray.com/core/proxy/vmess/inbound"
23 | "v2ray.com/core/proxy/vmess/outbound"
24 | "v2ray.com/core/transport/internet"
25 | "v2ray.com/core/transport/internet/domainsocket"
26 | "v2ray.com/core/transport/internet/headers/noop"
27 | "v2ray.com/core/transport/internet/headers/srtp"
28 | "v2ray.com/core/transport/internet/headers/tls"
29 | "v2ray.com/core/transport/internet/headers/utp"
30 | "v2ray.com/core/transport/internet/headers/wechat"
31 | "v2ray.com/core/transport/internet/headers/wireguard"
32 | "v2ray.com/core/transport/internet/kcp"
33 | "v2ray.com/core/transport/internet/websocket"
34 | )
35 |
36 | var KcpHeadMap = map[string]*serial.TypedMessage{
37 | "wechat-video": serial.ToTypedMessage(&wechat.VideoConfig{}),
38 | "srtp": serial.ToTypedMessage(&srtp.Config{}),
39 | "utp": serial.ToTypedMessage(&utp.Config{}),
40 | "wireguard": serial.ToTypedMessage(&wireguard.WireguardConfig{}),
41 | "dtls": serial.ToTypedMessage(&tls.PacketConfig{}),
42 | "noop": serial.ToTypedMessage(&noop.Config{}),
43 | }
44 | var CipherTypeMap = map[string]shadowsocks.CipherType{
45 | "aes-256-cfb": shadowsocks.CipherType_AES_256_CFB,
46 | "aes-128-cfb": shadowsocks.CipherType_AES_128_CFB,
47 | "aes-128-gcm": shadowsocks.CipherType_AES_128_GCM,
48 | "aes-256-gcm": shadowsocks.CipherType_AES_256_GCM,
49 | "chacha20": shadowsocks.CipherType_CHACHA20,
50 | "chacha20-ietf": shadowsocks.CipherType_CHACHA20_IETF,
51 | "chacha20-ploy1305": shadowsocks.CipherType_CHACHA20_POLY1305,
52 | "chacha20-ietf-poly1305": shadowsocks.CipherType_CHACHA20_POLY1305,
53 | "xchacha20-ietf-poly1305": shadowsocks.CipherType_XCHACHA20_POLY1305,
54 | }
55 |
56 | type HandlerServiceClient struct {
57 | command.HandlerServiceClient
58 | InboundTag string
59 | }
60 |
61 | func NewHandlerServiceClient(client *grpc.ClientConn, inboundTag string) *HandlerServiceClient {
62 | return &HandlerServiceClient{
63 | HandlerServiceClient: command.NewHandlerServiceClient(client),
64 | InboundTag: inboundTag,
65 | }
66 | }
67 |
68 | // user
69 | func (h *HandlerServiceClient) DelUser(user string) error {
70 | req := &command.AlterInboundRequest{
71 | Tag: h.InboundTag,
72 | Operation: serial.ToTypedMessage(&command.RemoveUserOperation{Email: user}),
73 | }
74 | return h.AlterInbound(req)
75 | }
76 |
77 | func (h *HandlerServiceClient) AddVmessUser(user model.UserModel) error {
78 | req := &command.AlterInboundRequest{
79 | Tag: h.InboundTag,
80 | Operation: serial.ToTypedMessage(&command.AddUserOperation{User: h.ConvertVmessUser(user)}),
81 | }
82 | return h.AlterInbound(req)
83 | }
84 | func (h *HandlerServiceClient) AddDokodemoUser(user model.UserModel) error {
85 | req := &command.AlterInboundRequest{
86 | Tag: h.InboundTag,
87 | Operation: serial.ToTypedMessage(&command.AddUserOperation{User: h.ConverDokodemoUser(user)}),
88 | }
89 | return h.AlterInbound(req)
90 | }
91 |
92 | func (h *HandlerServiceClient) AlterInbound(req *command.AlterInboundRequest) error {
93 | _, err := h.HandlerServiceClient.AlterInbound(context.Background(), req)
94 | return err
95 | }
96 |
97 | //streaming
98 | func GetKcpStreamConfig(headkey string) *internet.StreamConfig {
99 | var streamsetting internet.StreamConfig
100 | head, _ := KcpHeadMap["noop"]
101 | if _, ok := KcpHeadMap[headkey]; ok {
102 | head, _ = KcpHeadMap[headkey]
103 | }
104 | streamsetting = internet.StreamConfig{
105 | ProtocolName: "mkcp",
106 | TransportSettings: []*internet.TransportConfig{
107 | &internet.TransportConfig{
108 | ProtocolName: "mkcp",
109 | Settings: serial.ToTypedMessage(
110 | &kcp.Config{
111 | HeaderConfig: head,
112 | }),
113 | },
114 | },
115 | }
116 | return &streamsetting
117 | }
118 |
119 | func GetWebSocketStreamConfig(path string, host string, tm *serial.TypedMessage) *internet.StreamConfig {
120 | var streamsetting internet.StreamConfig
121 | if tm == nil {
122 | streamsetting = internet.StreamConfig{
123 | ProtocolName: "websocket",
124 | TransportSettings: []*internet.TransportConfig{
125 | &internet.TransportConfig{
126 | ProtocolName: "websocket",
127 | Settings: serial.ToTypedMessage(&websocket.Config{
128 | Path: path,
129 | Header: []*websocket.Header{
130 | &websocket.Header{
131 | Key: "Hosts",
132 | Value: host,
133 | },
134 | },
135 | }),
136 | },
137 | },
138 | }
139 | } else {
140 | streamsetting = internet.StreamConfig{
141 | ProtocolName: "websocket",
142 | TransportSettings: []*internet.TransportConfig{
143 | &internet.TransportConfig{
144 | ProtocolName: "websocket",
145 | Settings: serial.ToTypedMessage(&websocket.Config{
146 | Path: path,
147 | Header: []*websocket.Header{
148 | &websocket.Header{
149 | Key: "Hosts",
150 | Value: host,
151 | },
152 | },
153 | }),
154 | },
155 | },
156 | SecuritySettings: []*serial.TypedMessage{tm},
157 | SecurityType: tm.Type,
158 | }
159 | }
160 | return &streamsetting
161 | }
162 |
163 | func GetDomainsocketStreamConfig(filepath string) *internet.StreamConfig {
164 | var streamsetting internet.StreamConfig
165 | streamsetting = internet.StreamConfig{
166 | ProtocolName: "domainsocket",
167 | TransportSettings: []*internet.TransportConfig{
168 | &internet.TransportConfig{
169 | ProtocolName: "domainsocket",
170 | Settings: serial.ToTypedMessage(&domainsocket.Config{
171 | Path: filepath,
172 | }),
173 | },
174 | },
175 | }
176 | return &streamsetting
177 | }
178 |
179 | // different type inbounds
180 | func (h *HandlerServiceClient) AddVmessInbound(port uint16, address string, streamsetting *internet.StreamConfig) error {
181 | var addinboundrequest command.AddInboundRequest
182 | addinboundrequest = command.AddInboundRequest{
183 | Inbound: &core.InboundHandlerConfig{
184 | Tag: h.InboundTag,
185 | ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
186 | PortRange: net.SinglePortRange(net.Port(port)),
187 | Listen: net.NewIPOrDomain(net.ParseAddress(address)),
188 | StreamSettings: streamsetting,
189 | }),
190 | ProxySettings: serial.ToTypedMessage(&inbound.Config{
191 | User: []*protocol.User{
192 | {
193 | Level: 0,
194 | Rate: 0,
195 | Email: "githubphone@xxx.com",
196 | Account: serial.ToTypedMessage(&vmess.Account{
197 | Id: protocol.NewID(uuid.New()).String(),
198 | AlterId: 2,
199 | }),
200 | },
201 | },
202 | }),
203 | },
204 | }
205 | return h.AddInbound(&addinboundrequest)
206 | }
207 |
208 | func (h *HandlerServiceClient) AddVmessOutbound(tag string, port uint16, address string, streamsetting *internet.StreamConfig, user *protocol.User) error {
209 | var addoutboundrequest command.AddOutboundRequest
210 | addoutboundrequest = command.AddOutboundRequest{
211 | Outbound: &core.OutboundHandlerConfig{
212 | Tag: tag,
213 | SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
214 | StreamSettings: streamsetting,
215 | }),
216 | ProxySettings: serial.ToTypedMessage(&outbound.Config{
217 | Receiver: []*protocol.ServerEndpoint{
218 | {
219 | Address: net.NewIPOrDomain(net.ParseAddress(address)),
220 | Port: uint32(port),
221 | User: []*protocol.User{
222 | user,
223 | },
224 | },
225 | },
226 | }),
227 | },
228 | }
229 | return h.AddOutbound(&addoutboundrequest)
230 | }
231 |
232 | func (h *HandlerServiceClient) AddSSOutbound(user model.UserModel, dist *model.DisNodeInfo) error {
233 | var addoutboundrequest command.AddOutboundRequest
234 | addoutboundrequest = command.AddOutboundRequest{
235 | Outbound: &core.OutboundHandlerConfig{
236 | Tag: dist.Server_raw + fmt.Sprintf("%d", user.UserID),
237 | ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
238 | Server: []*protocol.ServerEndpoint{
239 | {
240 | Address: net.NewIPOrDomain(net.ParseAddress(dist.Server["server_address"].(string))),
241 | Port: uint32(dist.Port),
242 | User: []*protocol.User{
243 | h.ConverSSUser(user),
244 | },
245 | },
246 | },
247 | }),
248 | },
249 | }
250 | return h.AddOutbound(&addoutboundrequest)
251 | }
252 | func (h *HandlerServiceClient) AddMTInbound(port uint16, address string, streamsetting *internet.StreamConfig) error {
253 | var addinboundrequest command.AddInboundRequest
254 | addinboundrequest = command.AddInboundRequest{
255 | Inbound: &core.InboundHandlerConfig{
256 | Tag: "tg-in",
257 | ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
258 | PortRange: net.SinglePortRange(net.Port(port)),
259 | Listen: net.NewIPOrDomain(net.ParseAddress(address)),
260 | }),
261 | ProxySettings: serial.ToTypedMessage(&mtproto.ServerConfig{
262 | User: []*protocol.User{
263 | {
264 | Level: 0,
265 | Email: "githubphone@xxx.com",
266 | Rate: 0,
267 | Account: serial.ToTypedMessage(&mtproto.Account{
268 | Secret: utility.MD5(utility.GetRandomString(16)),
269 | }),
270 | },
271 | },
272 | }),
273 | },
274 | }
275 | return h.AddInbound(&addinboundrequest)
276 | }
277 | func (h *HandlerServiceClient) AddSSInbound(user model.UserModel, address string, streamsetting *internet.StreamConfig) error {
278 | var addinboundrequest command.AddInboundRequest
279 | addinboundrequest = command.AddInboundRequest{
280 | Inbound: &core.InboundHandlerConfig{
281 | Tag: user.PrefixedId,
282 | ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
283 | PortRange: net.SinglePortRange(net.Port(user.Port)),
284 | Listen: net.NewIPOrDomain(net.ParseAddress(address)),
285 | StreamSettings: streamsetting,
286 | }),
287 | ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{
288 | User: h.ConverSSUser(user),
289 | Network: []net.Network{net.Network_TCP, net.Network_UDP},
290 | }),
291 | },
292 | }
293 | return h.AddInbound(&addinboundrequest)
294 | }
295 |
296 | func (h *HandlerServiceClient) AddDokodemoInbound(port uint16, address string, streamsetting *internet.StreamConfig) error {
297 | var addinboundrequest command.AddInboundRequest
298 | addinboundrequest = command.AddInboundRequest{
299 | Inbound: &core.InboundHandlerConfig{
300 | Tag: h.InboundTag,
301 | ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
302 | PortRange: net.SinglePortRange(net.Port(port)),
303 | StreamSettings: streamsetting,
304 | }),
305 | ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
306 | Address: net.NewIPOrDomain(net.ParseAddress("v1.mux.cool")),
307 | Networks: []net.Network{net.Network_TCP, net.Network_UDP},
308 | FollowRedirect: false,
309 | User: []*protocol.User{
310 | {
311 | Level: 0,
312 | Rate: 0,
313 | Email: "githubphone@xxx.com",
314 | Account: serial.ToTypedMessage(&dokodemo.Account{
315 | Mu_Host: "githubphone",
316 | }),
317 | },
318 | },
319 | }),
320 | },
321 | }
322 | return h.AddInbound(&addinboundrequest)
323 | }
324 | func (h *HandlerServiceClient) AddFreedomOutbound(tag string, streamsetting *internet.StreamConfig) error {
325 | var addoutboundrequest command.AddOutboundRequest
326 | addoutboundrequest = command.AddOutboundRequest{
327 | Outbound: &core.OutboundHandlerConfig{
328 | Tag: tag,
329 | SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
330 | StreamSettings: streamsetting,
331 | }),
332 | ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
333 | },
334 | }
335 | return h.AddOutbound(&addoutboundrequest)
336 | }
337 |
338 | func (h *HandlerServiceClient) AddInbound(req *command.AddInboundRequest) error {
339 | _, err := h.HandlerServiceClient.AddInbound(context.Background(), req)
340 | return err
341 | }
342 | func (h *HandlerServiceClient) AddOutbound(req *command.AddOutboundRequest) error {
343 | _, err := h.HandlerServiceClient.AddOutbound(context.Background(), req)
344 | return err
345 | }
346 | func (h *HandlerServiceClient) RemoveInbound(tag string) error {
347 | req := command.RemoveInboundRequest{
348 | Tag: tag,
349 | }
350 | _, err := h.HandlerServiceClient.RemoveInbound(context.Background(), &req)
351 | return err
352 | }
353 | func (h *HandlerServiceClient) RemoveOutbound(tag string) error {
354 | req := command.RemoveOutboundRequest{
355 | Tag: tag,
356 | }
357 | _, err := h.HandlerServiceClient.RemoveOutbound(context.Background(), &req)
358 | return err
359 | }
360 |
361 | func (h *HandlerServiceClient) ConvertVmessUser(userModel model.UserModel) *protocol.User {
362 | return &protocol.User{
363 | Level: 0,
364 | Email: userModel.Email,
365 | Rate: userModel.Rate,
366 | Account: serial.ToTypedMessage(&vmess.Account{
367 | Id: userModel.Uuid,
368 | AlterId: userModel.AlterId,
369 | SecuritySettings: &protocol.SecurityConfig{
370 | Type: protocol.SecurityType(protocol.SecurityType_value[strings.ToUpper("AUTO")]),
371 | },
372 | }),
373 | }
374 | }
375 | func (h *HandlerServiceClient) ConverDokodemoUser(userModel model.UserModel) *protocol.User {
376 | return &protocol.User{
377 | Level: 0,
378 | Email: userModel.Email,
379 | Rate: userModel.Rate,
380 | Account: serial.ToTypedMessage(&dokodemo.Account{
381 | Mu_Host: userModel.Muhost,
382 | }),
383 | }
384 | }
385 | func (h *HandlerServiceClient) ConverSSUser(userModel model.UserModel) *protocol.User {
386 | return &protocol.User{
387 | Level: 0,
388 | Email: userModel.Email,
389 | Rate: userModel.Rate,
390 | Account: serial.ToTypedMessage(&shadowsocks.Account{
391 | Password: userModel.Passwd,
392 | CipherType: CipherTypeMap[strings.ToLower(userModel.Method)],
393 | Ota: shadowsocks.Account_Auto,
394 | }),
395 | }
396 | }
397 |
398 | func (h *HandlerServiceClient) ConverMTUser(userModel model.UserModel) *protocol.User {
399 | return &protocol.User{
400 | Level: 0,
401 | Email: userModel.Email,
402 | Rate: userModel.Rate,
403 | Account: serial.ToTypedMessage(&mtproto.Account{
404 | Secret: utility.MD5(userModel.Uuid),
405 | }),
406 | }
407 | }
408 |
--------------------------------------------------------------------------------
/client/statsServiceClient.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "google.golang.org/grpc"
7 | "google.golang.org/grpc/status"
8 | "strings"
9 | statsservice "v2ray.com/core/app/stats/command"
10 | )
11 |
12 | type StatsServiceClient struct {
13 | statsservice.StatsServiceClient
14 | }
15 |
16 | func NewStatsServiceClient(client *grpc.ClientConn) *StatsServiceClient {
17 | return &StatsServiceClient{
18 | StatsServiceClient: statsservice.NewStatsServiceClient(client),
19 | }
20 | }
21 |
22 | // traffic
23 | func (s *StatsServiceClient) GetUserUplink(email string) (uint64, error) {
24 | return s.GetUserTraffic(fmt.Sprintf("user>>>%s>>>traffic>>>uplink", email), true)
25 | }
26 |
27 | func (s *StatsServiceClient) GetUserDownlink(email string) (uint64, error) {
28 | return s.GetUserTraffic(fmt.Sprintf("user>>>%s>>>traffic>>>downlink", email), true)
29 | }
30 |
31 | func (s *StatsServiceClient) GetUserTraffic(name string, reset bool) (uint64, error) {
32 | req := &statsservice.GetStatsRequest{
33 | Name: name,
34 | Reset_: reset,
35 | }
36 |
37 | res, err := s.GetStats(context.Background(), req)
38 | if err != nil {
39 | if status, ok := status.FromError(err); ok && strings.HasSuffix(status.Message(), fmt.Sprintf("%s not found.", name)) {
40 | return 0, nil
41 | }
42 |
43 | return 0, err
44 | }
45 |
46 | return uint64(res.Stat.Value), nil
47 | }
48 |
49 | // ips
50 |
51 | func (s *StatsServiceClient) GetUserIPs(email string) ([]string, error) {
52 | name := fmt.Sprintf("user>>>%s>>>traffic>>>ips", email)
53 | req := &statsservice.GetStatsRequest{
54 | Name: name,
55 | Reset_: true,
56 | }
57 |
58 | res, err := s.GetStats(context.Background(), req)
59 |
60 | if err != nil {
61 | if status, ok := status.FromError(err); ok && strings.HasSuffix(status.Message(), fmt.Sprintf("%s not found.", name)) {
62 | return []string{}, nil
63 | }
64 | return []string{}, err
65 | }
66 | ips := strings.Split(res.Stat.Name, ";")
67 | if len(ips) > 1 {
68 | ips = ips[1:]
69 | } else {
70 | ips = []string{}
71 | }
72 | return ips, nil
73 | }
74 |
--------------------------------------------------------------------------------
/client/userRuleServerClient.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc"
6 | ruleservice "v2ray.com/core/app/router/command"
7 | )
8 |
9 | type RuleServerClient struct {
10 | ruleservice.RuleServerClient
11 | }
12 |
13 | func NewRuleServerClient(client *grpc.ClientConn) *RuleServerClient {
14 | return &RuleServerClient{
15 | RuleServerClient: ruleservice.NewRuleServerClient(client),
16 | }
17 | }
18 |
19 | func (s *RuleServerClient) AddUserRelyRule(targettag string, emails []string) error {
20 | _, err := s.AddUserRule(context.Background(), &ruleservice.AddUserRuleRequest{
21 | TargetTag: targettag,
22 | Email: emails,
23 | })
24 | return err
25 | }
26 |
27 | func (s *RuleServerClient) RemveUserRelayRule(email []string) error {
28 | _, err := s.RemoveUserRule(context.Background(), &ruleservice.RemoveUserRequest{
29 | Email: email,
30 | })
31 | return err
32 | }
33 |
34 | func (s *RuleServerClient) AddUserAttrMachter(targettag string, code string) error {
35 | _, err := s.AddAttrMachter(context.Background(), &ruleservice.AddAttrMachterRequest{
36 | TargetTag: targettag,
37 | Code: code,
38 | })
39 | return err
40 | }
41 |
42 | func (s *RuleServerClient) RemveUserAttrMachter(targettag string) error {
43 | _, err := s.RemoveAttrMachter(context.Background(), &ruleservice.RemoveAttrMachterRequest{
44 | TargetTag: targettag,
45 | })
46 | return err
47 | }
48 |
--------------------------------------------------------------------------------
/config/cipher.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "encoding/hex"
8 | "fmt"
9 | "strings"
10 | )
11 |
12 | var ivspec = []byte("0000000000000000")
13 |
14 | const Key = "iKwb6Kt5pnqcVZcd"
15 |
16 | func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
17 | padding := blockSize - len(ciphertext)%blockSize
18 | padtext := bytes.Repeat([]byte{byte(padding)}, padding)
19 | return append(ciphertext, padtext...)
20 | }
21 |
22 | func PKCS5Trimming(encrypt []byte) []byte {
23 | padding := encrypt[len(encrypt)-1]
24 | return encrypt[:len(encrypt)-int(padding)]
25 | }
26 | func AESEncodeStr(src, key string) string {
27 | block, err := aes.NewCipher([]byte(key))
28 | if err != nil {
29 | fmt.Println("key error1", err)
30 | }
31 | if src == "" {
32 | fmt.Println("plain content empty")
33 | }
34 | ecb := cipher.NewCBCEncrypter(block, ivspec)
35 | content := []byte(src)
36 | content = PKCS5Padding(content, block.BlockSize())
37 | crypted := make([]byte, len(content))
38 | ecb.CryptBlocks(crypted, content)
39 | return hex.EncodeToString(crypted)
40 | }
41 |
42 | func AESDecodeStr(crypt, key string) string {
43 | crypted, err := hex.DecodeString(strings.ToLower(crypt))
44 | if err != nil || len(crypted) == 0 {
45 | fmt.Println("plain content empty")
46 | }
47 | block, err := aes.NewCipher([]byte(key))
48 | if err != nil {
49 | fmt.Println("key error1", err)
50 | }
51 | ecb := cipher.NewCBCDecrypter(block, ivspec)
52 | decrypted := make([]byte, len(crypted))
53 | ecb.CryptBlocks(decrypted, crypted)
54 |
55 | return string(PKCS5Trimming(decrypted))
56 | }
57 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "encoding/json"
5 | "flag"
6 | "fmt"
7 | "github.com/go-sql-driver/mysql"
8 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/utility"
9 | "io/ioutil"
10 | "os"
11 | "path/filepath"
12 | "time"
13 | "v2ray.com/core/common/errors"
14 | "v2ray.com/core/common/platform"
15 | "v2ray.com/core/infra/conf"
16 | )
17 |
18 | //var (
19 | // CommandLine = flag.NewFlagSet(os.Args[0]+"-sspanel_v3_mod_Uim_plugin", flag.ContinueOnError)
20 | //
21 | // ConfigFile = CommandLine.String("config", "", "Config file for V2Ray.")
22 | // _ = CommandLine.Bool("version", false, "Show current version of V2Ray.")
23 | // Test = CommandLine.Bool("test", false, "Test config file only, without launching V2Ray server.")
24 | // _ = CommandLine.String("format", "json", "Format of input file.")
25 | // _ = CommandLine.Bool("plugin", false, "True to load plugins.")
26 | //)
27 |
28 | var (
29 | CommandLine = flag.NewFlagSet(os.Args[0]+"-sspanel_v3_mod_Uim_plugin", flag.ContinueOnError)
30 |
31 | ConfigFile = CommandLine.String("config", "", "Config file for V2Ray.")
32 | _ = CommandLine.Bool("version", false, "Show current version of V2Ray.")
33 | Test = CommandLine.Bool("test", false, "Test config file only, without launching V2Ray server.")
34 | _ = CommandLine.String("format", "json", "Format of input file.")
35 | _ = CommandLine.Bool("plugin", false, "True to load plugins.")
36 | )
37 |
38 | type MySQLConfig struct {
39 | Host string `json:"host"`
40 | Port int `json:"port"`
41 | User string `json:"user"`
42 | Password string `json:"password"`
43 | DBName string `json:"dbname"`
44 | }
45 |
46 | func (c *MySQLConfig) FormatDSN() (string, error) {
47 | loc, err := time.LoadLocation("Asia/Shanghai")
48 | if err != nil {
49 | return "", err
50 | }
51 |
52 | cc := &mysql.Config{
53 | Collation: "utf8mb4_unicode_ci",
54 | User: c.User,
55 | Passwd: c.Password,
56 | Loc: loc,
57 | DBName: c.DBName,
58 | Net: "tcp",
59 | Addr: fmt.Sprintf("%s:%d", c.Host, c.Port),
60 | AllowNativePasswords: true,
61 | ParseTime: true,
62 | }
63 |
64 | return cc.FormatDSN(), nil
65 | }
66 |
67 | type Config struct {
68 | NodeID uint `json:"nodeId"`
69 | CheckRate int `json:"checkRate"`
70 | PanelUrl string `json:"panelUrl"`
71 | PanelKey string `json:"panelKey"`
72 | SpeedTestCheckRate int `json:"speedTestCheckrate"`
73 | DownWithPanel int `json:"downWithPanel"`
74 | MySQL *MySQLConfig `json:"mysql"`
75 | Paneltype int `json:"paneltype"`
76 | Usemysql int `json:"usemysql"`
77 | MU_REGEX string `json:"mu_regex"`
78 | MU_SUFFIX string `json:"mu_suffix"`
79 | GoPanelKey string `json:"go_panel_key"`
80 | GoPanelHost string `json:"go_panel_host"`
81 | V2rayConfig *conf.Config
82 | }
83 |
84 | func GetConfig() (*Config, error) {
85 | type config struct {
86 | *conf.Config
87 | SSPanel *Config `json:"sspanel"`
88 | }
89 |
90 | configFile := GetConfigFilePath()
91 | // Open our jsonFile
92 | jsonFile, err := os.Open(configFile)
93 | // if we os.Open returns an error then handle it
94 | if err != nil {
95 | return nil, errors.New("failed to open config: ", configFile).Base(err)
96 | }
97 |
98 | // defer the closing of our jsonFile so that we can parse it later on
99 | defer jsonFile.Close()
100 |
101 | byteValue, _ := ioutil.ReadAll(jsonFile)
102 |
103 | cfg := &config{}
104 | err = json.Unmarshal(byteValue, &cfg)
105 | if err != nil {
106 | return nil, err
107 | }
108 | if cfg.SSPanel != nil {
109 | cfg.SSPanel.V2rayConfig = cfg.Config
110 | if err = CheckCfg(cfg.SSPanel); err != nil {
111 | return nil, err
112 | }
113 | }
114 | return cfg.SSPanel, err
115 | }
116 |
117 | func CheckCfg(cfg *Config) error {
118 |
119 | if cfg.V2rayConfig.Api == nil {
120 | return errors.New("Api must be set")
121 | }
122 |
123 | apiTag := cfg.V2rayConfig.Api.Tag
124 | if len(apiTag) == 0 {
125 | return errors.New("Api tag can't be empty")
126 | }
127 |
128 | services := cfg.V2rayConfig.Api.Services
129 | if !utility.InStr("HandlerService", services) {
130 | return errors.New("Api service, HandlerService, must be enabled")
131 | }
132 | if !utility.InStr("StatsService", services) {
133 | return errors.New("Api service, StatsService, must be enabled")
134 | }
135 |
136 | if cfg.V2rayConfig.Stats == nil {
137 | return errors.New("Stats must be enabled")
138 | }
139 |
140 | if apiInbound := GetInboundConfigByTag(apiTag, cfg.V2rayConfig.InboundConfigs); apiInbound == nil {
141 | return errors.New(fmt.Sprintf("Miss an inbound tagged %s", apiTag))
142 | } else if apiInbound.Protocol != "dokodemo-door" {
143 | return errors.New(fmt.Sprintf("The protocol of inbound tagged %s must be \"dokodemo-door\"", apiTag))
144 | } else {
145 | if apiInbound.ListenOn == nil || apiInbound.PortRange == nil {
146 | return errors.New(fmt.Sprintf("Fields, \"listen\" and \"port\", of inbound tagged %s must be set", apiTag))
147 | }
148 | }
149 |
150 | return nil
151 | }
152 |
153 | func GetInboundConfigByTag(apiTag string, inbounds []conf.InboundDetourConfig) *conf.InboundDetourConfig {
154 | for _, inbound := range inbounds {
155 | if inbound.Tag == apiTag {
156 | return &inbound
157 | }
158 | }
159 | return nil
160 | }
161 |
162 | func GetConfigFilePath() string {
163 | if len(*ConfigFile) > 0 {
164 | return *ConfigFile
165 | }
166 |
167 | if workingDir, err := os.Getwd(); err == nil {
168 | configFile := filepath.Join(workingDir, "config.json")
169 | if fileExists(configFile) {
170 | return configFile
171 | }
172 | }
173 |
174 | if configFile := platform.GetConfigurationPath(); fileExists(configFile) {
175 | return configFile
176 | }
177 |
178 | return ""
179 | }
180 |
181 | func fileExists(file string) bool {
182 | info, err := os.Stat(file)
183 | return err == nil && !info.IsDir()
184 | }
185 |
--------------------------------------------------------------------------------
/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "fmt"
7 | "github.com/imroc/req"
8 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/model"
9 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/speedtest"
10 | "regexp"
11 | "strconv"
12 | "strings"
13 | )
14 |
15 | func Min(x, y int64) int64 {
16 | if x < y {
17 | return x
18 | }
19 | return y
20 | }
21 |
22 | type NodeinfoResponse struct {
23 | Ret uint `json:"ret"`
24 | Data *model.NodeInfo `json:"data"`
25 | }
26 | type PostResponse struct {
27 | Ret uint `json:"ret"`
28 | Data string `json:"data"`
29 | }
30 | type UsersResponse struct {
31 | Ret uint `json:"ret"`
32 | Data []model.UserModel `json:"data"`
33 | }
34 | type AllUsers struct {
35 | Ret uint
36 | Data map[string]model.UserModel
37 | }
38 |
39 | type DisNodenfoResponse struct {
40 | Ret uint `json:"ret"`
41 | Data []*model.DisNodeInfo `json:"data"`
42 | }
43 | type AuthResponse struct {
44 | Ret uint `json:"ret"`
45 | Token string `json:"token"`
46 | }
47 |
48 | var id2string = map[uint]string{
49 | 0: "server_address",
50 | 1: "port",
51 | 2: "alterid",
52 | 3: "protocol",
53 | 4: "protocol_param",
54 | 5: "path",
55 | 6: "host",
56 | 7: "inside_port",
57 | 8: "server",
58 | }
59 | var maps = map[string]interface{}{
60 | "server_address": "",
61 | "port": "",
62 | "alterid": "2",
63 | "protocol": "tcp",
64 | "protocol_param": "",
65 | "path": "",
66 | "host": "",
67 | "inside_port": "",
68 | "server": "",
69 | }
70 |
71 | func getMD5(data string) string {
72 | md5Ctx := md5.New()
73 | md5Ctx.Write([]byte(data))
74 | cipherStr := md5Ctx.Sum(nil)
75 | current_md5 := hex.EncodeToString(cipherStr)
76 | return current_md5
77 | }
78 | func get_mu_host(id uint, md5 string, MU_REGEX string, MU_SUFFIX string) string {
79 | regex_text := MU_REGEX
80 | regex_text = strings.Replace(regex_text, "%id", fmt.Sprintf("%d", id), -1)
81 | regex_text = strings.Replace(regex_text, "%suffix", MU_SUFFIX, -1)
82 | regex := regexp.MustCompile(`%-?[1-9]\d*m`)
83 | for _, item := range regex.FindAllString(regex_text, -1) {
84 | regex_num := strings.Replace(item, "%", "", -1)
85 | regex_num = strings.Replace(regex_num, "m", "", -1)
86 | md5_length, _ := strconv.ParseInt(regex_num, 10, 0)
87 | if md5_length < 0 {
88 | regex_text = strings.Replace(regex_text, item, md5[32+md5_length:], -1)
89 | } else {
90 | regex_text = strings.Replace(regex_text, item, md5[:md5_length], -1)
91 | }
92 | }
93 | return regex_text
94 | }
95 |
96 | type Db interface {
97 | GetApi(url string, params map[string]interface{}) (*req.Resp, error)
98 |
99 | GetNodeInfo(nodeid uint) (*NodeinfoResponse, error)
100 |
101 | GetDisNodeInfo(nodeid uint) (*DisNodenfoResponse, error)
102 |
103 | GetALLUsers(info *model.NodeInfo) (*AllUsers, error)
104 |
105 | Post(url string, params map[string]interface{}, data map[string]interface{}) (*req.Resp, error)
106 |
107 | UploadSystemLoad(nodeid uint) bool
108 |
109 | UpLoadUserTraffics(nodeid uint, trafficLog []model.UserTrafficLog) bool
110 | UploadSpeedTest(nodeid uint, speedresult []speedtest.Speedresult) bool
111 | UpLoadOnlineIps(nodeid uint, onlineIPS []model.UserOnLineIP) bool
112 | }
113 |
--------------------------------------------------------------------------------
/db/mysql.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "fmt"
5 | "github.com/jinzhu/gorm"
6 | _ "github.com/jinzhu/gorm/dialects/mysql"
7 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/config"
8 | "os"
9 | "time"
10 | "v2ray.com/core/common/errors"
11 | )
12 |
13 | func NewMySQLConn(config *config.MySQLConfig) (*gorm.DB, error) {
14 | newError("Connecting database...").AtInfo().WriteToLog()
15 | defer newError("Connected").AtInfo().WriteToLog()
16 |
17 | dsn, err := config.FormatDSN()
18 | if err != nil {
19 | return nil, err
20 | }
21 |
22 | db, err := gorm.Open("mysql", dsn)
23 | if err != nil {
24 | return nil, err
25 | }
26 | db.SingularTable(true)
27 |
28 | return db, nil
29 | }
30 |
31 | func newErrorf(format string, a ...interface{}) *errors.Error {
32 | return newError(fmt.Sprintf(format, a...))
33 | }
34 |
35 | func newError(values ...interface{}) *errors.Error {
36 | values = append([]interface{}{"SSPanelPlugin: "}, values...)
37 | return errors.New(values...)
38 | }
39 |
40 | func fatal(values ...interface{}) {
41 | newError(values...).AtError().WriteToLog()
42 | // Wait log
43 | time.Sleep(1 * time.Second)
44 | os.Exit(-2)
45 | }
46 |
--------------------------------------------------------------------------------
/db/sspanel.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "fmt"
5 | "github.com/gofrs/uuid"
6 | "github.com/imroc/req"
7 | "github.com/jinzhu/gorm"
8 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/model"
9 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/speedtest"
10 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/utility"
11 | "math"
12 | "strconv"
13 | "strings"
14 | "time"
15 | )
16 |
17 | type SSpanel struct {
18 | Db *gorm.DB
19 | MU_REGEX string
20 | MU_SUFFIX string
21 | }
22 |
23 | func (api *SSpanel) GetApi(url string, params map[string]interface{}) (*req.Resp, error) {
24 | return nil, nil
25 | }
26 |
27 | func (api *SSpanel) GetNodeInfo(nodeid uint) (*NodeinfoResponse, error) {
28 | var response = NodeinfoResponse{Data: &model.NodeInfo{}}
29 | var nodeinfotable SSNode
30 | err := api.Db.First(&nodeinfotable, "id = ?", nodeid).Error
31 | if err != nil {
32 | return nil, newError("nodeinfo not find,please check your nodeid or mysql setting").Base(err)
33 | }
34 | response.Ret = 1
35 | response.Data.Server_raw = nodeinfotable.Server
36 | response.Data.Sort = nodeinfotable.Sort
37 | response.Data.NodeSpeedlimit = uint(nodeinfotable.NodeSpeedlimit)
38 | response.Data.TrafficRate = nodeinfotable.TrafficRate
39 | response.Data.NodeID = nodeinfotable.Id
40 | if response.Data.Server_raw != "" {
41 | response.Data.Server_raw = strings.ToLower(response.Data.Server_raw)
42 | data := strings.Split(response.Data.Server_raw, ";")
43 | var count uint
44 | count = 0
45 | for v := range data {
46 | if len(data[v]) > 0 {
47 | maps[id2string[count]] = data[v]
48 | }
49 | count += 1
50 | }
51 | var extraArgues []string
52 | if len(data) == 6 {
53 | extraArgues = append(extraArgues, strings.Split(data[5], "|")...)
54 | for item := range extraArgues {
55 | data = strings.Split(extraArgues[item], "=")
56 | if len(data) > 0 {
57 | if len(data[1]) > 0 {
58 | maps[data[0]] = data[1]
59 | }
60 |
61 | }
62 | }
63 | }
64 |
65 | if maps["protocol"] == "tls" {
66 | temp := maps["protocol_param"]
67 | maps["protocol"] = temp
68 | maps["protocol_param"] = "tls"
69 | }
70 | response.Data.Server = maps
71 | }
72 | response.Data.NodeID = nodeid
73 | return &response, nil
74 | }
75 |
76 | func (api *SSpanel) GetDisNodeInfo(nodeid uint) (*DisNodenfoResponse, error) {
77 | var response = DisNodenfoResponse{}
78 | var rules []Relay
79 |
80 | err := api.Db.Find(&rules, "source_node_id =?", nodeid).Error
81 | if err != nil {
82 | return nil, newError("no relay rule ,please check your nodeid or mysql setting").Base(err)
83 | }
84 | response.Ret = 1
85 | for index := range rules {
86 | rule := rules[index]
87 | if rule.DistNodeId == -1 {
88 | continue
89 | }
90 | var dis SSNode
91 | err = api.Db.First(&dis, "id = ?", rule.DistNodeId).Error
92 | if err != nil {
93 | continue
94 | } else {
95 | disnode := model.DisNodeInfo{
96 | Server_raw: dis.Server,
97 | Sort: dis.Sort,
98 | Port: uint16(rule.Port),
99 | UserId: rule.Userid}
100 | response.Data = append(response.Data, &disnode)
101 | }
102 | }
103 | if len(response.Data) > 0 {
104 | for _, relayrule := range response.Data {
105 | relayrule.Server_raw = strings.ToLower(relayrule.Server_raw)
106 | data := strings.Split(relayrule.Server_raw, ";")
107 | var count uint
108 | count = 0
109 | for v := range data {
110 | if len(data[v]) > 0 {
111 | maps[id2string[count]] = data[v]
112 | }
113 | count += 1
114 | }
115 | var extraArgues []string
116 | if len(data) == 6 {
117 | extraArgues = append(extraArgues, strings.Split(data[5], "|")...)
118 | for item := range extraArgues {
119 | data = strings.Split(extraArgues[item], "=")
120 | if len(data) > 0 {
121 | if len(data[1]) > 0 {
122 | maps[data[0]] = data[1]
123 | }
124 |
125 | }
126 | }
127 | }
128 |
129 | if maps["protocol"] == "tls" {
130 | temp := maps["protocol_param"]
131 | maps["protocol"] = temp
132 | maps["protocol_param"] = "tls"
133 | }
134 | relayrule.Server = maps
135 | }
136 | }
137 | return &response, nil
138 | }
139 |
140 | func (api *SSpanel) GetALLUsers(info *model.NodeInfo) (*AllUsers, error) {
141 | sort := info.Sort
142 | var prifix string
143 | var allusers = AllUsers{
144 | Data: map[string]model.UserModel{},
145 | }
146 | if sort == 0 {
147 | prifix = "SS_"
148 | } else {
149 | prifix = "Vmess_"
150 | if info.Server["protocol"] == "tcp" {
151 | prifix += "tcp_"
152 | } else if info.Server["protocol"] == "ws" {
153 | if info.Server["protocol_param"] != "" {
154 | prifix += "ws_" + info.Server["protocol_param"].(string) + "_"
155 | } else {
156 | prifix += "ws_" + "none" + "_"
157 | }
158 | } else if info.Server["protocol"] == "kcp" {
159 | if info.Server["protocol_param"] != "" {
160 | prifix += "kcp_" + info.Server["protocol_param"].(string) + "_"
161 | } else {
162 | prifix += "kcp_" + "none" + "_"
163 | }
164 | }
165 | }
166 | var response = UsersResponse{}
167 | var node SSNode
168 | err := api.Db.First(&node, "id = ?", info.NodeID).Error
169 | if err != nil {
170 | return nil, err
171 | }
172 | node.NodeHeartbeat = time.Now().Unix()
173 | api.Db.Save(&node)
174 |
175 | var users []User
176 | time1 := time.Now()
177 | location, _ := time.LoadLocation("Asia/Shanghai")
178 | t_in := time1.In(location).Format("2006-01-02 15:04:05")
179 | if node.NodeGroup != 0 {
180 | err := api.Db.Where("class >= ? AND node_group = ?", node.NodeClass, node.NodeGroup).Or("is_admin = 1").Where("enable =1 AND expire_in > ?", t_in).Find(&users).Error
181 | if err != nil {
182 | return nil, err
183 | }
184 | } else {
185 | err := api.Db.Where("class >= ?", node.NodeClass).Or("is_admin = 1").Where("enable =1 AND expire_in > ?", t_in).Find(&users).Error
186 | if err != nil {
187 | return nil, err
188 | }
189 | }
190 | response.Ret = 1
191 |
192 | var filterd_user []model.UserModel
193 | for index := range users {
194 | user := users[index]
195 | if user.TransferEnable > user.Download+user.Upload {
196 | filterd_user = append(filterd_user, model.UserModel{
197 | UserID: user.ID,
198 | Uuid: uuid.NewV3(uuid.NamespaceDNS, fmt.Sprintf("%d|%s", user.ID, user.Passwd)).String(),
199 | Email: user.Email,
200 | Passwd: user.Passwd,
201 | Method: user.Method,
202 | Port: user.Port,
203 | NodeSpeedlimit: uint(user.NodeSpeedlimit),
204 | Obfs: user.Obfs,
205 | Protocol: user.Protocol,
206 | })
207 | }
208 | }
209 | if node.NodeBrandwithLimit != 0 {
210 | if node.NodeBrandwithLimit < node.NodeBrandwith {
211 | response.Data = []model.UserModel{}
212 | } else {
213 | response.Data = filterd_user
214 | }
215 | } else {
216 | response.Data = filterd_user
217 | }
218 | for index := range response.Data {
219 | // 按照node 限速来调整用户限速
220 | if info.NodeSpeedlimit != 0 {
221 | if info.NodeSpeedlimit > response.Data[index].NodeSpeedlimit && response.Data[index].NodeSpeedlimit != 0 {
222 | } else if response.Data[index].NodeSpeedlimit == 0 || response.Data[index].NodeSpeedlimit > info.NodeSpeedlimit {
223 | response.Data[index].NodeSpeedlimit = info.NodeSpeedlimit
224 | }
225 | }
226 | // 接受到的是 Mbps, 然后我们的一个buffer 是2048byte, 差不多61个
227 | response.Data[index].Rate = uint32(response.Data[index].NodeSpeedlimit * 62)
228 | if info.Server["alterid"].(string) == "" {
229 | response.Data[index].AlterId = 2
230 | } else {
231 | alterid, err := strconv.ParseUint(info.Server["alterid"].(string), 10, 0)
232 | if err == nil {
233 | response.Data[index].AlterId = uint32(alterid)
234 | }
235 | }
236 | user := response.Data[index]
237 | response.Data[index].Muhost = get_mu_host(user.UserID, getMD5(fmt.Sprintf("%d%s%s%s%s", user.UserID, user.Passwd, user.Method, user.Obfs, user.Protocol)), api.MU_REGEX, api.MU_SUFFIX)
238 | key := prifix + response.Data[index].Email + fmt.Sprintf("Rate_%d_AlterID_%d_Method_%s_Passwd_%s_Port_%d_Obfs_%s_Protocol_%s", response.Data[index].Rate,
239 | response.Data[index].AlterId, response.Data[index].Method, response.Data[index].Passwd, response.Data[index].Port, response.Data[index].Obfs, response.Data[index].Protocol,
240 | )
241 | response.Data[index].PrefixedId = key
242 | allusers.Data[key] = response.Data[index]
243 | }
244 | return &allusers, nil
245 | }
246 |
247 | func (api *SSpanel) Post(url string, params map[string]interface{}, data map[string]interface{}) (*req.Resp, error) {
248 |
249 | return nil, nil
250 | }
251 |
252 | func (api *SSpanel) UploadSystemLoad(nodeid uint) bool {
253 | uptime, _ := strconv.ParseFloat(utility.GetSystemUptime(), 64)
254 | err := api.Db.Create(&SSNodeInfo{
255 | NodeId: nodeid,
256 | Load: utility.GetSystemLoad(),
257 | Uptime: uptime,
258 | Logtime: time.Now().Unix(),
259 | }).Error
260 | if err != nil {
261 | return false
262 | }
263 | return true
264 | }
265 |
266 | func (api *SSpanel) flowAutoShow(value float64) string {
267 | value_float := value
268 | kb := 1024.0
269 | mb := 1048576.0
270 | gb := 1073741824.0
271 | tb := gb * 1024.0
272 | pb := tb * 1024.0
273 | if math.Abs(value_float) > pb {
274 | return fmt.Sprintf("%.2fPB", math.Round(value_float/pb))
275 | } else if math.Abs(value_float) > tb {
276 | return fmt.Sprintf("%.2fTB", math.Round(value_float/tb))
277 | } else if math.Abs(value_float) > gb {
278 | return fmt.Sprintf("%.2fGB", math.Round(value_float/gb))
279 | } else if math.Abs(value_float) > mb {
280 | return fmt.Sprintf("%.2fMB", math.Round(value_float/mb))
281 | } else if math.Abs(value_float) > kb {
282 | return fmt.Sprintf("%.2fKB", math.Round(value_float/kb))
283 | } else {
284 | return fmt.Sprintf("%.2fB", math.Round(value_float))
285 | }
286 | }
287 |
288 | func (api *SSpanel) UpLoadUserTraffics(nodeid uint, trafficLog []model.UserTrafficLog) bool {
289 | var ssnode SSNode
290 | err := api.Db.First(&ssnode, "id = ?", nodeid).Error
291 | if err != nil {
292 | return false
293 | }
294 | var this_time_total_bandwidth uint64 = 0
295 | if len(trafficLog) > 0 {
296 | for index := range trafficLog {
297 | traffic := trafficLog[index]
298 |
299 | var user User
300 | err := api.Db.First(&user, "id = ?", traffic.UserID).Error
301 |
302 | if err != nil {
303 | continue
304 | }
305 | user.Time = time.Now().Unix()
306 | user.Upload += int64(float64(traffic.Uplink) * ssnode.TrafficRate)
307 | user.Download += int64(float64(traffic.Downlink) * ssnode.TrafficRate)
308 | this_time_total_bandwidth += traffic.Uplink + traffic.Downlink
309 | api.Db.Save(&user)
310 |
311 | api.Db.Save(&UserTrafficLog{
312 | Userid: traffic.UserID,
313 | Upload: traffic.Uplink,
314 | Download: traffic.Downlink,
315 | Nodeid: nodeid,
316 | Rate: ssnode.TrafficRate,
317 | Logtime: time.Now().Unix(),
318 | Traffic: api.flowAutoShow(float64(traffic.Uplink+traffic.Downlink) * ssnode.TrafficRate),
319 | })
320 | }
321 | }
322 | ssnode.NodeBrandwith += this_time_total_bandwidth
323 | api.Db.Save(&ssnode)
324 | api.Db.Create(&SsNodeOnlineLog{
325 | NodeId: nodeid,
326 | OnlineUser: uint(len(trafficLog)),
327 | Logtime: time.Now().Unix(),
328 | })
329 | return true
330 | }
331 | func (api *SSpanel) UploadSpeedTest(nodeid uint, speedresult []speedtest.Speedresult) bool {
332 | var ssnode SSNode
333 | err := api.Db.First(&ssnode, "id = ?", nodeid).Error
334 | if err != nil {
335 | return false
336 | }
337 |
338 | if len(speedresult) > 0 {
339 | for index := range speedresult {
340 | speed := speedresult[index]
341 | api.Db.Create(&Speedtest{
342 | Nodeid: nodeid,
343 | Datetime: time.Now().Unix(),
344 | Telecomping: speed.CTPing,
345 | Telecomeupload: speed.CTUpSpeed,
346 | Telecomedownload: speed.CTDLSpeed,
347 | Unicomping: speed.CUPing,
348 | Unicomupload: speed.CUUpSpeed,
349 | Unicomdownload: speed.CUDLSpeed,
350 | Cmccping: speed.CMPing,
351 | Cmccupload: speed.CMUpSpeed,
352 | Cmccdownload: speed.CMDLSpeed,
353 | })
354 | }
355 | }
356 | return true
357 | }
358 | func (api *SSpanel) UpLoadOnlineIps(nodeid uint, onlineIPS []model.UserOnLineIP) bool {
359 | var ssnode SSNode
360 | err := api.Db.First(&ssnode, "id = ?", nodeid).Error
361 | if err != nil {
362 | return false
363 | }
364 | if len(onlineIPS) > 0 {
365 | for index := range onlineIPS {
366 | ip := onlineIPS[index]
367 | api.Db.Create(&AliveIP{
368 | Nodeid: nodeid,
369 | Userid: ip.UserId,
370 | Ip: ip.Ip,
371 | Datetime: time.Now().Unix(),
372 | })
373 | }
374 | }
375 | return true
376 | }
377 |
378 | func (api *SSpanel) CheckAuth(url string, params map[string]interface{}) (*AuthResponse, error) {
379 | var response = AuthResponse{}
380 | parm := req.Param{}
381 | for k, v := range params {
382 | parm[k] = v
383 | }
384 | r, err := req.Get(url, parm)
385 | if err != nil {
386 | return nil, err
387 | } else {
388 | err = r.ToJSON(&response)
389 | if err != nil {
390 | return &response, err
391 | } else if response.Ret != 1 {
392 | return nil, err
393 | }
394 | }
395 | return &response, nil
396 | }
397 |
--------------------------------------------------------------------------------
/db/sspaneltabel.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | type AliveIP struct {
8 | Nodeid uint `gorm:"column:nodeid"`
9 | Userid uint `gorm:"column:userid"`
10 | Ip string `gorm:"column:ip"`
11 | Datetime int64 `gorm:"column:datetime"`
12 | }
13 |
14 | func (*AliveIP) TableName() string {
15 | return "alive_ip"
16 | }
17 |
18 | type Speedtest struct {
19 | Nodeid uint `gorm:"column:nodeid"`
20 | Datetime int64 `gorm:"column:datetime"`
21 | Telecomping string `gorm:"column:telecomping"`
22 | Telecomeupload string `gorm:"column:telecomeupload"`
23 | Telecomedownload string `gorm:"column:telecomedownload"`
24 | Unicomping string `gorm:"column:unicomping"`
25 | Unicomupload string `gorm:"column:unicomupload"`
26 | Unicomdownload string `gorm:"column:unicomdownload"`
27 | Cmccping string `gorm:"column:cmccping"`
28 | Cmccupload string `gorm:"column:cmccupload"`
29 | Cmccdownload string `gorm:"column:cmccdownload"`
30 | }
31 |
32 | func (*Speedtest) TableName() string {
33 | return "speedtest"
34 | }
35 |
36 | type SSNode struct {
37 | Id uint `gorm:"column:id"`
38 | Name string `gorm:"column:name"`
39 | Type uint `gorm:"column:type"`
40 | Server string `gorm:"column:server"`
41 | Method string `gorm:"column:method"`
42 | Info string `gorm:"column:info"`
43 | Status string `gorm:"column:status"`
44 | Sort uint `gorm:"column:sort"`
45 | CustomMethod uint `gorm:"column:custom_method"`
46 | TrafficRate float64 `gorm:"column:traffic_rate"`
47 | NodeClass uint `gorm:"column:node_class"`
48 | NodeSpeedlimit float64 `gorm:"column:node_speedlimit"`
49 | NodeConnector uint `gorm:"column:node_connector"`
50 | NodeBrandwith uint64 `gorm:"column:node_bandwidth"`
51 | NodeBrandwithLimit uint64 `gorm:"column:node_bandwidth_limit"`
52 | BrandwithlimitRestday uint `gorm:"column:bandwidthlimit_resetday"`
53 | NodeHeartbeat int64 `gorm:"column:node_heartbeat"`
54 | NodeIp string `gorm:"column:node_ip"`
55 | NodeGroup uint `gorm:"column:node_group"`
56 | }
57 |
58 | func (*SSNode) TableName() string {
59 | return "ss_node"
60 | }
61 |
62 | type SSNodeInfo struct {
63 | NodeId uint `gorm:"column:node_id"`
64 | Uptime float64 `gorm:"column:uptime"`
65 | Load string `gorm:"column:load"`
66 | Logtime int64 `gorm:"column:log_time"`
67 | }
68 |
69 | func (*SSNodeInfo) TableName() string {
70 | return "ss_node_info"
71 | }
72 |
73 | type SsNodeOnlineLog struct {
74 | NodeId uint `gorm:"column:node_id"`
75 | OnlineUser uint `gorm:"column:online_user"`
76 | Logtime int64 `gorm:"column:log_time"`
77 | }
78 |
79 | func (*SsNodeOnlineLog) TableName() string {
80 | return "ss_node_online_log"
81 | }
82 |
83 | type User struct {
84 | ID uint `gorm:"column:id"`
85 | Email string `gorm:"column:email"`
86 | Passwd string `gorm:"column:passwd"`
87 | Method string `gorm:"column:method"`
88 | Port uint16 `gorm:"column:port"`
89 | NodeSpeedlimit float64 `gorm:"column:node_speedlimit"`
90 | Upload int64 `gorm:"column:u"`
91 | Download int64 `gorm:"column:d"`
92 | Enable uint `gorm:"column:enable"`
93 | ExpireTime uint `gorm:"column:expire_time"`
94 | IsAdmin uint `gorm:"column:is_admin"`
95 | Class uint `gorm:"column:class"`
96 | ExpireIN time.Time `gorm:"colum:expire_in"`
97 | TransferEnable int64 `gorm:"column:transfer_enable"`
98 | Time int64 `gorm:"column:t"`
99 | Obfs string `gorm:"column:obfs"`
100 | Protocol string `gorm:"column:protocol"`
101 | }
102 |
103 | func (*User) TableName() string {
104 | return "user"
105 | }
106 |
107 | type UserTrafficLog struct {
108 | Userid uint `gorm:"column:user_id"`
109 | Upload uint64 `gorm:"column:u"`
110 | Download uint64 `gorm:"column:d"`
111 | Nodeid uint `gorm:"column:node_id"`
112 | Rate float64 `gorm:"column:rate"`
113 | Logtime int64 `gorm:"column:log_time"`
114 | Traffic string `gorm:"column:traffic"`
115 | }
116 |
117 | func (*UserTrafficLog) TableName() string {
118 | return "user_traffic_log"
119 | }
120 |
121 | type Relay struct {
122 | ID uint `gorm:"column:id"`
123 | Userid uint `gorm:"column:user_id"`
124 | SourceNodeId uint `gorm:"column:source_node_id"`
125 | DistNodeId int `gorm:"column:dist_node_id"`
126 | DistIp string `gorm:"column:dist_ip"`
127 | Port uint `gorm:"column:port"`
128 | Priority uint `gorm:"column:priority"`
129 | }
130 |
131 | func (*Relay) TableName() string {
132 | return "relay"
133 | }
134 |
--------------------------------------------------------------------------------
/db/ssrpanel.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/imroc/req"
7 | "github.com/jinzhu/gorm"
8 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/model"
9 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/speedtest"
10 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/utility"
11 | "math"
12 | "strconv"
13 | "time"
14 | )
15 |
16 | type SSRpanel struct {
17 | Db *gorm.DB
18 | MU_REGEX string
19 | MU_SUFFIX string
20 | }
21 |
22 | func (api *SSRpanel) GetApi(url string, params map[string]interface{}) (*req.Resp, error) {
23 | return nil, nil
24 | }
25 |
26 | func (api *SSRpanel) GetNodeInfo(nodeid uint) (*NodeinfoResponse, error) {
27 | var response = NodeinfoResponse{Data: &model.NodeInfo{}}
28 |
29 | node := SSRNode{}
30 | err := api.Db.First(&node, nodeid).Error
31 | if err != nil {
32 | return nil, newError("nodeinfo not find,please check your nodeid or mysql setting").Base(err)
33 | }
34 |
35 | response.Ret = 1
36 | sever_raw, _ := json.Marshal(node)
37 | response.Data.Server_raw = string(sever_raw)
38 | if node.Type == 1 {
39 | response.Data.Sort = 0
40 | } else if node.Type == 2 {
41 | response.Data.Sort = 11
42 | } else {
43 | return nil, newError("not implement type ")
44 | }
45 | response.Data.NodeSpeedlimit = node.Bandwidth
46 | response.Data.TrafficRate = node.TrafficRate
47 | response.Data.NodeID = node.ID
48 | response.Data.Server = map[string]interface{}{
49 | "server_address": node.Server,
50 | "port": fmt.Sprintf("%d", node.V2Port),
51 | "alterid": fmt.Sprintf("%d", node.V2AlterId),
52 | "protocol": node.V2Type,
53 | "protocol_param": node.V2Net,
54 | "path": node.V2Path,
55 | "host": node.V2Host,
56 | "inside_port": node.V2rayInsiderPort,
57 | "server": node.Server,
58 | }
59 | if node.V2Tls == 1 {
60 | response.Data.Server["protocol_param"] = "tls"
61 | }
62 | return &response, nil
63 | }
64 |
65 | func (api *SSRpanel) GetDisNodeInfo(nodeid uint) (*DisNodenfoResponse, error) {
66 | var response = DisNodenfoResponse{}
67 | return &response, nil
68 | }
69 |
70 | func (api *SSRpanel) GetALLUsers(info *model.NodeInfo) (*AllUsers, error) {
71 | sort := info.Sort
72 | var prifix string
73 | var allusers = AllUsers{
74 | Data: map[string]model.UserModel{},
75 | }
76 | if sort == 0 {
77 | prifix = "SS_"
78 | } else {
79 | prifix = "Vmess_"
80 | if info.Server["protocol"] == "tcp" {
81 | prifix += "tcp_"
82 | } else if info.Server["protocol"] == "ws" {
83 | if info.Server["protocol_param"] != "" {
84 | prifix += "ws_" + info.Server["protocol_param"].(string) + "_"
85 | } else {
86 | prifix += "ws_" + "none" + "_"
87 | }
88 | } else if info.Server["protocol"] == "kcp" {
89 | if info.Server["protocol_param"] != "" {
90 | prifix += "kcp_" + info.Server["protocol_param"].(string) + "_"
91 | } else {
92 | prifix += "kcp_" + "none" + "_"
93 | }
94 | }
95 | }
96 | var response = UsersResponse{}
97 | users := make([]SSRUserModel, 0)
98 | err := api.Db.Select("id, vmess_id, username").Where("enable = 1 AND u + d < transfer_enable").Find(&users).Error
99 | if err != nil {
100 | return nil, err
101 | }
102 |
103 | response.Ret = 1
104 |
105 | var filterd_user []model.UserModel
106 | for index := range users {
107 | user := users[index]
108 | filterd_user = append(filterd_user, model.UserModel{
109 | UserID: user.ID,
110 | Uuid: user.VmessID,
111 | Email: user.Email,
112 | Passwd: user.Passwd,
113 | Method: user.Method,
114 | Port: uint16(user.Port),
115 | NodeSpeedlimit: uint(user.SpeedLimitPerUser / 125000),
116 | Obfs: user.Obfs,
117 | Protocol: user.Protocol,
118 | })
119 |
120 | }
121 |
122 | response.Data = filterd_user
123 |
124 | for index := range response.Data {
125 | // 按照node 限速来调整用户限速
126 | if info.NodeSpeedlimit != 0 {
127 | if info.NodeSpeedlimit > response.Data[index].NodeSpeedlimit && response.Data[index].NodeSpeedlimit != 0 {
128 | } else if response.Data[index].NodeSpeedlimit == 0 || response.Data[index].NodeSpeedlimit > info.NodeSpeedlimit {
129 | response.Data[index].NodeSpeedlimit = info.NodeSpeedlimit
130 | }
131 | }
132 | // 接受到的是 Mbps, 然后我们的一个buffer 是2048byte, 差不多61个
133 | response.Data[index].Rate = uint32(response.Data[index].NodeSpeedlimit * 62)
134 |
135 | if info.Server["alterid"].(string) == "" {
136 | response.Data[index].AlterId = 2
137 | } else {
138 | alterid, err := strconv.ParseUint(info.Server["alterid"].(string), 10, 0)
139 | if err == nil {
140 | response.Data[index].AlterId = uint32(alterid)
141 | }
142 | }
143 | user := response.Data[index]
144 | response.Data[index].Muhost = get_mu_host(user.UserID, getMD5(fmt.Sprintf("%d%s%s%s%s", user.UserID, user.Passwd, user.Method, user.Obfs, user.Protocol)), api.MU_REGEX, api.MU_SUFFIX)
145 | key := prifix + response.Data[index].Email + fmt.Sprintf("Rate_%d_AlterID_%d_Method_%s_Passwd_%s_Port_%d_Obfs_%s_Protocol_%s", response.Data[index].Rate,
146 | response.Data[index].AlterId, response.Data[index].Method, response.Data[index].Passwd, response.Data[index].Port, response.Data[index].Obfs, response.Data[index].Protocol,
147 | )
148 | response.Data[index].PrefixedId = key
149 | allusers.Data[key] = response.Data[index]
150 | }
151 | return &allusers, nil
152 | }
153 |
154 | func (api *SSRpanel) Post(url string, params map[string]interface{}, data map[string]interface{}) (*req.Resp, error) {
155 |
156 | return nil, nil
157 | }
158 |
159 | func (api *SSRpanel) UploadSystemLoad(nodeid uint) bool {
160 | uptime, _ := strconv.ParseFloat(utility.GetSystemUptime(), 64)
161 | err := api.Db.Create(&SSRNodeInfo{
162 | NodeID: nodeid,
163 | Load: utility.GetSystemLoad(),
164 | Uptime: uptime,
165 | }).Error
166 | if err != nil {
167 | return false
168 | }
169 | return true
170 | }
171 |
172 | func (api *SSRpanel) flowAutoShow(value float64) string {
173 | value_float := value
174 | kb := 1024.0
175 | mb := 1048576.0
176 | gb := 1073741824.0
177 | tb := gb * 1024.0
178 | pb := tb * 1024.0
179 | if math.Abs(value_float) > pb {
180 | return fmt.Sprintf("%.2fPB", math.Round(value_float/pb))
181 | } else if math.Abs(value_float) > tb {
182 | return fmt.Sprintf("%.2fTB", math.Round(value_float/tb))
183 | } else if math.Abs(value_float) > gb {
184 | return fmt.Sprintf("%.2fGB", math.Round(value_float/gb))
185 | } else if math.Abs(value_float) > mb {
186 | return fmt.Sprintf("%.2fMB", math.Round(value_float/mb))
187 | } else if math.Abs(value_float) > kb {
188 | return fmt.Sprintf("%.2fKB", math.Round(value_float/kb))
189 | } else {
190 | return fmt.Sprintf("%.2fB", math.Round(value_float))
191 | }
192 | }
193 |
194 | func (api *SSRpanel) UpLoadUserTraffics(nodeid uint, trafficLog []model.UserTrafficLog) bool {
195 | var ssrnode SSRNode
196 | err := api.Db.First(&ssrnode, "id = ?", nodeid).Error
197 | if err != nil {
198 | return false
199 | }
200 | var this_time_total_bandwidth uint64 = 0
201 | if len(trafficLog) > 0 {
202 | for index := range trafficLog {
203 | traffic := trafficLog[index]
204 |
205 | var user SSRUserModel
206 | err := api.Db.First(&user, "id = ?", traffic.UserID).Error
207 |
208 | if err != nil {
209 | continue
210 | }
211 | user.Time = time.Now().Unix()
212 | user.Upload += int64(float64(traffic.Uplink) * ssrnode.TrafficRate)
213 | user.Download += int64(float64(traffic.Downlink) * ssrnode.TrafficRate)
214 | this_time_total_bandwidth += traffic.Uplink + traffic.Downlink
215 | api.Db.Save(&user)
216 |
217 | api.Db.Save(&SSRUserTrafficLog{
218 | UserID: traffic.UserID,
219 | Uplink: traffic.Uplink,
220 | Downlink: traffic.Downlink,
221 | NodeID: nodeid,
222 | Rate: ssrnode.TrafficRate,
223 | LogTime: time.Now().Unix(),
224 | Traffic: api.flowAutoShow(float64(traffic.Uplink+traffic.Downlink) * ssrnode.TrafficRate),
225 | })
226 | }
227 | }
228 |
229 | api.Db.Create(&SSRNodeOnlineLog{
230 | NodeID: nodeid,
231 | OnlineUser: len(trafficLog),
232 | LogTime: time.Now().Unix(),
233 | })
234 | return true
235 | }
236 | func (api *SSRpanel) UploadSpeedTest(nodeid uint, speedresult []speedtest.Speedresult) bool {
237 | return true
238 | }
239 | func (api *SSRpanel) UpLoadOnlineIps(nodeid uint, onlineIPS []model.UserOnLineIP) bool {
240 | var ssrnode SSRNode
241 | err := api.Db.First(&ssrnode, "id = ?", nodeid).Error
242 | if err != nil {
243 | return false
244 | }
245 | if len(onlineIPS) > 0 {
246 | for index := range onlineIPS {
247 | ip := onlineIPS[index]
248 | api.Db.Create(&SSRNodeIP{
249 | NodeID: nodeid,
250 | UserID: ip.UserId,
251 | CreatedAt: time.Now().Unix(),
252 | })
253 | }
254 | }
255 | return true
256 | }
257 |
--------------------------------------------------------------------------------
/db/ssrpaneltabel.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "github.com/jinzhu/gorm"
5 | "time"
6 | )
7 |
8 | type SSRUserModel struct {
9 | ID uint
10 | VmessID string
11 | Passwd string
12 | Method string
13 | Port int
14 | SpeedLimitPerUser int
15 | Email string `gorm:"column:username"`
16 | Time int64 `gorm:"column:t"`
17 | Upload int64 `gorm:"column:u"`
18 | Download int64 `gorm:"column:d"`
19 | Obfs string `gorm:"column:obfs"`
20 | Protocol string `gorm:"column:protocol"`
21 | }
22 |
23 | func (*SSRUserModel) TableName() string {
24 | return "user"
25 | }
26 |
27 | type SSRUserTrafficLog struct {
28 | ID uint `gorm:"primary_key"`
29 | UserID uint
30 | Uplink uint64 `gorm:"column:u"`
31 | Downlink uint64 `gorm:"column:d"`
32 | NodeID uint
33 | Rate float64
34 | Traffic string
35 | LogTime int64
36 | }
37 |
38 | func (l *SSRUserTrafficLog) BeforeCreate(scope *gorm.Scope) error {
39 | l.LogTime = time.Now().Unix()
40 | return nil
41 | }
42 |
43 | type SSRNodeOnlineLog struct {
44 | ID uint `gorm:"primary_key"`
45 | NodeID uint
46 | OnlineUser int
47 | LogTime int64
48 | }
49 |
50 | func (*SSRNodeOnlineLog) TableName() string {
51 | return "ss_node_online_log"
52 | }
53 |
54 | func (l *SSRNodeOnlineLog) BeforeCreate(scope *gorm.Scope) error {
55 | l.LogTime = time.Now().Unix()
56 | return nil
57 | }
58 |
59 | type SSRNodeInfo struct {
60 | ID uint `gorm:"primary_key"`
61 | NodeID uint
62 | Uptime float64
63 | Load string
64 | LogTime int64
65 | }
66 |
67 | func (*SSRNodeInfo) TableName() string {
68 | return "ss_node_info"
69 | }
70 |
71 | func (l *SSRNodeInfo) BeforeCreate(scope *gorm.Scope) error {
72 | l.LogTime = time.Now().Unix()
73 | return nil
74 | }
75 |
76 | type SSRNode struct {
77 | ID uint `gorm:"primary_key"`
78 | TrafficRate float64
79 | Type uint `gorm:"column:type"`
80 | Method string `gorm:"column:method"`
81 | V2AlterId uint
82 | V2Port uint
83 | V2Net string
84 | V2Type string
85 | V2Host string
86 | V2Path string
87 | V2Tls uint
88 | Bandwidth uint
89 | Ip string
90 | V2rayInsiderPort string
91 | Server string
92 | }
93 |
94 | func (*SSRNode) TableName() string {
95 | return "ss_node"
96 | }
97 |
98 | type SSRNodeIP struct {
99 | ID uint `gorm:"primary_key"`
100 | NodeID uint
101 | UserID uint
102 | Ip string
103 | CreatedAt int64
104 | }
105 |
106 | func (*SSRNodeIP) TableName() string {
107 | return "ss_node_ip"
108 | }
109 |
--------------------------------------------------------------------------------
/db/webapi.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "fmt"
5 | "github.com/imroc/req"
6 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/model"
7 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/speedtest"
8 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/utility"
9 | "log"
10 | "strconv"
11 | "strings"
12 | "time"
13 | )
14 |
15 | type Webapi struct {
16 | WebToken string
17 | WebBaseURl string
18 | MU_REGEX string
19 | MU_SUFFIX string
20 | }
21 |
22 | func (api *Webapi) GetApi(url string, params map[string]interface{}) (*req.Resp, error) {
23 | req.SetTimeout(50 * time.Second)
24 | parm := req.Param{
25 | "key": api.WebToken,
26 | }
27 | for k, v := range params {
28 | parm[k] = v
29 | }
30 |
31 | r, err := req.Get(fmt.Sprintf("%s/mod_mu/%s", api.WebBaseURl, url), parm)
32 | return r, err
33 | }
34 |
35 | func (api *Webapi) GetNodeInfo(nodeid uint) (*NodeinfoResponse, error) {
36 | var response = NodeinfoResponse{}
37 | var params map[string]interface{}
38 |
39 | r, err := api.GetApi(fmt.Sprintf("nodes/%d/info", nodeid), params)
40 | if err != nil {
41 | return &response, err
42 | } else {
43 | err = r.ToJSON(&response)
44 | if err != nil {
45 | return &response, err
46 | } else if response.Ret != 1 {
47 | return &response, err
48 | }
49 | }
50 |
51 | if response.Data.Server_raw != "" {
52 | response.Data.Server_raw = strings.ToLower(response.Data.Server_raw)
53 | data := strings.Split(response.Data.Server_raw, ";")
54 | var count uint
55 | count = 0
56 | for v := range data {
57 | if len(data[v]) > 0 {
58 | maps[id2string[count]] = data[v]
59 | }
60 | count += 1
61 | }
62 | var extraArgues []string
63 | if len(data) == 6 {
64 | extraArgues = append(extraArgues, strings.Split(data[5], "|")...)
65 | for item := range extraArgues {
66 | data = strings.Split(extraArgues[item], "=")
67 | if len(data) > 0 {
68 | if len(data[1]) > 0 {
69 | maps[data[0]] = data[1]
70 | }
71 |
72 | }
73 | }
74 | }
75 |
76 | if maps["protocol"] == "tls" {
77 | temp := maps["protocol_param"]
78 | maps["protocol"] = temp
79 | maps["protocol_param"] = "tls"
80 | }
81 | response.Data.Server = maps
82 | }
83 | response.Data.NodeID = nodeid
84 | return &response, nil
85 | }
86 |
87 | func (api *Webapi) GetDisNodeInfo(nodeid uint) (*DisNodenfoResponse, error) {
88 | var response = DisNodenfoResponse{}
89 | var params map[string]interface{}
90 | params = map[string]interface{}{
91 | "node_id": nodeid,
92 | }
93 | r, err := api.GetApi("func/relay_rules", params)
94 | if err != nil {
95 | return &response, err
96 | } else {
97 | err = r.ToJSON(&response)
98 | if err != nil {
99 | return &response, err
100 | } else if response.Ret != 1 {
101 | return &response, err
102 | }
103 | }
104 |
105 | if len(response.Data) > 0 {
106 | for _, relayrule := range response.Data {
107 | relayrule.Server_raw = strings.ToLower(relayrule.Server_raw)
108 | data := strings.Split(relayrule.Server_raw, ";")
109 | var count uint
110 | count = 0
111 | for v := range data {
112 | if len(data[v]) > 1 {
113 | maps[id2string[count]] = data[v]
114 | }
115 | count += 1
116 | }
117 | var extraArgues []string
118 | if len(data) == 6 {
119 | extraArgues = append(extraArgues, strings.Split(data[5], "|")...)
120 | for item := range extraArgues {
121 | data = strings.Split(extraArgues[item], "=")
122 | if len(data) > 1 {
123 | if len(data[1]) > 1 {
124 | maps[data[0]] = data[1]
125 | }
126 |
127 | }
128 | }
129 | }
130 |
131 | if maps["protocol"] == "tls" {
132 | temp := maps["protocol_param"]
133 | maps["protocol"] = temp
134 | maps["protocol_param"] = "tls"
135 | }
136 | relayrule.Server = maps
137 | }
138 | }
139 | return &response, nil
140 | }
141 |
142 | func (api *Webapi) GetALLUsers(info *model.NodeInfo) (*AllUsers, error) {
143 | sort := info.Sort
144 | var prifix string
145 | var allusers = AllUsers{
146 | Data: map[string]model.UserModel{},
147 | }
148 | if sort == 0 {
149 | prifix = "SS_"
150 | } else {
151 | prifix = "Vmess_"
152 | if info.Server["protocol"] == "tcp" {
153 | prifix += "tcp_"
154 | } else if info.Server["protocol"] == "ws" {
155 | if info.Server["protocol_param"] != "" {
156 | prifix += "ws_" + info.Server["protocol_param"].(string) + "_"
157 | } else {
158 | prifix += "ws_" + "none" + "_"
159 | }
160 | } else if info.Server["protocol"] == "kcp" {
161 | if info.Server["protocol_param"] != "" {
162 | prifix += "kcp_" + info.Server["protocol_param"].(string) + "_"
163 | } else {
164 | prifix += "kcp_" + "none" + "_"
165 | }
166 | }
167 | }
168 | var response = UsersResponse{}
169 | params := map[string]interface{}{
170 | "node_id": info.NodeID,
171 | }
172 | r, err := api.GetApi("users", params)
173 | if err != nil {
174 | return &allusers, err
175 | } else {
176 | err = r.ToJSON(&response)
177 | allusers.Ret = response.Ret
178 | if err != nil {
179 | return &allusers, err
180 | } else if response.Ret != 1 {
181 | return &allusers, err
182 | }
183 | }
184 | for index := range response.Data {
185 | // 按照node 限速来调整用户限速
186 | if info.NodeSpeedlimit != 0 {
187 | if info.NodeSpeedlimit > response.Data[index].NodeSpeedlimit && response.Data[index].NodeSpeedlimit != 0 {
188 | } else if response.Data[index].NodeSpeedlimit == 0 || response.Data[index].NodeSpeedlimit > info.NodeSpeedlimit {
189 | response.Data[index].NodeSpeedlimit = info.NodeSpeedlimit
190 | }
191 | }
192 | // 接受到的是 Mbps, 然后我们的一个buffer 是2048byte, 差不多61个
193 | response.Data[index].Rate = uint32(response.Data[index].NodeSpeedlimit * 62)
194 | if info.Server["alterid"].(string) == "" {
195 | response.Data[index].AlterId = 2
196 | } else {
197 | alterid, err := strconv.ParseUint(info.Server["alterid"].(string), 10, 0)
198 | if err == nil {
199 | response.Data[index].AlterId = uint32(alterid)
200 | }
201 | }
202 | user := response.Data[index]
203 | response.Data[index].Muhost = get_mu_host(user.UserID, getMD5(fmt.Sprintf("%d%s%s%s%s", user.UserID, user.Passwd, user.Method, user.Obfs, user.Protocol)), api.MU_REGEX, api.MU_SUFFIX)
204 | key := prifix + response.Data[index].Email + fmt.Sprintf("Rate_%d_AlterID_%d_Method_%s_Passwd_%s_Port_%d_Obfs_%s_Protocol_%s", response.Data[index].Rate,
205 | response.Data[index].AlterId, response.Data[index].Method, response.Data[index].Passwd, response.Data[index].Port, response.Data[index].Obfs, response.Data[index].Protocol,
206 | )
207 | response.Data[index].PrefixedId = key
208 | allusers.Data[key] = response.Data[index]
209 | }
210 | return &allusers, nil
211 | }
212 |
213 | func (api *Webapi) Post(url string, params map[string]interface{}, data map[string]interface{}) (*req.Resp, error) {
214 | parm := req.Param{
215 | "key": api.WebToken,
216 | }
217 | for k, v := range params {
218 | parm[k] = v
219 | }
220 | r, err := req.Post(fmt.Sprintf("%s/mod_mu/%s", api.WebBaseURl, url), parm, req.BodyJSON(&data))
221 | return r, err
222 | }
223 |
224 | func (api *Webapi) UploadSystemLoad(nodeid uint) bool {
225 | var postresponse PostResponse
226 | params := map[string]interface{}{
227 | "node_id": nodeid,
228 | }
229 | upload_systemload := map[string]interface{}{
230 | "uptime": utility.GetSystemUptime(),
231 | "load": utility.GetSystemLoad(),
232 | }
233 | r, err := api.Post(fmt.Sprintf("nodes/%d/info", nodeid), params, upload_systemload)
234 | if err != nil {
235 | return false
236 | } else {
237 | err = r.ToJSON(&postresponse)
238 | if err != nil {
239 | return false
240 | } else if postresponse.Ret != 1 {
241 | log.Fatal(postresponse.Data)
242 | }
243 | }
244 | return true
245 | }
246 |
247 | func (api *Webapi) UpLoadUserTraffics(nodeid uint, trafficLog []model.UserTrafficLog) bool {
248 | var postresponse PostResponse
249 | params := map[string]interface{}{
250 | "node_id": nodeid,
251 | }
252 |
253 | data := map[string]interface{}{
254 | "data": trafficLog,
255 | }
256 | r, err := api.Post("users/traffic", params, data)
257 | if err != nil {
258 | return false
259 | } else {
260 | err = r.ToJSON(&postresponse)
261 | if err != nil {
262 | return false
263 | } else if postresponse.Ret != 1 {
264 | log.Fatal(postresponse.Data)
265 | }
266 | }
267 | return true
268 | }
269 | func (api *Webapi) UploadSpeedTest(nodeid uint, speedresult []speedtest.Speedresult) bool {
270 | var postresponse PostResponse
271 | params := map[string]interface{}{
272 | "node_id": nodeid,
273 | }
274 |
275 | data := map[string]interface{}{
276 | "data": speedresult,
277 | }
278 | r, err := api.Post("func/speedtest", params, data)
279 | if err != nil {
280 | return false
281 | } else {
282 | err = r.ToJSON(&postresponse)
283 | if err != nil {
284 | return false
285 | } else if postresponse.Ret != 1 {
286 | log.Fatal(postresponse.Data)
287 | }
288 | }
289 | return true
290 | }
291 | func (api *Webapi) UpLoadOnlineIps(nodeid uint, onlineIPS []model.UserOnLineIP) bool {
292 | var postresponse PostResponse
293 | params := map[string]interface{}{
294 | "node_id": nodeid,
295 | }
296 |
297 | data := map[string]interface{}{
298 | "data": onlineIPS,
299 | }
300 | r, err := api.Post("users/aliveip", params, data)
301 | if err != nil {
302 | return false
303 | } else {
304 | err = r.ToJSON(&postresponse)
305 | if err != nil {
306 | return false
307 | } else if postresponse.Ret != 1 {
308 | log.Fatal(postresponse.Data)
309 | }
310 | }
311 | return true
312 | }
313 |
--------------------------------------------------------------------------------
/install-release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This file is accessible as https://install.direct/go.sh
4 | # Original source is located at github.com/v2ray/v2ray-core/release/install-release.sh
5 |
6 | # If not specify, default meaning of return value:
7 | # 0: Success
8 | # 1: System error
9 | # 2: Application error
10 | # 3: Network error
11 |
12 | CUR_VER=""
13 | NEW_VER=""
14 | ARCH=""
15 | VDIS="64"
16 | ZIPFILE="/tmp/v2ray/v2ray.zip"
17 | V2RAY_RUNNING=0
18 | VSRC_ROOT="/tmp/v2ray"
19 | EXTRACT_ONLY=0
20 | ERROR_IF_UPTODATE=0
21 |
22 | CMD_INSTALL=""
23 | CMD_UPDATE=""
24 | SOFTWARE_UPDATED=0
25 |
26 | SYSTEMCTL_CMD=$(command -v systemctl 2>/dev/null)
27 | SERVICE_CMD=$(command -v service 2>/dev/null)
28 |
29 | CHECK=""
30 | FORCE=""
31 | HELP=""
32 |
33 | #######color code########
34 | RED="31m" # Error message
35 | GREEN="32m" # Success message
36 | YELLOW="33m" # Warning message
37 | BLUE="36m" # Info message
38 |
39 |
40 | #########################
41 | while [[ $# > 0 ]];do
42 | key="$1"
43 | case $key in
44 | -p|--proxy)
45 | PROXY="-x ${2}"
46 | shift # past argument
47 | ;;
48 | -h|--help)
49 | HELP="1"
50 | ;;
51 | -f|--force)
52 | FORCE="1"
53 | ;;
54 | -c|--check)
55 | CHECK="1"
56 | ;;
57 | --remove)
58 | REMOVE="1"
59 | ;;
60 | --version)
61 | VERSION="$2"
62 | shift
63 | ;;
64 | --extract)
65 | VSRC_ROOT="$2"
66 | shift
67 | ;;
68 | --extractonly)
69 | EXTRACT_ONLY="1"
70 | ;;
71 | -l|--local)
72 | LOCAL="$2"
73 | LOCAL_INSTALL="1"
74 | shift
75 | ;;
76 | --errifuptodate)
77 | ERROR_IF_UPTODATE="1"
78 | ;;
79 | --panelurl)
80 | PANELURL="$2"
81 | ;;
82 | --panelkey)
83 | PANELKEY="$2"
84 | ;;
85 | --nodeid)
86 | NODEID="$2"
87 | ;;
88 | --downwithpanel)
89 | DOWNWITHPANEL="$2"
90 | ;;
91 | --mysqlhost)
92 | MYSQLHOST="$2"
93 | ;;
94 | --mysqldbname)
95 | MYSQLDBNAME="$2"
96 | ;;
97 | --mysqluser)
98 | MYSQLUSR="$2"
99 | ;;
100 | --mysqlpasswd)
101 | MYSQLPASSWD="$2"
102 | ;;
103 | --mysqlport)
104 | MYSQLPORT="$2"
105 | ;;
106 | --speedtestrate)
107 | SPEEDTESTRATE="$2"
108 | ;;
109 | --paneltype)
110 | PANELTYPE="$2"
111 | ;;
112 | --usemysql)
113 | USEMYSQL="$2"
114 | ;;
115 | *)
116 | # unknown option
117 | ;;
118 | esac
119 | shift # past argument or value
120 | done
121 |
122 | ###############################
123 | colorEcho(){
124 | COLOR=$1
125 | echo -e "\033[${COLOR}${@:2}\033[0m"
126 | }
127 |
128 | sysArch(){
129 | ARCH=$(uname -m)
130 | if [[ "$ARCH" == "i686" ]] || [[ "$ARCH" == "i386" ]]; then
131 | VDIS="32"
132 | elif [[ "$ARCH" == *"armv7"* ]] || [[ "$ARCH" == "armv6l" ]]; then
133 | VDIS="arm"
134 | elif [[ "$ARCH" == *"armv8"* ]] || [[ "$ARCH" == "aarch64" ]]; then
135 | VDIS="arm64"
136 | elif [[ "$ARCH" == *"mips64le"* ]]; then
137 | VDIS="mips64le"
138 | elif [[ "$ARCH" == *"mips64"* ]]; then
139 | VDIS="mips64"
140 | elif [[ "$ARCH" == *"mipsle"* ]]; then
141 | VDIS="mipsle"
142 | elif [[ "$ARCH" == *"mips"* ]]; then
143 | VDIS="mips"
144 | elif [[ "$ARCH" == *"s390x"* ]]; then
145 | VDIS="s390x"
146 | elif [[ "$ARCH" == "ppc64le" ]]; then
147 | VDIS="ppc64le"
148 | elif [[ "$ARCH" == "ppc64" ]]; then
149 | VDIS="ppc64"
150 | fi
151 | return 0
152 | }
153 |
154 | downloadV2Ray(){
155 | rm -rf /tmp/v2ray
156 | mkdir -p /tmp/v2ray
157 | colorEcho ${BLUE} "Downloading V2Ray."
158 | DOWNLOAD_LINK="https://github.com/v2ray/v2ray-core/releases/download/v4.19.1/v2ray-linux-64.zip"
159 | curl ${PROXY} -L -H "Cache-Control: no-cache" -o ${ZIPFILE} ${DOWNLOAD_LINK}
160 | if [ $? != 0 ];then
161 | colorEcho ${RED} "Failed to download! Please check your network or try again."
162 | return 3
163 | fi
164 | return 0
165 | }
166 |
167 | installSoftware(){
168 | COMPONENT=$1
169 | if [[ -n `command -v $COMPONENT` ]]; then
170 | return 0
171 | fi
172 |
173 | getPMT
174 | if [[ $? -eq 1 ]]; then
175 | colorEcho ${RED} "The system package manager tool isn't APT or YUM, please install ${COMPONENT} manually."
176 | return 1
177 | fi
178 | if [[ $SOFTWARE_UPDATED -eq 0 ]]; then
179 | colorEcho ${BLUE} "Updating software repo"
180 | $CMD_UPDATE
181 | SOFTWARE_UPDATED=1
182 | fi
183 |
184 | colorEcho ${BLUE} "Installing ${COMPONENT}"
185 | $CMD_INSTALL $COMPONENT
186 | if [[ $? -ne 0 ]]; then
187 | colorEcho ${RED} "Failed to install ${COMPONENT}. Please install it manually."
188 | return 1
189 | fi
190 | return 0
191 | }
192 |
193 | # return 1: not apt, yum, or zypper
194 | getPMT(){
195 | if [[ -n `command -v apt-get` ]];then
196 | CMD_INSTALL="apt-get -y -qq install"
197 | CMD_UPDATE="apt-get -qq update"
198 | elif [[ -n `command -v yum` ]]; then
199 | CMD_INSTALL="yum -y -q install"
200 | CMD_UPDATE="yum -q makecache"
201 | elif [[ -n `command -v zypper` ]]; then
202 | CMD_INSTALL="zypper -y install"
203 | CMD_UPDATE="zypper ref"
204 | else
205 | return 1
206 | fi
207 | return 0
208 | }
209 |
210 | extract(){
211 | colorEcho ${BLUE}"Extracting V2Ray package to /tmp/v2ray."
212 | mkdir -p /tmp/v2ray
213 | unzip $1 -d ${VSRC_ROOT}
214 | if [[ $? -ne 0 ]]; then
215 | colorEcho ${RED} "Failed to extract V2Ray."
216 | return 2
217 | fi
218 | if [[ -d "/tmp/v2ray/v2ray-${NEW_VER}-linux-${VDIS}" ]]; then
219 | VSRC_ROOT="/tmp/v2ray/v2ray-${NEW_VER}-linux-${VDIS}"
220 | fi
221 | return 0
222 | }
223 |
224 |
225 | # 1: new V2Ray. 0: no. 2: not installed. 3: check failed. 4: don't check.
226 | getVersion(){
227 | if [[ -n "$VERSION" ]]; then
228 | NEW_VER="$VERSION"
229 | if [[ ${NEW_VER} != v* ]]; then
230 | NEW_VER=v${NEW_VER}
231 | fi
232 | return 4
233 | else
234 | VER=`/usr/bin/v2ray/v2ray -version 2>/dev/null`
235 | RETVAL="$?"
236 | CUR_VER=`echo $VER | head -n 1 | cut -d " " -f2`
237 | if [[ ${CUR_VER} != v* ]]; then
238 | CUR_VER=v${CUR_VER}
239 | fi
240 | TAG_URL="https://api.github.com/repos/githubphone/pay-v2ray-sspanel-v3-mod_Uim-plugin/releases/latest"
241 | NEW_VER=`curl ${PROXY} -s ${TAG_URL} --connect-timeout 10| grep 'tag_name' | cut -d\" -f4`
242 | if [[ ${NEW_VER} != v* ]]; then
243 | NEW_VER=v${NEW_VER}
244 | fi
245 | if [[ $? -ne 0 ]] || [[ $NEW_VER == "" ]]; then
246 | colorEcho ${RED} "Failed to fetch release information. Please check your network or try again."
247 | return 3
248 | elif [[ $RETVAL -ne 0 ]];then
249 | return 2
250 | elif [[ `echo $NEW_VER | cut -d. -f-2` != `echo $CUR_VER | cut -d. -f-2` ]];then
251 | return 1
252 | fi
253 | return 0
254 | fi
255 | }
256 |
257 | stopV2ray(){
258 | colorEcho ${BLUE} "Shutting down V2Ray service."
259 | if [[ -n "${SYSTEMCTL_CMD}" ]] || [[ -f "/lib/systemd/system/v2ray.service" ]] || [[ -f "/etc/systemd/system/v2ray.service" ]]; then
260 | ${SYSTEMCTL_CMD} stop v2ray
261 | elif [[ -n "${SERVICE_CMD}" ]] || [[ -f "/etc/init.d/v2ray" ]]; then
262 | ${SERVICE_CMD} v2ray stop
263 | fi
264 | if [[ $? -ne 0 ]]; then
265 | colorEcho ${YELLOW} "Failed to shutdown V2Ray service."
266 | return 2
267 | fi
268 | return 0
269 | }
270 |
271 | startV2ray(){
272 | if [ -n "${SYSTEMCTL_CMD}" ] && [ -f "/lib/systemd/system/v2ray.service" ]; then
273 | ${SYSTEMCTL_CMD} start v2ray
274 | elif [ -n "${SYSTEMCTL_CMD}" ] && [ -f "/etc/systemd/system/v2ray.service" ]; then
275 | ${SYSTEMCTL_CMD} start v2ray
276 | elif [ -n "${SERVICE_CMD}" ] && [ -f "/etc/init.d/v2ray" ]; then
277 | ${SERVICE_CMD} v2ray start
278 | fi
279 | if [[ $? -ne 0 ]]; then
280 | colorEcho ${YELLOW} "Failed to start V2Ray service."
281 | return 2
282 | fi
283 | return 0
284 | }
285 |
286 | copyFile() {
287 | NAME=$1
288 | ERROR=`cp "${VSRC_ROOT}/${NAME}" "/usr/bin/v2ray/${NAME}" 2>&1`
289 | if [[ $? -ne 0 ]]; then
290 | colorEcho ${YELLOW} "${ERROR}"
291 | return 1
292 | fi
293 | return 0
294 | }
295 |
296 | makeExecutable() {
297 | chmod +x "/usr/bin/v2ray/$1"
298 | }
299 |
300 | installV2Ray(){
301 | # Install V2Ray binary to /usr/bin/v2ray
302 | mkdir -p /usr/bin/v2ray
303 | copyFile v2ray
304 | if [[ $? -ne 0 ]]; then
305 | colorEcho ${RED} "Failed to copy V2Ray binary and resources."
306 | return 1
307 | fi
308 | makeExecutable v2ray
309 | copyFile v2ctl && makeExecutable v2ctl
310 | copyFile geoip.dat
311 | copyFile geosite.dat
312 | colorEcho ${BLUE} "Setting TimeZone to Shanghai"
313 | ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
314 | date -s "$(curl -sI g.cn | grep Date | cut -d' ' -f3-6)Z"
315 |
316 | # Install V2Ray server config to /etc/v2ray
317 | if [[ ! -f "/etc/v2ray/config.json" ]]; then
318 | mkdir -p /etc/v2ray
319 | mkdir -p /var/log/v2ray
320 | cp "${VSRC_ROOT}/vpoint_vmess_freedom.json" "/etc/v2ray/config.json"
321 | if [[ $? -ne 0 ]]; then
322 | colorEcho ${YELLOW} "Failed to create V2Ray configuration file. Please create it manually."
323 | return 1
324 | fi
325 |
326 | if [ ! -z "${PANELURL}" ]
327 | then
328 | sed -i "s|"https://google.com"|"${PANELURL}"|g" "/etc/v2ray/config.json"
329 | colorEcho ${BLUE} "PANELURL:${PANELURL}"
330 | fi
331 | if [ ! -z "${PANELKEY}" ]
332 | then
333 | sed -i "s/"55fUxDGFzH3n"/"${PANELKEY}"/g" "/etc/v2ray/config.json"
334 | colorEcho ${BLUE} "PANELKEY:${PANELKEY}"
335 |
336 | fi
337 | if [ ! -z "${NODEID}" ]
338 | then
339 | sed -i "s/123456,/${NODEID},/g" "/etc/v2ray/config.json"
340 | colorEcho ${BLUE} "NODEID:${NODEID}"
341 |
342 | fi
343 |
344 | if [ ! -z "${DOWNWITHPANEL}" ]
345 | then
346 | sed -i "s|\"downWithPanel\": 1|\"downWithPanel\": ${DOWNWITHPANEL}|g" "/etc/v2ray/config.json"
347 | colorEcho ${BLUE} "DOWNWITHPANEL:${DOWNWITHPANEL}"
348 | fi
349 |
350 | if [ ! -z "${MYSQLHOST}" ]
351 | then
352 | sed -i "s|"https://bing.com"|"${MYSQLHOST}"|g" "/etc/v2ray/config.json"
353 | colorEcho ${BLUE} "MYSQLHOST:${MYSQLHOST}"
354 |
355 | fi
356 | if [ ! -z "${MYSQLDBNAME}" ]
357 | then
358 | sed -i "s/"demo_dbname"/"${MYSQLDBNAME}"/g" "/etc/v2ray/config.json"
359 | colorEcho ${BLUE} "MYSQLDBNAME:${MYSQLDBNAME}"
360 |
361 | fi
362 | if [ ! -z "${MYSQLUSR}" ]
363 | then
364 | sed -i "s|\"demo_user\"|\"${MYSQLUSR}\"|g" "/etc/v2ray/config.json"
365 | colorEcho ${BLUE} "MYSQLUSR:${MYSQLUSR}"
366 | fi
367 | if [ ! -z "${MYSQLPASSWD}" ]
368 | then
369 | sed -i "s/"demo_dbpassword"/"${MYSQLPASSWD}"/g" "/etc/v2ray/config.json"
370 | colorEcho ${BLUE} "MYSQLPASSWD:${MYSQLPASSWD}"
371 |
372 | fi
373 | if [ ! -z "${MYSQLPORT}" ]
374 | then
375 | sed -i "s/3306,/${MYSQLPORT},/g" "/etc/v2ray/config.json"
376 | colorEcho ${BLUE} "MYSQLPORT:${MYSQLPORT}"
377 |
378 | fi
379 |
380 | if [ ! -z "${SPEEDTESTRATE}" ]
381 | then
382 | sed -i "s|\"SpeedTestCheckRate\": 6|\"SpeedTestCheckRate\": ${SPEEDTESTRATE}|g" "/etc/v2ray/config.json"
383 | colorEcho ${BLUE} "SPEEDTESTRATE:${SPEEDTESTRATE}"
384 |
385 | fi
386 | if [ ! -z "${PANELTYPE}" ]
387 | then
388 | sed -i "s|\"paneltype\": 0|\"paneltype\": ${PANELTYPE}|g" "/etc/v2ray/config.json"
389 | colorEcho ${BLUE} "PANELTYPE:${PANELTYPE}"
390 |
391 | fi
392 | if [ ! -z "${USEMYSQL}" ]
393 | then
394 | sed -i "s|\"usemysql\": 0|\"usemysql\": ${USEMYSQL}|g" "/etc/v2ray/config.json"
395 | colorEcho ${BLUE} "USEMYSQL:${USEMYSQL}"
396 |
397 | fi
398 |
399 |
400 | fi
401 | return 0
402 | }
403 |
404 |
405 | installInitScript(){
406 | if [[ -n "${SYSTEMCTL_CMD}" ]];then
407 | if [[ ! -f "/etc/systemd/system/v2ray.service" ]]; then
408 | if [[ ! -f "/lib/systemd/system/v2ray.service" ]]; then
409 | cp "${VSRC_ROOT}/systemd/v2ray.service" "/etc/systemd/system/"
410 | systemctl enable v2ray.service
411 | fi
412 | fi
413 | return
414 | elif [[ -n "${SERVICE_CMD}" ]] && [[ ! -f "/etc/init.d/v2ray" ]]; then
415 | installSoftware "daemon" || return $?
416 | cp "${VSRC_ROOT}/systemv/v2ray" "/etc/init.d/v2ray"
417 | chmod +x "/etc/init.d/v2ray"
418 | update-rc.d v2ray defaults
419 | fi
420 | return
421 | }
422 |
423 | Help(){
424 | echo "./install-release.sh [-h] [-c] [--remove] [-p proxy] [-f] [--version vx.y.z] [-l file]"
425 | echo " -h, --help Show help"
426 | echo " -p, --proxy To download through a proxy server, use -p socks5://127.0.0.1:1080 or -p http://127.0.0.1:3128 etc"
427 | echo " -f, --force Force install"
428 | echo " --version Install a particular version, use --version v3.15"
429 | echo " -l, --local Install from a local file"
430 | echo " --remove Remove installed V2Ray"
431 | echo " -c, --check Check for update"
432 | return 0
433 | }
434 |
435 | remove(){
436 | if [[ -n "${SYSTEMCTL_CMD}" ]] && [[ -f "/etc/systemd/system/v2ray.service" ]];then
437 | if pgrep "v2ray" > /dev/null ; then
438 | stopV2ray
439 | fi
440 | systemctl disable v2ray.service
441 | rm -rf "/usr/bin/v2ray" "/etc/systemd/system/v2ray.service"
442 | if [[ $? -ne 0 ]]; then
443 | colorEcho ${RED} "Failed to remove V2Ray."
444 | return 0
445 | else
446 | colorEcho ${GREEN} "Removed V2Ray successfully."
447 | colorEcho ${BLUE} "If necessary, please remove configuration file and log file manually."
448 | return 0
449 | fi
450 | elif [[ -n "${SYSTEMCTL_CMD}" ]] && [[ -f "/lib/systemd/system/v2ray.service" ]];then
451 | if pgrep "v2ray" > /dev/null ; then
452 | stopV2ray
453 | fi
454 | systemctl disable v2ray.service
455 | rm -rf "/usr/bin/v2ray" "/lib/systemd/system/v2ray.service"
456 | if [[ $? -ne 0 ]]; then
457 | colorEcho ${RED} "Failed to remove V2Ray."
458 | return 0
459 | else
460 | colorEcho ${GREEN} "Removed V2Ray successfully."
461 | colorEcho ${BLUE} "If necessary, please remove configuration file and log file manually."
462 | return 0
463 | fi
464 | elif [[ -n "${SERVICE_CMD}" ]] && [[ -f "/etc/init.d/v2ray" ]]; then
465 | if pgrep "v2ray" > /dev/null ; then
466 | stopV2ray
467 | fi
468 | rm -rf "/usr/bin/v2ray" "/etc/init.d/v2ray"
469 | if [[ $? -ne 0 ]]; then
470 | colorEcho ${RED} "Failed to remove V2Ray."
471 | return 0
472 | else
473 | colorEcho ${GREEN} "Removed V2Ray successfully."
474 | colorEcho ${BLUE} "If necessary, please remove configuration file and log file manually."
475 | return 0
476 | fi
477 | else
478 | colorEcho ${YELLOW} "V2Ray not found."
479 | return 0
480 | fi
481 | }
482 |
483 | checkUpdate(){
484 | echo "Checking for update."
485 | VERSION=""
486 | getVersion
487 | RETVAL="$?"
488 | if [[ $RETVAL -eq 1 ]]; then
489 | colorEcho ${BLUE} "Found new version ${NEW_VER} for V2Ray.(Current version:$CUR_VER)"
490 | elif [[ $RETVAL -eq 0 ]]; then
491 | colorEcho ${BLUE} "No new version. Current version is ${NEW_VER}."
492 | elif [[ $RETVAL -eq 2 ]]; then
493 | colorEcho ${YELLOW} "No V2Ray installed."
494 | colorEcho ${BLUE} "The newest version for V2Ray is ${NEW_VER}."
495 | fi
496 | return 0
497 | }
498 |
499 | main(){
500 | #helping information
501 | [[ "$HELP" == "1" ]] && Help && return
502 | [[ "$CHECK" == "1" ]] && checkUpdate && return
503 | [[ "$REMOVE" == "1" ]] && remove && return
504 |
505 | sysArch
506 | # extract local file
507 | if [[ $LOCAL_INSTALL -eq 1 ]]; then
508 | colorEcho ${YELLOW} "Installing V2Ray via local file. Please make sure the file is a valid V2Ray package, as we are not able to determine that."
509 | NEW_VER=local
510 | installSoftware unzip || return $?
511 | installSoftware "socat" || return $?
512 | colorEcho ${YELLOW} "Downloading acme.sh"
513 | curl https://get.acme.sh | sh
514 | rm -rf /tmp/v2ray
515 | extract $LOCAL || return $?
516 | #FILEVDIS=`ls /tmp/v2ray |grep v2ray-v |cut -d "-" -f4`
517 | #SYSTEM=`ls /tmp/v2ray |grep v2ray-v |cut -d "-" -f3`
518 | #if [[ ${SYSTEM} != "linux" ]]; then
519 | # colorEcho ${RED} "The local V2Ray can not be installed in linux."
520 | # return 1
521 | #elif [[ ${FILEVDIS} != ${VDIS} ]]; then
522 | # colorEcho ${RED} "The local V2Ray can not be installed in ${ARCH} system."
523 | # return 1
524 | #else
525 | # NEW_VER=`ls /tmp/v2ray |grep v2ray-v |cut -d "-" -f2`
526 | #fi
527 | else
528 | # download via network and extract
529 | installSoftware "curl" || return $?
530 | installSoftware "socat" || return $?
531 | colorEcho ${YELLOW} "Downloading acme.sh"
532 | curl https://get.acme.sh | sh
533 | getVersion
534 | RETVAL="$?"
535 | if [[ $RETVAL == 0 ]] && [[ "$FORCE" != "1" ]]; then
536 | colorEcho ${BLUE} "Latest version ${NEW_VER} is already installed."
537 | if [[ "${ERROR_IF_UPTODATE}" == "1" ]]; then
538 | return 10
539 | fi
540 | return
541 | elif [[ $RETVAL == 3 ]]; then
542 | return 3
543 | else
544 | colorEcho ${BLUE} "Installing V2Ray ${NEW_VER} on ${ARCH}"
545 | downloadV2Ray || return $?
546 | installSoftware unzip || return $?
547 | extract ${ZIPFILE} || return $?
548 | fi
549 | fi
550 |
551 | if [[ "${EXTRACT_ONLY}" == "1" ]]; then
552 | colorEcho ${GREEN} "V2Ray extracted to ${VSRC_ROOT}, and exiting..."
553 | return 0
554 | fi
555 |
556 | if pgrep "v2ray" > /dev/null ; then
557 | V2RAY_RUNNING=1
558 | stopV2ray
559 | fi
560 | installV2Ray || return $?
561 | installInitScript || return $?
562 | if [[ ${V2RAY_RUNNING} -eq 1 ]];then
563 | colorEcho ${BLUE} "Restarting V2Ray service."
564 | startV2ray
565 | fi
566 | colorEcho ${GREEN} "V2Ray ${NEW_VER} is installed."
567 | rm -rf /tmp/v2ray
568 | return 0
569 | }
570 |
571 | main
572 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
3 | export PATH
4 |
5 | # Current folder
6 | cur_dir=`pwd`
7 | # Color
8 | red='\033[0;31m'
9 | green='\033[0;32m'
10 | yellow='\033[0;33m'
11 | plain='\033[0m'
12 | software=(Docker_Caddy Docker_Caddy_cloudflare Docker)
13 | operation=(install update_config update_image logs)
14 | # Make sure only root can run our script
15 | [[ $EUID -ne 0 ]] && echo -e "[${red}Error${plain}] This script must be run as root!" && exit 1
16 |
17 | #Check system
18 | check_sys(){
19 | local checkType=$1
20 | local value=$2
21 |
22 | local release=''
23 | local systemPackage=''
24 |
25 | if [[ -f /etc/redhat-release ]]; then
26 | release="centos"
27 | systemPackage="yum"
28 | elif grep -Eqi "debian|raspbian" /etc/issue; then
29 | release="debian"
30 | systemPackage="apt"
31 | elif grep -Eqi "ubuntu" /etc/issue; then
32 | release="ubuntu"
33 | systemPackage="apt"
34 | elif grep -Eqi "centos|red hat|redhat" /etc/issue; then
35 | release="centos"
36 | systemPackage="yum"
37 | elif grep -Eqi "debian|raspbian" /proc/version; then
38 | release="debian"
39 | systemPackage="apt"
40 | elif grep -Eqi "ubuntu" /proc/version; then
41 | release="ubuntu"
42 | systemPackage="apt"
43 | elif grep -Eqi "centos|red hat|redhat" /proc/version; then
44 | release="centos"
45 | systemPackage="yum"
46 | fi
47 |
48 | if [[ "${checkType}" == "sysRelease" ]]; then
49 | if [ "${value}" == "${release}" ]; then
50 | return 0
51 | else
52 | return 1
53 | fi
54 | elif [[ "${checkType}" == "packageManager" ]]; then
55 | if [ "${value}" == "${systemPackage}" ]; then
56 | return 0
57 | else
58 | return 1
59 | fi
60 | fi
61 | }
62 |
63 | # Get version
64 | getversion(){
65 | if [[ -s /etc/redhat-release ]]; then
66 | grep -oE "[0-9.]+" /etc/redhat-release
67 | else
68 | grep -oE "[0-9.]+" /etc/issue
69 | fi
70 | }
71 |
72 | # CentOS version
73 | centosversion(){
74 | if check_sys sysRelease centos; then
75 | local code=$1
76 | local version="$(getversion)"
77 | local main_ver=${version%%.*}
78 | if [ "$main_ver" == "$code" ]; then
79 | return 0
80 | else
81 | return 1
82 | fi
83 | else
84 | return 1
85 | fi
86 | }
87 |
88 | get_char(){
89 | SAVEDSTTY=`stty -g`
90 | stty -echo
91 | stty cbreak
92 | dd if=/dev/tty bs=1 count=1 2> /dev/null
93 | stty -raw
94 | stty echo
95 | stty $SAVEDSTTY
96 | }
97 | error_detect_depends(){
98 | local command=$1
99 | local depend=`echo "${command}" | awk '{print $4}'`
100 | echo -e "[${green}Info${plain}] Starting to install package ${depend}"
101 | ${command} > /dev/null 2>&1
102 | if [ $? -ne 0 ]; then
103 | echo -e "[${red}Error${plain}] Failed to install ${red}${depend}${plain}"
104 | echo "Please visit: https://teddysun.com/486.html and contact."
105 | exit 1
106 | fi
107 | }
108 |
109 | # Pre-installation settings
110 | pre_install_docker_compose(){
111 | echo "Which Panel Do you use SSpanel 0, SSRpanel 1"
112 | read -p "(v2ray_paneltype (Default 0):" v2ray_paneltype
113 | [ -z "${v2ray_paneltype}" ] && v2ray_paneltype=0
114 | echo
115 | echo "---------------------------"
116 | echo "v2ray_paneltype = ${v2ray_paneltype}"
117 | echo "---------------------------"
118 | echo
119 | # Set ssrpanel node_id
120 | echo "node_id"
121 | read -p "(Default value: 0 ):" ssrpanel_node_id
122 | [ -z "${ssrpanel_node_id}" ] && ssrpanel_node_id=0
123 | echo
124 | echo "---------------------------"
125 | echo "node_id = ${ssrpanel_node_id}"
126 | echo "---------------------------"
127 | echo
128 |
129 |
130 | echo "Which connection do you prefer 0 for webapi and 1 for mysql"
131 | read -p "(v2ray_usemysql (Default 0):" v2ray_usemysql
132 | [ -z "${v2ray_usemysql}" ] && v2ray_usemysql=0
133 | echo
134 | echo "---------------------------"
135 | echo "v2ray_usemysql = ${v2ray_usemysql}"
136 | echo "---------------------------"
137 | echo
138 |
139 | # Set ssrpanel_url
140 | echo "Please sspanel_url, u can pass this setting if u use mysql"
141 | read -p "(There is no default value please make sure you input the right thing):" ssrpanel_url
142 | [ -z "${ssrpanel_url}" ]
143 | echo
144 | echo "---------------------------"
145 | echo "ssrpanel_url = ${ssrpanel_url}"
146 | echo "---------------------------"
147 | echo
148 | # Set ssrpanel key
149 | echo "sspanel key u can pass this setting if u use mysql"
150 | read -p "(There is no default value please make sure you input the right thing):" ssrpanel_key
151 | [ -z "${ssrpanel_key}" ]
152 | echo
153 | echo "---------------------------"
154 | echo "ssrpanel_key = ${ssrpanel_key}"
155 | echo "---------------------------"
156 | echo
157 | # Set Setting if the node go downwith panel
158 | echo "Setting Myqlhost, u can pass this setting u chosen use webapi"
159 | read -p "(v2ray_downWithPanel :" v2ray_mysqlhost
160 | [ -z "${v2ray_mysqlhost}" ] && v2ray_mysqlhost=""
161 | echo
162 | echo "---------------------------"
163 | echo "v2ray_mysqlhost = ${v2ray_mysqlhost}"
164 | echo "---------------------------"
165 | echo
166 | # Set Setting if the node go downwith panel
167 | echo "Setting MysqlPort u can pass this setting u chosen use webapi"
168 | read -p "(v2ray_mysqlport (Default 3306):" v2ray_mysqlport
169 | [ -z "${v2ray_mysqlport}" ] && v2ray_mysqlport=3306
170 | echo
171 | echo "---------------------------"
172 | echo "v2ray_mysqlport = ${v2ray_mysqlport}"
173 | echo "---------------------------"
174 | echo
175 | # Set Setting if the node go downwith panel
176 | echo "Setting MysqlUser u can pass this setting if u use webapi"
177 | read -p "(v2ray_myqluser (Default root):" v2ray_myqluser
178 | [ -z "${v2ray_myqluser}" ] && v2ray_myqluser="root"
179 | echo
180 | echo "---------------------------"
181 | echo "v2ray_myqluser = ${v2ray_myqluser}"
182 | echo "---------------------------"
183 | echo
184 | # Set Setting if the node go downwith panel
185 | echo "Setting MysqlPassword u can pass this setting if u use webapi"
186 | read -p "(v2ray_mysqlpassword (Default 1):" v2ray_mysqlpassword
187 | [ -z "${v2ray_mysqlpassword}" ] && v2ray_mysqlpassword=1
188 | echo
189 | echo "---------------------------"
190 | echo "v2ray_mysqlpassword = ${v2ray_mysqlpassword}"
191 | echo "---------------------------"
192 | echo
193 | # Set Setting if the node go downwith panel
194 | echo "Setting MysqlDbname u can pass this setting u chosen use webapi"
195 | read -p "(v2ray_mysqldbname (Default sspanel):" v2ray_mysqldbname
196 | [ -z "${v2ray_mysqldbname}" ] && v2ray_mysqldbname="sspanel"
197 | echo
198 | echo "---------------------------"
199 | echo "v2ray_mysqldbname = ${v2ray_mysqldbname}"
200 | echo "---------------------------"
201 | echo
202 |
203 |
204 | # Set ssrpanel speedtest function
205 | echo "use sspanel speedtest"
206 | read -p "(sspanel speedtest: Default (6) hours every time):" ssrpanel_speedtest
207 | [ -z "${ssrpanel_speedtest}" ] && ssrpanel_speedtest=6
208 | echo
209 | echo "---------------------------"
210 | echo "ssrpanel_speedtest = ${ssrpanel_speedtest}"
211 | echo "---------------------------"
212 | echo
213 |
214 | # Set V2ray backend API Listen port
215 | echo "Setting V2ray Grpc API Listen port"
216 | read -p "(V2ray Grpc API Listen port(Default 2333):" v2ray_api_port
217 | [ -z "${v2ray_api_port}" ] && v2ray_api_port=2333
218 | echo
219 | echo "---------------------------"
220 | echo "V2ray Grpc API Listen port = ${v2ray_api_port}"
221 | echo "---------------------------"
222 | echo
223 |
224 | # Set Setting if the node go downwith panel
225 | echo "Setting if the node go downwith panel"
226 | read -p "(v2ray_downWithPanel (Default 1):" v2ray_downWithPanel
227 | [ -z "${v2ray_downWithPanel}" ] && v2ray_downWithPanel=1
228 | echo
229 | echo "---------------------------"
230 | echo "v2ray_downWithPanel = ${v2ray_downWithPanel}"
231 | echo "---------------------------"
232 | echo
233 |
234 | # Set Setting if the node go downwith panel
235 |
236 | }
237 |
238 | pre_install_caddy(){
239 |
240 | # Set caddy v2ray domain
241 | echo "caddy v2ray domain"
242 | read -p "(There is no default value please make sure you input the right thing):" v2ray_domain
243 | [ -z "${v2ray_domain}" ]
244 | echo
245 | echo "---------------------------"
246 | echo "v2ray_domain = ${v2ray_domain}"
247 | echo "---------------------------"
248 | echo
249 |
250 |
251 | # Set caddy v2ray path
252 | echo "caddy v2ray path"
253 | read -p "(Default path: /v2ray):" v2ray_path
254 | [ -z "${v2ray_path}" ] && v2ray_path="/v2ray"
255 | echo
256 | echo "---------------------------"
257 | echo "v2ray_path = ${v2ray_path}"
258 | echo "---------------------------"
259 | echo
260 |
261 | # Set caddy v2ray tls email
262 | echo "caddy v2ray tls email"
263 | read -p "(No default ):" v2ray_email
264 | [ -z "${v2ray_email}" ]
265 | echo
266 | echo "---------------------------"
267 | echo "v2ray_email = ${v2ray_email}"
268 | echo "---------------------------"
269 | echo
270 |
271 | # Set Caddy v2ray listen port
272 | echo "caddy v2ray local listen port"
273 | read -p "(Default port: 10550):" v2ray_local_port
274 | [ -z "${v2ray_local_port}" ] && v2ray_local_port=10550
275 | echo
276 | echo "---------------------------"
277 | echo "v2ray_local_port = ${v2ray_local_port}"
278 | echo "---------------------------"
279 | echo
280 |
281 | # Set Caddy listen port
282 | echo "caddy listen port"
283 | read -p "(Default port: 443):" caddy_listen_port
284 | [ -z "${caddy_listen_port}" ] && caddy_listen_port=443
285 | echo
286 | echo "---------------------------"
287 | echo "caddy_listen_port = ${caddy_listen_port}"
288 | echo "---------------------------"
289 | echo
290 |
291 |
292 | }
293 |
294 | # Config docker
295 | config_docker(){
296 | echo "Press any key to start...or Press Ctrl+C to cancel"
297 | char=`get_char`
298 | cd ${cur_dir}
299 | echo "install curl"
300 | install_dependencies
301 | echo "Writing docker-compose.yml"
302 | curl -L https://raw.githubusercontent.com/githubphone/pay-v2ray-sspanel-v3-mod_Uim-plugin/master/Docker/V2ray/docker-compose.yml > docker-compose.yml
303 | sed -i "s|node_id:.*|node_id: ${ssrpanel_node_id}|" ./docker-compose.yml
304 | sed -i "s|sspanel_url:.*|sspanel_url: '${ssrpanel_url}'|" ./docker-compose.yml
305 | sed -i "s|key:.*|key: '${ssrpanel_key}'|" ./docker-compose.yml
306 | sed -i "s|speedtest:.*|speedtest: ${ssrpanel_speedtest}|" ./docker-compose.yml
307 | sed -i "s|api_port:.*|api_port: ${v2ray_api_port}|" ./docker-compose.yml
308 | sed -i "s|downWithPanel:.*|downWithPanel: ${v2ray_downWithPanel}|" ./docker-compose.yml
309 | sed -i "s|usemysql:.*|usemysql: ${v2ray_usemysql}|" ./docker-compose.yml
310 | sed -i "s|PANELTYPE:.*|PANELTYPE: ${v2ray_paneltype}|" ./docker-compose.yml
311 | sed -i "s|MYSQLHOST:.*|MYSQLHOST: ${v2ray_mysqlhost}|" ./docker-compose.yml
312 | sed -i "s|MYSQLPORT:.*|MYSQLPORT: ${v2ray_mysqlport}|" ./docker-compose.yml
313 | sed -i "s|MYSQLUSR:.*|MYSQLUSR: ${v2ray_myqluser}|" ./docker-compose.yml
314 | sed -i "s|MYSQLPASSWD:.*|MYSQLPASSWD: ${v2ray_mysqlpassword}|" ./docker-compose.yml
315 | sed -i "s|MYSQLDBNAME:.*|MYSQLDBNAME: ${v2ray_mysqldbname}|" ./docker-compose.yml
316 | }
317 |
318 |
319 | # Config caddy_docker
320 | config_caddy_docker(){
321 | echo "Press any key to start...or Press Ctrl+C to cancel"
322 | char=`get_char`
323 | cd ${cur_dir}
324 | echo "install curl"
325 | install_dependencies
326 | curl -L https://raw.githubusercontent.com/githubphone/pay-v2ray-sspanel-v3-mod_Uim-plugin/master/Docker/Caddy_V2ray/Caddyfile > Caddyfile
327 | echo "Writing docker-compose.yml"
328 | curl -L https://raw.githubusercontent.com/githubphone/pay-v2ray-sspanel-v3-mod_Uim-plugin/master/Docker/Caddy_V2ray/docker-compose.yml > docker-compose.yml
329 | sed -i "s|node_id:.*|node_id: ${ssrpanel_node_id}|" ./docker-compose.yml
330 | sed -i "s|sspanel_url:.*|sspanel_url: '${ssrpanel_url}'|" ./docker-compose.yml
331 | sed -i "s|key:.*|key: '${ssrpanel_key}'|" ./docker-compose.yml
332 | sed -i "s|speedtest:.*|speedtest: ${ssrpanel_speedtest}|" ./docker-compose.yml
333 | sed -i "s|api_port:.*|api_port: ${v2ray_api_port}|" ./docker-compose.yml
334 | sed -i "s|downWithPanel:.*|downWithPanel: ${v2ray_downWithPanel}|" ./docker-compose.yml
335 | sed -i "s|usemysql:.*|usemysql: ${v2ray_usemysql}|" ./docker-compose.yml
336 | sed -i "s|PANELTYPE:.*|PANELTYPE: ${v2ray_paneltype}|" ./docker-compose.yml
337 | sed -i "s|MYSQLHOST:.*|MYSQLHOST: ${v2ray_mysqlhost}|" ./docker-compose.yml
338 | sed -i "s|MYSQLPORT:.*|MYSQLPORT: ${v2ray_mysqlport}|" ./docker-compose.yml
339 | sed -i "s|MYSQLUSR:.*|MYSQLUSR: ${v2ray_myqluser}|" ./docker-compose.yml
340 | sed -i "s|MYSQLPASSWD:.*|MYSQLPASSWD: ${v2ray_mysqlpassword}|" ./docker-compose.yml
341 | sed -i "s|MYSQLDBNAME:.*|MYSQLDBNAME: ${v2ray_mysqldbname}|" ./docker-compose.yml
342 | sed -i "s|V2RAY_DOMAIN=xxxx.com|V2RAY_DOMAIN=${v2ray_domain}|" ./docker-compose.yml
343 | sed -i "s|V2RAY_PATH=/v2ray|V2RAY_PATH=${v2ray_path}|" ./docker-compose.yml
344 | sed -i "s|V2RAY_EMAIL=xxxx@outlook.com|V2RAY_EMAIL=${v2ray_email}|" ./docker-compose.yml
345 | sed -i "s|V2RAY_PORT=10550|V2RAY_PORT=${v2ray_local_port}|" ./docker-compose.yml
346 | sed -i "s|V2RAY_OUTSIDE_PORT=443|V2RAY_OUTSIDE_PORT=${caddy_listen_port}|" ./docker-compose.yml
347 | }
348 |
349 | # Config caddy_docker
350 | config_caddy_docker_cloudflare(){
351 |
352 | # Set caddy cloudflare ddns email
353 | echo "caddy cloudflare ddns email"
354 | read -p "(No default ):" cloudflare_email
355 | [ -z "${cloudflare_email}" ]
356 | echo
357 | echo "---------------------------"
358 | echo "cloudflare_email = ${cloudflare_email}"
359 | echo "---------------------------"
360 | echo
361 |
362 | # Set caddy cloudflare ddns key
363 | echo "caddy cloudflare ddns key"
364 | read -p "(No default ):" cloudflare_key
365 | [ -z "${cloudflare_email}" ]
366 | echo
367 | echo "---------------------------"
368 | echo "cloudflare_email = ${cloudflare_key}"
369 | echo "---------------------------"
370 | echo
371 | echo
372 |
373 | echo "Press any key to start...or Press Ctrl+C to cancel"
374 | char=`get_char`
375 | cd ${cur_dir}
376 | echo "install curl first "
377 | install_dependencies
378 | echo "Starting Writing Caddy file and docker-compose.yml"
379 | curl -L https://raw.githubusercontent.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/master/Docker/Caddy_V2ray/Caddyfile >Caddyfile
380 | epcho "Writing docker-compose.yml"
381 | curl -L https://raw.githubusercontent.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/master/Docker/Caddy_V2ray/docker-compose.yml >docker-compose.yml
382 | sed -i "s|node_id:.*|node_id: ${ssrpanel_node_id}|" ./docker-compose.yml
383 | sed -i "s|sspanel_url:.*|sspanel_url: '${ssrpanel_url}'|" ./docker-compose.yml
384 | sed -i "s|key:.*|key: '${ssrpanel_key}'|" ./docker-compose.yml
385 | sed -i "s|speedtest:.*|speedtest: ${ssrpanel_speedtest}|" ./docker-compose.yml
386 | sed -i "s|api_port:.*|api_port: ${v2ray_api_port}|" ./docker-compose.yml
387 | sed -i "s|downWithPanel:.*|downWithPanel: ${v2ray_downWithPanel}|" ./docker-compose.yml
388 | sed -i "s|usemysql:.*|usemysql: ${v2ray_usemysql}|" ./docker-compose.yml
389 | sed -i "s|PANELTYPE:.*|PANELTYPE: ${v2ray_paneltype}|" ./docker-compose.yml
390 | sed -i "s|MYSQLHOST:.*|MYSQLHOST: ${v2ray_mysqlhost}|" ./docker-compose.yml
391 | sed -i "s|MYSQLPORT:.*|MYSQLPORT: ${v2ray_mysqlport}|" ./docker-compose.yml
392 | sed -i "s|MYSQLUSR:.*|MYSQLUSR: ${v2ray_myqluser}|" ./docker-compose.yml
393 | sed -i "s|MYSQLPASSWD:.*|MYSQLPASSWD: ${v2ray_mysqlpassword}|" ./docker-compose.yml
394 | sed -i "s|MYSQLDBNAME:.*|MYSQLDBNAME: ${v2ray_mysqldbname}|" ./docker-compose.yml
395 | sed -i "s|V2RAY_DOMAIN=xxxx.com|V2RAY_DOMAIN=${v2ray_domain}|" ./docker-compose.yml
396 | sed -i "s|V2RAY_PATH=/v2ray|V2RAY_PATH=${v2ray_path}|" ./docker-compose.yml
397 | sed -i "s|V2RAY_EMAIL=xxxx@outlook.com|V2RAY_EMAIL=${v2ray_email}|" ./docker-compose.yml
398 | sed -i "s|V2RAY_PORT=10550|V2RAY_PORT=${v2ray_local_port}|" ./docker-compose.yml
399 | sed -i "s|V2RAY_OUTSIDE_PORT=443|V2RAY_OUTSIDE_PORT=${caddy_listen_port}|" ./docker-compose.yml
400 | sed -i "s|# - CLOUDFLARE_EMAIL=xxxxxx@out.look.com| - CLOUDFLARE_EMAIL=${cloudflare_email}|" ./docker-compose.yml
401 | sed -i "s|# - CLOUDFLARE_API_KEY=xxxxxxx| - CLOUDFLARE_API_KEY=${cloudflare_key}|" ./docker-compose.yml
402 | sed -i "s|# dns cloudflare|dns cloudflare|" ./Caddyfile
403 |
404 | }
405 |
406 | # Install docker and docker compose
407 | install_docker(){
408 | echo -e "Starting installing Docker "
409 | curl -fsSL https://get.docker.com -o get-docker.sh
410 | bash get-docker.sh
411 | echo -e "Starting installing Docker Compose "
412 | curl -L https://github.com/docker/compose/releases/download/1.17.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
413 | chmod +x /usr/local/bin/docker-compose
414 | curl -L https://raw.githubusercontent.com/docker/compose/1.8.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
415 | clear
416 | echo "Start Docker "
417 | service docker start
418 | echo "Start Docker-Compose "
419 | docker-compose pull
420 | docker-compose up -d
421 | echo
422 | echo -e "Congratulations, V2ray server install completed!"
423 | echo
424 | echo "Enjoy it!"
425 | echo
426 | }
427 |
428 | install_check(){
429 | if check_sys packageManager yum || check_sys packageManager apt; then
430 | if centosversion 5; then
431 | return 1
432 | fi
433 | return 0
434 | else
435 | return 1
436 | fi
437 | }
438 |
439 | install_select(){
440 | clear
441 | while true
442 | do
443 | echo "Which v2ray Docker you'd select:"
444 | for ((i=1;i<=${#software[@]};i++ )); do
445 | hint="${software[$i-1]}"
446 | echo -e "${green}${i}${plain}) ${hint}"
447 | done
448 | read -p "Please enter a number (Default ${software[0]}):" selected
449 | [ -z "${selected}" ] && selected="1"
450 | case "${selected}" in
451 | 1|2|3|4)
452 | echo
453 | echo "You choose = ${software[${selected}-1]}"
454 | echo
455 | break
456 | ;;
457 | *)
458 | echo -e "[${red}Error${plain}] Please only enter a number [1-4]"
459 | ;;
460 | esac
461 | done
462 | }
463 | install_dependencies(){
464 | if check_sys packageManager yum; then
465 | echo -e "[${green}Info${plain}] Checking the EPEL repository..."
466 | if [ ! -f /etc/yum.repos.d/epel.repo ]; then
467 | yum install -y epel-release > /dev/null 2>&1
468 | fi
469 | [ ! -f /etc/yum.repos.d/epel.repo ] && echo -e "[${red}Error${plain}] Install EPEL repository failed, please check it." && exit 1
470 | [ ! "$(command -v yum-config-manager)" ] && yum install -y yum-utils > /dev/null 2>&1
471 | [ x"$(yum-config-manager epel | grep -w enabled | awk '{print $3}')" != x"True" ] && yum-config-manager --enable epel > /dev/null 2>&1
472 | echo -e "[${green}Info${plain}] Checking the EPEL repository complete..."
473 |
474 | yum_depends=(
475 | curl
476 | )
477 | for depend in ${yum_depends[@]}; do
478 | error_detect_depends "yum -y install ${depend}"
479 | done
480 | elif check_sys packageManager apt; then
481 | apt_depends=(
482 | curl
483 | )
484 | apt-get -y update
485 | for depend in ${apt_depends[@]}; do
486 | error_detect_depends "apt-get -y install ${depend}"
487 | done
488 | fi
489 | echo -e "[${green}Info${plain}] Setting TimeZone to Shanghai"
490 | ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
491 | date -s "$(curl -sI g.cn | grep Date | cut -d' ' -f3-6)Z"
492 |
493 | }
494 | #update_image
495 | update_image_v2ray(){
496 | echo "Shut down the current service"
497 | docker-compose down
498 | echo "Pulling Images"
499 | docker-compose pull
500 | echo "Start Service"
501 | docker-compose up -d
502 | }
503 |
504 | #show last 100 line log
505 |
506 | logs_v2ray(){
507 | echo "Last 100 line logs"
508 | docker-compose logs --tail 100
509 | }
510 |
511 | # Update config
512 | update_config_v2ray(){
513 | cd ${cur_dir}
514 | echo "Shut down the current service"
515 | docker-compose down
516 | install_select
517 | case "${selected}" in
518 | 1)
519 | pre_install_docker_compose
520 | pre_install_caddy
521 | config_caddy_docker
522 | ;;
523 | 2)
524 | pre_install_docker_compose
525 | pre_install_caddy
526 | config_caddy_docker_cloudflare
527 | ;;
528 | 3)
529 | pre_install_docker_compose
530 | config_docker
531 | ;;
532 | *)
533 | echo "Wrong number"
534 | ;;
535 | esac
536 |
537 | echo "Start Service"
538 | docker-compose pull
539 | docker-compose up -d
540 |
541 | }
542 | # remove config
543 | # Install v2ray
544 | install_v2ray(){
545 | install_select
546 | case "${selected}" in
547 | 1)
548 | pre_install_docker_compose
549 | pre_install_caddy
550 | config_caddy_docker
551 | ;;
552 | 2)
553 | pre_install_docker_compose
554 | pre_install_caddy
555 | config_caddy_docker_cloudflare
556 | ;;
557 | 3)
558 | pre_install_docker_compose
559 | config_docker
560 | ;;
561 | *)
562 | echo "Wrong number"
563 | ;;
564 | esac
565 | install_docker
566 | }
567 |
568 | # Initialization step
569 | clear
570 | while true
571 | do
572 | echo "Which operation you'd select:"
573 | for ((i=1;i<=${#operation[@]};i++ )); do
574 | hint="${operation[$i-1]}"
575 | echo -e "${green}${i}${plain}) ${hint}"
576 | done
577 | read -p "Please enter a number (Default ${operation[0]}):" selected
578 | [ -z "${selected}" ] && selected="1"
579 | case "${selected}" in
580 | 1|2|3|4)
581 | echo
582 | echo "You choose = ${operation[${selected}-1]}"
583 | echo
584 | ${operation[${selected}-1]}_v2ray
585 | break
586 | ;;
587 | *)
588 | echo -e "[${red}Error${plain}] Please only enter a number [1-4]"
589 | ;;
590 | esac
591 | done
592 |
--------------------------------------------------------------------------------
/model/model.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type UserModel struct {
4 | UserID uint `json:"id"`
5 | Uuid string `json:"uuid"`
6 | Email string `json:"email"`
7 | Passwd string `json:"passwd"`
8 | Method string `json:"method"`
9 | Port uint16 `json:"port"`
10 | NodeSpeedlimit uint `json:"node_speedlimit"`
11 | Obfs string `json:"obfs"`
12 | Protocol string `json:"protocol"`
13 | Rate uint32
14 | AlterId uint32
15 | PrefixedId string
16 | Muhost string
17 | }
18 |
19 | type UserTrafficLog struct {
20 | UserID uint `json:"user_id"`
21 | Uplink uint64 `json:"u"`
22 | Downlink uint64 `json:"d"`
23 | }
24 |
25 | type NodeInfo struct {
26 | NodeID uint
27 | NodeSpeedlimit uint `json:"node_speedlimit"`
28 | Server_raw string `json:"server"`
29 | Sort uint `json:"sort"`
30 | Server map[string]interface{}
31 | TrafficRate float64
32 | }
33 |
34 | type UserOnLineIP struct {
35 | UserId uint `json:"user_id"`
36 | Ip string `json:"ip"`
37 | }
38 |
39 | type DisNodeInfo struct {
40 | Server_raw string `json:"dist_node_server"`
41 | Sort uint `json:"dist_node_sort"`
42 | Port uint16 `json:"port"`
43 | Server map[string]interface{}
44 | UserId uint `json:"user_id"`
45 | }
46 |
--------------------------------------------------------------------------------
/panel.go:
--------------------------------------------------------------------------------
1 | package v2ray_sspanel_v3_mod_Uim_plugin
2 |
3 | import (
4 | "fmt"
5 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/Manager"
6 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/client"
7 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/config"
8 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/db"
9 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/model"
10 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/speedtest"
11 | "github.com/robfig/cron"
12 | "google.golang.org/grpc"
13 | "reflect"
14 | "runtime"
15 | )
16 |
17 | type Panel struct {
18 | db db.Db
19 | manager *Manager.Manager
20 | speedtestClient speedtest.Client
21 | downwithpanel int
22 | }
23 |
24 | func NewPanel(gRPCConn *grpc.ClientConn, db db.Db, cfg *config.Config) (*Panel, error) {
25 | opts := speedtest.NewOpts()
26 | speedtestClient := speedtest.NewClient(opts)
27 | var newpanel = Panel{
28 | speedtestClient: speedtestClient,
29 | db: db,
30 | downwithpanel: cfg.DownWithPanel,
31 | manager: &Manager.Manager{
32 | HandlerServiceClient: client.NewHandlerServiceClient(gRPCConn, "MAIN_INBOUND"),
33 | StatsServiceClient: client.NewStatsServiceClient(gRPCConn),
34 | RuleServiceClient: client.NewRuleServerClient(gRPCConn),
35 | NodeID: cfg.NodeID,
36 | CheckRate: cfg.CheckRate,
37 | SpeedTestCheckRate: cfg.SpeedTestCheckRate,
38 | CurrentNodeInfo: &model.NodeInfo{},
39 | NextNodeInfo: &model.NodeInfo{},
40 | Users: map[string]model.UserModel{},
41 | UserToBeMoved: map[string]model.UserModel{},
42 | UserToBeAdd: map[string]model.UserModel{},
43 | Id2PrefixedIdmap: map[uint]string{},
44 | Id2DisServer: map[uint]string{},
45 | },
46 | }
47 | return &newpanel, nil
48 | }
49 |
50 | func (p *Panel) Start() {
51 | doFunc := func() {
52 | if err := p.do(); err != nil {
53 | newError("panel#do").Base(err).AtError().WriteToLog()
54 | }
55 | // Explicitly triggering GC to remove garbage
56 | runtime.GC()
57 | }
58 | doFunc()
59 |
60 | speedTestFunc := func() {
61 | result, err := speedtest.GetSpeedtest(p.speedtestClient)
62 | if err != nil {
63 | newError("panel#speedtest").Base(err).AtError().WriteToLog()
64 | }
65 | newError(result).AtInfo().WriteToLog()
66 | if p.db.UploadSpeedTest(p.manager.NodeID, result) {
67 | newError("succesfully upload speedtest result").AtInfo().WriteToLog()
68 | } else {
69 | newError("failed to upload speedtest result").AtInfo().WriteToLog()
70 | }
71 | // Explicitly triggering GC to remove garbage
72 | runtime.GC()
73 | }
74 | c := cron.New()
75 | err := c.AddFunc(fmt.Sprintf("@every %ds", p.manager.CheckRate), doFunc)
76 | if err != nil {
77 | fatal(err)
78 | }
79 | if p.manager.SpeedTestCheckRate > 0 {
80 | newErrorf("SpeedTest @every %dh", p.manager.SpeedTestCheckRate).AtInfo().WriteToLog()
81 | err = c.AddFunc(fmt.Sprintf("@every %dh", p.manager.SpeedTestCheckRate), speedTestFunc)
82 | if err != nil {
83 | newError("Can't add speed test into cron").AtWarning().WriteToLog()
84 | }
85 | }
86 | c.Start()
87 | c.Run()
88 | }
89 |
90 | func (p *Panel) do() error {
91 | if p.updateManager() {
92 | p.updateThroughout()
93 | }
94 | return nil
95 | }
96 | func (p *Panel) initial() {
97 | newError("initial system").AtWarning().WriteToLog()
98 | p.manager.RemoveInbound()
99 | p.manager.CopyUsers()
100 | p.manager.UpdataUsers()
101 | p.manager.RemoveAllUserOutBound()
102 | p.manager.CurrentNodeInfo = &model.NodeInfo{}
103 | p.manager.NextNodeInfo = &model.NodeInfo{}
104 | p.manager.UserToBeAdd = map[string]model.UserModel{}
105 | p.manager.UserToBeMoved = map[string]model.UserModel{}
106 | p.manager.Users = map[string]model.UserModel{}
107 | p.manager.Id2PrefixedIdmap = map[uint]string{}
108 | p.manager.Id2DisServer = map[uint]string{}
109 |
110 | }
111 |
112 | func (p *Panel) updateManager() bool {
113 | newNodeinfo, err := p.db.GetNodeInfo(p.manager.NodeID)
114 | if err != nil {
115 | newError("Can't get nodeinfo").Base(err).AtWarning().WriteToLog()
116 | if p.downwithpanel == 1 {
117 | p.initial()
118 | }
119 | return false
120 | }
121 | if newNodeinfo.Ret != 1 {
122 | newError("please check your setting nodata finded").AtWarning().WriteToLog()
123 | if p.downwithpanel == 1 {
124 | p.initial()
125 | }
126 | return false
127 | }
128 | newErrorf("old node info %s ", p.manager.NextNodeInfo.Server_raw).AtInfo().WriteToLog()
129 | newErrorf("new node info %s", newNodeinfo.Data.Server_raw).AtInfo().WriteToLog()
130 | if p.manager.NextNodeInfo.Server_raw != newNodeinfo.Data.Server_raw {
131 | p.manager.NextNodeInfo = newNodeinfo.Data
132 | if err = p.manager.UpdateServer(); err != nil {
133 | newError(err).AtWarning().WriteToLog()
134 | }
135 | p.manager.UserChanged = true
136 | }
137 | users, err := p.db.GetALLUsers(p.manager.NextNodeInfo)
138 | if err != nil {
139 | newError(err).AtDebug().WriteToLog()
140 | }
141 | newError("now begin to check users").AtInfo().WriteToLog()
142 | current_user := p.manager.GetUsers()
143 | // remove user by prefixed_id
144 | for key, _ := range current_user {
145 | _, ok := users.Data[key]
146 | if !ok {
147 | p.manager.Remove(key)
148 | newErrorf("need to remove client: %s.", key).AtInfo().WriteToLog()
149 | }
150 | }
151 | // add users
152 | for key, value := range users.Data {
153 | current, ok := current_user[key]
154 | if !ok {
155 | p.manager.Add(value)
156 | newErrorf("need to add user email %s", key).AtInfo().WriteToLog()
157 | } else {
158 | if !reflect.DeepEqual(value, current) {
159 | p.manager.Remove(key)
160 | p.manager.Add(value)
161 | newErrorf("need to add user email %s due to method or password changed", key).AtInfo().WriteToLog()
162 | }
163 | }
164 |
165 | }
166 |
167 | if p.manager.UserChanged {
168 | p.manager.UserChanged = false
169 | newErrorf("Before Update, Current Users %d need to be add %d need to be romved %d", len(p.manager.Users),
170 | len(p.manager.UserToBeAdd), len(p.manager.UserToBeMoved)).AtWarning().WriteToLog()
171 | p.manager.UpdataUsers()
172 | newErrorf("After Update, Current Users %d need to be add %d need to be romved %d", len(p.manager.Users),
173 | len(p.manager.UserToBeAdd), len(p.manager.UserToBeMoved)).AtWarning().WriteToLog()
174 | p.manager.CopyNodeinfo()
175 | } else {
176 | newError("check ports finished. No need to update ").AtInfo().WriteToLog()
177 | }
178 | if newNodeinfo.Data.Sort == 12 {
179 | newError("Start to check relay rules ").AtInfo().WriteToLog()
180 | p.updateOutbounds()
181 | }
182 | return true
183 | }
184 | func (p *Panel) updateOutbounds() {
185 | data, err := p.db.GetDisNodeInfo(p.manager.NodeID)
186 | if err != nil {
187 | newError(err).AtWarning().WriteToLog()
188 | if p.downwithpanel == 1 {
189 | p.initial()
190 | }
191 | return
192 | }
193 | if data.Ret != 1 {
194 | newError(data.Data).AtWarning().WriteToLog()
195 | if p.downwithpanel == 1 {
196 | p.initial()
197 | }
198 | return
199 | }
200 | if len(data.Data) > 0 {
201 | newErrorf("Recieve %d User Rules", len(data.Data)).AtInfo().WriteToLog()
202 | globalSettingindex := -1
203 | for index, value := range data.Data {
204 | if value.UserId == 0 {
205 | globalSettingindex = index
206 | break
207 | }
208 | }
209 | if globalSettingindex != -1 {
210 | nextserver := data.Data[globalSettingindex]
211 | newErrorf("Got A Global Rule %s ", nextserver.Server_raw).AtInfo().WriteToLog()
212 | remove_count := 0
213 | add_count := 0
214 | for _, user := range p.manager.Users {
215 | currentserver, find := p.manager.Id2DisServer[user.UserID]
216 | nextserver.UserId = user.UserID
217 | if find {
218 | if currentserver != nextserver.Server_raw {
219 | p.manager.RemoveOutBound(currentserver+fmt.Sprintf("%d", user.UserID), user.UserID)
220 | err := p.manager.AddOuntBound(nextserver)
221 | if err != nil {
222 | newError("ADDOUTBOUND ").Base(err).AtInfo().WriteToLog()
223 | } else {
224 |
225 | remove_count += 1
226 | add_count += 1
227 | }
228 | }
229 | } else {
230 | err := p.manager.AddOuntBound(nextserver)
231 | if err != nil {
232 | newError("ADDOUTBOUND ").Base(err).AtInfo().WriteToLog()
233 | } else {
234 | add_count += 1
235 | }
236 | }
237 | }
238 | p.manager.Id2DisServer = map[uint]string{}
239 | for _, user := range p.manager.Users {
240 | p.manager.Id2DisServer[user.UserID] = nextserver.Server_raw
241 | }
242 | newErrorf("Add %d and REMOVE %d Rules, Current Rules %d", add_count, remove_count, len(p.manager.Id2DisServer)).AtInfo().WriteToLog()
243 |
244 | } else {
245 | remove_count := 0
246 | add_count := 0
247 | for _, value := range data.Data {
248 | _, find := p.manager.Id2DisServer[value.UserId]
249 | if !find {
250 | err := p.manager.AddOuntBound(value)
251 | if err != nil {
252 | newError("ADDOUTBOUND ").Base(err).AtInfo().WriteToLog()
253 | } else {
254 | add_count += 1
255 | }
256 | }
257 | }
258 | for id, currentserver := range p.manager.Id2DisServer {
259 | flag := false
260 | currenttag := currentserver + fmt.Sprintf("%d", id)
261 | for _, nextserver := range data.Data {
262 | if id == nextserver.UserId && currenttag == nextserver.Server_raw+fmt.Sprintf("%d", nextserver.UserId) {
263 | flag = true
264 | break
265 | } else if id == nextserver.UserId && currenttag != nextserver.Server_raw+fmt.Sprintf("%d", nextserver.UserId) {
266 | p.manager.RemoveOutBound(currenttag, id)
267 | err := p.manager.AddOuntBound(nextserver)
268 | if err != nil {
269 | newError("ADDOUTBOUND ").Base(err).AtInfo().WriteToLog()
270 | } else {
271 | remove_count += 1
272 | add_count += 1
273 | }
274 | flag = true
275 | break
276 | }
277 | if !flag {
278 | p.manager.RemoveOutBound(currenttag, id)
279 | remove_count += 1
280 | }
281 | }
282 |
283 | }
284 |
285 | p.manager.Id2DisServer = map[uint]string{}
286 | for _, nextserver := range data.Data {
287 | p.manager.Id2DisServer[nextserver.UserId] = nextserver.Server_raw
288 | }
289 | newErrorf("Add %d and REMOVE %d Rules, Current Rules %d", add_count, remove_count, len(p.manager.Id2DisServer)).AtInfo().WriteToLog()
290 | }
291 | } else {
292 | newErrorf("There is No User Rules, Need To Remove %d RULEs", len(p.manager.Id2DisServer)).AtInfo().WriteToLog()
293 | if len(p.manager.Id2DisServer) > 0 {
294 | remove_count := 0
295 | add_count := 0
296 | for id, currentserver := range p.manager.Id2DisServer {
297 | currenttag := currentserver + fmt.Sprintf("%d", id)
298 | p.manager.RemoveOutBound(currenttag, id)
299 | remove_count += 1
300 | }
301 | p.manager.Id2DisServer = map[uint]string{}
302 | newErrorf("Add %d and REMOVE %d Rules, Current Rules %d", add_count, remove_count, len(p.manager.Id2DisServer)).AtInfo().WriteToLog()
303 |
304 | }
305 | }
306 |
307 | }
308 |
309 | func (p *Panel) updateThroughout() {
310 | current_user := p.manager.GetUsers()
311 | usertraffic := []model.UserTrafficLog{}
312 | userIPS := []model.UserOnLineIP{}
313 | for _, value := range current_user {
314 | current_upload, err := p.manager.StatsServiceClient.GetUserUplink(value.Email)
315 | current_download, err := p.manager.StatsServiceClient.GetUserDownlink(value.Email)
316 | if err != nil {
317 | newError(err).AtDebug().WriteToLog()
318 | }
319 | if current_upload+current_download > 0 {
320 |
321 | newErrorf("USER %s has use %d", value.Email, current_upload+current_download).AtDebug().WriteToLog()
322 | usertraffic = append(usertraffic, model.UserTrafficLog{
323 | UserID: value.UserID,
324 | Downlink: current_download,
325 | Uplink: current_upload,
326 | })
327 | current_user_ips, err := p.manager.StatsServiceClient.GetUserIPs(value.Email)
328 | if current_upload+current_download > 1024 {
329 | if err != nil {
330 | newError(err).AtDebug().WriteToLog()
331 | }
332 | for index := range current_user_ips {
333 | userIPS = append(userIPS, model.UserOnLineIP{
334 | UserId: value.UserID,
335 | Ip: current_user_ips[index],
336 | })
337 | }
338 |
339 | }
340 | }
341 | }
342 | if p.db.UpLoadUserTraffics(p.manager.NodeID, usertraffic) {
343 | newErrorf("Successfully upload %d users traffics", len(usertraffic)).AtInfo().WriteToLog()
344 | } else {
345 | newError("update trafic failed").AtDebug().WriteToLog()
346 | }
347 | if p.db.UpLoadOnlineIps(p.manager.NodeID, userIPS) {
348 | newErrorf("Successfully upload %d ips", len(userIPS)).AtInfo().WriteToLog()
349 | } else {
350 | newError("update trafic failed").AtDebug().WriteToLog()
351 | }
352 | if p.db.UploadSystemLoad(p.manager.NodeID) {
353 | newError("Uploaded systemLoad successfully").AtInfo().WriteToLog()
354 | } else {
355 | newError("Failed to uploaded systemLoad ").AtDebug().WriteToLog()
356 | }
357 | }
358 |
--------------------------------------------------------------------------------
/plugin.go:
--------------------------------------------------------------------------------
1 | package v2ray_sspanel_v3_mod_Uim_plugin
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "fmt"
7 | "github.com/imroc/req"
8 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/client"
9 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/config"
10 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/db"
11 | "google.golang.org/grpc/status"
12 | "os"
13 | "runtime"
14 | "strings"
15 | "time"
16 | "v2ray.com/core/common/errors"
17 | )
18 |
19 | func init1() {
20 | go func() {
21 | err := run()
22 | if err != nil {
23 | fatal(err)
24 | }
25 | }()
26 | }
27 | func CheckAuth(url string, params map[string]interface{}) (*db.AuthResponse, error) {
28 | var response = db.AuthResponse{}
29 | parm := req.Param{}
30 | for k, v := range params {
31 | parm[k] = v
32 | }
33 | r, err := req.Get(url, parm)
34 | if err != nil {
35 | return nil, err
36 | } else {
37 | err = r.ToJSON(&response)
38 | if err != nil {
39 | return &response, err
40 | } else if response.Ret != 1 {
41 | return nil, err
42 | }
43 | }
44 | return &response, nil
45 | }
46 | func checkAuth(panelurl string) (bool, error) {
47 | md5Ctx := md5.New()
48 | md5Ctx.Write([]byte(panelurl))
49 | cipherStr := md5Ctx.Sum(nil)
50 | current_md5 := hex.EncodeToString(cipherStr)
51 | re, err := CheckAuth("https://auth.rico93.com", map[string]interface{}{"md5": current_md5})
52 | if re != nil {
53 | if re.Token != "" {
54 | auth := config.AESDecodeStr(re.Token, config.Key)
55 | return current_md5 == auth, nil
56 | } else {
57 | return false, newErrorf("Auth failed, current url: %s current md5 %s", panelurl, current_md5)
58 | }
59 | } else {
60 | return false, newErrorf("Can't get data from server or the data is not as expected current url: %s current md5 %s", panelurl, current_md5).Base(err)
61 | }
62 |
63 | }
64 | func run() error {
65 |
66 | err := config.CommandLine.Parse(os.Args[1:])
67 |
68 | cfg, err := config.GetConfig()
69 | if err != nil || *config.Test || cfg == nil {
70 | return err
71 | }
72 |
73 | // wait v2ray
74 | time.Sleep(3 * time.Second)
75 |
76 | go func() {
77 | var ok bool
78 | var err error
79 | if cfg.Usemysql == 0 {
80 | if strings.HasPrefix(cfg.PanelUrl, "https://") || strings.HasPrefix(cfg.PanelUrl, "http://") {
81 | ok, err = checkAuth(cfg.PanelUrl)
82 | } else {
83 | ok, err = checkAuth(cfg.PanelUrl)
84 | cfg.PanelUrl = "https://" + cfg.PanelUrl
85 | }
86 |
87 | } else {
88 | if cfg.MySQL != nil {
89 | ok, err = checkAuth(cfg.MySQL.Host)
90 | } else {
91 | fatal("Please Add Mysql setting")
92 | }
93 | }
94 | if ok && err == nil {
95 | apiInbound := config.GetInboundConfigByTag(cfg.V2rayConfig.Api.Tag, cfg.V2rayConfig.InboundConfigs)
96 | gRPCAddr := fmt.Sprintf("%s:%d", apiInbound.ListenOn.String(), apiInbound.PortRange.From)
97 | gRPCConn, err := client.ConnectGRPC(gRPCAddr, 10*time.Second)
98 | if err != nil {
99 | if s, ok := status.FromError(err); ok {
100 | err = errors.New(s.Message())
101 | }
102 | fatal(fmt.Sprintf("connect to gRPC server \"%s\" err: ", gRPCAddr), err)
103 | }
104 | newErrorf("Connected gRPC server \"%s\" ", gRPCAddr).AtWarning().WriteToLog()
105 | var database db.Db
106 | if cfg.Paneltype == 0 {
107 | newError("Using SSpanel").AtInfo().WriteToLog()
108 | if cfg.Usemysql == 1 {
109 | mysql, err := db.NewMySQLConn(cfg.MySQL)
110 | if err != nil {
111 | fmt.Println(err)
112 | }
113 | database = &db.SSpanel{Db: mysql, MU_SUFFIX: cfg.MU_SUFFIX, MU_REGEX: cfg.MU_REGEX}
114 | newError("Using Mysql Now").AtInfo().WriteToLog()
115 | } else {
116 | database = &db.Webapi{
117 | WebToken: cfg.PanelKey,
118 | WebBaseURl: cfg.PanelUrl,
119 | MU_SUFFIX: cfg.MU_SUFFIX,
120 | MU_REGEX: cfg.MU_REGEX,
121 | }
122 | newError("Using Webapi Now").AtInfo().WriteToLog()
123 | }
124 | } else {
125 | newError("Using SSRpanel").AtInfo().WriteToLog()
126 | if cfg.Usemysql == 1 {
127 | mysql, err := db.NewMySQLConn(cfg.MySQL)
128 | if err != nil {
129 | fmt.Println(err)
130 | }
131 | database = &db.SSRpanel{Db: mysql, MU_SUFFIX: cfg.MU_SUFFIX, MU_REGEX: cfg.MU_REGEX}
132 | newError("Using Mysql Now").AtInfo().WriteToLog()
133 | } else {
134 | fatal("No databese config for ssrpanel")
135 | }
136 | }
137 |
138 | p, err := NewPanel(gRPCConn, database, cfg)
139 | if err != nil {
140 | fatal("new panel error", err)
141 | }
142 | p.Start()
143 | } else {
144 | if err != nil {
145 | fatal(err)
146 | }
147 | }
148 |
149 | }()
150 |
151 | // Explicitly triggering GC to remove garbage
152 | runtime.GC()
153 |
154 | return nil
155 | }
156 |
157 | func newErrorf(format string, a ...interface{}) *errors.Error {
158 | return newError(fmt.Sprintf(format, a...))
159 | }
160 |
161 | func newError(values ...interface{}) *errors.Error {
162 | values = append([]interface{}{"SSPanelPlugin: "}, values...)
163 | return errors.New(values...)
164 | }
165 |
166 | func fatal(values ...interface{}) {
167 | newError(values...).AtError().WriteToLog()
168 | // Wait log
169 | time.Sleep(1 * time.Second)
170 | os.Exit(-2)
171 | }
172 |
--------------------------------------------------------------------------------
/speedtest/client.go:
--------------------------------------------------------------------------------
1 | package speedtest
2 |
3 | import (
4 | "encoding/xml"
5 | "fmt"
6 | "io"
7 | "io/ioutil"
8 | "net"
9 | "net/http"
10 | "runtime"
11 | "strings"
12 | "sync"
13 | )
14 |
15 | type Client interface {
16 | Log(format string, a ...interface{})
17 | Config() (*Config, error)
18 | LoadConfig(ret chan ConfigRef)
19 | NewRequest(method string, url string, body io.Reader) (*http.Request, error)
20 | Get(url string) (resp *Response, err error)
21 | Post(url string, bodyType string, body io.Reader) (resp *Response, err error)
22 | AllServers() (*Servers, error)
23 | LoadAllServers(ret chan ServersRef)
24 | ClosestServers() (*Servers, error)
25 | LoadClosestServers(ret chan ServersRef)
26 | }
27 |
28 | type client struct {
29 | http.Client
30 | opts *Opts
31 | mutex sync.Mutex
32 | config chan ConfigRef
33 | allServers chan ServersRef
34 | closestServers chan ServersRef
35 | }
36 |
37 | type Response http.Response
38 |
39 | func NewClient(opts *Opts) Client {
40 | dialer := &net.Dialer{
41 | Timeout: opts.Timeout,
42 | KeepAlive: opts.Timeout,
43 | }
44 |
45 | if len(opts.Interface) != 0 {
46 | dialer.LocalAddr = &net.IPAddr{IP: net.ParseIP(opts.Interface)}
47 | if dialer.LocalAddr == nil {
48 | newErrorf("Invalid source IP: %s\n", opts.Interface).AtWarning().WriteToLog()
49 | }
50 | }
51 |
52 | transport := &http.Transport{
53 | Proxy: http.ProxyFromEnvironment,
54 | Dial: dialer.Dial,
55 | TLSHandshakeTimeout: opts.Timeout,
56 | ExpectContinueTimeout: opts.Timeout,
57 | }
58 |
59 | client := &client{
60 | Client: http.Client{
61 | Transport: transport,
62 | Timeout: opts.Timeout,
63 | },
64 | opts: opts,
65 | }
66 |
67 | return client
68 | }
69 |
70 | func (client *client) NewRequest(method string, url string, body io.Reader) (*http.Request, error) {
71 | if strings.HasPrefix(url, ":") {
72 | if client.opts.Secure {
73 | url = "https" + url
74 | } else {
75 | url = "http" + url
76 | }
77 | }
78 | req, err := http.NewRequest(method, url, body)
79 | if err == nil {
80 | req.Header.Set(
81 | "User-Agent",
82 | "Mozilla/5.0 "+
83 | fmt.Sprintf("(%s; U; %s; en-us)", runtime.GOOS, runtime.GOARCH)+
84 | fmt.Sprintf("Go/%s", runtime.Version())+
85 | fmt.Sprintf("(KHTML, like Gecko) speedtest-cli/%s", Version))
86 | }
87 | return req, err
88 | }
89 |
90 | func (client *client) Get(url string) (resp *Response, err error) {
91 | req, err := client.NewRequest("GET", url, nil)
92 | if err != nil {
93 | return nil, err
94 | }
95 |
96 | htResp, err := client.Client.Do(req)
97 |
98 | return (*Response)(htResp), err
99 | }
100 |
101 | func (client *client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error) {
102 | req, err := client.NewRequest("POST", url, body)
103 | if err != nil {
104 | return nil, err
105 | }
106 |
107 | req.Header.Set("Content-Type", bodyType)
108 | htResp, err := client.Client.Do(req)
109 |
110 | return (*Response)(htResp), err
111 | }
112 |
113 | func (resp *Response) ReadContent() ([]byte, error) {
114 | content, err := ioutil.ReadAll(resp.Body)
115 | cerr := resp.Body.Close()
116 | if err != nil {
117 | return nil, err
118 | }
119 | if cerr != nil {
120 | return content, cerr
121 | }
122 | return content, nil
123 | }
124 |
125 | func (resp *Response) ReadXML(out interface{}) error {
126 | content, err := resp.ReadContent()
127 | if err != nil {
128 | return err
129 | }
130 | return xml.Unmarshal(content, out)
131 | }
132 |
--------------------------------------------------------------------------------
/speedtest/config.go:
--------------------------------------------------------------------------------
1 | package speedtest
2 |
3 | import (
4 | "encoding/xml"
5 | "strconv"
6 | "strings"
7 | )
8 |
9 | type ClientConfig struct {
10 | Coordinates
11 | IP string `xml:"ip,attr"`
12 | ISP string `xml:"isp,attr"`
13 | ISPRating float32 `xml:"isprating,attr"`
14 | ISPDownloadAverage uint32 `xml:"ispdlavg,attr"`
15 | ISPUploadAverage uint32 `xml:"ispulavg,attr"`
16 | Rating float32 `xml:"rating,attr"`
17 | LoggedIn uint8 `xml:"loggedin,attr"`
18 | }
19 |
20 | type ConfigTime struct {
21 | Upload uint32
22 | Download uint32
23 | }
24 |
25 | type ConfigTimes []ConfigTime
26 |
27 | type Config struct {
28 | Client ClientConfig `xml:"client"`
29 | Times ConfigTimes `xml:"times"`
30 | }
31 |
32 | func (client *client) Log(format string, a ...interface{}) {
33 | if !client.opts.Quiet {
34 | newErrorf(format, a...).AtInfo().WriteToLog()
35 | }
36 | }
37 |
38 | type ConfigRef struct {
39 | Config *Config
40 | Error error
41 | }
42 |
43 | func (client *client) Config() (*Config, error) {
44 | configChan := make(chan ConfigRef)
45 | client.LoadConfig(configChan)
46 | configRef := <-configChan
47 | return configRef.Config, configRef.Error
48 | }
49 |
50 | func (client *client) LoadConfig(ret chan ConfigRef) {
51 | client.mutex.Lock()
52 | defer client.mutex.Unlock()
53 |
54 | if client.config == nil {
55 | client.config = make(chan ConfigRef)
56 | go client.loadConfig()
57 | }
58 |
59 | go func() {
60 | result := <-client.config
61 | ret <- result
62 | client.config <- result
63 | }()
64 | }
65 |
66 | func (client *client) loadConfig() {
67 | client.Log("Retrieving speedtest.net configuration...")
68 |
69 | result := ConfigRef{}
70 |
71 | resp, err := client.Get("://www.speedtest.net/speedtest-config.php")
72 | if err != nil {
73 | result.Error = err
74 | } else {
75 | config := &Config{}
76 | err = resp.ReadXML(config)
77 | if err != nil {
78 | result.Error = err
79 | } else {
80 | result.Config = config
81 | }
82 | }
83 |
84 | client.config <- result
85 | }
86 |
87 | func (times ConfigTimes) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
88 | for _, attr := range start.Attr {
89 | name := attr.Name.Local
90 | if dl := strings.HasPrefix(name, "dl"); dl || strings.HasPrefix(name, "ul") {
91 | num, err := strconv.Atoi(name[2:])
92 | if err != nil {
93 | return err
94 | }
95 | if num > cap(times) {
96 | newTimes := make([]ConfigTime, num)
97 | copy(newTimes, times)
98 | times = newTimes[0:num]
99 | }
100 |
101 | speed, err := strconv.ParseUint(attr.Value, 10, 32)
102 |
103 | if err != nil {
104 | return err
105 | }
106 | if dl {
107 | times[num-1].Download = uint32(speed)
108 | } else {
109 | times[num-1].Upload = uint32(speed)
110 | }
111 | }
112 | }
113 |
114 | return d.Skip()
115 | }
116 |
--------------------------------------------------------------------------------
/speedtest/coordinates.go:
--------------------------------------------------------------------------------
1 | package speedtest
2 |
3 | import "math"
4 |
5 | type Coordinates struct {
6 | Latitude float32 `xml:"lat,attr"`
7 | Longitude float32 `xml:"lon,attr"`
8 | }
9 |
10 | const radius = 6371 // km
11 |
12 | func radians32(degrees float32) float64 {
13 | return radians(float64(degrees))
14 | }
15 |
16 | func radians(degrees float64) float64 {
17 | return degrees * math.Pi / 180
18 | }
19 |
20 | func (org Coordinates) DistanceTo(dest Coordinates) float64 {
21 | dlat := radians32(dest.Latitude - org.Latitude)
22 | dlon := radians32(dest.Longitude - org.Longitude)
23 | a := (math.Sin(dlat/2)*math.Sin(dlat/2) +
24 | math.Cos(radians32(org.Latitude))*
25 | math.Cos(radians32(dest.Latitude))*math.Sin(dlon/2)*
26 | math.Sin(dlon/2))
27 | c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
28 | d := radius * c
29 |
30 | return d
31 | }
32 |
--------------------------------------------------------------------------------
/speedtest/download.go:
--------------------------------------------------------------------------------
1 | package speedtest
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "time"
7 | )
8 |
9 | const downloadStreamLimit = 6
10 | const maxDownloadDuration = 10 * time.Second
11 | const downloadBufferSize = 4096
12 | const downloadRepeats = 5
13 |
14 | var downloadImageSizes = []int{350, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000}
15 |
16 | func (client *client) downloadFile(url string, start time.Time, ret chan int) {
17 | totalRead := 0
18 | defer func() {
19 | ret <- totalRead
20 | }()
21 |
22 | if time.Since(start) > maxDownloadDuration {
23 | if !client.opts.Quiet {
24 | newErrorf("[%s] Download timeout", url).AtWarning().WriteToLog()
25 | }
26 |
27 | return
28 | }
29 | resp, err := client.Get(url)
30 | if err != nil {
31 | newErrorf("[%s] Download failed: %v", url, err).AtWarning().WriteToLog()
32 | return
33 | }
34 |
35 | defer resp.Body.Close()
36 |
37 | buf := make([]byte, downloadBufferSize)
38 | for time.Since(start) <= maxDownloadDuration {
39 | read, err := resp.Body.Read(buf)
40 | totalRead += read
41 | if err != nil {
42 | if err != io.EOF {
43 | newErrorf("[%s] Download error: %v\n", url, err).AtWarning().WriteToLog()
44 | return
45 | }
46 | break
47 | }
48 | }
49 | }
50 |
51 | func (server *Server) DownloadSpeed() int {
52 | client := server.client.(*client)
53 |
54 | starterChan := make(chan int, downloadStreamLimit)
55 | downloads := downloadRepeats * len(downloadImageSizes)
56 | resultChan := make(chan int, downloadStreamLimit)
57 | start := time.Now()
58 |
59 | go func() {
60 | for _, size := range downloadImageSizes {
61 | for i := 0; i < downloadRepeats; i++ {
62 | url := server.RelativeURL(fmt.Sprintf("random%dx%d.jpg", size, size))
63 | starterChan <- 1
64 | go func() {
65 | client.downloadFile(url, start, resultChan)
66 | <-starterChan
67 | }()
68 | }
69 | }
70 | close(starterChan)
71 | }()
72 |
73 | var totalSize int64 = 0
74 |
75 | for i := 0; i < downloads; i++ {
76 | totalSize += int64(<-resultChan)
77 | }
78 |
79 | duration := time.Since(start)
80 |
81 | return int(totalSize * int64(time.Second) / int64(duration))
82 | }
83 |
--------------------------------------------------------------------------------
/speedtest/latency.go:
--------------------------------------------------------------------------------
1 | package speedtest
2 |
3 | import (
4 | "sort"
5 | "strings"
6 | "time"
7 | )
8 |
9 | const DefaultLatencyMeasureTimes = 4
10 | const DefaultErrorLatency = time.Hour
11 |
12 | // Measures latencies for each server.
13 | // Returns server list sorted by latencies.
14 | // This is synchronous operation, because multiple simultaneous requests may affect results.
15 | func (servers *Servers) MeasureLatencies(times uint, errorLatency time.Duration) *Servers {
16 | first := true
17 | for _, server := range servers.List {
18 | if first {
19 | first = false
20 | server.client.Log("Measuring server latencies...")
21 | }
22 | server.doMeasureLatency(times, errorLatency)
23 | }
24 |
25 | latencies := &serverLatencies{List: make([]*Server, servers.Len())}
26 | copy(latencies.List, servers.List)
27 | sort.Sort(latencies)
28 |
29 | return (*Servers)(latencies)
30 | }
31 |
32 | type serverLatencies Servers
33 |
34 | func (servers *serverLatencies) Len() int {
35 | return len(servers.List)
36 | }
37 |
38 | func (servers *serverLatencies) Less(i, j int) bool {
39 | return servers.List[i].Latency < servers.List[j].Latency
40 | }
41 |
42 | func (servers *serverLatencies) Swap(i, j int) {
43 | temp := servers.List[i]
44 | servers.List[i] = servers.List[j]
45 | servers.List[j] = temp
46 | }
47 |
48 | func (server *Server) MeasureLatency(times uint, errorLatency time.Duration) time.Duration {
49 | server.client.Log("Measuring server latency...\n")
50 | return server.doMeasureLatency(times, errorLatency)
51 | }
52 |
53 | func (server *Server) doMeasureLatency(times uint, errorLatency time.Duration) time.Duration {
54 |
55 | var results time.Duration = 0
56 | var i uint
57 |
58 | for i = 0; i < times; i++ {
59 | results += server.measureLatency(errorLatency)
60 | }
61 |
62 | server.Latency = time.Duration(results / time.Duration(times))
63 |
64 | return server.Latency
65 | }
66 |
67 | func (server *Server) measureLatency(errorLatency time.Duration) time.Duration {
68 | url := server.RelativeURL("latency.txt")
69 | start := time.Now()
70 | resp, err := server.client.Get(url)
71 | duration := time.Since(start)
72 | if resp != nil {
73 | url = resp.Request.URL.String()
74 | }
75 | if err != nil {
76 | server.client.Log("[%s] Failed to detect latency: %v\n", url, err)
77 | return errorLatency
78 | }
79 | if resp.StatusCode != 200 {
80 | server.client.Log("[%s] Invalid latency detection HTTP status: %d\n", url, resp.StatusCode)
81 | duration = errorLatency
82 | }
83 | content, err := resp.ReadContent()
84 | if err != nil {
85 | server.client.Log("[%s] Failed to read latency response: %v\n", url, err)
86 | duration = errorLatency
87 | }
88 | if !strings.HasPrefix(string(content), "test=test") {
89 | server.client.Log("[%s] Invalid latency response: %s\n", url, content)
90 | duration = errorLatency
91 | }
92 | return duration
93 | }
94 |
--------------------------------------------------------------------------------
/speedtest/latency_test.go:
--------------------------------------------------------------------------------
1 | package speedtest
2 |
3 | import (
4 | "errors"
5 | "io"
6 | "net/http"
7 | "testing"
8 | "time"
9 | )
10 |
11 | func Test_measureLatency(t *testing.T) {
12 | tests := []struct {
13 | name string
14 | client Client
15 | input time.Duration
16 | want time.Duration
17 | }{
18 | {
19 | name: "Client.Get() error with DefaultErrorLatency input",
20 | client: &latencyErrorClient{},
21 | input: DefaultErrorLatency,
22 | want: DefaultErrorLatency,
23 | },
24 | {
25 | name: "Client.Get() error with 10 Second input",
26 | client: &latencyErrorClient{},
27 | input: 10 * time.Second,
28 | want: 10 * time.Second,
29 | },
30 | }
31 |
32 | for _, tc := range tests {
33 | tc := tc
34 | t.Run(tc.name, func(t *testing.T) {
35 | s := &Server{client: tc.client}
36 | if got, want := s.measureLatency(tc.input), tc.want; got != want {
37 | t.Fatalf("unexpected result:\n- want: %v\n- got: %v",
38 | want, got)
39 | }
40 | })
41 | }
42 | }
43 |
44 | // latencyErrorClient is a client returns error from most of the methods.
45 | type latencyErrorClient struct{}
46 |
47 | func (c *latencyErrorClient) Log(_ string, _ ...interface{}) {}
48 | func (c *latencyErrorClient) Config() (*Config, error) {
49 | return nil, errors.New("Config()")
50 | }
51 | func (c *latencyErrorClient) LoadConfig(_ chan ConfigRef) {}
52 | func (c *latencyErrorClient) NewRequest(_ string, _ string, _ io.Reader) (*http.Request, error) {
53 | return nil, errors.New("NewRequest()")
54 | }
55 | func (c *latencyErrorClient) Get(_ string) (resp *Response, err error) {
56 | return nil, errors.New("Get()")
57 | }
58 | func (c *latencyErrorClient) Post(_ string, _ string, _ io.Reader) (*Response, error) {
59 | return nil, errors.New("Post()")
60 | }
61 | func (c *latencyErrorClient) AllServers() (*Servers, error) {
62 | return nil, errors.New("AllServers()")
63 | }
64 | func (c *latencyErrorClient) LoadAllServers(_ chan ServersRef) {}
65 | func (c *latencyErrorClient) ClosestServers() (*Servers, error) {
66 | return nil, errors.New("ClosestServers()")
67 | }
68 | func (c *latencyErrorClient) LoadClosestServers(_ chan ServersRef) {}
69 |
--------------------------------------------------------------------------------
/speedtest/opts.go:
--------------------------------------------------------------------------------
1 | package speedtest
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | type Opts struct {
8 | SpeedInBytes bool
9 | Quiet bool
10 | List bool
11 | Server ServerID
12 | Interface string
13 | Timeout time.Duration
14 | Secure bool
15 | Help bool
16 | Version bool
17 | }
18 |
19 | func NewOpts() *Opts {
20 |
21 | return &Opts{
22 | Quiet: true,
23 | Server: 0,
24 | Interface: "",
25 | Timeout: 10 * time.Second,
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/speedtest/server.go:
--------------------------------------------------------------------------------
1 | package speedtest
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "log"
7 | "net/url"
8 | "sort"
9 | "time"
10 | )
11 |
12 | type ServerID uint64
13 |
14 | type Server struct {
15 | Coordinates
16 | URL string `xml:"url,attr"`
17 | Name string `xml:"name,attr"`
18 | Country string `xml:"country,attr"`
19 | CC string `xml:"cc,attr"`
20 | Sponsor string `xml:"sponsor,attr"`
21 | ID ServerID `xml:"id,attr"`
22 | URL2 string `xml:"url2,attr"`
23 | Host string `xml:"host,attr"`
24 | client Client `xml:"-"`
25 | Distance float64 `xml:"-"`
26 | Latency time.Duration `xml:"-"`
27 | }
28 |
29 | func (s *Server) String() string {
30 | return fmt.Sprintf("%8d: %s (%s, %s) [%.2f km] %s", s.ID, s.Sponsor, s.Name, s.Country, s.Distance, s.URL)
31 | }
32 |
33 | func (s *Server) RelativeURL(local string) string {
34 | u, err := url.Parse(s.URL)
35 | if err != nil {
36 | log.Fatalf("[%s] Failed to parse server URL: %v\n", s.URL, err)
37 | return ""
38 | }
39 | localURL, err := url.Parse(local)
40 | if err != nil {
41 | log.Fatalf("Failed to parse local URL `%s`: %v\n", local, err)
42 | }
43 | return u.ResolveReference(localURL).String()
44 | }
45 |
46 | type Servers struct {
47 | List []*Server `xml:"servers>server"`
48 | }
49 |
50 | type ServersRef struct {
51 | Servers *Servers
52 | Error error
53 | }
54 |
55 | func (servers *Servers) First() *Server {
56 | if len(servers.List) == 0 {
57 | return nil
58 | }
59 | return servers.List[0]
60 | }
61 |
62 | func (servers *Servers) Find(id ServerID) *Server {
63 | for _, server := range servers.List {
64 | if server.ID == id {
65 | return server
66 | }
67 | }
68 | return nil
69 | }
70 |
71 | func (servers *Servers) Len() int {
72 | return len(servers.List)
73 | }
74 |
75 | func (servers *Servers) Less(i, j int) bool {
76 | server1 := servers.List[i]
77 | server2 := servers.List[j]
78 | if server1.ID == server2.ID {
79 | return false
80 | }
81 | if server1.Distance < server2.Distance {
82 | return true
83 | }
84 | if server1.Distance > server2.Distance {
85 | return false
86 | }
87 | return server1.ID < server2.ID
88 | }
89 |
90 | func (servers *Servers) Swap(i, j int) {
91 | temp := servers.List[i]
92 | servers.List[i] = servers.List[j]
93 | servers.List[j] = temp
94 | }
95 |
96 | func (servers *Servers) truncate(max int) *Servers {
97 | size := servers.Len()
98 | if size <= max {
99 | return servers
100 | }
101 | return &Servers{servers.List[:max]}
102 | }
103 |
104 | func (servers *Servers) String() string {
105 | out := ""
106 | for _, server := range servers.List {
107 | out += server.String() + "\n"
108 | }
109 | return out
110 | }
111 |
112 | func (servers *Servers) append(other *Servers) *Servers {
113 | if servers == nil {
114 | return other
115 | }
116 | servers.List = append(servers.List, other.List...)
117 | return servers
118 | }
119 |
120 | func (servers *Servers) sort(client Client, config *Config) {
121 | for _, server := range servers.List {
122 | server.client = client
123 | server.Distance = server.DistanceTo(config.Client.Coordinates)
124 | }
125 | sort.Sort(servers)
126 | }
127 |
128 | func (servers *Servers) deduplicate() {
129 | dedup := make([]*Server, 0, len(servers.List))
130 | var prevId ServerID = 0
131 | for _, server := range servers.List {
132 | if prevId != server.ID {
133 | prevId = server.ID
134 | dedup = append(dedup, server)
135 | }
136 | }
137 | servers.List = dedup
138 | }
139 |
140 | var serverURLs = [...]string{
141 | "://www.speedtest.net/speedtest-servers-static.php",
142 | "://c.speedtest.net/speedtest-servers-static.php",
143 | "://www.speedtest.net/speedtest-servers.php",
144 | "://c.speedtest.net/speedtest-servers.php",
145 | }
146 |
147 | var NoServersError error = errors.New("No servers available")
148 |
149 | func (client *client) AllServers() (*Servers, error) {
150 | serversChan := make(chan ServersRef)
151 | client.LoadAllServers(serversChan)
152 | serversRef := <-serversChan
153 | return serversRef.Servers, serversRef.Error
154 | }
155 |
156 | func (client *client) LoadAllServers(ret chan ServersRef) {
157 | client.mutex.Lock()
158 | defer client.mutex.Unlock()
159 |
160 | if client.allServers == nil {
161 | client.allServers = make(chan ServersRef)
162 | go client.loadServers()
163 | }
164 |
165 | go func() {
166 | result := <-client.allServers
167 | ret <- result
168 | client.allServers <- result // Make it available again
169 | }()
170 | }
171 |
172 | func (client *client) loadServers() {
173 | configChan := make(chan ConfigRef)
174 | client.LoadConfig(configChan)
175 |
176 | client.Log("Retrieving speedtest.net server list...")
177 |
178 | serversChan := make(chan *Servers, len(serverURLs))
179 | for _, url := range serverURLs {
180 | go client.loadServersFrom(url, serversChan)
181 | }
182 |
183 | var servers *Servers
184 |
185 | for range serverURLs {
186 | servers = servers.append(<-serversChan)
187 | }
188 |
189 | result := ServersRef{}
190 |
191 | if servers.Len() == 0 {
192 | result.Error = NoServersError
193 | } else {
194 | configRef := <-configChan
195 | if configRef.Error != nil {
196 | result.Error = configRef.Error
197 | } else {
198 | servers.sort(client, configRef.Config)
199 | servers.deduplicate()
200 | result.Servers = servers
201 | }
202 | }
203 |
204 | client.allServers <- result
205 | }
206 |
207 | func (client *client) loadServersFrom(url string, ret chan *Servers) {
208 | resp, err := client.Get(url)
209 | if resp != nil {
210 | url = resp.Request.URL.String()
211 | }
212 | if err != nil {
213 | client.Log("[%s] Failed to retrieve server list: %v", url, err)
214 | }
215 |
216 | servers := &Servers{}
217 | if err = resp.ReadXML(servers); err != nil {
218 | client.Log("[%s] Failed to read server list: %v", url, err)
219 | }
220 | ret <- servers
221 | }
222 |
223 | func (client *client) ClosestServers() (*Servers, error) {
224 | serversChan := make(chan ServersRef)
225 | client.LoadClosestServers(serversChan)
226 | serversRef := <-serversChan
227 | return serversRef.Servers, serversRef.Error
228 | }
229 |
230 | func (client *client) LoadClosestServers(ret chan ServersRef) {
231 | client.mutex.Lock()
232 | defer client.mutex.Unlock()
233 |
234 | if client.closestServers == nil {
235 | client.closestServers = make(chan ServersRef)
236 | go client.loadClosestServers()
237 | }
238 |
239 | go func() {
240 | result := <-client.closestServers
241 | ret <- result
242 | client.closestServers <- result // Make it available again
243 | }()
244 | }
245 |
246 | func (client *client) loadClosestServers() {
247 | serversChan := make(chan ServersRef)
248 | client.LoadAllServers(serversChan)
249 | serversRef := <-serversChan
250 | if serversRef.Error != nil {
251 | client.closestServers <- serversRef
252 | } else {
253 | client.closestServers <- ServersRef{serversRef.Servers.truncate(5), nil}
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/speedtest/speedtest_thread.go:
--------------------------------------------------------------------------------
1 | package speedtest
2 |
3 | // the go speedtest-cli code is from https://github.com/surol/speedtest-cli
4 | import (
5 | "fmt"
6 | "os"
7 | "strings"
8 | "time"
9 | "v2ray.com/core/common/errors"
10 | )
11 |
12 | func newErrorf(format string, a ...interface{}) *errors.Error {
13 | return newError(fmt.Sprintf(format, a...))
14 | }
15 |
16 | func newError(values ...interface{}) *errors.Error {
17 | values = append([]interface{}{"SSPanelPlugin: "}, values...)
18 | return errors.New(values...)
19 | }
20 |
21 | func fatal(values ...interface{}) {
22 | newError(values...).AtError().WriteToLog()
23 | // Wait log
24 | time.Sleep(1 * time.Second)
25 | os.Exit(-2)
26 | }
27 |
28 | type Speedresult struct {
29 | CTPing string `json:"telecomping"`
30 | CTUpSpeed string `json:"telecomeupload"`
31 | CTDLSpeed string `json:"telecomedownload"`
32 | CUPing string `json:"unicomping"`
33 | CUUpSpeed string `json:"unicomupload"`
34 | CUDLSpeed string `json:"unicomdownload"`
35 | CMPing string `json:"cmccping"`
36 | CMUpSpeed string `json:"cmccupload"`
37 | CMDLSpeed string `json:"cmccdownload"`
38 | }
39 |
40 | func GetSpeedtest(client Client) ([]Speedresult, error) {
41 | config, err := client.Config()
42 | if err != nil {
43 | return nil, newError(err)
44 | }
45 | newErrorf("Testing from %s (%s)...\n", config.Client.ISP, config.Client.IP).AtInfo().WriteToLog()
46 | final_result := []Speedresult{}
47 | result := Speedresult{
48 | CTPing: "0.000 ms",
49 | CUPing: "0.000 ms",
50 | CMPing: "0.000 ms",
51 | CTDLSpeed: "0.00 Mib/s",
52 | CTUpSpeed: "0.00 Mib/s",
53 | CUDLSpeed: "0.00 Mib/s",
54 | CUUpSpeed: "0.00 Mib/s",
55 | CMDLSpeed: "0.00 Mib/s",
56 | CMUpSpeed: "0.00 Mib/s"}
57 | server := selectServer("Telecom", client)
58 | if server != nil {
59 | result.CTPing = fmt.Sprintf("%.3f ms", server.Latency.Seconds()*1e3)
60 | result.CTDLSpeed = fmt.Sprintf("%.2f Mib/s", float64(server.DownloadSpeed()/(1<<17)))
61 | result.CTUpSpeed = fmt.Sprintf("%.2f Mib/s", float64(server.UploadSpeed()/(1<<17)))
62 | }
63 | server = selectServer("Mobile", client)
64 | if server != nil {
65 | result.CMPing = fmt.Sprintf("%.3f ms", server.Latency.Seconds()*1e3)
66 | result.CMDLSpeed = fmt.Sprintf("%.2f Mib/s", float64(server.DownloadSpeed()/(1<<17)))
67 | result.CMUpSpeed = fmt.Sprintf("%.2f Mib/s", float64(server.UploadSpeed()/(1<<17)))
68 | }
69 |
70 | server = selectServer("Unicom", client)
71 | if server != nil {
72 | result.CUPing = fmt.Sprintf("%.3f ms", server.Latency.Seconds()*1e3)
73 | result.CUDLSpeed = fmt.Sprintf("%.2f Mib/s", float64(server.DownloadSpeed()/(1<<17)))
74 | result.CUUpSpeed = fmt.Sprintf("%.2f Mib/s", float64(server.UploadSpeed()/(1<<17)))
75 | }
76 | return append(final_result, result), nil
77 | }
78 |
79 | func selectServer(sponsor string, client Client) (selected *Server) {
80 | servers, err := client.AllServers()
81 | if err != nil {
82 | newError("Failed to load server list: %v", err).AtWarning().WriteToLog()
83 | return nil
84 | }
85 | sponsor_servers := new(Servers)
86 | for _, server := range servers.List {
87 | if (server.Country == "China" || server.Country == "CN") && strings.Contains(server.Sponsor, sponsor) {
88 | sponsor_servers.List = append(sponsor_servers.List, server)
89 | }
90 | }
91 | if len(sponsor_servers.List) > 0 {
92 |
93 | selected = sponsor_servers.MeasureLatencies(
94 | DefaultLatencyMeasureTimes,
95 | DefaultErrorLatency).First()
96 | return selected
97 | }
98 | return nil
99 | }
100 |
--------------------------------------------------------------------------------
/speedtest/upload.go:
--------------------------------------------------------------------------------
1 | package speedtest
2 |
3 | import (
4 | "crypto/rand"
5 | "io"
6 | "strings"
7 | "time"
8 | )
9 |
10 | const maxUploadDuration = maxDownloadDuration
11 | const uploadStreamLimit = downloadStreamLimit
12 | const uploadRepeats = downloadRepeats
13 |
14 | var uploadSizes []int
15 |
16 | func init() {
17 |
18 | var uploadSizeSizes = []int{int(1000 * 1000 / 4), int(1000 * 1000 / 2)}
19 |
20 | uploadSizes = make([]int, len(uploadSizeSizes)*25)
21 | for _, size := range uploadSizeSizes {
22 | for i := 0; i < 25; i++ {
23 | uploadSizes[i] = size
24 | }
25 | }
26 | }
27 |
28 | const safeChars = "0123456789abcdefghijklmnopqrstuv"
29 |
30 | type safeReader struct {
31 | in io.Reader
32 | }
33 |
34 | func (r safeReader) Read(p []byte) (n int, err error) {
35 | n, err = r.in.Read(p)
36 |
37 | for i := 0; i < n; i++ {
38 | p[i] = safeChars[p[i]&31]
39 | }
40 |
41 | return n, err
42 | }
43 |
44 | func (client *client) uploadFile(url string, start time.Time, size int, ret chan int) {
45 | totalWrote := 0
46 | defer func() {
47 | ret <- totalWrote
48 | }()
49 |
50 | if time.Since(start) > maxUploadDuration {
51 | return
52 | }
53 |
54 | resp, err := client.Post(
55 | url,
56 | "application/x-www-form-urlencoded",
57 | io.MultiReader(
58 | strings.NewReader("content1="),
59 | io.LimitReader(&safeReader{rand.Reader}, int64(size-9))))
60 | if err != nil {
61 | if !client.opts.Quiet {
62 | newErrorf("[%s] Upload failed: %v\n", url, err).AtWarning().WriteToLog()
63 | }
64 | return
65 | }
66 |
67 | totalWrote = size
68 |
69 | defer resp.Body.Close()
70 | }
71 |
72 | func (server *Server) UploadSpeed() int {
73 | client := server.client.(*client)
74 |
75 | starterChan := make(chan int, uploadStreamLimit)
76 | uploads := uploadRepeats * len(uploadSizes)
77 | resultChan := make(chan int, uploadStreamLimit)
78 | start := time.Now()
79 |
80 | go func() {
81 | for _, size := range uploadSizes {
82 | size := size // local copy to avoid the data race.
83 | for i := 0; i < uploadRepeats; i++ {
84 | url := server.URL
85 | starterChan <- 1
86 | go func() {
87 | client.uploadFile(url, start, size, resultChan)
88 | <-starterChan
89 | }()
90 | }
91 | }
92 | close(starterChan)
93 | }()
94 |
95 | var totalSize int64 = 0
96 |
97 | for i := 0; i < uploads; i++ {
98 | totalSize += int64(<-resultChan)
99 | }
100 |
101 | duration := time.Since(start)
102 |
103 | return int(totalSize * int64(time.Second) / int64(duration))
104 | }
105 |
--------------------------------------------------------------------------------
/speedtest/upload_test.go:
--------------------------------------------------------------------------------
1 | package speedtest
2 |
3 | import (
4 | "testing"
5 | "time"
6 | )
7 |
8 | func TestUpload(t *testing.T) {
9 | tests := []struct {
10 | name string
11 | opts Opts
12 | }{
13 | {
14 | name: "default options",
15 | opts: Opts{},
16 | },
17 | {
18 | name: "quiet option",
19 | opts: Opts{Quiet: true},
20 | },
21 | }
22 |
23 | for _, tc := range tests {
24 | tc := tc
25 | t.Run(tc.name, func(t *testing.T) {
26 | // set timeout to avoid the longer tests.
27 | tc.opts.Timeout = 10 * time.Second
28 | c := NewClient(&tc.opts)
29 | if _, err := c.Config(); err != nil {
30 | t.Fatalf("unexpected config error: %v", err)
31 | }
32 | s, err := c.ClosestServers()
33 | if err != nil {
34 | t.Fatalf("unexpected server selection error: %v", err)
35 | }
36 | // pick the firstest server to test.
37 | upload := s.MeasureLatencies(
38 | DefaultLatencyMeasureTimes,
39 | DefaultErrorLatency,
40 | ).First().UploadSpeed()
41 | t.Logf("upload %d bps", upload)
42 | })
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/speedtest/version.go:
--------------------------------------------------------------------------------
1 | package speedtest
2 |
3 | const Version = "1.0.0"
4 |
--------------------------------------------------------------------------------
/utility/utility.go:
--------------------------------------------------------------------------------
1 | package utility
2 |
3 | import (
4 | "crypto/md5"
5 | "fmt"
6 | "github.com/shirou/gopsutil/host"
7 | "github.com/shirou/gopsutil/load"
8 | "math/rand"
9 | "time"
10 | )
11 |
12 | func InStr(s string, list []string) bool {
13 | for _, v := range list {
14 | if v == s {
15 | return true
16 | }
17 | }
18 | return false
19 | }
20 |
21 | func GetSystemLoad() string {
22 | stat, err := load.Avg()
23 | if err != nil {
24 | return "0.00 0.00 0.00"
25 | }
26 |
27 | return fmt.Sprintf("%.2f %.2f %.2f", stat.Load1, stat.Load5, stat.Load15)
28 | }
29 | func GetSystemUptime() string {
30 | time, err := host.Uptime()
31 | if err != nil {
32 | return ""
33 | }
34 | return fmt.Sprint(time)
35 |
36 | }
37 | func MD5(text string) []byte {
38 | ctx := md5.New()
39 | ctx.Write([]byte(text))
40 | return ctx.Sum(nil)
41 | }
42 |
43 | func GetRandomString(len1 int) string {
44 | str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
45 | bytes := []byte(str)
46 | result := []byte{}
47 | r := rand.New(rand.NewSource(time.Now().UnixNano()))
48 | for i := 0; i < len1; i++ {
49 | result = append(result, bytes[r.Intn(len(bytes))])
50 | }
51 | return string(result)
52 | }
53 |
--------------------------------------------------------------------------------
/webapi/webapi.go:
--------------------------------------------------------------------------------
1 | package webapi
2 |
3 | import (
4 | "fmt"
5 | "github.com/imroc/req"
6 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/model"
7 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/speedtest"
8 | "github.com/githubphone/v2ray-sspanel-v3-mod_Uim-plugin/utility"
9 | "log"
10 | "strconv"
11 | "strings"
12 | "time"
13 | )
14 |
15 | type NodeinfoResponse struct {
16 | Ret uint `json:"ret"`
17 | Data *model.NodeInfo `json:"data"`
18 | }
19 | type PostResponse struct {
20 | Ret uint `json:"ret"`
21 | Data string `json:"data"`
22 | }
23 | type UsersResponse struct {
24 | Ret uint `json:"ret"`
25 | Data []model.UserModel `json:"data"`
26 | }
27 | type AllUsers struct {
28 | Ret uint
29 | Data map[string]model.UserModel
30 | }
31 | type Webapi struct {
32 | WebToken string
33 | WebBaseURl string
34 | }
35 |
36 | type DisNodenfoResponse struct {
37 | Ret uint `json:"ret"`
38 | Data []*model.DisNodeInfo `json:"data"`
39 | }
40 |
41 | var id2string = map[uint]string{
42 | 0: "server_address",
43 | 1: "port",
44 | 2: "alterid",
45 | 3: "protocol",
46 | 4: "protocol_param",
47 | 5: "path",
48 | 6: "host",
49 | 7: "inside_port",
50 | 8: "server",
51 | }
52 | var maps = map[string]interface{}{
53 | "server_address": "",
54 | "port": "",
55 | "alterid": "16",
56 | "protocol": "tcp",
57 | "protocol_param": "",
58 | "path": "",
59 | "host": "",
60 | "inside_port": "",
61 | "server": "",
62 | }
63 |
64 | func (api *Webapi) GetApi(url string, params map[string]interface{}) (*req.Resp, error) {
65 | req.SetTimeout(50 * time.Second)
66 | parm := req.Param{
67 | "key": api.WebToken,
68 | }
69 | for k, v := range params {
70 | parm[k] = v
71 | }
72 |
73 | r, err := req.Get(fmt.Sprintf("%s/mod_mu/%s", api.WebBaseURl, url), parm)
74 | return r, err
75 | }
76 |
77 | func (api *Webapi) GetNodeInfo(nodeid uint) (*NodeinfoResponse, error) {
78 | var response = NodeinfoResponse{}
79 | var params map[string]interface{}
80 |
81 | r, err := api.GetApi(fmt.Sprintf("nodes/%d/info", nodeid), params)
82 | if err != nil {
83 | return &response, err
84 | } else {
85 | err = r.ToJSON(&response)
86 | if err != nil {
87 | return &response, err
88 | } else if response.Ret != 1 {
89 | return &response, err
90 | }
91 | }
92 |
93 | if response.Data.Server_raw != "" {
94 | response.Data.Server_raw = strings.ToLower(response.Data.Server_raw)
95 | data := strings.Split(response.Data.Server_raw, ";")
96 | var count uint
97 | count = 0
98 | for v := range data {
99 | if len(data[v]) > 1 {
100 | maps[id2string[count]] = data[v]
101 | }
102 | count += 1
103 | }
104 | var extraArgues []string
105 | if len(data) == 6 {
106 | extraArgues = append(extraArgues, strings.Split(data[5], "|")...)
107 | for item := range extraArgues {
108 | data = strings.Split(extraArgues[item], "=")
109 | if len(data) > 1 {
110 | if len(data[1]) > 1 {
111 | maps[data[0]] = data[1]
112 | }
113 |
114 | }
115 | }
116 | }
117 |
118 | if maps["protocol"] == "tls" {
119 | temp := maps["protocol_param"]
120 | maps["protocol"] = temp
121 | maps["protocol_param"] = "tls"
122 | }
123 | response.Data.Server = maps
124 | }
125 | response.Data.NodeID = nodeid
126 | return &response, nil
127 | }
128 |
129 | func (api *Webapi) GetDisNodeInfo(nodeid uint) (*DisNodenfoResponse, error) {
130 | var response = DisNodenfoResponse{}
131 | var params map[string]interface{}
132 | params = map[string]interface{}{
133 | "node_id": nodeid,
134 | }
135 | r, err := api.GetApi("func/relay_rules", params)
136 | if err != nil {
137 | return &response, err
138 | } else {
139 | err = r.ToJSON(&response)
140 | if err != nil {
141 | return &response, err
142 | } else if response.Ret != 1 {
143 | return &response, err
144 | }
145 | }
146 |
147 | if len(response.Data) > 0 {
148 | for _, relayrule := range response.Data {
149 | relayrule.Server_raw = strings.ToLower(relayrule.Server_raw)
150 | data := strings.Split(relayrule.Server_raw, ";")
151 | var count uint
152 | count = 0
153 | for v := range data {
154 | if len(data[v]) > 1 {
155 | maps[id2string[count]] = data[v]
156 | }
157 | count += 1
158 | }
159 | var extraArgues []string
160 | if len(data) == 6 {
161 | extraArgues = append(extraArgues, strings.Split(data[5], "|")...)
162 | for item := range extraArgues {
163 | data = strings.Split(extraArgues[item], "=")
164 | if len(data) > 1 {
165 | if len(data[1]) > 1 {
166 | maps[data[0]] = data[1]
167 | }
168 |
169 | }
170 | }
171 | }
172 |
173 | if maps["protocol"] == "tls" {
174 | temp := maps["protocol_param"]
175 | maps["protocol"] = temp
176 | maps["protocol_param"] = "tls"
177 | }
178 | relayrule.Server = maps
179 | }
180 | }
181 | return &response, nil
182 | }
183 |
184 | func (api *Webapi) GetALLUsers(info *model.NodeInfo) (*AllUsers, error) {
185 | sort := info.Sort
186 | var prifix string
187 | var allusers = AllUsers{
188 | Data: map[string]model.UserModel{},
189 | }
190 | if sort == 0 {
191 | prifix = "SS_"
192 | } else {
193 | prifix = "Vmess_"
194 | if info.Server["protocol"] == "tcp" {
195 | prifix += "tcp_"
196 | } else if info.Server["protocol"] == "ws" {
197 | if info.Server["protocol_param"] != "" {
198 | prifix += "ws_" + info.Server["protocol_param"].(string) + "_"
199 | } else {
200 | prifix += "ws_" + "none" + "_"
201 | }
202 | } else if info.Server["protocol"] == "kcp" {
203 | if info.Server["protocol_param"] != "" {
204 | prifix += "kcp_" + info.Server["protocol_param"].(string) + "_"
205 | } else {
206 | prifix += "kcp_" + "none" + "_"
207 | }
208 | }
209 | }
210 | var response = UsersResponse{}
211 | params := map[string]interface{}{
212 | "node_id": info.NodeID,
213 | }
214 | r, err := api.GetApi("users", params)
215 | if err != nil {
216 | return &allusers, err
217 | } else {
218 | err = r.ToJSON(&response)
219 | allusers.Ret = response.Ret
220 | if err != nil {
221 | return &allusers, err
222 | } else if response.Ret != 1 {
223 | return &allusers, err
224 | }
225 | }
226 | for index := range response.Data {
227 | if info.Server["alterid"] == "" {
228 | response.Data[index].AlterId = 16
229 | } else {
230 | alterid, err := strconv.ParseUint(info.Server["alterid"].(string), 10, 0)
231 | if err == nil {
232 | response.Data[index].AlterId = uint32(alterid)
233 | }
234 | }
235 | key := prifix + response.Data[index].Email + fmt.Sprintf("_AlterID_%d_Method_%s_Passwd_%s_Port_%d",
236 | response.Data[index].AlterId, response.Data[index].Method, response.Data[index].Passwd, response.Data[index].Port,
237 | )
238 | response.Data[index].PrefixedId = key
239 | allusers.Data[key] = response.Data[index]
240 | }
241 | return &allusers, nil
242 | }
243 |
244 | func (api *Webapi) Post(url string, params map[string]interface{}, data map[string]interface{}) (*req.Resp, error) {
245 | parm := req.Param{
246 | "key": api.WebToken,
247 | }
248 | for k, v := range params {
249 | parm[k] = v
250 | }
251 | r, err := req.Post(fmt.Sprintf("%s/mod_mu/%s", api.WebBaseURl, url), parm, req.BodyJSON(&data))
252 | return r, err
253 | }
254 |
255 | func (api *Webapi) UploadSystemLoad(nodeid uint) bool {
256 | var postresponse PostResponse
257 | params := map[string]interface{}{
258 | "node_id": nodeid,
259 | }
260 | upload_systemload := map[string]interface{}{
261 | "uptime": utility.GetSystemUptime(),
262 | "load": utility.GetSystemLoad(),
263 | }
264 | r, err := api.Post(fmt.Sprintf("nodes/%d/info", nodeid), params, upload_systemload)
265 | if err != nil {
266 | return false
267 | } else {
268 | err = r.ToJSON(&postresponse)
269 | if err != nil {
270 | return false
271 | } else if postresponse.Ret != 1 {
272 | log.Fatal(postresponse.Data)
273 | }
274 | }
275 | return true
276 | }
277 |
278 | func (api *Webapi) UpLoadUserTraffics(nodeid uint, trafficLog []model.UserTrafficLog) bool {
279 | var postresponse PostResponse
280 | params := map[string]interface{}{
281 | "node_id": nodeid,
282 | }
283 |
284 | data := map[string]interface{}{
285 | "data": trafficLog,
286 | }
287 | r, err := api.Post("users/traffic", params, data)
288 | if err != nil {
289 | return false
290 | } else {
291 | err = r.ToJSON(&postresponse)
292 | if err != nil {
293 | return false
294 | } else if postresponse.Ret != 1 {
295 | log.Fatal(postresponse.Data)
296 | }
297 | }
298 | return true
299 | }
300 | func (api *Webapi) UploadSpeedTest(nodeid uint, speedresult []speedtest.Speedresult) bool {
301 | var postresponse PostResponse
302 | params := map[string]interface{}{
303 | "node_id": nodeid,
304 | }
305 |
306 | data := map[string]interface{}{
307 | "data": speedresult,
308 | }
309 | r, err := api.Post("func/speedtest", params, data)
310 | if err != nil {
311 | return false
312 | } else {
313 | err = r.ToJSON(&postresponse)
314 | if err != nil {
315 | return false
316 | } else if postresponse.Ret != 1 {
317 | log.Fatal(postresponse.Data)
318 | }
319 | }
320 | return true
321 | }
322 | func (api *Webapi) UpLoadOnlineIps(nodeid uint, onlineIPS []model.UserOnLineIP) bool {
323 | var postresponse PostResponse
324 | params := map[string]interface{}{
325 | "node_id": nodeid,
326 | }
327 |
328 | data := map[string]interface{}{
329 | "data": onlineIPS,
330 | }
331 | r, err := api.Post("users/aliveip", params, data)
332 | if err != nil {
333 | return false
334 | } else {
335 | err = r.ToJSON(&postresponse)
336 | if err != nil {
337 | return false
338 | } else if postresponse.Ret != 1 {
339 | log.Fatal(postresponse.Data)
340 | }
341 | }
342 | return true
343 | }
344 |
--------------------------------------------------------------------------------