More refactoring to use cobra

This commit is contained in:
David Dworken 2022-11-14 19:26:56 -08:00
parent 342a02a843
commit c59de42008
No known key found for this signature in database
12 changed files with 287 additions and 129 deletions

View File

@ -0,0 +1,30 @@
package cmd
import (
"github.com/ddworken/hishtory/client/hctx"
"github.com/ddworken/hishtory/client/lib"
"github.com/spf13/cobra"
)
var enableCmd = &cobra.Command{
Use: "enable",
Short: "Enable hiSHtory recording",
Run: func(cmd *cobra.Command, args []string) {
ctx := hctx.MakeContext()
lib.CheckFatalError(lib.Enable(ctx))
},
}
var disableCmd = &cobra.Command{
Use: "disable",
Short: "Disable hiSHtory recording",
Run: func(cmd *cobra.Command, args []string) {
ctx := hctx.MakeContext()
lib.CheckFatalError(lib.Disable(ctx))
},
}
func init() {
rootCmd.AddCommand(enableCmd)
rootCmd.AddCommand(disableCmd)
}

27
client/cmd/import.go Normal file
View File

@ -0,0 +1,27 @@
package cmd
import (
"fmt"
"github.com/ddworken/hishtory/client/hctx"
"github.com/ddworken/hishtory/client/lib"
"github.com/spf13/cobra"
)
var importCmd = &cobra.Command{
Use: "import",
Short: "Re-import history entries from your existing shell history",
Long: "Note that you must pipe commands to be imported in via stdin. For example `history | hishtory import`.",
Run: func(cmd *cobra.Command, args []string) {
ctx := hctx.MakeContext()
numImported, err := lib.ImportHistory(ctx, true, true)
lib.CheckFatalError(err)
if numImported > 0 {
fmt.Printf("Imported %v history entries from your existing shell history\n", numImported)
}
},
}
func init() {
rootCmd.AddCommand(importCmd)
}

56
client/cmd/init.go Normal file
View File

@ -0,0 +1,56 @@
package cmd
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/ddworken/hishtory/client/hctx"
"github.com/ddworken/hishtory/client/lib"
"github.com/spf13/cobra"
)
var offline *bool
var initCmd = &cobra.Command{
Use: "init",
Short: "Re-initialize hiSHtory with a specified secret key",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cmd.Flag("offline").Value.String()
db, err := hctx.OpenLocalSqliteDb()
lib.CheckFatalError(err)
data, err := lib.Search(nil, db, "", 10)
lib.CheckFatalError(err)
if len(data) > 0 {
fmt.Printf("Your current hishtory profile has saved history entries, are you sure you want to run `init` and reset?\nNote: This won't clear any imported history entries from your existing shell\n[y/N]")
reader := bufio.NewReader(os.Stdin)
resp, err := reader.ReadString('\n')
lib.CheckFatalError(err)
if strings.TrimSpace(resp) != "y" {
fmt.Printf("Aborting init per user response of %#v\n", strings.TrimSpace(resp))
return
}
}
secretKey := ""
if len(args) > 0 {
secretKey = args[0]
}
lib.CheckFatalError(lib.Setup(secretKey, *offline))
if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" {
fmt.Println("Importing existing shell history...")
ctx := hctx.MakeContext()
numImported, err := lib.ImportHistory(ctx, false, false)
lib.CheckFatalError(err)
if numImported > 0 {
fmt.Printf("Imported %v history entries from your existing shell history\n", numImported)
}
}
},
}
func init() {
rootCmd.AddCommand(initCmd)
offline = initCmd.Flags().Bool("offline", false, "Install hiSHtory in offline mode wiht all syncing capabilities disabled")
}

37
client/cmd/install.go Normal file
View File

@ -0,0 +1,37 @@
package cmd
import (
"fmt"
"os"
"github.com/ddworken/hishtory/client/hctx"
"github.com/ddworken/hishtory/client/lib"
"github.com/spf13/cobra"
)
var installCmd = &cobra.Command{
Use: "install",
Short: "Copy this binary to ~/.hishtory/ and configure your shell to use it for recording your shell history",
Run: func(cmd *cobra.Command, args []string) {
lib.CheckFatalError(lib.Install())
if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" {
db, err := hctx.OpenLocalSqliteDb()
lib.CheckFatalError(err)
data, err := lib.Search(nil, db, "", 10)
lib.CheckFatalError(err)
if len(data) < 10 {
fmt.Println("Importing existing shell history...")
ctx := hctx.MakeContext()
numImported, err := lib.ImportHistory(ctx, false, false)
lib.CheckFatalError(err)
if numImported > 0 {
fmt.Printf("Imported %v history entries from your existing shell history\n", numImported)
}
}
}
},
}
func init() {
rootCmd.AddCommand(installCmd)
}

