@cryptotaxi247 / kubo / commits / fbea69965

feat: derive AgentSuffix from build origin (#11341)

Fork builds previously announced as plain `kubo/<ver>/<commit>`, indistinguishable from upstream in ecosystem dashboards. When `Version.AgentSuffix` and `--agent-version-suffix` are both unset, kubo now derives a default from the build origin so fork traffic self-identifies in the swarm. - mk/git.mk, cmd/ipfs/Rules.mk: normalize `git remote get-url origin` to `host/org/repo` and inject as `buildOrigin` ldflag - version.go: ImplicitAgentSuffix prefers buildOrigin, falls back to debug.ReadBuildInfo Main.Path; suffixFromForkPath strips known forges (github, gitlab, codeberg, bitbucket) and trailing `/kubo` - cmd/ipfs/kubo/daemon.go: use as fallback when explicit values empty - AGENTS.md: state builds must use `make build` so ldflags are set - docs/config.md: document the implicit-suffix behavior Co-authored-by: Guillaume Michel <guillaumemichel@users.noreply.github.com>

Marcin Rataj committed Jun 2, 2026 at 15:24 UTC fbea699654d16b736de3768eb42ab444778d71ee
7 files changed +121 -4
AGENTS.md
+2
@@ -79,6 +79,8 @@ make install # install to $GOPATH/bin
79 make -O test_go_lint # run linter (use this instead of golangci-lint directly)
80 ```
81
82 +**Always build with `make build`, never `go build`.** The Makefile injects required `-ldflags` for `CurrentCommit`, `taggedRelease`, and `buildOrigin`.
83 +
84 If you modify `go.mod` (add/remove/update dependencies), you must run `make mod_tidy` first, before building or testing. Use `make mod_tidy` instead of `go mod tidy` directly, as the project has multiple `go.mod` files.
85
86 If you modify any `.go` files outside of `test/`, you must run `make build` before running integration tests.
cmd/ipfs/Rules.mk
+1 -1
@@ -12,7 +12,7 @@ PATH := $(realpath $(d)):$(PATH)
12 # DEPS_OO_$(d) += merkledag/pb/merkledag.pb.go namesys/pb/namesys.pb.go
13 # DEPS_OO_$(d) += pin/internal/pb/header.pb.go unixfs/pb/unixfs.pb.go
14
15 -$(d)_flags =-ldflags="-X "github.com/ipfs/kubo".CurrentCommit=$(git-hash) -X "github.com/ipfs/kubo".taggedRelease=$(git-tag)"
15 +$(d)_flags =-ldflags="-X "github.com/ipfs/kubo".CurrentCommit=$(git-hash) -X "github.com/ipfs/kubo".taggedRelease=$(git-tag) -X "github.com/ipfs/kubo".buildOrigin=$(git-origin)"
16
17 $(IPFS_BIN_$(d)): GOFLAGS += $(cmd/ipfs_flags)
18
cmd/ipfs/kubo/daemon.go
+5 -1
@@ -520,9 +520,13 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment
520 return fmt.Errorf("unrecognized routing option: %s", routingOption)
521 }
522
523 - // Set optional agent version suffix
523 + // Resolve agent version suffix:
524 + // Version.AgentSuffix > --agent-version-suffix > implicit (build origin).
525 versionSuffixFromCli, _ := req.Options[agentVersionSuffix].(string)
526 versionSuffix := cfg.Version.AgentSuffix.WithDefault(versionSuffixFromCli)
527 + if versionSuffix == "" {
528 + versionSuffix = version.ImplicitAgentSuffix()
529 + }
530 if versionSuffix != "" {
531 version.SetUserAgentSuffix(versionSuffix)
532 }
docs/config.md
+2 -2
@@ -4253,12 +4253,12 @@ other peers version for detecting when there is time to update.
4253
4254 Optional suffix to the AgentVersion presented by `ipfs id` and exposed via [libp2p identify protocol](https://github.com/libp2p/specs/blob/master/identify/README.md#agentversion).
4255
4256 -The value from config takes precedence over value passed via `ipfs daemon --agent-version-suffix`.
4256 +The value from config takes precedence over value passed via `ipfs daemon --agent-version-suffix`. When both are empty, kubo derives an implicit suffix from the build origin (`git remote get-url origin`, or `debug.ReadBuildInfo` for `go install` builds), stripping public forge hostnames so a fork hosted at `github.com/myorg/kubo` becomes `myorg`. Set this option to override the implicit value.
4257
4258 > [!NOTE]
4259 > Setting a custom version suffix helps with ecosystem analysis, such as Amino DHT reports published at <https://stats.ipfs.network>
4260
4261 -Default: `""` (no suffix, or value from `ipfs daemon --agent-version-suffix=`)
4261 +Default: implicit suffix from build origin, or `""` for upstream builds and when `ipfs daemon --agent-version-suffix=` is empty.
4262
4263 Type: `optionalString`
4264
mk/git.mk
+9
@@ -10,3 +10,12 @@ ifeq ($(findstring dirty,$(git-hash)),)
10 else
11 git-tag:=
12 endif
13 +
14 +# Normalize `origin` to `host/org/repo` for runtime fork detection via
15 +# Version.AgentSuffix. Handles ssh and https forms, strips `.git`, drops
16 +# userinfo. Empty when no git, no `origin`, or git is unavailable.
17 +git-origin:=$(shell git remote get-url origin 2>/dev/null \
18 + | sed -E -e 's|^git@([^:]+):|\1/|' \
19 + -e 's|^[a-z]+://||' \
20 + -e 's|^[^/]+@||' \
21 + -e 's|\.git$$||')
version.go
+57
@@ -3,6 +3,8 @@ package ipfs
3 import (
4 "fmt"
5 "runtime"
6 + "runtime/debug"
7 + "strings"
8
9 "github.com/ipfs/kubo/core/commands/cmdutils"
10 )
@@ -16,6 +18,15 @@ var CurrentCommit string
18 // already identifies the exact source.
19 var taggedRelease string
20
21 +// buildOrigin is the Makefile-injected `host/org/repo` form of
22 +// `git remote get-url origin`. ImplicitAgentSuffix turns a non-upstream
23 +// value into the Version.AgentSuffix default so fork builds self-identify.
24 +var buildOrigin string
25 +
26 +// upstreamModulePath is the canonical upstream module path. Builds whose
27 +// origin matches it contribute no implicit suffix.
28 +const upstreamModulePath = "github.com/ipfs/kubo"
29 +
30 // CurrentVersionNumber is the current application's version literal.
31 const CurrentVersionNumber = "0.43.0-dev"
32
@@ -49,6 +60,52 @@ func SetUserAgentSuffix(suffix string) {
60 userAgentSuffix = cmdutils.CleanAndTrim(suffix)
61 }
62
63 +// ImplicitAgentSuffix returns a Version.AgentSuffix default derived from
64 +// the build origin. It prefers the Makefile-injected URL (covers forks
65 +// that keep the upstream `module` line) and falls back to
66 +// debug.ReadBuildInfo's main module path (covers `go install` and forks
67 +// that renamed their module). Returns "" for upstream builds.
68 +func ImplicitAgentSuffix() string {
69 + if s := suffixFromForkPath(buildOrigin); s != "" {
70 + return s
71 + }
72 + if bi, ok := debug.ReadBuildInfo(); ok {
73 + return suffixFromForkPath(bi.Main.Path)
74 + }
75 + return ""
76 +}
77 +
78 +// knownForges lists public git hosts whose hostname is dropped from the
79 +// implicit suffix; other hosts are kept so the origin stays identifiable.
80 +var knownForges = map[string]struct{}{
81 + "github.com": {},
82 + "gitlab.com": {},
83 + "codeberg.org": {},
84 + "bitbucket.org": {},
85 +}
86 +
87 +// suffixFromForkPath turns a normalized `host/org/repo` into the implicit
88 +// Version.AgentSuffix. Returns "" for upstream and empty inputs.
89 +func suffixFromForkPath(p string) string {
90 + p = strings.Trim(p, "/")
91 + if p == "" || p == upstreamModulePath {
92 + return ""
93 + }
94 + parts := strings.Split(p, "/")
95 + // Only normalize canonical `host/org/repo`; shorter inputs pass through
96 + // so operators can still identify them.
97 + if len(parts) < 3 {
98 + return p
99 + }
100 + if _, ok := knownForges[parts[0]]; ok {
101 + parts = parts[1:]
102 + }
103 + if parts[len(parts)-1] == "kubo" {
104 + parts = parts[:len(parts)-1]
105 + }
106 + return strings.Join(parts, "/")
107 +}
108 +
109 type VersionInfo struct {
110 Version string
111 Commit string
version_test.go
+45
@@ -6,6 +6,51 @@ import (
6 "github.com/stretchr/testify/assert"
7 )
8
9 +func TestSuffixFromForkPath(t *testing.T) {
10 + tests := []struct {
11 + name string
12 + path string
13 + expected string
14 + }{
15 + {name: "empty", path: "", expected: ""},
16 + {name: "upstream", path: "github.com/ipfs/kubo", expected: ""},
17 + {name: "github fork", path: "github.com/myorg/kubo", expected: "myorg"},
18 + {name: "gitlab fork", path: "gitlab.com/myorg/kubo", expected: "myorg"},
19 + {name: "codeberg fork", path: "codeberg.org/myorg/kubo", expected: "myorg"},
20 + {name: "bitbucket fork", path: "bitbucket.org/myorg/kubo", expected: "myorg"},
21 + {name: "github renamed repo", path: "github.com/myorg/kubo-experimental", expected: "myorg/kubo-experimental"},
22 + {name: "unknown host canonical repo", path: "git.example.com/team/kubo", expected: "git.example.com/team"},
23 + {name: "unknown host renamed repo", path: "git.example.com/team/kubo-fork", expected: "git.example.com/team/kubo-fork"},
24 + {name: "unknown host nested path", path: "git.example.com/group/sub/kubo", expected: "git.example.com/group/sub"},
25 + {name: "trailing slash", path: "github.com/myorg/kubo/", expected: "myorg"},
26 + {name: "leading slash", path: "/github.com/myorg/kubo", expected: "myorg"},
27 + {name: "single segment", path: "kubo", expected: "kubo"},
28 + {name: "two segment fork on known host", path: "github.com/kubo", expected: "github.com/kubo"},
29 + }
30 + for _, tt := range tests {
31 + t.Run(tt.name, func(t *testing.T) {
32 + assert.Equal(t, tt.expected, suffixFromForkPath(tt.path))
33 + })
34 + }
35 +}
36 +
37 +func TestImplicitAgentSuffix_PrefersBuildOrigin(t *testing.T) {
38 + orig := buildOrigin
39 + t.Cleanup(func() { buildOrigin = orig })
40 +
41 + buildOrigin = "github.com/myorg/kubo"
42 + assert.Equal(t, "myorg", ImplicitAgentSuffix())
43 +
44 + // Falls through to BuildInfo when origin matches upstream or is empty;
45 + // BuildInfo.Main.Path is "github.com/ipfs/kubo" during `go test` of this
46 + // package, so the implicit suffix is empty.
47 + buildOrigin = ""
48 + assert.Equal(t, "", ImplicitAgentSuffix())
49 +
50 + buildOrigin = upstreamModulePath
51 + assert.Equal(t, "", ImplicitAgentSuffix())
52 +}
53 +
54 // TestGetUserAgentVersion verifies the user agent string used in libp2p
55 // identify and HTTP requests. Tagged release builds (where the commit matches
56 // the tag) skip the commit hash from the agent version, since the version