🔀 Merge pull request '♻️ RoadSign v2' (#6) from refactor/v2 into master
All checks were successful
release-nightly / build-docker (push) Successful in 1m59s

Reviewed-on: https://code.smartsheep.studio/Goatworks/RoadSign/pulls/6
This commit is contained in:
LittleSheep 2024-01-26 05:32:06 +00:00
commit 72d23c35f5
91 changed files with 1264 additions and 1791 deletions

View File

@ -3,9 +3,8 @@
{
"type": "go",
"name": "Run RoadSign",
"goExecPath": "C:\\Tools\\Scoop\\shims\\go.exe",
"buildParams": ["code.smartsheep.studio/goatworks/roadsign/pkg/cmd"],
"goExecPath": "/opt/homebrew/bin/go",
"buildParams": ["code.smartsheep.studio/goatworks/roadsign/pkg/cmd/server"],
},
]
}

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
/config
/letsencrypt
.DS_Store

View File

@ -1,9 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="FacetManager">
<facet type="Python" name="Python facet">
<configuration sdkName="Python 3.9" />
</facet>
</component>
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
</component>
</module>

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.9" />
</component>
</project>

View File

@ -5,15 +5,19 @@ A blazing fast reverse proxy with a lot of shining features.
## Features
1. Reverse proxy
2. Static file hosting
3. ~~Analytics and Metrics~~
4. Integrate with CI/CD
5. Webhook integration
6. ~~Web management panel~~
7. One-liner CLI
8. **Blazing fast ⚡**
2. HTTP2 Support
3. WebSocket Support
4. Static File Hosting
5. Low Configuration
6. Analytics and Metrics
7. Integrate with CI/CD
8. Web management panel (Work in progres for v2, available in v1)
9. One-liner CLI
10. Open-source and free
11. **Blazing fast ⚡**
> Deleted item means under construction, check out our roadmap!
> [!IMPORTANT]
> Currently roadsign haven't supported for server-side events. We are working on it.
### How fast is it?
@ -42,7 +46,7 @@ We strongly recommend you install RoadSign via docker compose.
version: "3"
services:
roadsign:
image: code.smartsheep.studio/goatworks/roadsign:nightly
image: xsheep2010/roadsign:nightly
restart: always
volumes:
- "./certs:/certs" # Optional, use for storage certificates
@ -55,12 +59,11 @@ services:
- "81:81"
```
After that, you can manage your roadsign instance with RoadSign CLI aka. RDS CLI.
After that, you can manage your roadsign instance with RoadSign CLI aka. RDC.
To install it, run this command. (Make sure you have golang toolchain on your computer)
```shell
go install -buildvcs code.smartsheep.studio/goatworks/roadsign/pkg/cmd/rds@latest
# Tips: Add `buildvsc` flag to provide more detail compatibility check.
go install code.smartsheep.studio/goatworks/roadsign/pkg/cmd/rdc@latest
```
## Usage
@ -73,7 +76,7 @@ After configure, you need sync your config to remote server. Before that, add a
rds cli with this command.
```shell
rds connect <id> <url> <password>
rdc connect <id> <url> <password>
# ID will allow you find this server in after commands.
# URL is to your roadsign server sideload api.
# Password is your roadsign server credential.
@ -85,7 +88,7 @@ rds connect <id> <url> <password>
Then, sync your local config to remote.
```shell
rds sync <server id> <site id> <config file>
rdc sync <server id> <region id> <config file>
# Server ID is your server added by last command.
# Site ID is your new site id or old site id if you need update it.
# Config File is your local config file path.

31
config/example.toml Normal file
View File

@ -0,0 +1,31 @@
id = "example-region"
[[locations]]
id = "example-websocket"
host = ["localhost:8000"]
path = ["/ws"]
[[locations.destinations]]
id = "example-websocket-destination"
uri = "http://localhost:8765"
[[locations]]
id = "example-warden"
host = ["localhost:4321"]
path = ["/"]
[[locations.destinations]]
id = "example-warden-destination"
uri = "http://localhost:4321"
[[applications]]
id = "example-warden-app"
workdir = "test/data/warden"
command = ["node", "dist/server/entry.mjs"]
environment = ["PUBLIC_CMS=https://smartsheep.studio"]
[[locations]]
id = "example-static"
host = ["localhost:8000"]
path = ["/roadsign"]
[[locations.destinations]]
id = "example-static-destination"
uri = "files://test/data"

24
go.mod
View File

@ -3,29 +3,32 @@ 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/fasthttp/websocket v1.5.7
github.com/gofiber/fiber/v2 v2.52.0
github.com/google/uuid v1.6.0
github.com/json-iterator/go v1.1.12
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
github.com/valyala/fasthttp v1.51.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/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sync v0.5.0 // indirect
)
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/andybalholm/brotli v1.1.0 // 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
@ -34,13 +37,13 @@ require (
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/pelletier/go-toml/v2 v2.1.1
github.com/philhofer/fwd v1.1.2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rivo/uniseg v0.4.4 // 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/afero v1.11.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
@ -50,9 +53,8 @@ require (
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
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.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
)

457
go.sum
View File

@ -1,53 +1,5 @@
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/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
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=
@ -55,100 +7,28 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
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/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KHau4=
github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU=
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/gofiber/fiber/v2 v2.52.0 h1:S+qXi7y+/Pgvqq4DrSmREGiFwtB7Bu6+QFLuIHYw/UE=
github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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=
@ -168,19 +48,17 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
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/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.1.1/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/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
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=
@ -198,10 +76,12 @@ github.com/saracen/fastzip v0.1.11 h1:NnExbTEJbya7148cov09BCxwfur9tQ5BQ1QyQH6Xle
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/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
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/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
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=
@ -212,9 +92,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
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=
@ -227,163 +104,36 @@ 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/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
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/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
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=
@ -392,186 +142,27 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
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/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.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/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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=

View File

@ -17,7 +17,7 @@ type CliConnection struct {
}
func (v CliConnection) CheckConnectivity() error {
client := fiber.Get(v.Url + "/cgi/connectivity")
client := fiber.Get(v.Url + "/cgi/metadata")
client.BasicAuth("RoadSign CLI", v.Credential)
if status, data, err := client.Bytes(); len(err) > 0 {

View File

@ -2,17 +2,16 @@ 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"
jsoniter "github.com/json-iterator/go"
"code.smartsheep.studio/goatworks/roadsign/pkg/cmd/rdc/conn"
"github.com/gofiber/fiber/v2"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v2"
)
var DeployCommands = []*cli.Command{
@ -58,7 +57,6 @@ var DeployCommands = []*cli.Command{
},
{
Name: "sync",
Aliases: []string{"sc"},
ArgsUsage: "<server> <site> <configuration path>",
Action: func(ctx *cli.Context) error {
if ctx.Args().Len() < 3 {
@ -72,19 +70,18 @@ var DeployCommands = []*cli.Command{
return fmt.Errorf("couldn't connect server: %s", err.Error())
}
var site sign.SiteConfig
var raw []byte
if file, err := os.Open(ctx.Args().Get(2)); err != nil {
return err
} else {
raw, _ := io.ReadAll(file)
yaml.Unmarshal(raw, &site)
raw, _ = io.ReadAll(file)
}
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).
Body(raw).
BasicAuth("RoadSign CLI", server.Credential)
if status, data, err := client.Bytes(); len(err) > 0 {

View File

@ -4,8 +4,8 @@ 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"
"code.smartsheep.studio/goatworks/roadsign/pkg/cmd/rdc/conn"
"code.smartsheep.studio/goatworks/roadsign/pkg/cmd/rdc/deploy"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
@ -21,7 +21,7 @@ func main() {
// Configure settings
viper.AddConfigPath("$HOME")
viper.SetConfigName(".roadsignrc")
viper.SetConfigType("yaml")
viper.SetConfigType("toml")
// Load settings
if err := viper.ReadInConfig(); err != nil {

View File

@ -8,8 +8,8 @@ import (
roadsign "code.smartsheep.studio/goatworks/roadsign/pkg"
"code.smartsheep.studio/goatworks/roadsign/pkg/hypertext"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"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"
@ -26,7 +26,7 @@ func main() {
viper.AddConfigPath(".")
viper.AddConfigPath("..")
viper.SetConfigName("settings")
viper.SetConfigType("yaml")
viper.SetConfigType("toml")
// Load settings
if err := viper.ReadInConfig(); err != nil {
@ -43,20 +43,15 @@ func main() {
log.Warn().Msgf("RoadSign auto generated api credential is %s", credential)
}
// Load & init sign
if err := sign.ReadInConfig(viper.GetString("paths.configs")); err != nil {
// Load & init navigator
if err := navi.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.")
log.Info().Int("count", len(navi.R.Regions)).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 warden
navi.InitializeWarden(navi.R.Regions)
// Init hypertext server
hypertext.RunServer(

View File

@ -1,9 +1,11 @@
package hypertext
import (
"github.com/spf13/viper"
"math/rand"
"regexp"
"code.smartsheep.studio/goatworks/roadsign/pkg/sign"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
@ -16,16 +18,16 @@ func UseProxies(app *fiber.App) {
headers := ctx.GetReqHeaders()
// Filtering sites
for _, site := range sign.App.Sites {
for _, region := range navi.R.Regions {
// Matching rules
for _, rule := range site.Rules {
if !lo.Contains(rule.Host, host) {
for _, location := range region.Locations {
if !lo.Contains(location.Host, host) {
continue
}
if !func() bool {
flag := false
for _, pattern := range rule.Path {
for _, pattern := range location.Path {
if ok, _ := regexp.MatchString(pattern, path); ok {
flag = true
break
@ -38,7 +40,7 @@ func UseProxies(app *fiber.App) {
// Filter query strings
flag := true
for rk, rv := range rule.Queries {
for rk, rv := range location.Queries {
for ik, iv := range queries {
if rk != ik && rv != iv {
flag = false
@ -54,7 +56,7 @@ func UseProxies(app *fiber.App) {
}
// Filter headers
for rk, rv := range rule.Headers {
for rk, rv := range location.Headers {
for ik, iv := range headers {
if rk == ik {
for _, ov := range iv {
@ -76,9 +78,12 @@ func UseProxies(app *fiber.App) {
continue
}
idx := rand.Intn(len(location.Destinations))
dest := location.Destinations[idx]
// Passing all the rules means the site is what we are looking for.
// Let us respond to our client!
return makeResponse(ctx, site)
return makeResponse(ctx, region, &location, &dest)
}
}
@ -89,23 +94,46 @@ func UseProxies(app *fiber.App) {
})
}
func makeResponse(ctx *fiber.Ctx, site *sign.SiteConfig) error {
func makeResponse(ctx *fiber.Ctx, region *navi.Region, location *navi.Location, dest *navi.Destination) error {
uri := ctx.Request().URI().String()
// Modify request
for _, transformer := range site.Transformers {
for _, transformer := range dest.Transformers {
if err := transformer.TransformRequest(ctx); err != nil {
return err
}
}
// Forward
err := sign.App.Forward(ctx, site)
err := navi.R.Forward(ctx, dest)
// Modify response
for _, transformer := range site.Transformers {
for _, transformer := range dest.Transformers {
if err := transformer.TransformResponse(ctx); err != nil {
return err
}
}
// Collect trace
if viper.GetBool("telemetry.capture_traces") {
var message string
if err != nil {
message = err.Error()
}
go navi.R.AddTrace(navi.RoadTrace{
Region: region.ID,
Location: location.ID,
Destination: dest.ID,
Uri: uri,
IpAddress: ctx.IP(),
UserAgent: ctx.Get(fiber.HeaderUserAgent),
Error: navi.RoadTraceError{
IsNull: err == nil,
Message: message,
},
})
}
return err
}

View File

@ -14,4 +14,4 @@ func init() {
}
}
var AppVersion = "1.2.1"
var AppVersion = "2.0.0-delta1"

View File

@ -1,4 +1,4 @@
package sign
package navi
import (
"io"
@ -6,32 +6,39 @@ import (
"path/filepath"
"strings"
"gopkg.in/yaml.v2"
"github.com/spf13/viper"
"github.com/pelletier/go-toml/v2"
)
var App *RoadApp
var R *RoadApp
func ReadInConfig(root string) error {
instance := &RoadApp{
Sites: []*SiteConfig{},
Regions: make([]*Region, 0),
Traces: make([]RoadTrace, 0, viper.GetInt("performance.traces_limit")),
}
if err := filepath.Walk(root, func(fp string, info os.FileInfo, err error) error {
var site SiteConfig
if err := filepath.Walk(root, func(fp string, info os.FileInfo, _ error) error {
var region Region
if info.IsDir() {
return nil
} else if !strings.HasSuffix(info.Name(), ".toml") {
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 {
} else if err := toml.Unmarshal(data, &region); 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)
if region.Disabled {
return nil
}
instance.Regions = append(instance.Regions, &region)
}
return nil
@ -39,7 +46,7 @@ func ReadInConfig(root string) error {
return err
}
App = instance
R = instance
return nil
}

25
pkg/navi/metrics.go Normal file
View File

@ -0,0 +1,25 @@
package navi
import "github.com/spf13/viper"
type RoadTrace struct {
Region string `json:"region"`
Location string `json:"location"`
Destination string `json:"destination"`
Uri string `json:"uri"`
IpAddress string `json:"ip_address"`
UserAgent string `json:"user_agent"`
Error RoadTraceError `json:"error"`
}
type RoadTraceError struct {
IsNull bool `json:"is_null"`
Message string `json:"message"`
}
func (v *RoadApp) AddTrace(trace RoadTrace) {
v.Traces = append(v.Traces, trace)
if len(v.Traces) > viper.GetInt("performance.traces_limit") {
v.Traces = v.Traces[1:]
}
}

View File

@ -1,33 +1,110 @@
package sign
package navi
import (
"errors"
"fmt"
"github.com/fasthttp/websocket"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/proxy"
"github.com/gofiber/fiber/v2/utils"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
"github.com/spf13/viper"
"github.com/valyala/fasthttp"
"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 {
func makeUnifiedResponse(c *fiber.Ctx, dest *Destination) error {
if websocket.FastHTTPIsWebSocketUpgrade(c.Context()) {
// Handle websocket
return makeWebsocketResponse(c, dest)
} else {
_, queries := dest.GetRawUri()
if len(queries.Get("sse")) > 0 {
// Handle server-side event
return makeSeverSideEventResponse(c, dest)
} else {
// Handle normal http request
return makeHypertextResponse(c, dest)
}
}
}
func makeHypertextResponse(c *fiber.Ctx, dest *Destination) error {
timeout := time.Duration(viper.GetInt64("performance.network_timeout")) * time.Millisecond
return proxy.Do(c, upstream.MakeURI(c), &fasthttp.Client{
return proxy.Do(c, dest.MakeUri(c), &fasthttp.Client{
ReadTimeout: timeout,
WriteTimeout: timeout,
})
}
func makeFileResponse(c *fiber.Ctx, upstream *UpstreamInstance) error {
uri, queries := upstream.GetRawURI()
var wsUpgrader = websocket.FastHTTPUpgrader{}
func makeWebsocketResponse(c *fiber.Ctx, dest *Destination) error {
uri := dest.MakeWebsocketUri(c)
// Upgrade connection
return wsUpgrader.Upgrade(c.Context(), func(conn *websocket.Conn) {
// Dial the destination
remote, _, err := websocket.DefaultDialer.Dial(uri, nil)
if err != nil {
return
}
defer remote.Close()
// Read messages from remote
disconnect := make(chan struct{})
signal := make(chan struct {
head int
data []byte
})
go func() {
defer close(disconnect)
for {
mode, message, err := remote.ReadMessage()
if err != nil {
log.Warn().Err(err).Msg("An error occurred during the websocket proxying...")
return
} else {
signal <- struct {
head int
data []byte
}{head: mode, data: message}
}
}
}()
// Relay the destination websocket to client
for {
select {
case <-disconnect:
case val := <-signal:
if err := conn.WriteMessage(val.head, val.data); err != nil {
return
}
default:
if head, data, err := conn.ReadMessage(); err != nil {
return
} else {
remote.WriteMessage(head, data)
}
}
}
})
}
func makeSeverSideEventResponse(c *fiber.Ctx, dest *Destination) error {
// TODO Impl SSE with https://github.com/gofiber/recipes/blob/master/sse/main.go
return fiber.NewError(fiber.StatusNotImplemented, "Server-side-events was not available now.")
}
func makeFileResponse(c *fiber.Ctx, dest *Destination) error {
uri, queries := dest.GetRawUri()
root := http.Dir(uri)
method := c.Method()

25
pkg/navi/route.go Normal file
View File

@ -0,0 +1,25 @@
package navi
import (
"code.smartsheep.studio/goatworks/roadsign/pkg/navi/transformers"
"github.com/gofiber/fiber/v2"
)
type RoadApp struct {
Regions []*Region `json:"regions"`
Traces []RoadTrace `json:"traces"`
}
func (v *RoadApp) Forward(ctx *fiber.Ctx, dest *Destination) error {
switch dest.GetType() {
case DestinationHypertext:
return makeUnifiedResponse(ctx, dest)
case DestinationStaticFile:
return makeFileResponse(ctx, dest)
default:
return fiber.ErrBadGateway
}
}
type RequestTransformerConfig = transformers.TransformerConfig

87
pkg/navi/struct.go Normal file
View File

@ -0,0 +1,87 @@
package navi
import (
"fmt"
"net/url"
"strings"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi/transformers"
"code.smartsheep.studio/goatworks/roadsign/pkg/warden"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
type Region struct {
ID string `json:"id" toml:"id"`
Disabled bool `json:"disabled" toml:"disabled"`
Locations []Location `json:"locations" toml:"locations"`
Applications []warden.Application `json:"applications" toml:"applications"`
}
type Location struct {
ID string `json:"id" toml:"id"`
Host []string `json:"host" toml:"host"`
Path []string `json:"path" toml:"path"`
Queries map[string]string `json:"queries" toml:"queries"`
Headers map[string][]string `json:"headers" toml:"headers"`
Destinations []Destination `json:"destinations" toml:"destinations"`
}
type DestinationType = int8
const (
DestinationHypertext = DestinationType(iota)
DestinationStaticFile
DestinationUnknown
)
type Destination struct {
ID string `json:"id" toml:"id"`
Uri string `json:"uri" toml:"uri"`
Transformers []transformers.TransformerConfig `json:"transformers" toml:"transformers"`
}
func (v *Destination) GetProtocol() string {
return strings.SplitN(v.Uri, "://", 2)[0]
}
func (v *Destination) GetType() DestinationType {
protocol := v.GetProtocol()
switch protocol {
case "http", "https":
return DestinationHypertext
case "file", "files":
return DestinationStaticFile
}
return DestinationUnknown
}
func (v *Destination) 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[1])
return data[0], qs
}
func (v *Destination) 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())
uri, _ := v.GetRawUri()
return uri + path +
lo.Ternary(len(queries) > 0, "?"+strings.Join(queries, "&"), "") +
lo.Ternary(len(hash) > 0, "#"+hash, "")
}
func (v *Destination) MakeWebsocketUri(ctx *fiber.Ctx) string {
return strings.Replace(v.MakeUri(ctx), "http", "ws", 1)
}

View File

@ -5,13 +5,13 @@ import (
"github.com/valyala/fasthttp"
)
var CompressResponse = RequestTransformer{
var CompressResponse = Transformer{
ModifyResponse: func(options any, ctx *fiber.Ctx) error {
opts := DeserializeOptions[struct {
Level int `json:"level" yaml:"level"`
Level int `json:"level" toml:"level"`
}](options)
var fctx = func(c *fasthttp.RequestCtx) {}
fctx := func(c *fasthttp.RequestCtx) {}
var compressor fasthttp.RequestHandler
switch opts.Level {
// Best Speed Mode

View File

@ -9,17 +9,17 @@ import (
var json = jsoniter.ConfigCompatibleWithStandardLibrary
type RequestTransformer struct {
type Transformer 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"`
type TransformerConfig struct {
Type string `json:"type" toml:"type"`
Options any `json:"options" toml:"options"`
}
func (v *RequestTransformerConfig) TransformRequest(ctx *fiber.Ctx) error {
func (v *TransformerConfig) TransformRequest(ctx *fiber.Ctx) error {
for k, f := range Transformers {
if k == v.Type {
if f.ModifyRequest != nil {
@ -31,7 +31,7 @@ func (v *RequestTransformerConfig) TransformRequest(ctx *fiber.Ctx) error {
return nil
}
func (v *RequestTransformerConfig) TransformResponse(ctx *fiber.Ctx) error {
func (v *TransformerConfig) TransformResponse(ctx *fiber.Ctx) error {
for k, f := range Transformers {
if k == v.Type {
if f.ModifyResponse != nil {
@ -55,7 +55,7 @@ func DeserializeOptions[T any](data any) T {
// Map of Transformers
// Every transformer need to be mapped here so that they can get work.
var Transformers = map[string]RequestTransformer{
var Transformers = map[string]Transformer{
"replacePath": ReplacePath,
"compressResponse": CompressResponse,
}

View File

@ -1,18 +1,19 @@
package transformers
import (
"github.com/gofiber/fiber/v2"
"regexp"
"strings"
"github.com/gofiber/fiber/v2"
)
var ReplacePath = RequestTransformer{
var ReplacePath = Transformer{
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"`
Pattern string `json:"pattern" toml:"pattern"`
Value string `json:"value" toml:"value"`
Repl string `json:"repl" toml:"repl"` // Use when complex mode(regexp) enabled
Complex bool `json:"complex" toml:"complex"`
}](options)
path := string(ctx.Request().URI().Path())
if !opts.Complex {

17
pkg/navi/warden.go Normal file
View File

@ -0,0 +1,17 @@
package navi
import "code.smartsheep.studio/goatworks/roadsign/pkg/warden"
func InitializeWarden(regions []*Region) {
for _, region := range regions {
for _, application := range region.Applications {
warden.InstancePool = append(warden.InstancePool, &warden.AppInstance{
Manifest: application,
})
}
}
for _, instance := range warden.InstancePool {
instance.Wake()
}
}

View File

@ -0,0 +1,26 @@
package sideload
import (
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"code.smartsheep.studio/goatworks/roadsign/pkg/warden"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
func getApplications(c *fiber.Ctx) error {
applications := lo.FlatMap(navi.R.Regions, func(item *navi.Region, idx int) []warden.Application {
return item.Applications
})
return c.JSON(applications)
}
func getApplicationLogs(c *fiber.Ctx) error {
if instance, ok := lo.Find(warden.InstancePool, func(item *warden.AppInstance) bool {
return item.Manifest.ID == c.Params("id")
}); !ok {
return fiber.NewError(fiber.StatusNotFound)
} else {
return c.SendString(instance.Logs())
}
}

View File

@ -5,7 +5,7 @@ import (
"github.com/gofiber/fiber/v2"
)
func responseConnectivity(c *fiber.Ctx) error {
func getMetadata(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"server": "RoadSign",
"version": roadsign.AppVersion,

10
pkg/sideload/metrics.go Normal file
View File

@ -0,0 +1,10 @@
package sideload
import (
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"github.com/gofiber/fiber/v2"
)
func getTraces(c *fiber.Ctx) error {
return c.JSON(navi.R.Traces)
}

View File

@ -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())
}
}

View File

@ -1,11 +1,12 @@
package sideload
import (
"code.smartsheep.studio/goatworks/roadsign/pkg/warden"
"context"
"os"
"path/filepath"
"code.smartsheep.studio/goatworks/roadsign/pkg/sign"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/samber/lo"
@ -14,23 +15,23 @@ import (
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 {
var destination *navi.Destination
var application *warden.Application
for _, item := range navi.R.Regions {
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 _, location := range item.Locations {
for _, dest := range location.Destinations {
if dest.ID == c.Params("slug") {
destination = &dest
workdir, _ = dest.GetRawUri()
break
}
}
}
for _, proc := range item.Processes {
if proc.ID == c.Params("slug") {
process = proc
workdir = proc.Workdir
for _, app := range item.Applications {
if app.ID == c.Params("slug") {
application = &app
workdir = app.Workdir
break
}
}
@ -38,14 +39,15 @@ func doPublish(c *fiber.Ctx) error {
}
}
if upstream == nil && process == nil {
return fiber.ErrNotFound
} else if upstream != nil && upstream.GetType() != sign.UpstreamTypeFile {
var instance *warden.AppInstance
if application != nil {
if instance = warden.GetFromPool(application.ID); instance != nil {
instance.Stop()
}
} else if destination != nil && destination.GetType() != navi.DestinationStaticFile {
return fiber.ErrUnprocessableEntity
}
for _, process := range site.Processes {
process.StopProcess()
} else {
return fiber.ErrNotFound
}
if c.Query("overwrite", "yes") == "yes" {
@ -81,5 +83,9 @@ func doPublish(c *fiber.Ctx) error {
}
}
if instance != nil {
instance.Wake()
}
return c.SendStatus(fiber.StatusOK)
}

67
pkg/sideload/regions.go Normal file
View File

@ -0,0 +1,67 @@
package sideload
import (
"fmt"
"os"
"path/filepath"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"code.smartsheep.studio/goatworks/roadsign/pkg/warden"
"github.com/gofiber/fiber/v2"
"github.com/pelletier/go-toml/v2"
"github.com/samber/lo"
"github.com/spf13/viper"
)
func getRegions(c *fiber.Ctx) error {
return c.JSON(navi.R.Regions)
}
func getRegionConfig(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 + ".toml"); err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
return c.Type("toml").SendString(string(data))
}
func doSync(c *fiber.Ctx) error {
req := string(c.Body())
id := c.Params("slug")
path := filepath.Join(viper.GetString("paths.configs"), fmt.Sprintf("%s.toml", 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, _ := toml.Marshal(req)
file.Write(raw)
defer file.Close()
}
var rebootQueue []*warden.AppInstance
if region, ok := lo.Find(navi.R.Regions, func(item *navi.Region) bool {
return item.ID == id
}); ok {
for _, application := range region.Applications {
if instance := warden.GetFromPool(application.ID); instance != nil {
instance.Stop()
rebootQueue = append(rebootQueue, instance)
}
}
}
// Reload
navi.ReadInConfig(viper.GetString("paths.configs"))
// Reboot
for _, instance := range rebootQueue {
instance.Wake()
}
return c.SendStatus(fiber.StatusOK)
}

View File

@ -1,11 +1,12 @@
package sideload
import (
"code.smartsheep.studio/goatworks/roadsign/pkg/sideload/view"
"fmt"
"net/http"
"code.smartsheep.studio/goatworks/roadsign/pkg/sideload/view"
"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"
@ -51,18 +52,19 @@ func InitSideload() *fiber.App {
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)
cgi.Get("/metadata", getMetadata)
cgi.Get("/traces", getTraces)
cgi.Get("/stats", getStats)
cgi.Get("/regions", getRegions)
cgi.Get("/regions/cfg/:id", getRegionConfig)
cgi.Get("/applications", getApplications)
cgi.Get("/applications/logs/:id", getApplicationLogs)
}
webhooks := app.Group("/webhooks").Name("WebHooks")
{
webhooks.Put("/publish/:site/:slug", doPublish)
webhooks.Put("/sync/:slug", doSyncSite)
webhooks.Put("/sync/:slug", doSync)
}
return app

View File

@ -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)
}

View File

@ -1,28 +1,27 @@
package sideload
import (
"code.smartsheep.studio/goatworks/roadsign/pkg/sign"
"code.smartsheep.studio/goatworks/roadsign/pkg/navi"
"code.smartsheep.studio/goatworks/roadsign/pkg/warden"
"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
func getStats(c *fiber.Ctx) error {
locations := lo.FlatMap(navi.R.Regions, func(item *navi.Region, idx int) []navi.Location {
return item.Locations
})
processes := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.ProcessInstance {
return item.Processes
destinations := lo.FlatMap(locations, func(item navi.Location, idx int) []navi.Destination {
return item.Destinations
})
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
})
applications := lo.FlatMap(navi.R.Regions, func(item *navi.Region, idx int) []warden.Application {
return item.Applications
})
return c.JSON(fiber.Map{
"sites": len(sign.App.Sites),
"upstreams": len(upstreams),
"processes": len(processes),
"status": len(unhealthy) == 0,
"regions": len(navi.R.Regions),
"locations": len(locations),
"destinations": len(destinations),
"applications": len(applications),
})
}

View File

@ -1,3 +0,0 @@
/dist
/node_modules
/*.lock

View File

@ -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'
}
}

View File

@ -8,23 +8,20 @@ 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
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
*.lock
package-lock.json

View File

@ -1,8 +0,0 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": false,
"printWidth": 120,
"trailingComma": "none"
}

View File

@ -1,8 +0,0 @@
{
"recommendations": [
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

View File

@ -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
```

View File

@ -1,13 +1,13 @@
<!DOCTYPE html>
<!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>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RoadSign Sideload</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

View File

@ -1,44 +1,24 @@
{
"name": "@roadsign/sideload-ui",
"version": "0.0.0",
"name": "roadsign-sideload",
"private": true,
"version": "0.0.0",
"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/"
"build": "tsc && vite build",
"preview": "vite preview"
},
"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"
"@solidjs/router": "^0.10.10",
"solid-js": "^1.8.7"
},
"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"
"autoprefixer": "^10.4.17",
"daisyui": "^4.6.0",
"postcss": "^8.4.33",
"tailwindcss": "^3.4.1",
"typescript": "^5.2.2",
"vite": "^5.0.8",
"vite-plugin-solid": "^2.8.0"
}
}

View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -1,6 +0,0 @@
@import "vfonts/IBMPlexSans.css";
@import "vfonts/IBMPlexMono.css";
a {
color: #3f7ee8;
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,17 @@
import "./index.css";
/* @refresh reload */
import { render } from "solid-js/web";
import { Route, Router } from "@solidjs/router";
import RootLayout from "./layouts/RootLayout";
import Dashboard from "./pages/dashboard";
const root = document.getElementById("root");
render(() => (
<Router root={RootLayout}>
<Route path="/" component={Dashboard} />
</Router>
), root!);

View File

@ -0,0 +1,11 @@
import Navbar from "./shared/Navbar";
export default function RootLayout(props: any) {
return (
<div>
<Navbar />
<main class="h-[calc(100vh-64px)]">{props.children}</main>
</div>
);
}

View File

@ -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>

View File

@ -0,0 +1,62 @@
import { For } from "solid-js";
interface MenuItem {
label: string;
href: string;
}
export default function Navbar() {
const nav: MenuItem[] = [{ label: "Dashboard", href: "/" }];
return (
<div class="navbar bg-base-100 shadow-md">
<div class="navbar-start">
<div class="dropdown">
<div tabIndex={0} role="button" class="btn btn-ghost lg:hidden">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h8m-8 6h16"
/>
</svg>
</div>
<ul
tabIndex={0}
class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52"
>
<For each={nav}>
{(item) => (
<li>
<a href={item.href}>{item.label}</a>
</li>
)}
</For>
</ul>
</div>
<a href="/" class="btn btn-ghost text-xl">
RoadSign
</a>
</div>
<div class="navbar-center hidden lg:flex">
<ul class="menu menu-horizontal px-1">
<For each={nav}>
{(item) => (
<li>
<a href={item.href}>{item.label}</a>
</li>
)}
</For>
</ul>
</div>
<div class="navbar-end"></div>
</div>
);
}

View File

@ -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")

View File

@ -0,0 +1,10 @@
export default function Dashboard() {
return (
<div class="h-full w-full flex justify-center items-center">
<div class="max-w-96 text-center">
<h1 class="text-2xl font-bold">Hold on</h1>
<p>Our brand new sideload administration panel is still in progress. For now, you can use sideload api and roadsign cli.</p>
</div>
</div>
)
}

View File

@ -1,9 +0,0 @@
<template>
<n-message-provider>
<router-view />
</n-message-provider>
</template>
<script setup lang="ts">
import { NMessageProvider } from "naive-ui"
</script>

View File

@ -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

View File

@ -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>

View File

@ -0,0 +1,44 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
daisyui: {
themes: [
{
light: {
...require("daisyui/src/theming/themes")["light"],
primary: "#4750a3",
secondary: "#93c5fd",
accent: "#0f766e",
info: "#67e8f9",
success: "#15803d",
warning: "#f97316",
error: "#dc2626",
"--rounded-box": "0",
"--rounded-btn": "0",
"--rounded-badge": "0",
"--tab-radius": "0",
},
},
{
dark: {
...require("daisyui/src/theming/themes")["dark"],
primary: "#4750a3",
secondary: "#93c5fd",
accent: "#0f766e",
info: "#67e8f9",
success: "#15803d",
warning: "#f97316",
error: "#dc2626",
"--rounded-box": "0",
"--rounded-btn": "0",
"--rounded-badge": "0",
"--tab-radius": "0",
},
},
],
},
plugins: [require("daisyui")],
};

View File

@ -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/*"]
}
}
}

View File

@ -1,11 +1,26 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -1,17 +1,10 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@ -1,5 +0,0 @@
import { defineConfig, presetUno } from "unocss"
export default defineConfig({
presets: [presetUno({ preflight: false })]
})

View File

@ -1,24 +1,6 @@
import { fileURLToPath, URL } from "node:url"
import { defineConfig } from 'vite'
import solid from 'vite-plugin-solid'
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"
}
}
plugins: [solid()],
})

View File

@ -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)
}
}
}

View File

@ -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"`
}

View File

@ -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, "")
}

120
pkg/warden/executor.go Normal file
View File

@ -0,0 +1,120 @@
package warden
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/samber/lo"
)
var InstancePool []*AppInstance
func GetFromPool(id string) *AppInstance {
val, ok := lo.Find(InstancePool, func(item *AppInstance) bool {
return item.Manifest.ID == id
})
return lo.Ternary(ok, val, nil)
}
func StartPool() []error {
var errors []error
for _, instance := range InstancePool {
if err := instance.Wake(); err != nil {
errors = append(errors, err)
}
}
return errors
}
type AppStatus = int8
const (
AppCreated = AppStatus(iota)
AppStarting
AppStarted
AppExited
AppFailure
)
type AppInstance struct {
Manifest Application `json:"manifest"`
Cmd *exec.Cmd `json:"-"`
Logger strings.Builder `json:"-"`
Status AppStatus `json:"status"`
}
func (v *AppInstance) Wake() error {
if v.Cmd != nil {
return nil
}
if v.Cmd == nil {
return v.Start()
}
if v.Cmd.Process == nil || v.Cmd.ProcessState == nil {
return v.Start()
}
if v.Cmd.ProcessState.Exited() {
return v.Start()
} 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 *AppInstance) Start() error {
manifest := v.Manifest
if len(manifest.Command) <= 0 {
return fmt.Errorf("you need set the command for %s to enable process manager", manifest.ID)
}
v.Cmd = exec.Command(manifest.Command[0], manifest.Command[1:]...)
v.Cmd.Dir = filepath.Join(manifest.Workdir)
v.Cmd.Env = append(v.Cmd.Env, manifest.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 = AppStarting
} else if !v.Cmd.ProcessState.Exited() {
v.Status = AppStarted
} else {
v.Status = lo.Ternary(v.Cmd.ProcessState.Success(), AppExited, AppFailure)
return
}
time.Sleep(100 * time.Millisecond)
}
}()
return v.Cmd.Start()
}
func (v *AppInstance) Stop() 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 *AppInstance) Logs() string {
return v.Logger.String()
}

8
pkg/warden/manifest.go Normal file
View File

@ -0,0 +1,8 @@
package warden
type Application struct {
ID string `json:"id" toml:"id"`
Workdir string `json:"workdir" toml:"workdir"`
Command []string `json:"command" toml:"command"`
Environment []string `json:"environment" toml:"environment"`
}

35
settings.toml Normal file
View File

@ -0,0 +1,35 @@
[debug]
print_routes = false
[hypertext]
sideload_ports = [":81"]
sideload_secured_ports = []
ports = [":8000"]
secured_ports = []
[hypertext.certificate]
redirect = false
sideload_key = "./cert.key"
sideload_pem = "./cert.pem"
key = "./cert.key"
pem = "./cert.pem"
[hypertext.limitation]
max_body_size = 549_755_813_888 # 512 GiB
max_qps = -1
[paths]
configs = "./config"
[telemetry]
request_logging = true
capture_traces = true
[performance]
traces_limit = 256
network_timeout = 3_000
prefork = false
[security]
sideload_trusted_proxies = ["localhost"]
credential = "e81f43f32d934271af6322e5376f5f59"

View File

@ -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

View File

@ -1,3 +1 @@
/ssr
/spa
/congress
/warden

27
test/data/sse/index.html Normal file
View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSE Example</title>
</head>
<body>
<h1>Server-Sent Events Example</h1>
<div id="sse-data"></div>
<script>
const eventSource = new EventSource("/sse")
const sseDataElement = document.getElementById("sse-data")
eventSource.onmessage = (event) => {
sseDataElement.innerText = `Data from server: ${event.data}`
}
eventSource.onerror = (error) => {
console.error("EventSource failed:", error)
eventSource.close()
}
</script>
</body>
</html>

28
test/data/sse/server.py Normal file
View File

@ -0,0 +1,28 @@
import time
from flask import Flask, render_template, Response
app = Flask(__name__, template_folder=".")
# Generator function to simulate real-time updates
def event_stream():
count = 0
while True:
time.sleep(1)
count += 1
yield f"data: {count}\n\n"
@app.route('/')
def index():
return render_template('index.html')
@app.route('/sse')
def sse():
return Response(event_stream(), content_type='text/event-stream')
if __name__ == '__main__':
app.run(debug=True, threaded=True)

View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebSocket Durability Test</title>
</head>
<body>
<h1>WebSocket Client</h1>
<p>
This client will send a message every 500ms, or you can send message
manually.
</p>
<div id="messages"></div>
<input type="text" id="messageInput" placeholder="Type a message..." />
<button onclick="sendMessage()">Send Message</button>
<script>
const socket = new WebSocket("ws://localhost:8000/ws");
socket.onopen = function (event) {
appendMessage("Connection opened");
setInterval(() => autoSendMessage(), 500)
};
socket.onmessage = function (event) {
appendMessage("Received: " + event.data);
};
socket.onclose = function (event) {
appendMessage("Connection closed");
};
function sendMessage() {
const messageInput = document.getElementById("messageInput");
const message = messageInput.value;
if (message.trim() !== "") {
socket.send(message);
appendMessage("Sent: " + message);
messageInput.value = "";
}
}
function autoSendMessage() {
const message = `[AutoSend] A new message has been sent at ${new Date().toUTCString()}`;
socket.send(message);
}
function appendMessage(message) {
const messagesDiv = document.getElementById("messages");
messagesDiv.innerHTML += `<p>${message}</p>`;
}
</script>
</body>
</html>

View File

@ -0,0 +1,29 @@
import asyncio
import websockets
async def handle_websocket(websocket, path):
# This function will be called whenever a new WebSocket connection is established
# Send a welcome message to the client
await websocket.send("Welcome to the WebSocket server!")
try:
# Enter the main loop to handle incoming messages
async for message in websocket:
# Print the received message
print(f"Received message: {message}")
# Send a response back to the client
response = f"Server received: {message}"
await websocket.send(response)
except websockets.exceptions.ConnectionClosedError:
print("Connection closed by the client.")
# Create the WebSocket server
start_server = websockets.serve(handle_websocket, "localhost", 8765)
print("WebSocket server started at ws://localhost:8765")
# Run the server indefinitely
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

View File

@ -0,0 +1,9 @@
id = "example"
[[locations]]
id = "example-location"
host = ["localhost:8000"]
path = ["/"]
[[locations.destinations]]
id = "example-destination"
uri = "files://../data/spa?fallback=index.html"

View File

@ -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

View File

@ -0,0 +1,31 @@
[debug]
print_routes = true
[hypertext]
sideload_ports = [":81"]
sideload_secured_ports = []
ports = [":8000"]
secured_ports = []
[hypertext.certificate]
redirect = false
sideload_key = "./cert.key"
sideload_pem = "./cert.pem"
key = "./cert.key"
pem = "./cert.pem"
[hypertext.limitation]
max_body_size = 549_755_813_888 # 512 GiB
max_qps = -1
[paths]
configs = "./config"
[performance]
request_logging = true
network_timeout = 3_000
prefork = false
[security]
sideload_trusted_proxies = ["localhost"]
credential = "e81f43f32d934271af6322e5376f5f59"

View File

@ -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

View File

@ -0,0 +1,15 @@
id = "example-region"
[[locations]]
id = "example-warden"
host = ["localhost:8000"]
path = ["/"]
[[locations.destinations]]
id = "example-warden-destination"
uri = "http://localhost:4321"
[[applications]]
id = "example-warden-app"
workdir = "../data/warden"
command = ["node", "dist/server/entry.mjs"]
environment = ["PUBLIC_CMS=https://smartsheep.studio"]

View File

@ -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"]

View File

@ -0,0 +1,31 @@
[debug]
print_routes = true
[hypertext]
sideload_ports = [":81"]
sideload_secured_ports = []
ports = [":8000"]
secured_ports = []
[hypertext.certificate]
redirect = false
sideload_key = "./cert.key"
sideload_pem = "./cert.pem"
key = "./cert.key"
pem = "./cert.pem"
[hypertext.limitation]
max_body_size = 549_755_813_888 # 512 GiB
max_qps = -1
[paths]
configs = "./config"
[performance]
request_logging = true
network_timeout = 3_000
prefork = false
[security]
sideload_trusted_proxies = ["localhost"]
credential = "e81f43f32d934271af6322e5376f5f59"

View File

@ -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

View File

@ -0,0 +1,9 @@
id = "example"
[[locations]]
id = "example-location"
host = ["localhost:8000"]
path = ["/"]
[[locations.destinations]]
id = "example-destination"
uri = "files://../data/spa?fallback=index.html"

View File

@ -1,8 +0,0 @@
name: Example Site
rules:
- host: ["localhost:8000"]
path: ["/"]
upstreams:
- id: example
name: Benchmarking Data
uri: files://../data

View File

@ -0,0 +1,31 @@
[debug]
print_routes = true
[hypertext]
sideload_ports = [":81"]
sideload_secured_ports = []
ports = [":8000"]
secured_ports = []
[hypertext.certificate]
redirect = false
sideload_key = "./cert.key"
sideload_pem = "./cert.pem"
key = "./cert.key"
pem = "./cert.pem"
[hypertext.limitation]
max_body_size = 549_755_813_888 # 512 GiB
max_qps = -1
[paths]
configs = "./config"
[performance]
request_logging = true
network_timeout = 3_000
prefork = true
[security]
sideload_trusted_proxies = ["localhost"]
credential = "e81f43f32d934271af6322e5376f5f59"

View File

@ -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

View File

@ -0,0 +1,31 @@
[debug]
print_routes = true
[hypertext]
sideload_ports = [":81"]
sideload_secured_ports = []
ports = [":8000"]
secured_ports = []
[hypertext.certificate]
redirect = false
sideload_key = "./cert.key"
sideload_pem = "./cert.pem"
key = "./cert.key"
pem = "./cert.pem"
[hypertext.limitation]
max_body_size = 549_755_813_888 # 512 GiB
max_qps = -1
[paths]
configs = "./config"
[performance]
request_logging = true
network_timeout = 3_000
prefork = false
[security]
sideload_trusted_proxies = ["localhost"]
credential = "e81f43f32d934271af6322e5376f5f59"

View File

@ -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