diff --git a/CHANGELOG.md b/CHANGELOG.md index cfd84d9..ed6c1bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog - Paw +## Unreleased + +- all: merge CLI and GUI apps to provide only a binary +- deps upgrade: + - fyne.io/fyne v2.4.3 + - golang.org/x/crypto v0.18.0 + - golang.org/x/image v0.15.0 + - golang.org/x/sync v0.6.0 + - golang.org/x/term v0.16.0 + - golang.org/x/text v0.14.0 + ## 0.20.1 - 15 November 2023 - ui: update the vault layout to focus the search box using shift+tab diff --git a/README.md b/README.md index 0168985..3fb476d 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ It is written in Go and uses [Fyne](https://github.com/fyne-io/fyne) as UI toolk ## Main features * Cross platform application (linux, macOS, Windows, BSD ...) with a single codebase -* Desktop, Mobile and CLI application +* Desktop, Mobile and CLI application with a single binary * Minimal direct dependencies * Agent to handle SSH keys and CLI sessions * Open source: code can be audited @@ -45,29 +45,18 @@ It is written in Go and uses [Fyne](https://github.com/fyne-io/fyne) as UI toolk ### Latest version -#### Desktop application ``` go install lucor.dev/paw/cmd/paw@latest ``` -#### CLI application - -``` -go install lucor.dev/paw/cmd/paw-cli@latest -``` - ### Development version To try the development version or help with testing: ``` -# Desktop application go install lucor.dev/paw/cmd/paw@develop - -# CLI application -go install lucor.dev/paw/cmd/paw-cli@develop ``` ## How it works - cryptography details diff --git a/cmd/paw-cli/main.go b/cmd/paw-cli/main.go deleted file mode 100644 index b6d6c2a..0000000 --- a/cmd/paw-cli/main.go +++ /dev/null @@ -1,72 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "lucor.dev/paw/internal/cli" - "lucor.dev/paw/internal/paw" -) - -// Version allow to set the version at link time -var Version string - -func main() { - s, err := paw.NewOSStorage() - if err != nil { - fmt.Fprintf(os.Stderr, "[✗] %s\n", err) - os.Exit(1) - } - - // Define the command to use - commands := []cli.Cmd{ - &cli.AgentCmd{}, - &cli.AddCmd{}, - &cli.EditCmd{}, - &cli.InitCmd{}, - &cli.ListCmd{}, - &cli.LockCmd{}, - &cli.PwGenCmd{}, - &cli.RemoveCmd{}, - &cli.ShowCmd{}, - &cli.UnlockCmd{}, - &cli.VersionCmd{Version: Version}, - } - - // display the usage if no command is specified - if len(os.Args) == 1 { - cli.Usage(commands) - os.Exit(1) - } - - // check for valid command - var cmd cli.Cmd - for _, v := range commands { - if os.Args[1] == v.Name() { - cmd = v - break - } - } - - // If no valid command is specified display the usage - if cmd == nil { - cli.Usage(commands) - os.Exit(1) - } - - // Parse the arguments for the command - // It will display the command usage if -help is specified - // and will exit in case of error - err = cmd.Parse(os.Args[2:]) - if err != nil { - fmt.Fprintf(os.Stderr, "[✗] %s\n", err) - os.Exit(1) - } - - // Finally run the command - err = cmd.Run(s) - if err != nil { - fmt.Fprintf(os.Stderr, "[✗] %s\n", err) - os.Exit(1) - } -} diff --git a/cmd/paw/main.go b/cmd/paw/main.go index b11d409..651aab8 100644 --- a/cmd/paw/main.go +++ b/cmd/paw/main.go @@ -1,37 +1,61 @@ package main import ( - "runtime/debug" + "fmt" + "log" + "os" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" + "lucor.dev/paw/internal/agent" + "lucor.dev/paw/internal/cli" "lucor.dev/paw/internal/icon" + "lucor.dev/paw/internal/paw" "lucor.dev/paw/internal/ui" ) -// Version allow to set the version at link time -var Version string - func main() { + + s, err := paw.NewOSStorage() + if err != nil { + log.Fatal(err) + } + + // check for running instance + var agentType agent.Type + c, err := agent.NewClient(s.SocketAgentPath()) + if err == nil { + agentType, _ = c.Type() + } + + // handle application start: CLI, GUI + args := len(os.Args) + if args > 1 { + if os.Args[1] == "cli" { + // make CLI app + cli.New(s) + return + } + } + + if ui.HealthServiceCheck() { + fmt.Println("paw GUI is already running, exits") + os.Exit(1) + } + + go ui.HealthService() + + if agentType.IsZero() { + go agent.Run(agent.NewGUI(), s.SocketAgentPath()) + } + a := app.NewWithID("dev.lucor.paw") a.SetIcon(icon.PawIcon) w := a.NewWindow("Paw") w.SetMaster() w.Resize(fyne.NewSize(400, 600)) - w.SetContent(ui.MakeApp(w, version())) + w.SetContent(ui.MakeApp(w)) w.ShowAndRun() } - -func version() string { - if Version != "" { - return Version - } - - info, ok := debug.ReadBuildInfo() - if ok { - return info.Main.Version - } - return "(unknown)" -} diff --git a/go.mod b/go.mod index fba758f..25652e2 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,14 @@ go 1.18 require ( filippo.io/age v1.1.1 - fyne.io/fyne/v2 v2.4.1 + fyne.io/fyne/v2 v2.4.3 github.com/stretchr/testify v1.8.4 golang.design/x/clipboard v0.7.0 - golang.org/x/crypto v0.14.0 - golang.org/x/image v0.13.0 - golang.org/x/sync v0.4.0 - golang.org/x/term v0.13.0 - golang.org/x/text v0.13.0 + golang.org/x/crypto v0.18.0 + golang.org/x/image v0.15.0 + golang.org/x/sync v0.6.0 + golang.org/x/term v0.16.0 + golang.org/x/text v0.14.0 ) require ( @@ -38,7 +38,7 @@ require ( golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 // indirect golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/sys v0.16.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect diff --git a/go.sum b/go.sum index 024c399..56dfc3b 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg= filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE= -fyne.io/fyne/v2 v2.4.1 h1:Es100N6HIhJGg8H2ZAS2j5H/YibfxecXHs2V4A4hbq8= -fyne.io/fyne/v2 v2.4.1/go.mod h1:AWM1iPM2YfliduZ4u/kQzP9E6ARIWm0gg+57GpYzWro= +fyne.io/fyne/v2 v2.4.3 h1:v2wncjEAcwXZ8UNmTCWTGL9+sGyPc5RuzBvM96GcC78= +fyne.io/fyne/v2 v2.4.3/go.mod h1:1h3BKxmQYRJlr2g+RGVxedzr6vLVQ/AJmFWcF9CJnoQ= fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e h1:Hvs+kW2VwCzNToF3FmnIAzmivNgrclwPgoUdVSrjkP8= fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -301,8 +301,8 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -317,8 +317,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= -golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= +golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= +golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -407,8 +407,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -455,11 +455,11 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -468,8 +468,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 36232fb..7043ee2 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -19,6 +19,10 @@ var ErrOperationUnsupported = errors.New("operation unsupported") // Type represents the agent type type Type string +func (t Type) IsZero() bool { + return t == "" +} + const ( // CLI represents the agent started in CLI mode CLI Type = "CLI" diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 9b482dd..872daad 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -20,7 +20,62 @@ const ( sessionEnvName = "PAW_SESSION" ) -// Cmd wraps the methods for a paw-cli command +func New(s paw.Storage) { + + // Define the command to use + commands := []Cmd{ + &AgentCmd{}, + &AddCmd{}, + &EditCmd{}, + &InitCmd{}, + &ListCmd{}, + &LockCmd{}, + &PwGenCmd{}, + &RemoveCmd{}, + &ShowCmd{}, + &UnlockCmd{}, + &VersionCmd{}, + } + + // display the usage if no command is specified + if len(os.Args) == 2 { + Usage(commands) + os.Exit(1) + } + + // check for valid command + var cmd Cmd + for _, v := range commands { + if os.Args[2] == v.Name() { + cmd = v + break + } + } + + // If no valid command is specified display the usage + if cmd == nil { + Usage(commands) + os.Exit(1) + } + + // Parse the arguments for the command + // It will display the command usage if -help is specified + // and will exit in case of error + err := cmd.Parse(os.Args[3:]) + if err != nil { + fmt.Fprintf(os.Stderr, "[✗] %s\n", err) + os.Exit(1) + } + + // Finally run the command + err = cmd.Run(s) + if err != nil { + fmt.Fprintf(os.Stderr, "[✗] %s\n", err) + os.Exit(1) + } +} + +// Cmd wraps the methods for a paw cli command type Cmd interface { Name() string // Name returns the one word command name Description() string // Description returns the command description @@ -31,14 +86,14 @@ type Cmd interface { // Usage prints the command usage func Usage(commands []Cmd) { - template := `paw-cli is the CLI application for Paw + template := `paw cli is the CLI application for Paw -Usage: paw-cli [arguments] +Usage: paw cli [arguments] The commands are: {{ range $k, $cmd := . }} {{ printf "%-13s %s\n" $cmd.Name $cmd.Description }}{{ end }} -Use "paw-cli -help" for more information about a command. +Use "paw cli -help" for more information about a command. ` printTemplate(os.Stdout, template, commands) } diff --git a/internal/cli/cmd_add.go b/internal/cli/cmd_add.go index eb0e72c..4103708 100644 --- a/internal/cli/cmd_add.go +++ b/internal/cli/cmd_add.go @@ -27,7 +27,7 @@ func (cmd *AddCmd) Description() string { // Usage displays the command usage func (cmd *AddCmd) Usage() { - template := `Usage: paw-cli add [OPTION] VAULT_NAME/ITEM_TYPE/ITEM_NAME + template := `Usage: paw cli add [OPTION] VAULT_NAME/ITEM_TYPE/ITEM_NAME {{ . }} diff --git a/internal/cli/cmd_agent.go b/internal/cli/cmd_agent.go index 0c4af98..2826728 100644 --- a/internal/cli/cmd_agent.go +++ b/internal/cli/cmd_agent.go @@ -32,7 +32,7 @@ func (cmd *AgentCmd) Description() string { // Usage displays the command usage func (cmd *AgentCmd) Usage() { - template := `Usage: paw-cli agent COMMAND + template := `Usage: paw cli agent COMMAND {{ . }} diff --git a/internal/cli/cmd_edit.go b/internal/cli/cmd_edit.go index 265a695..d17c70c 100644 --- a/internal/cli/cmd_edit.go +++ b/internal/cli/cmd_edit.go @@ -25,7 +25,7 @@ func (cmd *EditCmd) Description() string { // Usage displays the command usage func (cmd *EditCmd) Usage() { - template := `Usage: paw-cli [OPTION] edit VAULT_NAME/ITEM_TYPE/ITEM_NAME + template := `Usage: paw cli [OPTION] edit VAULT_NAME/ITEM_TYPE/ITEM_NAME {{ . }} diff --git a/internal/cli/cmd_init.go b/internal/cli/cmd_init.go index ecde122..95a2083 100644 --- a/internal/cli/cmd_init.go +++ b/internal/cli/cmd_init.go @@ -24,7 +24,7 @@ func (cmd *InitCmd) Description() string { // Usage displays the command usage func (cmd *InitCmd) Usage() { - template := `Usage: paw-cli init VAULT + template := `Usage: paw cli init VAULT {{ . }} diff --git a/internal/cli/cmd_list.go b/internal/cli/cmd_list.go index 3ccc897..1f2a9ca 100644 --- a/internal/cli/cmd_list.go +++ b/internal/cli/cmd_list.go @@ -24,7 +24,7 @@ func (cmd *ListCmd) Description() string { // Usage displays the command usage func (cmd *ListCmd) Usage() { - template := `Usage: paw-cli ls [OPTION] [VAULT_NAME/ITEM_TYPE/ITEM_NAME] + template := `Usage: paw cli ls [OPTION] [VAULT_NAME/ITEM_TYPE/ITEM_NAME] {{ . }} @@ -141,7 +141,7 @@ func (cmd *ListCmd) vaults(s paw.Storage) (tree.Node, error) { return n, err } if len(vaults) == 0 { - return n, fmt.Errorf("no vaults found. To create one: paw-cli init VAULT") + return n, fmt.Errorf("no vaults found. To create one: paw cli init VAULT") } for _, v := range vaults { if cmd.vaultName != "" && cmd.vaultName != v { diff --git a/internal/cli/cmd_lock.go b/internal/cli/cmd_lock.go index 914e3ef..e6d5523 100644 --- a/internal/cli/cmd_lock.go +++ b/internal/cli/cmd_lock.go @@ -26,7 +26,7 @@ func (cmd *LockCmd) Description() string { // Usage displays the command usage func (cmd *LockCmd) Usage() { - template := `Usage: paw-cli lock [OPTION] VAULT + template := `Usage: paw cli lock [OPTION] VAULT {{ . }} diff --git a/internal/cli/cmd_pwgen.go b/internal/cli/cmd_pwgen.go index e32d9c3..8dceede 100644 --- a/internal/cli/cmd_pwgen.go +++ b/internal/cli/cmd_pwgen.go @@ -21,7 +21,7 @@ func (cmd *PwGenCmd) Description() string { // Usage displays the command usage func (cmd *PwGenCmd) Usage() { - template := `Usage: paw-cli pwgen [OPTION] + template := `Usage: paw cli pwgen [OPTION] {{ . }} diff --git a/internal/cli/cmd_rm.go b/internal/cli/cmd_rm.go index 1baf99c..b3ded1f 100644 --- a/internal/cli/cmd_rm.go +++ b/internal/cli/cmd_rm.go @@ -24,7 +24,7 @@ func (cmd *RemoveCmd) Description() string { // Usage displays the command usage func (cmd *RemoveCmd) Usage() { - template := `Usage: paw-cli rm [OPTION] VAULT_NAME/ITEM_TYPE/ITEM_NAME + template := `Usage: paw cli rm [OPTION] VAULT_NAME/ITEM_TYPE/ITEM_NAME {{ . }} diff --git a/internal/cli/cmd_show.go b/internal/cli/cmd_show.go index 300758a..4c7c1ef 100644 --- a/internal/cli/cmd_show.go +++ b/internal/cli/cmd_show.go @@ -29,7 +29,7 @@ func (cmd *ShowCmd) Description() string { // Usage displays the command usage func (cmd *ShowCmd) Usage() { - template := `Usage: paw-cli show [OPTION] VAULT_NAME/ITEM_TYPE/ITEM_NAME + template := `Usage: paw cli show [OPTION] VAULT_NAME/ITEM_TYPE/ITEM_NAME {{ . }} diff --git a/internal/cli/cmd_unlock.go b/internal/cli/cmd_unlock.go index 3b2b733..eb2bdec 100644 --- a/internal/cli/cmd_unlock.go +++ b/internal/cli/cmd_unlock.go @@ -28,7 +28,7 @@ func (cmd *UnlockCmd) Description() string { // Usage displays the command usage func (cmd *UnlockCmd) Usage() { - template := `Usage: paw-cli session [OPTION] COMMAND VAULT + template := `Usage: paw cli session [OPTION] COMMAND VAULT {{ . }} diff --git a/internal/cli/cmd_version.go b/internal/cli/cmd_version.go index 26ccef5..d5e92a4 100644 --- a/internal/cli/cmd_version.go +++ b/internal/cli/cmd_version.go @@ -2,15 +2,12 @@ package cli import ( "fmt" - "runtime/debug" "lucor.dev/paw/internal/paw" ) // Version is the version command -type VersionCmd struct { - Version string -} +type VersionCmd struct{} // Name returns the one word command name func (cmd *VersionCmd) Name() string { @@ -24,7 +21,7 @@ func (cmd *VersionCmd) Description() string { // Usage displays the command usage func (cmd *VersionCmd) Usage() { - template := `Usage: paw-cli version + template := `Usage: paw cli version {{ . }} @@ -48,16 +45,6 @@ func (cmd *VersionCmd) Parse(args []string) error { // Run runs the command func (cmd *VersionCmd) Run(s paw.Storage) error { - fmt.Printf("paw-cli version %s\n", cmd.version()) + fmt.Printf("paw cli version %s\n", paw.Version()) return nil } - -func (cmd *VersionCmd) version() string { - if cmd.Version != "" { - return cmd.Version - } - if info, ok := debug.ReadBuildInfo(); ok { - return info.Main.Version - } - return "(unknown)" -} diff --git a/internal/paw/paw.go b/internal/paw/paw.go index 31640ed..556c665 100644 --- a/internal/paw/paw.go +++ b/internal/paw/paw.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "io" + "runtime/debug" "strings" "time" @@ -19,8 +20,13 @@ import ( ) const ( - ID = "dev.lucor.paw" - Version = "lucor/paw/v1" + ID = "dev.lucor.paw" + ServicePrefix = "paw/" +) + +var ( + // BuildVersion allow to set the version at link time + BuildVersion string ) type Ruler interface { @@ -251,3 +257,21 @@ func (k *Key) UnmarshalJSON(data []byte) error { func (k *Key) String() string { return k.ageIdentity.String() } + +// Version returns the Paw's version +func Version() string { + if BuildVersion != "" { + return BuildVersion + } + + info, ok := debug.ReadBuildInfo() + if ok { + return info.Main.Version + } + return "(unknown)" +} + +// ServiceVersion returns the Paw's service version +func ServiceVersion() string { + return ServicePrefix + Version() +} diff --git a/internal/ui/app.go b/internal/ui/app.go index 1ccaa39..1b86642 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -3,7 +3,6 @@ package ui import ( "fmt" "log" - "os" "runtime" "fyne.io/fyne/v2" @@ -35,13 +34,11 @@ type app struct { filter map[string]*paw.VaultFilterOptions - version string - // Paw agent client client agent.PawAgent } -func MakeApp(w fyne.Window, ver string) fyne.CanvasObject { +func MakeApp(w fyne.Window) fyne.CanvasObject { var s paw.Storage var err error @@ -54,24 +51,6 @@ func MakeApp(w fyne.Window, ver string) fyne.CanvasObject { log.Fatal(err) } - if ver == "" { - ver = "(unknown)" - } - - // check for running instance - cliAgentRunning := false - c, err := agent.NewClient(s.SocketAgentPath()) - if err == nil { - t, _ := c.Type() - switch t { - case agent.GUI: - // a GUI instance is already running, exit - os.Exit(1) - case agent.CLI: - cliAgentRunning = true - } - } - config, err := s.LoadConfig() if err != nil { dialog.NewError(err, w) @@ -82,7 +61,6 @@ func MakeApp(w fyne.Window, ver string) fyne.CanvasObject { storage: s, config: config, unlockedVault: make(map[string]*paw.Vault), - version: ver, filter: make(map[string]*paw.VaultFilterOptions), } @@ -90,9 +68,6 @@ func MakeApp(w fyne.Window, ver string) fyne.CanvasObject { a.main = a.makeApp() a.makeSysTray() - if !cliAgentRunning { - go agent.Run(agent.NewGUI(), s.SocketAgentPath()) - } return a.main } diff --git a/internal/ui/health.go b/internal/ui/health.go new file mode 100644 index 0000000..b114901 --- /dev/null +++ b/internal/ui/health.go @@ -0,0 +1,86 @@ +package ui + +import ( + "bytes" + "errors" + "fmt" + "log" + "net" + "time" + + "lucor.dev/paw/internal/paw" +) + +const ( + startPortRange = 54321 + endPortRange = 55000 + timeout = 100 * time.Millisecond +) + +// handleConnection handles the connection returning the paw version +func handleConnection(conn net.Conn) { + defer conn.Close() + + // Send service information to the client and exits + _, err := conn.Write([]byte(paw.ServiceVersion() + "\n")) + if err != nil { + fmt.Println("Error writing server info:", err) + return + } +} + +// HealthService +func HealthService() (net.Listener, error) { + var listener net.Listener + var err error + var address string + for i := startPortRange; i < endPortRange; i++ { + address = fmt.Sprintf("127.0.0.1:%d", i) + listener, err = net.Listen("tcp", address) + if err == nil { + defer listener.Close() + break + } + log.Println("health service: error listening:", err) + } + + if listener == nil { + return listener, errors.New("health service: could not start") + } + + for { + conn, err := listener.Accept() + if err != nil { + return listener, fmt.Errorf("health service: error accepting connection: %w", err) + } + go handleConnection(conn) + } +} + +func HealthServiceCheck() bool { + var address string + for i := startPortRange; i < endPortRange; i++ { + address = fmt.Sprintf("127.0.0.1:%d", i) + conn, err := net.DialTimeout("tcp", address, timeout) + if err != nil { + continue + } + + // Read the service version from the app and close the connection + conn.SetReadDeadline(time.Now().Add(timeout)) + buffer := make([]byte, 4) + _, err = conn.Read(buffer) + conn.Close() + if err != nil { + // error reading + continue + } + + // check for paw service + if bytes.Equal([]byte(paw.ServicePrefix), buffer) { + return true + } + } + + return false +} diff --git a/internal/ui/health_test.go b/internal/ui/health_test.go new file mode 100644 index 0000000..07d4d13 --- /dev/null +++ b/internal/ui/health_test.go @@ -0,0 +1,22 @@ +package ui + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestHealthService(t *testing.T) { + t.Run("service not available", func(t *testing.T) { + status := HealthServiceCheck() + require.False(t, status) + }) + + t.Run("service available", func(t *testing.T) { + go HealthService() + time.Sleep(5 * time.Millisecond) + status := HealthServiceCheck() + require.True(t, status) + }) +} diff --git a/internal/ui/menu.go b/internal/ui/menu.go index 282679e..ae04266 100644 --- a/internal/ui/menu.go +++ b/internal/ui/menu.go @@ -11,6 +11,7 @@ import ( "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + "lucor.dev/paw/internal/paw" ) func (a *app) makeMainMenu() *fyne.MainMenu { @@ -50,7 +51,7 @@ func (a *app) makeMainMenu() *fyne.MainMenu { func (a *app) about() { u, _ := url.Parse("https://lucor.dev/paw") - l := widget.NewLabel("Paw - " + a.version) + l := widget.NewLabel("Paw - " + paw.Version()) l.Alignment = fyne.TextAlignCenter link := widget.NewHyperlink("https://lucor.dev/paw", u) link.Alignment = fyne.TextAlignCenter