From e72fe7945fce351f00c75027e898a7f8598c8fa9 Mon Sep 17 00:00:00 2001 From: Bruce MacDonald Date: Mon, 31 Jul 2023 16:25:57 -0400 Subject: [PATCH 1/5] check server is running before running command --- api/client.go | 7 ++++ cmd/cmd.go | 90 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/api/client.go b/api/client.go index 6786fa48..ed1053e8 100644 --- a/api/client.go +++ b/api/client.go @@ -223,3 +223,10 @@ func (c *Client) Delete(ctx context.Context, req *DeleteRequest) error { } return nil } + +func (c *Client) Heartbeat(ctx context.Context) error { + if err := c.do(ctx, http.MethodGet, "/", nil, nil); err != nil { + return err + } + return nil +} diff --git a/cmd/cmd.go b/cmd/cmd.go index 61658f87..fa06e85b 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -10,7 +10,9 @@ import ( "net" "net/http" "os" + "os/exec" "path/filepath" + "runtime" "strings" "time" @@ -431,7 +433,6 @@ func generateInteractive(cmd *cobra.Command, model string) error { usage() continue } - } else { usage() continue @@ -525,6 +526,38 @@ func RunServer(_ *cobra.Command, _ []string) error { return server.Serve(ln) } +func checkServerHeartbeat(_ *cobra.Command, _ []string) error { + client := api.NewClient() + if err := client.Heartbeat(context.Background()); err != nil { + if strings.Contains(err.Error(), "connection refused") { + if runtime.GOOS == "darwin" { + // if the mac app is available, start it + if _, err := os.Stat("/Applications/Ollama.app"); err == nil { + if err := exec.Command("/usr/bin/open", "-a", "/Applications/Ollama.app").Run(); err != nil { + return err + } + // wait for the server to start + timeout := time.After(5 * time.Second) + tick := time.Tick(500 * time.Millisecond) + for { + select { + case <-timeout: + return errors.New("timed out waiting for server to start") + case <-tick: + if err := client.Heartbeat(context.Background()); err == nil { + return nil // server has started + } + } + } + } + } + return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it") + } + return err + } + return nil +} + func NewCLI() *cobra.Command { log.SetFlags(log.LstdFlags | log.Lshortfile) @@ -540,19 +573,21 @@ func NewCLI() *cobra.Command { cobra.EnableCommandSorting = false createCmd := &cobra.Command{ - Use: "create MODEL", - Short: "Create a model from a Modelfile", - Args: cobra.MinimumNArgs(1), - RunE: CreateHandler, + Use: "create MODEL", + Short: "Create a model from a Modelfile", + Args: cobra.MinimumNArgs(1), + PreRunE: checkServerHeartbeat, + RunE: CreateHandler, } createCmd.Flags().StringP("file", "f", "Modelfile", "Name of the Modelfile (default \"Modelfile\")") runCmd := &cobra.Command{ - Use: "run MODEL [PROMPT]", - Short: "Run a model", - Args: cobra.MinimumNArgs(1), - RunE: RunHandler, + Use: "run MODEL [PROMPT]", + Short: "Run a model", + Args: cobra.MinimumNArgs(1), + PreRunE: checkServerHeartbeat, + RunE: RunHandler, } runCmd.Flags().Bool("verbose", false, "Show timings for response") @@ -565,19 +600,21 @@ func NewCLI() *cobra.Command { } pullCmd := &cobra.Command{ - Use: "pull MODEL", - Short: "Pull a model from a registry", - Args: cobra.MinimumNArgs(1), - RunE: PullHandler, + Use: "pull MODEL", + Short: "Pull a model from a registry", + Args: cobra.MinimumNArgs(1), + PreRunE: checkServerHeartbeat, + RunE: PullHandler, } pullCmd.Flags().Bool("insecure", false, "Use an insecure registry") pushCmd := &cobra.Command{ - Use: "push MODEL", - Short: "Push a model to a registry", - Args: cobra.MinimumNArgs(1), - RunE: PushHandler, + Use: "push MODEL", + Short: "Push a model to a registry", + Args: cobra.MinimumNArgs(1), + PreRunE: checkServerHeartbeat, + RunE: PushHandler, } pushCmd.Flags().Bool("insecure", false, "Use an insecure registry") @@ -586,21 +623,24 @@ func NewCLI() *cobra.Command { Use: "list", Aliases: []string{"ls"}, Short: "List models", + PreRunE: checkServerHeartbeat, RunE: ListHandler, } copyCmd := &cobra.Command{ - Use: "cp", - Short: "Copy a model", - Args: cobra.MinimumNArgs(2), - RunE: CopyHandler, + Use: "cp", + Short: "Copy a model", + Args: cobra.MinimumNArgs(2), + PreRunE: checkServerHeartbeat, + RunE: CopyHandler, } deleteCmd := &cobra.Command{ - Use: "rm", - Short: "Remove a model", - Args: cobra.MinimumNArgs(1), - RunE: DeleteHandler, + Use: "rm", + Short: "Remove a model", + Args: cobra.MinimumNArgs(1), + PreRunE: checkServerHeartbeat, + RunE: DeleteHandler, } rootCmd.AddCommand( From 36d6081ed179ad78f3061e44161715c4a7f24154 Mon Sep 17 00:00:00 2001 From: Bruce MacDonald Date: Mon, 31 Jul 2023 17:38:10 -0400 Subject: [PATCH 2/5] find symlink of mac app --- cmd/cmd.go | 53 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index fa06e85b..f1967d00 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -526,34 +526,43 @@ func RunServer(_ *cobra.Command, _ []string) error { return server.Serve(ln) } +func startMacApp(client *api.Client) error { + link, err := os.Readlink("/usr/local/bin/ollama") + if err != nil { + return err + } + path := strings.Split(link, "Ollama.app") + if err := exec.Command("/usr/bin/open", "-a", path[0]+"Ollama.app").Run(); err != nil { + return err + } + // wait for the server to start + timeout := time.After(5 * time.Second) + tick := time.Tick(500 * time.Millisecond) + for { + select { + case <-timeout: + return errors.New("timed out waiting for server to start") + case <-tick: + if err := client.Heartbeat(context.Background()); err == nil { + return nil // server has started + } + } + } +} + func checkServerHeartbeat(_ *cobra.Command, _ []string) error { client := api.NewClient() if err := client.Heartbeat(context.Background()); err != nil { - if strings.Contains(err.Error(), "connection refused") { - if runtime.GOOS == "darwin" { - // if the mac app is available, start it - if _, err := os.Stat("/Applications/Ollama.app"); err == nil { - if err := exec.Command("/usr/bin/open", "-a", "/Applications/Ollama.app").Run(); err != nil { - return err - } - // wait for the server to start - timeout := time.After(5 * time.Second) - tick := time.Tick(500 * time.Millisecond) - for { - select { - case <-timeout: - return errors.New("timed out waiting for server to start") - case <-tick: - if err := client.Heartbeat(context.Background()); err == nil { - return nil // server has started - } - } - } - } + if !strings.Contains(err.Error(), "connection refused") { + return err + } + if runtime.GOOS == "darwin" { + if err := startMacApp(client); err != nil { + return fmt.Errorf("could not connect to ollama app server, run 'ollama serve' to start it") } + } else { return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it") } - return err } return nil } From 40a25bf8c396cd09befc5521cd3d60d1b8acdb01 Mon Sep 17 00:00:00 2001 From: Bruce MacDonald Date: Tue, 1 Aug 2023 13:48:48 -0400 Subject: [PATCH 3/5] pr comments --- cmd/cmd.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index f1967d00..e0df2e75 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -531,6 +531,9 @@ func startMacApp(client *api.Client) error { if err != nil { return err } + if !strings.Contains(link, "Ollama.app") { + return fmt.Errorf("could not find ollama app") + } path := strings.Split(link, "Ollama.app") if err := exec.Command("/usr/bin/open", "-a", path[0]+"Ollama.app").Run(); err != nil { return err @@ -558,7 +561,7 @@ func checkServerHeartbeat(_ *cobra.Command, _ []string) error { } if runtime.GOOS == "darwin" { if err := startMacApp(client); err != nil { - return fmt.Errorf("could not connect to ollama app server, run 'ollama serve' to start it") + return fmt.Errorf("could not connect to ollama app, is it running?") } } else { return fmt.Errorf("could not connect to ollama server, run 'ollama serve' to start it") From 765994362cf2860b9d137d113f23590ba8b2cf16 Mon Sep 17 00:00:00 2001 From: Bruce MacDonald Date: Tue, 1 Aug 2023 14:50:38 -0400 Subject: [PATCH 4/5] use head to check heartbeat --- api/client.go | 2 +- server/routes.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/api/client.go b/api/client.go index ed1053e8..7753eb91 100644 --- a/api/client.go +++ b/api/client.go @@ -225,7 +225,7 @@ func (c *Client) Delete(ctx context.Context, req *DeleteRequest) error { } func (c *Client) Heartbeat(ctx context.Context) error { - if err := c.do(ctx, http.MethodGet, "/", nil, nil); err != nil { + if err := c.do(ctx, http.MethodHead, "/", nil, nil); err != nil { return err } return nil diff --git a/server/routes.go b/server/routes.go index cc9df958..108c0743 100644 --- a/server/routes.go +++ b/server/routes.go @@ -319,6 +319,9 @@ func Serve(ln net.Listener) error { r.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Ollama is running") }) + r.HEAD("/", func(c *gin.Context) { + c.Status(http.StatusOK) + }) r.POST("/api/pull", PullModelHandler) r.POST("/api/generate", GenerateHandler) From 50e87c66911b18bb7ffbf6951ee5e30b50167bc9 Mon Sep 17 00:00:00 2001 From: Bruce MacDonald Date: Tue, 1 Aug 2023 16:01:55 -0400 Subject: [PATCH 5/5] read from os executable --- cmd/cmd.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index e0df2e75..1406cb0e 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -527,7 +527,11 @@ func RunServer(_ *cobra.Command, _ []string) error { } func startMacApp(client *api.Client) error { - link, err := os.Readlink("/usr/local/bin/ollama") + exe, err := os.Executable() + if err != nil { + return err + } + link, err := os.Readlink(exe) if err != nil { return err }