🎉 Start developing!
This commit is contained in:
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,6 @@ | ||||
| /config | ||||
| /letsencrypt | ||||
|  | ||||
| # Added by cargo | ||||
|  | ||||
| /target | ||||
|   | ||||
							
								
								
									
										2005
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										2005
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										15
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| [package] | ||||
| name = "roadsign" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
|  | ||||
| [dependencies] | ||||
| config = { version = "0.13.4", features = ["toml"] } | ||||
| poem = { version = "2.0.0", features = ["tokio-metrics"] } | ||||
| poem-openapi = "4.0.0" | ||||
| serde = "1.0.195" | ||||
| serde_json = "1.0.111" | ||||
| tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros", "time"] } | ||||
| tracing-subscriber = "0.3.18" | ||||
							
								
								
									
										4
									
								
								Settings.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								Settings.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| [listen] | ||||
| proxies = "0.0.0.0:80" | ||||
| proxies_tls = "0.0.0.0:443" | ||||
| sideload = "0.0.0.0:81" | ||||
							
								
								
									
										58
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								go.mod
									
									
									
									
									
								
							| @@ -1,58 +0,0 @@ | ||||
| module code.smartsheep.studio/goatworks/roadsign | ||||
|  | ||||
| go 1.21.4 | ||||
|  | ||||
| require ( | ||||
| 	github.com/gofiber/fiber/v2 v2.51.0 | ||||
| 	github.com/google/uuid v1.4.0 | ||||
| 	github.com/rs/zerolog v1.31.0 | ||||
| 	github.com/samber/lo v1.38.1 | ||||
| 	github.com/saracen/fastzip v0.1.11 | ||||
| 	github.com/spf13/viper v1.17.0 | ||||
| 	github.com/urfave/cli/v2 v2.26.0 | ||||
| 	github.com/valyala/fasthttp v1.50.0 | ||||
| ) | ||||
|  | ||||
| require ( | ||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect | ||||
| 	github.com/json-iterator/go v1.1.12 // indirect | ||||
| 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||
| 	github.com/modern-go/reflect2 v1.0.2 // indirect | ||||
| 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||||
| 	github.com/saracen/zipextra v0.0.0-20220303013732-0187cb0159ea // indirect | ||||
| 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect | ||||
| 	golang.org/x/sync v0.5.0 // indirect | ||||
| ) | ||||
|  | ||||
| require ( | ||||
| 	github.com/andybalholm/brotli v1.0.5 // indirect | ||||
| 	github.com/fsnotify/fsnotify v1.6.0 // indirect | ||||
| 	github.com/hashicorp/hcl v1.0.0 // indirect | ||||
| 	github.com/klauspost/compress v1.17.4 // indirect | ||||
| 	github.com/magiconair/properties v1.8.7 // indirect | ||||
| 	github.com/mattn/go-colorable v0.1.13 // indirect | ||||
| 	github.com/mattn/go-isatty v0.0.20 // indirect | ||||
| 	github.com/mattn/go-runewidth v0.0.15 // indirect | ||||
| 	github.com/mitchellh/mapstructure v1.5.0 // indirect | ||||
| 	github.com/pelletier/go-toml/v2 v2.1.0 // indirect | ||||
| 	github.com/philhofer/fwd v1.1.2 // indirect | ||||
| 	github.com/rivo/uniseg v0.2.0 // indirect | ||||
| 	github.com/sagikazarmark/locafero v0.3.0 // indirect | ||||
| 	github.com/sagikazarmark/slog-shim v0.1.0 // indirect | ||||
| 	github.com/sourcegraph/conc v0.3.0 // indirect | ||||
| 	github.com/spf13/afero v1.10.0 // indirect | ||||
| 	github.com/spf13/cast v1.5.1 // indirect | ||||
| 	github.com/spf13/pflag v1.0.5 // indirect | ||||
| 	github.com/subosito/gotenv v1.6.0 // indirect | ||||
| 	github.com/tinylib/msgp v1.1.8 // indirect | ||||
| 	github.com/valyala/bytebufferpool v1.0.0 // indirect | ||||
| 	github.com/valyala/tcplisten v1.0.0 // indirect | ||||
| 	go.uber.org/atomic v1.9.0 // indirect | ||||
| 	go.uber.org/multierr v1.9.0 // indirect | ||||
| 	golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect | ||||
| 	golang.org/x/sys v0.15.0 // indirect | ||||
| 	golang.org/x/text v0.13.0 // indirect | ||||
| 	gopkg.in/ini.v1 v1.67.0 // indirect | ||||
| 	gopkg.in/yaml.v2 v2.4.0 | ||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||
| ) | ||||
							
								
								
									
										577
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										577
									
								
								go.sum
									
									
									
									
									
								
							| @@ -1,577 +0,0 @@ | ||||
