diff --git a/docs/tutorial/chat2.md b/docs/tutorial/chat2.md index 29780c57d..e8224a1fb 100644 --- a/docs/tutorial/chat2.md +++ b/docs/tutorial/chat2.md @@ -2,7 +2,12 @@ ## Background -The `chat2` application is a basic command-line chat app using the [Waku v2 suite of protocols](https://specs.vac.dev/specs/waku/v2/waku-v2). It connects to a [fleet of test nodes](fleets.status.im) to provide end-to-end p2p chat capabilities. The Waku team is currently using this application for internal testing. If you want try our protocols, or join the dogfooding fun, follow the instructions below. +The `chat2` application is a basic command-line chat app using the [Waku v2 suite of protocols](https://specs.vac.dev/specs/waku/v2/waku-v2). +It optionally connects to a [fleet of nodes](fleets.status.im) to provide end-to-end p2p chat capabilities. +Each fleet is a publicly accessible network of Waku v2 peers, providing a bootstrap connection point for new peers, historical message storage, etc. +The Waku team is currently using this application on the _production_ fleet for internal testing. +For more information on the available fleets, see [`Connecting to a Waku v2 fleet`](#connecting-to-a-waku-v2-fleet). +If you want try our protocols, or join the dogfooding fun, follow the instructions below. ## Preparation @@ -28,29 +33,47 @@ You should be prompted to provide a nickname for the chat session. Choose a nickname >> ``` -After entering a nickname, the app will randomly select and connect to a peer from the test fleet. +After entering a nickname, the app will randomly select and connect to a peer from the `prod` fleet. ``` -No static peers configured. Choosing one at random from test fleet... +No static peers configured. Choosing one at random from prod fleet... +``` + +It will then attempt to download historical messages from a random peer in the `prod` fleet. + +``` +Store enabled, but no store nodes configured. Choosing one at random from prod fleet... ``` Wait for the chat prompt (`>>`) and chat away! +To gracefully exit the `chat2` application, use the `/exit` [in-chat option](#in-chat-options) + +``` +>> /exit +quitting... +``` + ## Retrieving historical messages -The `chat2` application can retrieve historical chat messages from a node supporting and running the [Waku v2 store protocol](https://specs.vac.dev/specs/waku/v2/waku-store). Just specify the selected node's `multiaddr` as `storenode` when starting the app: +The `chat2` application can retrieve historical chat messages from a node supporting and running the [Waku v2 store protocol](https://specs.vac.dev/specs/waku/v2/waku-store), and will attempt to do so by default. +It's possible to query a *specific* store node by configuring its `multiaddr` as `storenode` when starting the app: ``` ./build/chat2 --storenode:/ip4/134.209.139.210/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ ``` -Alternatively, the `chat2` application will select a random `storenode` for you from the test fleet if `storenode` left unspecified. +Alternatively, the `chat2` application will select a random `storenode` for you from the configured fleet (`prod` by default) if `storenode` is left unspecified. ``` ./build/chat2 ``` -> *NOTE: Currently (Mar 3, 2021) the only node in the test fleet that provides reliable store functionality is `/ip4/134.209.139.210/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ`. We're working on fixing this.* +To disable historical message retrieval, use the `--store:false` option: + +``` +./build/chat2 --store:false +``` ## Specifying a static peer @@ -62,6 +85,31 @@ In order to connect to a *specific* node as [`relay`](https://specs.vac.dev/spec This will bypass the random peer selection process and connect to the specified node. +## Connecting to a Waku v2 fleet + +It is possible to specify a specific Waku v2 fleet to connect to when starting the app by using the `--fleet` option: + +``` +./build/chat2 --fleet:test +``` + +There are currently two fleets to select from, namely _production_ (`wakuv2.prod`) and _test_ (`wakuv2.test`). +The `test` fleet is updated with each incremental change to the `nim-waku` codebase. +As a result it may have more advanced and experimental features, but will be less stable than `prod`. +The `prod` fleet is a deployed network of the latest released Waku v2 nodes. +If no `fleet` is specified, `chat2` will connect to the `prod` fleet by default. +To start `chat2` without connecting to a fleet, use the `--fleet:none` option _or_ [specify a static peer](#specifying-a-static-peer). + +## Specifying a content topic + +To publish chat messages on a specific [content topic](https://rfc.vac.dev/spec/14/#wakumessage), use the `--content-topic` option: + +``` +./build/chat2 --content-topic:/waku/2/my-content-topic/proto +``` + +> **NOTE:** Currently (2021/05/26) the content topic defaults to `/waku/2/huilong/proto` if left unspecified, where `huilong` is the name of our latest testnet. + ## In-chat options | Command | Effect | @@ -83,7 +131,8 @@ message Chat2Message { } ``` -where `timestamp` is the Unix timestamp of the message, `nick` is the relevant `chat2` user's selected nickname and `payload` is the actual chat message being sent. The `payload` is the byte array representation of a UTF8 encoded string. +where `timestamp` is the Unix timestamp of the message, `nick` is the relevant `chat2` user's selected nickname and `payload` is the actual chat message being sent. +The `payload` is the byte array representation of a UTF8 encoded string. # Bridge messages between `chat2` and matterbridge diff --git a/examples/v2/chat2.nim b/examples/v2/chat2.nim index e7a778618..5860500d9 100644 --- a/examples/v2/chat2.nim +++ b/examples/v2/chat2.nim @@ -4,7 +4,7 @@ when not(compileOption("threads")): {.fatal: "Please, compile this program with the --threads:on option!".} -import std/[tables, strformat, strutils, times, httpclient, json, sequtils, random] +import std/[tables, strformat, strutils, times, httpclient, json, sequtils, random, options] import confutils, chronicles, chronos, stew/shims/net as stewNet, eth/keys, bearssl, stew/[byteutils, endians2], nimcrypto/pbkdf2 @@ -18,15 +18,16 @@ import libp2p/[switch, # manage transports, a single entry poi protocols/protocol, # define the protocol base type protocols/secure/secio, # define the protocol of secure input / output, allows encrypted communication that uses public keys to validate signed messages instead of a certificate authority like in TLS muxers/muxer] # define an interface for stream multiplexing, allowing peers to offer many protocols over a single connection -import ../../waku/v2/node/[config, wakunode2, waku_payload], +import ../../waku/v2/node/[wakunode2, waku_payload], ../../waku/v2/protocol/waku_message, ../../waku/v2/protocol/waku_store/waku_store, ../../waku/v2/protocol/waku_filter/waku_filter, ../../waku/v2/utils/peers, - ../../waku/common/utils/nat + ../../waku/common/utils/nat, + ./config_chat2 const Help = """ - Commands: /[?|help|connect|nick] + Commands: /[?|help|connect|nick|exit] help: Prints this help connect: dials a remote peer nick: change nickname for current chat session @@ -36,7 +37,6 @@ const Help = """ const PayloadV1* {.booldefine.} = false DefaultTopic* = "/waku/2/default-waku/proto" - DefaultContentTopic* = ContentTopic("dingpu") # XXX Connected is a bit annoying, because incoming connections don't trigger state change # Could poll connection pool or something here, I suppose @@ -49,6 +49,8 @@ type Chat = ref object started: bool # if the node has started nick: string # nickname for this chat session prompt: bool # chat prompt is showing + contentTopic: string # default content topic for chat messages + symkey: SymKey # SymKey used for v1 payload encryption (if enabled) type PrivateKey* = crypto.PrivateKey @@ -102,8 +104,6 @@ proc generateSymKey(contentTopic: ContentTopic): SymKey = symKey -let DefaultSymKey = generateSymKey(DefaultContentTopic) - proc connectToNodes(c: Chat, nodes: seq[string]) {.async.} = echo "Connecting to nodes" await c.node.connectToNodes(nodes) @@ -115,15 +115,15 @@ proc showChatPrompt(c: Chat) = stdout.flushFile() c.prompt = true -proc selectRandomNode(): string = +proc selectRandomNode(fleetStr: string): string = randomize() let - # Get latest fleet + # Get latest fleet addresses fleet = newHttpClient().getContent("https://fleets.status.im") - # Select the JSONObject corresponding to the wakuv2 test fleet and convert to seq of key-val pairs - nodes = toSeq(fleet.parseJson(){"fleets", "wakuv2.test", "waku"}.pairs()) + # Select the JSONObject corresponding to the selected wakuv2 fleet and convert to seq of key-val pairs + nodes = toSeq(fleet.parseJson(){"fleets", "wakuv2." & fleetStr, "waku"}.pairs()) - # Select a random node from the test fleet, convert to string and return + # Select a random node from the selected fleet, convert to string and return return nodes[rand(nodes.len - 1)].val.getStr() proc readNick(transp: StreamTransport): Future[string] {.async.} = @@ -141,19 +141,19 @@ proc publish(c: Chat, line: string) = when PayloadV1: # Use Waku v1 payload encoding/encryption let - payload = Payload(payload: chat2pb.buffer, symKey: some(DefaultSymKey)) + payload = Payload(payload: chat2pb.buffer, symKey: some(c.symKey)) version = 1'u32 encodedPayload = payload.encode(version, c.node.rng[]) if encodedPayload.isOk(): let message = WakuMessage(payload: encodedPayload.get(), - contentTopic: DefaultContentTopic, version: version) + contentTopic: c.contentTopic, version: version) asyncSpawn c.node.publish(DefaultTopic, message) else: warn "Payload encoding failed", error = encodedPayload.error else: # No payload encoding/encryption from Waku let message = WakuMessage(payload: chat2pb.buffer, - contentTopic: DefaultContentTopic, version: 0) + contentTopic: c.contentTopic, version: 0) asyncSpawn c.node.publish(DefaultTopic, message) # TODO This should read or be subscribe handler subscribe @@ -238,7 +238,7 @@ proc processInput(rfd: AsyncFD, rng: ref BrHmacDrbgContext) {.async.} = let transp = fromPipe(rfd) let - conf = WakuNodeConf.load() + conf = Chat2Conf.load() (extIp, extTcpPort, extUdpPort) = setupNat(conf.nat, clientId, Port(uint16(conf.tcpPort) + conf.portsShift), Port(uint16(conf.udpPort) + conf.portsShift)) @@ -255,15 +255,23 @@ proc processInput(rfd: AsyncFD, rng: ref BrHmacDrbgContext) {.async.} = let nick = await readNick(transp) echo "Welcome, " & nick & "!" - var chat = Chat(node: node, transp: transp, subscribed: true, connected: false, started: true, nick: nick, prompt: false) + var chat = Chat(node: node, + transp: transp, + subscribed: true, + connected: false, + started: true, + nick: nick, + prompt: false, + contentTopic: conf.contentTopic, + symKey: generateSymKey(conf.contentTopic)) if conf.staticnodes.len > 0: await connectToNodes(chat, conf.staticnodes) - else: + elif conf.fleet != Fleet.none: # Connect to at least one random fleet node - echo "No static peers configured. Choosing one at random from test fleet..." + echo "No static peers configured. Choosing one at random from " & $conf.fleet & " fleet..." - let randNode = selectRandomNode() + let randNode = selectRandomNode($conf.fleet) echo "Connecting to " & randNode @@ -279,29 +287,32 @@ proc processInput(rfd: AsyncFD, rng: ref BrHmacDrbgContext) {.async.} = if (conf.storenode != "") or (conf.store == true): node.mountStore(persistMessages = conf.persistMessages) - var storenode: string + var storenode: Option[string] if conf.storenode != "": - storenode = conf.storenode - else: - echo "Store enabled, but no store nodes configured. Choosing one at random from test fleet..." + storenode = some(conf.storenode) + elif conf.fleet != Fleet.none: + echo "Store enabled, but no store nodes configured. Choosing one at random from " & $conf.fleet & " fleet..." - storenode = selectRandomNode() + storenode = some(selectRandomNode($conf.fleet)) - echo "Connecting to storenode: " & storenode + echo "Connecting to storenode: " & storenode.get() - node.wakuStore.setPeer(parsePeerInfo(storenode)) + if storenode.isSome(): + # We have a viable storenode. Let's query it for historical messages. - proc storeHandler(response: HistoryResponse) {.gcsafe.} = - for msg in response.messages: - let - pb = Chat2Message.init(msg.payload) - chatLine = if pb.isOk: pb[].toString() - else: string.fromBytes(msg.payload) - echo &"{chatLine}" - info "Hit store handler" + node.wakuStore.setPeer(parsePeerInfo(storenode.get())) - await node.query(HistoryQuery(contentFilters: @[HistoryContentFilter(contentTopic: DefaultContentTopic)]), storeHandler) + proc storeHandler(response: HistoryResponse) {.gcsafe.} = + for msg in response.messages: + let + pb = Chat2Message.init(msg.payload) + chatLine = if pb.isOk: pb[].toString() + else: string.fromBytes(msg.payload) + echo &"{chatLine}" + info "Hit store handler" + + await node.query(HistoryQuery(contentFilters: @[HistoryContentFilter(contentTopic: chat.contentTopic)]), storeHandler) if conf.filternode != "": node.mountFilter() @@ -317,7 +328,7 @@ proc processInput(rfd: AsyncFD, rng: ref BrHmacDrbgContext) {.async.} = info "Hit filter handler" await node.subscribe( - FilterRequest(contentFilters: @[ContentFilter(contentTopic: DefaultContentTopic)], pubSubTopic: DefaultTopic, subscribe: true), + FilterRequest(contentFilters: @[ContentFilter(contentTopic: chat.contentTopic)], pubSubTopic: DefaultTopic, subscribe: true), filterHandler ) @@ -331,7 +342,7 @@ proc processInput(rfd: AsyncFD, rng: ref BrHmacDrbgContext) {.async.} = when PayloadV1: # Use Waku v1 payload encoding/encryption let - keyInfo = KeyInfo(kind: Symmetric, symKey: DefaultSymKey) + keyInfo = KeyInfo(kind: Symmetric, symKey: chat.symKey) decodedPayload = decodePayload(decoded.get(), keyInfo) if decodedPayload.isOK(): diff --git a/examples/v2/config_chat2.nim b/examples/v2/config_chat2.nim new file mode 100644 index 000000000..559b54fb1 --- /dev/null +++ b/examples/v2/config_chat2.nim @@ -0,0 +1,229 @@ +import + std/strutils, + confutils, confutils/defs, confutils/std/net, + chronicles, chronos, + libp2p/crypto/crypto, + libp2p/crypto/secp, + nimcrypto/utils, + eth/keys + +type + Fleet* = enum + none + prod + test + + Chat2Conf* = object + ## General node config + + logLevel* {. + desc: "Sets the log level." + defaultValue: LogLevel.INFO + name: "log-level" }: LogLevel + + nodekey* {. + desc: "P2P node private key as 64 char hex string.", + defaultValue: crypto.PrivateKey.random(Secp256k1, keys.newRng()[]).tryGet() + name: "nodekey" }: crypto.PrivateKey + + listenAddress* {. + defaultValue: defaultListenAddress(config) + desc: "Listening address for the LibP2P traffic." + name: "listen-address"}: ValidIpAddress + + tcpPort* {. + desc: "TCP listening port." + defaultValue: 60000 + name: "tcp-port" }: Port + + udpPort* {. + desc: "UDP listening port." + defaultValue: 60000 + name: "udp-port" }: Port + + portsShift* {. + desc: "Add a shift to all port numbers." + defaultValue: 0 + name: "ports-shift" }: uint16 + + nat* {. + desc: "Specify method to use for determining public address. " & + "Must be one of: any, none, upnp, pmp, extip:." + defaultValue: "any" }: string + + ## Persistence config + + dbPath* {. + desc: "The database path for peristent storage", + defaultValue: "" + name: "db-path" }: string + + persistPeers* {. + desc: "Enable peer persistence: true|false", + defaultValue: false + name: "persist-peers" }: bool + + persistMessages* {. + desc: "Enable message persistence: true|false", + defaultValue: false + name: "persist-messages" }: bool + + ## Relay config + + relay* {. + desc: "Enable relay protocol: true|false", + defaultValue: true + name: "relay" }: bool + + rlnRelay* {. + desc: "Enable spam protection through rln-relay: true|false", + defaultValue: false + name: "rln-relay" }: bool + + staticnodes* {. + desc: "Peer multiaddr to directly connect with. Argument may be repeated." + name: "staticnode" }: seq[string] + + keepAlive* {. + desc: "Enable keep-alive for idle connections: true|false", + defaultValue: false + name: "keep-alive" }: bool + + topics* {. + desc: "Default topics to subscribe to (space separated list)." + defaultValue: "/waku/2/default-waku/proto" + name: "topics" .}: string + + ## Store config + + store* {. + desc: "Enable store protocol: true|false", + defaultValue: true + name: "store" }: bool + + storenode* {. + desc: "Peer multiaddr to query for storage.", + defaultValue: "" + name: "storenode" }: string + + ## Filter config + + filter* {. + desc: "Enable filter protocol: true|false", + defaultValue: false + name: "filter" }: bool + + filternode* {. + desc: "Peer multiaddr to request content filtering of messages.", + defaultValue: "" + name: "filternode" }: string + + ## Swap config + + swap* {. + desc: "Enable swap protocol: true|false", + defaultValue: true + name: "swap" }: bool + + ## Lightpush config + + lightpush* {. + desc: "Enable lightpush protocol: true|false", + defaultValue: false + name: "lightpush" }: bool + + ## JSON-RPC config + + rpc* {. + desc: "Enable Waku JSON-RPC server: true|false", + defaultValue: true + name: "rpc" }: bool + + rpcAddress* {. + desc: "Listening address of the JSON-RPC server.", + defaultValue: ValidIpAddress.init("127.0.0.1") + name: "rpc-address" }: ValidIpAddress + + rpcPort* {. + desc: "Listening port of the JSON-RPC server.", + defaultValue: 8545 + name: "rpc-port" }: uint16 + + rpcAdmin* {. + desc: "Enable access to JSON-RPC Admin API: true|false", + defaultValue: false + name: "rpc-admin" }: bool + + rpcPrivate* {. + desc: "Enable access to JSON-RPC Private API: true|false", + defaultValue: false + name: "rpc-private" }: bool + + ## Metrics config + + metricsServer* {. + desc: "Enable the metrics server: true|false" + defaultValue: false + name: "metrics-server" }: bool + + metricsServerAddress* {. + desc: "Listening address of the metrics server." + defaultValue: ValidIpAddress.init("127.0.0.1") + name: "metrics-server-address" }: ValidIpAddress + + metricsServerPort* {. + desc: "Listening HTTP port of the metrics server." + defaultValue: 8008 + name: "metrics-server-port" }: uint16 + + metricsLogging* {. + desc: "Enable metrics logging: true|false" + defaultValue: false + name: "metrics-logging" }: bool + + ## Chat2 configuration + + fleet* {. + desc: "Select the fleet to connect to." + defaultValue: Fleet.prod + name: "fleet" }: Fleet + + contentTopic* {. + desc: "Content topic for chat messages." + defaultValue: "/waku/2/huilong/proto" + name: "content-topic" }: string + +# NOTE: Keys are different in nim-libp2p +proc parseCmdArg*(T: type crypto.PrivateKey, p: TaintedString): T = + try: + let key = SkPrivateKey.init(utils.fromHex(p)).tryGet() + # XXX: Here at the moment + result = crypto.PrivateKey(scheme: Secp256k1, skkey: key) + except CatchableError as e: + raise newException(ConfigurationError, "Invalid private key") + +proc completeCmdArg*(T: type crypto.PrivateKey, val: TaintedString): seq[string] = + return @[] + +proc parseCmdArg*(T: type ValidIpAddress, p: TaintedString): T = + try: + result = ValidIpAddress.init(p) + except CatchableError as e: + raise newException(ConfigurationError, "Invalid IP address") + +proc completeCmdArg*(T: type ValidIpAddress, val: TaintedString): seq[string] = + return @[] + +proc parseCmdArg*(T: type Port, p: TaintedString): T = + try: + result = Port(parseInt(p)) + except CatchableError as e: + raise newException(ConfigurationError, "Invalid Port number") + +proc completeCmdArg*(T: type Port, val: TaintedString): seq[string] = + return @[] + +func defaultListenAddress*(conf: Chat2Conf): ValidIpAddress = + # TODO: How should we select between IPv4 and IPv6 + # Maybe there should be a config option for this. + (static ValidIpAddress.init("0.0.0.0")) \ No newline at end of file diff --git a/examples/v2/matterbridge/chat2bridge.nim b/examples/v2/matterbridge/chat2bridge.nim index 9a6f3874a..d1a221b09 100644 --- a/examples/v2/matterbridge/chat2bridge.nim +++ b/examples/v2/matterbridge/chat2bridge.nim @@ -28,7 +28,6 @@ logScope: const DefaultTopic* = chat2.DefaultTopic - DefaultContentTopic* = chat2.DefaultContentTopic DeduplQSize = 20 # Maximum number of seen messages to keep in deduplication queue ######### @@ -42,6 +41,7 @@ type running: bool pollPeriod: chronos.Duration seen: seq[Hash] #FIFO queue + contentTopic: string MbMessageHandler* = proc (jsonNode: JsonNode) {.gcsafe.} @@ -61,7 +61,7 @@ proc containsOrAdd(sequence: var seq[Hash], hash: Hash): bool = return false -proc toWakuMessage(jsonNode: JsonNode): WakuMessage = +proc toWakuMessage(cmb: Chat2MatterBridge, jsonNode: JsonNode): WakuMessage = # Translates a Matterbridge API JSON response to a Waku v2 message let msgFields = jsonNode.getFields() @@ -72,11 +72,11 @@ proc toWakuMessage(jsonNode: JsonNode): WakuMessage = payload: msgFields["text"].getStr().toBytes()).encode() WakuMessage(payload: chat2pb.buffer, - contentTopic: DefaultContentTopic, + contentTopic: cmb.contentTopic, version: 0) proc toChat2(cmb: Chat2MatterBridge, jsonNode: JsonNode) {.async.} = - let msg = jsonNode.toWakuMessage() + let msg = cmb.toWakuMessage(jsonNode) if cmb.seen.containsOrAdd(msg.payload.hash()): # This is a duplicate message. Return. @@ -131,7 +131,8 @@ proc new*(T: type Chat2MatterBridge, # NodeV2 initialisation nodev2Key: crypto.PrivateKey, nodev2BindIp: ValidIpAddress, nodev2BindPort: Port, - nodev2ExtIp = none[ValidIpAddress](), nodev2ExtPort = none[Port]()): T = + nodev2ExtIp = none[ValidIpAddress](), nodev2ExtPort = none[Port](), + contentTopic: string): T = # Setup Matterbridge let @@ -152,7 +153,11 @@ proc new*(T: type Chat2MatterBridge, nodev2BindIp, nodev2BindPort, nodev2ExtIp, nodev2ExtPort) - return Chat2MatterBridge(mbClient: mbClient, nodev2: nodev2, running: false, pollPeriod: chronos.seconds(1)) + return Chat2MatterBridge(mbClient: mbClient, + nodev2: nodev2, + running: false, + pollPeriod: chronos.seconds(1), + contentTopic: contentTopic) proc start*(cmb: Chat2MatterBridge) {.async.} = info "Starting Chat2MatterBridge" @@ -237,7 +242,8 @@ when isMainModule: mbGateway = conf.mbGateway, nodev2Key = conf.nodekey, nodev2BindIp = conf.listenAddress, nodev2BindPort = Port(uint16(conf.libp2pTcpPort) + conf.portsShift), - nodev2ExtIp = nodev2ExtIp, nodev2ExtPort = nodev2ExtPort) + nodev2ExtIp = nodev2ExtIp, nodev2ExtPort = nodev2ExtPort, + contentTopic = conf.contentTopic) waitFor bridge.start() diff --git a/examples/v2/matterbridge/config_chat2bridge.nim b/examples/v2/matterbridge/config_chat2bridge.nim index 70072edd3..783343b2f 100644 --- a/examples/v2/matterbridge/config_chat2bridge.nim +++ b/examples/v2/matterbridge/config_chat2bridge.nim @@ -122,6 +122,13 @@ type defaultValue: "gateway1" name: "mb-gateway" }: string + ## Chat2 options + + contentTopic* {. + desc: "Content topic to bridge chat messages to." + defaultValue: "/waku/2/huilong/proto" + name: "content-topic" }: string + proc parseCmdArg*(T: type keys.KeyPair, p: TaintedString): T = try: let privkey = keys.PrivateKey.fromHex(string(p)).tryGet() diff --git a/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool b/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool index d02540088..7f6b7f520 100755 --- a/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool +++ b/vendor/nim-libbacktrace/vendor/libbacktrace-upstream/libtool @@ -2,7 +2,7 @@ # libtool - Provide generalized library-building support services. # Generated automatically by config.status (libbacktrace) version-unused -# Libtool was configured on host fv-az278-250: +# Libtool was configured on host fv-az129-444: # NOTE: Changes made to this file will be lost: look at ltmain.sh. # # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,