Compare commits

..

4 Commits

Author SHA1 Message Date
Stanislav Chzhen
0a6c8b4198 Pull request 2409: upd-proxy
Merge in DNS/adguard-home from upd-proxy to master

Squashed commit of the following:

commit 2d9f5f1c3c48102bc82806721b63a7095332cc0b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue May 13 17:51:14 2025 +0300

    all: upd proxy
2025-05-13 18:16:12 +03:00
Ainar Garipov
ae840c9c96 Pull request 2405: AGDNS-2374-updater-slog
Squashed commit of the following:

commit 89c3df471964b674b7ddafeb22566e5be9b56a13
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon May 12 18:59:39 2025 +0300

    updater: imp log

commit d78ba4368027ddcbb41c10fbf09d43fe0721dc4c
Merge: 68410954c 187b759fc
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon May 12 18:53:33 2025 +0300

    Merge branch 'master' into AGDNS-2374-updater-slog

commit 68410954c80d76b2adafe4ed28fafdd6b6b6daae
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Apr 30 15:54:30 2025 +0300

    updater: imp docs

commit 99a705218fb849bb59dee5b801c5279a501bcf98
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Apr 30 15:40:30 2025 +0300

    updater: imp docs, logs

commit 2a83ee3ebf9610a2703d99ec6a6b327a315f6cce
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Apr 29 21:01:02 2025 +0300

    updater: use slog
2025-05-13 14:42:33 +03:00
Stanislav Chzhen
187b759fc6 Pull request 2404: 7801-fix-cmd-update
Closes #7801.

Squashed commit of the following:

commit f6e924e939eb9487e2c7743f04bb217e758ef253
Merge: 9caa54933 8c8323ae6
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed May 7 16:22:09 2025 +0300

    Merge branch 'master' into 7801-fix-cmd-update

commit 9caa5493302af11b8d522feb2cf6e6f0facaec53
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Apr 29 20:52:09 2025 +0300

    home: imp code

commit 765ea0023972e326c54f0c17ba79f3feca8ff803
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Apr 29 20:16:55 2025 +0300

    all: fix cmd update
2025-05-07 16:34:46 +03:00
Dimitry Kolyshev
8c8323ae68 Pull request: AGDNS-2818-upd-golibs
Merge in DNS/adguard-home from AGDNS-2818-upd-golibs to master

Squashed commit of the following:

commit f2a41b85ec27b306407b3fa96778b266dc8232e9
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 7 09:52:32 2025 +0300

    client: imp code

commit b4b668f7386c4abf0bbcf255c6c7b1edc5050727
Merge: c6f89e0b5 b5c47054a
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed May 7 09:47:54 2025 +0300

    Merge remote-tracking branch 'origin/master' into AGDNS-2818-upd-golibs
    
    # Conflicts:
    #	go.mod
    #	go.sum

commit c6f89e0b5dcaf8842187e72c22cf9109a1c4edc9
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Apr 30 14:10:33 2025 +0300

    client: imp code

commit cf07b1802fb0f1aa005af86c2bd59485683582d2
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Apr 30 10:31:44 2025 +0300

    client: imp code

commit a10d4b1b9265f6eecd7a40746de058b961d6fdd7
Merge: 447a79ca6 e5d0f0b11
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Apr 30 10:24:00 2025 +0300

    Merge remote-tracking branch 'origin/master' into AGDNS-2818-upd-golibs

commit 447a79ca6eb296a339b1e8c57edddcfcce3efdc2
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 28 13:02:40 2025 +0300

    all: upd golibs

commit 693ef4f39d628a97dbe94e1a0c5d2078f31b7e63
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 28 12:47:39 2025 +0300

    all: upd golibs

commit a4f90eac8547eea74bfdaa8b1bb23e0502638777
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Apr 28 10:25:33 2025 +0300

    all: upd golibs
2025-05-07 16:07:25 +03:00
13 changed files with 291 additions and 217 deletions

View File

