diff --git a/.gitignore b/.gitignore index 48531dfa93..224445583b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea tmp -bin/xray-darwin-arm64 \ No newline at end of file +bin/xray-darwin-arm64 +bin/config.json \ No newline at end of file diff --git a/bin/geoip.dat b/bin/geoip.dat old mode 100644 new mode 100755 diff --git a/bin/geosite.dat b/bin/geosite.dat old mode 100644 new mode 100755 diff --git a/bin/xray-linux-arm64 b/bin/xray-linux-arm64 new file mode 100755 index 0000000000..05cd6eecb3 Binary files /dev/null and b/bin/xray-linux-arm64 differ diff --git a/config/config.go b/config/config.go index 668d6ebd3e..50f67ac84a 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,9 @@ package config -import "fmt" +import ( + "fmt" + "os" +) type LogLevel string @@ -11,10 +14,6 @@ const ( Error LogLevel = "error" ) -func init() { - -} - func GetVersion() string { return "0.0.1" } @@ -23,34 +22,21 @@ func GetName() string { return "x-ui" } -func GetListen() string { - return ":27827" -} - -func GetCertFile() string { - return "" -} - -func GetKeyFile() string { - return "" -} - func GetLogLevel() LogLevel { - return Debug + if IsDebug() { + return Debug + } + logLevel := os.Getenv("XUI_LOG_LEVEL") + if logLevel == "" { + return Info + } + return LogLevel(logLevel) } func IsDebug() bool { - return true -} - -func GetSecret() []byte { - return []byte("") + return os.Getenv("XUI_DEBUG") == "true" } func GetDBPath() string { return fmt.Sprintf("/etc/%s/%s.db", GetName(), GetName()) } - -func GetBasePath() string { - return "/" -} diff --git a/database/db.go b/database/db.go index 715ae71b76..df0e71c607 100644 --- a/database/db.go +++ b/database/db.go @@ -1,13 +1,15 @@ package database import ( + "gorm.io/driver/sqlite" "gorm.io/gorm" + "gorm.io/gorm/logger" "io/fs" "os" "path" + "x-ui/config" "x-ui/database/model" ) -import "gorm.io/driver/sqlite" var db *gorm.DB @@ -35,6 +37,10 @@ func initInbound() error { return db.AutoMigrate(&model.Inbound{}) } +func initSetting() error { + return db.AutoMigrate(&model.Setting{}) +} + func InitDB(dbPath string) error { dir := path.Dir(dbPath) err := os.MkdirAll(dir, fs.ModeDir) @@ -42,7 +48,17 @@ func InitDB(dbPath string) error { return err } - c := &gorm.Config{} + var gormLogger logger.Interface + + if config.IsDebug() { + gormLogger = logger.Discard + } else { + gormLogger = logger.Default + } + + c := &gorm.Config{ + Logger: gormLogger, + } db, err = gorm.Open(sqlite.Open(dbPath), c) if err != nil { return err @@ -56,6 +72,10 @@ func InitDB(dbPath string) error { if err != nil { return err } + err = initSetting() + if err != nil { + return err + } return nil } @@ -63,3 +83,7 @@ func InitDB(dbPath string) error { func GetDB() *gorm.DB { return db } + +func IsNotFound(err error) bool { + return err == gorm.ErrRecordNotFound +} diff --git a/database/model/model.go b/database/model/model.go index 82e781d47a..4625d6661d 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -1,6 +1,9 @@ package model -import "time" +import ( + "encoding/json" + "x-ui/xray" +) type Protocol string @@ -20,20 +23,38 @@ type User struct { } type Inbound struct { - Id int `json:"id" gorm:"primaryKey;autoIncrement"` - UserId int `json:"user_id"` - Up int64 `json:"up"` - Down int64 `json:"down"` - Remark string `json:"remark"` - Enable bool `json:"enable"` - ExpiryTime time.Time `json:"expiry_time"` + Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` + UserId int `json:"user_id" form:"user_id"` + Up int64 `json:"up" form:"up"` + Down int64 `json:"down" form:"down"` + Remark string `json:"remark" form:"remark"` + Enable bool `json:"enable" form:"enable"` + ExpiryTime int64 `json:"expiry_time" form:"expiry_time"` // config part - Listen string `json:"listen"` - Port int `json:"port"` - Protocol Protocol `json:"protocol"` - Settings string `json:"settings"` - StreamSettings string `json:"stream_settings"` - Tag string `json:"tag"` - Sniffing string `json:"sniffing"` + Listen string `json:"listen" form:"listen"` + Port int `json:"port" form:"port"` + Protocol Protocol `json:"protocol" form:"protocol"` + Settings string `json:"settings" form:"settings"` + StreamSettings string `json:"stream_settings" form:"stream_settings"` + Tag string `json:"tag" form:"tag"` + Sniffing string `json:"sniffing" form:"sniffing"` +} + +func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig { + return &xray.InboundConfig{ + Listen: i.Listen, + Port: i.Port, + Protocol: string(i.Protocol), + Settings: json.RawMessage(i.Settings), + StreamSettings: json.RawMessage(i.StreamSettings), + Tag: i.Tag, + Sniffing: json.RawMessage(i.Sniffing), + } +} + +type Setting struct { + Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` + Key string `json:"key" form:"key"` + Value string `json:"value" form:"value"` } diff --git a/go.mod b/go.mod index 85b8fbcd91..80d492aa37 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module x-ui go 1.16 require ( - github.com/BurntSushi/toml v0.3.1 // indirect + github.com/BurntSushi/toml v0.3.1 github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect - github.com/fatih/color v1.11.0 // indirect + github.com/Workiva/go-datastructures v1.0.53 github.com/gin-contrib/sessions v0.0.3 github.com/gin-gonic/gin v1.7.1 github.com/go-ole/go-ole v1.2.5 // indirect @@ -14,8 +14,9 @@ require ( github.com/pelletier/go-toml v1.9.1 // indirect github.com/shirou/gopsutil v3.21.3+incompatible github.com/tklauser/go-sysconf v0.3.5 // indirect + github.com/xtls/xray-core v1.4.2 golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 // indirect - golang.org/x/text v0.3.6 // indirect + golang.org/x/text v0.3.6 gopkg.in/yaml.v2 v2.4.0 // indirect gorm.io/driver/sqlite v1.1.4 gorm.io/gorm v1.21.9 diff --git a/go.sum b/go.sum index 5af40ba6bd..59ff44f13a 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,51 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY= github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= +github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI= -github.com/cosmtrek/air v1.27.3 h1:laO93SnYnEiJsH0QIeXyso6FJ5maSNufE5d/MmHKBmk= -github.com/cosmtrek/air v1.27.3/go.mod h1:vrGZm+zmL5htsEr6YjqLXyjSoelgDQIl/DuOtsWVLeU= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.11.0 h1:l4iX0RqNnx/pU7rY2DB/I+znuYY0K3x6Ywac6EIr0PA= -github.com/fatih/color v1.11.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E= +github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= +github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/gin-contrib/sessions v0.0.3 h1:PoBXki+44XdJdlgDqDrY5nDVe3Wk7wDV/UCOuLP6fBI= github.com/gin-contrib/sessions v0.0.3/go.mod h1:8C/J6cad3Il1mWYYgtw0w+hqasmpvy25mPkXdOgeB9I= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -24,7 +53,9 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gin-gonic/gin v1.7.1 h1:qC89GU3p8TvKWMAVhEpmpB2CIb1hnqt2UdKZaP93mS8= github.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= @@ -37,11 +68,47 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= @@ -49,90 +116,306 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/lucas-clemente/quic-go v0.20.0 h1:FSU3YN5VnLafHR27Ejs1r1CYMS7XMyIVDzRewkDLNBw= +github.com/lucas-clemente/quic-go v0.20.0/go.mod h1:fZq/HUDIM+mW6X6wtzORjC0E/WDBMKe5Hf9bgjISwLk= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= +github.com/marten-seemann/qtls-go1-15 v0.1.4 h1:RehYMOyRW8hPVEja1KBVsFVNSm35Jj9Mvs5yNoZZ28A= +github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/marten-seemann/qtls-go1-16 v0.1.3 h1:XEZ1xGorVy9u+lJq+WXNE+hiqRYLNvJGYmwfwKQN2gU= +github.com/marten-seemann/qtls-go1-16 v0.1.3/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ= github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nicksnyder/go-i18n/v2 v2.1.2 h1:QHYxcUJnGHBaq7XbvgunmZ2Pn0focXFqTD61CkH146c= github.com/nicksnyder/go-i18n/v2 v2.1.2/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.1 h1:a6qW1EVNZWH9WGI6CsYdD8WAylkoXBS5yv0XHlh17Tc= github.com/pelletier/go-toml v1.9.1/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pires/go-proxyproto v0.5.0 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo= +github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= +github.com/refraction-networking/utls v0.0.0-20201210053706-2179f286686b h1:lzo71oHzQEz0fKMSjR0BpVzuh2hOHvJTxnN3Rnikmtg= +github.com/refraction-networking/utls v0.0.0-20201210053706-2179f286686b/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c h1:pqy40B3MQWYrza7YZXOXgl0Nf0QGFqrOC0BKae1UNAA= +github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.3+incompatible h1:uenXGGa8ESCQq+dbgtl916dmg6PSAz2cXov0uORQ9v8= github.com/shirou/gopsutil v3.21.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499 h1:QHESTXtfgc1ABV+ArlbPVqUx9Ht5I0dDkYhxYoXFxNo= +github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499/go.mod h1:5TB2+k58gx4A4g2Nf5miSHNDF6CuAzHKpWBooLAshTs= +github.com/xtls/xray-core v1.4.2 h1:D0Le+Qy9L/eY5LbUQfrk7WJ8wbODpQSW/ZRCg+BRe7c= +github.com/xtls/xray-core v1.4.2/go.mod h1:DmL/9rOCliev/a6HciWEvSJVEhUF6C0EpD3clW8v0pc= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.starlark.net v0.0.0-20210312235212-74c10e2c17dc h1:pVkptfeOTFfx+zXZo7HEHN3d5LmhatBFvHdm/f2QnpY= +go.starlark.net v0.0.0-20210312235212-74c10e2c17dc/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210330230544-e57232859fb2 h1:nGCZOty+lVDsc4H2qPFksI5Se296+V+GhMiL/TzmYNk= +golang.org/x/net v0.0.0-20210330230544-e57232859fb2/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa h1:ZYxPR6aca/uhfRJyaOAtflSHjJYiktO7QnJC5ut7iY4= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q= golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.36.1 h1:cmUfbeGKnz9+2DD/UYsMQXeqbHZqZDs4eQwW0sFOpBY= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM= gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw= gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= gorm.io/gorm v1.21.9 h1:INieZtn4P2Pw6xPJ8MzT0G4WUOsHq3RhfuDF1M6GW0E= gorm.io/gorm v1.21.9/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +h12.io/socks v1.0.2/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/main.go b/main.go index b0425e7113..5498b23ccc 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,9 @@ package main import ( "github.com/op/go-logging" "log" + "os" + "os/signal" + "syscall" "x-ui/config" "x-ui/database" "x-ui/logger" @@ -30,9 +33,23 @@ func main() { log.Fatal(err) } - server := web.NewServer() - err = server.Run() - if err != nil { - log.Println(err) + var server *web.Server + + server = web.NewServer() + go server.Run() + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGHUP) + for { + sig := <-sigCh + + if sig == syscall.SIGHUP { + server.Stop() + server = web.NewServer() + go server.Run() + } else { + return + } } + } diff --git a/util/file.go b/util/file.go deleted file mode 100644 index c7d868219f..0000000000 --- a/util/file.go +++ /dev/null @@ -1 +0,0 @@ -package util diff --git a/util/random/random.go b/util/random/random.go new file mode 100644 index 0000000000..b1dd2e09d4 --- /dev/null +++ b/util/random/random.go @@ -0,0 +1,43 @@ +package random + +import ( + "math/rand" + "time" +) + +var numSeq [10]rune +var lowerSeq [26]rune +var upperSeq [26]rune +var numLowerSeq [36]rune +var numUpperSeq [36]rune +var allSeq [62]rune + +func init() { + rand.Seed(time.Now().UnixNano()) + + for i := 0; i < 10; i++ { + numSeq[i] = rune('0' + i) + } + for i := 0; i < 26; i++ { + lowerSeq[i] = rune('a' + i) + upperSeq[i] = rune('A' + i) + } + + copy(numLowerSeq[:], numSeq[:]) + copy(numLowerSeq[len(numSeq):], lowerSeq[:]) + + copy(numUpperSeq[:], numSeq[:]) + copy(numUpperSeq[len(numSeq):], upperSeq[:]) + + copy(allSeq[:], numSeq[:]) + copy(allSeq[len(numSeq):], lowerSeq[:]) + copy(allSeq[len(numSeq)+len(lowerSeq):], upperSeq[:]) +} + +func Seq(n int) string { + runes := make([]rune, n) + for i := 0; i < n; i++ { + runes[i] = allSeq[rand.Intn(len(allSeq))] + } + return string(runes) +} diff --git a/web/assets/js/model/models.js b/web/assets/js/model/models.js index 6d803aec12..1f242f85a2 100644 --- a/web/assets/js/model/models.js +++ b/web/assets/js/model/models.js @@ -19,4 +19,28 @@ class Msg { this.obj = obj; } } +} + +class DBInbound { + id = 0; + userId = 0; + up = 0; + down = 0; + remark = 0; + enable = false; + expiryTime = 0; + listen = ""; + port = 0; + protocol = ""; + settings = ""; + streamSettings = ""; + tag = ""; + sniffing = ""; + + constructor(data) { + if (data == null) { + return; + } + ObjectUtil.cloneProps(this, data); + } } \ No newline at end of file diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js new file mode 100644 index 0000000000..61dbdfde55 --- /dev/null +++ b/web/assets/js/model/xray.js @@ -0,0 +1,1192 @@ +const Protocols = { + VMESS: 'vmess', + VLESS: 'vless', + TROJAN: 'trojan', + SHADOWSOCKS: 'shadowsocks', + DOKODEMO: 'dokodemo-door', + MTPROTO: 'mtproto', + SOCKS: 'socks', + HTTP: 'http', +}; + +const VmessMethods = { + AES_128_GCM: 'aes-128-gcm', + CHACHA20_POLY1305: 'chacha20-poly1305', + AUTO: 'auto', + NONE: 'none', +}; + +const SSMethods = { + // AES_256_CFB: 'aes-256-cfb', + // AES_128_CFB: 'aes-128-cfb', + // CHACHA20: 'chacha20', + // CHACHA20_IETF: 'chacha20-ietf', + CHACHA20_POLY1305: 'chacha20-poly1305', + AES_256_GCM: 'aes-256-gcm', + AES_128_GCM: 'aes-128-gcm', +}; + +const RULE_IP = { + PRIVATE: 'geoip:private', + CN: 'geoip:cn', +}; + +const RULE_DOMAIN = { + ADS: 'geosite:category-ads', + ADS_ALL: 'geosite:category-ads-all', + CN: 'geosite:cn', + GOOGLE: 'geosite:google', + FACEBOOK: 'geosite:facebook', + SPEEDTEST: 'geosite:speedtest', +}; + +const VLESS_FLOW = { + ORIGIN: "xtls-rprx-origin", + DIRECT: "xtls-rprx-direct", +}; + +Object.freeze(Protocols); +Object.freeze(VmessMethods); +Object.freeze(SSMethods); +Object.freeze(RULE_IP); +Object.freeze(RULE_DOMAIN); +Object.freeze(VLESS_FLOW); + +class XrayCommonClass { + + static toJsonArray(arr) { + return arr.map(obj => obj.toJson()); + } + + static fromJson() { + return new XrayCommonClass(); + } + + toJson() { + return this; + } + + toString(format=true) { + return format ? JSON.stringify(this.toJson(), null, 2) : JSON.stringify(this.toJson()); + } + + static toHeaders(v2Headers) { + let newHeaders = []; + if (v2Headers) { + Object.keys(v2Headers).forEach(key => { + let values = v2Headers[key]; + if (typeof(values) === 'string') { + newHeaders.push({ name: key, value: values }); + } else { + for (let i = 0; i < values.length; ++i) { + newHeaders.push({ name: key, value: values[i] }); + } + } + }); + } + return newHeaders; + } + + static toV2Headers(headers, arr=true) { + let v2Headers = {}; + for (let i = 0; i < headers.length; ++i) { + let name = headers[i].name; + let value = headers[i].value; + if (ObjectUtil.isEmpty(name) || ObjectUtil.isEmpty(value)) { + continue; + } + if (!(name in v2Headers)) { + v2Headers[name] = arr ? [value] : value; + } else { + if (arr) { + v2Headers[name].push(value); + } else { + v2Headers[name] = value; + } + } + } + return v2Headers; + } +} + +class TcpStreamSettings extends XrayCommonClass { + constructor(type='none', + request=new TcpStreamSettings.TcpRequest(), + response=new TcpStreamSettings.TcpResponse(), + ) { + super(); + this.type = type; + this.request = request; + this.response = response; + } + + static fromJson(json={}) { + let header = json.header; + if (!header) { + header = {}; + } + return new TcpStreamSettings( + header.type, + TcpStreamSettings.TcpRequest.fromJson(header.request), + TcpStreamSettings.TcpResponse.fromJson(header.response), + ); + } + + toJson() { + return { + header: { + type: this.type, + request: this.type === 'http' ? this.request.toJson() : undefined, + response: this.type === 'http' ? this.response.toJson() : undefined, + }, + }; + } +} + +TcpStreamSettings.TcpRequest = class extends XrayCommonClass { + constructor(version='1.1', + method='GET', + path=['/'], + headers=[], + ) { + super(); + this.version = version; + this.method = method; + this.path = path.length === 0 ? ['/'] : path; + this.headers = headers; + } + + addPath(path) { + this.path.push(path); + } + + removePath(index) { + this.path.splice(index, 1); + } + + addHeader(name, value) { + this.headers.push({ name: name, value: value }); + } + + removeHeader(index) { + this.headers.splice(index, 1); + } + + static fromJson(json={}) { + return new TcpStreamSettings.TcpRequest( + json.version, + json.method, + json.path, + XrayCommonClass.toHeaders(json.headers), + ); + } + + toJson() { + return { + method: this.method, + path: ObjectUtil.clone(this.path), + headers: XrayCommonClass.toV2Headers(this.headers), + }; + } +}; + +TcpStreamSettings.TcpResponse = class extends XrayCommonClass { + constructor(version='1.1', + status='200', + reason='OK', + headers=[], + ) { + super(); + this.version = version; + this.status = status; + this.reason = reason; + this.headers = headers; + } + + addHeader(name, value) { + this.headers.push({ name: name, value: value }); + } + + removeHeader(index) { + this.headers.splice(index, 1); + } + + static fromJson(json={}) { + return new TcpStreamSettings.TcpResponse( + json.version, + json.status, + json.reason, + XrayCommonClass.toHeaders(json.headers), + ); + } + + toJson() { + return { + version: this.version, + status: this.status, + reason: this.reason, + headers: XrayCommonClass.toV2Headers(this.headers), + }; + } +}; + +class KcpStreamSettings extends XrayCommonClass { + constructor(mtu=1350, tti=20, + uplinkCapacity=5, + downlinkCapacity=20, + congestion=false, + readBufferSize=2, + writeBufferSize=2, + type='none', + seed=RandomUtil.randomSeq(10), + ) { + super(); + this.mtu = mtu; + this.tti = tti; + this.upCap = uplinkCapacity; + this.downCap = downlinkCapacity; + this.congestion = congestion; + this.readBuffer = readBufferSize; + this.writeBuffer = writeBufferSize; + this.type = type; + this.seed = seed; + } + + static fromJson(json={}) { + return new KcpStreamSettings( + json.mtu, + json.tti, + json.uplinkCapacity, + json.downlinkCapacity, + json.congestion, + json.readBufferSize, + json.writeBufferSize, + ObjectUtil.isEmpty(json.header) ? 'none' : json.header.type, + json.seed, + ); + } + + toJson() { + return { + mtu: this.mtu, + tti: this.tti, + uplinkCapacity: this.upCap, + downlinkCapacity: this.downCap, + congestion: this.congestion, + readBufferSize: this.readBuffer, + writeBufferSize: this.writeBuffer, + header: { + type: this.type, + }, + seed: this.seed, + }; + } +} + +class WsStreamSettings extends XrayCommonClass { + constructor(path='/', headers=[]) { + super(); + this.path = path; + this.headers = headers; + } + + addHeader(name, value) { + this.headers.push({ name: name, value: value }); + } + + removeHeader(index) { + this.headers.splice(index, 1); + } + + static fromJson(json={}) { + return new WsStreamSettings( + json.path, + XrayCommonClass.toHeaders(json.headers), + ); + } + + toJson() { + return { + path: this.path, + headers: XrayCommonClass.toV2Headers(this.headers, false), + }; + } +} + +class HttpStreamSettings extends XrayCommonClass { + constructor(path='/', host=['']) { + super(); + this.path = path; + this.host = host.length === 0 ? [''] : host; + } + + addHost(host) { + this.host.push(host); + } + + removeHost(index) { + this.host.splice(index, 1); + } + + static fromJson(json={}) { + return new HttpStreamSettings(json.path, json.host); + } + + toJson() { + let host = []; + for (let i = 0; i < this.host.length; ++i) { + if (!ObjectUtil.isEmpty(this.host[i])) { + host.push(this.host[i]); + } + } + return { + path: this.path, + host: host, + } + } +} + +class QuicStreamSettings extends XrayCommonClass { + constructor(security=VmessMethods.NONE, + key='', type='none') { + super(); + this.security = security; + this.key = key; + this.type = type; + } + + static fromJson(json={}) { + return new QuicStreamSettings( + json.security, + json.key, + json.header ? json.header.type : 'none', + ); + } + + toJson() { + return { + security: this.security, + key: this.key, + header: { + type: this.type, + } + } + } +} + +class TlsStreamSettings extends XrayCommonClass { + constructor(serverName='', + certificates=[new TlsStreamSettings.Cert()]) { + super(); + this.server = serverName; + this.certs = certificates; + } + + addCert(cert) { + this.certs.push(cert); + } + + removeCert(index) { + this.certs.splice(index, 1); + } + + static fromJson(json={}) { + let certs; + if (!ObjectUtil.isEmpty(json.certificates)) { + certs = json.certificates.map(cert => TlsStreamSettings.Cert.fromJson(cert)); + } + return new TlsStreamSettings( + json.serverName, + certs, + ); + } + + toJson() { + return { + serverName: this.server, + certificates: TlsStreamSettings.toJsonArray(this.certs), + }; + } +} + +TlsStreamSettings.Cert = class extends XrayCommonClass { + constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='') { + super(); + this.useFile = useFile; + this.certFile = certificateFile; + this.keyFile = keyFile; + this.cert = certificate instanceof Array ? certificate.join('\n') : certificate; + this.key = key instanceof Array ? key.join('\n') : key; + } + + static fromJson(json={}) { + if ('certificateFile' in json && 'keyFile' in json) { + return new TlsStreamSettings.Cert( + true, + json.certificateFile, + json.keyFile, + ); + } else { + return new TlsStreamSettings.Cert( + false, '', '', + json.certificate.join('\n'), + json.key.join('\n'), + ); + } + } + + toJson() { + if (this.useFile) { + return { + certificateFile: this.certFile, + keyFile: this.keyFile, + }; + } else { + return { + certificate: this.cert.split('\n'), + key: this.key.split('\n'), + }; + } + } +}; + +class StreamSettings extends XrayCommonClass { + constructor(network='tcp', + security='none', + tlsSettings=new TlsStreamSettings(), + tcpSettings=new TcpStreamSettings(), + kcpSettings=new KcpStreamSettings(), + wsSettings=new WsStreamSettings(), + httpSettings=new HttpStreamSettings(), + quicSettings=new QuicStreamSettings(), + ) { + super(); + this.network = network; + if (security === "xtls") { + this.security = "tls"; + this._is_xtls = true; + } else { + this.security = security; + this._is_xtls = false; + } + this.tls = tlsSettings; + this.tcp = tcpSettings; + this.kcp = kcpSettings; + this.ws = wsSettings; + this.http = httpSettings; + this.quic = quicSettings; + } + + get is_xtls() { + return this.security === "tls" && this.network === "tcp" && this._is_xtls; + } + + set is_xtls(is_xtls) { + this._is_xtls = is_xtls; + } + + static fromJson(json={}) { + let tls; + if (json.security === "xtls") { + tls = TlsStreamSettings.fromJson(json.xtlsSettings); + } else { + tls = TlsStreamSettings.fromJson(json.tlsSettings); + } + return new StreamSettings( + json.network, + json.security, + tls, + TcpStreamSettings.fromJson(json.tcpSettings), + KcpStreamSettings.fromJson(json.kcpSettings), + WsStreamSettings.fromJson(json.wsSettings), + HttpStreamSettings.fromJson(json.httpSettings), + QuicStreamSettings.fromJson(json.quicSettings), + ); + } + + toJson() { + let network = this.network; + let security = this.security; + if (this.is_xtls) { + security = "xtls"; + } + return { + network: network, + security: security, + tlsSettings: this.security === 'tls' && ['tcp', 'ws', 'http', 'quic'].indexOf(network) >= 0 && !this.is_xtls ? this.tls.toJson() : undefined, + xtlsSettings: this.is_xtls ? this.tls.toJson() : undefined, + tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined, + kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined, + wsSettings: network === 'ws' ? this.ws.toJson() : undefined, + httpSettings: network === 'http' ? this.http.toJson() : undefined, + quicSettings: network === 'quic' ? this.quic.toJson() : undefined, + }; + } +} + +class Sniffing extends XrayCommonClass { + constructor(enabled=true, destOverride=['http', 'tls']) { + super(); + this.enabled = enabled; + this.destOverride = destOverride; + } + + static fromJson(json={}) { + let destOverride = ObjectUtil.clone(json.destOverride); + if (!ObjectUtil.isEmpty(destOverride) && !ObjectUtil.isArrEmpty(destOverride)) { + if (ObjectUtil.isEmpty(destOverride[0])) { + destOverride = ['http', 'tls']; + } + } + return new Sniffing( + !!json.enabled, + destOverride, + ); + } +} + +class Inbound extends XrayCommonClass { + constructor(port=RandomUtil.randomIntRange(10000, 60000), + listen='0.0.0.0', + protocol=Protocols.VMESS, + settings=null, + streamSettings=new StreamSettings(), + tag='', + sniffing=new Sniffing(), + remark='', + enable=true, + ) { + super(); + this.port = port; + this.listen = listen; + this.protocol = protocol; + this.settings = ObjectUtil.isEmpty(settings) ? Inbound.Settings.getSettings(protocol) : settings; + this.stream = streamSettings; + this.tag = tag; + this.sniffing = sniffing; + this.remark = remark; + this.enable = enable; + } + + reset() { + this.port = RandomUtil.randomIntRange(10000, 60000); + this.listen = '0.0.0.0'; + this.protocol = Protocols.VMESS; + this.settings = Inbound.Settings.getSettings(Protocols.VMESS); + this.stream = new StreamSettings(); + this.tag = ''; + this.sniffing = new Sniffing(); + this.remark = ''; + this.enable = true; + } + + genVmessLink(address='') { + if (this.protocol !== Protocols.VMESS) { + return ''; + } + let network = this.stream.network; + let type = 'none'; + let host = ''; + let path = ''; + if (network === 'tcp') { + let tcp = this.stream.tcp; + type = tcp.type; + if (type === 'http') { + let request = tcp.request; + path = request.path.join(','); + let index = request.headers.findIndex(header => header.name.toLowerCase() === 'host'); + if (index >= 0) { + host = request.headers[index].value; + } + } + } else if (network === 'kcp') { + let kcp = this.stream.kcp; + type = kcp.type; + path = kcp.seed; + } else if (network === 'ws') { + let ws = this.stream.ws; + path = ws.path; + let index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host'); + if (index >= 0) { + host = ws.headers[index].value; + } + } else if (network === 'http') { + network = 'h2'; + path = this.stream.http.path; + host = this.stream.http.host.join(','); + } else if (network === 'quic') { + type = this.stream.quic.type; + host = this.stream.quic.security; + path = this.stream.quic.key; + } + + if (this.stream.security === 'tls') { + if (!ObjectUtil.isEmpty(this.stream.tls.server)) { + address = this.stream.tls.server; + } + } + + let obj = { + v: '2', + ps: this.remark, + add: address, + port: this.port, + id: this.settings.vmesses[0].id, + aid: this.settings.vmesses[0].alterId, + net: network, + type: type, + host: host, + path: path, + tls: this.stream.security, + }; + return 'vmess://' + base64(JSON.stringify(obj, null, 2)); + } + + genVLESSLink(address = '') { + const settings = this.settings; + const uuid = settings.vlesses[0].id; + const port = this.port; + const type = this.stream.network; + const params = new Map(); + params.set("type", this.stream.network); + if (this.stream.is_xtls) { + params.set("security", "xtls"); + } else { + params.set("security", this.stream.security); + } + switch (type) { + case "tcp": + const tcp = this.stream.tcp; + if (tcp.type === 'http') { + const request = tcp.request; + params.set("path", request.path.join(',')); + const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host'); + if (index >= 0) { + const host = request.headers[index].value; + params.set("host", host); + } + } + break; + case "kcp": + const kcp = this.stream.kcp; + params.set("headerType", kcp.type); + params.set("seed", kcp.seed); + break; + case "ws": + const ws = this.stream.ws; + params.set("path", ws.path); + const index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host'); + if (index >= 0) { + const host = ws.headers[index].value; + params.set("host", host); + } + break; + case "http": + const http = this.stream.http; + params.set("path", http.path); + params.set("host", http.host); + break; + case "quic": + const quic = this.stream.quic; + params.set("quicSecurity", quic.security); + params.set("key", quic.key); + params.set("headerType", quic.type); + break; + } + + if (this.stream.security === 'tls') { + if (!ObjectUtil.isEmpty(this.stream.tls.server)) { + address = this.stream.tls.server; + params.set("sni", address); + } + } + + if (this.stream.is_xtls) { + params.set("flow", this.settings.vlesses[0].flow); + } + + for (const [key, value] of params) { + switch (key) { + case "host": + case "path": + case "seed": + case "key": + case "alpn": + params.set(key, encodeURIComponent(value)); + break; + } + } + + const link = `vless://${uuid}@${address}:${port}`; + const url = new URL(link); + for (const [key, value] of params) { + url.searchParams.set(key, value) + } + url.hash = encodeURIComponent(this.remark); + return url.toString(); + } + + genSSLink(address='') { + let settings = this.settings; + const server = this.stream.tls.server; + if (!ObjectUtil.isEmpty(server)) { + address = server; + } + return 'ss://' + safeBase64(settings.method + ':' + settings.password + '@' + address + ':' + this.port) + + '#' + encodeURIComponent(this.remark); + } + + genTrojanLink(address='') { + let settings = this.settings; + return `trojan://${settings.clients[0].password}@${address}:${this.port}#${encodeURIComponent(this.remark)}`; + } + + genLink(address='') { + switch (this.protocol) { + case Protocols.VMESS: return this.genVmessLink(address); + case Protocols.VLESS: return this.genVLESSLink(address); + case Protocols.SHADOWSOCKS: return this.genSSLink(address); + case Protocols.TROJAN: return this.genTrojanLink(address); + default: return ''; + } + } + + static fromJson(json={}) { + return new Inbound( + json.port, + json.listen, + json.protocol, + Inbound.Settings.fromJson(json.protocol, json.settings), + StreamSettings.fromJson(json.streamSettings), + json.tag, + Sniffing.fromJson(json.sniffing), + json.remark, + json.enable, + ) + } + + toJson() { + let streamSettings; + if (this.protocol === Protocols.VMESS + || this.protocol === Protocols.VLESS + || this.protocol === Protocols.TROJAN + || this.protocol === Protocols.SHADOWSOCKS) { + streamSettings = this.stream.toJson(); + } + return { + port: this.port, + listen: this.listen, + protocol: this.protocol, + settings: this.settings instanceof XrayCommonClass ? this.settings.toJson() : this.settings, + streamSettings: streamSettings, + tag: this.tag, + sniffing: this.sniffing.toJson(), + remark: this.remark, + enable: this.enable, + }; + } +} + +Inbound.Settings = class extends XrayCommonClass { + constructor(protocol) { + super(); + this.protocol = protocol; + } + + static getSettings(protocol) { + switch (protocol) { + case Protocols.VMESS: return new Inbound.VmessSettings(protocol); + case Protocols.VLESS: return new Inbound.VLESSSettings(protocol); + case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol); + case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol); + case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol); + case Protocols.MTPROTO: return new Inbound.MtprotoSettings(protocol); + case Protocols.SOCKS: return new Inbound.SocksSettings(protocol); + case Protocols.HTTP: return new Inbound.HttpSettings(protocol); + default: return null; + } + } + + static fromJson(protocol, json) { + switch (protocol) { + case Protocols.VMESS: return Inbound.VmessSettings.fromJson(json); + case Protocols.VLESS: return Inbound.VLESSSettings.fromJson(json); + case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json); + case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json); + case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json); + case Protocols.MTPROTO: return Inbound.MtprotoSettings.fromJson(json); + case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json); + case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json); + default: return null; + } + } + + toJson() { + return {}; + } +}; + +Inbound.VmessSettings = class extends Inbound.Settings { + constructor(protocol, + vmesses=[new Inbound.VmessSettings.Vmess()], + disableInsecureEncryption=false) { + super(protocol); + this.vmesses = vmesses; + this.disableInsecure = disableInsecureEncryption; + } + + indexOfVmessById(id) { + return this.vmesses.findIndex(vmess => vmess.id === id); + } + + addVmess(vmess) { + if (this.indexOfVmessById(vmess.id) >= 0) { + return false; + } + this.vmesses.push(vmess); + } + + delVmess(vmess) { + const i = this.indexOfVmessById(vmess.id); + if (i >= 0) { + this.vmesses.splice(i, 1); + } + } + + static fromJson(json={}) { + return new Inbound.VmessSettings( + Protocols.VMESS, + json.clients.map(client => Inbound.VmessSettings.Vmess.fromJson(client)), + ObjectUtil.isEmpty(json.disableInsecureEncryption) ? false : json.disableInsecureEncryption, + ); + } + + toJson() { + return { + clients: Inbound.VmessSettings.toJsonArray(this.vmesses), + disableInsecureEncryption: this.disableInsecure, + }; + } +}; +Inbound.VmessSettings.Vmess = class extends XrayCommonClass { + constructor(id=RandomUtil.randomUUID(), alterId=64) { + super(); + this.id = id; + this.alterId = alterId; + } + + static fromJson(json={}) { + return new Inbound.VmessSettings.Vmess( + json.id, + json.alterId, + ); + } +}; + +Inbound.VLESSSettings = class extends Inbound.Settings { + constructor(protocol, + vlesses=[new Inbound.VLESSSettings.VLESS()], + decryption='none', + fallbacks=[],) { + super(protocol); + this.vlesses = vlesses; + this.decryption = decryption; + this.fallbacks = fallbacks; + } + + addFallback() { + this.fallbacks.push(new Inbound.VLESSSettings.Fallback()); + } + + delFallback(index) { + this.fallbacks.splice(index, 1); + } + + static fromJson(json={}) { + return new Inbound.VLESSSettings( + Protocols.VLESS, + json.clients.map(client => Inbound.VLESSSettings.VLESS.fromJson(client)), + json.decryption, + Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks), + ); + } + + toJson() { + return { + clients: Inbound.VLESSSettings.toJsonArray(this.vlesses), + decryption: this.decryption, + fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks), + }; + } +}; +Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { + + constructor(id=RandomUtil.randomUUID(), flow=VLESS_FLOW.DIRECT) { + super(); + this.id = id; + this.flow = flow; + } + + static fromJson(json={}) { + return new Inbound.VLESSSettings.VLESS( + json.id, + json.flow, + ); + } +}; +Inbound.VLESSSettings.Fallback = class extends XrayCommonClass { + constructor(name="", alpn='', path='', dest='', xver=0) { + super(); + this.name = name; + this.alpn = alpn; + this.path = path; + this.dest = dest; + this.xver = xver; + } + + toJson() { + let xver = this.xver; + if (!Number.isInteger(xver)) { + xver = 0; + } + return { + name: this.name, + alpn: this.alpn, + path: this.path, + dest: this.dest, + xver: xver, + } + } + + static fromJson(json=[]) { + const fallbacks = []; + for (let fallback of json) { + fallbacks.push(new Inbound.VLESSSettings.Fallback( + fallback.name, + fallback.alpn, + fallback.path, + fallback.dest, + fallback.xver, + )) + } + return fallbacks; + } +}; + +Inbound.TrojanSettings = class extends Inbound.Settings { + constructor(protocol, clients=[new Inbound.TrojanSettings.Client()]) { + super(protocol); + this.clients = clients; + } + + toJson() { + return { + clients: Inbound.TrojanSettings.toJsonArray(this.clients), + }; + } + + static fromJson(json={}) { + const clients = []; + for (const c of json.clients) { + clients.push(Inbound.TrojanSettings.Client.fromJson(c)); + } + return new Inbound.TrojanSettings(Protocols.TROJAN, clients); + } +}; +Inbound.TrojanSettings.Client = class extends XrayCommonClass { + constructor(password=RandomUtil.randomSeq(10)) { + super(); + this.password = password; + } + + toJson() { + return { + password: this.password, + }; + } + + static fromJson(json={}) { + return new Inbound.TrojanSettings.Client(json.password); + } + +}; + +Inbound.ShadowsocksSettings = class extends Inbound.Settings { + constructor(protocol, + method=SSMethods.AES_256_GCM, + password=RandomUtil.randomSeq(10), + network='tcp,udp' + ) { + super(protocol); + this.method = method; + this.password = password; + this.network = network; + } + + static fromJson(json={}) { + return new Inbound.ShadowsocksSettings( + Protocols.SHADOWSOCKS, + json.method, + json.password, + json.network, + ); + } + + toJson() { + return { + method: this.method, + password: this.password, + network: this.network, + }; + } +}; + +Inbound.DokodemoSettings = class extends Inbound.Settings { + constructor(protocol, address, port, network='tcp,udp') { + super(protocol); + this.address = address; + this.port = port; + this.network = network; + } + + static fromJson(json={}) { + return new Inbound.DokodemoSettings( + Protocols.DOKODEMO, + json.address, + json.port, + json.network, + ); + } + + toJson() { + return { + address: this.address, + port: this.port, + network: this.network, + }; + } +}; + +Inbound.MtprotoSettings = class extends Inbound.Settings { + constructor(protocol, users=[new Inbound.MtprotoSettings.MtUser()]) { + super(protocol); + this.users = users; + } + + static fromJson(json={}) { + return new Inbound.MtprotoSettings( + Protocols.MTPROTO, + json.users.map(user => Inbound.MtprotoSettings.MtUser.fromJson(user)), + ); + } + + toJson() { + return { + users: XrayCommonClass.toJsonArray(this.users), + }; + } +}; +Inbound.MtprotoSettings.MtUser = class extends XrayCommonClass { + constructor(secret=RandomUtil.randomMTSecret()) { + super(); + this.secret = secret; + } + + static fromJson(json={}) { + return new Inbound.MtprotoSettings.MtUser(json.secret); + } +}; + +Inbound.SocksSettings = class extends Inbound.Settings { + constructor(protocol, auth='password', accounts=[new Inbound.SocksSettings.SocksAccount()], udp=false, ip='127.0.0.1') { + super(protocol); + this.auth = auth; + this.accounts = accounts; + this.udp = udp; + this.ip = ip; + } + + addAccount(account) { + this.accounts.push(account); + } + + delAccount(index) { + this.accounts.splice(index, 1); + } + + static fromJson(json={}) { + let accounts; + if (json.auth === 'password') { + accounts = json.accounts.map( + account => Inbound.SocksSettings.SocksAccount.fromJson(account) + ) + } + return new Inbound.SocksSettings( + Protocols.SOCKS, + json.auth, + accounts, + json.udp, + json.ip, + ); + } + + toJson() { + return { + auth: this.auth, + accounts: this.auth === 'password' ? this.accounts.map(account => account.toJson()) : undefined, + udp: this.udp, + ip: this.ip, + }; + } +}; +Inbound.SocksSettings.SocksAccount = class extends XrayCommonClass { + constructor(user=RandomUtil.randomSeq(10), pass=RandomUtil.randomSeq(10)) { + super(); + this.user = user; + this.pass = pass; + } + + static fromJson(json={}) { + return new Inbound.SocksSettings.SocksAccount(json.user, json.pass); + } +}; + +Inbound.HttpSettings = class extends Inbound.Settings { + constructor(protocol, accounts=[new Inbound.HttpSettings.HttpAccount()]) { + super(protocol); + this.accounts = accounts; + } + + addAccount(account) { + this.accounts.push(account); + } + + delAccount(index) { + this.accounts.splice(index, 1); + } + + static fromJson(json={}) { + return new Inbound.HttpSettings( + Protocols.HTTP, + json.accounts.map(account => Inbound.HttpSettings.HttpAccount.fromJson(account)), + ); + } + + toJson() { + return { + accounts: Inbound.HttpSettings.toJsonArray(this.accounts), + }; + } +}; + +Inbound.HttpSettings.HttpAccount = class extends XrayCommonClass { + constructor(user=RandomUtil.randomSeq(10), pass=RandomUtil.randomSeq(10)) { + super(); + this.user = user; + this.pass = pass; + } + + static fromJson(json={}) { + return new Inbound.HttpSettings.HttpAccount(json.user, json.pass); + } +}; diff --git a/web/assets/js/util/date-util.js b/web/assets/js/util/date-util.js index fbb93e3108..24e0887919 100644 --- a/web/assets/js/util/date-util.js +++ b/web/assets/js/util/date-util.js @@ -135,7 +135,7 @@ class DateUtil { } static formatMillis(millis) { - return moment(millis).format('YYYY年M月D日 H时m分s秒') + return moment(millis).format('YYYY-M-D H:m:s') } static firstDayOfMonth() { diff --git a/web/assets/js/util/utils.js b/web/assets/js/util/utils.js index 7b129b9a23..faeced1945 100644 --- a/web/assets/js/util/utils.js +++ b/web/assets/js/util/utils.js @@ -13,7 +13,7 @@ class HttpUtil { } } - static async _respToMsg(resp) { + static _respToMsg(resp) { const data = resp.data; if (data == null) { return new Msg(true); @@ -227,6 +227,10 @@ class ObjectUtil { for (const key of Object.keys(src)) { if (!src.hasOwnProperty(key)) { continue; + } else if (!dest.hasOwnProperty(key)) { + continue; + } else if (src[key] === undefined) { + continue; } if (ignoreEmpty) { dest[key] = src[key]; @@ -259,4 +263,11 @@ class ObjectUtil { } } + static orDefault(obj, defaultValue) { + if (obj == null) { + return defaultValue; + } + return obj; + } + } diff --git a/web/controller/base_controller.go b/web/controller/base.go similarity index 76% rename from web/controller/base_controller.go rename to web/controller/base.go index 742b66f31f..b2f300c40c 100644 --- a/web/controller/base_controller.go +++ b/web/controller/base.go @@ -8,10 +8,6 @@ import ( type BaseController struct { } -func NewBaseController(g *gin.RouterGroup) *BaseController { - return &BaseController{} -} - func (a *BaseController) before(c *gin.Context) { if !session.IsLogin(c) { pureJsonMsg(c, false, "登录时效已过,请重新登录") diff --git a/web/controller/index_controller.go b/web/controller/index.go similarity index 100% rename from web/controller/index_controller.go rename to web/controller/index.go diff --git a/web/controller/server.go b/web/controller/server.go new file mode 100644 index 0000000000..d80d2690d3 --- /dev/null +++ b/web/controller/server.go @@ -0,0 +1,116 @@ +package controller + +import ( + "context" + "github.com/gin-gonic/gin" + "runtime" + "time" + "x-ui/web/service" +) + +func stopServerController(a *ServerController) { + a.stopTask() +} + +type ServerController struct { + *serverController +} + +func NewServerController(g *gin.RouterGroup) *ServerController { + a := &ServerController{ + serverController: newServerController(g), + } + runtime.SetFinalizer(a, stopServerController) + return a +} + +type serverController struct { + BaseController + + serverService service.ServerService + + ctx context.Context + cancel context.CancelFunc + + lastStatus *service.Status + lastGetStatusTime time.Time + + lastVersions []string + lastGetVersionsTime time.Time +} + +func newServerController(g *gin.RouterGroup) *serverController { + ctx, cancel := context.WithCancel(context.Background()) + a := &serverController{ + ctx: ctx, + cancel: cancel, + lastGetStatusTime: time.Now(), + } + a.initRouter(g) + a.startTask() + return a +} + +func (a *serverController) initRouter(g *gin.RouterGroup) { + g.POST("/server/status", a.status) + g.POST("/server/getXrayVersion", a.getXrayVersion) + g.POST("/server/installXray/:version", a.installXray) +} + +func (a *serverController) refreshStatus() { + status := a.serverService.GetStatus(a.lastStatus) + a.lastStatus = status +} + +func (a *serverController) startTask() { + go func() { + for { + select { + case <-a.ctx.Done(): + break + default: + } + now := time.Now() + if now.Sub(a.lastGetStatusTime) > time.Minute*3 { + time.Sleep(time.Second * 2) + continue + } + a.refreshStatus() + } + }() +} + +func (a *serverController) stopTask() { + a.cancel() +} + +func (a *serverController) status(c *gin.Context) { + a.lastGetStatusTime = time.Now() + + jsonObj(c, a.lastStatus, nil) +} + +func (a *serverController) getXrayVersion(c *gin.Context) { + now := time.Now() + if now.Sub(a.lastGetVersionsTime) <= time.Minute { + jsonObj(c, a.lastVersions, nil) + return + } + + versions, err := a.serverService.GetXrayVersions() + if err != nil { + jsonMsg(c, "获取版本", err) + return + } + + a.lastVersions = versions + a.lastGetVersionsTime = time.Now() + + jsonObj(c, versions, nil) +} + +func (a *serverController) installXray(c *gin.Context) { + version := c.Param("version") + err := a.serverService.UpdateXray(version) + jsonMsg(c, "安装 xray", err) +} diff --git a/web/controller/util.go b/web/controller/util.go index 83de4808f3..0b214fdc9a 100644 --- a/web/controller/util.go +++ b/web/controller/util.go @@ -51,7 +51,7 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) { } else { m.Success = false m.Msg = msg + "失败: " + err.Error() - logger.Warning(msg, err) + logger.Warning(msg+"失败: ", err) } c.JSON(http.StatusOK, m) } @@ -76,7 +76,7 @@ func html(c *gin.Context, name string, title string, data gin.H) { } data["title"] = title data["request_uri"] = c.Request.RequestURI - data["base_path"] = config.GetBasePath() + data["base_path"] = c.GetString("base_path") c.HTML(http.StatusOK, name, getContext(data)) } diff --git a/web/controller/xui.go b/web/controller/xui.go new file mode 100644 index 0000000000..253e91d4d3 --- /dev/null +++ b/web/controller/xui.go @@ -0,0 +1,80 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "log" + "strconv" + "x-ui/database/model" + "x-ui/web/service" + "x-ui/web/session" +) + +type XUIController struct { + BaseController + + inboundService service.InboundService +} + +func NewXUIController(g *gin.RouterGroup) *XUIController { + a := &XUIController{} + a.initRouter(g) + return a +} + +func (a *XUIController) initRouter(g *gin.RouterGroup) { + g = g.Group("/xui") + + g.GET("/", a.index) + g.GET("/inbounds", a.inbounds) + g.POST("/inbounds", a.postInbounds) + g.POST("/inbound/add", a.addInbound) + g.POST("/inbound/del/:id", a.delInbound) + g.GET("/setting", a.setting) +} + +func (a *XUIController) index(c *gin.Context) { + html(c, "index.html", "系统状态", nil) +} + +func (a *XUIController) inbounds(c *gin.Context) { + html(c, "inbounds.html", "入站列表", nil) +} + +func (a *XUIController) setting(c *gin.Context) { + html(c, "setting.html", "设置", nil) +} + +func (a *XUIController) postInbounds(c *gin.Context) { + user := session.GetLoginUser(c) + inbounds, err := a.inboundService.GetInbounds(user.Id) + if err != nil { + jsonMsg(c, "获取", err) + return + } + jsonObj(c, inbounds, nil) +} + +func (a *XUIController) addInbound(c *gin.Context) { + inbound := &model.Inbound{} + err := c.ShouldBind(inbound) + if err != nil { + jsonMsg(c, "添加", err) + return + } + user := session.GetLoginUser(c) + inbound.UserId = user.Id + inbound.Enable = true + log.Println(inbound) + err = a.inboundService.AddInbound(inbound) + jsonMsg(c, "添加", err) +} + +func (a *XUIController) delInbound(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + jsonMsg(c, "删除", err) + return + } + err = a.inboundService.DelInbound(id) + jsonMsg(c, "删除", err) +} diff --git a/web/controller/xui_controller.go b/web/controller/xui_controller.go deleted file mode 100644 index c410cbcdad..0000000000 --- a/web/controller/xui_controller.go +++ /dev/null @@ -1,31 +0,0 @@ -package controller - -import ( - "github.com/gin-gonic/gin" -) - -type XUIController struct { - BaseController -} - -func NewXUIController(g *gin.RouterGroup) *XUIController { - a := &XUIController{} - a.initRouter(g) - return a -} - -func (a *XUIController) initRouter(g *gin.RouterGroup) { - g = g.Group("/xui") - - g.GET("/", a.index) - g.GET("/accounts", a.index) - g.GET("/setting", a.setting) -} - -func (a *XUIController) index(c *gin.Context) { - html(c, "index.html", "系统状态", nil) -} - -func (a *XUIController) setting(c *gin.Context) { - -} diff --git a/web/html/common/js.html b/web/html/common/js.html index f835851df1..b8ee57b2f8 100644 --- a/web/html/common/js.html +++ b/web/html/common/js.html @@ -9,10 +9,11 @@ - + + +{{end}} \ No newline at end of file diff --git a/web/html/common/text_modal.html b/web/html/common/text_modal.html new file mode 100644 index 0000000000..0ae04a88d8 --- /dev/null +++ b/web/html/common/text_modal.html @@ -0,0 +1,58 @@ +{{define "textModal"}} + + + {{ i18n "download" }} [[ txtModal.fileName ]] + + + + + +{{end}} \ No newline at end of file diff --git a/web/html/x-ui/common_sider.html b/web/html/x-ui/common_sider.html index a83a61e2b1..7aaa2ab74f 100644 --- a/web/html/x-ui/common_sider.html +++ b/web/html/x-ui/common_sider.html @@ -3,7 +3,7 @@ 系统状态 - + 账号列表 diff --git a/web/html/x-ui/inbound_modal.html b/web/html/x-ui/inbound_modal.html new file mode 100644 index 0000000000..24655cdfd5 --- /dev/null +++ b/web/html/x-ui/inbound_modal.html @@ -0,0 +1,535 @@ +{{define "inboundModal"}} + + + + + + + + + + + + + [[ p ]] + + + + + 监听 IP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [[ key ]] + + + + + + + + + + + + + + + + + + + + fallback[[ index + 1 ]] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [[ method ]] + + + + + + + + tcp+udp + tcp + udp + + + + + + + + + + + + + + + + + + tcp+udp + tcp + udp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sniffing + + + + + + + + + + +{{end}} \ No newline at end of file diff --git a/web/html/x-ui/inbounds.html b/web/html/x-ui/inbounds.html new file mode 100644 index 0000000000..7439ac0aef --- /dev/null +++ b/web/html/x-ui/inbounds.html @@ -0,0 +1,286 @@ + + +{{template "head" .}} + + + + {{ template "commonSider" . }} + + + + + + Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information + + + + +
+ +
+ + + + + + upload / download: + [[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]] + + + total traffic: + + [[ sizeFormat(total.up + total.down) ]] + + + + number of accounts: + [[ dbInbounds.length ]] + + +
+
+ + + + + + + + + + + + + + +
+
+
+
+{{template "js" .}} + +{{template "inboundModal"}} +{{template "promptModal"}} +{{template "qrcodeModal"}} +{{template "textModal"}} + + \ No newline at end of file diff --git a/web/html/x-ui/index.html b/web/html/x-ui/index.html index 887ee9dd34..b6a8b1605b 100644 --- a/web/html/x-ui/index.html +++ b/web/html/x-ui/index.html @@ -68,7 +68,7 @@ xray 状态: [[ status.xray.state ]] - + @@ -175,7 +175,6 @@

请谨慎选择,旧版本可能配置不兼容

- {{template "js" .}} + \ No newline at end of file diff --git a/web/service/config.json b/web/service/config.json new file mode 100644 index 0000000000..7eec72693b --- /dev/null +++ b/web/service/config.json @@ -0,0 +1,64 @@ +{ + "api": { + "services": [ + "HandlerService", + "LoggerService", + "StatsService" + ], + "tag": "api" + }, + "inbounds": [ + { + "listen": "127.0.0.1", + "port": 62789, + "protocol": "dokodemo-door", + "settings": { + "address": "127.0.0.1" + }, + "tag": "api" + } + ], + "outbounds": [ + { + "protocol": "freedom", + "settings": {} + }, + { + "protocol": "blackhole", + "settings": {}, + "tag": "blocked" + } + ], + "policy": { + "system": { + "statsInboundDownlink": true, + "statsInboundUplink": true + } + }, + "routing": { + "rules": [ + { + "inboundTag": [ + "api" + ], + "outboundTag": "api", + "type": "field" + }, + { + "ip": [ + "geoip:private" + ], + "outboundTag": "blocked", + "type": "field" + }, + { + "outboundTag": "blocked", + "protocol": [ + "bittorrent" + ], + "type": "field" + } + ] + }, + "stats": {} +} \ No newline at end of file diff --git a/web/service/inbound.go b/web/service/inbound.go new file mode 100644 index 0000000000..ff6f2d6536 --- /dev/null +++ b/web/service/inbound.go @@ -0,0 +1,40 @@ +package service + +import ( + "gorm.io/gorm" + "x-ui/database" + "x-ui/database/model" +) + +type InboundService struct { +} + +func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) { + db := database.GetDB() + var inbounds []*model.Inbound + err := db.Model(model.Inbound{}).Where("user_id = ?", userId).Find(&inbounds).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + return inbounds, nil +} + +func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) { + db := database.GetDB() + var inbounds []*model.Inbound + err := db.Model(model.Inbound{}).Find(&inbounds).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + return inbounds, nil +} + +func (s *InboundService) AddInbound(inbound *model.Inbound) error { + db := database.GetDB() + return db.Save(inbound).Error +} + +func (s *InboundService) DelInbound(id int) error { + db := database.GetDB() + return db.Delete(model.Inbound{}, id).Error +} diff --git a/web/controller/server_controller.go b/web/service/server.go similarity index 56% rename from web/controller/server_controller.go rename to web/service/server.go index 28821d87a9..2d0ea0ce44 100644 --- a/web/controller/server_controller.go +++ b/web/service/server.go @@ -1,20 +1,24 @@ -package controller +package service import ( + "archive/zip" "bytes" - "context" "encoding/json" - "github.com/gin-gonic/gin" + "fmt" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/host" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" "github.com/shirou/gopsutil/net" + "io" + "io/fs" "net/http" + "os" "runtime" "time" "x-ui/logger" + "x-ui/xray" ) type ProcessState string @@ -26,7 +30,8 @@ const ( ) type Status struct { - Cpu float64 `json:"cpu"` + T time.Time `json:"-"` + Cpu float64 `json:"cpu"` Mem struct { Current uint64 `json:"current"` Total uint64 `json:"total"` @@ -62,43 +67,15 @@ type Release struct { TagName string `json:"tag_name"` } -func stopServerController(a *ServerController) { - a.stopTask() +type ServerService struct { + xrayService XrayService } -type ServerController struct { - BaseController - - ctx context.Context - cancel context.CancelFunc - - lastStatus *Status - lastRefreshTime time.Time - lastGetStatusTime time.Time -} - -func NewServerController(g *gin.RouterGroup) *ServerController { - ctx, cancel := context.WithCancel(context.Background()) - a := &ServerController{ - ctx: ctx, - cancel: cancel, - lastGetStatusTime: time.Now(), - } - a.initRouter(g) - go a.runTask() - runtime.SetFinalizer(a, stopServerController) - return a -} - -func (a *ServerController) initRouter(g *gin.RouterGroup) { - g.POST("/server/status", a.status) - g.POST("/server/getXrayVersion", a.getXrayVersion) -} - -func (a *ServerController) refreshStatus() { - status := &Status{} - +func (s *ServerService) GetStatus(lastStatus *Status) *Status { now := time.Now() + status := &Status{ + T: now, + } percents, err := cpu.Percent(time.Second*2, false) if err != nil { @@ -153,11 +130,11 @@ func (a *ServerController) refreshStatus() { status.NetTraffic.Sent = ioStat.BytesSent status.NetTraffic.Recv = ioStat.BytesRecv - if a.lastStatus != nil { - duration := now.Sub(a.lastRefreshTime) + if lastStatus != nil { + duration := now.Sub(lastStatus.T) seconds := float64(duration) / float64(time.Second) - up := uint64(float64(status.NetTraffic.Sent-a.lastStatus.NetTraffic.Sent) / seconds) - down := uint64(float64(status.NetTraffic.Recv-a.lastStatus.NetTraffic.Recv) / seconds) + up := uint64(float64(status.NetTraffic.Sent-lastStatus.NetTraffic.Sent) / seconds) + down := uint64(float64(status.NetTraffic.Recv-lastStatus.NetTraffic.Recv) / seconds) status.NetIO.Up = up status.NetIO.Down = down } @@ -179,55 +156,28 @@ func (a *ServerController) refreshStatus() { status.UdpCount = len(udpConnStats) } - // TODO 临时 - status.Xray.State = Running - status.Xray.ErrorMsg = "" - status.Xray.Version = "1.0.0" - - a.lastStatus = status - a.lastRefreshTime = now -} - -func (a *ServerController) runTask() { - for { - select { - case <-a.ctx.Done(): - break - default: - } - now := time.Now() - if now.Sub(a.lastGetStatusTime) > time.Minute*3 { - time.Sleep(time.Second * 2) - continue + if s.xrayService.IsXrayRunning() { + status.Xray.State = Running + status.Xray.ErrorMsg = "" + } else { + err := s.xrayService.GetXrayErr() + if err != nil { + status.Xray.State = Error + } else { + status.Xray.State = Stop } - a.refreshStatus() + status.Xray.ErrorMsg = s.xrayService.GetXrayResult() } -} + status.Xray.Version = s.xrayService.GetXrayVersion() -func (a *ServerController) stopTask() { - a.cancel() + return status } -func (a *ServerController) status(c *gin.Context) { - a.lastGetStatusTime = time.Now() - - jsonMsgObj(c, "", a.lastStatus, nil) -} - -var lastVersions []string -var lastGetReleaseTime time.Time - -func (a *ServerController) getXrayVersion(c *gin.Context) { - now := time.Now() - if now.Sub(lastGetReleaseTime) <= time.Minute { - jsonMsgObj(c, "", lastVersions, nil) - return - } +func (s *ServerService) GetXrayVersions() ([]string, error) { url := "https://api.github.com/repos/XTLS/Xray-core/releases" resp, err := http.Get(url) if err != nil { - jsonMsg(c, "获取版本失败,请稍后尝试", err) - return + return nil, err } defer resp.Body.Close() @@ -235,22 +185,115 @@ func (a *ServerController) getXrayVersion(c *gin.Context) { buffer.Reset() _, err = buffer.ReadFrom(resp.Body) if err != nil { - jsonMsg(c, "获取版本失败,请稍后尝试", err) - return + return nil, err } releases := make([]Release, 0) err = json.Unmarshal(buffer.Bytes(), &releases) if err != nil { - jsonMsg(c, "获取版本失败,请向作者反馈此问题", err) - return + return nil, err } versions := make([]string, 0, len(releases)) for _, release := range releases { versions = append(versions, release.TagName) } - lastVersions = versions - lastGetReleaseTime = time.Now() + return versions, nil +} + +func (s *ServerService) downloadXRay(version string) (string, error) { + osName := runtime.GOOS + arch := runtime.GOARCH + + switch osName { + case "darwin": + osName = "macos" + } + + switch arch { + case "amd64": + arch = "64" + case "arm64": + arch = "arm64-v8a" + } + + fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch) + url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName) + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + + os.Remove(fileName) + file, err := os.Create(fileName) + if err != nil { + return "", err + } + defer file.Close() + + _, err = io.Copy(file, resp.Body) + if err != nil { + return "", err + } + + return fileName, nil +} + +func (s *ServerService) UpdateXray(version string) error { + zipFileName, err := s.downloadXRay(version) + if err != nil { + return err + } + + zipFile, err := os.Open(zipFileName) + if err != nil { + return err + } + defer func() { + zipFile.Close() + os.Remove(zipFileName) + }() + + stat, err := zipFile.Stat() + if err != nil { + return err + } + reader, err := zip.NewReader(zipFile, stat.Size()) + if err != nil { + return err + } + + s.xrayService.StopXray() + defer s.xrayService.StartXray() + + copyZipFile := func(zipName string, fileName string) error { + zipFile, err := reader.Open(zipName) + if err != nil { + return err + } + os.Remove(fileName) + file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm) + if err != nil { + return err + } + defer file.Close() + _, err = io.Copy(file, zipFile) + return err + } + + err = copyZipFile("xray", xray.GetBinaryPath()) + if err != nil { + return err + } + err = copyZipFile("geosite.dat", xray.GetGeositePath()) + if err != nil { + return err + } + err = copyZipFile("geoip.dat", xray.GetGeoipPath()) + if err != nil { + return err + } + + return nil - jsonMsgObj(c, "", versions, nil) } diff --git a/web/service/setting.go b/web/service/setting.go new file mode 100644 index 0000000000..0b17677fe1 --- /dev/null +++ b/web/service/setting.go @@ -0,0 +1,112 @@ +package service + +import ( + _ "embed" + "strconv" + "strings" + "x-ui/database" + "x-ui/database/model" + "x-ui/logger" + "x-ui/util/random" +) + +//go:embed config.json +var xrayTemplateConfig string + +type SettingService struct { +} + +func (s *SettingService) ClearSetting() error { + db := database.GetDB() + return db.Delete(model.Setting{}).Error +} + +func (s *SettingService) getSetting(key string) (*model.Setting, error) { + db := database.GetDB() + setting := &model.Setting{} + err := db.Model(model.Setting{}).Where("key = ?", key).First(setting).Error + if err != nil { + return nil, err + } + return setting, nil +} + +func (s *SettingService) saveSetting(key string, value string) error { + setting, err := s.getSetting(key) + db := database.GetDB() + if database.IsNotFound(err) { + return db.Create(&model.Setting{ + Key: key, + Value: value, + }).Error + } else if err != nil { + return err + } + setting.Key = key + setting.Value = value + return db.Save(setting).Error +} + +func (s *SettingService) getString(key string, defaultValue string) (string, error) { + setting, err := s.getSetting(key) + if database.IsNotFound(err) { + return defaultValue, nil + } else if err != nil { + return "", err + } + return setting.Value, nil +} + +func (s *SettingService) getInt(key string, defaultValue int) (int, error) { + str, err := s.getString(key, strconv.Itoa(defaultValue)) + if err != nil { + return 0, err + } + return strconv.Atoi(str) +} + +func (s *SettingService) GetXrayConfigTemplate() (string, error) { + return s.getString("xray_template_config", xrayTemplateConfig) +} + +func (s *SettingService) GetListen() (string, error) { + return s.getString("web_listen", "") +} + +func (s *SettingService) GetPort() (int, error) { + return s.getInt("web_port", 65432) +} + +func (s *SettingService) GetCertFile() (string, error) { + return s.getString("web_cert_file", "") +} + +func (s *SettingService) GetKeyFile() (string, error) { + return s.getString("web_key_file", "") +} + +func (s *SettingService) GetSecret() ([]byte, error) { + seq := random.Seq(32) + secret, err := s.getString("secret", seq) + if secret == seq { + err := s.saveSetting("secret", secret) + if err != nil { + logger.Warning("save secret failed:", err) + } + } + return []byte(secret), err +} + +func (s *SettingService) GetBasePath() (string, error) { + basePath, err := s.getString("web_base_path", "/") + if err != nil { + return "", err + } + if !strings.HasPrefix(basePath, "/") { + basePath = "/" + basePath + } + if !strings.HasSuffix(basePath, "/") { + basePath += "/" + } + return basePath, nil +} diff --git a/web/service/xray.go b/web/service/xray.go new file mode 100644 index 0000000000..a6334b27f8 --- /dev/null +++ b/web/service/xray.go @@ -0,0 +1,100 @@ +package service + +import ( + "encoding/json" + "errors" + "x-ui/util/common" + "x-ui/xray" +) + +var p *xray.Process +var result string + +type XrayService struct { + inboundService InboundService + settingService SettingService +} + +func (s *XrayService) IsXrayRunning() bool { + return p != nil && p.IsRunning() +} + +func (s *XrayService) GetXrayErr() error { + if p == nil { + return nil + } + return p.GetErr() +} + +func (s *XrayService) GetXrayResult() string { + if result != "" { + return result + } + if s.IsXrayRunning() { + return "" + } + if p == nil { + return "" + } + result = p.GetResult() + return result +} + +func (s *XrayService) GetXrayVersion() string { + if p == nil { + return "Unknown" + } + return p.GetVersion() +} + +func (s *XrayService) GetXrayConfig() (*xray.Config, error) { + templateConfig, err := s.settingService.GetXrayConfigTemplate() + if err != nil { + return nil, err + } + + xrayConfig := &xray.Config{} + err = json.Unmarshal([]byte(templateConfig), xrayConfig) + if err != nil { + return nil, err + } + + inbounds, err := s.inboundService.GetAllInbounds() + if err != nil { + return nil, err + } + for _, inbound := range inbounds { + inboundConfig := inbound.GenXrayInboundConfig() + xrayConfig.InboundConfigs = append(xrayConfig.InboundConfigs, *inboundConfig) + } + return xrayConfig, nil +} + +func (s *XrayService) StartXray() error { + if s.IsXrayRunning() { + return nil + } + + xrayConfig, err := s.GetXrayConfig() + if err != nil { + return err + } + + p = xray.NewProcess(xrayConfig) + err = p.Start() + result = "" + return err +} + +func (s *XrayService) StopXray() error { + if s.IsXrayRunning() { + return p.Stop() + } + return errors.New("xray is not running") +} + +func (s *XrayService) RestartXray() error { + err1 := s.StopXray() + err2 := s.StartXray() + return common.Combine(err1, err2) +} diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 7bb42cea18..0a288e7fb0 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -1,3 +1,12 @@ "username" = "username" "password" = "password" -"login" = "login" \ No newline at end of file +"login" = "login" +"confirm" = "confirm" +"cancel" = "cancel" +"close" = "close" +"copy" = "copy" +"copied" = "copied" +"download" = "download" +"remark" = "remark" +"enable" = "enable" +"protocol" = "protocol" \ No newline at end of file diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index 071bf5c943..9828ea53e3 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -1,3 +1,12 @@ "username" = "用户名" "password" = "密码" -"login" = "登录" \ No newline at end of file +"login" = "登录" +"confirm" = "confirm" +"cancel" = "cancel" +"close" = "close" +"copy" = "copy" +"copied" = "copied" +"download" = "download" +"remark" = "remark" +"enable" = "enable" +"protocol" = "protocol" \ No newline at end of file diff --git a/web/translation/translate.zh_Hant.toml b/web/translation/translate.zh_Hant.toml index 940a5fd690..34f65dcf7f 100644 --- a/web/translation/translate.zh_Hant.toml +++ b/web/translation/translate.zh_Hant.toml @@ -1,3 +1,12 @@ "username" = "用戶名" "password" = "密碼" -"login" = "登錄" \ No newline at end of file +"login" = "登錄" +"confirm" = "confirm" +"cancel" = "cancel" +"close" = "close" +"copy" = "copy" +"copied" = "copied" +"download" = "download" +"remark" = "remark" +"enable" = "enable" +"protocol" = "protocol" \ No newline at end of file diff --git a/web/web.go b/web/web.go index bb08b2a9e0..1706d786d1 100644 --- a/web/web.go +++ b/web/web.go @@ -15,10 +15,14 @@ import ( "net" "net/http" "os" + "runtime" + "strconv" + "time" "x-ui/config" "x-ui/logger" "x-ui/util/common" "x-ui/web/controller" + "x-ui/web/service" ) //go:embed assets/* @@ -38,22 +42,43 @@ func (f *wrapAssetsFS) Open(name string) (fs.File, error) { return f.FS.Open("assets/" + name) } +func stopServer(s *Server) { + s.Stop() +} + type Server struct { + *server +} + +func NewServer() *Server { + s := &Server{newServer()} + runtime.SetFinalizer(s, stopServer) + return s +} + +type server struct { listener net.Listener index *controller.IndexController server *controller.ServerController xui *controller.XUIController + xrayService service.XrayService + settingService service.SettingService + ctx context.Context cancel context.CancelFunc } -func NewServer() *Server { - return new(Server) +func newServer() *server { + ctx, cancel := context.WithCancel(context.Background()) + return &server{ + ctx: ctx, + cancel: cancel, + } } -func (s *Server) initRouter() (*gin.Engine, error) { +func (s *server) initRouter() (*gin.Engine, error) { if config.IsDebug() { gin.SetMode(gin.DebugMode) } else { @@ -64,9 +89,22 @@ func (s *Server) initRouter() (*gin.Engine, error) { engine := gin.Default() - store := cookie.NewStore(config.GetSecret()) + secret, err := s.settingService.GetSecret() + if err != nil { + return nil, err + } + + basePath, err := s.settingService.GetBasePath() + if err != nil { + return nil, err + } + + store := cookie.NewStore(secret) engine.Use(sessions.Sessions("session", store)) - err := s.initI18n(engine) + engine.Use(func(c *gin.Context) { + c.Set("base_path", basePath) + }) + err = s.initI18n(engine) if err != nil { return nil, err } @@ -74,7 +112,7 @@ func (s *Server) initRouter() (*gin.Engine, error) { if config.IsDebug() { // for develop engine.LoadHTMLGlob("web/html/**/*.html") - engine.StaticFS(config.GetBasePath()+"assets", http.FS(os.DirFS("web/assets"))) + engine.StaticFS(basePath+"assets", http.FS(os.DirFS("web/assets"))) } else { t := template.New("") t, err = t.ParseFS(htmlFS, "html/**/*.html") @@ -82,10 +120,10 @@ func (s *Server) initRouter() (*gin.Engine, error) { return nil, err } engine.SetHTMLTemplate(t) - engine.StaticFS(config.GetBasePath()+"assets", http.FS(&wrapAssetsFS{FS: assetsFS})) + engine.StaticFS(basePath+"assets", http.FS(&wrapAssetsFS{FS: assetsFS})) } - g := engine.Group(config.GetBasePath()) + g := engine.Group(basePath) s.index = controller.NewIndexController(g) s.server = controller.NewServerController(g) @@ -94,7 +132,7 @@ func (s *Server) initRouter() (*gin.Engine, error) { return engine, nil } -func (s *Server) initI18n(engine *gin.Engine) error { +func (s *server) initI18n(engine *gin.Engine) error { bundle := i18n.NewBundle(language.SimplifiedChinese) bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) err := fs.WalkDir(i18nFS, "translation", func(path string, d fs.DirEntry, err error) error { @@ -163,18 +201,66 @@ func (s *Server) initI18n(engine *gin.Engine) error { return nil } -func (s *Server) Run() error { +func (s *server) startTask() { + go func() { + err := s.xrayService.StartXray() + if err != nil { + logger.Warning("start xray failed:", err) + } + ticker := time.NewTicker(time.Second * 30) + defer ticker.Stop() + for { + select { + case <-s.ctx.Done(): + return + case <-ticker.C: + } + if s.xrayService.IsXrayRunning() { + continue + } + err := s.xrayService.StartXray() + if err != nil { + logger.Warning("start xray failed:", err) + } + } + }() +} + +func (s *server) Run() error { engine, err := s.initRouter() if err != nil { return err } - certFile := config.GetCertFile() - keyFile := config.GetKeyFile() + + s.startTask() + + certFile, err := s.settingService.GetCertFile() + if err != nil { + return err + } + keyFile, err := s.settingService.GetKeyFile() + if err != nil { + return err + } + listen, err := s.settingService.GetListen() + if err != nil { + return err + } + port, err := s.settingService.GetPort() + if err != nil { + return err + } + listenAddr := net.JoinHostPort(listen, strconv.Itoa(port)) if certFile != "" || keyFile != "" { - logger.Info("web server run https on", config.GetListen()) - return engine.RunTLS(config.GetListen(), certFile, keyFile) + logger.Info("web server run https on", listenAddr) + return engine.RunTLS(listenAddr, certFile, keyFile) } else { - logger.Info("web server run http on", config.GetListen()) - return engine.Run(config.GetListen()) + logger.Info("web server run http on", listenAddr) + return engine.Run(listenAddr) } } + +func (s *Server) Stop() error { + s.cancel() + return s.listener.Close() +} diff --git a/xray/config.go b/xray/config.go new file mode 100644 index 0000000000..86b5798cf5 --- /dev/null +++ b/xray/config.go @@ -0,0 +1,17 @@ +package xray + +import "encoding/json" + +type Config struct { + LogConfig json.RawMessage `json:"log"` + RouterConfig json.RawMessage `json:"routing"` + DNSConfig json.RawMessage `json:"dns"` + InboundConfigs []InboundConfig `json:"inbounds"` + OutboundConfigs json.RawMessage `json:"outbounds"` + Transport json.RawMessage `json:"transport"` + Policy json.RawMessage `json:"policy"` + API json.RawMessage `json:"api"` + Stats json.RawMessage `json:"stats"` + Reverse json.RawMessage `json:"reverse"` + FakeDNS json.RawMessage `json:"fakeDns"` +} diff --git a/xray/inbound.go b/xray/inbound.go new file mode 100644 index 0000000000..2b61d8db58 --- /dev/null +++ b/xray/inbound.go @@ -0,0 +1,13 @@ +package xray + +import "encoding/json" + +type InboundConfig struct { + Listen string `json:"listen"` + Port int `json:"port"` + Protocol string `json:"protocol"` + Settings json.RawMessage `json:"settings"` + StreamSettings json.RawMessage `json:"streamSettings"` + Tag string `json:"tag"` + Sniffing json.RawMessage `json:"sniffing"` +} diff --git a/xray/process.go b/xray/process.go new file mode 100644 index 0000000000..8e06a86aca --- /dev/null +++ b/xray/process.go @@ -0,0 +1,194 @@ +package xray + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "fmt" + "github.com/Workiva/go-datastructures/queue" + "io/fs" + "os" + "os/exec" + "runtime" + "strings" + "x-ui/util/common" +) + +func GetBinaryName() string { + return fmt.Sprintf("xray-%s-%s", runtime.GOOS, runtime.GOARCH) +} + +func GetBinaryPath() string { + return "bin/" + GetBinaryName() +} + +func GetConfigPath() string { + return "bin/config.json" +} + +func GetGeositePath() string { + return "bin/geosite.dat" +} + +func GetGeoipPath() string { + return "bin/geoip.dat" +} + +func stopProcess(p *Process) { + p.Stop() +} + +type Process struct { + *process +} + +func NewProcess(xrayConfig *Config) *Process { + p := &Process{newProcess(xrayConfig)} + runtime.SetFinalizer(p, stopProcess) + return p +} + +type process struct { + cmd *exec.Cmd + + version string + + xrayConfig *Config + lines *queue.Queue + exitErr error +} + +func newProcess(xrayConfig *Config) *process { + return &process{ + version: "Unknown", + xrayConfig: xrayConfig, + lines: queue.New(100), + } +} + +func (p *process) IsRunning() bool { + if p.cmd == nil || p.cmd.Process == nil { + return false + } + if p.cmd.ProcessState == nil { + return true + } + return false +} + +func (p *process) GetErr() error { + return p.exitErr +} + +func (p *process) GetResult() string { + items, _ := p.lines.TakeUntil(func(item interface{}) bool { + return true + }) + lines := make([]string, 0, len(items)) + for _, item := range items { + lines = append(lines, item.(string)) + } + return strings.Join(lines, "\n") +} + +func (p *process) GetVersion() string { + return p.version +} + +func (p *process) refreshVersion() { + cmd := exec.Command(GetBinaryPath(), "-version") + data, err := cmd.Output() + if err != nil { + p.version = "Unknown" + } else { + datas := bytes.Split(data, []byte(" ")) + if len(datas) <= 1 { + p.version = "Unknown" + } else { + p.version = string(datas[1]) + } + } +} + +func (p *process) Start() error { + if p.IsRunning() { + return errors.New("xray is already running") + } + + data, err := json.MarshalIndent(p.xrayConfig, "", " ") + if err != nil { + return err + } + configPath := GetConfigPath() + err = os.WriteFile(configPath, data, fs.ModePerm) + if err != nil { + return err + } + + cmd := exec.Command(GetBinaryPath(), "-c", configPath) + p.cmd = cmd + + stdReader, err := cmd.StdoutPipe() + if err != nil { + return err + } + errReader, err := cmd.StderrPipe() + if err != nil { + return err + } + + go func() { + defer func() { + common.Recover("") + stdReader.Close() + }() + reader := bufio.NewReaderSize(stdReader, 8192) + for { + line, _, err := reader.ReadLine() + if err != nil { + return + } + if p.lines.Len() >= 100 { + p.lines.Get(1) + } + p.lines.Put(string(line)) + } + }() + + go func() { + defer func() { + common.Recover("") + errReader.Close() + }() + reader := bufio.NewReaderSize(errReader, 8192) + for { + line, _, err := reader.ReadLine() + if err != nil { + return + } + if p.lines.Len() >= 100 { + p.lines.Get(1) + } + p.lines.Put(string(line)) + } + }() + + go func() { + err := cmd.Run() + if err != nil { + p.exitErr = err + } + }() + + p.refreshVersion() + + return nil +} + +func (p *process) Stop() error { + if !p.IsRunning() { + return errors.New("xray is not running") + } + return p.cmd.Process.Kill() +}