1// Copyright (C) 2020 Storj Labs, Inc. 2// See LICENSE for copying information. 3 4package main 5 6import ( 7 "log" 8 "os" 9 "runtime" 10 "strings" 11 "time" 12 13 "github.com/spf13/cobra" 14 "go.uber.org/zap" 15 16 "storj.io/common/errs2" 17 "storj.io/common/fpath" 18 "storj.io/common/identity" 19 "storj.io/common/storj" 20 "storj.io/common/sync2" 21 "storj.io/private/cfgstruct" 22 "storj.io/private/process" 23 "storj.io/private/version" 24 _ "storj.io/storj/private/version" // This attaches version information during release builds. 25 "storj.io/storj/private/version/checker" 26) 27 28const ( 29 updaterServiceName = "storagenode-updater" 30 minCheckInterval = time.Minute 31) 32 33var ( 34 // TODO: replace with config value of random bytes in storagenode config. 35 nodeID storj.NodeID 36 37 updaterBinaryPath string 38 39 rootCmd = &cobra.Command{ 40 Use: "storagenode-updater", 41 Short: "Version updater for storage node", 42 } 43 runCmd = &cobra.Command{ 44 Use: "run", 45 Short: "Run the storagenode-updater for storage node", 46 Args: cobra.OnlyValidArgs, 47 RunE: cmdRun, 48 } 49 restartCmd = &cobra.Command{ 50 Use: "restart-service <new binary path>", 51 Short: "Restart service with the new binary", 52 Args: cobra.ExactArgs(1), 53 RunE: cmdRestart, 54 } 55 56 runCfg struct { 57 Identity identity.Config 58 Version checker.Config 59 60 BinaryLocation string `help:"the storage node executable binary location" default:"storagenode"` 61 ServiceName string `help:"storage node OS service name" default:"storagenode"` 62 // deprecated 63 Log string `help:"deprecated, use --log.output" default:""` 64 } 65 66 confDir string 67 identityDir string 68) 69 70func init() { 71 defaults := cfgstruct.DefaultsFlag(rootCmd) 72 defaultConfDir := fpath.ApplicationDir("storj", "storagenode") 73 defaultIdentityDir := fpath.ApplicationDir("storj", "identity", "storagenode") 74 cfgstruct.SetupFlag(zap.L(), rootCmd, &confDir, "config-dir", defaultConfDir, "main directory for storagenode configuration") 75 cfgstruct.SetupFlag(zap.L(), rootCmd, &identityDir, "identity-dir", defaultIdentityDir, "main directory for storagenode identity credentials") 76 77 rootCmd.AddCommand(runCmd) 78 rootCmd.AddCommand(restartCmd) 79 80 process.Bind(runCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir)) 81 process.Bind(restartCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir)) 82} 83 84func cmdRun(cmd *cobra.Command, args []string) (err error) { 85 if runCfg.Log != "" { 86 if err = openLog(runCfg.Log); err != nil { 87 zap.L().Error("Error creating new logger.", zap.Error(err)) 88 } 89 } 90 91 updaterBinaryPath, err = os.Executable() 92 if err != nil { 93 zap.L().Fatal("Unable to find storage node updater binary path.") 94 } 95 96 if !fileExists(runCfg.BinaryLocation) { 97 zap.L().Fatal("Unable to find storage node executable binary.") 98 } 99 100 ident, err := runCfg.Identity.Load() 101 if err != nil { 102 zap.L().Fatal("Error loading identity.", zap.Error(err)) 103 } 104 nodeID = ident.ID 105 if nodeID.IsZero() { 106 zap.L().Fatal("Empty node ID.") 107 } 108 109 zap.L().Info("Running on version", 110 zap.String("Service", updaterServiceName), 111 zap.String("Version", version.Build.Version.String()), 112 ) 113 114 ctx, _ := process.Ctx(cmd) 115 116 switch { 117 case runCfg.Version.CheckInterval <= 0: 118 err = loopFunc(ctx) 119 case runCfg.Version.CheckInterval < minCheckInterval: 120 zap.L().Error("Check interval below minimum. Overriding it minimum.", 121 zap.Stringer("Check Interval", runCfg.Version.CheckInterval), 122 zap.Stringer("Minimum Check Interval", minCheckInterval), 123 ) 124 runCfg.Version.CheckInterval = minCheckInterval 125 fallthrough 126 default: 127 loop := sync2.NewCycle(runCfg.Version.CheckInterval) 128 err = loop.Run(ctx, loopFunc) 129 } 130 if err != nil && !errs2.IsCanceled(err) { 131 log.Fatal(err) 132 } 133 134 return nil 135} 136 137func fileExists(filename string) bool { 138 info, err := os.Stat(filename) 139 if os.IsNotExist(err) { 140 return false 141 } 142 return info.Mode().IsRegular() 143} 144 145func openLog(logPath string) error { 146 if runtime.GOOS == "windows" && !strings.HasPrefix(logPath, "winfile:///") { 147 logPath = "winfile:///" + logPath 148 } 149 150 logger, err := process.NewLoggerWithOutputPaths("storagenode-updater", logPath) 151 if err != nil { 152 return err 153 } 154 155 zap.ReplaceGlobals(logger) 156 return nil 157} 158