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:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user