diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb77546..1f90726c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ NOTE: Add new changes BELOW THIS COMMENT. ### Added +- Ability to define custom directories for storage of query log files and + statistics ([#5992]). - Context menu item in the Query Log to add a Client to the Persistent client list ([#6679]). @@ -59,6 +61,7 @@ NOTE: Add new changes BELOW THIS COMMENT. - Go 1.20 support, as it has reached end of life. +[#5992]: https://github.com/AdguardTeam/AdGuardHome/issues/5992 [#6679]: https://github.com/AdguardTeam/AdGuardHome/issues/6679 [go-toolchain]: https://go.dev/blog/toolchain diff --git a/internal/aghnet/hostscontainer.go b/internal/aghnet/hostscontainer.go index 76b61b03..b8e86448 100644 --- a/internal/aghnet/hostscontainer.go +++ b/internal/aghnet/hostscontainer.go @@ -154,8 +154,8 @@ func pathsToPatterns(fsys fs.FS, paths []string) (patterns []string, err error) } // handleEvents concurrently handles the file system events. It closes the -// update channel of HostsContainer when finishes. It's used to be called -// within a separate goroutine. +// update channel of HostsContainer when finishes. It is intended to be used as +// a goroutine. func (hc *HostsContainer) handleEvents() { defer log.OnPanic(fmt.Sprintf("%s: handling events", hostsContainerPrefix)) diff --git a/internal/aghos/fswatcher.go b/internal/aghos/fswatcher.go index 32d88f21..1694242e 100644 --- a/internal/aghos/fswatcher.go +++ b/internal/aghos/fswatcher.go @@ -66,7 +66,7 @@ func NewOSWritesWatcher() (w FSWatcher, err error) { return fsw, nil } -// handleErrors handles accompanying errors. It used to be called in a separate +// handleErrors handles accompanying errors. It is intended to be used as a // goroutine. func (w *osWatcher) handleErrors() { defer log.OnPanic(fmt.Sprintf("%s: handling errors", osWatcherPref)) @@ -100,7 +100,7 @@ func (w *osWatcher) Close() (err error) { } // handleEvents notifies about the received file system's event if needed. It -// used to be called in a separate goroutine. +// is intended to be used as a goroutine. func (w *osWatcher) handleEvents() { defer log.OnPanic(fmt.Sprintf("%s: handling events", osWatcherPref)) diff --git a/internal/dhcpd/http_unix.go b/internal/dhcpd/http_unix.go index 42ea315a..db81bafc 100644 --- a/internal/dhcpd/http_unix.go +++ b/internal/dhcpd/http_unix.go @@ -592,7 +592,7 @@ func setOtherDHCPResult(ifaceName string, result *dhcpSearchResult) { } // parseLease parses a lease from r. If there is no error returns DHCPServer -// and *Lease. r must be non-nil. +// and *Lease. r must be non-nil. func (s *server) parseLease(r io.Reader) (srv DHCPServer, lease *dhcpsvc.Lease, err error) { l := &leaseStatic{} err = json.NewDecoder(r).Decode(l) diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index d4b8092e..30211677 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -40,7 +40,7 @@ type ClientsContainer interface { ) (conf *proxy.CustomUpstreamConfig, err error) } -// Config represents the DNS filtering configuration of AdGuard Home. The zero +// Config represents the DNS filtering configuration of AdGuard Home. The zero // Config is empty and ready for use. type Config struct { // Callbacks for other modules diff --git a/internal/home/clients.go b/internal/home/clients.go index 3da8572f..5aa2c81e 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -140,8 +140,7 @@ func (clients *clientsContainer) Init( } // handleHostsUpdates receives the updates from the hosts container and adds -// them to the clients container. It's used to be called in a separate -// goroutine. +// them to the clients container. It is intended to be used as a goroutine. func (clients *clientsContainer) handleHostsUpdates() { for upd := range clients.etcHosts.Upd() { clients.addFromHostsFile(upd) diff --git a/internal/home/config.go b/internal/home/config.go index fac08df0..17a5661d 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -259,6 +259,10 @@ type tlsConfigSettings struct { } type queryLogConfig struct { + // DirPath is the custom directory for logs. If it's empty the default + // directory will be used. See [homeContext.getDataDir]. + DirPath string `yaml:"dir_path"` + // Ignored is the list of host names, which should not be written to log. // "." is considered to be the root domain. Ignored []string `yaml:"ignored"` @@ -278,6 +282,10 @@ type queryLogConfig struct { } type statsConfig struct { + // DirPath is the custom directory for statistics. If it's empty the + // default directory is used. See [homeContext.getDataDir]. + DirPath string `yaml:"dir_path"` + // Ignored is the list of host names, which should not be counted. Ignored []string `yaml:"ignored"` diff --git a/internal/home/dns.go b/internal/home/dns.go index 5d601694..2b0267e8 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -46,12 +46,15 @@ func onConfigModified() { // server and initializes it at last. It also must not be called unless // [config] and [Context] are initialized. func initDNS() (err error) { - baseDir := Context.getDataDir() - anonymizer := config.anonymizer() + statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&Context, config) + if err != nil { + return err + } + statsConf := stats.Config{ - Filename: filepath.Join(baseDir, "stats.db"), + Filename: filepath.Join(statsDir, "stats.db"), Limit: config.Stats.Interval.Duration, ConfigModified: onConfigModified, HTTPRegister: httpRegister, @@ -75,7 +78,7 @@ func initDNS() (err error) { ConfigModified: onConfigModified, HTTPRegister: httpRegister, FindClient: Context.clients.findMultiple, - BaseDir: baseDir, + BaseDir: querylogDir, AnonymizeClientIP: config.DNS.AnonymizeClientIP, RotationIvl: config.QueryLog.Interval.Duration, MemSize: config.QueryLog.MemSize, @@ -545,3 +548,50 @@ func (r safeSearchResolver) LookupIP( return ips, nil } + +// checkStatsAndQuerylogDirs checks and returns directory paths to store +// statistics and query log. +func checkStatsAndQuerylogDirs( + ctx *homeContext, + conf *configuration, +) (statsDir, querylogDir string, err error) { + baseDir := ctx.getDataDir() + + statsDir = conf.Stats.DirPath + if statsDir == "" { + statsDir = baseDir + } else { + err = checkDir(statsDir) + if err != nil { + return "", "", fmt.Errorf("statistics: custom directory: %w", err) + } + } + + querylogDir = conf.QueryLog.DirPath + if querylogDir == "" { + querylogDir = baseDir + } else { + err = checkDir(querylogDir) + if err != nil { + return "", "", fmt.Errorf("querylog: custom directory: %w", err) + } + } + + return statsDir, querylogDir, nil +} + +// checkDir checks if the path is a directory. It's used to check for +// misconfiguration at startup. +func checkDir(path string) (err error) { + var fi os.FileInfo + if fi, err = os.Stat(path); err != nil { + // Don't wrap the error, since it's informative enough as is. + return err + } + + if !fi.IsDir() { + return fmt.Errorf("%q is not a directory", path) + } + + return nil +} diff --git a/internal/home/tls.go b/internal/home/tls.go index e022d043..7bd573dd 100644 --- a/internal/home/tls.go +++ b/internal/home/tls.go @@ -704,9 +704,9 @@ const ( keyTypeRSA = "RSA" ) -// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates +// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates // PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys. -// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three. +// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three. // // TODO(a.garipov): Find out if this version of parsePrivateKey from the stdlib // is actually necessary. diff --git a/internal/querylog/search.go b/internal/querylog/search.go index b5e0c4ec..f8c8f90e 100644 --- a/internal/querylog/search.go +++ b/internal/querylog/search.go @@ -49,8 +49,8 @@ func (l *queryLog) client(clientID, ip string, cache clientCache) (c *Client, er // the total amount of records in the buffer at the moment of searching. // l.confMu is expected to be locked. func (l *queryLog) searchMemory(params *searchParams, cache clientCache) (entries []*logEntry, total int) { - // We use this configuration check because a buffer can contain a single log - // record. See [newQueryLog]. + // Check memory size, as the buffer can contain a single log record. See + // [newQueryLog]. if l.conf.MemSize == 0 { return nil, 0 } @@ -186,7 +186,7 @@ func (l *queryLog) setQLogReader(olderThan time.Time) (qr *qLogReader, err error return r, nil } -// readEntries reads entries from the reader to totalLimit. By default, we do +// readEntries reads entries from the reader to totalLimit. By default, we do // not scan more than maxFileScanEntries at once. The idea is to make search // calls faster so that the UI could handle it and show something quicker. // This behavior can be overridden if maxFileScanEntries is set to 0. diff --git a/internal/updater/updater.go b/internal/updater/updater.go index bf7a9dae..4e4a21d4 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -314,7 +314,7 @@ func (u *Updater) clean() { _ = os.RemoveAll(u.updateDir) } -// MaxPackageFileSize is a maximum package file length in bytes. The largest +// MaxPackageFileSize is a maximum package file length in bytes. The largest // package whose size is limited by this constant currently has the size of // approximately 9 MiB. const MaxPackageFileSize = 32 * 1024 * 1024