diff --git a/api/client.go b/api/client.go index a83fdc7d..8386f290 100644 --- a/api/client.go +++ b/api/client.go @@ -10,7 +10,10 @@ import ( "net/http" "net/url" "os" + "runtime" "strings" + + "github.com/jmorganca/ollama/version" ) const DefaultHost = "localhost:11434" @@ -83,21 +86,21 @@ func (c *Client) do(ctx context.Context, method, path string, reqData, respData reqBody = bytes.NewReader(data) } - url := c.Base.JoinPath(path).String() - - req, err := http.NewRequestWithContext(ctx, method, url, reqBody) + requestURL := c.Base.JoinPath(path) + request, err := http.NewRequestWithContext(ctx, method, requestURL.String(), reqBody) if err != nil { return err } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Accept", "application/json") + request.Header.Set("User-Agent", fmt.Sprintf("ollama/%s (%s %s) Go/%s", version.Version, runtime.GOARCH, runtime.GOOS, runtime.Version())) for k, v := range c.Headers { - req.Header[k] = v + request.Header[k] = v } - respObj, err := c.HTTP.Do(req) + respObj, err := c.HTTP.Do(request) if err != nil { return err } @@ -131,13 +134,15 @@ func (c *Client) stream(ctx context.Context, method, path string, data any, fn f buf = bytes.NewBuffer(bts) } - request, err := http.NewRequestWithContext(ctx, method, c.Base.JoinPath(path).String(), buf) + requestURL := c.Base.JoinPath(path) + request, err := http.NewRequestWithContext(ctx, method, requestURL.String(), buf) if err != nil { return err } request.Header.Set("Content-Type", "application/json") request.Header.Set("Accept", "application/json") + request.Header.Set("User-Agent", fmt.Sprintf("ollama/%s (%s %s) Go/%s", version.Version, runtime.GOARCH, runtime.GOOS, runtime.Version())) response, err := http.DefaultClient.Do(request) if err != nil { diff --git a/cmd/cmd.go b/cmd/cmd.go index 09fb2e92..eab7bad8 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -30,6 +30,7 @@ import ( "github.com/jmorganca/ollama/format" "github.com/jmorganca/ollama/progressbar" "github.com/jmorganca/ollama/server" + "github.com/jmorganca/ollama/version" ) func CreateHandler(cmd *cobra.Command, args []string) error { @@ -731,6 +732,7 @@ func NewCLI() *cobra.Command { CompletionOptions: cobra.CompletionOptions{ DisableDefaultCmd: true, }, + Version: version.Version, } cobra.EnableCommandSorting = false diff --git a/scripts/build_darwin.sh b/scripts/build_darwin.sh index fcc06276..3144349b 100755 --- a/scripts/build_darwin.sh +++ b/scripts/build_darwin.sh @@ -2,9 +2,11 @@ mkdir -p dist +GO_LDFLAGS="-X github.com/jmorganca/ollama/version.Version=$VERSION" + # build universal binary -CGO_ENABLED=1 GOARCH=arm64 go build -o dist/ollama-darwin-arm64 -CGO_ENABLED=1 GOARCH=amd64 go build -o dist/ollama-darwin-amd64 +CGO_ENABLED=1 GOARCH=arm64 go build -ldflags "$GO_LDFLAGS" -o dist/ollama-darwin-arm64 +CGO_ENABLED=1 GOARCH=amd64 go build -ldflags "$GO_LDFLAGS" -o dist/ollama-darwin-amd64 lipo -create -output dist/ollama dist/ollama-darwin-arm64 dist/ollama-darwin-amd64 rm dist/ollama-darwin-amd64 dist/ollama-darwin-arm64 codesign --deep --force --options=runtime --sign "$APPLE_IDENTITY" --timestamp dist/ollama diff --git a/server/auth.go b/server/auth.go index 87b1194b..80982a43 100644 --- a/server/auth.go +++ b/server/auth.go @@ -94,10 +94,8 @@ func getAuthToken(ctx context.Context, redirData AuthRedirect, regOpts *Registry return "", err } - headers := map[string]string{ - "Authorization": sig, - } - + headers := make(http.Header) + headers.Set("Authorization", sig) resp, err := makeRequest(ctx, "GET", url, headers, nil, regOpts) if err != nil { log.Printf("couldn't get token: %q", err) diff --git a/server/download.go b/server/download.go index 246d300d..ca3427d0 100644 --- a/server/download.go +++ b/server/download.go @@ -156,9 +156,9 @@ func doDownload(ctx context.Context, opts downloadOpts, f *FileDownload) error { } url := fmt.Sprintf("%s/v2/%s/blobs/%s", opts.mp.Registry, opts.mp.GetNamespaceRepository(), f.Digest) - headers := map[string]string{ - "Range": fmt.Sprintf("bytes=%d-", size), - } + + headers := make(http.Header) + headers.Set("Range", fmt.Sprintf("bytes=%d-", size)) resp, err := makeRequest(ctx, "GET", url, headers, nil, opts.regOpts) if err != nil { diff --git a/server/images.go b/server/images.go index 441c54ea..4c93660d 100644 --- a/server/images.go +++ b/server/images.go @@ -16,6 +16,7 @@ import ( "path" "path/filepath" "reflect" + "runtime" "strconv" "strings" @@ -23,6 +24,7 @@ import ( "github.com/jmorganca/ollama/llm" "github.com/jmorganca/ollama/parser" "github.com/jmorganca/ollama/vector" + "github.com/jmorganca/ollama/version" ) const MaxRetries = 3 @@ -978,15 +980,14 @@ func PushModel(ctx context.Context, name string, regOpts *RegistryOptions, fn fu fn(api.ProgressResponse{Status: "pushing manifest"}) url := fmt.Sprintf("%s/v2/%s/manifests/%s", mp.Registry, mp.GetNamespaceRepository(), mp.Tag) - headers := map[string]string{ - "Content-Type": "application/vnd.docker.distribution.manifest.v2+json", - } manifestJSON, err := json.Marshal(manifest) if err != nil { return err } + headers := make(http.Header) + headers.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json") resp, err := makeRequestWithRetry(ctx, "PUT", url, headers, bytes.NewReader(manifestJSON), regOpts) if err != nil { return err @@ -1072,10 +1073,9 @@ func PullModel(ctx context.Context, name string, regOpts *RegistryOptions, fn fu func pullModelManifest(ctx context.Context, mp ModelPath, regOpts *RegistryOptions) (*ManifestV2, error) { url := fmt.Sprintf("%s/v2/%s/manifests/%s", mp.Registry, mp.GetNamespaceRepository(), mp.Tag) - headers := map[string]string{ - "Accept": "application/vnd.docker.distribution.manifest.v2+json", - } + headers := make(http.Header) + headers.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json") resp, err := makeRequest(ctx, "GET", url, headers, nil, regOpts) if err != nil { log.Printf("couldn't get manifest: %v", err) @@ -1200,11 +1200,10 @@ func uploadBlobChunked(ctx context.Context, mp ModelPath, url string, layer *Lay sectionReader := io.NewSectionReader(f, int64(completed), chunk) - headers := make(map[string]string) - headers["Content-Type"] = "application/octet-stream" - headers["Content-Length"] = strconv.Itoa(int(chunk)) - headers["Content-Range"] = fmt.Sprintf("%d-%d", completed, completed+sectionReader.Size()-1) - + headers := make(http.Header) + headers.Set("Content-Type", "application/octet-stream") + headers.Set("Content-Length", strconv.Itoa(int(chunk))) + headers.Set("Content-Range", fmt.Sprintf("%d-%d", completed, completed+sectionReader.Size()-1)) resp, err := makeRequestWithRetry(ctx, "PATCH", url, headers, sectionReader, regOpts) if err != nil && !errors.Is(err, io.EOF) { fn(api.ProgressResponse{ @@ -1234,9 +1233,9 @@ func uploadBlobChunked(ctx context.Context, mp ModelPath, url string, layer *Lay url = fmt.Sprintf("%s&digest=%s", url, layer.Digest) - headers := make(map[string]string) - headers["Content-Type"] = "application/octet-stream" - headers["Content-Length"] = "0" + headers := make(http.Header) + headers.Set("Content-Type", "application/octet-stream") + headers.Set("Content-Length", "0") // finish the upload resp, err := makeRequest(ctx, "PUT", url, headers, nil, regOpts) @@ -1253,7 +1252,7 @@ func uploadBlobChunked(ctx context.Context, mp ModelPath, url string, layer *Lay return nil } -func makeRequestWithRetry(ctx context.Context, method, url string, headers map[string]string, body io.ReadSeeker, regOpts *RegistryOptions) (*http.Response, error) { +func makeRequestWithRetry(ctx context.Context, method, url string, headers http.Header, body io.ReadSeeker, regOpts *RegistryOptions) (*http.Response, error) { var status string for try := 0; try < MaxRetries; try++ { resp, err := makeRequest(ctx, method, url, headers, body, regOpts) @@ -1292,7 +1291,7 @@ func makeRequestWithRetry(ctx context.Context, method, url string, headers map[s return nil, fmt.Errorf("max retry exceeded: %v", status) } -func makeRequest(ctx context.Context, method, url string, headers map[string]string, body io.Reader, regOpts *RegistryOptions) (*http.Response, error) { +func makeRequest(ctx context.Context, method, url string, headers http.Header, body io.Reader, regOpts *RegistryOptions) (*http.Response, error) { if !strings.HasPrefix(url, "http") { if regOpts.Insecure { url = "http://" + url @@ -1306,15 +1305,17 @@ func makeRequest(ctx context.Context, method, url string, headers map[string]str return nil, err } + if headers != nil { + req.Header = headers + } + if regOpts.Token != "" { req.Header.Set("Authorization", "Bearer "+regOpts.Token) } else if regOpts.Username != "" && regOpts.Password != "" { req.SetBasicAuth(regOpts.Username, regOpts.Password) } - for k, v := range headers { - req.Header.Set(k, v) - } + req.Header.Set("User-Agent", fmt.Sprintf("ollama/%s (%s %s) Go/%s", version.Version, runtime.GOARCH, runtime.GOOS, runtime.Version())) client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { diff --git a/version/version.go b/version/version.go new file mode 100644 index 00000000..820e2f76 --- /dev/null +++ b/version/version.go @@ -0,0 +1,3 @@ +package version + +var Version string = "0.0.0"