mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2025-12-20 01:11:03 +08:00
Pull request 2506: AGDNS-3334-imp-home
Squashed commit of the following: commit ae110ad162ea7f30854750255a378f3b6484de94 Merge:965d052f93bebde610Author: f.setrakov <f.setrakov@adguard.com> Date: Sat Nov 1 13:05:43 2025 +0300 Merge branch 'master' into AGDNS-3334-imp-home commit965d052f96Author: f.setrakov <f.setrakov@adguard.com> Date: Mon Oct 27 15:58:20 2025 +0300 home: imp docs commite53565e055Merge:3bafcd30c8b8bfb21eAuthor: f.setrakov <f.setrakov@adguard.com> Date: Mon Oct 27 15:50:12 2025 +0300 Merge branch 'master' into AGDNS-3334-imp-home commit3bafcd30cdAuthor: f.setrakov <f.setrakov@adguard.com> Date: Fri Oct 24 16:53:48 2025 +0300 all: imp style commitb762eaa51cAuthor: f.setrakov <f.setrakov@adguard.com> Date: Thu Oct 23 17:08:48 2025 +0300 home: imp docs commitc54a961c2eAuthor: f.setrakov <f.setrakov@adguard.com> Date: Thu Oct 23 11:21:03 2025 +0300 home: fix tls server commit0801b367f8Author: f.setrakov <f.setrakov@adguard.com> Date: Wed Oct 22 18:33:48 2025 +0300 home: imp style commitf148198fe0Author: f.setrakov <f.setrakov@adguard.com> Date: Wed Oct 22 14:35:12 2025 +0300 home: imp allocs commitdbe768ec96Author: f.setrakov <f.setrakov@adguard.com> Date: Wed Oct 22 14:20:38 2025 +0300 home: fix docs, style commit74e88ba044Author: f.setrakov <f.setrakov@adguard.com> Date: Wed Oct 22 14:07:24 2025 +0300 home: imp docs, imp maintain commit6260af1aceAuthor: f.setrakov <f.setrakov@adguard.com> Date: Tue Oct 21 20:00:30 2025 +0300 scripts: changed gocognit value for home pkg commit4a5dfaa618Author: f.setrakov <f.setrakov@adguard.com> Date: Tue Oct 21 19:51:38 2025 +0300 all: imp style commit18f9d9a022Author: f.setrakov <f.setrakov@adguard.com> Date: Mon Oct 20 19:17:36 2025 +0300 home: imp cognit complexity
This commit is contained in:
@@ -374,32 +374,11 @@ func (mw *authMiddlewareDefault) Wrap(h http.Handler) (wrapped http.Handler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
path := r.URL.Path
|
path := r.URL.Path
|
||||||
u, err := mw.userFromRequest(ctx, r)
|
if mw.handleAuthenticatedUser(ctx, w, r, h, path) {
|
||||||
if err != nil {
|
|
||||||
mw.logger.ErrorContext(ctx, "retrieving user from request", slogutil.KeyError, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if u != nil {
|
|
||||||
if path == "/login.html" {
|
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.ServeHTTP(w, r.WithContext(withWebUser(ctx, u)))
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPublicResource(path) {
|
if mw.handlePublicAccess(w, r, h, path) {
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if path == "/" || path == "/index.html" {
|
|
||||||
http.Redirect(w, r, "login.html", http.StatusFound)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,6 +386,58 @@ func (mw *authMiddlewareDefault) Wrap(h http.Handler) (wrapped http.Handler) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleAuthenticatedUser tries to get user from request and processes request
|
||||||
|
// if user was successfully authenticated. Returns true if request was handled.
|
||||||
|
func (mw *authMiddlewareDefault) handleAuthenticatedUser(
|
||||||
|
ctx context.Context,
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
h http.Handler,
|
||||||
|
path string,
|
||||||
|
) (ok bool) {
|
||||||
|
u, err := mw.userFromRequest(ctx, r)
|
||||||
|
if err != nil {
|
||||||
|
mw.logger.ErrorContext(ctx, "retrieving user from request", slogutil.KeyError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if path == "/login.html" {
|
||||||
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
h.ServeHTTP(w, r.WithContext(withWebUser(ctx, u)))
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlePublicAccess handles request if user is trying to access public or root
|
||||||
|
// pages.
|
||||||
|
func (mw *authMiddlewareDefault) handlePublicAccess(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
h http.Handler,
|
||||||
|
path string,
|
||||||
|
) (ok bool) {
|
||||||
|
if isPublicResource(path) {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if path == "/" || path == "/index.html" {
|
||||||
|
http.Redirect(w, r, "login.html", http.StatusFound)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// needsAuthentication returns true if there are stored web users and requests
|
// needsAuthentication returns true if there are stored web users and requests
|
||||||
// should be authenticated first.
|
// should be authenticated first.
|
||||||
func (mw *authMiddlewareDefault) needsAuthentication(ctx context.Context) (ok bool) {
|
func (mw *authMiddlewareDefault) needsAuthentication(ctx context.Context) (ok bool) {
|
||||||
|
|||||||
@@ -228,56 +228,60 @@ func finishUpdate(
|
|||||||
cleanup(ctx)
|
cleanup(ctx)
|
||||||
cleanupAlways()
|
cleanupAlways()
|
||||||
|
|
||||||
var err error
|
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
if runningAsService {
|
finalizeWindowsUpdate(ctx, l, cmdCons, execPath, runningAsService)
|
||||||
// NOTE: We can't restart the service via "kardianos/service"
|
|
||||||
// package, because it kills the process first we can't start a new
|
|
||||||
// instance, because Windows doesn't allow it.
|
|
||||||
//
|
|
||||||
// TODO(a.garipov): Recheck the claim above.
|
|
||||||
var cmd executil.Command
|
|
||||||
cmd, err = cmdCons.New(ctx, &executil.CommandConfig{
|
|
||||||
Path: "cmd",
|
|
||||||
Args: []string{"/c", "net stop AdGuardHome & net start AdGuardHome"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("constructing cmd: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmd.Start(ctx)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("restarting service: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(osutil.ExitCodeSuccess)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.InfoContext(ctx, "restarting", "exec_path", execPath, "args", os.Args[1:])
|
|
||||||
|
|
||||||
var cmd executil.Command
|
|
||||||
cmd, err = cmdCons.New(ctx, &executil.CommandConfig{
|
|
||||||
Path: execPath,
|
|
||||||
Args: os.Args[1:],
|
|
||||||
Stdin: os.Stdin,
|
|
||||||
Stdout: os.Stdout,
|
|
||||||
Stderr: os.Stderr,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("constructing cmd: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmd.Start(ctx)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("restarting: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(osutil.ExitCodeSuccess)
|
os.Exit(osutil.ExitCodeSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
l.InfoContext(ctx, "restarting", "exec_path", execPath, "args", os.Args[1:])
|
l.InfoContext(ctx, "restarting", "exec_path", execPath, "args", os.Args[1:])
|
||||||
err = syscall.Exec(execPath, os.Args, os.Environ())
|
err = syscall.Exec(execPath, os.Args, os.Environ())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("restarting: %w", err))
|
panic(fmt.Errorf("restarting: %w", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// finalizeWindowsUpdate completes an update procedure on windows. l and
|
||||||
|
// cmdCons must not be nil.
|
||||||
|
func finalizeWindowsUpdate(ctx context.Context,
|
||||||
|
l *slog.Logger,
|
||||||
|
cmdCons executil.CommandConstructor,
|
||||||
|
execPath string,
|
||||||
|
runningAsService bool,
|
||||||
|
) {
|
||||||
|
var commandConf *executil.CommandConfig
|
||||||
|
|
||||||
|
if runningAsService {
|
||||||
|
// NOTE: We can't restart the service via "kardianos/service" package,
|
||||||
|
// because it kills the process first we can't start a new instance,
|
||||||
|
// because Windows doesn't allow it.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Recheck the claim above.
|
||||||
|
commandConf = &executil.CommandConfig{
|
||||||
|
Path: "cmd",
|
||||||
|
Args: []string{"/c", "net stop AdGuardHome & net start AdGuardHome"},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
commandConf = &executil.CommandConfig{
|
||||||
|
Path: execPath,
|
||||||
|
Args: os.Args[1:],
|
||||||
|
Stdin: os.Stdin,
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.InfoContext(ctx, "restarting", "exec_path", execPath, "args", os.Args[1:])
|
||||||
|
|
||||||
|
var cmd executil.Command
|
||||||
|
cmd, err := cmdCons.New(ctx, commandConf)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("constructing cmd: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Start(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("restarting: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -695,100 +695,29 @@ func run(
|
|||||||
workDir string,
|
workDir string,
|
||||||
confPath string,
|
confPath string,
|
||||||
) {
|
) {
|
||||||
ls := getLogSettings(ctx, slogLogger, opts, workDir, confPath)
|
initEnvironment(ctx, opts, slogLogger, workDir, confPath)
|
||||||
|
|
||||||
// Configure log level and output.
|
|
||||||
err := configureLogger(ls, workDir)
|
|
||||||
fatalOnError(err)
|
|
||||||
|
|
||||||
// Print the first message after logger is configured.
|
|
||||||
slogLogger.InfoContext(ctx, "starting adguard home", "version", version.Full())
|
|
||||||
slogLogger.DebugContext(ctx, "current working directory", "path", workDir)
|
|
||||||
if opts.runningAsService {
|
|
||||||
slogLogger.InfoContext(ctx, "adguard home is running as a service")
|
|
||||||
}
|
|
||||||
|
|
||||||
aghtls.Init(ctx, slogLogger.With(slogutil.KeyPrefix, "aghtls"))
|
|
||||||
|
|
||||||
isFirstRun := detectFirstRun(ctx, slogLogger, workDir, confPath)
|
isFirstRun := detectFirstRun(ctx, slogLogger, workDir, confPath)
|
||||||
|
|
||||||
err = setupContext(ctx, slogLogger, opts, workDir, confPath, isFirstRun)
|
|
||||||
fatalOnError(err)
|
|
||||||
|
|
||||||
err = configureOS(config)
|
|
||||||
fatalOnError(err)
|
|
||||||
|
|
||||||
// Clients package uses filtering package's static data
|
|
||||||
// (filtering.BlockedSvcKnown()), so we have to initialize filtering static
|
|
||||||
// data first, but also to avoid relying on automatic Go init() function.
|
|
||||||
filtering.InitModule(ctx, slogLogger)
|
|
||||||
|
|
||||||
confModifier := newDefaultConfigModifier(
|
|
||||||
config,
|
|
||||||
slogLogger.With(slogutil.KeyPrefix, "config_modifier"),
|
|
||||||
workDir,
|
|
||||||
confPath,
|
|
||||||
)
|
|
||||||
|
|
||||||
mw := &webMw{}
|
mw := &webMw{}
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
httpReg := aghhttp.NewDefaultRegistrar(mux, mw.wrap)
|
httpReg := aghhttp.NewDefaultRegistrar(mux, mw.wrap)
|
||||||
|
|
||||||
err = initContextClients(ctx, slogLogger, sigHdlr, confModifier, httpReg, workDir)
|
confModifier, tlsMgr := initFiltering(
|
||||||
fatalOnError(err)
|
|
||||||
|
|
||||||
tlsMgrLogger := slogLogger.With(slogutil.KeyPrefix, "tls_manager")
|
|
||||||
|
|
||||||
tlsMgr, err := newTLSManager(ctx, &tlsManagerConfig{
|
|
||||||
logger: tlsMgrLogger,
|
|
||||||
confModifier: confModifier,
|
|
||||||
httpReg: httpReg,
|
|
||||||
tlsSettings: config.TLS,
|
|
||||||
servePlainDNS: config.DNS.ServePlainDNS,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
tlsMgrLogger.ErrorContext(ctx, "initializing", slogutil.KeyError, err)
|
|
||||||
confModifier.Apply(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
confModifier.setTLSManager(tlsMgr)
|
|
||||||
|
|
||||||
err = setupDNSFilteringConf(
|
|
||||||
ctx,
|
ctx,
|
||||||
slogLogger,
|
slogLogger,
|
||||||
config.Filtering,
|
opts,
|
||||||
tlsMgr,
|
isFirstRun,
|
||||||
confModifier,
|
sigHdlr,
|
||||||
httpReg,
|
|
||||||
workDir,
|
workDir,
|
||||||
|
confPath,
|
||||||
|
httpReg,
|
||||||
)
|
)
|
||||||
fatalOnError(err)
|
|
||||||
|
|
||||||
err = setupOpts(opts)
|
upd, isCustomURL := initUpdate(ctx, slogLogger, opts, tlsMgr, isFirstRun, workDir, confPath)
|
||||||
fatalOnError(err)
|
|
||||||
|
|
||||||
execPath, err := os.Executable()
|
|
||||||
fatalOnError(errors.Annotate(err, "getting executable path: %w"))
|
|
||||||
|
|
||||||
updLogger := slogLogger.With(slogutil.KeyPrefix, "updater")
|
|
||||||
upd, isCustomURL := newUpdater(ctx, updLogger, config, workDir, confPath, execPath)
|
|
||||||
|
|
||||||
// TODO(e.burkov): This could be made earlier, probably as the option's
|
|
||||||
// effect.
|
|
||||||
cmdlineUpdate(ctx, updLogger, opts, upd, tlsMgr, isFirstRun)
|
|
||||||
|
|
||||||
if !isFirstRun {
|
|
||||||
// Save the updated config.
|
|
||||||
err = config.write(ctx, slogLogger, nil, nil, workDir, confPath)
|
|
||||||
fatalOnError(err)
|
|
||||||
|
|
||||||
if config.HTTPConfig.Pprof.Enabled {
|
|
||||||
startPprof(slogLogger, config.HTTPConfig.Pprof.Port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dataDirPath := filepath.Join(workDir, dataDir)
|
dataDirPath := filepath.Join(workDir, dataDir)
|
||||||
err = os.MkdirAll(dataDirPath, aghos.DefaultPermDir)
|
err := os.MkdirAll(dataDirPath, aghos.DefaultPermDir)
|
||||||
fatalOnError(errors.Annotate(err, "creating DNS data dir at %s: %w", dataDirPath))
|
fatalOnError(errors.Annotate(err, "creating DNS data dir at %s: %w", dataDirPath))
|
||||||
|
|
||||||
auth, err := initUsers(ctx, slogLogger, workDir, opts.glinetMode)
|
auth, err := initUsers(ctx, slogLogger, workDir, opts.glinetMode)
|
||||||
@@ -825,25 +754,7 @@ func run(
|
|||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
if !isFirstRun {
|
if !isFirstRun {
|
||||||
err = initDNS(ctx, slogLogger, tlsMgr, confModifier, httpReg, statsDir, querylogDir)
|
runDNSServer(ctx, slogLogger, tlsMgr, confModifier, statsDir, querylogDir, httpReg)
|
||||||
fatalOnError(err)
|
|
||||||
|
|
||||||
tlsMgr.start(ctx)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
startErr := startDNSServer()
|
|
||||||
if startErr != nil {
|
|
||||||
closeDNSServer(ctx)
|
|
||||||
fatalOnError(startErr)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if globalContext.dhcpServer != nil {
|
|
||||||
err = globalContext.dhcpServer.Start(ctx)
|
|
||||||
if err != nil {
|
|
||||||
slogLogger.ErrorContext(ctx, "starting dhcp server", slogutil.KeyError, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.noPermCheck {
|
if !opts.noPermCheck {
|
||||||
@@ -856,6 +767,171 @@ func run(
|
|||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runDNSServer initializes and starts DNS and DHCP servers if this is not the
|
||||||
|
// first run. httpReg, slogLogger, tlsMgr and confModifier must not be nil.
|
||||||
|
func runDNSServer(
|
||||||
|
ctx context.Context,
|
||||||
|
slogLogger *slog.Logger,
|
||||||
|
tlsMgr *tlsManager,
|
||||||
|
confModifier *defaultConfigModifier,
|
||||||
|
statsDir string,
|
||||||
|
querylogDir string,
|
||||||
|
httpReg *aghhttp.DefaultRegistrar,
|
||||||
|
) {
|
||||||
|
err := initDNS(ctx, slogLogger, tlsMgr, confModifier, httpReg, statsDir, querylogDir)
|
||||||
|
fatalOnError(err)
|
||||||
|
|
||||||
|
tlsMgr.start(ctx)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
startErr := startDNSServer()
|
||||||
|
if startErr != nil {
|
||||||
|
closeDNSServer(ctx)
|
||||||
|
fatalOnError(startErr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if globalContext.dhcpServer != nil {
|
||||||
|
err = globalContext.dhcpServer.Start(ctx)
|
||||||
|
if err != nil {
|
||||||
|
slogLogger.ErrorContext(ctx, "starting dhcp server", slogutil.KeyError, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initFiltering configures the core filtering and TLS subsystems. Returns a
|
||||||
|
// configuration modifier and the initialized TLS manager. slogLogger, sigHdlr
|
||||||
|
// and httpReg must not be nil.
|
||||||
|
func initFiltering(
|
||||||
|
ctx context.Context,
|
||||||
|
slogLogger *slog.Logger,
|
||||||
|
opts options,
|
||||||
|
isFirstRun bool,
|
||||||
|
sigHdlr *signalHandler,
|
||||||
|
workDir string,
|
||||||
|
confPath string,
|
||||||
|
httpReg *aghhttp.DefaultRegistrar,
|
||||||
|
) (confModifier *defaultConfigModifier, tlsMgr *tlsManager) {
|
||||||
|
err := setupContext(ctx, slogLogger, opts, workDir, confPath, isFirstRun)
|
||||||
|
fatalOnError(err)
|
||||||
|
|
||||||
|
err = configureOS(config)
|
||||||
|
fatalOnError(err)
|
||||||
|
|
||||||
|
// Clients package uses filtering package's static data
|
||||||
|
// (filtering.BlockedSvcKnown()), so we have to initialize filtering static
|
||||||
|
// data first, but also to avoid relying on automatic Go init() function.
|
||||||
|
filtering.InitModule(ctx, slogLogger)
|
||||||
|
|
||||||
|
confModifier = newDefaultConfigModifier(
|
||||||
|
config,
|
||||||
|
slogLogger.With(slogutil.KeyPrefix, "config_modifier"),
|
||||||
|
workDir,
|
||||||
|
confPath,
|
||||||
|
)
|
||||||
|
|
||||||
|
err = initContextClients(ctx, slogLogger, sigHdlr, confModifier, httpReg, workDir)
|
||||||
|
fatalOnError(err)
|
||||||
|
|
||||||
|
tlsMgrLogger := slogLogger.With(slogutil.KeyPrefix, "tls_manager")
|
||||||
|
|
||||||
|
tlsMgr, err = newTLSManager(ctx, &tlsManagerConfig{
|
||||||
|
logger: tlsMgrLogger,
|
||||||
|
confModifier: confModifier,
|
||||||
|
httpReg: httpReg,
|
||||||
|
tlsSettings: config.TLS,
|
||||||
|
servePlainDNS: config.DNS.ServePlainDNS,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
tlsMgrLogger.ErrorContext(ctx, "initializing", slogutil.KeyError, err)
|
||||||
|
confModifier.Apply(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
confModifier.setTLSManager(tlsMgr)
|
||||||
|
|
||||||
|
err = setupDNSFilteringConf(
|
||||||
|
ctx,
|
||||||
|
slogLogger,
|
||||||
|
config.Filtering,
|
||||||
|
tlsMgr,
|
||||||
|
confModifier,
|
||||||
|
httpReg,
|
||||||
|
workDir,
|
||||||
|
)
|
||||||
|
fatalOnError(err)
|
||||||
|
|
||||||
|
err = setupOpts(opts)
|
||||||
|
fatalOnError(err)
|
||||||
|
|
||||||
|
return confModifier, tlsMgr
|
||||||
|
}
|
||||||
|
|
||||||
|
// initUpdate configures and runs update of this application. slogLogger and
|
||||||
|
// tlsMgr must not be nil.
|
||||||
|
func initUpdate(
|
||||||
|
ctx context.Context,
|
||||||
|
slogLogger *slog.Logger,
|
||||||
|
opts options,
|
||||||
|
tlsMgr *tlsManager,
|
||||||
|
isFirstRun bool,
|
||||||
|
workDir string,
|
||||||
|
confPath string,
|
||||||
|
) (upd *updater.Updater, isCustomURL bool) {
|
||||||
|
execPath, err := os.Executable()
|
||||||
|
fatalOnError(errors.Annotate(err, "getting executable path: %w"))
|
||||||
|
|
||||||
|
updLogger := slogLogger.With(slogutil.KeyPrefix, "updater")
|
||||||
|
upd, isCustomURL = newUpdater(
|
||||||
|
ctx,
|
||||||
|
updLogger,
|
||||||
|
config,
|
||||||
|
workDir,
|
||||||
|
confPath,
|
||||||
|
execPath,
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(e.burkov): This could be made earlier, probably as the option's
|
||||||
|
// effect.
|
||||||
|
cmdlineUpdate(ctx, updLogger, opts, upd, tlsMgr, isFirstRun)
|
||||||
|
|
||||||
|
if !isFirstRun {
|
||||||
|
// Save the updated config.
|
||||||
|
err = config.write(ctx, slogLogger, nil, nil, workDir, confPath)
|
||||||
|
fatalOnError(err)
|
||||||
|
|
||||||
|
if config.HTTPConfig.Pprof.Enabled {
|
||||||
|
startPprof(slogLogger, config.HTTPConfig.Pprof.Port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return upd, isCustomURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// initEnvironment inits working environment. opts and slogLogger must not be
|
||||||
|
// nil.
|
||||||
|
func initEnvironment(
|
||||||
|
ctx context.Context,
|
||||||
|
opts options,
|
||||||
|
slogLogger *slog.Logger,
|
||||||
|
workDir,
|
||||||
|
confPath string,
|
||||||
|
) {
|
||||||
|
ls := getLogSettings(ctx, slogLogger, opts, workDir, confPath)
|
||||||
|
|
||||||
|
// Configure log level and output.
|
||||||
|
err := configureLogger(ls, workDir)
|
||||||
|
fatalOnError(err)
|
||||||
|
|
||||||
|
// Print the first message after logger is configured.
|
||||||
|
slogLogger.InfoContext(ctx, "starting adguard home", "version", version.Full())
|
||||||
|
slogLogger.DebugContext(ctx, "current working directory", "path", workDir)
|
||||||
|
if opts.runningAsService {
|
||||||
|
slogLogger.InfoContext(ctx, "adguard home is running as a service")
|
||||||
|
}
|
||||||
|
|
||||||
|
aghtls.Init(ctx, slogLogger.With(slogutil.KeyPrefix, "aghtls"))
|
||||||
|
}
|
||||||
|
|
||||||
// newUpdater creates a new AdGuard Home updater. l and conf must not be nil.
|
// newUpdater creates a new AdGuard Home updater. l and conf must not be nil.
|
||||||
// workDir, confPath, and execPath must not be empty. isCustomURL is true if
|
// workDir, confPath, and execPath must not be empty. isCustomURL is true if
|
||||||
// the user has specified a custom version announcement URL.
|
// the user has specified a custom version announcement URL.
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package home
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"iter"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -384,41 +386,89 @@ func printHelp(exec string) {
|
|||||||
|
|
||||||
// parseCmdOpts parses the command-line arguments into options and effects.
|
// parseCmdOpts parses the command-line arguments into options and effects.
|
||||||
func parseCmdOpts(cmdName string, args []string) (o options, eff effect, err error) {
|
func parseCmdOpts(cmdName string, args []string) (o options, eff effect, err error) {
|
||||||
// Don't use range since the loop changes the loop variable.
|
next, stop := iter.Pull2(slices.All(args))
|
||||||
argsLen := len(args)
|
defer stop()
|
||||||
for i := 0; i < len(args); i++ {
|
|
||||||
arg := args[i]
|
|
||||||
isKnown := false
|
|
||||||
for _, opt := range cmdLineOpts {
|
|
||||||
isKnown = argMatches(opt, arg)
|
|
||||||
if !isKnown {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.updateWithValue != nil {
|
for i, arg, ok := next(); ok; i, arg, ok = next() {
|
||||||
i++
|
o, eff, err = parseArg(cmdName, next, o, eff, arg)
|
||||||
if i >= argsLen {
|
if err != nil {
|
||||||
return o, eff, fmt.Errorf("got %s without argument", arg)
|
return o, eff, fmt.Errorf("parsing arg at index %d: %w", i, err)
|
||||||
}
|
|
||||||
|
|
||||||
o, err = opt.updateWithValue(o, args[i])
|
|
||||||
} else {
|
|
||||||
o, eff, err = updateOptsNoValue(o, eff, opt, cmdName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return o, eff, fmt.Errorf("applying option %s: %w", arg, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isKnown {
|
|
||||||
return o, eff, fmt.Errorf("unknown option %s", arg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return o, eff, err
|
return o, eff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseArg parses command-line argument into options and effects. next and
|
||||||
|
// eff must not be nil.
|
||||||
|
func parseArg(
|
||||||
|
cmdName string,
|
||||||
|
next func() (int, string, bool),
|
||||||
|
o options,
|
||||||
|
eff effect,
|
||||||
|
arg string,
|
||||||
|
) (newOpt options, newEff effect, err error) {
|
||||||
|
opt, found := findMatchingOpt(arg)
|
||||||
|
if !found {
|
||||||
|
return o, eff, fmt.Errorf("unknown option %s", arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.updateWithValue != nil {
|
||||||
|
return applyOptWithValue(opt, next, o, eff, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return applyOptNoValue(opt, cmdName, o, eff, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyOptNoValue applies option with no value. eff must not be
|
||||||
|
// nil.
|
||||||
|
func applyOptNoValue(
|
||||||
|
opt cmdLineOpt,
|
||||||
|
cmdName string,
|
||||||
|
o options,
|
||||||
|
eff effect,
|
||||||
|
arg string,
|
||||||
|
) (newOpt options, newEff effect, err error) {
|
||||||
|
newOpts, newEff, err := updateOptsNoValue(o, eff, opt, cmdName)
|
||||||
|
if err != nil {
|
||||||
|
return o, eff, fmt.Errorf("applying option %s: %w", arg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOpts, newEff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyOptWithValue applies argument with value. next and eff must not
|
||||||
|
// be nil.
|
||||||
|
func applyOptWithValue(
|
||||||
|
opt cmdLineOpt,
|
||||||
|
next func() (int, string, bool),
|
||||||
|
o options,
|
||||||
|
eff effect,
|
||||||
|
arg string,
|
||||||
|
) (newOpt options, newEff effect, err error) {
|
||||||
|
_, val, ok := next()
|
||||||
|
if !ok {
|
||||||
|
return o, eff, fmt.Errorf("got %s without argument", arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
newOpts, err := opt.updateWithValue(o, val)
|
||||||
|
if err != nil {
|
||||||
|
return o, eff, fmt.Errorf("applying option %s: %w", arg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOpts, eff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findMatchingOpt returns cmdLineOpt which matches the given arg. ok indicates
|
||||||
|
// whether the appropriate option was found.
|
||||||
|
func findMatchingOpt(arg string) (opt cmdLineOpt, ok bool) {
|
||||||
|
for _, opt := range cmdLineOpts {
|
||||||
|
if argMatches(opt, arg) {
|
||||||
|
return opt, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmdLineOpt{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// argMatches returns true if arg matches command-line option opt.
|
// argMatches returns true if arg matches command-line option opt.
|
||||||
|
|||||||
@@ -390,16 +390,11 @@ func handleServiceInstallCommand(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleServiceUninstallCommand handles service "uninstall" command.
|
// handleServiceUninstallCommand handles service "uninstall" command. l and s
|
||||||
|
// must not be nil.
|
||||||
func handleServiceUninstallCommand(ctx context.Context, l *slog.Logger, s service.Service) {
|
func handleServiceUninstallCommand(ctx context.Context, l *slog.Logger, s service.Service) {
|
||||||
if aghos.IsOpenWrt() {
|
if aghos.IsOpenWrt() {
|
||||||
// On OpenWrt it is important to run disable command first
|
handleOpenWrtUninstall(ctx, l)
|
||||||
// as it will remove the symlink
|
|
||||||
_, err := runInitdCommand(ctx, "disable")
|
|
||||||
if err != nil {
|
|
||||||
l.ErrorContext(ctx, "running init disable", slogutil.KeyError, err)
|
|
||||||
os.Exit(osutil.ExitCodeFailure)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := svcAction(ctx, l, s, "stop"); err != nil {
|
if err := svcAction(ctx, l, s, "stop"); err != nil {
|
||||||
@@ -408,20 +403,39 @@ func handleServiceUninstallCommand(ctx context.Context, l *slog.Logger, s servic
|
|||||||
|
|
||||||
if err := svcAction(ctx, l, s, "uninstall"); err != nil {
|
if err := svcAction(ctx, l, s, "uninstall"); err != nil {
|
||||||
l.ErrorContext(ctx, "executing action uninstall", slogutil.KeyError, err)
|
l.ErrorContext(ctx, "executing action uninstall", slogutil.KeyError, err)
|
||||||
|
|
||||||
os.Exit(osutil.ExitCodeFailure)
|
os.Exit(osutil.ExitCodeFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
// Remove log files on cleanup and log errors.
|
handleDarwinUninstall(ctx, l)
|
||||||
err := os.Remove(launchdStdoutPath)
|
}
|
||||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
}
|
||||||
l.WarnContext(ctx, "removing stdout file", slogutil.KeyError, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Remove(launchdStderrPath)
|
// handleOpenWrtUninstall handles service "uninstall" command for OpenWrt.
|
||||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
// Exits on error. l must not be nil.
|
||||||
l.WarnContext(ctx, "removing stderr file", slogutil.KeyError, err)
|
func handleOpenWrtUninstall(ctx context.Context, l *slog.Logger) {
|
||||||
}
|
// On OpenWrt it is important to run disable command first as it will remove
|
||||||
|
// the symlink.
|
||||||
|
_, err := runInitdCommand(ctx, "disable")
|
||||||
|
if err != nil {
|
||||||
|
l.ErrorContext(ctx, "running init disable", slogutil.KeyError, err)
|
||||||
|
os.Exit(osutil.ExitCodeFailure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleDarwinUninstall handles service "uninstall" command for Darwin. l
|
||||||
|
// must not be nil.
|
||||||
|
func handleDarwinUninstall(ctx context.Context, l *slog.Logger) {
|
||||||
|
// Remove log files on cleanup and log errors.
|
||||||
|
err := os.Remove(launchdStdoutPath)
|
||||||
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
l.WarnContext(ctx, "removing stdout file", slogutil.KeyError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(launchdStderrPath)
|
||||||
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
l.WarnContext(ctx, "removing stderr file", slogutil.KeyError, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -318,72 +318,92 @@ func (web *webAPI) close(ctx context.Context) {
|
|||||||
web.logger.InfoContext(ctx, "stopped http server")
|
web.logger.InfoContext(ctx, "stopped http server")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tlsServerLoop implements retry logic for http server start.
|
||||||
func (web *webAPI) tlsServerLoop(ctx context.Context) {
|
func (web *webAPI) tlsServerLoop(ctx context.Context) {
|
||||||
defer slogutil.RecoverAndExit(ctx, web.logger, osutil.ExitCodeFailure)
|
defer slogutil.RecoverAndExit(ctx, web.logger, osutil.ExitCodeFailure)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
web.httpsServer.cond.L.Lock()
|
shouldContinue := web.serveTLS(ctx)
|
||||||
if web.httpsServer.inShutdown {
|
if !shouldContinue {
|
||||||
web.httpsServer.cond.L.Unlock()
|
return
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// this mechanism doesn't let us through until all conditions are met
|
|
||||||
for !web.httpsServer.enabled { // sleep until necessary data is supplied
|
|
||||||
web.httpsServer.cond.Wait()
|
|
||||||
if web.httpsServer.inShutdown {
|
|
||||||
web.httpsServer.cond.L.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
web.httpsServer.cond.L.Unlock()
|
|
||||||
|
|
||||||
var portHTTPS uint16
|
|
||||||
func() {
|
|
||||||
config.RLock()
|
|
||||||
defer config.RUnlock()
|
|
||||||
|
|
||||||
portHTTPS = config.TLS.PortHTTPS
|
|
||||||
}()
|
|
||||||
|
|
||||||
addr := netip.AddrPortFrom(web.conf.BindAddr.Addr(), portHTTPS).String()
|
|
||||||
logger := web.baseLogger.With(loggerKeyServer, "https")
|
|
||||||
|
|
||||||
// TODO(a.garipov): Remove other logs like this in other code.
|
|
||||||
logMw := httputil.NewLogMiddleware(logger, slog.LevelDebug)
|
|
||||||
hdlr := logMw.Wrap(withMiddlewares(web.conf.mux, limitRequestBody))
|
|
||||||
|
|
||||||
web.httpsServer.server = &http.Server{
|
|
||||||
Addr: addr,
|
|
||||||
Handler: web.auth.middleware().Wrap(hdlr),
|
|
||||||
TLSConfig: &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{web.httpsServer.cert},
|
|
||||||
RootCAs: web.tlsManager.rootCerts,
|
|
||||||
CipherSuites: web.tlsManager.customCipherIDs,
|
|
||||||
MinVersion: tls.VersionTLS12,
|
|
||||||
},
|
|
||||||
ReadTimeout: web.conf.ReadTimeout,
|
|
||||||
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
|
|
||||||
WriteTimeout: web.conf.WriteTimeout,
|
|
||||||
ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError),
|
|
||||||
}
|
|
||||||
|
|
||||||
printHTTPAddresses(urlutil.SchemeHTTPS, web.tlsManager)
|
|
||||||
|
|
||||||
if web.conf.serveHTTP3 {
|
|
||||||
go web.mustStartHTTP3(ctx, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.InfoContext(ctx, "starting https server")
|
|
||||||
err := web.httpsServer.server.ListenAndServeTLS("", "")
|
|
||||||
if !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
cleanupAlways()
|
|
||||||
panic(fmt.Errorf("https: %w", err))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// serveTLS initializes and starts the HTTPS server. Returns true when next
|
||||||
|
// retry is necessary.
|
||||||
|
func (web *webAPI) serveTLS(ctx context.Context) (next bool) {
|
||||||
|
if !web.waitForTLSReady() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var portHTTPS uint16
|
||||||
|
func() {
|
||||||
|
config.RLock()
|
||||||
|
defer config.RUnlock()
|
||||||
|
|
||||||
|
portHTTPS = config.TLS.PortHTTPS
|
||||||
|
}()
|
||||||
|
|
||||||
|
addr := netip.AddrPortFrom(web.conf.BindAddr.Addr(), portHTTPS).String()
|
||||||
|
logger := web.baseLogger.With(loggerKeyServer, "https")
|
||||||
|
|
||||||
|
// TODO(a.garipov): Remove other logs like this in other code.
|
||||||
|
logMw := httputil.NewLogMiddleware(logger, slog.LevelDebug)
|
||||||
|
hdlr := logMw.Wrap(withMiddlewares(web.conf.mux, limitRequestBody))
|
||||||
|
|
||||||
|
web.httpsServer.server = &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: web.auth.middleware().Wrap(hdlr),
|
||||||
|
TLSConfig: &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{web.httpsServer.cert},
|
||||||
|
RootCAs: web.tlsManager.rootCerts,
|
||||||
|
CipherSuites: web.tlsManager.customCipherIDs,
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
},
|
||||||
|
ReadTimeout: web.conf.ReadTimeout,
|
||||||
|
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
|
||||||
|
WriteTimeout: web.conf.WriteTimeout,
|
||||||
|
ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError),
|
||||||
|
}
|
||||||
|
|
||||||
|
printHTTPAddresses(urlutil.SchemeHTTPS, web.tlsManager)
|
||||||
|
|
||||||
|
if web.conf.serveHTTP3 {
|
||||||
|
go web.mustStartHTTP3(ctx, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.InfoContext(ctx, "starting https server")
|
||||||
|
err := web.httpsServer.server.ListenAndServeTLS("", "")
|
||||||
|
if !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
cleanupAlways()
|
||||||
|
panic(fmt.Errorf("https: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForTLSReady blocks until the HTTPS server is enabled or a shutdown signal
|
||||||
|
// is received. Returns true when server is ready.
|
||||||
|
func (web *webAPI) waitForTLSReady() (ok bool) {
|
||||||
|
web.httpsServer.cond.L.Lock()
|
||||||
|
defer web.httpsServer.cond.L.Unlock()
|
||||||
|
|
||||||
|
if web.httpsServer.inShutdown {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// this mechanism doesn't let us through until all conditions are met
|
||||||
|
for !web.httpsServer.enabled { // sleep until necessary data is supplied
|
||||||
|
web.httpsServer.cond.Wait()
|
||||||
|
if web.httpsServer.inShutdown {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (web *webAPI) mustStartHTTP3(ctx context.Context, address string) {
|
func (web *webAPI) mustStartHTTP3(ctx context.Context, address string) {
|
||||||
defer slogutil.RecoverAndExit(ctx, web.logger, osutil.ExitCodeFailure)
|
defer slogutil.RecoverAndExit(ctx, web.logger, osutil.ExitCodeFailure)
|
||||||
|
|
||||||
|
|||||||
@@ -172,10 +172,6 @@ run_linter gocognit --over='20' \
|
|||||||
./internal/querylog/ \
|
./internal/querylog/ \
|
||||||
;
|
;
|
||||||
|
|
||||||
run_linter gocognit --over='19' \
|
|
||||||
./internal/home/ \
|
|
||||||
;
|
|
||||||
|
|
||||||
run_linter gocognit --over='14' \
|
run_linter gocognit --over='14' \
|
||||||
./internal/dhcpd \
|
./internal/dhcpd \
|
||||||
;
|
;
|
||||||
@@ -195,6 +191,7 @@ run_linter gocognit --over='10' \
|
|||||||
./internal/dhcpsvc \
|
./internal/dhcpsvc \
|
||||||
./internal/dnsforward/ \
|
./internal/dnsforward/ \
|
||||||
./internal/filtering/ \
|
./internal/filtering/ \
|
||||||
|
./internal/home/ \
|
||||||
./internal/ipset \
|
./internal/ipset \
|
||||||
./internal/next/ \
|
./internal/next/ \
|
||||||
./internal/rdns/ \
|
./internal/rdns/ \
|
||||||
|
|||||||
Reference in New Issue
Block a user