diff --git a/MirageClient b/MirageClient index ae9f955..48d3407 160000 --- a/MirageClient +++ b/MirageClient @@ -1 +1 @@ -Subproject commit ae9f95586f6c88f0149e42bf47c27c9f7eb87adc +Subproject commit 48d3407123063f8179a842f9546b5849618f6cfb diff --git a/cockpit_web/src/setpart/ClientPublish.vue b/cockpit_web/src/setpart/ClientPublish.vue index ea64f77..1e990ea 100644 --- a/cockpit_web/src/setpart/ClientPublish.vue +++ b/cockpit_web/src/setpart/ClientPublish.vue @@ -38,6 +38,9 @@ const naviAarch64Uploader = ref(null); const naviProc = ref(""); const naviProcPercent = ref(0); +const wantLinuxRepo = ref(""); +const wantLinuxRepoCred = ref(""); + const wantWinVersion = ref({}); const winExtURL = ref(false); const winUploader = ref(null); @@ -185,15 +188,42 @@ function handleWinError(err) { toastShow.value = true; } -onMounted(() => { +var getLinuxBuildStateIntID; + +function getLinuxBuildState() { axios - .get("/cockpit/api/setting/general") + .get("/cockpit/api/publish") .then(function (response) { // 处理成功情况 if (response.data["status"] == "success") { - uploadURL.value = - "https://" + response.data["data"]["server_url"] + "/cockpit/api/publish/"; + uploadURL.value = response.data["data"]["upload_url"]; ClientVersion.value = response.data["data"]["client_version"]; + if (ClientVersion.value["linux"]["buildst"] != "正在进行") { + clearInterval(getLinuxBuildStateIntID); + } + } else { + toastMsg.value = response.data["status"].substring(6); + toastShow.value = true; + } + }) + .catch(function (error) { + // 处理错误情况 + toastMsg.value = error; + toastShow.value = true; + }); +} + +onMounted(() => { + axios + .get("/cockpit/api/publish") + .then(function (response) { + // 处理成功情况 + if (response.data["status"] == "success") { + uploadURL.value = response.data["data"]["upload_url"]; + ClientVersion.value = response.data["data"]["client_version"]; + + wantLinuxRepo.value = response.data["data"]["client_version"]["linux"]["url"]; + wantWinVersion.value = {}; wantWinVersion.value["version"] = response.data["data"]["client_version"]["win"]["version"]; @@ -221,6 +251,33 @@ onMounted(() => { }); }); +function publishLinux() { + axios + .post("/cockpit/api/publish/linux", { + version: wantLinuxRepoCred.value, + url: wantLinuxRepo.value, + }) + .then(function (response) { + // 处理成功情况 + if (response.data["status"] == "success") { + toastMsg.value = "Linux已提交构建"; + toastShow.value = true; + getLinuxBuildStateIntID = setInterval(() => { + getLinuxBuildState(); + }, 3000); + } else { + toastMsg.value = response.data["status"].substring(6); + toastShow.value = true; + } + }) + .catch(function (error) { + // 处理错误情况 + toastMsg.value = error; + toastShow.value = true; + }); + return; +} + function publishWin() { if (winExtURL.value) { axios @@ -501,6 +558,178 @@ function publishIOSToTestflight() { +
+
+ + + + + + + + + + + + + + + + +

+ Linux 客户端发布 +

+
+ + + + + +
+
+
+
+
+ 当前源码仓库 +
+
+ {{ + ClientVersion.linux.url && ClientVersion.linux.url != "" + ? ClientVersion.linux.url + : "未设置" + }} +
+
当前版本
+
+ {{ + ClientVersion.linux.version && ClientVersion.linux.version != "" + ? ClientVersion.linux.version + : "尚未构建本" + }} +
+
+ 最近一次构建情况 +
+
+ {{ + ClientVersion.linux.buildst && ClientVersion.linux.buildst != "" + ? ClientVersion.linux.buildst + : "尚未进行" + }} +
+
+
+
+

仓库地址

+
+ +
+
+
+ +
+
+

+ 用户名:口令 (选填, 填 clear 清除) +

+
+
+ +
+
+
- 蜃境 - 控制台 + 蜃境 - 客户端下载 @@ -17,32 +17,6 @@ diff --git a/console_web/src/downloads/Linux.vue b/console_web/src/downloads/Linux.vue index fe2ebcb..0209437 100644 --- a/console_web/src/downloads/Linux.vue +++ b/console_web/src/downloads/Linux.vue @@ -4,5 +4,160 @@ const props = defineProps({ }); diff --git a/controller/cockpit.go b/controller/cockpit.go index 70464f1..0353851 100644 --- a/controller/cockpit.go +++ b/controller/cockpit.go @@ -18,6 +18,7 @@ import ( webauthn "github.com/go-webauthn/webauthn/webauthn" "github.com/gorilla/mux" "github.com/patrickmn/go-cache" + "github.com/robfig/cron/v3" "github.com/rs/zerolog/log" "go4.org/netipx" "golang.org/x/sync/errgroup" @@ -39,6 +40,8 @@ type Cockpit struct { author *webauthn.WebAuthn superAdmin *MirageSuperAdmin authCache *cache.Cache + + BuildCron *cron.Cron } type CtrlMsg struct { @@ -81,11 +84,14 @@ func NewCockpit(sysAddr string, ctrlChn, msgChn chan CtrlMsg, db *gorm.DB) (*Coc serviceState: false, CtrlChn: ctrlChn, MsgChn: msgChn, + BuildCron: cron.New(), } cockpit.authCache = cache.New(0, 0) // cockpit.superAdmin, cockpit.hasAdmin = cockpit.GetAdmin() cockpit.superAdmin = cockpit.GetSuperAdmin() + cockpit.BuildCron.AddFunc("0 0 * * *", cockpit.BuildLinuxClient) + return cockpit, nil } @@ -172,6 +178,7 @@ func (c *Cockpit) createRouter() *mux.Router { cockpit_router.HandleFunc("/api/service/state", c.GetServiceState).Methods(http.MethodGet) cockpit_router.HandleFunc("/api/setting/general", c.GetSettingGeneral).Methods(http.MethodGet) cockpit_router.HandleFunc("/api/tenants", c.CAPIGetTenant).Methods(http.MethodGet) + cockpit_router.HandleFunc("/api/publish", c.GetPublishInfo).Methods(http.MethodGet) cockpit_router.HandleFunc("/api/derp/query", c.CAPIQueryDERP).Methods(http.MethodGet) cockpit_router.PathPrefix("/api/derp/{id}").HandlerFunc(c.CAPIDelNaviNode).Methods(http.MethodDelete) @@ -972,6 +979,9 @@ func (c *Cockpit) Run() error { errorGroup.Go(func() error { return httpServer.Serve(httpListener) }) + // 启动buildCron + c.BuildCron.Start() + msgFunc := func(c *Cockpit) { for { msg := <-c.MsgChn diff --git a/controller/cockpit_api_publish.go b/controller/cockpit_api_publish.go index dfe206c..3e2cbf7 100644 --- a/controller/cockpit_api_publish.go +++ b/controller/cockpit_api_publish.go @@ -102,7 +102,7 @@ func (c *Cockpit) CAPIPublishClient( reqData.Url = "https://" + sysCfg.ServerURL + "/download/" + fileName } - if reqData.Url == "" || reqData.Version == "" { + if reqData.Url == "" || reqData.Version == "" && osType != "linux" { c.doAPIResponse(w, "客户端发布请求处理失败", nil) return } @@ -118,6 +118,15 @@ func (c *Cockpit) CAPIPublishClient( sysCfg.ClientVersion.NaviAMD64 = reqData.Version case "navi_aarch64": sysCfg.ClientVersion.NaviAARCH64 = reqData.Version + case "linux": + sysCfg.ClientVersion.Linux.Url = reqData.Url + sysCfg.ClientVersion.Linux.BuildState = "正在进行" + if reqData.Version != "" { + sysCfg.ClientVersion.Linux.RepoCred = reqData.Version + if reqData.Version == "clear" { + sysCfg.ClientVersion.Linux.RepoCred = "" + } + } default: c.doAPIResponse(w, "未支持的客户端类型", nil) return @@ -127,6 +136,10 @@ func (c *Cockpit) CAPIPublishClient( return } + if osType == "linux" { + go c.BuildLinuxClient() + } + if c.serviceState { newCfg, err := c.GetSysCfg().toSrvConfig() if err != nil { @@ -141,3 +154,23 @@ func (c *Cockpit) CAPIPublishClient( c.GetSettingGeneral(w, r) } + +type PublishInfoData struct { + UploadURL string `json:"upload_url"` + ClientVersion ClientVersionInfo `json:"client_version"` +} + +func (c *Cockpit) GetPublishInfo( + w http.ResponseWriter, + r *http.Request, +) { + sysCfg := c.GetSysCfg() + if sysCfg == nil { + c.doAPIResponse(w, "获取系统配置失败", nil) + return + } + c.doAPIResponse(w, "", PublishInfoData{ + UploadURL: "https://" + sysCfg.ServerURL + "/cockpit/api/publish", + ClientVersion: sysCfg.ClientVersion, + }) +} diff --git a/controller/cockpit_build.go b/controller/cockpit_build.go new file mode 100644 index 0000000..2cadb7b --- /dev/null +++ b/controller/cockpit_build.go @@ -0,0 +1,295 @@ +package controller + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "strings" + + "errors" + + "github.com/rs/zerolog/log" +) + +func (c *Cockpit) BuildLinuxClient() { + sysCfg := c.GetSysCfg() + if sysCfg == nil || sysCfg.ClientVersion.Linux.Url == "" { + return + } // 未设置Linux客户端源码仓库,无需构建 + + repoURL := sysCfg.ClientVersion.Linux.Url + if sysCfg.ClientVersion.Linux.RepoCred != "" { + repoURL = strings.Replace(repoURL, "://", "://"+sysCfg.ClientVersion.Linux.RepoCred+"@", 1) + } + var repoHash string + if _, err := os.Stat("src/linux"); err != nil && !os.IsNotExist(err) { + log.Error().Caller().Err(err).Msg("Linux 源码文件夹检查失败") + c.markLinuxLastBuildFail() + return + } else if err == nil { + // 获取本地Hash + cmd := exec.Command("git", "ls-remote", "src/linux") + var out bytes.Buffer + cmd.Stdout = &out + var stderr bytes.Buffer + cmd.Stderr = &stderr + err = cmd.Run() + if err != nil { + log.Error().Caller().Msg("检查本地Linux源码仓库Hash出错" + stderr.String()) + c.markLinuxLastBuildFail() + return + } + lines := strings.Split(out.String(), "\n") + localHash := lines[0][:39] + // 获取远程Hash + cmd = exec.Command("git", "ls-remote", repoURL) + cmd.Stdout = &out + cmd.Stderr = &stderr + err = cmd.Run() + if err != nil { + log.Error().Caller().Msg("检查远程Linux源码仓库Hash出错" + stderr.String()) + c.markLinuxLastBuildFail() + return + } + lines = strings.Split(out.String(), "\n") + repoHash = lines[0][:39] + + if localHash != repoHash { + err = os.RemoveAll("src/linux") + if err != nil { + log.Error().Caller().Err(err).Msg("Linux源码文件夹删除失败") + c.markLinuxLastBuildFail() + return + } + + err = gitCloneRepo(repoURL, "src/linux") + if err != nil { + log.Warn().Caller().Err(err).Msg("Linux源码仓库更新失败") + c.markLinuxLastBuildFail() + return + } + } else if sysCfg.ClientVersion.Linux.BuildState == "成功" { + log.Info().Caller().Msg("已成功构建过同样Hash版本,无需重复构建") + return + } + } else if os.IsNotExist(err) { + if _, err := os.Stat("src"); os.IsNotExist(err) { + err = os.Mkdir("src", os.ModePerm) + if err != nil { + log.Error().Caller().Err(err).Msg("源码文件夹创建失败") + c.markLinuxLastBuildFail() + return + } + } else if err != nil { + log.Error().Caller().Err(err).Msg("源码文件夹检查失败") + c.markLinuxLastBuildFail() + return + } + err = gitCloneRepo(repoURL, "src/linux") + if err != nil { + log.Error().Caller().Err(err).Msg("Linux源码仓库更新失败") + c.markLinuxLastBuildFail() + return + } + } + // 以上保证需要构建的代码仓库已放到本地src/linux + cmd := exec.Command("go", "run", "cmd/dist/dist.go", "build", "all") + cmd.Dir = "src/linux" + var out bytes.Buffer + cmd.Stdout = &out + var stderr bytes.Buffer + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + log.Error().Caller().Msg("Linux客户端构建失败:" + stderr.String()) + c.markLinuxLastBuildFail() + return + } + lines := strings.Split(out.String(), "\n") + shortVersion := strings.Split(lines[len(lines)-3], "=")[1] + log.Info().Caller().Msg("Linux客户端构建成功! 版本号:" + shortVersion) + + // 检查发布路径情况 + _, err = os.Stat("download") + if err != nil { + err = os.Mkdir("download", os.ModePerm) + if err != nil { + log.Error().Caller().Err(err).Msg("下载文件夹创建失败") + c.markLinuxLastBuildFail() + return + } + } + + if err = ReleaseDeb(sysCfg.ServerURL); err != nil { + log.Error().Caller().Err(err).Msg("deb发布失败") + c.markLinuxLastBuildFail() + return + } + if err = ReleaseRpm(sysCfg.ServerURL); err != nil { + log.Error().Caller().Err(err).Msg("rpm发布失败") + c.markLinuxLastBuildFail() + return + } + if err = ReleaseTgz(); err != nil { + log.Error().Caller().Err(err).Msg("tgz发布失败") + c.markLinuxLastBuildFail() + return + } + + // 记录构建成功,更新版本信息 + sysCfg = c.GetSysCfg() + if sysCfg == nil { + log.Error().Caller().Msg("构建完成,但获取系统配置失败") + c.markLinuxLastBuildFail() + return + } + sysCfg.ClientVersion.Linux.BuildState = "成功" + sysCfg.ClientVersion.Linux.Version = shortVersion + sysCfg.ClientVersion.Linux.Hash = repoHash + if err := c.db.Model(&sysCfg).Update("client_version", sysCfg.ClientVersion).Error; err != nil { + log.Error().Caller().Err(err).Msg("记录Linux客户端最近一次构建成功信息未能完成!") + } + log.Info().Msg("Linux客户端构建成功!") +} + +func gitCloneRepo(repo, target string) error { + cmd := exec.Command("git", "clone", "--recurse-submodules", repo, target) + var stderr bytes.Buffer + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + return errors.New(stderr.String()) + } + return nil +} + +func ReleaseDeb(srvURL string) error { + _, err := os.Stat("download/deb") + if err != nil { + err = os.Mkdir("download/deb", os.ModePerm) + if err != nil { + log.Error().Caller().Err(err).Msg("deb发布文件夹创建失败") + return err + } + } + + cmd := exec.Command("sh", "-c", "mv src/linux/dist/*.deb download/deb/") + var stderr bytes.Buffer + cmd.Stderr = &stderr + err = cmd.Run() + if err != nil { + return errors.New(stderr.String()) + } + + // 执行packages构建 + cmd = exec.Command("sh", "-c", "dpkg-scanpackages deb /dev/null | gzip -9c > deb/Packages.gz") + cmd.Dir = "download" + cmd.Stderr = &stderr + err = cmd.Run() + if err != nil { + return errors.New(stderr.String()) + } + + // 创建list文件 + file, err := os.Create("download/deb/mirage.list") + if err != nil { + return err + } + defer file.Close() + + debList := + `# Mirage Repo for deb format +deb https://%s/download deb` + _, err = fmt.Fprintf(file, debList, srvURL) + if err != nil { + return err + } + return nil +} + +func ReleaseRpm(srvURL string) error { + _, err := os.Stat("download/rpm") + if err != nil { + err = os.Mkdir("download/rpm", os.ModePerm) + if err != nil { + log.Error().Caller().Err(err).Msg("rpm发布文件夹创建失败") + return err + } + } + cmd := exec.Command("sh", "-c", "mv src/linux/dist/*.rpm download/rpm/") + var stderr bytes.Buffer + cmd.Stderr = &stderr + err = cmd.Run() + if err != nil { + return errors.New(stderr.String()) + } + + // 执行packages构建 - 默认在ubuntu下执行,使用createrepo_c + cmd = exec.Command("lsb_release", "-d") + output, _ := cmd.CombinedOutput() + + if strings.Contains(string(output), "Ubuntu") { + cmd = exec.Command("createrepo_c", "rpm", "--update") + } else { + cmd = exec.Command("createrepo", "rpm", "--update") + } + cmd.Dir = "download" + cmd.Stderr = &stderr + err = cmd.Run() + if err != nil { + return errors.New(stderr.String()) + } + + // 创建repo文件 + file, err := os.Create("download/rpm/mirage.repo") + if err != nil { + return err + } + defer file.Close() + + rpmRepo := + `[mirage] +name=Mirage +baseurl=https://%s/download/rpm +enabled=1 +type=rpm +repo_gpgcheck=0 +gpgcheck=0` + _, err = fmt.Fprintf(file, rpmRepo, srvURL) + if err != nil { + return err + } + return nil +} + +func ReleaseTgz() error { + _, err := os.Stat("download/tgz") + if err != nil { + err = os.Mkdir("download/tgz", os.ModePerm) + if err != nil { + log.Error().Caller().Err(err).Msg("tgz发布文件夹创建失败") + return err + } + } + cmd := exec.Command("sh", "-c", "mv src/linux/dist/*.tgz download/tgz/") + var stderr bytes.Buffer + cmd.Stderr = &stderr + err = cmd.Run() + if err != nil { + return errors.New(stderr.String()) + } + return nil +} + +func (c *Cockpit) markLinuxLastBuildFail() { + sysCfg := c.GetSysCfg() + if sysCfg == nil { + return + } + sysCfg.ClientVersion.Linux.BuildState = "失败" + if err := c.db.Model(&sysCfg).Update("client_version", sysCfg.ClientVersion).Error; err != nil { + log.Error().Caller().Err(err).Msg("记录Linux客户端最近一次构建失败信息未能完成!") + return + } +} diff --git a/controller/cockpit_syscfg.go b/controller/cockpit_syscfg.go index f6fd834..9312ae1 100644 --- a/controller/cockpit_syscfg.go +++ b/controller/cockpit_syscfg.go @@ -260,16 +260,15 @@ func (ghCfg AppleCfg) Value() (driver.Value, error) { } type ClientVersionInfo struct { - NaviAMD64 string `json:"naviAmd64"` - NaviAARCH64 string `json:"naviAarch64"` - Win ClientVer `json:"win"` - MacStore ClientVer `json:"mac_store"` - MacTestFlight ClientVer `json:"mac_test"` - LinuxAMD64 ClientVer `json:"linuxAmd64"` - LinuxAARCH64 ClientVer `json:"linuxAarch64"` - Android ClientVer `json:"android"` - IOSStore ClientVer `json:"ios_store"` - IOSTestFlight ClientVer `json:"ios_test"` + NaviAMD64 string `json:"naviAmd64"` + NaviAARCH64 string `json:"naviAarch64"` + Win ClientVer `json:"win"` + MacStore ClientVer `json:"mac_store"` + MacTestFlight ClientVer `json:"mac_test"` + Linux LinuxBuildInfo `json:"linux"` + Android ClientVer `json:"android"` + IOSStore ClientVer `json:"ios_store"` + IOSTestFlight ClientVer `json:"ios_test"` } func (c *ClientVersionInfo) Scan(value interface{}) error { @@ -292,3 +291,11 @@ type ClientVer struct { Version string `json:"version"` Url string `json:"url"` } + +type LinuxBuildInfo struct { + Version string `json:"version"` // 当前版本号 + Url string `json:"url"` // 当前仓库地址 + RepoCred string `json:"repo_cred"` // 当前仓库凭据 + Hash string `json:"hash"` // 当前发布hash + BuildState string `json:"buildst"` // 最近一次构建状态 - 尚未进行、正在进行、成功、失败 +} diff --git a/controller/console_downloads.go b/controller/console_downloads.go index c075294..39a6ed8 100644 --- a/controller/console_downloads.go +++ b/controller/console_downloads.go @@ -56,8 +56,8 @@ func (m *Mirage) sendDownloadsPage( Primary: clientsInfo.Win.Url, }, Linux: DownloadLinks{ - Primary: clientsInfo.LinuxAMD64.Url, - Secondary: clientsInfo.LinuxAARCH64.Url, + Primary: clientsInfo.Linux.Version, + Secondary: m.cfg.ServerURL, }, Android: DownloadLinks{ Primary: clientsInfo.Android.Url, diff --git a/go.mod b/go.mod index 3d450a5..422eaca 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/sftp v1.13.5 github.com/puzpuzpuz/xsync/v2 v2.4.0 + github.com/robfig/cron/v3 v3.0.1 github.com/rs/zerolog v1.29.1 github.com/samber/lo v1.38.1 github.com/sirupsen/logrus v1.9.0 @@ -29,8 +30,8 @@ require ( github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 golang.org/x/crypto v0.8.0 - golang.org/x/net v0.9.0 - golang.org/x/oauth2 v0.7.0 + golang.org/x/net v0.10.0 + golang.org/x/oauth2 v0.8.0 golang.org/x/sync v0.2.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/gorm v1.25.0 @@ -57,14 +58,14 @@ require ( github.com/alibabacloud-go/tea-xml v1.1.3 // indirect github.com/aliyun/credentials-go v1.2.7 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect - github.com/beevik/etree v1.1.3 // indirect + github.com/beevik/etree v1.1.4 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/clbanning/mxj/v2 v2.5.7 // indirect github.com/coreos/go-oidc v2.2.1+incompatible // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/dexidp/dex/api/v2 v2.0.0-00010101000000-000000000000 // indirect + github.com/dexidp/dex/api/v2 v2.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/elastic/elastic-transport-go/v8 v8.2.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect diff --git a/go.sum b/go.sum index 00effe2..281486e 100644 --- a/go.sum +++ b/go.sum @@ -115,8 +115,8 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6 github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= -github.com/beevik/etree v1.1.3 h1:RM50lzyrX4BhfIR7LI7LKq2HQtcksDWasTBnE2PaV7o= -github.com/beevik/etree v1.1.3/go.mod h1:MEjUJqV1InUci5lSfeIonCXMjsMW9yC3APDqKtnyNQg= +github.com/beevik/etree v1.1.4 h1:34PFKrJczQ1qXVC4QCqvY0Iz7m3xu89OShTjYRl4Nbk= +github.com/beevik/etree v1.1.4/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -489,6 +489,8 @@ github.com/puzpuzpuz/xsync/v2 v2.4.0/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2z github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -712,8 +714,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 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= @@ -724,8 +726,8 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ 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/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= -golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= 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= @@ -801,7 +803,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= 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=