Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,28 @@ hishtory init $YOUR_HISHTORY_SECRET

Now if you press `Control+R` on first computer, you can automatically see the commands you've run on all your other computers!

### Manual installation

Custom install path
```bash
HISHTORY_PATH="$HOME/.local/share/hishtory" hishtory install
```

Sync disabled
```bash
hishtory hishtory install --offline
```

Do not modify shell startup rc files on install
```bash
hishtory install --skip-config-modification
```

Do not modify shell startup rc files on update
```bash
hishtory install --skip-update-config-modification
```

## Features

### Querying
Expand Down Expand Up @@ -230,7 +252,7 @@ Note that this uses [HTTP Basic Auth](https://en.wikipedia.org/wiki/Basic_access
<details>
<summary>Customizing the install folder</summary><blockquote>

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.
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 an absolute path (e.g. `export HISHTORY_PATH=$HOME/.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.

</blockquote></details>

Expand Down
5 changes: 4 additions & 1 deletion client/cmd/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ func importFromJson(ctx context.Context) (int, error) {
if err != nil {
return 0, err
}
homedir := hctx.GetHome(ctx)
homedir, err := os.UserHomeDir()
if err != nil {
return 0, err
}

// Build the entries
lines, err := lib.ReadStdin()
Expand Down
32 changes: 19 additions & 13 deletions client/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,16 @@ func warnIfUnsupportedBashVersion() error {
}

func install(secretKey string, offline, skipConfigModification bool) error {
homedir, err := os.UserHomeDir()
err := hctx.MakeHishtoryDir()
if err != nil {
return fmt.Errorf("failed to get user's home directory: %w", err)
return err
}
err = hctx.MakeHishtoryDir()

homedir, err := os.UserHomeDir()
if err != nil {
return err
}

path, err := installBinary(homedir)
if err != nil {
return err
Expand Down Expand Up @@ -246,7 +248,7 @@ func handleUpgradedFeatures() error {
func installBinary(homedir string) (string, error) {
clientPath, err := exec.LookPath("hishtory")
if err != nil {
clientPath = path.Join(homedir, data.GetHishtoryPath(), "hishtory")
clientPath = path.Join(data.GetHishtoryPath(), "hishtory")
}
if _, err := os.Stat(clientPath); err == nil {
err = syscall.Unlink(clientPath)
Expand All @@ -266,7 +268,7 @@ func installBinary(homedir string) (string, error) {
}

func getFishConfigPath(homedir string) string {
return path.Join(homedir, data.GetHishtoryPath(), "config.fish")
return path.Join(data.GetHishtoryPath(), "config.fish")
}

func configureFish(homedir, binaryPath string, skipConfigModification bool) error {
Expand Down Expand Up @@ -309,7 +311,7 @@ func configureFish(homedir, binaryPath string, skipConfigModification bool) erro
}

func getFishConfigFragment(homedir string) string {
return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.GetHishtoryPath()) + "\"\nsource " + getFishConfigPath(homedir) + "\n"
return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(data.GetHishtoryPath()) + "\"\nsource " + getFishConfigPath(homedir) + "\n"
}

func isFishConfigured(homedir string) (bool, error) {
Expand All @@ -325,7 +327,7 @@ func isFishConfigured(homedir string) (bool, error) {
}

func getZshConfigPath(homedir string) string {
return path.Join(homedir, data.GetHishtoryPath(), "config.zsh")
return path.Join(data.GetHishtoryPath(), "config.zsh")
}

func configureZshrc(homedir, binaryPath string, skipConfigModification bool) error {
Expand Down Expand Up @@ -362,7 +364,7 @@ func getZshRcPath(homedir string) string {
}

func getZshConfigFragment(homedir string) string {
return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.GetHishtoryPath()) + "\"\nsource " + getZshConfigPath(homedir) + "\n"
return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(data.GetHishtoryPath()) + "\"\nsource " + getZshConfigPath(homedir) + "\n"
}

func isZshConfigured(homedir string) (bool, error) {
Expand All @@ -378,7 +380,7 @@ func isZshConfigured(homedir string) (bool, error) {
}

func getBashConfigPath(homedir string) string {
return path.Join(homedir, data.GetHishtoryPath(), "config.sh")
return path.Join(data.GetHishtoryPath(), "config.sh")
}

func configureBashrc(homedir, binaryPath string, skipConfigModification bool) error {
Expand Down Expand Up @@ -462,7 +464,7 @@ func convertToRelativePath(path string) string {
}

func getBashConfigFragment(homedir string) string {
return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.GetHishtoryPath()) + "\"\nsource " + getBashConfigPath(homedir) + "\n"
return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(data.GetHishtoryPath()) + "\"\nsource " + getBashConfigPath(homedir) + "\n"
}

func isBashRcConfigured(homedir string) (bool, error) {
Expand Down Expand Up @@ -556,8 +558,12 @@ func copyFile(src, dst string) error {
}

func uninstall(ctx context.Context) error {
homedir := hctx.GetHome(ctx)
err := stripLines(path.Join(homedir, ".bashrc"), getBashConfigFragment(homedir))
homedir, err := os.UserHomeDir()
if err != nil {
return err
}

err = stripLines(path.Join(homedir, ".bashrc"), getBashConfigFragment(homedir))
if err != nil {
return err
}
Expand All @@ -569,7 +575,7 @@ func uninstall(ctx context.Context) error {
if err != nil {
return err
}
err = os.RemoveAll(path.Join(homedir, data.GetHishtoryPath()))
err = os.RemoveAll(path.Join(data.GetHishtoryPath()))
if err != nil {
return err
}
Expand Down
12 changes: 6 additions & 6 deletions client/cmd/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ func TestSetup(t *testing.T) {

homedir, err := os.UserHomeDir()
require.NoError(t, err)
if _, err := os.Stat(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH)); err == nil {
if _, err := os.Stat(path.Join(data.GetHishtoryPath(), data.CONFIG_PATH)); err == nil {
t.Fatalf("hishtory secret file already exists!")
}
require.NoError(t, setup("", false))
if _, err := os.Stat(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH)); err != nil {
if _, err := os.Stat(path.Join(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.GetHishtoryPath(), data.CONFIG_PATH))
data, err := os.ReadFile(path.Join(data.GetHishtoryPath(), data.CONFIG_PATH))
require.NoError(t, err)
if len(data) < 10 {
t.Fatalf("hishtory secret has unexpected length: %d", len(data))
Expand All @@ -42,14 +42,14 @@ func TestSetupOffline(t *testing.T) {

homedir, err := os.UserHomeDir()
require.NoError(t, err)
if _, err := os.Stat(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH)); err == nil {
if _, err := os.Stat(path.Join(data.GetHishtoryPath(), data.CONFIG_PATH)); err == nil {
t.Fatalf("hishtory secret file already exists!")
}
require.NoError(t, setup("", true))
if _, err := os.Stat(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH)); err != nil {
if _, err := os.Stat(path.Join(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.GetHishtoryPath(), data.CONFIG_PATH))
data, err := os.ReadFile(path.Join(data.GetHishtoryPath(), data.CONFIG_PATH))
require.NoError(t, err)
if len(data) < 10 {
t.Fatalf("hishtory secret has unexpected length: %d", len(data))
Expand Down
6 changes: 5 additions & 1 deletion client/cmd/saveHistoryEntry.go
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,11 @@ func getCwd(ctx context.Context) (string, string, error) {
if err != nil {
return "", "", fmt.Errorf("failed to get cwd for last command: %w", err)
}
homedir := hctx.GetHome(ctx)
homedir, err := os.UserHomeDir()
if err != nil {
return "", "", err
}

if cwd == homedir {
return "~/", homedir, nil
}
Expand Down
5 changes: 2 additions & 3 deletions client/cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,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.GetHishtoryPath(), "hishtory"))
err = syscall.Unlink(path.Join(data.GetHishtoryPath(), "hishtory"))
if err != nil {
return fmt.Errorf("failed to unlink %s for update: %w", path.Join(homedir, data.GetHishtoryPath(), "hishtory"), err)
return fmt.Errorf("failed to unlink %s for update: %w", path.Join(data.GetHishtoryPath(), "hishtory"), err)
}
}

Expand Down
20 changes: 6 additions & 14 deletions client/data/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"fmt"
"io"
"os"
"strings"
"time"

"github.com/ddworken/hishtory/shared"
Expand Down Expand Up @@ -171,23 +170,16 @@ func DecryptHistoryEntry(userSecret string, entry shared.EncHistoryEntry) (Histo
return decryptedEntry, nil
}

func ValidateHishtoryPath() error {
func GetHishtoryPath() string {
hishtoryPath := os.Getenv("HISHTORY_PATH")
if strings.HasPrefix(hishtoryPath, "/") {
return fmt.Errorf("HISHTORY_PATH must be a relative path")
if hishtoryPath != "" {
return hishtoryPath
}
return nil
}

func GetHishtoryPath() string {
err := ValidateHishtoryPath()
UserHome, err := os.UserHomeDir()
if err != nil {
// This panic() can only trigger if the env variable is changed after process startup
panic(err)
}
hishtoryPath := os.Getenv("HISHTORY_PATH")
if hishtoryPath != "" {
return hishtoryPath
}
return defaultHishtoryPath

return fmt.Sprintf("%s/%s", UserHome, defaultHishtoryPath)
}
50 changes: 9 additions & 41 deletions client/hctx/hctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,13 @@ var (

func GetLogger() *logrus.Logger {
getLoggerOnce.Do(func() {
homedir, err := os.UserHomeDir()
if err != nil {
panic(fmt.Errorf("failed to get user's home directory: %w", err))
}
err = MakeHishtoryDir()
err := MakeHishtoryDir()
if err != nil {
panic(err)
}

lumberjackLogger := &lumberjack.Logger{
Filename: path.Join(homedir, data.GetHishtoryPath(), "hishtory.log"),
Filename: path.Join(data.GetHishtoryPath(), "hishtory.log"),
MaxSize: 1, // MB
MaxBackups: 1,
MaxAge: 30, // days
Expand All @@ -65,23 +61,15 @@ func GetLogger() *logrus.Logger {
}

func MakeHishtoryDir() error {
homedir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get user's home directory: %w", err)
}
err = os.MkdirAll(path.Join(homedir, data.GetHishtoryPath()), 0o744)
err := os.MkdirAll(path.Join(data.GetHishtoryPath()), 0o744)
if err != nil {
return fmt.Errorf("failed to create ~/%s dir: %w", data.GetHishtoryPath(), err)
}
return nil
}

func OpenLocalSqliteDb() (*gorm.DB, error) {
homedir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("failed to get user's home directory: %w", err)
}
err = MakeHishtoryDir()
err := MakeHishtoryDir()
if err != nil {
return nil, err
}
Expand All @@ -94,7 +82,7 @@ func OpenLocalSqliteDb() (*gorm.DB, error) {
Colorful: false,
},
)
dbFilePath := path.Join(homedir, data.GetHishtoryPath(), data.DB_PATH)
dbFilePath := path.Join(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 {
Expand Down Expand Up @@ -161,14 +149,6 @@ func GetDb(ctx context.Context) *gorm.DB {
panic(fmt.Errorf("failed to find db in ctx"))
}

func GetHome(ctx context.Context) string {
v := ctx.Value(HomedirCtxKey)
if v != nil {
return v.(string)
}
panic(fmt.Errorf("failed to find homedir in ctx"))
}

type ClientConfig struct {
// The user secret that is used to derive encryption keys for syncing history entries
UserSecret string `json:"user_secret" yaml:"-"`
Expand Down Expand Up @@ -241,13 +221,9 @@ type CustomColumnDefinition struct {
}

func GetConfigContents() ([]byte, error) {
homedir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("failed to retrieve homedir: %w", err)
}
dat, err := os.ReadFile(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH))
dat, err := os.ReadFile(path.Join(data.GetHishtoryPath(), data.CONFIG_PATH))
if err != nil {
files, err := os.ReadDir(path.Join(homedir, data.GetHishtoryPath()))
files, err := os.ReadDir(data.GetHishtoryPath())
if err != nil {
return nil, fmt.Errorf("failed to read config file (and failed to list too): %w", err)
}
Expand Down Expand Up @@ -312,15 +288,11 @@ func SetConfig(config *ClientConfig) error {
if err != nil {
return fmt.Errorf("failed to serialize config: %w", err)
}
homedir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to retrieve homedir: %w", err)
}
err = MakeHishtoryDir()
if err != nil {
return err
}
configPath := path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH)
configPath := path.Join(data.GetHishtoryPath(), data.CONFIG_PATH)
stagedConfigPath := configPath + ".tmp-" + uuid.Must(uuid.NewRandom()).String()
err = os.WriteFile(stagedConfigPath, serializedConfig, 0o644)
if err != nil {
Expand All @@ -334,11 +306,7 @@ func SetConfig(config *ClientConfig) error {
}

func InitConfig() error {
homedir, err := os.UserHomeDir()
if err != nil {
return err
}
_, err = os.Stat(path.Join(homedir, data.GetHishtoryPath(), data.CONFIG_PATH))
_, err := os.Stat(path.Join(data.GetHishtoryPath(), data.CONFIG_PATH))
if errors.Is(err, os.ErrNotExist) {
return SetConfig(&ClientConfig{})
}
Expand Down
Loading