Pull request 2481: AGDNS-3218-rm-global-firstrun

Squashed commit of the following:

commit a4c1fd48c9ce67e381d24404e883f3bf27760cd6
Merge: 3dd245369 6dbf114ff
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Oct 6 15:34:13 2025 +0300

    Merge branch 'master' into AGDNS-3218-rm-global-firstrun

commit 3dd2453697
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Sep 29 15:52:51 2025 +0300

    home: imp code

commit 7db3d79e25
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Sep 25 15:33:19 2025 +0300

    home: fix test

commit ea4b5bf549
Merge: 6295f8fb9 394b8c529
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Sep 25 15:27:49 2025 +0300

    Merge branch 'master' into AGDNS-3218-rm-global-firstrun

commit 6295f8fb9c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Sep 25 15:27:37 2025 +0300

    home: imp code

commit eaf322f336
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Sep 23 22:02:16 2025 +0300

    home: imp code

commit a193c5c917
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Sep 23 11:01:02 2025 +0300

    home: imp code

commit 4703ce54bd
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Sep 22 14:43:00 2025 +0300

    home: imp code

commit 86a9ac2f3b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Sep 17 15:47:28 2025 +0300

    home: rm global firstrun
This commit is contained in:
Stanislav Chzhen 2025-10-06 15:48:24 +03:00
parent 6dbf114ff9
commit daa3b5d63a
8 changed files with 203 additions and 152 deletions

View File

@ -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)
}

View File

@ -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)

View File

@ -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)
}
})
}

View File

@ -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)),
)
}

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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")