mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2025-10-26 11:27:18 +00:00
Pull request 2481: AGDNS-3218-rm-global-firstrun
Squashed commit of the following: commit a4c1fd48c9ce67e381d24404e883f3bf27760cd6 Merge:3dd2453696dbf114ffAuthor: Stanislav Chzhen <s.chzhen@adguard.com> Date: Mon Oct 6 15:34:13 2025 +0300 Merge branch 'master' into AGDNS-3218-rm-global-firstrun commit3dd2453697Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Mon Sep 29 15:52:51 2025 +0300 home: imp code commit7db3d79e25Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Thu Sep 25 15:33:19 2025 +0300 home: fix test commitea4b5bf549Merge:6295f8fb9394b8c529Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Thu Sep 25 15:27:49 2025 +0300 Merge branch 'master' into AGDNS-3218-rm-global-firstrun commit6295f8fb9cAuthor: Stanislav Chzhen <s.chzhen@adguard.com> Date: Thu Sep 25 15:27:37 2025 +0300 home: imp code commiteaf322f336Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Tue Sep 23 22:02:16 2025 +0300 home: imp code commita193c5c917Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Tue Sep 23 11:01:02 2025 +0300 home: imp code commit4703ce54bdAuthor: Stanislav Chzhen <s.chzhen@adguard.com> Date: Mon Sep 22 14:43:00 2025 +0300 home: imp code commit86a9ac2f3bAuthor: Stanislav Chzhen <s.chzhen@adguard.com> Date: Wed Sep 17 15:47:28 2025 +0300 home: rm global firstrun
This commit is contained in:
parent
6dbf114ff9
commit
daa3b5d63a
@ -264,11 +264,11 @@ func (web *webAPI) handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusFound)
|
||||
}
|
||||
|
||||
// RegisterAuthHandlers - register handlers
|
||||
func RegisterAuthHandlers(web *webAPI) {
|
||||
globalContext.mux.Handle(
|
||||
// registerAuthHandlers registers authentication handlers.
|
||||
func (web *webAPI) registerAuthHandlers() {
|
||||
web.conf.mux.Handle(
|
||||
"/control/login",
|
||||
postInstallHandler(ensureHandler(http.MethodPost, web.handleLogin)),
|
||||
web.postInstallHandler(ensure(http.MethodPost, web.handleLogin)),
|
||||
)
|
||||
httpRegister(http.MethodGet, "/control/logout", web.handleLogout)
|
||||
}
|
||||
|
||||
@ -321,8 +321,6 @@ func authRequest(path string, c *http.Cookie, user, pass string) (r *http.Reques
|
||||
func TestAuth_ServeHTTP_firstRun(t *testing.T) {
|
||||
storeGlobals(t)
|
||||
|
||||
globalContext.firstRun = true
|
||||
|
||||
mux := http.NewServeMux()
|
||||
globalContext.mux = mux
|
||||
|
||||
@ -335,8 +333,10 @@ func TestAuth_ServeHTTP_firstRun(t *testing.T) {
|
||||
testLogger,
|
||||
nil,
|
||||
nil,
|
||||
mux,
|
||||
agh.EmptyConfigModifier{},
|
||||
false,
|
||||
true,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -484,7 +484,8 @@ func TestAuth_ServeHTTP_auth(t *testing.T) {
|
||||
|
||||
t.Cleanup(func() { auth.close(testutil.ContextWithTimeout(t, testTimeout)) })
|
||||
|
||||
globalContext.mux = http.NewServeMux()
|
||||
baseMux := http.NewServeMux()
|
||||
globalContext.mux = baseMux
|
||||
|
||||
tlsMgr, err := newTLSManager(testutil.ContextWithTimeout(t, testTimeout), &tlsManagerConfig{
|
||||
logger: testLogger,
|
||||
@ -501,17 +502,19 @@ func TestAuth_ServeHTTP_auth(t *testing.T) {
|
||||
testLogger,
|
||||
tlsMgr,
|
||||
auth,
|
||||
baseMux,
|
||||
agh.EmptyConfigModifier{},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
globalContext.web = web
|
||||
|
||||
mux := auth.middleware().Wrap(globalContext.mux)
|
||||
mux := auth.middleware().Wrap(baseMux)
|
||||
|
||||
auth.isGLiNet = true
|
||||
gliNetMw := auth.middleware().Wrap(globalContext.mux)
|
||||
gliNetMw := auth.middleware().Wrap(baseMux)
|
||||
|
||||
loginCookie := generateAuthCookie(t, mux, userName, userPassword)
|
||||
|
||||
@ -642,24 +645,28 @@ func TestAuth_ServeHTTP_logout(t *testing.T) {
|
||||
|
||||
t.Cleanup(func() { auth.close(testutil.ContextWithTimeout(t, testTimeout)) })
|
||||
|
||||
globalContext.mux = http.NewServeMux()
|
||||
baseMux := http.NewServeMux()
|
||||
globalContext.mux = baseMux
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
web, err := initWeb(ctx,
|
||||
web, err := initWeb(
|
||||
ctx,
|
||||
options{},
|
||||
nil,
|
||||
nil,
|
||||
testLogger,
|
||||
nil,
|
||||
auth,
|
||||
baseMux,
|
||||
agh.EmptyConfigModifier{},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
globalContext.web = web
|
||||
|
||||
mux := auth.middleware().Wrap(globalContext.mux)
|
||||
mux := auth.middleware().Wrap(baseMux)
|
||||
|
||||
loginCookie := generateAuthCookie(t, mux, userName, userPassword)
|
||||
|
||||
|
||||
@ -171,9 +171,13 @@ func (web *webAPI) handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// registerControlHandlers sets up HTTP handlers for various control endpoints.
|
||||
// web must not be nil.
|
||||
func registerControlHandlers(web *webAPI) {
|
||||
globalContext.mux.HandleFunc("/control/version.json", postInstall(web.handleVersionJSON))
|
||||
func (web *webAPI) registerControlHandlers() {
|
||||
mux := web.conf.mux
|
||||
|
||||
mux.Handle(
|
||||
"/control/version.json",
|
||||
web.postInstallHandler(http.HandlerFunc(web.handleVersionJSON)),
|
||||
)
|
||||
httpRegister(http.MethodPost, "/control/update", web.handleUpdate)
|
||||
|
||||
httpRegister(http.MethodGet, "/control/status", web.handleStatus)
|
||||
@ -182,23 +186,32 @@ func registerControlHandlers(web *webAPI) {
|
||||
httpRegister(http.MethodGet, "/control/profile", web.handleGetProfile)
|
||||
httpRegister(http.MethodPut, "/control/profile/update", web.handlePutProfile)
|
||||
|
||||
// No auth is necessary for DoH/DoT configurations
|
||||
globalContext.mux.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoH))
|
||||
globalContext.mux.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDoT))
|
||||
RegisterAuthHandlers(web)
|
||||
// No authentication is required for DoH/DoT configuration endpoints.
|
||||
mux.Handle(
|
||||
"/apple/doh.mobileconfig",
|
||||
web.postInstallHandler(http.HandlerFunc(handleMobileConfigDoH)),
|
||||
)
|
||||
mux.Handle(
|
||||
"/apple/dot.mobileconfig",
|
||||
web.postInstallHandler(http.HandlerFunc(handleMobileConfigDoT)),
|
||||
)
|
||||
|
||||
web.registerAuthHandlers()
|
||||
}
|
||||
|
||||
// httpRegister registers an HTTP handler.
|
||||
//
|
||||
// TODO(s.chzhen): Do not use [globalContext.mux].
|
||||
func httpRegister(method, url string, handler http.HandlerFunc) {
|
||||
if method == "" {
|
||||
// "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method
|
||||
globalContext.mux.HandleFunc(url, postInstall(handler))
|
||||
globalContext.mux.Handle(url, postInstallHandler(handler))
|
||||
return
|
||||
}
|
||||
|
||||
globalContext.mux.Handle(
|
||||
url,
|
||||
postInstallHandler(gziphandler.GzipHandler(ensureHandler(method, handler))),
|
||||
postInstallHandler(gziphandler.GzipHandler(ensure(method, handler))),
|
||||
)
|
||||
}
|
||||
|
||||
@ -207,7 +220,7 @@ func httpRegister(method, url string, handler http.HandlerFunc) {
|
||||
func ensure(
|
||||
method string,
|
||||
handler func(http.ResponseWriter, *http.Request),
|
||||
) (wrapped func(http.ResponseWriter, *http.Request)) {
|
||||
) (wrapped http.HandlerFunc) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
m := r.Method
|
||||
if m != method {
|
||||
@ -265,53 +278,20 @@ func ensureContentType(w http.ResponseWriter, r *http.Request) (ok bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
func ensurePOST(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||
return ensure(http.MethodPost, handler)
|
||||
}
|
||||
|
||||
func ensureGET(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||
return ensure(http.MethodGet, handler)
|
||||
}
|
||||
|
||||
// Bridge between http.Handler object and Go function
|
||||
type httpHandler struct {
|
||||
handler func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.handler(w, r)
|
||||
}
|
||||
|
||||
func ensureHandler(method string, handler func(http.ResponseWriter, *http.Request)) http.Handler {
|
||||
h := httpHandler{}
|
||||
h.handler = ensure(method, handler)
|
||||
return &h
|
||||
}
|
||||
|
||||
// preInstall lets the handler run only if firstRun is true, no redirects
|
||||
func preInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !globalContext.firstRun {
|
||||
// if it's not first run, don't let users access it (for example /install.html when configuration is done)
|
||||
// preInstallHandler lets the handler run only if firstRun is true; it does not
|
||||
// perform redirects.
|
||||
func (web *webAPI) preInstallHandler(handler http.Handler) (wrapped http.Handler) {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !web.conf.firstRun {
|
||||
// If it's not first run, do not allow access to install-only routes
|
||||
// (for example, /install.html once configuration is complete).
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
|
||||
return
|
||||
}
|
||||
handler(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// preInstallStruct wraps preInstall into a struct that can be returned as an interface where necessary
|
||||
type preInstallHandlerStruct struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func (p *preInstallHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
preInstall(p.handler.ServeHTTP)(w, r)
|
||||
}
|
||||
|
||||
// preInstallHandler returns http.Handler interface for preInstall wrapper
|
||||
func preInstallHandler(handler http.Handler) http.Handler {
|
||||
return &preInstallHandlerStruct{handler}
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// handleHTTPSRedirect redirects the request to HTTPS, if needed, and adds some
|
||||
@ -401,34 +381,52 @@ func httpsURL(u *url.URL, host string, portHTTPS uint16) (redirectURL *url.URL)
|
||||
}
|
||||
}
|
||||
|
||||
// postInstall lets the handler to run only if firstRun is false. Otherwise, it
|
||||
// redirects to /install.html. It also enforces HTTPS if it is enabled and
|
||||
// configured and sets appropriate access control headers.
|
||||
func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// postInstallHandler lets the handler to run only if firstRun is false.
|
||||
// Otherwise, it redirects to /install.html. It also enforces HTTPS if it is
|
||||
// enabled and configured and sets appropriate access control headers.
|
||||
//
|
||||
// TODO(s.chzhen): Replace with [web.postInstall] after fixing its usage in
|
||||
// [httpRegister], which is called by [dhcpd.Create] before [web] is
|
||||
// initialized.
|
||||
func postInstallHandler(handler http.Handler) (wrapped http.Handler) {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if globalContext.web == nil {
|
||||
aghhttp.Error(r, w, http.StatusTooEarly, "it is not initialized yet")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
path := r.URL.Path
|
||||
if globalContext.firstRun && !strings.HasPrefix(path, "/install.") &&
|
||||
if globalContext.web.conf.firstRun &&
|
||||
!strings.HasPrefix(path, "/install.") &&
|
||||
!strings.HasPrefix(path, "/assets/") {
|
||||
http.Redirect(w, r, "install.html", http.StatusFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
proceed := handleHTTPSRedirect(w, r)
|
||||
if proceed {
|
||||
handler(w, r)
|
||||
if handleHTTPSRedirect(w, r) {
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type postInstallHandlerStruct struct {
|
||||
handler http.Handler
|
||||
}
|
||||
// postInstallHandler lets the handler to run only if firstRun is false.
|
||||
// Otherwise, it redirects to /install.html. It also enforces HTTPS if it is
|
||||
// enabled and configured and sets appropriate access control headers.
|
||||
func (web *webAPI) postInstallHandler(handler http.Handler) (wrapped http.Handler) {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
if web.conf.firstRun &&
|
||||
!strings.HasPrefix(path, "/install.") &&
|
||||
!strings.HasPrefix(path, "/assets/") {
|
||||
http.Redirect(w, r, "install.html", http.StatusFound)
|
||||
|
||||
func (p *postInstallHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
postInstall(p.handler.ServeHTTP)(w, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func postInstallHandler(handler http.Handler) http.Handler {
|
||||
return &postInstallHandlerStruct{handler}
|
||||
if handleHTTPSRedirect(w, r) {
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -445,10 +445,28 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
|
||||
web.finalizeInstall(ctx, w, r, req, restartHTTP)
|
||||
}
|
||||
|
||||
// finalizeInstall completes first-run setup by applying user-provided settings.
|
||||
// w, r, and req must not be nil.
|
||||
func (web *webAPI) finalizeInstall(
|
||||
ctx context.Context,
|
||||
w http.ResponseWriter,
|
||||
r *http.Request,
|
||||
req *applyConfigReq,
|
||||
restartHTTP bool,
|
||||
) {
|
||||
var err error
|
||||
curConfig := &configuration{}
|
||||
copyInstallSettings(curConfig, config)
|
||||
|
||||
globalContext.firstRun = false
|
||||
defer func() {
|
||||
if err != nil {
|
||||
copyInstallSettings(config, curConfig)
|
||||
}
|
||||
}()
|
||||
|
||||
config.DNS.BindHosts = []netip.Addr{req.DNS.IP}
|
||||
config.DNS.Port = req.DNS.Port
|
||||
config.Filtering.Logger = web.baseLogger.With(slogutil.KeyPrefix, "filtering")
|
||||
@ -462,8 +480,6 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
err = web.auth.addUser(ctx, u, req.Password)
|
||||
if err != nil {
|
||||
globalContext.firstRun = true
|
||||
copyInstallSettings(config, curConfig)
|
||||
aghhttp.Error(r, w, http.StatusUnprocessableEntity, "%s", err)
|
||||
|
||||
return
|
||||
@ -475,8 +491,6 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
|
||||
// functions potentially restart the HTTPS server.
|
||||
err = startMods(ctx, web.baseLogger, web.tlsManager, web.confModifier)
|
||||
if err != nil {
|
||||
globalContext.firstRun = true
|
||||
copyInstallSettings(config, curConfig)
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err)
|
||||
|
||||
return
|
||||
@ -484,8 +498,6 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
|
||||
|
||||
err = config.write(web.tlsManager, web.auth)
|
||||
if err != nil {
|
||||
globalContext.firstRun = true
|
||||
copyInstallSettings(config, curConfig)
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't write config: %s", err)
|
||||
|
||||
return
|
||||
@ -494,7 +506,7 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request
|
||||
web.conf.firstRun = false
|
||||
web.conf.BindAddr = netip.AddrPortFrom(req.Web.IP, req.Web.Port)
|
||||
|
||||
registerControlHandlers(web)
|
||||
web.registerControlHandlers()
|
||||
|
||||
aghhttp.OK(w)
|
||||
|
||||
@ -581,8 +593,20 @@ func startMods(
|
||||
return nil
|
||||
}
|
||||
|
||||
// registerInstallHandlers registers install handlers.
|
||||
func (web *webAPI) registerInstallHandlers() {
|
||||
globalContext.mux.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses)))
|
||||
globalContext.mux.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig)))
|
||||
globalContext.mux.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure)))
|
||||
mux := web.conf.mux
|
||||
|
||||
mux.Handle(
|
||||
"/control/install/get_addresses",
|
||||
web.preInstallHandler(ensure(http.MethodGet, web.handleInstallGetAddresses)),
|
||||
)
|
||||
mux.Handle(
|
||||
"/control/install/check_config",
|
||||
web.preInstallHandler(ensure(http.MethodPost, web.handleInstallCheckConfig)),
|
||||
)
|
||||
mux.Handle(
|
||||
"/control/install/configure",
|
||||
web.preInstallHandler(ensure(http.MethodPost, web.handleInstallConfigure)),
|
||||
)
|
||||
}
|
||||
|
||||
@ -58,21 +58,9 @@ type homeContext struct {
|
||||
dnsServer *dnsforward.Server // DNS module
|
||||
dhcpServer dhcpd.Interface // DHCP module
|
||||
|
||||
// auth stores web user information and handles authentication.
|
||||
//
|
||||
// TODO(s.chzhen): Remove once it is no longer called from different
|
||||
// modules. See [onConfigModified].
|
||||
auth *auth
|
||||
|
||||
filters *filtering.DNSFilter // DNS filtering module
|
||||
web *webAPI // Web (HTTP, HTTPS) module
|
||||
|
||||
// tls contains the current configuration and state of TLS encryption.
|
||||
//
|
||||
// TODO(s.chzhen): Remove once it is no longer called from different
|
||||
// modules. See [onConfigModified].
|
||||
tls *tlsManager
|
||||
|
||||
// etcHosts contains IP-hostname mappings taken from the OS-specific hosts
|
||||
// configuration files, for example /etc/hosts.
|
||||
etcHosts *aghnet.HostsContainer
|
||||
@ -90,10 +78,6 @@ type homeContext struct {
|
||||
workDir string // Location of our directory, used to protect against CWD being somewhere else
|
||||
pidFileName string // PID file name. Empty if no PID file was created.
|
||||
controlLock sync.Mutex
|
||||
|
||||
// firstRun, if true, tells AdGuard Home to only start the web interface
|
||||
// service, and only serve the first-run APIs.
|
||||
firstRun bool
|
||||
}
|
||||
|
||||
// getDataDir returns path to the directory where we store databases and filters
|
||||
@ -160,9 +144,12 @@ func Main(clientBuildFS fs.FS) {
|
||||
|
||||
// setupContext initializes [globalContext] fields. It also reads and upgrades
|
||||
// config file if necessary. baseLogger must not be nil.
|
||||
func setupContext(ctx context.Context, baseLogger *slog.Logger, opts options) (err error) {
|
||||
globalContext.firstRun = detectFirstRun()
|
||||
|
||||
func setupContext(
|
||||
ctx context.Context,
|
||||
baseLogger *slog.Logger,
|
||||
opts options,
|
||||
isFirstRun bool,
|
||||
) (err error) {
|
||||
globalContext.mux = http.NewServeMux()
|
||||
|
||||
if !opts.noEtcHosts {
|
||||
@ -173,7 +160,7 @@ func setupContext(ctx context.Context, baseLogger *slog.Logger, opts options) (e
|
||||
}
|
||||
}
|
||||
|
||||
if globalContext.firstRun {
|
||||
if isFirstRun {
|
||||
log.Info("This is the first time AdGuard Home is launched")
|
||||
checkNetworkPermissions()
|
||||
|
||||
@ -562,8 +549,8 @@ func isUpdateEnabled(
|
||||
}
|
||||
}
|
||||
|
||||
// initWeb initializes the web module. upd, baseLogger, tlsMgr, and auth must
|
||||
// not be nil.
|
||||
// initWeb initializes the web module. upd, baseLogger, tlsMgr, auth, and mux
|
||||
// must not be nil.
|
||||
func initWeb(
|
||||
ctx context.Context,
|
||||
opts options,
|
||||
@ -572,8 +559,10 @@ func initWeb(
|
||||
baseLogger *slog.Logger,
|
||||
tlsMgr *tlsManager,
|
||||
auth *auth,
|
||||
mux *http.ServeMux,
|
||||
confModifier agh.ConfigModifier,
|
||||
isCustomUpdURL bool,
|
||||
isFirstRun bool,
|
||||
) (web *webAPI, err error) {
|
||||
logger := baseLogger.With(slogutil.KeyPrefix, "webapi")
|
||||
|
||||
@ -601,6 +590,7 @@ func initWeb(
|
||||
confModifier: confModifier,
|
||||
tlsManager: tlsMgr,
|
||||
auth: auth,
|
||||
mux: mux,
|
||||
|
||||
clientFS: clientFS,
|
||||
|
||||
@ -612,7 +602,7 @@ func initWeb(
|
||||
|
||||
defaultWebPort: webPort,
|
||||
|
||||
firstRun: globalContext.firstRun,
|
||||
firstRun: isFirstRun,
|
||||
disableUpdate: disableUpdate,
|
||||
runningAsService: opts.runningAsService,
|
||||
serveHTTP3: config.DNS.ServeHTTP3,
|
||||
@ -696,7 +686,9 @@ func run(
|
||||
|
||||
aghtls.Init(ctx, slogLogger.With(slogutil.KeyPrefix, "aghtls"))
|
||||
|
||||
err = setupContext(ctx, slogLogger, opts)
|
||||
isFirstRun := detectFirstRun()
|
||||
|
||||
err = setupContext(ctx, slogLogger, opts, isFirstRun)
|
||||
fatalOnError(err)
|
||||
|
||||
err = configureOS(config)
|
||||
@ -728,7 +720,6 @@ func run(
|
||||
confModifier.Apply(ctx)
|
||||
}
|
||||
|
||||
globalContext.tls = tlsMgr
|
||||
confModifier.setTLSManager(tlsMgr)
|
||||
|
||||
err = setupDNSFilteringConf(ctx, slogLogger, config.Filtering, tlsMgr, confModifier)
|
||||
@ -747,9 +738,9 @@ func run(
|
||||
|
||||
// TODO(e.burkov): This could be made earlier, probably as the option's
|
||||
// effect.
|
||||
cmdlineUpdate(ctx, updLogger, opts, upd, tlsMgr)
|
||||
cmdlineUpdate(ctx, updLogger, opts, upd, tlsMgr, isFirstRun)
|
||||
|
||||
if !globalContext.firstRun {
|
||||
if !isFirstRun {
|
||||
// Save the updated config.
|
||||
err = config.write(nil, nil)
|
||||
fatalOnError(err)
|
||||
@ -766,7 +757,6 @@ func run(
|
||||
auth, err := initUsers(ctx, slogLogger, opts.glinetMode)
|
||||
fatalOnError(err)
|
||||
|
||||
globalContext.auth = auth
|
||||
confModifier.setAuth(auth)
|
||||
|
||||
web, err := initWeb(
|
||||
@ -777,8 +767,10 @@ func run(
|
||||
slogLogger,
|
||||
tlsMgr,
|
||||
auth,
|
||||
globalContext.mux,
|
||||
confModifier,
|
||||
isCustomURL,
|
||||
isFirstRun,
|
||||
)
|
||||
fatalOnError(err)
|
||||
|
||||
@ -790,7 +782,7 @@ func run(
|
||||
statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&globalContext, config)
|
||||
fatalOnError(err)
|
||||
|
||||
if !globalContext.firstRun {
|
||||
if !isFirstRun {
|
||||
err = initDNS(ctx, slogLogger, tlsMgr, confModifier, statsDir, querylogDir)
|
||||
fatalOnError(err)
|
||||
|
||||
@ -1176,14 +1168,15 @@ type jsonError struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// cmdlineUpdate updates current application and exits. l and tlsMgr must not
|
||||
// be nil.
|
||||
// cmdlineUpdate updates current application and exits. l, upd, and tlsMgr must
|
||||
// not be nil.
|
||||
func cmdlineUpdate(
|
||||
ctx context.Context,
|
||||
l *slog.Logger,
|
||||
opts options,
|
||||
upd *updater.Updater,
|
||||
tlsMgr *tlsManager,
|
||||
isFirstRun bool,
|
||||
) {
|
||||
if !opts.performUpdate {
|
||||
return
|
||||
@ -1212,7 +1205,7 @@ func cmdlineUpdate(
|
||||
os.Exit(osutil.ExitCodeSuccess)
|
||||
}
|
||||
|
||||
err = upd.Update(ctx, globalContext.firstRun)
|
||||
err = upd.Update(ctx, isFirstRun)
|
||||
fatalOnError(err)
|
||||
|
||||
err = restartService(ctx, l)
|
||||
|
||||
@ -71,7 +71,8 @@ func TestWeb_HandleGetProfile(t *testing.T) {
|
||||
|
||||
t.Cleanup(func() { auth.close(testutil.ContextWithTimeout(t, testTimeout)) })
|
||||
|
||||
globalContext.mux = http.NewServeMux()
|
||||
baseMux := http.NewServeMux()
|
||||
globalContext.mux = baseMux
|
||||
|
||||
tlsMgr, err := newTLSManager(testutil.ContextWithTimeout(t, testTimeout), &tlsManagerConfig{
|
||||
logger: testLogger,
|
||||
@ -87,14 +88,16 @@ func TestWeb_HandleGetProfile(t *testing.T) {
|
||||
testLogger,
|
||||
tlsMgr,
|
||||
auth,
|
||||
baseMux,
|
||||
agh.EmptyConfigModifier{},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
globalContext.web = web
|
||||
|
||||
mux := auth.middleware().Wrap(globalContext.mux)
|
||||
mux := auth.middleware().Wrap(baseMux)
|
||||
|
||||
require.True(t, t.Run("userless", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
@ -138,7 +141,19 @@ func TestWeb_HandlePutProfile(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := testutil.ContextWithTimeout(t, testTimeout)
|
||||
web, err := initWeb(ctx, options{}, nil, nil, testLogger, nil, nil, confModifier, false)
|
||||
web, err := initWeb(
|
||||
ctx,
|
||||
options{},
|
||||
nil,
|
||||
nil,
|
||||
testLogger,
|
||||
nil,
|
||||
nil,
|
||||
mux,
|
||||
confModifier,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
globalContext.web = web
|
||||
|
||||
@ -153,7 +153,6 @@ func storeGlobals(tb testing.TB) {
|
||||
prefGLFilePrefix := glFilePrefix
|
||||
storage := globalContext.clients.storage
|
||||
dnsServer := globalContext.dnsServer
|
||||
firstRun := globalContext.firstRun
|
||||
mux := globalContext.mux
|
||||
web := globalContext.web
|
||||
|
||||
@ -162,7 +161,6 @@ func storeGlobals(tb testing.TB) {
|
||||
glFilePrefix = prefGLFilePrefix
|
||||
globalContext.clients.storage = storage
|
||||
globalContext.dnsServer = dnsServer
|
||||
globalContext.firstRun = firstRun
|
||||
globalContext.mux = mux
|
||||
globalContext.web = web
|
||||
})
|
||||
@ -295,6 +293,28 @@ func assertCertSerialNumber(tb testing.TB, conf *tlsConfigSettings, wantSN int64
|
||||
assert.Equal(tb, wantSN, cert.Leaf.SerialNumber.Int64())
|
||||
}
|
||||
|
||||
// initEmptyWeb returns an initialized *webAPI with zero values and no-op mocks.
|
||||
func initEmptyWeb(tb testing.TB) (web *webAPI) {
|
||||
tb.Helper()
|
||||
|
||||
web, err := initWeb(
|
||||
testutil.ContextWithTimeout(tb, testTimeout),
|
||||
options{},
|
||||
nil,
|
||||
nil,
|
||||
testLogger,
|
||||
nil,
|
||||
nil,
|
||||
http.NewServeMux(),
|
||||
agh.EmptyConfigModifier{},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return web
|
||||
}
|
||||
|
||||
func TestTLSManager_Reload(t *testing.T) {
|
||||
storeGlobals(t)
|
||||
|
||||
@ -343,9 +363,7 @@ func TestTLSManager_Reload(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
web, err := initWeb(ctx, options{}, nil, nil, testLogger, nil, nil, agh.EmptyConfigModifier{}, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
web := initEmptyWeb(t)
|
||||
m.setWebAPI(web)
|
||||
|
||||
conf := m.config()
|
||||
@ -415,9 +433,7 @@ func TestValidateTLSSettings(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
web, err := initWeb(ctx, options{}, nil, nil, testLogger, nil, nil, agh.EmptyConfigModifier{}, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
web := initEmptyWeb(t)
|
||||
m.setWebAPI(web)
|
||||
|
||||
tcpLn, err := net.Listen("tcp", ":0")
|
||||
@ -519,9 +535,7 @@ func TestTLSManager_HandleTLSValidate(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
web, err := initWeb(ctx, options{}, nil, nil, testLogger, nil, nil, agh.EmptyConfigModifier{}, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
web := initEmptyWeb(t)
|
||||
m.setWebAPI(web)
|
||||
|
||||
setts := &tlsConfigSettingsExt{
|
||||
@ -612,9 +626,7 @@ func TestTLSManager_HandleTLSConfigure(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
web, err := initWeb(ctx, options{}, nil, nil, testLogger, nil, nil, agh.EmptyConfigModifier{}, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
web := initEmptyWeb(t)
|
||||
m.setWebAPI(web)
|
||||
|
||||
conf := m.config()
|
||||
|
||||
@ -63,6 +63,10 @@ type webConfig struct {
|
||||
// be nil.
|
||||
auth *auth
|
||||
|
||||
// mux is the default *http.ServeMux, the same as [globalContext.mux]. It
|
||||
// must not be nil.
|
||||
mux *http.ServeMux
|
||||
|
||||
clientFS fs.FS
|
||||
|
||||
// BindAddr is the binding address with port for plain HTTP web interface.
|
||||
@ -162,11 +166,9 @@ func newWebAPI(ctx context.Context, conf *webConfig) (w *webAPI) {
|
||||
|
||||
clientFS := http.FileServer(http.FS(conf.clientFS))
|
||||
|
||||
mux := conf.mux
|
||||
// if not configured, redirect / to /install.html, otherwise redirect /install.html to /
|
||||
globalContext.mux.Handle(
|
||||
"/",
|
||||
withMiddlewares(clientFS, gziphandler.GzipHandler, postInstallHandler),
|
||||
)
|
||||
mux.Handle("/", withMiddlewares(clientFS, gziphandler.GzipHandler, w.postInstallHandler))
|
||||
|
||||
// add handlers for /install paths, we only need them when we're not configured yet
|
||||
if conf.firstRun {
|
||||
@ -175,10 +177,10 @@ func newWebAPI(ctx context.Context, conf *webConfig) (w *webAPI) {
|
||||
"This is the first launch of AdGuard Home, redirecting everything to /install.html",
|
||||
)
|
||||
|
||||
globalContext.mux.Handle("/install.html", preInstallHandler(clientFS))
|
||||
mux.Handle("/install.html", w.preInstallHandler(clientFS))
|
||||
w.registerInstallHandlers()
|
||||
} else {
|
||||
registerControlHandlers(w)
|
||||
w.registerControlHandlers()
|
||||
}
|
||||
|
||||
w.httpsServer.cond = sync.NewCond(&w.httpsServer.condLock)
|
||||
@ -241,7 +243,7 @@ func (web *webAPI) start(ctx context.Context) {
|
||||
|
||||
// Use an h2c handler to support unencrypted HTTP/2, e.g. for proxies.
|
||||
hdlr := h2c.NewHandler(
|
||||
withMiddlewares(globalContext.mux, limitRequestBody),
|
||||
withMiddlewares(web.conf.mux, limitRequestBody),
|
||||
&http2.Server{},
|
||||
)
|
||||
|
||||
@ -336,7 +338,7 @@ func (web *webAPI) tlsServerLoop(ctx context.Context) {
|
||||
|
||||
// TODO(a.garipov): Remove other logs like this in other code.
|
||||
logMw := httputil.NewLogMiddleware(logger, slog.LevelDebug)
|
||||
hdlr := logMw.Wrap(withMiddlewares(globalContext.mux, limitRequestBody))
|
||||
hdlr := logMw.Wrap(withMiddlewares(web.conf.mux, limitRequestBody))
|
||||
|
||||
web.httpsServer.server = &http.Server{
|
||||
Addr: addr,
|
||||
@ -381,7 +383,7 @@ func (web *webAPI) mustStartHTTP3(ctx context.Context, address string) {
|
||||
CipherSuites: web.tlsManager.customCipherIDs,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
Handler: web.auth.middleware().Wrap(withMiddlewares(globalContext.mux, limitRequestBody)),
|
||||
Handler: web.auth.middleware().Wrap(withMiddlewares(web.conf.mux, limitRequestBody)),
|
||||
}
|
||||
|
||||
web.logger.DebugContext(ctx, "starting http/3 server")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user