Select Git revision
Matrix.java
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())
}