diff --git a/sn-manager/internal/updater/updater.go b/sn-manager/internal/updater/updater.go index b15cad05..69be8dfb 100644 --- a/sn-manager/internal/updater/updater.go +++ b/sn-manager/internal/updater/updater.go @@ -109,6 +109,14 @@ func (u *AutoUpdater) ShouldUpdate(current, latest string) bool { current = strings.TrimPrefix(current, "v") latest = strings.TrimPrefix(latest, "v") + // Allow testnet-tagged releases (e.g., v1.2.3-testnet.1). + if utils.IsTestnetReleaseTag(latest) { + if !utils.SameMajor(current, latest) { + return false + } + return utils.CompareVersions(current, latest) < 0 + } + // Skip pre-release targets (beta, alpha, rc, etc.) if strings.Contains(latest, "-") { return false @@ -192,8 +200,10 @@ func (u *AutoUpdater) ForceSyncToLatest(_ context.Context) { // If force is true, bypass gateway idleness and version policy checks. func (u *AutoUpdater) checkAndUpdateCombined(force bool) { - // Fetch latest stable release once - release, err := u.githubClient.GetLatestStableRelease() + chainID, _ := utils.ReadSupernodeChainID() + + // Fetch latest release once (testnet prefers "-testnet" tags, otherwise stable) + release, err := utils.LatestReleaseForChainID(u.githubClient, chainID) if err != nil { log.Printf("Failed to check releases: %v", err) return diff --git a/sn-manager/internal/utils/network.go b/sn-manager/internal/utils/network.go new file mode 100644 index 00000000..01c443ef --- /dev/null +++ b/sn-manager/internal/utils/network.go @@ -0,0 +1,85 @@ +package utils + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/LumeraProtocol/supernode/v2/pkg/github" + "gopkg.in/yaml.v3" +) + +const testnetTagMarker = "-testnet" + +// SupernodeConfigPath returns the expected path for the SuperNode config file +// based on the current process HOME/user. +func SupernodeConfigPath() string { + home, _ := os.UserHomeDir() + if home == "" { + home = os.Getenv("HOME") + } + return filepath.Join(home, ".supernode", "config.yml") +} + +// ReadSupernodeChainID reads lumera.chain_id from the SuperNode config file. +func ReadSupernodeChainID() (string, error) { + path := SupernodeConfigPath() + data, err := os.ReadFile(path) + if err != nil { + return "", err + } + + var cfg struct { + Lumera struct { + ChainID string `yaml:"chain_id"` + } `yaml:"lumera"` + } + if err := yaml.Unmarshal(data, &cfg); err != nil { + return "", fmt.Errorf("failed to parse supernode config %s: %w", path, err) + } + + chainID := strings.TrimSpace(cfg.Lumera.ChainID) + if chainID == "" { + return "", fmt.Errorf("chain_id not set in %s", path) + } + return chainID, nil +} + +func IsTestnetChainID(chainID string) bool { + return strings.Contains(strings.ToLower(chainID), "testnet") +} + +func IsTestnetReleaseTag(tag string) bool { + return strings.Contains(strings.ToLower(tag), testnetTagMarker) +} + +// LatestTestnetRelease returns the most recent non-draft release whose tag +// contains "-testnet". Release ordering is taken from the GitHub API response. +func LatestTestnetRelease(client github.GithubClient) (*github.Release, error) { + releases, err := client.ListReleases() + if err != nil { + return nil, err + } + for _, r := range releases { + if r == nil || r.Draft { + continue + } + if IsTestnetReleaseTag(r.TagName) { + return r, nil + } + } + return nil, fmt.Errorf("no testnet releases found") +} + +// LatestReleaseForChainID selects the appropriate "latest" release based on the +// chain ID. Testnet chains prefer "-testnet" tagged releases; otherwise it +// falls back to the latest stable release. +func LatestReleaseForChainID(client github.GithubClient, chainID string) (*github.Release, error) { + if IsTestnetChainID(chainID) { + if r, err := LatestTestnetRelease(client); err == nil { + return r, nil + } + } + return client.GetLatestStableRelease() +}