Pull request 1899: nextapi-pidfile-webaddr

Squashed commit of the following:

commit 73b97b638016dd3992376c2cd7d11b2e85b2c3a4
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jun 29 18:43:05 2023 +0300

    next: use maybe; sync conf

commit 99e18b8fbfad11343a1e66f746085d54be7aafea
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jun 29 18:13:13 2023 +0300

    next: add local frontend, pidfile, webaddr
This commit is contained in:
Ainar Garipov
2023-06-29 19:10:39 +03:00
parent 39f5c50acd
commit ee8eb1d8a6
7 changed files with 230 additions and 133 deletions

View File

@@ -16,7 +16,7 @@ import (
)
// Main is the entry point of AdGuard Home.
func Main(frontend fs.FS) {
func Main(embeddedFrontend fs.FS) {
start := time.Now()
cmdName := os.Args[0]
@@ -37,7 +37,17 @@ func Main(frontend fs.FS) {
check(err)
}
confMgr, err := newConfigMgr(opts.confFile, frontend, start)
frontend, err := frontendFromOpts(opts, embeddedFrontend)
check(err)
confMgrConf := &configmgr.Config{
Frontend: frontend,
WebAddr: opts.webAddr,
Start: start,
FileName: opts.confFile,
}
confMgr, err := newConfigMgr(confMgrConf)
check(err)
web := confMgr.Web()
@@ -49,9 +59,8 @@ func Main(frontend fs.FS) {
check(err)
sigHdlr := newSignalHandler(
opts.confFile,
frontend,
start,
confMgrConf,
opts.pidFile,
web,
dns,
)
@@ -71,11 +80,11 @@ func ctxWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) {
// newConfigMgr returns a new configuration manager using defaultTimeout as the
// context timeout.
func newConfigMgr(confFile string, frontend fs.FS, start time.Time) (m *configmgr.Manager, err error) {
func newConfigMgr(c *configmgr.Config) (m *configmgr.Manager, err error) {
ctx, cancel := ctxWithDefaultTimeout()
defer cancel()
return configmgr.New(ctx, confFile, frontend, start)
return configmgr.New(ctx, c)
}
// check is a simple error-checking helper. It must only be used within Main.

View File

@@ -1,15 +1,18 @@
package cmd
import (
"encoding"
"flag"
"fmt"
"io"
"io/fs"
"net/netip"
"os"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
"github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/log"
"golang.org/x/exp/slices"
)
@@ -26,8 +29,6 @@ type options struct {
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:
@@ -50,10 +51,8 @@ type options struct {
// 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
// webAddr contains the address on which to serve the web UI.
webAddr netip.AddrPort
// checkConfig, if true, instructs AdGuard Home to check the configuration
// file, optionally print an error message to stdout, and exit with a
@@ -103,7 +102,7 @@ const (
pidFileIdx
serviceActionIdx
workDirIdx
webAddrsIdx
webAddrIdx
checkConfigIdx
disableUpdateIdx
glinetModeIdx
@@ -172,13 +171,12 @@ var commandLineOptions = []*commandLineOption{
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",
webAddrIdx: {
defaultValue: netip.AddrPort{},
description: `Address to serve the web UI on, in the host:port format.`,
long: "web-addr",
short: "",
valueType: "host:port",
},
checkConfigIdx: {
@@ -258,7 +256,7 @@ func parseOptions(cmdName string, args []string) (opts *options, err error) {
pidFileIdx: &opts.pidFile,
serviceActionIdx: &opts.serviceAction,
workDirIdx: &opts.workDir,
webAddrsIdx: &opts.webAddrs,
webAddrIdx: &opts.webAddr,
checkConfigIdx: &opts.checkConfig,
disableUpdateIdx: &opts.disableUpdate,
glinetModeIdx: &opts.glinetMode,
@@ -291,23 +289,16 @@ func addOption(flags *flag.FlagSet, fieldPtr any, o *commandLineOption) {
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)
}
case encoding.TextUnmarshaler:
flags.TextVar(fieldPtr, o.long, o.defaultValue.(encoding.TextMarshaler), o.description)
if o.short != "" {
flags.TextVar(fieldPtr, o.short, o.defaultValue.(encoding.TextMarshaler), o.description)
}
default:
panic(fmt.Errorf("unexpected field pointer type %T", fieldPtr))
}
@@ -380,13 +371,13 @@ func processOptions(
) (exitCode int, needExit bool) {
if parseErr != nil {
// Assume that usage has already been printed.
return 2, true
return statusArgumentError, true
}
if opts.help {
usage(cmdName, os.Stdout)
return 0, true
return statusSuccess, true
}
if opts.version {
@@ -396,7 +387,7 @@ func processOptions(
fmt.Printf("AdGuard Home %s\n", version.Version())
}
return 0, true
return statusSuccess, true
}
if opts.checkConfig {
@@ -404,11 +395,24 @@ func processOptions(
if err != nil {
_, _ = io.WriteString(os.Stdout, err.Error()+"\n")
return 1, true
return statusError, true
}
return 0, true
return statusSuccess, true
}
return 0, false
}
// frontendFromOpts returns the frontend to use based on the options.
func frontendFromOpts(opts *options, embeddedFrontend fs.FS) (frontend fs.FS, err error) {
const frontendSubdir = "build/static"
if opts.localFrontend {
log.Info("warning: using local frontend files")
return os.DirFS(frontendSubdir), nil
}
return fs.Sub(embeddedFrontend, frontendSubdir)
}

View File

@@ -1,29 +1,27 @@
package cmd
import (
"io/fs"
"os"
"time"
"strconv"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
"github.com/AdguardTeam/golibs/log"
"github.com/google/renameio/maybe"
)
// signalHandler processes incoming signals and shuts services down.
type signalHandler struct {
// confMgrConf contains the configuration parameters for the configuration
// manager.
confMgrConf *configmgr.Config
// signal is the channel to which OS signals are sent.
signal chan os.Signal
// confFile is the path to the configuration file.
confFile string
// frontend is the filesystem with the frontend and other statically
// compiled files.
frontend fs.FS
// start is the time at which AdGuard Home has been started.
start time.Time
// pidFile is the path to the file where to store the PID, if any.
pidFile string
// services are the services that are shut down before application exiting.
services []agh.Service
@@ -33,6 +31,8 @@ type signalHandler struct {
func (h *signalHandler) handle() {
defer log.OnPanic("signalHandler.handle")
h.writePID()
for sig := range h.signal {
log.Info("sighdlr: received signal %q", sig)
@@ -40,6 +40,8 @@ func (h *signalHandler) handle() {
h.reconfigure()
} else if aghos.IsShutdownSignal(sig) {
status := h.shutdown()
h.removePID()
log.Info("sighdlr: exiting with status %d", status)
os.Exit(status)
@@ -62,7 +64,7 @@ func (h *signalHandler) reconfigure() {
// reconfigured without the full shutdown, and the error handling is
// currently not the best.
confMgr, err := newConfigMgr(h.confFile, h.frontend, h.start)
confMgr, err := newConfigMgr(h.confMgrConf)
check(err)
web := confMgr.Web()
@@ -83,8 +85,9 @@ func (h *signalHandler) reconfigure() {
// Exit status constants.
const (
statusSuccess = 0
statusError = 1
statusSuccess = 0
statusError = 1
statusArgumentError = 2
)
// shutdown gracefully shuts down all services.
@@ -108,17 +111,15 @@ func (h *signalHandler) shutdown() (status int) {
// newSignalHandler returns a new signalHandler that shuts down svcs.
func newSignalHandler(
confFile string,
frontend fs.FS,
start time.Time,
confMgrConf *configmgr.Config,
pidFile string,
svcs ...agh.Service,
) (h *signalHandler) {
h = &signalHandler{
signal: make(chan os.Signal, 1),
confFile: confFile,
frontend: frontend,
start: start,
services: svcs,
confMgrConf: confMgrConf,
signal: make(chan os.Signal, 1),
pidFile: pidFile,
services: svcs,
}
aghos.NotifyShutdownSignal(h.signal)
@@ -126,3 +127,41 @@ func newSignalHandler(
return h
}
// writePID writes the PID to the file, if needed. Any errors are reported to
// log.
func (h *signalHandler) writePID() {
if h.pidFile == "" {
return
}
// Use 8, since most PIDs will fit.
data := make([]byte, 0, 8)
data = strconv.AppendInt(data, int64(os.Getpid()), 10)
data = append(data, '\n')
err := maybe.WriteFile(h.pidFile, data, 0o644)
if err != nil {
log.Error("sighdlr: writing pidfile: %s", err)
return
}
log.Debug("sighdlr: wrote pid to %q", h.pidFile)
}
// removePID removes the PID file, if any.
func (h *signalHandler) removePID() {
if h.pidFile == "" {
return
}
err := os.Remove(h.pidFile)
if err != nil {
log.Error("sighdlr: removing pidfile: %s", err)
return
}
log.Debug("sighdlr: removed pid at %q", h.pidFile)
}