From 491cfccbe0ee8859066142ac207e7a3d43e83d3a Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 1 Dec 2025 16:29:46 -0800 Subject: [PATCH 1/7] move history dir back to the one in HOME on macos if it got set incorrectly to wave's shell dir --- pkg/util/shellutil/shellintegration/zsh_zshrc.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/util/shellutil/shellintegration/zsh_zshrc.sh b/pkg/util/shellutil/shellintegration/zsh_zshrc.sh index f24abee181..47fd1a7f85 100644 --- a/pkg/util/shellutil/shellintegration/zsh_zshrc.sh +++ b/pkg/util/shellutil/shellintegration/zsh_zshrc.sh @@ -18,6 +18,11 @@ if [[ -n ${_comps+x} ]]; then source <(wsh completion zsh) fi +# fix history (macos) +if [[ "$OSTYPE" == darwin* && "$HISTFILE" == "$WAVETERM_ZDOTDIR/.zsh_history" ]]; then + HISTFILE="$HOME/.zsh_history" +fi + typeset -g _WAVETERM_SI_FIRSTPRECMD=1 # shell integration From 0098bdee0b8c982f297ad48cd79fb08af2acae69 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 1 Dec 2025 18:26:49 -0800 Subject: [PATCH 2/7] first cut at wave history migration --- pkg/util/shellutil/shellutil.go | 102 ++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/pkg/util/shellutil/shellutil.go b/pkg/util/shellutil/shellutil.go index dd9fe697a1..ed391f2341 100644 --- a/pkg/util/shellutil/shellutil.go +++ b/pkg/util/shellutil/shellutil.go @@ -74,6 +74,7 @@ const ( PwshIntegrationDir = "shell/pwsh" FishIntegrationDir = "shell/fish" WaveHomeBinDir = "bin" + ZshHistoryFileName = ".zsh_history" ) func DetectLocalShellPath() string { @@ -208,6 +209,47 @@ func GetLocalZshZDotDir() string { return filepath.Join(wavebase.GetWaveDataDir(), ZshIntegrationDir) } +func HasWaveZshHistory() (bool, int64) { + zshDir := GetLocalZshZDotDir() + historyFile := filepath.Join(zshDir, ZshHistoryFileName) + fileInfo, err := os.Stat(historyFile) + if err != nil { + return false, 0 + } + return true, fileInfo.Size() +} + +func IsExtendedZshHistoryFile(fileName string) (bool, error) { + file, err := os.Open(fileName) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + defer file.Close() + + buf := make([]byte, 1024) + n, err := file.Read(buf) + if err != nil { + return false, err + } + + content := string(buf[:n]) + lines := strings.Split(content, "\n") + + extendedPattern := regexp.MustCompile(`^: [0-9]+:`) + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + return extendedPattern.MatchString(line), nil + } + + return false, nil +} + func GetLocalWshBinaryPath(version string, goos string, goarch string) (string, error) { ext := "" if goarch == "amd64" { @@ -422,6 +464,66 @@ func getShellVersion(shellPath string, shellType string) (string, error) { return matches[1], nil } +func FixupWaveZshHistory() error { + if runtime.GOOS != "darwin" { + return nil + } + + hasHistory, size := HasWaveZshHistory() + if !hasHistory { + return nil + } + + zshDir := GetLocalZshZDotDir() + waveHistFile := filepath.Join(zshDir, ZshHistoryFileName) + + if size == 0 { + os.Remove(waveHistFile) + return nil + } + + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("error getting home directory: %w", err) + } + realHistFile := filepath.Join(homeDir, ".zsh_history") + + isExtended, err := IsExtendedZshHistoryFile(realHistFile) + if err != nil { + return fmt.Errorf("error checking if history is extended: %w", err) + } + + hasExtendedStr := "false" + if isExtended { + hasExtendedStr = "true" + } + + quotedWaveHistFile := utilfn.ShellQuote(waveHistFile, true, -1) + + script := fmt.Sprintf(` + HISTFILE=~/.zsh_history + HISTSIZE=999999 + SAVEHIST=999999 + has_extended_history=%s + [[ $has_extended_history == true ]] && setopt EXTENDED_HISTORY + fc -R %s + fc -W + `, hasExtendedStr, quotedWaveHistFile) + + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + cmd := exec.CommandContext(ctx, "zsh", "-c", script) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("error executing zsh history fixup script: %w, output: %s", err, string(output)) + } + + os.Remove(waveHistFile) + + return nil +} + func FormatOSC(oscNum int, parts ...string) string { if len(parts) == 0 { return fmt.Sprintf("\x1b]%d\x07", oscNum) From 45818ef0ec0e980df3a04d5d81b4a4aa329ae646 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 1 Dec 2025 21:01:03 -0800 Subject: [PATCH 3/7] add logging, and do fixup in main --- cmd/server/main-server.go | 1 + pkg/util/shellutil/shellutil.go | 3 +++ 2 files changed, 4 insertions(+) diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go index b52d97491c..c6825b75fa 100644 --- a/cmd/server/main-server.go +++ b/cmd/server/main-server.go @@ -465,6 +465,7 @@ func main() { return } + shellutil.FixupWaveZshHistory() createMainWshClient() sigutil.InstallShutdownSignalHandlers(doShutdown) sigutil.InstallSIGUSR1Handler() diff --git a/pkg/util/shellutil/shellutil.go b/pkg/util/shellutil/shellutil.go index ed391f2341..8e347d63fc 100644 --- a/pkg/util/shellutil/shellutil.go +++ b/pkg/util/shellutil/shellutil.go @@ -482,6 +482,8 @@ func FixupWaveZshHistory() error { return nil } + log.Printf("merging wave zsh history %s into ~/.zsh_history\n", waveHistFile) + homeDir, err := os.UserHomeDir() if err != nil { return fmt.Errorf("error getting home directory: %w", err) @@ -520,6 +522,7 @@ func FixupWaveZshHistory() error { } os.Remove(waveHistFile) + log.Printf("successfully merged wave zsh history %s into ~/.zsh_history\n", waveHistFile) return nil } From 070ae5c1ea906842de31d6dc7b92eaffc8de25df Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 2 Dec 2025 11:22:39 -0800 Subject: [PATCH 4/7] fix nits --- cmd/server/main-server.go | 5 ++++- pkg/util/shellutil/shellutil.go | 15 +++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go index c6825b75fa..30540e351f 100644 --- a/cmd/server/main-server.go +++ b/cmd/server/main-server.go @@ -465,7 +465,10 @@ func main() { return } - shellutil.FixupWaveZshHistory() + err = shellutil.FixupWaveZshHistory() + if err != nil { + log.Printf("error fixing up wave zsh history: %v\n", err) + } createMainWshClient() sigutil.InstallShutdownSignalHandlers(doShutdown) sigutil.InstallSIGUSR1Handler() diff --git a/pkg/util/shellutil/shellutil.go b/pkg/util/shellutil/shellutil.go index 8e347d63fc..794663c29f 100644 --- a/pkg/util/shellutil/shellutil.go +++ b/pkg/util/shellutil/shellutil.go @@ -47,6 +47,8 @@ var ( //go:embed shellintegration/pwsh_wavepwsh.sh PwshStartup_wavepwsh string + + ZshExtendedHistoryPattern = regexp.MustCompile(`^: [0-9]+:`) ) const DefaultTermType = "xterm-256color" @@ -238,13 +240,12 @@ func IsExtendedZshHistoryFile(fileName string) (bool, error) { content := string(buf[:n]) lines := strings.Split(content, "\n") - extendedPattern := regexp.MustCompile(`^: [0-9]+:`) for _, line := range lines { line = strings.TrimSpace(line) if line == "" { continue } - return extendedPattern.MatchString(line), nil + return ZshExtendedHistoryPattern.MatchString(line), nil } return false, nil @@ -478,7 +479,10 @@ func FixupWaveZshHistory() error { waveHistFile := filepath.Join(zshDir, ZshHistoryFileName) if size == 0 { - os.Remove(waveHistFile) + err := os.Remove(waveHistFile) + if err != nil { + log.Printf("error removing wave zsh history file %s: %v\n", waveHistFile, err) + } return nil } @@ -521,7 +525,10 @@ func FixupWaveZshHistory() error { return fmt.Errorf("error executing zsh history fixup script: %w, output: %s", err, string(output)) } - os.Remove(waveHistFile) + err = os.Remove(waveHistFile) + if err != nil { + log.Printf("error removing wave zsh history file %s: %v\n", waveHistFile, err) + } log.Printf("successfully merged wave zsh history %s into ~/.zsh_history\n", waveHistFile) return nil From cf489dd0bcdac9f3733cfcdb21991fb3477fd50a Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 2 Dec 2025 14:48:50 -0800 Subject: [PATCH 5/7] get history fixup working (needed interactive shell) --- go.mod | 1 + go.sum | 2 ++ pkg/util/envutil/envutil.go | 24 +++++++++++++++++++ .../shellutil/shellintegration/zsh_zshrc.sh | 2 +- pkg/util/shellutil/shellutil.go | 10 ++++++-- 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ef1927874f..6711ded66a 100644 --- a/go.mod +++ b/go.mod @@ -81,6 +81,7 @@ require ( github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/outrigdev/goid v0.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sirupsen/logrus v1.9.3 // indirect diff --git a/go.sum b/go.sum index ee73e96e87..ea416cd09a 100644 --- a/go.sum +++ b/go.sum @@ -146,6 +146,8 @@ github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuE github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b h1:cLGKfKb1uk0hxI0Q8L83UAJPpeJ+gSpn3cCU/tjd3eg= github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b/go.mod h1:KO+FcPtyLAiRC0hJwreJVvfwc7vnNz77UxBTIGHdPVk= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= diff --git a/pkg/util/envutil/envutil.go b/pkg/util/envutil/envutil.go index c2505e7c47..dff40c1842 100644 --- a/pkg/util/envutil/envutil.go +++ b/pkg/util/envutil/envutil.go @@ -67,3 +67,27 @@ func RmEnv(envStr string, key string) string { delete(envMap, key) return MapToEnv(envMap) } + +func SliceToEnv(env []string) string { + var sb strings.Builder + for _, envVar := range env { + if len(envVar) == 0 { + continue + } + sb.WriteString(envVar) + sb.WriteByte('\x00') + } + return sb.String() +} + +func EnvToSlice(envStr string) []string { + envLines := strings.Split(envStr, "\x00") + result := make([]string, 0, len(envLines)) + for _, line := range envLines { + if len(line) == 0 { + continue + } + result = append(result, line) + } + return result +} diff --git a/pkg/util/shellutil/shellintegration/zsh_zshrc.sh b/pkg/util/shellutil/shellintegration/zsh_zshrc.sh index 47fd1a7f85..0c0271a29d 100644 --- a/pkg/util/shellutil/shellintegration/zsh_zshrc.sh +++ b/pkg/util/shellutil/shellintegration/zsh_zshrc.sh @@ -19,7 +19,7 @@ if [[ -n ${_comps+x} ]]; then fi # fix history (macos) -if [[ "$OSTYPE" == darwin* && "$HISTFILE" == "$WAVETERM_ZDOTDIR/.zsh_history" ]]; then +if [[ "$HISTFILE" == "$WAVETERM_ZDOTDIR/.zsh_history" ]]; then HISTFILE="$HOME/.zsh_history" fi diff --git a/pkg/util/shellutil/shellutil.go b/pkg/util/shellutil/shellutil.go index 794663c29f..a6268bf5dd 100644 --- a/pkg/util/shellutil/shellutil.go +++ b/pkg/util/shellutil/shellutil.go @@ -18,6 +18,7 @@ import ( "sync" "time" + "github.com/wavetermdev/waveterm/pkg/util/envutil" "github.com/wavetermdev/waveterm/pkg/util/utilfn" "github.com/wavetermdev/waveterm/pkg/wavebase" "github.com/wavetermdev/waveterm/pkg/waveobj" @@ -512,14 +513,19 @@ func FixupWaveZshHistory() error { SAVEHIST=999999 has_extended_history=%s [[ $has_extended_history == true ]] && setopt EXTENDED_HISTORY - fc -R %s + fc -RI + fc -RI %s fc -W `, hasExtendedStr, quotedWaveHistFile) ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) defer cancelFn() - cmd := exec.CommandContext(ctx, "zsh", "-c", script) + cmd := exec.CommandContext(ctx, "zsh", "-f", "-i", "-c", script) + cmd.Stdin = nil + envStr := envutil.SliceToEnv(os.Environ()) + envStr = envutil.RmEnv(envStr, "ZDOTDIR") + cmd.Env = envutil.EnvToSlice(envStr) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("error executing zsh history fixup script: %w, output: %s", err, string(output)) From 4715f4e099235a172773eb94c5ea2d4dc1687482 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 2 Dec 2025 15:22:17 -0800 Subject: [PATCH 6/7] cap telemetry at 7-days BEFORE send. cap to 1-day for telemetry off --- pkg/telemetry/telemetry.go | 7 +++++-- pkg/wcloud/wcloud.go | 8 +++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/telemetry/telemetry.go b/pkg/telemetry/telemetry.go index 3a208055b9..24c7541b54 100644 --- a/pkg/telemetry/telemetry.go +++ b/pkg/telemetry/telemetry.go @@ -254,10 +254,13 @@ func RecordTEvent(ctx context.Context, tevent *telemetrydata.TEvent) error { } func CleanOldTEvents(ctx context.Context) error { + daysToKeep := 7 + if !IsTelemetryEnabled() { + daysToKeep = 1 + } + olderThan := time.Now().AddDate(0, 0, -daysToKeep).UnixMilli() return wstore.WithTx(ctx, func(tx *wstore.TxWrap) error { - // delete events older than 28 days query := `DELETE FROM db_tevent WHERE ts < ?` - olderThan := time.Now().AddDate(0, 0, -28).UnixMilli() tx.Exec(query, olderThan) return nil }) diff --git a/pkg/wcloud/wcloud.go b/pkg/wcloud/wcloud.go index 9101b34469..390f6448ca 100644 --- a/pkg/wcloud/wcloud.go +++ b/pkg/wcloud/wcloud.go @@ -214,11 +214,9 @@ func sendTEvents(clientId string) (int, error) { } func SendAllTelemetry(clientId string) error { - defer func() { - ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) - defer cancelFn() - telemetry.CleanOldTEvents(ctx) - }() + ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) + defer cancelFn() + telemetry.CleanOldTEvents(ctx) if !telemetry.IsTelemetryEnabled() { log.Printf("telemetry disabled, not sending\n") return nil From 71c5ff149ef322ec01949b328bcf711fbe375d88 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 2 Dec 2025 15:47:48 -0800 Subject: [PATCH 7/7] log any error from clean --- pkg/wcloud/wcloud.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/wcloud/wcloud.go b/pkg/wcloud/wcloud.go index 390f6448ca..455acd8944 100644 --- a/pkg/wcloud/wcloud.go +++ b/pkg/wcloud/wcloud.go @@ -216,7 +216,9 @@ func sendTEvents(clientId string) (int, error) { func SendAllTelemetry(clientId string) error { ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) defer cancelFn() - telemetry.CleanOldTEvents(ctx) + if err := telemetry.CleanOldTEvents(ctx); err != nil { + log.Printf("error cleaning old telemetry events: %v\n", err) + } if !telemetry.IsTelemetryEnabled() { log.Printf("telemetry disabled, not sending\n") return nil