From 1611057852417c7d355d4289c2d394e9e7bc35cf Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 16 Apr 2019 18:36:10 +0300 Subject: [PATCH 01/18] * setRlimit(): move OS-specific code to separate files --- app.go | 12 ------------ os_unix.go | 21 +++++++++++++++++++++ os_windows.go | 5 +++++ 3 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 os_unix.go create mode 100644 os_windows.go diff --git a/app.go b/app.go index 0ba9a765..dacf1034 100644 --- a/app.go +++ b/app.go @@ -311,18 +311,6 @@ func enableTLS13() { } } -// Set user-specified limit of how many fd's we can use -// https://github.com/AdguardTeam/AdGuardHome/issues/659 -func setRlimit(val uint) { - var rlim syscall.Rlimit - rlim.Max = uint64(val) - rlim.Cur = uint64(val) - err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim) - if err != nil { - log.Error("Setrlimit() failed: %v", err) - } -} - func cleanup() { log.Info("Stopping AdGuard Home") diff --git a/os_unix.go b/os_unix.go new file mode 100644 index 00000000..12a918c8 --- /dev/null +++ b/os_unix.go @@ -0,0 +1,21 @@ +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +package main + +import ( + "syscall" + + "github.com/AdguardTeam/golibs/log" +) + +// Set user-specified limit of how many fd's we can use +// https://github.com/AdguardTeam/AdGuardHome/issues/659 +func setRlimit(val uint) { + var rlim syscall.Rlimit + rlim.Max = uint64(val) + rlim.Cur = uint64(val) + err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim) + if err != nil { + log.Error("Setrlimit() failed: %v", err) + } +} diff --git a/os_windows.go b/os_windows.go new file mode 100644 index 00000000..1155e04b --- /dev/null +++ b/os_windows.go @@ -0,0 +1,5 @@ +package main + +// Set user-specified limit of how many fd's we can use +func setRlimit(val uint) { +} From e4b53db55846dd74bbcfe7f3abc66f7481a1c577 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Mon, 1 Apr 2019 12:22:54 +0300 Subject: [PATCH 02/18] + app: unix, windows: require root user on first launch --- app.go | 56 ++++++++++++++++++++++++++++++++++++++++++--------- os_unix.go | 6 ++++++ os_windows.go | 23 +++++++++++++++++++++ 3 files changed, 76 insertions(+), 9 deletions(-) diff --git a/app.go b/app.go index dacf1034..acb463de 100644 --- a/app.go +++ b/app.go @@ -1,16 +1,20 @@ package main import ( + "bufio" "crypto/tls" "fmt" + "io" "io/ioutil" "net" "net/http" "os" + "os/exec" "os/signal" "path/filepath" "runtime" "strconv" + "strings" "sync" "syscall" @@ -45,15 +49,6 @@ func main() { return } - signalChannel := make(chan os.Signal) - signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT) - go func() { - <-signalChannel - cleanup() - cleanupAlways() - os.Exit(0) - }() - // run the protection run(args) } @@ -83,6 +78,18 @@ func run(args options) { } config.firstRun = detectFirstRun() + if config.firstRun { + requireAdminRights() + } + + signalChannel := make(chan os.Signal) + signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT) + go func() { + <-signalChannel + cleanup() + cleanupAlways() + os.Exit(0) + }() // Do the upgrade if necessary err := upgradeConfig() @@ -228,6 +235,37 @@ func run(args options) { } } +// Check if the current user has root (administrator) rights +// and if not, ask and try to run as root +func requireAdminRights() { + admin, _ := haveAdminRights() + if admin { + return + } + + if runtime.GOOS == "windows" { + log.Fatal("This is the first launch of AdGuard Home. You must run it as Administrator.") + + } else { + log.Error("This is the first launch of AdGuard Home. You must run it as root.") + + _, _ = io.WriteString(os.Stdout, "Do you want to start AdGuard Home as root user? [y/n] ") + stdin := bufio.NewReader(os.Stdin) + buf, _ := stdin.ReadString('\n') + buf = strings.TrimSpace(buf) + if buf != "y" { + os.Exit(1) + } + + cmd := exec.Command("sudo", os.Args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + _ = cmd.Run() + os.Exit(1) + } +} + // Write PID to a file func writePIDFile(fn string) bool { data := fmt.Sprintf("%d", os.Getpid()) diff --git a/os_unix.go b/os_unix.go index 12a918c8..9baa357d 100644 --- a/os_unix.go +++ b/os_unix.go @@ -3,6 +3,7 @@ package main import ( + "os" "syscall" "github.com/AdguardTeam/golibs/log" @@ -19,3 +20,8 @@ func setRlimit(val uint) { log.Error("Setrlimit() failed: %v", err) } } + +// Check if the current user has root (administrator) rights +func haveAdminRights() (bool, error) { + return os.Getuid() == 0, nil +} diff --git a/os_windows.go b/os_windows.go index 1155e04b..e847ccce 100644 --- a/os_windows.go +++ b/os_windows.go @@ -1,5 +1,28 @@ package main +import "golang.org/x/sys/windows" + // Set user-specified limit of how many fd's we can use func setRlimit(val uint) { } + +func haveAdminRights() (bool, error) { + var token windows.Token + h, _ := windows.GetCurrentProcess() + err := windows.OpenProcessToken(h, windows.TOKEN_QUERY, &token) + if err != nil { + return false, err + } + + info := make([]byte, 4) + var returnedLen uint32 + err = windows.GetTokenInformation(token, windows.TokenElevation, &info[0], uint32(len(info)), &returnedLen) + token.Close() + if err != nil { + return false, err + } + if info[0] == 0 { + return false, nil + } + return true, nil +} From 00c128f0a4deff119f93d02de03fa3fc47c54ec1 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 2 Apr 2019 17:21:12 +0300 Subject: [PATCH 03/18] * control: /install/get_addresses: don't check if ports are available * always use port 80 --- control.go | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/control.go b/control.go index 8ded7372..7fd78cde 100644 --- a/control.go +++ b/control.go @@ -970,15 +970,9 @@ func handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) { } } -type ipport struct { - IP string `json:"ip,omitempty"` - Port int `json:"port"` - Warning string `json:"warning"` -} - type firstRunData struct { - Web ipport `json:"web"` - DNS ipport `json:"dns"` + WebPort int `json:"web_port"` + DNSPort int `json:"dns_port"` Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` Interfaces map[string]interface{} `json:"interfaces"` @@ -987,19 +981,8 @@ type firstRunData struct { func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) { log.Tracef("%s %v", r.Method, r.URL) data := firstRunData{} - - // find out if port 80 is available -- if not, fall back to 3000 - if checkPortAvailable("", 80) == nil { - data.Web.Port = 80 - } else { - data.Web.Port = 3000 - } - - // find out if port 53 is available -- if not, show a big warning - data.DNS.Port = 53 - if checkPacketPortAvailable("", 53) != nil { - data.DNS.Warning = "Port 53 is not available for binding -- this will make DNS clients unable to contact AdGuard Home." - } + data.WebPort = 80 + data.DNSPort = 53 ifaces, err := getValidNetInterfacesForWeb() if err != nil { From 133dd75ec3c9d14dbaf629b99b6f660e05632e01 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 2 Apr 2019 19:15:37 +0300 Subject: [PATCH 04/18] + control: add /install/check_config handler --- control.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/control.go b/control.go index 7fd78cde..18c981fa 100644 --- a/control.go +++ b/control.go @@ -1003,6 +1003,58 @@ func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) { } } +type checkConfigReqEnt struct { + Port int `json:"port"` + IP string `json:"ip"` + Autofix bool `json:"autofix"` +} +type checkConfigReq struct { + Web checkConfigReqEnt `json:"web"` + DNS checkConfigReqEnt `json:"dns"` +} + +type checkConfigRespEnt struct { + Status string `json:"status"` + CanAutofix bool `json:"can_autofix"` +} +type checkConfigResp struct { + Web checkConfigRespEnt `json:"web"` + DNS checkConfigRespEnt `json:"dns"` +} + +// Check if ports are available, respond with results +func handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) { + log.Tracef("%s %v", r.Method, r.URL) + reqData := checkConfigReq{} + respData := checkConfigResp{} + err := json.NewDecoder(r.Body).Decode(&reqData) + if err != nil { + httpError(w, http.StatusBadRequest, "Failed to parse 'check_config' JSON data: %s", err) + return + } + + if reqData.Web.Port != 0 && reqData.Web.Port != config.BindPort { + err = checkPortAvailable(reqData.Web.IP, reqData.Web.Port) + if err != nil { + respData.Web.Status = fmt.Sprintf("%v", err) + } + } + + if reqData.DNS.Port != 0 && reqData.DNS.Port != config.DNS.Port { + err = checkPacketPortAvailable(reqData.DNS.IP, reqData.DNS.Port) + if err != nil { + respData.DNS.Status = fmt.Sprintf("%v", err) + } + } + + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(respData) + if err != nil { + httpError(w, http.StatusInternalServerError, "Unable to marshal JSON: %s", err) + return + } +} + func handleInstallConfigure(w http.ResponseWriter, r *http.Request) { log.Tracef("%s %v", r.Method, r.URL) newSettings := firstRunData{} @@ -1082,6 +1134,7 @@ func handleDOH(w http.ResponseWriter, r *http.Request) { // ------------------------ func registerInstallHandlers() { http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(handleInstallGetAddresses))) + http.HandleFunc("/control/install/check_config", preInstall(ensurePOST(handleInstallCheckConfig))) http.HandleFunc("/control/install/configure", preInstall(ensurePOST(handleInstallConfigure))) } From 4ca24b77075969bc90611ca8feb5cd6d564359a2 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 2 Apr 2019 19:21:59 +0300 Subject: [PATCH 05/18] * control: /install/configure: refactor --- control.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/control.go b/control.go index 18c981fa..a62b3b6d 100644 --- a/control.go +++ b/control.go @@ -973,11 +973,10 @@ func handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) { type firstRunData struct { WebPort int `json:"web_port"` DNSPort int `json:"dns_port"` - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` Interfaces map[string]interface{} `json:"interfaces"` } +// Get initial installation settings func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) { log.Tracef("%s %v", r.Method, r.URL) data := firstRunData{} @@ -1055,12 +1054,24 @@ func handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) { } } +type applyConfigReqEnt struct { + IP string `json:"ip"` + Port int `json:"port"` +} +type applyConfigReq struct { + Web applyConfigReqEnt `json:"web"` + DNS applyConfigReqEnt `json:"dns"` + Username string `json:"username"` + Password string `json:"password"` +} + +// Apply new configuration, start DNS server, restart Web server func handleInstallConfigure(w http.ResponseWriter, r *http.Request) { log.Tracef("%s %v", r.Method, r.URL) - newSettings := firstRunData{} + newSettings := applyConfigReq{} err := json.NewDecoder(r.Body).Decode(&newSettings) if err != nil { - httpError(w, http.StatusBadRequest, "Failed to parse new config json: %s", err) + httpError(w, http.StatusBadRequest, "Failed to parse 'configure' JSON: %s", err) return } @@ -1074,14 +1085,16 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) { if restartHTTP { err = checkPortAvailable(newSettings.Web.IP, newSettings.Web.Port) if err != nil { - httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s due to %s", net.JoinHostPort(newSettings.Web.IP, strconv.Itoa(newSettings.Web.Port)), err) + httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s due to %s", + net.JoinHostPort(newSettings.Web.IP, strconv.Itoa(newSettings.Web.Port)), err) return } } err = checkPacketPortAvailable(newSettings.DNS.IP, newSettings.DNS.Port) if err != nil { - httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s due to %s", net.JoinHostPort(newSettings.DNS.IP, strconv.Itoa(newSettings.DNS.Port)), err) + httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s due to %s", + net.JoinHostPort(newSettings.DNS.IP, strconv.Itoa(newSettings.DNS.Port)), err) return } From 8d936b5756cb93741a4162edb778b86ac1f78e72 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 2 Apr 2019 19:16:00 +0300 Subject: [PATCH 06/18] + control: /install/check_config: Check and deactivate DNSStubListener --- control.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++- helpers.go | 26 ++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/control.go b/control.go index a62b3b6d..08747e7c 100644 --- a/control.go +++ b/control.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "os" + "os/exec" "sort" "strconv" "strings" @@ -1039,8 +1040,25 @@ func handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) { } } - if reqData.DNS.Port != 0 && reqData.DNS.Port != config.DNS.Port { + if reqData.DNS.Port != 0 { err = checkPacketPortAvailable(reqData.DNS.IP, reqData.DNS.Port) + + if errorIsAddrInUse(err) { + canAutofix := checkDNSStubListener() + if canAutofix && reqData.DNS.Autofix { + + err = disableDNSStubListener() + if err != nil { + log.Error("Couldn't disable DNSStubListener: %s", err) + } + + err = checkPacketPortAvailable(reqData.DNS.IP, reqData.DNS.Port) + canAutofix = false + } + + respData.DNS.CanAutofix = canAutofix + } + if err != nil { respData.DNS.Status = fmt.Sprintf("%v", err) } @@ -1054,6 +1072,56 @@ func handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) { } } +// Check if DNSStubListener is active +func checkDNSStubListener() bool { + cmd := exec.Command("systemctl", "is-enabled", "systemd-resolved") + log.Tracef("executing %s %v", cmd.Path, cmd.Args) + _, err := cmd.Output() + if err != nil || cmd.ProcessState.ExitCode() != 0 { + log.Error("command %s has failed: %v code:%d", + cmd.Path, err, cmd.ProcessState.ExitCode()) + return false + } + + cmd = exec.Command("grep", "-E", "#?DNSStubListener=yes", "/etc/systemd/resolved.conf") + log.Tracef("executing %s %v", cmd.Path, cmd.Args) + _, err = cmd.Output() + if err != nil || cmd.ProcessState.ExitCode() != 0 { + log.Error("command %s has failed: %v code:%d", + cmd.Path, err, cmd.ProcessState.ExitCode()) + return false + } + + return true +} + +// Deactivate DNSStubListener +func disableDNSStubListener() error { + cmd := exec.Command("sed", "-r", "-i.orig", "s/#?DNSStubListener=yes/DNSStubListener=no/g", "/etc/systemd/resolved.conf") + log.Tracef("executing %s %v", cmd.Path, cmd.Args) + _, err := cmd.Output() + if err != nil { + return err + } + if cmd.ProcessState.ExitCode() != 0 { + return fmt.Errorf("process %s exited with an error: %d", + cmd.Path, cmd.ProcessState.ExitCode()) + } + + cmd = exec.Command("systemctl", "reload-or-restart", "systemd-resolved") + log.Tracef("executing %s %v", cmd.Path, cmd.Args) + _, err = cmd.Output() + if err != nil { + return err + } + if cmd.ProcessState.ExitCode() != 0 { + return fmt.Errorf("process %s exited with an error: %d", + cmd.Path, cmd.ProcessState.ExitCode()) + } + + return nil +} + type applyConfigReqEnt struct { IP string `json:"ip"` Port int `json:"port"` diff --git a/helpers.go b/helpers.go index 184789d4..e4bb0d10 100644 --- a/helpers.go +++ b/helpers.go @@ -15,6 +15,7 @@ import ( "runtime" "strconv" "strings" + "syscall" "time" "github.com/AdguardTeam/dnsproxy/upstream" @@ -346,6 +347,31 @@ func customDialContext(ctx context.Context, network, addr string) (net.Conn, err return nil, firstErr } +// check if error is "address already in use" +func errorIsAddrInUse(err error) bool { + errOpError, ok := err.(*net.OpError) + if !ok { + return false + } + + errSyscallError, ok := errOpError.Err.(*os.SyscallError) + if !ok { + return false + } + + errErrno, ok := errSyscallError.Err.(syscall.Errno) + if !ok { + return false + } + + if runtime.GOOS == "windows" { + const WSAEADDRINUSE = 10048 + return errErrno == WSAEADDRINUSE + } + + return errErrno == syscall.EADDRINUSE +} + // --------------------- // debug logging helpers // --------------------- From 87147ac89fc08bc4a1fff2c63aef9e6ec5a36ae7 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Wed, 17 Apr 2019 12:03:51 +0300 Subject: [PATCH 07/18] + control: /install/*: test TCP port availability for DNS server --- control.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/control.go b/control.go index 08747e7c..47c8c885 100644 --- a/control.go +++ b/control.go @@ -1059,6 +1059,10 @@ func handleInstallCheckConfig(w http.ResponseWriter, r *http.Request) { respData.DNS.CanAutofix = canAutofix } + if err == nil { + err = checkPortAvailable(reqData.DNS.IP, reqData.DNS.Port) + } + if err != nil { respData.DNS.Status = fmt.Sprintf("%v", err) } @@ -1161,8 +1165,13 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) { err = checkPacketPortAvailable(newSettings.DNS.IP, newSettings.DNS.Port) if err != nil { - httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s due to %s", - net.JoinHostPort(newSettings.DNS.IP, strconv.Itoa(newSettings.DNS.Port)), err) + httpError(w, http.StatusBadRequest, "%s", err) + return + } + + err = checkPortAvailable(newSettings.DNS.IP, newSettings.DNS.Port) + if err != nil { + httpError(w, http.StatusBadRequest, "%s", err) return } From 73fbe8b95aca6092b0beb7327a9c811a32464bcc Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Wed, 17 Apr 2019 12:05:34 +0300 Subject: [PATCH 08/18] * control: /install/configure: reset configuration back to its current state on error --- control.go | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/control.go b/control.go index 47c8c885..2207edde 100644 --- a/control.go +++ b/control.go @@ -1137,6 +1137,16 @@ type applyConfigReq struct { Password string `json:"password"` } +// Copy installation parameters between two configuration objects +func copyInstallSettings(dst *configuration, src *configuration) { + dst.BindHost = src.BindHost + dst.BindPort = src.BindPort + dst.DNS.BindHost = src.DNS.BindHost + dst.DNS.Port = src.DNS.Port + dst.AuthName = src.AuthName + dst.AuthPass = src.AuthPass +} + // Apply new configuration, start DNS server, restart Web server func handleInstallConfigure(w http.ResponseWriter, r *http.Request) { log.Tracef("%s %v", r.Method, r.URL) @@ -1175,6 +1185,9 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) { return } + var curConfig configuration + copyInstallSettings(&curConfig, &config) + config.firstRun = false config.BindHost = newSettings.Web.IP config.BindPort = newSettings.Web.Port @@ -1183,15 +1196,22 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) { config.AuthName = newSettings.Username config.AuthPass = newSettings.Password - if config.DNS.Port != 0 { - err = startDNSServer() - if err != nil { - httpError(w, http.StatusInternalServerError, "Couldn't start DNS server: %s", err) - return - } + err = startDNSServer() + if err != nil { + config.firstRun = true + copyInstallSettings(&config, &curConfig) + httpError(w, http.StatusInternalServerError, "Couldn't start DNS server: %s", err) + return + } + + err = config.write() + if err != nil { + config.firstRun = true + copyInstallSettings(&config, &curConfig) + httpError(w, http.StatusInternalServerError, "Couldn't write config: %s", err) + return } - httpUpdateConfigReloadDNSReturnOK(w, r) // this needs to be done in a goroutine because Shutdown() is a blocking call, and it will block // until all requests are finished, and _we_ are inside a request right now, so it will block indefinitely if restartHTTP { @@ -1199,6 +1219,8 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) { httpServer.Shutdown(context.TODO()) }() } + + returnOK(w) } // -------------- From d326d1bc8bc1d9246e70a09c7d2861e69f95f471 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Wed, 17 Apr 2019 12:40:27 +0300 Subject: [PATCH 09/18] * control: /install/configure: validate port number --- control.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/control.go b/control.go index 2207edde..3dca2c36 100644 --- a/control.go +++ b/control.go @@ -1157,6 +1157,11 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) { return } + if newSettings.Web.Port == 0 || newSettings.DNS.Port == 0 { + httpError(w, http.StatusBadRequest, "port value can't be 0") + return + } + restartHTTP := true if config.BindHost == newSettings.Web.IP && config.BindPort == newSettings.Web.Port { // no need to rebind From faa7c7b2d4ca6ce7062a85bdf75ff8ad7c1c9166 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Wed, 17 Apr 2019 19:57:42 +0300 Subject: [PATCH 10/18] + service install: a post-install guide of what to do next --- service.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/service.go b/service.go index 2b3b11e8..a73da52f 100644 --- a/service.go +++ b/service.go @@ -108,6 +108,15 @@ func handleServiceControlAction(action string) { log.Fatalf("Failed to start the service: %s", err) } log.Printf("Service has been started") + + if detectFirstRun() { + log.Printf(`Almost ready! +AdGuard Home is successfully installed and will automatically start on boot. +There are a few more things that must be configured before you can use it. +Click on the link below and follow the Installation Wizard steps to finish setup.`) + printHTTPAddresses("http") + } + } else if action == "uninstall" { cleanupService() } From 9e68a522cb901e81aee1c419c5ec34b5ad9a585d Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Mon, 22 Apr 2019 12:45:50 +0300 Subject: [PATCH 11/18] * update openapi.yaml --- openapi/openapi.yaml | 72 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 72fc07c5..5395ca99 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -739,6 +739,26 @@ paths: description: OK schema: $ref: "#/definitions/AddressesInfo" + /install/check_config: + post: + tags: + - install + operationId: installCheckConfig + summary: "Checks configuration" + parameters: + - in: "body" + name: "body" + description: "Configuration to be checked" + required: true + schema: + $ref: "#/definitions/CheckConfigRequest" + responses: + 200: + description: OK + schema: + $ref: "#/definitions/CheckConfigResponse" + 400: + description: "Failed to parse JSON or cannot listen on the specified address" /install/configure: post: tags: @@ -1320,17 +1340,18 @@ definitions: type: "integer" format: "int32" example: 53 - warning: - type: "string" - example: "Cannot bind to this port" AddressesInfo: type: "object" description: "AdGuard Home addresses configuration" properties: - dns: - $ref: "#/definitions/AddressInfo" - web: - $ref: "#/definitions/AddressInfo" + dns_port: + type: "integer" + format: "int32" + example: 53 + web_port: + type: "integer" + format: "int32" + example: 80 interfaces: type: "object" description: "Network interfaces dictionary (key is the interface name)" @@ -1353,6 +1374,43 @@ definitions: items: $ref: "#/definitions/Client" description: "Clients array" + CheckConfigRequest: + type: "object" + description: "Configuration to be checked" + properties: + dns: + $ref: "#/definitions/CheckConfigRequestInfo" + web: + $ref: "#/definitions/CheckConfigRequestInfo" + CheckConfigRequestInfo: + type: "object" + properties: + ip: + type: "string" + example: "127.0.0.1" + port: + type: "integer" + format: "int32" + example: 53 + autofix: + type: "boolean" + example: false + CheckConfigResponse: + type: "object" + properties: + dns: + $ref: "#/definitions/CheckConfigResponseInfo" + web: + $ref: "#/definitions/CheckConfigResponseInfo" + CheckConfigResponseInfo: + type: "object" + properties: + status: + type: "string" + example: "" + can_autofix: + type: "boolean" + example: false InitialConfiguration: type: "object" description: "AdGuard Home initial configuration (for the first-install wizard)" From f76b7c3d941070ba815aa25460612277ae1f1a62 Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Wed, 17 Apr 2019 14:50:27 +0300 Subject: [PATCH 12/18] * client: installation wizard additional checks --- client/src/__locales/en.json | 4 +- client/src/actions/install.js | 15 +++++ client/src/api/Api.js | 10 ++++ client/src/install/Setup/Controls.js | 2 + client/src/install/Setup/Settings.js | 85 ++++++++++++++++++++++------ client/src/install/Setup/Setup.css | 4 ++ client/src/install/Setup/index.js | 32 ++++++++++- client/src/reducers/install.js | 30 ++++++++-- 8 files changed, 156 insertions(+), 26 deletions(-) diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index ac8560c3..171df718 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -257,5 +257,7 @@ "reset_settings": "Reset settings", "update_announcement": "AdGuard Home {{version}} is now available! <0>Click here<\/0> for more info.", "setup_guide": "Setup guide", - "dns_addresses": "DNS addresses" + "dns_addresses": "DNS addresses", + "down": "Down", + "fix": "Fix" } \ No newline at end of file diff --git a/client/src/actions/install.js b/client/src/actions/install.js index c738f75d..3070ff2d 100644 --- a/client/src/actions/install.js +++ b/client/src/actions/install.js @@ -44,3 +44,18 @@ export const setAllSettings = values => async (dispatch) => { dispatch(prevStep()); } }; + +export const checkConfigRequest = createAction('CHECK_CONFIG_REQUEST'); +export const checkConfigFailure = createAction('CHECK_CONFIG_FAILURE'); +export const checkConfigSuccess = createAction('CHECK_CONFIG_SUCCESS'); + +export const checkConfig = values => async (dispatch) => { + dispatch(checkConfigRequest()); + try { + const check = await apiClient.checkConfig(values); + dispatch(checkConfigSuccess(check)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(checkConfigFailure()); + } +}; diff --git a/client/src/api/Api.js b/client/src/api/Api.js index a0ca7ed2..6d8a2f52 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -350,6 +350,7 @@ export default class Api { // Installation INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' }; INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' }; + INSTALL_CHECK_CONFIG = { path: 'install/check_config', method: 'POST' }; getDefaultAddresses() { const { path, method } = this.INSTALL_GET_ADDRESSES; @@ -365,6 +366,15 @@ export default class Api { return this.makeRequest(path, method, parameters); } + checkConfig(config) { + const { path, method } = this.INSTALL_CHECK_CONFIG; + const parameters = { + data: config, + headers: { 'Content-Type': 'application/json' }, + }; + return this.makeRequest(path, method, parameters); + } + // DNS-over-HTTPS and DNS-over-TLS TLS_STATUS = { path: 'tls/status', method: 'GET' }; TLS_CONFIG = { path: 'tls/configure', method: 'POST' }; diff --git a/client/src/install/Setup/Controls.js b/client/src/install/Setup/Controls.js index 5560c511..489ce08d 100644 --- a/client/src/install/Setup/Controls.js +++ b/client/src/install/Setup/Controls.js @@ -55,6 +55,8 @@ class Controls extends Component { invalid || pristine || install.processingSubmit + || install.dns.status + || install.web.status } > next diff --git a/client/src/install/Setup/Settings.js b/client/src/install/Setup/Settings.js index 25fd2d76..ff11f0ab 100644 --- a/client/src/install/Setup/Settings.js +++ b/client/src/install/Setup/Settings.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { Field, reduxForm, formValueSelector } from 'redux-form'; @@ -30,10 +30,25 @@ const toNumber = value => value && parseInt(value, 10); const renderInterfaces = (interfaces => ( Object.keys(interfaces).map((item) => { const option = interfaces[item]; - const { name } = option; + const { + name, + ip_addresses, + flags, + } = option; - if (option.ip_addresses && option.ip_addresses.length > 0) { + if (option && ip_addresses && ip_addresses.length > 0) { const ip = getInterfaceIp(option); + const isDown = flags && flags.includes('down'); + + if (isDown) { + return ( + + ); + } return (