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 ]]
+ inModal.inbound.settings.delFallback(index)" style="color: rgb(255, 77, 79);cursor: pointer;"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ [[ method ]]
+
+
+
+
+
+
+
+ tcp+udp
+ tcp
+ udp
+
+
+
+
+
+
+
+
+
+
+
+ tcp
+ kcp
+ ws
+ http
+ quic
+
+
+
+
+
+
+
+
+
+ inModal.inbound.stream.tcp.type = checked ? 'http' : 'none'">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+ none(not camouflage)
+ srtp(camouflage video call)
+ utp(camouflage BT download)
+ wechat-video(camouflage WeChat video)
+ dtls(camouflage DTLS 1.2 packages)
+ wireguard(camouflage wireguard packages)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ none
+ aes-128-gcm
+ chacha20-poly1305
+
+
+
+
+
+
+
+ none(not camouflage)
+ srtp(camouflage video call)
+ utp(camouflage BT download)
+ wechat-video(camouflage WeChat video)
+ dtls(camouflage DTLS 1.2 packages)
+ wireguard(camouflage wireguard packages)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ tcp+udp
+ tcp
+ udp
+
+
+
+
+
+
+
+ inModal.inbound.settings.auth = checked ? 'password' : 'noauth'">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ inModal.inbound.stream.security = checked ? 'tls' : 'none'">
+
+
+
+
+
+
+
+
+
+
+
+
+{# #}
+{# #}
+{# #}
+
+
+ certificate file path
+ certificate file content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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 ]]
+
+
+
+
+
+
+ getDBInbounds()">
+
+ [[ inbound.protocol ]]
+
+
+ 查看
+
+
+ 查看
+
+
+ 启用
+ 禁用
+
+
+ [[ DateUtil.formatMillis(inbound.expiryTime) ]]
+ 无限期
+
+
+
+
+
+
+
+
+ [[ inbound.id ]]
+
+
+
+
+
+
+
+
+{{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 ]]
-
+
[[ line ]]
@@ -175,7 +175,6 @@ 请谨慎选择,旧版本可能配置不兼容
-