diff --git a/agent/agent.go b/agent/agent.go index 1cc74cf..cd3c895 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -58,6 +58,8 @@ type Agent struct { MeshSystemEXE string MeshSVC string PyBin string + NuBin string + DenoBin string Headers map[string]string Logger *logrus.Logger Version string @@ -86,10 +88,13 @@ const ( nixAgentDir = "/opt/tacticalagent" nixMeshDir = "/opt/tacticalmesh" nixAgentBin = nixAgentDir + "/tacticalagent" + nixAgentBinDir = nixAgentDir + "/bin" nixMeshAgentBin = nixMeshDir + "/meshagent" macPlistPath = "/Library/LaunchDaemons/tacticalagent.plist" macPlistName = "tacticalagent" defaultMacMeshSvcDir = "/usr/local/mesh_services" + nuVersion = "0.87.0" + denoVersion = "v1.38.2" ) var defaultWinTmpDir = filepath.Join(os.Getenv("PROGRAMDATA"), "TacticalRMM") @@ -119,6 +124,22 @@ func New(logger *logrus.Logger, version string) *Agent { pybin = filepath.Join(pd, "py38-x32", "python.exe") } + var nuBin string + switch runtime.GOOS { + case "windows": + nuBin = filepath.Join(pd, "bin", "nu.exe") + default: + nuBin = filepath.Join(nixAgentBinDir, "nu") + } + + var denoBin string + switch runtime.GOOS { + case "windows": + denoBin = filepath.Join(pd, "bin", "deno.exe") + default: + denoBin = filepath.Join(nixAgentBinDir, "deno") + } + ac := NewAgentConfig() headers := make(map[string]string) @@ -232,6 +253,8 @@ func New(logger *logrus.Logger, version string) *Agent { MeshSystemEXE: MeshSysExe, MeshSVC: meshSvcName, PyBin: pybin, + NuBin: nuBin, + DenoBin: denoBin, Headers: headers, Logger: logger, Version: version, diff --git a/agent/agent_unix.go b/agent/agent_unix.go index c77c52e..aebd808 100644 --- a/agent/agent_unix.go +++ b/agent/agent_unix.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "os" + "path" "path/filepath" "runtime" "strconv" @@ -196,31 +197,42 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, opts.IsScript = true switch shell { case "nushell": - // FIXME: Make this dynamic and use /opt/tacticalagent/bin/nu - opts.Shell = "/usr/local/bin/nu" + opts.Shell = a.NuBin opts.Args = append([]string{ "--no-config-file", f.Name(), }, args...) + if !trmm.FileExists(a.NuBin) { + a.Logger.Errorln("RunScript(): Executable does not exist. Install Nu and try again:", a.NuBin) + err := errors.New("File Not Found: " + a.NuBin) + return "", err.Error(), 85, err + } case "deno": - // FIXME: Make this dynamic and use /opt/tacticalagent/bin/nu - opts.Shell = "/usr/local/bin/deno" + opts.Shell = a.DenoBin opts.Args = []string{ "run", "--no-prompt", } + if !trmm.FileExists(a.DenoBin) { + a.Logger.Errorln("RunScript(): Executable does not exist. Install deno and try again:", a.DenoBin) + err := errors.New("File Not Found: " + a.DenoBin) + return "", err.Error(), 85, err + } // Search the environment variables for DENO_PERMISSIONS and use that to set permissions for the script. // https://docs.deno.com/runtime/manual/basics/permissions#permissions-list // DENO_PERMISSIONS is not an official environment variable. // https://docs.deno.com/runtime/manual/basics/env_variables - // TODO: Remove DENO_PERMISSIONS from the environment variables. - for _, v := range envVars { + for i, v := range envVars { if strings.HasPrefix(v, "DENO_PERMISSIONS=") { permissions := strings.Split(v, "=")[1] opts.Args = append(opts.Args, strings.Split(permissions, " ")...) + // Remove the DENO_PERMISSIONS variable from the environment variables slice. + // It's possible more variables may exist with the same prefix. + envVars = append(envVars[:i], envVars[i+1:]...) + break } } @@ -236,7 +248,7 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, opts.EnvVars = envVars opts.Timeout = time.Duration(timeout) - a.Logger.Debugln("RunScript(): ", opts.Shell, opts.Args) + a.Logger.Debugln("RunScript():", opts.Shell, opts.Args) out := a.CmdV2(opts) retError := "" if out.Status.Error != nil { @@ -541,6 +553,235 @@ func GetServiceStatus(name string) (string, error) { return "", nil } func (a *Agent) GetPython(force bool) {} +// GetNushell will download nushell from GitHub and install (copy) it to nixAgentBinDir +func (a *Agent) GetNushell(force bool) { + if trmm.FileExists(a.NuBin) { + if force { + err := os.Remove(a.NuBin) + if err != nil { + a.Logger.Errorln("GetNushell(): Error removing nu binary:", err) + return + } + } else { + return + } + } + + if !trmm.FileExists(nixAgentBinDir) { + err := os.MkdirAll(nixAgentBinDir, 0755) + if err != nil { + a.Logger.Errorln("GetNushell(): Error creating nixAgentBinDir:", err) + return + } + } + + var ( + assetName string + url string + targzDirName string + ) + + switch runtime.GOOS { + case "darwin": + switch runtime.GOARCH { + case "arm64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-darwin-full.tar.gz + assetName = fmt.Sprintf("nu-%s-aarch64-darwin-full.tar.gz", nuVersion) + default: + a.Logger.Debugln("GetNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + case "linux": + switch runtime.GOARCH { + case "amd64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-x86_64-linux-musl-full.tar.gz + assetName = fmt.Sprintf("nu-%s-x86_64-linux-musl-full.tar.gz", nuVersion) + case "arm64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-linux-gnu-full.tar.gz + assetName = fmt.Sprintf("nu-%s-aarch64-linux-gnu-full.tar.gz", nuVersion) + default: + a.Logger.Debugln("GetNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + default: + a.Logger.Debugln("GetNushell(): Unsupported OS:", runtime.GOOS) + return + } + url = fmt.Sprintf("https://github.com/nushell/nushell/releases/download/%s/%s", nuVersion, assetName) + a.Logger.Debugln("GetNushell(): Nu download url:", url) + + tmpDir, err := os.MkdirTemp("", "trmm") + if err != nil { + a.Logger.Errorln("GetNushell(): Error creating temp directory:", err) + return + } + defer func(path string) { + err := os.RemoveAll(path) + if err != nil { + a.Logger.Errorln("GetNushell(): Error removing temp directory:", err) + } + }(tmpDir) + + tmpAssetName := filepath.Join(tmpDir, assetName) + a.Logger.Debugln("GetNushell(): tmpAssetName:", tmpAssetName) + + rClient := resty.New() + rClient.SetTimeout(20 * time.Minute) + rClient.SetRetryCount(10) + rClient.SetRetryWaitTime(1 * time.Minute) + rClient.SetRetryMaxWaitTime(15 * time.Minute) + if len(a.Proxy) > 0 { + rClient.SetProxy(a.Proxy) + } + + r, err := rClient.R().SetOutput(tmpAssetName).Get(url) + if err != nil { + a.Logger.Errorln("GetNushell(): Unable to download nu from github.", err) + return + } + if r.IsError() { + a.Logger.Errorln("GetNushell(): Unable to download nu from github. Status code", r.StatusCode()) + return + } + + targzDirName, err = a.ExtractTarGz(tmpAssetName, tmpDir) + if err != nil { + a.Logger.Errorln("GetNushell(): Failed to extract downloaded tar.gz file:", err) + return + } + + err = copyFile(path.Join(tmpDir, targzDirName, "nu"), a.NuBin) + if err != nil { + a.Logger.Errorln("GetNushell(): Failed to copy nu file to install dir:", err) + return + } + + err = os.Chmod(a.NuBin, 0755) + if err != nil { + a.Logger.Errorln("GetNushell(): Failed to chmod nu binary:", err) + return + } + +} + +// GetDeno will download deno from GitHub and install (copy) it to nixAgentBinDir +func (a *Agent) GetDeno(force bool) { + if trmm.FileExists(a.DenoBin) { + if force { + err := os.Remove(a.DenoBin) + if err != nil { + a.Logger.Errorln("GetDeno(): Error removing deno binary:", err) + return + } + } else { + return + } + } + + if !trmm.FileExists(nixAgentBinDir) { + err := os.MkdirAll(nixAgentBinDir, 0755) + if err != nil { + a.Logger.Errorln("GetDeno(): Error creating nixAgentBinDir:", err) + return + } + } + + var ( + assetName string + url string + ) + + switch runtime.GOOS { + case "darwin": + switch runtime.GOARCH { + case "arm64": + // https://github.com/denoland/deno/releases/download/v1.38.2/deno-aarch64-apple-darwin.zip + assetName = fmt.Sprintf("deno-aarch64-apple-darwin.zip") + case "amd64": + // https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-apple-darwin.zip + assetName = fmt.Sprintf("deno-x86_64-apple-darwin.zip") + default: + a.Logger.Debugln("GetDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + case "linux": + switch runtime.GOARCH { + case "amd64": + // https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-unknown-linux-gnu.zip + assetName = fmt.Sprintf("deno-x86_64-unknown-linux-gnu.zip") + default: + a.Logger.Debugln("GetDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + default: + a.Logger.Debugln("GetDeno(): Unsupported OS:", runtime.GOOS) + return + } + url = fmt.Sprintf("https://github.com/denoland/deno/releases/download/%s/%s", denoVersion, assetName) + a.Logger.Debugln("GetDeno(): Deno download url:", url) + + tmpDir, err := os.MkdirTemp("", "trmm") + if err != nil { + a.Logger.Errorln("GetDeno(): Error creating temp directory:", err) + return + } + defer func(path string) { + err := os.RemoveAll(path) + if err != nil { + a.Logger.Errorln("GetDeno(): Error removing temp directory:", err) + } + }(tmpDir) + + tmpAssetName := filepath.Join(tmpDir, assetName) + a.Logger.Debugln("GetDeno(): tmpAssetName:", tmpAssetName) + + if !trmm.FileExists(nixAgentBinDir) { + err = os.MkdirAll(nixAgentBinDir, 0755) + if err != nil { + a.Logger.Errorln("GetDeno(): Error creating nixAgentBinDir:", err) + return + } + } + + rClient := resty.New() + rClient.SetTimeout(20 * time.Minute) + rClient.SetRetryCount(10) + rClient.SetRetryWaitTime(1 * time.Minute) + rClient.SetRetryMaxWaitTime(15 * time.Minute) + if len(a.Proxy) > 0 { + rClient.SetProxy(a.Proxy) + } + + r, err := rClient.R().SetOutput(tmpAssetName).Get(url) + if err != nil { + a.Logger.Errorln("GetDeno(): Unable to download deno from github.", err) + return + } + if r.IsError() { + a.Logger.Errorln("GetDeno(): Unable to download deno from github. Status code", r.StatusCode()) + return + } + + err = Unzip(tmpAssetName, tmpDir) + if err != nil { + a.Logger.Errorln("GetDeno(): Failed to unzip downloaded zip file:", err) + return + } + + err = copyFile(path.Join(tmpDir, "deno"), a.DenoBin) + if err != nil { + a.Logger.Errorln("GetDeno(): Failed to copy deno file to install dir:", err) + return + } + + err = os.Chmod(a.DenoBin, 0755) + if err != nil { + a.Logger.Errorln("GetDeno(): Failed to chmod deno binary:", err) + return + } + +} + type SchedTask struct{ Name string } func (a *Agent) PatchMgmnt(enable bool) error { return nil } diff --git a/agent/agent_windows.go b/agent/agent_windows.go index d9f6678..c61a29a 100644 --- a/agent/agent_windows.go +++ b/agent/agent_windows.go @@ -19,6 +19,7 @@ import ( "fmt" "os" "os/exec" + "path" "path/filepath" "runtime" "strconv" @@ -156,9 +157,41 @@ func (a *Agent) RunScript(code string, shell string, args []string, timeout int, case "cmd": exe = tmpfn.Name() case "nushell": - exe = tmpfn.Name() + exe = a.NuBin + cmdArgs = []string{"--no-config-file", tmpfn.Name()} + if !trmm.FileExists(a.NuBin) { + a.Logger.Errorln("RunScript(): Executable does not exist. Install Nu and try again:", a.NuBin) + err := errors.New("File Not Found: " + a.NuBin) + return "", err.Error(), 85, err + } case "deno": - exe = tmpfn.Name() + exe = a.DenoBin + cmdArgs = []string{"run", "--no-prompt"} + if !trmm.FileExists(a.DenoBin) { + a.Logger.Errorln("RunScript(): Executable does not exist. Install deno and try again:", a.DenoBin) + err := errors.New("File Not Found: " + a.DenoBin) + return "", err.Error(), 85, err + } + + // Search the environment variables for DENO_PERMISSIONS and use that to set permissions for the script. + // https://docs.deno.com/runtime/manual/basics/permissions#permissions-list + // DENO_PERMISSIONS is not an official environment variable. + // https://docs.deno.com/runtime/manual/basics/env_variables + for i, v := range envVars { + if strings.HasPrefix(v, "DENO_PERMISSIONS=") { + permissions := strings.Split(v, "=")[1] + cmdArgs = append(cmdArgs, strings.Split(permissions, " ")...) + // Remove the DENO_PERMISSIONS variable from the environment variables slice. + // It's possible more variables may exist with the same prefix. + envVars = append(envVars[:i], envVars[i+1:]...) + break + } + } + + // Can't append a variadic slice after a string arg. + // https://pkg.go.dev/builtin#append + cmdArgs = append(cmdArgs, tmpfn.Name()) + } if len(args) > 0 { @@ -846,6 +879,204 @@ func (a *Agent) GetPython(force bool) { } } +// GetNushell will download nushell from GitHub and install (copy) it to ProgramDir\bin, where ProgramDir is +// initialized to C:\Program Files\TacticalAgent +func (a *Agent) GetNushell(force bool) { + if trmm.FileExists(a.NuBin) { + if force { + err := os.Remove(a.NuBin) + if err != nil { + a.Logger.Errorln("GetNushell(): Error removing nu binary:", err) + return + } + } else { + return + } + } + + programBinDir := path.Join(a.ProgramDir, "bin") + if !trmm.FileExists(programBinDir) { + err := os.MkdirAll(programBinDir, 0755) + if err != nil { + a.Logger.Errorln("GetNushell(): Error creating Program Files bin folder:", err) + return + } + } + + var ( + assetName string + url string + ) + + switch runtime.GOOS { + case "windows": + switch runtime.GOARCH { + case "amd64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-x86_64-windows-msvc-full.zip + assetName = fmt.Sprintf("nu-%s-x86_64-windows-msvc-full.zip", nuVersion) + case "arm64": + // https://github.com/nushell/nushell/releases/download/0.87.0/nu-0.87.0-aarch64-windows-msvc-full.zip + assetName = fmt.Sprintf("nu-%s-aarch64-windows-msvc-full.zip", nuVersion) + default: + a.Logger.Debugln("GetNushell(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + default: + a.Logger.Debugln("GetNushell(): Unsupported OS:", runtime.GOOS) + return + } + url = fmt.Sprintf("https://github.com/nushell/nushell/releases/download/%s/%s", nuVersion, assetName) + a.Logger.Debugln("GetNushell(): Nu download url:", url) + + tmpDir, err := os.MkdirTemp("", "trmm") + if err != nil { + a.Logger.Errorln("GetNushell(): Error creating temp directory:", err) + return + } + defer func(path string) { + err := os.RemoveAll(path) + if err != nil { + a.Logger.Errorln("GetNushell(): Error removing temp directory:", err) + } + }(tmpDir) + + tmpAssetName := filepath.Join(tmpDir, assetName) + a.Logger.Debugln("GetNushell(): tmpAssetName:", tmpAssetName) + + rClient := resty.New() + rClient.SetTimeout(20 * time.Minute) + rClient.SetRetryCount(10) + rClient.SetRetryWaitTime(1 * time.Minute) + rClient.SetRetryMaxWaitTime(15 * time.Minute) + if len(a.Proxy) > 0 { + rClient.SetProxy(a.Proxy) + } + + r, err := rClient.R().SetOutput(tmpAssetName).Get(url) + if err != nil { + a.Logger.Errorln("GetNushell(): Unable to download nu from github.", err) + return + } + if r.IsError() { + a.Logger.Errorln("GetNushell(): Unable to download nu from github. Status code", r.StatusCode()) + return + } + + err = Unzip(tmpAssetName, tmpDir) + if err != nil { + a.Logger.Errorln("GetNushell(): Failed to unzip downloaded zip file:", err) + return + } + + err = copyFile(path.Join(tmpDir, "nu.exe"), a.NuBin) + if err != nil { + a.Logger.Errorln("GetNushell(): Failed to copy nu.exe file to install dir:", err) + return + } + +} + +// GetDeno will download deno from GitHub and install (copy) it to nixAgentBinDir +func (a *Agent) GetDeno(force bool) { + if trmm.FileExists(a.DenoBin) { + if force { + err := os.Remove(a.DenoBin) + if err != nil { + a.Logger.Errorln("GetDeno(): Error removing deno binary:", err) + return + } + } else { + return + } + } + + programBinDir := path.Join(a.ProgramDir, "bin") + if !trmm.FileExists(programBinDir) { + err := os.MkdirAll(programBinDir, 0755) + if err != nil { + a.Logger.Errorln("GetDeno(): Error creating Program Files bin folder:", err) + return + } + } + + var ( + assetName string + url string + ) + + switch runtime.GOOS { + case "windows": + switch runtime.GOARCH { + case "amd64": + // https://github.com/denoland/deno/releases/download/v1.38.2/deno-x86_64-pc-windows-msvc.zip + assetName = fmt.Sprintf("deno-x86_64-pc-windows-msvc.zip") + default: + a.Logger.Debugln("GetDeno(): Unsupported architecture and OS:", runtime.GOARCH, runtime.GOOS) + return + } + default: + a.Logger.Debugln("GetDeno(): Unsupported OS:", runtime.GOOS) + return + } + url = fmt.Sprintf("https://github.com/denoland/deno/releases/download/%s/%s", denoVersion, assetName) + a.Logger.Debugln("GetDeno(): Deno download url:", url) + + tmpDir, err := os.MkdirTemp("", "trmm") + if err != nil { + a.Logger.Errorln("GetDeno(): Error creating temp directory:", err) + return + } + defer func(path string) { + err := os.RemoveAll(path) + if err != nil { + a.Logger.Errorln("GetDeno(): Error removing temp directory:", err) + } + }(tmpDir) + + tmpAssetName := filepath.Join(tmpDir, assetName) + a.Logger.Debugln("GetDeno(): tmpAssetName:", tmpAssetName) + + if !trmm.FileExists(nixAgentBinDir) { + err = os.MkdirAll(nixAgentBinDir, 0755) + if err != nil { + a.Logger.Errorln("GetDeno(): Error creating nixAgentBinDir:", err) + return + } + } + + rClient := resty.New() + rClient.SetTimeout(20 * time.Minute) + rClient.SetRetryCount(10) + rClient.SetRetryWaitTime(1 * time.Minute) + rClient.SetRetryMaxWaitTime(15 * time.Minute) + if len(a.Proxy) > 0 { + rClient.SetProxy(a.Proxy) + } + + r, err := rClient.R().SetOutput(tmpAssetName).Get(url) + if err != nil { + a.Logger.Errorln("GetDeno(): Unable to download deno from github.", err) + return + } + if r.IsError() { + a.Logger.Errorln("GetDeno(): Unable to download deno from github. Status code", r.StatusCode()) + return + } + + err = Unzip(tmpAssetName, tmpDir) + if err != nil { + a.Logger.Errorln("GetDeno(): Failed to unzip downloaded zip file:", err) + return + } + + err = copyFile(path.Join(tmpDir, "deno.exe"), a.DenoBin) + if err != nil { + a.Logger.Errorln("GetDeno(): Failed to copy deno.exe file to install dir:", err) + return + } + +} + func (a *Agent) RecoverMesh() { a.Logger.Infoln("Attempting mesh recovery") defer CMD("net", []string{"start", a.MeshSVC}, 60, false) diff --git a/agent/install.go b/agent/install.go index 1d6c75a..4186754 100644 --- a/agent/install.go +++ b/agent/install.go @@ -17,6 +17,7 @@ import ( "io" "net/url" "os" + "path" "path/filepath" "regexp" "runtime" @@ -258,8 +259,13 @@ func (a *Agent) Install(i *Installer) { // check in once a.DoNatsCheckIn() + if runtime.GOOS == "linux" { + // Used for Nushell and Deno binaries + os.MkdirAll(nixAgentBinDir, 0755) + } + if runtime.GOOS == "darwin" { - os.MkdirAll(nixAgentDir, 0755) + os.MkdirAll(nixAgentBinDir, 0755) self, _ := os.Executable() copyFile(self, nixAgentBin) os.Chmod(nixAgentBin, 0755) @@ -300,6 +306,8 @@ func (a *Agent) Install(i *Installer) { } if runtime.GOOS == "windows" { + os.MkdirAll(path.Join(a.ProgramDir, "bin"), 0755) + // send software api a.SendSoftware() @@ -341,7 +349,7 @@ func (a *Agent) Install(i *Installer) { } } - a.installerMsg("Installation was successfull!\nAllow a few minutes for the agent to properly display in the RMM", "info", i.Silent) + a.installerMsg("Installation was successful!\nAllow a few minutes for the agent to properly display in the RMM", "info", i.Silent) } func copyFile(src, dst string) error { diff --git a/agent/rpc.go b/agent/rpc.go index 8ecae5c..03b1508 100644 --- a/agent/rpc.go +++ b/agent/rpc.go @@ -437,6 +437,10 @@ func (a *Agent) RunRPC() { }() case "installpython": go a.GetPython(true) + case "installnushell": + go a.GetNushell(true) + case "installdeno": + go a.GetDeno(true) case "installchoco": go a.InstallChoco() case "installwithchoco": diff --git a/agent/svc.go b/agent/svc.go index e7f03ea..b04bb9d 100644 --- a/agent/svc.go +++ b/agent/svc.go @@ -49,6 +49,9 @@ func (a *Agent) AgentSvc(nc *nats.Conn) { a.Logger.Errorln("AgentSvc() createWinTempDir():", err) } } + a.GetNushell(false) + a.GetDeno(false) + a.RunMigrations() sleepDelay := randRange(7, 25) diff --git a/agent/utils.go b/agent/utils.go index 6eacaca..f915fc5 100644 --- a/agent/utils.go +++ b/agent/utils.go @@ -12,8 +12,10 @@ https://license.tacticalrmm.com package agent import ( + "archive/tar" "archive/zip" "bytes" + "compress/gzip" "fmt" "io" "math" @@ -21,6 +23,7 @@ import ( "net" "os" "os/exec" + "path" "path/filepath" "runtime" goDebug "runtime/debug" @@ -280,6 +283,70 @@ func Unzip(src, dest string) error { return nil } +// ExtractTarGz extracts a tar.gz file to the specified directory. +// Returns the extracted directory name. +// https://stackoverflow.com/questions/57639648/how-to-decompress-tar-gz-file-in-go +func (a *Agent) ExtractTarGz(targz string, destDir string) (extractedDir string, err error) { + gzipStream, err := os.Open(targz) + if err != nil { + a.Logger.Errorln("ExtractTarGz(): Open() failed:", err.Error()) + return "", err + } + + uncompressedStream, err := gzip.NewReader(gzipStream) + if err != nil { + a.Logger.Errorln("ExtractTarGz(): NewReader() failed:", err.Error()) + return "", err + } + + extractedDir = "" + tarReader := tar.NewReader(uncompressedStream) + for true { + header, err := tarReader.Next() + + if err == io.EOF { + break + } + + if err != nil { + a.Logger.Errorln("ExtractTarGz(): Next() failed:", err.Error()) + return "", err + } + + switch header.Typeflag { + case tar.TypeDir: + if err := os.MkdirAll(path.Join(destDir, header.Name), 0755); err != nil { + a.Logger.Errorln("ExtractTarGz(): Mkdir() failed:", err.Error()) + return "", err + } + if extractedDir == "" { + extractedDir = header.Name + } + case tar.TypeReg: + outFile, err := os.Create(path.Join(destDir, header.Name)) + if err != nil { + a.Logger.Errorln("ExtractTarGz(): Create() failed:", err.Error()) + return "", err + } + if _, err := io.Copy(outFile, tarReader); err != nil { + a.Logger.Errorln("ExtractTarGz(): Copy() failed:", err.Error()) + return "", err + } + err = outFile.Close() + if err != nil { + a.Logger.Errorln("ExtractTarGz(): Close() failed:", err.Error()) + return "", err + } + + default: + a.Logger.Errorln("ExtractTarGz(): Unknown type: %s in %s", header.Typeflag, header.Name) + return "", err + } + + } + return extractedDir, nil +} + // https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/ func ByteCountSI(b uint64) string { const unit = 1024 diff --git a/main.go b/main.go index cc302ca..0d46a62 100644 --- a/main.go +++ b/main.go @@ -117,6 +117,10 @@ func main() { fmt.Println(a.PublicIP()) case "getpython": a.GetPython(true) + case "getdeno": + a.GetDeno(true) + case "getnushell": + a.GetNushell(true) case "runmigrations": a.RunMigrations() case "recovermesh":