Pull request: 5191-update-flag
Merge in DNS/adguard-home from 5191-update-flag to master
Updates #5191.
Updates #4223.
Squashed commit of the following:
commit fbace4942844dc67f2467479385e06843c3abb6a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Mon Jan 9 12:05:16 2023 +0400
all: imp code, docs
commit 8237dceb771ba95f545f79565d76cbb4ebd0d805
Merge: ca9518f2 bbdcc673
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Fri Dec 30 14:45:55 2022 +0400
Merge branch 'master' into 5191-update-flag
commit ca9518f20e5643572adf9734b93a5436ba30c865
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Dec 29 20:36:33 2022 +0400
home: imp code
commit 1dc6c7c3480df3df4a5f3f923f1feab7761a7945
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Dec 29 18:26:08 2022 +0400
all: imp code, docs
commit 7bbe893e98063b956482fd6f1c6be95a4f1956cf
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Dec 29 03:44:48 2022 +0400
home: restart service on update
commit e0d3c287a7e1c05b1e397f4727c447a1fcd9f7f6
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Dec 29 03:22:49 2022 +0400
all: update on first run
commit 0aa4e78f03bf3819425accb468ce59e747506ef3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Dec 29 02:47:30 2022 +0400
all: move some code to init less
commit 68aebfa050b9965afef26653e9b699ff4aaf5b8b
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Dec 29 00:36:00 2022 +0400
WIP
commit 2c7fb97d701ac158613c5a3a4d4d35c5b79b3d59
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Dec 28 14:15:59 2022 +0400
home: imp logs
commit 4b06d089da835d6d187803bbb5ca1caf9973e2d3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Tue Dec 27 19:21:17 2022 +0400
all: fix update flag
This commit is contained in:
parent
bbdcc673a2
commit
d8d7a5c335
|
@ -42,6 +42,9 @@ See also the [v0.107.21 GitHub milestone][ms-v0.107.21].
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- `AdGuardHome --update` freezing when another instance of AdGuard Home is
|
||||||
|
running ([#4223], [#5191]).
|
||||||
|
- The `--update` flag performing an update even with the same version.
|
||||||
- Failing HTTPS redirection on saving the encryption settings ([#4898]).
|
- Failing HTTPS redirection on saving the encryption settings ([#4898]).
|
||||||
- Zeroing rules counter of erroneusly edited filtering rule lists ([#5290]).
|
- Zeroing rules counter of erroneusly edited filtering rule lists ([#5290]).
|
||||||
- Filters updating strategy, which could sometimes lead to use of broken or
|
- Filters updating strategy, which could sometimes lead to use of broken or
|
||||||
|
@ -50,6 +53,7 @@ See also the [v0.107.21 GitHub milestone][ms-v0.107.21].
|
||||||
server to stop responding ([#5251]).
|
server to stop responding ([#5251]).
|
||||||
|
|
||||||
[#4898]: https://github.com/AdguardTeam/AdGuardHome/issues/4898
|
[#4898]: https://github.com/AdguardTeam/AdGuardHome/issues/4898
|
||||||
|
[#5191]: https://github.com/AdguardTeam/AdGuardHome/issues/5191
|
||||||
[#5238]: https://github.com/AdguardTeam/AdGuardHome/issues/5238
|
[#5238]: https://github.com/AdguardTeam/AdGuardHome/issues/5238
|
||||||
[#5251]: https://github.com/AdguardTeam/AdGuardHome/issues/5251
|
[#5251]: https://github.com/AdguardTeam/AdGuardHome/issues/5251
|
||||||
[#5258]: https://github.com/AdguardTeam/AdGuardHome/issues/5258
|
[#5258]: https://github.com/AdguardTeam/AdGuardHome/issues/5258
|
||||||
|
|
|
@ -530,14 +530,14 @@ func validateBlockingMode(mode BlockingMode, blockingIPv4, blockingIPv6 net.IP)
|
||||||
// prepareInternalProxy initializes the DNS proxy that is used for internal DNS
|
// prepareInternalProxy initializes the DNS proxy that is used for internal DNS
|
||||||
// queries, such as public clients PTR resolving and updater hostname resolving.
|
// queries, such as public clients PTR resolving and updater hostname resolving.
|
||||||
func (s *Server) prepareInternalProxy() (err error) {
|
func (s *Server) prepareInternalProxy() (err error) {
|
||||||
|
srvConf := s.conf
|
||||||
conf := &proxy.Config{
|
conf := &proxy.Config{
|
||||||
CacheEnabled: true,
|
CacheEnabled: true,
|
||||||
CacheSizeBytes: 4096,
|
CacheSizeBytes: 4096,
|
||||||
UpstreamConfig: s.conf.UpstreamConfig,
|
UpstreamConfig: srvConf.UpstreamConfig,
|
||||||
MaxGoroutines: int(s.conf.MaxGoroutines),
|
MaxGoroutines: int(s.conf.MaxGoroutines),
|
||||||
}
|
}
|
||||||
|
|
||||||
srvConf := s.conf
|
|
||||||
setProxyUpstreamMode(
|
setProxyUpstreamMode(
|
||||||
conf,
|
conf,
|
||||||
srvConf.AllServers,
|
srvConf.AllServers,
|
||||||
|
|
|
@ -123,7 +123,7 @@ func handleUpdate(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Context.updater.Update()
|
err = Context.updater.Update(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
|
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||||
|
@ -39,17 +41,13 @@ func onConfigModified() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// initDNSServer creates an instance of the dnsforward.Server
|
// initDNS updates all the fields of the [Context] needed to initialize the DNS
|
||||||
// Please note that we must do it even if we don't start it
|
// server and initializes it at last. It also must not be called unless
|
||||||
// so that we had access to the query log and the stats
|
// [config] and [Context] are initialized.
|
||||||
func initDNSServer() (err error) {
|
func initDNS() (err error) {
|
||||||
baseDir := Context.getDataDir()
|
baseDir := Context.getDataDir()
|
||||||
|
|
||||||
var anonFunc aghnet.IPMutFunc
|
anonymizer := config.anonymizer()
|
||||||
if config.DNS.AnonymizeClientIP {
|
|
||||||
anonFunc = querylog.AnonymizeIP
|
|
||||||
}
|
|
||||||
anonymizer := aghnet.NewIPMut(anonFunc)
|
|
||||||
|
|
||||||
statsConf := stats.Config{
|
statsConf := stats.Config{
|
||||||
Filename: filepath.Join(baseDir, "stats.db"),
|
Filename: filepath.Join(baseDir, "stats.db"),
|
||||||
|
@ -82,34 +80,46 @@ func initDNSServer() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var privateNets netutil.SubnetSet
|
tlsConf := &tlsConfigSettings{}
|
||||||
switch len(config.DNS.PrivateNets) {
|
Context.tls.WriteDiskConfig(tlsConf)
|
||||||
case 0:
|
|
||||||
// Use an optimized locally-served matcher.
|
|
||||||
privateNets = netutil.SubnetSetFunc(netutil.IsLocallyServed)
|
|
||||||
case 1:
|
|
||||||
privateNets, err = netutil.ParseSubnet(config.DNS.PrivateNets[0])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("preparing the set of private subnets: %w", err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
var nets []*net.IPNet
|
|
||||||
nets, err = netutil.ParseSubnets(config.DNS.PrivateNets...)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("preparing the set of private subnets: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
privateNets = netutil.SliceSubnetSet(nets)
|
return initDNSServer(
|
||||||
|
Context.filters,
|
||||||
|
Context.stats,
|
||||||
|
Context.queryLog,
|
||||||
|
Context.dhcpServer,
|
||||||
|
anonymizer,
|
||||||
|
httpRegister,
|
||||||
|
tlsConf,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initDNSServer initializes the [context.dnsServer]. To only use the internal
|
||||||
|
// proxy, none of the arguments are required, but tlsConf 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 [Context] are initialized.
|
||||||
|
func initDNSServer(
|
||||||
|
filters *filtering.DNSFilter,
|
||||||
|
sts stats.Interface,
|
||||||
|
qlog querylog.QueryLog,
|
||||||
|
dhcpSrv dhcpd.Interface,
|
||||||
|
anonymizer *aghnet.IPMut,
|
||||||
|
httpReg aghhttp.RegisterFunc,
|
||||||
|
tlsConf *tlsConfigSettings,
|
||||||
|
) (err error) {
|
||||||
|
privateNets, err := parseSubnetSet(config.DNS.PrivateNets)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("preparing set of private subnets: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := dnsforward.DNSCreateParams{
|
p := dnsforward.DNSCreateParams{
|
||||||
DNSFilter: Context.filters,
|
DNSFilter: filters,
|
||||||
Stats: Context.stats,
|
Stats: sts,
|
||||||
QueryLog: Context.queryLog,
|
QueryLog: qlog,
|
||||||
PrivateNets: privateNets,
|
PrivateNets: privateNets,
|
||||||
Anonymizer: anonymizer,
|
Anonymizer: anonymizer,
|
||||||
LocalDomain: config.DHCP.LocalDomainName,
|
LocalDomain: config.DHCP.LocalDomainName,
|
||||||
DHCPServer: Context.dhcpServer,
|
DHCPServer: dhcpSrv,
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.dnsServer, err = dnsforward.NewServer(p)
|
Context.dnsServer, err = dnsforward.NewServer(p)
|
||||||
|
@ -120,15 +130,15 @@ func initDNSServer() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.clients.dnsServer = Context.dnsServer
|
Context.clients.dnsServer = Context.dnsServer
|
||||||
var dnsConfig dnsforward.ServerConfig
|
|
||||||
dnsConfig, err = generateServerConfig()
|
dnsConf, err := generateServerConfig(tlsConf, httpReg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
closeDNSServer()
|
closeDNSServer()
|
||||||
|
|
||||||
return fmt.Errorf("generateServerConfig: %w", err)
|
return fmt.Errorf("generateServerConfig: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Context.dnsServer.Prepare(&dnsConfig)
|
err = Context.dnsServer.Prepare(&dnsConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
closeDNSServer()
|
closeDNSServer()
|
||||||
|
|
||||||
|
@ -146,6 +156,32 @@ func initDNSServer() (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseSubnetSet parses a slice of subnets. If the slice is empty, it returns
|
||||||
|
// a subnet set that matches all locally served networks, see
|
||||||
|
// [netutil.IsLocallyServed].
|
||||||
|
func parseSubnetSet(nets []string) (s netutil.SubnetSet, err error) {
|
||||||
|
switch len(nets) {
|
||||||
|
case 0:
|
||||||
|
// Use an optimized function-based matcher.
|
||||||
|
return netutil.SubnetSetFunc(netutil.IsLocallyServed), nil
|
||||||
|
case 1:
|
||||||
|
s, err = netutil.ParseSubnet(nets[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
default:
|
||||||
|
var nets []*net.IPNet
|
||||||
|
nets, err = netutil.ParseSubnets(config.DNS.PrivateNets...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return netutil.SliceSubnetSet(nets), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func isRunning() bool {
|
func isRunning() bool {
|
||||||
return Context.dnsServer != nil && Context.dnsServer.IsRunning()
|
return Context.dnsServer != nil && Context.dnsServer.IsRunning()
|
||||||
}
|
}
|
||||||
|
@ -193,7 +229,10 @@ func ipsToUDPAddrs(ips []netip.Addr, port int) (udpAddrs []*net.UDPAddr) {
|
||||||
return udpAddrs
|
return udpAddrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
|
func generateServerConfig(
|
||||||
|
tlsConf *tlsConfigSettings,
|
||||||
|
httpReg aghhttp.RegisterFunc,
|
||||||
|
) (newConf dnsforward.ServerConfig, err error) {
|
||||||
dnsConf := config.DNS
|
dnsConf := config.DNS
|
||||||
hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()})
|
hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()})
|
||||||
newConf = dnsforward.ServerConfig{
|
newConf = dnsforward.ServerConfig{
|
||||||
|
@ -201,12 +240,10 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
|
||||||
TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port),
|
TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port),
|
||||||
FilteringConfig: dnsConf.FilteringConfig,
|
FilteringConfig: dnsConf.FilteringConfig,
|
||||||
ConfigModified: onConfigModified,
|
ConfigModified: onConfigModified,
|
||||||
HTTPRegister: httpRegister,
|
HTTPRegister: httpReg,
|
||||||
OnDNSRequest: onDNSRequest,
|
OnDNSRequest: onDNSRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConf := tlsConfigSettings{}
|
|
||||||
Context.tls.WriteDiskConfig(&tlsConf)
|
|
||||||
if tlsConf.Enabled {
|
if tlsConf.Enabled {
|
||||||
newConf.TLSConfig = tlsConf.TLSConfig
|
newConf.TLSConfig = tlsConf.TLSConfig
|
||||||
newConf.TLSConfig.ServerName = tlsConf.ServerName
|
newConf.TLSConfig.ServerName = tlsConf.ServerName
|
||||||
|
@ -224,7 +261,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlsConf.PortDNSCrypt != 0 {
|
if tlsConf.PortDNSCrypt != 0 {
|
||||||
newConf.DNSCryptConfig, err = newDNSCrypt(hosts, tlsConf)
|
newConf.DNSCryptConfig, err = newDNSCrypt(hosts, *tlsConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't wrap the error, because it's already
|
// Don't wrap the error, because it's already
|
||||||
// wrapped by newDNSCrypt.
|
// wrapped by newDNSCrypt.
|
||||||
|
@ -413,7 +450,11 @@ func startDNSServer() error {
|
||||||
|
|
||||||
func reconfigureDNSServer() (err error) {
|
func reconfigureDNSServer() (err error) {
|
||||||
var newConf dnsforward.ServerConfig
|
var newConf dnsforward.ServerConfig
|
||||||
newConf, err = generateServerConfig()
|
|
||||||
|
tlsConf := &tlsConfigSettings{}
|
||||||
|
Context.tls.WriteDiskConfig(tlsConf)
|
||||||
|
|
||||||
|
newConf, err = generateServerConfig(tlsConf, httpRegister)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("generating forwarding dns server config: %w", err)
|
return fmt.Errorf("generating forwarding dns server config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -455,6 +455,10 @@ func run(opts options, clientBuildFS fs.FS) {
|
||||||
err = setupConfig(opts)
|
err = setupConfig(opts)
|
||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
|
// TODO(e.burkov): This could be made earlier, probably as the option's
|
||||||
|
// effect.
|
||||||
|
cmdlineUpdate(opts)
|
||||||
|
|
||||||
if !Context.firstRun {
|
if !Context.firstRun {
|
||||||
// Save the updated config
|
// Save the updated config
|
||||||
err = config.write()
|
err = config.write()
|
||||||
|
@ -522,7 +526,7 @@ func run(opts options, clientBuildFS fs.FS) {
|
||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
if !Context.firstRun {
|
if !Context.firstRun {
|
||||||
err = initDNSServer()
|
err = initDNS()
|
||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
Context.tls.start()
|
Context.tls.start()
|
||||||
|
@ -543,20 +547,24 @@ func run(opts options, clientBuildFS fs.FS) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(a.garipov): This could be made much earlier and could be done on
|
|
||||||
// the first run as well, but to achieve this we need to bypass requests
|
|
||||||
// over dnsforward resolver.
|
|
||||||
cmdlineUpdate(opts)
|
|
||||||
|
|
||||||
Context.web.Start()
|
Context.web.Start()
|
||||||
|
|
||||||
// wait indefinitely for other go-routines to complete their job
|
// wait indefinitely for other go-routines to complete their job
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *configuration) anonymizer() (ipmut *aghnet.IPMut) {
|
||||||
|
var anonFunc aghnet.IPMutFunc
|
||||||
|
if c.DNS.AnonymizeClientIP {
|
||||||
|
anonFunc = querylog.AnonymizeIP
|
||||||
|
}
|
||||||
|
|
||||||
|
return aghnet.NewIPMut(anonFunc)
|
||||||
|
}
|
||||||
|
|
||||||
// startMods initializes and starts the DNS server after installation.
|
// startMods initializes and starts the DNS server after installation.
|
||||||
func startMods() error {
|
func startMods() (err error) {
|
||||||
err := initDNSServer()
|
err = initDNS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -927,8 +935,8 @@ func getHTTPProxy(_ *http.Request) (*url.URL, error) {
|
||||||
|
|
||||||
// jsonError is a generic JSON error response.
|
// jsonError is a generic JSON error response.
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): Merge together with the implementations in .../dhcpd and
|
// TODO(a.garipov): Merge together with the implementations in [dhcpd] and other
|
||||||
// other packages after refactoring the web handler registering.
|
// packages after refactoring the web handler registering.
|
||||||
type jsonError struct {
|
type jsonError struct {
|
||||||
// Message is the error message, an opaque string.
|
// Message is the error message, an opaque string.
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
@ -940,30 +948,40 @@ func cmdlineUpdate(opts options) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("starting update")
|
// Initialize the DNS server to use the internal resolver which the updater
|
||||||
|
// needs to be able to resolve the update source hostname.
|
||||||
|
//
|
||||||
|
// TODO(e.burkov): We could probably initialize the internal resolver
|
||||||
|
// separately.
|
||||||
|
err := initDNSServer(nil, nil, nil, nil, nil, nil, &tlsConfigSettings{})
|
||||||
|
fatalOnError(err)
|
||||||
|
|
||||||
if Context.firstRun {
|
log.Info("cmdline update: performing update")
|
||||||
log.Info("update not allowed on first run")
|
|
||||||
|
|
||||||
os.Exit(0)
|
updater := Context.updater
|
||||||
}
|
info, err := updater.VersionInfo(true)
|
||||||
|
|
||||||
_, err := Context.updater.VersionInfo(true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
vcu := Context.updater.VersionCheckURL()
|
vcu := updater.VersionCheckURL()
|
||||||
log.Error("getting version info from %s: %s", vcu, err)
|
log.Error("getting version info from %s: %s", vcu, err)
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if Context.updater.NewVersion() == "" {
|
if info.NewVersion == version.Version() {
|
||||||
log.Info("no updates available")
|
log.Info("no updates available")
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Context.updater.Update()
|
err = updater.Update(Context.firstRun)
|
||||||
fatalOnError(err)
|
fatalOnError(err)
|
||||||
|
|
||||||
|
err = restartService()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("restarting service: %s", err)
|
||||||
|
log.Info("AdGuard Home was not installed as a service. " +
|
||||||
|
"Please restart running instances of AdGuardHome manually.")
|
||||||
|
}
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,7 +229,7 @@ var cmdLineOpts = []cmdLineOpt{{
|
||||||
updateNoValue: func(o options) (options, error) { o.performUpdate = true; return o, nil },
|
updateNoValue: func(o options) (options, error) { o.performUpdate = true; return o, nil },
|
||||||
effect: nil,
|
effect: nil,
|
||||||
serialize: func(o options) (val string, ok bool) { return "", o.performUpdate },
|
serialize: func(o options) (val string, ok bool) { return "", o.performUpdate },
|
||||||
description: "Update application and exit.",
|
description: "Update the current binary and restart the service in case it's installed.",
|
||||||
longName: "update",
|
longName: "update",
|
||||||
shortName: "",
|
shortName: "",
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -159,6 +159,38 @@ func sendSigReload() {
|
||||||
log.Debug("service: sent signal to pid %d", pid)
|
log.Debug("service: sent signal to pid %d", pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// restartService restarts the service. It returns error if the service is not
|
||||||
|
// running.
|
||||||
|
func restartService() (err error) {
|
||||||
|
// Call chooseSystem explicitly to introduce OpenBSD support for service
|
||||||
|
// package. It's a noop for other GOOS values.
|
||||||
|
chooseSystem()
|
||||||
|
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting current directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svcConfig := &service.Config{
|
||||||
|
Name: serviceName,
|
||||||
|
DisplayName: serviceDisplayName,
|
||||||
|
Description: serviceDescription,
|
||||||
|
WorkingDirectory: pwd,
|
||||||
|
}
|
||||||
|
configureService(svcConfig)
|
||||||
|
|
||||||
|
var s service.Service
|
||||||
|
if s, err = service.New(&program{}, svcConfig); err != nil {
|
||||||
|
return fmt.Errorf("initializing service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = svcAction(s, "restart"); err != nil {
|
||||||
|
return fmt.Errorf("restarting service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// handleServiceControlAction one of the possible control actions:
|
// handleServiceControlAction one of the possible control actions:
|
||||||
//
|
//
|
||||||
// - install: Installs a service/daemon.
|
// - install: Installs a service/daemon.
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// chooseSystem checks the current system detected and substitutes it with local
|
||||||
|
// implementation if needed.
|
||||||
func chooseSystem() {
|
func chooseSystem() {
|
||||||
sys := service.ChosenSystem()
|
sys := service.ChosenSystem()
|
||||||
// By default, package service uses the SysV system if it cannot detect
|
// By default, package service uses the SysV system if it cannot detect
|
||||||
|
|
|
@ -30,6 +30,8 @@ import (
|
||||||
// sysVersion is the version of local service.System interface implementation.
|
// sysVersion is the version of local service.System interface implementation.
|
||||||
const sysVersion = "openbsd-runcom"
|
const sysVersion = "openbsd-runcom"
|
||||||
|
|
||||||
|
// chooseSystem checks the current system detected and substitutes it with local
|
||||||
|
// implementation if needed.
|
||||||
func chooseSystem() {
|
func chooseSystem() {
|
||||||
service.ChooseSystem(openbsdSystem{})
|
service.ChooseSystem(openbsdSystem{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,7 +180,7 @@ func withRecovered(orig *error) {
|
||||||
// type check
|
// type check
|
||||||
var _ Interface = (*StatsCtx)(nil)
|
var _ Interface = (*StatsCtx)(nil)
|
||||||
|
|
||||||
// Start implements the Interface interface for *StatsCtx.
|
// Start implements the [Interface] interface for *StatsCtx.
|
||||||
func (s *StatsCtx) Start() {
|
func (s *StatsCtx) Start() {
|
||||||
s.initWeb()
|
s.initWeb()
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ func (u *Updater) VersionInfo(forceRecheck bool) (vi VersionInfo, err error) {
|
||||||
return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", vcu, err)
|
return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", vcu, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.prevCheckTime = time.Now()
|
u.prevCheckTime = now
|
||||||
u.prevCheckResult, u.prevCheckError = u.parseVersionResponse(body)
|
u.prevCheckResult, u.prevCheckError = u.parseVersionResponse(body)
|
||||||
|
|
||||||
return u.prevCheckResult, u.prevCheckError
|
return u.prevCheckResult, u.prevCheckError
|
||||||
|
|
|
@ -104,49 +104,58 @@ func NewUpdater(conf *Config) *Updater {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update performs the auto-update.
|
// Update performs the auto-update. It returns an error if the update failed.
|
||||||
func (u *Updater) Update() (err error) {
|
// If firstRun is true, it assumes the configuration file doesn't exist.
|
||||||
|
func (u *Updater) Update(firstRun bool) (err error) {
|
||||||
u.mu.Lock()
|
u.mu.Lock()
|
||||||
defer u.mu.Unlock()
|
defer u.mu.Unlock()
|
||||||
|
|
||||||
log.Info("updater: updating")
|
log.Info("updater: updating")
|
||||||
defer func() { log.Info("updater: finished; errors: %v", err) }()
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
log.Error("updater: failed: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Info("updater: finished")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
execPath, err := os.Executable()
|
execPath, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("getting executable path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = u.prepare(execPath)
|
err = u.prepare(execPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("preparing: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer u.clean()
|
defer u.clean()
|
||||||
|
|
||||||
err = u.downloadPackageFile(u.packageURL, u.packageName)
|
err = u.downloadPackageFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("downloading package file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = u.unpack()
|
err = u.unpack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("unpacking: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = u.check()
|
if !firstRun {
|
||||||
if err != nil {
|
err = u.check()
|
||||||
return err
|
if err != nil {
|
||||||
|
return fmt.Errorf("checking config: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = u.backup()
|
err = u.backup(firstRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("making backup: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = u.replace()
|
err = u.replace()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("replacing: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -204,6 +213,7 @@ func (u *Updater) prepare(exePath string) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unpack extracts the files from the downloaded archive.
|
||||||
func (u *Updater) unpack() error {
|
func (u *Updater) unpack() error {
|
||||||
var err error
|
var err error
|
||||||
_, pkgNameOnly := filepath.Split(u.packageURL)
|
_, pkgNameOnly := filepath.Split(u.packageURL)
|
||||||
|
@ -228,38 +238,48 @@ func (u *Updater) unpack() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check returns an error if the configuration file couldn't be used with the
|
||||||
|
// version of AdGuard Home just downloaded.
|
||||||
func (u *Updater) check() error {
|
func (u *Updater) check() error {
|
||||||
log.Debug("updater: checking configuration")
|
log.Debug("updater: checking configuration")
|
||||||
|
|
||||||
err := copyFile(u.confName, filepath.Join(u.updateDir, "AdGuardHome.yaml"))
|
err := copyFile(u.confName, filepath.Join(u.updateDir, "AdGuardHome.yaml"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("copyFile() failed: %w", err)
|
return fmt.Errorf("copyFile() failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(u.updateExeName, "--check-config")
|
cmd := exec.Command(u.updateExeName, "--check-config")
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
||||||
return fmt.Errorf("exec.Command(): %s %d", err, cmd.ProcessState.ExitCode())
|
return fmt.Errorf("exec.Command(): %s %d", err, cmd.ProcessState.ExitCode())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Updater) backup() 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")
|
log.Debug("updater: backing up current configuration")
|
||||||
_ = os.Mkdir(u.backupDir, 0o755)
|
_ = os.Mkdir(u.backupDir, 0o755)
|
||||||
err := copyFile(u.confName, filepath.Join(u.backupDir, "AdGuardHome.yaml"))
|
if !firstRun {
|
||||||
if err != nil {
|
err = copyFile(u.confName, filepath.Join(u.backupDir, "AdGuardHome.yaml"))
|
||||||
return fmt.Errorf("copyFile() failed: %w", err)
|
if err != nil {
|
||||||
|
return fmt.Errorf("copyFile() failed: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wd := u.workDir
|
wd := u.workDir
|
||||||
err = copySupportingFiles(u.unpackedFiles, wd, u.backupDir)
|
err = copySupportingFiles(u.unpackedFiles, wd, u.backupDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %s",
|
return fmt.Errorf("copySupportingFiles(%s, %s) failed: %s", wd, u.backupDir, err)
|
||||||
wd, u.backupDir, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// replace moves the current executable with the updated one and also copies the
|
||||||
|
// supporting files.
|
||||||
func (u *Updater) replace() error {
|
func (u *Updater) replace() error {
|
||||||
err := copySupportingFiles(u.unpackedFiles, u.updateDir, u.workDir)
|
err := copySupportingFiles(u.unpackedFiles, u.updateDir, u.workDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -287,6 +307,7 @@ func (u *Updater) replace() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clean removes the temporary directory itself and all it's contents.
|
||||||
func (u *Updater) clean() {
|
func (u *Updater) clean() {
|
||||||
_ = os.RemoveAll(u.updateDir)
|
_ = os.RemoveAll(u.updateDir)
|
||||||
}
|
}
|
||||||
|
@ -297,9 +318,9 @@ func (u *Updater) clean() {
|
||||||
const MaxPackageFileSize = 32 * 1024 * 1024
|
const MaxPackageFileSize = 32 * 1024 * 1024
|
||||||
|
|
||||||
// Download package file and save it to disk
|
// Download package file and save it to disk
|
||||||
func (u *Updater) downloadPackageFile(url, filename string) (err error) {
|
func (u *Updater) downloadPackageFile() (err error) {
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
resp, err = u.client.Get(url)
|
resp, err = u.client.Get(u.packageURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("http request failed: %w", err)
|
return fmt.Errorf("http request failed: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -321,7 +342,7 @@ func (u *Updater) downloadPackageFile(url, filename string) (err error) {
|
||||||
_ = os.Mkdir(u.updateDir, 0o755)
|
_ = os.Mkdir(u.updateDir, 0o755)
|
||||||
|
|
||||||
log.Debug("updater: saving package to file")
|
log.Debug("updater: saving package to file")
|
||||||
err = os.WriteFile(filename, body, 0o644)
|
err = os.WriteFile(u.packageName, body, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("os.WriteFile() failed: %w", err)
|
return fmt.Errorf("os.WriteFile() failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,10 +136,10 @@ func TestUpdate(t *testing.T) {
|
||||||
u.packageURL = fakeURL.String()
|
u.packageURL = fakeURL.String()
|
||||||
|
|
||||||
require.NoError(t, u.prepare(exePath))
|
require.NoError(t, u.prepare(exePath))
|
||||||
require.NoError(t, u.downloadPackageFile(u.packageURL, u.packageName))
|
require.NoError(t, u.downloadPackageFile())
|
||||||
require.NoError(t, u.unpack())
|
require.NoError(t, u.unpack())
|
||||||
// require.NoError(t, u.check())
|
// require.NoError(t, u.check())
|
||||||
require.NoError(t, u.backup())
|
require.NoError(t, u.backup(false))
|
||||||
require.NoError(t, u.replace())
|
require.NoError(t, u.replace())
|
||||||
|
|
||||||
u.clean()
|
u.clean()
|
||||||
|
@ -215,10 +215,10 @@ func TestUpdateWindows(t *testing.T) {
|
||||||
u.packageURL = fakeURL.String()
|
u.packageURL = fakeURL.String()
|
||||||
|
|
||||||
require.NoError(t, u.prepare(exePath))
|
require.NoError(t, u.prepare(exePath))
|
||||||
require.NoError(t, u.downloadPackageFile(u.packageURL, u.packageName))
|
require.NoError(t, u.downloadPackageFile())
|
||||||
require.NoError(t, u.unpack())
|
require.NoError(t, u.unpack())
|
||||||
// assert.Nil(t, u.check())
|
// assert.Nil(t, u.check())
|
||||||
require.NoError(t, u.backup())
|
require.NoError(t, u.backup(false))
|
||||||
require.NoError(t, u.replace())
|
require.NoError(t, u.replace())
|
||||||
|
|
||||||
u.clean()
|
u.clean()
|
||||||
|
|
Loading…
Reference in New Issue