lux/main.go
2025-01-30 15:07:55 +02:00

948 lines
21 KiB
Go

package main
import (
"bytes"
"encoding/xml"
"flag"
"fmt"
"io"
"lux/crypto"
"lux/host"
"lux/node"
"lux/proto"
"lux/rpc"
"net"
"net/netip"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"time"
"github.com/op/go-logging"
)
var isNode bool
var isHost bool
var isRpc bool
var configPath string
var bootstrap bool
var justNodeId bool
type LogConfig struct {
XMLName xml.Name `xml:"log"`
Level string `xml:"level,attr"`
LogPath string `xml:",innerxml"`
}
type UpdateHookConfig struct {
XMLName xml.Name `xml:"hook"`
HostID string `xml:"id"`
Script string `xml:"script"`
}
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"`
Sync int `xml:"sync"`
RPCEndpoints []string `xml:"rpc"`
DNS []string `xml:"dns"`
Log LogConfig `xml:"log"`
Hooks []UpdateHookConfig `xml:"hook"`
}
func setupLogging(log LogConfig) {
var level logging.Level
switch log.Level {
case "critical":
level = logging.CRITICAL
case "error":
level = logging.ERROR
case "warning":
level = logging.WARNING
case "notice":
level = logging.NOTICE
case "info":
level = logging.INFO
case "debug":
level = logging.DEBUG
default:
level = logging.INFO
}
// if log tag has file path, then it open file in append mode,
// otherwise it will use default stdout logger
if log.LogPath != "" {
logFile, err := os.OpenFile(log.LogPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.FileMode(0600))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to open log file: %v\n", err)
os.Exit(1)
}
logging.SetBackend(logging.NewLogBackend(logFile, "", 0))
}
logging.SetLevel(level, "")
}
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 <id>%s</id> to your node config!\n",
nodeKey.Id.String(), nodeKey.Id.String())
}
}
var log = logging.MustGetLogger("main")
type hookUpdateSubscriber struct {
Hooks map[proto.LuxID]UpdateHookConfig
}
func (subscriber *hookUpdateSubscriber) HandleStateUpdate(state node.LuxHostState) {
hook, ok := subscriber.Hooks[state.HostId]
if !ok {
return
}
// spawn executable and pipe xml host state into it's stdin
xmlBytes, err := xml.Marshal(&rpc.LuxRpcHost{
HostID: state.HostId.String(),
Hostname: state.State.Hostname,
State: state.State.IntoRpc(),
})
if err != nil {
log.Errorf("failed to marshal host update for hook: %v\n", err)
return
}
buffer := bytes.Buffer{}
buffer.Write(xmlBytes)
cmd := exec.Command(hook.Script)
cmd.Stdin = &buffer
cmd.Stdout = io.Discard
cmd.Stderr = io.Discard
if err := cmd.Run(); err != nil {
log.Warningf("failed to execute script %s: %v\n", hook.Script, err)
}
}
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 {
log.Criticalf("failed to parse xml: %v", err)
os.Exit(1)
}
// setup logging
setupLogging(config.Log)
// check presense of keystore and id in config
if config.KeyStore == "" {
log.Critical("no keystore path specified!")
os.Exit(1)
}
if config.ID == "" {
log.Critical("no ID in config! You need to --bootstrap")
os.Exit(1)
}
nodeId, err := proto.ParseLuxID(config.ID)
if err != nil {
log.Criticalf("failed to parse node id: %v", err)
os.Exit(1)
}
// load keystore
ks := crypto.NewLuxKeyStore(config.KeyStore)
if err := ks.Load(); err != nil {
log.Criticalf("failed to laod keystore: %v", err)
os.Exit(1)
}
nodeKey, ok := ks.Get(nodeId)
if !ok {
log.Critical("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 {
log.Criticalf("failed to add interior %s: %v", interior, err)
os.Exit(1)
}
}
for _, exterior := range config.Exterior {
if err := node.AddExterior(exterior); err != nil {
log.Criticalf("failed to add exterior %s: %v", exterior, err)
os.Exit(1)
}
}
// add neighbors
for _, neighbor := range config.Neighbors {
neighId, err := proto.ParseLuxID(neighbor.ID)
if err != nil {
log.Criticalf("failed to parse neigh id %s: %v", neighbor.ID, err)
os.Exit(1)
}
if err := node.AddNeighbor(neighId, neighbor.Address); err != nil {
log.Criticalf("failed to add neighbor %s: %v", neighbor.ID, err)
os.Exit(1)
}
}
// create rpc server
sv := rpc.NewLuxRpcServer()
sv.RegisterController(&node)
// 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 {
log.Criticalf("failed to add root rpc %s: %v", path, err)
os.Exit(1)
}
} else if strings.HasPrefix(rpcPath, "tcp://") {
path := rpcPath[6:]
if err := sv.AddEndpoint("tcp", path, rpc.LuxRpcTypeQuery); err != nil {
log.Criticalf("failed to add query rpc %s: %v", path, err)
os.Exit(1)
}
} else {
log.Criticalf("unknown rpc type %s. It must be either unix:// or tcp://\n", rpcPath)
os.Exit(1)
}
}
// add dns server frontends
for _, dnsListen := range config.DNS {
if err := node.AddDnsFrontend(dnsListen); err != nil {
log.Criticalf("failed to spawn dns %s: %v\n", dnsListen, err)
os.Exit(1)
}
}
// add update hooks
hook := hookUpdateSubscriber{
Hooks: make(map[proto.LuxID]UpdateHookConfig),
}
for _, item := range config.Hooks {
id, err := proto.ParseLuxID(item.HostID)
if err != nil {
log.Criticalf("failed to parse hook host id %s: %v\n", item.HostID, err)
os.Exit(1)
}
hook.Hooks[id] = item
}
node.AddSubscriber(&hook)
// start node
node.Start()
// node state sync scheduled task
stopChan := make(chan struct{})
if config.Sync != 0 {
ticker := time.NewTicker(time.Duration(config.Sync) * time.Minute)
go func() {
for {
select {
case <-ticker.C:
err := node.MulticastSync()
if err != nil {
log.Errorf("MulticastSync err: %v", err)
}
case <-stopChan:
ticker.Stop()
return
}
}
}()
} else {
log.Info("sync interval is not set. Node will not sync with neighbors")
}
// 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)
signaling:
for {
sig := <-sigs
log.Debug(sig)
switch sig {
case syscall.SIGINT, syscall.SIGTERM:
break signaling
}
}
// stop daemon
close(stopChan)
node.Stop()
}
type HostCommand struct {
XMLName xml.Name `xml:"command"`
Executable string `xml:",innerxml"`
}
type HostConfig struct {
XMLName xml.Name `xml:"host"`
KeyStore string `xml:"keystore"`
ID string `xml:"id"`
Hostname string `xml:"hostname"`
Log LogConfig `xml:"log"`
Heartbeat int `xml:"heartbeat"`
Options []struct {
XMLName xml.Name `xml:"option"`
Type string `xml:"type,attr"`
WAN struct {
XMLName xml.Name `xml:"wan"`
Method string `xml:"method,attr"`
Addr4 string `xml:"addr4"`
Addr6 string `xml:"addr6"`
Command HostCommand `xml:"command"`
} `xml:"wan"`
} `xml:"option"`
Nodes []struct {
XMLName xml.Name `xml:"node"`
ID string `xml:"id"`
Exterior string `xml:"exterior"`
} `xml:"node"`
}
type HostStaticWAN struct {
addr4 netip.Addr
addr6 netip.Addr
}
func NewHostStaticWAN(ip4 string, ip6 string) (HostStaticWAN, error) {
addr4, err := netip.ParseAddr(ip4)
if err != nil {
return HostStaticWAN{}, err
}
addr6, err := netip.ParseAddr(ip6)
if err != nil {
return HostStaticWAN{}, err
}
return HostStaticWAN{addr4, addr6}, nil
}
func (wan *HostStaticWAN) Provide() (host.LuxOption, error) {
return &host.LuxOptionWAN{Addr4: wan.addr4, Addr6: wan.addr6}, nil
}
type HostIdentWAN struct{}
func dialIdentMe(identIp netip.Addr) (netip.Addr, error) {
var network string
if identIp.Is6() {
network = "tcp6"
} else {
network = "tcp4"
}
cl, err := net.DialTCP(network, nil,
net.TCPAddrFromAddrPort(netip.AddrPortFrom(identIp, 80)))
if err != nil {
return netip.Addr{}, err
}
defer cl.Close()
// I dont want to bundle http package for embedded reasons,
// so I will just bang http headers directly
const req = "GET / HTTP/1.1\r\nHost: ident.me\r\nUser-Agent: LUX-v1\r\nAccept: */*\r\n\r\n"
_, err = cl.Write([]byte(req))
if err != nil {
return netip.Addr{}, err
}
res := make([]byte, 1024)
n, err := cl.Read(res)
if err != nil {
return netip.Addr{}, err
}
// parse http headers to get response
sep := []byte{0x0D, 0x0A, 0x0D, 0x0A}
ipStr := ""
for i := 0; i < n-4; i++ {
if bytes.Equal(res[i:i+4], sep) {
ipStr = string(res[i+4 : n])
}
}
// parse ip
if ipStr == "" {
return netip.Addr{}, fmt.Errorf("IP not found in ident.me response: %s", string(res))
}
// to prevent panics
parsedIp := net.ParseIP(ipStr)
if parsedIp == nil {
return netip.Addr{}, fmt.Errorf("failed to parse ident.me IP")
}
return proto.LuxProtoIPToAddr(parsedIp), nil
}
func (*HostIdentWAN) Provide() (host.LuxOption, error) {
wan := host.NewLuxOptionWAN()
// so first we gonna resolve ident.me and see if there is IPv6
addrs, err := net.LookupHost("ident.me")
if err != nil {
return &wan, err
}
for _, addr := range addrs {
ip := net.ParseIP(addr)
if ip.To4() == nil && wan.Addr6.IsUnspecified() {
// we gonna resolve IPv6
addr, err := dialIdentMe(proto.LuxProtoIPToAddr(ip))
if err == nil {
// we got IPv6
wan.Addr6 = addr
}
} else if wan.Addr4.IsUnspecified() {
addr, err := dialIdentMe(proto.LuxProtoIPToAddr(ip))
if err != nil {
// if no IPv4 its considered catastrophic error
return &wan, err
} else {
wan.Addr4 = addr
}
}
}
return &wan, nil
}
type HostNetif struct{}
func (*HostNetif) Provide() (host.LuxOption, error) {
netif := host.NewLuxOptionNetIf()
if err := netif.EnumerateNetlink(); err != nil {
return nil, err
}
return &netif, nil
}
func hostMain() {
xmlBytes, err := os.ReadFile(configPath)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to open config: %v\n", err)
os.Exit(1)
}
var config HostConfig
if err := xml.Unmarshal(xmlBytes, &config); err != nil {
fmt.Fprintf(os.Stderr, "failed to parse host config: %v\n", err)
os.Exit(1)
}
ks := crypto.NewLuxKeyStore(config.KeyStore)
if err := ks.Load(); err != nil {
fmt.Fprintf(os.Stderr, "failed to load keystore: %v\n", err)
os.Exit(1)
}
hostId, err := proto.ParseLuxID(config.ID)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to parse host ID: %v\n", err)
os.Exit(1)
}
hostKey, ok := ks.Get(hostId)
if !ok {
fmt.Fprintln(os.Stderr, "host key is not present in keystore!")
os.Exit(1)
}
if config.Hostname == "" {
fmt.Fprintln(os.Stderr, "no hostname specified!")
os.Exit(1)
}
if config.Heartbeat == 0 {
fmt.Fprintln(os.Stderr, "no minute interval provided in <heartbeat>!")
os.Exit(1)
}
// setup logging
setupLogging(config.Log)
// create host
host := host.NewLuxHost(config.Hostname, hostKey, ks)
// populate option providers
for _, option := range config.Options {
if option.Type == "wan" {
wan := &option.WAN
if wan.Method == "static" {
provider, err := NewHostStaticWAN(wan.Addr4, wan.Addr6)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to create static wan provider: %v\n", err)
os.Exit(1)
}
host.AddOptionProvider(&provider)
} else if wan.Method == "identme" {
host.AddOptionProvider(&HostIdentWAN{})
}
} else if option.Type == "netif" {
host.AddOptionProvider(&HostNetif{})
}
}
// add nodes
for _, node := range config.Nodes {
nodeId, err := proto.ParseLuxID(node.ID)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to parse node id: %v\n", err)
os.Exit(1)
}
if err := host.AddNode(nodeId, node.Exterior); err != nil {
fmt.Fprintf(os.Stderr, "failed to add node: %v\n", err)
os.Exit(1)
}
}
// start host
host.Start()
// start heartbeat timer
stopChan := make(chan struct{})
ticker := time.NewTicker(time.Duration(config.Heartbeat) * time.Minute)
go func() {
for {
select {
case <-ticker.C:
if err := host.Heartbeat(); err != nil {
fmt.Fprintf(os.Stderr, "failed to heartbeat: %v\n", err)
}
case <-stopChan:
ticker.Stop()
return
}
}
}()
// handle signals in main thread
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
signaling:
for {
sig := <-sigs
switch sig {
case syscall.SIGINT:
break signaling
case syscall.SIGTERM:
break signaling
}
}
// stop host
close(stopChan)
host.Stop()
}
func printRpcHost(host rpc.LuxRpcHost) {
fmt.Printf("%s\n", host.HostID)
fmt.Printf("|hostname: %s\n", host.Hostname)
fmt.Printf("|wan:\n|\taddr4\t%s\n|\taddr6\t%s\n", host.State.WAN.Addr4, host.State.WAN.Addr6)
for _, netif := range host.State.NetIf.Interfaces {
fmt.Printf("|netif %d: %s\n", netif.Index, netif.Name)
for _, addr := range netif.Addrs {
fmt.Printf("|\t%s\t%s\t\n", addr.Type, addr.Addr)
}
}
}
var rpcPath string
var rpcNewHost string
var rpcNewNode string
var rpcQueryHost string
var rpcQueryHostname string
var rpcGetRoutes bool
var rpcGetKeys bool
var rpcGetHosts bool
var rpcXml bool
func rpcMain() {
var cl rpc.LuxRpcClient
var err error
if strings.HasPrefix(rpcPath, "unix://") {
cl, err = rpc.LuxDialRpc("unix", rpcPath[7:])
} else if strings.HasPrefix(rpcPath, "tcp://") {
cl, err = rpc.LuxDialRpc("tcp", rpcPath[6:])
} else {
fmt.Fprintln(os.Stderr, "unknown RPC network (must be unix:// or tcp://)")
os.Exit(1)
}
if err != nil {
fmt.Fprintf(os.Stderr, "failed to dial RPC: %v\n", err)
os.Exit(1)
}
defer cl.Close()
// now we send requests
counter := 0
if rpcGetRoutes {
rpcRes, rpcErr, err := cl.Execute(rpc.LuxRpcRequest{
RequestID: counter,
Controller: "router",
Command: "get",
})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to send request: %v\n", err)
os.Exit(1)
}
counter++
if rpcErr.ErrorCode != 0 {
// we got error
fmt.Fprintf(os.Stderr, "RPC error %d: %s\n", rpcErr.ErrorCode, rpcErr.Message)
os.Exit(1)
}
if rpcXml {
xmlBytes, err := xml.Marshal(&rpcRes)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to marshal rpc output: %v\n", err)
os.Exit(1)
}
fmt.Println(string(xmlBytes))
return
}
// pretty print routes
for _, route := range rpcRes.Routes {
fmt.Println(route.String())
}
}
if rpcGetKeys {
rpcRes, rpcErr, err := cl.Execute(rpc.LuxRpcRequest{
RequestID: counter,
Controller: "ks",
Command: "get",
})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to send request: %v\n", err)
os.Exit(1)
}
counter++
if rpcErr.ErrorCode != 0 {
// we got error
fmt.Fprintf(os.Stderr, "RPC error %d: %s\n", rpcErr.ErrorCode, rpcErr.Message)
os.Exit(1)
}
if rpcXml {
xmlBytes, err := xml.Marshal(&rpcRes)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to marshal rpc output: %v\n", err)
os.Exit(1)
}
fmt.Println(string(xmlBytes))
return
}
// pretty print keys
for _, node := range rpcRes.Keystore.Nodes {
fmt.Printf("node %s\n", node.ID)
}
for _, host := range rpcRes.Keystore.Hosts {
fmt.Printf("host %s\n", host.ID)
}
}
if rpcNewHost != "" {
rpcRes, rpcErr, err := cl.Execute(rpc.LuxRpcRequest{
RequestID: counter,
Controller: "node",
Command: "new-host",
})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to send request: %v\n", err)
os.Exit(1)
}
counter++
if rpcErr.ErrorCode != 0 {
// we got error
fmt.Fprintf(os.Stderr, "RPC error %d: %s\n", rpcErr.ErrorCode, rpcErr.Message)
os.Exit(1)
}
if rpcXml {
xmlBytes, err := xml.Marshal(&rpcRes)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to marshal rpc output: %v\n", err)
os.Exit(1)
}
fmt.Println(string(xmlBytes))
return
}
// deserialize keystore
_, err = crypto.LuxKeyStoreFromRpc(rpcRes.Keystore, rpcNewHost)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to save host keystore: %v\n", err)
}
fmt.Printf("New host ID: %s\n", rpcRes.NewHostID)
}
if rpcNewNode != "" {
rpcRes, rpcErr, err := cl.Execute(rpc.LuxRpcRequest{
RequestID: counter,
Controller: "node",
Command: "new-node",
})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to send request: %v\n", err)
os.Exit(1)
}
counter++
if rpcErr.ErrorCode != 0 {
// we got error
fmt.Fprintf(os.Stderr, "RPC error %d: %s\n", rpcErr.ErrorCode, rpcErr.Message)
os.Exit(1)
}
if rpcXml {
xmlBytes, err := xml.Marshal(&rpcRes)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to marshal rpc output: %v\n", err)
os.Exit(1)
}
fmt.Println(string(xmlBytes))
return
}
// deserialize keystore
_, err = crypto.LuxKeyStoreFromRpc(rpcRes.Keystore, rpcNewNode)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to save node keystore: %v\n", err)
}
fmt.Printf("New neighbor node ID: %s\n", rpcRes.NewNodeID)
}
if rpcQueryHost != "" || rpcQueryHostname != "" {
var rpcReq rpc.LuxRpcRequest
if rpcQueryHost != "" {
rpcReq = rpc.LuxRpcRequest{
RequestID: counter,
Controller: "node",
Command: "query",
Hosts: []rpc.LuxRpcHost{
{HostID: rpcQueryHost},
},
}
} else {
rpcReq = rpc.LuxRpcRequest{
RequestID: counter,
Controller: "node",
Command: "query",
Hosts: []rpc.LuxRpcHost{
{Hostname: rpcQueryHostname},
},
}
}
rpcRes, rpcErr, err := cl.Execute(rpcReq)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to send request: %v\n", err)
os.Exit(1)
}
counter++
if rpcErr.ErrorCode != 0 {
// we got error
fmt.Fprintf(os.Stderr, "RPC error %d: %s\n", rpcErr.ErrorCode, rpcErr.Message)
os.Exit(1)
}
if rpcXml {
xmlBytes, err := xml.Marshal(&rpcRes)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to marshal rpc output: %v\n", err)
os.Exit(1)
}
fmt.Println(string(xmlBytes))
return
}
// print state
printRpcHost(rpcRes.Hosts[0])
}
if rpcGetHosts {
rpcRes, rpcErr, err := cl.Execute(rpc.LuxRpcRequest{
RequestID: counter,
Controller: "node",
Command: "get-hosts",
})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to send request: %v\n", err)
os.Exit(1)
}
counter++
if rpcErr.ErrorCode != 0 {
// we got error
fmt.Fprintf(os.Stderr, "RPC error %d: %s\n", rpcErr.ErrorCode, rpcErr.Message)
os.Exit(1)
}
if rpcXml {
xmlBytes, err := xml.Marshal(&rpcRes)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to marshal rpc output: %v\n", err)
os.Exit(1)
}
fmt.Println(string(xmlBytes))
return
}
for _, host := range rpcRes.Hosts {
printRpcHost(host)
}
}
}
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.StringVar(&configPath, "config", "", "node or host config")
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.StringVar(&rpcPath, "rpc", "", "Run as RPC client, specify path to RPC UNIX socket or TCP socket, must be in unix:// or tcp:// form")
flag.StringVar(&rpcNewHost, "rpc-new-host", "", "RPC node create new host, specifies path for new keystore")
flag.StringVar(&rpcNewNode, "rpc-new-node", "", "RPC node create new node, specifies path for new keystore")
flag.StringVar(&rpcQueryHost, "rpc-query-host", "", "RPC node query host state by ID")
flag.StringVar(&rpcQueryHostname, "rpc-query-hostname", "", "RPC node querty host state by hostname")
flag.BoolVar(&rpcGetRoutes, "rpc-get-routes", false, "RPC node list established routes")
flag.BoolVar(&rpcGetKeys, "rpc-get-keys", false, "RPC node list keys")
flag.BoolVar(&rpcGetHosts, "rpc-get-hosts", false, "RPC node list hosts")
flag.BoolVar(&rpcXml, "rpc-xml", false, "output RPC results in XML")
flag.Parse()
if rpcPath != "" {
isRpc = true
} else if !isNode && !isHost {
// determine by argv[0]
if strings.Contains(os.Args[0], "node") {
isNode = true
} else if strings.Contains(os.Args[0], "host") {
isHost = 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()
} else if isHost {
hostMain()
} else if isRpc {
rpcMain()
}
}