From b5a031001bd3f5ad6eedbdbcdf28a815a255775f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E6=8B=89?= Date: Fri, 7 Jun 2024 12:19:33 +0800 Subject: [PATCH] docs: infrastructure --- README.md | 8 +- SECURITY.md | 1 + config/application.json | 86 ------------------- config/platform.json | 19 ---- docs/mkdocs.yml | 12 ++- docs/pages/challenge/index.md | 3 + docs/pages/config/cache.md | 3 + docs/pages/config/database.md | 3 + docs/pages/config/index.md | 4 +- docs/pages/config/proxy/index.md | 13 +++ docs/pages/deploy/docker-k8s.md | 2 +- docs/pages/deploy/docker.md | 77 ++++++++++++++++- docs/pages/{quick-start => deploy}/img/1.png | Bin docs/pages/deploy/k8s.md | 2 +- docs/pages/faq/index.md | 1 + docs/pages/game/index.md | 9 ++ docs/pages/index.md | 4 +- docs/pages/proxy/index.md | 54 ------------ docs/pages/quick-start/index.md | 75 ---------------- go.mod | 5 +- go.sum | 6 +- 21 files changed, 134 insertions(+), 253 deletions(-) create mode 100644 SECURITY.md delete mode 100644 config/application.json delete mode 100644 config/platform.json create mode 100644 docs/pages/challenge/index.md create mode 100644 docs/pages/config/cache.md create mode 100644 docs/pages/config/database.md create mode 100644 docs/pages/config/proxy/index.md rename docs/pages/{quick-start => deploy}/img/1.png (100%) create mode 100644 docs/pages/faq/index.md create mode 100644 docs/pages/game/index.md delete mode 100644 docs/pages/proxy/index.md delete mode 100644 docs/pages/quick-start/index.md diff --git a/README.md b/README.md index 07fb1248..2b257d4a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,15 @@ # Cloudsdale -The **Cloudsdale** project is an *open-source, light-weight, Jeopardy-style's* CTF platform. +[![Go Report Card](https://goreportcard.com/badge/github.com/elabosak233/cloudsdale)](https://goreportcard.com/report/github.com/elabosak233/cloudsdale) + +The **Cloudsdale** project is an _open-source, light-weight, Jeopardy-style's_ CTF platform. You can read more in the [Documentation](https://docs.ctf.e23.dev). ## Special Thanks +Thanks to everyone who has contributed to the project! Without you, Cloudsdale would not be what it is today. + +![](https://contrib.rocks/image?repo=ElaBosak233/Cloudsdale) + The current version of Cloudsdale, while ensuring the originality of the code, largely draws inspiration from [GZ::CTF](https://github.com/GZTimeWalker/GZCTF) in its frontend design. Therefore, I express my highest respect to [GZTimeWalker](https://github.com/GZTimeWalker), the author of GZ::CTF. At the same time, I also thank GZTime for his suggestions on Cloudsdale. \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..04e907af --- /dev/null +++ b/SECURITY.md @@ -0,0 +1 @@ +# Security Policy \ No newline at end of file diff --git a/config/application.json b/config/application.json deleted file mode 100644 index 36dd89bf..00000000 --- a/config/application.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "gin": { - "cors": { - "allow_methods": ["GET", "POST", "PUT", "DELETE"], - "allow_origins": ["*"] - }, - "jwt": { - "expiration": 180 - }, - "cache": { - "provider": "memory", - "redis": { - "host": "cache", - "port": 6379, - "password": "", - "db": 0 - } - }, - "host": "0.0.0.0", - "port": 8888 - }, - "container": { - "provider": "docker", - "entry": "127.0.0.1", - "docker": { - "uri": "unix:///var/run/docker.sock" - }, - "k8s": { - "namespace": "default", - "config": { - "path": "./configs/k8s.yml" - } - }, - "proxy": { - "enabled": false, - "type": "ws", - "traffic_capture": { - "enabled": false - } - } - }, - "db": { - "provider": "sqlite", - "postgres": { - "dbname": "cloudsdale", - "host": "db", - "username": "cloudsdale", - "password": "cloudsdale", - "port": 5432, - "sslmode": "disable" - }, - "mysql": { - "dbname": "cloudsdale", - "host": "db", - "username": "cloudsdale", - "password": "cloudsdale", - "port": 3306 - }, - "sqlite": { - "path": "./db/db.sqlite" - } - }, - "email": { - "address": "", - "password": "", - "smtp": { - "host": "", - "port": 0 - } - }, - "captcha": { - "enabled": false, - "provider": "turnstile", - "turnstile": { - "url": "https://challenges.cloudflare.com/turnstile/v0/siteverify", - "site_key": "", - "secret_key": "" - }, - "recaptcha": { - "url:": "https://www.google.com/recaptcha/api/siteverify", - "site_key": "", - "secret_key": "", - "threshold": 0.5 - } - } -} diff --git a/config/platform.json b/config/platform.json deleted file mode 100644 index 1fd63c4d..00000000 --- a/config/platform.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "site": { - "description": "Hack for fun not for profit.", - "title": "Cloudsdale" - }, - "container": { - "parallel_limit": 1, - "request_limit": 0 - }, - "user": { - "registration": { - "enabled": true, - "email": { - "domain": [], - "verification": false - } - } - } -} \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 4c43e9d9..f4258f8b 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -36,11 +36,15 @@ markdown_extensions: nav: - 简介: index.md - - 快速上手: quick-start/index.md - 部署: + - Docker: deploy/docker.md - Docker + K8s: deploy/docker-k8s.md - K8s: deploy/k8s.md - - 配置文件: + - 配置: - config/index.md - - 代理与流量捕获: - - proxy/index.md \ No newline at end of file + - 数据库: config/database.md + - 缓存: config/cache.md + - 代理与流量捕获: config/proxy/index.md + - 题目: challenge/index.md + - 比赛: game/index.md + - FAQ: faq/index.md \ No newline at end of file diff --git a/docs/pages/challenge/index.md b/docs/pages/challenge/index.md new file mode 100644 index 00000000..96967688 --- /dev/null +++ b/docs/pages/challenge/index.md @@ -0,0 +1,3 @@ +# 题目 + +## 创建第一道题目 \ No newline at end of file diff --git a/docs/pages/config/cache.md b/docs/pages/config/cache.md new file mode 100644 index 00000000..0fbf8f2a --- /dev/null +++ b/docs/pages/config/cache.md @@ -0,0 +1,3 @@ +# 缓存 + +Cloudsdale 有两种缓存实现方式,一种是基于 `go-cache`,另一种是基于 `go-redis`,如果你有分布式需求,请使用 `go-redis` 保证缓存的正确性。 \ No newline at end of file diff --git a/docs/pages/config/database.md b/docs/pages/config/database.md new file mode 100644 index 00000000..875d5c34 --- /dev/null +++ b/docs/pages/config/database.md @@ -0,0 +1,3 @@ +# 数据库 + +Cloudsdale 目前支持三种数据库,PostgreSQL,MySQL 和 SQLite \ No newline at end of file diff --git a/docs/pages/config/index.md b/docs/pages/config/index.md index 65d74ec8..e8e40962 100644 --- a/docs/pages/config/index.md +++ b/docs/pages/config/index.md @@ -1,3 +1,3 @@ -# 配置文件 +# 配置 -对于 Cloudsdale 而言,配置文件通常指的是 `application.json` \ No newline at end of file +对于 Cloudsdale 而言,配置通常指的是 `application.json` \ No newline at end of file diff --git a/docs/pages/config/proxy/index.md b/docs/pages/config/proxy/index.md new file mode 100644 index 00000000..7cc965ef --- /dev/null +++ b/docs/pages/config/proxy/index.md @@ -0,0 +1,13 @@ +# 代理与流量捕获 + +Cloudsdale 的代理功能主要是为了流量捕获功能服务的。 + +当然,如果你不希望选手直接通过 IP 地址进入容器,你可以使用平台代理。你只需要保证 Cloudsdale 能够访问容器即可。 + +Clousdale 通过 **TCP over Websocket** 进行代理,简单来说,TCP over Websocket 就是通过一个 Websocket 链接(在 Cloudsdale 中是 `/api/proxies/[UUID]`)作为桥梁,连接靶机。 + +那又产生了一个问题,我是不是需要以 `ws://` 这样的形式来访问题目呢?这时候你就需要连接器。 + +所谓无感交互,就是让连接器在本地开启一个 TCP 端口,做题的时候你只需要向连接器提供 `ws://` 链接,然后访问被分配到的 TCP 端口即可,做题的体验没什么太大的区别。 + +Cloudsdale 推荐使用 [WebsocketReflectorX(简称 WSRX)](https://github.com/XDSEC/WebsocketReflectorX) 作为你的连接器。 diff --git a/docs/pages/deploy/docker-k8s.md b/docs/pages/deploy/docker-k8s.md index c0eacf35..3eda2f5f 100644 --- a/docs/pages/deploy/docker-k8s.md +++ b/docs/pages/deploy/docker-k8s.md @@ -1 +1 @@ -# Docker + K8s 联合部署 \ No newline at end of file +# Docker + K8s 部署 \ No newline at end of file diff --git a/docs/pages/deploy/docker.md b/docs/pages/deploy/docker.md index bac19899..301dcf64 100644 --- a/docs/pages/deploy/docker.md +++ b/docs/pages/deploy/docker.md @@ -1,4 +1,75 @@ -# 纯 Docker 部署 +# Docker 部署 -!!! info "提示" - 如果你在举办一场小型的比赛,纯 Docker 部署可以大大节约你在平台配置上的时间 \ No newline at end of file +本篇将教会你如何快速地部署一个 Cloudsdale 实例。这个实例具有以下特点: + +- 使用 PostgreSQL 作为数据库 +- 使用 Docker 单机实例,承担平台和动态容器后端 + +部署她的方式非常简单,你只需要准备一个 `docker-compose.yml` 即可,当然,你可以直接从仓库中的 [deploys](https://github.com/ElaBosak233/Cloudsdale/blob/main/deploy) 目录中找到你所需要的,一个简单的 `docker-compose.yml` 应该长这样: + +```yaml +version: "3.0" +services: + core: + image: elabosak233/cloudsdale:main + restart: always + ports: + - "8888:8888" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" # 映射 Docker 守护进程,使得 Cloudsdale 可以控制宿主机 Docker + - "./configs:/app/configs" # 映射配置文件夹,里面存放 application.json + - "./media:/app/media" # 映射媒体资源文件夹,里面存放题目附件、用户头像等 + - "./logs:/app/logs" # 映射日志文件夹 + depends_on: + - db # 依赖于 db + + db: + image: postgres:alpine + restart: always + ports: + - "5432:5432" + environment: + POSTGRES_USER: cloudsdale + POSTGRES_PASSWORD: cloudsdale + POSTGRES_DB: cloudsdale + volumes: + - "./db:/var/lib/postgresql/data" +``` + +你需要在你的服务器上建一个新的空文件夹,然后将 `docker-compose.yml` 放入其中,然后运行: + +``` +docker compose up +``` + +第一次运行大概率是失败的,因为 Cloudsdale 默认生成的配置中,依赖的数据库不是 PostgreSQL,我们需要在初始化后进入 `/configs/application.json` 进行些许修改 + +```json +"db": { + "provider": "postgres", // 修改这里 + "postgres": { + "dbname": "cloudsdale", + "host": "db", + "username": "cloudsdale", + "password": "cloudsdale", + "port": 5432, + "sslmode": "disable" + }, + "mysql": { + "dbname": "cloudsdale", + "host": "db", + "username": "cloudsdale", + "password": "cloudsdale", + "port": 3306 + }, + "sqlite": { + "path": "./db/db.sqlite" + } + }, +``` + +修改完成后,再运行一遍 `docker compose up`,如果你看到下面这样的输出,那么恭喜你,Cloudsdale 启动的很成功,此后,你可以使用 `docker compose up -d` 将 Cloudsdale 以后台的形式启动 + +![](./img/1.png) + +然后,你就可以通过 `http://localhost:8888` 进入 Cloudsdale,端口你可以通过编辑 `docker-compose.yml` 中的 `ports` 进行更改 \ No newline at end of file diff --git a/docs/pages/quick-start/img/1.png b/docs/pages/deploy/img/1.png similarity index 100% rename from docs/pages/quick-start/img/1.png rename to docs/pages/deploy/img/1.png diff --git a/docs/pages/deploy/k8s.md b/docs/pages/deploy/k8s.md index aadadab1..9a10ce1a 100644 --- a/docs/pages/deploy/k8s.md +++ b/docs/pages/deploy/k8s.md @@ -1 +1 @@ -# 纯 K8s 部署 \ No newline at end of file +# K8s 部署 \ No newline at end of file diff --git a/docs/pages/faq/index.md b/docs/pages/faq/index.md new file mode 100644 index 00000000..32cce907 --- /dev/null +++ b/docs/pages/faq/index.md @@ -0,0 +1 @@ +# FAQ \ No newline at end of file diff --git a/docs/pages/game/index.md b/docs/pages/game/index.md new file mode 100644 index 00000000..1281d323 --- /dev/null +++ b/docs/pages/game/index.md @@ -0,0 +1,9 @@ +# 比赛 + +在介绍比赛功能前,需要先捋清一个关系,即用户、团队和比赛之间的关系。 + +用户是比赛的间接参与者,用户可以创建团队,并邀请其他用户加入团队; + +团队是比赛的直接参与者,团队可以参与比赛,且参与比赛必须以团队为单位。 + +也就是说,不论是团队赛还是个人赛,用户都需要创建或者加入一个团队来参赛。 \ No newline at end of file diff --git a/docs/pages/index.md b/docs/pages/index.md index 81df8087..6e034f93 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -3,7 +3,7 @@ !!! warning "警告" Cloudsdale 仍然处于未发布阶段,快照构建成果可能十分不稳定,功能也可能未完善,快照的品质不能代表正式版本的品质。 -**Cloudsdale** 是一个基于 GO 构建、使用解题模式(Jeopardy)的 CTF 平台。她非常地轻量,并且可以使用 _非常简单(可能)_ 的配置文件快速部署。 +**Cloudsdale** 是一个基于 GO 构建、使用解题模式(Jeopardy)的 CTF 平台。她非常地 _轻量_,并且可以使用 _非常简单(可能)_ 的配置文件快速部署。 本项目灵感来源于 [CTFd](https://github.com/CTFd/CTFd)、[Cardinal](https://github.com/05sec/Cardinal) 和 [GZ::CTF](https://github.com/GZTimeWalker/GZCTF),博采众长之下诞生了本项目。但最初的想法仅仅以尽可能处处简单的方式,给学校的 CTF 战队提供训练平台。 @@ -32,6 +32,6 @@ ## 开源协议 -Cloudsdale 基于 [GPLv3](https://github.com/ElaBosak233/PgsHub/blob/main/LICENSE) 协议开源,使用和二次开发需严格遵守此协议。 +Cloudsdale 基于 [GPLv3](https://github.com/ElaBosak233/Cloudsdale/blob/main/LICENSE) 协议开源,使用和二次开发需严格遵守此协议。
\ No newline at end of file diff --git a/docs/pages/proxy/index.md b/docs/pages/proxy/index.md deleted file mode 100644 index 079e5820..00000000 --- a/docs/pages/proxy/index.md +++ /dev/null @@ -1,54 +0,0 @@ -# 代理与流量捕获 - -Cloudsdale 的代理功能主要是为了流量捕获功能服务的。 - -当然,如果你不希望选手直接通过 IP 地址进入容器,你可以使用平台代理。你只需要保证 Cloudsdale 能够访问容器即可。 - -Clousdale 通过 **TCP over Websocket** 进行代理,简单来说,TCP over Websocket 就是通过一个 Websocket 链接(在 Cloudsdale 中是 `/api/proxies/[UUID]`)作为桥梁,连接靶机。 - -那又产生了一个问题,我是不是需要以 `ws://` 这样的形式来访问题目呢? - -虽然 Cloudsdale 返回的是一个这样的 `ws://` 链接,但是你只需要用一个很简单的 Python 脚本就能实现无感交互了。 - -所谓无感交互,就是让 Python 在本地开启一个 TCP 端口,做题的时候你只需要给脚本提供 `WS_URL` 常量,然后连接这个被脚本分配到的地址即可,做题的体验没什么太大的区别。 - -下面是一个比较简单的实现这个功能的 Python 脚本,仅作为参考。 - -```python -import asyncio -import websockets -import socket - -WS_URL = "ws://0.0.0.0:8888/api/proxies/[UUID]" - - -async def handle_tcp_client(tcp, address): - print(f"Accepted connection from {address}") - - async with websockets.connect(WS_URL) as ws: - while True: - data = tcp.recv(1024) - if not data: - break - await ws.send(data) - - response = await ws.recv() - if isinstance(response, str): - response = response.encode("utf-8") - tcp.send(response) - - -tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -tcp_server.bind(("localhost", 0)) -tcp_server.listen(1) - -port = tcp_server.getsockname()[1] -print(f"TCP server is listening on localhost:{port}") - -while True: - tcp_client, addr = tcp_server.accept() - asyncio.get_event_loop().run_until_complete(handle_tcp_client(tcp_client, addr)) - -``` - -当然,我们还有另一种方法,就是使用一个连接器,比如 [Netweaver](https://github.com/elabosak233/Netweaver),这样就能实现更好地交互 \ No newline at end of file diff --git a/docs/pages/quick-start/index.md b/docs/pages/quick-start/index.md deleted file mode 100644 index 43c752ac..00000000 --- a/docs/pages/quick-start/index.md +++ /dev/null @@ -1,75 +0,0 @@ -# 快速上手 - -本篇将教会你如何快速地部署一个 Cloudsdale 实例。这个实例具有以下特点: - -- 使用 PostgreSQL 作为数据库 -- 使用 Docker 单机实例,承担平台和动态容器后端 - -部署她的方式非常简单,你只需要准备一个 `docker-compose.yml` 即可,当然,你可以直接从仓库中的 [deploys](https://github.com/ElaBosak233/Cloudsdale/blob/main/deploy) 目录中找到你所需要的,一个简单的 `docker-compose.yml` 应该长这样: - -```yaml -version: "3.0" -services: - core: - image: elabosak233/cloudsdale:main - restart: always - ports: - - "8888:8888" - volumes: - - "/var/run/docker.sock:/var/run/docker.sock" # 映射 Docker 守护进程,使得 Cloudsdale 可以控制宿主机 Docker - - "./configs:/app/configs" # 映射配置文件夹,里面存放 application.json - - "./media:/app/media" # 映射媒体资源文件夹,里面存放题目附件、用户头像等 - - "./logs:/app/logs" # 映射日志文件夹 - depends_on: - - db # 依赖于 db - - db: - image: postgres:alpine - restart: always - ports: - - "5432:5432" - environment: - POSTGRES_USER: cloudsdale - POSTGRES_PASSWORD: cloudsdale - POSTGRES_DB: cloudsdale - volumes: - - "./db:/var/lib/postgresql/data" -``` - -你需要在你的服务器上建一个新的空文件夹,然后将 `docker-compose.yml` 放入其中,然后运行: - -``` -docker compose up -``` - -第一次运行大概率是失败的,因为 Cloudsdale 默认生成的配置中,依赖的数据库不是 PostgreSQL,我们需要在初始化后进入 `/configs/application.json` 进行些许修改 - -```json -"db": { - "provider": "postgres", // 修改这里 - "postgres": { - "dbname": "cloudsdale", - "host": "db", - "username": "cloudsdale", - "password": "cloudsdale", - "port": 5432, - "sslmode": "disable" - }, - "mysql": { - "dbname": "cloudsdale", - "host": "db", - "username": "cloudsdale", - "password": "cloudsdale", - "port": 3306 - }, - "sqlite": { - "path": "./db/db.sqlite" - } - }, -``` - -修改完成后,再运行一遍 `docker compose up`,如果你看到下面这样的输出,那么恭喜你,Cloudsdale 启动的很成功,此后,你可以使用 `docker compose up -d` 将 Cloudsdale 以后台的形式启动 - -![](./img/1.png) - -然后,你就可以通过 `http://localhost:8888` 进入 Cloudsdale,端口你可以通过编辑 `docker-compose.yml` 中的 `ports` 进行更改 \ No newline at end of file diff --git a/go.mod b/go.mod index fc22d8e0..03278673 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,8 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.1 github.com/mitchellh/mapstructure v1.5.0 - github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 + github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/redis/go-redis/v9 v9.5.1 github.com/spf13/viper v1.18.2 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 @@ -96,10 +97,8 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/redis/go-redis/v9 v9.5.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect diff --git a/go.sum b/go.sum index f5b400a7..1ff23f92 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,10 @@ github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc= github.com/TwiN/go-color v1.4.1/go.mod h1:WcPf/jtiW95WBIsEeY1Lc/b8aaWoiqQpu5cf8WFxu+s= github.com/agiledragon/gomonkey/v2 v2.2.0 h1:QJWqpdEhGV/JJy70sZ/LDnhbSlMrqHAWHcNOjz1kyuI= github.com/agiledragon/gomonkey/v2 v2.2.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= @@ -214,8 +218,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= -github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=