44
client/cmd/status.go Normal file
View File

@ -0,0 +1,44 @@
package cmd
import (
"fmt"
"github.com/ddworken/hishtory/client/data"
"github.com/ddworken/hishtory/client/hctx"
"github.com/ddworken/hishtory/client/lib"
"github.com/spf13/cobra"
)
var verbose *bool
var statusCmd = &cobra.Command{
Use: "status",
Short: "Get the hishtory status",
Run: func(cmd *cobra.Command, args []string) {
ctx := hctx.MakeContext()
config := hctx.GetConf(ctx)
fmt.Printf("hiSHtory: v0.%s\nEnabled: %v\n", lib.Version, config.IsEnabled)
fmt.Printf("Secret Key: %s\n", config.UserSecret)
if *verbose {
fmt.Printf("User ID: %s\n", data.UserId(config.UserSecret))
fmt.Printf("Device ID: %s\n", config.DeviceId)
printDumpStatus(config)
}
fmt.Printf("Commit Hash: %s\n", lib.GitCommit)
},
}
func printDumpStatus(config hctx.ClientConfig) {
dumpRequests, err := lib.GetDumpRequests(config)
lib.CheckFatalError(err)
fmt.Printf("Dump Requests: ")
for _, d := range dumpRequests {
fmt.Printf("%#v, ", *d)
}
fmt.Print("\n")
}
func init() {
rootCmd.AddCommand(statusCmd)
verbose = statusCmd.Flags().BoolP("verbose", "v", false, "Display verbose hiSHtory information")
}

32
client/cmd/uninstall.go Normal file
View File

@ -0,0 +1,32 @@
package cmd
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/ddworken/hishtory/client/hctx"
"github.com/ddworken/hishtory/client/lib"
"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) {
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
}
lib.CheckFatalError(lib.Uninstall(hctx.MakeContext()))
},
}
func init() {
rootCmd.AddCommand(uninstallCmd)
}

19
client/cmd/update.go Normal file
View File

@ -0,0 +1,19 @@
package cmd
import (
"github.com/ddworken/hishtory/client/hctx"
"github.com/ddworken/hishtory/client/lib"
"github.com/spf13/cobra"
)
var updateCmd = &cobra.Command{
Use: "update",
Short: "Securely update hishtory to the latest version",
Run: func(cmd *cobra.Command, args []string) {
lib.CheckFatalError(lib.Update(hctx.MakeContext()))
},
}
func init() {
rootCmd.AddCommand(updateCmd)
}

View File

