Support customizing HISHTORY_PATH for people who want to install hishtory in an alternate location to fix #54

This commit is contained in:
David Dworken 2022-12-16 22:22:57 -08:00
parent 53e97253e5
commit f8b51e49da
No known key found for this signature in database
8 changed files with 80 additions and 52 deletions

View File

@ -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).
</details>
<details>
<summary>Customizing the install folder</summary>
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.
</details>
<details>
<summary>Viewing debug logs</summary>
Debug logs are stored in `~/.hishtory/hishtory.log`. If you run into any issues, these may contain useful information.

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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{})
}

View File

@ -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)
}
}

View File

@ -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))

View File

@ -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