Skip to content
Snippets Groups Projects
Select Git revision
  • d7055b26283a18abe785dd0517cb6e82fb860cb3
  • main default protected
  • thibault.capt-main-patch-72132
3 results

Matrix.java

Blame
  • nexush.go 10.13 KiB
    package main
    
    import (
        "os"
        "fmt"
        "path"
        "errors"
        "strings"
        "syscall"
        "os/signal"
        "golang.org/x/term"
        u "nexus-client/utils"
        "nexus-common/utils"
        g "nexus-client/globals"
        "nexus-client/defaults"
        "nexus-client/version"
        "nexus-client/cmd"
        "nexus-client/cmdVM"
        "nexus-client/cmdMisc"
        "nexus-client/cmdUser"
        "nexus-client/cmdLogin"
        "nexus-client/cmdToken"
        "nexus-client/cmdVersion"
        "nexus-client/cmdTemplate"
        "github.com/peterh/liner"
        "github.com/go-resty/resty/v2"
    )
    
    var cmdList = []cmd.Command {
        &cmdMisc.HelpHeader{"!═════╡ GENERAL commands ╞═════════════════════════════════════════════════════════════════"},
        // Commands in this block are specific to nexush:
        &cmdMisc.Ls{"ls"},
        // End of nexush specific commands
    
        &cmdToken.Refresh{"refresh"},
        &cmdVersion.Version{"version"},
    
        &cmdMisc.HelpHeader{"!═════╡ USER commands ╞═════════════════════════════════════════════════════════════════"},
        &cmdUser.Whoami{"whoami"},
        &cmdUser.UpdatePwd{"passwd"},
        &cmdUser.List{"userlist"},
        &cmdUser.Add{"usercreate"},
        &cmdUser.Del{"userdel"},
        &cmdUser.SetCaps{"usersetcaps"},
    
        &cmdMisc.HelpHeader{"!═════╡ TEMPLATE commands ╞═════════════════════════════════════════════════════════════════"},
        &cmdTemplate.Create{"tplcreate"},
        &cmdTemplate.Del{"tpldel"},
        &cmdTemplate.Edit{"tpledit"},
        &cmdTemplate.ExportDisk{"tplexportdisk"},
        &cmdTemplate.List{"tpllist"},
        // &cmdTemplate.ListSingle{"tpllistsingle"},   // for testing the route only
    
        &cmdMisc.HelpHeader{"!═════╡ VM commands ╞═════════════════════════════════════════════════════════════════"},
        &cmdVM.AddAccess{"vmaddaccess"},
        &cmdVM.AttachAsync{"vmattach"},
        &cmdVM.Create{"vmcreate"},
        &cmdVM.Creds2pdf{"vmcreds2pdf"},
        &cmdVM.Creds2csv{"vmcreds2csv"},
        &cmdVM.Del{"vmdel"},
        &cmdVM.DelAccess{"vmdelaccess"},
        &cmdVM.Edit{"vmedit"},
        &cmdVM.ExportDir{"vmexportdir"},
        &cmdVM.ImportDir{"vmimportdir"},
        &cmdVM.Stop{"vmkill"},
        &cmdVM.List{"vmlist"},
        // &cmdVM.ListSingle{"vmlistsingle"},   // for testing the route only
        &cmdVM.Reboot{"vmreboot"},
        &cmdVM.Shutdown{"vmshutdown"},
        &cmdVM.Start{"vmstart"},
        &cmdVM.StartWithCreds{"vmstartwithcreds"},
    }
    
    var prompt *liner.State = nil
    var savedTermState *term.State = nil
    
    func run() int {
        var appname = path.Base(os.Args[0])
        u.PrintlnErr(appname+" version "+version.Get().String())
    
        if len(os.Args) < 2 {
            u.PrintlnErr("USAGE: "+appname+" EMAIL")
            u.PrintlnErr("EMAIL is the user with which to log in.")
            return 1
        }
    
        serverEnvVar, found := os.LookupEnv(g.ENV_NEXUS_SERVER)
        if !found {
            serverEnvVar = defaults.NexusServer
            // u.PrintlnErr("Environment variable \""+g.ENV_NEXUS_SERVER+"\" must be set!")
            // u.PrintlnErr("It defines the nexus server to connect to along the port number.")
            // u.PrintlnErr("Example: export "+g.ENV_NEXUS_SERVER+"=192.168.1.42:1077")
            // return 1
        }
    
        savedTermState, _ = term.GetState(int(os.Stdin.Fd()))
    
        var err error  // necessary for certEnvVar below to be ref as the same variable
    
        certEnvVar, found := os.LookupEnv(g.ENV_NEXUS_CERT)
        if found {
            // This thread acts as a signal handler for SIGINT or SIGTERM.
            // When one of these signals is received, the temporary certificate file is deleted.
            // Without this "handler", the temporary certificate file wouldn't be deleted.
            go func() {
                waitForSignals()
                restoreTerm()
                os.Exit(1)
            }()
        } else {
            certEnvVar, err = defaults.CreateCert()
            if err != nil {
                u.PrintlnErr("Failed creating certificate from embedded certificate: ")
                return 1
            }
            defer os.Remove(certEnvVar)
    
            // As above, this thread acts as a signal handler for SIGINT or SIGTERM.
            go func(certFile string) {
                waitForSignals()
                os.Remove(certFile)
                restoreTerm()
                os.Exit(1)
            }(certEnvVar)
    
            // u.PrintlnErr("Environment variable \""+g.ENV_NEXUS_CERT+"\" must be set!")
            // u.PrintlnErr("It specifies the path to the public certificate required for encrypted communications (TLS) with the nexus server.")
            // u.PrintlnErr("Example: export "+g.ENV_NEXUS_CERT+"=ca-cert.pem")
            // return 1
        }
    
        if !utils.FileExists(certEnvVar) {
            u.PrintlnErr("Failed reading certificate \""+certEnvVar+"\"!")
            return 1
        }
    
        parts := strings.Split(serverEnvVar, ":")
        hostname := parts[0]
    
        client := resty.New()
        client.SetRootCertificate(certEnvVar)
        host := "https://"+serverEnvVar
    
        g.Init(hostname, host, certEnvVar, client)
    
        // Checks the client version is compatible with the server's API.
        if !cmdVersion.CheckServerCompatibility(appname) {
            return 1
        }
    
        // Obtains password from cmd line.
        email := os.Args[1]
        fmt.Printf(email+"'s password: ")
        bytePwd, err := term.ReadPassword(int(syscall.Stdin))
        if err != nil {
            u.PrintlnErr(err.Error())
            return 1
        }
        pwd := string(bytePwd)
    
        // Logins and obtains a JWT token.
        token, err := cmdLogin.GetToken(email, pwd)
        u.PrintlnErr("")
        if err != nil {
            u.PrintlnErr("Error: "+err.Error())
            return 1
        }
    
        shell(token)
        return 0
    }
    
    func shell(token string) {
        u.Println(`Welcome to nexush, the nexus shell.
    Type: "help" for help on commands
          "quit" or "exit" to quit nexush`)
    
        client := g.GetInstance().Client
    
        prompt = liner.NewLiner()
        prompt.SetCtrlCAborts(true)
    
        prompt.SetCompleter(func(line string) (commands []string) {
            for _, cmd := range cmdList {
                c := cmd.GetName()
                if strings.HasPrefix(c, strings.ToLower(line)) {
                    commands = append(commands, c)
                }
            }
            return
        })
    
        quit := false
        for !quit {
            var line string
            if myline, err := prompt.Prompt("nexush> "); err == nil {
                line = myline
                prompt.AppendHistory(myline)
            } else if err == liner.ErrPromptAborted {
                u.Println("Aborted")
                continue
            } else {
                u.Println("Error reading line: ", err)
                continue
            }
    
            line = strings.TrimSpace(line)
            args, err := extractArgs(line)
            if err != nil {
                u.Println("invalid command line: "+err.Error())
                continue
            }
            if args == nil {
                continue
            }
            if len(args) == 0 {
                continue
            }
            typedCmd := args[0]
    
            switch {
                case typedCmd == "quit" || typedCmd == "exit":
                    quit = true
                case typedCmd == "help":
                    cmd.Help(cmdList)
                case line == "":
                    continue
                default:
                    found, cmd := cmd.Match(typedCmd, cmdList)
                    if found {
                        client.SetHeader("Content-Type", "application/json")
                        client.SetHeader("Authorization", "Bearer "+token)
                        if typedCmd == "refresh" {
                            refreshedToken, err := cmdToken.GetToken()
                            if err != nil {
                                u.Println("refresh error: "+err.Error())
                                break
                            }
                            token = refreshedToken
                            u.Println("Token refreshed.")
                            break
                        } else {
                            cmd.Run(args[1:])
                            break
                        }
                    } else {
                        u.Println(typedCmd+": unknown command")
                    }
            }
        }
    
        restoreTerm()
    }
    
    func waitForSignals() {
        // Wait on dedicated signals.
        sigs := make(chan os.Signal, 1)
        signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
    
        for {
            sig := <-sigs  // blocks on any of the above signals.
            u.Println("Caught signal ("+sig.String()+")")
            break;
        }
    }
    
    func restoreTerm() {
        if prompt != nil {
            prompt.Close()
        }
        if savedTermState != nil {
            term.Restore(int(os.Stdin.Fd()), savedTermState)
        }
    }
    
    // Extract arguments from a command line, taking in account args between quotes ("").
    // Returns arguments as a slice of strings.
    func extractArgs(line string) ([]string, error) {
        toggleReplace := false
        var sb strings.Builder
        quoteCount := 0
        for _, c := range line {
            if c == ' ' {
                if toggleReplace {
                    // IMPORTANT:
                    // The rune below is used to replace spaces within quotes in order
                    // to split the string on spaces as normal. Then, after the split,
                    // we simply replace the rune with spaces.
                    // Consequently the input string MUST NOT contain this specific rune!
                    sb.WriteRune('妖')
                } else {
                    sb.WriteRune(c)
                }
            } else if c == '"' {
                toggleReplace = !toggleReplace
                quoteCount += 1
            } else {
                sb.WriteRune(c)
            }
        }
    
        if quoteCount % 2 == 1 {
            return nil, errors.New("missing closing quote")
        }
    
        args := strings.Split(sb.String(), " ")
        newArgs := []string{}
    
        for _, arg := range args {
            r := strings.NewReplacer("妖", " ")
            str := r.Replace(arg)
            str = strings.TrimSpace(str)
            if len(str) > 0 {
                newArgs = append(newArgs, str)
            }
        }
    
        return newArgs, nil
    }
    
    func main() {
        os.Exit(run())
    }