// Copyright (C) 2018 Storj Labs, Inc. // See LICENSE for copying information. package main import ( "crypto/rand" "errors" "flag" "fmt" "log" "net" "os" "os/user" "path" "sort" _ "github.com/mattn/go-sqlite3" "github.com/mr-tron/base58/base58" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/urfave/cli" "golang.org/x/net/context" "google.golang.org/grpc" "storj.io/storj/pkg/kademlia" "storj.io/storj/pkg/piecestore/rpc/server" "storj.io/storj/pkg/piecestore/rpc/server/ttl" "storj.io/storj/pkg/process" proto "storj.io/storj/protos/overlay" pb "storj.io/storj/protos/piecestore" ) func newID() string { b := make([]byte, 32) _, err := rand.Read(b) if err != nil { panic(err) } encoding := base58.Encode(b) return encoding[:20] } func connectToKad(id, ip, kadlistenport, kadaddress string) *kademlia.Kademlia { node := proto.Node{ Id: string(id), Address: &proto.NodeAddress{ Transport: proto.NodeTransport_TCP, Address: kadaddress, }, } nodeid, err := kademlia.NewID() if err != nil { log.Fatalf("Failed to instantiate new Kademlia ID: %s", err.Error()) } kad, err := kademlia.NewKademlia(nodeid, []proto.Node{node}, ip, kadlistenport) if err != nil { log.Fatalf("Failed to instantiate new Kademlia: %s", err.Error()) } if err := kad.ListenAndServe(); err != nil { log.Fatalf("Failed to ListenAndServe on new Kademlia: %s", err.Error()) } if err := kad.Bootstrap(context.Background()); err != nil { log.Fatalf("Failed to Bootstrap on new Kademlia: %s", err.Error()) } return kad } func main() { process.Must(process.Main(process.ServiceFunc(run))) } func run(ctx context.Context, _ *cobra.Command, _ []string) error { app := cli.NewApp() app.Name = "Piece Store Farmer CLI" app.Usage = "Connect your drive to the network" app.Version = "1.0.0" // Flags app.Flags = []cli.Flag{} var kadhost string var kadport string var kadlistenport string var pshost string var psport string var dir string app.Commands = []cli.Command{ { Name: "create", Aliases: []string{"c"}, Usage: "create farmer node", ArgsUsage: "", Flags: []cli.Flag{ cli.StringFlag{Name: "pieceStoreHost", Usage: "Farmer's public ip/host", Destination: &pshost}, cli.StringFlag{Name: "pieceStorePort", Usage: "`port` where piece store data is accessed", Destination: &psport}, cli.StringFlag{Name: "kademliaPort", Usage: "Kademlia server `host`", Destination: &kadport}, cli.StringFlag{Name: "kademliaHost", Usage: "Kademlia server `host`", Destination: &kadhost}, cli.StringFlag{Name: "kademliaListenPort", Usage: "Kademlia server `host`", Destination: &kadlistenport}, cli.StringFlag{Name: "dir", Usage: "`dir` of drive being shared", Destination: &dir}, }, Action: func(c *cli.Context) error { nodeID := newID() usr, err := user.Current() if err != nil { return err } viper.SetDefault("piecestore.host", "127.0.0.1") viper.SetDefault("piecestore.port", "7777") viper.SetDefault("piecestore.dir", usr.HomeDir) viper.SetDefault("piecestore.id", nodeID) viper.SetDefault("kademlia.host", "bootstrap.storj.io") viper.SetDefault("kademlia.port", "8080") viper.SetDefault("kademlia.listen.port", "7776") viper.SetConfigName(nodeID) viper.SetConfigType("yaml") configPath := path.Join(usr.HomeDir, ".storj/") if err = os.MkdirAll(configPath, 0700); err != nil { return err } viper.AddConfigPath(configPath) fullPath := path.Join(configPath, fmt.Sprintf("%s.yaml", nodeID)) _, err = os.Stat(fullPath) if os.IsExist(err) { if err != nil { return errors.New("config already exists") } return err } // Create empty file at configPath _, err = os.Create(fullPath) if err != nil { return err } if pshost != "" { viper.Set("piecestore.host", pshost) } if psport != "" { viper.Set("piecestore.port", psport) } if dir != "" { viper.Set("piecestore.dir", dir) } if kadhost != "" { viper.Set("kademlia.host", kadhost) } if kadport != "" { viper.Set("kademlia.port", kadport) } if kadlistenport != "" { viper.Set("kademlia.listen.port", kadlistenport) } if err := viper.WriteConfig(); err != nil { return err } path := viper.ConfigFileUsed() fmt.Printf("Config: %s\n", path) fmt.Printf("ID: %s\n", nodeID) return nil }, }, { Name: "start", Aliases: []string{"s"}, Usage: "start farmer node", ArgsUsage: "[id]", Action: func(c *cli.Context) error { if c.Args().Get(0) == "" { return errors.New("no id specified") } usr, err := user.Current() if err != nil { log.Fatalf(err.Error()) } configPath := path.Join(usr.HomeDir, ".storj/") viper.AddConfigPath(configPath) viper.SetConfigName(c.Args().Get(0)) viper.SetConfigType("yaml") if err := viper.ReadInConfig(); err != nil { log.Fatalf(err.Error()) } nodeid := viper.GetString("piecestore.id") pshost = viper.GetString("piecestore.host") psport = viper.GetString("piecestore.port") kadlistenport = viper.GetString("kademlia.listen.port") kadport = viper.GetString("kademlia.port") kadhost = viper.GetString("kademlia.host") piecestoreDir := viper.GetString("piecestore.dir") dbPath := path.Join(piecestoreDir, fmt.Sprintf("store-%s", nodeid), "/ttl-data.db") dataDir := path.Join(piecestoreDir, fmt.Sprintf("store-%s", nodeid), "/piece-store-data/") if err = os.MkdirAll(piecestoreDir, 0700); err != nil { log.Fatalf(err.Error()) } _ = connectToKad(nodeid, pshost, kadlistenport, fmt.Sprintf("%s:%s", kadhost, kadport)) fileInfo, err := os.Stat(piecestoreDir) if err != nil { log.Fatalf(err.Error()) } if fileInfo.IsDir() != true { log.Fatalf("Error: %s is not a directory", piecestoreDir) } ttlDB, err := ttl.NewTTL(dbPath) if err != nil { log.Fatalf("failed to open DB") } // create a listener on TCP port lis, err := net.Listen("tcp", fmt.Sprintf(":%s", psport)) if err != nil { log.Fatalf("failed to listen: %v", err) } defer lis.Close() // create a server instance s := server.Server{PieceStoreDir: dataDir, DB: ttlDB} // create a gRPC server object grpcServer := grpc.NewServer() // attach the api service to the server pb.RegisterPieceStoreRoutesServer(grpcServer, &s) // routinely check DB and delete expired entries go func() { err := s.DB.DBCleanup(dataDir) log.Fatalf("Error in DBCleanup: %v", err) }() // start the server if err := grpcServer.Serve(lis); err != nil { log.Fatalf("failed to serve: %s", err) } return nil }, }, { Name: "delete", Aliases: []string{"d"}, Usage: "delete farmer node", ArgsUsage: "[id]", Action: func(c *cli.Context) error { return nil }, }, { Name: "list", Aliases: []string{"l"}, Usage: "list farmer nodes", ArgsUsage: "", Action: func(c *cli.Context) error { return nil }, }, } sort.Sort(cli.FlagsByName(app.Flags)) sort.Sort(cli.CommandsByName(app.Commands)) return app.Run(append([]string{os.Args[0]}, flag.Args()...)) }