@ -48,6 +48,7 @@ var ConfigZshContents string
var ConfigFishContents string var ConfigFishContents string
var Version string = "Unknown" var Version string = "Unknown"
var GitCommit string = "Unknown"
// 256KB ought to be enough for any reasonable cmd // 256KB ought to be enough for any reasonable cmd
var maxSupportedLineLengthForImport = 256_000 var maxSupportedLineLengthForImport = 256_000
@ -333,18 +334,9 @@ func shouldSkipHiddenCommand(ctx *context.Context, historyLine string) (bool, er
return false, nil return false, nil
} }
func Setup(args []string) error { func Setup(userSecret string, isOffline bool) error {
userSecret := uuid.Must(uuid.NewRandom()).String() if userSecret == "" {
isOffline := false userSecret = uuid.Must(uuid.NewRandom()).String()
if len(args) > 2 && args[2] != "" {
if args[2] == "--offline" {
isOffline = true
} else {
if args[2][0] == '-' {
return fmt.Errorf("refusing to set user secret to %#v since it looks like a flag", args[2])
}
userSecret = args[2]
}
} }
fmt.Println("Setting secret hishtory key to " + string(userSecret)) fmt.Println("Setting secret hishtory key to " + string(userSecret))
@ -691,8 +683,8 @@ func Install() error {
} }
_, err = hctx.GetConfig() _, err = hctx.GetConfig()
if err != nil { if err != nil {
// No config, so set up a new installation // No config, so set up a new installation with a new key and in online mode
return Setup(os.Args) return Setup("", false)
} }
return nil return nil
} }
@ -1470,12 +1462,12 @@ func ProcessDeletionRequests(ctx *context.Context) error {
return nil return nil
} }
func GetBanner(ctx *context.Context, gitCommit string) ([]byte, error) { func GetBanner(ctx *context.Context) ([]byte, error) {
config := hctx.GetConf(ctx) config := hctx.GetConf(ctx)
if config.IsOffline { if config.IsOffline {
return []byte{}, nil return []byte{}, nil
} }
url := "/api/v1/banner?commit_hash=" + gitCommit + "&user_id=" + data.UserId(config.UserSecret) + "&device_id=" + config.DeviceId + "&version=" + Version + "&forced_banner=" + os.Getenv("FORCED_BANNER") url := "/api/v1/banner?commit_hash=" + GitCommit + "&user_id=" + data.UserId(config.UserSecret) + "&device_id=" + config.DeviceId + "&version=" + Version + "&forced_banner=" + os.Getenv("FORCED_BANNER")
return ApiGet(url) return ApiGet(url)
} }
@ -1703,3 +1695,19 @@ func Uninstall(ctx *context.Context) error {
fmt.Println("Successfully uninstalled hishtory, please restart your terminal...") fmt.Println("Successfully uninstalled hishtory, please restart your terminal...")
return nil return nil
} }
func GetDumpRequests(config hctx.ClientConfig) ([]*shared.DumpRequest, error) {
if config.IsOffline {
return make([]*shared.DumpRequest, 0), nil
}
resp, err := ApiGet("/api/v1/get-dump-requests?user_id=" + data.UserId(config.UserSecret) + "&device_id=" + config.DeviceId)
if IsOfflineError(err) {
return []*shared.DumpRequest{}, nil
}
if err != nil {
return nil, err
}
var dumpRequests []*shared.DumpRequest
err = json.Unmarshal(resp, &dumpRequests)
return dumpRequests, err
}

View File

