├── 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 | --------------------------------------------------------------------------------