diff --git a/.goreleaser.yml b/.goreleaser.yml index 4cc663d..cefc5d7 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -94,18 +94,18 @@ builds: - arm - arm64 - - id: "config-setup" - main: ./cmd/config-setup - binary: bin/resh-config-setup + id: "install-utils" + main: ./cmd/install-utils + binary: bin/resh-install-utils goarch: - 386 - amd64 - arm - arm64 - - id: "install-utils" - main: ./cmd/install-utils - binary: bin/resh-install-utils + id: "generate-uuid" + main: ./cmd/generate-uuid + binary: bin/resh-generate-uuid goarch: - 386 - amd64 diff --git a/Makefile b/Makefile index e5b5c87..fa33c86 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ GOFLAGS=-ldflags "-X main.version=${VERSION} -X main.commit=${COMMIT} -X main.de build: submodules bin/resh-session-init bin/resh-collect bin/resh-postcollect\ bin/resh-daemon bin/resh-control bin/resh-config bin/resh-cli\ - bin/resh-install-utils + bin/resh-install-utils bin/resh-generate-uuid install: build scripts/install.sh @@ -22,11 +22,11 @@ rebuild: make build clean: - rm -f bin/* + rm -f -- bin/* uninstall: # Uninstalling ... - -rm -rf ~/.resh/ + -rm -rf -- ~/.resh/ go_files = $(shell find -name '*.go') bin/resh-%: $(go_files) diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 5d588f9..1de3544 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -16,6 +16,8 @@ import ( "github.com/awesome-gocui/gocui" "github.com/curusarn/resh/internal/cfg" + "github.com/curusarn/resh/internal/datadir" + "github.com/curusarn/resh/internal/device" "github.com/curusarn/resh/internal/logger" "github.com/curusarn/resh/internal/msg" "github.com/curusarn/resh/internal/output" @@ -29,14 +31,14 @@ import ( // info passed during build var version string var commit string -var developement bool +var development string // special constant recognized by RESH wrappers const exitCodeExecute = 111 func main() { config, errCfg := cfg.New() - logger, _ := logger.New("search-app", config.LogLevel, developement) + logger, _ := logger.New("search-app", config.LogLevel, development) defer logger.Sync() // flushes buffer, if any if errCfg != nil { logger.Error("Error while getting configuration", zap.Error(errCfg)) @@ -50,7 +52,6 @@ func main() { func runReshCli(out *output.Output, config cfg.Config) (string, int) { sessionID := flag.String("sessionID", "", "resh generated session id") - host := flag.String("host", "", "host") pwd := flag.String("pwd", "", "present working directory") gitOriginRemote := flag.String("gitOriginRemote", "DEFAULT", "git origin remote") query := flag.String("query", "", "search query") @@ -62,15 +63,20 @@ func runReshCli(out *output.Output, config cfg.Config) (string, int) { if *sessionID == "" { out.Fatal(errMsg, errors.New("missing option --sessionId")) } - if *host == "" { - out.Fatal(errMsg, errors.New("missing option --host")) - } if *pwd == "" { out.Fatal(errMsg, errors.New("missing option --pwd")) } if *gitOriginRemote == "DEFAULT" { out.Fatal(errMsg, errors.New("missing option --gitOriginRemote")) } + dataDir, err := datadir.GetPath() + if err != nil { + out.Fatal("Could not get user data directory", err) + } + deviceName, err := device.GetName(dataDir) + if err != nil { + out.Fatal("Could not get device name", err) + } g, err := gocui.NewGui(gocui.OutputNormal, false) if err != nil { @@ -100,11 +106,12 @@ func runReshCli(out *output.Output, config cfg.Config) (string, int) { initialQuery: *query, } + // TODO: Use device ID layout := manager{ out: out, config: config, sessionID: *sessionID, - host: *host, + host: deviceName, pwd: *pwd, gitOriginRemote: *gitOriginRemote, s: &st, diff --git a/cmd/collect/main.go b/cmd/collect/main.go index 230fda8..0bfbed2 100644 --- a/cmd/collect/main.go +++ b/cmd/collect/main.go @@ -22,11 +22,11 @@ import ( // info passed during build var version string var commit string -var developement bool +var development string func main() { config, errCfg := cfg.New() - logger, _ := logger.New("collect", config.LogLevel, developement) + logger, _ := logger.New("collect", config.LogLevel, development) defer logger.Sync() // flushes buffer, if any if errCfg != nil { logger.Error("Error while getting configuration", zap.Error(errCfg)) @@ -46,17 +46,12 @@ func main() { home := flag.String("home", "", "$HOME") pwd := flag.String("pwd", "", "$PWD - present working directory") - // FIXME: get device ID - deviceID := flag.String("deviceID", "", "RESH device ID") sessionID := flag.String("sessionID", "", "resh generated session ID") recordID := flag.String("recordID", "", "resh generated record ID") sessionPID := flag.Int("sessionPID", -1, "PID at the start of the terminal session") shell := flag.String("shell", "", "current shell") - // logname := flag.String("logname", "", "$LOGNAME") - device := flag.String("device", "", "device name, usually $HOSTNAME") - // non-posix shlvl := flag.Int("shlvl", -1, "$SHLVL") @@ -100,7 +95,6 @@ func main() { Shell: *shell, Rec: record.V1{ - DeviceID: *deviceID, SessionID: *sessionID, RecordID: *recordID, @@ -111,9 +105,6 @@ func main() { Pwd: *pwd, RealPwd: realPwd, - // Logname: *logname, - Device: *device, - GitOriginRemote: *gitRemote, Time: fmt.Sprintf("%.4f", time), diff --git a/cmd/config/main.go b/cmd/config/main.go index eaa68b8..e397901 100644 --- a/cmd/config/main.go +++ b/cmd/config/main.go @@ -14,11 +14,11 @@ import ( // info passed during build var version string var commit string -var developement bool +var development string func main() { config, errCfg := cfg.New() - logger, _ := logger.New("config", config.LogLevel, developement) + logger, _ := logger.New("config", config.LogLevel, development) defer logger.Sync() // flushes buffer, if any if errCfg != nil { logger.Error("Error while getting configuration", zap.Error(errCfg)) diff --git a/cmd/control/cmd/root.go b/cmd/control/cmd/root.go index baf12b0..475452c 100644 --- a/cmd/control/cmd/root.go +++ b/cmd/control/cmd/root.go @@ -7,10 +7,8 @@ import ( "github.com/spf13/cobra" ) -// info passed during build var version string var commit string -var developement bool // globals var config cfg.Config @@ -22,12 +20,12 @@ var rootCmd = &cobra.Command{ } // Execute reshctl -func Execute(ver, com string) { +func Execute(ver, com, development string) { version = ver commit = com config, errCfg := cfg.New() - logger, _ := logger.New("reshctl", config.LogLevel, developement) + logger, _ := logger.New("reshctl", config.LogLevel, development) defer logger.Sync() // flushes buffer, if any out = output.New(logger, "ERROR") if errCfg != nil { diff --git a/cmd/control/main.go b/cmd/control/main.go index 79d7289..b431b67 100644 --- a/cmd/control/main.go +++ b/cmd/control/main.go @@ -4,12 +4,10 @@ import ( "github.com/curusarn/resh/cmd/control/cmd" ) -// version from git set during build var version string - -// commit from git set during build var commit string +var development string func main() { - cmd.Execute(version, commit) + cmd.Execute(version, commit, development) } diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go index 6a6feff..5433419 100644 --- a/cmd/daemon/main.go +++ b/cmd/daemon/main.go @@ -10,6 +10,8 @@ import ( "strings" "github.com/curusarn/resh/internal/cfg" + "github.com/curusarn/resh/internal/datadir" + "github.com/curusarn/resh/internal/device" "github.com/curusarn/resh/internal/httpclient" "github.com/curusarn/resh/internal/logger" "go.uber.org/zap" @@ -18,11 +20,11 @@ import ( // info passed during build var version string var commit string -var developement bool +var development string func main() { config, errCfg := cfg.New() - logger, err := logger.New("daemon", config.LogLevel, developement) + logger, err := logger.New("daemon", config.LogLevel, development) if err != nil { fmt.Printf("Error while creating logger: %v", err) } @@ -32,48 +34,60 @@ func main() { } sugar := logger.Sugar() d := daemon{sugar: sugar} - sugar.Infow("Deamon starting ...", + sugar.Infow("Daemon starting ...", "version", version, "commit", commit, ) - - // TODO: rethink PID file and logs location + dataDir, err := datadir.MakePath() + if err != nil { + sugar.Fatalw("Could not get user data directory", zap.Error(err)) + } homeDir, err := os.UserHomeDir() if err != nil { - sugar.Fatalw("Could not get user home dir", zap.Error(err)) + sugar.Fatalw("Could not get user home directory", zap.Error(err)) } - PIDFile := filepath.Join(homeDir, ".resh/resh.pid") - reshHistoryPath := filepath.Join(homeDir, ".resh_history.json") + // TODO: These paths should be probably defined in a package + pidFile := filepath.Join(dataDir, "daemon.pid") + reshHistoryPath := filepath.Join(dataDir, "history.reshjson") bashHistoryPath := filepath.Join(homeDir, ".bash_history") zshHistoryPath := filepath.Join(homeDir, ".zsh_history") + deviceID, err := device.GetID(dataDir) + if err != nil { + sugar.Fatalw("Could not get resh device ID", zap.Error(err)) + } + deviceName, err := device.GetName(dataDir) + if err != nil { + sugar.Fatalw("Could not get resh device name", zap.Error(err)) + } sugar = sugar.With(zap.Int("daemonPID", os.Getpid())) res, err := d.isDaemonRunning(config.Port) if err != nil { - sugar.Errorw("Error while checking daemon status - "+ - "it's probably not running", "error", err) + sugar.Errorw("Error while checking daemon status - it's probably not running", + "error", err) } if res { sugar.Errorw("Daemon is already running - exiting!") return } - _, err = os.Stat(PIDFile) + _, err = os.Stat(pidFile) if err == nil { - sugar.Warn("Pidfile exists") + sugar.Warnw("PID file exists", + "PIDFile", pidFile) // kill daemon - err = d.killDaemon(PIDFile) + err = d.killDaemon(pidFile) if err != nil { sugar.Errorw("Could not kill daemon", "error", err, ) } } - err = ioutil.WriteFile(PIDFile, []byte(strconv.Itoa(os.Getpid())), 0644) + err = ioutil.WriteFile(pidFile, []byte(strconv.Itoa(os.Getpid())), 0644) if err != nil { - sugar.Fatalw("Could not create pidfile", + sugar.Fatalw("Could not create PID file", "error", err, - "PIDFile", PIDFile, + "PIDFile", pidFile, ) } server := Server{ @@ -82,12 +96,15 @@ func main() { reshHistoryPath: reshHistoryPath, bashHistoryPath: bashHistoryPath, zshHistoryPath: zshHistoryPath, + + deviceID: deviceID, + deviceName: deviceName, } server.Run() sugar.Infow("Removing PID file ...", - "PIDFile", PIDFile, + "PIDFile", pidFile, ) - err = os.Remove(PIDFile) + err = os.Remove(pidFile) if err != nil { sugar.Errorw("Could not delete PID file", "error", err) } @@ -98,16 +115,6 @@ type daemon struct { sugar *zap.SugaredLogger } -func (d *daemon) getEnvOrPanic(envVar string) string { - val, found := os.LookupEnv(envVar) - if !found { - d.sugar.Fatalw("Required env variable is not set", - "variableName", envVar, - ) - } - return val -} - func (d *daemon) isDaemonRunning(port int) (bool, error) { url := "http://localhost:" + strconv.Itoa(port) + "/status" client := httpclient.New() @@ -119,14 +126,14 @@ func (d *daemon) isDaemonRunning(port int) (bool, error) { return true, nil } -func (d *daemon) killDaemon(pidfile string) error { - dat, err := ioutil.ReadFile(pidfile) +func (d *daemon) killDaemon(pidFile string) error { + dat, err := ioutil.ReadFile(pidFile) if err != nil { - d.sugar.Errorw("Reading pid file failed", - "PIDFile", pidfile, + d.sugar.Errorw("Reading PID file failed", + "PIDFile", pidFile, "error", err) } - d.sugar.Infow("Succesfully read PID file", "contents", string(dat)) + d.sugar.Infow("Successfully read PID file", "contents", string(dat)) pid, err := strconv.Atoi(strings.TrimSuffix(string(dat), "\n")) if err != nil { return fmt.Errorf("could not parse PID file contents: %w", err) diff --git a/cmd/daemon/record.go b/cmd/daemon/record.go index 34e9c1c..8fc6bb4 100644 --- a/cmd/daemon/record.go +++ b/cmd/daemon/record.go @@ -19,6 +19,9 @@ func NewRecordHandler(sugar *zap.SugaredLogger, subscribers []chan recordint.Col type recordHandler struct { sugar *zap.SugaredLogger subscribers []chan recordint.Collect + + deviceID string + deviceName string } func (h *recordHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -51,6 +54,8 @@ func (h *recordHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { "cmdLine", rec.Rec.CmdLine, "part", part, ) + rec.Rec.DeviceID = h.deviceID + rec.Rec.Device = h.deviceName sugar.Debugw("Got record, sending to subscribers ...") for _, sub := range h.subscribers { sub <- rec diff --git a/cmd/daemon/run-server.go b/cmd/daemon/run-server.go index afe77a1..e7631c4 100644 --- a/cmd/daemon/run-server.go +++ b/cmd/daemon/run-server.go @@ -23,6 +23,9 @@ type Server struct { reshHistoryPath string bashHistoryPath string zshHistoryPath string + + deviceID string + deviceName string } func (s *Server) Run() { @@ -63,7 +66,12 @@ func (s *Server) Run() { // handlers mux := http.NewServeMux() mux.Handle("/status", &statusHandler{sugar: s.sugar}) - mux.Handle("/record", &recordHandler{sugar: s.sugar, subscribers: recordSubscribers}) + mux.Handle("/record", &recordHandler{ + sugar: s.sugar, + subscribers: recordSubscribers, + deviceID: s.deviceID, + deviceName: s.deviceName, + }) mux.Handle("/session_init", &sessionInitHandler{sugar: s.sugar, subscribers: sessionInitSubscribers}) mux.Handle("/dump", &dumpHandler{sugar: s.sugar, histfileBox: histfileBox}) diff --git a/cmd/generate-uuid/main.go b/cmd/generate-uuid/main.go new file mode 100644 index 0000000..8c238a9 --- /dev/null +++ b/cmd/generate-uuid/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "fmt" + "os" + + "github.com/google/uuid" +) + +// Small utility to generate UUID's using google/uuid golang package +// Doesn't check arguments +// Exits with status 1 on error +func main() { + rnd, err := uuid.NewRandom() + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: could not get new random source: %v", err) + os.Exit(1) + } + id := rnd.String() + if id == "" { + fmt.Fprintf(os.Stderr, "ERROR: got invalid UUID from package") + os.Exit(1) + } + // No newline + fmt.Print(id) +} diff --git a/cmd/install-utils/device.go b/cmd/install-utils/device.go new file mode 100644 index 0000000..0f51998 --- /dev/null +++ b/cmd/install-utils/device.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "os" + + "github.com/curusarn/resh/internal/datadir" + "github.com/curusarn/resh/internal/device" +) + +func setupDevice() { + dataDir, err := datadir.MakePath() + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: Failed to get/setup data directory: %v\n", err) + os.Exit(1) + } + err = device.SetupName(dataDir) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: Failed to check/setup device name: %v\n", err) + os.Exit(1) + } + err = device.SetupID(dataDir) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: Failed to check/setup device ID: %v\n", err) + os.Exit(1) + } +} diff --git a/cmd/install-utils/main.go b/cmd/install-utils/main.go index 0d09a30..d06d6f5 100644 --- a/cmd/install-utils/main.go +++ b/cmd/install-utils/main.go @@ -3,17 +3,39 @@ package main import ( "fmt" "os" + + "github.com/curusarn/resh/internal/cfg" + "github.com/curusarn/resh/internal/logger" + "github.com/curusarn/resh/internal/output" + "go.uber.org/zap" ) // info passed during build var version string var commit string -var developement bool +var development string func main() { + config, errCfg := cfg.New() + logger, err := logger.New("install-utils", config.LogLevel, development) + if err != nil { + fmt.Printf("Error while creating logger: %v", err) + } + defer logger.Sync() // flushes buffer, if any + if errCfg != nil { + logger.Error("Error while getting configuration", zap.Error(errCfg)) + } + sugar := logger.Sugar() + sugar.Infow("Install-utils invoked ...", + "version", version, + "commit", commit, + ) + out := output.New(logger, "install-utils") + if len(os.Args) < 2 { - fmt.Fprintf(os.Stderr, "ERROR: Not eonugh arguments\n") + out.ErrorWOErr("ERROR: Not enough arguments\n") printUsage(os.Stderr) + os.Exit(1) } command := os.Args[1] switch command { @@ -24,27 +46,31 @@ func main() { case "migrate-config": migrateConfig() case "migrate-history": - migrateHistory() + migrateHistory(out) + case "setup-device": + setupDevice() case "help": printUsage(os.Stdout) default: - fmt.Fprintf(os.Stderr, "ERROR: Unknown command: %s\n", command) + out.ErrorWOErr(fmt.Sprintf("ERROR: Unknown command: %s\n", command)) printUsage(os.Stderr) + os.Exit(1) } } func printUsage(f *os.File) { usage := ` USAGE: ./install-utils COMMAND -Utils used during RESH instalation. +Utils used during RESH installation. COMMANDS: - backup backup resh installation and data - rollback restore resh installation and data from backup - migrate-config update config to reflect updates - migrate-history update history to reflect updates - help show this help + backup backup resh installation and data + rollback restore resh installation and data from backup + migrate-config update config to latest format + migrate-history update history to latest format + setup-device setup device name and device ID + help show this help ` - fmt.Fprintf(f, usage) + fmt.Fprint(f, usage) } diff --git a/cmd/install-utils/migrate.go b/cmd/install-utils/migrate.go index e68ffe2..0336f98 100644 --- a/cmd/install-utils/migrate.go +++ b/cmd/install-utils/migrate.go @@ -3,8 +3,14 @@ package main import ( "fmt" "os" + "path" "github.com/curusarn/resh/internal/cfg" + "github.com/curusarn/resh/internal/datadir" + "github.com/curusarn/resh/internal/futil" + "github.com/curusarn/resh/internal/output" + "github.com/curusarn/resh/internal/recio" + "github.com/curusarn/resh/record" ) func migrateConfig() { @@ -23,18 +29,101 @@ func migrateConfig() { } } -func migrateHistory() { - // homeDir, err := os.UserHomeDir() - // if err != nil { +func migrateHistory(out *output.Output) { + migrateHistoryLocation(out) + migrateHistoryFormat(out) +} + +func migrateHistoryLocation(out *output.Output) { + dataDir, err := datadir.MakePath() + if err != nil { + out.Fatal("ERROR: Failed to get data directory", err) + } + // TODO: de-hardcode this + historyPath := path.Join(dataDir, "resh/history.reshjson") + + exists, err := futil.FileExists(historyPath) + if err != nil { + out.Fatal("ERROR: Failed to check history file", err) + } + if exists { + out.Info(fmt.Sprintf("Found history file in '%s'", historyPath)) + return + } + + homeDir, err := os.UserHomeDir() + if err != nil { + out.Fatal("ERROR: Failed to get user home directory", err) + } + + legacyHistoryPaths := []string{ + path.Join(homeDir, ".resh_history.json"), + path.Join(homeDir, ".resh/history.json"), + } + for _, path := range legacyHistoryPaths { + exists, err = futil.FileExists(path) + if err != nil { + out.Fatal("ERROR: Failed to check legacy history file", err) + } + if exists { + out.Info(fmt.Sprintf("Copying history file to new location: '%s' -> '%s' ...", path, historyPath)) + err = futil.CopyFile(path, historyPath) + if err != nil { + out.Fatal("ERROR: Failed to copy history file", err) + } + out.Info("History file copied successfully") + return + } + } +} - // } +func migrateHistoryFormat(out *output.Output) { + dataDir, err := datadir.MakePath() + if err != nil { + out.Fatal("ERROR: Could not get user data directory", err) + } + // TODO: de-hardcode this + historyPath := path.Join(dataDir, "history.reshjson") + historyPathBak := historyPath + ".bak" + + exists, err := futil.FileExists(historyPath) + if err != nil { + out.Fatal("ERROR: Failed to check existence of history file", err) + } + if !exists { + out.ErrorWOErr("There is no history file - this is normal if you are installing RESH for the first time on this device") + err = futil.CreateFile(historyPath) + if err != nil { + out.Fatal("ERROR: Failed to create history file", err) + } + os.Exit(0) + } - // TODO: Find history in: - // - .resh/history.json (copy) - message user to delete the file once they confirm the new setup works - // - .resh_history.json (copy) - message user to delete the file once they confirm the new setup works - // - xdg_data/resh/history.reshjson + err = futil.CopyFile(historyPath, historyPathBak) + if err != nil { + out.Fatal("ERROR: Could not back up history file", err) + } - // Read xdg_data/resh/history.reshjson - // Write xdg_data/resh/history.reshjson - // the old format can be found in the backup dir + rio := recio.New(out.Logger.Sugar()) + + recs, err := rio.ReadAndFixFile(historyPath, 3) + if err != nil { + out.Fatal("ERROR: Could not load history file", err) + } + // TODO: get rid of this conversion + var recsV1 []record.V1 + for _, rec := range recs { + recsV1 = append(recsV1, rec.Rec) + } + err = rio.OverwriteFile(historyPath, recsV1) + if err != nil { + out.Error("ERROR: Could not update format of history file", err) + + err = futil.CopyFile(historyPathBak, historyPath) + if err != nil { + out.Fatal("ERROR: Could not restore history file from backup!", err) + // TODO: history restoration tutorial + } + out.Info("History file was restored to the original form") + } } diff --git a/cmd/postcollect/main.go b/cmd/postcollect/main.go index a187808..b1d9f6f 100644 --- a/cmd/postcollect/main.go +++ b/cmd/postcollect/main.go @@ -21,11 +21,11 @@ import ( // info passed during build var version string var commit string -var developement bool +var development string func main() { config, errCfg := cfg.New() - logger, _ := logger.New("postcollect", config.LogLevel, developement) + logger, _ := logger.New("postcollect", config.LogLevel, development) defer logger.Sync() // flushes buffer, if any if errCfg != nil { logger.Error("Error while getting configuration", zap.Error(errCfg)) diff --git a/cmd/session-init/main.go b/cmd/session-init/main.go index c966f23..f7c5293 100644 --- a/cmd/session-init/main.go +++ b/cmd/session-init/main.go @@ -18,11 +18,11 @@ import ( // info passed during build var version string var commit string -var developement bool +var development string func main() { config, errCfg := cfg.New() - logger, _ := logger.New("collect", config.LogLevel, developement) + logger, _ := logger.New("collect", config.LogLevel, development) defer logger.Sync() // flushes buffer, if any if errCfg != nil { logger.Error("Error while getting configuration", zap.Error(errCfg)) diff --git a/go.mod b/go.mod index db1e19f..f0a521f 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ go 1.18 require ( github.com/BurntSushi/toml v0.4.1 github.com/awesome-gocui/gocui v1.0.0 + github.com/google/uuid v1.1.2 + github.com/mattn/go-isatty v0.0.3 github.com/mitchellh/go-ps v1.0.0 github.com/spf13/cobra v1.2.1 github.com/whilp/git-urls v1.0.0 diff --git a/go.sum b/go.sum index ec1b8bf..311695f 100644 --- a/go.sum +++ b/go.sum @@ -143,6 +143,7 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -187,6 +188,7 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= diff --git a/internal/device/device.go b/internal/device/device.go index 8a34ead..a952f22 100644 --- a/internal/device/device.go +++ b/internal/device/device.go @@ -5,45 +5,113 @@ import ( "os" "path" "strings" + + "github.com/curusarn/resh/internal/futil" + "github.com/google/uuid" + isatty "github.com/mattn/go-isatty" ) +const fnameID = "device-id" +const fnameName = "device-name" + +const filePerm = 0644 + +// Getters + func GetID(dataDir string) (string, error) { - fname := "device-id" - dat, err := os.ReadFile(path.Join(dataDir, fname)) + return readValue(dataDir, fnameID) +} + +func GetName(dataDir string) (string, error) { + return readValue(dataDir, fnameName) +} + +// Install helpers + +func SetupID(dataDir string) error { + return generateIDIfUnset(dataDir) +} + +func SetupName(dataDir string) error { + return promptAndWriteNameIfUnset(dataDir) +} + +func readValue(dataDir, fname string) (string, error) { + fpath := path.Join(dataDir, fname) + dat, err := os.ReadFile(fpath) if err != nil { - return "", fmt.Errorf("could not read file with device-id: %w", err) + return "", fmt.Errorf("could not read file with %s: %w", fname, err) } - id := strings.TrimRight(string(dat), "\n") - return id, nil + val := strings.TrimRight(string(dat), "\n") + return val, nil } -func GetName(dataDir string) (string, error) { - fname := "device-name" - dat, err := os.ReadFile(path.Join(dataDir, fname)) - if err != nil { - return "", fmt.Errorf("could not read file with device-name: %w", err) - } - name := strings.TrimRight(string(dat), "\n") - return name, nil -} - -// TODO: implement, possibly with a better name -// func CheckID(dataDir string) (string, error) { -// fname := "device-id" -// dat, err := os.ReadFile(path.Join(dataDir, fname)) -// if err != nil { -// return "", fmt.Errorf("could not read file with device-id: %w", err) -// } -// id := strings.TrimRight(string(dat), "\n") -// return id, nil -// } -// -// func CheckName(dataDir string) (string, error) { -// fname := "device-id" -// dat, err := os.ReadFile(path.Join(dataDir, fname)) -// if err != nil { -// return "", fmt.Errorf("could not read file with device-id: %w", err) -// } -// id := strings.TrimRight(string(dat), "\n") -// return id, nil -// } +func generateIDIfUnset(dataDir string) error { + fpath := path.Join(dataDir, fnameID) + exists, err := futil.FileExists(fpath) + if err != nil { + return err + } + if exists { + return nil + } + + rnd, err := uuid.NewRandom() + if err != nil { + return fmt.Errorf("could not get new random source: %w", err) + } + id := rnd.String() + if id == "" { + return fmt.Errorf("got invalid UUID from package") + } + err = os.WriteFile(fpath, []byte(id), filePerm) + if err != nil { + return fmt.Errorf("could not write generated ID to file: %w", err) + } + return nil +} + +func promptAndWriteNameIfUnset(dataDir string) error { + fpath := path.Join(dataDir, fnameName) + exists, err := futil.FileExists(fpath) + if err != nil { + return err + } + if exists { + return nil + } + + name, err := promptForName(fpath) + if err != nil { + return fmt.Errorf("error while prompting for input: %w", err) + } + err = os.WriteFile(fpath, []byte(name), filePerm) + if err != nil { + return fmt.Errorf("could not write name to file: %w", err) + } + return nil +} + +func promptForName(fpath string) (string, error) { + // This function should be only ran from install-utils with attached terminal + if !isatty.IsTerminal(os.Stdout.Fd()) { + return "", fmt.Errorf("output is not a terminal - write name of this device to '%s' to bypass this error", fpath) + } + host, err := os.Hostname() + if err != nil { + return "", fmt.Errorf("could not get hostname (prompt default): %w", err) + } + hostStub := strings.Split(host, ".")[0] + fmt.Printf("\nPlease choose a short name for this device (default: '%s'): ", hostStub) + var input string + n, err := fmt.Scanln(&input) + if n != 1 { + return "", fmt.Errorf("expected 1 value from prompt got %d", n) + } + if err != nil { + return "", fmt.Errorf("scanln error: %w", err) + } + fmt.Printf("Input was: %s\n", input) + fmt.Printf("You can change the device name at any time by editing '%s' file\n", fpath) + return input, nil +} diff --git a/internal/futil/futil.go b/internal/futil/futil.go new file mode 100644 index 0000000..93b542d --- /dev/null +++ b/internal/futil/futil.go @@ -0,0 +1,54 @@ +package futil + +import ( + "fmt" + "io" + "os" +) + +func CopyFile(source, dest string) error { + from, err := os.Open(source) + if err != nil { + return err + } + defer from.Close() + + // This is equivalent to: os.OpenFile(dest, os.O_RDWR|os.O_CREATE, 0666) + to, err := os.Create(dest) + if err != nil { + return err + } + defer to.Close() + + _, err = io.Copy(to, from) + if err != nil { + return err + } + return nil +} + +func FileExists(fpath string) (bool, error) { + _, err := os.Stat(fpath) + if err == nil { + // File exists + return true, nil + } + if os.IsNotExist(err) { + // File doesn't exist + return false, nil + } + // Any other error + return false, fmt.Errorf("could not stat file: %w", err) +} + +func CreateFile(fpath string) error { + ff, err := os.Create(fpath) + if err != nil { + return err + } + err = ff.Close() + if err != nil { + return err + } + return nil +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 3167412..029e834 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -9,7 +9,7 @@ import ( "go.uber.org/zap/zapcore" ) -func New(executable string, level zapcore.Level, developement bool) (*zap.Logger, error) { +func New(executable string, level zapcore.Level, development string) (*zap.Logger, error) { dataDir, err := datadir.GetPath() if err != nil { return nil, fmt.Errorf("error while getting resh data dir: %w", err) @@ -18,7 +18,7 @@ func New(executable string, level zapcore.Level, developement bool) (*zap.Logger loggerConfig := zap.NewProductionConfig() loggerConfig.OutputPaths = []string{logPath} loggerConfig.Level.SetLevel(level) - loggerConfig.Development = developement // DPanic panics in developement + loggerConfig.Development = development == "true" // DPanic panics in development logger, err := loggerConfig.Build() if err != nil { return logger, fmt.Errorf("error while creating logger: %w", err) diff --git a/internal/output/output.go b/internal/output/output.go index e3fd15f..817494a 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -7,7 +7,7 @@ import ( "go.uber.org/zap" ) -// Output wrapper for writting to logger and stdout/stderr at the same time +// Output wrapper for writing to logger and stdout/stderr at the same time // useful for errors that should be presented to the user type Output struct { Logger *zap.Logger @@ -22,21 +22,26 @@ func New(logger *zap.Logger, prefix string) *Output { } func (f *Output) Info(msg string) { - fmt.Fprintf(os.Stdout, msg) + fmt.Printf("%s\n", msg) f.Logger.Info(msg) } func (f *Output) Error(msg string, err error) { - fmt.Fprintf(os.Stderr, "%s: %s: %v", f.ErrPrefix, msg, err) + fmt.Fprintf(os.Stderr, "%s: %s: %v\n", f.ErrPrefix, msg, err) f.Logger.Error(msg, zap.Error(err)) } +func (f *Output) ErrorWOErr(msg string) { + fmt.Fprintf(os.Stderr, "%s: %s\n", f.ErrPrefix, msg) + f.Logger.Error(msg) +} + func (f *Output) Fatal(msg string, err error) { - fmt.Fprintf(os.Stderr, "%s: %s: %v", f.ErrPrefix, msg, err) + fmt.Fprintf(os.Stderr, "%s: %s: %v\n", f.ErrPrefix, msg, err) f.Logger.Fatal(msg, zap.Error(err)) } -var msgDeamonNotRunning = `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 -> If the problem persists you can check resh-daemon logs: ~/.local/share/resh/log.json (or ~/$XDG_DATA_HOME/resh/log.json) @@ -51,12 +56,12 @@ It looks like you updated resh and didn't restart this terminal. ` func (f *Output) ErrorDaemonNotRunning(err error) { - fmt.Fprintf(os.Stderr, "%s: %s", f.ErrPrefix, msgDeamonNotRunning) + fmt.Fprintf(os.Stderr, "%s: %s", f.ErrPrefix, msgDaemonNotRunning) f.Logger.Error("Daemon is not running", zap.Error(err)) } func (f *Output) FatalDaemonNotRunning(err error) { - fmt.Fprintf(os.Stderr, "%s: %s", f.ErrPrefix, msgDeamonNotRunning) + fmt.Fprintf(os.Stderr, "%s: %s", f.ErrPrefix, msgDaemonNotRunning) f.Logger.Fatal("Daemon is not running", zap.Error(err)) } diff --git a/internal/recio/read.go b/internal/recio/read.go index b71cbb6..6e03f8c 100644 --- a/internal/recio/read.go +++ b/internal/recio/read.go @@ -8,6 +8,7 @@ import ( "os" "strings" + "github.com/curusarn/resh/internal/futil" "github.com/curusarn/resh/internal/recconv" "github.com/curusarn/resh/internal/recordint" "github.com/curusarn/resh/record" @@ -34,8 +35,8 @@ func (r *RecIO) ReadAndFixFile(fpath string, maxErrors int) ([]recordint.Indexed r.sugar.Infow("Backing up current corrupted history file", "backupFilename", fpathBak, ) - // TODO: maybe use upstram copy function - err = copyFile(fpath, fpathBak) + // TODO: maybe use upstream copy function + err = futil.CopyFile(fpath, fpathBak) if err != nil { r.sugar.Errorw("Failed to create a backup history file - aborting fixing history file", "backupFilename", fpathBak, @@ -99,31 +100,10 @@ func (r *RecIO) ReadFile(fpath string) ([]recordint.Indexed, int, error) { return recs, numErrs, nil } -func copyFile(source, dest string) error { - from, err := os.Open(source) - if err != nil { - return err - } - defer from.Close() - - // This is equivalnet to: os.OpenFile(dest, os.O_RDWR|os.O_CREATE, 0666) - to, err := os.Create(dest) - if err != nil { - return err - } - defer to.Close() - - _, err = io.Copy(to, from) - if err != nil { - return err - } - return nil -} - func (r *RecIO) decodeLine(line string) (*record.V1, error) { idx := strings.Index(line, "{") if idx == -1 { - return nil, fmt.Errorf("no openning brace found") + return nil, fmt.Errorf("no opening brace found") } schema := line[:idx] jsn := line[idx:] diff --git a/internal/recio/write.go b/internal/recio/write.go index 1ff4506..f3caa37 100644 --- a/internal/recio/write.go +++ b/internal/recio/write.go @@ -30,6 +30,7 @@ func (r *RecIO) AppendToFile(fpath string, recs []record.V1) error { } // TODO: better errors +// TODO: rethink this func (r *RecIO) EditRecordFlagsInFile(fpath string, idx int, rec recordint.Flag) error { // FIXME: implement // open file "not as append" diff --git a/internal/recordint/indexed.go b/internal/recordint/indexed.go index 950cadf..8e527a7 100644 --- a/internal/recordint/indexed.go +++ b/internal/recordint/indexed.go @@ -2,6 +2,8 @@ package recordint import "github.com/curusarn/resh/record" +// TODO: rethink this + // Indexed record allows us to find records in history file in order to edit them type Indexed struct { Rec record.V1 diff --git a/scripts/hooks.sh b/scripts/hooks.sh index b331ec0..9372e20 100644 --- a/scripts/hooks.sh +++ b/scripts/hooks.sh @@ -1,7 +1,7 @@ #!/hint/sh __resh_reset_variables() { - __RESH_RECORD_ID=$(__resh_get_uuid) + __RESH_RECORD_ID=$(resh-generate-uuid) } __resh_preexec() { @@ -43,8 +43,6 @@ __resh_collect() { resh-collect -requireVersion "$__RESH_VERSION" \ -requireRevision "$__RESH_REVISION" \ -shell "$__RESH_SHELL" \ - -device "$__RESH_HOST" \ - -deviceID "$(cat ~/.resh/resh-uuid 2>/dev/null)" \ -sessionID "$__RESH_SESSION_ID" \ -recordID "$__RESH_RECORD_ID" \ -home "$__RESH_HOME" \ diff --git a/scripts/install.sh b/scripts/install.sh index e5fcb6e..4ee46c4 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -64,28 +64,20 @@ else fi -if [ "$(uname)" = Darwin ]; then - if gnohup --version >/dev/null 2>&1; then - echo " * Nohup installed: OK" - else - echo " * Nohup installed: NOT INSTALLED!" - echo " > You don't have nohup" - echo " > Please install GNU coreutils" - echo - echo " $ brew install coreutils" - echo - exit 1 - fi +if [ "$(uname)" = Darwin ] && gnohup --version >/dev/null 2>&1; then + echo " * Nohup installed: OK" +elif nohup --version >/dev/null 2>&1; then + echo " * Nohup installed: OK" else - if setsid --version >/dev/null 2>&1; then - echo " * Setsid installed: OK" - else - echo " * Setsid installed: NOT INSTALLED!" - echo " > You don't have setsid" - echo " > Please install unix-util" + echo " * Nohup installed: NOT INSTALLED!" + echo " > You don't have nohup" + echo " > Please install GNU coreutils" + echo + if [ "$(uname)" = Darwin ]; then + echo " $ brew install coreutils" echo - exit 1 fi + exit 1 fi # echo @@ -93,22 +85,6 @@ fi # # shellcheck disable=2034 # read -r x -echo "Backing up previous installation" -#./bin/resh-install-utils backup -# TODO: ~/.resh -> XDG_DATA_HOME/resh/rollback/ -# TODO: ~/XDG_DATA_HOME/resh/history.reshjson -> XDG_DATA/resh/rollback/ -# TODO: what about legacy history locations -# TODO: ~/XDG_DATA_HOME/resh/log.json -> XDG_DATA/resh/rollback/ - -echo "Cleaning up installation directory ..." -rm ~/.resh/bin/* 2>/dev/null ||: -rm ~/.resh/* 2>/dev/null ||: -# TODO: put this behind version condition -# backward compatibility: We have a new location for resh history file -[ ! -f ~/.resh/history.json ] || mv ~/.resh/history.json ~/.resh_history.json - -#[ ! -f ~/.resh_history.json ] || mv ~/.resh_history.json $XDG .resh_history.json - echo "Creating directories ..." mkdir_if_not_exists() { @@ -129,13 +105,19 @@ cp -f scripts/shellrc.sh ~/.resh/shellrc cp -f scripts/reshctl.sh scripts/widgets.sh scripts/hooks.sh scripts/util.sh ~/.resh/ cp -f scripts/rawinstall.sh ~/.resh/ +# Copy all executables. We don't really need to omit install-utils from the bin directory echo "Copying more files ..." -cp -f scripts/uuid.sh ~/.resh/bin/resh-uuid -cp -f bin/resh-{daemon,cli,control,collect,postcollect,session-init,config} ~/.resh/bin/ +cp -f bin/resh-* ~/.resh/bin/ echo "Creating/updating config file ..." ./bin/resh-install-utils migrate-config +echo "Checking/setting up device files ..." +./bin/resh-install-utils setup-device + +echo "Updating format of history file ..." +./bin/resh-install-utils migrate-history + echo "Finishing up ..." # Adding resh shellrc to .bashrc ... if [ ! -f ~/.bashrc ]; then @@ -155,15 +137,6 @@ fi # Deleting zsh completion cache - for future use # [ ! -e ~/.zcompdump ] || rm ~/.zcompdump -# Final touch -# TODO: change -touch ~/.resh_history.json - -# Generating resh-uuid ... -[ -e ~/.resh/resh-uuid ] \ - || cat /proc/sys/kernel/random/uuid > ~/.resh/resh-uuid 2>/dev/null \ - || scripts/uuid.sh > ~/.resh/resh-uuid 2>/dev/null - # Source utils to get __resh_run_daemon function # shellcheck source=util.sh . ~/.resh/util.sh @@ -197,7 +170,7 @@ RESH SEARCH APPLICATION = Redesigned reverse search that actually works (you will need to restart your terminal first) Search your history by commands. - Host, directories, git remote, and exit status is used to display relevant results first. + Device, directories, git remote, and exit status is used to display relevant results first. At first, the search application will use the standard shell history without context. All history recorded from now on will have context which will be used by the RESH SEARCH app. @@ -207,9 +180,9 @@ CHECK FOR UPDATES $ reshctl update HISTORY - Your resh history will be recorded to '${XDG_DATA_HOME-~/.local/share}/resh/history/.reshjson' + Your resh history will be recorded to '${XDG_DATA_HOME-~/.local/share}/resh/history.reshjson' Look at it using e.g. following command (you might need to install jq) - $ cat ${XDG_DATA_HOME-~/.local/share}/resh/history/.reshjson | sed 's/^v[^{]*{/{/' | jq . + $ cat ${XDG_DATA_HOME-~/.local/share}/resh/history.reshjson | sed 's/^v[^{]*{/{/' | jq . ISSUES & FEEDBACK Please report issues to: https://github.com/curusarn/resh/issues @@ -226,13 +199,12 @@ fi info="$info ---- Close this by pressing Q ----" - -echo "$info" | ${PAGER:-less} +printf "%s\n" "$info" | ${PAGER:-less} echo echo "All done!" echo "Thank you for using RESH" -echo "Issues go 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" if [ -z "${__RESH_VERSION:-}" ]; then echo " diff --git a/scripts/reshctl.sh b/scripts/reshctl.sh index 69ab09d..034c9d4 100644 --- a/scripts/reshctl.sh +++ b/scripts/reshctl.sh @@ -62,7 +62,7 @@ __resh_unbind_all() { resh() { local buffer local git_remote; git_remote="$(git remote get-url origin 2>/dev/null)" - buffer=$(resh-cli --sessionID "$__RESH_SESSION_ID" --host "$__RESH_HOST" --pwd "$PWD" --gitOriginRemote "$git_remote" "$@") + buffer=$(resh-cli --sessionID "$__RESH_SESSION_ID" --pwd "$PWD" --gitOriginRemote "$git_remote" "$@") status_code=$? if [ $status_code = 111 ]; then # execute diff --git a/scripts/shellrc.sh b/scripts/shellrc.sh index cf56daf..a72b81f 100644 --- a/scripts/shellrc.sh +++ b/scripts/shellrc.sh @@ -15,10 +15,8 @@ PATH=$PATH:~/.resh/bin if [ -n "${ZSH_VERSION-}" ]; then # shellcheck disable=SC1009 __RESH_SHELL="zsh" - __RESH_HOST="$HOST" elif [ -n "${BASH_VERSION-}" ]; then __RESH_SHELL="bash" - __RESH_HOST="$HOSTNAME" else echo "RESH PANIC: unrecognized shell - please report this to https://github.com/curusarn/resh/issues" fi @@ -41,7 +39,7 @@ __resh_run_daemon # NOTE: nested shells are still the same session # i.e. $__RESH_SESSION_ID will be set in nested shells if [ -z "${__RESH_SESSION_ID+x}" ]; then - export __RESH_SESSION_ID; __RESH_SESSION_ID=$(__resh_get_uuid) + export __RESH_SESSION_ID; __RESH_SESSION_ID=$(resh-generate-uuid) export __RESH_SESSION_PID="$$" __resh_reset_variables diff --git a/scripts/util.sh b/scripts/util.sh index 37d7b1f..428c5c6 100644 --- a/scripts/util.sh +++ b/scripts/util.sh @@ -1,9 +1,6 @@ #!/hint/sh # util.sh - resh utility functions -__resh_get_uuid() { - cat /proc/sys/kernel/random/uuid 2>/dev/null || resh-uuid -} __resh_get_pid() { if [ -n "${ZSH_VERSION-}" ]; then diff --git a/scripts/uuid.sh b/scripts/uuid.sh deleted file mode 100755 index 8b4527b..0000000 --- a/scripts/uuid.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -# https://gist.github.com/markusfisch/6110640 - -# Generate a pseudo UUID -uuid() -{ - local N B C='89ab' - - for (( N=0; N < 16; ++N )) - do - B=$(( $RANDOM%256 )) - - case $N in - 6) - printf '4%x' $(( B%16 )) - ;; - 8) - printf '%c%x' ${C:$RANDOM%${#C}:1} $(( B%16 )) - ;; - 3 | 5 | 7 | 9) - printf '%02x-' $B - ;; - *) - printf '%02x' $B - ;; - esac - done - - echo -} - -if [ "$BASH_SOURCE" == "$0" ] -then - uuid -fi diff --git a/scripts/widgets.sh b/scripts/widgets.sh index e8acd55..9b92f71 100644 --- a/scripts/widgets.sh +++ b/scripts/widgets.sh @@ -14,7 +14,7 @@ __resh_widget_control_R() { local status_code local git_remote; git_remote="$(git remote get-url origin 2>/dev/null)" - BUFFER=$(resh-cli --sessionID "$__RESH_SESSION_ID" --host "$__RESH_HOST" --pwd "$PWD" --gitOriginRemote "$git_remote" --query "$BUFFER") + BUFFER=$(resh-cli --sessionID "$__RESH_SESSION_ID" --pwd "$PWD" --gitOriginRemote "$git_remote" --query "$BUFFER") status_code=$? if [ $status_code = 111 ]; then # execute