| cloud.google.com/go v0.26.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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= | ||||
| cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= | ||||
| cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= | ||||
| cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= | ||||
| cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= | ||||
| cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= | ||||
| cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= | ||||
| cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= | ||||
| cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= | ||||
| cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= | ||||
| cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= | ||||
| cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= | ||||
| cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= | ||||
| cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= | ||||
| cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= | ||||
| cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= | ||||
| cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= | ||||
| cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= | ||||
| cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= | ||||
| cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= | ||||
| cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= | ||||
| cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= | ||||
| cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= | ||||
| cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= | ||||
| cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= | ||||
| cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= | ||||
| cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= | ||||
| cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= | ||||
| cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= | ||||
| cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= | ||||
| cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= | ||||
| cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= | ||||
| cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= | ||||
| cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= | ||||
| cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= | ||||
| dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= | ||||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||||
| github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= | ||||
| github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= | ||||
| github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= | ||||
| github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | ||||
| 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-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= | ||||
| github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= | ||||
| github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= | ||||
| github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= | ||||
| github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= | ||||
| github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= | ||||
| github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| 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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= | ||||
| github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= | ||||
| 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/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= | ||||
| github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= | ||||
| github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= | ||||
| github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= | ||||
| github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= | ||||
| github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | ||||
| github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | ||||
| github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= | ||||
| github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ= | ||||
| github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U= | ||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||||
| github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| 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.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= | ||||
| github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= | ||||
| github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= | ||||
| github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= | ||||
| github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= | ||||
| 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/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= | ||||
| github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= | ||||
| github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= | ||||
| 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.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | ||||
| github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | ||||
| github.com/google/btree v1.0.0/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.4.1/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.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= | ||||
| github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||
| 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/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= | ||||
| github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= | ||||
| github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= | ||||
| github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= | ||||
| github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= | ||||
| github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= | ||||
| github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= | ||||
| github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= | ||||
| github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= | ||||
| github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= | ||||
| github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= | ||||
| github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= | ||||
| github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= | ||||
| github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||
| github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= | ||||
| github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||
| github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= | ||||
| github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= | ||||
| github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= | ||||
| github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||
| github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||
| github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= | ||||
| github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | ||||
| github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||||
| github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||||
| github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | ||||
| github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= | ||||
| github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | ||||
| github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= | ||||
| github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | ||||
| github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= | ||||
| github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= | ||||
| github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= | ||||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||||
| github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | ||||
| github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | ||||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||
| github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||
| github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= | ||||
| github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= | ||||
| github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= | ||||
| github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= | ||||
| github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||||
| github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||
| github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||||
| github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||
| github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= | ||||
| github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||||
| github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= | ||||
| github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | ||||
| github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||
| 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 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= | ||||
| github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= | ||||
| github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= | ||||
| github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= | ||||
| github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= | ||||
| github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= | ||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= | ||||
| github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= | ||||
| github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||
| github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | ||||
| github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= | ||||
| github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= | ||||
| github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= | ||||
| github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= | ||||
| github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= | ||||
| github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= | ||||
| github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||
| github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= | ||||
| github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= | ||||
| github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= | ||||
| github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= | ||||
| github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= | ||||
| github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= | ||||
| github.com/saracen/fastzip v0.1.11 h1:NnExbTEJbya7148cov09BCxwfur9tQ5BQ1QyQH6XleA= | ||||
| github.com/saracen/fastzip v0.1.11/go.mod h1:/lN5BiU451/OZMS+hfhVsSDj/RNrxYmO9EYxCtMrFrY= | ||||
| github.com/saracen/zipextra v0.0.0-20220303013732-0187cb0159ea h1:8czYLkvzZRE+AElIQeDffQdgR+CC3wKEFILYU/1PeX4= | ||||
| github.com/saracen/zipextra v0.0.0-20220303013732-0187cb0159ea/go.mod h1:hnzuad9d2wdd3z8fC6UouHQK5qZxqv3F/E6MMzXc7q0= | ||||
| github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= | ||||
| github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= | ||||
| github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= | ||||
| github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= | ||||
| github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= | ||||
| github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= | ||||
| github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||||
| github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= | ||||
| github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||||
| github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||
| 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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||||
| github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||||
| github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||||
| github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= | ||||
| github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= | ||||
| github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= | ||||
| github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= | ||||
| github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI= | ||||
| github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= | ||||
| github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||||
| github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | ||||
| github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= | ||||
| github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= | ||||
| github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= | ||||
| github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= | ||||
| github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= | ||||
| github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= | ||||
| github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||
| go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | ||||
| go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= | ||||
| go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||||
| go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||||
| go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||||
| go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= | ||||
| go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= | ||||
| go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= | ||||
| go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= | ||||
| go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= | ||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= | ||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= | ||||
| golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= | ||||
| golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= | ||||
| golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||||
| golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||||
| golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||||
| golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= | ||||
| golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= | ||||
| golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= | ||||
| golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= | ||||
| golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | ||||
| golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | ||||
| 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||
| golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||||
| golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||||
| golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||||
| golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||||
| golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= | ||||
| golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= | ||||
| golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= | ||||
| golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= | ||||
| golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= | ||||
| golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= | ||||
| golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= | ||||
| golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= | ||||
| golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | ||||
| golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | ||||
| golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||||
| golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||
| 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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||
| golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||
| golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||
| golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= | ||||
| golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||||
| golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||
| golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||
| golang.org/x/net v0.0.0-20200822124328-c89045814202/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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||
| golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||
| golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= | ||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||
| golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= | ||||
| golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= | ||||
| golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= | ||||
| golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= | ||||
| 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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= | ||||
| golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= | ||||
| golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||
| golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= | ||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| 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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||
| golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||
| golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= | ||||
| golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | ||||
| golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||||
| golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||||
| golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||||
| golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||||
| golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||||
| golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= | ||||
| golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= | ||||
| golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= | ||||
| golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||
| golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||
| golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||
| golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||
| golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||
| golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||
| golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||
| golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||
| golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||
| golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||||
| golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= | ||||
| golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= | ||||
| golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= | ||||
| golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | ||||
| golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | ||||
| golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | ||||
| golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | ||||
| golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||||
| golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||||
| golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | ||||
| golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= | ||||
| golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||
| golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||
| golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||
| golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||
| golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||
| golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= | ||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||
| golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= | ||||
| 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= | ||||
| google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= | ||||
| google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= | ||||
| google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= | ||||
| google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= | ||||
| google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= | ||||
| google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= | ||||
| google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= | ||||
| google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= | ||||
| google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= | ||||
| google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= | ||||
| google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= | ||||
| google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= | ||||
| google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= | ||||
| google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= | ||||
| google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= | ||||
| google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= | ||||
| google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= | ||||
| google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= | ||||
| google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= | ||||
| google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||||
| google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||||
| google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= | ||||
| google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | ||||
| google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | ||||
| google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | ||||
| google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | ||||
| google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||||
| google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||||
| google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||||
| google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||||
| google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= | ||||
| google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= | ||||
| google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= | ||||
| google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||||
| google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||||
| google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||||
| google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||||
| google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||||
| google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||||
| google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= | ||||
| google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||||
| google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||||
| google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||||
| google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||||
| google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||||
| google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||||
| google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||||
| google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||||
| google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= | ||||
| google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= | ||||
| google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= | ||||
| google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | ||||
| google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= | ||||
| google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= | ||||
| 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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||||
| google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||||
| google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||||
| google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= | ||||
| google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= | ||||
| google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= | ||||
| google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= | ||||
| google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= | ||||
| google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= | ||||
| google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= | ||||
| google.golang.org/grpc v1.35.0/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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= | ||||
| google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= | ||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | ||||
| gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= | ||||
| gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| gopkg.in/yaml.v2 v2.2.2/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| 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-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||
| honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||
| honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= | ||||
| honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= | ||||
| honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= | ||||
| rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= | ||||
| rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= | ||||
| rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= | ||||
| @@ -1,89 +0,0 @@ | ||||
| package conn | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"github.com/samber/lo" | ||||
| 	"github.com/spf13/viper" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| ) | ||||
|  | ||||
| var CliCommands = []*cli.Command{ | ||||
| 	{ | ||||
| 		Name:        "list", | ||||
| 		Aliases:     []string{"ls"}, | ||||
| 		Description: "List all connected remote server", | ||||
| 		Action: func(ctx *cli.Context) error { | ||||
| 			var servers []CliConnection | ||||
| 			raw, _ := json.Marshal(viper.Get("servers")) | ||||
| 			_ = json.Unmarshal(raw, &servers) | ||||
|  | ||||
| 			log.Info().Msgf("There are %d server(s) connected in total.", len(servers)) | ||||
| 			for idx, server := range servers { | ||||
| 				log.Info().Msgf("%d) %s: %s", idx+1, server.ID, server.Url) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Name:        "connect", | ||||
| 		Aliases:     []string{"add"}, | ||||
| 		Description: "Connect and save configuration of remote server", | ||||
| 		ArgsUsage:   "<id> <server url> <credential>", | ||||
| 		Action: func(ctx *cli.Context) error { | ||||
| 			if ctx.Args().Len() < 3 { | ||||
| 				return fmt.Errorf("must have three arguments: <id> <server url> <credential>") | ||||
| 			} | ||||
|  | ||||
| 			c := CliConnection{ | ||||
| 				ID:         ctx.Args().Get(0), | ||||
| 				Url:        ctx.Args().Get(1), | ||||
| 				Credential: ctx.Args().Get(2), | ||||
| 			} | ||||
|  | ||||
| 			if err := c.CheckConnectivity(); err != nil { | ||||
| 				return fmt.Errorf("couldn't connect server: %s", err.Error()) | ||||
| 			} else { | ||||
| 				var servers []CliConnection | ||||
| 				raw, _ := json.Marshal(viper.Get("servers")) | ||||
| 				_ = json.Unmarshal(raw, &servers) | ||||
| 				viper.Set("servers", append(servers, c)) | ||||
|  | ||||
| 				if err := viper.WriteConfig(); err != nil { | ||||
| 					return err | ||||
| 				} else { | ||||
| 					log.Info().Msg("Successfully connected a new remote server, enter \"rds ls\" to get more info.") | ||||
| 					return nil | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Name:        "disconnect", | ||||
| 		Aliases:     []string{"remove"}, | ||||
| 		Description: "Remove a remote server configuration", | ||||
| 		ArgsUsage:   "<id>", | ||||
| 		Action: func(ctx *cli.Context) error { | ||||
| 			if ctx.Args().Len() < 1 { | ||||
| 				return fmt.Errorf("must have more one arguments: <server url>") | ||||
| 			} | ||||
|  | ||||
| 			var servers []CliConnection | ||||
| 			raw, _ := json.Marshal(viper.Get("servers")) | ||||
| 			_ = json.Unmarshal(raw, &servers) | ||||
| 			viper.Set("servers", lo.Filter(servers, func(item CliConnection, idx int) bool { | ||||
| 				return item.ID != ctx.Args().Get(0) | ||||
| 			})) | ||||
|  | ||||
| 			if err := viper.WriteConfig(); err != nil { | ||||
| 				return err | ||||
| 			} else { | ||||
| 				log.Info().Msg("Successfully disconnected a remote server, enter \"rds ls\" to get more info.") | ||||
| 				return nil | ||||
| 			} | ||||
| 		}, | ||||
| 	}, | ||||
| } | ||||
| @@ -1,42 +0,0 @@ | ||||
| package conn | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	roadsign "code.smartsheep.studio/goatworks/roadsign/pkg" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| ) | ||||
|  | ||||
| type CliConnection struct { | ||||
| 	ID         string `json:"id"` | ||||
| 	Url        string `json:"url"` | ||||
| 	Credential string `json:"credential"` | ||||
| } | ||||
|  | ||||
| func (v CliConnection) CheckConnectivity() error { | ||||
| 	client := fiber.Get(v.Url + "/cgi/connectivity") | ||||
| 	client.BasicAuth("RoadSign CLI", v.Credential) | ||||
|  | ||||
| 	if status, data, err := client.Bytes(); len(err) > 0 { | ||||
| 		return fmt.Errorf("couldn't connect to server: %q", err) | ||||
| 	} else if status != 200 { | ||||
| 		return fmt.Errorf("server rejected request, may cause by invalid credential") | ||||
| 	} else { | ||||
| 		var resp fiber.Map | ||||
| 		if err := json.Unmarshal(data, &resp); err != nil { | ||||
| 			return err | ||||
| 		} else if resp["server"] != "RoadSign" { | ||||
| 			return fmt.Errorf("remote server isn't roadsign") | ||||
| 		} else if resp["version"] != roadsign.AppVersion { | ||||
| 			if strings.Contains(roadsign.AppVersion, "#") { | ||||
| 				return fmt.Errorf("remote server version mismatch client version, update or downgrade client required") | ||||
| 			} else { | ||||
| 				log.Warn().Msg("RoadSign CLI didn't complied with vcs information, compatibility was disabled. To enable it, reinstall cli with -buildvcs flag.") | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -1,17 +0,0 @@ | ||||
| package conn | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
|  | ||||
| 	"github.com/samber/lo" | ||||
| 	"github.com/spf13/viper" | ||||
| ) | ||||
|  | ||||
| func GetConnection(id string) (CliConnection, bool) { | ||||
| 	var servers []CliConnection | ||||
| 	raw, _ := json.Marshal(viper.Get("servers")) | ||||
| 	_ = json.Unmarshal(raw, &servers) | ||||
| 	return lo.Find(servers, func(item CliConnection) bool { | ||||
| 		return item.ID == id | ||||
| 	}) | ||||
| } | ||||
| @@ -1,101 +0,0 @@ | ||||
| package deploy | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	jsoniter "github.com/json-iterator/go" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/cmd/rds/conn" | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/sign" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"gopkg.in/yaml.v2" | ||||
| ) | ||||
|  | ||||
| var DeployCommands = []*cli.Command{ | ||||
| 	{ | ||||
| 		Name:      "deploy", | ||||
| 		Aliases:   []string{"dp"}, | ||||
| 		ArgsUsage: "<server> <site> <upstream> [path]", | ||||
| 		Action: func(ctx *cli.Context) error { | ||||
| 			if ctx.Args().Len() < 4 { | ||||
| 				return fmt.Errorf("must have four arguments: <server> <site> <upstream> <path>") | ||||
| 			} | ||||
|  | ||||
| 			if !strings.HasSuffix(ctx.Args().Get(3), ".zip") { | ||||
| 				return fmt.Errorf("input file must be a zip file and ends with .zip") | ||||
| 			} | ||||
|  | ||||
| 			server, ok := conn.GetConnection(ctx.Args().Get(0)) | ||||
| 			if !ok { | ||||
| 				return fmt.Errorf("server was not found, use \"rds connect\" add one first") | ||||
| 			} else if err := server.CheckConnectivity(); err != nil { | ||||
| 				return fmt.Errorf("couldn't connect server: %s", err.Error()) | ||||
| 			} | ||||
|  | ||||
| 			// Send request | ||||
| 			log.Info().Msg("Now publishing to remote server...") | ||||
|  | ||||
| 			url := fmt.Sprintf("/webhooks/publish/%s/%s?mimetype=%s", ctx.Args().Get(1), ctx.Args().Get(2), "application/zip") | ||||
| 			client := fiber.Put(server.Url+url). | ||||
| 				SendFile(ctx.Args().Get(3), "attachments"). | ||||
| 				MultipartForm(nil). | ||||
| 				BasicAuth("RoadSign CLI", server.Credential) | ||||
|  | ||||
| 			if status, data, err := client.Bytes(); len(err) > 0 { | ||||
| 				return fmt.Errorf("failed to publish to remote: %q", err) | ||||
| 			} else if status != 200 { | ||||
| 				return fmt.Errorf("server rejected request, status code %d, response %s", status, string(data)) | ||||
| 			} | ||||
|  | ||||
| 			log.Info().Msg("Well done! Your site is successfully published! 🎉") | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Name:      "sync", | ||||
| 		Aliases:   []string{"sc"}, | ||||
| 		ArgsUsage: "<server> <site> <configuration path>", | ||||
| 		Action: func(ctx *cli.Context) error { | ||||
| 			if ctx.Args().Len() < 3 { | ||||
| 				return fmt.Errorf("must have three arguments: <server> <site> <configuration path>") | ||||
| 			} | ||||
|  | ||||
| 			server, ok := conn.GetConnection(ctx.Args().Get(0)) | ||||
| 			if !ok { | ||||
| 				return fmt.Errorf("server was not found, use \"rds connect\" add one first") | ||||
| 			} else if err := server.CheckConnectivity(); err != nil { | ||||
| 				return fmt.Errorf("couldn't connect server: %s", err.Error()) | ||||
| 			} | ||||
|  | ||||
| 			var site sign.SiteConfig | ||||
| 			if file, err := os.Open(ctx.Args().Get(2)); err != nil { | ||||
| 				return err | ||||
| 			} else { | ||||
| 				raw, _ := io.ReadAll(file) | ||||
| 				yaml.Unmarshal(raw, &site) | ||||
| 			} | ||||
|  | ||||
| 			url := fmt.Sprintf("/webhooks/sync/%s", ctx.Args().Get(1)) | ||||
| 			client := fiber.Put(server.Url+url). | ||||
| 				JSONEncoder(jsoniter.ConfigCompatibleWithStandardLibrary.Marshal). | ||||
| 				JSONDecoder(jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal). | ||||
| 				JSON(site). | ||||
| 				BasicAuth("RoadSign CLI", server.Credential) | ||||
|  | ||||
| 			if status, data, err := client.Bytes(); len(err) > 0 { | ||||
| 				return fmt.Errorf("failed to sync to remote: %q", err) | ||||
| 			} else if status != 200 { | ||||
| 				return fmt.Errorf("server rejected request, status code %d, response %s", status, string(data)) | ||||
| 			} | ||||
|  | ||||
| 			log.Info().Msg("Well done! Your site configuration is up-to-date! 🎉") | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 	}, | ||||
| } | ||||
| @@ -1,48 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
|  | ||||
| 	roadsign "code.smartsheep.studio/goatworks/roadsign/pkg" | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/cmd/rds/conn" | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/cmd/rds/deploy" | ||||
| 	"github.com/rs/zerolog" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"github.com/spf13/viper" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	zerolog.TimeFieldFormat = zerolog.TimeFormatUnix | ||||
| 	log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	// Configure settings | ||||
| 	viper.AddConfigPath("$HOME") | ||||
| 	viper.SetConfigName(".roadsignrc") | ||||
| 	viper.SetConfigType("yaml") | ||||
|  | ||||
| 	// Load settings | ||||
| 	if err := viper.ReadInConfig(); err != nil { | ||||
| 		if _, ok := err.(viper.ConfigFileNotFoundError); ok { | ||||
| 			viper.SafeWriteConfig() | ||||
| 			viper.ReadInConfig() | ||||
| 		} else { | ||||
| 			log.Panic().Err(err).Msg("An error occurred when loading settings.") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Configure CLI | ||||
| 	app := &cli.App{ | ||||
| 		Name:     "RoadSign CLI", | ||||
| 		Version:  roadsign.AppVersion, | ||||
| 		Suggest:  true, | ||||
| 		Commands: append(append([]*cli.Command{}, conn.CliCommands...), deploy.DeployCommands...), | ||||
| 	} | ||||
|  | ||||
| 	// Run CLI | ||||
| 	if err := app.Run(os.Args); err != nil { | ||||
| 		log.Fatal().Err(err).Msg("An error occurred when running cli.") | ||||
| 	} | ||||
| } | ||||
| @@ -1,86 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
|  | ||||
| 	roadsign "code.smartsheep.studio/goatworks/roadsign/pkg" | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/hypertext" | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/sideload" | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/sign" | ||||
| 	"github.com/google/uuid" | ||||
| 	"github.com/rs/zerolog" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"github.com/spf13/viper" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	zerolog.TimeFieldFormat = zerolog.TimeFormatUnix | ||||
| 	log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	// Configure settings | ||||
| 	viper.AddConfigPath(".") | ||||
| 	viper.AddConfigPath("..") | ||||
| 	viper.SetConfigName("settings") | ||||
| 	viper.SetConfigType("yaml") | ||||
|  | ||||
| 	// Load settings | ||||
| 	if err := viper.ReadInConfig(); err != nil { | ||||
| 		log.Panic().Err(err).Msg("An error occurred when loading settings.") | ||||
| 	} | ||||
|  | ||||
| 	// Present settings | ||||
| 	if len(viper.GetString("security.credential")) <= 0 { | ||||
| 		credential := strings.ReplaceAll(uuid.NewString(), "-", "") | ||||
| 		viper.Set("security.credential", credential) | ||||
| 		_ = viper.WriteConfig() | ||||
|  | ||||
| 		log.Warn().Msg("There isn't any api credential configured in settings.yml, auto generated a credential for api accessing.") | ||||
| 		log.Warn().Msgf("RoadSign auto generated api credential is %s", credential) | ||||
| 	} | ||||
|  | ||||
| 	// Load & init sign | ||||
| 	if err := sign.ReadInConfig(viper.GetString("paths.configs")); err != nil { | ||||
| 		log.Panic().Err(err).Msg("An error occurred when loading configurations.") | ||||
| 	} else { | ||||
| 		log.Info().Int("count", len(sign.App.Sites)).Msg("All configuration has been loaded.") | ||||
| 	} | ||||
|  | ||||
| 	// Preheat processes | ||||
| 	go func() { | ||||
| 		log.Info().Msg("Preheating processes...") | ||||
| 		sign.App.PreheatProcesses(func(total int, success int) { | ||||
| 			log.Info().Int("requested", total).Int("succeed", success).Msgf("Preheat processes completed!") | ||||
| 		}) | ||||
| 	}() | ||||
|  | ||||
| 	// Init hypertext server | ||||
| 	hypertext.RunServer( | ||||
| 		hypertext.InitServer(), | ||||
| 		viper.GetStringSlice("hypertext.ports"), | ||||
| 		viper.GetStringSlice("hypertext.secured_ports"), | ||||
| 		viper.GetString("hypertext.certificate.pem"), | ||||
| 		viper.GetString("hypertext.certificate.key"), | ||||
| 	) | ||||
|  | ||||
| 	// Init sideload server | ||||
| 	hypertext.RunServer( | ||||
| 		sideload.InitSideload(), | ||||
| 		viper.GetStringSlice("hypertext.sideload_ports"), | ||||
| 		viper.GetStringSlice("hypertext.sideload_secured_ports"), | ||||
| 		viper.GetString("hypertext.certificate.sideload_pem"), | ||||
| 		viper.GetString("hypertext.certificate.sideload_key"), | ||||
| 	) | ||||
|  | ||||
| 	log.Info().Msgf("RoadSign v%s is started...", roadsign.AppVersion) | ||||
|  | ||||
| 	quit := make(chan os.Signal, 1) | ||||
| 	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) | ||||
| 	<-quit | ||||
|  | ||||
| 	log.Info().Msgf("RoadSign v%s is quitting...", roadsign.AppVersion) | ||||
| } | ||||
| @@ -1,111 +0,0 @@ | ||||
| package hypertext | ||||
|  | ||||
| import ( | ||||
| 	"regexp" | ||||
|  | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/sign" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| func UseProxies(app *fiber.App) { | ||||
| 	app.All("/*", func(ctx *fiber.Ctx) error { | ||||
| 		host := ctx.Hostname() | ||||
| 		path := ctx.Path() | ||||
| 		queries := ctx.Queries() | ||||
| 		headers := ctx.GetReqHeaders() | ||||
|  | ||||
| 		// Filtering sites | ||||
| 		for _, site := range sign.App.Sites { | ||||
| 			// Matching rules | ||||
| 			for _, rule := range site.Rules { | ||||
| 				if !lo.Contains(rule.Host, host) { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				if !func() bool { | ||||
| 					flag := false | ||||
| 					for _, pattern := range rule.Path { | ||||
| 						if ok, _ := regexp.MatchString(pattern, path); ok { | ||||
| 							flag = true | ||||
| 							break | ||||
| 						} | ||||
| 					} | ||||
| 					return flag | ||||
| 				}() { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				// Filter query strings | ||||
| 				flag := true | ||||
| 				for rk, rv := range rule.Queries { | ||||
| 					for ik, iv := range queries { | ||||
| 						if rk != ik && rv != iv { | ||||
| 							flag = false | ||||
| 							break | ||||
| 						} | ||||
| 					} | ||||
| 					if !flag { | ||||
| 						break | ||||
| 					} | ||||
| 				} | ||||
| 				if !flag { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				// Filter headers | ||||
| 				for rk, rv := range rule.Headers { | ||||
| 					for ik, iv := range headers { | ||||
| 						if rk == ik { | ||||
| 							for _, ov := range iv { | ||||
| 								if !lo.Contains(rv, ov) { | ||||
| 									flag = false | ||||
| 									break | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 						if !flag { | ||||
| 							break | ||||
| 						} | ||||
| 					} | ||||
| 					if !flag { | ||||
| 						break | ||||
| 					} | ||||
| 				} | ||||
| 				if !flag { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				// Passing all the rules means the site is what we are looking for. | ||||
| 				// Let us respond to our client! | ||||
| 				return makeResponse(ctx, site) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// There is no site available for this request. | ||||
| 		// Just ignore it and give our client a not found status. | ||||
| 		// Do not care about the user experience, we can do it in custom error handler. | ||||
| 		return fiber.ErrNotFound | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func makeResponse(ctx *fiber.Ctx, site *sign.SiteConfig) error { | ||||
| 	// Modify request | ||||
| 	for _, transformer := range site.Transformers { | ||||
| 		if err := transformer.TransformRequest(ctx); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Forward | ||||
| 	err := sign.App.Forward(ctx, site) | ||||
|  | ||||
| 	// Modify response | ||||
| 	for _, transformer := range site.Transformers { | ||||
| 		if err := transformer.TransformResponse(ctx); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return err | ||||
| } | ||||
| @@ -1,79 +0,0 @@ | ||||
| package hypertext | ||||
|  | ||||
| import ( | ||||
| 	jsoniter "github.com/json-iterator/go" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/gofiber/fiber/v2/middleware/limiter" | ||||
| 	"github.com/gofiber/fiber/v2/middleware/logger" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"github.com/spf13/viper" | ||||
| ) | ||||
|  | ||||
| func InitServer() *fiber.App { | ||||
| 	app := fiber.New(fiber.Config{ | ||||
| 		AppName:               "RoadSign", | ||||
| 		ServerHeader:          "RoadSign", | ||||
| 		DisableStartupMessage: true, | ||||
| 		EnableIPValidation:    true, | ||||
| 		JSONDecoder:           jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal, | ||||
| 		JSONEncoder:           jsoniter.ConfigCompatibleWithStandardLibrary.Marshal, | ||||
| 		Prefork:               viper.GetBool("performance.prefork"), | ||||
| 		BodyLimit:             viper.GetInt("hypertext.limitation.max_body_size"), | ||||
| 	}) | ||||
|  | ||||
| 	if viper.GetBool("performance.request_logging") { | ||||
| 		app.Use(logger.New(logger.Config{ | ||||
| 			Output: log.Logger, | ||||
| 			Format: "[Proxies] [${time}] ${status} - ${latency} ${method} ${path}\n", | ||||
| 		})) | ||||
| 	} | ||||
|  | ||||
| 	if viper.GetInt("hypertext.limitation.max_qps") > 0 { | ||||
| 		app.Use(limiter.New(limiter.Config{ | ||||
| 			Max:        viper.GetInt("hypertext.limitation.max_qps"), | ||||
| 			Expiration: 1 * time.Second, | ||||
| 		})) | ||||
| 	} | ||||
|  | ||||
| 	UseProxies(app) | ||||
|  | ||||
| 	return app | ||||
| } | ||||
|  | ||||
| func RunServer(app *fiber.App, ports []string, securedPorts []string, pem string, key string) { | ||||
| 	for _, port := range ports { | ||||
| 		port := port | ||||
| 		go func() { | ||||
| 			if viper.GetBool("hypertext.certificate.redirect") { | ||||
| 				redirector := fiber.New(fiber.Config{ | ||||
| 					AppName:               "RoadSign", | ||||
| 					ServerHeader:          "RoadSign", | ||||
| 					DisableStartupMessage: true, | ||||
| 					EnableIPValidation:    true, | ||||
| 				}) | ||||
| 				redirector.All("/", func(c *fiber.Ctx) error { | ||||
| 					return c.Redirect(strings.ReplaceAll(string(c.Request().URI().FullURI()), "http", "https")) | ||||
| 				}) | ||||
| 				if err := redirector.Listen(port); err != nil { | ||||
| 					log.Panic().Err(err).Msg("An error occurred when listening hypertext common ports.") | ||||
| 				} | ||||
| 			} else { | ||||
| 				if err := app.Listen(port); err != nil { | ||||
| 					log.Panic().Err(err).Msg("An error occurred when listening hypertext common ports.") | ||||
| 				} | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	for _, port := range securedPorts { | ||||
| 		port := port | ||||
| 		go func() { | ||||
| 			if err := app.ListenTLS(port, pem, key); err != nil { | ||||
| 				log.Panic().Err(err).Msg("An error occurred when listening hypertext tls ports.") | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										17
									
								
								pkg/meta.go
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								pkg/meta.go
									
									
									
									
									
								
							| @@ -1,17 +0,0 @@ | ||||
| package roadsign | ||||
|  | ||||
| import ( | ||||
| 	"runtime/debug" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	if info, ok := debug.ReadBuildInfo(); ok { | ||||
| 		for _, setting := range info.Settings { | ||||
| 			if setting.Key == "vcs.revision" { | ||||
| 				AppVersion += "#" + setting.Value | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var AppVersion = "1.2.1" | ||||
| @@ -1,13 +0,0 @@ | ||||
| package sideload | ||||
|  | ||||
| import ( | ||||
| 	roadsign "code.smartsheep.studio/goatworks/roadsign/pkg" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| ) | ||||
|  | ||||
| func responseConnectivity(c *fiber.Ctx) error { | ||||
| 	return c.Status(fiber.StatusOK).JSON(fiber.Map{ | ||||
| 		"server":  "RoadSign", | ||||
| 		"version": roadsign.AppVersion, | ||||
| 	}) | ||||
| } | ||||
| @@ -1,29 +0,0 @@ | ||||
| package sideload | ||||
|  | ||||
| import ( | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/sign" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| func getProcesses(c *fiber.Ctx) error { | ||||
| 	processes := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.ProcessInstance { | ||||
| 		return item.Processes | ||||
| 	}) | ||||
|  | ||||
| 	return c.JSON(processes) | ||||
| } | ||||
|  | ||||
| func getProcessLog(c *fiber.Ctx) error { | ||||
| 	processes := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.ProcessInstance { | ||||
| 		return item.Processes | ||||
| 	}) | ||||
|  | ||||
| 	if target, ok := lo.Find(processes, func(item *sign.ProcessInstance) bool { | ||||
| 		return item.ID == c.Params("id") | ||||
| 	}); !ok { | ||||
| 		return fiber.NewError(fiber.StatusNotFound) | ||||
| 	} else { | ||||
| 		return c.SendString(target.GetLogs()) | ||||
| 	} | ||||
| } | ||||
| @@ -1,85 +0,0 @@ | ||||
| package sideload | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/sign" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/google/uuid" | ||||
| 	"github.com/samber/lo" | ||||
| 	"github.com/saracen/fastzip" | ||||
| ) | ||||
|  | ||||
| func doPublish(c *fiber.Ctx) error { | ||||
| 	var workdir string | ||||
| 	var site *sign.SiteConfig | ||||
| 	var upstream *sign.UpstreamInstance | ||||
| 	var process *sign.ProcessInstance | ||||
| 	for _, item := range sign.App.Sites { | ||||
| 		if item.ID == c.Params("site") { | ||||
| 			site = item | ||||
| 			for _, stream := range item.Upstreams { | ||||
| 				if stream.ID == c.Params("slug") { | ||||
| 					upstream = stream | ||||
| 					workdir, _ = stream.GetRawURI() | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			for _, proc := range item.Processes { | ||||
| 				if proc.ID == c.Params("slug") { | ||||
| 					process = proc | ||||
| 					workdir = proc.Workdir | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if upstream == nil && process == nil { | ||||
| 		return fiber.ErrNotFound | ||||
| 	} else if upstream != nil && upstream.GetType() != sign.UpstreamTypeFile { | ||||
| 		return fiber.ErrUnprocessableEntity | ||||
| 	} | ||||
|  | ||||
| 	for _, process := range site.Processes { | ||||
| 		process.StopProcess() | ||||
| 	} | ||||
|  | ||||
| 	if c.Query("overwrite", "yes") == "yes" { | ||||
| 		files, _ := filepath.Glob(filepath.Join(workdir, "*")) | ||||
| 		for _, file := range files { | ||||
| 			_ = os.Remove(file) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if form, err := c.MultipartForm(); err == nil { | ||||
| 		files := form.File["attachments"] | ||||
| 		for _, file := range files { | ||||
| 			mimetype := lo.Ternary(len(c.Query("mimetype")) > 0, c.Query("mimetype"), file.Header["Content-Type"][0]) | ||||
| 			switch mimetype { | ||||
| 			case "application/zip": | ||||
| 				dst := filepath.Join(os.TempDir(), uuid.NewString()+".zip") | ||||
| 				if err := c.SaveFile(file, dst); err != nil { | ||||
| 					return err | ||||
| 				} else { | ||||
| 					if ex, err := fastzip.NewExtractor(dst, workdir); err != nil { | ||||
| 						return err | ||||
| 					} else if err = ex.Extract(context.Background()); err != nil { | ||||
| 						defer ex.Close() | ||||
| 						return err | ||||
| 					} | ||||
| 				} | ||||
| 			default: | ||||
| 				dst := filepath.Join(workdir, file.Filename) | ||||
| 				if err := c.SaveFile(file, dst); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return c.SendStatus(fiber.StatusOK) | ||||
| } | ||||
| @@ -1,69 +0,0 @@ | ||||
| package sideload | ||||
|  | ||||
| import ( | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/sideload/view" | ||||
| 	"fmt" | ||||
| 	"github.com/gofiber/fiber/v2/middleware/filesystem" | ||||
| 	jsoniter "github.com/json-iterator/go" | ||||
| 	"net/http" | ||||
|  | ||||
| 	roadsign "code.smartsheep.studio/goatworks/roadsign/pkg" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/gofiber/fiber/v2/middleware/basicauth" | ||||
| 	"github.com/gofiber/fiber/v2/middleware/logger" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"github.com/spf13/viper" | ||||
| ) | ||||
|  | ||||
| func InitSideload() *fiber.App { | ||||
| 	app := fiber.New(fiber.Config{ | ||||
| 		AppName:               "RoadSign Sideload", | ||||
| 		ServerHeader:          "RoadSign Sideload", | ||||
| 		DisableStartupMessage: true, | ||||
| 		EnableIPValidation:    true, | ||||
| 		JSONDecoder:           jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal, | ||||
| 		JSONEncoder:           jsoniter.ConfigCompatibleWithStandardLibrary.Marshal, | ||||
| 		EnablePrintRoutes:     viper.GetBool("debug.print_routes"), | ||||
| 		TrustedProxies:        viper.GetStringSlice("security.sideload_trusted_proxies"), | ||||
| 		BodyLimit:             viper.GetInt("hypertext.limitation.max_body_size"), | ||||
| 	}) | ||||
|  | ||||
| 	if viper.GetBool("performance.request_logging") { | ||||
| 		app.Use(logger.New(logger.Config{ | ||||
| 			Output: log.Logger, | ||||
| 			Format: "[Sideload] [${time}] ${status} - ${latency} ${method} ${path}\n", | ||||
| 		})) | ||||
| 	} | ||||
|  | ||||
| 	app.Use(basicauth.New(basicauth.Config{ | ||||
| 		Realm: fmt.Sprintf("RoadSign v%s", roadsign.AppVersion), | ||||
| 		Authorizer: func(_, password string) bool { | ||||
| 			return password == viper.GetString("security.credential") | ||||
| 		}, | ||||
| 	})) | ||||
|  | ||||
| 	app.Use("/", filesystem.New(filesystem.Config{ | ||||
| 		Root:         http.FS(view.FS), | ||||
| 		PathPrefix:   "dist", | ||||
| 		Index:        "index.html", | ||||
| 		NotFoundFile: "index.html", | ||||
| 	})) | ||||
|  | ||||
| 	cgi := app.Group("/cgi").Name("CGI") | ||||
| 	{ | ||||
| 		cgi.All("/connectivity", responseConnectivity) | ||||
| 		cgi.Get("/statistics", getStatistics) | ||||
| 		cgi.Get("/sites", getSites) | ||||
| 		cgi.Get("/sites/cfg/:id", getSiteConfig) | ||||
| 		cgi.Get("/processes", getProcesses) | ||||
| 		cgi.Get("/processes/logs/:id", getProcessLog) | ||||
| 	} | ||||
|  | ||||
| 	webhooks := app.Group("/webhooks").Name("WebHooks") | ||||
| 	{ | ||||
| 		webhooks.Put("/publish/:site/:slug", doPublish) | ||||
| 		webhooks.Put("/sync/:slug", doSyncSite) | ||||
| 	} | ||||
|  | ||||
| 	return app | ||||
| } | ||||
| @@ -1,63 +0,0 @@ | ||||
| package sideload | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/sign" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/samber/lo" | ||||
| 	"github.com/spf13/viper" | ||||
| 	"gopkg.in/yaml.v2" | ||||
| ) | ||||
|  | ||||
| func getSites(c *fiber.Ctx) error { | ||||
| 	return c.JSON(sign.App.Sites) | ||||
| } | ||||
|  | ||||
| func getSiteConfig(c *fiber.Ctx) error { | ||||
| 	fp := filepath.Join(viper.GetString("paths.configs"), c.Params("id")) | ||||
|  | ||||
| 	var err error | ||||
| 	var data []byte | ||||
| 	if data, err = os.ReadFile(fp + ".yml"); err != nil { | ||||
| 		if data, err = os.ReadFile(fp + ".yaml"); err != nil { | ||||
| 			return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return c.Type("yaml").SendString(string(data)) | ||||
| } | ||||
|  | ||||
| func doSyncSite(c *fiber.Ctx) error { | ||||
| 	var req sign.SiteConfig | ||||
|  | ||||
| 	if err := c.BodyParser(&req); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	id := c.Params("slug") | ||||
| 	path := filepath.Join(viper.GetString("paths.configs"), fmt.Sprintf("%s.yaml", id)) | ||||
|  | ||||
| 	if file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755); err != nil { | ||||
| 		return fiber.NewError(fiber.ErrInternalServerError.Code, err.Error()) | ||||
| 	} else { | ||||
| 		raw, _ := yaml.Marshal(req) | ||||
| 		file.Write(raw) | ||||
| 		defer file.Close() | ||||
| 	} | ||||
| 	if site, ok := lo.Find(sign.App.Sites, func(item *sign.SiteConfig) bool { | ||||
| 		return item.ID == id | ||||
| 	}); ok { | ||||
| 		for _, process := range site.Processes { | ||||
| 			process.StopProcess() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Reload | ||||
| 	sign.ReadInConfig(viper.GetString("paths.configs")) | ||||
| 	sign.App.PreheatProcesses() | ||||
|  | ||||
| 	return c.SendStatus(fiber.StatusOK) | ||||
| } | ||||
| @@ -1,28 +0,0 @@ | ||||
| package sideload | ||||
|  | ||||
| import ( | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/sign" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| func getStatistics(c *fiber.Ctx) error { | ||||
| 	upstreams := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.UpstreamInstance { | ||||
| 		return item.Upstreams | ||||
| 	}) | ||||
| 	processes := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.ProcessInstance { | ||||
| 		return item.Processes | ||||
| 	}) | ||||
| 	unhealthy := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.ProcessInstance { | ||||
| 		return lo.Filter(item.Processes, func(item *sign.ProcessInstance, idx int) bool { | ||||
| 			return item.Status != sign.ProcessStarted | ||||
| 		}) | ||||
| 	}) | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"sites":     len(sign.App.Sites), | ||||
| 		"upstreams": len(upstreams), | ||||
| 		"processes": len(processes), | ||||
| 		"status":    len(unhealthy) == 0, | ||||
| 	}) | ||||
| } | ||||
| @@ -1,3 +0,0 @@ | ||||
| /dist | ||||
| /node_modules | ||||
| /*.lock | ||||
| @@ -1,15 +0,0 @@ | ||||
| /* eslint-env node */ | ||||
| require('@rushstack/eslint-patch/modern-module-resolution') | ||||
|  | ||||
| module.exports = { | ||||
|   root: true, | ||||
|   'extends': [ | ||||
|     'plugin:vue/vue3-essential', | ||||
|     'eslint:recommended', | ||||
|     '@vue/eslint-config-typescript', | ||||
|     '@vue/eslint-config-prettier/skip-formatting' | ||||
|   ], | ||||
|   parserOptions: { | ||||
|     ecmaVersion: 'latest' | ||||
|   } | ||||
| } | ||||
							
								
								
									
										30
									
								
								pkg/sideload/view/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								pkg/sideload/view/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,30 +0,0 @@ | ||||
| # Logs | ||||
| logs | ||||
| *.log | ||||
| npm-debug.log* | ||||
| yarn-debug.log* | ||||
| yarn-error.log* | ||||
| pnpm-debug.log* | ||||
| lerna-debug.log* | ||||
|  | ||||
| node_modules | ||||
| .DS_Store | ||||
| dist | ||||
| dist-ssr | ||||
| coverage | ||||
| *.local | ||||
|  | ||||
| /cypress/videos/ | ||||
| /cypress/screenshots/ | ||||
|  | ||||
| # Editor directories and files | ||||
| .vscode/* | ||||
| !.vscode/extensions.json | ||||
| .idea | ||||
| *.suo | ||||
| *.ntvs* | ||||
| *.njsproj | ||||
| *.sln | ||||
| *.sw? | ||||
|  | ||||
| *.tsbuildinfo | ||||
| @@ -1,8 +0,0 @@ | ||||
| { | ||||
|   "$schema": "https://json.schemastore.org/prettierrc", | ||||
|   "semi": false, | ||||
|   "tabWidth": 2, | ||||
|   "singleQuote": false, | ||||
|   "printWidth": 120, | ||||
|   "trailingComma": "none" | ||||
| } | ||||
							
								
								
									
										8
									
								
								pkg/sideload/view/.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								pkg/sideload/view/.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							| @@ -1,8 +0,0 @@ | ||||
| { | ||||
|   "recommendations": [ | ||||
|     "Vue.volar", | ||||
|     "Vue.vscode-typescript-vue-plugin", | ||||
|     "dbaeumer.vscode-eslint", | ||||
|     "esbenp.prettier-vscode" | ||||
|   ] | ||||
| } | ||||
| @@ -1,46 +0,0 @@ | ||||
| # @roadsign/sideload-ui | ||||
|  | ||||
| This template should help get you started developing with Vue 3 in Vite. | ||||
|  | ||||
| ## Recommended IDE Setup | ||||
|  | ||||
| [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). | ||||
|  | ||||
| ## Type Support for `.vue` Imports in TS | ||||
|  | ||||
| TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types. | ||||
|  | ||||
| If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: | ||||
|  | ||||
| 1. Disable the built-in TypeScript Extension | ||||
|     1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette | ||||
|     2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` | ||||
| 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. | ||||
|  | ||||
| ## Customize configuration | ||||
|  | ||||
| See [Vite Configuration Reference](https://vitejs.dev/config/). | ||||
|  | ||||
| ## Project Setup | ||||
|  | ||||
| ```sh | ||||
| yarn | ||||
| ``` | ||||
|  | ||||
| ### Compile and Hot-Reload for Development | ||||
|  | ||||
| ```sh | ||||
| yarn dev | ||||
| ``` | ||||
|  | ||||
| ### Type-Check, Compile and Minify for Production | ||||
|  | ||||
| ```sh | ||||
| yarn build | ||||
| ``` | ||||
|  | ||||
| ### Lint with [ESLint](https://eslint.org/) | ||||
|  | ||||
| ```sh | ||||
| yarn lint | ||||
| ``` | ||||
| @@ -1,6 +0,0 @@ | ||||
| package view | ||||
|  | ||||
| import "embed" | ||||
|  | ||||
| //go:embed all:dist | ||||
| var FS embed.FS | ||||
							
								
								
									
										1
									
								
								pkg/sideload/view/env.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								pkg/sideload/view/env.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| /// <reference types="vite/client" /> | ||||
| @@ -1,13 +0,0 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <link rel="icon" href="/favicon.ico"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <title>RoadSign</title> | ||||
|   </head> | ||||
|   <body> | ||||
|     <div id="app"></div> | ||||
|     <script type="module" src="/src/main.ts"></script> | ||||
|   </body> | ||||
| </html> | ||||
| @@ -1,44 +0,0 @@ | ||||
| { | ||||
|   "name": "@roadsign/sideload-ui", | ||||
|   "version": "0.0.0", | ||||
|   "private": true, | ||||
|   "type": "module", | ||||
|   "scripts": { | ||||
|     "dev": "vite", | ||||
|     "build": "run-p type-check \"build-only {@}\" --", | ||||
|     "preview": "vite preview", | ||||
|     "build-only": "vite build", | ||||
|     "type-check": "vue-tsc --build --force", | ||||
|     "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", | ||||
|     "format": "prettier --write src/" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@guolao/vue-monaco-editor": "^1.4.1", | ||||
|     "highlight.js": "^11.9.0", | ||||
|     "js-yaml": "^4.1.0", | ||||
|     "pinia": "^2.1.7", | ||||
|     "vue": "^3.3.11", | ||||
|     "vue-router": "^4.2.5" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@rushstack/eslint-patch": "^1.3.3", | ||||
|     "@tsconfig/node18": "^18.2.2", | ||||
|     "@types/js-yaml": "^4.0.9", | ||||
|     "@types/node": "^18.19.3", | ||||
|     "@vicons/carbon": "^0.12.0", | ||||
|     "@vitejs/plugin-vue": "^4.5.2", | ||||
|     "@vue/eslint-config-prettier": "^8.0.0", | ||||
|     "@vue/eslint-config-typescript": "^12.0.0", | ||||
|     "@vue/tsconfig": "^0.5.0", | ||||
|     "eslint": "^8.49.0", | ||||
|     "eslint-plugin-vue": "^9.17.0", | ||||
|     "naive-ui": "^2.36.0", | ||||
|     "npm-run-all2": "^6.1.1", | ||||
|     "prettier": "^3.0.3", | ||||
|     "typescript": "~5.3.0", | ||||
|     "unocss": "^0.58.2", | ||||
|     "vfonts": "^0.0.3", | ||||
|     "vite": "^5.0.10", | ||||
|     "vue-tsc": "^1.8.25" | ||||
|   } | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| @import "vfonts/IBMPlexSans.css"; | ||||
| @import "vfonts/IBMPlexMono.css"; | ||||
|  | ||||
| a { | ||||
|     color: #3f7ee8; | ||||
| } | ||||
| @@ -1,135 +0,0 @@ | ||||
| <template> | ||||
|   <div class="flex gap-[4px]"> | ||||
|     <n-button size="small" @click="publishing = true"> | ||||
|       <template #icon> | ||||
|         <n-icon :component="CloudUpload" /> | ||||
|       </template> | ||||
|     </n-button> | ||||
|     <n-button size="small" @click="editConfig()"> | ||||
|       <template #icon> | ||||
|         <n-icon :component="Edit" /> | ||||
|       </template> | ||||
|     </n-button> | ||||
|  | ||||
|     <n-modal | ||||
|       v-model:show="publishing" | ||||
|       class="w-[720px]" | ||||
|       preset="card" | ||||
|       title="Publish Artifacts" | ||||
|       segmented | ||||
|       closable | ||||
|     > | ||||
|       We are sorry about this tool isn't completed yet. <br> | ||||
|       For now, you can use our <b>Wonderful Command Line Tool —— RDS</b> <br> | ||||
|       Learn more on our <a href="https://wiki.smartsheep.studio/roadsign/index.html" target="_blank">official wiki</a>. | ||||
|       <br> | ||||
|       <br> | ||||
|       Install it by this command below | ||||
|       <n-code code="go install code.smartsheep.studio/goatworks/roadsign/pkg/cmd/rds@latest" /> | ||||
|       <br> | ||||
|       Then connect your rds client to this server | ||||
|       <n-code :code="`rds connect <name> ${host} <credentials>`" /> | ||||
|       <br> | ||||
|       After that you can publish your stuff (You need to compress them to zip archive before publish) | ||||
|       <n-code :code="`rds deploy <name> ${props.id} <upstream id or process id>`" /> | ||||
|     </n-modal> | ||||
|  | ||||
|     <n-modal | ||||
|       v-model:show="editing" | ||||
|       class="w-[720px]" | ||||
|       content-style="padding: 0" | ||||
|       preset="card" | ||||
|       title="Edit Configuration" | ||||
|       segmented | ||||
|       closable | ||||
|     > | ||||
|       <div class="relative h-[540px]"> | ||||
|         <vue-monaco-editor | ||||
|           v-model:value="config" | ||||
|           :options="{ automaticLayout: true, minimap: { enabled: false } }" | ||||
|           language="yaml" | ||||
|         /> | ||||
|  | ||||
|         <div class="fab"> | ||||
|           <n-tooltip placement="left"> | ||||
|             <template #trigger> | ||||
|               <n-button | ||||
|                 circle | ||||
|                 type="primary" | ||||
|                 size="large" | ||||
|                 class="shadow-lg" | ||||
|                 :loading="submitting" | ||||
|                 @click="syncConfig()" | ||||
|               > | ||||
|                 <template #icon> | ||||
|                   <n-icon :component="Save" /> | ||||
|                 </template> | ||||
|               </n-button> | ||||
|             </template> | ||||
|             This operation will restart all processes related. Service may interrupted for some while. | ||||
|           </n-tooltip> | ||||
|         </div> | ||||
|       </div> | ||||
|     </n-modal> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { NButton, NCode, NIcon, NModal, NTooltip, useMessage } from "naive-ui" | ||||
| import { CloudUpload, Edit, Save } from "@vicons/carbon" | ||||
| import { ref } from "vue" | ||||
| import { VueMonacoEditor } from "@guolao/vue-monaco-editor" | ||||
| import * as yaml from "js-yaml" | ||||
|  | ||||
| const message = useMessage() | ||||
|  | ||||
| const props = defineProps<{ id: string, rules: any[], upstreams: any[], processes: any[] }>() | ||||
| const emits = defineEmits(["reload"]) | ||||
| const host = location.protocol + "//" + location.host | ||||
|  | ||||
| const submitting = ref(false) | ||||
|  | ||||
| const publishing = ref(false) | ||||
| const editing = ref(false) | ||||
|  | ||||
| const config = ref<string | undefined>(undefined) | ||||
|  | ||||
| async function editConfig() { | ||||
|   const resp = await fetch(`/cgi/sites/cfg/${props.id}`) | ||||
|   config.value = await resp.text() | ||||
|   editing.value = true | ||||
| } | ||||
|  | ||||
| async function syncConfig() { | ||||
|   if (config.value == null) return | ||||
|  | ||||
|   let content | ||||
|   try { | ||||
|     content = yaml.load(config.value) | ||||
|   } catch (e: any) { | ||||
|     message.warning(`Your configuration has some issue: ${e.message}`) | ||||
|     return | ||||
|   } | ||||
|  | ||||
|   submitting.value = true | ||||
|   const resp = await fetch(`/webhooks/sync/${props.id}`, { | ||||
|     method: "PUT", | ||||
|     headers: { "Content-Type": "application/json" }, | ||||
|     body: JSON.stringify(content) | ||||
|   }) | ||||
|   if (resp.status != 200) { | ||||
|     message.error(`Something went wrong... ${await resp.text()}`) | ||||
|   } else { | ||||
|     emits("reload") | ||||
|   } | ||||
|   submitting.value = false | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .fab { | ||||
|   position: absolute; | ||||
|   bottom: 16px; | ||||
|   right: 24px; | ||||
| } | ||||
| </style> | ||||
| @@ -1,110 +0,0 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <n-button circle size="small" type="primary" @click="creating = true"> | ||||
|       <template #icon> | ||||
|         <n-icon :component="Add" /> | ||||
|       </template> | ||||
|     </n-button> | ||||
|  | ||||
|     <n-modal | ||||
|       v-model:show="creating" | ||||
|       class="w-[720px]" | ||||
|       content-style="padding: 0" | ||||
|       preset="card" | ||||
|       title="Create Site" | ||||
|       segmented | ||||
|       closable | ||||
|     > | ||||
|       <div class="py-4 px-5 border border-solid border-b border-[#eee]"> | ||||
|         <n-input | ||||
|           v-model:value="data.id" | ||||
|           placeholder="Will be the file name of this file" | ||||
|         /> | ||||
|       </div> | ||||
|  | ||||
|       <div class="relative mt-[4px] h-[540px]"> | ||||
|         <vue-monaco-editor | ||||
|           v-model:value="data.content" | ||||
|           :options="{ automaticLayout: true, minimap: { enabled: false } }" | ||||
|           language="yaml" | ||||
|         /> | ||||
|  | ||||
|         <div class="fab"> | ||||
|           <n-tooltip placement="left"> | ||||
|             <template #trigger> | ||||
|               <n-button | ||||
|                 circle | ||||
|                 type="primary" | ||||
|                 size="large" | ||||
|                 class="shadow-lg" | ||||
|                 :loading="submitting" | ||||
|                 @click="submit()" | ||||
|               > | ||||
|                 <template #icon> | ||||
|                   <n-icon :component="Checkmark" /> | ||||
|                 </template> | ||||
|               </n-button> | ||||
|             </template> | ||||
|             This operation will publish this site right away. | ||||
|           </n-tooltip> | ||||
|         </div> | ||||
|       </div> | ||||
|     </n-modal> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { NButton, NIcon, NInput, NModal, NTooltip, useMessage } from "naive-ui" | ||||
| import { Add, Checkmark } from "@vicons/carbon" | ||||
| import { VueMonacoEditor } from "@guolao/vue-monaco-editor" | ||||
| import { ref } from "vue" | ||||
| import * as yaml from "js-yaml" | ||||
|  | ||||
| const message = useMessage() | ||||
|  | ||||
| const emits = defineEmits(["reload"]) | ||||
|  | ||||
| const submitting = ref(false) | ||||
| const creating = ref(false) | ||||
|  | ||||
| const data = ref<any>({}) | ||||
|  | ||||
| async function submit() { | ||||
|   let content | ||||
|   try { | ||||
|     content = yaml.load(data.value.content) | ||||
|   } catch (e: any) { | ||||
|     message.warning(`Your configuration has some issue: ${e.message}`) | ||||
|     return | ||||
|   } | ||||
|  | ||||
|   submitting.value = true | ||||
|   const resp = await fetch(`/webhooks/sync/${data.value.id}`, { | ||||
|     method: "PUT", | ||||
|     headers: { "Content-Type": "application/json" }, | ||||
|     body: JSON.stringify(content) | ||||
|   }) | ||||
|   if (resp.status != 200) { | ||||
|     message.error(`Something went wrong... ${await resp.text()}`) | ||||
|   } else { | ||||
|     reset() | ||||
|     emits("reload") | ||||
|     message.success("Your site has been created! 🎉") | ||||
|     creating.value = false | ||||
|   } | ||||
|   submitting.value = false | ||||
| } | ||||
|  | ||||
| function reset() { | ||||
|   data.value.id = "" | ||||
|   data.value.content = "" | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .fab { | ||||
|   position: absolute; | ||||
|   bottom: 16px; | ||||
|   right: 24px; | ||||
| } | ||||
| </style> | ||||
| @@ -1,36 +0,0 @@ | ||||
| <template> | ||||
|   <div class="flex flex-col gap-1"> | ||||
|  | ||||
|     <div> | ||||
|       <div class="font-bold">Rules</div> | ||||
|       <n-code :hljs="hljs" :code="parseData(props.rules)" language="json" /> | ||||
|     </div> | ||||
|  | ||||
|     <div> | ||||
|       <div class="font-bold">Upstreams</div> | ||||
|       <n-code :hljs="hljs" :code="parseData(props.upstreams)" language="json" /> | ||||
|     </div> | ||||
|  | ||||
|     <div> | ||||
|       <div class="font-bold">Processes</div> | ||||
|       <n-code :hljs="hljs" :code="parseData(props.processes)" language="json" /> | ||||
|     </div> | ||||
|  | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { NCode } from "naive-ui" | ||||
| import hljs from "highlight.js/lib/core" | ||||
| import json from "highlight.js/lib/languages/json" | ||||
|  | ||||
| hljs.registerLanguage("json", json) | ||||
|  | ||||
| const props = defineProps<{ rules: any[], upstreams: any[], processes: any[] }>() | ||||
|  | ||||
| function parseData(data: any): string { | ||||
|   return JSON.stringify(data, null, 1) | ||||
|     .replace(/  +/g, " ") | ||||
|     .replace(/\n/g, "") | ||||
| } | ||||
| </script> | ||||
| @@ -1,76 +0,0 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <n-card title="Sites"> | ||||
|       <template #header-extra> | ||||
|         <sites-table-add @reload="readSites()" /> | ||||
|       </template> | ||||
|  | ||||
|       <n-data-table | ||||
|         :columns="columns" | ||||
|         :data="data" | ||||
|         :row-key="(row: any) => row.id" | ||||
|       /> | ||||
|     </n-card> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { NCard, NDataTable, NTag } from "naive-ui" | ||||
| import { h, ref } from "vue" | ||||
| import SitesTableExpand from "@/components/data/sites-table-expand.vue" | ||||
| import SitesTableAction from "@/components/data/sites-table-action.vue" | ||||
| import SitesTableAdd from "@/components/data/sites-table-add.vue" | ||||
|  | ||||
| const columns: any[] = [ | ||||
|   { | ||||
|     type: "expand", | ||||
|     renderExpand(row: any) { | ||||
|       return h(SitesTableExpand, { ...row, class: "pl-[38px]" }) | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     title: "ID", | ||||
|     key: "id", | ||||
|     render(row: any) { | ||||
|       return h(NTag, { type: "info", bordered: false, size: "small" }, row?.id) | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     title: "Rules", | ||||
|     key: "rules", | ||||
|     render(row: any) { | ||||
|       return row?.rules?.length ?? 0 | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     title: "Upstreams", | ||||
|     key: "upstreams", | ||||
|     render(row: any) { | ||||
|       return row?.upstreams?.length ?? 0 | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     title: "Processes", | ||||
|     key: "processes", | ||||
|     render(row: any) { | ||||
|       return row?.processes?.length ?? 0 | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     title: "Actions", | ||||
|     key: "actions", | ||||
|     render(row: any) { | ||||
|       return h(SitesTableAction, { ...row, onReload: () => readSites() }) | ||||
|     } | ||||
|   } | ||||
| ] | ||||
|  | ||||
| const data = ref<any[]>([]) | ||||
|  | ||||
| async function readSites() { | ||||
|   const resp = await fetch("/cgi/sites") | ||||
|   data.value = await resp.json() | ||||
| } | ||||
|  | ||||
| readSites() | ||||
| </script> | ||||
| @@ -1,60 +0,0 @@ | ||||
| <template> | ||||
|   <n-layout> | ||||
|     <n-layout-header class="header py-[8px] px-[36px]" bordered> | ||||
|       <div class="flex items-center gap-2"> | ||||
|         <router-link class="link" to="/"> | ||||
|           RoadSign<i>!</i> | ||||
|         </router-link> | ||||
|       </div> | ||||
|  | ||||
|       <div class="nav-menu"> | ||||
|         <div class="h-full flex items-center header-nav"> | ||||
|           <n-menu v-model:value="key" :options="options" mode="horizontal" /> | ||||
|         </div> | ||||
|       </div> | ||||
|     </n-layout-header> | ||||
|     <n-layout-content class="h-[calc(100vh-70px)] container mx-auto" content-style="padding: 24px"> | ||||
|       <router-view /> | ||||
|     </n-layout-content> | ||||
|   </n-layout> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { type MenuOption, NIcon, NLayout, NLayoutContent, NLayoutHeader, NMenu } from "naive-ui" | ||||
| import { type Component, h, ref } from "vue" | ||||
| import { Dashboard } from "@vicons/carbon" | ||||
| import { RouterLink, useRoute, useRouter } from "vue-router" | ||||
|  | ||||
| const route = useRoute() | ||||
| const router = useRouter() | ||||
| const key = ref(route.name?.toString()) | ||||
|  | ||||
| router.afterEach((to) => { | ||||
|   key.value = to.name?.toString() ?? "index" | ||||
| }) | ||||
|  | ||||
| const options: MenuOption[] = [ | ||||
|   { | ||||
|     label: () => h(RouterLink, { to: { name: "dashboard" } }, "Dashboard"), | ||||
|     icon: renderIcon(Dashboard), | ||||
|     key: "dashboard" | ||||
|   } | ||||
| ] | ||||
|  | ||||
| function renderIcon(icon: Component) { | ||||
|   return () => h(NIcon, null, { default: () => h(icon) }) | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .header { | ||||
|   display: grid; | ||||
|   grid-template-columns: 1fr auto 1fr; | ||||
|   gap: 40px; | ||||
| } | ||||
|  | ||||
| .link { | ||||
|   all: unset; | ||||
|   cursor: pointer; | ||||
| } | ||||
| </style> | ||||
| @@ -1,16 +0,0 @@ | ||||
| import "./assets/main.css" | ||||
|  | ||||
| import "virtual:uno.css" | ||||
|  | ||||
| import { createApp } from "vue" | ||||
| import { createPinia } from "pinia" | ||||
|  | ||||
| import root from "./root.vue" | ||||
| import router from "./router" | ||||
|  | ||||
| const app = createApp(root) | ||||
|  | ||||
| app.use(createPinia()) | ||||
| app.use(router) | ||||
|  | ||||
| app.mount("#app") | ||||
| @@ -1,9 +0,0 @@ | ||||
| <template> | ||||
|   <n-message-provider> | ||||
|     <router-view /> | ||||
|   </n-message-provider> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { NMessageProvider } from "naive-ui" | ||||
| </script> | ||||
| @@ -1,21 +0,0 @@ | ||||
| import { createRouter, createWebHistory } from "vue-router" | ||||
|  | ||||
| const router = createRouter({ | ||||
|   history: createWebHistory(import.meta.env.BASE_URL), | ||||
|   routes: [ | ||||
|     { | ||||
|       path: "/", | ||||
|       name: "layouts.main", | ||||
|       component: () => import("@/layouts/main.vue"), | ||||
|       children: [ | ||||
|         { | ||||
|           path: "/", | ||||
|           name: "dashboard", | ||||
|           component: () => import("@/views/dashboard.vue") | ||||
|         }, | ||||
|       ] | ||||
|     }, | ||||
|   ] | ||||
| }) | ||||
|  | ||||
| export default router | ||||
| @@ -1,35 +0,0 @@ | ||||
| <template> | ||||
|   <div class="flex flex-col gap-2"> | ||||
|     <div class="grid gap-2 grid-cols-2 lg:grid-cols-4"> | ||||
|       <n-card embedded> | ||||
|         <n-statistic label="Status">{{ data?.status ? "Operational" : "Incident" }}</n-statistic> | ||||
|       </n-card> | ||||
|       <n-card embedded> | ||||
|         <n-statistic label="Sites">{{ data?.sites }}</n-statistic> | ||||
|       </n-card> | ||||
|       <n-card embedded> | ||||
|         <n-statistic label="Upstreams">{{ data?.upstreams }}</n-statistic> | ||||
|       </n-card> | ||||
|       <n-card embedded> | ||||
|         <n-statistic label="Processes">{{ data?.processes }}</n-statistic> | ||||
|       </n-card> | ||||
|     </div> | ||||
|  | ||||
|     <sites-table /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { NCard, NStatistic } from "naive-ui" | ||||
| import { ref } from "vue" | ||||
| import SitesTable from "@/components/data/sites-table.vue" | ||||
|  | ||||
| const data = ref<any>({}) | ||||
|  | ||||
| async function readStatistics() { | ||||
|   const resp = await fetch("/cgi/statistics") | ||||
|   data.value = await resp.json() | ||||
| } | ||||
|  | ||||
| readStatistics() | ||||
| </script> | ||||
| @@ -1,13 +0,0 @@ | ||||
| { | ||||
|   "extends": "@vue/tsconfig/tsconfig.dom.json", | ||||
|   "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], | ||||
|   "exclude": ["src/**/__tests__/*"], | ||||
|   "compilerOptions": { | ||||
|     "composite": true, | ||||
|     "noEmit": true, | ||||
|     "baseUrl": ".", | ||||
|     "paths": { | ||||
|       "@/*": ["./src/*"] | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -1,11 +0,0 @@ | ||||
| { | ||||
|   "files": [], | ||||
|   "references": [ | ||||
|     { | ||||
|       "path": "./tsconfig.node.json" | ||||
|     }, | ||||
|     { | ||||
|       "path": "./tsconfig.app.json" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @@ -1,17 +0,0 @@ | ||||
| { | ||||
|   "extends": "@tsconfig/node18/tsconfig.json", | ||||
|   "include": [ | ||||
|     "vite.config.*", | ||||
|     "vitest.config.*", | ||||
|     "cypress.config.*", | ||||
|     "nightwatch.conf.*", | ||||
|     "playwright.config.*" | ||||
|   ], | ||||
|   "compilerOptions": { | ||||
|     "composite": true, | ||||
|     "noEmit": true, | ||||
|     "module": "ESNext", | ||||
|     "moduleResolution": "Bundler", | ||||
|     "types": ["node"] | ||||
|   } | ||||
| } | ||||
| @@ -1,5 +0,0 @@ | ||||
| import { defineConfig, presetUno } from "unocss" | ||||
|  | ||||
| export default defineConfig({ | ||||
|   presets: [presetUno({ preflight: false })] | ||||
| }) | ||||
| @@ -1,24 +0,0 @@ | ||||
| import { fileURLToPath, URL } from "node:url" | ||||
|  | ||||
| import { defineConfig } from "vite" | ||||
| import vue from "@vitejs/plugin-vue" | ||||
| import unocss from "unocss/vite" | ||||
|  | ||||
| // https://vitejs.dev/config/ | ||||
| export default defineConfig({ | ||||
|   plugins: [ | ||||
|     vue(), | ||||
|     unocss() | ||||
|   ], | ||||
|   resolve: { | ||||
|     alias: { | ||||
|       "@": fileURLToPath(new URL("./src", import.meta.url)) | ||||
|     } | ||||
|   }, | ||||
|   server: { | ||||
|     proxy: { | ||||
|       "/webhooks": "http://127.0.0.1:81", | ||||
|       "/cgi": "http://127.0.0.1:81" | ||||
|     } | ||||
|   } | ||||
| }) | ||||
| @@ -1,45 +0,0 @@ | ||||
| package sign | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"gopkg.in/yaml.v2" | ||||
| ) | ||||
|  | ||||
| var App *RoadApp | ||||
|  | ||||
| func ReadInConfig(root string) error { | ||||
| 	instance := &RoadApp{ | ||||
| 		Sites: []*SiteConfig{}, | ||||
| 	} | ||||
|  | ||||
| 	if err := filepath.Walk(root, func(fp string, info os.FileInfo, err error) error { | ||||
| 		var site SiteConfig | ||||
| 		if info.IsDir() { | ||||
| 			return nil | ||||
| 		} else if file, err := os.OpenFile(fp, os.O_RDONLY, 0755); err != nil { | ||||
| 			return err | ||||
| 		} else if data, err := io.ReadAll(file); err != nil { | ||||
| 			return err | ||||
| 		} else if err := yaml.Unmarshal(data, &site); err != nil { | ||||
| 			return err | ||||
| 		} else { | ||||
| 			defer file.Close() | ||||
|  | ||||
| 			// Extract file name as site id | ||||
| 			site.ID = strings.SplitN(filepath.Base(fp), ".", 2)[0] | ||||
| 			instance.Sites = append(instance.Sites, &site) | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	App = instance | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										144
									
								
								pkg/sign/pm.go
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								pkg/sign/pm.go
									
									
									
									
									
								
							| @@ -1,144 +0,0 @@ | ||||
| package sign | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/samber/lo" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type ProcessStatus = int8 | ||||
|  | ||||
| const ( | ||||
| 	ProcessCreated = ProcessStatus(iota) | ||||
| 	ProcessStarting | ||||
| 	ProcessStarted | ||||
| 	ProcessExited | ||||
| 	ProcessFailure | ||||
| ) | ||||
|  | ||||
| type ProcessInstance struct { | ||||
| 	ID          string     `json:"id" yaml:"id"` | ||||
| 	Workdir     string     `json:"workdir" yaml:"workdir"` | ||||
| 	Command     []string   `json:"command" yaml:"command"` | ||||
| 	Environment []string   `json:"environment" yaml:"environment"` | ||||
| 	Prepares    [][]string `json:"prepares" yaml:"prepares"` | ||||
| 	Preheat     bool       `json:"preheat" yaml:"preheat"` | ||||
|  | ||||
| 	Cmd    *exec.Cmd       `json:"-"` | ||||
| 	Logger strings.Builder `json:"-"` | ||||
|  | ||||
| 	Status ProcessStatus `json:"status"` | ||||
| } | ||||
|  | ||||
| func (v *ProcessInstance) BootProcess() error { | ||||
| 	if v.Cmd != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if err := v.PrepareProcess(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if v.Cmd == nil { | ||||
| 		return v.StartProcess() | ||||
| 	} | ||||
| 	if v.Cmd.Process == nil || v.Cmd.ProcessState == nil { | ||||
| 		return v.StartProcess() | ||||
| 	} | ||||
| 	if v.Cmd.ProcessState.Exited() { | ||||
| 		return v.StartProcess() | ||||
| 	} else if v.Cmd.ProcessState.Exited() { | ||||
| 		return fmt.Errorf("process already dead") | ||||
| 	} | ||||
| 	if v.Cmd.ProcessState.Exited() { | ||||
| 		return fmt.Errorf("cannot start process") | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (v *ProcessInstance) PrepareProcess() error { | ||||
| 	for _, script := range v.Prepares { | ||||
| 		if len(script) <= 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		cmd := exec.Command(script[0], script[1:]...) | ||||
| 		cmd.Dir = filepath.Join(v.Workdir) | ||||
| 		if err := cmd.Run(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (v *ProcessInstance) StartProcess() error { | ||||
| 	if len(v.Command) <= 0 { | ||||
| 		return fmt.Errorf("you need set the command for %s to enable process manager", v.ID) | ||||
| 	} | ||||
|  | ||||
| 	v.Cmd = exec.Command(v.Command[0], v.Command[1:]...) | ||||
| 	v.Cmd.Dir = filepath.Join(v.Workdir) | ||||
| 	v.Cmd.Env = append(v.Cmd.Env, v.Environment...) | ||||
| 	v.Cmd.Stdout = &v.Logger | ||||
| 	v.Cmd.Stderr = &v.Logger | ||||
|  | ||||
| 	// Monitor | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			if v.Cmd.Process == nil || v.Cmd.ProcessState == nil { | ||||
| 				v.Status = ProcessStarting | ||||
| 			} else if !v.Cmd.ProcessState.Exited() { | ||||
| 				v.Status = ProcessStarted | ||||
| 			} else { | ||||
| 				v.Status = lo.Ternary(v.Cmd.ProcessState.Success(), ProcessExited, ProcessFailure) | ||||
| 				return | ||||
| 			} | ||||
| 			time.Sleep(100 * time.Millisecond) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	return v.Cmd.Start() | ||||
| } | ||||
|  | ||||
| func (v *ProcessInstance) StopProcess() error { | ||||
| 	if v.Cmd != nil && v.Cmd.Process != nil { | ||||
| 		if err := v.Cmd.Process.Signal(os.Interrupt); err != nil { | ||||
| 			v.Cmd.Process.Kill() | ||||
| 			return err | ||||
| 		} else { | ||||
| 			v.Cmd = nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (v *ProcessInstance) GetLogs() string { | ||||
| 	return v.Logger.String() | ||||
| } | ||||
|  | ||||
| func (v *RoadApp) PreheatProcesses(callbacks ...func(total int, success int)) { | ||||
| 	var processes []*ProcessInstance | ||||
| 	for _, site := range v.Sites { | ||||
| 		for _, process := range site.Processes { | ||||
| 			if process.Preheat { | ||||
| 				processes = append(processes, process) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	success := 0 | ||||
| 	for _, process := range processes { | ||||
| 		if process.BootProcess() == nil { | ||||
| 			success++ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(callbacks) > 0 { | ||||
| 		for _, callback := range callbacks { | ||||
| 			callback(len(processes), success) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,127 +0,0 @@ | ||||
| package sign | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/fs" | ||||
| 	"net/http" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/gofiber/fiber/v2/middleware/proxy" | ||||
| 	"github.com/gofiber/fiber/v2/utils" | ||||
| 	"github.com/samber/lo" | ||||
| 	"github.com/spf13/viper" | ||||
| 	"github.com/valyala/fasthttp" | ||||
| ) | ||||
|  | ||||
| func makeHypertextResponse(c *fiber.Ctx, upstream *UpstreamInstance) error { | ||||
| 	timeout := time.Duration(viper.GetInt64("performance.network_timeout")) * time.Millisecond | ||||
| 	return proxy.Do(c, upstream.MakeURI(c), &fasthttp.Client{ | ||||
| 		ReadTimeout:  timeout, | ||||
| 		WriteTimeout: timeout, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func makeFileResponse(c *fiber.Ctx, upstream *UpstreamInstance) error { | ||||
| 	uri, queries := upstream.GetRawURI() | ||||
| 	root := http.Dir(uri) | ||||
|  | ||||
| 	method := c.Method() | ||||
|  | ||||
| 	// We only serve static assets for GET and HEAD methods | ||||
| 	if method != fiber.MethodGet && method != fiber.MethodHead { | ||||
| 		return c.Next() | ||||
| 	} | ||||
|  | ||||
| 	// Strip prefix | ||||
| 	prefix := c.Route().Path | ||||
| 	path := strings.TrimPrefix(c.Path(), prefix) | ||||
| 	if !strings.HasPrefix(path, "/") { | ||||
| 		path = "/" + path | ||||
| 	} | ||||
|  | ||||
| 	// Add prefix | ||||
| 	if queries.Get("prefix") != "" { | ||||
| 		path = queries.Get("prefix") + path | ||||
| 	} | ||||
|  | ||||
| 	if len(path) > 1 { | ||||
| 		path = utils.TrimRight(path, '/') | ||||
| 	} | ||||
|  | ||||
| 	file, err := root.Open(path) | ||||
| 	if err != nil && errors.Is(err, fs.ErrNotExist) { | ||||
| 		if queries.Get("suffix") != "" { | ||||
| 			file, err = root.Open(path + queries.Get("suffix")) | ||||
| 		} | ||||
| 		if err != nil && queries.Get("fallback") != "" { | ||||
| 			file, err = root.Open(queries.Get("fallback")) | ||||
| 		} | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, fs.ErrNotExist) { | ||||
| 			return fiber.ErrNotFound | ||||
| 		} | ||||
| 		return fmt.Errorf("failed to open: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	stat, err := file.Stat() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to stat: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// Serve index if path is directory | ||||
| 	if stat.IsDir() { | ||||
| 		indexFile := lo.Ternary(len(queries.Get("index")) > 0, queries.Get("index"), "index.html") | ||||
| 		indexPath := utils.TrimRight(path, '/') + indexFile | ||||
| 		index, err := root.Open(indexPath) | ||||
| 		if err == nil { | ||||
| 			indexStat, err := index.Stat() | ||||
| 			if err == nil { | ||||
| 				file = index | ||||
| 				stat = indexStat | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	c.Status(fiber.StatusOK) | ||||
|  | ||||
| 	modTime := stat.ModTime() | ||||
| 	contentLength := int(stat.Size()) | ||||
|  | ||||
| 	// Set Content-Type header | ||||
| 	if queries.Get("charset") == "" { | ||||
| 		c.Type(filepath.Ext(stat.Name())) | ||||
| 	} else { | ||||
| 		c.Type(filepath.Ext(stat.Name()), queries.Get("charset")) | ||||
| 	} | ||||
|  | ||||
| 	// Set Last-Modified header | ||||
| 	if !modTime.IsZero() { | ||||
| 		c.Set(fiber.HeaderLastModified, modTime.UTC().Format(http.TimeFormat)) | ||||
| 	} | ||||
|  | ||||
| 	if method == fiber.MethodGet { | ||||
| 		maxAge, err := strconv.Atoi(queries.Get("maxAge")) | ||||
| 		if lo.Ternary(err != nil, maxAge, 0) > 0 { | ||||
| 			c.Set(fiber.HeaderCacheControl, "public, max-age="+queries.Get("maxAge")) | ||||
| 		} | ||||
| 		c.Response().SetBodyStream(file, contentLength) | ||||
| 		return nil | ||||
| 	} | ||||
| 	if method == fiber.MethodHead { | ||||
| 		c.Request().ResetBody() | ||||
| 		c.Response().SkipBody = true | ||||
| 		c.Response().Header.SetContentLength(contentLength) | ||||
| 		if err := file.Close(); err != nil { | ||||
| 			return fmt.Errorf("failed to close: %w", err) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	return fiber.ErrNotFound | ||||
| } | ||||
| @@ -1,58 +0,0 @@ | ||||
| package sign | ||||
|  | ||||
| import ( | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/sign/transformers" | ||||
| 	"errors" | ||||
| 	"math/rand" | ||||
|  | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| ) | ||||
|  | ||||
| type RoadApp struct { | ||||
| 	Sites []*SiteConfig `json:"sites"` | ||||
| } | ||||
|  | ||||
| func (v *RoadApp) Forward(ctx *fiber.Ctx, site *SiteConfig) error { | ||||
| 	if len(site.Upstreams) == 0 { | ||||
| 		return errors.New("invalid configuration") | ||||
| 	} | ||||
|  | ||||
| 	// Boot processes | ||||
| 	for _, process := range site.Processes { | ||||
| 		if err := process.BootProcess(); err != nil { | ||||
| 			log.Warn().Err(err).Msgf("An error occurred when booting process (%s) for %s", process.ID, site.ID) | ||||
| 			return fiber.ErrBadGateway | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Do forward | ||||
| 	idx := rand.Intn(len(site.Upstreams)) | ||||
| 	upstream := site.Upstreams[idx] | ||||
|  | ||||
| 	switch upstream.GetType() { | ||||
| 	case UpstreamTypeHypertext: | ||||
| 		return makeHypertextResponse(ctx, upstream) | ||||
| 	case UpstreamTypeFile: | ||||
| 		return makeFileResponse(ctx, upstream) | ||||
| 	default: | ||||
| 		return fiber.ErrBadGateway | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type RequestTransformerConfig = transformers.RequestTransformerConfig | ||||
|  | ||||
| type SiteConfig struct { | ||||
| 	ID           string                      `json:"id"` | ||||
| 	Rules        []*RouterRule               `json:"rules" yaml:"rules"` | ||||
| 	Transformers []*RequestTransformerConfig `json:"transformers" yaml:"transformers"` | ||||
| 	Upstreams    []*UpstreamInstance         `json:"upstreams" yaml:"upstreams"` | ||||
| 	Processes    []*ProcessInstance          `json:"processes" yaml:"processes"` | ||||
| } | ||||
|  | ||||
| type RouterRule struct { | ||||
| 	Host    []string            `json:"host" yaml:"host"` | ||||
| 	Path    []string            `json:"path" yaml:"path"` | ||||
| 	Queries map[string]string   `json:"queries" yaml:"queries"` | ||||
| 	Headers map[string][]string `json:"headers" yaml:"headers"` | ||||
| } | ||||
| @@ -1,41 +0,0 @@ | ||||
| package transformers | ||||
|  | ||||
| import ( | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/valyala/fasthttp" | ||||
| ) | ||||
|  | ||||
| var CompressResponse = RequestTransformer{ | ||||
| 	ModifyResponse: func(options any, ctx *fiber.Ctx) error { | ||||
| 		opts := DeserializeOptions[struct { | ||||
| 			Level int `json:"level" yaml:"level"` | ||||
| 		}](options) | ||||
|  | ||||
| 		var fctx = func(c *fasthttp.RequestCtx) {} | ||||
| 		var compressor fasthttp.RequestHandler | ||||
| 		switch opts.Level { | ||||
| 		// Best Speed Mode | ||||
| 		case 1: | ||||
| 			compressor = fasthttp.CompressHandlerBrotliLevel(fctx, | ||||
| 				fasthttp.CompressBrotliBestSpeed, | ||||
| 				fasthttp.CompressBestSpeed, | ||||
| 			) | ||||
| 		// Best Compression Mode | ||||
| 		case 2: | ||||
| 			compressor = fasthttp.CompressHandlerBrotliLevel(fctx, | ||||
| 				fasthttp.CompressBrotliBestCompression, | ||||
| 				fasthttp.CompressBestCompression, | ||||
| 			) | ||||
| 		// Default Mode | ||||
| 		default: | ||||
| 			compressor = fasthttp.CompressHandlerBrotliLevel(fctx, | ||||
| 				fasthttp.CompressBrotliDefaultCompression, | ||||
| 				fasthttp.CompressDefaultCompression, | ||||
| 			) | ||||
| 		} | ||||
|  | ||||
| 		compressor(ctx.Context()) | ||||
|  | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
| @@ -1,61 +0,0 @@ | ||||
| package transformers | ||||
|  | ||||
| import ( | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	jsoniter "github.com/json-iterator/go" | ||||
| ) | ||||
|  | ||||
| // Definitions | ||||
|  | ||||
| var json = jsoniter.ConfigCompatibleWithStandardLibrary | ||||
|  | ||||
| type RequestTransformer struct { | ||||
| 	ModifyRequest  func(options any, ctx *fiber.Ctx) error | ||||
| 	ModifyResponse func(options any, ctx *fiber.Ctx) error | ||||
| } | ||||
|  | ||||
| type RequestTransformerConfig struct { | ||||
| 	Type    string `json:"type" yaml:"type"` | ||||
| 	Options any    `json:"options" yaml:"options"` | ||||
| } | ||||
|  | ||||
| func (v *RequestTransformerConfig) TransformRequest(ctx *fiber.Ctx) error { | ||||
| 	for k, f := range Transformers { | ||||
| 		if k == v.Type { | ||||
| 			if f.ModifyRequest != nil { | ||||
| 				return f.ModifyRequest(v.Options, ctx) | ||||
| 			} | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (v *RequestTransformerConfig) TransformResponse(ctx *fiber.Ctx) error { | ||||
| 	for k, f := range Transformers { | ||||
| 		if k == v.Type { | ||||
| 			if f.ModifyResponse != nil { | ||||
| 				return f.ModifyResponse(v.Options, ctx) | ||||
| 			} | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Helpers | ||||
|  | ||||
| func DeserializeOptions[T any](data any) T { | ||||
| 	var out T | ||||
| 	raw, _ := json.Marshal(data) | ||||
| 	_ = json.Unmarshal(raw, &out) | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| // Map of Transformers | ||||
| // Every transformer need to be mapped here so that they can get work. | ||||
|  | ||||
| var Transformers = map[string]RequestTransformer{ | ||||
| 	"replacePath":      ReplacePath, | ||||
| 	"compressResponse": CompressResponse, | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| package transformers | ||||
|  | ||||
| import ( | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| var ReplacePath = RequestTransformer{ | ||||
| 	ModifyRequest: func(options any, ctx *fiber.Ctx) error { | ||||
| 		opts := DeserializeOptions[struct { | ||||
| 			Pattern string `json:"pattern" yaml:"pattern"` | ||||
| 			Value   string `json:"value" yaml:"value"` | ||||
| 			Repl    string `json:"repl" yaml:"repl"` // Use when complex mode(regexp) enabled | ||||
| 			Complex bool   `json:"complex" yaml:"complex"` | ||||
| 		}](options) | ||||
| 		path := string(ctx.Request().URI().Path()) | ||||
| 		if !opts.Complex { | ||||
| 			ctx.Path(strings.ReplaceAll(path, opts.Pattern, opts.Value)) | ||||
| 		} else if ex := regexp.MustCompile(opts.Pattern); ex != nil { | ||||
| 			ctx.Path(ex.ReplaceAllString(path, opts.Repl)) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
| @@ -1,58 +0,0 @@ | ||||
| package sign | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	UpstreamTypeFile      = "file" | ||||
| 	UpstreamTypeHypertext = "hypertext" | ||||
| 	UpstreamTypeUnknown   = "unknown" | ||||
| ) | ||||
|  | ||||
| type UpstreamInstance struct { | ||||
| 	ID  string `json:"id" yaml:"id"` | ||||
| 	URI string `json:"uri" yaml:"uri"` | ||||
| } | ||||
|  | ||||
| func (v *UpstreamInstance) GetType() string { | ||||
| 	protocol := strings.SplitN(v.URI, "://", 2)[0] | ||||
| 	switch protocol { | ||||
| 	case "file", "files": | ||||
| 		return UpstreamTypeFile | ||||
| 	case "http", "https": | ||||
| 		return UpstreamTypeHypertext | ||||
| 	} | ||||
|  | ||||
| 	return UpstreamTypeUnknown | ||||
| } | ||||
|  | ||||
| func (v *UpstreamInstance) GetRawURI() (string, url.Values) { | ||||
| 	uri := strings.SplitN(v.URI, "://", 2)[1] | ||||
| 	data := strings.SplitN(uri, "?", 2) | ||||
| 	data = append(data, " ") // Make data array least have two element | ||||
| 	qs, _ := url.ParseQuery(data[0]) | ||||
|  | ||||
| 	return data[0], qs | ||||
| } | ||||
|  | ||||
| func (v *UpstreamInstance) MakeURI(ctx *fiber.Ctx) string { | ||||
| 	var queries []string | ||||
| 	for k, v := range ctx.Queries() { | ||||
| 		parsed, _ := url.QueryUnescape(v) | ||||
| 		value := url.QueryEscape(parsed) | ||||
| 		queries = append(queries, fmt.Sprintf("%s=%s", k, value)) | ||||
| 	} | ||||
|  | ||||
| 	path := string(ctx.Request().URI().Path()) | ||||
| 	hash := string(ctx.Request().URI().Hash()) | ||||
|  | ||||
| 	return v.URI + path + | ||||
| 		lo.Ternary(len(queries) > 0, "?"+strings.Join(queries, "&"), "") + | ||||
| 		lo.Ternary(len(hash) > 0, "#"+hash, "") | ||||
| } | ||||
							
								
								
									
										28
									
								
								settings.yml
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								settings.yml
									
									
									
									
									
								
							| @@ -1,28 +0,0 @@ | ||||
| debug: | ||||
|   print_routes: true | ||||
| hypertext: | ||||
|   sideload_ports: | ||||
|     - :81 | ||||
|   sideload_secured_ports: [ ] | ||||
|   certificate: | ||||
|     redirect: false | ||||
|     sideload_key: ./cert.key | ||||
|     sideload_pem: ./cert.pem | ||||
|     key: ./cert.key | ||||
|     pem: ./cert.pem | ||||
|   limitation: | ||||
|     max_body_size: 536870912 | ||||
|     max_qps: -1 | ||||
|   ports: | ||||
|     - :8000 | ||||
|   secured_ports: [ ] | ||||
| paths: | ||||
|   configs: ./config | ||||
| performance: | ||||
|   request_logging: true | ||||
|   network_timeout: 3000 | ||||
|   prefork: false | ||||
| security: | ||||
|   sideload_trusted_proxies: | ||||
|     - localhost | ||||
|   credential: e81f43f32d934271af6322e5376f5f59 | ||||
							
								
								
									
										9
									
								
								src/config/loader.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/config/loader.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| use config::Config; | ||||
|  | ||||
| pub fn load_settings() -> Config { | ||||
|     Config::builder() | ||||
|         .add_source(config::File::with_name("Settings")) | ||||
|         .add_source(config::Environment::with_prefix("ROADSIGN")) | ||||
|         .build() | ||||
|         .unwrap() | ||||
| } | ||||
							
								
								
									
										1
									
								
								src/config/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/config/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| pub mod loader; | ||||
							
								
								
									
										39
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| mod config; | ||||
| mod proxies; | ||||
| mod sideload; | ||||
|  | ||||
| use poem::{listener::TcpListener, Route, Server}; | ||||
| use poem_openapi::OpenApiService; | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() -> Result<(), std::io::Error> { | ||||
|     // Load settings | ||||
|     let settings = config::loader::load_settings(); | ||||
|  | ||||
|     println!( | ||||
|         "Will listen at {:?}", | ||||
|         settings.get_array("listen.proxies").unwrap() | ||||
|     ); | ||||
|  | ||||
|     if std::env::var_os("RUST_LOG").is_none() { | ||||
|         std::env::set_var("RUST_LOG", "poem=debug"); | ||||
|     } | ||||
|     tracing_subscriber::fmt::init(); | ||||
|  | ||||
|     // Proxies | ||||
|  | ||||
|     // Sideload | ||||
|     let sideload = OpenApiService::new(sideload::SideloadApi, "Sideload API", "1.0") | ||||
|         .server("http://localhost:3000/cgi"); | ||||
|  | ||||
|     let sideload_server = Server::new(TcpListener::bind( | ||||
|         settings | ||||
|             .get_string("listen.sideload") | ||||
|             .unwrap_or("0.0.0.0:81".to_string()), | ||||
|     )) | ||||
|     .run(Route::new().nest("/cgi", sideload)); | ||||
|  | ||||
|     tokio::try_join!(sideload_server)?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
							
								
								
									
										22
									
								
								src/proxies/config.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/proxies/config.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| use std::{collections::HashMap, time::Duration}; | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct Region { | ||||
|     id: String, | ||||
|     locations: Vec<Location>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct Location { | ||||
|     hosts: Vec<String>, | ||||
|     paths: Vec<String>, | ||||
|     headers: Vec<HashMap<String, String>>, | ||||
|     query_strings: Vec<HashMap<String, String>>, | ||||
|     destination: Vec<Destination>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct Destination { | ||||
|     uri: Vec<String>, | ||||
|     timeout: Duration, | ||||
| } | ||||
							
								
								
									
										1
									
								
								src/proxies/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/proxies/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| pub mod config; | ||||
							
								
								
									
										3
									
								
								src/sideload/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/sideload/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| pub mod overview; | ||||
|  | ||||
| pub struct SideloadApi; | ||||
							
								
								
									
										14
									
								
								src/sideload/overview.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/sideload/overview.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| use poem_openapi::{param::Query, payload::PlainText, OpenApi}; | ||||
|  | ||||
| use super::SideloadApi; | ||||
|  | ||||
| #[OpenApi] | ||||
| impl SideloadApi { | ||||
|     #[oai(path = "/hello", method = "get")] | ||||
|     async fn index(&self, name: Query<Option<String>>) -> PlainText<String> { | ||||
|         match name.0 { | ||||
|             Some(name) => PlainText(format!("hello, {name}!")), | ||||
|             None => PlainText("hello!".to_string()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,85 +0,0 @@ | ||||
| # Benchmark | ||||
|  | ||||
| This result is design for test the performance of the roadsign. | ||||
| Welcome to contribute more tests of others reverse proxy software! | ||||
|  | ||||
| ## Platform | ||||
|  | ||||
| All tests are running on my workstation: | ||||
|  | ||||
| ```text | ||||
|                      ..'          littlesheep@LittleSheepdeMacBook-Pro | ||||
|                  ,xNMM.           ------------------------------------ | ||||
|                .OMMMMo            OS: macOS Sonoma 14.1 23B2073 arm64 | ||||
|                lMM"               Host: MacBook Pro (14-inch, Nov 2023, Three Thunderbolt 4 ports) | ||||
|      .;loddo:.  .olloddol;.       Kernel: 23.1.0 | ||||
|    cKMMMMMMMMMMNWMMMMMMMMMM0:     Uptime: 2 days, 1 hour, 16 mins | ||||
|  .KMMMMMMMMMMMMMMMMMMMMMMMWd.     Packages: 63 (brew), 4 (brew-cask) | ||||
|  XMMMMMMMMMMMMMMMMMMMMMMMX.       Shell: zsh 5.9 | ||||
| ;MMMMMMMMMMMMMMMMMMMMMMMM:        Display (Color LCD): 3024x1964 @ 120Hz (as 1512x982) [Built-in] | ||||
| :MMMMMMMMMMMMMMMMMMMMMMMM:        DE: Aqua | ||||
| .MMMMMMMMMMMMMMMMMMMMMMMMX.       WM: Quartz Compositor | ||||
|  kMMMMMMMMMMMMMMMMMMMMMMMMWd.     WM Theme: Multicolor (Dark) | ||||
|  'XMMMMMMMMMMMMMMMMMMMMMMMMMMk    Font: .AppleSystemUIFont [System], Helvetica [User] | ||||
|   'XMMMMMMMMMMMMMMMMMMMMMMMMK.    Cursor: Fill - Black, Outline - White (32px) | ||||
|     kMMMMMMMMMMMMMMMMMMMMMMd      Terminal: iTerm 3.4.22 | ||||
|      ;KMMMMMMMWXXWMMMMMMMk.       Terminal Font: MesloLGMNFM-Regular (12pt) | ||||
|        "cooc*"    "*coo'"         CPU: Apple M3 Max (14) @ 4.06 GHz | ||||
|                                   GPU: Apple M3 Max (30) [Integrated] | ||||
|                                   Memory: 18.45 GiB / 36.00 GiB (51%) | ||||
|                                   Swap: Disabled | ||||
|                                   Disk (/): 72.52 GiB / 926.35 GiB (8%) - apfs [Read-only] | ||||
|                                   Local IP (en0): 192.168.50.0/24 * | ||||
|                                   Battery: 100% [AC connected] | ||||
|                                   Power Adapter: 96W USB-C Power Adapter | ||||
|                                   Locale: zh_CN.UTF-8 | ||||
| ``` | ||||
|  | ||||
| ## Results | ||||
|  | ||||
| The tests are run in the order `nginx -> roadsign without prefork -> roadsign with prefork`. There is no reason why nginx performance should be affected by hardware temperature. | ||||
|  | ||||
| ### Nginx | ||||
|  | ||||
| ```shell | ||||
| go-wrk -c 60 -d 120 http://localhost:8001 | ||||
| # => Running 120s test @ http://localhost:8001 | ||||
| # =>   60 goroutine(s) running concurrently | ||||
| # => 515749 requests in 1m59.953302003s, 245.92MB read | ||||
| # => Requests/sec:           4299.58 | ||||
| # => Transfer/sec:           2.05MB | ||||
| # => Avg Req Time:           13.954846ms | ||||
| # => Fastest Request:        0s | ||||
| # => Slowest Request:        410.6972ms | ||||
| # => Number of Errors:       0 | ||||
| ``` | ||||
|  | ||||
| ### RoadSign | ||||
|  | ||||
| ```shell | ||||
| go-wrk -c 60 -d 120 http://localhost:8000 | ||||
| # => Running 120s test @ http://localhost:8000 | ||||
| # =>   60 goroutine(s) running concurrently | ||||
| # => 8905230 requests in 1m56.215762709s, 3.52GB read | ||||
| # => Requests/sec:		76626.70 | ||||
| # => Transfer/sec:		30.98MB | ||||
| # => Avg Req Time:		783.016µs | ||||
| # => Fastest Request:	28.542µs | ||||
| # => Slowest Request:	46.773083ms | ||||
| # => Number of Errors:	0 | ||||
| ``` | ||||
|  | ||||
| ### RoadSign w/ Prefork | ||||
|  | ||||
| ```shell | ||||
| go-wrk -c 60 -d 120 http://localhost:8000 | ||||
| # => Running 120s test @ http://localhost:8000 | ||||
| # =>  60 goroutine(s) running concurrently | ||||
| # => 4784308 requests in 1m59.100307178s, 1.89GB read | ||||
| # => Requests/sec:		40170.41 | ||||
| # => Transfer/sec:		16.24MB | ||||
| # => Avg Req Time:		1.493636ms | ||||
| # => Fastest Request:	34.291µs | ||||
| # => Slowest Request:	8.727666ms | ||||
| # => Number of Errors:	0 | ||||
| ``` | ||||
							
								
								
									
										3
									
								
								test/data/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								test/data/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +0,0 @@ | ||||
| /ssr | ||||
| /spa | ||||
| /congress | ||||
| @@ -1,12 +0,0 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head> | ||||
| 	<meta charset="utf-8"> | ||||
| 	<meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 	<title>Hello, World!</title> | ||||
| </head> | ||||
| <body> | ||||
| 	<p>Hello, there!</p> | ||||
| 	<p>Here's the roadsign vs. nginx benchmarking test data!</p> | ||||
| </body> | ||||
| </html> | ||||
| @@ -1,117 +0,0 @@ | ||||
|  | ||||
| #user  nobody; | ||||
| worker_processes  1; | ||||
|  | ||||
| #error_log  logs/error.log; | ||||
| #error_log  logs/error.log  notice; | ||||
| #error_log  logs/error.log  info; | ||||
|  | ||||
| #pid        logs/nginx.pid; | ||||
|  | ||||
|  | ||||
| events { | ||||
|     worker_connections  1024; | ||||
| } | ||||
|  | ||||
|  | ||||
| http { | ||||
|     include       mime.types; | ||||
|     default_type  application/octet-stream; | ||||
|  | ||||
|     #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" ' | ||||
|     #                  '$status $body_bytes_sent "$http_referer" ' | ||||
|     #                  '"$http_user_agent" "$http_x_forwarded_for"'; | ||||
|  | ||||
|     #access_log  logs/access.log  main; | ||||
|  | ||||
|     sendfile        on; | ||||
|     #tcp_nopush     on; | ||||
|  | ||||
|     #keepalive_timeout  0; | ||||
|     keepalive_timeout  65; | ||||
|  | ||||
|     #gzip  on; | ||||
|  | ||||
|     server { | ||||
|         listen       8001; | ||||
|         server_name  localhost; | ||||
|  | ||||
|         #charset koi8-r; | ||||
|  | ||||
|         #access_log  logs/host.access.log  main; | ||||
|  | ||||
|         location / { | ||||
|             root   ../data; | ||||
|             index  index.html index.htm; | ||||
|         } | ||||
|  | ||||
|         #error_page  404              /404.html; | ||||
|  | ||||
|         # redirect server error pages to the static page /50x.html | ||||
|         # | ||||
|         error_page   500 502 503 504  /50x.html; | ||||
|         location = /50x.html { | ||||
|             root   html; | ||||
|         } | ||||
|  | ||||
|         # proxy the PHP scripts to Apache listening on 127.0.0.1:80 | ||||
|         # | ||||
|         #location ~ \.php$ { | ||||
|         #    proxy_pass   http://127.0.0.1; | ||||
|         #} | ||||
|  | ||||
|         # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 | ||||
|         # | ||||
|         #location ~ \.php$ { | ||||
|         #    root           html; | ||||
|         #    fastcgi_pass   127.0.0.1:9000; | ||||
|         #    fastcgi_index  index.php; | ||||
|         #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name; | ||||
|         #    include        fastcgi_params; | ||||
|         #} | ||||
|  | ||||
|         # deny access to .htaccess files, if Apache's document root | ||||
|         # concurs with nginx's one | ||||
|         # | ||||
|         #location ~ /\.ht { | ||||
|         #    deny  all; | ||||
|         #} | ||||
|     } | ||||
|  | ||||
|  | ||||
|     # another virtual host using mix of IP-, name-, and port-based configuration | ||||
|     # | ||||
|     #server { | ||||
|     #    listen       8000; | ||||
|     #    listen       somename:8080; | ||||
|     #    server_name  somename  alias  another.alias; | ||||
|  | ||||
|     #    location / { | ||||
|     #        root   html; | ||||
|     #        index  index.html index.htm; | ||||
|     #    } | ||||
|     #} | ||||
|  | ||||
|  | ||||
|     # HTTPS server | ||||
|     # | ||||
|     #server { | ||||
|     #    listen       443 ssl; | ||||
|     #    server_name  localhost; | ||||
|  | ||||
|     #    ssl_certificate      cert.pem; | ||||
|     #    ssl_certificate_key  cert.key; | ||||
|  | ||||
|     #    ssl_session_cache    shared:SSL:1m; | ||||
|     #    ssl_session_timeout  5m; | ||||
|  | ||||
|     #    ssl_ciphers  HIGH:!aNULL:!MD5; | ||||
|     #    ssl_prefer_server_ciphers  on; | ||||
|  | ||||
|     #    location / { | ||||
|     #        root   html; | ||||
|     #        index  index.html index.htm; | ||||
|     #    } | ||||
|     #} | ||||
|  | ||||
| } | ||||
| @@ -1,8 +0,0 @@ | ||||
| name: Example Site | ||||
| rules: | ||||
|   - host: ["localhost:8000"] | ||||
|     path: ["/"] | ||||
| upstreams: | ||||
|   - id: example | ||||
|     name: Benchmarking Data | ||||
|     uri: files://../data/spa?fallback=index.html | ||||
| @@ -1,26 +0,0 @@ | ||||
| debug: | ||||
|     print_routes: false | ||||
| hypertext: | ||||
|     sideload_ports: [":81"] | ||||
|     sideload_secured_ports: [] | ||||
|     certificate: | ||||
|         sideload_key: ./cert.key | ||||
|         sideload_pem: ./cert.pem | ||||
|         key: ./cert.key | ||||
|         pem: ./cert.pem | ||||
|     limitation: | ||||
|         max_body_size: 536870912 | ||||
|         max_qps: -1 | ||||
|     ports: | ||||
|         - :8000 | ||||
|     secured_ports: [] | ||||
| paths: | ||||
|     configs: ./config | ||||
| performance: | ||||
|     request_logging: false | ||||
|     network_timeout: 3000 | ||||
|     prefork: false | ||||
| security: | ||||
|     sideload_trusted_proxies: | ||||
|         - localhost | ||||
|     credential: e81f43f32d934271af6322e5376f5f59 | ||||
| @@ -1,12 +0,0 @@ | ||||
| name: Example Site | ||||
| rules: | ||||
|   - host: ["localhost:8000"] | ||||
|     path: ["/"] | ||||
| upstreams: | ||||
|   - id: example | ||||
|     name: Benchmarking Data | ||||
|     uri: http://localhost:3000 | ||||
| processes: | ||||
|   - id: nuxt-ssr | ||||
|     workdir: ../data/ssr | ||||
|     command: ["node", ".output/server/index.mjs"] | ||||
| @@ -1,26 +0,0 @@ | ||||
| debug: | ||||
|     print_routes: false | ||||
| hypertext: | ||||
|     sideload_ports: [":81"] | ||||
|     sideload_secured_ports: [] | ||||
|     certificate: | ||||
|         sideload_key: ./cert.key | ||||
|         sideload_pem: ./cert.pem | ||||
|         key: ./cert.key | ||||
|         pem: ./cert.pem | ||||
|     limitation: | ||||
|         max_body_size: 536870912 | ||||
|         max_qps: -1 | ||||
|     ports: | ||||
|         - :8000 | ||||
|     secured_ports: [] | ||||
| paths: | ||||
|     configs: ./config | ||||
| performance: | ||||
|     request_logging: false | ||||
|     network_timeout: 3000 | ||||
|     prefork: false | ||||
| security: | ||||
|     sideload_trusted_proxies: | ||||
|         - localhost | ||||
|     credential: e81f43f32d934271af6322e5376f5f59 | ||||
| @@ -1,8 +0,0 @@ | ||||
| name: Example Site | ||||
| rules: | ||||
|   - host: ["localhost:8000"] | ||||
|     path: ["/"] | ||||
| upstreams: | ||||
|   - id: example | ||||
|     name: Benchmarking Data | ||||
|     uri: files://../data | ||||
| @@ -1,26 +0,0 @@ | ||||
| debug: | ||||
|     print_routes: false | ||||
| hypertext: | ||||
|     sideload_ports: [":81"] | ||||
|     sideload_secured_ports: [] | ||||
|     certificate: | ||||
|         sideload_key: ./cert.key | ||||
|         sideload_pem: ./cert.pem | ||||
|         key: ./cert.key | ||||
|         pem: ./cert.pem | ||||
|     limitation: | ||||
|         max_body_size: 536870912 | ||||
|         max_qps: -1 | ||||
|     ports: | ||||
|         - :8000 | ||||
|     secured_ports: [] | ||||
| paths: | ||||
|     configs: ./config | ||||
| performance: | ||||
|     request_logging: false | ||||
|     network_timeout: 3000 | ||||
|     prefork: true | ||||
| security: | ||||
|     sideload_trusted_proxies: | ||||
|         - localhost | ||||
|     credential: e81f43f32d934271af6322e5376f5f59 | ||||
| @@ -1,8 +0,0 @@ | ||||
| name: Example Site | ||||
| rules: | ||||
|   - host: ["localhost:8000"] | ||||
|     path: ["/"] | ||||
| upstreams: | ||||
|   - id: example | ||||
|     name: Benchmarking Data | ||||
|     uri: files://../data | ||||
| @@ -1,26 +0,0 @@ | ||||
| debug: | ||||
|     print_routes: false | ||||
| hypertext: | ||||
|     sideload_ports: [":81"] | ||||
|     sideload_secured_ports: [] | ||||
|     certificate: | ||||
|         sideload_key: ./cert.key | ||||
|         sideload_pem: ./cert.pem | ||||
|         key: ./cert.key | ||||
|         pem: ./cert.pem | ||||
|     limitation: | ||||
|         max_body_size: 536870912 | ||||
|         max_qps: -1 | ||||
|     ports: | ||||
|         - :8000 | ||||
|     secured_ports: [] | ||||
| paths: | ||||
|     configs: ./config | ||||
| performance: | ||||
|     request_logging: false | ||||
|     network_timeout: 3000 | ||||
|     prefork: false | ||||
| security: | ||||
|     sideload_trusted_proxies: | ||||
|         - localhost | ||||
|     credential: e81f43f32d934271af6322e5376f5f59 | ||||
		Reference in New Issue
	
	Block a user