From f8b51e49daa4ddf6c95a6eaa40b7fd14b2c14caa Mon Sep 17 00:00:00 2001 From: David Dworken Date: Fri, 16 Dec 2022 22:22:57 -0800 Subject: [PATCH] Support customizing HISHTORY_PATH for people who want to install hishtory in an alternate location to fix #54 --- README.md | 5 +++++ client/client_test.go | 25 ++++++++++++++++++------- client/cmd/install.go | 24 ++++++++++++------------ client/data/data.go | 14 +++++++++++++- client/hctx/hctx.go | 18 +++++++++--------- client/lib/lib.go | 4 ++-- client/lib/lib_test.go | 12 ++++++------ shared/testutils/testutils.go | 30 +++++++++++++++--------------- 8 files changed, 80 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 0d6b4f3..3b81d89 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,11 @@ hiSHtory imports your existing shell history by default. If for some reason this You can configure a custom timestamp format for hiSHtory via `hishtory config-set timestamp-format '2006/Jan/2 15:04'`. The timestamp format string should be in [the format used by Go's `time.Format(...)`](https://pkg.go.dev/time#Time.Format). +
+Customizing the install folder +By default, hiSHtory is installed in `~/.hishtory/`. If you want to customize this, you can do so by setting the `HISHTORY_PATH` environment variable to a path relative to your home directory (e.g. `export HISHTORY_PATH=.config/hishtory`). This must be set both when you install hiSHtory and when you use hiSHtory, so it is recommend to set it in your `.bashrc`/`.zshrc`/`.fishrc` before installing hiSHtory. +
+
Viewing debug logs Debug logs are stored in `~/.hishtory/hishtory.log`. If you run into any issues, these may contain useful information. diff --git a/client/client_test.go b/client/client_test.go index 262f082..89ed972 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -351,7 +351,7 @@ func testBasicUserFlow(t *testing.T, tester shellTester, onlineStatus OnlineStat if err != nil { t.Fatalf("failed to get homedir: %v", err) } - dat, err := os.ReadFile(path.Join(homedir, data.HISHTORY_PATH, "config.sh")) + dat, err := os.ReadFile(path.Join(homedir, data.GetHishtoryPath(), "config.sh")) if err != nil { t.Fatalf("failed to read config.sh: %v", err) } @@ -952,7 +952,7 @@ echo other`) if strings.Count(out, "\n") != 2 { t.Fatalf("hishtory query has unexpected number of lines: out=%#v", out) } - if !strings.Contains(out, "~/.hishtory") { + if !strings.Contains(out, "~/"+data.GetHishtoryPath()) { t.Fatalf("hishtory query has an incorrect CWD: out=%#v", out) } out = hishtoryQuery(t, tester, "echo other") @@ -992,7 +992,7 @@ CGO_ENABLED=0 go build -o /tmp/client if err != nil { t.Fatalf("failed to get homedir: %v", err) } - dat, err := os.ReadFile(path.Join(homedir, data.HISHTORY_PATH, "config.sh")) + dat, err := os.ReadFile(path.Join(homedir, data.GetHishtoryPath(), "config.sh")) if err != nil { t.Fatalf("failed to read config.sh: %v", err) } @@ -1215,13 +1215,24 @@ echo other`) } } +func TestInstallViaPythonScriptWithCustomHishtoryPath(t *testing.T) { + defer testutils.BackupAndRestore(t)() + defer testutils.BackupAndRestoreEnv("HISHTORY_PATH")() + os.Setenv("HISHTORY_PATH", ".other-path") + testInstallViaPythonScriptChild(t, bashTester{}) +} + func testInstallViaPythonScript(t *testing.T, tester shellTester) { + defer testutils.BackupAndRestore(t)() + testInstallViaPythonScriptChild(t, tester) +} + +func testInstallViaPythonScriptChild(t *testing.T, tester shellTester) { if !testutils.IsOnline() { t.Skip("skipping because we're currently offline") } // Set up - defer testutils.BackupAndRestore(t)() defer testutils.BackupAndRestoreEnv("HISHTORY_TEST")() // Install via the python script @@ -1348,7 +1359,7 @@ func TestStripBashTimePrefix(t *testing.T) { if err != nil { t.Fatal(err) } - f, err := os.OpenFile(path.Join(homedir, ".hishtory", "config.sh"), + f, err := os.OpenFile(path.Join(homedir, data.GetHishtoryPath(), "config.sh"), os.O_APPEND|os.O_WRONLY, 0644) if err != nil { t.Fatal(err) @@ -1373,7 +1384,7 @@ func TestStripBashTimePrefix(t *testing.T) { if err != nil { t.Fatal(err) } - f, err = os.OpenFile(path.Join(homedir, ".hishtory", "config.sh"), + f, err = os.OpenFile(path.Join(homedir, data.GetHishtoryPath(), "config.sh"), os.O_APPEND|os.O_WRONLY, 0644) if err != nil { t.Fatal(err) @@ -1702,7 +1713,7 @@ func clearControlRSearchFromConfig(t *testing.T) { configContents = []byte(strings.ReplaceAll(string(configContents), "enable_control_r_search", "something-else")) homedir, err := os.UserHomeDir() testutils.Check(t, err) - err = os.WriteFile(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH), configContents, 0o644) + err = os.WriteFile(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH), configContents, 0o644) testutils.Check(t, err) } diff --git a/client/cmd/install.go b/client/cmd/install.go index decd9ff..24a7c59 100644 --- a/client/cmd/install.go +++ b/client/cmd/install.go @@ -195,7 +195,7 @@ func handleUpgradedFeatures() error { func installBinary(homedir string) (string, error) { clientPath, err := exec.LookPath("hishtory") if err != nil { - clientPath = path.Join(homedir, data.HISHTORY_PATH, "hishtory") + clientPath = path.Join(homedir, data.GetHishtoryPath(), "hishtory") } if _, err := os.Stat(clientPath); err == nil { err = syscall.Unlink(clientPath) @@ -215,7 +215,7 @@ func installBinary(homedir string) (string, error) { } func getFishConfigPath(homedir string) string { - return path.Join(homedir, data.HISHTORY_PATH, "config.fish") + return path.Join(homedir, data.GetHishtoryPath(), "config.fish") } func configureFish(homedir, binaryPath string) error { @@ -254,7 +254,7 @@ func configureFish(homedir, binaryPath string) error { } func getFishConfigFragment(homedir string) string { - return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.HISHTORY_PATH) + "\"\nsource " + getFishConfigPath(homedir) + "\n" + return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.GetHishtoryPath()) + "\"\nsource " + getFishConfigPath(homedir) + "\n" } func isFishConfigured(homedir string) (bool, error) { @@ -266,11 +266,11 @@ func isFishConfigured(homedir string) (bool, error) { if err != nil { return false, fmt.Errorf("failed to read ~/.config/fish/config.fish: %v", err) } - return strings.Contains(string(fishConfig), "# Hishtory Config:"), nil + return strings.Contains(string(fishConfig), getFishConfigFragment(homedir)), nil } func getZshConfigPath(homedir string) string { - return path.Join(homedir, data.HISHTORY_PATH, "config.zsh") + return path.Join(homedir, data.GetHishtoryPath(), "config.zsh") } func configureZshrc(homedir, binaryPath string) error { @@ -307,7 +307,7 @@ func getZshRcPath(homedir string) string { } func getZshConfigFragment(homedir string) string { - return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.HISHTORY_PATH) + "\"\nsource " + getZshConfigPath(homedir) + "\n" + return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.GetHishtoryPath()) + "\"\nsource " + getZshConfigPath(homedir) + "\n" } func isZshConfigured(homedir string) (bool, error) { @@ -319,11 +319,11 @@ func isZshConfigured(homedir string) (bool, error) { if err != nil { return false, fmt.Errorf("failed to read zshrc: %v", err) } - return strings.Contains(string(bashrc), "# Hishtory Config:"), nil + return strings.Contains(string(bashrc), getZshConfigFragment(homedir)), nil } func getBashConfigPath(homedir string) string { - return path.Join(homedir, data.HISHTORY_PATH, "config.sh") + return path.Join(homedir, data.GetHishtoryPath(), "config.sh") } func configureBashrc(homedir, binaryPath string) error { @@ -379,7 +379,7 @@ func addToShellConfig(shellConfigPath, configFragment string) error { } func getBashConfigFragment(homedir string) string { - return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.HISHTORY_PATH) + "\"\nsource " + getBashConfigPath(homedir) + "\n" + return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.GetHishtoryPath()) + "\"\nsource " + getBashConfigPath(homedir) + "\n" } func isBashRcConfigured(homedir string) (bool, error) { @@ -391,7 +391,7 @@ func isBashRcConfigured(homedir string) (bool, error) { if err != nil { return false, fmt.Errorf("failed to read bashrc: %v", err) } - return strings.Contains(string(bashrc), "# Hishtory Config:"), nil + return strings.Contains(string(bashrc), getBashConfigFragment(homedir)), nil } func isBashProfileConfigured(homedir string) (bool, error) { @@ -403,7 +403,7 @@ func isBashProfileConfigured(homedir string) (bool, error) { if err != nil { return false, fmt.Errorf("failed to read bash_profile: %v", err) } - return strings.Contains(string(bashrc), "# Hishtory Config:"), nil + return strings.Contains(string(bashrc), getBashConfigFragment(homedir)), nil } func tweakConfigForTests(configContents string) (string, error) { @@ -472,7 +472,7 @@ func uninstall(ctx *context.Context) error { if err != nil { return err } - err = os.RemoveAll(path.Join(homedir, ".hishtory")) + err = os.RemoveAll(path.Join(homedir, data.GetHishtoryPath())) if err != nil { return err } diff --git a/client/data/data.go b/client/data/data.go index 292a611..b4da535 100644 --- a/client/data/data.go +++ b/client/data/data.go @@ -11,6 +11,7 @@ import ( "encoding/json" "fmt" "io" + "os" "time" "github.com/ddworken/hishtory/shared" @@ -21,10 +22,13 @@ const ( KdfUserID = "user_id" KdfEncryptionKey = "encryption_key" CONFIG_PATH = ".hishtory.config" - HISHTORY_PATH = ".hishtory" DB_PATH = ".hishtory.db" ) +const ( + defaultHishtoryPath = ".hishtory" +) + type HistoryEntry struct { LocalUsername string `json:"local_username" gorm:"uniqueIndex:compositeindex"` Hostname string `json:"hostname" gorm:"uniqueIndex:compositeindex"` @@ -163,3 +167,11 @@ func EntryEquals(entry1, entry2 HistoryEntry) bool { entry1.StartTime.Format(time.RFC3339) == entry2.StartTime.Format(time.RFC3339) && entry1.EndTime.Format(time.RFC3339) == entry2.EndTime.Format(time.RFC3339) } + +func GetHishtoryPath() string { + hishtoryPath := os.Getenv("HISHTORY_PATH") + if hishtoryPath != "" { + return hishtoryPath + } + return defaultHishtoryPath +} diff --git a/client/hctx/hctx.go b/client/hctx/hctx.go index 667ae9a..dc82e29 100644 --- a/client/hctx/hctx.go +++ b/client/hctx/hctx.go @@ -38,7 +38,7 @@ func GetLogger() *logrus.Logger { } lumberjackLogger := &lumberjack.Logger{ - Filename: path.Join(homedir, data.HISHTORY_PATH, "hishtory.log"), + Filename: path.Join(homedir, data.GetHishtoryPath(), "hishtory.log"), MaxSize: 1, // MB MaxBackups: 10, MaxAge: 30, // days @@ -61,9 +61,9 @@ func MakeHishtoryDir() error { if err != nil { return fmt.Errorf("failed to get user's home directory: %v", err) } - err = os.MkdirAll(path.Join(homedir, data.HISHTORY_PATH), 0o744) + err = os.MkdirAll(path.Join(homedir, data.GetHishtoryPath()), 0o744) if err != nil { - return fmt.Errorf("failed to create ~/.hishtory dir: %v", err) + return fmt.Errorf("failed to create ~/%s dir: %v", data.GetHishtoryPath(), err) } return nil } @@ -86,7 +86,7 @@ func OpenLocalSqliteDb() (*gorm.DB, error) { Colorful: false, }, ) - dbFilePath := path.Join(homedir, data.HISHTORY_PATH, data.DB_PATH) + dbFilePath := path.Join(homedir, data.GetHishtoryPath(), data.DB_PATH) dsn := fmt.Sprintf("file:%s?mode=rwc&_journal_mode=WAL", dbFilePath) db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{SkipDefaultTransaction: true, Logger: newLogger}) if err != nil { @@ -189,9 +189,9 @@ func GetConfigContents() ([]byte, error) { if err != nil { return nil, fmt.Errorf("failed to retrieve homedir: %v", err) } - dat, err := os.ReadFile(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)) + dat, err := os.ReadFile(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH)) if err != nil { - files, err := os.ReadDir(path.Join(homedir, data.HISHTORY_PATH)) + files, err := os.ReadDir(path.Join(homedir, data.GetHishtoryPath())) if err != nil { return nil, fmt.Errorf("failed to read config file (and failed to list too): %v", err) } @@ -200,7 +200,7 @@ func GetConfigContents() ([]byte, error) { filenames += file.Name() filenames += ", " } - return nil, fmt.Errorf("failed to read config file (files in ~/.hishtory/: %s): %v", filenames, err) + return nil, fmt.Errorf("failed to read config file (files in HISHTORY_PATH: %s): %v", filenames, err) } return dat, nil } @@ -237,7 +237,7 @@ func SetConfig(config ClientConfig) error { if err != nil { return err } - configPath := path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH) + configPath := path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH) stagedConfigPath := configPath + ".tmp-" + uuid.Must(uuid.NewRandom()).String() err = os.WriteFile(stagedConfigPath, serializedConfig, 0o644) if err != nil { @@ -255,7 +255,7 @@ func InitConfig() error { if err != nil { return err } - _, err = os.Stat(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)) + _, err = os.Stat(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH)) if errors.Is(err, os.ErrNotExist) { return SetConfig(ClientConfig{}) } diff --git a/client/lib/lib.go b/client/lib/lib.go index 7d726c2..19466a8 100644 --- a/client/lib/lib.go +++ b/client/lib/lib.go @@ -692,9 +692,9 @@ func Update(ctx *context.Context) error { // Unlink the existing binary so we can overwrite it even though it is still running if runtime.GOOS == "linux" { homedir := hctx.GetHome(ctx) - err = syscall.Unlink(path.Join(homedir, data.HISHTORY_PATH, "hishtory")) + err = syscall.Unlink(path.Join(homedir, data.GetHishtoryPath(), "hishtory")) if err != nil { - return fmt.Errorf("failed to unlink %s for update: %v", path.Join(homedir, data.HISHTORY_PATH, "hishtory"), err) + return fmt.Errorf("failed to unlink %s for update: %v", path.Join(homedir, data.GetHishtoryPath(), "hishtory"), err) } } diff --git a/client/lib/lib_test.go b/client/lib/lib_test.go index f89c1bd..36e5f79 100644 --- a/client/lib/lib_test.go +++ b/client/lib/lib_test.go @@ -21,14 +21,14 @@ func TestSetup(t *testing.T) { homedir, err := os.UserHomeDir() testutils.Check(t, err) - if _, err := os.Stat(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)); err == nil { + if _, err := os.Stat(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH)); err == nil { t.Fatalf("hishtory secret file already exists!") } testutils.Check(t, Setup("", false)) - if _, err := os.Stat(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)); err != nil { + if _, err := os.Stat(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH)); err != nil { t.Fatalf("hishtory secret file does not exist after Setup()!") } - data, err := os.ReadFile(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)) + data, err := os.ReadFile(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH)) testutils.Check(t, err) if len(data) < 10 { t.Fatalf("hishtory secret has unexpected length: %d", len(data)) @@ -45,14 +45,14 @@ func TestSetupOffline(t *testing.T) { homedir, err := os.UserHomeDir() testutils.Check(t, err) - if _, err := os.Stat(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)); err == nil { + if _, err := os.Stat(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH)); err == nil { t.Fatalf("hishtory secret file already exists!") } testutils.Check(t, Setup("", true)) - if _, err := os.Stat(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)); err != nil { + if _, err := os.Stat(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH)); err != nil { t.Fatalf("hishtory secret file does not exist after Setup()!") } - data, err := os.ReadFile(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)) + data, err := os.ReadFile(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH)) testutils.Check(t, err) if len(data) < 10 { t.Fatalf("hishtory secret has unexpected length: %d", len(data)) diff --git a/shared/testutils/testutils.go b/shared/testutils/testutils.go index 7be7034..5947544 100644 --- a/shared/testutils/testutils.go +++ b/shared/testutils/testutils.go @@ -28,7 +28,7 @@ func ResetLocalState(t *testing.T) { Check(t, err) persistLog() _ = BackupAndRestoreWithId(t, "-reset-local-state") - _ = os.RemoveAll(path.Join(homedir, data.HISHTORY_PATH)) + _ = os.RemoveAll(path.Join(homedir, data.GetHishtoryPath())) } func BackupAndRestore(t *testing.T) func() { @@ -36,8 +36,8 @@ func BackupAndRestore(t *testing.T) func() { } func getBackPath(file, id string) string { - if strings.Contains(file, "/"+data.HISHTORY_PATH+"/") { - return strings.Replace(file, data.HISHTORY_PATH, data.HISHTORY_PATH+".test", 1) + id + if strings.Contains(file, "/"+data.GetHishtoryPath()+"/") { + return strings.Replace(file, data.GetHishtoryPath(), data.GetHishtoryPath()+".test", 1) + id } return file + ".bak" + id } @@ -48,17 +48,17 @@ func BackupAndRestoreWithId(t *testing.T, id string) func() { Check(t, err) initialWd, err := os.Getwd() Check(t, err) - Check(t, os.MkdirAll(path.Join(homedir, data.HISHTORY_PATH+".test"), os.ModePerm)) + Check(t, os.MkdirAll(path.Join(homedir, data.GetHishtoryPath()+".test"), os.ModePerm)) renameFiles := []string{ - path.Join(homedir, data.HISHTORY_PATH, data.DB_PATH), - path.Join(homedir, data.HISHTORY_PATH, DB_WAL_PATH), - path.Join(homedir, data.HISHTORY_PATH, DB_SHM_PATH), - path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH), - path.Join(homedir, data.HISHTORY_PATH, "hishtory"), - path.Join(homedir, data.HISHTORY_PATH, "config.sh"), - path.Join(homedir, data.HISHTORY_PATH, "config.zsh"), - path.Join(homedir, data.HISHTORY_PATH, "config.fish"), + path.Join(homedir, data.GetHishtoryPath(), data.DB_PATH), + path.Join(homedir, data.GetHishtoryPath(), DB_WAL_PATH), + path.Join(homedir, data.GetHishtoryPath(), DB_SHM_PATH), + path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH), + path.Join(homedir, data.GetHishtoryPath(), "hishtory"), + path.Join(homedir, data.GetHishtoryPath(), "config.sh"), + path.Join(homedir, data.GetHishtoryPath(), "config.zsh"), + path.Join(homedir, data.GetHishtoryPath(), "config.fish"), path.Join(homedir, ".bash_history"), path.Join(homedir, ".zsh_history"), path.Join(homedir, ".local/share/fish/fish_history"), @@ -89,8 +89,8 @@ func BackupAndRestoreWithId(t *testing.T, id string) func() { } } persistLog() - Check(t, os.RemoveAll(path.Join(homedir, data.HISHTORY_PATH))) - Check(t, os.MkdirAll(path.Join(homedir, data.HISHTORY_PATH), os.ModePerm)) + Check(t, os.RemoveAll(path.Join(homedir, data.GetHishtoryPath()))) + Check(t, os.MkdirAll(path.Join(homedir, data.GetHishtoryPath()), os.ModePerm)) for _, file := range renameFiles { checkError(os.Rename(getBackPath(file, id), file)) } @@ -308,7 +308,7 @@ func TestLog(t *testing.T, line string) { func persistLog() { homedir, err := os.UserHomeDir() checkError(err) - fp := path.Join(homedir, data.HISHTORY_PATH, "hishtory.log") + fp := path.Join(homedir, data.GetHishtoryPath(), "hishtory.log") log, err := os.ReadFile(fp) if err != nil { return