diff --git a/cmd/ipfs/kubo/daemon.go b/cmd/ipfs/kubo/daemon.go index ab034b20a14..194d9796e9e 100644 --- a/cmd/ipfs/kubo/daemon.go +++ b/cmd/ipfs/kubo/daemon.go @@ -575,6 +575,8 @@ take effect. // start MFS pinning thread startPinMFS(daemonConfigPollInterval, cctx, &ipfsPinMFSNode{node}) + core.StartVersionChecker(node) + // The daemon is *finally* ready. fmt.Printf("Daemon is ready\n") notifyReady() diff --git a/core/commands/version.go b/core/commands/version.go index 541a49f9c30..a86373e28a8 100644 --- a/core/commands/version.go +++ b/core/commands/version.go @@ -5,16 +5,11 @@ import ( "fmt" "io" "runtime/debug" - "strings" + cmds "github.com/ipfs/go-ipfs-cmds" version "github.com/ipfs/kubo" + "github.com/ipfs/kubo/core" "github.com/ipfs/kubo/core/commands/cmdenv" - - cmds "github.com/ipfs/go-ipfs-cmds" - - versioncmp "github.com/hashicorp/go-version" - "github.com/libp2p/go-libp2p-kad-dht/fullrt" - pstore "github.com/libp2p/go-libp2p/core/peerstore" ) const ( @@ -139,12 +134,6 @@ Print out all dependencies and their versions.`, }, } -type CheckOutput struct { - PeersCounted int - GreatestVersion string - OldVersion bool -} - var checkVersionCommand = &cmds.Command{ Helptext: cmds.HelpText{ Tagline: "Checks IPFS version against network (online only).", @@ -154,13 +143,10 @@ Checks node versions in our DHT to compare if we're running an older version.`, Options: []cmds.Option{ cmds.FloatOption(versionCompareNewFractionOptionName, "f", "Fraction of peers with new version to generate update warning.").WithDefault(0.1), }, - Type: CheckOutput{}, + Type: core.VersionCheckOutput{}, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { nd, err := cmdenv.GetNode(env) - if err != nil { - return err - } if !nd.IsOnline { return ErrNotOnline @@ -170,100 +156,26 @@ Checks node versions in our DHT to compare if we're running an older version.`, return ErrNotDHT } - ourVersion, err := versioncmp.NewVersion(strings.Replace(version.CurrentVersionNumber, "-dev", "", -1)) if err != nil { - return fmt.Errorf("could not parse our own version %s: %w", - version.CurrentVersionNumber, err) + return err } - greatestVersionSeen := ourVersion - totalPeersCounted := 1 // Us (and to avoid division-by-zero edge case). - withGreaterVersion := 0 - - recordPeerVersion := func(agentVersion string) { - // We process the version as is it assembled in GetUserAgentVersion. - segments := strings.Split(agentVersion, "/") - if len(segments) < 2 { - return - } - if segments[0] != "kubo" { - return - } - versionNumber := segments[1] // As in our CurrentVersionNumber. - - // Ignore development releases. - if strings.Contains(versionNumber, "-dev") { - return - } - if strings.Contains(versionNumber, "-rc") { - return - } - - peerVersion, err := versioncmp.NewVersion(versionNumber) - if err != nil { - // Do not error on invalid remote versions, just ignore. - return - } - - // Valid peer version number. - totalPeersCounted += 1 - if ourVersion.LessThan(peerVersion) { - withGreaterVersion += 1 - } - if peerVersion.GreaterThan(greatestVersionSeen) { - greatestVersionSeen = peerVersion - } - } + newerFraction, _ := req.Options[versionCompareNewFractionOptionName].(float64) + output, err := core.CheckVersion(nd, newerFraction) - // Logic taken from `ipfs stats dht` command. - if nd.DHTClient != nd.DHT { - client, ok := nd.DHTClient.(*fullrt.FullRT) - if !ok { - return cmds.Errorf(cmds.ErrClient, "could not generate stats for the WAN DHT client type") - } - for _, p := range client.Stat() { - if ver, err := nd.Peerstore.Get(p, "AgentVersion"); err == nil { - recordPeerVersion(ver.(string)) - } else if err == pstore.ErrNotFound { - // ignore - } else { - // this is a bug, usually. - log.Errorw( - "failed to get agent version from peerstore", - "error", err, - ) - } - } - } else { - for _, pi := range nd.DHT.WAN.RoutingTable().GetPeerInfos() { - if ver, err := nd.Peerstore.Get(pi.Id, "AgentVersion"); err == nil { - recordPeerVersion(ver.(string)) - } else if err == pstore.ErrNotFound { - // ignore - } else { - // this is a bug, usually. - log.Errorw( - "failed to get agent version from peerstore", - "error", err, - ) - } - } + if err != nil { + return err } - newerFraction, _ := req.Options[versionCompareNewFractionOptionName].(float64) - if err := cmds.EmitOnce(res, CheckOutput{ - PeersCounted: totalPeersCounted, - GreatestVersion: greatestVersionSeen.String(), - OldVersion: (float64(withGreaterVersion) / float64(totalPeersCounted)) > newerFraction, - }); err != nil { + if err := cmds.EmitOnce(res, output); err != nil { return err } return nil }, Encoders: cmds.EncoderMap{ - cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, checkOutput CheckOutput) error { - if checkOutput.OldVersion { - fmt.Fprintf(w, "⚠️WARNING: this Kubo node is running an outdated version compared to other peers, update to %s\n", checkOutput.GreatestVersion) + cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, checkOutput core.VersionCheckOutput) error { + if checkOutput.IsOutdated { + fmt.Fprint(w, checkOutput.Msg) } return nil }), diff --git a/core/ver_checker.go b/core/ver_checker.go new file mode 100644 index 00000000000..4cc3b4897a9 --- /dev/null +++ b/core/ver_checker.go @@ -0,0 +1,130 @@ +package core + +import ( + "errors" + "fmt" + versioncmp "github.com/hashicorp/go-version" + version "github.com/ipfs/kubo" + "github.com/libp2p/go-libp2p-kad-dht/fullrt" + pstore "github.com/libp2p/go-libp2p/core/peerstore" + "strings" + "time" +) + +type VersionCheckOutput struct { + IsOutdated bool + Msg string +} + +func StartVersionChecker(nd *IpfsNode) { + ticker := time.NewTicker(time.Hour) + go func() { + for { + output, err := CheckVersion(nd, 0.1) + if err != nil { + log.Errorw("Failed to check version", "error", err) + } + if output.IsOutdated { + fmt.Println(output.Msg) + } + + select { + case <-nd.Process.Closing(): + return + case <-ticker.C: + continue + } + } + }() +} + +func CheckVersion(nd *IpfsNode, newerFraction float64) (VersionCheckOutput, error) { + ourVersion, err := versioncmp.NewVersion(strings.Replace(version.CurrentVersionNumber, "-dev", "", -1)) + if err != nil { + return VersionCheckOutput{}, fmt.Errorf("could not parse our own version %s: %w", + version.CurrentVersionNumber, err) + } + + greatestVersionSeen := ourVersion + totalPeersCounted := 1 // Us (and to avoid division-by-zero edge case). + withGreaterVersion := 0 + + recordPeerVersion := func(agentVersion string) { + // We process the version as is it assembled in GetUserAgentVersion. + segments := strings.Split(agentVersion, "/") + if len(segments) < 2 { + return + } + if segments[0] != "kubo" { + return + } + versionNumber := segments[1] // As in our CurrentVersionNumber. + + // Ignore development releases. + if strings.Contains(versionNumber, "-dev") { + return + } + if strings.Contains(versionNumber, "-rc") { + return + } + + peerVersion, err := versioncmp.NewVersion(versionNumber) + if err != nil { + // Do not error on invalid remote versions, just ignore. + return + } + + // Valid peer version number. + totalPeersCounted += 1 + if ourVersion.LessThan(peerVersion) { + withGreaterVersion += 1 + } + if peerVersion.GreaterThan(greatestVersionSeen) { + greatestVersionSeen = peerVersion + } + } + + // Logic taken from `ipfs stats dht` command. + if nd.DHTClient != nd.DHT { + client, ok := nd.DHTClient.(*fullrt.FullRT) + if !ok { + return VersionCheckOutput{}, errors.New("could not generate stats for the WAN DHT client type") + } + for _, p := range client.Stat() { + if ver, err := nd.Peerstore.Get(p, "AgentVersion"); err == nil { + recordPeerVersion(ver.(string)) + } else if errors.Is(err, pstore.ErrNotFound) { + // ignore + } else { + // this is a bug, usually. + log.Errorw( + "failed to get agent version from peerstore", + "error", err, + ) + } + } + } else { + for _, pi := range nd.DHT.WAN.RoutingTable().GetPeerInfos() { + if ver, err := nd.Peerstore.Get(pi.Id, "AgentVersion"); err == nil { + recordPeerVersion(ver.(string)) + } else if errors.Is(err, pstore.ErrNotFound) { + // ignore + } else { + // this is a bug, usually. + log.Errorw( + "failed to get agent version from peerstore", + "error", err, + ) + } + } + } + + if (float64(withGreaterVersion) / float64(totalPeersCounted)) > newerFraction { + return VersionCheckOutput{ + IsOutdated: true, + Msg: fmt.Sprintf("⚠️WARNING: this Kubo node is running an outdated version compared to other peers, update to %s\n", greatestVersionSeen.String()), + }, nil + } else { + return VersionCheckOutput{}, nil + } +}