Add doctor command, minor changes

pull/184/head
Simon Let 3 years ago
parent dd573cac03
commit 668308a528
  1. 2
      README.md
  2. 55
      cmd/cli/main.go
  3. 4
      cmd/collect/main.go
  4. 151
      cmd/control/cmd/doctor.go
  5. 11
      cmd/control/cmd/root.go
  6. 4
      cmd/control/cmd/update.go
  7. 2
      cmd/control/cmd/version.go
  8. 4
      cmd/install-utils/main.go
  9. 30
      cmd/install-utils/migrate.go
  10. 4
      cmd/postcollect/main.go
  11. 90
      internal/check/check.go
  12. 8
      internal/collect/collect.go
  13. 15
      internal/device/device.go
  14. 64
      internal/output/output.go
  15. 9
      scripts/install.sh

@ -23,8 +23,6 @@ Context-based replacement/enhancement for zsh and bash shell history
### Prerequisites ### Prerequisites
Nohup
Standard stuff: `bash(4.3+)`, `curl`, `tar`, ... Standard stuff: `bash(4.3+)`, `curl`, `tar`, ...
MacOS: `coreutils` (`brew install coreutils`) MacOS: `coreutils` (`brew install coreutils`)

@ -62,34 +62,31 @@ func runReshCli(out *output.Output, config cfg.Config) (string, int) {
pwd := flags.String("pwd", "", "$PWD - present working directory") pwd := flags.String("pwd", "", "$PWD - present working directory")
gitOriginRemote := flags.String("git-origin-remote", "<<<MISSING>>>", "> git origin remote") gitOriginRemote := flags.String("git-origin-remote", "<<<MISSING>>>", "> git origin remote")
query := flags.String("query", "", "Search query") query := flags.String("query", "", "Search query")
// TODO: Do we still need this?
testHistory := flags.String("test-history", "", "Load history from a file instead from the daemon (for testing purposes only!)")
testHistoryLines := flags.Int("test-lines", 0, "The number of lines to load from a file passed with --test-history (for testing purposes only!)")
flags.Parse(args) flags.Parse(args)
// TODO: These errors should tell the user that they should not be running the command directly // TODO: These errors should tell the user that they should not be running the command directly
errMsg := "Failed to get required command-line arguments" errMsg := "Failed to get required command-line arguments"
if *sessionID == "" { if *sessionID == "" {
out.Fatal(errMsg, errors.New("missing required option --session-id")) out.FatalE(errMsg, errors.New("missing required option --session-id"))
} }
if *pwd == "" { if *pwd == "" {
out.Fatal(errMsg, errors.New("missing required option --pwd")) out.FatalE(errMsg, errors.New("missing required option --pwd"))
} }
if *gitOriginRemote == "<<<MISSING>>>" { if *gitOriginRemote == "<<<MISSING>>>" {
out.Fatal(errMsg, errors.New("missing required option --git-origin-remote")) out.FatalE(errMsg, errors.New("missing required option --git-origin-remote"))
} }
dataDir, err := datadir.GetPath() dataDir, err := datadir.GetPath()
if err != nil { if err != nil {
out.Fatal("Could not get user data directory", err) out.FatalE("Could not get user data directory", err)
} }
deviceName, err := device.GetName(dataDir) deviceName, err := device.GetName(dataDir)
if err != nil { if err != nil {
out.Fatal("Could not get device name", err) out.FatalE("Could not get device name", err)
} }
g, err := gocui.NewGui(gocui.OutputNormal, false) g, err := gocui.NewGui(gocui.OutputNormal, false)
if err != nil { if err != nil {
out.Fatal("Failed to launch TUI", err) out.FatalE("Failed to launch TUI", err)
} }
defer g.Close() defer g.Close()
@ -99,15 +96,11 @@ func runReshCli(out *output.Output, config cfg.Config) (string, int) {
g.Highlight = true g.Highlight = true
var resp msg.CliResponse var resp msg.CliResponse
if *testHistory == "" {
mess := msg.CliMsg{ mess := msg.CliMsg{
SessionID: *sessionID, SessionID: *sessionID,
PWD: *pwd, PWD: *pwd,
} }
resp = SendCliMsg(out, mess, strconv.Itoa(config.Port)) resp = SendCliMsg(out, mess, strconv.Itoa(config.Port))
} else {
resp = searchapp.LoadHistoryFromFile(out.Logger.Sugar(), *testHistory, *testHistoryLines)
}
st := state{ st := state{
// lock sync.Mutex // lock sync.Mutex
@ -129,46 +122,46 @@ func runReshCli(out *output.Output, config cfg.Config) (string, int) {
errMsg = "Failed to set keybindings" errMsg = "Failed to set keybindings"
if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, layout.Next); err != nil { if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, layout.Next); err != nil {
out.Fatal(errMsg, err) out.FatalE(errMsg, err)
} }
if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, layout.Next); err != nil { if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, layout.Next); err != nil {
out.Fatal(errMsg, err) out.FatalE(errMsg, err)
} }
if err := g.SetKeybinding("", gocui.KeyCtrlN, gocui.ModNone, layout.Next); err != nil { if err := g.SetKeybinding("", gocui.KeyCtrlN, gocui.ModNone, layout.Next); err != nil {
out.Fatal(errMsg, err) out.FatalE(errMsg, err)
} }
if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, layout.Prev); err != nil { if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, layout.Prev); err != nil {
out.Fatal(errMsg, err) out.FatalE(errMsg, err)
} }
if err := g.SetKeybinding("", gocui.KeyCtrlP, gocui.ModNone, layout.Prev); err != nil { if err := g.SetKeybinding("", gocui.KeyCtrlP, gocui.ModNone, layout.Prev); err != nil {
out.Fatal(errMsg, err) out.FatalE(errMsg, err)
} }
if err := g.SetKeybinding("", gocui.KeyArrowRight, gocui.ModNone, layout.SelectPaste); err != nil { if err := g.SetKeybinding("", gocui.KeyArrowRight, gocui.ModNone, layout.SelectPaste); err != nil {
out.Fatal(errMsg, err) out.FatalE(errMsg, err)
} }
if err := g.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, layout.SelectExecute); err != nil { if err := g.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, layout.SelectExecute); err != nil {
out.Fatal(errMsg, err) out.FatalE(errMsg, err)
} }
if err := g.SetKeybinding("", gocui.KeyCtrlG, gocui.ModNone, layout.AbortPaste); err != nil { if err := g.SetKeybinding("", gocui.KeyCtrlG, gocui.ModNone, layout.AbortPaste); err != nil {
out.Fatal(errMsg, err) out.FatalE(errMsg, err)
} }
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
out.Fatal(errMsg, err) out.FatalE(errMsg, err)
} }
if err := g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone, quit); err != nil { if err := g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone, quit); err != nil {
out.Fatal(errMsg, err) out.FatalE(errMsg, err)
} }
if err := g.SetKeybinding("", gocui.KeyCtrlR, gocui.ModNone, layout.SwitchModes); err != nil { if err := g.SetKeybinding("", gocui.KeyCtrlR, gocui.ModNone, layout.SwitchModes); err != nil {
out.Fatal(errMsg, err) out.FatalE(errMsg, err)
} }
layout.UpdateData(*query) layout.UpdateData(*query)
layout.UpdateRawData(*query) layout.UpdateRawData(*query)
err = g.MainLoop() err = g.MainLoop()
if err != nil && !errors.Is(err, gocui.ErrQuit) { if err != nil && !errors.Is(err, gocui.ErrQuit) {
out.Fatal("Main application loop finished with error", err) out.FatalE("Main application loop finished with error", err)
} }
return layout.s.output, layout.s.exitCode return layout.s.output, layout.s.exitCode
} }
@ -393,7 +386,7 @@ func (m manager) Layout(g *gocui.Gui) error {
v, err := g.SetView("input", 0, 0, maxX-1, 2, b) v, err := g.SetView("input", 0, 0, maxX-1, 2, b)
if err != nil && !errors.Is(err, gocui.ErrUnknownView) { if err != nil && !errors.Is(err, gocui.ErrUnknownView) {
m.out.Fatal("Failed to set view 'input'", err) m.out.FatalE("Failed to set view 'input'", err)
} }
v.Editable = true v.Editable = true
@ -416,7 +409,7 @@ func (m manager) Layout(g *gocui.Gui) error {
v, err = g.SetView("body", 0, 2, maxX-1, maxY, b) v, err = g.SetView("body", 0, 2, maxX-1, maxY, b)
if err != nil && !errors.Is(err, gocui.ErrUnknownView) { if err != nil && !errors.Is(err, gocui.ErrUnknownView) {
m.out.Fatal("Failed to set view 'body'", err) m.out.FatalE("Failed to set view 'body'", err)
} }
v.Frame = false v.Frame = false
v.Autoscroll = false v.Autoscroll = false
@ -579,7 +572,7 @@ func SendCliMsg(out *output.Output, m msg.CliMsg, port string) msg.CliResponse {
sugar := out.Logger.Sugar() sugar := out.Logger.Sugar()
recJSON, err := json.Marshal(m) recJSON, err := json.Marshal(m)
if err != nil { if err != nil {
out.Fatal("Failed to marshal message", err) out.FatalE("Failed to marshal message", err)
} }
req, err := http.NewRequest( req, err := http.NewRequest(
@ -587,7 +580,7 @@ func SendCliMsg(out *output.Output, m msg.CliMsg, port string) msg.CliResponse {
"http://localhost:"+port+"/dump", "http://localhost:"+port+"/dump",
bytes.NewBuffer(recJSON)) bytes.NewBuffer(recJSON))
if err != nil { if err != nil {
out.Fatal("Failed to build request", err) out.FatalE("Failed to build request", err)
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
@ -602,13 +595,13 @@ func SendCliMsg(out *output.Output, m msg.CliMsg, port string) msg.CliResponse {
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
out.Fatal("Failed read response", err) out.FatalE("Failed read response", err)
} }
// sugar.Println(string(body)) // sugar.Println(string(body))
response := msg.CliResponse{} response := msg.CliResponse{}
err = json.Unmarshal(body, &response) err = json.Unmarshal(body, &response)
if err != nil { if err != nil {
out.Fatal("Failed decode response", err) out.FatalE("Failed decode response", err)
} }
sugar.Debugw("Received records from daemon", sugar.Debugw("Received records from daemon",
"recordCount", len(response.Records), "recordCount", len(response.Records),

@ -52,12 +52,12 @@ func main() {
time, err := strconv.ParseFloat(*timeStr, 64) time, err := strconv.ParseFloat(*timeStr, 64)
if err != nil { if err != nil {
out.Fatal("Error while parsing flag --time", err) out.FatalE("Error while parsing flag --time", err)
} }
realPwd, err := filepath.EvalSymlinks(*pwd) realPwd, err := filepath.EvalSymlinks(*pwd)
if err != nil { if err != nil {
out.Error("Error while evaluating symlinks in PWD", err) out.ErrorE("Error while evaluating symlinks in PWD", err)
realPwd = "" realPwd = ""
} }

@ -0,0 +1,151 @@
package cmd
import (
"fmt"
"os"
"os/exec"
"time"
"github.com/curusarn/resh/internal/cfg"
"github.com/curusarn/resh/internal/check"
"github.com/curusarn/resh/internal/msg"
"github.com/curusarn/resh/internal/status"
"github.com/spf13/cobra"
"go.uber.org/zap"
)
func doctorCmdFunc(config cfg.Config) func(*cobra.Command, []string) {
return func(cmd *cobra.Command, args []string) {
allOK := true
if !checkDaemon(config) {
allOK = false
printDivider()
}
if !checkShellSession() {
allOK = false
printDivider()
}
if !checkShells() {
allOK = false
printDivider()
}
if allOK {
out.Info("Everything looks good.")
}
}
}
func printDivider() {
fmt.Printf("\n")
}
var msgFailedDaemonStart = `Failed to start RESH daemon.
-> Start RESH daemon manually - run: resh-daemon-start
-> Or restart this terminal window to bring RESH daemon back up
-> You can check logs: ~/.local/share/resh/log.json (or ~/$XDG_DATA_HOME/resh/log.json)
-> You can create an issue at: https://github.com/curusarn/resh/issues
`
func checkDaemon(config cfg.Config) bool {
ok := true
resp, err := status.GetDaemonStatus(config.Port)
if err != nil {
out.InfoE("RESH Daemon is not running", err)
out.Info("Attempting to start RESH daemon ...")
resp, err = startDaemon(config.Port, 5, 200*time.Millisecond)
if err != nil {
out.InfoE(msgFailedDaemonStart, err)
return false
}
ok = false
out.Info("Successfully started daemon.")
}
if version != resp.Version {
out.InfoDaemonVersionMismatch(version, resp.Version)
return false
}
return ok
}
func startDaemon(port int, maxRetries int, backoff time.Duration) (*msg.StatusResponse, error) {
err := exec.Command("resh-daemon-start").Run()
if err != nil {
return nil, err
}
var resp *msg.StatusResponse
retry := 0
for {
time.Sleep(backoff)
resp, err = status.GetDaemonStatus(port)
if err == nil {
break
}
if retry == maxRetries {
return nil, err
}
out.Logger.Error("Failed to get daemon status - retrying", zap.Error(err), zap.Int("retry", retry))
retry++
continue
}
return resp, nil
}
var msgShellFilesNotLoaded = `RESH shell files were not properly loaded in this terminal.
-> Try restarting this terminal to see if the issue persists
-> Check your shell rc files (e.g. .zshrc, .bashrc, ...)
-> You can create an issue at: https://github.com/curusarn/resh/issues
`
func checkShellSession() bool {
versionEnv, found := os.LookupEnv("__RESH_VERSION")
if !found {
out.Info(msgShellFilesNotLoaded)
return false
}
if version != versionEnv {
out.InfoTerminalVersionMismatch(version, versionEnv)
return false
}
return true
}
func checkShells() bool {
allOK := true
msg, err := check.LoginShell()
if err != nil {
out.InfoE("Failed to get login shell", err)
allOK = false
}
if msg != "" {
out.Info(msg)
allOK = false
}
msg, err = check.ZshVersion()
if err != nil {
out.InfoE("Failed to check zsh version", err)
allOK = false
}
if msg != "" {
out.Info(msg)
allOK = false
}
msg, err = check.BashVersion()
if err != nil {
out.InfoE("Failed to check bash version", err)
allOK = false
}
if msg != "" {
out.Info(msg)
allOK = false
}
return allOK
}

@ -33,7 +33,7 @@ func Execute(ver, com, development string) {
defer logger.Sync() // flushes buffer, if any defer logger.Sync() // flushes buffer, if any
out = output.New(logger, "ERROR") out = output.New(logger, "ERROR")
if errCfg != nil { if errCfg != nil {
out.Error("Error while getting configuration", errCfg) out.ErrorE("Error while getting configuration", errCfg)
} }
var versionCmd = cobra.Command{ var versionCmd = cobra.Command{
@ -43,10 +43,17 @@ func Execute(ver, com, development string) {
} }
rootCmd.AddCommand(&versionCmd) rootCmd.AddCommand(&versionCmd)
doctorCmd := cobra.Command{
Use: "doctor",
Short: "check common problems",
Run: doctorCmdFunc(config),
}
rootCmd.AddCommand(&doctorCmd)
updateCmd.Flags().BoolVar(&betaFlag, "beta", false, "Update to latest version even if it's beta.") updateCmd.Flags().BoolVar(&betaFlag, "beta", false, "Update to latest version even if it's beta.")
rootCmd.AddCommand(updateCmd) rootCmd.AddCommand(updateCmd)
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
out.Fatal("Command ended with error", err) out.FatalE("Command ended with error", err)
} }
} }

@ -15,7 +15,7 @@ var updateCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
out.Fatal("Could not get user home dir", err) out.FatalE("Could not get user home dir", err)
} }
rawinstallPath := filepath.Join(homeDir, ".resh/rawinstall.sh") rawinstallPath := filepath.Join(homeDir, ".resh/rawinstall.sh")
execArgs := []string{rawinstallPath} execArgs := []string{rawinstallPath}
@ -27,7 +27,7 @@ var updateCmd = &cobra.Command{
execCmd.Stderr = os.Stderr execCmd.Stderr = os.Stderr
err = execCmd.Run() err = execCmd.Run()
if err != nil { if err != nil {
out.Fatal("Update ended with error", err) out.FatalE("Update ended with error", err)
} }
}, },
} }

@ -11,6 +11,7 @@ import (
func versionCmdFunc(config cfg.Config) func(*cobra.Command, []string) { func versionCmdFunc(config cfg.Config) func(*cobra.Command, []string) {
return func(cmd *cobra.Command, args []string) { return func(cmd *cobra.Command, args []string) {
fmt.Printf("Installed: %s\n", version) fmt.Printf("Installed: %s\n", version)
versionEnv := getEnvVarWithDefault("__RESH_VERSION", "<unknown>") versionEnv := getEnvVarWithDefault("__RESH_VERSION", "<unknown>")
@ -18,6 +19,7 @@ func versionCmdFunc(config cfg.Config) func(*cobra.Command, []string) {
resp, err := status.GetDaemonStatus(config.Port) resp, err := status.GetDaemonStatus(config.Port)
if err != nil { if err != nil {
fmt.Printf("Running checks: %s\n", version)
out.ErrorDaemonNotRunning(err) out.ErrorDaemonNotRunning(err)
return return
} }

@ -33,7 +33,7 @@ func main() {
out := output.New(logger, "install-utils ERROR") out := output.New(logger, "install-utils ERROR")
if len(os.Args) < 2 { if len(os.Args) < 2 {
out.ErrorWOErr("ERROR: Not enough arguments\n") out.Error("ERROR: Not enough arguments\n")
printUsage(os.Stderr) printUsage(os.Stderr)
os.Exit(1) os.Exit(1)
} }
@ -52,7 +52,7 @@ func main() {
case "help": case "help":
printUsage(os.Stdout) printUsage(os.Stdout)
default: default:
out.ErrorWOErr(fmt.Sprintf("ERROR: Unknown command: %s\n", command)) out.Error(fmt.Sprintf("ERROR: Unknown command: %s\n", command))
printUsage(os.Stderr) printUsage(os.Stderr)
os.Exit(1) os.Exit(1)
} }

@ -15,11 +15,11 @@ import (
func migrateConfig(out *output.Output) { func migrateConfig(out *output.Output) {
err := cfg.Touch() err := cfg.Touch()
if err != nil { if err != nil {
out.Fatal("ERROR: Failed to touch config file", err) out.FatalE("ERROR: Failed to touch config file", err)
} }
changes, err := cfg.Migrate() changes, err := cfg.Migrate()
if err != nil { if err != nil {
out.Fatal("ERROR: Failed to update config file", err) out.FatalE("ERROR: Failed to update config file", err)
} }
if changes { if changes {
out.Info("RESH config file format has changed since last update - your config was updated to reflect the changes.") out.Info("RESH config file format has changed since last update - your config was updated to reflect the changes.")
@ -36,14 +36,14 @@ func migrateHistory(out *output.Output) {
func migrateHistoryLocation(out *output.Output) { func migrateHistoryLocation(out *output.Output) {
dataDir, err := datadir.MakePath() dataDir, err := datadir.MakePath()
if err != nil { if err != nil {
out.Fatal("ERROR: Failed to get data directory", err) out.FatalE("ERROR: Failed to get data directory", err)
} }
// TODO: de-hardcode this // TODO: de-hardcode this
historyPath := path.Join(dataDir, "resh/history.reshjson") historyPath := path.Join(dataDir, "resh/history.reshjson")
exists, err := futil.FileExists(historyPath) exists, err := futil.FileExists(historyPath)
if err != nil { if err != nil {
out.Fatal("ERROR: Failed to check history file", err) out.FatalE("ERROR: Failed to check history file", err)
} }
if exists { if exists {
out.Info(fmt.Sprintf("Found history file in '%s'", historyPath)) out.Info(fmt.Sprintf("Found history file in '%s'", historyPath))
@ -52,7 +52,7 @@ func migrateHistoryLocation(out *output.Output) {
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
out.Fatal("ERROR: Failed to get user home directory", err) out.FatalE("ERROR: Failed to get user home directory", err)
} }
legacyHistoryPaths := []string{ legacyHistoryPaths := []string{
@ -62,13 +62,13 @@ func migrateHistoryLocation(out *output.Output) {
for _, path := range legacyHistoryPaths { for _, path := range legacyHistoryPaths {
exists, err = futil.FileExists(path) exists, err = futil.FileExists(path)
if err != nil { if err != nil {
out.Fatal("ERROR: Failed to check legacy history file", err) out.FatalE("ERROR: Failed to check legacy history file", err)
} }
if exists { if exists {
out.Info(fmt.Sprintf("Copying history file to new location: '%s' -> '%s' ...", path, historyPath)) out.Info(fmt.Sprintf("Copying history file to new location: '%s' -> '%s' ...", path, historyPath))
err = futil.CopyFile(path, historyPath) err = futil.CopyFile(path, historyPath)
if err != nil { if err != nil {
out.Fatal("ERROR: Failed to copy history file", err) out.FatalE("ERROR: Failed to copy history file", err)
} }
out.Info("History file copied successfully") out.Info("History file copied successfully")
return return
@ -79,7 +79,7 @@ func migrateHistoryLocation(out *output.Output) {
func migrateHistoryFormat(out *output.Output) { func migrateHistoryFormat(out *output.Output) {
dataDir, err := datadir.MakePath() dataDir, err := datadir.MakePath()
if err != nil { if err != nil {
out.Fatal("ERROR: Could not get user data directory", err) out.FatalE("ERROR: Could not get user data directory", err)
} }
// TODO: de-hardcode this // TODO: de-hardcode this
historyPath := path.Join(dataDir, "history.reshjson") historyPath := path.Join(dataDir, "history.reshjson")
@ -87,35 +87,35 @@ func migrateHistoryFormat(out *output.Output) {
exists, err := futil.FileExists(historyPath) exists, err := futil.FileExists(historyPath)
if err != nil { if err != nil {
out.Fatal("ERROR: Failed to check existence of history file", err) out.FatalE("ERROR: Failed to check existence of history file", err)
} }
if !exists { if !exists {
out.ErrorWOErr("There is no history file - this is normal if you are installing RESH for the first time on this device") out.Error("There is no history file - this is normal if you are installing RESH for the first time on this device")
err = futil.TouchFile(historyPath) err = futil.TouchFile(historyPath)
if err != nil { if err != nil {
out.Fatal("ERROR: Failed to touch history file", err) out.FatalE("ERROR: Failed to touch history file", err)
} }
os.Exit(0) os.Exit(0)
} }
err = futil.CopyFile(historyPath, historyPathBak) err = futil.CopyFile(historyPath, historyPathBak)
if err != nil { if err != nil {
out.Fatal("ERROR: Could not back up history file", err) out.FatalE("ERROR: Could not back up history file", err)
} }
rio := recio.New(out.Logger.Sugar()) rio := recio.New(out.Logger.Sugar())
recs, err := rio.ReadAndFixFile(historyPath, 3) recs, err := rio.ReadAndFixFile(historyPath, 3)
if err != nil { if err != nil {
out.Fatal("ERROR: Could not load history file", err) out.FatalE("ERROR: Could not load history file", err)
} }
err = rio.OverwriteFile(historyPath, recs) err = rio.OverwriteFile(historyPath, recs)
if err != nil { if err != nil {
out.Error("ERROR: Could not update format of history file", err) out.ErrorE("ERROR: Could not update format of history file", err)
err = futil.CopyFile(historyPathBak, historyPath) err = futil.CopyFile(historyPathBak, historyPath)
if err != nil { if err != nil {
out.Fatal("ERROR: Could not restore history file from backup!", err) out.FatalE("ERROR: Could not restore history file from backup!", err)
// TODO: history restoration tutorial // TODO: history restoration tutorial
} }
out.Info("History file was restored to the original form") out.Info("History file was restored to the original form")

@ -47,11 +47,11 @@ func main() {
timeAfter, err := strconv.ParseFloat(*rta, 64) timeAfter, err := strconv.ParseFloat(*rta, 64)
if err != nil { if err != nil {
out.Fatal("Error while parsing flag --time-after", err) out.FatalE("Error while parsing flag --time-after", err)
} }
timeBefore, err := strconv.ParseFloat(*rtb, 64) timeBefore, err := strconv.ParseFloat(*rtb, 64)
if err != nil { if err != nil {
out.Fatal("Error while parsing flag --time-before", err) out.FatalE("Error while parsing flag --time-before", err)
} }
duration := timeAfter - timeBefore duration := timeAfter - timeBefore

@ -0,0 +1,90 @@
package check
import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
)
func LoginShell() (string, error) {
shellPath, found := os.LookupEnv("SHELL")
if !found {
return "", fmt.Errorf("env variable $SHELL is not set")
}
parts := strings.Split(shellPath, "/")
shell := parts[len(parts)-1]
if shell != "bash" && shell != "zsh" {
return fmt.Sprintf("Current shell (%s) is unsupported\n", shell), nil
}
return "", nil
}
func msgShellVersion(shell, expectedVer, actualVer string) string {
return fmt.Sprintf(`Minimal supported %s version is %s. You have %s.
-> Update your %s if you want to use RESH with it.
`, shell, expectedVer, actualVer, shell)
}
func BashVersion() (string, error) {
out, err := exec.Command("bash", "-c", "echo $BASH_VERSION").Output()
if err != nil {
return "", fmt.Errorf("command failed: %w", err)
}
verStr := strings.TrimSuffix(string(out), "\n")
ver, err := parseVersion(verStr)
if err != nil {
return "", fmt.Errorf("failed to parse version: %w", err)
}
if ver.Major < 4 || (ver.Major == 4 && ver.Minor < 3) {
return msgShellVersion("bash", "4.3", verStr), nil
}
return "", nil
}
func ZshVersion() (string, error) {
out, err := exec.Command("zsh", "-c", "echo $ZSH_VERSION").Output()
if err != nil {
return "", fmt.Errorf("command failed: %w", err)
}
verStr := strings.TrimSuffix(string(out), "\n")
ver, err := parseVersion(string(out))
if err != nil {
return "", fmt.Errorf("failed to parse version: %w", err)
}
if ver.Major < 5 {
return msgShellVersion("zsh", "5.0", verStr), nil
}
return "", nil
}
type version struct {
Major int
Minor int
Rest string
}
func parseVersion(str string) (version, error) {
parts := strings.SplitN(str, ".", 3)
if len(parts) < 3 {
return version{}, fmt.Errorf("not enough parts")
}
major, err := strconv.Atoi(parts[0])
if err != nil {
return version{}, fmt.Errorf("failed to parse major version: %w", err)
}
minor, err := strconv.Atoi(parts[1])
if err != nil {
return version{}, fmt.Errorf("failed to parse minor version: %w", err)
}
ver := version{
Major: major,
Minor: minor,
Rest: parts[2],
}
return ver, nil
}

@ -23,13 +23,13 @@ func SendRecord(out *output.Output, r recordint.Collect, port, path string) {
) )
recJSON, err := json.Marshal(r) recJSON, err := json.Marshal(r)
if err != nil { if err != nil {
out.Fatal("Error while encoding record", err) out.FatalE("Error while encoding record", err)
} }
req, err := http.NewRequest("POST", "http://localhost:"+port+path, req, err := http.NewRequest("POST", "http://localhost:"+port+path,
bytes.NewBuffer(recJSON)) bytes.NewBuffer(recJSON))
if err != nil { if err != nil {
out.Fatal("Error while sending record", err) out.FatalE("Error while sending record", err)
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
@ -50,13 +50,13 @@ func SendSessionInit(out *output.Output, r recordint.SessionInit, port string) {
) )
recJSON, err := json.Marshal(r) recJSON, err := json.Marshal(r)
if err != nil { if err != nil {
out.Fatal("Error while encoding record", err) out.FatalE("Error while encoding record", err)
} }
req, err := http.NewRequest("POST", "http://localhost:"+port+"/session_init", req, err := http.NewRequest("POST", "http://localhost:"+port+"/session_init",
bytes.NewBuffer(recJSON)) bytes.NewBuffer(recJSON))
if err != nil { if err != nil {
out.Fatal("Error while sending record", err) out.FatalE("Error while sending record", err)
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")

@ -2,6 +2,7 @@
package device package device
import ( import (
"bufio"
"fmt" "fmt"
"os" "os"
"path" "path"
@ -127,12 +128,16 @@ func promptForName(out *output.Output, fpath string) (string, error) {
hostStub := strings.Split(host, ".")[0] hostStub := strings.Split(host, ".")[0]
fmt.Printf("\nPlease choose a short name for this device (default: '%s'): ", hostStub) fmt.Printf("\nPlease choose a short name for this device (default: '%s'): ", hostStub)
var input string var input string
n, err := fmt.Scanln(&input) scanner := bufio.NewScanner(os.Stdin)
if n != 1 { if scanner.Scan() {
return "", fmt.Errorf("expected 1 value from prompt got %d", n) input = scanner.Text()
} }
if err != nil { if err = scanner.Err(); err != nil {
return "", fmt.Errorf("scanln error: %w", err) return "", fmt.Errorf("scanner error: %w", err)
}
if input == "" {
out.Info("Got no input - using default ...")
input = hostStub
} }
out.Info(fmt.Sprintf("Device name set to '%s'", input)) out.Info(fmt.Sprintf("Device name set to '%s'", input))
fmt.Printf("You can change the device name at any time by editing '%s' file\n", fpath) fmt.Printf("You can change the device name at any time by editing '%s' file\n", fpath)

@ -21,48 +21,72 @@ func New(logger *zap.Logger, prefix string) *Output {
} }
} }
// Info outputs string to stdout and to log (as info)
// This is how we write output to users from interactive commands
// This way we have full record in logs
func (f *Output) Info(msg string) { func (f *Output) Info(msg string) {
fmt.Printf("%s\n", msg) fmt.Printf("%s\n", msg)
f.Logger.Info(msg) f.Logger.Info(msg)
} }
func (f *Output) Error(msg string, err error) { // InfoE outputs string to stdout and to log (as error)
fmt.Fprintf(os.Stderr, "%s: %s: %v\n", f.ErrPrefix, msg, err) // Passed error is only written to log
// This is how we output errors to users from interactive commands
// This way we have errors in logs
func (f *Output) InfoE(msg string, err error) {
fmt.Printf("%s\n", msg)
f.Logger.Error(msg, zap.Error(err)) f.Logger.Error(msg, zap.Error(err))
} }
func (f *Output) ErrorWOErr(msg string) { // Error outputs string to stderr and to log (as error)
// This is how we output errors from non-interactive commands
func (f *Output) Error(msg string) {
fmt.Fprintf(os.Stderr, "%s: %s\n", f.ErrPrefix, msg) fmt.Fprintf(os.Stderr, "%s: %s\n", f.ErrPrefix, msg)
f.Logger.Error(msg) f.Logger.Error(msg)
} }
func (f *Output) Fatal(msg string, err error) { // ErrorE outputs string and error to stderr and to log (as error)
// This is how we output errors from non-interactive commands
func (f *Output) ErrorE(msg string, err error) {
fmt.Fprintf(os.Stderr, "%s: %s: %v\n", f.ErrPrefix, msg, err)
f.Logger.Error(msg, zap.Error(err))
}
// FatalE outputs string and error to stderr and to log (as fatal)
// This is how we raise fatal errors from non-interactive commands
func (f *Output) FatalE(msg string, err error) {
fmt.Fprintf(os.Stderr, "%s: %s: %v\n", f.ErrPrefix, msg, err) fmt.Fprintf(os.Stderr, "%s: %s: %v\n", f.ErrPrefix, msg, err)
f.Logger.Fatal(msg, zap.Error(err)) f.Logger.Fatal(msg, zap.Error(err))
} }
var msgDaemonNotRunning = `Resh-daemon didn't respond - it's probably not running. var msgDaemonNotRunning = `RESH daemon didn't respond - it's probably not running.
-> Try restarting this terminal window to bring resh-daemon back up -> Start RESH daemon manually - run: resh-daemon-start
-> If the problem persists you can check resh-daemon logs: ~/.local/share/resh/log.json (or ~/$XDG_DATA_HOME/resh/log.json) -> Or restart this terminal window to bring RESH daemon back up
-> You can check logs: ~/.local/share/resh/log.json (or ~/$XDG_DATA_HOME/resh/log.json)
-> You can create an issue at: https://github.com/curusarn/resh/issues -> You can create an issue at: https://github.com/curusarn/resh/issues
` `
var msgTerminalVersionMismatch = `This terminal session was started with different resh version than is installed now. var msgTerminalVersionMismatch = `This terminal session was started with different RESH version than is installed now.
It looks like you updated resh and didn't restart this terminal. It looks like you updated RESH and didn't restart this terminal.
-> Restart this terminal window to fix that -> Restart this terminal window to fix that
` `
var msgDaemonVersionMismatch = `Resh-daemon is running in different version than is installed now. var msgDaemonVersionMismatch = `RESH daemon is running in different version than is installed now.
It looks like something went wrong during resh update. It looks like something went wrong during RESH update.
-> Kill resh-daemon and then launch a new terminal window to fix that: pkill resh-daemon -> Kill resh-daemon and then launch a new terminal window to fix that: killall resh-daemon
-> You can create an issue at: https://github.com/curusarn/resh/issues -> You can create an issue at: https://github.com/curusarn/resh/issues
` `
func (f *Output) InfoDaemonNotRunning(err error) {
fmt.Printf("%s", msgDaemonNotRunning)
f.Logger.Error("Daemon is not running", zap.Error(err))
}
func (f *Output) ErrorDaemonNotRunning(err error) { func (f *Output) ErrorDaemonNotRunning(err error) {
fmt.Fprintf(os.Stderr, "%s: %s", f.ErrPrefix, msgDaemonNotRunning) fmt.Fprintf(os.Stderr, "%s: %s", f.ErrPrefix, msgDaemonNotRunning)
f.Logger.Error("Daemon is not running", zap.Error(err)) f.Logger.Error("Daemon is not running", zap.Error(err))
@ -73,6 +97,14 @@ func (f *Output) FatalDaemonNotRunning(err error) {
f.Logger.Fatal("Daemon is not running", zap.Error(err)) f.Logger.Fatal("Daemon is not running", zap.Error(err))
} }
func (f *Output) InfoTerminalVersionMismatch(installedVer, terminalVer string) {
fmt.Printf("%s(installed version: %s, this terminal version: %s)\n\n",
msgTerminalVersionMismatch, installedVer, terminalVer)
f.Logger.Fatal("Version mismatch",
zap.String("installed", installedVer),
zap.String("terminal", terminalVer))
}
func (f *Output) ErrorTerminalVersionMismatch(installedVer, terminalVer string) { func (f *Output) ErrorTerminalVersionMismatch(installedVer, terminalVer string) {
fmt.Fprintf(os.Stderr, "%s: %s(installed version: %s, this terminal version: %s)\n\n", fmt.Fprintf(os.Stderr, "%s: %s(installed version: %s, this terminal version: %s)\n\n",
f.ErrPrefix, msgTerminalVersionMismatch, installedVer, terminalVer) f.ErrPrefix, msgTerminalVersionMismatch, installedVer, terminalVer)
@ -89,6 +121,14 @@ func (f *Output) FatalTerminalVersionMismatch(installedVer, terminalVer string)
zap.String("terminal", terminalVer)) zap.String("terminal", terminalVer))
} }
func (f *Output) InfoDaemonVersionMismatch(installedVer, daemonVer string) {
fmt.Printf("%s(installed version: %s, running daemon version: %s)\n\n",
msgDaemonVersionMismatch, installedVer, daemonVer)
f.Logger.Error("Version mismatch",
zap.String("installed", installedVer),
zap.String("daemon", daemonVer))
}
func (f *Output) ErrorDaemonVersionMismatch(installedVer, daemonVer string) { func (f *Output) ErrorDaemonVersionMismatch(installedVer, daemonVer string) {
fmt.Fprintf(os.Stderr, "%s: %s(installed version: %s, running daemon version: %s)\n\n", fmt.Fprintf(os.Stderr, "%s: %s(installed version: %s, running daemon version: %s)\n\n",
f.ErrPrefix, msgDaemonVersionMismatch, installedVer, daemonVer) f.ErrPrefix, msgDaemonVersionMismatch, installedVer, daemonVer)

@ -115,10 +115,10 @@ failed_to_kill() {
} }
if [ -f "$pid_file" ]; then if [ -f "$pid_file" ]; then
kill -SIGKILL "$pid_file" || failed_to_kill kill -SIGTERM "$pid_file" || failed_to_kill
rm "$pid_file" rm "$pid_file"
else else
pkill -SIGKILL "resh-daemon" || failed_to_kill killall -SIGTERM resh-daemon || failed_to_kill
fi fi
echo "Creating/updating config file ..." echo "Creating/updating config file ..."
@ -152,6 +152,9 @@ fi
echo "Launching resh daemon ..." echo "Launching resh daemon ..."
~/.resh/bin/resh-daemon-start ~/.resh/bin/resh-daemon-start
# FIXME: Figure out how to give people enough time to read everything
# sleep 1
info="---- Scroll down using arrow keys ---- info="---- Scroll down using arrow keys ----
##################################### #####################################
# ____ _____ ____ _ _ # # ____ _____ ____ _ _ #
@ -188,6 +191,7 @@ ISSUES & FEEDBACK
Please report issues to: https://github.com/curusarn/resh/issues Please report issues to: https://github.com/curusarn/resh/issues
Feedback and suggestions are very welcome! Feedback and suggestions are very welcome!
" "
# Show banner if RESH is not loaded in the terminal
if [ -z "${__RESH_VERSION:-}" ]; then info="$info if [ -z "${__RESH_VERSION:-}" ]; then info="$info
############################################################## ##############################################################
# # # #
@ -206,6 +210,7 @@ echo "Thank you for using RESH"
echo "Report issues here: https://github.com/curusarn/resh/issues" echo "Report issues here: https://github.com/curusarn/resh/issues"
echo "Ctrl+R launches the RESH SEARCH app" echo "Ctrl+R launches the RESH SEARCH app"
# Show banner if RESH is not loaded in the terminal
if [ -z "${__RESH_VERSION:-}" ]; then printf " if [ -z "${__RESH_VERSION:-}" ]; then printf "
############################################################## ##############################################################
# # # #

Loading…
Cancel
Save