Pull request 1930: fewer-globals

Squashed commit of the following:

commit ce882cfff4c1f7afdf0cba13b39e6ee568eb812f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Jul 19 15:56:52 2023 +0300

    all: imp code, lint

commit 96fc5c589e7474f4bba291b0a20a0834148bb9c1
Merge: 3e91eea6b b0185201c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 18 21:22:32 2023 +0300

    Merge branch 'master' into fewer-globals

commit 3e91eea6b68bac51251784e3069b1c9d241da439
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jul 18 19:01:45 2023 +0300

    home: rm globals
This commit is contained in:
Ainar Garipov 2023-07-19 16:57:57 +03:00
parent b0185201c6
commit 685d982924
7 changed files with 132 additions and 112 deletions

View File

@ -176,12 +176,16 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
// ------------------------ // ------------------------
// registration of handlers // registration of handlers
// ------------------------ // ------------------------
func registerControlHandlers() { func registerControlHandlers(web *webAPI) {
Context.mux.HandleFunc(
"/control/version.json",
postInstall(optionalAuth(web.handleVersionJSON)),
)
httpRegister(http.MethodPost, "/control/update", web.handleUpdate)
httpRegister(http.MethodGet, "/control/status", handleStatus) httpRegister(http.MethodGet, "/control/status", handleStatus)
httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage) httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage)
httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage) httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage)
Context.mux.HandleFunc("/control/version.json", postInstall(optionalAuth(handleVersionJSON)))
httpRegister(http.MethodPost, "/control/update", handleUpdate)
httpRegister(http.MethodGet, "/control/profile", handleGetProfile) httpRegister(http.MethodGet, "/control/profile", handleGetProfile)
httpRegister(http.MethodPut, "/control/profile/update", handlePutProfile) httpRegister(http.MethodPut, "/control/profile/update", handlePutProfile)

View File

