diff --git a/.goreleaser.yaml b/.goreleaser.yaml index a43785a..dc50cb2 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/goreleaser/goreleaser/refs/heads/main/www/static/schema.json # GoReleaser configuration for ATCR # See https://goreleaser.com for documentation @@ -12,7 +13,8 @@ builds: # Credential helper - cross-platform native binary distribution - id: credential-helper binary: docker-credential-atcr - main: ./cmd/credential-helper + main: . + dir: ./cmd/credential-helper/atcr env: - CGO_ENABLED=0 goos: diff --git a/Makefile b/Makefile index 69cb846..f02526b 100644 --- a/Makefile +++ b/Makefile @@ -51,10 +51,10 @@ build-hold: $(GENERATED_ASSETS) ## Build hold binary only @mkdir -p bin go build -o bin/atcr-hold ./cmd/hold -build-credential-helper: ## Build credential helper only +build-credential-helper: ## Build credential helper only (atcr brand) @echo "→ Building credential helper..." @mkdir -p bin - go build -o bin/docker-credential-atcr ./cmd/credential-helper + cd cmd/credential-helper/atcr && go build -ldflags="-X main.version=$(shell git describe --tags --always 2>/dev/null || echo dev) -X main.commit=$(shell git rev-parse HEAD 2>/dev/null || echo none)" -o ../../../bin/docker-credential-atcr . build-oauth-helper: ## Build OAuth helper only @echo "→ Building OAuth helper..." @@ -84,7 +84,7 @@ build-trixie: $(GENERATED_ASSETS) ## Build all production binaries (appview, hol set -e && \ go build -trimpath -tags billing -ldflags="-s -w $(APPVIEW_LDFLAGS)" -o bin/atcr-appview ./cmd/appview && \ go build -trimpath -ldflags="-s -w" -o bin/atcr-hold ./cmd/hold && \ - go build -trimpath -ldflags="-s -w" -o bin/docker-credential-atcr ./cmd/credential-helper && \ + (cd cmd/credential-helper/atcr && go build -trimpath -ldflags="-s -w" -o ../../../bin/docker-credential-atcr .) && \ go build -trimpath -ldflags="-s -w" -o bin/atcr-labeler ./cmd/labeler && \ cd scanner && go build -trimpath -ldflags="-s -w" -o ../bin/atcr-scanner ./cmd/scanner' @echo "✓ Built to bin/ (glibc ≥ 2.41 compatible)" diff --git a/cmd/credential-helper/atcr/go.mod b/cmd/credential-helper/atcr/go.mod new file mode 100644 index 0000000..f272a53 --- /dev/null +++ b/cmd/credential-helper/atcr/go.mod @@ -0,0 +1,10 @@ +module atcr.io/cmd/credential-helper/atcr + +go 1.26.2 + +// atcr.io is provided by the workspace during local development (go.work +// at the repo root). The require line below is what `go install +// atcr.io/cmd/credential-helper/atcr@latest` resolves against when the +// module is fetched standalone via the proxy. Bump it when cutting a +// new credhelper release. +require atcr.io v0.1.4 diff --git a/cmd/credential-helper/atcr/go.sum b/cmd/credential-helper/atcr/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/cmd/credential-helper/atcr/main.go b/cmd/credential-helper/atcr/main.go new file mode 100644 index 0000000..4f500dc --- /dev/null +++ b/cmd/credential-helper/atcr/main.go @@ -0,0 +1,28 @@ +// docker-credential-atcr is the Docker credential helper for atcr.io. +// +// Thin main that delegates to atcr.io/pkg/credhelper. The seamark.dev sibling +// at ../seamark mirrors this with brand-specific Config values. +package main + +import "atcr.io/pkg/credhelper" + +// Stamped at link time via -ldflags by the Makefile and goreleaser. +var ( + version = "dev" + commit = "none" + date = "unknown" +) + +func main() { + credhelper.Run(credhelper.Config{ + BinaryName: "docker-credential-atcr", + DefaultRegistry: "atcr.io", + ConfigDirName: ".atcr", + SecretPrefix: "atcr_device_", + UpdateAssetName: "docker-credential-atcr", + ReleasesBaseURL: "https://tangled.org/did:plc:e3kzdezk5gsirzh7eoqplc64", + Version: version, + Commit: commit, + Date: date, + }) +} diff --git a/cmd/credential-helper/main.go b/cmd/credential-helper/main.go deleted file mode 100644 index 6afeb04..0000000 --- a/cmd/credential-helper/main.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "fmt" - "os" - "time" - - "github.com/spf13/cobra" -) - -var ( - version = "dev" - commit = "none" - date = "unknown" - - // Update check cache TTL (24 hours) - updateCheckCacheTTL = 24 * time.Hour -) - -// timeNow is a variable so tests can override it. -var timeNow = time.Now - -func main() { - rootCmd := &cobra.Command{ - Use: "docker-credential-atcr", - Short: "ATCR container registry credential helper", - Long: `docker-credential-atcr manages authentication for ATCR-compatible container registries. - -It implements the Docker credential helper protocol and provides commands -for managing multiple accounts across multiple registries.`, - Version: fmt.Sprintf("%s (commit: %s, built: %s)", version, commit, date), - SilenceUsage: true, - SilenceErrors: true, - } - - // Docker protocol commands (hidden — called by Docker, not users) - rootCmd.AddCommand(newGetCmd()) - rootCmd.AddCommand(newStoreCmd()) - rootCmd.AddCommand(newEraseCmd()) - rootCmd.AddCommand(newListCmd()) - - // User-facing commands - rootCmd.AddCommand(newLoginCmd()) - rootCmd.AddCommand(newLogoutCmd()) - rootCmd.AddCommand(newStatusCmd()) - rootCmd.AddCommand(newSwitchCmd()) - rootCmd.AddCommand(newConfigureDockerCmd()) - rootCmd.AddCommand(newUpdateCmd()) - - if err := rootCmd.Execute(); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} diff --git a/cmd/credential-helper/seamark/go.mod b/cmd/credential-helper/seamark/go.mod new file mode 100644 index 0000000..89f8008 --- /dev/null +++ b/cmd/credential-helper/seamark/go.mod @@ -0,0 +1,10 @@ +module seamark.dev/cmd/credential-helper/seamark + +go 1.26.2 + +// atcr.io is provided by the workspace during local development (go.work +// at the repo root). The require line below is what `go install +// seamark.dev/cmd/credential-helper/seamark@latest` resolves against when +// the module is fetched standalone via the proxy. Bump it when cutting a +// new credhelper release. +require atcr.io v0.1.4 diff --git a/cmd/credential-helper/seamark/go.sum b/cmd/credential-helper/seamark/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/cmd/credential-helper/seamark/main.go b/cmd/credential-helper/seamark/main.go new file mode 100644 index 0000000..1d942eb --- /dev/null +++ b/cmd/credential-helper/seamark/main.go @@ -0,0 +1,28 @@ +// docker-credential-seamark is the Docker credential helper for seamark.dev. +// +// Thin main that delegates to atcr.io/pkg/credhelper. The atcr.io sibling +// at ../atcr mirrors this with brand-specific Config values. +package main + +import "atcr.io/pkg/credhelper" + +// Stamped at link time via -ldflags by the Makefile and goreleaser. +var ( + version = "dev" + commit = "none" + date = "unknown" +) + +func main() { + credhelper.Run(credhelper.Config{ + BinaryName: "docker-credential-seamark", + DefaultRegistry: "seamark.cr", + ConfigDirName: ".seamark", + SecretPrefix: "seamark_device_", + UpdateAssetName: "docker-credential-seamark", + ReleasesBaseURL: "https://tangled.org/did:plc:e3kzdezk5gsirzh7eoqplc64", + Version: version, + Commit: commit, + Date: date, + }) +} diff --git a/go.mod b/go.mod index 5dbe974..d7a47a7 100644 --- a/go.mod +++ b/go.mod @@ -184,6 +184,7 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect + go.etcd.io/bbolt v1.4.3 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.68.0 // indirect diff --git a/go.sum b/go.sum index fd2db9a..f0f4f42 100644 --- a/go.sum +++ b/go.sum @@ -732,8 +732,8 @@ gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRyS gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= -go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= +go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= diff --git a/go.work b/go.work index c525ec6..f7e4985 100644 --- a/go.work +++ b/go.work @@ -2,6 +2,8 @@ go 1.26.2 use ( . + ./cmd/credential-helper/atcr + ./cmd/credential-helper/seamark ./deploy/upcloud ./scanner ) diff --git a/pkg/appview/middleware/goimport.go b/pkg/appview/middleware/goimport.go index 1815058..b348ad4 100644 --- a/pkg/appview/middleware/goimport.go +++ b/pkg/appview/middleware/goimport.go @@ -4,6 +4,7 @@ import ( "fmt" "html" "net/http" + "strings" ) // GoImport serves the `` tag required by `go install` / @@ -17,6 +18,12 @@ import ( // The meta tag must be present on every subpath under the module root, so this // runs as middleware at the top of the chain and short-circuits any request // carrying `?go-get=1`. +// +// For non-go-get requests on paths that look like Go module subpaths +// (/cmd/, /pkg/, /internal/, /scanner/), the middleware redirects to the +// corresponding source-tree URL in the git host so a browser visit doesn't +// 404. The redirect template mirrors the `go-source` meta tag's +// `{repoURL}/tree/main{/dir}` form. func GoImport(modulePath, repoURL string) func(http.Handler) http.Handler { body := fmt.Sprintf( `
go get %s`, @@ -31,13 +38,36 @@ func GoImport(modulePath, repoURL string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Query().Get("go-get") != "1" { - next.ServeHTTP(w, r) + if r.URL.Query().Get("go-get") == "1" { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Header().Set("Cache-Control", "public, max-age=300") + _, _ = w.Write([]byte(body)) return } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.Header().Set("Cache-Control", "public, max-age=300") - _, _ = w.Write([]byte(body)) + + // Browser visits to recognizable Go subpaths get redirected to + // the repo source tree instead of falling through to the + // appview router (which 404s for these paths). + if isGoModuleSubpath(r.URL.Path) { + target := repoURL + "/tree/main" + r.URL.Path + http.Redirect(w, r, target, http.StatusFound) + return + } + + next.ServeHTTP(w, r) }) } } + +// isGoModuleSubpath reports whether p looks like a Go module source path +// (under cmd/, pkg/, internal/, or scanner/). The check is intentionally +// narrow so the redirect doesn't hijack other appview routes. +func isGoModuleSubpath(p string) bool { + switch { + case strings.HasPrefix(p, "/cmd/"), + strings.HasPrefix(p, "/pkg/"), + strings.HasPrefix(p, "/internal/"): + return true + } + return false +} diff --git a/cmd/credential-helper/cmd_configure.go b/pkg/credhelper/cmd_configure.go similarity index 91% rename from cmd/credential-helper/cmd_configure.go rename to pkg/credhelper/cmd_configure.go index 7b44e56..c690128 100644 --- a/cmd/credential-helper/cmd_configure.go +++ b/pkg/credhelper/cmd_configure.go @@ -1,4 +1,4 @@ -package main +package credhelper import ( "encoding/json" @@ -21,20 +21,20 @@ func newConfigureDockerCmd() *cobra.Command { } func runConfigureDocker(cmd *cobra.Command, args []string) error { - cfg, err := loadConfig() + sc, err := loadConfig() if err != nil { return fmt.Errorf("loading config: %w", err) } - if len(cfg.Registries) == 0 { + if len(sc.Registries) == 0 { fmt.Fprintf(os.Stderr, "No registries configured.\n") - fmt.Fprintf(os.Stderr, "Run: docker-credential-atcr login\n") + fmt.Fprintf(os.Stderr, "Run: %s login\n", cfg.BinaryName) return nil } // Collect registry hosts var hosts []string - for url := range cfg.Registries { + for url := range sc.Registries { host := strings.TrimPrefix(url, "https://") host = strings.TrimPrefix(host, "http://") hosts = append(hosts, host) @@ -58,11 +58,13 @@ func runConfigureDocker(cmd *cobra.Command, args []string) error { helpersMap = make(map[string]any) } + helper := helperName(cfg) + // Check what needs to change var toAdd []string for _, host := range hosts { current, exists := helpersMap[host] - if !exists || current != "atcr" { + if !exists || current != helper { toAdd = append(toAdd, host) } } @@ -74,7 +76,7 @@ func runConfigureDocker(cmd *cobra.Command, args []string) error { fmt.Printf("Will update %s:\n", dockerConfigPath) for _, host := range toAdd { - fmt.Printf(" + credHelpers[%q] = \"atcr\"\n", host) + fmt.Printf(" + credHelpers[%q] = %q\n", host, helper) } fmt.Println() @@ -90,7 +92,7 @@ func runConfigureDocker(cmd *cobra.Command, args []string) error { // Apply changes for _, host := range toAdd { - helpersMap[host] = "atcr" + helpersMap[host] = helper } dockerCfg["credHelpers"] = helpersMap diff --git a/cmd/credential-helper/cmd_login.go b/pkg/credhelper/cmd_login.go similarity index 93% rename from cmd/credential-helper/cmd_login.go rename to pkg/credhelper/cmd_login.go index 377f0d5..d54560b 100644 --- a/cmd/credential-helper/cmd_login.go +++ b/pkg/credhelper/cmd_login.go @@ -1,4 +1,4 @@ -package main +package credhelper import ( "bufio" @@ -12,31 +12,30 @@ import ( ) func newLoginCmd() *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ Use: "login [registry]", Short: "Authenticate with a container registry", - Long: "Starts a device authorization flow to authenticate with a registry.\nDefault registry: atcr.io", + Long: "Starts a device authorization flow to authenticate with a registry.\nDefault registry: " + cfg.DefaultRegistry, Args: cobra.MaximumNArgs(1), RunE: runLogin, } - return cmd } func runLogin(cmd *cobra.Command, args []string) error { - serverURL := "atcr.io" + serverURL := cfg.DefaultRegistry if len(args) > 0 { serverURL = args[0] } appViewURL := buildAppViewURL(serverURL) - cfg, err := loadConfig() + sc, err := loadConfig() if err != nil { fmt.Fprintf(os.Stderr, "Warning: config load error: %v\n", err) } // Check if already logged in - reg := cfg.findRegistry(appViewURL) + reg := sc.findRegistry(appViewURL) if reg != nil && len(reg.Accounts) > 0 { var lines []string for _, acct := range reg.Accounts { @@ -102,8 +101,8 @@ func runLogin(cmd *cobra.Command, args []string) error { logSuccess("Authentication complete.") // 4. Save - cfg.addAccount(resolvedURL, acct) - if err := cfg.save(); err != nil { + sc.addAccount(resolvedURL, acct) + if err := sc.save(); err != nil { return fmt.Errorf("saving config: %w", err) } @@ -174,7 +173,7 @@ func configureDockerForRegistry(serverURL string) error { helpersMap = make(map[string]any) } - helpersMap[host] = "atcr" + helpersMap[host] = helperName(cfg) dockerCfg["credHelpers"] = helpersMap return saveDockerConfig(dockerConfigPath, dockerCfg) diff --git a/cmd/credential-helper/cmd_logout.go b/pkg/credhelper/cmd_logout.go similarity index 88% rename from cmd/credential-helper/cmd_logout.go rename to pkg/credhelper/cmd_logout.go index 1bf4349..e6b7960 100644 --- a/cmd/credential-helper/cmd_logout.go +++ b/pkg/credhelper/cmd_logout.go @@ -1,4 +1,4 @@ -package main +package credhelper import ( "fmt" @@ -13,26 +13,26 @@ func newLogoutCmd() *cobra.Command { return &cobra.Command{ Use: "logout [registry]", Short: "Remove account credentials", - Long: "Remove stored credentials for an account.\nDefault registry: atcr.io", + Long: "Remove stored credentials for an account.\nDefault registry: " + cfg.DefaultRegistry, Args: cobra.MaximumNArgs(1), RunE: runLogout, } } func runLogout(cmd *cobra.Command, args []string) error { - serverURL := "atcr.io" + serverURL := cfg.DefaultRegistry if len(args) > 0 { serverURL = args[0] } appViewURL := buildAppViewURL(serverURL) - cfg, err := loadConfig() + sc, err := loadConfig() if err != nil { return fmt.Errorf("loading config: %w", err) } - reg := cfg.findRegistry(appViewURL) + reg := sc.findRegistry(appViewURL) if reg == nil || len(reg.Accounts) == 0 { fmt.Fprintf(os.Stderr, "No accounts configured for %s.\n", serverURL) return nil @@ -83,8 +83,8 @@ func runLogout(cmd *cobra.Command, args []string) error { return nil } - cfg.removeAccount(appViewURL, handle) - if err := cfg.save(); err != nil { + sc.removeAccount(appViewURL, handle) + if err := sc.save(); err != nil { return fmt.Errorf("saving config: %w", err) } diff --git a/cmd/credential-helper/cmd_status.go b/pkg/credhelper/cmd_status.go similarity index 83% rename from cmd/credential-helper/cmd_status.go rename to pkg/credhelper/cmd_status.go index 02a8702..c39320e 100644 --- a/cmd/credential-helper/cmd_status.go +++ b/pkg/credhelper/cmd_status.go @@ -1,4 +1,4 @@ -package main +package credhelper import ( "fmt" @@ -17,26 +17,26 @@ func newStatusCmd() *cobra.Command { } func runStatus(cmd *cobra.Command, args []string) error { - cfg, err := loadConfig() + sc, err := loadConfig() if err != nil { return fmt.Errorf("loading config: %w", err) } - if len(cfg.Registries) == 0 { + if len(sc.Registries) == 0 { fmt.Fprintf(os.Stderr, "No accounts configured.\n") - fmt.Fprintf(os.Stderr, "Run: docker-credential-atcr login\n") + fmt.Fprintf(os.Stderr, "Run: %s login\n", cfg.BinaryName) return nil } // Sort registry URLs for stable output var urls []string - for url := range cfg.Registries { + for url := range sc.Registries { urls = append(urls, url) } sort.Strings(urls) for _, url := range urls { - reg := cfg.Registries[url] + reg := sc.Registries[url] fmt.Printf("%s\n", url) // Sort handles for stable output diff --git a/cmd/credential-helper/cmd_switch.go b/pkg/credhelper/cmd_switch.go similarity index 86% rename from cmd/credential-helper/cmd_switch.go rename to pkg/credhelper/cmd_switch.go index 9f289ca..34b2abd 100644 --- a/cmd/credential-helper/cmd_switch.go +++ b/pkg/credhelper/cmd_switch.go @@ -1,4 +1,4 @@ -package main +package credhelper import ( "fmt" @@ -13,29 +13,29 @@ func newSwitchCmd() *cobra.Command { return &cobra.Command{ Use: "switch [registry]", Short: "Switch the active account for a registry", - Long: "Switch the active account used for Docker operations.\nDefault registry: atcr.io", + Long: "Switch the active account used for Docker operations.\nDefault registry: " + cfg.DefaultRegistry, Args: cobra.MaximumNArgs(1), RunE: runSwitch, } } func runSwitch(cmd *cobra.Command, args []string) error { - serverURL := "atcr.io" + serverURL := cfg.DefaultRegistry if len(args) > 0 { serverURL = args[0] } appViewURL := buildAppViewURL(serverURL) - cfg, err := loadConfig() + sc, err := loadConfig() if err != nil { return fmt.Errorf("loading config: %w", err) } - reg := cfg.findRegistry(appViewURL) + reg := sc.findRegistry(appViewURL) if reg == nil || len(reg.Accounts) == 0 { fmt.Fprintf(os.Stderr, "No accounts configured for %s.\n", serverURL) - fmt.Fprintf(os.Stderr, "Run: docker-credential-atcr login\n") + fmt.Fprintf(os.Stderr, "Run: %s login\n", cfg.BinaryName) return nil } @@ -51,7 +51,7 @@ func runSwitch(cmd *cobra.Command, args []string) error { for h := range reg.Accounts { if h != reg.Active { reg.Active = h - if err := cfg.save(); err != nil { + if err := sc.save(); err != nil { return fmt.Errorf("saving config: %w", err) } fmt.Printf("Switched to %s on %s\n", h, serverURL) @@ -87,7 +87,7 @@ func runSwitch(cmd *cobra.Command, args []string) error { } reg.Active = selected - if err := cfg.save(); err != nil { + if err := sc.save(); err != nil { return fmt.Errorf("saving config: %w", err) } diff --git a/cmd/credential-helper/cmd_update.go b/pkg/credhelper/cmd_update.go similarity index 86% rename from cmd/credential-helper/cmd_update.go rename to pkg/credhelper/cmd_update.go index 07d003a..695107f 100644 --- a/cmd/credential-helper/cmd_update.go +++ b/pkg/credhelper/cmd_update.go @@ -1,4 +1,4 @@ -package main +package credhelper import ( "fmt" @@ -16,11 +16,6 @@ import ( "github.com/spf13/cobra" ) -// tangledReleasesBase is the tangled.org path for the credential-helper's -// release repository. /tags/latest issues a 302 redirect to the latest tag, -// and /tags/{version}/download/{filename} serves goreleaser artifacts directly. -const tangledReleasesBase = "https://tangled.org/did:plc:e3kzdezk5gsirzh7eoqplc64" - func newUpdateCmd() *cobra.Command { cmd := &cobra.Command{ Use: "update", @@ -39,12 +34,12 @@ func runUpdate(cmd *cobra.Command, args []string) error { return fmt.Errorf("checking for updates: %w", err) } - if !isNewerVersion(latest, version) { - fmt.Printf("You're already running the latest version (%s)\n", version) + if !isNewerVersion(latest, cfg.Version) { + fmt.Printf("You're already running the latest version (%s)\n", cfg.Version) return nil } - fmt.Printf("New version available: %s (current: %s)\n", latest, version) + fmt.Printf("New version available: %s (current: %s)\n", latest, cfg.Version) if checkOnly { return nil @@ -59,13 +54,13 @@ func runUpdate(cmd *cobra.Command, args []string) error { } // fetchLatestVersion resolves the latest released version by following the -// {tangledReleasesBase}/tags/latest redirect chain. Tangled redirects +// {ReleasesBaseURL}/tags/latest redirect chain. Tangled redirects // DID→handle first, then handle/tags/latest→handle/tags/vX.Y.Z, so we follow // the chain and read the tag from the final effective URL. func fetchLatestVersion() (string, error) { client := httpClientWithTimeout(10*time.Second, nil) - resp, err := client.Get(tangledReleasesBase + "/tags/latest") + resp, err := client.Get(cfg.ReleasesBaseURL + "/tags/latest") if err != nil { return "", fmt.Errorf("fetching latest tag: %w", err) } @@ -115,7 +110,7 @@ func isNewerVersion(newVersion, currentVersion string) bool { // goreleaserArchiveName returns the archive filename goreleaser publishes for // the given version and the current platform. The naming template lives in -// .goreleaser.yaml: docker-credential-atcr_{Version}_{Title(OS)}_{Arch} with +// .goreleaser.yaml: