LXMF/LXMF/Utilities/lxmd.py

795 lines
33 KiB
Python

#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
DEFFERED_JOBS_DELAY = 10
JOBS_INTERVAL = 5
import RNS
import LXMF
import argparse
import threading
import subprocess
import shlex
import time
import os
from LXMF._version import __version__
from LXMF import APP_NAME
from RNS.vendor.configobj import ConfigObj
configpath = None
ignoredpath = None
identitypath = None
storagedir = None
lxmdir = None
targetloglevel = None
identity = None
lxmd_config = None
message_router = None
lxmf_destination = None
active_configuration = {}
last_peer_announce = None
last_node_announce = None
def create_default_config(configpath):
lxmd_config = ConfigObj(__default_lxmd_config__.splitlines())
lxmd_config.filename = configpath
lxmd_config.write()
def apply_config():
global active_configuration, targetloglevel
try:
# Load peer settings
if "lxmf" in lxmd_config and "display_name" in lxmd_config["lxmf"]:
active_configuration["display_name"] = lxmd_config["lxmf"]["display_name"]
else:
active_configuration["display_name"] = "Anonymous Peer"
if "lxmf" in lxmd_config and "announce_at_start" in lxmd_config["lxmf"]:
active_configuration["peer_announce_at_start"] = lxmd_config["lxmf"].as_bool("announce_at_start")
else:
active_configuration["peer_announce_at_start"] = False
if "lxmf" in lxmd_config and "announce_interval" in lxmd_config["lxmf"]:
active_configuration["peer_announce_interval"] = lxmd_config["lxmf"].as_int("announce_interval")*60
else:
active_configuration["peer_announce_interval"] = None
if "lxmf" in lxmd_config and "delivery_transfer_max_accepted_size" in lxmd_config["lxmf"]:
active_configuration["delivery_transfer_max_accepted_size"] = lxmd_config["lxmf"].as_float("delivery_transfer_max_accepted_size")
if active_configuration["delivery_transfer_max_accepted_size"] < 0.38:
active_configuration["delivery_transfer_max_accepted_size"] = 0.38
else:
active_configuration["delivery_transfer_max_accepted_size"] = 1000
if "lxmf" in lxmd_config and "on_inbound" in lxmd_config["lxmf"]:
active_configuration["on_inbound"] = lxmd_config["lxmf"]["on_inbound"]
else:
active_configuration["on_inbound"] = None
# Load propagation node settings
if "propagation" in lxmd_config and "enable_node" in lxmd_config["propagation"]:
active_configuration["enable_propagation_node"] = lxmd_config["propagation"].as_bool("enable_node")
else:
active_configuration["enable_propagation_node"] = False
if "propagation" in lxmd_config and "auth_required" in lxmd_config["propagation"]:
active_configuration["auth_required"] = lxmd_config["propagation"].as_bool("auth_required")
else:
active_configuration["auth_required"] = False
if "propagation" in lxmd_config and "announce_at_start" in lxmd_config["propagation"]:
active_configuration["node_announce_at_start"] = lxmd_config["propagation"].as_bool("announce_at_start")
else:
active_configuration["node_announce_at_start"] = False
if "propagation" in lxmd_config and "autopeer" in lxmd_config["propagation"]:
active_configuration["autopeer"] = lxmd_config["propagation"].as_bool("autopeer")
else:
active_configuration["autopeer"] = True
if "propagation" in lxmd_config and "autopeer_maxdepth" in lxmd_config["propagation"]:
active_configuration["autopeer_maxdepth"] = lxmd_config["propagation"].as_int("autopeer_maxdepth")
else:
active_configuration["autopeer_maxdepth"] = None
if "propagation" in lxmd_config and "announce_interval" in lxmd_config["propagation"]:
active_configuration["node_announce_interval"] = lxmd_config["propagation"].as_int("announce_interval")*60
else:
active_configuration["node_announce_interval"] = None
if "propagation" in lxmd_config and "message_storage_limit" in lxmd_config["propagation"]:
active_configuration["message_storage_limit"] = lxmd_config["propagation"].as_float("message_storage_limit")
if active_configuration["message_storage_limit"] < 0.005:
active_configuration["message_storage_limit"] = 0.005
else:
active_configuration["message_storage_limit"] = 500
if "propagation" in lxmd_config and "propagation_transfer_max_accepted_size" in lxmd_config["propagation"]:
active_configuration["propagation_transfer_max_accepted_size"] = lxmd_config["propagation"].as_float("propagation_transfer_max_accepted_size")
if active_configuration["propagation_transfer_max_accepted_size"] < 0.38:
active_configuration["propagation_transfer_max_accepted_size"] = 0.38
else:
active_configuration["propagation_transfer_max_accepted_size"] = 256
if "propagation" in lxmd_config and "prioritise_destinations" in lxmd_config["propagation"]:
active_configuration["prioritised_lxmf_destinations"] = lxmd_config["propagation"].as_list("prioritise_destinations")
else:
active_configuration["prioritised_lxmf_destinations"] = []
if "propagation" in lxmd_config and "static_peers" in lxmd_config["propagation"]:
static_peers = lxmd_config["propagation"].as_list("static_peers")
active_configuration["static_peers"] = []
for static_peer in static_peers:
active_configuration["static_peers"].append(bytes.fromhex(static_peer))
else:
active_configuration["static_peers"] = []
if "propagation" in lxmd_config and "max_peers" in lxmd_config["propagation"]:
active_configuration["max_peers"] = lxmd_config["propagation"].as_int("max_peers")
else:
active_configuration["max_peers"] = None
if "propagation" in lxmd_config and "from_static_only" in lxmd_config["propagation"]:
active_configuration["from_static_only"] = lxmd_config["propagation"].as_bool("from_static_only")
else:
active_configuration["from_static_only"] = False
# Load various settings
if "logging" in lxmd_config and "loglevel" in lxmd_config["logging"]:
targetloglevel = lxmd_config["logging"].as_int("loglevel")
active_configuration["ignored_lxmf_destinations"] = []
if os.path.isfile(ignoredpath):
try:
fh = open(ignoredpath, "rb")
ignored_input = fh.read()
fh.close()
ignored_hash_strs = ignored_input.splitlines()
for hash_str in ignored_hash_strs:
if len(hash_str) == RNS.Identity.TRUNCATED_HASHLENGTH//8*2:
try:
ignored_hash = bytes.fromhex(hash_str.decode("utf-8"))
active_configuration["ignored_lxmf_destinations"].append(ignored_hash)
except Exception as e:
RNS.log("Could not decode hash from: "+str(hash_str), RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
except Exception as e:
RNS.log("Error while loading list of ignored destinations: "+str(e), RNS.LOG_ERROR)
active_configuration["allowed_identities"] = []
if os.path.isfile(allowedpath):
try:
fh = open(allowedpath, "rb")
allowed_input = fh.read()
fh.close()
allowed_hash_strs = allowed_input.splitlines()
for hash_str in allowed_hash_strs:
if len(hash_str) == RNS.Identity.TRUNCATED_HASHLENGTH//8*2:
try:
allowed_hash = bytes.fromhex(hash_str.decode("utf-8"))
active_configuration["allowed_identities"].append(allowed_hash)
except Exception as e:
RNS.log("Could not decode hash from: "+str(hash_str), RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
except Exception as e:
RNS.log("Error while loading list of allowed identities: "+str(e), RNS.LOG_ERROR)
except Exception as e:
RNS.log("Could not apply LXM Daemon configuration. The contained exception was: "+str(e), RNS.LOG_ERROR)
raise e
exit(3)
def lxmf_delivery(lxm):
global active_configuration, lxmdir
try:
written_path = lxm.write_to_directory(lxmdir)
RNS.log("Received "+str(lxm)+" written to "+str(written_path), RNS.LOG_DEBUG)
if active_configuration["on_inbound"]:
RNS.log("Calling external program to handle message", RNS.LOG_DEBUG)
command = active_configuration["on_inbound"]
processing_command = command+" \""+written_path+"\""
return_code = subprocess.call(shlex.split(processing_command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
else:
RNS.log("No action defined for inbound messages, ignoring", RNS.LOG_DEBUG)
except Exception as e:
RNS.log("Error occurred while processing received message "+str(lxm)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def program_setup(configdir = None, rnsconfigdir = None, run_pn = False, on_inbound = None, verbosity = 0, quietness = 0, service = False):
global configpath, ignoredpath, identitypath, allowedpath, storagedir, lxmdir
global lxmd_config, active_configuration, targetloglevel
global message_router, lxmf_destination
if service:
targetlogdest = RNS.LOG_FILE
targetloglevel = None
else:
targetlogdest = RNS.LOG_STDOUT
# Get configuration
if configdir == None:
if os.path.isdir("/etc/lxmd") and os.path.isfile("/etc/lxmd/config"):
configdir = "/etc/lxmd"
elif os.path.isdir(RNS.Reticulum.userdir+"/.config/lxmd") and os.path.isfile(Reticulum.userdir+"/.config/lxmd/config"):
configdir = RNS.Reticulum.userdir+"/.config/lxmd"
else:
configdir = RNS.Reticulum.userdir+"/.lxmd"
configpath = configdir+"/config"
ignoredpath = configdir+"/ignored"
allowedpath = configdir+"/allowed"
identitypath = configdir+"/identity"
storagedir = configdir+"/storage"
lxmdir = storagedir+"/messages"
if not os.path.isdir(storagedir):
os.makedirs(storagedir)
if not os.path.isdir(lxmdir):
os.makedirs(lxmdir)
if not os.path.isfile(configpath):
RNS.log("Could not load config file, creating default configuration file...")
create_default_config(configpath)
RNS.log("Default config file created. Make any necessary changes in "+configpath+" and restart lxmd if needed.")
time.sleep(1.5)
if os.path.isfile(configpath):
try:
lxmd_config = ConfigObj(configpath)
except Exception as e:
RNS.log("Could not parse the configuration at "+configpath, RNS.LOG_ERROR)
RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
RNS.panic()
apply_config()
RNS.log("Configuration loaded from "+configpath, RNS.LOG_VERBOSE)
if targetloglevel == None:
targetloglevel = 3
if verbosity != 0 or quietness != 0:
targetloglevel = targetloglevel+verbosity-quietness
# Start Reticulum
RNS.log("Substantiating Reticulum...")
reticulum = RNS.Reticulum(configdir=rnsconfigdir, loglevel=targetloglevel, logdest=targetlogdest)
# Generate or load primary identity
if os.path.isfile(identitypath):
try:
identity = RNS.Identity.from_file(identitypath)
if identity != None:
RNS.log("Loaded Primary Identity %s" % (str(identity)))
else:
RNS.log("Could not load the Primary Identity from "+identitypath, RNS.LOG_ERROR)
exit(4)
except Exception as e:
RNS.log("Could not load the Primary Identity from "+identitypath, RNS.LOG_ERROR)
RNS.log("The contained exception was: %s" % (str(e)), RNS.LOG_ERROR)
exit(1)
else:
try:
RNS.log("No Primary Identity file found, creating new...")
identity = RNS.Identity()
identity.to_file(identitypath)
RNS.log("Created new Primary Identity %s" % (str(identity)))
except Exception as e:
RNS.log("Could not create and save a new Primary Identity", RNS.LOG_ERROR)
RNS.log("The contained exception was: %s" % (str(e)), RNS.LOG_ERROR)
exit(2)
# Start LXMF
message_router = LXMF.LXMRouter(
identity = identity,
storagepath = storagedir,
autopeer = active_configuration["autopeer"],
autopeer_maxdepth = active_configuration["autopeer_maxdepth"],
propagation_limit = active_configuration["propagation_transfer_max_accepted_size"],
delivery_limit = active_configuration["delivery_transfer_max_accepted_size"],
max_peers = active_configuration["max_peers"],
static_peers = active_configuration["static_peers"],
from_static_only = active_configuration["from_static_only"])
message_router.register_delivery_callback(lxmf_delivery)
for destination_hash in active_configuration["ignored_lxmf_destinations"]:
message_router.ignore_destination(destination_hash)
lxmf_destination = message_router.register_delivery_identity(identity, display_name=active_configuration["display_name"])
RNS.Identity.remember(
packet_hash=None,
destination_hash=lxmf_destination.hash,
public_key=identity.get_public_key(),
app_data=None
)
# Set up authentication
if active_configuration["auth_required"]:
message_router.set_authentication(required=True)
if len(active_configuration["allowed_identities"]) == 0:
RNS.log("Clint authentication was enabled, but no identity hashes could be loaded from "+str(allowedpath)+". Nobody will be able to sync messages from this propagation node.", RNS.LOG_WARNING)
for identity_hash in active_configuration["allowed_identities"]:
message_router.allow(identity_hash)
RNS.log("LXMF Router ready to receive on "+RNS.prettyhexrep(lxmf_destination.hash))
if run_pn or active_configuration["enable_propagation_node"]:
message_router.set_message_storage_limit(megabytes=active_configuration["message_storage_limit"])
for dest_str in active_configuration["prioritised_lxmf_destinations"]:
try:
dest_hash = bytes.fromhex(dest_str)
if len(dest_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8:
message_router.prioritise(dest_hash)
except Exception as e:
RNS.log("Cannot prioritise "+str(dest_str)+", it is not a valid destination hash", RNS.LOG_ERROR)
message_router.enable_propagation()
RNS.log("LXMF Propagation Node started on "+RNS.prettyhexrep(message_router.propagation_destination.hash))
RNS.log("Started lxmd version {version}".format(version=__version__), RNS.LOG_NOTICE)
threading.Thread(target=deferred_start_jobs, daemon=True).start()
while True:
time.sleep(1)
def jobs():
global active_configuration, last_peer_announce, last_node_announce
global message_router, lxmf_destination
while True:
try:
if "peer_announce_interval" in active_configuration and active_configuration["peer_announce_interval"] != None:
if time.time() > last_peer_announce + active_configuration["peer_announce_interval"]:
RNS.log("Sending announce for LXMF delivery destination", RNS.LOG_VERBOSE)
message_router.announce(lxmf_destination.hash)
last_peer_announce = time.time()
if "node_announce_interval" in active_configuration and active_configuration["node_announce_interval"] != None:
if time.time() > last_node_announce + active_configuration["node_announce_interval"]:
RNS.log("Sending announce for LXMF Propagation Node", RNS.LOG_VERBOSE)
message_router.announce_propagation_node()
last_node_announce = time.time()
except Exception as e:
RNS.log("An error occurred while running periodic jobs. The contained exception was: "+str(e), RNS.LOG_ERROR)
time.sleep(JOBS_INTERVAL)
def deferred_start_jobs():
global active_configuration, last_peer_announce, last_node_announce
global message_router, lxmf_destination
time.sleep(DEFFERED_JOBS_DELAY)
RNS.log("Running deferred start jobs", RNS.LOG_DEBUG)
if active_configuration["peer_announce_at_start"]:
RNS.log("Sending announce for LXMF delivery destination", RNS.LOG_EXTREME)
message_router.announce(lxmf_destination.hash)
if active_configuration["node_announce_at_start"]:
RNS.log("Sending announce for LXMF Propagation Node", RNS.LOG_EXTREME)
message_router.announce_propagation_node()
last_peer_announce = time.time()
last_node_announce = time.time()
threading.Thread(target=jobs, daemon=True).start()
def query_status(identity, timeout=5, exit_on_fail=False):
control_destination = RNS.Destination(identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "propagation", "control")
timeout = time.time()+timeout
def check_timeout():
if time.time() > timeout:
if exit_on_fail:
RNS.log("Getting lxmd statistics timed out, exiting now", RNS.LOG_ERROR)
exit(200)
else:
return LXMF.LXMPeer.LXMPeer.ERROR_TIMEOUT
else:
time.sleep(0.1)
if not RNS.Transport.has_path(control_destination.hash):
RNS.Transport.request_path(control_destination.hash)
while not RNS.Transport.has_path(control_destination.hash):
tc = check_timeout()
if tc:
return tc
link = RNS.Link(control_destination)
while not link.status == RNS.Link.ACTIVE:
tc = check_timeout()
if tc:
return tc
link.identify(identity)
request_receipt = link.request(LXMF.LXMRouter.STATS_GET_PATH, data=None, response_callback=None, failed_callback=None)
while not request_receipt.get_status() == RNS.RequestReceipt.READY:
tc = check_timeout()
if tc:
return tc
link.teardown()
return request_receipt.get_response()
def get_status(configdir = None, rnsconfigdir = None, verbosity = 0, quietness = 0, timeout=5, show_status=False, show_peers=False, identity_path=None):
global configpath, identitypath, storagedir, lxmdir
global lxmd_config, active_configuration, targetloglevel
targetlogdest = RNS.LOG_STDOUT
if identity_path == None:
if configdir == None:
if os.path.isdir("/etc/lxmd") and os.path.isfile("/etc/lxmd/config"):
configdir = "/etc/lxmd"
elif os.path.isdir(RNS.Reticulum.userdir+"/.config/lxmd") and os.path.isfile(Reticulum.userdir+"/.config/lxmd/config"):
configdir = RNS.Reticulum.userdir+"/.config/lxmd"
else:
configdir = RNS.Reticulum.userdir+"/.lxmd"
configpath = configdir+"/config"
identitypath = configdir+"/identity"
identity = None
if not os.path.isdir(configdir):
RNS.log("Specified configuration directory does not exist, exiting now", RNS.LOG_ERROR)
exit(201)
if not os.path.isfile(identitypath):
RNS.log("Identity file not found in specified configuration directory, exiting now", RNS.LOG_ERROR)
exit(202)
else:
identity = RNS.Identity.from_file(identitypath)
if identity == None:
RNS.log("Could not load the Primary Identity from "+identitypath, RNS.LOG_ERROR)
exit(4)
else:
if not os.path.isfile(identity_path):
RNS.log("Identity file not found in specified configuration directory, exiting now", RNS.LOG_ERROR)
exit(202)
else:
identity = RNS.Identity.from_file(identity_path)
if identity == None:
RNS.log("Could not load the Primary Identity from "+identity_path, RNS.LOG_ERROR)
exit(4)
if targetloglevel == None:
targetloglevel = 3
if verbosity != 0 or quietness != 0:
targetloglevel = targetloglevel+verbosity-quietness
reticulum = RNS.Reticulum(configdir=rnsconfigdir, loglevel=targetloglevel, logdest=targetlogdest)
response = query_status(identity, timeout=timeout, exit_on_fail=True)
if response == LXMF.LXMPeer.LXMPeer.ERROR_NO_IDENTITY:
RNS.log("Remote received no identity")
exit(203)
if response == LXMF.LXMPeer.LXMPeer.ERROR_NO_ACCESS:
RNS.log("Access denied")
exit(204)
else:
s = response
mutil = round((s["messagestore"]["bytes"]/s["messagestore"]["limit"])*100, 2)
ms_util = f"{mutil}%"
if s["from_static_only"]:
who_str = "static peers only"
else:
who_str = "all nodes"
available_peers = 0
unreachable_peers = 0
peered_incoming = 0
peered_outgoing = 0
peered_rx_bytes = 0
peered_tx_bytes = 0
for peer_id in s["peers"]:
p = s["peers"][peer_id]
pm = p["messages"]
peered_incoming += pm["incoming"]
peered_outgoing += pm["outgoing"]
peered_rx_bytes += p["rx_bytes"]
peered_tx_bytes += p["tx_bytes"]
if p["alive"]:
available_peers += 1
else:
unreachable_peers += 1
total_incoming = peered_incoming+s["unpeered_propagation_incoming"]+s["clients"]["client_propagation_messages_received"]
total_rx_bytes = peered_rx_bytes+s["unpeered_propagation_rx_bytes"]
df = round(peered_outgoing/total_incoming, 2)
dhs = RNS.prettyhexrep(s["destination_hash"]); uts = RNS.prettytime(s["uptime"])
print(f"\nLXMF Propagation Node running on {dhs}, uptime is {uts}")
if show_status:
msb = RNS.prettysize(s["messagestore"]["bytes"]); msl = RNS.prettysize(s["messagestore"]["limit"])
ptl = RNS.prettysize(s["propagation_limit"]*1000); uprx = RNS.prettysize(s["unpeered_propagation_rx_bytes"])
mscnt = s["messagestore"]["count"]; stp = s["total_peers"]; smp = s["max_peers"]; sdp = s["discovered_peers"]
ssp = s["static_peers"]; cprr = s["clients"]["client_propagation_messages_received"]
cprs = s["clients"]["client_propagation_messages_served"]; upi = s["unpeered_propagation_incoming"]
print(f"Messagestore contains {mscnt} messages, {msb} ({ms_util} utilised of {msl})")
print(f"Accepting propagated messages from {who_str}, {ptl} per-transfer limit")
print(f"")
print(f"Peers : {stp} total (peer limit is {smp})")
print(f" {sdp} discovered, {ssp} static")
print(f" {available_peers} available, {unreachable_peers} unreachable")
print(f"")
print(f"Traffic : {total_incoming} messages received in total ({RNS.prettysize(total_rx_bytes)})")
print(f" {peered_incoming} messages received from peered nodes ({RNS.prettysize(peered_rx_bytes)})")
print(f" {upi} messages received from unpeered nodes ({uprx})")
print(f" {peered_outgoing} messages transferred to peered nodes ({RNS.prettysize(peered_tx_bytes)})")
print(f" {cprr} propagation messages received directly from clients")
print(f" {cprs} propagation messages served to clients")
print(f" Distribution factor is {df}")
print(f"")
if show_peers:
if not show_status:
print("")
for peer_id in s["peers"]:
ind = " "
p = s["peers"][peer_id]
if p["type"] == "static":
t = "Static peer "
elif p["type"] == "discovered":
t = "Discovered peer "
else:
t = "Unknown peer "
a = "Available" if p["alive"] == True else "Unreachable"
h = max(time.time()-p["last_heard"], 0)
hops = p["network_distance"]
hs = "hops unknown" if hops == RNS.Transport.PATHFINDER_M else f"{hops} hop away" if hops == 1 else f"{hops} hops away"
pm = p["messages"]
if p["last_sync_attempt"] != 0:
lsa = p["last_sync_attempt"]
ls = f"last synced {RNS.prettytime(max(time.time()-lsa, 0))} ago"
else:
ls = "never synced"
sstr = RNS.prettyspeed(p["str"]); sler = RNS.prettyspeed(p["ler"]); stl = RNS.prettysize(p["transfer_limit"]*1000)
srxb = RNS.prettysize(p["rx_bytes"]); stxb = RNS.prettysize(p["tx_bytes"]); pmo = pm["offered"]; pmout = pm["outgoing"]
pmi = pm["incoming"]; pmuh = pm["unhandled"]
print(f"{ind}{t}{RNS.prettyhexrep(peer_id)}")
print(f"{ind*2}Status : {a}, {hs}, last heard {RNS.prettytime(h)} ago")
print(f"{ind*2}Speeds : {sstr} STR, {sler} LER, {stl} transfer limit")
print(f"{ind*2}Messages : {pmo} offered, {pmout} outgoing, {pmi} incoming")
print(f"{ind*2}Traffic : {srxb} received, {stxb} sent")
ms = "" if pm["unhandled"] == 1 else "s"
print(f"{ind*2}Sync state : {pmuh} unhandled message{ms}, {ls}")
print("")
def main():
try:
parser = argparse.ArgumentParser(description="Lightweight Extensible Messaging Daemon")
parser.add_argument("--config", action="store", default=None, help="path to alternative lxmd config directory", type=str)
parser.add_argument("--rnsconfig", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
parser.add_argument("-p", "--propagation-node", action="store_true", default=False, help="run an LXMF Propagation Node")
parser.add_argument("-i", "--on-inbound", action="store", metavar="PATH", default=None, help="executable to run when a message is received", type=str)
parser.add_argument("-v", "--verbose", action="count", default=0)
parser.add_argument("-q", "--quiet", action="count", default=0)
parser.add_argument("-s", "--service", action="store_true", default=False, help="lxmd is running as a service and should log to file")
parser.add_argument("--status", action="store_true", default=False, help="display node status")
parser.add_argument("--peers", action="store_true", default=False, help="display peered nodes")
parser.add_argument("--timeout", action="store", default=5, help="timeout in seconds for query operations", type=float)
parser.add_argument("--identity", action="store", default=None, help="path to identity used for query request", type=str)
parser.add_argument("--exampleconfig", action="store_true", default=False, help="print verbose configuration example to stdout and exit")
parser.add_argument("--version", action="version", version="lxmd {version}".format(version=__version__))
args = parser.parse_args()
if args.exampleconfig:
print(__default_lxmd_config__)
exit()
if args.status or args.peers:
get_status(configdir = args.config,
rnsconfigdir=args.rnsconfig,
verbosity=args.verbose,
quietness=args.quiet,
timeout=args.timeout,
show_status=args.status,
show_peers=args.peers,
identity_path=args.identity)
exit()
program_setup(configdir = args.config,
rnsconfigdir=args.rnsconfig,
run_pn=args.propagation_node,
on_inbound=args.on_inbound,
verbosity=args.verbose,
quietness=args.quiet,
service=args.service)
except KeyboardInterrupt:
print("")
exit()
__default_lxmd_config__ = """# This is an example LXM Daemon config file.
# You should probably edit it to suit your
# intended usage.
[propagation]
# Whether to enable propagation node
enable_node = no
# Automatic announce interval in minutes.
# 6 hours by default.
announce_interval = 360
# Whether to announce when the node starts.
announce_at_start = yes
# Wheter to automatically peer with other
# propagation nodes on the network.
autopeer = yes
# The maximum peering depth (in hops) for
# automatically peered nodes.
autopeer_maxdepth = 4
# The maximum accepted transfer size per in-
# coming propagation transfer, in kilobytes.
# This also sets the upper limit for the size
# of single messages accepted onto this node.
#
# If a node wants to propagate a larger number
# of messages to this node, than what can fit
# within this limit, it will prioritise sending
# the smallest messages first, and try again
# with any remaining messages at a later point.
propagation_transfer_max_accepted_size = 256
# The maximum amount of storage to use for
# the LXMF Propagation Node message store,
# specified in megabytes. When this limit
# is reached, LXMF will periodically remove
# messages in its message store. By default,
# LXMF prioritises keeping messages that are
# new and small. Large and old messages will
# be removed first. This setting is optional
# and defaults to 500 megabytes.
# message_storage_limit = 500
# You can tell the LXMF message router to
# prioritise storage for one or more
# destinations. If the message store reaches
# the specified limit, LXMF will prioritise
# keeping messages for destinations specified
# with this option. This setting is optional,
# and generally you do not need to use it.
# prioritise_destinations = 41d20c727598a3fbbdf9106133a3a0ed, d924b81822ca24e68e2effea99bcb8cf
# You can configure the maximum number of other
# propagation nodes that this node will peer
# with automatically. The default is 50.
# max_peers = 25
# You can configure a list of static propagation
# node peers, that this node will always be
# peered with, by specifying a list of
# destination hashes.
# static_peers = e17f833c4ddf8890dd3a79a6fea8161d, 5a2d0029b6e5ec87020abaea0d746da4
# You can configure the propagation node to
# only accept incoming propagation messages
# from configured static peers.
# from_static_only = True
# By default, any destination is allowed to
# connect and download messages, but you can
# optionally restrict this. If you enable
# authentication, you must provide a list of
# allowed identity hashes in the a file named
# "allowed" in the lxmd config directory.
auth_required = no
[lxmf]
# The LXM Daemon will create an LXMF destination
# that it can receive messages on. This option sets
# the announced display name for this destination.
display_name = Anonymous Peer
# It is possible to announce the internal LXMF
# destination when the LXM Daemon starts up.
announce_at_start = no
# You can also announce the delivery destination
# at a specified interval. This is not enabled by
# default.
# announce_interval = 360
# The maximum accepted unpacked size for mes-
# sages received directly from other peers,
# specified in kilobytes. Messages larger than
# this will be rejected before the transfer
# begins.
delivery_transfer_max_accepted_size = 1000
# You can configure an external program to be run
# every time a message is received. The program
# will receive as an argument the full path to the
# message saved as a file. The example below will
# simply result in the message getting deleted as
# soon as it has been received.
# on_inbound = rm
[logging]
# Valid log levels are 0 through 7:
# 0: Log only critical information
# 1: Log errors and lower log levels
# 2: Log warnings and lower log levels
# 3: Log notices and lower log levels
# 4: Log info and lower (this is the default)
# 5: Verbose logging
# 6: Debug logging
# 7: Extreme logging
loglevel = 4
"""
if __name__ == "__main__":
main()