github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/cmd/lit-af/lit-af.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strconv" 8 "strings" 9 10 "github.com/chzyer/readline" 11 "github.com/fatih/color" 12 flags "github.com/jessevdk/go-flags" 13 "github.com/mit-dci/lit/crypto/koblitz" 14 "github.com/mit-dci/lit/litrpc" 15 "github.com/mit-dci/lit/lnutil" 16 "github.com/mit-dci/lit/logging" 17 ) 18 19 /* 20 Lit-AF 21 22 The Lit Advanced Functionality interface. 23 This is a text mode interface to lit. It connects over jsonrpc to the a lit 24 node and tells that lit node what to do. The lit node also responds so that 25 lit-af can tell what's going on. 26 27 lit-gtk does most of the same things with a gtk interface, but there will be 28 some yet-undefined advanced functionality only available in lit-af. 29 30 May end up using termbox-go 31 32 */ 33 34 const ( 35 litHomeDirName = ".lit" 36 historyFilename = "lit-af.history" 37 defaultKeyFileName = "privkey.hex" 38 ) 39 40 type Command struct { 41 Format string 42 Description string 43 ShortDescription string 44 } 45 46 type litAfClient struct { 47 RPCClient *litrpc.LndcRpcClient 48 } 49 50 type litAfConfig struct { 51 Con string `long:"con" description:"host to connect to in the form of [<lnadr>@][<host>][:<port>]"` 52 LitHomeDir string `long:"litHomeDir" description:"directory to save settings"` 53 Port string `long:"autoListenPort" description:"port that the lit is listening to."` 54 Tracker string `long:"tracker" description:"service to use for looking up node addresses"` 55 LogLevel []bool `short:"v" description:"Set verbosity level to verbose (-v), very verbose (-vv) or very very verbose (-vvv)"` 56 } 57 58 var ( 59 defaultCon = "2448" 60 defaultLitHomeDirName = filepath.Join(os.Getenv("HOME"), litHomeDirName) 61 defaultTracker = "http://hubris.media.mit.edu:46580" 62 ) 63 64 // newConfigParser returns a new command line flags parser. 65 func newConfigParser(conf *litAfConfig, options flags.Options) *flags.Parser { 66 parser := flags.NewParser(conf, options) 67 return parser 68 } 69 70 func (lc *litAfClient) litAfSetup(conf litAfConfig) { 71 72 var err error 73 74 preParser := newConfigParser(&conf, flags.HelpFlag) 75 _, err = preParser.ParseArgs(os.Args) // parse the cli 76 if err != nil { 77 logging.Fatal(err) 78 } 79 logLevel := 0 80 if len(conf.LogLevel) == 1 { // -v 81 logLevel = 1 82 } else if len(conf.LogLevel) == 2 { // -vv 83 logLevel = 2 84 } else if len(conf.LogLevel) >= 3 { // -vvv 85 logLevel = 3 86 } 87 logging.SetLogLevel(logLevel) // defaults to zero 88 89 // create home directory if it does not exist 90 _, err = os.Stat(conf.LitHomeDir) 91 if os.IsNotExist(err) { 92 os.Mkdir(conf.LitHomeDir, 0700) 93 } 94 95 adr, host, port := lnutil.ParseAdrStringWithPort(conf.Con) 96 97 if len(conf.Port) > 0 { 98 custom_port, err := strconv.ParseUint(conf.Port, 10, 32) 99 if err != nil { 100 logging.Fatal(err.Error()) 101 } 102 port = uint32(custom_port) 103 } 104 105 logging.Infof("Adr: %s, Host: %s, Port: %d, LitHomeDir: %s", adr, host, port, conf.LitHomeDir) 106 107 if litrpc.LndcRpcCanConnectLocallyWithHomeDir(conf.LitHomeDir) && adr == "" && (host == "localhost" || host == "127.0.0.1") { 108 // con parameter was not passed. 109 lc.RPCClient, err = litrpc.NewLocalLndcRpcClientWithHomeDirAndPort(conf.LitHomeDir, port) 110 if err != nil { 111 logging.Fatal(err.Error()) 112 } 113 } else { 114 // con parameter passed. 115 if !lnutil.LitAdrOK(adr) { 116 logging.Fatal("lit address passed in -con parameter is not valid") 117 } 118 119 keyFilePath := filepath.Join(conf.LitHomeDir, "lit-af-key.hex") 120 privKey, err := lnutil.ReadKeyFile(keyFilePath) 121 if err != nil { 122 logging.Fatal(err.Error()) 123 } 124 key, _ := koblitz.PrivKeyFromBytes(koblitz.S256(), privKey[:]) 125 126 if adr != "" && strings.HasPrefix(adr, "ln1") && host == "" { 127 ipv4, _, err := lnutil.Lookup(adr, conf.Tracker, "") 128 if err != nil { 129 logging.Fatalf("Error looking up address on the tracker: %s", err) 130 } else { 131 adr = fmt.Sprintf("%s@%s", adr, ipv4) 132 } 133 } else { 134 adr = fmt.Sprintf("%s@%s:%d", adr, host, port) 135 } 136 137 lc.RPCClient, err = litrpc.NewLndcRpcClient(adr, key) 138 if err != nil { 139 logging.Fatal(err.Error()) 140 } 141 } 142 } 143 144 // for now just testing how to connect and get messages back and forth 145 func main() { 146 147 var err error 148 lc := new(litAfClient) 149 conf := litAfConfig{ 150 Con: defaultCon, 151 LitHomeDir: defaultLitHomeDirName, 152 Tracker: defaultTracker, 153 } 154 lc.litAfSetup(conf) // setup lit-af to start 155 156 rl, err := readline.NewEx(&readline.Config{ 157 Prompt: lnutil.Prompt("lit-af") + lnutil.White("# "), 158 HistoryFile: filepath.Join(conf.LitHomeDir, historyFilename), 159 AutoComplete: lc.NewAutoCompleter(), 160 }) 161 if err != nil { 162 logging.Fatal(err) 163 } 164 defer rl.Close() 165 166 // main shell loop 167 for { 168 // setup reader with max 4K input chars 169 msg, err := rl.Readline() 170 if err != nil { 171 break 172 } 173 msg = strings.TrimSpace(msg) 174 if len(msg) == 0 { 175 continue 176 } 177 rl.SaveHistory(msg) 178 179 cmdslice := strings.Fields(msg) // chop input up on whitespace 180 fmt.Fprintf(color.Output, "entered command: %s\n", msg) // immediate feedback 181 182 err = lc.Shellparse(cmdslice) 183 if err != nil { // only error should be user exit 184 logging.Fatal(err) 185 } 186 } 187 } 188 189 func (lc *litAfClient) Call(serviceMethod string, args interface{}, reply interface{}) error { 190 return lc.RPCClient.Call(serviceMethod, args, reply) 191 }