From 9255a241ce9ac41ab2a33e94b334eb53de1b9ace Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Wed, 6 May 2026 22:31:12 +0200 Subject: [PATCH 01/20] Updated version --- sbapp/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbapp/main.py b/sbapp/main.py index f7e9032..c23bc56 100644 --- a/sbapp/main.py +++ b/sbapp/main.py @@ -1,6 +1,6 @@ __debug_build__ = False __disable_shaders__ = False -__version__ = "1.9.2" +__version__ = "1.9.3" __variant__ = "" import sys From 4234d31225ba4e6be0769487a8a9034a72cb15f2 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Thu, 7 May 2026 20:24:28 +0200 Subject: [PATCH 02/20] Updated versions --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index cd8e2e8..0beadc4 100644 --- a/setup.py +++ b/setup.py @@ -114,8 +114,8 @@ setuptools.setup( ] }, install_requires=[ - "rns>=1.2.0", - "lxmf>=0.9.6", + "rns>=1.2.4", + "lxmf>=0.9.7", "lxst>=0.4.6", "kivy>=2.3.0", "numpy>=2.0.0", From 4ef45a790ff8ae0e022999391b040d9a02f1ee5b Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sun, 10 May 2026 13:39:17 +0200 Subject: [PATCH 03/20] Handle telemetry entries with invalid timestamps --- sbapp/sideband/core.py | 115 ++++++++++++++++++++++++-------------- sbapp/sideband/sense.py | 36 ++++++------ sbapp/ui/objectdetails.py | 4 +- 3 files changed, 94 insertions(+), 61 deletions(-) diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py index baef83b..6be50b2 100644 --- a/sbapp/sideband/core.py +++ b/sbapp/sideband/core.py @@ -20,7 +20,7 @@ from copy import deepcopy from threading import Lock from collections import deque from .res import sideband_fb_data -from .sense import Telemeter, Commands +from .sense import Telemeter, Commands, valid_telemetry_timebase, TB_DIFF_GRACE from .plugins import SidebandCommandPlugin, SidebandServicePlugin, SidebandTelemetryPlugin from .mqtt import MQTT @@ -114,6 +114,7 @@ class SidebandCore(): PERIODIC_JOBS_INTERVAL = 60 PERIODIC_SYNC_RETRY = 360 TELEMETRY_KEEP = 60*60*24*7 + TELEMETRY_FUTURE_GRACE = TB_DIFF_GRACE TELEMETRY_INTERVAL = 60 SERVICE_TELEMETRY_INTERVAL = 300 TELEMETRY_CLEAN_INTERVAL = 3600 @@ -1352,7 +1353,7 @@ class SidebandCore(): else: desired_method = LXMF.LXMessage.DIRECT - request_timebase = self.getpersistent(f"telemetry.{RNS.hexrep(from_addr, delimit=False)}.timebase") or now - self.telemetry_request_max_history + request_timebase = self._telemetry_timebase(from_addr) or now - self.telemetry_request_max_history lxm_fields = { LXMF.FIELD_COMMANDS: [ {Commands.TELEMETRY_REQUEST: [request_timebase, is_collector_request]}, ]} @@ -1431,6 +1432,27 @@ class SidebandCore(): RNS.log(ed, RNS.LOG_DEBUG) return ed + def _last_telemetry_send_success_timebase(self, addr): + tb = self.getpersistent(f"telemetry.{RNS.hexrep(addr, delimit=False)}.last_send_success_timebase") + if tb == None: return None + if not valid_telemetry_timebase(tb): + RNS.log(f"Invalid telemetry send success timebase found for {RNS.prettyhexrep(addr)}, clearing value", RNS.LOG_WARNING) + self.setpersistent(f"telemetry.{RNS.hexrep(addr, delimit=False)}.last_send_success_timebase", None) + return None + + else: return tb + + + def _telemetry_timebase(self, addr): + tb = self.getpersistent(f"telemetry.{RNS.hexrep(addr, delimit=False)}.timebase") + if tb == None: return None + if not valid_telemetry_timebase(tb): + RNS.log(f"Invalid telemetry timebase found for {RNS.prettyhexrep(addr)}, clearing value", RNS.LOG_WARNING) + self.setpersistent(f"telemetry.{RNS.hexrep(addr, delimit=False)}.timebase", None) + return None + + else: return tb + def _service_send_latest_telemetry(self, to_addr=None, stream=None, is_authorized_telemetry_request=False): if not RNS.vendor.platformutils.is_android(): return False @@ -1452,17 +1474,15 @@ class SidebandCore(): def send_latest_telemetry(self, to_addr=None, stream=None, is_authorized_telemetry_request=False, is_collector_response=False): if self.allow_service_dispatch and self.is_client: - try: - return self._service_send_latest_telemetry(to_addr, stream, is_authorized_telemetry_request) + try: return self._service_send_latest_telemetry(to_addr, stream, is_authorized_telemetry_request) except Exception as e: - RNS.log("Error requesting latest telemetry: "+str(e), RNS.LOG_ERROR) + RNS.log("Error sending latest telemetry: "+str(e), RNS.LOG_ERROR) RNS.trace_exception(e) return "not_sent" else: - if to_addr == None or to_addr == self.lxmf_destination.hash: - return "no_address" + if to_addr == None or to_addr == self.lxmf_destination.hash: return "no_address" else: if to_addr == self.config["telemetry_collector"]: is_authorized_telemetry_request = True @@ -1483,22 +1503,19 @@ class SidebandCore(): dest = RNS.Destination(dest_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") source = self.lxmf_destination - if self.config["telemetry_use_propagation_only"] == True: - desired_method = LXMF.LXMessage.PROPAGATED + if self.config["telemetry_use_propagation_only"] == True: desired_method = LXMF.LXMessage.PROPAGATED else: if not self.message_router.delivery_link_available(to_addr) and RNS.Identity.current_ratchet_id(to_addr) != None: RNS.log(f"Have ratchet for {RNS.prettyhexrep(to_addr)}, requesting opportunistic delivery of telemetry", RNS.LOG_DEBUG) desired_method = LXMF.LXMessage.OPPORTUNISTIC - else: - desired_method = LXMF.LXMessage.DIRECT + + else: desired_method = LXMF.LXMessage.DIRECT lxm_fields = self.get_message_fields(to_addr, is_authorized_telemetry_request=is_authorized_telemetry_request, signal_already_sent=True, is_collector_response=is_collector_response) - if lxm_fields == False and stream == None: - return "already_sent" + if lxm_fields == False and stream == None: return "already_sent" if stream != None and len(stream) > 0: - if lxm_fields == False: - lxm_fields = {} + if lxm_fields == False: lxm_fields = {} lxm_fields[LXMF.FIELD_TELEMETRY_STREAM] = stream if lxm_fields != None and lxm_fields != False and (LXMF.FIELD_TELEMETRY in lxm_fields or LXMF.FIELD_TELEMETRY_STREAM in lxm_fields): @@ -1509,9 +1526,14 @@ class SidebandCore(): telemetry_timebase = 0 for te in lxm_fields[LXMF.FIELD_TELEMETRY_STREAM]: ts = te[1] - telemetry_timebase = max(telemetry_timebase, ts) + t_src = RNS.prettyhexrep(te[0]) if te[0] else "unknown" + if not valid_telemetry_timebase(ts): + RNS.log(f"Skipping invalid timebase {ts} from {t_src} in send timebase accounting for {RNS.prettyhexrep(to_addr)}", RNS.LOG_WARNING) - if telemetry_timebase > (self.getpersistent(f"telemetry.{RNS.hexrep(to_addr, delimit=False)}.last_send_success_timebase") or 0): + else: telemetry_timebase = max(telemetry_timebase, ts) + + last_sent_timebase = (self._last_telemetry_send_success_timebase(to_addr) or 0) + if telemetry_timebase > last_sent_timebase: lxm = LXMF.LXMessage(dest, source, "", desired_method=desired_method, fields = lxm_fields, include_ticket=self.is_trusted(to_addr)) lxm.telemetry_timebase = telemetry_timebase lxm.register_delivery_callback(self.outbound_telemetry_finished) @@ -2314,6 +2336,12 @@ class SidebandCore(): read_telemetry = remote_telemeter.read_all() telemetry_timestamp = read_telemetry["time"]["utc"] + if not valid_telemetry_timebase(telemetry_timestamp): + now = time.time() + RNS.log(f"Invalid timestamp in telemetry from source {RNS.prettyhexrep(context_dest)}, not saving", RNS.LOG_WARNING) + if telemetry_timestamp > now: RNS.log(f"The timestamp was {RNS.prettytime(telemetry_timestamp - now)} in the future", RNS.LOG_WARNING) + return None + db = self.__db_connect() dbc = db.cursor() @@ -2361,6 +2389,7 @@ class SidebandCore(): if "by" in remote_telemeter.sensors["received"].data: remote_telemeter.sensors["received"].by = remote_telemeter.sensors["received"].data["by"] + if "distance" in remote_telemeter.sensors["received"].data: remote_telemeter.sensors["received"].geodesic_distance = remote_telemeter.sensors["received"].data["distance"]["geodesic"] remote_telemeter.sensors["received"].euclidian_distance = remote_telemeter.sensors["received"].data["distance"]["euclidian"] @@ -2373,21 +2402,16 @@ class SidebandCore(): data = (context_dest, telemetry_timestamp, telemetry) dbc.execute(query, data) - try: - db.commit() + try: db.commit() except Exception as e: RNS.log("An error occurred while commiting telemetry to database: "+str(e), RNS.LOG_ERROR) self.__db_reconnect() - # if not is_retry: - # RNS.log("Retrying operation...", RNS.LOG_ERROR) - # self._db_save_telemetry(context_dest, telemetry, physical_link, source_dest, via, is_retry = True) return self.setstate("app.flags.last_telemetry", time.time()) if self.config["telemetry_to_mqtt"] == True: - def mqtt_job(): - self.mqtt_handle_telemetry(context_dest, telemetry) + def mqtt_job(): self.mqtt_handle_telemetry(context_dest, telemetry) threading.Thread(target=mqtt_job, daemon=True).start() return telemetry @@ -2850,18 +2874,27 @@ class SidebandCore(): db.commit() def _db_clean_telemetry(self): - RNS.log("Cleaning telemetry... ", RNS.LOG_DEBUG) + RNS.log("Cleaning telemetry...", RNS.LOG_NOTICE) clean_time = time.time()-self.TELEMETRY_KEEP + future_time = time.time()-self.TELEMETRY_FUTURE_GRACE with self.db_lock: db = self.__db_connect() dbc = db.cursor() query = f"delete from telemetry where (ts < {clean_time});" - dbc.execute(query, {"outbound_state": LXMF.LXMessage.OUTBOUND, "sending_state": LXMF.LXMessage.SENDING}) + dbc.execute(query) + cleaned_old = dbc.rowcount + + query = f"delete from telemetry where (ts > {future_time});" + dbc.execute(query) + cleaned_future = dbc.rowcount + db.commit() self.last_telemetry_clean = time.time() + if cleaned_old: RNS.log(f"Cleaned {cleaned_old} expired telemetry entries", RNS.LOG_NOTICE) + if cleaned_future: RNS.log(f"Cleaned {cleaned_future} telemetry entries with timestamps too far in the future", RNS.LOG_NOTICE) def _db_message_set_state(self, lxm_hash, state, is_retry=False, ratchet_id=None, originator_stamp=None): msg_extras = None @@ -3088,25 +3121,25 @@ class SidebandCore(): packed_telemetry = self._db_save_telemetry(context_dest, lxm.fields[LXMF.FIELD_TELEMETRY], physical_link=physical_link, source_dest=context_dest) if LXMF.FIELD_TELEMETRY_STREAM in lxm.fields: - max_timebase = self.getpersistent(f"telemetry.{RNS.hexrep(context_dest, delimit=False)}.timebase") or 0 + max_timebase = self._telemetry_timebase(context_dest) or 0 if lxm.fields[LXMF.FIELD_TELEMETRY_STREAM] != None and len(lxm.fields[LXMF.FIELD_TELEMETRY_STREAM]) > 0: for telemetry_entry in lxm.fields[LXMF.FIELD_TELEMETRY_STREAM]: tsource = telemetry_entry[0] ttstamp = telemetry_entry[1] tpacked = telemetry_entry[2] appearance = telemetry_entry[3] - max_timebase = max(max_timebase, ttstamp) if self._db_save_telemetry(tsource, tpacked, via = context_dest): RNS.log("Saved telemetry stream entry from "+RNS.prettyhexrep(tsource), RNS.LOG_DEBUG) + max_timebase = max(max_timebase, ttstamp) if appearance != None: self._db_update_appearance(tsource, ttstamp, appearance, from_bulk_telemetry=True) RNS.log("Updated appearance entry from "+RNS.prettyhexrep(tsource), RNS.LOG_DEBUG) self.setpersistent(f"telemetry.{RNS.hexrep(context_dest, delimit=False)}.timebase", max_timebase) + RNS.log(f"Telemetry timebase for {RNS.hexrep(context_dest, delimit=False)} is now {max_timebase}", RNS.LOG_DEBUG) - else: - RNS.log("Received telemetry stream field with no data: "+str(lxm.fields[LXMF.FIELD_TELEMETRY_STREAM]), RNS.LOG_DEBUG) + else: RNS.log("Received telemetry stream field with no data: "+str(lxm.fields[LXMF.FIELD_TELEMETRY_STREAM]), RNS.LOG_DEBUG) attach_msg = False if lxm.fields != None and len(lxm.fields) > 0: @@ -3683,7 +3716,7 @@ class SidebandCore(): try: now = time.time() collector_address = self.config["telemetry_collector"] - last_send_timebase = self.getpersistent(f"telemetry.{RNS.hexrep(collector_address, delimit=False)}.last_send_success_timebase") or 0 + last_send_timebase = self._last_telemetry_send_success_timebase(collector_address) or 0 send_interval = self.config["telemetry_send_interval"] next_send = last_send_timebase+send_interval @@ -3698,7 +3731,7 @@ class SidebandCore(): self.pending_telemetry_send = True self.pending_telemetry_send_try += 1 if self.config["telemetry_send_all_to_collector"]: - last_timebase = (self.getpersistent(f"telemetry.{RNS.hexrep(collector_address, delimit=False)}.last_send_success_timebase") or 0) + last_timebase = self._last_telemetry_send_success_timebase(collector_address) or 0 self.create_telemetry_collector_response(to_addr=collector_address, timebase=last_timebase, is_authorized_telemetry_request=True) else: self.send_latest_telemetry(to_addr=collector_address) @@ -4446,8 +4479,7 @@ class SidebandCore(): self._db_message_set_state(message.hash, LXMF.LXMessage.OUTBOUND) self.message_router.handle_outbound(message) else: - if not no_display: - self.lxm_ingest(message, originator=True) + if not no_display: self.lxm_ingest(message, originator=True) if message.fields != None and LXMF.FIELD_TELEMETRY in message.fields: if len(message.fields[LXMF.FIELD_TELEMETRY]) > 0: @@ -4467,9 +4499,9 @@ class SidebandCore(): if send_telemetry and self.latest_packed_telemetry != None: telemeter = Telemeter.from_packed(self.latest_packed_telemetry) telemetry_timebase = telemeter.read_all()["time"]["utc"] - last_success_tb = (self.getpersistent(f"telemetry.{RNS.hexrep(context_dest, delimit=False)}.last_send_success_timebase") or 0) + last_success_tb = self._last_telemetry_send_success_timebase(context_dest) or 0 if is_collector_response and self.lxmf_destination.hash in self.telemetry_response_excluded: - RNS.log("Not embedding own telemetry collector response since own destination hash is excluded", RNS.LOG_DEBUG) + RNS.log("Not embedding own telemetry in collector response since own destination hash is excluded", RNS.LOG_DEBUG) send_telemetry = False elif telemetry_timebase > last_success_tb: RNS.log("Embedding own telemetry in message since current telemetry is newer than latest successful timebase", RNS.LOG_DEBUG) @@ -5212,10 +5244,8 @@ class SidebandCore(): if self.allow_request_from(context_dest): commands = message.fields[LXMF.FIELD_COMMANDS] self.handle_commands(commands, message) - else: - # TODO: Add these event to built-in log/event viewer - # when it is implemented. - RNS.log("Unauthorized command received from "+RNS.prettyhexrep(context_dest), RNS.LOG_WARNING) + + else: RNS.log("Unauthorized command received from "+RNS.prettyhexrep(context_dest), RNS.LOG_WARNING) else: self.lxm_ingest(message) @@ -5250,8 +5280,9 @@ class SidebandCore(): command_timebase = command[Commands.TELEMETRY_REQUEST] enable_collector_request = True - timebase = int(command_timebase) - RNS.log("Handling telemetry request with timebase "+str(timebase), RNS.LOG_DEBUG) + timebase = int(command_timebase); tbdiff = time.time()-timebase + if tbdiff < 0: RNS.log(f"Request timebase is in {RNS.prettytime(abs(tbdiff))} the future", RNS.LOG_WARNING) + RNS.log(f"Handling telemetry request with timebase "+str(timebase), RNS.LOG_DEBUG) if self.config["telemetry_collector_enabled"] and enable_collector_request: RNS.log(f"Collector requests enabled, returning complete telemetry response for all known objects since {timebase}", RNS.LOG_DEBUG) self.create_telemetry_collector_response(to_addr=context_dest, timebase=timebase, is_authorized_telemetry_request=True, is_collector_response=True) diff --git a/sbapp/sideband/sense.py b/sbapp/sideband/sense.py index 0a22767..4d49d90 100644 --- a/sbapp/sideband/sense.py +++ b/sbapp/sideband/sense.py @@ -329,29 +329,23 @@ class Time(Sensor): def pack(self): d = self.data - if d == None: - return None - else: - return d["utc"] + if d == None: return None + else: return d["utc"] def unpack(self, packed): try: - if packed == None: - return None - else: - return {"utc": packed} - except: - return None + if packed == None: return None + else: return {"utc": packed} + + except: return None def render(self, relative_to=None): if self.data != None: - rendered = { - "icon": "clock-time-ten-outline", - "name": "Timestamp", - "values": { "UTC": self.data["utc"] }, - } - else: - rendered = None + rendered = { "icon": "clock-time-ten-outline", + "name": "Timestamp", + "values": { "UTC": self.data["utc"] } } + + else: rendered = None return rendered @@ -3148,4 +3142,10 @@ def mqtt_hash(ihash): if type(ihash) == bytes: return RNS.hexrep(ihash, delimit=False) else: - return None \ No newline at end of file + return None + +TB_DIFF_GRACE = 4*60 +def valid_telemetry_timebase(ts): + if type(ts) not in [int, float]: return False + if ts > time.time()+TB_DIFF_GRACE: return False + else: return True \ No newline at end of file diff --git a/sbapp/ui/objectdetails.py b/sbapp/ui/objectdetails.py index e61b36e..5615ba9 100644 --- a/sbapp/ui/objectdetails.py +++ b/sbapp/ui/objectdetails.py @@ -364,7 +364,9 @@ class RVDetails(MDRecycleView): ts = s["values"]["UTC"] if ts != None: ts_str = datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S") - formatted_values = f"Recorded [b]{RNS.prettytime(time.time()-ts, compact=True)} ago[/b] ({ts_str})" + tsdiff = time.time()-ts + tspec = "ago" if tsdiff >= 0 else "in the future" + formatted_values = f"Recorded [b]{RNS.prettytime(abs(tsdiff), compact=True)} {tspec}[/b] ({ts_str})" def copy_info(e=None): Clipboard.copy(ts_str) toast("Copied to clipboard") From f5df1ec446ee5a7baaf88270c3b04ef32b07711b Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sun, 10 May 2026 14:18:18 +0200 Subject: [PATCH 04/20] Improved message send feedback --- sbapp/ui/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbapp/ui/messages.py b/sbapp/ui/messages.py index 5e2026b..7c3e18a 100644 --- a/sbapp/ui/messages.py +++ b/sbapp/ui/messages.py @@ -363,7 +363,7 @@ class Messages(): sphrase = f"Generating PN stamp with cost {prop_cost}" prgstr = "" else: - sphrase = "Waiting for path" + sphrase = "Preparing" elif prg <= 0.01: sphrase = "Waiting for path" elif prg <= 0.03: From 888ef4f4b02c666eef608deef6ede479c54bcb90 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sun, 10 May 2026 17:26:20 +0200 Subject: [PATCH 05/20] Updated versions --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 0beadc4..0aa1b84 100644 --- a/setup.py +++ b/setup.py @@ -114,8 +114,8 @@ setuptools.setup( ] }, install_requires=[ - "rns>=1.2.4", - "lxmf>=0.9.7", + "rns>=1.2.5", + "lxmf>=0.9.8", "lxst>=0.4.6", "kivy>=2.3.0", "numpy>=2.0.0", From 45f755b0a5234727558985a8660c138e10376de2 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sun, 10 May 2026 17:33:20 +0200 Subject: [PATCH 06/20] Updated version --- sbapp/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbapp/main.py b/sbapp/main.py index c23bc56..baab1a9 100644 --- a/sbapp/main.py +++ b/sbapp/main.py @@ -1,6 +1,6 @@ __debug_build__ = False __disable_shaders__ = False -__version__ = "1.9.3" +__version__ = "1.9.4" __variant__ = "" import sys From cae7152070ac6001596b13b36b6ff428b0dac45d Mon Sep 17 00:00:00 2001 From: markqvist Date: Fri, 29 May 2026 11:04:33 +0200 Subject: [PATCH 07/20] Added release manifest --- sideband_1.9.5.rsm | Bin 0 -> 1455 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 sideband_1.9.5.rsm diff --git a/sideband_1.9.5.rsm b/sideband_1.9.5.rsm new file mode 100644 index 0000000000000000000000000000000000000000..373843f2a3f8286f40b62a4cb695c370793e9969 GIT binary patch literal 1455 zcmdPf^ZMEaks}kf*CsCCcDsB2;-=!O5?6bSx30PSY%a^HTWLo$8$54lwfvZ0%Bi3jXt1x z_Q~b17q>M8PP=lBC(B{4ckSfN|G6#c%F^4GQ?)nYpROC5gEO=dvV2d6wlD zWu|B59T6z=T*}*$$YgZUG28J{XV(8E1&JjYn~G9PGLuVlN^|vLb}dWJ&&|y&xv62C zXk=t)lxC4^WMOEOlxAU+Y+z|_l4N3RWN2Vyk!qT1kd&5el$yLUv8W_7Eit*Icybfi z*?WqU5(^4+!EV(ps5I8i%g;;IP0Xv*E6>PTTvVKX&A2-$akJy4^r)ofgnxDSH5Tov3OQ!VjVn~%1yokp<@nnFUVrGw+#9_% ziHm>TRo-Km@$hl^iNJsZ$a*@PM@dWi+ukkB}x&hy~HhaVdqn=g8lZFEc!uT(jp zGV@K(yoe-;sTEH}Hq3vlCHZ8@VFTH78ogIU8hH=gwtRUdDOqc(?b;Jl&++044W>6u zKfC>4Ld3;)gzKA6zTF*yY9mg~oDVt}RYXPd#!uBqOs}AvZBI zuOu}uF)uk)0h-?xGV_viN>fsc6-qKv6><_wfZ1FDS~3(X Date: Mon, 1 Jun 2026 14:38:40 +0200 Subject: [PATCH 08/20] Added manifest --- sideband_1.9.6.rsm | Bin 0 -> 1611 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 sideband_1.9.6.rsm diff --git a/sideband_1.9.6.rsm b/sideband_1.9.6.rsm new file mode 100644 index 0000000000000000000000000000000000000000..440a7249d050a1f5f1b09acca558b59e7d8c7aa2 GIT binary patch literal 1611 zcmc(feNa?Y7{;q{l2#NUb)2E5Lm=tmF8jgm0#UK?F+(~{1`Ze*oW1w#-oxI#cf05A zF3Xn<9|Q=OpIYen1tJ8<*KMOVFr>PM5GpjCQi(D0d1yc=)cC9bN)DI z-t(Mie$Tw`G?lmFqWAu$s*s|-v1cbgztI1C{jDw;+c32%=E&*RZ&GsnD9LL2Gc@y_ zA65rG&No{4_-b3vxjnCL56N?HT2kJ|K*ES_JMs_)YIOSM*|Qfkc4mEcdBiuRiS<`} zXEKJ2-zt$ccp`J(NGR`oeoMuPW*ZV=wTEB}hXn8PUSZefsIivU^(!B3ScF!+=&?IY zR^;|3CS5)gxTdBkb#2^Z;d{#RLk=FUtjMVvt@*klE3>`pr{}g#T*~fS=ev0N`at(9 zo9fOS56nwEw4t~b`)%cx@X+pN4%$!~!89^Kj&5}#fnc6%Q7hw=`ZfWv2ogje(5N(e zg-TlL47Da+tBcpgz0fXV8zLgK*^lQvT-52|1#H2bceyjIWobFQPcvY&8c$W_`J3%f zWV!?-V#>ifY|(fA@=(0ZhQ*<^Dzye_O_U0$4dxg_tk!_kNJVK4P!*>^YMs`sL9qsH zOl&(8L~MqXNNP*xZ1)lqwA&SPUKMt?R>AQcQb5kFbTRB+fmpoPdc&A=OOfvF(p~S$ z4j0H38K2gi{YpQv?*m2U*vE-i%El|#Y#i1k3~w9HY~TN2N6&6UP|(zkWR)-hXDFX2 zD_bDGcdn(4QV{`1oBBEzX9cZ$y0Pv0WH4}kAFd3Z+2D>jFc)9h{~zDKLYU{$lYTqS zoH;Z(nV6$_7f}{q`#hC^_^?Ht9j_H0CI0E0q4aIb&i*!jQTwxc>R8cRf!~gf{PD|J zfo^|wIIC}WTF+;{|48xR;1?Z7hJCBk$rYw)$wE~4L;aCAUP|9`ss6nY>dBkMz_la! z;3q3e0%}U4W~h_L?kbh8e^OCGyLFz%fGJ_;)xz4~^7G#Al~)J*ce;vBOz&0J-;DaO z@qv#rdWRNIv#wLovAT#Fd3ID?RI@eG|5VV%AGiBT-wX?lT(%GmU)?jIE%sk`Cp=LR zG}4_EaI$cEU(x!mh{=)lDGyzsx85Zhty0$#XhC1z%VjW_fZI(DD4r7`<_Lfsz&1NC zh>#P38Rr8tFMwR5L*&yq5eZTUqFg|DX+X^m0APlg#he9*yd Date: Tue, 9 Jun 2026 22:52:21 +0200 Subject: [PATCH 09/20] Update README.md --- README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e7648b..3b5cd95 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,31 @@ This also means that Sideband is designed to be fully compatible with custom (an ## On Linux -On all Linux-based operating systems, Sideband is available as a `pip`/`pipx` package. You can either pull and install Sideband directly from the `pip` repository, or download and install locally using `whl` package from the [latest release page](https://github.com/markqvist/Sideband/releases/latest). +On most Linux-based operating systems, you can simply download the pre-compiled AppImage from the [latest release page](https://github.com/markqvist/Sideband/releases/latest) and run it. + +Depending on your Linux distribution, you may need to install a few other packages for full voice call, audio messaging and clipboard functionality. Make sure you have `opusfile`, and a clipboard manager like `xclip` or `wl-clipboard` installed: + +```bash +# For Debian 13+, Ubuntu 24.04+ and derivatives +sudo apt install libopusfile0 xclip xsel + +# For Debian 12+, Ubuntu 22.04+ and derivatives +sudo apt install libopusfile0 xclip xsel + +# For Manjaro and derivatives +pamac install opusfile xclip xsel + +# For Arch and derivatives +sudo pacman -Sy opusfile xclip xsel + +# Clipboard support on Wayland: If the clipboard is not +# working correctly on Wayland with xclip and xsel, you +# can install wl-clipboard and wl-clipboard-x11 instead. +pamac remove xclip xsel +pamac install wl-clipboard wl-clipboard-x11 +``` + +You can also install Sideband as a `pip`/`pipx` package. You can either pull and install Sideband directly from the `pip` repository, or download and install locally using `whl` package from the [latest release page](https://github.com/markqvist/Sideband/releases/latest). This installation method **includes desktop integration**, so that Sideband will show up in your applications menu and launchers. Below are install steps for the most common recent Linux distros. For Debian 11, see the end of this section. From d1b108035a263c8ae46eeb1c36d0ced155b365e0 Mon Sep 17 00:00:00 2001 From: markqvist Date: Wed, 10 Jun 2026 20:21:59 +0200 Subject: [PATCH 10/20] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 3b5cd95..15cd0e1 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,6 @@ On most Linux-based operating systems, you can simply download the pre-compiled Depending on your Linux distribution, you may need to install a few other packages for full voice call, audio messaging and clipboard functionality. Make sure you have `opusfile`, and a clipboard manager like `xclip` or `wl-clipboard` installed: ```bash -# For Debian 13+, Ubuntu 24.04+ and derivatives -sudo apt install libopusfile0 xclip xsel - # For Debian 12+, Ubuntu 22.04+ and derivatives sudo apt install libopusfile0 xclip xsel From 863b925011473e7e906ef7c93ed14dc8d8f76d70 Mon Sep 17 00:00:00 2001 From: markqvist Date: Wed, 10 Jun 2026 20:27:23 +0200 Subject: [PATCH 11/20] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15cd0e1..ef9599a 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,9 @@ python3 -m sbapp.main ## On Raspberry Pi -You can install Sideband on all Raspberry Pi models that support 64-bit operating systems, and can run at least Python version 3.11. +The easiest way to install Sideband on Raspberry Pi is to simply download and run the `aarch64` AppImage. On some versions of Raspberry Pi OS, you may also need to install additional libraries for full voice and clipboard functionality with `sudo apt install libopusfile0 xclip xsel`. + +You can also install Sideband using `pip` on all Raspberry Pi models that support 64-bit operating systems, and can run at least Python version 3.11. The install instructions below assume that you are installing Sideband on 64-bit Raspberry Pi OS (based on Debian 13 "Trixie" or later). If you're running something else on your Pi, you might need to modify some commands slightly. From 8601d84580341f2c499041615d772610edb9eaab Mon Sep 17 00:00:00 2001 From: markqvist Date: Fri, 3 Jul 2026 13:51:23 +0200 Subject: [PATCH 12/20] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ef9599a..780531b 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ This also means that Sideband is designed to be fully compatible with custom (an On most Linux-based operating systems, you can simply download the pre-compiled AppImage from the [latest release page](https://github.com/markqvist/Sideband/releases/latest) and run it. -Depending on your Linux distribution, you may need to install a few other packages for full voice call, audio messaging and clipboard functionality. Make sure you have `opusfile`, and a clipboard manager like `xclip` or `wl-clipboard` installed: +Depending on your Linux distribution, you *may* need to install a few other packages for full voice call, audio messaging and clipboard functionality. If any of those features do not work, make sure you have `opusfile`, and a clipboard manager like `xclip` or `wl-clipboard` installed: ```bash # For Debian 12+, Ubuntu 22.04+ and derivatives @@ -181,7 +181,7 @@ python3 -m sbapp.main ## On Raspberry Pi -The easiest way to install Sideband on Raspberry Pi is to simply download and run the `aarch64` AppImage. On some versions of Raspberry Pi OS, you may also need to install additional libraries for full voice and clipboard functionality with `sudo apt install libopusfile0 xclip xsel`. +The easiest way to install Sideband on Raspberry Pi is to simply download and run the `aarch64` AppImage. On some versions of Raspberry Pi OS, you *may* also need to install additional libraries for full voice and clipboard functionality with `sudo apt install libopusfile0 xclip xsel`. You can also install Sideband using `pip` on all Raspberry Pi models that support 64-bit operating systems, and can run at least Python version 3.11. From fa55fb9fae77b49cdf8f69c8388436533873d7c3 Mon Sep 17 00:00:00 2001 From: markqvist Date: Tue, 21 Jul 2026 23:27:34 +0200 Subject: [PATCH 13/20] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 780531b..0ebf24e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Sideband ========= -*This repository is [a public mirror](./MIRROR.md). All development is happening elsewhere.* +*This repository is [a public mirror](./MIRROR.md). All development is happening elsewhere. While new releases are posted here, the latest source code is only available over Reticulum.* To understand the foundational philosophy and goals of this system, read the [Zen of Reticulum](Zen%20of%20Reticulum.md). From 5c2fad812614a024c01ef31d0729cf90f6633ee7 Mon Sep 17 00:00:00 2001 From: markqvist Date: Tue, 21 Jul 2026 23:27:53 +0200 Subject: [PATCH 14/20] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ebf24e..a4cfca0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ Sideband ========= -*This repository is [a public mirror](./MIRROR.md). All development is happening elsewhere. While new releases are posted here, the latest source code is only available over Reticulum.* +*This repository is [a public mirror](./MIRROR.md). All development is happening elsewhere.* +*While new releases are posted here, the latest source code is only available over Reticulum.* To understand the foundational philosophy and goals of this system, read the [Zen of Reticulum](Zen%20of%20Reticulum.md). From 1dd47f53cc41e1d6d6def0291f7db2a84902fe04 Mon Sep 17 00:00:00 2001 From: markqvist Date: Tue, 21 Jul 2026 23:28:15 +0200 Subject: [PATCH 15/20] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index a4cfca0..1af833f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ Sideband ========= -*This repository is [a public mirror](./MIRROR.md). All development is happening elsewhere.* -*While new releases are posted here, the latest source code is only available over Reticulum.* +*This repository is [a public mirror](./MIRROR.md). All development is happening elsewhere. While new releases are posted here, the latest source code is only available over Reticulum itself.* To understand the foundational philosophy and goals of this system, read the [Zen of Reticulum](Zen%20of%20Reticulum.md). From 7db7c067d50b0de05e3bcda7a05f409f11dd155c Mon Sep 17 00:00:00 2001 From: markqvist Date: Sat, 26 Sep 2026 22:04:36 +0200 Subject: [PATCH 18/20] Update README.md --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1af833f..18aba11 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,24 @@ Sideband ========= -*This repository is [a public mirror](./MIRROR.md). All development is happening elsewhere. While new releases are posted here, the latest source code is only available over Reticulum itself.* +> [!WARNING] +> Several hastily launched, incorrect, and in most cases entirely LLM-generated fakes of Sideband and associated tools are currently being circulated and marketed. Most of these "projects" violate the license that Sideband was published under, but claim independent ownership and license grants. +> +> Such claims or grants are **not legally valid**, and **not** recognized by the Sideband author and copyright holder. +> +> Any claimed assertion of copyright or grant of license that such projects are making are [void grants](https://reticulum.network/manual/brandolinis.html#void-grants-legal-foundations-of-machine-generated-code). +> +> Both the violators themselves, and **all** downstream projects using the infringing derivatives, are **directly** and **personally** liable for the legal consequences. +> +> For more detailed information, see the relevant sections of the [Brandolini's Reference](https://reticulum.network/manual/brandolinis.html) chapter of the Reticulum Manual. For software and projects recognized by the Reticulum community, see the [Programs Using Reticulum](https://reticulum.network/manual/software.html) chapter. +> +> Do **not** use these "projects", they are a hazard to the entire ecosystem we have been carefully building over the last ten years. + +*Due to the blatant copyright infringement, LLM-laundering and disrespect for open-source developers, the source code in this repository is no longer updated. All development is happening over Reticulum, where the latest source is always available.* + +*Packages and updates continue to ship on PyPI and over `rngit`. Compiled APKs, AppImages, wheels and portable exe bundles will continue to be published here as well.* + +--- To understand the foundational philosophy and goals of this system, read the [Zen of Reticulum](Zen%20of%20Reticulum.md). From ccf1b082080f175324c63581579083ac81362297 Mon Sep 17 00:00:00 2001 From: markqvist Date: Sun, 27 Sep 2026 11:32:57 +0200 Subject: [PATCH 19/20] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 18aba11..d9ff57c 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ Sideband