├── speedtest
├── version.go
├── opts.go
├── coordinates.go
├── upload_test.go
├── download.go
├── latency_test.go
├── upload.go
├── latency.go
├── config.go
├── speedtest_thread.go
├── client.go
└── server.go
├── Docker
├── Caddy_V2ray
│ ├── Dockerfile
│ ├── index.html
│ ├── Caddyfile
│ └── docker-compose.yml
├── V2ray
│ └── docker-compose.yml
├── arm
│ └── Dockerfile
└── alpine
│ ├── Dockerfile
│ └── config.json
├── client
├── gRPCClient.go
├── userRuleServerClient.go
├── statsServiceClient.go
└── handlerServiceClient.go
├── model
└── model.go
├── utility
└── utility.go
├── plugin.go
├── config
└── config.go
├── README.md
├── webapi
└── webapi.go
├── Manager
└── Manager.go
├── panel.go
├── install-release.sh
└── install.sh
/speedtest/version.go:
--------------------------------------------------------------------------------
1 | package speedtest
2 |
3 | const Version = "1.0.0"
4 |
--------------------------------------------------------------------------------
/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/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 404
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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.2 tls1.3
12 | # remove comment if u want to use cloudflare (for DNS challenge authentication)
13 | # dns cloudflare
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Docker/V2ray/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | v2ray:
5 | image: ephz3nt/v2ray_v3:go
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 | volumes:
17 | - /etc/localtime:/etc/localtime:ro
18 | logging:
19 | options:
20 | max-size: "10m"
21 | max-file: "3"
22 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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"]
--------------------------------------------------------------------------------
/client/userRuleServerClient.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc"
6 | userruleservice "v2ray.com/core/app/router/command"
7 | )
8 |
9 | type UserRuleServerClient struct {
10 | userruleservice.RuleServerClient
11 | }
12 |
13 | func NewUserRuleServerClient(client *grpc.ClientConn) *UserRuleServerClient {
14 | return &UserRuleServerClient{
15 | RuleServerClient: userruleservice.NewRuleServerClient(client),
16 | }
17 | }
18 |
19 | func (s *UserRuleServerClient) AddUserRelyRule(targettag string, emails []string) error {
20 | _, err := s.AddUserRule(context.Background(), &userruleservice.AddUserRuleRequest{
21 | TargetTag: targettag,
22 | Email: emails,
23 | })
24 | return err
25 | }
26 |
27 | func (s *UserRuleServerClient) RemveUserRelayRule(email []string) error {
28 | _, err := s.RemoveUserRule(context.Background(), &userruleservice.RemoveUserRequest{
29 | Email: email,
30 | })
31 | return err
32 | }
33 |
--------------------------------------------------------------------------------
/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 | AlterId uint32
11 | PrefixedId string
12 | }
13 |
14 | type UserTrafficLog struct {
15 | UserID uint `json:"user_id"`
16 | Uplink uint64 `json:"u"`
17 | Downlink uint64 `json:"d"`
18 | }
19 |
20 | type NodeInfo struct {
21 | NodeID uint
22 | Server_raw string `json:"server"`
23 | Sort uint `json:"sort"`
24 | Server map[string]interface{}
25 | }
26 |
27 | type UserOnLineIP struct {
28 | UserId uint `json:"user_id"`
29 | Ip string `json:"ip"`
30 | }
31 |
32 | type DisNodeInfo struct {
33 | Server_raw string `json:"dist_node_server"`
34 | Sort uint `json:"dist_node_sort"`
35 | Port uint16 `json:"port"`
36 | Server map[string]interface{}
37 | UserId uint `json:"user_id"`
38 | }
39 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Docker/Caddy_V2ray/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | v2ray:
5 | image: hulisang/v2ray_v3:go
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 | volumes:
18 | - /etc/localtime:/etc/localtime:ro
19 | logging:
20 | options:
21 | max-size: "10m"
22 | max-file: "3"
23 |
24 | caddy:
25 | image: hulisang/v2ray_v3:caddy
26 | restart: always
27 | environment:
28 | - ACME_AGREE=true
29 | # if u want to use cloudflare (for DNS challenge authentication)
30 | # - CLOUDFLARE_EMAIL=xxxxxx@out.look.com
31 | # - CLOUDFLARE_API_KEY=xxxxxxx
32 | - V2RAY_DOMAIN=xxxx.com
33 | - V2RAY_PATH=/v2ray
34 | - V2RAY_EMAIL=xxxx@outlook.com
35 | - V2RAY_PORT=10550
36 | - V2RAY_OUTSIDE_PORT=443
37 | network_mode: "host"
38 | volumes:
39 | - ./.caddy:/root/.caddy
40 | - ./Caddyfile:/etc/Caddyfile
41 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Docker/alpine/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:latest as builder
2 |
3 | LABEL maintainer="Rico "
4 |
5 | ENV v2ray_version=4.18.3
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/rico93/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
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 |
21 | RUN set -ex && \
22 | apk --no-cache add ca-certificates && \
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 | rm -rf /var/cache/apk/*
29 |
30 | ENV TZ=Asia/Shanghai
31 | ENV PATH /usr/bin/v2ray:$PATH
32 | VOLUME /var/log/v2ray/
33 | WORKDIR /var/log/v2ray/
34 |
35 | CMD sed -i "s|\"port\": 2333,|\"port\": ${api_port},|" "/etc/v2ray/config.json" &&\
36 | sed -i "s|\"https://google.com\"|\"${sspanel_url}\"|g" "/etc/v2ray/config.json" && \
37 | sed -i "s/\"55fUxDGFzH3n\"/\"${key}\"/g" "/etc/v2ray/config.json" && \
38 | sed -i "s/123456/${node_id}/g" "/etc/v2ray/config.json" && \
39 | sed -i "s/\"SpeedTestCheckRate\": 6/\"SpeedTestCheckRate\": ${speedtest}/g" "/etc/v2ray/config.json" && \
40 | sed -i "s/\"downWithPanel\": 6/\"downWithPanel\": ${downWithPanel}/g" "/etc/v2ray/config.json" && \
41 | v2ray -config=/etc/v2ray/config.json
42 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/plugin.go:
--------------------------------------------------------------------------------
1 | package v2ray_sspanel_v3_mod_Uim_plugin
2 |
3 | import (
4 | "fmt"
5 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/client"
6 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/config"
7 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/webapi"
8 | "google.golang.org/grpc/status"
9 | "os"
10 | "runtime"
11 | "time"
12 | "v2ray.com/core/common/errors"
13 | )
14 |
15 | func init() {
16 | go func() {
17 | err := run()
18 | if err != nil {
19 | fatal(err)
20 | }
21 | }()
22 | }
23 |
24 | func run() error {
25 |
26 | err := config.CommandLine.Parse(os.Args[1:])
27 |
28 | cfg, err := config.GetConfig()
29 | if err != nil || *config.Test || cfg == nil {
30 | return err
31 | }
32 |
33 | // wait v2ray
34 | time.Sleep(3 * time.Second)
35 | db := &webapi.Webapi{
36 | WebToken: cfg.PanelKey,
37 | WebBaseURl: cfg.PanelUrl,
38 | }
39 | go func() {
40 | apiInbound := config.GetInboundConfigByTag(cfg.V2rayConfig.Api.Tag, cfg.V2rayConfig.InboundConfigs)
41 | gRPCAddr := fmt.Sprintf("%s:%d", apiInbound.ListenOn.String(), apiInbound.PortRange.From)
42 | gRPCConn, err := client.ConnectGRPC(gRPCAddr, 10*time.Second)
43 | if err != nil {
44 | if s, ok := status.FromError(err); ok {
45 | err = errors.New(s.Message())
46 | }
47 | fatal(fmt.Sprintf("connect to gRPC server \"%s\" err: ", gRPCAddr), err)
48 | }
49 | newErrorf("Connected gRPC server \"%s\" ", gRPCAddr).AtWarning().WriteToLog()
50 |
51 | p, err := NewPanel(gRPCConn, db, cfg)
52 | if err != nil {
53 | fatal("new panel error", err)
54 | }
55 |
56 | p.Start()
57 | }()
58 | // Explicitly triggering GC to remove garbage
59 | runtime.GC()
60 | return nil
61 | }
62 |
63 | func newErrorf(format string, a ...interface{}) *errors.Error {
64 | return newError(fmt.Sprintf(format, a...))
65 | }
66 |
67 | func newError(values ...interface{}) *errors.Error {
68 | values = append([]interface{}{"SSPanelPlugin: "}, values...)
69 | return errors.New(values...)
70 | }
71 |
72 | func fatal(values ...interface{}) {
73 | newError(values...).AtError().WriteToLog()
74 | // Wait log
75 | time.Sleep(1 * time.Second)
76 | os.Exit(-2)
77 | }
78 |
--------------------------------------------------------------------------------
/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_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/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 |
--------------------------------------------------------------------------------
/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 | }
106 | }
107 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "encoding/json"
5 | "flag"
6 | "fmt"
7 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/utility"
8 | "io/ioutil"
9 | "os"
10 | "path/filepath"
11 | "v2ray.com/core/common/errors"
12 | "v2ray.com/core/common/platform"
13 | "v2ray.com/core/infra/conf"
14 | )
15 |
16 | //var (
17 | // CommandLine = flag.NewFlagSet(os.Args[0]+"-sspanel_v3_mod_Uim_plugin", flag.ContinueOnError)
18 | //
19 | // ConfigFile = CommandLine.String("config", "", "Config file for V2Ray.")
20 | // _ = CommandLine.Bool("version", false, "Show current version of V2Ray.")
21 | // Test = CommandLine.Bool("test", false, "Test config file only, without launching V2Ray server.")
22 | // _ = CommandLine.String("format", "json", "Format of input file.")
23 | // _ = CommandLine.Bool("plugin", false, "True to load plugins.")
24 | //)
25 |
26 | var (
27 | CommandLine = flag.NewFlagSet(os.Args[0]+"-sspanel_v3_mod_Uim_plugin", flag.ContinueOnError)
28 |
29 | ConfigFile = CommandLine.String("config", "", "Config file for V2Ray.")
30 | _ = CommandLine.Bool("version", false, "Show current version of V2Ray.")
31 | Test = CommandLine.Bool("test", false, "Test config file only, without launching V2Ray server.")
32 | _ = CommandLine.String("format", "json", "Format of input file.")
33 | _ = CommandLine.Bool("plugin", false, "True to load plugins.")
34 | )
35 |
36 | type Config struct {
37 | NodeID uint `json:"nodeId"`
38 | CheckRate int `json:"checkRate"`
39 | PanelUrl string `json:"panelUrl"`
40 | PanelKey string `json:"panelKey"`
41 | SpeedTestCheckRate int `json:"speedTestCheckrate"`
42 | DownWithPanel int `json:"downWithPanel"`
43 | V2rayConfig *conf.Config
44 | }
45 |
46 | func GetConfig() (*Config, error) {
47 | type config struct {
48 | *conf.Config
49 | SSPanel *Config `json:"sspanel"`
50 | }
51 |
52 | configFile := GetConfigFilePath()
53 | // Open our jsonFile
54 | jsonFile, err := os.Open(configFile)
55 | // if we os.Open returns an error then handle it
56 | if err != nil {
57 | return nil, errors.New("failed to open config: ", configFile).Base(err)
58 | }
59 |
60 | // defer the closing of our jsonFile so that we can parse it later on
61 | defer jsonFile.Close()
62 |
63 | byteValue, _ := ioutil.ReadAll(jsonFile)
64 |
65 | cfg := &config{}
66 | err = json.Unmarshal(byteValue, &cfg)
67 | if err != nil {
68 | return nil, err
69 | }
70 | if cfg.SSPanel != nil {
71 | cfg.SSPanel.V2rayConfig = cfg.Config
72 | if err = CheckCfg(cfg.SSPanel); err != nil {
73 | return nil, err
74 | }
75 | }
76 | return cfg.SSPanel, err
77 | }
78 |
79 | func CheckCfg(cfg *Config) error {
80 |
81 | if cfg.V2rayConfig.Api == nil {
82 | return errors.New("Api must be set")
83 | }
84 |
85 | apiTag := cfg.V2rayConfig.Api.Tag
86 | if len(apiTag) == 0 {
87 | return errors.New("Api tag can't be empty")
88 | }
89 |
90 | services := cfg.V2rayConfig.Api.Services
91 | if !utility.InStr("HandlerService", services) {
92 | return errors.New("Api service, HandlerService, must be enabled")
93 | }
94 | if !utility.InStr("StatsService", services) {
95 | return errors.New("Api service, StatsService, must be enabled")
96 | }
97 |
98 | if cfg.V2rayConfig.Stats == nil {
99 | return errors.New("Stats must be enabled")
100 | }
101 |
102 | if apiInbound := GetInboundConfigByTag(apiTag, cfg.V2rayConfig.InboundConfigs); apiInbound == nil {
103 | return errors.New(fmt.Sprintf("Miss an inbound tagged %s", apiTag))
104 | } else if apiInbound.Protocol != "dokodemo-door" {
105 | return errors.New(fmt.Sprintf("The protocol of inbound tagged %s must be \"dokodemo-door\"", apiTag))
106 | } else {
107 | if apiInbound.ListenOn == nil || apiInbound.PortRange == nil {
108 | return errors.New(fmt.Sprintf("Fields, \"listen\" and \"port\", of inbound tagged %s must be set", apiTag))
109 | }
110 | }
111 |
112 | return nil
113 | }
114 |
115 | func GetInboundConfigByTag(apiTag string, inbounds []conf.InboundDetourConfig) *conf.InboundDetourConfig {
116 | for _, inbound := range inbounds {
117 | if inbound.Tag == apiTag {
118 | return &inbound
119 | }
120 | }
121 | return nil
122 | }
123 |
124 | func GetConfigFilePath() string {
125 | if len(*ConfigFile) > 0 {
126 | return *ConfigFile
127 | }
128 |
129 | if workingDir, err := os.Getwd(); err == nil {
130 | configFile := filepath.Join(workingDir, "config.json")
131 | if fileExists(configFile) {
132 | return configFile
133 | }
134 | }
135 |
136 | if configFile := platform.GetConfigurationPath(); fileExists(configFile) {
137 | return configFile
138 | }
139 |
140 | return ""
141 | }
142 |
143 | func fileExists(file string) bool {
144 | info, err := os.Stat(file)
145 | return err == nil && !info.IsDir()
146 | }
147 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 由于hulisang大佬已经把仓库删除,本人仅做备份
2 | 下面是hulisang的原话:
3 |
4 | 感恩原作者rico辛苦付出
5 | 本人仅做备份和后续维护
6 | caddy镜像更新支持tls1.3
7 |
8 | # v2ray-sspanel-v3-mod_Uim-plugin
9 |
10 |
11 | ## Thanks
12 | 1. 感恩的 [ColetteContreras's repo](https://github.com/ColetteContreras/v2ray-ssrpanel-plugin). 让我一个go小白有了下手地。主要起始框架来源于这里
13 | 2. 感恩 [eycorsican](https://github.com/eycorsican) 在v2ray-core [issue](https://github.com/v2ray/v2ray-core/issues/1514), 促成了go版本提上日程
14 |
15 |
16 | # 划重点
17 | 1. 用户务必保证,host 务必填写没有被墙的地址
18 | 2. 已经适配了中转,必须用我自己维护的[panel](https://github.com/rico93/ss-panel-v3-mod_Uim)
19 |
20 |
21 | ## 项目状态
22 |
23 | 支持 [ss-panel-v3-mod_Uim](https://github.com/NimaQu/ss-panel-v3-mod_Uim) 的 webapi。 目前自己也尝试维护了一个版本
24 |
25 | 目前只适配了流量记录、服务器是否在线、在线人数,在线ip上报、负载、中转,后端根据前端的设定自动调用 API 增加用户。
26 |
27 | v2ray 后端 kcp、tcp、ws 都是多用户共用一个端口。
28 |
29 | 也可作为 ss 后端一个用户一个端口。
30 |
31 | ## 已知 Bug
32 |
33 | ## 作为 ss 后端
34 |
35 | 面板配置是节点类型为 Shadowsocks,普通端口。
36 |
37 | 加密方式只支持:
38 |
39 | - [x] aes-256-cfb
40 | - [x] aes-128-cfb
41 | - [x] chacha20
42 | - [x] chacha20-ietf
43 | - [x] aes-256-gcm
44 | - [x] aes-128-gcm
45 | - [x] chacha20-poly1305 或称 chacha20-ietf-poly1305
46 |
47 | ## 作为 V2ray 后端
48 |
49 | 这里面板设置是节点类型v2ray, 普通端口。 v2ray的API接口默认是2333
50 |
51 | 支持 tcp,kcp、ws+(tls 由镜像 Caddy或者ngnix 提供,默认是443接口哦)。或者自己调整。
52 |
53 | [面板设置说明 主要是这个](https://github.com/NimaQu/ss-panel-v3-mod_Uim/wiki/v2ray-%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B)
54 |
55 | ~~~
56 | 没有CDN的域名或者ip;端口(外部链接的);AlterId;协议层;;额外参数(path=/v2ray|host=xxxx.win|inside_port=10550这个端口内部监听))
57 |
58 | // ws 示例
59 | xxxxx.com;10550;16;ws;;path=/v2ray|host=oxxxx.com
60 |
61 | // ws + tls (Caddy 提供)
62 | xxxxx.com;0;16;tls;ws;path=/v2ray|host=oxxxx.com|inside_port=10550
63 | xxxxx.com;;16;tls;ws;path=/v2ray|host=oxxxx.com|inside_port=10550
64 |
65 |
66 |
67 | // nat🐔 ws 示例
68 | xxxxx.com;11120;16;ws;;path=/v2ray|host=oxxxx.com
69 |
70 | // nat🐔 ws + tls (Caddy 提供)
71 | xxxxx.com;0;16;tls;ws;path=/v2ray|host=oxxxx.com|inside_port=10550|outside_port=11120
72 | xxxxx.com;;16;tls;ws;path=/v2ray|host=oxxxx.com|inside_port=10550|outside_port=11120
73 | ~~~
74 |
75 | 目前的逻辑是
76 |
77 | - 如果为外部链接的端口是0或者不填,则默认监听本地127.0.0.1:inside_port
78 | - 如果外部端口设定不是 0或者空,则监听 0.0.0.0:外部设定端口,此端口为所有用户的单端口,此时 inside_port 弃用。
79 | - 默认使用 Caddy 镜像来提供 tls,控制代码不会生成 tls 相关的配置。Caddyfile 可以在Docker/Caddy_V2ray文件夹里面找到。
80 | - Nat🐔,如果要用ws+tls,则需要使用outside_port=xxx,php后端会生成订阅时候,使用outside_port覆盖port部分。 outside_port是内部映射端口,
81 | 建议内网和外网的两个端口数值一致。
82 |
83 | tcp 配置:
84 |
85 | ~~~
86 | xxxxx.com;非0;16;tcp;;
87 | ~~~
88 |
89 | kcp 支持所有 v2ray 的 type:
90 |
91 | - none: 默认值,不进行伪装,发送的数据是没有特征的数据包。
92 |
93 | ~~~
94 | xxxxx.com;非0;16;kcp;noop;
95 | ~~~
96 |
97 | - srtp: 伪装成 SRTP 数据包,会被识别为视频通话数据(如 FaceTime)。
98 |
99 | ~~~
100 | xxxxx.com;非0;16;kcp;srtp;
101 | ~~~
102 |
103 | - utp: 伪装成 uTP 数据包,会被识别为 BT 下载数据。
104 |
105 | ~~~
106 | xxxxx.com;非0;16;kcp;utp;
107 | ~~~
108 |
109 | - wechat-video: 伪装成微信视频通话的数据包。
110 |
111 | ~~~
112 | xxxxx.com;非0;16;kcp;wechat-video;
113 | ~~~
114 |
115 | - dtls: 伪装成 DTLS 1.2 数据包。
116 |
117 | ~~~
118 | xxxxx.com;非0;16;kcp;dtls;
119 | ~~~
120 |
121 | - wireguard: 伪装成 WireGuard 数据包(并不是真正的 WireGuard 协议) 。
122 |
123 | ~~~
124 | xxxxx.com;非0;16;kcp;wireguard;
125 | ~~~
126 |
127 | ### [可选] 安装 BBR
128 |
129 | 看 [Rat的](https://www.moerats.com/archives/387/)
130 | OpenVZ 看这里 [南琴浪](https://github.com/tcp-nanqinlang/wiki/wiki/lkl-haproxy)
131 |
132 | ~~~
133 | wget -N --no-check-certificate "https://raw.githubusercontent.com/chiakge/Linux-NetSpeed/master/tcp.sh" && chmod +x tcp.sh && ./tcp.sh
134 | ~~~
135 |
136 | Ubuntu 18.04 魔改 BBR 暂时有点问题,可使用以下命令安装:
137 |
138 | ~~~
139 | wget -N --no-check-certificate "https://raw.githubusercontent.com/chiakge/Linux-NetSpeed/master/tcp.sh"
140 | apt install make gcc -y
141 | sed -i 's#/usr/bin/gcc-4.9#/usr/bin/gcc#g' '/root/tcp.sh'
142 | chmod +x tcp.sh && ./tcp.sh
143 | ~~~
144 | ### [可选] 增加swap
145 | 整数是M
146 | ~~~
147 | wget https://www.moerats.com/usr/shell/swap.sh && bash swap.sh
148 | ~~~
149 |
150 | ### [推荐] 脚本部署
151 |
152 | #### Docker-compose 安装
153 | 这里一直保持最新版
154 | ~~~
155 | mkdir v2ray-agent && \
156 | cd v2ray-agent && \
157 | curl https://raw.githubusercontent.com/haig233/v2ray-sspanel-v3-mod_Uim-plugin/master/install.sh -o install.sh && \
158 | chmod +x install.sh && \
159 | bash install.sh
160 | ~~~
161 |
162 | ##### 安装caddy
163 |
164 | 一键安装 caddy 和cf ddns tls插件
165 |
166 | ~~~
167 | curl https://getcaddy.com | bash -s dyndns,tls.dns.cloudflare
168 | ~~~
169 |
170 | Caddyfile
171 |
172 | 自行修改,或者设置对应环境变量
173 |
174 | ~~~
175 | {$V2RAY_DOMAIN}:{$V2RAY_OUTSIDE_PORT}
176 | {
177 | root /srv/www
178 | log ./caddy.log
179 | proxy {$V2RAY_PATH} 127.0.0.1:{$V2RAY_PORT} {
180 | websocket
181 | header_upstream -Origin
182 | }
183 | gzip
184 | tls {$V2RAY_EMAIL} {
185 | protocols tls1.0 tls1.2
186 | # remove comment if u want to use cloudflare ddns
187 | # dns cloudflare
188 | }
189 | }
190 | ~~~
191 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/webapi/webapi.go:
--------------------------------------------------------------------------------
1 | package webapi
2 |
3 | import (
4 | "fmt"
5 | "github.com/imroc/req"
6 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/model"
7 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/speedtest"
8 | "github.com/rico93/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]) > 0 {
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]) > 0 {
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]) > 0 {
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]) > 0 {
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 |
--------------------------------------------------------------------------------
/client/handlerServiceClient.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/model"
7 | "github.com/rico93/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/mtproto"
18 | "v2ray.com/core/proxy/shadowsocks"
19 | "v2ray.com/core/proxy/vmess"
20 | "v2ray.com/core/proxy/vmess/inbound"
21 | "v2ray.com/core/proxy/vmess/outbound"
22 | "v2ray.com/core/transport/internet"
23 | "v2ray.com/core/transport/internet/headers/noop"
24 | "v2ray.com/core/transport/internet/headers/srtp"
25 | "v2ray.com/core/transport/internet/headers/tls"
26 | "v2ray.com/core/transport/internet/headers/utp"
27 | "v2ray.com/core/transport/internet/headers/wechat"
28 | "v2ray.com/core/transport/internet/headers/wireguard"
29 | "v2ray.com/core/transport/internet/kcp"
30 | "v2ray.com/core/transport/internet/websocket"
31 | )
32 |
33 | var KcpHeadMap = map[string]*serial.TypedMessage{
34 | "wechat-video": serial.ToTypedMessage(&wechat.VideoConfig{}),
35 | "srtp": serial.ToTypedMessage(&srtp.Config{}),
36 | "utp": serial.ToTypedMessage(&utp.Config{}),
37 | "wireguard": serial.ToTypedMessage(&wireguard.WireguardConfig{}),
38 | "dtls": serial.ToTypedMessage(&tls.PacketConfig{}),
39 | "noop": serial.ToTypedMessage(&noop.Config{}),
40 | }
41 | var CipherTypeMap = map[string]shadowsocks.CipherType{
42 | "aes-256-cfb": shadowsocks.CipherType_AES_256_CFB,
43 | "aes-128-cfb": shadowsocks.CipherType_AES_128_CFB,
44 | "aes-128-gcm": shadowsocks.CipherType_AES_128_GCM,
45 | "aes-256-gcm": shadowsocks.CipherType_AES_256_GCM,
46 | "chacha20": shadowsocks.CipherType_CHACHA20,
47 | "chacha20-ietf": shadowsocks.CipherType_CHACHA20_IETF,
48 | "chacha20-ploy1305": shadowsocks.CipherType_CHACHA20_POLY1305,
49 | "chacha20-ietf-poly1305": shadowsocks.CipherType_CHACHA20_POLY1305,
50 | "xchacha20-ietf-poly1305": shadowsocks.CipherType_XCHACHA20_POLY1305,
51 | }
52 |
53 | type HandlerServiceClient struct {
54 | command.HandlerServiceClient
55 | InboundTag string
56 | }
57 |
58 | func NewHandlerServiceClient(client *grpc.ClientConn, inboundTag string) *HandlerServiceClient {
59 | return &HandlerServiceClient{
60 | HandlerServiceClient: command.NewHandlerServiceClient(client),
61 | InboundTag: inboundTag,
62 | }
63 | }
64 |
65 | // user
66 | func (h *HandlerServiceClient) DelUser(email string) error {
67 | req := &command.AlterInboundRequest{
68 | Tag: h.InboundTag,
69 | Operation: serial.ToTypedMessage(&command.RemoveUserOperation{Email: email}),
70 | }
71 | return h.AlterInbound(req)
72 | }
73 |
74 | func (h *HandlerServiceClient) AddUser(user model.UserModel) error {
75 | req := &command.AlterInboundRequest{
76 | Tag: h.InboundTag,
77 | Operation: serial.ToTypedMessage(&command.AddUserOperation{User: h.ConvertVmessUser(user)}),
78 | }
79 | return h.AlterInbound(req)
80 | }
81 |
82 | func (h *HandlerServiceClient) AlterInbound(req *command.AlterInboundRequest) error {
83 | _, err := h.HandlerServiceClient.AlterInbound(context.Background(), req)
84 | return err
85 | }
86 |
87 | //streaming
88 | func GetKcpStreamConfig(headkey string) *internet.StreamConfig {
89 | var streamsetting internet.StreamConfig
90 | head, _ := KcpHeadMap["noop"]
91 | if _, ok := KcpHeadMap[headkey]; ok {
92 | head, _ = KcpHeadMap[headkey]
93 | }
94 | streamsetting = internet.StreamConfig{
95 | ProtocolName: "mkcp",
96 | TransportSettings: []*internet.TransportConfig{
97 | &internet.TransportConfig{
98 | ProtocolName: "mkcp",
99 | Settings: serial.ToTypedMessage(
100 | &kcp.Config{
101 | HeaderConfig: head,
102 | }),
103 | },
104 | },
105 | }
106 | return &streamsetting
107 | }
108 |
109 | func GetWebSocketStreamConfig(path string, host string, tm *serial.TypedMessage) *internet.StreamConfig {
110 | var streamsetting internet.StreamConfig
111 | if tm == nil {
112 | streamsetting = internet.StreamConfig{
113 | ProtocolName: "websocket",
114 | TransportSettings: []*internet.TransportConfig{
115 | &internet.TransportConfig{
116 | ProtocolName: "websocket",
117 | Settings: serial.ToTypedMessage(&websocket.Config{
118 | Path: path,
119 | Header: []*websocket.Header{
120 | &websocket.Header{
121 | Key: "Hosts",
122 | Value: host,
123 | },
124 | },
125 | }),
126 | },
127 | },
128 | }
129 | } else {
130 | streamsetting = internet.StreamConfig{
131 | ProtocolName: "websocket",
132 | TransportSettings: []*internet.TransportConfig{
133 | &internet.TransportConfig{
134 | ProtocolName: "websocket",
135 | Settings: serial.ToTypedMessage(&websocket.Config{
136 | Path: path,
137 | Header: []*websocket.Header{
138 | &websocket.Header{
139 | Key: "Hosts",
140 | Value: host,
141 | },
142 | },
143 | }),
144 | },
145 | },
146 | SecuritySettings: []*serial.TypedMessage{tm},
147 | SecurityType: tm.Type,
148 | }
149 | }
150 | return &streamsetting
151 | }
152 |
153 | // different type inbounds
154 | func (h *HandlerServiceClient) AddVmessInbound(port uint16, address string, streamsetting *internet.StreamConfig) error {
155 | var addinboundrequest command.AddInboundRequest
156 | addinboundrequest = command.AddInboundRequest{
157 | Inbound: &core.InboundHandlerConfig{
158 | Tag: h.InboundTag,
159 | ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
160 | PortRange: net.SinglePortRange(net.Port(port)),
161 | Listen: net.NewIPOrDomain(net.ParseAddress(address)),
162 | StreamSettings: streamsetting,
163 | }),
164 | ProxySettings: serial.ToTypedMessage(&inbound.Config{
165 | User: []*protocol.User{
166 | {
167 | Level: 0,
168 | Email: "rico93@xxx.com",
169 | Account: serial.ToTypedMessage(&vmess.Account{
170 | Id: protocol.NewID(uuid.New()).String(),
171 | AlterId: 16,
172 | }),
173 | },
174 | },
175 | }),
176 | },
177 | }
178 | return h.AddInbound(&addinboundrequest)
179 | }
180 |
181 | func (h *HandlerServiceClient) AddVmessOutbound(tag string, port uint16, address string, streamsetting *internet.StreamConfig, user *protocol.User) error {
182 | var addoutboundrequest command.AddOutboundRequest
183 | addoutboundrequest = command.AddOutboundRequest{
184 | Outbound: &core.OutboundHandlerConfig{
185 | Tag: tag,
186 | SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
187 | StreamSettings: streamsetting,
188 | }),
189 | ProxySettings: serial.ToTypedMessage(&outbound.Config{
190 | Receiver: []*protocol.ServerEndpoint{
191 | {
192 | Address: net.NewIPOrDomain(net.ParseAddress(address)),
193 | Port: uint32(port),
194 | User: []*protocol.User{
195 | user,
196 | },
197 | },
198 | },
199 | }),
200 | },
201 | }
202 | return h.AddOutbound(&addoutboundrequest)
203 | }
204 |
205 | func (h *HandlerServiceClient) AddSSOutbound(user model.UserModel, dist *model.DisNodeInfo) error {
206 | var addoutboundrequest command.AddOutboundRequest
207 | addoutboundrequest = command.AddOutboundRequest{
208 | Outbound: &core.OutboundHandlerConfig{
209 | Tag: dist.Server_raw + fmt.Sprintf("%d", user.UserID),
210 | ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
211 | Server: []*protocol.ServerEndpoint{
212 | {
213 | Address: net.NewIPOrDomain(net.ParseAddress(dist.Server["server_address"].(string))),
214 | Port: uint32(dist.Port),
215 | User: []*protocol.User{
216 | h.ConverSSUser(user),
217 | },
218 | },
219 | },
220 | }),
221 | },
222 | }
223 | return h.AddOutbound(&addoutboundrequest)
224 | }
225 | func (h *HandlerServiceClient) AddMTInbound(port uint16, address string, streamsetting *internet.StreamConfig) error {
226 | var addinboundrequest command.AddInboundRequest
227 | addinboundrequest = command.AddInboundRequest{
228 | Inbound: &core.InboundHandlerConfig{
229 | Tag: "tg-in",
230 | ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
231 | PortRange: net.SinglePortRange(net.Port(port)),
232 | Listen: net.NewIPOrDomain(net.ParseAddress(address)),
233 | }),
234 | ProxySettings: serial.ToTypedMessage(&mtproto.ServerConfig{
235 | User: []*protocol.User{
236 | {
237 | Level: 0,
238 | Email: "rico93@xxx.com",
239 | Account: serial.ToTypedMessage(&mtproto.Account{
240 | Secret: utility.MD5(utility.GetRandomString(16)),
241 | }),
242 | },
243 | },
244 | }),
245 | },
246 | }
247 | return h.AddInbound(&addinboundrequest)
248 | }
249 | func (h *HandlerServiceClient) AddSSInbound(user model.UserModel) error {
250 | var addinboundrequest command.AddInboundRequest
251 | addinboundrequest = command.AddInboundRequest{
252 | Inbound: &core.InboundHandlerConfig{
253 | Tag: user.PrefixedId,
254 | ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
255 | PortRange: net.SinglePortRange(net.Port(user.Port)),
256 | Listen: net.NewIPOrDomain(net.ParseAddress("0.0.0.0")),
257 | }),
258 | ProxySettings: serial.ToTypedMessage(&shadowsocks.ServerConfig{
259 | User: h.ConverSSUser(user),
260 | Network: []net.Network{net.Network_TCP, net.Network_UDP},
261 | }),
262 | },
263 | }
264 | return h.AddInbound(&addinboundrequest)
265 | }
266 | func (h *HandlerServiceClient) AddInbound(req *command.AddInboundRequest) error {
267 | _, err := h.HandlerServiceClient.AddInbound(context.Background(), req)
268 | return err
269 | }
270 | func (h *HandlerServiceClient) AddOutbound(req *command.AddOutboundRequest) error {
271 | _, err := h.HandlerServiceClient.AddOutbound(context.Background(), req)
272 | return err
273 | }
274 | func (h *HandlerServiceClient) RemoveInbound(tag string) error {
275 | req := command.RemoveInboundRequest{
276 | Tag: tag,
277 | }
278 | _, err := h.HandlerServiceClient.RemoveInbound(context.Background(), &req)
279 | return err
280 | }
281 | func (h *HandlerServiceClient) RemoveOutbound(tag string) error {
282 | req := command.RemoveOutboundRequest{
283 | Tag: tag,
284 | }
285 | _, err := h.HandlerServiceClient.RemoveOutbound(context.Background(), &req)
286 | return err
287 | }
288 |
289 | func (h *HandlerServiceClient) ConvertVmessUser(userModel model.UserModel) *protocol.User {
290 | return &protocol.User{
291 | Level: 0,
292 | Email: userModel.Email,
293 | Account: serial.ToTypedMessage(&vmess.Account{
294 | Id: userModel.Uuid,
295 | AlterId: userModel.AlterId,
296 | SecuritySettings: &protocol.SecurityConfig{
297 | Type: protocol.SecurityType(protocol.SecurityType_value[strings.ToUpper("AUTO")]),
298 | },
299 | }),
300 | }
301 | }
302 | func (h *HandlerServiceClient) ConverSSUser(userModel model.UserModel) *protocol.User {
303 | return &protocol.User{
304 | Level: 0,
305 | Email: userModel.Email,
306 | Account: serial.ToTypedMessage(&shadowsocks.Account{
307 | Password: userModel.Passwd,
308 | CipherType: CipherTypeMap[strings.ToLower(userModel.Method)],
309 | Ota: shadowsocks.Account_Auto,
310 | }),
311 | }
312 | }
313 |
314 | func (h *HandlerServiceClient) ConverMTUser(userModel model.UserModel) *protocol.User {
315 | return &protocol.User{
316 | Level: 0,
317 | Email: userModel.Email,
318 | Account: serial.ToTypedMessage(&mtproto.Account{
319 | Secret: utility.MD5(userModel.Uuid),
320 | }),
321 | }
322 | }
323 |
--------------------------------------------------------------------------------
/Manager/Manager.go:
--------------------------------------------------------------------------------
1 | package Manager
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/client"
7 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/model"
8 | "strconv"
9 | "v2ray.com/core/common/errors"
10 | "v2ray.com/core/common/serial"
11 | "v2ray.com/core/infra/conf"
12 | "v2ray.com/core/transport/internet"
13 | )
14 |
15 | type Manager struct {
16 | HandlerServiceClient *client.HandlerServiceClient
17 | StatsServiceClient *client.StatsServiceClient
18 | UserRuleServiceClient *client.UserRuleServerClient
19 | CurrentNodeInfo *model.NodeInfo
20 | NextNodeInfo *model.NodeInfo
21 | UserChanged bool
22 | UserToBeMoved map[string]model.UserModel
23 | UserToBeAdd map[string]model.UserModel
24 | Users map[string]model.UserModel
25 | Id2PrefixedIdmap map[uint]string
26 | Id2DisServer map[uint]string
27 | MainAddress string
28 | MainListenPort uint16
29 | NodeID uint
30 | CheckRate int
31 | SpeedTestCheckRate int
32 | }
33 |
34 | func (manager *Manager) GetUsers() map[string]model.UserModel {
35 | return manager.Users
36 | }
37 |
38 | func (manager *Manager) Add(user model.UserModel) {
39 | manager.UserChanged = true
40 | manager.UserToBeAdd[user.PrefixedId] = user
41 | }
42 |
43 | func (manager *Manager) Remove(prefixedId string) bool {
44 | user, ok := manager.Users[prefixedId]
45 | if ok {
46 | manager.UserChanged = true
47 | manager.UserToBeMoved[user.PrefixedId] = user
48 | return true
49 | }
50 | return false
51 | }
52 |
53 | func (manager *Manager) UpdataUsers() {
54 | var successfully_removed, successfully_add []string
55 | if manager.CurrentNodeInfo.Server_raw != "" {
56 | if manager.CurrentNodeInfo.Sort == 0 {
57 | // SS server
58 | /// remove inbounds
59 | for key, value := range manager.UserToBeMoved {
60 | if err := manager.HandlerServiceClient.RemoveInbound(value.PrefixedId); err == nil {
61 | newErrorf("Successfully remove user %s ", key).AtInfo().WriteToLog()
62 | successfully_removed = append(successfully_removed, key)
63 | } else {
64 | newError(err).AtDebug().WriteToLog()
65 | successfully_removed = append(successfully_removed, key)
66 | }
67 | }
68 | } else if manager.CurrentNodeInfo.Sort == 11 || manager.CurrentNodeInfo.Sort == 12 {
69 | // VMESS
70 | // Remove users
71 | for key, value := range manager.UserToBeMoved {
72 | if err := manager.HandlerServiceClient.DelUser(value.Email); err == nil {
73 | newErrorf("Successfully remove user %s ", key).AtInfo().WriteToLog()
74 | successfully_removed = append(successfully_removed, key)
75 | } else {
76 | newError(err).AtDebug().WriteToLog()
77 | successfully_removed = append(successfully_removed, key)
78 | }
79 | }
80 | }
81 |
82 | }
83 | if manager.NextNodeInfo.Server_raw != "" {
84 | if manager.NextNodeInfo.Sort == 0 {
85 | // SS server
86 | /// add inbounds
87 | for key, value := range manager.UserToBeAdd {
88 | if err := manager.HandlerServiceClient.AddSSInbound(value); err == nil {
89 | newErrorf("Successfully add user %s ", key).AtInfo().WriteToLog()
90 | successfully_add = append(successfully_add, key)
91 | } else {
92 | newError(err).AtDebug().WriteToLog()
93 | successfully_add = append(successfully_add, key)
94 | }
95 | }
96 | } else if manager.NextNodeInfo.Sort == 11 || manager.NextNodeInfo.Sort == 12 {
97 | // VMESS
98 | // add users
99 | for key, value := range manager.UserToBeAdd {
100 | if err := manager.HandlerServiceClient.AddUser(value); err == nil {
101 | newErrorf("Successfully add user %s ", key).AtInfo().WriteToLog()
102 | successfully_add = append(successfully_add, key)
103 | } else {
104 | newError(err).AtDebug().WriteToLog()
105 | successfully_add = append(successfully_add, key)
106 | }
107 | }
108 | }
109 | }
110 | for index := range successfully_removed {
111 | delete(manager.Users, successfully_removed[index])
112 | delete(manager.UserToBeMoved, successfully_removed[index])
113 | }
114 | for index := range successfully_add {
115 | manager.Users[successfully_add[index]] = manager.UserToBeAdd[successfully_add[index]]
116 | delete(manager.UserToBeAdd, successfully_add[index])
117 | }
118 | manager.Id2PrefixedIdmap = map[uint]string{}
119 | for key, value := range manager.Users {
120 | manager.Id2PrefixedIdmap[value.UserID] = key
121 | }
122 | }
123 |
124 | func (manager *Manager) UpdateMainAddressAndProt(node_info *model.NodeInfo) {
125 | if node_info.Sort == 11 || node_info.Sort == 12 {
126 | if node_info.Server["port"] == "0" || node_info.Server["port"] == "" {
127 | manager.MainAddress = "127.0.0.1"
128 | manager.MainListenPort = 10550
129 | if node_info.Server["inside_port"] != "" {
130 | port, err := strconv.ParseUint(node_info.Server["inside_port"].(string), 10, 0)
131 | if err == nil {
132 | manager.MainListenPort = uint16(port)
133 | }
134 | }
135 | } else {
136 | manager.MainAddress = "0.0.0.0"
137 | manager.MainListenPort = 10550
138 | if node_info.Server["port"] != "" {
139 | port, err := strconv.ParseUint(node_info.Server["port"].(string), 10, 0)
140 | if err == nil {
141 | manager.MainListenPort = uint16(port)
142 | }
143 | }
144 | }
145 | }
146 | }
147 | func (m *Manager) AddMainInbound() error {
148 | if m.NextNodeInfo.Server_raw != "" {
149 | if m.NextNodeInfo.Sort == 11 || m.NextNodeInfo.Sort == 12 {
150 | m.UpdateMainAddressAndProt(m.NextNodeInfo)
151 | var streamsetting *internet.StreamConfig
152 | streamsetting = &internet.StreamConfig{}
153 |
154 | if m.NextNodeInfo.Server["protocol"] == "ws" {
155 | host := "www.bing.com"
156 | path := "/"
157 | if m.NextNodeInfo.Server["path"] != "" {
158 | path = m.NextNodeInfo.Server["path"].(string)
159 | }
160 | if m.NextNodeInfo.Server["host"] != "" {
161 | host = m.NextNodeInfo.Server["host"].(string)
162 | }
163 | streamsetting = client.GetWebSocketStreamConfig(path, host, nil)
164 | } else if m.NextNodeInfo.Server["protocol"] == "kcp" || m.NextNodeInfo.Server["protocol"] == "mkcp" {
165 | header_key := "noop"
166 | if m.NextNodeInfo.Server["protocol_param"] != "" {
167 | header_key = m.NextNodeInfo.Server["protocol_param"].(string)
168 | }
169 | streamsetting = client.GetKcpStreamConfig(header_key)
170 | }
171 | if err := m.HandlerServiceClient.AddVmessInbound(m.MainListenPort, m.MainAddress, streamsetting); err != nil {
172 | return err
173 | } else {
174 | newErrorf("Successfully add MAIN INBOUND %s port %d", m.MainAddress, m.MainListenPort).AtInfo().WriteToLog()
175 | }
176 | }
177 |
178 | }
179 | return nil
180 | }
181 | func (m *Manager) AddOuntBound(disnodeinfo *model.DisNodeInfo) error {
182 | if disnodeinfo.Server_raw != "" {
183 | if disnodeinfo.Sort == 11 || disnodeinfo.Sort == 12 {
184 | var streamsetting *internet.StreamConfig
185 | var tm *serial.TypedMessage
186 | streamsetting = &internet.StreamConfig{}
187 |
188 | if disnodeinfo.Server["protocol"] == "ws" {
189 | host := "www.bing.com"
190 | path := "/"
191 | if disnodeinfo.Server["path"] != "" {
192 | path = disnodeinfo.Server["path"].(string)
193 | }
194 | if disnodeinfo.Server["host"] != "" {
195 | host = disnodeinfo.Server["host"].(string)
196 | }
197 | if disnodeinfo.Server["protocol_param"] == "tls" {
198 | tlsconfig := &conf.TLSConfig{
199 | InsecureCiphers: true,
200 | }
201 | cert, _ := tlsconfig.Build()
202 | tm = serial.ToTypedMessage(cert)
203 | }
204 | streamsetting = client.GetWebSocketStreamConfig(path, host, tm)
205 | } else if disnodeinfo.Server["protocol"] == "kcp" || disnodeinfo.Server["protocol"] == "mkcp" {
206 | header_key := "noop"
207 | if m.NextNodeInfo.Server["protocol_param"] != "" {
208 | header_key = disnodeinfo.Server["protocol_param"].(string)
209 | }
210 | streamsetting = client.GetKcpStreamConfig(header_key)
211 | }
212 | if err := m.HandlerServiceClient.AddVmessOutbound(disnodeinfo.Server_raw+fmt.Sprintf("%d", disnodeinfo.UserId), disnodeinfo.Port, disnodeinfo.Server["server_address"].(string), streamsetting, m.HandlerServiceClient.ConvertVmessUser(
213 | m.Users[m.Id2PrefixedIdmap[disnodeinfo.UserId]])); err != nil {
214 | return err
215 | } else {
216 | newErrorf("Successfully add Outbound %s port %d", disnodeinfo.Server_raw+fmt.Sprintf("%d", disnodeinfo.UserId), disnodeinfo.Port).AtInfo().WriteToLog()
217 | }
218 | }
219 | if disnodeinfo.Sort == 0 {
220 | if err := m.HandlerServiceClient.AddSSOutbound(m.Users[m.Id2PrefixedIdmap[disnodeinfo.UserId]], disnodeinfo); err != nil {
221 | return newError("User Chipter %S", m.Users[m.Id2PrefixedIdmap[disnodeinfo.UserId]].Method).Base(err)
222 | } else {
223 | newErrorf("Successfully add user %s outbound %s ", m.Id2PrefixedIdmap[disnodeinfo.UserId], disnodeinfo.Server_raw).AtInfo().WriteToLog()
224 |
225 | }
226 | }
227 | m.AddUserRule(disnodeinfo.Server_raw+fmt.Sprintf("%d", disnodeinfo.UserId), m.Users[m.Id2PrefixedIdmap[disnodeinfo.UserId]].Email)
228 | }
229 |
230 | return nil
231 | }
232 | func (m *Manager) AddUserRule(tag, email string) {
233 | if err := m.UserRuleServiceClient.AddUserRelyRule(tag, []string{email}); err == nil {
234 | newErrorf("Successfully add user %s %s server rule ", email, tag).AtInfo().WriteToLog()
235 | } else {
236 | newError(err).AtDebug().WriteToLog()
237 | }
238 | }
239 | func (m *Manager) RemoveUserRule(email string) {
240 |
241 | if err := m.UserRuleServiceClient.RemveUserRelayRule([]string{email}); err == nil {
242 | newErrorf("Successfully remove user %s rule", email).AtInfo().WriteToLog()
243 | } else {
244 | newError(err).AtDebug().WriteToLog()
245 | }
246 | }
247 |
248 | func (m *Manager) RemoveInbound() {
249 | if m.CurrentNodeInfo.Server_raw != "" {
250 | if m.CurrentNodeInfo.Sort == 11 || m.CurrentNodeInfo.Sort == 12 {
251 | m.UpdateMainAddressAndProt(m.CurrentNodeInfo)
252 | if err := m.HandlerServiceClient.RemoveInbound(m.HandlerServiceClient.InboundTag); err != nil {
253 | newError(err).AtWarning().WriteToLog()
254 | } else {
255 | newErrorf("Successfully remove main inbound %s", m.HandlerServiceClient.InboundTag).AtInfo().WriteToLog()
256 | }
257 | }
258 | }
259 | }
260 | func (m *Manager) RemoveOutBound(tag string, userid uint) {
261 | if err := m.HandlerServiceClient.RemoveOutbound(tag); err != nil {
262 | newError(err).AtWarning().WriteToLog()
263 | } else {
264 | newErrorf("Successfully remove outbound %s", tag).AtInfo().WriteToLog()
265 | }
266 | m.RemoveUserRule(m.Users[m.Id2PrefixedIdmap[userid]].Email)
267 | }
268 | func (m *Manager) RemoveAllUserOutBound() {
269 | for id, server := range m.Id2DisServer {
270 | m.RemoveOutBound(server+fmt.Sprintf("%d", id), id)
271 | }
272 | }
273 | func (m *Manager) CopyUsers() {
274 | jsonString, err := json.Marshal(m.Users)
275 | if err != nil {
276 | newError(err).AtWarning().WriteToLog()
277 | }
278 | var usertobemoved map[string]model.UserModel
279 | err = json.Unmarshal(jsonString, &usertobemoved)
280 | if err != nil {
281 | newError(err).AtWarning().WriteToLog()
282 | }
283 | m.UserToBeMoved = usertobemoved
284 | m.UserToBeAdd = map[string]model.UserModel{}
285 | }
286 | func (m *Manager) CopyNodeinfo() {
287 | jsonString, err := json.Marshal(m.NextNodeInfo)
288 | if err != nil {
289 | newError(err).AtWarning().WriteToLog()
290 | }
291 | currentnodeinfo := model.NodeInfo{}
292 | err = json.Unmarshal(jsonString, ¤tnodeinfo)
293 | if err != nil {
294 | newError(err).AtWarning().WriteToLog()
295 | }
296 | m.CurrentNodeInfo = ¤tnodeinfo
297 |
298 | }
299 | func (m *Manager) UpdateServer() error {
300 | m.CopyUsers()
301 | m.UpdataUsers()
302 | m.RemoveInbound()
303 | err := m.AddMainInbound()
304 | m.Users = map[string]model.UserModel{}
305 | return err
306 | }
307 |
308 | func newErrorf(format string, a ...interface{}) *errors.Error {
309 | return newError(fmt.Sprintf(format, a...))
310 | }
311 |
312 | func newError(values ...interface{}) *errors.Error {
313 | values = append([]interface{}{"SSPanelPlugin: "}, values...)
314 | return errors.New(values...)
315 | }
316 |
--------------------------------------------------------------------------------
/panel.go:
--------------------------------------------------------------------------------
1 | package v2ray_sspanel_v3_mod_Uim_plugin
2 |
3 | import (
4 | "fmt"
5 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/Manager"
6 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/client"
7 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/config"
8 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/model"
9 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/speedtest"
10 | "github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/webapi"
11 | "github.com/robfig/cron"
12 | "google.golang.org/grpc"
13 | "reflect"
14 | "runtime"
15 | )
16 |
17 | type Panel struct {
18 | db *webapi.Webapi
19 | manager *Manager.Manager
20 | speedtestClient speedtest.Client
21 | downwithpanel int
22 | }
23 |
24 | func NewPanel(gRPCConn *grpc.ClientConn, db *webapi.Webapi, 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 | UserRuleServiceClient: client.NewUserRuleServerClient(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("@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(err).AtWarning().WriteToLog()
116 | if p.downwithpanel == 1 {
117 | p.initial()
118 | }
119 | return false
120 | }
121 | if newNodeinfo.Ret != 1 {
122 | newError(newNodeinfo.Data).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 |
--------------------------------------------------------------------------------
/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 | *)
89 | # unknown option
90 | ;;
91 | esac
92 | shift # past argument or value
93 | done
94 |
95 | ###############################
96 | colorEcho(){
97 | COLOR=$1
98 | echo -e "\033[${COLOR}${@:2}\033[0m"
99 | }
100 |
101 | sysArch(){
102 | ARCH=$(uname -m)
103 | if [[ "$ARCH" == "i686" ]] || [[ "$ARCH" == "i386" ]]; then
104 | VDIS="32"
105 | elif [[ "$ARCH" == *"armv7"* ]] || [[ "$ARCH" == "armv6l" ]]; then
106 | VDIS="arm"
107 | elif [[ "$ARCH" == *"armv8"* ]] || [[ "$ARCH" == "aarch64" ]]; then
108 | VDIS="arm64"
109 | elif [[ "$ARCH" == *"mips64le"* ]]; then
110 | VDIS="mips64le"
111 | elif [[ "$ARCH" == *"mips64"* ]]; then
112 | VDIS="mips64"
113 | elif [[ "$ARCH" == *"mipsle"* ]]; then
114 | VDIS="mipsle"
115 | elif [[ "$ARCH" == *"mips"* ]]; then
116 | VDIS="mips"
117 | elif [[ "$ARCH" == *"s390x"* ]]; then
118 | VDIS="s390x"
119 | elif [[ "$ARCH" == "ppc64le" ]]; then
120 | VDIS="ppc64le"
121 | elif [[ "$ARCH" == "ppc64" ]]; then
122 | VDIS="ppc64"
123 | fi
124 | return 0
125 | }
126 |
127 | downloadV2Ray(){
128 | rm -rf /tmp/v2ray
129 | mkdir -p /tmp/v2ray
130 | colorEcho ${BLUE} "Downloading V2Ray."
131 | DOWNLOAD_LINK="https://github.com/rico93/v2ray-sspanel-v3-mod_Uim-plugin/releases/download/${NEW_VER}/v2ray-linux-${VDIS}.zip"
132 | curl ${PROXY} -L -H "Cache-Control: no-cache" -o ${ZIPFILE} ${DOWNLOAD_LINK}
133 | if [ $? != 0 ];then
134 | colorEcho ${RED} "Failed to download! Please check your network or try again."
135 | return 3
136 | fi
137 | return 0
138 | }
139 |
140 | installSoftware(){
141 | COMPONENT=$1
142 | if [[ -n `command -v $COMPONENT` ]]; then
143 | return 0
144 | fi
145 |
146 | getPMT
147 | if [[ $? -eq 1 ]]; then
148 | colorEcho ${RED} "The system package manager tool isn't APT or YUM, please install ${COMPONENT} manually."
149 | return 1
150 | fi
151 | if [[ $SOFTWARE_UPDATED -eq 0 ]]; then
152 | colorEcho ${BLUE} "Updating software repo"
153 | $CMD_UPDATE
154 | SOFTWARE_UPDATED=1
155 | fi
156 |
157 | colorEcho ${BLUE} "Installing ${COMPONENT}"
158 | $CMD_INSTALL $COMPONENT
159 | if [[ $? -ne 0 ]]; then
160 | colorEcho ${RED} "Failed to install ${COMPONENT}. Please install it manually."
161 | return 1
162 | fi
163 | return 0
164 | }
165 |
166 | # return 1: not apt, yum, or zypper
167 | getPMT(){
168 | if [[ -n `command -v apt-get` ]];then
169 | CMD_INSTALL="apt-get -y -qq install"
170 | CMD_UPDATE="apt-get -qq update"
171 | elif [[ -n `command -v yum` ]]; then
172 | CMD_INSTALL="yum -y -q install"
173 | CMD_UPDATE="yum -q makecache"
174 | elif [[ -n `command -v zypper` ]]; then
175 | CMD_INSTALL="zypper -y install"
176 | CMD_UPDATE="zypper ref"
177 | else
178 | return 1
179 | fi
180 | return 0
181 | }
182 |
183 | extract(){
184 | colorEcho ${BLUE}"Extracting V2Ray package to /tmp/v2ray."
185 | mkdir -p /tmp/v2ray
186 | unzip $1 -d ${VSRC_ROOT}
187 | if [[ $? -ne 0 ]]; then
188 | colorEcho ${RED} "Failed to extract V2Ray."
189 | return 2
190 | fi
191 | if [[ -d "/tmp/v2ray/v2ray-${NEW_VER}-linux-${VDIS}" ]]; then
192 | VSRC_ROOT="/tmp/v2ray/v2ray-${NEW_VER}-linux-${VDIS}"
193 | fi
194 | return 0
195 | }
196 |
197 |
198 | # 1: new V2Ray. 0: no. 2: not installed. 3: check failed. 4: don't check.
199 | getVersion(){
200 | if [[ -n "$VERSION" ]]; then
201 | NEW_VER="$VERSION"
202 | if [[ ${NEW_VER} != v* ]]; then
203 | NEW_VER=v${NEW_VER}
204 | fi
205 | return 4
206 | else
207 | VER=`/usr/bin/v2ray/v2ray -version 2>/dev/null`
208 | RETVAL="$?"
209 | CUR_VER=`echo $VER | head -n 1 | cut -d " " -f2`
210 | if [[ ${CUR_VER} != v* ]]; then
211 | CUR_VER=v${CUR_VER}
212 | fi
213 | TAG_URL="https://api.github.com/repos/rico93/v2ray-sspanel-v3-mod_Uim-plugin/releases/latest"
214 | NEW_VER=`curl ${PROXY} -s ${TAG_URL} --connect-timeout 10| grep 'tag_name' | cut -d\" -f4`
215 | if [[ ${NEW_VER} != v* ]]; then
216 | NEW_VER=v${NEW_VER}
217 | fi
218 | if [[ $? -ne 0 ]] || [[ $NEW_VER == "" ]]; then
219 | colorEcho ${RED} "Failed to fetch release information. Please check your network or try again."
220 | return 3
221 | elif [[ $RETVAL -ne 0 ]];then
222 | return 2
223 | elif [[ `echo $NEW_VER | cut -d. -f-2` != `echo $CUR_VER | cut -d. -f-2` ]];then
224 | return 1
225 | fi
226 | return 0
227 | fi
228 | }
229 |
230 | stopV2ray(){
231 | colorEcho ${BLUE} "Shutting down V2Ray service."
232 | if [[ -n "${SYSTEMCTL_CMD}" ]] || [[ -f "/lib/systemd/system/v2ray.service" ]] || [[ -f "/etc/systemd/system/v2ray.service" ]]; then
233 | ${SYSTEMCTL_CMD} stop v2ray
234 | elif [[ -n "${SERVICE_CMD}" ]] || [[ -f "/etc/init.d/v2ray" ]]; then
235 | ${SERVICE_CMD} v2ray stop
236 | fi
237 | if [[ $? -ne 0 ]]; then
238 | colorEcho ${YELLOW} "Failed to shutdown V2Ray service."
239 | return 2
240 | fi
241 | return 0
242 | }
243 |
244 | startV2ray(){
245 | if [ -n "${SYSTEMCTL_CMD}" ] && [ -f "/lib/systemd/system/v2ray.service" ]; then
246 | ${SYSTEMCTL_CMD} start v2ray
247 | elif [ -n "${SYSTEMCTL_CMD}" ] && [ -f "/etc/systemd/system/v2ray.service" ]; then
248 | ${SYSTEMCTL_CMD} start v2ray
249 | elif [ -n "${SERVICE_CMD}" ] && [ -f "/etc/init.d/v2ray" ]; then
250 | ${SERVICE_CMD} v2ray start
251 | fi
252 | if [[ $? -ne 0 ]]; then
253 | colorEcho ${YELLOW} "Failed to start V2Ray service."
254 | return 2
255 | fi
256 | return 0
257 | }
258 |
259 | copyFile() {
260 | NAME=$1
261 | ERROR=`cp "${VSRC_ROOT}/${NAME}" "/usr/bin/v2ray/${NAME}" 2>&1`
262 | if [[ $? -ne 0 ]]; then
263 | colorEcho ${YELLOW} "${ERROR}"
264 | return 1
265 | fi
266 | return 0
267 | }
268 |
269 | makeExecutable() {
270 | chmod +x "/usr/bin/v2ray/$1"
271 | }
272 |
273 | installV2Ray(){
274 | # Install V2Ray binary to /usr/bin/v2ray
275 | mkdir -p /usr/bin/v2ray
276 | copyFile v2ray
277 | if [[ $? -ne 0 ]]; then
278 | colorEcho ${RED} "Failed to copy V2Ray binary and resources."
279 | return 1
280 | fi
281 | makeExecutable v2ray
282 | copyFile v2ctl && makeExecutable v2ctl
283 | copyFile geoip.dat
284 | copyFile geosite.dat
285 | colorEcho ${BLUE} "Setting TimeZone to Shanghai"
286 | ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
287 | date -s "$(curl -sI g.cn | grep Date | cut -d' ' -f3-6)Z"
288 |
289 | # Install V2Ray server config to /etc/v2ray
290 | if [[ ! -f "/etc/v2ray/config.json" ]]; then
291 | mkdir -p /etc/v2ray
292 | mkdir -p /var/log/v2ray
293 | cp "${VSRC_ROOT}/vpoint_vmess_freedom.json" "/etc/v2ray/config.json"
294 | if [[ $? -ne 0 ]]; then
295 | colorEcho ${YELLOW} "Failed to create V2Ray configuration file. Please create it manually."
296 | return 1
297 | fi
298 |
299 | sed -i "s|"https://google.com"|"${PANELURL}"|g" "/etc/v2ray/config.json"
300 | sed -i "s/"55fUxDGFzH3n"/"${PANELKEY}"/g" "/etc/v2ray/config.json"
301 | sed -i "s/123456,/${NODEID},/g" "/etc/v2ray/config.json"
302 |
303 | colorEcho ${BLUE} "PANELURL:${PANELURL}"
304 | colorEcho ${BLUE} "PANELKEY:${PANELKEY}"
305 | colorEcho ${BLUE} "NODEID:${NODEID}"
306 | fi
307 | return 0
308 | }
309 |
310 |
311 | installInitScript(){
312 | if [[ -n "${SYSTEMCTL_CMD}" ]];then
313 | if [[ ! -f "/etc/systemd/system/v2ray.service" ]]; then
314 | if [[ ! -f "/lib/systemd/system/v2ray.service" ]]; then
315 | cp "${VSRC_ROOT}/systemd/v2ray.service" "/etc/systemd/system/"
316 | systemctl enable v2ray.service
317 | fi
318 | fi
319 | return
320 | elif [[ -n "${SERVICE_CMD}" ]] && [[ ! -f "/etc/init.d/v2ray" ]]; then
321 | installSoftware "daemon" || return $?
322 | cp "${VSRC_ROOT}/systemv/v2ray" "/etc/init.d/v2ray"
323 | chmod +x "/etc/init.d/v2ray"
324 | update-rc.d v2ray defaults
325 | fi
326 | return
327 | }
328 |
329 | Help(){
330 | echo "./install-release.sh [-h] [-c] [--remove] [-p proxy] [-f] [--version vx.y.z] [-l file]"
331 | echo " -h, --help Show help"
332 | 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"
333 | echo " -f, --force Force install"
334 | echo " --version Install a particular version, use --version v3.15"
335 | echo " -l, --local Install from a local file"
336 | echo " --remove Remove installed V2Ray"
337 | echo " -c, --check Check for update"
338 | return 0
339 | }
340 |
341 | remove(){
342 | if [[ -n "${SYSTEMCTL_CMD}" ]] && [[ -f "/etc/systemd/system/v2ray.service" ]];then
343 | if pgrep "v2ray" > /dev/null ; then
344 | stopV2ray
345 | fi
346 | systemctl disable v2ray.service
347 | rm -rf "/usr/bin/v2ray" "/etc/systemd/system/v2ray.service"
348 | if [[ $? -ne 0 ]]; then
349 | colorEcho ${RED} "Failed to remove V2Ray."
350 | return 0
351 | else
352 | colorEcho ${GREEN} "Removed V2Ray successfully."
353 | colorEcho ${BLUE} "If necessary, please remove configuration file and log file manually."
354 | return 0
355 | fi
356 | elif [[ -n "${SYSTEMCTL_CMD}" ]] && [[ -f "/lib/systemd/system/v2ray.service" ]];then
357 | if pgrep "v2ray" > /dev/null ; then
358 | stopV2ray
359 | fi
360 | systemctl disable v2ray.service
361 | rm -rf "/usr/bin/v2ray" "/lib/systemd/system/v2ray.service"
362 | if [[ $? -ne 0 ]]; then
363 | colorEcho ${RED} "Failed to remove V2Ray."
364 | return 0
365 | else
366 | colorEcho ${GREEN} "Removed V2Ray successfully."
367 | colorEcho ${BLUE} "If necessary, please remove configuration file and log file manually."
368 | return 0
369 | fi
370 | elif [[ -n "${SERVICE_CMD}" ]] && [[ -f "/etc/init.d/v2ray" ]]; then
371 | if pgrep "v2ray" > /dev/null ; then
372 | stopV2ray
373 | fi
374 | rm -rf "/usr/bin/v2ray" "/etc/init.d/v2ray"
375 | if [[ $? -ne 0 ]]; then
376 | colorEcho ${RED} "Failed to remove V2Ray."
377 | return 0
378 | else
379 | colorEcho ${GREEN} "Removed V2Ray successfully."
380 | colorEcho ${BLUE} "If necessary, please remove configuration file and log file manually."
381 | return 0
382 | fi
383 | else
384 | colorEcho ${YELLOW} "V2Ray not found."
385 | return 0
386 | fi
387 | }
388 |
389 | checkUpdate(){
390 | echo "Checking for update."
391 | VERSION=""
392 | getVersion
393 | RETVAL="$?"
394 | if [[ $RETVAL -eq 1 ]]; then
395 | colorEcho ${BLUE} "Found new version ${NEW_VER} for V2Ray.(Current version:$CUR_VER)"
396 | elif [[ $RETVAL -eq 0 ]]; then
397 | colorEcho ${BLUE} "No new version. Current version is ${NEW_VER}."
398 | elif [[ $RETVAL -eq 2 ]]; then
399 | colorEcho ${YELLOW} "No V2Ray installed."
400 | colorEcho ${BLUE} "The newest version for V2Ray is ${NEW_VER}."
401 | fi
402 | return 0
403 | }
404 |
405 | main(){
406 | #helping information
407 | [[ "$HELP" == "1" ]] && Help && return
408 | [[ "$CHECK" == "1" ]] && checkUpdate && return
409 | [[ "$REMOVE" == "1" ]] && remove && return
410 |
411 | sysArch
412 | # extract local file
413 | if [[ $LOCAL_INSTALL -eq 1 ]]; then
414 | 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."
415 | NEW_VER=local
416 | installSoftware unzip || return $?
417 | rm -rf /tmp/v2ray
418 | extract $LOCAL || return $?
419 | #FILEVDIS=`ls /tmp/v2ray |grep v2ray-v |cut -d "-" -f4`
420 | #SYSTEM=`ls /tmp/v2ray |grep v2ray-v |cut -d "-" -f3`
421 | #if [[ ${SYSTEM} != "linux" ]]; then
422 | # colorEcho ${RED} "The local V2Ray can not be installed in linux."
423 | # return 1
424 | #elif [[ ${FILEVDIS} != ${VDIS} ]]; then
425 | # colorEcho ${RED} "The local V2Ray can not be installed in ${ARCH} system."
426 | # return 1
427 | #else
428 | # NEW_VER=`ls /tmp/v2ray |grep v2ray-v |cut -d "-" -f2`
429 | #fi
430 | else
431 | # download via network and extract
432 | installSoftware "curl" || return $?
433 | getVersion
434 | RETVAL="$?"
435 | if [[ $RETVAL == 0 ]] && [[ "$FORCE" != "1" ]]; then
436 | colorEcho ${BLUE} "Latest version ${NEW_VER} is already installed."
437 | if [[ "${ERROR_IF_UPTODATE}" == "1" ]]; then
438 | return 10
439 | fi
440 | return
441 | elif [[ $RETVAL == 3 ]]; then
442 | return 3
443 | else
444 | colorEcho ${BLUE} "Installing V2Ray ${NEW_VER} on ${ARCH}"
445 | downloadV2Ray || return $?
446 | installSoftware unzip || return $?
447 | extract ${ZIPFILE} || return $?
448 | fi
449 | fi
450 |
451 | if [[ "${EXTRACT_ONLY}" == "1" ]]; then
452 | colorEcho ${GREEN} "V2Ray extracted to ${VSRC_ROOT}, and exiting..."
453 | return 0
454 | fi
455 |
456 | if pgrep "v2ray" > /dev/null ; then
457 | V2RAY_RUNNING=1
458 | stopV2ray
459 | fi
460 | installV2Ray || return $?
461 | installInitScript || return $?
462 | if [[ ${V2RAY_RUNNING} -eq 1 ]];then
463 | colorEcho ${BLUE} "Restarting V2Ray service."
464 | startV2ray
465 | fi
466 | colorEcho ${GREEN} "V2Ray ${NEW_VER} is installed."
467 | rm -rf /tmp/v2ray
468 | return 0
469 | }
470 |
471 | main
472 |
--------------------------------------------------------------------------------
/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 | # Set ssrpanel_url
112 | echo "Please ssrpanel_url"
113 | read -p "(There is no default value please make sure you input the right thing):" ssrpanel_url
114 | [ -z "${ssrpanel_url}" ]
115 | echo
116 | echo "---------------------------"
117 | echo "ssrpanel_url = ${ssrpanel_url}"
118 | echo "---------------------------"
119 | echo
120 | # Set ssrpanel key
121 | echo "ssrpanel key"
122 | read -p "(There is no default value please make sure you input the right thing):" ssrpanel_key
123 | [ -z "${ssrpanel_key}" ]
124 | echo
125 | echo "---------------------------"
126 | echo "ssrpanel_key = ${ssrpanel_key}"
127 | echo "---------------------------"
128 | echo
129 |
130 | # Set ssrpanel speedtest function
131 | echo "use ssrpanel speedtest"
132 | read -p "(ssrpanel speedtest: Default (6) hours every time):" ssrpanel_speedtest
133 | [ -z "${ssrpanel_speedtest}" ] && ssrpanel_speedtest=6
134 | echo
135 | echo "---------------------------"
136 | echo "ssrpanel_speedtest = ${ssrpanel_speedtest}"
137 | echo "---------------------------"
138 | echo
139 |
140 | # Set ssrpanel node_id
141 | echo "ssrpanel node_id"
142 | read -p "(Default value: 0 ):" ssrpanel_node_id
143 | [ -z "${ssrpanel_node_id}" ] && ssrpanel_node_id=0
144 | echo
145 | echo "---------------------------"
146 | echo "ssrpanel_node_id = ${ssrpanel_node_id}"
147 | echo "---------------------------"
148 | echo
149 |
150 | # Set V2ray backend API Listen port
151 | echo "Setting V2ray backend API Listen port"
152 | read -p "(V2ray API Listen port(Default 2333):" v2ray_api_port
153 | [ -z "${v2ray_api_port}" ] && v2ray_api_port=2333
154 | echo
155 | echo "---------------------------"
156 | echo "V2ray API Listen port = ${v2ray_api_port}"
157 | echo "---------------------------"
158 | echo
159 |
160 | # Set Setting if the node go downwith panel
161 | echo "Setting if the node go downwith panel"
162 | read -p "(v2ray_downWithPanel (Default 1):" v2ray_downWithPanel
163 | [ -z "${v2ray_downWithPanel}" ] && v2ray_downWithPanel=1
164 | echo
165 | echo "---------------------------"
166 | echo "v2ray_downWithPanel = ${v2ray_downWithPanel}"
167 | echo "---------------------------"
168 | echo
169 | }
170 |
171 | pre_install_caddy(){
172 |
173 | # Set caddy v2ray domain
174 | echo "caddy v2ray domain"
175 | read -p "(There is no default value please make sure you input the right thing):" v2ray_domain
176 | [ -z "${v2ray_domain}" ]
177 | echo
178 | echo "---------------------------"
179 | echo "v2ray_domain = ${v2ray_domain}"
180 | echo "---------------------------"
181 | echo
182 |
183 |
184 | # Set caddy v2ray path
185 | echo "caddy v2ray path"
186 | read -p "(Default path: /v2ray):" v2ray_path
187 | [ -z "${v2ray_path}" ] && v2ray_path="/v2ray"
188 | echo
189 | echo "---------------------------"
190 | echo "v2ray_path = ${v2ray_path}"
191 | echo "---------------------------"
192 | echo
193 |
194 | # Set caddy v2ray tls email
195 | echo "caddy v2ray tls email"
196 | read -p "(No default ):" v2ray_email
197 | [ -z "${v2ray_email}" ]
198 | echo
199 | echo "---------------------------"
200 | echo "v2ray_email = ${v2ray_email}"
201 | echo "---------------------------"
202 | echo
203 |
204 | # Set Caddy v2ray listen port
205 | echo "caddy v2ray local listen port"
206 | read -p "(Default port: 10550):" v2ray_local_port
207 | [ -z "${v2ray_local_port}" ] && v2ray_local_port=10550
208 | echo
209 | echo "---------------------------"
210 | echo "v2ray_local_port = ${v2ray_local_port}"
211 | echo "---------------------------"
212 | echo
213 |
214 | # Set Caddy listen port
215 | echo "caddy listen port"
216 | read -p "(Default port: 443):" caddy_listen_port
217 | [ -z "${caddy_listen_port}" ] && caddy_listen_port=443
218 | echo
219 | echo "---------------------------"
220 | echo "caddy_listen_port = ${caddy_listen_port}"
221 | echo "---------------------------"
222 | echo
223 |
224 |
225 | }
226 |
227 | # Config docker
228 | config_docker(){
229 | echo "Press any key to start...or Press Ctrl+C to cancel"
230 | char=`get_char`
231 | cd ${cur_dir}
232 | echo "install curl"
233 | install_dependencies
234 | echo "Writing docker-compose.yml"
235 | curl -L https://raw.githubusercontent.com/haig233/v2ray-sspanel-v3-mod_Uim-plugin/master/Docker/V2ray/docker-compose.yml > docker-compose.yml
236 | sed -i "s|node_id:.*|node_id: ${ssrpanel_node_id}|" ./docker-compose.yml
237 | sed -i "s|sspanel_url:.*|sspanel_url: '${ssrpanel_url}'|" ./docker-compose.yml
238 | sed -i "s|key:.*|key: '${ssrpanel_key}'|" ./docker-compose.yml
239 | sed -i "s|speedtest:.*|speedtest: ${ssrpanel_speedtest}|" ./docker-compose.yml
240 | sed -i "s|api_port:.*|api_port: ${v2ray_api_port}|" ./docker-compose.yml
241 | sed -i "s|downWithPanel:.*|downWithPanel: ${v2ray_downWithPanel}|" ./docker-compose.yml
242 | }
243 |
244 |
245 | # Config caddy_docker
246 | config_caddy_docker(){
247 | echo "Press any key to start...or Press Ctrl+C to cancel"
248 | char=`get_char`
249 | cd ${cur_dir}
250 | echo "install curl"
251 | install_dependencies
252 | curl -L https://raw.githubusercontent.com/haig233/v2ray-sspanel-v3-mod_Uim-plugin/master/Docker/Caddy_V2ray/Caddyfile > Caddyfile
253 | echo "Writing docker-compose.yml"
254 | curl -L https://raw.githubusercontent.com/haig233/v2ray-sspanel-v3-mod_Uim-plugin/master/Docker/Caddy_V2ray/docker-compose.yml > docker-compose.yml
255 | sed -i "s|node_id:.*|node_id: ${ssrpanel_node_id}|" ./docker-compose.yml
256 | sed -i "s|sspanel_url:.*|sspanel_url: '${ssrpanel_url}'|" ./docker-compose.yml
257 | sed -i "s|key:.*|key: '${ssrpanel_key}'|" ./docker-compose.yml
258 | sed -i "s|speedtest:.*|speedtest: ${ssrpanel_speedtest}|" ./docker-compose.yml
259 | sed -i "s|api_port:.*|api_port: ${v2ray_api_port}|" ./docker-compose.yml
260 | sed -i "s|downWithPanel:.*|downWithPanel: ${v2ray_downWithPanel}|" ./docker-compose.yml
261 | sed -i "s|V2RAY_DOMAIN=xxxx.com|V2RAY_DOMAIN=${v2ray_domain}|" ./docker-compose.yml
262 | sed -i "s|V2RAY_PATH=/v2ray|V2RAY_PATH=${v2ray_path}|" ./docker-compose.yml
263 | sed -i "s|V2RAY_EMAIL=xxxx@outlook.com|V2RAY_EMAIL=${v2ray_email}|" ./docker-compose.yml
264 | sed -i "s|V2RAY_PORT=10550|V2RAY_PORT=${v2ray_local_port}|" ./docker-compose.yml
265 | sed -i "s|V2RAY_OUTSIDE_PORT=443|V2RAY_OUTSIDE_PORT=${caddy_listen_port}|" ./docker-compose.yml
266 | }
267 |
268 | # Config caddy_docker
269 | config_caddy_docker_cloudflare(){
270 |
271 | # Set caddy cloudflare ddns email
272 | echo "caddy cloudflare ddns email"
273 | read -p "(No default ):" cloudflare_email
274 | [ -z "${cloudflare_email}" ]
275 | echo
276 | echo "---------------------------"
277 | echo "cloudflare_email = ${cloudflare_email}"
278 | echo "---------------------------"
279 | echo
280 |
281 | # Set caddy cloudflare ddns key
282 | echo "caddy cloudflare ddns key"
283 | read -p "(No default ):" cloudflare_key
284 | [ -z "${cloudflare_email}" ]
285 | echo
286 | echo "---------------------------"
287 | echo "cloudflare_email = ${cloudflare_key}"
288 | echo "---------------------------"
289 | echo
290 | echo
291 |
292 | echo "Press any key to start...or Press Ctrl+C to cancel"
293 | char=`get_char`
294 | cd ${cur_dir}
295 | echo "install curl first "
296 | install_dependencies
297 | echo "Starting Writing Caddy file and docker-compose.yml"
298 | curl -L https://raw.githubusercontent.com/haig233/v2ray-sspanel-v3-mod_Uim-plugin/master/Docker/Caddy_V2ray/Caddyfile >Caddyfile
299 | epcho "Writing docker-compose.yml"
300 | curl -L https://raw.githubusercontent.com/haig233/v2ray-sspanel-v3-mod_Uim-plugin/master/Docker/Caddy_V2ray/docker-compose.yml >docker-compose.yml
301 | sed -i "s|node_id:.*|node_id: ${ssrpanel_node_id}|" ./docker-compose.yml
302 | sed -i "s|sspanel_url:.*|sspanel_url: '${ssrpanel_url}'|" ./docker-compose.yml
303 | sed -i "s|key:.*|key: '${ssrpanel_key}'|" ./docker-compose.yml
304 | sed -i "s|speedtest:.*|speedtest: ${ssrpanel_speedtest}|" ./docker-compose.yml
305 | sed -i "s|api_port:.*|api_port: ${v2ray_api_port}|" ./docker-compose.yml
306 | sed -i "s|downWithPanel:.*|downWithPanel: ${v2ray_downWithPanel}|" ./docker-compose.yml
307 | sed -i "s|V2RAY_DOMAIN=xxxx.com|V2RAY_DOMAIN=${v2ray_domain}|" ./docker-compose.yml
308 | sed -i "s|V2RAY_PATH=/v2ray|V2RAY_PATH=${v2ray_path}|" ./docker-compose.yml
309 | sed -i "s|V2RAY_EMAIL=xxxx@outlook.com|V2RAY_EMAIL=${v2ray_email}|" ./docker-compose.yml
310 | sed -i "s|V2RAY_PORT=10550|V2RAY_PORT=${v2ray_local_port}|" ./docker-compose.yml
311 | sed -i "s|V2RAY_OUTSIDE_PORT=443|V2RAY_OUTSIDE_PORT=${caddy_listen_port}|" ./docker-compose.yml
312 | sed -i "s|# - CLOUDFLARE_EMAIL=xxxxxx@out.look.com| - CLOUDFLARE_EMAIL=${cloudflare_email}|" ./docker-compose.yml
313 | sed -i "s|# - CLOUDFLARE_API_KEY=xxxxxxx| - CLOUDFLARE_API_KEY=${cloudflare_key}|" ./docker-compose.yml
314 | sed -i "s|# dns cloudflare|dns cloudflare|" ./Caddyfile
315 |
316 | }
317 |
318 | # Install docker and docker compose
319 | install_docker(){
320 | echo -e "Starting installing Docker "
321 | curl -fsSL https://get.docker.com -o get-docker.sh
322 | bash get-docker.sh
323 | echo -e "Starting installing Docker Compose "
324 | curl -L https://github.com/docker/compose/releases/download/1.17.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
325 | chmod +x /usr/local/bin/docker-compose
326 | curl -L https://raw.githubusercontent.com/docker/compose/1.8.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
327 | clear
328 | echo "Start Docker "
329 | service docker start
330 | echo "Start Docker-Compose "
331 | docker-compose up -d
332 | echo
333 | echo -e "Congratulations, V2ray server install completed!"
334 | echo
335 | echo "Enjoy it!"
336 | echo
337 | }
338 |
339 | install_check(){
340 | if check_sys packageManager yum || check_sys packageManager apt; then
341 | if centosversion 5; then
342 | return 1
343 | fi
344 | return 0
345 | else
346 | return 1
347 | fi
348 | }
349 |
350 | install_select(){
351 | clear
352 | while true
353 | do
354 | echo "Which v2ray Docker you'd select:"
355 | for ((i=1;i<=${#software[@]};i++ )); do
356 | hint="${software[$i-1]}"
357 | echo -e "${green}${i}${plain}) ${hint}"
358 | done
359 | read -p "Please enter a number (Default ${software[0]}):" selected
360 | [ -z "${selected}" ] && selected="1"
361 | case "${selected}" in
362 | 1|2|3|4)
363 | echo
364 | echo "You choose = ${software[${selected}-1]}"
365 | echo
366 | break
367 | ;;
368 | *)
369 | echo -e "[${red}Error${plain}] Please only enter a number [1-4]"
370 | ;;
371 | esac
372 | done
373 | }
374 | install_dependencies(){
375 | if check_sys packageManager yum; then
376 | echo -e "[${green}Info${plain}] Checking the EPEL repository..."
377 | if [ ! -f /etc/yum.repos.d/epel.repo ]; then
378 | yum install -y epel-release > /dev/null 2>&1
379 | fi
380 | [ ! -f /etc/yum.repos.d/epel.repo ] && echo -e "[${red}Error${plain}] Install EPEL repository failed, please check it." && exit 1
381 | [ ! "$(command -v yum-config-manager)" ] && yum install -y yum-utils > /dev/null 2>&1
382 | [ x"$(yum-config-manager epel | grep -w enabled | awk '{print $3}')" != x"True" ] && yum-config-manager --enable epel > /dev/null 2>&1
383 | echo -e "[${green}Info${plain}] Checking the EPEL repository complete..."
384 |
385 | yum_depends=(
386 | curl
387 | )
388 | for depend in ${yum_depends[@]}; do
389 | error_detect_depends "yum -y install ${depend}"
390 | done
391 | elif check_sys packageManager apt; then
392 | apt_depends=(
393 | curl
394 | )
395 | apt-get -y update
396 | for depend in ${apt_depends[@]}; do
397 | error_detect_depends "apt-get -y install ${depend}"
398 | done
399 | fi
400 | echo -e "[${green}Info${plain}] Setting TimeZone to Shanghai"
401 | ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
402 | date -s "$(curl -sI g.cn | grep Date | cut -d' ' -f3-6)Z"
403 |
404 | }
405 | #update_image
406 | update_image_v2ray(){
407 | echo "Shut down the current service"
408 | docker-compose down
409 | echo "Pulling Images"
410 | docker-compose pull
411 | echo "Start Service"
412 | docker-compose up -d
413 | }
414 |
415 | #show last 100 line log
416 |
417 | logs_v2ray(){
418 | echo "Last 100 line logs"
419 | docker-compose logs --tail 100
420 | }
421 |
422 | # Update config
423 | update_config_v2ray(){
424 | cd ${cur_dir}
425 | echo "Shut down the current service"
426 | docker-compose down
427 | install_select
428 | case "${selected}" in
429 | 1)
430 | pre_install_docker_compose
431 | pre_install_caddy
432 | config_caddy_docker
433 | ;;
434 | 2)
435 | pre_install_docker_compose
436 | pre_install_caddy
437 | config_caddy_docker_cloudflare
438 | ;;
439 | 3)
440 | pre_install_docker_compose
441 | config_docker
442 | ;;
443 | *)
444 | echo "Wrong number"
445 | ;;
446 | esac
447 |
448 | echo "Start Service"
449 | docker-compose up -d
450 |
451 | }
452 | # remove config
453 | # Install v2ray
454 | install_v2ray(){
455 | install_select
456 | case "${selected}" in
457 | 1)
458 | pre_install_docker_compose
459 | pre_install_caddy
460 | config_caddy_docker
461 | ;;
462 | 2)
463 | pre_install_docker_compose
464 | pre_install_caddy
465 | config_caddy_docker_cloudflare
466 | ;;
467 | 3)
468 | pre_install_docker_compose
469 | config_docker
470 | ;;
471 | *)
472 | echo "Wrong number"
473 | ;;
474 | esac
475 | install_docker
476 | }
477 |
478 | # Initialization step
479 | clear
480 | while true
481 | do
482 | echo "Which operation you'd select:"
483 | for ((i=1;i<=${#operation[@]};i++ )); do
484 | hint="${operation[$i-1]}"
485 | echo -e "${green}${i}${plain}) ${hint}"
486 | done
487 | read -p "Please enter a number (Default ${operation[0]}):" selected
488 | [ -z "${selected}" ] && selected="1"
489 | case "${selected}" in
490 | 1|2|3|4)
491 | echo
492 | echo "You choose = ${operation[${selected}-1]}"
493 | echo
494 | ${operation[${selected}-1]}_v2ray
495 | break
496 | ;;
497 | *)
498 | echo -e "[${red}Error${plain}] Please only enter a number [1-4]"
499 | ;;
500 | esac
501 | done
502 |
--------------------------------------------------------------------------------