@ -23,7 +23,7 @@ func TestSetup(t *testing.T) {
if _, err := os.Stat(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)); err == nil { if _, err := os.Stat(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)); err == nil {
t.Fatalf("hishtory secret file already exists!") t.Fatalf("hishtory secret file already exists!")
} }
testutils.Check(t, Setup([]string{})) 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.HISHTORY_PATH, data.CONFIG_PATH)); err != nil {
t.Fatalf("hishtory secret file does not exist after Setup()!") t.Fatalf("hishtory secret file does not exist after Setup()!")
} }
@ -47,7 +47,7 @@ func TestSetupOffline(t *testing.T) {
if _, err := os.Stat(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)); err == nil { if _, err := os.Stat(path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH)); err == nil {
t.Fatalf("hishtory secret file already exists!") t.Fatalf("hishtory secret file already exists!")
} }
testutils.Check(t, Setup([]string{"", "", "--offline"})) 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.HISHTORY_PATH, data.CONFIG_PATH)); err != nil {
t.Fatalf("hishtory secret file does not exist after Setup()!") t.Fatalf("hishtory secret file does not exist after Setup()!")
} }
@ -65,7 +65,7 @@ func TestSetupOffline(t *testing.T) {
func TestBuildHistoryEntry(t *testing.T) { func TestBuildHistoryEntry(t *testing.T) {
defer testutils.BackupAndRestore(t)() defer testutils.BackupAndRestore(t)()
defer testutils.RunTestServer()() defer testutils.RunTestServer()()
testutils.Check(t, Setup([]string{})) testutils.Check(t, Setup("", false))
// Test building an actual entry for bash // Test building an actual entry for bash
entry, err := BuildHistoryEntry(hctx.MakeContext(), []string{"unused", "saveHistoryEntry", "bash", "120", " 123 ls /foo ", "1641774958"}) entry, err := BuildHistoryEntry(hctx.MakeContext(), []string{"unused", "saveHistoryEntry", "bash", "120", " 123 ls /foo ", "1641774958"})
@ -158,7 +158,7 @@ func TestBuildHistoryEntryWithTimestampStripping(t *testing.T) {
defer testutils.BackupAndRestoreEnv("HISTTIMEFORMAT")() defer testutils.BackupAndRestoreEnv("HISTTIMEFORMAT")()
defer testutils.BackupAndRestore(t)() defer testutils.BackupAndRestore(t)()
defer testutils.RunTestServer()() defer testutils.RunTestServer()()
testutils.Check(t, Setup([]string{})) testutils.Check(t, Setup("", false))
testcases := []struct { testcases := []struct {
input, histtimeformat, expectedCommand string input, histtimeformat, expectedCommand string

View File

@ -408,7 +408,7 @@ func makeTable(ctx *context.Context, rows []table.Row) (table.Model, error) {
return t, nil return t, nil
} }
func TuiQuery(ctx *context.Context, gitCommit, initialQuery string) error { func TuiQuery(ctx *context.Context, initialQuery string) error {
lipgloss.SetColorProfile(termenv.ANSI) lipgloss.SetColorProfile(termenv.ANSI)
rows, numEntries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, initialQuery, PADDED_NUM_ENTRIES) rows, numEntries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, initialQuery, PADDED_NUM_ENTRIES)
if err != nil { if err != nil {
@ -435,7 +435,7 @@ func TuiQuery(ctx *context.Context, gitCommit, initialQuery string) error {
}() }()
// Async: Check for any banner from the server // Async: Check for any banner from the server
go func() { go func() {
banner, err := GetBanner(ctx, gitCommit) banner, err := GetBanner(ctx)
if err != nil { if err != nil {
if IsOfflineError(err) { if IsOfflineError(err) {
p.Send(offlineMsg{}) p.Send(offlineMsg{})

View File

@ -1,11 +1,9 @@
package main package main
import ( import (
"bufio"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"os" "os"
"strings" "strings"
"time" "time"
@ -17,8 +15,6 @@ import (
"github.com/ddworken/hishtory/shared" "github.com/ddworken/hishtory/shared"
) )
var GitCommit string = "Unknown"
func main() { func main() {
if len(os.Args) == 1 { if len(os.Args) == 1 {
fmt.Println("Must specify a command! Do you mean `hishtory query`?") fmt.Println("Must specify a command! Do you mean `hishtory query`?")
@ -35,7 +31,7 @@ func main() {
query(ctx, strings.Join(os.Args[2:], " ")) query(ctx, strings.Join(os.Args[2:], " "))
case "tquery": case "tquery":
ctx := hctx.MakeContext() ctx := hctx.MakeContext()
lib.CheckFatalError(lib.TuiQuery(ctx, GitCommit, strings.Join(os.Args[2:], " "))) lib.CheckFatalError(lib.TuiQuery(ctx, strings.Join(os.Args[2:], " ")))
case "export": case "export":
ctx := hctx.MakeContext() ctx := hctx.MakeContext()
lib.CheckFatalError(lib.ProcessDeletionRequests(ctx)) lib.CheckFatalError(lib.ProcessDeletionRequests(ctx))
@ -54,88 +50,23 @@ func main() {
} }
lib.CheckFatalError(lib.Redact(ctx, query, force)) lib.CheckFatalError(lib.Redact(ctx, query, force))
case "init": case "init":
db, err := hctx.OpenLocalSqliteDb() fallthrough
lib.CheckFatalError(err)
data, err := lib.Search(nil, db, "", 10)
lib.CheckFatalError(err)
if len(data) > 0 {
fmt.Printf("Your current hishtory profile has saved history entries, are you sure you want to run `init` and reset?\nNote: This won't clear any imported history entries from your existing shell\n[y/N]")
reader := bufio.NewReader(os.Stdin)
resp, err := reader.ReadString('\n')
lib.CheckFatalError(err)
if strings.TrimSpace(resp) != "y" {
fmt.Printf("Aborting init per user response of %#v\n", strings.TrimSpace(resp))
return
}
}
lib.CheckFatalError(lib.Setup(os.Args))
if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" {
fmt.Println("Importing existing shell history...")
ctx := hctx.MakeContext()
numImported, err := lib.ImportHistory(ctx, false, false)
lib.CheckFatalError(err)
if numImported > 0 {
fmt.Printf("Imported %v history entries from your existing shell history\n", numImported)
}
}
case "install": case "install":
lib.CheckFatalError(lib.Install()) fallthrough
if os.Getenv("HISHTORY_SKIP_INIT_IMPORT") == "" {
db, err := hctx.OpenLocalSqliteDb()
lib.CheckFatalError(err)
data, err := lib.Search(nil, db, "", 10)
lib.CheckFatalError(err)
if len(data) < 10 {
fmt.Println("Importing existing shell history...")
ctx := hctx.MakeContext()
numImported, err := lib.ImportHistory(ctx, false, false)
lib.CheckFatalError(err)
if numImported > 0 {
fmt.Printf("Imported %v history entries from your existing shell history\n", numImported)
}
}
}
case "uninstall": case "uninstall":
fmt.Printf("Are you sure you want to uninstall hiSHtory and delete all locally saved history data [y/N]") fallthrough
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
}
lib.CheckFatalError(lib.Uninstall(hctx.MakeContext()))
case "import": case "import":
ctx := hctx.MakeContext() fallthrough
numImported, err := lib.ImportHistory(ctx, true, true)
lib.CheckFatalError(err)
if numImported > 0 {
fmt.Printf("Imported %v history entries from your existing shell history\n", numImported)
}
case "enable": case "enable":
ctx := hctx.MakeContext() fallthrough
lib.CheckFatalError(lib.Enable(ctx))
case "disable": case "disable":
ctx := hctx.MakeContext() fallthrough
lib.CheckFatalError(lib.Disable(ctx))
case "version": case "version":
fallthrough fallthrough
case "status": case "status":
ctx := hctx.MakeContext() fallthrough
config := hctx.GetConf(ctx)
fmt.Printf("hiSHtory: v0.%s\nEnabled: %v\n", lib.Version, config.IsEnabled)
fmt.Printf("Secret Key: %s\n", config.UserSecret)
if len(os.Args) == 3 && os.Args[2] == "-v" {
fmt.Printf("User ID: %s\n", data.UserId(config.UserSecret))
fmt.Printf("Device ID: %s\n", config.DeviceId)
printDumpStatus(config)
}
fmt.Printf("Commit Hash: %s\n", GitCommit)
case "update": case "update":
err := lib.Update(hctx.MakeContext()) fallthrough
if err != nil {
log.Fatalf("Failed to update hishtory: %v", err)
}
case "config-set": case "config-set":
fallthrough fallthrough
case "config-get": case "config-get":
@ -181,32 +112,6 @@ Supported commands:
} }
} }
func printDumpStatus(config hctx.ClientConfig) {
dumpRequests, err := getDumpRequests(config)
lib.CheckFatalError(err)
fmt.Printf("Dump Requests: ")
for _, d := range dumpRequests {
fmt.Printf("%#v, ", *d)
}
fmt.Print("\n")
}
func getDumpRequests(config hctx.ClientConfig) ([]*shared.DumpRequest, error) {
if config.IsOffline {
return make([]*shared.DumpRequest, 0), nil
}
resp, err := lib.ApiGet("/api/v1/get-dump-requests?user_id=" + data.UserId(config.UserSecret) + "&device_id=" + config.DeviceId)
if lib.IsOfflineError(err) {
return []*shared.DumpRequest{}, nil
}
if err != nil {
return nil, err
}
var dumpRequests []*shared.DumpRequest
err = json.Unmarshal(resp, &dumpRequests)
return dumpRequests, err
}
func query(ctx *context.Context, query string) { func query(ctx *context.Context, query string) {
db := hctx.GetDb(ctx) db := hctx.GetDb(ctx)
err := lib.RetrieveAdditionalEntriesFromRemote(ctx) err := lib.RetrieveAdditionalEntriesFromRemote(ctx)
@ -225,7 +130,7 @@ func query(ctx *context.Context, query string) {
} }
func displayBannerIfSet(ctx *context.Context) error { func displayBannerIfSet(ctx *context.Context) error {
respBody, err := lib.GetBanner(ctx, GitCommit) respBody, err := lib.GetBanner(ctx)
if lib.IsOfflineError(err) { if lib.IsOfflineError(err) {
return nil return nil
} }
@ -313,7 +218,7 @@ func saveHistoryEntry(ctx *context.Context) {
} }
// Check if there is a pending dump request and reply to it if so // Check if there is a pending dump request and reply to it if so
dumpRequests, err := getDumpRequests(config) dumpRequests, err := lib.GetDumpRequests(config)
if err != nil { if err != nil {
if lib.IsOfflineError(err) { if lib.IsOfflineError(err) {
// It is fine to just ignore this, the next command will retry the API and eventually we will respond to any pending dump requests // It is fine to just ignore this, the next command will retry the API and eventually we will respond to any pending dump requests

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env bash
GIT_HASH=$(git rev-parse HEAD) GIT_HASH=$(git rev-parse HEAD)
echo "-X main.GitCommit=$GIT_HASH -X github.com/ddworken/hishtory/client/lib.Version=`cat VERSION` -w -extldflags \"-static\"" echo "-X github.com/ddworken/hishtory/client/lib.GitCommit=$GIT_HASH -X github.com/ddworken/hishtory/client/lib.Version=`cat VERSION` -w -extldflags \"-static\""