mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2025-10-26 11:27:18 +00:00
Pull request 2497: AGDNS-3306-rm-global-conf-filepath
Squashed commit of the following: commit 6d50f74a25c8dcf2627bbb88674cca0ed7b2bbe0 Merge:aff0466ba5c9fef62fAuthor: Stanislav Chzhen <s.chzhen@adguard.com> Date: Thu Oct 23 18:46:46 2025 +0300 Merge branch 'master' into AGDNS-3306-rm-global-conf-filepath commitaff0466ba7Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Tue Oct 21 10:17:59 2025 +0300 home: fix tests commit8e2e1d871fMerge:fe67680f7feef4cd2aAuthor: Stanislav Chzhen <s.chzhen@adguard.com> Date: Fri Oct 17 09:19:13 2025 +0300 Merge branch 'master' into AGDNS-3306-rm-global-conf-filepath commitfe67680f7bAuthor: Stanislav Chzhen <s.chzhen@adguard.com> Date: Fri Oct 17 09:13:37 2025 +0300 home: add todo commit6dc5635a7eAuthor: Stanislav Chzhen <s.chzhen@adguard.com> Date: Wed Oct 15 19:43:37 2025 +0300 home: add test cases commit233263bd5fAuthor: Stanislav Chzhen <s.chzhen@adguard.com> Date: Wed Oct 15 10:22:41 2025 +0300 home: add test commit354e13a60eAuthor: Stanislav Chzhen <s.chzhen@adguard.com> Date: Fri Oct 10 23:07:21 2025 +0300 home: imp logs commit8541861248Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Wed Oct 8 15:36:50 2025 +0300 home: rm global conf filepath
This commit is contained in:
parent
5c9fef62f1
commit
8f1940f759
@ -337,6 +337,8 @@ func TestAuth_ServeHTTP_firstRun(t *testing.T) {
|
||||
mux,
|
||||
agh.EmptyConfigModifier{},
|
||||
httpReg,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
true,
|
||||
)
|
||||
@ -509,6 +511,8 @@ func TestAuth_ServeHTTP_auth(t *testing.T) {
|
||||
baseMux,
|
||||
agh.EmptyConfigModifier{},
|
||||
httpReg,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
)
|
||||
@ -667,6 +671,8 @@ func TestAuth_ServeHTTP_logout(t *testing.T) {
|
||||
baseMux,
|
||||
agh.EmptyConfigModifier{},
|
||||
httpReg,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
)
|
||||
|
||||
@ -24,7 +24,6 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
||||
"github.com/AdguardTeam/dnsproxy/fastip"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
@ -593,25 +592,36 @@ var config = &configuration{
|
||||
Theme: ThemeAuto,
|
||||
}
|
||||
|
||||
// configFilePath returns the absolute path to the symlink-evaluated path to the
|
||||
// current config file.
|
||||
func configFilePath() (confPath string) {
|
||||
confPath, err := filepath.EvalSymlinks(globalContext.confFilePath)
|
||||
// configFilePath returns the absolute, symlink-resolved path to the current
|
||||
// configuration file. l must not be nil.
|
||||
//
|
||||
// TODO(s.chzhen): Fix the bug where the wrong file may be resolved:
|
||||
// [filepath.EvalSymlinks] resolves a relative path against the current working
|
||||
// directory, not workDir. Make the path absolute relative to workDir before
|
||||
// calling EvalSymlinks.
|
||||
func configFilePath(
|
||||
ctx context.Context,
|
||||
l *slog.Logger,
|
||||
workDir string,
|
||||
confPath string,
|
||||
) (resolved string) {
|
||||
resolved, err := filepath.EvalSymlinks(confPath)
|
||||
if err != nil {
|
||||
confPath = globalContext.confFilePath
|
||||
logFunc := log.Error
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
logFunc = log.Debug
|
||||
}
|
||||
l.DebugContext(
|
||||
ctx,
|
||||
"symlink resolve failed; using original path",
|
||||
"path", confPath,
|
||||
slogutil.KeyError, err,
|
||||
)
|
||||
|
||||
logFunc("evaluating config path: %s; using %q", err, confPath)
|
||||
resolved = confPath
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(confPath) {
|
||||
confPath = filepath.Join(globalContext.workDir, confPath)
|
||||
resolved = filepath.Join(workDir, confPath)
|
||||
}
|
||||
|
||||
return confPath
|
||||
return resolved
|
||||
}
|
||||
|
||||
// validateBindHosts returns error if any of binding hosts from configuration is
|
||||
@ -639,17 +649,17 @@ func validateBindHosts(
|
||||
|
||||
// parseConfig loads configuration from the YAML file, upgrading it if
|
||||
// necessary. l must not be nil.
|
||||
func parseConfig(ctx context.Context, l *slog.Logger) (err error) {
|
||||
func parseConfig(ctx context.Context, l *slog.Logger, workDir, confPath string) (err error) {
|
||||
// Do the upgrade if necessary.
|
||||
config.fileData, err = readConfigFile()
|
||||
config.fileData, err = readConfigFile(ctx, l, workDir, confPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migrator := configmigrate.New(&configmigrate.Config{
|
||||
Logger: l.With(slogutil.KeyPrefix, "config_migrator"),
|
||||
WorkingDir: globalContext.workDir,
|
||||
DataDir: globalContext.getDataDir(),
|
||||
WorkingDir: workDir,
|
||||
DataDir: filepath.Join(workDir, dataDir),
|
||||
})
|
||||
|
||||
var upgraded bool
|
||||
@ -662,7 +672,7 @@ func parseConfig(ctx context.Context, l *slog.Logger) (err error) {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
} else if upgraded {
|
||||
confPath := configFilePath()
|
||||
confPath = configFilePath(ctx, l, workDir, confPath)
|
||||
l.DebugContext(ctx, "writing config file after config upgrade", "path", confPath)
|
||||
|
||||
err = maybe.WriteFile(confPath, config.fileData, aghos.DefaultPermFile)
|
||||
@ -808,27 +818,39 @@ func addPorts[T tcpPort | udpPort](uc aghalg.UniqChecker[T], ports ...T) {
|
||||
}
|
||||
}
|
||||
|
||||
// readConfigFile reads configuration file contents.
|
||||
func readConfigFile() (fileData []byte, err error) {
|
||||
// readConfigFile reads configuration file contents. l must not be nil.
|
||||
func readConfigFile(
|
||||
ctx context.Context,
|
||||
l *slog.Logger,
|
||||
workDir string,
|
||||
confPath string,
|
||||
) (fileData []byte, err error) {
|
||||
if len(config.fileData) > 0 {
|
||||
return config.fileData, nil
|
||||
}
|
||||
|
||||
confPath := configFilePath()
|
||||
log.Debug("reading config file %q", confPath)
|
||||
confPath = configFilePath(ctx, l, workDir, confPath)
|
||||
l.DebugContext(ctx, "reading config file", "path", confPath)
|
||||
|
||||
// Do not wrap the error because it's informative enough as is.
|
||||
return os.ReadFile(confPath)
|
||||
}
|
||||
|
||||
// Saves configuration to the YAML file and also saves the user filter contents to a file
|
||||
func (c *configuration) write(tlsMgr *tlsManager, auth *auth) (err error) {
|
||||
// write saves configuration to the YAML file and also saves the user filter
|
||||
// contents to a file. l must not be nil.
|
||||
func (c *configuration) write(
|
||||
ctx context.Context,
|
||||
l *slog.Logger,
|
||||
tlsMgr *tlsManager,
|
||||
auth *auth,
|
||||
workDir string,
|
||||
confPath string,
|
||||
) (err error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if auth != nil {
|
||||
// TODO(s.chzhen): Pass context.
|
||||
config.Users = auth.usersList(context.TODO())
|
||||
config.Users = auth.usersList(ctx)
|
||||
}
|
||||
|
||||
if tlsMgr != nil {
|
||||
@ -883,8 +905,8 @@ func (c *configuration) write(tlsMgr *tlsManager, auth *auth) (err error) {
|
||||
|
||||
config.Clients.Persistent = globalContext.clients.forConfig()
|
||||
|
||||
confPath := configFilePath()
|
||||
log.Debug("writing config file %q", confPath)
|
||||
confPath = configFilePath(ctx, l, workDir, confPath)
|
||||
l.DebugContext(ctx, "writing config file", "path", confPath)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
enc := yaml.NewEncoder(buf)
|
||||
@ -919,10 +941,12 @@ func validateTLSCipherIDs(cipherIDs []string) (err error) {
|
||||
|
||||
// defaultConfigModifier is a default [agh.ConfigModifier] implementation.
|
||||
type defaultConfigModifier struct {
|
||||
auth *auth
|
||||
config *configuration
|
||||
logger *slog.Logger
|
||||
tlsMgr *tlsManager
|
||||
auth *auth
|
||||
config *configuration
|
||||
logger *slog.Logger
|
||||
tlsMgr *tlsManager
|
||||
workDir string
|
||||
confPath string
|
||||
}
|
||||
|
||||
// newDefaultConfigModifier returns the new properly initialized
|
||||
@ -932,10 +956,14 @@ type defaultConfigModifier struct {
|
||||
func newDefaultConfigModifier(
|
||||
conf *configuration,
|
||||
l *slog.Logger,
|
||||
workDir string,
|
||||
confPath string,
|
||||
) (cm *defaultConfigModifier) {
|
||||
return &defaultConfigModifier{
|
||||
config: conf,
|
||||
logger: l,
|
||||
config: conf,
|
||||
logger: l,
|
||||
workDir: workDir,
|
||||
confPath: confPath,
|
||||
}
|
||||
}
|
||||
|
||||
@ -945,7 +973,7 @@ var _ agh.ConfigModifier = (*defaultConfigModifier)(nil)
|
||||
// Apply implements the [agh.ConfigModifier] interface for
|
||||
// *defaultConfigModifier.
|
||||
func (cm *defaultConfigModifier) Apply(ctx context.Context) {
|
||||
err := cm.config.write(cm.tlsMgr, cm.auth)
|
||||
err := cm.config.write(ctx, cm.logger, cm.tlsMgr, cm.auth, cm.workDir, cm.confPath)
|
||||
if err != nil {
|
||||
cm.logger.ErrorContext(ctx, "writing config", slogutil.KeyError, err)
|
||||
}
|
||||
|
||||
91
internal/home/config_internal_test.go
Normal file
91
internal/home/config_internal_test.go
Normal file
@ -0,0 +1,91 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfigFilePath(t *testing.T) {
|
||||
const (
|
||||
realConf = "real.yaml"
|
||||
linkConf = "conf.link"
|
||||
missingConf = "missing.yaml"
|
||||
brokenLinkConf = "broken.link"
|
||||
)
|
||||
|
||||
workDir := t.TempDir()
|
||||
targetPath := filepath.Join(workDir, realConf)
|
||||
linkPath := filepath.Join(workDir, linkConf)
|
||||
missingPath := filepath.Join(workDir, missingConf)
|
||||
brokenLinkPath := filepath.Join(workDir, brokenLinkConf)
|
||||
|
||||
err := os.Symlink(targetPath, linkPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = os.Symlink(missingPath, brokenLinkPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
f, err := os.Create(targetPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
testutil.CleanupAndRequireSuccess(t, f.Close)
|
||||
|
||||
otherDir := t.TempDir()
|
||||
|
||||
// Canonicalize the absolute path (e.g., on macOS: /var -> /private/var; on
|
||||
// Windows: RUNNER~1 -> runneradmin).
|
||||
wantAbs := targetPath
|
||||
p, err := filepath.EvalSymlinks(wantAbs)
|
||||
if err == nil {
|
||||
wantAbs = p
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
chDir string
|
||||
confPath string
|
||||
want string
|
||||
}{{
|
||||
name: "absolute_path",
|
||||
chDir: "",
|
||||
confPath: targetPath,
|
||||
want: wantAbs,
|
||||
}, {
|
||||
name: "relative_path",
|
||||
chDir: "",
|
||||
confPath: realConf,
|
||||
want: targetPath,
|
||||
}, {
|
||||
name: "symlink",
|
||||
chDir: "",
|
||||
confPath: linkConf,
|
||||
want: linkPath,
|
||||
}, {
|
||||
name: "symlink_broken",
|
||||
chDir: "",
|
||||
confPath: brokenLinkConf,
|
||||
want: brokenLinkPath,
|
||||
}, {
|
||||
name: "symlink_before_join",
|
||||
chDir: otherDir,
|
||||
confPath: linkConf,
|
||||
want: linkPath,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.chDir != "" {
|
||||
t.Chdir(tc.chDir)
|
||||
}
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
got := configFilePath(ctx, testLogger, workDir, tc.confPath)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -489,7 +489,7 @@ func (web *webAPI) finalizeInstall(
|
||||
config.DNS.Port = req.DNS.Port
|
||||
config.Filtering.Logger = web.baseLogger.With(slogutil.KeyPrefix, "filtering")
|
||||
config.Filtering.SafeFSPatterns = []string{
|
||||
filepath.Join(globalContext.workDir, userFilterDataDir, "*"),
|
||||
filepath.Join(web.conf.workDir, userFilterDataDir, "*"),
|
||||
}
|
||||
config.HTTPConfig.Address = netip.AddrPortFrom(req.Web.IP, req.Web.Port)
|
||||
|
||||
@ -507,14 +507,28 @@ func (web *webAPI) finalizeInstall(
|
||||
// moment we'll allow setting up TLS in the initial configuration or the
|
||||
// configuration itself will use HTTPS protocol, because the underlying
|
||||
// functions potentially restart the HTTPS server.
|
||||
err = startMods(ctx, web.baseLogger, web.tlsManager, web.confModifier, web.httpReg)
|
||||
err = startMods(
|
||||
ctx,
|
||||
web.baseLogger,
|
||||
web.tlsManager,
|
||||
web.confModifier,
|
||||
web.httpReg,
|
||||
web.conf.workDir,
|
||||
)
|
||||
if err != nil {
|
||||
aghhttp.ErrorAndLog(ctx, l, r, w, http.StatusInternalServerError, "%s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = config.write(web.tlsManager, web.auth)
|
||||
err = config.write(
|
||||
ctx,
|
||||
web.logger,
|
||||
web.tlsManager,
|
||||
web.auth,
|
||||
web.conf.workDir,
|
||||
web.conf.confPath,
|
||||
)
|
||||
if err != nil {
|
||||
aghhttp.ErrorAndLog(
|
||||
ctx,
|
||||
@ -597,8 +611,9 @@ func startMods(
|
||||
tlsMgr *tlsManager,
|
||||
confModifier agh.ConfigModifier,
|
||||
httpReg aghhttp.Registrar,
|
||||
workDir string,
|
||||
) (err error) {
|
||||
statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&globalContext, config)
|
||||
statsDir, querylogDir, err := checkStatsAndQuerylogDirs(config, workDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -177,7 +177,7 @@ func initDNSServer(
|
||||
// failed to prepare as is. See TODO on [dnsforward.PrivateRDNSError].
|
||||
err = globalContext.dnsServer.Prepare(ctx, dnsConf)
|
||||
if privRDNSErr := (&dnsforward.PrivateRDNSError{}); errors.As(err, &privRDNSErr) {
|
||||
log.Info("WARNING: %s; trying to disable private RDNS resolution", err)
|
||||
l.WarnContext(ctx, "private rdns resolution failed; disabling", slogutil.KeyError, err)
|
||||
|
||||
dnsConf.UsePrivateRDNS = false
|
||||
err = globalContext.dnsServer.Prepare(ctx, dnsConf)
|
||||
@ -521,10 +521,10 @@ func closeDNSServer(ctx context.Context) {
|
||||
// checkStatsAndQuerylogDirs checks and returns directory paths to store
|
||||
// statistics and query log.
|
||||
func checkStatsAndQuerylogDirs(
|
||||
ctx *homeContext,
|
||||
conf *configuration,
|
||||
workDir string,
|
||||
) (statsDir, querylogDir string, err error) {
|
||||
baseDir := ctx.getDataDir()
|
||||
baseDir := filepath.Join(workDir, dataDir)
|
||||
|
||||
statsDir = conf.Stats.DirPath
|
||||
if statsDir == "" {
|
||||
|
||||
@ -69,20 +69,10 @@ type homeContext struct {
|
||||
// Runtime properties
|
||||
// --
|
||||
|
||||
// confFilePath is the configuration file path as set by default or from the
|
||||
// command-line options.
|
||||
confFilePath string
|
||||
|
||||
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.
|
||||
controlLock sync.Mutex
|
||||
}
|
||||
|
||||
// getDataDir returns path to the directory where we store databases and filters
|
||||
func (c *homeContext) getDataDir() string {
|
||||
return filepath.Join(c.workDir, dataDir)
|
||||
}
|
||||
|
||||
// globalContext is a global context object.
|
||||
//
|
||||
// TODO(a.garipov): Refactor.
|
||||
@ -90,6 +80,8 @@ var globalContext homeContext
|
||||
|
||||
// Main is the entry point
|
||||
func Main(clientBuildFS fs.FS) {
|
||||
ctx := context.Background()
|
||||
|
||||
initCmdLineOpts()
|
||||
|
||||
// The configuration file path can be overridden, but other command-line
|
||||
@ -100,7 +92,18 @@ func Main(clientBuildFS fs.FS) {
|
||||
// package flag.
|
||||
opts := loadCmdLineOpts()
|
||||
|
||||
ls := getLogSettings(opts)
|
||||
// TODO(s.chzhen): Construct logger from command-line options.
|
||||
l := slog.Default()
|
||||
workDir, err := initWorkingDir(opts)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "failed to init working directory", slogutil.KeyError, err)
|
||||
|
||||
os.Exit(osutil.ExitCodeFailure)
|
||||
}
|
||||
|
||||
confPath := initConfigFilename(ctx, l, opts, workDir)
|
||||
|
||||
ls := getLogSettings(ctx, l, opts, workDir, confPath)
|
||||
|
||||
// TODO(a.garipov): Use slog everywhere.
|
||||
baseLogger := newSlogLogger(ls)
|
||||
@ -110,7 +113,6 @@ func Main(clientBuildFS fs.FS) {
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
||||
|
||||
ctx := context.Background()
|
||||
sigHdlrLogger := baseLogger.With(slogutil.KeyPrefix, "signalhdlr")
|
||||
sigHdlr := newSignalHandler(sigHdlrLogger, signals, func(ctx context.Context) {
|
||||
cleanup(ctx)
|
||||
@ -131,13 +133,15 @@ func Main(clientBuildFS fs.FS) {
|
||||
signals,
|
||||
done,
|
||||
sigHdlr,
|
||||
workDir,
|
||||
confPath,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// run the protection
|
||||
run(ctx, baseLogger, opts, clientBuildFS, done, sigHdlr)
|
||||
run(ctx, baseLogger, opts, clientBuildFS, done, sigHdlr, workDir, confPath)
|
||||
}
|
||||
|
||||
// setupContext initializes [globalContext] fields. It also reads and upgrades
|
||||
@ -146,6 +150,8 @@ func setupContext(
|
||||
ctx context.Context,
|
||||
baseLogger *slog.Logger,
|
||||
opts options,
|
||||
workDir string,
|
||||
confPath string,
|
||||
isFirstRun bool,
|
||||
) (err error) {
|
||||
if !opts.noEtcHosts {
|
||||
@ -157,22 +163,22 @@ func setupContext(
|
||||
}
|
||||
|
||||
if isFirstRun {
|
||||
log.Info("This is the first time AdGuard Home is launched")
|
||||
baseLogger.InfoContext(ctx, "this is the first time adguard home has been launched")
|
||||
checkNetworkPermissions(ctx, baseLogger)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(s.chzhen): Consider adding a key prefix.
|
||||
err = parseConfig(ctx, baseLogger)
|
||||
err = parseConfig(ctx, baseLogger, workDir, confPath)
|
||||
if err != nil {
|
||||
log.Error("parsing configuration file: %s", err)
|
||||
baseLogger.ErrorContext(ctx, "failed to parse configuration file", slogutil.KeyError, err)
|
||||
|
||||
os.Exit(osutil.ExitCodeFailure)
|
||||
}
|
||||
|
||||
if opts.checkConfig {
|
||||
log.Info("configuration file is ok")
|
||||
baseLogger.InfoContext(ctx, "configuration file is ok")
|
||||
|
||||
os.Exit(osutil.ExitCodeSuccess)
|
||||
}
|
||||
@ -299,10 +305,11 @@ func initContextClients(
|
||||
sigHdlr *signalHandler,
|
||||
confModifier agh.ConfigModifier,
|
||||
httpReg aghhttp.Registrar,
|
||||
workDir string,
|
||||
) (err error) {
|
||||
//lint:ignore SA1019 Migration is not over.
|
||||
config.DHCP.WorkDir = globalContext.workDir
|
||||
config.DHCP.DataDir = globalContext.getDataDir()
|
||||
config.DHCP.WorkDir = workDir
|
||||
config.DHCP.DataDir = filepath.Join(workDir, dataDir)
|
||||
config.DHCP.HTTPReg = httpReg
|
||||
config.DHCP.CommandConstructor = executil.SystemCommandConstructor{}
|
||||
config.DHCP.Logger = logger.With(slogutil.KeyPrefix, "dhcpd")
|
||||
@ -385,6 +392,7 @@ func setupDNSFilteringConf(
|
||||
tlsMgr *tlsManager,
|
||||
confModifier agh.ConfigModifier,
|
||||
httpReg aghhttp.Registrar,
|
||||
workDir string,
|
||||
) (err error) {
|
||||
const (
|
||||
dnsTimeout = 3 * time.Second
|
||||
@ -408,7 +416,7 @@ func setupDNSFilteringConf(
|
||||
|
||||
conf.ConfModifier = confModifier
|
||||
conf.HTTPReg = httpReg
|
||||
conf.DataDir = globalContext.getDataDir()
|
||||
conf.DataDir = filepath.Join(workDir, dataDir)
|
||||
conf.Filters = slices.Clone(config.Filters)
|
||||
conf.WhitelistFilters = slices.Clone(config.WhitelistFilters)
|
||||
conf.UserRules = slices.Clone(config.UserRules)
|
||||
@ -563,6 +571,8 @@ func isUpdateEnabled(
|
||||
}
|
||||
|
||||
// initWeb initializes the web module. All arguments must not be nil.
|
||||
//
|
||||
// TODO(s.chzhen): Use a configuration structure.
|
||||
func initWeb(
|
||||
ctx context.Context,
|
||||
opts options,
|
||||
@ -574,6 +584,8 @@ func initWeb(
|
||||
mux *http.ServeMux,
|
||||
confModifier agh.ConfigModifier,
|
||||
httpReg aghhttp.Registrar,
|
||||
workDir string,
|
||||
confPath string,
|
||||
isCustomUpdURL bool,
|
||||
isFirstRun bool,
|
||||
) (web *webAPI, err error) {
|
||||
@ -610,6 +622,9 @@ func initWeb(
|
||||
|
||||
BindAddr: config.HTTPConfig.Address,
|
||||
|
||||
workDir: workDir,
|
||||
confPath: confPath,
|
||||
|
||||
ReadTimeout: readTimeout,
|
||||
ReadHeaderTimeout: readHdrTimeout,
|
||||
WriteTimeout: writeTimeout,
|
||||
@ -677,32 +692,27 @@ func run(
|
||||
clientBuildFS fs.FS,
|
||||
done chan struct{},
|
||||
sigHdlr *signalHandler,
|
||||
workDir string,
|
||||
confPath string,
|
||||
) {
|
||||
// Configure working dir.
|
||||
err := initWorkingDir(opts)
|
||||
fatalOnError(err)
|
||||
|
||||
// Configure config filename.
|
||||
initConfigFilename(opts)
|
||||
|
||||
ls := getLogSettings(opts)
|
||||
ls := getLogSettings(ctx, slogLogger, opts, workDir, confPath)
|
||||
|
||||
// Configure log level and output.
|
||||
err = configureLogger(ls)
|
||||
err := configureLogger(ls, workDir)
|
||||
fatalOnError(err)
|
||||
|
||||
// Print the first message after logger is configured.
|
||||
log.Info("%s", version.Full())
|
||||
log.Debug("current working directory is %s", globalContext.workDir)
|
||||
slogLogger.InfoContext(ctx, "starting adguard home", "version", version.Full())
|
||||
slogLogger.DebugContext(ctx, "current working directory", "path", workDir)
|
||||
if opts.runningAsService {
|
||||
log.Info("AdGuard Home is running as a service")
|
||||
slogLogger.InfoContext(ctx, "adguard home is running as a service")
|
||||
}
|
||||
|
||||
aghtls.Init(ctx, slogLogger.With(slogutil.KeyPrefix, "aghtls"))
|
||||
|
||||
isFirstRun := detectFirstRun()
|
||||
isFirstRun := detectFirstRun(ctx, slogLogger, workDir, confPath)
|
||||
|
||||
err = setupContext(ctx, slogLogger, opts, isFirstRun)
|
||||
err = setupContext(ctx, slogLogger, opts, workDir, confPath, isFirstRun)
|
||||
fatalOnError(err)
|
||||
|
||||
err = configureOS(config)
|
||||
@ -716,13 +726,15 @@ func run(
|
||||
confModifier := newDefaultConfigModifier(
|
||||
config,
|
||||
slogLogger.With(slogutil.KeyPrefix, "config_modifier"),
|
||||
workDir,
|
||||
confPath,
|
||||
)
|
||||
|
||||
mw := &webMw{}
|
||||
mux := http.NewServeMux()
|
||||
httpReg := aghhttp.NewDefaultRegistrar(mux, mw.wrap)
|
||||
|
||||
err = initContextClients(ctx, slogLogger, sigHdlr, confModifier, httpReg)
|
||||
err = initContextClients(ctx, slogLogger, sigHdlr, confModifier, httpReg, workDir)
|
||||
fatalOnError(err)
|
||||
|
||||
tlsMgrLogger := slogLogger.With(slogutil.KeyPrefix, "tls_manager")
|
||||
@ -748,6 +760,7 @@ func run(
|
||||
tlsMgr,
|
||||
confModifier,
|
||||
httpReg,
|
||||
workDir,
|
||||
)
|
||||
fatalOnError(err)
|
||||
|
||||
@ -757,17 +770,8 @@ func run(
|
||||
execPath, err := os.Executable()
|
||||
fatalOnError(errors.Annotate(err, "getting executable path: %w"))
|
||||
|
||||
confPath := configFilePath()
|
||||
|
||||
updLogger := slogLogger.With(slogutil.KeyPrefix, "updater")
|
||||
upd, isCustomURL := newUpdater(
|
||||
ctx,
|
||||
updLogger,
|
||||
config,
|
||||
globalContext.workDir,
|
||||
confPath,
|
||||
execPath,
|
||||
)
|
||||
upd, isCustomURL := newUpdater(ctx, updLogger, config, workDir, confPath, execPath)
|
||||
|
||||
// TODO(e.burkov): This could be made earlier, probably as the option's
|
||||
// effect.
|
||||
@ -775,7 +779,7 @@ func run(
|
||||
|
||||
if !isFirstRun {
|
||||
// Save the updated config.
|
||||
err = config.write(nil, nil)
|
||||
err = config.write(ctx, slogLogger, nil, nil, workDir, confPath)
|
||||
fatalOnError(err)
|
||||
|
||||
if config.HTTPConfig.Pprof.Enabled {
|
||||
@ -783,11 +787,11 @@ func run(
|
||||
}
|
||||
}
|
||||
|
||||
dataDir := globalContext.getDataDir()
|
||||
err = os.MkdirAll(dataDir, aghos.DefaultPermDir)
|
||||
fatalOnError(errors.Annotate(err, "creating DNS data dir at %s: %w", dataDir))
|
||||
dataDirPath := filepath.Join(workDir, dataDir)
|
||||
err = os.MkdirAll(dataDirPath, aghos.DefaultPermDir)
|
||||
fatalOnError(errors.Annotate(err, "creating DNS data dir at %s: %w", dataDirPath))
|
||||
|
||||
auth, err := initUsers(ctx, slogLogger, opts.glinetMode)
|
||||
auth, err := initUsers(ctx, slogLogger, workDir, opts.glinetMode)
|
||||
fatalOnError(err)
|
||||
|
||||
confModifier.setAuth(auth)
|
||||
@ -803,6 +807,8 @@ func run(
|
||||
mux,
|
||||
confModifier,
|
||||
httpReg,
|
||||
workDir,
|
||||
confPath,
|
||||
isCustomURL,
|
||||
isFirstRun,
|
||||
)
|
||||
@ -815,7 +821,7 @@ func run(
|
||||
tlsMgr.setWebAPI(web)
|
||||
sigHdlr.addTLSManager(tlsMgr)
|
||||
|
||||
statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&globalContext, config)
|
||||
statsDir, querylogDir, err := checkStatsAndQuerylogDirs(config, workDir)
|
||||
fatalOnError(err)
|
||||
|
||||
if !isFirstRun {
|
||||
@ -835,21 +841,13 @@ func run(
|
||||
if globalContext.dhcpServer != nil {
|
||||
err = globalContext.dhcpServer.Start(ctx)
|
||||
if err != nil {
|
||||
log.Error("starting dhcp server: %s", err)
|
||||
slogLogger.ErrorContext(ctx, "starting dhcp server", slogutil.KeyError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.noPermCheck {
|
||||
checkPermissions(
|
||||
ctx,
|
||||
slogLogger,
|
||||
globalContext.workDir,
|
||||
confPath,
|
||||
dataDir,
|
||||
statsDir,
|
||||
querylogDir,
|
||||
)
|
||||
checkPermissions(ctx, slogLogger, workDir, confPath, dataDirPath, statsDir, querylogDir)
|
||||
}
|
||||
|
||||
web.start(ctx)
|
||||
@ -919,17 +917,17 @@ func checkPermissions(
|
||||
baseLogger *slog.Logger,
|
||||
workDir string,
|
||||
confPath string,
|
||||
dataDir string,
|
||||
dataDirPath string,
|
||||
statsDir string,
|
||||
querylogDir string,
|
||||
) {
|
||||
l := baseLogger.With(slogutil.KeyPrefix, "permcheck")
|
||||
|
||||
if permcheck.NeedsMigration(ctx, l, workDir, confPath) {
|
||||
permcheck.Migrate(ctx, l, workDir, dataDir, statsDir, querylogDir, confPath)
|
||||
permcheck.Migrate(ctx, l, workDir, dataDirPath, statsDir, querylogDir, confPath)
|
||||
}
|
||||
|
||||
permcheck.Check(ctx, l, workDir, dataDir, statsDir, querylogDir, confPath)
|
||||
permcheck.Check(ctx, l, workDir, dataDirPath, statsDir, querylogDir, confPath)
|
||||
}
|
||||
|
||||
// initUsers initializes authentication module and clears the [config.Users]
|
||||
@ -937,6 +935,7 @@ func checkPermissions(
|
||||
func initUsers(
|
||||
ctx context.Context,
|
||||
baseLogger *slog.Logger,
|
||||
workDir string,
|
||||
isGLiNet bool,
|
||||
) (auth *auth, err error) {
|
||||
var rateLimiter loginRateLimiter
|
||||
@ -948,11 +947,12 @@ func initUsers(
|
||||
rateLimiter = emptyRateLimiter{}
|
||||
}
|
||||
|
||||
dataDirPath := filepath.Join(workDir, dataDir)
|
||||
auth, err = newAuth(ctx, &authConfig{
|
||||
baseLogger: baseLogger,
|
||||
rateLimiter: rateLimiter,
|
||||
trustedProxies: netutil.SliceSubnetSet(netutil.UnembedPrefixes(config.DNS.TrustedProxies)),
|
||||
dbFilename: filepath.Join(globalContext.getDataDir(), sessionsDBName),
|
||||
dbFilename: filepath.Join(dataDirPath, sessionsDBName),
|
||||
users: config.Users,
|
||||
sessionTTL: time.Duration(config.HTTPConfig.SessionTTL),
|
||||
isGLiNet: isGLiNet,
|
||||
@ -1030,47 +1030,50 @@ func writePIDFile(fn string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// initConfigFilename sets up context config file path. This file path can be
|
||||
// overridden by command-line arguments, or is set to default. Must only be
|
||||
// called after initializing the workDir with initWorkingDir.
|
||||
func initConfigFilename(opts options) {
|
||||
confPath := opts.confFilename
|
||||
if confPath == "" {
|
||||
globalContext.confFilePath = filepath.Join(globalContext.workDir, "AdGuardHome.yaml")
|
||||
// initConfigFilename returns the configuration file path. If a path is
|
||||
// provided via command-line argument, it is used; otherwise a default within
|
||||
// workDir is returned. l must not be nil.
|
||||
func initConfigFilename(
|
||||
ctx context.Context,
|
||||
l *slog.Logger,
|
||||
opts options,
|
||||
workDir string,
|
||||
) (confPath string) {
|
||||
confPath = opts.confFilename
|
||||
if confPath != "" {
|
||||
l.DebugContext(ctx, "config path overridden from cmdline", "path", confPath)
|
||||
|
||||
return
|
||||
return confPath
|
||||
}
|
||||
|
||||
log.Debug("config path overridden to %q from cmdline", confPath)
|
||||
confPath = filepath.Join(workDir, "AdGuardHome.yaml")
|
||||
|
||||
globalContext.confFilePath = confPath
|
||||
return confPath
|
||||
}
|
||||
|
||||
// initWorkingDir initializes the workDir. If no command-line arguments are
|
||||
// specified, the directory with the binary file is used.
|
||||
func initWorkingDir(opts options) (err error) {
|
||||
execPath, err := os.Executable()
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
// initWorkingDir returns the working directory path. If no command-line
|
||||
// argument is provided, it uses the executable's directory.
|
||||
func initWorkingDir(opts options) (workDir string, err error) {
|
||||
if opts.workDir != "" {
|
||||
// If there is a custom config file, use it's directory as our working dir
|
||||
globalContext.workDir = opts.workDir
|
||||
workDir = opts.workDir
|
||||
} else {
|
||||
globalContext.workDir = filepath.Dir(execPath)
|
||||
var execPath string
|
||||
execPath, err = os.Executable()
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return "", err
|
||||
}
|
||||
|
||||
workDir = filepath.Dir(execPath)
|
||||
}
|
||||
|
||||
workDir, err := filepath.EvalSymlinks(globalContext.workDir)
|
||||
workDir, err = filepath.EvalSymlinks(workDir)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
globalContext.workDir = workDir
|
||||
|
||||
return nil
|
||||
return workDir, nil
|
||||
}
|
||||
|
||||
// cleanup stops and resets all the modules.
|
||||
@ -1194,11 +1197,11 @@ func printHTTPAddresses(proto string, tlsMgr *tlsManager) {
|
||||
}
|
||||
}
|
||||
|
||||
// detectFirstRun returns true if this is the first run of AdGuard Home.
|
||||
func detectFirstRun() (ok bool) {
|
||||
confPath := globalContext.confFilePath
|
||||
// detectFirstRun returns true if this is the first run of AdGuard Home. l must
|
||||
// not be nil.
|
||||
func detectFirstRun(ctx context.Context, l *slog.Logger, workDir, confPath string) (ok bool) {
|
||||
if !filepath.IsAbs(confPath) {
|
||||
confPath = filepath.Join(globalContext.workDir, globalContext.confFilePath)
|
||||
confPath = filepath.Join(workDir, confPath)
|
||||
}
|
||||
|
||||
_, err := os.Stat(confPath)
|
||||
@ -1208,7 +1211,7 @@ func detectFirstRun() (ok bool) {
|
||||
return true
|
||||
}
|
||||
|
||||
log.Error("detecting first run: %s; considering first run", err)
|
||||
l.ErrorContext(ctx, "failed to detect first run; considering first run", slogutil.KeyError, err)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package home
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
@ -19,6 +20,7 @@ import (
|
||||
const configSyslog = "syslog"
|
||||
|
||||
// newSlogLogger returns new [*slog.Logger] configured with the given settings.
|
||||
// ls must not be nil.
|
||||
func newSlogLogger(ls *logSettings) (l *slog.Logger) {
|
||||
if !ls.Enabled {
|
||||
return slogutil.NewDiscardLogger()
|
||||
@ -36,8 +38,8 @@ func newSlogLogger(ls *logSettings) (l *slog.Logger) {
|
||||
})
|
||||
}
|
||||
|
||||
// configureLogger configures logger level and output.
|
||||
func configureLogger(ls *logSettings) (err error) {
|
||||
// configureLogger configures logger level and output. ls must not be nil.
|
||||
func configureLogger(ls *logSettings, workDir string) (err error) {
|
||||
// Configure logger level.
|
||||
if !ls.Enabled {
|
||||
log.SetLevel(log.OFF)
|
||||
@ -66,7 +68,7 @@ func configureLogger(ls *logSettings) (err error) {
|
||||
|
||||
logFilePath := ls.File
|
||||
if !filepath.IsAbs(logFilePath) {
|
||||
logFilePath = filepath.Join(globalContext.workDir, logFilePath)
|
||||
logFilePath = filepath.Join(workDir, logFilePath)
|
||||
}
|
||||
|
||||
log.SetOutput(&lumberjack.Logger{
|
||||
@ -82,10 +84,17 @@ func configureLogger(ls *logSettings) (err error) {
|
||||
}
|
||||
|
||||
// getLogSettings returns a log settings object properly initialized from opts.
|
||||
func getLogSettings(opts options) (ls *logSettings) {
|
||||
// l must not be nil.
|
||||
func getLogSettings(
|
||||
ctx context.Context,
|
||||
l *slog.Logger,
|
||||
opts options,
|
||||
workDir string,
|
||||
confPath string,
|
||||
) (ls *logSettings) {
|
||||
configLogSettings := config.Log
|
||||
|
||||
ls = readLogSettings()
|
||||
ls = readLogSettings(ctx, l, workDir, confPath)
|
||||
if ls == nil {
|
||||
// Use default log settings.
|
||||
ls = &configLogSettings
|
||||
@ -109,8 +118,13 @@ func getLogSettings(opts options) (ls *logSettings) {
|
||||
|
||||
// readLogSettings reads logging settings from the config file. We do it in a
|
||||
// separate method in order to configure logger before the actual configuration
|
||||
// is parsed and applied.
|
||||
func readLogSettings() (ls *logSettings) {
|
||||
// is parsed and applied. l must not be nil.
|
||||
func readLogSettings(
|
||||
ctx context.Context,
|
||||
l *slog.Logger,
|
||||
workDir string,
|
||||
confPath string,
|
||||
) (ls *logSettings) {
|
||||
// TODO(s.chzhen): Add a helper function that returns default parameters
|
||||
// for this structure and for the global configuration structure [config].
|
||||
conf := &configuration{
|
||||
@ -120,7 +134,7 @@ func readLogSettings() (ls *logSettings) {
|
||||
},
|
||||
}
|
||||
|
||||
yamlFile, err := readConfigFile()
|
||||
yamlFile, err := readConfigFile(ctx, l, workDir, confPath)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -90,6 +90,8 @@ func TestWeb_HandleGetProfile(t *testing.T) {
|
||||
baseMux,
|
||||
agh.EmptyConfigModifier{},
|
||||
aghhttp.EmptyRegistrar{},
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
)
|
||||
@ -153,6 +155,8 @@ func TestWeb_HandlePutProfile(t *testing.T) {
|
||||
mux,
|
||||
confModifier,
|
||||
httpReg,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
)
|
||||
|
||||
@ -45,6 +45,8 @@ type program struct {
|
||||
baseLogger *slog.Logger
|
||||
logger *slog.Logger
|
||||
sigHdlr *signalHandler
|
||||
workDir string
|
||||
confPath string
|
||||
}
|
||||
|
||||
// type check
|
||||
@ -56,7 +58,7 @@ func (p *program) Start(_ service.Service) (err error) {
|
||||
args := p.opts
|
||||
args.runningAsService = true
|
||||
|
||||
go run(p.ctx, p.baseLogger, args, p.clientBuildFS, p.done, p.sigHdlr)
|
||||
go run(p.ctx, p.baseLogger, args, p.clientBuildFS, p.done, p.sigHdlr, p.workDir, p.confPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -218,6 +220,8 @@ func handleServiceControlAction(
|
||||
signals chan os.Signal,
|
||||
done chan struct{},
|
||||
sigHdlr *signalHandler,
|
||||
workDir string,
|
||||
confPath string,
|
||||
) {
|
||||
// Call chooseSystem explicitly to introduce OpenBSD support for service
|
||||
// package. It's a noop for other GOOS values.
|
||||
@ -263,13 +267,15 @@ func handleServiceControlAction(
|
||||
baseLogger: l,
|
||||
logger: l.With(slogutil.KeyPrefix, "service"),
|
||||
sigHdlr: sigHdlr,
|
||||
workDir: workDir,
|
||||
confPath: confPath,
|
||||
}, svcConfig)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "initializing service", slogutil.KeyError, err)
|
||||
os.Exit(osutil.ExitCodeFailure)
|
||||
}
|
||||
|
||||
err = handleServiceCommand(ctx, l, s, action, opts)
|
||||
err = handleServiceCommand(ctx, l, s, action, workDir, confPath)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "handling command", slogutil.KeyError, err)
|
||||
os.Exit(osutil.ExitCodeFailure)
|
||||
@ -289,7 +295,8 @@ func handleServiceCommand(
|
||||
l *slog.Logger,
|
||||
s service.Service,
|
||||
action string,
|
||||
opts options,
|
||||
workDir string,
|
||||
confPath string,
|
||||
) (err error) {
|
||||
switch action {
|
||||
case "status":
|
||||
@ -299,13 +306,7 @@ func handleServiceCommand(
|
||||
return fmt.Errorf("failed to run service: %w", err)
|
||||
}
|
||||
case "install":
|
||||
if err = initWorkingDir(opts); err != nil {
|
||||
return fmt.Errorf("failed to init working dir: %w", err)
|
||||
}
|
||||
|
||||
initConfigFilename(opts)
|
||||
|
||||
handleServiceInstallCommand(ctx, l, s)
|
||||
handleServiceInstallCommand(ctx, l, s, workDir, confPath)
|
||||
case "uninstall":
|
||||
handleServiceUninstallCommand(ctx, l, s)
|
||||
default:
|
||||
@ -345,8 +346,15 @@ func handleServiceStatusCommand(
|
||||
}
|
||||
}
|
||||
|
||||
// handleServiceInstallCommand handles service "install" command.
|
||||
func handleServiceInstallCommand(ctx context.Context, l *slog.Logger, s service.Service) {
|
||||
// handleServiceInstallCommand handles the service "install" command. l must
|
||||
// not be nil.
|
||||
func handleServiceInstallCommand(
|
||||
ctx context.Context,
|
||||
l *slog.Logger,
|
||||
s service.Service,
|
||||
workDir string,
|
||||
confPath string,
|
||||
) {
|
||||
err := svcAction(ctx, l, s, "install")
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "executing install", slogutil.KeyError, err)
|
||||
@ -372,7 +380,7 @@ func handleServiceInstallCommand(ctx context.Context, l *slog.Logger, s service.
|
||||
}
|
||||
l.InfoContext(ctx, "started")
|
||||
|
||||
if detectFirstRun() {
|
||||
if detectFirstRun(ctx, l, workDir, confPath) {
|
||||
slogutil.PrintLines(ctx, l, slog.LevelInfo, "", "Almost ready!\n"+
|
||||
"AdGuard Home is successfully installed and will automatically start on boot.\n"+
|
||||
"There are a few more things that must be configured before you can use it.\n"+
|
||||
|
||||
@ -307,6 +307,8 @@ func initEmptyWeb(tb testing.TB) (web *webAPI) {
|
||||
http.NewServeMux(),
|
||||
agh.EmptyConfigModifier{},
|
||||
aghhttp.EmptyRegistrar{},
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
)
|
||||
|
||||
@ -76,6 +76,12 @@ type webConfig struct {
|
||||
// BindAddr is the binding address with port for plain HTTP web interface.
|
||||
BindAddr netip.AddrPort
|
||||
|
||||
// workDir is the base working directory.
|
||||
workDir string
|
||||
|
||||
// confPath is the configuration file path.
|
||||
confPath string
|
||||
|
||||
// ReadTimeout is an option to pass to http.Server for setting an
|
||||
// appropriate field.
|
||||
ReadTimeout time.Duration
|
||||
|
||||
Loading…
Reference in New Issue
Block a user