package main import ( "encoding/xml" "flag" "fmt" "lux/crypto" "lux/node" "lux/proto" "lux/rpc" "os" "os/signal" "strings" "syscall" ) var isNode bool var isHost bool var isRpc bool var configPath string var rpcPath string var bootstrap bool var justNodeId bool var daemonize bool var pidPath string type NodeConfig struct { XMLName xml.Name `xml:"node"` KeyStore string `xml:"keystore"` ID string `xml:"id"` Interior []string `xml:"interior"` Exterior []string `xml:"exterior"` Neighbors []struct { XMLName xml.Name `xml:"neighbor"` ID string `xml:"id"` Address string `xml:"address"` } `xml:"neighbor"` RPCEndpoints []string `xml:"rpc"` } func bootstrapNode() { xmlBytes, err := os.ReadFile(configPath) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } var config NodeConfig if err := xml.Unmarshal(xmlBytes, &config); err != nil { fmt.Fprintf(os.Stderr, "failed to parse xml: %v", err) os.Exit(1) } // create keystore, generate node key ks := crypto.NewLuxKeyStore(config.KeyStore) nodeKey, err := crypto.NewLuxKey(proto.LuxTypeNode) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } if err := ks.Put(nodeKey); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } if justNodeId { fmt.Println(nodeKey.Id.String()) } else { fmt.Printf("Your node key ID is: %s\nAdd %s to your node config!\n", nodeKey.Id.String(), nodeKey.Id.String()) } } func nodeMain() { xmlBytes, err := os.ReadFile(configPath) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } var config NodeConfig if err := xml.Unmarshal(xmlBytes, &config); err != nil { fmt.Fprintf(os.Stderr, "failed to parse xml: %v\n", err) os.Exit(1) } // check presense of keystore and id in config if config.KeyStore == "" { fmt.Fprintln(os.Stderr, "no keystore path specified!") os.Exit(1) } if config.ID == "" { fmt.Fprintln(os.Stderr, "no ID in config! You need to --bootstrap") os.Exit(1) } nodeId, err := proto.ParseLuxID(config.ID) if err != nil { fmt.Fprintf(os.Stderr, "failed to parse node id: %v\n", err) os.Exit(1) } // load keystore ks := crypto.NewLuxKeyStore(config.KeyStore) nodeKey, ok := ks.Get(nodeId) if !ok { fmt.Fprintln(os.Stderr, "node key is not present in key store!") os.Exit(1) } // create node node := node.NewLuxNode(nodeKey, ks) // add interior exterior channels for _, interior := range config.Interior { if err := node.AddInterior(interior); err != nil { fmt.Fprintf(os.Stderr, "failed to add interior %s: %v\n", interior, err) os.Exit(1) } } for _, exterior := range config.Exterior { if err := node.AddExterior(exterior); err != nil { fmt.Fprintf(os.Stderr, "failed to add exterior %s: %v\n", exterior, err) os.Exit(1) } } // add neighbors for _, neighbor := range config.Neighbors { neighId, err := proto.ParseLuxID(neighbor.ID) if err != nil { fmt.Fprintf(os.Stderr, "failed to parse neigh id %s: %v\n", neighbor.ID, err) os.Exit(1) } if err := node.AddNeighbor(neighId, neighbor.Address); err != nil { fmt.Fprintf(os.Stderr, "failed to add neighbor %s: %v\n", neighbor.ID, err) os.Exit(1) } } // create rpc server sv := rpc.NewLuxRpcServer() // TODO: register node controllers // parse and and spawn rpc endpoints for _, rpcPath := range config.RPCEndpoints { if strings.HasPrefix(rpcPath, "unix://") { path := rpcPath[7:] if err := sv.AddEndpoint("unix", path, rpc.LuxRpcTypeRoot); err != nil { fmt.Fprintf(os.Stderr, "failed to add root rpc %s: %v\n", path, err) os.Exit(1) } } else if strings.HasPrefix(rpcPath, "tcp://") { path := rpcPath[6:] if err := sv.AddEndpoint("tcp", path, rpc.LuxRpcTypeQuery); err != nil { fmt.Fprintf(os.Stderr, "failed to add query rpc %s: %v\n", path, err) os.Exit(1) } } else { fmt.Fprintf(os.Stderr, "unknown rpc type %s. It must be either unix:// or tcp://\n", rpcPath) os.Exit(1) } } // TODO: daemonize // register go channel to receive unix signals, // while hogging main thread to read them and take action. // its important to keep main thread alive for process to run sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) for { sig := <-sigs switch sig { case syscall.SIGINT: case syscall.SIGTERM: // stop node daemon node.Stop() os.Exit(0) } } } func main() { // first, we need to determine who we are: node, host or rpc. // determine by executable name (lux binary will be symlinked to lux-node, lux-host, luc-rpc), // or by explicit cli flag (--node, --host, --rpc) flag.BoolVar(&isNode, "node", false, "LUX node") flag.BoolVar(&isHost, "host", false, "LUX host") flag.BoolVar(&isRpc, "rpc", false, "RPC tool") flag.StringVar(&configPath, "config", "", "node or host config") flag.StringVar(&rpcPath, "rpc-path", "", "path to RPC UNIX socket or TCP socket, must be in unix:// or tcp:// form") flag.BoolVar(&bootstrap, "bootstrap", false, "bootstrap node keystore. config must be specified") flag.BoolVar(&justNodeId, "just-node-id", false, "when bootstrapping only output node id to stdout") flag.BoolVar(&daemonize, "daemonize", false, "run LUX as daemon in background") flag.StringVar(&pidPath, "pid", "", "after daemonization LUX will write its PID here") flag.Parse() if !isNode && !isHost && !isRpc { // determine by argv[0] if strings.Contains(os.Args[0], "node") { isNode = true } else if strings.Contains(os.Args[0], "host") { isHost = true } else if strings.Contains(os.Args[0], "rpc") { isRpc = true } } if (isNode || isHost) && configPath == "" { fmt.Fprintln(os.Stderr, "must provide config path") os.Exit(1) } else if isRpc && rpcPath == "" { fmt.Fprintln(os.Stderr, "must provide RPC socket path") os.Exit(1) } if isNode && bootstrap { bootstrapNode() return } if isNode { nodeMain() } }