mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-06-22 20:12:37 -07:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cbce84cbd | |||
| fe334c0d7c | |||
| 33d5a8e2a8 | |||
| e80bf471ec | |||
| 7dfdea2395 | |||
| 74b61aebd2 | |||
| ce9071e2d3 | |||
| d6cf59dcc8 | |||
| de61652d37 | |||
| 6181f62d93 | |||
| ed66b4873e | |||
| 26869941a4 | |||
| ee7b4e7ae5 | |||
| 7866484453 | |||
| 817b3b1a12 | |||
| 17f6968467 | |||
| a96a1d6692 | |||
| c1081fa9a4 | |||
| dd3104094b | |||
| dc68eea313 | |||
| 794e437f6d | |||
| ebf544d335 | |||
| 939f30fef2 | |||
| 4549bbfdb9 | |||
| 603f709139 |
@@ -1,3 +1,39 @@
|
||||
### 2026-05-21: RNS 1.3.0
|
||||
|
||||
This maintenance release fixes a number of bugs.
|
||||
|
||||
**Changes**
|
||||
- Added ability to use wildcards and pattern matches in `rngit` artifact fetch targets
|
||||
- Fixed channel outlet sequence holes and ghost envelopes on dying outlets by **neutral**
|
||||
- Fixed known destination iteration races by **neutral**
|
||||
- Fixed timeout deadlock in `rnsh` by **neutral**
|
||||
- Fixed commit message rendering in `rngit`
|
||||
- Fixed various minor bugs and output inconsistencies in `rngit`
|
||||
- Adjusted timeouts for remote operations in `rngit`
|
||||
- Updated documentation
|
||||
|
||||
**Verified Retrieval**
|
||||
You can retrieve and verify this release over Reticulum using the built-in `rngit release` utility. To download all artifacts, and the release manifest for future updates, you can use the following command:
|
||||
|
||||
```sh
|
||||
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch latest:all --signer bc7291552be7a58f361522990465165c
|
||||
```
|
||||
|
||||
To retrieve only the `.whl` package for installation, you can use:
|
||||
|
||||
```sh
|
||||
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch latest:rns-1.3.0-py3-none-any.whl --signer bc7291552be7a58f361522990465165c
|
||||
```
|
||||
|
||||
**Release Signatures**
|
||||
Release artifacts include a signed `rsm` release manifest and `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rnid`. To verify files, download the `rsm` and `rsg` signatures, make sure they are in the same folder as the release artifact, and run `rnid` signature verification with the release identity as the required signer:
|
||||
|
||||
```sh
|
||||
rnid -i bc7291552be7a58f361522990465165c -V manifest.rsm *.rsg
|
||||
```
|
||||
|
||||
The `rnid` utility will then verify the signatures, and display whether they are valid. If the signature cannot be verified, the release has been tampered with and should be discarded.
|
||||
|
||||
### 2026-05-19: RNS 1.2.9
|
||||
|
||||
This release completes the operational functionality of the `rngit` system, which now has full release creation, fetch and verified update support using the `rngit release` command. Additionally, two chapters have been added to the manual should cover all the things that `rngit` is currently capable of.
|
||||
|
||||
+89
-56
@@ -144,7 +144,7 @@ class MessageBase(abc.ABC):
|
||||
MSGTYPE = None
|
||||
"""
|
||||
Defines a unique identifier for a message class.
|
||||
|
||||
|
||||
* Must be unique within all classes registered with a ``Channel``
|
||||
* Must be less than ``0xf000``. Values greater than or equal to ``0xf000`` are reserved.
|
||||
"""
|
||||
@@ -255,11 +255,11 @@ class Channel(contextlib.AbstractContextManager):
|
||||
|
||||
# The maximum window size for transfers on fast links
|
||||
WINDOW_MAX_FAST = 48
|
||||
|
||||
|
||||
# For calculating maps and guard segments, this
|
||||
# must be set to the global maximum window.
|
||||
WINDOW_MAX = WINDOW_MAX_FAST
|
||||
|
||||
|
||||
# If the fast rate is sustained for this many request
|
||||
# rounds, the fast link window size will be allowed.
|
||||
FAST_RATE_THRESHOLD = 10
|
||||
@@ -285,6 +285,7 @@ class Channel(contextlib.AbstractContextManager):
|
||||
"""
|
||||
self._outlet = outlet
|
||||
self._lock = threading.RLock()
|
||||
self._send_lock = threading.Lock()
|
||||
self._tx_ring: collections.deque[Envelope] = collections.deque()
|
||||
self._rx_ring: collections.deque[Envelope] = collections.deque()
|
||||
self._message_callbacks: [MessageCallbackType] = []
|
||||
@@ -382,27 +383,30 @@ class Channel(contextlib.AbstractContextManager):
|
||||
if envelope.packet is not None:
|
||||
self._outlet.set_packet_timeout_callback(envelope.packet, None)
|
||||
self._outlet.set_packet_delivered_callback(envelope.packet, None)
|
||||
envelope.tracked = False
|
||||
for envelope in self._rx_ring:
|
||||
envelope.tracked = False
|
||||
self._tx_ring.clear()
|
||||
self._rx_ring.clear()
|
||||
|
||||
def _emplace_envelope(self, envelope: Envelope, ring: collections.deque[Envelope]) -> bool:
|
||||
with self._lock:
|
||||
i = 0
|
||||
|
||||
|
||||
for existing in ring:
|
||||
|
||||
if envelope.sequence == existing.sequence:
|
||||
RNS.log(f"Envelope: Emplacement of duplicate envelope with sequence "+str(envelope.sequence), RNS.LOG_EXTREME)
|
||||
return False
|
||||
|
||||
|
||||
if envelope.sequence < existing.sequence and not (self._next_rx_sequence - envelope.sequence) > (Channel.SEQ_MAX//2):
|
||||
ring.insert(i, envelope)
|
||||
|
||||
envelope.tracked = True
|
||||
return True
|
||||
|
||||
|
||||
i += 1
|
||||
|
||||
|
||||
envelope.tracked = True
|
||||
ring.append(envelope)
|
||||
|
||||
@@ -457,7 +461,7 @@ class Channel(contextlib.AbstractContextManager):
|
||||
m = e.unpack(self._message_factories)
|
||||
else:
|
||||
m = e.message
|
||||
|
||||
|
||||
self._rx_ring.remove(e)
|
||||
self._run_callbacks(m)
|
||||
|
||||
@@ -476,7 +480,7 @@ class Channel(contextlib.AbstractContextManager):
|
||||
with self._lock:
|
||||
outstanding = 0
|
||||
for envelope in self._tx_ring:
|
||||
if envelope.outlet == self._outlet:
|
||||
if envelope.outlet == self._outlet:
|
||||
if not envelope.packet or not self._outlet.get_packet_state(envelope.packet) == MessageState.MSGSTATE_DELIVERED:
|
||||
outstanding += 1
|
||||
|
||||
@@ -486,8 +490,10 @@ class Channel(contextlib.AbstractContextManager):
|
||||
return True
|
||||
|
||||
def _packet_tx_op(self, packet: TPacket, op: Callable[[TPacket], bool]):
|
||||
target_id = self._outlet.get_packet_id(packet)
|
||||
with self._lock:
|
||||
envelope = next(filter(lambda e: self._outlet.get_packet_id(e.packet) == self._outlet.get_packet_id(packet),
|
||||
envelope = next(filter(lambda e: e.packet is not None
|
||||
and self._outlet.get_packet_id(e.packet) == target_id,
|
||||
self._tx_ring), None)
|
||||
|
||||
if envelope and op(envelope):
|
||||
@@ -516,7 +522,7 @@ class Channel(contextlib.AbstractContextManager):
|
||||
# TODO: Remove at some point
|
||||
# RNS.log("Increased "+str(self)+" max window to "+str(self.window_max), RNS.LOG_DEBUG)
|
||||
# RNS.log("Increased "+str(self)+" min window to "+str(self.window_min), RNS.LOG_DEBUG)
|
||||
|
||||
|
||||
else:
|
||||
self.fast_rate_rounds += 1
|
||||
if self.window_max < Channel.WINDOW_MAX_FAST and self.fast_rate_rounds == Channel.FAST_RATE_THRESHOLD:
|
||||
@@ -547,36 +553,48 @@ class Channel(contextlib.AbstractContextManager):
|
||||
return to
|
||||
|
||||
def _packet_timeout(self, packet: TPacket):
|
||||
def retry_envelope(envelope: Envelope) -> bool:
|
||||
if self._outlet.get_packet_state(packet) == MessageState.MSGSTATE_DELIVERED:
|
||||
return
|
||||
|
||||
target_id = self._outlet.get_packet_id(packet)
|
||||
envelope_to_resend: Envelope | None = None
|
||||
should_teardown = False
|
||||
with self._lock:
|
||||
envelope = next(filter(
|
||||
lambda e: e.packet is not None and self._outlet.get_packet_id(e.packet) == target_id,
|
||||
self._tx_ring), None)
|
||||
if envelope is None:
|
||||
return
|
||||
|
||||
if envelope.tries >= self._max_tries:
|
||||
RNS.log("Retry count exceeded on "+str(self)+", tearing down Link.", RNS.LOG_ERROR)
|
||||
self._shutdown() # start on separate thread?
|
||||
self._outlet.timed_out()
|
||||
return True
|
||||
should_teardown = True
|
||||
else:
|
||||
envelope.tries += 1
|
||||
envelope_to_resend = envelope
|
||||
|
||||
envelope.tries += 1
|
||||
self._outlet.resend(envelope.packet)
|
||||
self._outlet.set_packet_delivered_callback(envelope.packet, self._packet_delivered)
|
||||
self._outlet.set_packet_timeout_callback(envelope.packet, self._packet_timeout, self._get_packet_timeout_time(envelope.tries))
|
||||
self._update_packet_timeouts()
|
||||
if self.window > self.window_min:
|
||||
self.window -= 1
|
||||
if self.window_max > (self.window_min+self.window_flexibility):
|
||||
self.window_max -= 1
|
||||
|
||||
if self.window > self.window_min:
|
||||
self.window -= 1
|
||||
# TODO: Remove at some point
|
||||
# RNS.log("Decreased "+str(self)+" window to "+str(self.window), RNS.LOG_DEBUG)
|
||||
if should_teardown:
|
||||
RNS.log("Retry count exceeded on "+str(self)+", tearing down Link.", RNS.LOG_ERROR)
|
||||
self._shutdown()
|
||||
self._outlet.timed_out()
|
||||
return
|
||||
|
||||
if self.window_max > (self.window_min+self.window_flexibility):
|
||||
self.window_max -= 1
|
||||
# TODO: Remove at some point
|
||||
# RNS.log("Decreased "+str(self)+" max window to "+str(self.window_max), RNS.LOG_DEBUG)
|
||||
if envelope_to_resend is not None:
|
||||
self._outlet.resend(envelope_to_resend.packet)
|
||||
with self._lock:
|
||||
self._outlet.set_packet_delivered_callback(envelope_to_resend.packet, self._packet_delivered)
|
||||
self._outlet.set_packet_timeout_callback(
|
||||
envelope_to_resend.packet, self._packet_timeout,
|
||||
self._get_packet_timeout_time(envelope_to_resend.tries))
|
||||
self._update_packet_timeouts()
|
||||
already_delivered = (self._outlet.get_packet_state(envelope_to_resend.packet) == MessageState.MSGSTATE_DELIVERED)
|
||||
|
||||
# TODO: Remove at some point
|
||||
# RNS.log("Decreased "+str(self)+" window to "+str(self.window), RNS.LOG_EXTREME)
|
||||
|
||||
return False
|
||||
|
||||
if self._outlet.get_packet_state(packet) != MessageState.MSGSTATE_DELIVERED:
|
||||
self._packet_tx_op(packet, retry_envelope)
|
||||
if already_delivered:
|
||||
self._packet_delivered(envelope_to_resend.packet)
|
||||
|
||||
def send(self, message: MessageBase) -> Envelope:
|
||||
"""
|
||||
@@ -585,27 +603,39 @@ class Channel(contextlib.AbstractContextManager):
|
||||
|
||||
:param message: an instance of a ``MessageBase`` subclass
|
||||
"""
|
||||
envelope: Envelope | None = None
|
||||
with self._lock:
|
||||
if not self.is_ready_to_send():
|
||||
raise ChannelException(CEType.ME_LINK_NOT_READY, f"Link is not ready")
|
||||
|
||||
envelope = Envelope(self._outlet, message=message, sequence=self._next_sequence)
|
||||
self._next_sequence = (self._next_sequence + 1) % Channel.SEQ_MODULUS
|
||||
self._emplace_envelope(envelope, self._tx_ring)
|
||||
with self._send_lock:
|
||||
with self._lock:
|
||||
if not self.is_ready_to_send():
|
||||
raise ChannelException(CEType.ME_LINK_NOT_READY, f"Link is not ready")
|
||||
|
||||
if envelope is None:
|
||||
raise BlockingIOError()
|
||||
reserved_sequence = self._next_sequence
|
||||
envelope = Envelope(self._outlet, message=message, sequence=reserved_sequence)
|
||||
envelope.pack()
|
||||
if len(envelope.raw) > self._outlet.mdu:
|
||||
raise ChannelException(CEType.ME_TOO_BIG,
|
||||
f"Packed message too big for packet: {len(envelope.raw)} > {self._outlet.mdu}")
|
||||
self._next_sequence = (reserved_sequence + 1) % Channel.SEQ_MODULUS
|
||||
|
||||
envelope.pack()
|
||||
if len(envelope.raw) > self._outlet.mdu:
|
||||
raise ChannelException(CEType.ME_TOO_BIG, f"Packed message too big for packet: {len(envelope.raw)} > {self._outlet.mdu}")
|
||||
|
||||
envelope.packet = self._outlet.send(envelope.raw)
|
||||
envelope.tries += 1
|
||||
self._outlet.set_packet_delivered_callback(envelope.packet, self._packet_delivered)
|
||||
self._outlet.set_packet_timeout_callback(envelope.packet, self._packet_timeout, self._get_packet_timeout_time(envelope.tries))
|
||||
self._update_packet_timeouts()
|
||||
envelope.packet = self._outlet.send(envelope.raw)
|
||||
|
||||
if (envelope.packet is None
|
||||
or getattr(envelope.packet, "raw", None) is None
|
||||
or (hasattr(envelope.packet, "receipt") and envelope.packet.receipt is None)):
|
||||
with self._lock:
|
||||
self._next_sequence = reserved_sequence
|
||||
raise ChannelException(CEType.ME_LINK_NOT_READY, "Outlet did not transmit packet")
|
||||
|
||||
with self._lock:
|
||||
self._emplace_envelope(envelope, self._tx_ring)
|
||||
envelope.tries += 1
|
||||
self._outlet.set_packet_delivered_callback(envelope.packet, self._packet_delivered)
|
||||
self._outlet.set_packet_timeout_callback(envelope.packet, self._packet_timeout, self._get_packet_timeout_time(envelope.tries))
|
||||
self._update_packet_timeouts()
|
||||
already_delivered = (self._outlet.get_packet_state(envelope.packet) == MessageState.MSGSTATE_DELIVERED)
|
||||
|
||||
# prevent _tx_ring envelope leak
|
||||
if already_delivered:
|
||||
self._packet_delivered(envelope.packet)
|
||||
|
||||
return envelope
|
||||
|
||||
@@ -699,7 +729,10 @@ class LinkChannelOutlet(ChannelOutletBase):
|
||||
packet.receipt.set_delivery_callback(inner if callback else None)
|
||||
|
||||
def get_packet_id(self, packet: RNS.Packet) -> any:
|
||||
if packet and hasattr(packet, "get_hash") and callable(packet.get_hash):
|
||||
if (packet
|
||||
and getattr(packet, "raw", None) is not None
|
||||
and hasattr(packet, "get_hash")
|
||||
and callable(packet.get_hash)):
|
||||
return packet.get_hash()
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -65,4 +65,6 @@ def sha512(data):
|
||||
|
||||
def file_sha256(file):
|
||||
if not hashlib: raise SystemError("The hashlib module is not available on this system")
|
||||
# TODO: Could implement fallback for old snakes here
|
||||
if not hasattr(hashlib, "file_digest"): raise SystemError("The file_digest method is not available on this system. This functionality requires Python 3.11 or later.")
|
||||
else: return hashlib.file_digest(file, "sha256").digest()
|
||||
|
||||
+15
-8
@@ -127,13 +127,15 @@ class Identity:
|
||||
:returns: An :ref:`RNS.Identity<api-identity>` instance that can be used to create an outgoing :ref:`RNS.Destination<api-destination>`, or *None* if the destination is unknown.
|
||||
"""
|
||||
if from_identity_hash:
|
||||
for destination_hash in Identity.known_destinations:
|
||||
if target_hash == Identity.truncated_hash(Identity.known_destinations[destination_hash][2]):
|
||||
with Identity.known_destinations_lock: destination_hashes = list(Identity.known_destinations.keys())
|
||||
for destination_hash in destination_hashes:
|
||||
entry = Identity.known_destinations.get(destination_hash)
|
||||
if not entry: continue
|
||||
if target_hash == Identity.truncated_hash(entry[2]):
|
||||
if not _no_use: RNS.Reticulum.get_instance()._used_destination_data(destination_hash)
|
||||
identity_data = Identity.known_destinations[destination_hash]
|
||||
identity = Identity(create_keys=False)
|
||||
identity.load_public_key(identity_data[2])
|
||||
identity.app_data = identity_data[3]
|
||||
identity.load_public_key(entry[2])
|
||||
identity.app_data = entry[3]
|
||||
return identity
|
||||
|
||||
return None
|
||||
@@ -294,8 +296,11 @@ class Identity:
|
||||
def _retain_identity(identity_hash):
|
||||
try:
|
||||
retained = False
|
||||
for destination_hash in Identity.known_destinations:
|
||||
if identity_hash == Identity.truncated_hash(Identity.known_destinations[destination_hash][2]):
|
||||
with Identity.known_destinations_lock: destination_hashes = list(Identity.known_destinations.keys())
|
||||
for destination_hash in destination_hashes:
|
||||
entry = Identity.known_destinations.get(destination_hash)
|
||||
if not entry: continue
|
||||
if identity_hash == Identity.truncated_hash(entry[2]):
|
||||
if Identity._retain_destination_data(destination_hash): retained = True
|
||||
|
||||
return retained
|
||||
@@ -311,7 +316,9 @@ class Identity:
|
||||
no_path = 0
|
||||
retained = 0
|
||||
never_used = 0
|
||||
for destination_hash in Identity.known_destinations:
|
||||
|
||||
with Identity.known_destinations_lock: destination_hashes = list(Identity.known_destinations.keys())
|
||||
for destination_hash in destination_hashes:
|
||||
try:
|
||||
if RNS.Transport.has_path(destination_hash): has_path = True
|
||||
else:
|
||||
|
||||
@@ -3405,6 +3405,14 @@ class Transport:
|
||||
|
||||
@staticmethod
|
||||
def blackhole_identity(identity_hash, until=None, reason=None):
|
||||
"""
|
||||
Blackholes an identity.
|
||||
|
||||
:param identity_hash: The identity hash to blackhole as *bytes*.
|
||||
:param until: Optional unix timestamp of when the blackhole expires as *float* or *int*.
|
||||
:param reason: Optional reason for the blackhole as *str*.
|
||||
:returns: *True* if successful, otherwise *False*.
|
||||
"""
|
||||
try:
|
||||
if not identity_hash in Transport.blackholed_identities:
|
||||
entry = {"source": Transport.identity.hash, "until": until, "reason": reason}
|
||||
@@ -3422,6 +3430,12 @@ class Transport:
|
||||
|
||||
@staticmethod
|
||||
def unblackhole_identity(identity_hash):
|
||||
"""
|
||||
Lifts blackhole for an identity.
|
||||
|
||||
:param identity_hash: The identity hash to blackhole as *bytes*.
|
||||
:returns: *True* if successful, otherwise *False*.
|
||||
"""
|
||||
try:
|
||||
if identity_hash in Transport.blackholed_identities:
|
||||
Transport.blackholed_identities.pop(identity_hash)
|
||||
|
||||
@@ -978,7 +978,7 @@ class NomadNetworkNode():
|
||||
|
||||
# Commit message
|
||||
if commit_info.get("message"):
|
||||
content_parts.append(self.m_escape(commit_info["message"]) + "\n")
|
||||
content_parts.append(self.format_commit(commit_info["message"]) + "\n")
|
||||
content_parts.append("\n")
|
||||
|
||||
# Changed files
|
||||
@@ -1147,7 +1147,7 @@ class NomadNetworkNode():
|
||||
|
||||
# Breadcrumb navigation
|
||||
repo_link = self.m_link(repo_name, self.PATH_REPO, g=group_name, r=repo_name)
|
||||
breadcrumb = f">>\n{self.m_link('Node', self.PATH_INDEX)} / {self.m_link(group_name, self.PATH_GROUP, g=group_name)} / {repo_link}"
|
||||
breadcrumb = f">>\n{self.m_link('Node', self.PATH_INDEX)} / {self.m_link(group_name, self.PATH_GROUP, g=group_name)} / {repo_link} / stats"
|
||||
nav_parts.append(breadcrumb + "\n")
|
||||
|
||||
repo = self.get_accessible_repository(remote_identity, group_name, repo_name)
|
||||
@@ -2345,6 +2345,15 @@ class NomadNetworkNode():
|
||||
|
||||
return "\n".join(formatted_lines)
|
||||
|
||||
def format_commit(self, diff_text):
|
||||
lines = diff_text.replace("\\", "\\\\").split("\n")
|
||||
formatted_lines = []
|
||||
|
||||
for line in lines:
|
||||
if line.startswith("-"): formatted_lines.append(self.m_escape(f"\\{line}"))
|
||||
else: formatted_lines.append(self.m_escape(line))
|
||||
|
||||
return "\n".join(formatted_lines)
|
||||
|
||||
def repository_thanks(self, repo_path, add=False, link_id=None):
|
||||
if add:
|
||||
|
||||
@@ -33,6 +33,7 @@ import os
|
||||
import sys
|
||||
import time
|
||||
import shutil
|
||||
import fnmatch
|
||||
import argparse
|
||||
import threading
|
||||
import subprocess
|
||||
@@ -331,45 +332,48 @@ class ReticulumGitClient():
|
||||
self.progress_enabled = True
|
||||
self.transfer_label = "unknown"
|
||||
|
||||
if not ReticulumGitNode._ensure_git(): RNS.log("The \"git\" command is not available. Aborting server startup.", RNS.LOG_ERROR)
|
||||
if configdir != None: self.configdir = configdir
|
||||
else:
|
||||
if configdir != None: self.configdir = configdir
|
||||
else:
|
||||
if os.path.isdir(self.userdir+"/.config/rngit") and os.path.isfile(self.userdir+"/.config/rngit/config"): self.configdir = self.userdir+"/.rngit/reticulum"
|
||||
else: self.configdir = self.userdir+"/.rngit"
|
||||
|
||||
self.logfile = self.configdir+"/client_log"
|
||||
self.configpath = self.configdir+"/client_config"
|
||||
self.identitypath = identitypath or self.configdir+"/client_identity"
|
||||
if os.path.isdir(self.userdir+"/.config/rngit") and os.path.isfile(self.userdir+"/.config/rngit/config"): self.configdir = self.userdir+"/.rngit/reticulum"
|
||||
else: self.configdir = self.userdir+"/.rngit"
|
||||
|
||||
if not os.path.isdir(self.configdir): os.makedirs(self.configdir)
|
||||
|
||||
if not os.path.isfile(self.identitypath):
|
||||
identity = RNS.Identity()
|
||||
identity.to_file(self.identitypath)
|
||||
RNS.log(f"Identity generated and persisted to {self.identitypath}", RNS.LOG_DEBUG)
|
||||
|
||||
else:
|
||||
identity = RNS.Identity.from_file(self.identitypath)
|
||||
RNS.log(f"Client identity loaded from {self.identitypath}", RNS.LOG_DEBUG)
|
||||
self.logfile = self.configdir+"/client_log"
|
||||
self.configpath = self.configdir+"/client_config"
|
||||
self.identitypath = identitypath or self.configdir+"/client_identity"
|
||||
|
||||
if not identity: self.abort("Could not initialize client identity")
|
||||
else: self.identity = identity
|
||||
if not os.path.isdir(self.configdir): os.makedirs(self.configdir)
|
||||
|
||||
if os.path.isfile(self.configpath):
|
||||
try: self.config = ConfigObj(self.configpath)
|
||||
except Exception as e:
|
||||
RNS.log("Could not parse the configuration at "+self.configpath, RNS.LOG_ERROR)
|
||||
RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
|
||||
RNS.panic()
|
||||
else:
|
||||
RNS.log("Could not load config file, creating default configuration file...")
|
||||
self.__create_default_config()
|
||||
RNS.log("Default config file created. Make any necessary changes in "+self.configdir+"/config and restart rngit.")
|
||||
RNS.log("Exiting now")
|
||||
exit(1)
|
||||
self.__ensure_identity()
|
||||
self.__ensure_config()
|
||||
self.__apply_config()
|
||||
|
||||
self.__apply_config()
|
||||
def _ensure_git(self):
|
||||
if not ReticulumGitNode._ensure_git(): self.abort("The \"git\" command is not available. Aborting operation.")
|
||||
|
||||
def __ensure_identity(self):
|
||||
if not os.path.isfile(self.identitypath):
|
||||
identity = RNS.Identity()
|
||||
identity.to_file(self.identitypath)
|
||||
RNS.log(f"Identity generated and persisted to {self.identitypath}", RNS.LOG_DEBUG)
|
||||
|
||||
else:
|
||||
identity = RNS.Identity.from_file(self.identitypath)
|
||||
RNS.log(f"Client identity loaded from {self.identitypath}", RNS.LOG_DEBUG)
|
||||
|
||||
if not identity: self.abort("Could not initialize client identity")
|
||||
else: self.identity = identity
|
||||
|
||||
def __ensure_config(self):
|
||||
if os.path.isfile(self.configpath):
|
||||
try: self.config = ConfigObj(self.configpath)
|
||||
except Exception as e:
|
||||
RNS.log("Could not parse the configuration at "+self.configpath, RNS.LOG_ERROR)
|
||||
RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
|
||||
RNS.panic()
|
||||
else:
|
||||
RNS.log("Could not load config file, creating default configuration file...")
|
||||
self.__create_default_config()
|
||||
RNS.log("Default config file created, make any necessary changes in "+self.configdir+"/config")
|
||||
|
||||
def __create_default_config(self):
|
||||
from RNS.Utilities.rngit.client import __default_rngit_config__ as __default_rngit_client_config__
|
||||
@@ -558,7 +562,7 @@ class ReticulumGitClient():
|
||||
repo_path = f"{group}/{repo}"
|
||||
|
||||
request_data = {self.IDX_REPOSITORY: repo_path}
|
||||
response, metadata = self.send_request(self.PATH_CREATE, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_CREATE, request_data, timeout=120)
|
||||
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
@@ -618,7 +622,7 @@ class ReticulumGitClient():
|
||||
|
||||
request_data = {self.IDX_REPOSITORY: repo_path, "source": source}
|
||||
print(f"Remote is {operation_name.lower()}ing repository to {repo_path}...")
|
||||
response, metadata = self.send_request(path, request_data, timeout=900)
|
||||
response, metadata = self.send_request(path, request_data, timeout=7200)
|
||||
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
@@ -662,7 +666,7 @@ class ReticulumGitClient():
|
||||
|
||||
request_data = {self.IDX_REPOSITORY: repo_path}
|
||||
print(f"Remote is syncing repository...")
|
||||
response, metadata = self.send_request(self.PATH_SYNC, request_data, timeout=900)
|
||||
response, metadata = self.send_request(self.PATH_SYNC, request_data, timeout=7200)
|
||||
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
@@ -750,7 +754,7 @@ class ReticulumGitClient():
|
||||
repo_path = f"{group}/{repo}"
|
||||
|
||||
request_data = {self.IDX_REPOSITORY: repo_path, "operation": "list"}
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=120)
|
||||
print("\r \r", end="")
|
||||
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
@@ -809,7 +813,7 @@ class ReticulumGitClient():
|
||||
repo_path = f"{group}/{repo}"
|
||||
|
||||
request_data = {self.IDX_REPOSITORY: repo_path, "operation": "view", "tag": target}
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=300)
|
||||
print("\r \r", end="")
|
||||
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
@@ -907,7 +911,7 @@ class ReticulumGitClient():
|
||||
elif offline: return local_manifest_dir+name
|
||||
self.transfer_label = name
|
||||
request_data = {self.IDX_REPOSITORY: repo_path, "operation": "fetch", "tag": tag, "artifact": name}
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=30, progress=True)
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=7200, progress=True)
|
||||
print("\r \r", end="")
|
||||
|
||||
if not response: self.abort(f"No response from remote")
|
||||
@@ -947,13 +951,14 @@ class ReticulumGitClient():
|
||||
manifest_out = os.path.basename(f"{release_name}_{release_version}.{self.MSG_EXT}")
|
||||
with open(manifest_out, "wb") as fh: fh.write(rsg)
|
||||
|
||||
def match_artifacts(match_expression, manifest_artifacts):
|
||||
return [entry for entry in manifest_artifacts if fnmatch.fnmatch(entry.get("name", ""), match_expression)]
|
||||
|
||||
fetch_artifacts = []
|
||||
artifacts = release_meta.get("artifacts", [])
|
||||
if not artifacts: self.abort("Release manifest contains no artifacts")
|
||||
if artifact == "all": fetch_artifacts = artifacts
|
||||
else:
|
||||
for entry in artifacts:
|
||||
if entry["name"] == artifact:
|
||||
fetch_artifacts = [entry]; break
|
||||
else: fetch_artifacts = match_artifacts(artifact, artifacts)
|
||||
|
||||
valid_count = 0
|
||||
if not fetch_artifacts: self.abort("No available artifacts specified for fetch")
|
||||
@@ -1067,7 +1072,7 @@ class ReticulumGitClient():
|
||||
"tag": tag, "hash": commit_hash,
|
||||
"notes": notes, "notes_format": "markdown" }
|
||||
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=120)
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote during release init")
|
||||
|
||||
status_byte = response[0]
|
||||
@@ -1093,7 +1098,7 @@ class ReticulumGitClient():
|
||||
"tag": tag, "artifact_name": artifact,
|
||||
"artifact_data": artifact_data }
|
||||
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=300)
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=7200)
|
||||
|
||||
if not response or not isinstance(response, bytes) or response[0] != 0:
|
||||
error_msg = response[1:].decode("utf-8", errors="ignore") if response else "Unknown error"
|
||||
@@ -1106,7 +1111,7 @@ class ReticulumGitClient():
|
||||
request_data = { self.IDX_REPOSITORY: repo_path,
|
||||
"operation": "create", "step": "finalize", "tag": tag }
|
||||
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=300)
|
||||
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote during finalize")
|
||||
|
||||
@@ -1119,7 +1124,7 @@ class ReticulumGitClient():
|
||||
|
||||
except Exception as e: self.abort(f"Error creating release: {e}")
|
||||
finally:
|
||||
if self.link: self.link.teardown()
|
||||
if hasattr(self, "link") and self.link: self.link.teardown()
|
||||
|
||||
def delete_release(self, remote=None, target=None):
|
||||
if not remote: self.abort(f"No remote specified")
|
||||
@@ -1149,7 +1154,7 @@ class ReticulumGitClient():
|
||||
request_data = { self.IDX_REPOSITORY: repo_path,
|
||||
"operation": "delete", "tag": target }
|
||||
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=120)
|
||||
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
@@ -1192,7 +1197,7 @@ class ReticulumGitClient():
|
||||
request_data = { self.IDX_REPOSITORY: repo_path,
|
||||
"operation": "latest", "tag": target }
|
||||
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_RELEASE, request_data, timeout=120)
|
||||
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
@@ -1228,7 +1233,7 @@ class ReticulumGitClient():
|
||||
|
||||
request_data = {self.IDX_GROUP: group, "operation": "gperms", "step": "get"}
|
||||
|
||||
response, metadata = self.send_request(self.PATH_PERMS, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_PERMS, request_data, timeout=120)
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
status_byte = response[0]
|
||||
@@ -1247,7 +1252,7 @@ class ReticulumGitClient():
|
||||
|
||||
request_data = {self.IDX_GROUP: group, "operation": "gperms", "step": "set", "content": content}
|
||||
|
||||
response, metadata = self.send_request(self.PATH_PERMS, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_PERMS, request_data, timeout=120)
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
status_byte = response[0]
|
||||
@@ -1279,7 +1284,7 @@ class ReticulumGitClient():
|
||||
|
||||
request_data = {self.IDX_REPOSITORY: repo_path, "operation": "rperms", "step": "get"}
|
||||
|
||||
response, metadata = self.send_request(self.PATH_PERMS, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_PERMS, request_data, timeout=120)
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
status_byte = response[0]
|
||||
@@ -1298,7 +1303,7 @@ class ReticulumGitClient():
|
||||
|
||||
request_data = {self.IDX_REPOSITORY: repo_path, "operation": "rperms", "step": "set", "content": content}
|
||||
|
||||
response, metadata = self.send_request(self.PATH_PERMS, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_PERMS, request_data, timeout=120)
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
status_byte = response[0]
|
||||
@@ -1333,7 +1338,7 @@ class ReticulumGitClient():
|
||||
repo_path = f"{group}/{repo}"
|
||||
|
||||
request_data = {self.IDX_REPOSITORY: repo_path, "operation": "list", "scope": scope}
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=120)
|
||||
print("\r \r", end="")
|
||||
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
@@ -1391,7 +1396,7 @@ class ReticulumGitClient():
|
||||
repo_path = f"{group}/{repo}"
|
||||
|
||||
request_data = {self.IDX_REPOSITORY: repo_path, "operation": "view", "doc_id": doc_id, "scope": scope}
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=120)
|
||||
print("\r \r", end="")
|
||||
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
@@ -1476,7 +1481,7 @@ class ReticulumGitClient():
|
||||
"title": title, "content": content, "format": "markdown",
|
||||
"signature": signature }
|
||||
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=600)
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
status_byte = response[0]
|
||||
@@ -1521,7 +1526,7 @@ class ReticulumGitClient():
|
||||
"title": title, "content": content, "format": "markdown",
|
||||
"signature": signature }
|
||||
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=600)
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
status_byte = response[0]
|
||||
@@ -1557,7 +1562,7 @@ class ReticulumGitClient():
|
||||
repo_path = f"{group}/{repo}"
|
||||
|
||||
request_data = {self.IDX_REPOSITORY: repo_path, "operation": "view", "doc_id": doc_id, "scope": scope}
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=600)
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
status_byte = response[0]
|
||||
@@ -1579,7 +1584,7 @@ class ReticulumGitClient():
|
||||
request_data = { self.IDX_REPOSITORY: repo_path, "operation": "edit", "doc_id": doc_id,
|
||||
"scope": scope, "content": content, "title": title, "signature": signature }
|
||||
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=600)
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
status_byte = response[0]
|
||||
@@ -1619,7 +1624,7 @@ class ReticulumGitClient():
|
||||
request_data = { self.IDX_REPOSITORY: repo_path,
|
||||
"operation": "delete", "doc_id": doc_id, "scope": scope }
|
||||
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=120)
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
status_byte = response[0]
|
||||
@@ -1658,7 +1663,7 @@ class ReticulumGitClient():
|
||||
"operation": "comment", "doc_id": doc_id, "scope": scope,
|
||||
"content": content, "format": "markdown" }
|
||||
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=600)
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
status_byte = response[0]
|
||||
@@ -1696,7 +1701,7 @@ class ReticulumGitClient():
|
||||
request_data = {self.IDX_REPOSITORY: repo_path,
|
||||
"operation": "complete", "doc_id": doc_id}
|
||||
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=120)
|
||||
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
@@ -1735,7 +1740,7 @@ class ReticulumGitClient():
|
||||
request_data = {self.IDX_REPOSITORY: repo_path,
|
||||
"operation": "activate", "doc_id": doc_id}
|
||||
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=120)
|
||||
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
@@ -1774,7 +1779,7 @@ class ReticulumGitClient():
|
||||
request_data = {self.IDX_REPOSITORY: repo_path,
|
||||
"operation": "perms", "doc_id": doc_id, "step": "get"}
|
||||
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=120)
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
status_byte = response[0]
|
||||
@@ -1795,7 +1800,7 @@ class ReticulumGitClient():
|
||||
"operation": "perms", "doc_id": doc_id, "step": "set",
|
||||
"content": content}
|
||||
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=30)
|
||||
response, metadata = self.send_request(self.PATH_WORK, request_data, timeout=120)
|
||||
if not response or not isinstance(response, bytes): self.abort("No response from remote")
|
||||
|
||||
status_byte = response[0]
|
||||
@@ -2008,9 +2013,7 @@ class ReticulumGitNode():
|
||||
else:
|
||||
RNS.log("Could not load config file, creating default configuration file...")
|
||||
self.__create_default_config()
|
||||
RNS.log("Default config file created. Make any necessary changes in "+self.configdir+"/config and restart rngit.")
|
||||
RNS.log("Exiting now")
|
||||
exit(1)
|
||||
RNS.log("Default config file created, make any necessary changes in "+self.configdir+"/config")
|
||||
|
||||
self.__apply_config()
|
||||
self.__load_stats()
|
||||
@@ -2219,7 +2222,7 @@ class ReticulumGitNode():
|
||||
for group_name in section:
|
||||
RNS.log(f"Loading repositery group \"{group_name}\"", RNS.LOG_VERBOSE)
|
||||
group_path = os.path.expanduser(section[group_name])
|
||||
if not os.path.isdir(group_path): RNS.log(f"The path \"{group_path}\" specified for repository group \"{group_name}\" does not exist, skipping.", RNS.LOG_ERROR)
|
||||
if not os.path.isdir(group_path): RNS.log(f"The path \"{group_path}\" specified for repository group \"{group_name}\" does not exist, skipping.", RNS.LOG_WARNING)
|
||||
else: self.load_repository_group(group_name, group_path)
|
||||
|
||||
def __resolve_identity_alias(self, alias):
|
||||
@@ -3912,6 +3915,8 @@ class ReticulumGitNode():
|
||||
doc_dir = d
|
||||
break
|
||||
|
||||
if not doc_dir: return self.RES_NOT_FOUND.to_bytes(1, "big") + b"Not found"
|
||||
|
||||
doc_dir = os.path.join(work_path, scope, str(doc_id))
|
||||
root_path = os.path.join(doc_dir, "root")
|
||||
|
||||
@@ -4075,6 +4080,8 @@ class ReticulumGitNode():
|
||||
doc_dir = d
|
||||
break
|
||||
|
||||
if not doc_dir: return self.RES_NOT_FOUND.to_bytes(1, "big") + b"Not found"
|
||||
|
||||
doc_dir = os.path.join(work_path, scope, str(doc_id))
|
||||
root_path = os.path.join(doc_dir, "root")
|
||||
|
||||
|
||||
@@ -214,6 +214,8 @@ async def _handle_error(errmsg: RNS.MessageBase):
|
||||
async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, verbosity: int, quietness: int, noid: bool, destination: str,
|
||||
timeout: float, command: [str] | None = None):
|
||||
global _finished, _link
|
||||
if timeout is None:
|
||||
timeout = RNS.Transport.PATH_REQUEST_TIMEOUT
|
||||
with process.TTYRestorer(sys.stdin.fileno()) as ttyRestorer:
|
||||
loop = asyncio.get_running_loop()
|
||||
state = InitiatorState.IS_INITIAL
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "1.2.9"
|
||||
__version__ = "1.3.0"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: eeadc4cc4157f9b7fb8f04414d9f0832
|
||||
config: 3af44598b1ae08e3d831a262fbc2ecf1
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -817,6 +817,42 @@ You can fetch individual artifacts from a release by specifying the artifact nam
|
||||
|
||||
This downloads only the specified artifact and verifies its signature against the manifest. If a file already exists locally, ``rngit`` verifies it against the manifest signature and skips the download if valid, making it safe to run the command multiple times. When fetching releases, ``rngit release`` will only download files that are missing or invalid according to the manifest. This means that partially completed release fetches can be continued later, if interrupted.
|
||||
|
||||
**Pattern Matching for Artifacts**
|
||||
|
||||
When fetching selective artifacts, you are not limited to exact names or the ``all`` keyword. You can use shell-style wildcard patterns to match multiple artifacts flexibly. This is particularly useful for selecting platform-specific builds, version ranges, or file types without specifying each file individually.
|
||||
|
||||
.. tip::
|
||||
When using pattern matching, make sure to enclose the target specification in quotes. Otherwise,
|
||||
your shell will probably interpret it as a shell expansion pattern *before* it is passed as an
|
||||
argument to ``rngit``!
|
||||
|
||||
The pattern matching supports standard Unix wildcards:
|
||||
|
||||
- ``*`` matches any sequence of characters (including empty)
|
||||
- ``?`` matches any single character
|
||||
- ``[seq]`` matches any character in *seq* (for example ``[0-9]`` or ``[abc]``)
|
||||
- ``[!seq]`` matches any character not in *seq*
|
||||
|
||||
For example, to fetch all wheel files for Python 3 across any platform:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit release rns://remote_node/public/myrepo fetch "1.2.0:*-py3-*.whl"
|
||||
|
||||
To fetch a specific patch version when you know the major and minor version:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit release rns://remote_node/public/myrepo fetch "1.2.0:myapp-1.2.?-linux-x86_64.tar.gz"
|
||||
|
||||
Or to retrieve all source archives:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit release rns://remote_node/public/myrepo fetch "1.2.0:source_*.tgz"
|
||||
|
||||
If your pattern contains no wildcard characters, it must match an artifact name exactly, which is useful for fetching single, specific artifacts. When a pattern matches multiple artifacts, all matched files are fetched and verified. If no artifacts match the pattern, the fetch aborts with an error indicating no matches were found.
|
||||
|
||||
**Offline Verification**
|
||||
|
||||
Because the release manifest contains embedded signatures, you can verify the integrity of release artifacts offline, without connecting to the repository node. The ``rnid`` and ``rngit`` utilities can validate artifact signatures against ``.rsg`` and manifest files.
|
||||
|
||||
@@ -210,8 +210,8 @@ This is not about "dropping out" of society. It is about building a substrate on
|
||||
|
||||
**Consider:**
|
||||
|
||||
- **The Old Way:** "My connection is slow. I should call my ISP and complain."
|
||||
- **The Zen Way:** "The path is noisy. I will adjust the antenna or find a better route."
|
||||
- **The Old Way:** *"My connection is slow. I should call my ISP and complain."*
|
||||
- **The Zen Way:** *"The path is noisy. I will adjust the antenna or find a better route."*
|
||||
|
||||
By taking ownership of the infrastructure, you take ownership of your voice. You stop shouting into someone else's megaphone and start building your own. The network is no longer something that happens to you; it is something you make happen.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const DOCUMENTATION_OPTIONS = {
|
||||
VERSION: '1.2.9',
|
||||
VERSION: '1.3.0',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Distributed Development - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Distributed Development - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -431,7 +431,7 @@
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Code Examples - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Code Examples - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -3665,7 +3665,7 @@ will be fully on-par with natively included interfaces, including all supported
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -296,7 +296,7 @@
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#"><link rel="search" title="Search" href="search.html">
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 --><title>Index - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 --><title>Index - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -178,7 +178,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -202,7 +202,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -309,10 +309,12 @@
|
||||
<h2>B</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Reticulum.blackhole_sources">blackhole_sources() (RNS.Reticulum static method)</a>
|
||||
<li><a href="reference.html#RNS.Transport.blackhole_identity">blackhole_identity() (RNS.Transport static method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Reticulum.blackhole_sources">blackhole_sources() (RNS.Reticulum static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Buffer">Buffer (class in RNS)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
@@ -792,6 +794,10 @@
|
||||
<section id="U" class="genindex-section">
|
||||
<h2>U</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Transport.unblackhole_identity">unblackhole_identity() (RNS.Transport static method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.MessageBase.unpack">unpack() (RNS.MessageBase method)</a>
|
||||
</li>
|
||||
@@ -840,7 +846,7 @@
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Getting Started Fast - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -968,7 +968,7 @@ All other available modules will still be loaded when needed.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
+32
-4
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Git Over Reticulum - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Git Over Reticulum - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -918,6 +918,34 @@ unicode_icons = yes
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This downloads only the specified artifact and verifies its signature against the manifest. If a file already exists locally, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> verifies it against the manifest signature and skips the download if valid, making it safe to run the command multiple times. When fetching releases, <code class="docutils literal notranslate"><span class="pre">rngit</span> <span class="pre">release</span></code> will only download files that are missing or invalid according to the manifest. This means that partially completed release fetches can be continued later, if interrupted.</p>
|
||||
<p><strong>Pattern Matching for Artifacts</strong></p>
|
||||
<p>When fetching selective artifacts, you are not limited to exact names or the <code class="docutils literal notranslate"><span class="pre">all</span></code> keyword. You can use shell-style wildcard patterns to match multiple artifacts flexibly. This is particularly useful for selecting platform-specific builds, version ranges, or file types without specifying each file individually.</p>
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
<p>When using pattern matching, make sure to enclose the target specification in quotes. Otherwise,
|
||||
your shell will probably interpret it as a shell expansion pattern <em>before</em> it is passed as an
|
||||
argument to <code class="docutils literal notranslate"><span class="pre">rngit</span></code>!</p>
|
||||
</div>
|
||||
<p>The pattern matching supports standard Unix wildcards:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">*</span></code> matches any sequence of characters (including empty)</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">?</span></code> matches any single character</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">[seq]</span></code> matches any character in <em>seq</em> (for example <code class="docutils literal notranslate"><span class="pre">[0-9]</span></code> or <code class="docutils literal notranslate"><span class="pre">[abc]</span></code>)</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">[!seq]</span></code> matches any character not in <em>seq</em></p></li>
|
||||
</ul>
|
||||
<p>For example, to fetch all wheel files for Python 3 across any platform:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://remote_node/public/myrepo fetch "1.2.0:*-py3-*.whl"
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>To fetch a specific patch version when you know the major and minor version:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://remote_node/public/myrepo fetch "1.2.0:myapp-1.2.?-linux-x86_64.tar.gz"
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Or to retrieve all source archives:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://remote_node/public/myrepo fetch "1.2.0:source_*.tgz"
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If your pattern contains no wildcard characters, it must match an artifact name exactly, which is useful for fetching single, specific artifacts. When a pattern matches multiple artifacts, all matched files are fetched and verified. If no artifacts match the pattern, the fetch aborts with an error indicating no matches were found.</p>
|
||||
<p><strong>Offline Verification</strong></p>
|
||||
<p>Because the release manifest contains embedded signatures, you can verify the integrity of release artifacts offline, without connecting to the repository node. The <code class="docutils literal notranslate"><span class="pre">rnid</span></code> and <code class="docutils literal notranslate"><span class="pre">rngit</span></code> utilities can validate artifact signatures against <code class="docutils literal notranslate"><span class="pre">.rsg</span></code> and manifest files.</p>
|
||||
<p><strong>For individual files:</strong></p>
|
||||
@@ -1446,7 +1474,7 @@ options:
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Communications Hardware - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Communications Hardware - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -676,7 +676,7 @@ can be used with Reticulum. This includes virtual software modems such as
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -710,7 +710,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -1774,7 +1774,7 @@ interface basis under the relevant interface configuration section.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Reticulum License - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Reticulum License - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -345,7 +345,7 @@ SOFTWARE.
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Building Networks - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Building Networks - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -664,7 +664,7 @@ differently than a mobile device roaming between radio cells.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
Binary file not shown.
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>API Reference - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>API Reference - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -2234,6 +2234,38 @@ will announce it.</p>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Transport.blackhole_identity">
|
||||
<em class="property"><span class="k"><span class="pre">static</span></span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">blackhole_identity</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">identity_hash</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">until</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">reason</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.blackhole_identity" title="Link to this definition">¶</a></dt>
|
||||
<dd><p>Blackholes an identity.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>identity_hash</strong> – The identity hash to blackhole as <em>bytes</em>.</p></li>
|
||||
<li><p><strong>until</strong> – Optional unix timestamp of when the blackhole expires as <em>float</em> or <em>int</em>.</p></li>
|
||||
<li><p><strong>reason</strong> – Optional reason for the blackhole as <em>str</em>.</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-even"><p><em>True</em> if successful, otherwise <em>False</em>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Transport.unblackhole_identity">
|
||||
<em class="property"><span class="k"><span class="pre">static</span></span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">unblackhole_identity</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">identity_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.unblackhole_identity" title="Link to this definition">¶</a></dt>
|
||||
<dd><p>Lifts blackhole for an identity.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p><strong>identity_hash</strong> – The identity hash to blackhole as <em>bytes</em>.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-even"><p><em>True</em> if successful, otherwise <em>False</em>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
</dd></dl>
|
||||
|
||||
</section>
|
||||
@@ -2472,6 +2504,8 @@ will announce it.</p>
|
||||
<li><a class="reference internal" href="#RNS.Transport.next_hop_interface"><code class="docutils literal notranslate"><span class="pre">next_hop_interface()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Transport.await_path"><code class="docutils literal notranslate"><span class="pre">await_path()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Transport.request_path"><code class="docutils literal notranslate"><span class="pre">request_path()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Transport.blackhole_identity"><code class="docutils literal notranslate"><span class="pre">blackhole_identity()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Transport.unblackhole_identity"><code class="docutils literal notranslate"><span class="pre">unblackhole_identity()</span></code></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -2485,7 +2519,7 @@ will announce it.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<meta name="robots" content="noindex" />
|
||||
<title>Search - Reticulum Network Stack 1.2.9 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<title>Search - Reticulum Network Stack 1.3.0 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="#" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -304,7 +304,7 @@
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Programs Using Reticulum - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Programs Using Reticulum - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -513,7 +513,7 @@ plugin system for expandability.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Support Reticulum - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Support Reticulum - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -383,7 +383,7 @@ circumstances, so we rely on old-fashioned human feedback.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -1338,7 +1338,7 @@ those risks are acceptable to you.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -1636,7 +1636,7 @@ systemctl --user enable rnsd.service
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>What is Reticulum? - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -505,7 +505,7 @@ network, and vice versa.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Zen of Reticulum - Reticulum Network Stack 1.2.9 documentation</title>
|
||||
<title>Zen of Reticulum - Reticulum Network Stack 1.3.0 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.0 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.0 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -399,8 +399,8 @@
|
||||
<p>This is not about “dropping out” of society. It is about building a substrate on which an actual <em>Society</em> can function.</p>
|
||||
<p><strong>Consider:</strong></p>
|
||||
<ul class="simple">
|
||||
<li><p><strong>The Old Way:</strong> “My connection is slow. I should call my ISP and complain.”</p></li>
|
||||
<li><p><strong>The Zen Way:</strong> “The path is noisy. I will adjust the antenna or find a better route.”</p></li>
|
||||
<li><p><strong>The Old Way:</strong> <em>“My connection is slow. I should call my ISP and complain.”</em></p></li>
|
||||
<li><p><strong>The Zen Way:</strong> <em>“The path is noisy. I will adjust the antenna or find a better route.”</em></p></li>
|
||||
</ul>
|
||||
<p>By taking ownership of the infrastructure, you take ownership of your voice. You stop shouting into someone else’s megaphone and start building your own. The network is no longer something that happens to you; it is something you make happen.</p>
|
||||
</section>
|
||||
@@ -677,7 +677,7 @@ Imagine a messaging app. You write it once. It works on a laptop connected to fi
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=e917149c"></script>
|
||||
</div><script src="_static/documentation_options.js?v=1f29e9d3"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -766,6 +766,37 @@ $ rngit release rns://remote_node/public/myrepo fetch 1.2.0:myapp-1.2.0.tar.gz
|
||||
|
||||
This downloads only the specified artifact and verifies its signature against the manifest. If a file already exists locally, `rngit` verifies it against the manifest signature and skips the download if valid, making it safe to run the command multiple times. When fetching releases, `rngit release` will only download files that are missing or invalid according to the manifest. This means that partially completed release fetches can be continued later, if interrupted.
|
||||
|
||||
**Pattern Matching for Artifacts**
|
||||
|
||||
When fetching selective artifacts, you are not limited to exact names or the `all` keyword. You can use shell-style wildcard patterns to match multiple artifacts flexibly. This is particularly useful for selecting platform-specific builds, version ranges, or file types without specifying each file individually.
|
||||
|
||||
The pattern matching supports standard Unix wildcards:
|
||||
|
||||
- `*` matches any sequence of characters (including empty)
|
||||
- `?` matches any single character
|
||||
- `[seq]` matches any character in *seq* (for example `[0-9]` or `[abc]`)
|
||||
- `[!seq]` matches any character not in *seq*
|
||||
|
||||
For example, to fetch all wheel files for Python 3 across any platform:
|
||||
|
||||
```text
|
||||
$ rngit release rns://remote_node/public/myrepo fetch "1.2.0:*-py3-*.whl"
|
||||
```
|
||||
|
||||
To fetch a specific patch version when you know the major and minor version:
|
||||
|
||||
```text
|
||||
$ rngit release rns://remote_node/public/myrepo fetch "1.2.0:myapp-1.2.?-linux-x86_64.tar.gz"
|
||||
```
|
||||
|
||||
Or to retrieve all source archives:
|
||||
|
||||
```text
|
||||
$ rngit release rns://remote_node/public/myrepo fetch "1.2.0:source_*.tgz"
|
||||
```
|
||||
|
||||
If your pattern contains no wildcard characters, it must match an artifact name exactly, which is useful for fetching single, specific artifacts. When a pattern matches multiple artifacts, all matched files are fetched and verified. If no artifacts match the pattern, the fetch aborts with an error indicating no matches were found.
|
||||
|
||||
**Offline Verification**
|
||||
|
||||
Because the release manifest contains embedded signatures, you can verify the integrity of release artifacts offline, without connecting to the repository node. The `rnid` and `rngit` utilities can validate artifact signatures against `.rsg` and manifest files.
|
||||
|
||||
@@ -1295,4 +1295,24 @@ will announce it.
|
||||
|
||||
* **Parameters:**
|
||||
* **destination_hash** – A destination hash as *bytes*.
|
||||
* **on_interface** – If specified, the path request will only be sent on this interface. In normal use, Reticulum handles this automatically, and this parameter should not be used.
|
||||
* **on_interface** – If specified, the path request will only be sent on this interface. In normal use, Reticulum handles this automatically, and this parameter should not be used.
|
||||
|
||||
#### `static blackhole_identity(identity_hash, until=None, reason=None)`
|
||||
|
||||
Blackholes an identity.
|
||||
|
||||
* **Parameters:**
|
||||
* **identity_hash** – The identity hash to blackhole as *bytes*.
|
||||
* **until** – Optional unix timestamp of when the blackhole expires as *float* or *int*.
|
||||
* **reason** – Optional reason for the blackhole as *str*.
|
||||
* **Returns:**
|
||||
*True* if successful, otherwise *False*.
|
||||
|
||||
#### `static unblackhole_identity(identity_hash)`
|
||||
|
||||
Lifts blackhole for an identity.
|
||||
|
||||
* **Parameters:**
|
||||
**identity_hash** – The identity hash to blackhole as *bytes*.
|
||||
* **Returns:**
|
||||
*True* if successful, otherwise *False*.
|
||||
@@ -189,8 +189,8 @@ This is not about “dropping out” of society. It is about building a substrat
|
||||
|
||||
**Consider:**
|
||||
|
||||
- **The Old Way:** “My connection is slow. I should call my ISP and complain.”
|
||||
- **The Zen Way:** “The path is noisy. I will adjust the antenna or find a better route.”
|
||||
- **The Old Way:** *“My connection is slow. I should call my ISP and complain.”*
|
||||
- **The Zen Way:** *“The path is noisy. I will adjust the antenna or find a better route.”*
|
||||
|
||||
By taking ownership of the infrastructure, you take ownership of your voice. You stop shouting into someone else’s megaphone and start building your own. The network is no longer something that happens to you; it is something you make happen.
|
||||
|
||||
|
||||
@@ -817,6 +817,42 @@ You can fetch individual artifacts from a release by specifying the artifact nam
|
||||
|
||||
This downloads only the specified artifact and verifies its signature against the manifest. If a file already exists locally, ``rngit`` verifies it against the manifest signature and skips the download if valid, making it safe to run the command multiple times. When fetching releases, ``rngit release`` will only download files that are missing or invalid according to the manifest. This means that partially completed release fetches can be continued later, if interrupted.
|
||||
|
||||
**Pattern Matching for Artifacts**
|
||||
|
||||
When fetching selective artifacts, you are not limited to exact names or the ``all`` keyword. You can use shell-style wildcard patterns to match multiple artifacts flexibly. This is particularly useful for selecting platform-specific builds, version ranges, or file types without specifying each file individually.
|
||||
|
||||
.. tip::
|
||||
When using pattern matching, make sure to enclose the target specification in quotes. Otherwise,
|
||||
your shell will probably interpret it as a shell expansion pattern *before* it is passed as an
|
||||
argument to ``rngit``!
|
||||
|
||||
The pattern matching supports standard Unix wildcards:
|
||||
|
||||
- ``*`` matches any sequence of characters (including empty)
|
||||
- ``?`` matches any single character
|
||||
- ``[seq]`` matches any character in *seq* (for example ``[0-9]`` or ``[abc]``)
|
||||
- ``[!seq]`` matches any character not in *seq*
|
||||
|
||||
For example, to fetch all wheel files for Python 3 across any platform:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit release rns://remote_node/public/myrepo fetch "1.2.0:*-py3-*.whl"
|
||||
|
||||
To fetch a specific patch version when you know the major and minor version:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit release rns://remote_node/public/myrepo fetch "1.2.0:myapp-1.2.?-linux-x86_64.tar.gz"
|
||||
|
||||
Or to retrieve all source archives:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit release rns://remote_node/public/myrepo fetch "1.2.0:source_*.tgz"
|
||||
|
||||
If your pattern contains no wildcard characters, it must match an artifact name exactly, which is useful for fetching single, specific artifacts. When a pattern matches multiple artifacts, all matched files are fetched and verified. If no artifacts match the pattern, the fetch aborts with an error indicating no matches were found.
|
||||
|
||||
**Offline Verification**
|
||||
|
||||
Because the release manifest contains embedded signatures, you can verify the integrity of release artifacts offline, without connecting to the repository node. The ``rnid`` and ``rngit`` utilities can validate artifact signatures against ``.rsg`` and manifest files.
|
||||
|
||||
+2
-2
@@ -210,8 +210,8 @@ This is not about "dropping out" of society. It is about building a substrate on
|
||||
|
||||
**Consider:**
|
||||
|
||||
- **The Old Way:** "My connection is slow. I should call my ISP and complain."
|
||||
- **The Zen Way:** "The path is noisy. I will adjust the antenna or find a better route."
|
||||
- **The Old Way:** *"My connection is slow. I should call my ISP and complain."*
|
||||
- **The Zen Way:** *"The path is noisy. I will adjust the antenna or find a better route."*
|
||||
|
||||
By taking ownership of the infrastructure, you take ownership of your voice. You stop shouting into someone else's megaphone and start building your own. The network is no longer something that happens to you; it is something you make happen.
|
||||
|
||||
|
||||
+42
-1
@@ -62,7 +62,7 @@ class Packet:
|
||||
|
||||
def set_delivered_callback(self, callback: Callable[[Packet], None]):
|
||||
self.delivered_callback = callback
|
||||
|
||||
|
||||
def delivered(self):
|
||||
with self.lock:
|
||||
self.state = MessageState.MSGSTATE_DELIVERED
|
||||
@@ -265,6 +265,47 @@ class TestChannel(unittest.TestCase):
|
||||
self.assertEqual(MessageState.MSGSTATE_FAILED, packet.state)
|
||||
self.assertFalse(envelope.tracked)
|
||||
|
||||
def test_send_on_failing_outlet_does_not_corrupt_state(self):
|
||||
# if outlet.send() returns a packet that never reached
|
||||
# the wire (LinkChannelOutlet does this when the link is not ACTIVE; the
|
||||
# returned packet has raw=None), Channel.send() must not consume a
|
||||
# sequence number or leave a packetless envelope in _tx_ring. Before
|
||||
# the fix, the envelope was queued before outlet.send() returned, so a
|
||||
# "dead" return left a raw=None envelope in the ring and silently
|
||||
# advanced _next_sequence, stalling the channel on the other end.
|
||||
print("Channel test send on failing outlet")
|
||||
|
||||
original_send = self.h.outlet.send
|
||||
|
||||
def ghost_send(raw):
|
||||
with self.h.outlet.lock:
|
||||
packet = Packet(None)
|
||||
packet.state = MessageState.MSGSTATE_FAILED
|
||||
self.h.outlet.packets.append(packet)
|
||||
return packet
|
||||
|
||||
self.h.outlet.send = ghost_send
|
||||
|
||||
pre_sequence = self.h.channel._next_sequence
|
||||
self.assertEqual(0, len(self.h.channel._tx_ring))
|
||||
|
||||
with self.assertRaises(RNS.Channel.ChannelException):
|
||||
self.h.channel.send(MessageTest())
|
||||
|
||||
# Sequence must not have been consumed.
|
||||
self.assertEqual(pre_sequence, self.h.channel._next_sequence)
|
||||
# _tx_ring must not contain a packetless envelope.
|
||||
self.assertEqual(0, len(self.h.channel._tx_ring))
|
||||
|
||||
# A subsequent successful send should use the same sequence number as
|
||||
# was reserved for the failed attempt.
|
||||
self.h.outlet.send = original_send
|
||||
envelope = self.h.channel.send(MessageTest())
|
||||
self.assertEqual(pre_sequence, envelope.sequence)
|
||||
self.assertIsNotNone(envelope.packet)
|
||||
self.assertIsNotNone(envelope.packet.raw)
|
||||
self.assertTrue(envelope in self.h.channel._tx_ring)
|
||||
|
||||
def test_multiple_handler(self):
|
||||
print("Channel test multiple handler short circuit")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user