From 7a52c4cbc474719b07bd9c025b95fda3b2120689 Mon Sep 17 00:00:00 2001 From: Corey Butler Date: Thu, 26 Jan 2023 20:03:39 -0600 Subject: [PATCH] Added initial environment check. Also prevent versions starting with non-numeric values other than 'v' that aren't a known alias. This closes #919 --- src/go.mod | 2 + src/go.sum | 2 + src/nvm.go | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 136 insertions(+), 2 deletions(-) diff --git a/src/go.mod b/src/go.mod index ec42ff1..49455d5 100644 --- a/src/go.mod +++ b/src/go.mod @@ -8,6 +8,8 @@ require ( github.com/blang/semver v3.5.1+incompatible github.com/coreybutler/go-fsutil v1.2.0 github.com/olekukonko/tablewriter v0.0.5 + golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a + golang.org/x/text v0.3.2 ) require github.com/mattn/go-runewidth v0.0.9 // indirect diff --git a/src/go.sum b/src/go.sum index 49148ff..b9b57c4 100644 --- a/src/go.sum +++ b/src/go.sum @@ -15,8 +15,10 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181207195948-8634b1ecd393/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/src/nvm.go b/src/nvm.go index 31f21e8..941585c 100644 --- a/src/nvm.go +++ b/src/nvm.go @@ -9,6 +9,7 @@ import ( "net/url" "os" "os/exec" + "os/user" "path/filepath" "regexp" "strconv" @@ -23,6 +24,9 @@ import ( "github.com/blang/semver" "github.com/olekukonko/tablewriter" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" + "golang.org/x/text/encoding" ) const ( @@ -153,6 +157,8 @@ func main() { setNodeMirror(detail) case "npm_mirror": setNpmMirror(detail) + case "check": + checkLocalEnvironment() default: help() } @@ -306,7 +312,7 @@ func install(version string, cpuarch string) { } if checkVersionExceedsLatest(version) { - fmt.Println("Node.js v" + version + " is not yet released or available.") + fmt.Println("Node.js v" + version + " is not yet released or is not available.") return } @@ -493,6 +499,14 @@ func uninstall(version string) { func versionNumberFrom(version string) string { reg, _ := regexp.Compile("[^0-9]") + + if reg.Match([]byte(version[:1])) { + if version[0:1] != "v" { + fmt.Printf("\"%v\" is not a valid version or recognized alias.\n", version) + os.Exit(0) + } + } + for reg.Match([]byte(version[:1])) { version = version[1:] } @@ -826,6 +840,93 @@ func disable() { fmt.Println("nvm disabled") } +func checkLocalEnvironment() { + problems := make([]string, 0) + + // Check for PATH problems + paths := strings.Split(os.Getenv("PATH"), ";") + current := env.symlink + if strings.HasSuffix(current, "/") || strings.HasSuffix(current, "\\") { + current = current[:len(current)-1] + } + + nvmsymlinkfound := false + for _, path := range paths { + if strings.HasSuffix(path, "/") || strings.HasSuffix(path, "\\") { + path = path[:len(path)-1] + } + + if strings.EqualFold(path, current) { + nvmsymlinkfound = true + break + } + + if _, err := os.Stat(filepath.Join(path, "node.exe")); err != nil { + problems = append(problems, "Another Node.js installation is blocking NVM4W installations from running. Please uninstall the conflicting version or update the PATH environment variable to assure \""+current+"\" precedes \""+path+"\".") + break + } else if !errors.Is(err, os.ErrNotExist) { + fmt.Println("Error running environment check:\n" + err.Error()) + } + } + + // Check for developer mode + devmode := "OFF" + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock`, registry.QUERY_VALUE) + if err == nil { + value, _, err := k.GetIntegerValue("AllowDevelopmentWithoutDevLicense") + if err == nil { + if value > 0 { + devmode = "ON" + } + } + } + defer k.Close() + + // Check for permission problems + admin, elevated, err := getProcessPermissions() + if err == nil { + if !admin && !elevated { + user, _ := user.Current() + username := strings.Split(user.Username, "\\") + fmt.Printf("\"%v\" does not have admin or elevated rights", username[len(username)-1]) + if devmode == "ON" { + fmt.Printf(", but windows developer mode is enabled.\nMost commands will still work unless \"%v\" lacks rights to modify \"%v\".\n", username[len(username)-1], current) + } else { + fmt.Println(".") + } + } else { + if admin { + fmt.Println("Running NVM for Windows as an admin user.") + } else if elevated { + fmt.Println("Running NVM for Windows with elevated permissions.") + } + } + } else { + fmt.Println(err) + } + + // Display developer mode status + if !admin { + fmt.Printf("\nWindows Developer Mode: %v\n", devmode) + } + + if !nvmsymlinkfound { + problems = append(problems, "The NVM4W symlink ("+env.symlink+") was not found in the PATH environment variable.") + } + + if len(problems) == 0 { + fmt.Println("\nNo problems detected.") + return + } + + fmt.Println("\nProblems Detected:") + for _, p := range problems { + fmt.Println(" - " + p) + } + + fmt.Println("\nFind help at https://github.com/coreybutler/nvm-windows/wiki/Common-Issues") +} + func help() { fmt.Println("\nRunning version " + NvmVersion + ".") fmt.Println("\nUsage:") @@ -1010,7 +1111,36 @@ func runElevated(command string, forceUAC ...bool) (bool, error) { func saveSettings() { content := "root: " + strings.Trim(env.root, " \n\r") + "\r\narch: " + strings.Trim(env.arch, " \n\r") + "\r\nproxy: " + strings.Trim(env.proxy, " \n\r") + "\r\noriginalpath: " + strings.Trim(env.originalpath, " \n\r") + "\r\noriginalversion: " + strings.Trim(env.originalversion, " \n\r") content = content + "\r\nnode_mirror: " + strings.Trim(env.node_mirror, " \n\r") + "\r\nnpm_mirror: " + strings.Trim(env.npm_mirror, " \n\r") - ioutil.WriteFile(env.settings, []byte(content), 0644) + decoder := encoding.Decoder{} + decoded, err := decoder.String(content) + if err != nil { + fmt.Printf("Error attempting to save settings:\n%v\n", err.Error()) + return + } + ioutil.WriteFile(env.settings, []byte(decoded), 0644) +} + +func getProcessPermissions() (admin bool, elevated bool, err error) { + admin = false + elevated = false + var sid *windows.SID + err = windows.AllocateAndInitializeSid( + &windows.SECURITY_NT_AUTHORITY, + 2, + windows.SECURITY_BUILTIN_DOMAIN_RID, + windows.DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &sid) + if err != nil { + return + } + defer windows.FreeSid(sid) + + token := windows.Token(0) + elevated = token.IsElevated() + admin, err = token.IsMember(sid) + + return } // NOT USED?