Merge branch 'master' into AG-20352-dhcpd-lease-is-static
This commit is contained in:
commit
e2d9ed0832
|
@ -1,7 +1,7 @@
|
||||||
'name': 'build'
|
'name': 'build'
|
||||||
|
|
||||||
'env':
|
'env':
|
||||||
'GO_VERSION': '1.19.7'
|
'GO_VERSION': '1.19.8'
|
||||||
'NODE_VERSION': '14'
|
'NODE_VERSION': '14'
|
||||||
|
|
||||||
'on':
|
'on':
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'name': 'lint'
|
'name': 'lint'
|
||||||
|
|
||||||
'env':
|
'env':
|
||||||
'GO_VERSION': '1.19.7'
|
'GO_VERSION': '1.19.8'
|
||||||
|
|
||||||
'on':
|
'on':
|
||||||
'push':
|
'push':
|
||||||
|
|
|
@ -23,6 +23,12 @@ See also the [v0.107.27 GitHub milestone][ms-v0.107.27].
|
||||||
NOTE: Add new changes BELOW THIS COMMENT.
|
NOTE: Add new changes BELOW THIS COMMENT.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Go version has been updated to prevent the possibility of exploiting the
|
||||||
|
CVE-2023-24534, CVE-2023-24536, CVE-2023-24537, and CVE-2023-24538 Go
|
||||||
|
vulnerabilities fixed in [Go 1.19.8][go-1.19.8].
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- The ability to make bootstrap DNS lookups prefer IPv6 addresses to IPv4 ones
|
- The ability to make bootstrap DNS lookups prefer IPv6 addresses to IPv4 ones
|
||||||
|
@ -146,7 +152,8 @@ In this release, the schema version has changed from 17 to 20.
|
||||||
[#5631]: https://github.com/AdguardTeam/AdGuardHome/issues/5631
|
[#5631]: https://github.com/AdguardTeam/AdGuardHome/issues/5631
|
||||||
[#5639]: https://github.com/AdguardTeam/AdGuardHome/issues/5639
|
[#5639]: https://github.com/AdguardTeam/AdGuardHome/issues/5639
|
||||||
|
|
||||||
[rfc6761]: https://www.rfc-editor.org/rfc/rfc6761
|
[go-1.19.8]: https://groups.google.com/g/golang-announce/c/Xdv6JL9ENs8/m/OV40vnafAwAJ
|
||||||
|
[rfc6761]: https://www.rfc-editor.org/rfc/rfc6761
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
# Make sure to sync any changes with the branch overrides below.
|
# Make sure to sync any changes with the branch overrides below.
|
||||||
'variables':
|
'variables':
|
||||||
'channel': 'edge'
|
'channel': 'edge'
|
||||||
'dockerGo': 'adguard/golang-ubuntu:6.2'
|
'dockerGo': 'adguard/golang-ubuntu:6.3'
|
||||||
|
|
||||||
'stages':
|
'stages':
|
||||||
- 'Build frontend':
|
- 'Build frontend':
|
||||||
|
@ -331,7 +331,7 @@
|
||||||
# need to build a few of these.
|
# need to build a few of these.
|
||||||
'variables':
|
'variables':
|
||||||
'channel': 'beta'
|
'channel': 'beta'
|
||||||
'dockerGo': 'adguard/golang-ubuntu:6.2'
|
'dockerGo': 'adguard/golang-ubuntu:6.3'
|
||||||
# release-vX.Y.Z branches are the branches from which the actual final release
|
# release-vX.Y.Z branches are the branches from which the actual final release
|
||||||
# is built.
|
# is built.
|
||||||
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
- '^release-v[0-9]+\.[0-9]+\.[0-9]+':
|
||||||
|
@ -346,4 +346,4 @@
|
||||||
# are the ones that actually get released.
|
# are the ones that actually get released.
|
||||||
'variables':
|
'variables':
|
||||||
'channel': 'release'
|
'channel': 'release'
|
||||||
'dockerGo': 'adguard/golang-ubuntu:6.2'
|
'dockerGo': 'adguard/golang-ubuntu:6.3'
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
'key': 'AHBRTSPECS'
|
'key': 'AHBRTSPECS'
|
||||||
'name': 'AdGuard Home - Build and run tests'
|
'name': 'AdGuard Home - Build and run tests'
|
||||||
'variables':
|
'variables':
|
||||||
'dockerGo': 'adguard/golang-ubuntu:6.2'
|
'dockerGo': 'adguard/golang-ubuntu:6.3'
|
||||||
|
|
||||||
'stages':
|
'stages':
|
||||||
- 'Tests':
|
- 'Tests':
|
||||||
|
|
|
@ -413,7 +413,7 @@
|
||||||
"fix": "Düzelt",
|
"fix": "Düzelt",
|
||||||
"dns_providers": "Aralarından seçim yapabileceğiniz, bilinen <0>DNS sağlayıcıların listesi</0>.",
|
"dns_providers": "Aralarından seçim yapabileceğiniz, bilinen <0>DNS sağlayıcıların listesi</0>.",
|
||||||
"update_now": "Şimdi güncelle",
|
"update_now": "Şimdi güncelle",
|
||||||
"update_failed": "Otomatik güncelleme başarısız oldu. Elle güncellemek için lütfen <a>bu adımları izleyin</a>.",
|
"update_failed": "Otomatik güncellenemedi. Elle güncellemek için lütfen <a>bu adımları izleyin</a>.",
|
||||||
"manual_update": "Elle güncellemek için lütfen <a>bu adımları uygulayın</a>.",
|
"manual_update": "Elle güncellemek için lütfen <a>bu adımları uygulayın</a>.",
|
||||||
"processing_update": "Lütfen bekleyin, AdGuard Home güncelleniyor",
|
"processing_update": "Lütfen bekleyin, AdGuard Home güncelleniyor",
|
||||||
"clients_title": "Kalıcı istemciler",
|
"clients_title": "Kalıcı istemciler",
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -4,7 +4,7 @@ go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AdguardTeam/dnsproxy v0.48.3
|
github.com/AdguardTeam/dnsproxy v0.48.3
|
||||||
github.com/AdguardTeam/golibs v0.13.0
|
github.com/AdguardTeam/golibs v0.13.1
|
||||||
github.com/AdguardTeam/urlfilter v0.16.1
|
github.com/AdguardTeam/urlfilter v0.16.1
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.2.6
|
github.com/ameshkov/dnscrypt/v2 v2.2.6
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -2,8 +2,8 @@ github.com/AdguardTeam/dnsproxy v0.48.3 h1:h9xgDSmd1MqsPFNApyaPVXolmSTtzOWOcfWvP
|
||||||
github.com/AdguardTeam/dnsproxy v0.48.3/go.mod h1:Y7g7jRTd/u7+KJ/QvnGI2PCE8vnisp6EsW47/Sz0DZw=
|
github.com/AdguardTeam/dnsproxy v0.48.3/go.mod h1:Y7g7jRTd/u7+KJ/QvnGI2PCE8vnisp6EsW47/Sz0DZw=
|
||||||
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||||
github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
|
github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
|
||||||
github.com/AdguardTeam/golibs v0.13.0 h1:hVBeNQXT/BgcjKz/4FMpFGvEYqXiXDJG+b5XpGCUOLk=
|
github.com/AdguardTeam/golibs v0.13.1 h1:x6ChoXk2jborbCWJ01TyBAEY3SilHts0SCG7yjnf6Sc=
|
||||||
github.com/AdguardTeam/golibs v0.13.0/go.mod h1:rIglKDHdLvFT1UbhumBLHO9S4cvWS9MEyT1njommI/Y=
|
github.com/AdguardTeam/golibs v0.13.1/go.mod h1:7ylQLv2Lqsc3UW3jHoITynYk6Y1tYtgEMkR09ppfsN8=
|
||||||
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
|
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
|
||||||
github.com/AdguardTeam/urlfilter v0.16.1 h1:ZPi0rjqo8cQf2FVdzo6cqumNoHZx2KPXj2yZa1A5BBw=
|
github.com/AdguardTeam/urlfilter v0.16.1 h1:ZPi0rjqo8cQf2FVdzo6cqumNoHZx2KPXj2yZa1A5BBw=
|
||||||
github.com/AdguardTeam/urlfilter v0.16.1/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
|
github.com/AdguardTeam/urlfilter v0.16.1/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI=
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/AdguardTeam/golibs/stringutil"
|
|
||||||
"github.com/AdguardTeam/golibs/timeutil"
|
"github.com/AdguardTeam/golibs/timeutil"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
@ -126,9 +125,7 @@ func (l *queryLog) WriteDiskConfig(c *Config) {
|
||||||
defer l.confMu.RUnlock()
|
defer l.confMu.RUnlock()
|
||||||
|
|
||||||
*c = *l.conf
|
*c = *l.conf
|
||||||
|
c.Ignored = l.conf.Ignored.Clone()
|
||||||
// TODO(a.garipov): Add stringutil.Set.Clone.
|
|
||||||
c.Ignored = stringutil.NewSet(l.conf.Ignored.Values()...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear memory buffer and remove log files
|
// Clear memory buffer and remove log files
|
||||||
|
|
|
@ -33,6 +33,8 @@ type QueryLog interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is the query log configuration structure.
|
// Config is the query log configuration structure.
|
||||||
|
//
|
||||||
|
// Do not alter any fields of this structure after using it.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Ignored is the list of host names, which should not be written to log.
|
// Ignored is the list of host names, which should not be written to log.
|
||||||
Ignored *stringutil.Set
|
Ignored *stringutil.Set
|
||||||
|
|
|
@ -42,13 +42,21 @@ type StatsResp struct {
|
||||||
AvgProcessingTime float64 `json:"avg_processing_time"`
|
AvgProcessingTime float64 `json:"avg_processing_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleStats handles requests to the GET /control/stats endpoint.
|
// handleStats is the handler for the GET /control/stats HTTP API.
|
||||||
func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
|
func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
|
||||||
s.lock.Lock()
|
|
||||||
defer s.lock.Unlock()
|
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
resp, ok := s.getData(uint32(s.limit.Hours()))
|
|
||||||
|
var (
|
||||||
|
resp StatsResp
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
func() {
|
||||||
|
s.confMu.RLock()
|
||||||
|
defer s.confMu.RUnlock()
|
||||||
|
|
||||||
|
resp, ok = s.getData(uint32(s.limit.Hours()))
|
||||||
|
}()
|
||||||
|
|
||||||
log.Debug("stats: prepared data in %v", time.Since(start))
|
log.Debug("stats: prepared data in %v", time.Since(start))
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -80,47 +88,58 @@ type getConfigResp struct {
|
||||||
Enabled aghalg.NullBool `json:"enabled"`
|
Enabled aghalg.NullBool `json:"enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleStatsInfo handles requests to the GET /control/stats_info endpoint.
|
// handleStatsInfo is the handler for the GET /control/stats_info HTTP API.
|
||||||
//
|
//
|
||||||
// Deprecated: Remove it when migration to the new API is over.
|
// Deprecated: Remove it when migration to the new API is over.
|
||||||
func (s *StatsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) {
|
func (s *StatsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
s.lock.Lock()
|
var (
|
||||||
defer s.lock.Unlock()
|
enabled bool
|
||||||
|
limit time.Duration
|
||||||
|
)
|
||||||
|
func() {
|
||||||
|
s.confMu.RLock()
|
||||||
|
defer s.confMu.RUnlock()
|
||||||
|
|
||||||
days := uint32(s.limit / timeutil.Day)
|
enabled, limit = s.enabled, s.limit
|
||||||
|
}()
|
||||||
|
|
||||||
|
days := uint32(limit / timeutil.Day)
|
||||||
ok := checkInterval(days)
|
ok := checkInterval(days)
|
||||||
if !ok || (s.enabled && days == 0) {
|
if !ok || (enabled && days == 0) {
|
||||||
// NOTE: If interval is custom we set it to 90 days for compatibility
|
// NOTE: If interval is custom we set it to 90 days for compatibility
|
||||||
// with old API.
|
// with old API.
|
||||||
days = 90
|
days = 90
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := configResp{IntervalDays: days}
|
resp := configResp{IntervalDays: days}
|
||||||
if !s.enabled {
|
if !enabled {
|
||||||
resp.IntervalDays = 0
|
resp.IntervalDays = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleGetStatsConfig handles requests to the GET /control/stats/config
|
// handleGetStatsConfig is the handler for the GET /control/stats/config HTTP
|
||||||
// endpoint.
|
// API.
|
||||||
func (s *StatsCtx) handleGetStatsConfig(w http.ResponseWriter, r *http.Request) {
|
func (s *StatsCtx) handleGetStatsConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
s.lock.Lock()
|
var resp *getConfigResp
|
||||||
defer s.lock.Unlock()
|
func() {
|
||||||
|
s.confMu.RLock()
|
||||||
|
defer s.confMu.RUnlock()
|
||||||
|
|
||||||
ignored := s.ignored.Values()
|
resp = &getConfigResp{
|
||||||
slices.Sort(ignored)
|
Ignored: s.ignored.Values(),
|
||||||
|
Interval: float64(s.limit.Milliseconds()),
|
||||||
|
Enabled: aghalg.BoolToNullBool(s.enabled),
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
slices.Sort(resp.Ignored)
|
||||||
|
|
||||||
resp := getConfigResp{
|
|
||||||
Ignored: ignored,
|
|
||||||
Interval: float64(s.limit.Milliseconds()),
|
|
||||||
Enabled: aghalg.BoolToNullBool(s.enabled),
|
|
||||||
}
|
|
||||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleStatsConfig handles requests to the POST /control/stats_config
|
// handleStatsConfig is the handler for the POST /control/stats_config HTTP API.
|
||||||
// endpoint.
|
|
||||||
//
|
//
|
||||||
// Deprecated: Remove it when migration to the new API is over.
|
// Deprecated: Remove it when migration to the new API is over.
|
||||||
func (s *StatsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) {
|
func (s *StatsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -138,17 +157,18 @@ func (s *StatsCtx) handleStatsConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
limit := time.Duration(reqData.IntervalDays) * timeutil.Day
|
||||||
|
|
||||||
defer s.configModified()
|
defer s.configModified()
|
||||||
|
|
||||||
s.lock.Lock()
|
s.confMu.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.confMu.Unlock()
|
||||||
|
|
||||||
limit := time.Duration(reqData.IntervalDays) * timeutil.Day
|
|
||||||
s.setLimit(limit)
|
s.setLimit(limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlePutStatsConfig handles requests to the PUT /control/stats/config/update
|
// handlePutStatsConfig is the handler for the PUT /control/stats/config/update
|
||||||
// endpoint.
|
// HTTP API.
|
||||||
func (s *StatsCtx) handlePutStatsConfig(w http.ResponseWriter, r *http.Request) {
|
func (s *StatsCtx) handlePutStatsConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
reqData := getConfigResp{}
|
reqData := getConfigResp{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&reqData)
|
err := json.NewDecoder(r.Body).Decode(&reqData)
|
||||||
|
@ -181,15 +201,15 @@ func (s *StatsCtx) handlePutStatsConfig(w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
defer s.configModified()
|
defer s.configModified()
|
||||||
|
|
||||||
s.lock.Lock()
|
s.confMu.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.confMu.Unlock()
|
||||||
|
|
||||||
s.ignored = set
|
s.ignored = set
|
||||||
s.limit = ivl
|
s.limit = ivl
|
||||||
s.enabled = reqData.Enabled == aghalg.NBTrue
|
s.enabled = reqData.Enabled == aghalg.NBTrue
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleStatsReset handles requests to the POST /control/stats_reset endpoint.
|
// handleStatsReset is the handler for the POST /control/stats_reset HTTP API.
|
||||||
func (s *StatsCtx) handleStatsReset(w http.ResponseWriter, r *http.Request) {
|
func (s *StatsCtx) handleStatsReset(w http.ResponseWriter, r *http.Request) {
|
||||||
err := s.clear()
|
err := s.clear()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -205,9 +225,10 @@ func (s *StatsCtx) initWeb() {
|
||||||
|
|
||||||
s.httpRegister(http.MethodGet, "/control/stats", s.handleStats)
|
s.httpRegister(http.MethodGet, "/control/stats", s.handleStats)
|
||||||
s.httpRegister(http.MethodPost, "/control/stats_reset", s.handleStatsReset)
|
s.httpRegister(http.MethodPost, "/control/stats_reset", s.handleStatsReset)
|
||||||
s.httpRegister(http.MethodPost, "/control/stats_config", s.handleStatsConfig)
|
|
||||||
s.httpRegister(http.MethodGet, "/control/stats_info", s.handleStatsInfo)
|
|
||||||
|
|
||||||
s.httpRegister(http.MethodGet, "/control/stats/config", s.handleGetStatsConfig)
|
s.httpRegister(http.MethodGet, "/control/stats/config", s.handleGetStatsConfig)
|
||||||
s.httpRegister(http.MethodPut, "/control/stats/config/update", s.handlePutStatsConfig)
|
s.httpRegister(http.MethodPut, "/control/stats/config/update", s.handlePutStatsConfig)
|
||||||
|
|
||||||
|
// Deprecated handlers.
|
||||||
|
s.httpRegister(http.MethodGet, "/control/stats_info", s.handleStatsInfo)
|
||||||
|
s.httpRegister(http.MethodPost, "/control/stats_config", s.handleStatsConfig)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,11 +24,11 @@ func TestHandleStatsConfig(t *testing.T) {
|
||||||
)
|
)
|
||||||
|
|
||||||
conf := Config{
|
conf := Config{
|
||||||
|
UnitID: func() (id uint32) { return 0 },
|
||||||
|
ConfigModified: func() {},
|
||||||
Filename: filepath.Join(t.TempDir(), "stats.db"),
|
Filename: filepath.Join(t.TempDir(), "stats.db"),
|
||||||
Limit: time.Hour * 24,
|
Limit: time.Hour * 24,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
UnitID: func() (id uint32) { return 0 },
|
|
||||||
ConfigModified: func() {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
|
|
@ -41,6 +41,8 @@ func validateIvl(ivl time.Duration) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is the configuration structure for the statistics collecting.
|
// Config is the configuration structure for the statistics collecting.
|
||||||
|
//
|
||||||
|
// Do not alter any fields of this structure after using it.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// UnitID is the function to generate the identifier for current unit. If
|
// UnitID is the function to generate the identifier for current unit. If
|
||||||
// nil, the default function is used, see newUnitID.
|
// nil, the default function is used, see newUnitID.
|
||||||
|
@ -54,6 +56,9 @@ type Config struct {
|
||||||
// endpoints.
|
// endpoints.
|
||||||
HTTPRegister aghhttp.RegisterFunc
|
HTTPRegister aghhttp.RegisterFunc
|
||||||
|
|
||||||
|
// Ignored is the list of host names, which should not be counted.
|
||||||
|
Ignored *stringutil.Set
|
||||||
|
|
||||||
// Filename is the name of the database file.
|
// Filename is the name of the database file.
|
||||||
Filename string
|
Filename string
|
||||||
|
|
||||||
|
@ -62,9 +67,6 @@ type Config struct {
|
||||||
|
|
||||||
// Enabled tells if the statistics are enabled.
|
// Enabled tells if the statistics are enabled.
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
|
||||||
// Ignored is the list of host names, which should not be counted.
|
|
||||||
Ignored *stringutil.Set
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface is the statistics interface to be used by other packages.
|
// Interface is the statistics interface to be used by other packages.
|
||||||
|
@ -110,20 +112,20 @@ type StatsCtx struct {
|
||||||
// interface.
|
// interface.
|
||||||
configModified func()
|
configModified func()
|
||||||
|
|
||||||
|
// confMu protects ignored, limit, and enabled.
|
||||||
|
confMu *sync.RWMutex
|
||||||
|
|
||||||
|
// ignored is the list of host names, which should not be counted.
|
||||||
|
ignored *stringutil.Set
|
||||||
|
|
||||||
// filename is the name of database file.
|
// filename is the name of database file.
|
||||||
filename string
|
filename string
|
||||||
|
|
||||||
// lock protects all the fields below.
|
|
||||||
lock sync.Mutex
|
|
||||||
|
|
||||||
// enabled tells if the statistics are enabled.
|
|
||||||
enabled bool
|
|
||||||
|
|
||||||
// limit is an upper limit for collecting statistics.
|
// limit is an upper limit for collecting statistics.
|
||||||
limit time.Duration
|
limit time.Duration
|
||||||
|
|
||||||
// ignored is the list of host names, which should not be counted.
|
// enabled tells if the statistics are enabled.
|
||||||
ignored *stringutil.Set
|
enabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates s from conf and properly initializes it. Don't use s before
|
// New creates s from conf and properly initializes it. Don't use s before
|
||||||
|
@ -131,21 +133,22 @@ type StatsCtx struct {
|
||||||
func New(conf Config) (s *StatsCtx, err error) {
|
func New(conf Config) (s *StatsCtx, err error) {
|
||||||
defer withRecovered(&err)
|
defer withRecovered(&err)
|
||||||
|
|
||||||
s = &StatsCtx{
|
|
||||||
enabled: conf.Enabled,
|
|
||||||
currMu: &sync.RWMutex{},
|
|
||||||
filename: conf.Filename,
|
|
||||||
configModified: conf.ConfigModified,
|
|
||||||
httpRegister: conf.HTTPRegister,
|
|
||||||
ignored: conf.Ignored,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = validateIvl(conf.Limit)
|
err = validateIvl(conf.Limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unsupported interval: %w", err)
|
return nil, fmt.Errorf("unsupported interval: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.limit = conf.Limit
|
s = &StatsCtx{
|
||||||
|
currMu: &sync.RWMutex{},
|
||||||
|
httpRegister: conf.HTTPRegister,
|
||||||
|
configModified: conf.ConfigModified,
|
||||||
|
filename: conf.Filename,
|
||||||
|
|
||||||
|
confMu: &sync.RWMutex{},
|
||||||
|
ignored: conf.Ignored,
|
||||||
|
limit: conf.Limit,
|
||||||
|
enabled: conf.Enabled,
|
||||||
|
}
|
||||||
|
|
||||||
if s.unitIDGen = newUnitID; conf.UnitID != nil {
|
if s.unitIDGen = newUnitID; conf.UnitID != nil {
|
||||||
s.unitIDGen = conf.UnitID
|
s.unitIDGen = conf.UnitID
|
||||||
|
@ -244,8 +247,8 @@ func (s *StatsCtx) Close() (err error) {
|
||||||
|
|
||||||
// Update implements the Interface interface for *StatsCtx.
|
// Update implements the Interface interface for *StatsCtx.
|
||||||
func (s *StatsCtx) Update(e Entry) {
|
func (s *StatsCtx) Update(e Entry) {
|
||||||
s.lock.Lock()
|
s.confMu.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.confMu.Unlock()
|
||||||
|
|
||||||
if !s.enabled || s.limit == 0 {
|
if !s.enabled || s.limit == 0 {
|
||||||
return
|
return
|
||||||
|
@ -276,18 +279,18 @@ func (s *StatsCtx) Update(e Entry) {
|
||||||
|
|
||||||
// WriteDiskConfig implements the Interface interface for *StatsCtx.
|
// WriteDiskConfig implements the Interface interface for *StatsCtx.
|
||||||
func (s *StatsCtx) WriteDiskConfig(dc *Config) {
|
func (s *StatsCtx) WriteDiskConfig(dc *Config) {
|
||||||
s.lock.Lock()
|
s.confMu.RLock()
|
||||||
defer s.lock.Unlock()
|
defer s.confMu.RUnlock()
|
||||||
|
|
||||||
|
dc.Ignored = s.ignored.Clone()
|
||||||
dc.Limit = s.limit
|
dc.Limit = s.limit
|
||||||
dc.Enabled = s.enabled
|
dc.Enabled = s.enabled
|
||||||
dc.Ignored = s.ignored
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TopClientsIP implements the [Interface] interface for *StatsCtx.
|
// TopClientsIP implements the [Interface] interface for *StatsCtx.
|
||||||
func (s *StatsCtx) TopClientsIP(maxCount uint) (ips []netip.Addr) {
|
func (s *StatsCtx) TopClientsIP(maxCount uint) (ips []netip.Addr) {
|
||||||
s.lock.Lock()
|
s.confMu.RLock()
|
||||||
defer s.lock.Unlock()
|
defer s.confMu.RUnlock()
|
||||||
|
|
||||||
limit := uint32(s.limit.Hours())
|
limit := uint32(s.limit.Hours())
|
||||||
if !s.enabled || limit == 0 {
|
if !s.enabled || limit == 0 {
|
||||||
|
@ -382,8 +385,8 @@ func (s *StatsCtx) openDB() (err error) {
|
||||||
func (s *StatsCtx) flush() (cont bool, sleepFor time.Duration) {
|
func (s *StatsCtx) flush() (cont bool, sleepFor time.Duration) {
|
||||||
id := s.unitIDGen()
|
id := s.unitIDGen()
|
||||||
|
|
||||||
s.lock.Lock()
|
s.confMu.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.confMu.Unlock()
|
||||||
|
|
||||||
s.currMu.Lock()
|
s.currMu.Lock()
|
||||||
defer s.currMu.Unlock()
|
defer s.currMu.Unlock()
|
||||||
|
@ -575,10 +578,14 @@ func (s *StatsCtx) loadUnits(limit uint32) (units []*unitDB, firstID uint32) {
|
||||||
|
|
||||||
// ShouldCount returns true if request for the host should be counted.
|
// ShouldCount returns true if request for the host should be counted.
|
||||||
func (s *StatsCtx) ShouldCount(host string, _, _ uint16) bool {
|
func (s *StatsCtx) ShouldCount(host string, _, _ uint16) bool {
|
||||||
|
s.confMu.RLock()
|
||||||
|
defer s.confMu.RUnlock()
|
||||||
|
|
||||||
return !s.isIgnored(host)
|
return !s.isIgnored(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isIgnored returns true if the host is in the Ignored list.
|
// isIgnored returns true if the host is in the ignored domains list. It
|
||||||
|
// assumes that s.confMu is locked for reading.
|
||||||
func (s *StatsCtx) isIgnored(host string) bool {
|
func (s *StatsCtx) isIgnored(host string) bool {
|
||||||
return s.ignored.Has(host)
|
return s.ignored.Has(host)
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,19 @@ type Entry struct {
|
||||||
|
|
||||||
// unit collects the statistics data for a specific period of time.
|
// unit collects the statistics data for a specific period of time.
|
||||||
type unit struct {
|
type unit struct {
|
||||||
|
// domains stores the number of requests for each domain.
|
||||||
|
domains map[string]uint64
|
||||||
|
|
||||||
|
// blockedDomains stores the number of requests for each domain that has
|
||||||
|
// been blocked.
|
||||||
|
blockedDomains map[string]uint64
|
||||||
|
|
||||||
|
// clients stores the number of requests from each client.
|
||||||
|
clients map[string]uint64
|
||||||
|
|
||||||
|
// nResult stores the number of requests grouped by it's result.
|
||||||
|
nResult []uint64
|
||||||
|
|
||||||
// id is the unique unit's identifier. It's set to an absolute hour number
|
// id is the unique unit's identifier. It's set to an absolute hour number
|
||||||
// since the beginning of UNIX time by the default ID generating function.
|
// since the beginning of UNIX time by the default ID generating function.
|
||||||
//
|
//
|
||||||
|
@ -81,29 +94,20 @@ type unit struct {
|
||||||
|
|
||||||
// nTotal stores the total number of requests.
|
// nTotal stores the total number of requests.
|
||||||
nTotal uint64
|
nTotal uint64
|
||||||
// nResult stores the number of requests grouped by it's result.
|
|
||||||
nResult []uint64
|
|
||||||
// timeSum stores the sum of processing time in milliseconds of each request
|
// timeSum stores the sum of processing time in milliseconds of each request
|
||||||
// written by the unit.
|
// written by the unit.
|
||||||
timeSum uint64
|
timeSum uint64
|
||||||
|
|
||||||
// domains stores the number of requests for each domain.
|
|
||||||
domains map[string]uint64
|
|
||||||
// blockedDomains stores the number of requests for each domain that has
|
|
||||||
// been blocked.
|
|
||||||
blockedDomains map[string]uint64
|
|
||||||
// clients stores the number of requests from each client.
|
|
||||||
clients map[string]uint64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newUnit allocates the new *unit.
|
// newUnit allocates the new *unit.
|
||||||
func newUnit(id uint32) (u *unit) {
|
func newUnit(id uint32) (u *unit) {
|
||||||
return &unit{
|
return &unit{
|
||||||
id: id,
|
domains: map[string]uint64{},
|
||||||
|
blockedDomains: map[string]uint64{},
|
||||||
|
clients: map[string]uint64{},
|
||||||
nResult: make([]uint64, resultLast),
|
nResult: make([]uint64, resultLast),
|
||||||
domains: make(map[string]uint64),
|
id: id,
|
||||||
blockedDomains: make(map[string]uint64),
|
|
||||||
clients: make(map[string]uint64),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,19 +119,25 @@ type countPair struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// unitDB is the structure for serializing statistics data into the database.
|
// unitDB is the structure for serializing statistics data into the database.
|
||||||
|
//
|
||||||
|
// NOTE: Do not change the names or types of fields, as this structure is used
|
||||||
|
// for GOB encoding.
|
||||||
type unitDB struct {
|
type unitDB struct {
|
||||||
// NTotal is the total number of requests.
|
|
||||||
NTotal uint64
|
|
||||||
// NResult is the number of requests by the result's kind.
|
// NResult is the number of requests by the result's kind.
|
||||||
NResult []uint64
|
NResult []uint64
|
||||||
|
|
||||||
// Domains is the number of requests for each domain name.
|
// Domains is the number of requests for each domain name.
|
||||||
Domains []countPair
|
Domains []countPair
|
||||||
|
|
||||||
// BlockedDomains is the number of requests blocked for each domain name.
|
// BlockedDomains is the number of requests blocked for each domain name.
|
||||||
BlockedDomains []countPair
|
BlockedDomains []countPair
|
||||||
|
|
||||||
// Clients is the number of requests from each client.
|
// Clients is the number of requests from each client.
|
||||||
Clients []countPair
|
Clients []countPair
|
||||||
|
|
||||||
|
// NTotal is the total number of requests.
|
||||||
|
NTotal uint64
|
||||||
|
|
||||||
// TimeAvg is the average of processing times in milliseconds of all the
|
// TimeAvg is the average of processing times in milliseconds of all the
|
||||||
// requests in the unit.
|
// requests in the unit.
|
||||||
TimeAvg uint32
|
TimeAvg uint32
|
||||||
|
|
|
@ -10,7 +10,7 @@ require (
|
||||||
github.com/kyoh86/looppointer v0.2.1
|
github.com/kyoh86/looppointer v0.2.1
|
||||||
github.com/securego/gosec/v2 v2.15.0
|
github.com/securego/gosec/v2 v2.15.0
|
||||||
golang.org/x/tools v0.7.0
|
golang.org/x/tools v0.7.0
|
||||||
golang.org/x/vuln v0.0.0-20230325131008-9550759f8614
|
golang.org/x/vuln v0.0.0-20230404205743-41aec7335792
|
||||||
honnef.co/go/tools v0.4.3
|
honnef.co/go/tools v0.4.3
|
||||||
mvdan.cc/gofumpt v0.4.0
|
mvdan.cc/gofumpt v0.4.0
|
||||||
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8
|
mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8
|
||||||
|
@ -26,8 +26,8 @@ require (
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
|
||||||
golang.org/x/exp/typeparams v0.0.0-20230321023759-10a507213a29 // indirect
|
golang.org/x/exp/typeparams v0.0.0-20230321023759-10a507213a29 // indirect
|
||||||
golang.org/x/mod v0.9.0 // indirect
|
golang.org/x/mod v0.10.0 // indirect
|
||||||
golang.org/x/sync v0.1.0 // indirect
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
golang.org/x/sys v0.6.0 // indirect
|
golang.org/x/sys v0.7.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
@ -55,8 +55,8 @@ golang.org/x/exp/typeparams v0.0.0-20230321023759-10a507213a29/go.mod h1:AbB0pIl
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
|
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
@ -78,8 +78,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
@ -93,8 +93,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
||||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||||
golang.org/x/vuln v0.0.0-20230325131008-9550759f8614 h1:ioRvkxpf+3iq9xvzP6AWjaBuROUad9SmjLr0QWBzxKw=
|
golang.org/x/vuln v0.0.0-20230404205743-41aec7335792 h1:NybXXIgk5dslpSHRStwyfI74htFvi9Cyk3mCr9ubE2I=
|
||||||
golang.org/x/vuln v0.0.0-20230325131008-9550759f8614/go.mod h1:64LpnL2PuSMzFYeCmJjYiRbroOUG9aCZYznINnF5PHE=
|
golang.org/x/vuln v0.0.0-20230404205743-41aec7335792/go.mod h1:8gQW8OCBfaUiPaWAPDQf/9V1w+ymmmB/05SwB/EXZNs=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
@ -35,7 +35,7 @@ set -f -u
|
||||||
go_version="$( "${GO:-go}" version )"
|
go_version="$( "${GO:-go}" version )"
|
||||||
readonly go_version
|
readonly go_version
|
||||||
|
|
||||||
go_min_version='go1.19.7'
|
go_min_version='go1.19.8'
|
||||||
go_version_msg="
|
go_version_msg="
|
||||||
warning: your go version (${go_version}) is different from the recommended minimal one (${go_min_version}).
|
warning: your go version (${go_version}) is different from the recommended minimal one (${go_min_version}).
|
||||||
if you have the version installed, please set the GO environment variable.
|
if you have the version installed, please set the GO environment variable.
|
||||||
|
|
Loading…
Reference in New Issue