github.com/ipni/storetheindex@v0.8.30/assigner/command/daemon.go (about) 1 package command 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 9 "github.com/ipfs/boxo/bootstrap" 10 "github.com/ipfs/boxo/peering" 11 logging "github.com/ipfs/go-log/v2" 12 "github.com/ipni/go-libipni/mautil" 13 "github.com/ipni/storetheindex/assigner/config" 14 "github.com/ipni/storetheindex/assigner/core" 15 server "github.com/ipni/storetheindex/assigner/server" 16 sticfg "github.com/ipni/storetheindex/config" 17 "github.com/libp2p/go-libp2p" 18 "github.com/libp2p/go-libp2p/core/host" 19 "github.com/libp2p/go-libp2p/core/network" 20 "github.com/multiformats/go-multiaddr" 21 "github.com/urfave/cli/v2" 22 ) 23 24 var log = logging.Logger("assigner") 25 26 const progName = "assigner" 27 28 var DaemonCmd = &cli.Command{ 29 Name: "daemon", 30 Usage: "Start assigner service daemon", 31 Flags: daemonFlags, 32 Action: daemonAction, 33 } 34 35 var daemonFlags = []cli.Flag{ 36 &cli.StringFlag{ 37 Name: "listen-admin", 38 Usage: "Admin HTTP API listen address", 39 EnvVars: []string{"ASSIGNER_LISTEN_ADMIN"}, 40 Required: false, 41 }, 42 &cli.StringFlag{ 43 Name: "listen-http", 44 Usage: "HTTP listen address", 45 EnvVars: []string{"ASSIGNER_LISTEN_HTTP"}, 46 Required: false, 47 }, 48 &cli.StringFlag{ 49 Name: "listen-p2p", 50 Usage: "P2P listen address", 51 EnvVars: []string{"ASSIGNER_LISTEN_P2P"}, 52 Required: false, 53 }, 54 } 55 56 func daemonAction(cctx *cli.Context) error { 57 cfg, err := loadConfig("") 58 if err != nil { 59 if errors.Is(err, sticfg.ErrNotInitialized) { 60 fmt.Fprintf(os.Stderr, "%s is not initialized\n", progName) 61 fmt.Fprintf(os.Stderr, "To initialize, run the command: ./%s init\n", progName) 62 os.Exit(1) 63 } 64 return err 65 } 66 67 err = setLoggingConfig(cfg.Logging) 68 if err != nil { 69 return err 70 } 71 72 if cfg.Version != config.Version { 73 log.Warnf("Configuration file out-of-date. Upgrade by running: ./%s init --upgrade", progName) 74 } 75 76 var p2pHost host.Host 77 p2pAddr := cfg.Daemon.P2PAddr 78 if cctx.String("listen-p2p") != "" { 79 p2pAddr = cctx.String("listen-p2p") 80 } 81 if p2pAddr != "none" { 82 _, privKey, err := cfg.Identity.Decode() 83 if err != nil { 84 return err 85 } 86 p2pmaddr, err := multiaddr.NewMultiaddr(p2pAddr) 87 if err != nil { 88 return fmt.Errorf("bad p2p address %s: %w", p2pAddr, err) 89 } 90 p2pOpts := []libp2p.Option{ 91 // Use the keypair generated during init 92 libp2p.Identity(privKey), 93 // Listen at specific address 94 libp2p.ListenAddrs(p2pmaddr), 95 } 96 if cfg.Daemon.NoResourceManager { 97 log.Info("libp2p resource manager disabled") 98 p2pOpts = append(p2pOpts, libp2p.ResourceManager(&network.NullResourceManager{})) 99 } 100 101 p2pHost, err = libp2p.New(p2pOpts...) 102 if err != nil { 103 return err 104 } 105 defer p2pHost.Close() 106 107 bootstrapper, err := startBootstrapper(cfg.Bootstrap, p2pHost) 108 if err != nil { 109 return fmt.Errorf("cannot start bootstrapper: %s", err) 110 } 111 if bootstrapper != nil { 112 defer bootstrapper.Close() 113 } 114 115 peeringService, err := startPeering(cfg.Peering, p2pHost) 116 if err != nil { 117 return fmt.Errorf("cannot start peering service: %s", err) 118 } 119 if peeringService != nil { 120 defer peeringService.Stop() 121 } 122 123 log.Infow("libp2p servers initialized", "host_id", p2pHost.ID(), "multiaddr", p2pmaddr) 124 } 125 126 assigner, err := core.NewAssigner(cctx.Context, cfg.Assignment, p2pHost) 127 if err != nil { 128 return err 129 } 130 131 // Create HTTP server 132 var httpServer *server.Server 133 httpAddr := cfg.Daemon.HTTPAddr 134 if cctx.String("listen-http") != "" { 135 httpAddr = cctx.String("listen-http") 136 } 137 if httpAddr != "none" { 138 httpNetAddr, err := mautil.MultiaddrStringToNetAddr(httpAddr) 139 if err != nil { 140 return fmt.Errorf("bad http address %s: %w", httpAddr, err) 141 } 142 143 httpServer, err = server.New(httpNetAddr.String(), assigner, 144 server.WithVersion(cctx.App.Version)) 145 if err != nil { 146 return err 147 } 148 } 149 150 svrErrChan := make(chan error, 3) 151 152 log.Info("Starting http servers") 153 if httpServer != nil { 154 go func() { 155 svrErrChan <- httpServer.Start() 156 }() 157 fmt.Println("http server:\t", httpAddr) 158 } else { 159 fmt.Println("http server:\t disabled") 160 } 161 162 // Output message to user (not to log). 163 fmt.Println("Daemon is ready") 164 var finalErr error 165 166 for endDaemon := false; !endDaemon; { 167 select { 168 case <-cctx.Done(): 169 // Command was canceled (ctrl-c) 170 endDaemon = true 171 case err = <-svrErrChan: 172 finalErr = fmt.Errorf("failed to start server: %w", err) 173 endDaemon = true 174 } 175 } 176 177 log.Infow("Shutting down daemon") 178 179 if httpServer != nil { 180 if err = httpServer.Close(); err != nil { 181 finalErr = fmt.Errorf("error shutting down http server: %w", err) 182 } 183 } 184 185 if err = assigner.Close(); err != nil { 186 finalErr = fmt.Errorf("error closing assigner: %w", err) 187 } 188 189 log.Info("Daemon stopped") 190 return finalErr 191 } 192 193 func setLoggingConfig(cfgLogging config.Logging) error { 194 // Set overall log level. 195 err := logging.SetLogLevel("*", cfgLogging.Level) 196 if err != nil { 197 return err 198 } 199 200 // Set level for individual loggers. 201 for loggerName, level := range cfgLogging.Loggers { 202 err = logging.SetLogLevel(loggerName, level) 203 if err != nil { 204 return err 205 } 206 } 207 return nil 208 } 209 210 func loadConfig(filePath string) (*config.Config, error) { 211 cfg, err := config.Load(filePath) 212 if err != nil { 213 return nil, fmt.Errorf("cannot load config file: %w", err) 214 } 215 if cfg.Version != config.Version { 216 log.Warnf("Configuration file out-of-date. Upgrade by running: ./%s init --upgrade", progName) 217 } 218 219 return cfg, nil 220 } 221 222 func startBootstrapper(cfg sticfg.Bootstrap, p2pHost host.Host) (io.Closer, error) { 223 // If there are bootstrap peers and bootstrapping is enabled, then try to 224 // connect to the minimum set of peers. This connects the indexer to other 225 // nodes in the gossip mesh, allowing it to receive advertisements from 226 // providers. 227 if len(cfg.Peers) == 0 || cfg.MinimumPeers == 0 { 228 return nil, nil 229 } 230 addrs, err := cfg.PeerAddrs() 231 if err != nil { 232 return nil, fmt.Errorf("bad bootstrap peer: %s", err) 233 } 234 235 bootCfg := bootstrap.BootstrapConfigWithPeers(addrs) 236 bootCfg.MinPeerThreshold = cfg.MinimumPeers 237 238 return bootstrap.Bootstrap(p2pHost.ID(), p2pHost, nil, bootCfg) 239 } 240 241 func startPeering(cfg sticfg.Peering, p2pHost host.Host) (*peering.PeeringService, error) { 242 if len(cfg.Peers) == 0 { 243 return nil, nil 244 } 245 246 curPeers, err := cfg.PeerAddrs() 247 if err != nil { 248 return nil, fmt.Errorf("bad peering peer: %s", err) 249 } 250 251 peeringService := peering.NewPeeringService(p2pHost) 252 for i := range curPeers { 253 peeringService.AddPeer(curPeers[i]) 254 } 255 if err = peeringService.Start(); err != nil { 256 return nil, err 257 } 258 return peeringService, nil 259 }