mirror of
https://github.com/ddworken/hishtory.git
synced 2025-01-11 16:58:47 +01:00
Move code out of lib that is only referenced for one command
This commit is contained in:
parent
d9c4a59ddd
commit
ab6bb719a7
@ -2,13 +2,23 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ddworken/hishtory/client/data"
|
||||||
"github.com/ddworken/hishtory/client/hctx"
|
"github.com/ddworken/hishtory/client/hctx"
|
||||||
"github.com/ddworken/hishtory/client/lib"
|
"github.com/ddworken/hishtory/client/lib"
|
||||||
|
"github.com/ddworken/hishtory/shared"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,7 +35,7 @@ var installCmd = &cobra.Command{
|
|||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
secretKey = args[0]
|
secretKey = args[0]
|
||||||
}
|
}
|
||||||
lib.CheckFatalError(lib.Install(secretKey, *offlineInstall))
|
lib.CheckFatalError(install(secretKey, *offlineInstall))
|
||||||
if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" {
|
if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" {
|
||||||
db, err := hctx.OpenLocalSqliteDb()
|
db, err := hctx.OpenLocalSqliteDb()
|
||||||
lib.CheckFatalError(err)
|
lib.CheckFatalError(err)
|
||||||
@ -82,6 +92,34 @@ var initCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var uninstallCmd = &cobra.Command{
|
||||||
|
Use: "uninstall",
|
||||||
|
Short: "Completely uninstall hiSHtory and remove your shell history",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
ctx := hctx.MakeContext()
|
||||||
|
fmt.Printf("Are you sure you want to uninstall hiSHtory and delete all locally saved history data [y/N]")
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
resp, err := reader.ReadString('\n')
|
||||||
|
lib.CheckFatalError(err)
|
||||||
|
if strings.TrimSpace(resp) != "y" {
|
||||||
|
fmt.Printf("Aborting uninstall per user response of %#v\n", strings.TrimSpace(resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Do you have any feedback on why you're uninstallying hiSHtory? Type any feedback and then hit enter.\nFeedback: ")
|
||||||
|
feedbackTxt, err := reader.ReadString('\n')
|
||||||
|
lib.CheckFatalError(err)
|
||||||
|
feedback := shared.Feedback{
|
||||||
|
Date: time.Now(),
|
||||||
|
Feedback: feedbackTxt,
|
||||||
|
UserId: data.UserId(hctx.GetConf(ctx).UserSecret),
|
||||||
|
}
|
||||||
|
reqBody, err := json.Marshal(feedback)
|
||||||
|
lib.CheckFatalError(err)
|
||||||
|
_, _ = lib.ApiPost("/api/v1/feedback", "application/json", reqBody)
|
||||||
|
lib.CheckFatalError(uninstall(ctx))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func warnIfUnsupportedBashVersion() error {
|
func warnIfUnsupportedBashVersion() error {
|
||||||
_, err := exec.LookPath("bash")
|
_, err := exec.LookPath("bash")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -99,9 +137,380 @@ func warnIfUnsupportedBashVersion() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func install(secretKey string, offline bool) error {
|
||||||
|
homedir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get user's home directory: %v", err)
|
||||||
|
}
|
||||||
|
err = hctx.MakeHishtoryDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
path, err := installBinary(homedir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = configureBashrc(homedir, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = configureZshrc(homedir, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = configureFish(homedir, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = handleUpgradedFeatures()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = hctx.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
// No config, so set up a new installation
|
||||||
|
return lib.Setup(secretKey, offline)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUpgradedFeatures() error {
|
||||||
|
configConents, err := hctx.GetConfigContents()
|
||||||
|
if err != nil {
|
||||||
|
// No config, so this is a new install and thus there is nothing to do
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.Contains(string(configConents), "enable_control_r_search") {
|
||||||
|
// control-r search is already configured, so there is nothing to do
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Enable control-r search
|
||||||
|
config, err := hctx.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
config.ControlRSearchEnabled = true
|
||||||
|
return hctx.SetConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func installBinary(homedir string) (string, error) {
|
||||||
|
clientPath, err := exec.LookPath("hishtory")
|
||||||
|
if err != nil {
|
||||||
|
clientPath = path.Join(homedir, data.HISHTORY_PATH, "hishtory")
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(clientPath); err == nil {
|
||||||
|
err = syscall.Unlink(clientPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to unlink %s for install: %v", clientPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = copyFile(os.Args[0], clientPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to copy hishtory binary to $PATH: %v", err)
|
||||||
|
}
|
||||||
|
err = os.Chmod(clientPath, 0o700)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to set permissions on hishtory binary: %v", err)
|
||||||
|
}
|
||||||
|
return clientPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFishConfigPath(homedir string) string {
|
||||||
|
return path.Join(homedir, data.HISHTORY_PATH, "config.fish")
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureFish(homedir, binaryPath string) error {
|
||||||
|
// Check if fish is installed
|
||||||
|
_, err := exec.LookPath("fish")
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Create the file we're going to source. Do this no matter what in case there are updates to it.
|
||||||
|
configContents := lib.ConfigFishContents
|
||||||
|
if os.Getenv("HISHTORY_TEST") != "" {
|
||||||
|
testConfig, err := tweakConfigForTests(configContents)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configContents = testConfig
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(getFishConfigPath(homedir), []byte(configContents), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write config.zsh file: %v", err)
|
||||||
|
}
|
||||||
|
// Check if we need to configure the fishrc
|
||||||
|
fishIsConfigured, err := isFishConfigured(homedir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to check ~/.config/fish/config.fish: %v", err)
|
||||||
|
}
|
||||||
|
if fishIsConfigured {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Add to fishrc
|
||||||
|
err = os.MkdirAll(path.Join(homedir, ".config/fish"), 0o744)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create fish config directory: %v", err)
|
||||||
|
}
|
||||||
|
return addToShellConfig(path.Join(homedir, ".config/fish/config.fish"), getFishConfigFragment(homedir))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFishConfigFragment(homedir string) string {
|
||||||
|
return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.HISHTORY_PATH) + "\"\nsource " + getFishConfigPath(homedir) + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFishConfigured(homedir string) (bool, error) {
|
||||||
|
_, err := os.Stat(path.Join(homedir, ".config/fish/config.fish"))
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
fishConfig, err := ioutil.ReadFile(path.Join(homedir, ".config/fish/config.fish"))
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to read ~/.config/fish/config.fish: %v", err)
|
||||||
|
}
|
||||||
|
return strings.Contains(string(fishConfig), "# Hishtory Config:"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getZshConfigPath(homedir string) string {
|
||||||
|
return path.Join(homedir, data.HISHTORY_PATH, "config.zsh")
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureZshrc(homedir, binaryPath string) error {
|
||||||
|
// Create the file we're going to source in our zshrc. Do this no matter what in case there are updates to it.
|
||||||
|
configContents := lib.ConfigZshContents
|
||||||
|
if os.Getenv("HISHTORY_TEST") != "" {
|
||||||
|
testConfig, err := tweakConfigForTests(configContents)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configContents = testConfig
|
||||||
|
}
|
||||||
|
err := ioutil.WriteFile(getZshConfigPath(homedir), []byte(configContents), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write config.zsh file: %v", err)
|
||||||
|
}
|
||||||
|
// Check if we need to configure the zshrc
|
||||||
|
zshIsConfigured, err := isZshConfigured(homedir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to check .zshrc: %v", err)
|
||||||
|
}
|
||||||
|
if zshIsConfigured {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Add to zshrc
|
||||||
|
return addToShellConfig(getZshRcPath(homedir), getZshConfigFragment(homedir))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getZshRcPath(homedir string) string {
|
||||||
|
if zdotdir := os.Getenv("ZDOTDIR"); zdotdir != "" {
|
||||||
|
return path.Join(zdotdir, ".zshrc")
|
||||||
|
}
|
||||||
|
return path.Join(homedir, ".zshrc")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getZshConfigFragment(homedir string) string {
|
||||||
|
return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.HISHTORY_PATH) + "\"\nsource " + getZshConfigPath(homedir) + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func isZshConfigured(homedir string) (bool, error) {
|
||||||
|
_, err := os.Stat(getZshRcPath(homedir))
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
bashrc, err := ioutil.ReadFile(getZshRcPath(homedir))
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to read zshrc: %v", err)
|
||||||
|
}
|
||||||
|
return strings.Contains(string(bashrc), "# Hishtory Config:"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBashConfigPath(homedir string) string {
|
||||||
|
return path.Join(homedir, data.HISHTORY_PATH, "config.sh")
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureBashrc(homedir, binaryPath string) error {
|
||||||
|
// Create the file we're going to source in our bashrc. Do this no matter what in case there are updates to it.
|
||||||
|
configContents := lib.ConfigShContents
|
||||||
|
if os.Getenv("HISHTORY_TEST") != "" {
|
||||||
|
testConfig, err := tweakConfigForTests(configContents)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configContents = testConfig
|
||||||
|
}
|
||||||
|
err := ioutil.WriteFile(getBashConfigPath(homedir), []byte(configContents), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write config.sh file: %v", err)
|
||||||
|
}
|
||||||
|
// Check if we need to configure the bashrc and configure it if so
|
||||||
|
bashRcIsConfigured, err := isBashRcConfigured(homedir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to check ~/.bashrc: %v", err)
|
||||||
|
}
|
||||||
|
if !bashRcIsConfigured {
|
||||||
|
err = addToShellConfig(path.Join(homedir, ".bashrc"), getBashConfigFragment(homedir))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if we need to configure the bash_profile and configure it if so
|
||||||
|
bashProfileIsConfigured, err := isBashProfileConfigured(homedir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to check ~/.bash_profile: %v", err)
|
||||||
|
}
|
||||||
|
if !bashProfileIsConfigured {
|
||||||
|
err = addToShellConfig(path.Join(homedir, ".bash_profile"), getBashConfigFragment(homedir))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addToShellConfig(shellConfigPath, configFragment string) error {
|
||||||
|
f, err := os.OpenFile(shellConfigPath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to append to %s: %v", shellConfigPath, err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = f.WriteString(configFragment)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to append to %s: %v", shellConfigPath, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBashConfigFragment(homedir string) string {
|
||||||
|
return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.HISHTORY_PATH) + "\"\nsource " + getBashConfigPath(homedir) + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBashRcConfigured(homedir string) (bool, error) {
|
||||||
|
_, err := os.Stat(path.Join(homedir, ".bashrc"))
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
bashrc, err := ioutil.ReadFile(path.Join(homedir, ".bashrc"))
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to read bashrc: %v", err)
|
||||||
|
}
|
||||||
|
return strings.Contains(string(bashrc), "# Hishtory Config:"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBashProfileConfigured(homedir string) (bool, error) {
|
||||||
|
_, err := os.Stat(path.Join(homedir, ".bash_profile"))
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
bashrc, err := ioutil.ReadFile(path.Join(homedir, ".bash_profile"))
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to read bash_profile: %v", err)
|
||||||
|
}
|
||||||
|
return strings.Contains(string(bashrc), "# Hishtory Config:"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tweakConfigForTests(configContents string) (string, error) {
|
||||||
|
madeSubstitution := false
|
||||||
|
skipLineIndex := -1
|
||||||
|
ret := ""
|
||||||
|
split := strings.Split(configContents, "\n")
|
||||||
|
for i, line := range split {
|
||||||
|
if strings.Contains(line, "# Background Run") {
|
||||||
|
ret += strings.ReplaceAll(split[i+1], "# hishtory", "hishtory")
|
||||||
|
madeSubstitution = true
|
||||||
|
skipLineIndex = i + 1
|
||||||
|
} else if i == skipLineIndex {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
ret += line
|
||||||
|
}
|
||||||
|
ret += "\n"
|
||||||
|
}
|
||||||
|
if !madeSubstitution {
|
||||||
|
return "", fmt.Errorf("failed to find substitution line in configConents=%#v", configContents)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
sourceFileStat, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sourceFileStat.Mode().IsRegular() {
|
||||||
|
return fmt.Errorf("%s is not a regular file", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
source, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer source.Close()
|
||||||
|
|
||||||
|
destination, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(destination, source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func uninstall(ctx *context.Context) error {
|
||||||
|
homedir := hctx.GetHome(ctx)
|
||||||
|
err := stripLines(path.Join(homedir, ".bashrc"), getBashConfigFragment(homedir))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = stripLines(getZshRcPath(homedir), getZshConfigFragment(homedir))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = stripLines(path.Join(homedir, ".config/fish/config.fish"), getFishConfigFragment(homedir))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.RemoveAll(path.Join(homedir, ".hishtory"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("Successfully uninstalled hishtory, please restart your terminal...")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripLines(filePath, lines string) error {
|
||||||
|
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
|
||||||
|
// File does not exist, nothing to do
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
origContents, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
linesToBeRemoved := make(map[string]bool, 0)
|
||||||
|
for _, line := range strings.Split(lines, "\n") {
|
||||||
|
if strings.TrimSpace(line) != "" {
|
||||||
|
linesToBeRemoved[line] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret := ""
|
||||||
|
for _, line := range strings.Split(string(origContents), "\n") {
|
||||||
|
if !linesToBeRemoved[line] {
|
||||||
|
ret += line
|
||||||
|
ret += "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return os.WriteFile(filePath, []byte(ret), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(installCmd)
|
rootCmd.AddCommand(installCmd)
|
||||||
rootCmd.AddCommand(initCmd)
|
rootCmd.AddCommand(initCmd)
|
||||||
|
rootCmd.AddCommand(uninstallCmd)
|
||||||
|
|
||||||
offlineInit = initCmd.Flags().Bool("offline", false, "Install hiSHtory in offline mode wiht all syncing capabilities disabled")
|
offlineInit = initCmd.Flags().Bool("offline", false, "Install hiSHtory in offline mode wiht all syncing capabilities disabled")
|
||||||
offlineInstall = installCmd.Flags().Bool("offline", false, "Install hiSHtory in offline mode wiht all syncing capabilities disabled")
|
offlineInstall = installCmd.Flags().Bool("offline", false, "Install hiSHtory in offline mode wiht all syncing capabilities disabled")
|
||||||
}
|
}
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ddworken/hishtory/client/data"
|
|
||||||
"github.com/ddworken/hishtory/client/hctx"
|
|
||||||
"github.com/ddworken/hishtory/client/lib"
|
|
||||||
"github.com/ddworken/hishtory/shared"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var uninstallCmd = &cobra.Command{
|
|
||||||
Use: "uninstall",
|
|
||||||
Short: "Completely uninstall hiSHtory and remove your shell history",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
ctx := hctx.MakeContext()
|
|
||||||
fmt.Printf("Are you sure you want to uninstall hiSHtory and delete all locally saved history data [y/N]")
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
resp, err := reader.ReadString('\n')
|
|
||||||
lib.CheckFatalError(err)
|
|
||||||
if strings.TrimSpace(resp) != "y" {
|
|
||||||
fmt.Printf("Aborting uninstall per user response of %#v\n", strings.TrimSpace(resp))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("Do you have any feedback on why you're uninstallying hiSHtory? Type any feedback and then hit enter.\nFeedback: ")
|
|
||||||
feedbackTxt, err := reader.ReadString('\n')
|
|
||||||
lib.CheckFatalError(err)
|
|
||||||
feedback := shared.Feedback{
|
|
||||||
Date: time.Now(),
|
|
||||||
Feedback: feedbackTxt,
|
|
||||||
UserId: data.UserId(hctx.GetConf(ctx).UserSecret),
|
|
||||||
}
|
|
||||||
reqBody, err := json.Marshal(feedback)
|
|
||||||
lib.CheckFatalError(err)
|
|
||||||
_, _ = lib.ApiPost("/api/v1/feedback", "application/json", reqBody)
|
|
||||||
lib.CheckFatalError(lib.Uninstall(ctx))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(uninstallCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: maybe prompt users for feedback on why they're uninstalling?
|
|
@ -640,305 +640,6 @@ func readFileToArray(path string) ([]string, error) {
|
|||||||
return lines, nil
|
return lines, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Install(secretKey string, offline bool) error {
|
|
||||||
homedir, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get user's home directory: %v", err)
|
|
||||||
}
|
|
||||||
err = hctx.MakeHishtoryDir()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
path, err := installBinary(homedir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = configureBashrc(homedir, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = configureZshrc(homedir, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = configureFish(homedir, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = handleUpgradedFeatures()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = hctx.GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
// No config, so set up a new installation
|
|
||||||
return Setup(secretKey, offline)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleUpgradedFeatures() error {
|
|
||||||
configConents, err := hctx.GetConfigContents()
|
|
||||||
if err != nil {
|
|
||||||
// No config, so this is a new install and thus there is nothing to do
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if strings.Contains(string(configConents), "enable_control_r_search") {
|
|
||||||
// control-r search is already configured, so there is nothing to do
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Enable control-r search
|
|
||||||
config, err := hctx.GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config.ControlRSearchEnabled = true
|
|
||||||
return hctx.SetConfig(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFishConfigPath(homedir string) string {
|
|
||||||
return path.Join(homedir, data.HISHTORY_PATH, "config.fish")
|
|
||||||
}
|
|
||||||
|
|
||||||
func configureFish(homedir, binaryPath string) error {
|
|
||||||
// Check if fish is installed
|
|
||||||
_, err := exec.LookPath("fish")
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Create the file we're going to source. Do this no matter what in case there are updates to it.
|
|
||||||
configContents := ConfigFishContents
|
|
||||||
if os.Getenv("HISHTORY_TEST") != "" {
|
|
||||||
testConfig, err := tweakConfigForTests(ConfigFishContents)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
configContents = testConfig
|
|
||||||
}
|
|
||||||
err = ioutil.WriteFile(getFishConfigPath(homedir), []byte(configContents), 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write config.zsh file: %v", err)
|
|
||||||
}
|
|
||||||
// Check if we need to configure the fishrc
|
|
||||||
fishIsConfigured, err := isFishConfigured(homedir)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to check ~/.config/fish/config.fish: %v", err)
|
|
||||||
}
|
|
||||||
if fishIsConfigured {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Add to fishrc
|
|
||||||
err = os.MkdirAll(path.Join(homedir, ".config/fish"), 0o744)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create fish config directory: %v", err)
|
|
||||||
}
|
|
||||||
return addToShellConfig(path.Join(homedir, ".config/fish/config.fish"), getFishConfigFragment(homedir))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFishConfigFragment(homedir string) string {
|
|
||||||
return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.HISHTORY_PATH) + "\"\nsource " + getFishConfigPath(homedir) + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
func isFishConfigured(homedir string) (bool, error) {
|
|
||||||
_, err := os.Stat(path.Join(homedir, ".config/fish/config.fish"))
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
fishConfig, err := ioutil.ReadFile(path.Join(homedir, ".config/fish/config.fish"))
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to read ~/.config/fish/config.fish: %v", err)
|
|
||||||
}
|
|
||||||
return strings.Contains(string(fishConfig), "# Hishtory Config:"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getZshConfigPath(homedir string) string {
|
|
||||||
return path.Join(homedir, data.HISHTORY_PATH, "config.zsh")
|
|
||||||
}
|
|
||||||
|
|
||||||
func configureZshrc(homedir, binaryPath string) error {
|
|
||||||
// Create the file we're going to source in our zshrc. Do this no matter what in case there are updates to it.
|
|
||||||
configContents := ConfigZshContents
|
|
||||||
if os.Getenv("HISHTORY_TEST") != "" {
|
|
||||||
testConfig, err := tweakConfigForTests(configContents)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
configContents = testConfig
|
|
||||||
}
|
|
||||||
err := ioutil.WriteFile(getZshConfigPath(homedir), []byte(configContents), 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write config.zsh file: %v", err)
|
|
||||||
}
|
|
||||||
// Check if we need to configure the zshrc
|
|
||||||
zshIsConfigured, err := isZshConfigured(homedir)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to check .zshrc: %v", err)
|
|
||||||
}
|
|
||||||
if zshIsConfigured {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Add to zshrc
|
|
||||||
return addToShellConfig(getZshRcPath(homedir), getZshConfigFragment(homedir))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getZshRcPath(homedir string) string {
|
|
||||||
if zdotdir := os.Getenv("ZDOTDIR"); zdotdir != "" {
|
|
||||||
return path.Join(zdotdir, ".zshrc")
|
|
||||||
}
|
|
||||||
return path.Join(homedir, ".zshrc")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getZshConfigFragment(homedir string) string {
|
|
||||||
return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.HISHTORY_PATH) + "\"\nsource " + getZshConfigPath(homedir) + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
func isZshConfigured(homedir string) (bool, error) {
|
|
||||||
_, err := os.Stat(getZshRcPath(homedir))
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
bashrc, err := ioutil.ReadFile(getZshRcPath(homedir))
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to read zshrc: %v", err)
|
|
||||||
}
|
|
||||||
return strings.Contains(string(bashrc), "# Hishtory Config:"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBashConfigPath(homedir string) string {
|
|
||||||
return path.Join(homedir, data.HISHTORY_PATH, "config.sh")
|
|
||||||
}
|
|
||||||
|
|
||||||
func configureBashrc(homedir, binaryPath string) error {
|
|
||||||
// Create the file we're going to source in our bashrc. Do this no matter what in case there are updates to it.
|
|
||||||
configContents := ConfigShContents
|
|
||||||
if os.Getenv("HISHTORY_TEST") != "" {
|
|
||||||
testConfig, err := tweakConfigForTests(ConfigShContents)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
configContents = testConfig
|
|
||||||
}
|
|
||||||
err := ioutil.WriteFile(getBashConfigPath(homedir), []byte(configContents), 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write config.sh file: %v", err)
|
|
||||||
}
|
|
||||||
// Check if we need to configure the bashrc and configure it if so
|
|
||||||
bashRcIsConfigured, err := isBashRcConfigured(homedir)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to check ~/.bashrc: %v", err)
|
|
||||||
}
|
|
||||||
if !bashRcIsConfigured {
|
|
||||||
err = addToShellConfig(path.Join(homedir, ".bashrc"), getBashConfigFragment(homedir))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check if we need to configure the bash_profile and configure it if so
|
|
||||||
bashProfileIsConfigured, err := isBashProfileConfigured(homedir)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to check ~/.bash_profile: %v", err)
|
|
||||||
}
|
|
||||||
if !bashProfileIsConfigured {
|
|
||||||
err = addToShellConfig(path.Join(homedir, ".bash_profile"), getBashConfigFragment(homedir))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addToShellConfig(shellConfigPath, configFragment string) error {
|
|
||||||
f, err := os.OpenFile(shellConfigPath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to append to %s: %v", shellConfigPath, err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
_, err = f.WriteString(configFragment)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to append to %s: %v", shellConfigPath, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBashConfigFragment(homedir string) string {
|
|
||||||
return "\n# Hishtory Config:\nexport PATH=\"$PATH:" + path.Join(homedir, data.HISHTORY_PATH) + "\"\nsource " + getBashConfigPath(homedir) + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBashRcConfigured(homedir string) (bool, error) {
|
|
||||||
_, err := os.Stat(path.Join(homedir, ".bashrc"))
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
bashrc, err := ioutil.ReadFile(path.Join(homedir, ".bashrc"))
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to read bashrc: %v", err)
|
|
||||||
}
|
|
||||||
return strings.Contains(string(bashrc), "# Hishtory Config:"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBashProfileConfigured(homedir string) (bool, error) {
|
|
||||||
_, err := os.Stat(path.Join(homedir, ".bash_profile"))
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
bashrc, err := ioutil.ReadFile(path.Join(homedir, ".bash_profile"))
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to read bash_profile: %v", err)
|
|
||||||
}
|
|
||||||
return strings.Contains(string(bashrc), "# Hishtory Config:"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func installBinary(homedir string) (string, error) {
|
|
||||||
clientPath, err := exec.LookPath("hishtory")
|
|
||||||
if err != nil {
|
|
||||||
clientPath = path.Join(homedir, data.HISHTORY_PATH, "hishtory")
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(clientPath); err == nil {
|
|
||||||
err = syscall.Unlink(clientPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to unlink %s for install: %v", clientPath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = copyFile(os.Args[0], clientPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to copy hishtory binary to $PATH: %v", err)
|
|
||||||
}
|
|
||||||
err = os.Chmod(clientPath, 0o700)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to set permissions on hishtory binary: %v", err)
|
|
||||||
}
|
|
||||||
return clientPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFile(src, dst string) error {
|
|
||||||
sourceFileStat, err := os.Stat(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sourceFileStat.Mode().IsRegular() {
|
|
||||||
return fmt.Errorf("%s is not a regular file", src)
|
|
||||||
}
|
|
||||||
|
|
||||||
source, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer source.Close()
|
|
||||||
|
|
||||||
destination, err := os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(destination, source)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return destination.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDownloadData() (shared.UpdateInfo, error) {
|
func GetDownloadData() (shared.UpdateInfo, error) {
|
||||||
respBody, err := ApiGet("/api/v1/download")
|
respBody, err := ApiGet("/api/v1/download")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1459,29 +1160,6 @@ func GetBanner(ctx *context.Context) ([]byte, error) {
|
|||||||
return ApiGet(url)
|
return ApiGet(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tweakConfigForTests(configContents string) (string, error) {
|
|
||||||
madeSubstitution := false
|
|
||||||
skipLineIndex := -1
|
|
||||||
ret := ""
|
|
||||||
split := strings.Split(configContents, "\n")
|
|
||||||
for i, line := range split {
|
|
||||||
if strings.Contains(line, "# Background Run") {
|
|
||||||
ret += strings.ReplaceAll(split[i+1], "# hishtory", "hishtory")
|
|
||||||
madeSubstitution = true
|
|
||||||
skipLineIndex = i + 1
|
|
||||||
} else if i == skipLineIndex {
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
ret += line
|
|
||||||
}
|
|
||||||
ret += "\n"
|
|
||||||
}
|
|
||||||
if !madeSubstitution {
|
|
||||||
return "", fmt.Errorf("failed to find substitution line in configConents=%#v", configContents)
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseTimeGenerously(input string) (time.Time, error) {
|
func parseTimeGenerously(input string) (time.Time, error) {
|
||||||
input = strings.ReplaceAll(input, "_", " ")
|
input = strings.ReplaceAll(input, "_", " ")
|
||||||
return dateparse.ParseLocal(input)
|
return dateparse.ParseLocal(input)
|
||||||
@ -1637,53 +1315,6 @@ func tokenize(query string) ([]string, error) {
|
|||||||
return strings.Split(query, " "), nil
|
return strings.Split(query, " "), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func stripLines(filePath, lines string) error {
|
|
||||||
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
|
|
||||||
// File does not exist, nothing to do
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
origContents, err := os.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
linesToBeRemoved := make(map[string]bool, 0)
|
|
||||||
for _, line := range strings.Split(lines, "\n") {
|
|
||||||
if strings.TrimSpace(line) != "" {
|
|
||||||
linesToBeRemoved[line] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret := ""
|
|
||||||
for _, line := range strings.Split(string(origContents), "\n") {
|
|
||||||
if !linesToBeRemoved[line] {
|
|
||||||
ret += line
|
|
||||||
ret += "\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return os.WriteFile(filePath, []byte(ret), 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Uninstall(ctx *context.Context) error {
|
|
||||||
homedir := hctx.GetHome(ctx)
|
|
||||||
err := stripLines(path.Join(homedir, ".bashrc"), getBashConfigFragment(homedir))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = stripLines(getZshRcPath(homedir), getZshConfigFragment(homedir))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = stripLines(path.Join(homedir, ".config/fish/config.fish"), getFishConfigFragment(homedir))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = os.RemoveAll(path.Join(homedir, ".hishtory"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println("Successfully uninstalled hishtory, please restart your terminal...")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDumpRequests(config hctx.ClientConfig) ([]*shared.DumpRequest, error) {
|
func GetDumpRequests(config hctx.ClientConfig) ([]*shared.DumpRequest, error) {
|
||||||
if config.IsOffline {
|
if config.IsOffline {
|
||||||
return make([]*shared.DumpRequest, 0), nil
|
return make([]*shared.DumpRequest, 0), nil
|
||||||
|
Loading…
Reference in New Issue
Block a user