diff --git a/api/client.go b/api/client.go index 6786fa48..7753eb91 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.MethodHead, "/", nil, nil); err != nil { + return err + } + return nil +} diff --git a/cmd/cmd.go b/cmd/cmd.go index 486dd1c8..79cb85a5 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" @@ -425,7 +427,6 @@ func generateInteractive(cmd *cobra.Command, model string) error { usage() continue } - } else { usage() continue @@ -519,6 +520,54 @@ func RunServer(_ *cobra.Command, _ []string) error { return server.Serve(ln) } +func startMacApp(client *api.Client) error { + exe, err := os.Executable() + if err != nil { + return err + } + link, err := os.Readlink(exe) + 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 + } + // 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") { + return err + } + if runtime.GOOS == "darwin" { + if err := startMacApp(client); err != nil { + 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") + } + } + return nil +} + func NewCLI() *cobra.Command { log.SetFlags(log.LstdFlags | log.Lshortfile) @@ -534,19 +583,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") @@ -559,19 +610,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") @@ -580,21 +633,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( diff --git a/server/routes.go b/server/routes.go index a7d25357..09c021b5 100644 --- a/server/routes.go +++ b/server/routes.go @@ -318,6 +318,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)