Pull request 1879: nextapi-opts
Merge in DNS/adguard-home from nextapi-opts to master
Squashed commit of the following:
commit 01f27e374785f47f41470126ddc17cf447e84dd7
Merge: 17d3b06e0 371261b2c
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Tue Jun 20 13:43:08 2023 +0300
Merge branch 'master' into nextapi-opts
commit 17d3b06e0551908b06be67233181ce4067b6c1cc
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Tue Jun 20 13:15:31 2023 +0300
next: imp chlog
commit 19d5ea9db0ee077c55c9f1d54a07a3c6b56b4e2e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Tue Jun 20 12:58:48 2023 +0300
cmd: typo
commit 082ad5b5fc634a1533127e428680ccf55c0811ab
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Mon Jun 19 22:49:16 2023 +0300
cmd: imp api, docs, names
commit a49b3cbcc591f36530306c2e08cf1ea7dc8f6d30
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date: Mon Jun 19 17:13:50 2023 +0300
next/cmd: add opt parsing
This commit is contained in:
parent
371261b2c6
commit
2902f030be
|
@ -0,0 +1,38 @@
|
||||||
|
# AdGuard Home v0.108.0 Changelog DRAFT
|
||||||
|
|
||||||
|
This changelog should be merged into the main one once the next API matures
|
||||||
|
enough.
|
||||||
|
|
||||||
|
## [v0.108.0] - TODO
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The ability to log to stderr using `--logFile=stderr`.
|
||||||
|
- The new `--web-addr` flag to set the Web UI address in a `host:port` form.
|
||||||
|
- `SIGHUP` now reloads all configuration from the configuration file ([#5676]).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
#### New HTTP API
|
||||||
|
|
||||||
|
**TODO(a.garipov):** Describe the new API and add a link to the new OpenAPI doc.
|
||||||
|
|
||||||
|
#### Other changes
|
||||||
|
|
||||||
|
- `-h` is now an alias for `--help` instead of the removed `--host`, see below.
|
||||||
|
Use `--web-addr=host:port` to set an address on which to serve the Web UI.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Inconsistent application of `--work-dir/-w` ([#2902]).
|
||||||
|
- The order of `-v/--verbose` and `--version` being significant ([#2893]).
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- The deprecated `--no-mem-optimization` and `--no-etc-hosts` flags.
|
||||||
|
- `--host` and `-p/--port` flags. Use `--web-addr=host:port` to set an address
|
||||||
|
on which to serve the Web UI. `-h` is now an alias for `--help`, see above.
|
||||||
|
|
||||||
|
[#2893]: https://github.com/AdguardTeam/AdGuardHome/issues/2893
|
||||||
|
[#2902]: https://github.com/AdguardTeam/AdGuardHome/issues/2902
|
||||||
|
[#5676]: https://github.com/AdguardTeam/AdGuardHome/issues/5676
|
|
@ -17,20 +17,31 @@ import (
|
||||||
|
|
||||||
// Main is the entry point of AdGuard Home.
|
// Main is the entry point of AdGuard Home.
|
||||||
func Main(frontend fs.FS) {
|
func Main(frontend fs.FS) {
|
||||||
// Initial Configuration
|
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// TODO(a.garipov): Set up logging.
|
// Initial Configuration
|
||||||
|
|
||||||
|
cmdName := os.Args[0]
|
||||||
|
opts, err := parseOptions(cmdName, os.Args[1:])
|
||||||
|
exitCode, needExit := processOptions(opts, cmdName, err)
|
||||||
|
if needExit {
|
||||||
|
os.Exit(exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setLog(opts)
|
||||||
|
check(err)
|
||||||
|
|
||||||
log.Info("starting adguard home, version %s, pid %d", version.Version(), os.Getpid())
|
log.Info("starting adguard home, version %s, pid %d", version.Version(), os.Getpid())
|
||||||
|
|
||||||
|
if opts.workDir != "" {
|
||||||
|
log.Info("changing working directory to %q", opts.workDir)
|
||||||
|
err = os.Chdir(opts.workDir)
|
||||||
|
check(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Web Service
|
// Web Service
|
||||||
|
|
||||||
// TODO(a.garipov): Set up configuration file name.
|
confMgr, err := configmgr.New(opts.confFile, frontend, start)
|
||||||
const confFile = "AdGuardHome.1.yaml"
|
|
||||||
|
|
||||||
confMgr, err := configmgr.New(confFile, frontend, start)
|
|
||||||
check(err)
|
check(err)
|
||||||
|
|
||||||
web := confMgr.Web()
|
web := confMgr.Web()
|
||||||
|
@ -42,7 +53,7 @@ func Main(frontend fs.FS) {
|
||||||
check(err)
|
check(err)
|
||||||
|
|
||||||
sigHdlr := newSignalHandler(
|
sigHdlr := newSignalHandler(
|
||||||
confFile,
|
opts.confFile,
|
||||||
frontend,
|
frontend,
|
||||||
start,
|
start,
|
||||||
web,
|
web,
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// syslogServiceName is the name of the AdGuard Home service used for writing
|
||||||
|
// logs to the system log.
|
||||||
|
const syslogServiceName = "AdGuardHome"
|
||||||
|
|
||||||
|
// setLog sets up the text logging.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Add parameters from configuration file.
|
||||||
|
func setLog(opts *options) (err error) {
|
||||||
|
switch opts.confFile {
|
||||||
|
case "stdout":
|
||||||
|
log.SetOutput(os.Stdout)
|
||||||
|
case "stderr":
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
case "syslog":
|
||||||
|
err = aghos.ConfigureSyslog(syslogServiceName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("initializing syslog: %w", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO(a.garipov): Use the path.
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.verbose {
|
||||||
|
log.SetLevel(log.DEBUG)
|
||||||
|
log.Debug("verbose logging enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,403 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/version"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// options contains all command-line options for the AdGuardHome(.exe) binary.
|
||||||
|
type options struct {
|
||||||
|
// confFile is the path to the configuration file.
|
||||||
|
confFile string
|
||||||
|
|
||||||
|
// logFile is the path to the log file. Special values:
|
||||||
|
//
|
||||||
|
// - "stdout": Write to stdout (the default).
|
||||||
|
// - "stderr": Write to stderr.
|
||||||
|
// - "syslog": Write to the system log.
|
||||||
|
logFile string
|
||||||
|
|
||||||
|
// pidFile is the path to the file where to store the PID.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Use.
|
||||||
|
pidFile string
|
||||||
|
|
||||||
|
// serviceAction is the service control action to perform:
|
||||||
|
//
|
||||||
|
// - "install": Installs AdGuard Home as a system service.
|
||||||
|
// - "uninstall": Uninstalls it.
|
||||||
|
// - "status": Prints the service status.
|
||||||
|
// - "start": Starts the previously installed service.
|
||||||
|
// - "stop": Stops the previously installed service.
|
||||||
|
// - "restart": Restarts the previously installed service.
|
||||||
|
// - "reload": Reloads the configuration.
|
||||||
|
// - "run": This is a special command that is not supposed to be used
|
||||||
|
// directly it is specified when we register a service, and it indicates
|
||||||
|
// to the app that it is being run as a service.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Use.
|
||||||
|
serviceAction string
|
||||||
|
|
||||||
|
// workDir is the path to the working directory. It is applied before all
|
||||||
|
// other configuration is read, so all relative paths are relative to it.
|
||||||
|
workDir string
|
||||||
|
|
||||||
|
// webAddrs contains the addresses on which to serve the web UI.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Use.
|
||||||
|
webAddrs []netip.AddrPort
|
||||||
|
|
||||||
|
// checkConfig, if true, instructs AdGuard Home to check the configuration
|
||||||
|
// file and exit with a corresponding exit code.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Use.
|
||||||
|
checkConfig bool
|
||||||
|
|
||||||
|
// disableUpdate, if true, prevents AdGuard Home from automatically checking
|
||||||
|
// for updates.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Use.
|
||||||
|
disableUpdate bool
|
||||||
|
|
||||||
|
// glinetMode enables the GL-Inet compatibility mode.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Use.
|
||||||
|
glinetMode bool
|
||||||
|
|
||||||
|
// help, if true, instructs AdGuard Home to print the command-line option
|
||||||
|
// help message and quit with a successful exit-code.
|
||||||
|
help bool
|
||||||
|
|
||||||
|
// localFrontend, if true, instructs AdGuard Home to use the local frontend
|
||||||
|
// directory instead of the files compiled into the binary.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Use.
|
||||||
|
localFrontend bool
|
||||||
|
|
||||||
|
// performUpdate, if true, instructs AdGuard Home to update the current
|
||||||
|
// binary and restart the service in case it's installed.
|
||||||
|
//
|
||||||
|
// TODO(a.garipov): Use.
|
||||||
|
performUpdate bool
|
||||||
|
|
||||||
|
// verbose, if true, instructs AdGuard Home to enable verbose logging.
|
||||||
|
verbose bool
|
||||||
|
|
||||||
|
// version, if true, instructs AdGuard Home to print the version to stdout
|
||||||
|
// and quit with a successful exit-code. If verbose is also true, print a
|
||||||
|
// more detailed version description.
|
||||||
|
version bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indexes to help with the [commandLineOptions] initialization.
|
||||||
|
const (
|
||||||
|
confFileIdx = iota
|
||||||
|
logFileIdx
|
||||||
|
pidFileIdx
|
||||||
|
serviceActionIdx
|
||||||
|
workDirIdx
|
||||||
|
webAddrsIdx
|
||||||
|
checkConfigIdx
|
||||||
|
disableUpdateIdx
|
||||||
|
glinetModeIdx
|
||||||
|
helpIdx
|
||||||
|
localFrontend
|
||||||
|
performUpdateIdx
|
||||||
|
verboseIdx
|
||||||
|
versionIdx
|
||||||
|
)
|
||||||
|
|
||||||
|
// commandLineOption contains information about a command-line option: its long
|
||||||
|
// and, if there is one, short forms, the value type, the description, and the
|
||||||
|
// default value.
|
||||||
|
type commandLineOption struct {
|
||||||
|
defaultValue any
|
||||||
|
description string
|
||||||
|
long string
|
||||||
|
short string
|
||||||
|
valueType string
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandLineOptions are all command-line options currently supported by
|
||||||
|
// AdGuard Home.
|
||||||
|
var commandLineOptions = []*commandLineOption{
|
||||||
|
confFileIdx: {
|
||||||
|
// TODO(a.garipov): Remove the ".1" when the new code is ready.
|
||||||
|
defaultValue: "AdGuardHome.1.yaml",
|
||||||
|
description: "Path to the config file.",
|
||||||
|
long: "config",
|
||||||
|
short: "c",
|
||||||
|
valueType: "path",
|
||||||
|
},
|
||||||
|
|
||||||
|
logFileIdx: {
|
||||||
|
defaultValue: "stdout",
|
||||||
|
description: `Path to log file. Special values include "stdout", "stderr", and "syslog".`,
|
||||||
|
long: "logfile",
|
||||||
|
short: "l",
|
||||||
|
valueType: "path",
|
||||||
|
},
|
||||||
|
|
||||||
|
pidFileIdx: {
|
||||||
|
defaultValue: "",
|
||||||
|
description: "Path to the file where to store the PID.",
|
||||||
|
long: "pidfile",
|
||||||
|
short: "",
|
||||||
|
valueType: "path",
|
||||||
|
},
|
||||||
|
|
||||||
|
serviceActionIdx: {
|
||||||
|
defaultValue: "",
|
||||||
|
description: `Service control action: "status", "install" (as a service), ` +
|
||||||
|
`"uninstall" (as a service), "start", "stop", "restart", "reload" (configuration).`,
|
||||||
|
long: "service",
|
||||||
|
short: "s",
|
||||||
|
valueType: "action",
|
||||||
|
},
|
||||||
|
|
||||||
|
workDirIdx: {
|
||||||
|
defaultValue: "",
|
||||||
|
description: `Path to the working directory. ` +
|
||||||
|
`It is applied before all other configuration is read, ` +
|
||||||
|
`so all relative paths are relative to it.`,
|
||||||
|
long: "work-dir",
|
||||||
|
short: "w",
|
||||||
|
valueType: "path",
|
||||||
|
},
|
||||||
|
|
||||||
|
webAddrsIdx: {
|
||||||
|
defaultValue: []netip.AddrPort(nil),
|
||||||
|
description: `Address(es) to serve the web UI on, in the host:port format. ` +
|
||||||
|
`Can be used multiple times.`,
|
||||||
|
long: "web-addr",
|
||||||
|
short: "",
|
||||||
|
valueType: "host:port",
|
||||||
|
},
|
||||||
|
|
||||||
|
checkConfigIdx: {
|
||||||
|
defaultValue: false,
|
||||||
|
description: "Check configuration and quit.",
|
||||||
|
long: "check-config",
|
||||||
|
short: "",
|
||||||
|
valueType: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
disableUpdateIdx: {
|
||||||
|
defaultValue: false,
|
||||||
|
description: "Disable automatic update checking.",
|
||||||
|
long: "no-check-update",
|
||||||
|
short: "",
|
||||||
|
valueType: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
glinetModeIdx: {
|
||||||
|
defaultValue: false,
|
||||||
|
description: "Run in GL-Inet compatibility mode.",
|
||||||
|
long: "glinet",
|
||||||
|
short: "",
|
||||||
|
valueType: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
helpIdx: {
|
||||||
|
defaultValue: false,
|
||||||
|
description: "Print this help message and quit.",
|
||||||
|
long: "help",
|
||||||
|
short: "h",
|
||||||
|
valueType: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
localFrontend: {
|
||||||
|
defaultValue: false,
|
||||||
|
description: "Use local frontend directories.",
|
||||||
|
long: "local-frontend",
|
||||||
|
short: "",
|
||||||
|
valueType: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
performUpdateIdx: {
|
||||||
|
defaultValue: false,
|
||||||
|
description: "Update the current binary and restart the service in case it's installed.",
|
||||||
|
long: "update",
|
||||||
|
short: "",
|
||||||
|
valueType: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
verboseIdx: {
|
||||||
|
defaultValue: false,
|
||||||
|
description: "Enable verbose logging.",
|
||||||
|
long: "verbose",
|
||||||
|
short: "v",
|
||||||
|
valueType: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
versionIdx: {
|
||||||
|
defaultValue: false,
|
||||||
|
description: `Print the version to stdout and quit. ` +
|
||||||
|
`Print a more detailed version description with -v.`,
|
||||||
|
long: "version",
|
||||||
|
short: "",
|
||||||
|
valueType: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseOptions parses the command-line options for AdGuardHome.
|
||||||
|
func parseOptions(cmdName string, args []string) (opts *options, err error) {
|
||||||
|
flags := flag.NewFlagSet(cmdName, flag.ContinueOnError)
|
||||||
|
|
||||||
|
opts = &options{}
|
||||||
|
for i, fieldPtr := range []any{
|
||||||
|
confFileIdx: &opts.confFile,
|
||||||
|
logFileIdx: &opts.logFile,
|
||||||
|
pidFileIdx: &opts.pidFile,
|
||||||
|
serviceActionIdx: &opts.serviceAction,
|
||||||
|
workDirIdx: &opts.workDir,
|
||||||
|
webAddrsIdx: &opts.webAddrs,
|
||||||
|
checkConfigIdx: &opts.checkConfig,
|
||||||
|
disableUpdateIdx: &opts.disableUpdate,
|
||||||
|
glinetModeIdx: &opts.glinetMode,
|
||||||
|
helpIdx: &opts.help,
|
||||||
|
localFrontend: &opts.localFrontend,
|
||||||
|
performUpdateIdx: &opts.performUpdate,
|
||||||
|
verboseIdx: &opts.verbose,
|
||||||
|
versionIdx: &opts.version,
|
||||||
|
} {
|
||||||
|
addOption(flags, fieldPtr, commandLineOptions[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
flags.Usage = func() { usage(cmdName, os.Stderr) }
|
||||||
|
|
||||||
|
err = flags.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap the error, because it's informative enough as is.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addOption adds the command-line option described by o to flags using fieldPtr
|
||||||
|
// as the pointer to the value.
|
||||||
|
func addOption(flags *flag.FlagSet, fieldPtr any, o *commandLineOption) {
|
||||||
|
switch fieldPtr := fieldPtr.(type) {
|
||||||
|
case *string:
|
||||||
|
flags.StringVar(fieldPtr, o.long, o.defaultValue.(string), o.description)
|
||||||
|
if o.short != "" {
|
||||||
|
flags.StringVar(fieldPtr, o.short, o.defaultValue.(string), o.description)
|
||||||
|
}
|
||||||
|
case *[]netip.AddrPort:
|
||||||
|
flags.Func(o.long, o.description, func(s string) (err error) {
|
||||||
|
addr, err := netip.ParseAddrPort(s)
|
||||||
|
if err != nil {
|
||||||
|
// Don't wrap the error, because it's informative enough as is.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*fieldPtr = append(*fieldPtr, addr)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
case *bool:
|
||||||
|
flags.BoolVar(fieldPtr, o.long, o.defaultValue.(bool), o.description)
|
||||||
|
if o.short != "" {
|
||||||
|
flags.BoolVar(fieldPtr, o.short, o.defaultValue.(bool), o.description)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unexpected field pointer type %T", fieldPtr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// usage prints a usage message similar to the one printed by package flag but
|
||||||
|
// taking long vs. short versions into account as well as using more informative
|
||||||
|
// value hints.
|
||||||
|
func usage(cmdName string, output io.Writer) {
|
||||||
|
options := slices.Clone(commandLineOptions)
|
||||||
|
slices.SortStableFunc(options, func(a, b *commandLineOption) (sortsBefore bool) {
|
||||||
|
return a.long < b.long
|
||||||
|
})
|
||||||
|
|
||||||
|
b := &strings.Builder{}
|
||||||
|
_, _ = fmt.Fprintf(b, "Usage of %s:\n", cmdName)
|
||||||
|
|
||||||
|
for _, o := range options {
|
||||||
|
writeUsageLine(b, o)
|
||||||
|
|
||||||
|
// Use four spaces before the tab to trigger good alignment for both 4-
|
||||||
|
// and 8-space tab stops.
|
||||||
|
if shouldIncludeDefault(o.defaultValue) {
|
||||||
|
_, _ = fmt.Fprintf(b, " \t%s (Default value: %q)\n", o.description, o.defaultValue)
|
||||||
|
} else {
|
||||||
|
_, _ = fmt.Fprintf(b, " \t%s\n", o.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = io.WriteString(output, b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldIncludeDefault returns true if this default value should be printed.
|
||||||
|
func shouldIncludeDefault(v any) (ok bool) {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case bool:
|
||||||
|
return v
|
||||||
|
case string:
|
||||||
|
return v != ""
|
||||||
|
default:
|
||||||
|
return v == nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeUsageLine writes the usage line for the provided command-line option.
|
||||||
|
func writeUsageLine(b *strings.Builder, o *commandLineOption) {
|
||||||
|
if o.short == "" {
|
||||||
|
if o.valueType == "" {
|
||||||
|
_, _ = fmt.Fprintf(b, " --%s\n", o.long)
|
||||||
|
} else {
|
||||||
|
_, _ = fmt.Fprintf(b, " --%s=%s\n", o.long, o.valueType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.valueType == "" {
|
||||||
|
_, _ = fmt.Fprintf(b, " --%s/-%s\n", o.long, o.short)
|
||||||
|
} else {
|
||||||
|
_, _ = fmt.Fprintf(b, " --%[1]s=%[3]s/-%[2]s %[3]s\n", o.long, o.short, o.valueType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processOptions decides if AdGuard Home should exit depending on the results
|
||||||
|
// of command-line option parsing.
|
||||||
|
func processOptions(
|
||||||
|
opts *options,
|
||||||
|
cmdName string,
|
||||||
|
parseErr error,
|
||||||
|
) (exitCode int, needExit bool) {
|
||||||
|
if parseErr != nil {
|
||||||
|
// Assume that usage has already been printed.
|
||||||
|
return 2, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.help {
|
||||||
|
usage(cmdName, os.Stdout)
|
||||||
|
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.version {
|
||||||
|
if opts.verbose {
|
||||||
|
fmt.Println(version.Verbose())
|
||||||
|
} else {
|
||||||
|
fmt.Printf("AdGuard Home %s\n", version.Version())
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ func New(
|
||||||
frontend fs.FS,
|
frontend fs.FS,
|
||||||
start time.Time,
|
start time.Time,
|
||||||
) (m *Manager, err error) {
|
) (m *Manager, err error) {
|
||||||
defer func() { err = errors.Annotate(err, "reading config") }()
|
defer func() { err = errors.Annotate(err, "reading config: %w") }()
|
||||||
|
|
||||||
conf := &config{}
|
conf := &config{}
|
||||||
f, err := os.Open(fileName)
|
f, err := os.Open(fileName)
|
||||||
|
|
Loading…
Reference in New Issue