@ -448,7 +448,7 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
web.conf.BindHost = req.Web.IP web.conf.BindHost = req.Web.IP
web.conf.BindPort = req.Web.Port web.conf.BindPort = req.Web.Port
registerControlHandlers() registerControlHandlers(web)
aghhttp.OK(w) aghhttp.OK(w)
if f, ok := w.(http.Flusher); ok { if f, ok := w.(http.Flusher); ok {

View File

@ -29,9 +29,9 @@ type temporaryError interface {
// handleVersionJSON is the handler for the POST /control/version.json HTTP API. // handleVersionJSON is the handler for the POST /control/version.json HTTP API.
// //
// TODO(a.garipov): Find out if this API used with a GET method by anyone. // TODO(a.garipov): Find out if this API used with a GET method by anyone.
func handleVersionJSON(w http.ResponseWriter, r *http.Request) { func (web *webAPI) handleVersionJSON(w http.ResponseWriter, r *http.Request) {
resp := &versionResponse{} resp := &versionResponse{}
if Context.disableUpdate { if web.conf.disableUpdate {
resp.Disabled = true resp.Disabled = true
_ = aghhttp.WriteJSONResponse(w, r, resp) _ = aghhttp.WriteJSONResponse(w, r, resp)
@ -52,7 +52,7 @@ func handleVersionJSON(w http.ResponseWriter, r *http.Request) {
} }
} }
err = requestVersionInfo(resp, req.Recheck) err = web.requestVersionInfo(resp, req.Recheck)
if err != nil { if err != nil {
// Don't wrap the error, because it's informative enough as is. // Don't wrap the error, because it's informative enough as is.
aghhttp.Error(r, w, http.StatusBadGateway, "%s", err) aghhttp.Error(r, w, http.StatusBadGateway, "%s", err)
@ -73,9 +73,10 @@ func handleVersionJSON(w http.ResponseWriter, r *http.Request) {
// requestVersionInfo sets the VersionInfo field of resp if it can reach the // requestVersionInfo sets the VersionInfo field of resp if it can reach the
// update server. // update server.
func requestVersionInfo(resp *versionResponse, recheck bool) (err error) { func (web *webAPI) requestVersionInfo(resp *versionResponse, recheck bool) (err error) {
updater := web.conf.updater
for i := 0; i != 3; i++ { for i := 0; i != 3; i++ {
resp.VersionInfo, err = Context.updater.VersionInfo(recheck) resp.VersionInfo, err = updater.VersionInfo(recheck)
if err != nil { if err != nil {
var terr temporaryError var terr temporaryError
if errors.As(err, &terr) && terr.Temporary() { if errors.As(err, &terr) && terr.Temporary() {
@ -95,7 +96,7 @@ func requestVersionInfo(resp *versionResponse, recheck bool) (err error) {
} }
if err != nil { if err != nil {
vcu := Context.updater.VersionCheckURL() vcu := updater.VersionCheckURL()
return fmt.Errorf("getting version info from %s: %w", vcu, err) return fmt.Errorf("getting version info from %s: %w", vcu, err)
} }
@ -104,8 +105,9 @@ func requestVersionInfo(resp *versionResponse, recheck bool) (err error) {
} }
// handleUpdate performs an update to the latest available version procedure. // handleUpdate performs an update to the latest available version procedure.
func handleUpdate(w http.ResponseWriter, r *http.Request) { func (web *webAPI) handleUpdate(w http.ResponseWriter, r *http.Request) {
if Context.updater.NewVersion() == "" { updater := web.conf.updater
if updater.NewVersion() == "" {
aghhttp.Error(r, w, http.StatusBadRequest, "/update request isn't allowed now") aghhttp.Error(r, w, http.StatusBadRequest, "/update request isn't allowed now")
return return
@ -122,7 +124,7 @@ func handleUpdate(w http.ResponseWriter, r *http.Request) {
return return
} }
err = Context.updater.Update(false) err = updater.Update(false)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err) aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
@ -137,7 +139,7 @@ func handleUpdate(w http.ResponseWriter, r *http.Request) {
// The background context is used because the underlying functions wrap it // The background context is used because the underlying functions wrap it
// with timeout and shut down the server, which handles current request. It // with timeout and shut down the server, which handles current request. It
// also should be done in a separate goroutine for the same reason. // also should be done in a separate goroutine for the same reason.
go finishUpdate(context.Background(), execPath) go finishUpdate(context.Background(), execPath, web.conf.runningAsService)
} }
// versionResponse is the response for /control/version.json endpoint. // versionResponse is the response for /control/version.json endpoint.
@ -178,7 +180,7 @@ func tlsConfUsesPrivilegedPorts(c *tlsConfigSettings) (ok bool) {
} }
// finishUpdate completes an update procedure. // finishUpdate completes an update procedure.
func finishUpdate(ctx context.Context, execPath string) { func finishUpdate(ctx context.Context, execPath string, runningAsService bool) {
var err error var err error
log.Info("stopping all tasks") log.Info("stopping all tasks")
@ -187,7 +189,7 @@ func finishUpdate(ctx context.Context, execPath string) {
cleanupAlways() cleanupAlways()
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
if Context.runningAsService { if runningAsService {
// NOTE: We can't restart the service via "kardianos/service" // NOTE: We can't restart the service via "kardianos/service"
// package, because it kills the process first we can't start a new // package, because it kills the process first we can't start a new
// instance, because Windows doesn't allow it. // instance, because Windows doesn't allow it.

View File

@ -64,33 +64,24 @@ type homeContext struct {
// configuration files, for example /etc/hosts. // configuration files, for example /etc/hosts.
etcHosts *aghnet.HostsContainer etcHosts *aghnet.HostsContainer
updater *updater.Updater
// mux is our custom http.ServeMux. // mux is our custom http.ServeMux.
mux *http.ServeMux mux *http.ServeMux
// Runtime properties // Runtime properties
// -- // --
configFilename string // Config filename (can be overridden via the command line arguments) configFilename string // Config filename (can be overridden via the command line arguments)
workDir string // Location of our directory, used to protect against CWD being somewhere else workDir string // Location of our directory, used to protect against CWD being somewhere else
pidFileName string // PID file name. Empty if no PID file was created. pidFileName string // PID file name. Empty if no PID file was created.
controlLock sync.Mutex controlLock sync.Mutex
tlsRoots *x509.CertPool // list of root CAs for TLSv1.2 tlsRoots *x509.CertPool // list of root CAs for TLSv1.2
appSignalChannel chan os.Signal // Channel for receiving OS signals by the console app
// tlsCipherIDs are the ID of the cipher suites that AdGuard Home must use. // tlsCipherIDs are the ID of the cipher suites that AdGuard Home must use.
tlsCipherIDs []uint16 tlsCipherIDs []uint16
// disableUpdate, if true, tells AdGuard Home to not check for updates.
disableUpdate bool
// firstRun, if true, tells AdGuard Home to only start the web interface // firstRun, if true, tells AdGuard Home to only start the web interface
// service, and only serve the first-run APIs. // service, and only serve the first-run APIs.
firstRun bool firstRun bool
// runningAsService flag is set to true when options are passed from the service runner
runningAsService bool
} }
// getDataDir returns path to the directory where we store databases and filters // getDataDir returns path to the directory where we store databases and filters
@ -113,11 +104,11 @@ func Main(clientBuildFS fs.FS) {
// package flag. // package flag.
opts := loadCmdLineOpts() opts := loadCmdLineOpts()
Context.appSignalChannel = make(chan os.Signal) signals := make(chan os.Signal, 1)
signal.Notify(Context.appSignalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
go func() { go func() {
for { for {
sig := <-Context.appSignalChannel sig := <-signals
log.Info("Received signal %q", sig) log.Info("Received signal %q", sig)
switch sig { switch sig {
case syscall.SIGHUP: case syscall.SIGHUP:
@ -132,7 +123,7 @@ func Main(clientBuildFS fs.FS) {
}() }()
if opts.serviceControlAction != "" { if opts.serviceControlAction != "" {
handleServiceControlAction(opts, clientBuildFS) handleServiceControlAction(opts, clientBuildFS, signals)
return return
} }
@ -144,61 +135,48 @@ func Main(clientBuildFS fs.FS) {
// setupContext initializes [Context] fields. It also reads and upgrades // setupContext initializes [Context] fields. It also reads and upgrades
// config file if necessary. // config file if necessary.
func setupContext(opts options) (err error) { func setupContext(opts options) (err error) {
setupContextFlags(opts) Context.firstRun = detectFirstRun()
Context.tlsRoots = aghtls.SystemRootCAs() Context.tlsRoots = aghtls.SystemRootCAs()
Context.mux = http.NewServeMux() Context.mux = http.NewServeMux()
if !Context.firstRun { if Context.firstRun {
// Do the upgrade if necessary. log.Info("This is the first time AdGuard Home is launched")
err = upgradeConfig() checkPermissions()
return nil
}
// Do the upgrade if necessary.
err = upgradeConfig()
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
if err = parseConfig(); err != nil {
log.Error("parsing configuration file: %s", err)
os.Exit(1)
}
if opts.checkConfig {
log.Info("configuration file is ok")
os.Exit(0)
}
if !opts.noEtcHosts && config.Clients.Sources.HostsFile {
err = setupHostsContainer()
if err != nil { if err != nil {
// Don't wrap the error, because it's informative enough as is. // Don't wrap the error, because it's informative enough as is.
return err return err
} }
if err = parseConfig(); err != nil {
log.Error("parsing configuration file: %s", err)
os.Exit(1)
}
if opts.checkConfig {
log.Info("configuration file is ok")
os.Exit(0)
}
if !opts.noEtcHosts && config.Clients.Sources.HostsFile {
err = setupHostsContainer()
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
}
} }
return nil return nil
} }
// setupContextFlags sets global flags and prints their status to the log.
func setupContextFlags(opts options) {
Context.firstRun = detectFirstRun()
if Context.firstRun {
log.Info("This is the first time AdGuard Home is launched")
checkPermissions()
}
Context.runningAsService = opts.runningAsService
// Don't print the runningAsService flag, since that has already been done
// in [run].
Context.disableUpdate = opts.disableUpdate || version.Channel() == version.ChannelDevelopment
if Context.disableUpdate {
log.Info("AdGuard Home updates are disabled")
}
}
// logIfUnsupported logs a formatted warning if the error is one of the // logIfUnsupported logs a formatted warning if the error is one of the
// unsupported errors and returns nil. If err is nil, logIfUnsupported returns // unsupported errors and returns nil. If err is nil, logIfUnsupported returns
// nil. Otherwise, it returns err. // nil. Otherwise, it returns err.
@ -303,7 +281,7 @@ func initContextClients() (err error) {
return err return err
} }
//lint:ignore SA1019 Migration is not over. //lint:ignore SA1019 Migration is not over.
config.DHCP.WorkDir = Context.workDir config.DHCP.WorkDir = Context.workDir
config.DHCP.DataDir = Context.getDataDir() config.DHCP.DataDir = Context.getDataDir()
config.DHCP.HTTPRegister = httpRegister config.DHCP.HTTPRegister = httpRegister
@ -318,18 +296,6 @@ func initContextClients() (err error) {
return fmt.Errorf("initing dhcp: %w", err) return fmt.Errorf("initing dhcp: %w", err)
} }
Context.updater = updater.NewUpdater(&updater.Config{
Client: config.DNS.DnsfilterConf.HTTPClient,
Version: version.Version(),
Channel: version.Channel(),
GOARCH: runtime.GOARCH,
GOOS: runtime.GOOS,
GOARM: version.GOARM(),
GOMIPS: version.GOMIPS(),
WorkDir: Context.workDir,
ConfName: config.getConfigFilename(),
})
var arpdb aghnet.ARPDB var arpdb aghnet.ARPDB
if config.Clients.Sources.ARP { if config.Clients.Sources.ARP {
arpdb = aghnet.NewARPDB() arpdb = aghnet.NewARPDB()
@ -493,7 +459,7 @@ func checkPorts() (err error) {
return nil return nil
} }
func initWeb(opts options, clientBuildFS fs.FS) (web *webAPI, err error) { func initWeb(opts options, clientBuildFS fs.FS, upd *updater.Updater) (web *webAPI, err error) {
var clientFS fs.FS var clientFS fs.FS
if opts.localFrontend { if opts.localFrontend {
log.Info("warning: using local frontend files") log.Info("warning: using local frontend files")
@ -506,8 +472,16 @@ func initWeb(opts options, clientBuildFS fs.FS) (web *webAPI, err error) {
} }
} }
webConf := webConfig{ disableUpdate := opts.disableUpdate || version.Channel() == version.ChannelDevelopment
firstRun: Context.firstRun, if disableUpdate {
log.Info("AdGuard Home updates are disabled")
}
webConf := &webConfig{
updater: upd,
clientFS: clientFS,
BindHost: config.HTTPConfig.Address.Addr(), BindHost: config.HTTPConfig.Address.Addr(),
BindPort: int(config.HTTPConfig.Address.Port()), BindPort: int(config.HTTPConfig.Address.Port()),
@ -515,12 +489,13 @@ func initWeb(opts options, clientBuildFS fs.FS) (web *webAPI, err error) {
ReadHeaderTimeout: readHdrTimeout, ReadHeaderTimeout: readHdrTimeout,
WriteTimeout: writeTimeout, WriteTimeout: writeTimeout,
clientFS: clientFS, firstRun: Context.firstRun,
disableUpdate: disableUpdate,
serveHTTP3: config.DNS.ServeHTTP3, runningAsService: opts.runningAsService,
serveHTTP3: config.DNS.ServeHTTP3,
} }
web = newWebAPI(&webConf) web = newWebAPI(webConf)
if web == nil { if web == nil {
return nil, fmt.Errorf("initializing web: %w", err) return nil, fmt.Errorf("initializing web: %w", err)
} }
@ -571,9 +546,21 @@ func run(opts options, clientBuildFS fs.FS) {
err = setupOpts(opts) err = setupOpts(opts)
fatalOnError(err) fatalOnError(err)
upd := updater.NewUpdater(&updater.Config{
Client: config.DNS.DnsfilterConf.HTTPClient,
Version: version.Version(),
Channel: version.Channel(),
GOARCH: runtime.GOARCH,
GOOS: runtime.GOOS,
GOARM: version.GOARM(),
GOMIPS: version.GOMIPS(),
WorkDir: Context.workDir,
ConfName: config.getConfigFilename(),
})
// TODO(e.burkov): This could be made earlier, probably as the option's // TODO(e.burkov): This could be made earlier, probably as the option's
// effect. // effect.
cmdlineUpdate(opts) cmdlineUpdate(opts, upd)
if !Context.firstRun { if !Context.firstRun {
// Save the updated config. // Save the updated config.
@ -602,7 +589,7 @@ func run(opts options, clientBuildFS fs.FS) {
onConfigModified() onConfigModified()
} }
Context.web, err = initWeb(opts, clientBuildFS) Context.web, err = initWeb(opts, clientBuildFS, upd)
fatalOnError(err) fatalOnError(err)
if !Context.firstRun { if !Context.firstRun {
@ -984,7 +971,7 @@ type jsonError struct {
} }
// cmdlineUpdate updates current application and exits. // cmdlineUpdate updates current application and exits.
func cmdlineUpdate(opts options) { func cmdlineUpdate(opts options, upd *updater.Updater) {
if !opts.performUpdate { if !opts.performUpdate {
return return
} }
@ -999,10 +986,9 @@ func cmdlineUpdate(opts options) {
log.Info("cmdline update: performing update") log.Info("cmdline update: performing update")
updater := Context.updater info, err := upd.VersionInfo(true)
info, err := updater.VersionInfo(true)
if err != nil { if err != nil {
vcu := updater.VersionCheckURL() vcu := upd.VersionCheckURL()
log.Error("getting version info from %s: %s", vcu, err) log.Error("getting version info from %s: %s", vcu, err)
os.Exit(1) os.Exit(1)
@ -1014,7 +1000,7 @@ func cmdlineUpdate(opts options) {
os.Exit(0) os.Exit(0)
} }
err = updater.Update(Context.firstRun) err = upd.Update(Context.firstRun)
fatalOnError(err) fatalOnError(err)
err = restartService() err = restartService()

View File

@ -33,9 +33,13 @@ const (
// daemon. // daemon.
type program struct { type program struct {
clientBuildFS fs.FS clientBuildFS fs.FS
signals chan os.Signal
opts options opts options
} }
// type check
var _ service.Interface = (*program)(nil)
// Start implements service.Interface interface for *program. // Start implements service.Interface interface for *program.
func (p *program) Start(_ service.Service) (err error) { func (p *program) Start(_ service.Service) (err error) {
// Start should not block. Do the actual work async. // Start should not block. Do the actual work async.
@ -48,14 +52,14 @@ func (p *program) Start(_ service.Service) (err error) {
} }
// Stop implements service.Interface interface for *program. // Stop implements service.Interface interface for *program.
func (p *program) Stop(_ service.Service) error { func (p *program) Stop(_ service.Service) (err error) {
// Stop should not block. Return with a few seconds. select {
if Context.appSignalChannel == nil { case p.signals <- syscall.SIGINT:
os.Exit(0) // Go on.
default:
// Stop should not block.
} }
Context.appSignalChannel <- syscall.SIGINT
return nil return nil
} }
@ -194,7 +198,7 @@ func restartService() (err error) {
// - run: This is a special command that is not supposed to be used directly // - run: This is a special command that is not supposed to be used directly
// it is specified when we register a service, and it indicates to the app // it is specified when we register a service, and it indicates to the app
// that it is being run as a service/daemon. // that it is being run as a service/daemon.
func handleServiceControlAction(opts options, clientBuildFS fs.FS) { func handleServiceControlAction(opts options, clientBuildFS fs.FS, signals chan os.Signal) {
// Call chooseSystem explicitly to introduce OpenBSD support for service // Call chooseSystem explicitly to introduce OpenBSD support for service
// package. It's a noop for other GOOS values. // package. It's a noop for other GOOS values.
chooseSystem() chooseSystem()
@ -226,7 +230,11 @@ func handleServiceControlAction(opts options, clientBuildFS fs.FS) {
} }
configureService(svcConfig) configureService(svcConfig)
s, err := service.New(&program{clientBuildFS: clientBuildFS, opts: runOpts}, svcConfig) s, err := service.New(&program{
clientBuildFS: clientBuildFS,
signals: signals,
opts: runOpts,
}, svcConfig)
if err != nil { if err != nil {
log.Fatalf("service: initializing service: %s", err) log.Fatalf("service: initializing service: %s", err)
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet" "github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/updater"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
@ -32,6 +33,8 @@ const (
) )
type webConfig struct { type webConfig struct {
updater *updater.Updater
clientFS fs.FS clientFS fs.FS
BindHost netip.Addr BindHost netip.Addr
@ -51,6 +54,13 @@ type webConfig struct {
firstRun bool firstRun bool
// disableUpdate, if true, tells AdGuard Home to not check for updates.
disableUpdate bool
// runningAsService flag is set to true when options are passed from the
// service runner.
runningAsService bool
serveHTTP3 bool serveHTTP3 bool
} }
@ -101,7 +111,7 @@ func newWebAPI(conf *webConfig) (w *webAPI) {
Context.mux.Handle("/install.html", preInstallHandler(clientFS)) Context.mux.Handle("/install.html", preInstallHandler(clientFS))
w.registerInstallHandlers() w.registerInstallHandlers()
} else { } else {
registerControlHandlers() registerControlHandlers(w)
} }
w.httpsServer.cond = sync.NewCond(&w.httpsServer.condLock) w.httpsServer.cond = sync.NewCond(&w.httpsServer.condLock)

View File

@ -189,6 +189,16 @@ run_linter gocognit --over 10\
./scripts/\ ./scripts/\
; ;
# TODO(a.garipov): move these to the group above.
run_linter gocognit --over 20 ./internal/aghnet/ ./internal/querylog/
run_linter gocognit --over 19 ./internal/dnsforward/ ./internal/home/
run_linter gocognit --over 18 ./internal/aghtls/
run_linter gocognit --over 17 ./internal/filtering ./internal/filtering/rewrite/
run_linter gocognit --over 15 ./internal/aghos/ ./internal/dhcpd/
run_linter gocognit --over 14 ./internal/stats/
run_linter gocognit --over 12 ./internal/updater/
run_linter gocognit --over 11 ./internal/aghtest/
run_linter ineffassign ./... run_linter ineffassign ./...
run_linter unparam ./... run_linter unparam ./...