@@ -20,10 +20,14 @@ NOTE: Add new changes BELOW THIS COMMENT.
### Fixed
- Command line option `--update` when the `dns.serve_plain_dns` configuration property was disabled ([7801]).
- DNS cache not working for custom upstream configurations.
- Validation process for the DNS-over-TLS, DNS-over-QUIC, and HTTPS ports on the *Encryption Settings* page.
[#7801]: https://github.com/AdguardTeam/AdGuardHome/issues/7801
<!--
NOTE: Add new changes ABOVE THIS COMMENT.
-->

12
go.mod
View File

@@ -3,8 +3,8 @@ module github.com/AdguardTeam/AdGuardHome
go 1.24.2
require (
github.com/AdguardTeam/dnsproxy v0.75.4
github.com/AdguardTeam/golibs v0.32.8
github.com/AdguardTeam/dnsproxy v0.75.5
github.com/AdguardTeam/golibs v0.32.9
github.com/AdguardTeam/urlfilter v0.20.0
github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.4.0
@@ -36,7 +36,7 @@ require (
golang.org/x/crypto v0.37.0
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
golang.org/x/net v0.39.0
golang.org/x/sys v0.32.0
golang.org/x/sys v0.33.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.1
@@ -61,7 +61,7 @@ require (
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/golangci/misspell v0.6.0 // indirect
github.com/google/generative-ai-go v0.19.0 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
@@ -89,11 +89,11 @@ require (
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/mock v0.5.1 // indirect
go.uber.org/mock v0.5.2 // indirect
golang.org/x/exp/typeparams v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3 // indirect
golang.org/x/term v0.31.0 // indirect
golang.org/x/text v0.24.0 // indirect

24
go.sum
View File

@@ -10,10 +10,10 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
github.com/AdguardTeam/dnsproxy v0.75.4 h1:hTnHh9HoTYKKhKqePpIxCzfecl7dAXykZTw2gcj0I5U=
github.com/AdguardTeam/dnsproxy v0.75.4/go.mod h1:50OyTHao+uQzUJiXay08hgfvWQ3o2Q2WV99W8u8ypDE=
github.com/AdguardTeam/golibs v0.32.8 h1:O3mc3kYcPkW3kbmd+gqzFNgUka13a+iBgFLThwOYSQE=
github.com/AdguardTeam/golibs v0.32.8/go.mod h1:McV1QFFlKLElKa306V4OL/T2kr7564PhsayfvTWYBVs=
github.com/AdguardTeam/dnsproxy v0.75.5 h1:/P7+Ku4bjl+sVC/FW3PbT7pabgCjKTcrAOHqsZe2e60=
github.com/AdguardTeam/dnsproxy v0.75.5/go.mod h1:fdwtHhrDkTueDagDCasYKZbXdppkkBXW7RGPBNH+pis=
github.com/AdguardTeam/golibs v0.32.9 h1:/6luT0aMOn05/s9eh1yA4lbcHgl0d1iEEvEBbIMMUk0=
github.com/AdguardTeam/golibs v0.32.9/go.mod h1:McV1QFFlKLElKa306V4OL/T2kr7564PhsayfvTWYBVs=
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs=
github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
@@ -72,8 +72,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4=
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
@@ -199,8 +199,8 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
@@ -227,8 +227,8 @@ golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -241,8 +241,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3 h1:RXY2+rSHXvxO2Y+gKrPjYVaEoGOqh3VEXFhnWAt1Irg=
golang.org/x/telemetry v0.0.0-20250417124945-06ef541f3fa3/go.mod h1:RoaXAWDwS90j6FxVKwJdBV+0HCU+llrKUGgJaxiKl6M=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View File

@@ -7,7 +7,6 @@ import (
"net"
"net/netip"
"slices"
"strings"
"sync"
"time"
@@ -478,7 +477,7 @@ const ErrBadIdentifier errors.Error = "bad client identifier"
func (p *FindParams) Set(id string) (err error) {
*p = FindParams{}
isClientID := true
isFound := false
if netutil.IsValidIPString(id) {
// It is safe to use [netip.MustParseAddr] because it has already been
@@ -488,24 +487,27 @@ func (p *FindParams) Set(id string) (err error) {
// Even if id can be parsed as an IP address, it may be a MAC address.
// So do not return prematurely, continue parsing.
isClientID = false
isFound = true
}
if canBeValidIPPrefixString(id) {
p.Subnet, err = netip.ParsePrefix(id)
if err == nil {
isClientID = false
}
}
if canBeMACString(id) {
if netutil.IsValidMACString(id) {
p.MAC, err = net.ParseMAC(id)
if err == nil {
isClientID = false
if err != nil {
panic(fmt.Errorf("parsing mac from %q: %w", id, err))
}
isFound = true
}
if !isClientID {
if isFound {
return nil
}
if netutil.IsValidIPPrefixString(id) {
// It is safe to use [netip.MustParsePrefix] because it has already been
// validated that id contains the string representation of IP prefix.
p.Subnet = netip.MustParsePrefix(id)
return nil
}
@@ -518,57 +520,6 @@ func (p *FindParams) Set(id string) (err error) {
return nil
}
// canBeValidIPPrefixString is a best-effort check to determine if s is a valid
// CIDR before using [netip.ParsePrefix], aimed at reducing allocations.
//
// TODO(s.chzhen): Replace this implementation with the more robust version
// from golibs.
func canBeValidIPPrefixString(s string) (ok bool) {
ipStr, bitStr, ok := strings.Cut(s, "/")
if !ok {
return false
}
if bitStr == "" || len(bitStr) > 3 {
return false
}
bits := 0
for _, c := range bitStr {
if c < '0' || c > '9' {
return false
}
bits = bits*10 + int(c-'0')
}
if bits > 128 {
return false
}
return netutil.IsValidIPString(ipStr)
}
// canBeMACString is a best-effort check to determine if s is a valid MAC
// address before using [net.ParseMAC], aimed at reducing allocations.
//
// TODO(s.chzhen): Replace this implementation with the more robust version
// from golibs.
func canBeMACString(s string) (ok bool) {
switch len(s) {
case
len("0000.0000.0000"),
len("00:00:00:00:00:00"),
len("0000.0000.0000.0000"),
len("00:00:00:00:00:00:00:00"),
len("0000.0000.0000.0000.0000.0000.0000.0000.0000.0000"),
len("00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"):
return true
default:
return false
}
}
// Find represents the parameters for searching a client. params must not be
// nil and must have at least one non-empty field.
func (s *Storage) Find(params *FindParams) (p *Persistent, ok bool) {

View File

@@ -82,7 +82,7 @@ func (web *webAPI) requestVersionInfo(
) (err error) {
updater := web.conf.updater
for range 3 {
resp.VersionInfo, err = updater.VersionInfo(recheck)
resp.VersionInfo, err = updater.VersionInfo(ctx, recheck)
if err == nil {
return nil
}
@@ -133,7 +133,7 @@ func (web *webAPI) handleUpdate(w http.ResponseWriter, r *http.Request) {
return
}
err = updater.Update(false)
err = updater.Update(r.Context(), false)
if err != nil {
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)

View File

@@ -119,16 +119,15 @@ func initDNS(
globalContext.dhcpServer,
anonymizer,
httpRegister,
tlsMgr.config(),
tlsMgr,
baseLogger,
)
}
// initDNSServer initializes the [context.dnsServer]. To only use the internal
// proxy, none of the arguments are required, but tlsConf, tlsMgr and l still
// must not be nil, in other cases all the arguments also must not be nil. It
// also must not be called unless [config] and [globalContext] are initialized.
// proxy, none of the arguments are required, but tlsMgr and l still must not be
// nil, in other cases all the arguments also must not be nil. It also must not
// be called unless [config] and [globalContext] are initialized.
//
// TODO(e.burkov): Use [dnsforward.DNSCreateParams] as a parameter.
func initDNSServer(
@@ -138,7 +137,6 @@ func initDNSServer(
dhcpSrv dnsforward.DHCP,
anonymizer *aghnet.IPMut,
httpReg aghhttp.RegisterFunc,
tlsConf *tlsConfigSettings,
tlsMgr *tlsManager,
l *slog.Logger,
) (err error) {
@@ -167,7 +165,7 @@ func initDNSServer(
dnsConf, err := newServerConfig(
&config.DNS,
config.Clients.Sources,
tlsConf,
tlsMgr.config(),
tlsMgr,
httpReg,
globalContext.clients.storage,

View File

@@ -487,9 +487,14 @@ func checkPorts() (err error) {
}
// isUpdateEnabled returns true if the update is enabled for current
// configuration. It also logs the decision. customURL should be true if the
// configuration. It also logs the decision. isCustomURL should be true if the
// updater is using a custom URL.
func isUpdateEnabled(ctx context.Context, l *slog.Logger, opts *options, customURL bool) (ok bool) {
func isUpdateEnabled(
ctx context.Context,
l *slog.Logger,
opts *options,
isCustomURL bool,
) (ok bool) {
if opts.disableUpdate {
l.DebugContext(ctx, "updates are disabled by command-line option")
@@ -500,13 +505,13 @@ func isUpdateEnabled(ctx context.Context, l *slog.Logger, opts *options, customU
case
version.ChannelDevelopment,
version.ChannelCandidate:
if customURL {
if isCustomURL {
l.DebugContext(ctx, "updates are enabled because custom url is used")
} else {
l.DebugContext(ctx, "updates are disabled for development and candidate builds")
}
return customURL
return isCustomURL
default:
l.DebugContext(ctx, "updates are enabled")
@@ -514,7 +519,7 @@ func isUpdateEnabled(ctx context.Context, l *slog.Logger, opts *options, customU
}
}
// initWeb initializes the web module. upd, baseLogger, and tlsMgr must not be
// initWeb initializes the web module. upd, baseLogger, and tlsMgr must not be
// nil.
func initWeb(
ctx context.Context,
@@ -523,7 +528,7 @@ func initWeb(
upd *updater.Updater,
baseLogger *slog.Logger,
tlsMgr *tlsManager,
customURL bool,
isCustomUpdURL bool,
) (web *webAPI, err error) {
logger := baseLogger.With(slogutil.KeyPrefix, "webapi")
@@ -539,7 +544,7 @@ func initWeb(
}
}
disableUpdate := !isUpdateEnabled(ctx, baseLogger, &opts, customURL)
disableUpdate := !isUpdateEnabled(ctx, baseLogger, &opts, isCustomUpdURL)
webConf := &webConfig{
updater: upd,
@@ -645,11 +650,12 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalH
confPath := configFilePath()
upd, customURL := newUpdater(ctx, slogLogger, globalContext.workDir, confPath, execPath, config)
updLogger := slogLogger.With(slogutil.KeyPrefix, "updater")
upd, isCustomURL := newUpdater(ctx, updLogger, config, globalContext.workDir, confPath, execPath)
// TODO(e.burkov): This could be made earlier, probably as the option's
// effect.
cmdlineUpdate(ctx, slogLogger, opts, upd, tlsMgr)
cmdlineUpdate(ctx, updLogger, opts, upd, tlsMgr)
if !globalContext.firstRun {
// Save the updated config.
@@ -671,7 +677,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalH
globalContext.auth, err = initUsers()
fatalOnError(err)
web, err := initWeb(ctx, opts, clientBuildFS, upd, slogLogger, tlsMgr, customURL)
web, err := initWeb(ctx, opts, clientBuildFS, upd, slogLogger, tlsMgr, isCustomURL)
fatalOnError(err)
globalContext.web = web
@@ -714,16 +720,17 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalH
<-done
}
// newUpdater creates a new AdGuard Home updater. customURL is true if the user
// has specified a custom version announcement URL.
// 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
// the user has specified a custom version announcement URL.
func newUpdater(
ctx context.Context,
l *slog.Logger,
conf *configuration,
workDir string,
confPath string,
execPath string,
config *configuration,
) (upd *updater.Updater, customURL bool) {
) (upd *updater.Updater, isCustomURL bool) {
// envName is the name of the environment variable that can be used to
// override the default version check URL.
const envName = "ADGUARD_HOME_TEST_UPDATE_VERSION_URL"
@@ -735,14 +742,14 @@ func newUpdater(
case version.Channel() == version.ChannelRelease:
// Only enable custom version URL for development builds.
l.DebugContext(ctx, "custom version url is disabled for release builds")
case !config.UnsafeUseCustomUpdateIndexURL:
case !conf.UnsafeUseCustomUpdateIndexURL:
l.DebugContext(ctx, "custom version url is disabled in config")
default:
versionURL, _ = url.Parse(customURLStr)
}
err := urlutil.ValidateHTTPURL(versionURL)
if customURL = err == nil; !customURL {
if isCustomURL = err == nil; !isCustomURL {
l.DebugContext(ctx, "parsing custom version url", slogutil.KeyError, err)
versionURL = updater.DefaultVersionURL()
@@ -751,7 +758,8 @@ func newUpdater(
l.DebugContext(ctx, "creating updater", "config_path", confPath)
return updater.NewUpdater(&updater.Config{
Client: config.Filtering.HTTPClient,
Client: conf.Filtering.HTTPClient,
Logger: l,
Version: version.Version(),
Channel: version.Channel(),
GOARCH: runtime.GOARCH,
@@ -762,7 +770,7 @@ func newUpdater(
ConfName: confPath,
ExecPath: execPath,
VersionCheckURL: versionURL,
}), customURL
}), isCustomURL
}
// checkPermissions checks and migrates permissions of the files and directories
@@ -1078,12 +1086,12 @@ func cmdlineUpdate(
//
// TODO(e.burkov): We could probably initialize the internal resolver
// separately.
err := initDNSServer(nil, nil, nil, nil, nil, nil, &tlsConfigSettings{}, tlsMgr, l)
err := initDNSServer(nil, nil, nil, nil, nil, nil, tlsMgr, l)
fatalOnError(err)
l.InfoContext(ctx, "performing update via cli")
info, err := upd.VersionInfo(true)
info, err := upd.VersionInfo(ctx, true)
if err != nil {
l.ErrorContext(ctx, "getting version info", slogutil.KeyError, err)
@@ -1096,7 +1104,7 @@ func cmdlineUpdate(
os.Exit(osutil.ExitCodeSuccess)
}
err = upd.Update(globalContext.firstRun)
err = upd.Update(ctx, globalContext.firstRun)
fatalOnError(err)
err = restartService()

View File

@@ -193,7 +193,10 @@ func (m *tlsManager) start(_ context.Context) {
m.web.tlsConfigChanged(context.Background(), m.conf)
}
// reload updates the configuration and restarts the TLS manager.
// reload updates the configuration and restarts the TLS manager. It logs any
// encountered errors.
//
// TODO(s.chzhen): Consider returning an error.
func (m *tlsManager) reload(ctx context.Context) {
m.mu.Lock()
defer m.mu.Unlock()

View File

@@ -1,6 +1,7 @@
package updater
import (
"context"
"encoding/json"
"fmt"
"io"
@@ -12,7 +13,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/ioutil"
"github.com/AdguardTeam/golibs/log"
"github.com/c2h5oh/datasize"
)
@@ -35,7 +35,7 @@ const maxVersionRespSize datasize.ByteSize = 64 * datasize.KB
// VersionInfo downloads the latest version information. If forceRecheck is
// false and there are cached results, those results are returned.
func (u *Updater) VersionInfo(forceRecheck bool) (vi VersionInfo, err error) {
func (u *Updater) VersionInfo(ctx context.Context, forceRecheck bool) (vi VersionInfo, err error) {
u.mu.Lock()
defer u.mu.Unlock()
@@ -45,11 +45,17 @@ func (u *Updater) VersionInfo(forceRecheck bool) (vi VersionInfo, err error) {
return u.prevCheckResult, u.prevCheckError
}
var resp *http.Response
vcu := u.versionCheckURL
resp, err = u.client.Get(vcu)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, vcu, nil)
if err != nil {
return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", vcu, err)
return VersionInfo{}, fmt.Errorf("constructing request to %s: %w", vcu, err)
}
u.logger.DebugContext(ctx, "requesting version data", "url", vcu)
resp, err := u.client.Do(req)
if err != nil {
return VersionInfo{}, fmt.Errorf("requesting %s: %w", vcu, err)
}
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
@@ -59,16 +65,16 @@ func (u *Updater) VersionInfo(forceRecheck bool) (vi VersionInfo, err error) {
// ReadCloser.
body, err := io.ReadAll(r)
if err != nil {
return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", vcu, err)
return VersionInfo{}, fmt.Errorf("reading response from %s: %w", vcu, err)
}
u.prevCheckTime = now
u.prevCheckResult, u.prevCheckError = u.parseVersionResponse(body)
u.prevCheckResult, u.prevCheckError = u.parseVersionResponse(ctx, body)
return u.prevCheckResult, u.prevCheckError
}
func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
func (u *Updater) parseVersionResponse(ctx context.Context, data []byte) (VersionInfo, error) {
info := VersionInfo{
CanAutoUpdate: aghalg.NBFalse,
}
@@ -92,7 +98,7 @@ func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
info.Announcement = versionJSON["announcement"]
info.AnnouncementURL = versionJSON["announcement_url"]
packageURL, key, found := u.downloadURL(versionJSON)
packageURL, key, found := u.downloadURL(ctx, versionJSON)
if !found {
return info, fmt.Errorf("version.json: no package URL: key %q not found in object", key)
}
@@ -108,7 +114,10 @@ func (u *Updater) parseVersionResponse(data []byte) (VersionInfo, error) {
// downloadURL returns the download URL for current build as well as its key in
// versionObj. If the key is not found, it additionally prints an informative
// log message.
func (u *Updater) downloadURL(versionObj map[string]string) (dlURL, key string, ok bool) {
func (u *Updater) downloadURL(
ctx context.Context,
versionObj map[string]string,
) (dlURL, key string, ok bool) {
if u.goarch == "arm" && u.goarm != "" {
key = fmt.Sprintf("download_%s_%sv%s", u.goos, u.goarch, u.goarm)
} else if isMIPS(u.goarch) && u.gomips != "" {
@@ -124,7 +133,7 @@ func (u *Updater) downloadURL(versionObj map[string]string) (dlURL, key string,
keys := slices.Sorted(maps.Keys(versionObj))
log.Error("updater: key %q not found; got keys %q", key, keys)
u.logger.ErrorContext(ctx, "key not found", "missing", key, "got", keys)
return "", key, false
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/updater"
"github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -58,6 +59,7 @@ func TestUpdater_VersionInfo(t *testing.T) {
u := updater.NewUpdater(&updater.Config{
Client: srv.Client(),
Logger: testLogger,
Version: "v0.103.0-beta.1",
Channel: version.ChannelBeta,
GOARCH: "arm",
@@ -65,7 +67,8 @@ func TestUpdater_VersionInfo(t *testing.T) {
VersionCheckURL: fakeURL,
})
info, err := u.VersionInfo(false)
ctx := testutil.ContextWithTimeout(t, testTimeout)
info, err := u.VersionInfo(ctx, false)
require.NoError(t, err)
assert.Equal(t, counter, 1)
@@ -75,14 +78,14 @@ func TestUpdater_VersionInfo(t *testing.T) {
assert.Equal(t, aghalg.NBTrue, info.CanAutoUpdate)
t.Run("cache_check", func(t *testing.T) {
_, err = u.VersionInfo(false)
_, err = u.VersionInfo(testutil.ContextWithTimeout(t, testTimeout), false)
require.NoError(t, err)
assert.Equal(t, counter, 1)
})
t.Run("force_check", func(t *testing.T) {
_, err = u.VersionInfo(true)
_, err = u.VersionInfo(testutil.ContextWithTimeout(t, testTimeout), true)
require.NoError(t, err)
assert.Equal(t, counter, 2)
@@ -91,7 +94,7 @@ func TestUpdater_VersionInfo(t *testing.T) {
t.Run("api_fail", func(t *testing.T) {
srv.Close()
_, err = u.VersionInfo(true)
_, err = u.VersionInfo(testutil.ContextWithTimeout(t, testTimeout), true)
var urlErr *url.Error
assert.ErrorAs(t, err, &urlErr)
})
@@ -130,6 +133,7 @@ func TestUpdater_VersionInfo_others(t *testing.T) {
for _, tc := range testCases {
u := updater.NewUpdater(&updater.Config{
Client: fakeClient,
Logger: testLogger,
Version: "v0.103.0-beta.1",
Channel: version.ChannelBeta,
GOOS: "linux",
@@ -139,7 +143,8 @@ func TestUpdater_VersionInfo_others(t *testing.T) {
VersionCheckURL: fakeURL,
})
info, err := u.VersionInfo(false)
ctx := testutil.ContextWithTimeout(t, testTimeout)
info, err := u.VersionInfo(ctx, false)
require.NoError(t, err)
assert.Equal(t, "v0.103.0-beta.2", info.NewVersion)

View File

@@ -5,9 +5,11 @@ import (
"archive/tar"
"archive/zip"
"compress/gzip"
"context"
"fmt"
"io"
"io/fs"
"log/slog"
"net/http"
"net/url"
"os"
@@ -22,13 +24,14 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/ioutil"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
)
// Updater is the AdGuard Home updater.
type Updater struct {
client *http.Client
logger *slog.Logger
version string
channel string
@@ -75,27 +78,48 @@ func DefaultVersionURL() *url.URL {
// Config is the AdGuard Home updater configuration.
type Config struct {
// Client is used to perform HTTP requests. It must not be nil.
Client *http.Client
// Logger is used for logging the update process. It must not be nil.
Logger *slog.Logger
// VersionCheckURL is URL to the latest version announcement. It must not
// be nil, see [DefaultVersionURL].
VersionCheckURL *url.URL
// Version is the current AdGuard Home version. It must not be empty.
Version string
Channel string
GOARCH string
GOOS string
GOARM string
GOMIPS string
// ConfName is the name of the current configuration file. Typically,
// "AdGuardHome.yaml".
// Channel is the current AdGuard Home update channel. It must be a valid
// channel, see [version.ChannelBeta] and the related constants.
Channel string
// GOARCH is the current CPU architecture. It must not be empty and must be
// one of the supported architectures.
GOARCH string
// GOOS is the current operating system. It must not be empty and must be
// one of the supported OSs.
GOOS string
// GOARM is the current ARM variant, if any. It must either be empty or be
// a valid and supported GOARM value.
GOARM string
// GOMIPS is the current MIPS variant, if any. It must either be empty or
// be a valid and supported GOMIPS value.
GOMIPS string
// ConfName is the name of the current configuration file. It must not be
// empty.
ConfName string
// WorkDir is the working directory that is used for temporary files.
// WorkDir is the working directory that is used for temporary files. It
// must not be empty.
WorkDir string
// ExecPath is path to the executable file.
// ExecPath is path to the executable file. It must not be empty.
ExecPath string
}
@@ -103,6 +127,7 @@ type Config struct {
func NewUpdater(conf *Config) *Updater {
return &Updater{
client: conf.Client,
logger: conf.Logger,
version: conf.Version,
channel: conf.Channel,
@@ -122,49 +147,49 @@ func NewUpdater(conf *Config) *Updater {
// Update performs the auto-update. It returns an error if the update failed.
// If firstRun is true, it assumes the configuration file doesn't exist.
func (u *Updater) Update(firstRun bool) (err error) {
func (u *Updater) Update(ctx context.Context, firstRun bool) (err error) {
u.mu.Lock()
defer u.mu.Unlock()
log.Info("updater: updating")
u.logger.InfoContext(ctx, "staring update", "first_run", firstRun)
defer func() {
if err != nil {
log.Info("updater: failed")
u.logger.ErrorContext(ctx, "update failed", slogutil.KeyError, err)
} else {
log.Info("updater: finished successfully")
u.logger.InfoContext(ctx, "update finished")
}
}()
err = u.prepare()
err = u.prepare(ctx)
if err != nil {
return fmt.Errorf("preparing: %w", err)
}
defer u.clean()
defer u.clean(ctx)
err = u.downloadPackageFile()
err = u.downloadPackageFile(ctx)
if err != nil {
return fmt.Errorf("downloading package file: %w", err)
}
err = u.unpack()
err = u.unpack(ctx)
if err != nil {
return fmt.Errorf("unpacking: %w", err)
}
if !firstRun {
err = u.check()
err = u.check(ctx)
if err != nil {
return fmt.Errorf("checking config: %w", err)
}
}
err = u.backup(firstRun)
err = u.backup(ctx, firstRun)
if err != nil {
return fmt.Errorf("making backup: %w", err)
}
err = u.replace()
err = u.replace(ctx)
if err != nil {
return fmt.Errorf("replacing: %w", err)
}
@@ -181,7 +206,7 @@ func (u *Updater) NewVersion() (nv string) {
}
// prepare fills all necessary fields in Updater object.
func (u *Updater) prepare() (err error) {
func (u *Updater) prepare(ctx context.Context) (err error) {
u.updateDir = filepath.Join(u.workDir, fmt.Sprintf("agh-update-%s", u.newVersion))
_, pkgNameOnly := filepath.Split(u.packageURL)
@@ -200,11 +225,12 @@ func (u *Updater) prepare() (err error) {
u.backupExeName = filepath.Join(u.backupDir, filepath.Base(u.execPath))
u.updateExeName = filepath.Join(u.updateDir, updateExeName)
log.Debug(
"updater: updating from %s to %s using url: %s",
version.Version(),
u.newVersion,
u.packageURL,
u.logger.InfoContext(
ctx,
"updating",
"from", version.Version(),
"to", u.newVersion,
"package_url", u.packageURL,
)
u.currentExeName = u.execPath
@@ -217,23 +243,20 @@ func (u *Updater) prepare() (err error) {
}
// unpack extracts the files from the downloaded archive.
func (u *Updater) unpack() error {
var err error
func (u *Updater) unpack(ctx context.Context) (err error) {
_, pkgNameOnly := filepath.Split(u.packageURL)
log.Debug("updater: unpacking package")
u.logger.InfoContext(ctx, "unpacking package", "package_name", pkgNameOnly)
if strings.HasSuffix(pkgNameOnly, ".zip") {
u.unpackedFiles, err = zipFileUnpack(u.packageName, u.updateDir)
u.unpackedFiles, err = u.unpackZip(ctx, u.packageName, u.updateDir)
if err != nil {
return fmt.Errorf(".zip unpack failed: %w", err)
}
} else if strings.HasSuffix(pkgNameOnly, ".tar.gz") {
u.unpackedFiles, err = tarGzFileUnpack(u.packageName, u.updateDir)
u.unpackedFiles, err = u.unpackTarGz(ctx, u.packageName, u.updateDir)
if err != nil {
return fmt.Errorf(".tar.gz unpack failed: %w", err)
}
} else {
return fmt.Errorf("unknown package extension")
}
@@ -243,8 +266,8 @@ func (u *Updater) unpack() error {
// check returns an error if the configuration file couldn't be used with the
// version of AdGuard Home just downloaded.
func (u *Updater) check() (err error) {
log.Debug("updater: checking configuration")
func (u *Updater) check(ctx context.Context) (err error) {
u.logger.InfoContext(ctx, "checking configuration")
err = copyFile(u.confName, filepath.Join(u.updateDir, "AdGuardHome.yaml"), aghos.DefaultPermFile)
if err != nil {
@@ -268,8 +291,9 @@ func (u *Updater) check() (err error) {
// backup makes a backup of the current configuration and supporting files. It
// ignores the configuration file if firstRun is true.
func (u *Updater) backup(firstRun bool) (err error) {
log.Debug("updater: backing up current configuration")
func (u *Updater) backup(ctx context.Context, firstRun bool) (err error) {
u.logger.InfoContext(ctx, "backing up current configuration")
_ = os.Mkdir(u.backupDir, aghos.DefaultPermDir)
if !firstRun {
err = copyFile(u.confName, filepath.Join(u.backupDir, "AdGuardHome.yaml"), aghos.DefaultPermFile)
@@ -279,7 +303,7 @@ func (u *Updater) backup(firstRun bool) (err error) {
}
wd := u.workDir
err = copySupportingFiles(u.unpackedFiles, wd, u.backupDir)
err = u.copySupportingFiles(ctx, u.unpackedFiles, wd, u.backupDir)
if err != nil {
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %w", wd, u.backupDir, err)
}
@@ -289,13 +313,18 @@ func (u *Updater) backup(firstRun bool) (err error) {
// replace moves the current executable with the updated one and also copies the
// supporting files.
func (u *Updater) replace() error {
err := copySupportingFiles(u.unpackedFiles, u.updateDir, u.workDir)
func (u *Updater) replace(ctx context.Context) (err error) {
err = u.copySupportingFiles(ctx, u.unpackedFiles, u.updateDir, u.workDir)
if err != nil {
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %w", u.updateDir, u.workDir, err)
}
log.Debug("updater: renaming: %s to %s", u.currentExeName, u.backupExeName)
u.logger.InfoContext(
ctx,
"backing up current executable",
"from", u.currentExeName,
"to", u.backupExeName,
)
err = os.Rename(u.currentExeName, u.backupExeName)
if err != nil {
return err
@@ -311,14 +340,22 @@ func (u *Updater) replace() error {
return err
}
log.Debug("updater: renamed: %s to %s", u.updateExeName, u.currentExeName)
u.logger.InfoContext(
ctx,
"replacing current executable",
"from", u.updateExeName,
"to", u.currentExeName,
)
return nil
}
// clean removes the temporary directory itself and all it's contents.
func (u *Updater) clean() {
_ = os.RemoveAll(u.updateDir)
func (u *Updater) clean(ctx context.Context) {
err := os.RemoveAll(u.updateDir)
if err != nil {
u.logger.WarnContext(ctx, "removing update dir", slogutil.KeyError, err)
}
}
// MaxPackageFileSize is a maximum package file length in bytes. The largest
@@ -327,34 +364,52 @@ func (u *Updater) clean() {
const MaxPackageFileSize = 32 * 1024 * 1024
// Download package file and save it to disk
func (u *Updater) downloadPackageFile() (err error) {
var resp *http.Response
resp, err = u.client.Get(u.packageURL)
func (u *Updater) downloadPackageFile(ctx context.Context) (err error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.packageURL, nil)
if err != nil {
return fmt.Errorf("http request failed: %w", err)
return fmt.Errorf("constructing package request: %w", err)
}
resp, err := u.client.Do(req)
if err != nil {
return fmt.Errorf("requesting package: %w", err)
}
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
r := ioutil.LimitReader(resp.Body, MaxPackageFileSize)
log.Debug("updater: reading http body")
u.logger.InfoContext(ctx, "reading http body")
// This use of ReadAll is now safe, because we limited body's Reader.
body, err := io.ReadAll(r)
if err != nil {
return fmt.Errorf("io.ReadAll() failed: %w", err)
}
_ = os.Mkdir(u.updateDir, aghos.DefaultPermDir)
err = os.Mkdir(u.updateDir, aghos.DefaultPermDir)
if err != nil {
// TODO(a.garipov): Consider returning this error.
u.logger.WarnContext(ctx, "creating update dir", slogutil.KeyError, err)
}
u.logger.InfoContext(ctx, "saving package", "to", u.packageName)
log.Debug("updater: saving package to file")
err = os.WriteFile(u.packageName, body, aghos.DefaultPermFile)
if err != nil {
return fmt.Errorf("writing package file: %w", err)
}
return nil
}
func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name string, err error) {
// unpackTarGzFile unpacks one file from a .tar.gz archive into outDir. All
// arguments must not be empty.
func (u *Updater) unpackTarGzFile(
ctx context.Context,
outDir string,
tr *tar.Reader,
hdr *tar.Header,
) (name string, err error) {
name = filepath.Base(hdr.Name)
if name == "" {
return "", nil
@@ -377,13 +432,18 @@ func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name st
return "", fmt.Errorf("creating directory %q: %w", outName, err)
}
log.Debug("updater: created directory %q", outName)
u.logger.InfoContext(ctx, "created directory", "name", outName)
return "", nil
}
if hdr.Typeflag != tar.TypeReg {
log.Info("updater: %s: unknown file type %d, skipping", name, hdr.Typeflag)
u.logger.WarnContext(
ctx,
"unknown file type; skipping",
"file_name", name,
"type", hdr.Typeflag,
)
return "", nil
}
@@ -400,16 +460,19 @@ func tarGzFileUnpackOne(outDir string, tr *tar.Reader, hdr *tar.Header) (name st
return "", fmt.Errorf("io.Copy(): %w", err)
}
log.Debug("updater: created file %q", outName)
u.logger.InfoContext(ctx, "created file", "name", outName)
return name, nil
}
// Unpack all files from .tar.gz file to the specified directory
// Existing files are overwritten
// All files are created inside outDir, subdirectories are not created
// Return the list of files (not directories) written
func tarGzFileUnpack(tarfile, outDir string) (files []string, err error) {
// unpackTarGz unpack all files from a .tar.gz archive to outDir. Existing
// files are overwritten. All files are created inside outDir. files are the
// list of created files.
func (u *Updater) unpackTarGz(
ctx context.Context,
tarfile string,
outDir string,
) (files []string, err error) {
f, err := os.Open(tarfile)
if err != nil {
return nil, fmt.Errorf("os.Open(): %w", err)
@@ -437,7 +500,7 @@ func tarGzFileUnpack(tarfile, outDir string) (files []string, err error) {
}
var name string
name, err = tarGzFileUnpackOne(outDir, tarReader, hdr)
name, err = u.unpackTarGzFile(ctx, outDir, tarReader, hdr)
if name != "" {
files = append(files, name)
@@ -447,7 +510,13 @@ func tarGzFileUnpack(tarfile, outDir string) (files []string, err error) {
return files, err
}
func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
// unpackZipFile unpacks one file from a .zip archive into outDir. All
// arguments must not be empty.
func (u *Updater) unpackZipFile(
ctx context.Context,
outDir string,
zf *zip.File,
) (name string, err error) {
var rc io.ReadCloser
rc, err = zf.Open()
if err != nil {
@@ -466,7 +535,8 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
if name == "AdGuardHome" {
// Top-level AdGuardHome/. Skip it.
//
// TODO(a.garipov): See the similar todo in tarGzFileUnpack.
// TODO(a.garipov): See the similar TODO in
// [Updater.unpackTarGzFile].
return "", nil
}
@@ -475,7 +545,7 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
return "", fmt.Errorf("creating directory %q: %w", outputName, err)
}
log.Debug("updater: created directory %q", outputName)
u.logger.InfoContext(ctx, "created directory", "name", outputName)
return "", nil
}
@@ -492,16 +562,19 @@ func zipFileUnpackOne(outDir string, zf *zip.File) (name string, err error) {
return "", fmt.Errorf("io.Copy(): %w", err)
}
log.Debug("updater: created file %q", outputName)
u.logger.InfoContext(ctx, "created file", "name", outputName)
return name, nil
}
// Unpack all files from .zip file to the specified directory
// Existing files are overwritten
// All files are created inside 'outDir', subdirectories are not created
// Return the list of files (not directories) written
func zipFileUnpack(zipfile, outDir string) (files []string, err error) {
// unpackZip unpack all files from a .zip archive to outDir. Existing files are
// overwritten. All files are created inside outDir. files are the list of
// created files.
func (u *Updater) unpackZip(
ctx context.Context,
zipfile string,
outDir string,
) (files []string, err error) {
zrc, err := zip.OpenReader(zipfile)
if err != nil {
return nil, fmt.Errorf("zip.OpenReader(): %w", err)
@@ -510,7 +583,7 @@ func zipFileUnpack(zipfile, outDir string) (files []string, err error) {
for _, zf := range zrc.File {
var name string
name, err = zipFileUnpackOne(outDir, zf)
name, err = u.unpackZipFile(ctx, outDir, zf)
if err != nil {
break
}
@@ -543,7 +616,12 @@ func copyFile(src, dst string, perm fs.FileMode) (err error) {
// copySupportingFiles copies each file specified in files from srcdir to
// dstdir. If a file specified as a path, only the name of the file is used.
// It skips AdGuardHome, AdGuardHome.exe, and AdGuardHome.yaml.
func copySupportingFiles(files []string, srcdir, dstdir string) error {
func (u *Updater) copySupportingFiles(
ctx context.Context,
files []string,
srcdir string,
dstdir string,
) (err error) {
for _, f := range files {
_, name := filepath.Split(f)
if name == "AdGuardHome" || name == "AdGuardHome.exe" || name == "AdGuardHome.yaml" {
@@ -553,12 +631,12 @@ func copySupportingFiles(files []string, srcdir, dstdir string) error {
src := filepath.Join(srcdir, name)
dst := filepath.Join(dstdir, name)
err := copyFile(src, dst, aghos.DefaultPermFile)
err = copyFile(src, dst, aghos.DefaultPermFile)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
log.Debug("updater: copied: %q to %q", src, dst)
u.logger.InfoContext(ctx, "copied", "from", src, "to", dst)
}
return nil

View File

@@ -1,12 +1,16 @@
package updater
import (
"context"
"net/url"
"os"
"path/filepath"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -55,6 +59,7 @@ func TestUpdater_internal(t *testing.T) {
u := NewUpdater(&Config{
Client: fakeClient,
Logger: slogutil.NewDiscardLogger(),
GOOS: tc.os,
Version: "v0.103.0",
ExecPath: exePath,
@@ -68,13 +73,13 @@ func TestUpdater_internal(t *testing.T) {
u.newVersion = "v0.103.1"
u.packageURL = fakeURL.String()
require.NoError(t, u.prepare())
require.NoError(t, u.downloadPackageFile())
require.NoError(t, u.unpack())
require.NoError(t, u.backup(false))
require.NoError(t, u.replace())
require.NoError(t, u.prepare(newCtx(t)))
require.NoError(t, u.downloadPackageFile(newCtx(t)))
require.NoError(t, u.unpack(newCtx(t)))
require.NoError(t, u.backup(newCtx(t), false))
require.NoError(t, u.replace(newCtx(t)))
u.clean()
u.clean(newCtx(t))
require.True(t, t.Run("backup", func(t *testing.T) {
var d []byte
@@ -113,3 +118,8 @@ func TestUpdater_internal(t *testing.T) {
}))
}
}
// newCtx is a helper that returns a new context with a timeout.
func newCtx(tb testing.TB) (ctx context.Context) {
return testutil.ContextWithTimeout(tb, 1*time.Second)
}

View File

@@ -10,17 +10,21 @@ import (
"path/filepath"
"runtime"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/updater"
"github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}
// testTimeout is the common timeout for tests.
const testTimeout = 1 * time.Second
// testLogger is the common logger for tests.
var testLogger = slogutil.NewDiscardLogger()
func TestUpdater_Update(t *testing.T) {
const jsonData = `{
@@ -73,6 +77,7 @@ func TestUpdater_Update(t *testing.T) {
u := updater.NewUpdater(&updater.Config{
Client: srv.Client(),
Logger: testLogger,
GOARCH: "amd64",
GOOS: "linux",
Version: "v0.103.0",
@@ -82,10 +87,12 @@ func TestUpdater_Update(t *testing.T) {
VersionCheckURL: versionCheckURL,
})
_, err = u.VersionInfo(false)
ctx := testutil.ContextWithTimeout(t, testTimeout)
_, err = u.VersionInfo(ctx, false)
require.NoError(t, err)
err = u.Update(true)
ctx = testutil.ContextWithTimeout(t, testTimeout)
err = u.Update(ctx, true)
require.NoError(t, err)
// check backup files
@@ -124,14 +131,15 @@ func TestUpdater_Update(t *testing.T) {
t.Skip("skipping config check test on windows")
}
err = u.Update(false)
err = u.Update(testutil.ContextWithTimeout(t, testTimeout), false)
assert.NoError(t, err)
})
t.Run("api_fail", func(t *testing.T) {
srv.Close()
err = u.Update(true)
err = u.Update(testutil.ContextWithTimeout(t, testTimeout), true)
var urlErr *url.Error
assert.ErrorAs(t, err, &urlErr)
})