Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/auto-update/coordinator.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ func (c *Coordinator) UpdateLoop(cancelSignal chan os.Signal) error {
return err
// periodic check for updates
case <-timer.C:
if !c.updater.Enabled {
continue
}
// wrap it on a goroutine so it doesn't block the main loop
go func() {
c.log.Infof("checking for updates")
Expand Down
55 changes: 43 additions & 12 deletions cmd/auto-update/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"os/exec"
"os/signal"
"path/filepath"
"syscall"
"time"

Expand Down Expand Up @@ -91,32 +92,32 @@ func main() {
"This message appears because the program was started directly instead of using 'start'.")
return
}
// do not run the auto-update process if its disabled
if !configs.Coordinator.Canopy.AutoUpdate {
logger.Info("auto-update disabled, starting CLI directly")
cli.Start()
return
// ensure the binary exists before proceeding
if !isExecutable(configs.Coordinator.BinPath) {
logger.Fatalf("canopy binary not found or not executable: %s", configs.Coordinator.BinPath)
}
if configs.Coordinator.Canopy.AutoUpdate {
logger.Infof("auto-update enabled, starting coordinator on version %s", rpc.SoftwareVersion)
} else {
logger.Infof("auto-update disabled, starting binary: %s", configs.Coordinator.BinPath)
}
logger.Infof("auto-update enabled, starting coordinator on version %s", rpc.SoftwareVersion)
// handle external shutdown signals
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
// setup the dependencies
updater := NewReleaseManager(configs.Updater, rpc.SoftwareVersion)
updater := NewReleaseManager(configs.Updater, rpc.SoftwareVersion, configs.Coordinator.Canopy.AutoUpdate)
snapshot := NewSnapshotManager(configs.Snapshot)

// setup plugin updater and config if configured
var pluginUpdater *ReleaseManager
var pluginConfig *PluginReleaseConfig
if configs.PluginUpdater != nil {
pluginUpdater = NewReleaseManager(configs.PluginUpdater, "v0.0.0")
pluginUpdater = NewReleaseManager(configs.PluginUpdater, "v0.0.0", true)
pluginConfig = configs.PluginUpdater.PluginConfig
logger.Infof("plugin auto-update enabled from %s/%s",
configs.PluginUpdater.RepoOwner,
configs.PluginUpdater.RepoName)
}
supervisor := NewSupervisor(logger, pluginConfig)

coordinator := NewCoordinator(configs.Coordinator, updater, pluginUpdater, supervisor, snapshot, logger)
// start the update loop
err := coordinator.UpdateLoop(sigChan)
Expand Down Expand Up @@ -155,10 +156,20 @@ func getConfigs() (*Configs, lib.LoggerI) {
binPath := envOrDefault("BIN_PATH", defaultBinPath)
githubToken := envOrDefault("CANOPY_GITHUB_API_TOKEN", "")

// core auto-update repo: config.json or defaults
repoOwner := canopyConfig.AutoUpdateRepoOwner
if repoOwner == "" {
repoOwner = defaultRepoOwner
}
repoName := canopyConfig.AutoUpdateRepoName
if repoName == "" {
repoName = defaultRepoName
}

updater := &ReleaseManagerConfig{
Type: ReleaseTypeCLI,
RepoName: envOrDefault("REPO_NAME", defaultRepoName),
RepoOwner: envOrDefault("REPO_OWNER", defaultRepoOwner),
RepoName: repoName,
RepoOwner: repoOwner,
GithubApiToken: githubToken,
BinPath: binPath,
SnapshotKey: snapshotMetadataKey,
Expand Down Expand Up @@ -213,6 +224,26 @@ func getConfigs() (*Configs, lib.LoggerI) {
}, l
}

// isExecutable returns true if path exists, is a regular file, and has execute permission.
func isExecutable(path string) bool {
// resolve to absolute path to avoid relative path ambiguity
absPath, err := filepath.Abs(path)
if err != nil {
return false
}
// check if the file exists and is accessible
info, err := os.Stat(absPath)
if err != nil {
return false
}
// directories are not executable binaries
if info.IsDir() {
return false
}
// check for any execute bit (owner, group, or other)
return info.Mode()&0111 != 0
}

// envOrDefault returns the value of the environment variable with the given key,
// or the default value if the variable is not set.
func envOrDefault(key, defaultValue string) string {
Expand Down
4 changes: 3 additions & 1 deletion cmd/auto-update/releaser.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,16 @@ type ReleaseManager struct {
config *ReleaseManagerConfig
httpClient *http.Client
Version string // current version
Enabled bool // whether this updater should actively check for updates
}

// NewReleaseManager creates a new ReleaseManager instance
func NewReleaseManager(config *ReleaseManagerConfig, version string) *ReleaseManager {
func NewReleaseManager(config *ReleaseManagerConfig, version string, enabled bool) *ReleaseManager {
return &ReleaseManager{
config: config,
httpClient: &http.Client{Timeout: httpReleaseClientTimeout},
Version: version,
Enabled: enabled,
}
}

Expand Down
6 changes: 4 additions & 2 deletions lib/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,10 @@ type MainConfig struct {
RootChain []RootChain `json:"rootChain"` // a list of the root chain(s) a node could connect to as dictated by the governance parameter 'RootChainId'
RunVDF bool `json:"runVDF"` // whether the node should run a Verifiable Delay Function to help secure the network against Long-Range-Attacks
Headless bool `json:"headless"` // turn off the web wallet and block explorer 'web' front ends
AutoUpdate bool `json:"autoUpdate"` // check for new versions of software each X time
Plugin string `json:"plugin"` // the configured plugin to use
AutoUpdate bool `json:"autoUpdate"` // check for new versions of software each X time
AutoUpdateRepoOwner string `json:"autoUpdateRepoOwner"` // GitHub repo owner for core auto-updates (e.g., "canopy-network")
AutoUpdateRepoName string `json:"autoUpdateRepoName"` // GitHub repo name for core auto-updates (e.g., "canopy")
Plugin string `json:"plugin"` // the configured plugin to use
PluginTimeoutMS int `json:"pluginTimeoutMS"` // plugin request timeout in milliseconds
PluginAutoUpdate PluginAutoUpdateConfig `json:"pluginAutoUpdate"` // plugin auto-update configuration
}
Expand Down
Loading