Compare commits

...

73 Commits

Author SHA1 Message Date
Mark Qvist 7cbce84cbd Prepare release 2026-05-21 17:50:18 +02:00
Mark Qvist fe334c0d7c Updated changelog 2026-05-21 17:41:27 +02:00
Mark Qvist 33d5a8e2a8 Cleanup 2026-05-21 17:38:29 +02:00
Mark Qvist e80bf471ec Slight robustification 2026-05-21 17:31:20 +02:00
Mark Qvist 7dfdea2395 Raise descriptive error if hashlib.file_digest is not available. 2026-05-21 17:16:31 +02:00
Mark Qvist 74b61aebd2 Updated docs 2026-05-21 17:06:47 +02:00
Mark Qvist ce9071e2d3 Added ability to use wildcards in artifact fetch specifications 2026-05-21 17:06:04 +02:00
Mark Qvist d6cf59dcc8 Fix error message when no specified artifacts were available for fetch 2026-05-21 16:34:34 +02:00
Mark Qvist de61652d37 Updated changelog 2026-05-21 16:31:48 +02:00
Mark Qvist 6181f62d93 Return not found instead of remote error on missing document 2026-05-21 15:31:22 +02:00
Mark Qvist ed66b4873e Updated version 2026-05-21 15:17:33 +02:00
Mark Qvist 26869941a4 Merge branch 'patch_fix_channel_outlet_race' 2026-05-21 15:16:14 +02:00
Mark Qvist ee7b4e7ae5 Consistency 2026-05-21 15:16:09 +02:00
Mark Qvist 7866484453 Merge branch 'fix_kd_iter' 2026-05-21 15:01:37 +02:00
Mark Qvist 817b3b1a12 Consistency 2026-05-21 15:01:17 +02:00
Mark Qvist 17f6968467 Added blackhole methods to API docs 2026-05-21 13:19:06 +02:00
Mark Qvist a96a1d6692 Adjusted timeouts 2026-05-20 01:08:11 +02:00
Mark Qvist c1081fa9a4 Consistency 2026-05-20 01:07:54 +02:00
Mark Qvist dd3104094b Fixed check for existing link at shutdown 2026-05-20 00:32:08 +02:00
Mark Qvist dc68eea313 Fix commit message rendering 2026-05-19 21:35:45 +02:00
Jeremy O'Brien 794e437f6d Channel: prevent sequence holes and ghost envelopes when sending on a dying outlet
RNSChannelOutlet.send() can return a packet that never reached the wire
(link not ACTIVE, no capable interface, etc). The old Channel.send()
queued the envelope in _tx_ring before calling outlet.send(), then
tried to rewind _next_sequence and remove the envelope if the outlet
returned a failed packet. Two problems:

- Between queueing and outlet.send() returning, _tx_ring held an
envelope with packet.raw=None. Any worker thread iterating the
ring (timeout fire, proof callback) crashed in get_packet_id's
packet.get_hash() with a TypeError on None.raw.

- The rewind was only safe for a single-threaded sender: it checked
"is _next_sequence one past mine?" and skipped the rewind otherwise.
Under concurrent senders, the rewind silently failed, leaving a
hole in the on-wire sequence stream. The receiver's contiguous
seqnum rule then stalled the channel permanently with no error.

This fix serializes the reservation-and-transmit pair with a per-channel
_send_lock so the rewind is always correct, and defers queueing until
outlet.send() returns a real packet so _tx_ring never contains a
packet-less envelope. _packet_tx_op() and get_packet_id() now also
defensively skip/return-None for packet-less envelopes.

Also handle the small race where a proof arrives between outlet.send()
registering the receipt and us installing the delivery callback: after
registration, re-read the receipt status and synthesize the
_packet_delivered() call if it's already DELIVERED.
2026-05-19 14:52:59 -04:00
Jeremy O'Brien ebf544d335 rnsh: don't wait forever for rns operations when timeout isn't set 2026-05-19 07:46:50 -04:00
Jeremy O'Brien 939f30fef2 Don't iterate known_destinations directly; it can change during iteration 2026-05-19 07:46:50 -04:00
Mark Qvist 4549bbfdb9 Docs formatting 2026-05-19 11:41:09 +02:00
Mark Qvist 6c989eb38e Prepare release 2026-05-19 01:08:42 +02:00
Mark Qvist 137d73ad0d Updated version 2026-05-19 00:51:12 +02:00
Mark Qvist 58d4162f6d Updated rngit documentation 2026-05-19 00:48:14 +02:00
Mark Qvist 67625395fe Updated logging 2026-05-18 22:48:18 +02:00
Mark Qvist f62512381a Updated rngit documentation 2026-05-18 22:01:55 +02:00
Mark Qvist 888e3102de Added offline RSM release manifest verification 2026-05-18 17:55:10 +02:00
Mark Qvist f83435c697 Updated rngit documentation 2026-05-18 17:13:31 +02:00
Mark Qvist 5243d646f0 Improved known destination persist reliability 2026-05-18 14:57:17 +02:00
Mark Qvist bdb284ce5d Improved page node ref handling in commit links 2026-05-18 14:46:00 +02:00
Mark Qvist 1b34820601 Added ability to fetch new verified releases directly from RSM-embedded release manifest data. Added local release generation and signing with the --local option to rnid. 2026-05-18 13:49:01 +02:00
Mark Qvist 01010dd599 Added version getter to setup.py 2026-05-18 13:46:18 +02:00
Mark Qvist da32709f7c Updated makefile 2026-05-18 13:45:40 +02:00
Mark Qvist cdc6159a15 Added canonical release RSM structure validator to rnid 2026-05-18 13:32:54 +02:00
Mark Qvist eb2f7ae455 Implemented remote HEAD tracking for forks and mirrors in rngit 2026-05-18 12:39:22 +02:00
Mark Qvist a74f1bd89f Save manifest on release fetch 2026-05-18 11:52:46 +02:00
Jeremy O'Brien 603f709139 Don't iterate known_destinations directly; it can change during iteration 2026-05-18 00:10:25 -04:00
Mark Qvist 0dd063a32e Clear previous request progress 2026-05-18 03:37:52 +02:00
Mark Qvist 9885a70a88 Prepare release 2026-05-18 03:32:46 +02:00
Mark Qvist e4a85de089 Actually send manifest and rsgs 2026-05-18 03:31:40 +02:00
Mark Qvist ca0d2dffbe Prepare release 2026-05-18 03:06:16 +02:00
Mark Qvist 511d169c77 Cleanup 2026-05-18 03:03:30 +02:00
Mark Qvist 19bc8ef85c Cleanup 2026-05-18 03:00:52 +02:00
Mark Qvist 1e33d3eebb Updated version 2026-05-18 02:52:30 +02:00
Mark Qvist d18f434583 Implemented rsm-verified release fetching with embedded artifact signatures 2026-05-18 02:51:53 +02:00
Mark Qvist 64749b4d18 Cleanup. Prepared artifact fetch. 2026-05-18 01:24:15 +02:00
Mark Qvist 875d8ef7eb Updated changelog 2026-05-18 01:22:59 +02:00
Mark Qvist 20283f1536 Added automatic signing and release manifest generation to rnid release 2026-05-18 00:44:41 +02:00
Mark Qvist c4af328802 Dropped note meta field requirement from rsg structure 2026-05-18 00:10:22 +02:00
Mark Qvist a2193b9ffd Dropped note meta field requirement from rsg structure 2026-05-18 00:05:29 +02:00
Mark Qvist d2542fd49b Added blocked identities handling and push stats ignore to rngit 2026-05-17 23:26:19 +02:00
Mark Qvist 0333884877 Handle silly links 2026-05-17 23:09:21 +02:00
Mark Qvist 63947ed69a Oops 2026-05-17 23:07:44 +02:00
Mark Qvist d6d18ce29c Fixed micron tags escaping to where they shouldn't go and wreaking havoc on the rest of the pafe. Looking at you table cell truncator. 2026-05-17 23:01:36 +02:00
Mark Qvist 2ef58d8b59 Better table cell truncation method 2026-05-17 22:01:30 +02:00
Mark Qvist 15f2d1635e Added fork and mirror sync time to repo pages 2026-05-17 21:14:26 +02:00
Mark Qvist c83b71f49a Cleanup 2026-05-17 20:45:38 +02:00
Mark Qvist f0824fd71e Added RSM metadata embedding and spec validation to rnid 2026-05-17 20:29:40 +02:00
Mark Qvist 8dde60658f Added validate.py for spec validation 2026-05-17 20:27:16 +02:00
Mark Qvist 9437648ae5 Adjusted logging 2026-05-17 19:06:24 +02:00
Mark Qvist 71b19aca2c Added signed message from file creation to rnid. Added signed message metadata output option to rnid. 2026-05-17 18:29:44 +02:00
Mark Qvist 7d320f8cd5 Fixed missing working identity check on message signing op 2026-05-17 17:27:18 +02:00
Mark Qvist 340d0883a7 Updated docs 2026-05-17 17:24:44 +02:00
Mark Qvist 66096acc29 Added ability to render raw micron in markdown files 2026-05-17 17:22:25 +02:00
Mark Qvist e35100d865 Updated docs 2026-05-17 16:51:20 +02:00
Mark Qvist 128455ef01 Implemented remote permissions management in rngit 2026-05-17 16:49:44 +02:00
Mark Qvist 10156cc90e Updated rngit docs 2026-05-17 15:19:42 +02:00
Mark Qvist 4f5482f2ae Implemented identity and destination aliases in rngit 2026-05-17 15:09:00 +02:00
Mark Qvist 2b9fdae74b Fixed typo 2026-05-17 12:49:53 +02:00
Mark Qvist 7506caa0da Fixed f-string for old snakes 2026-05-17 12:25:21 +02:00
55 changed files with 4581 additions and 465 deletions
+102
View File
@@ -1,3 +1,105 @@
### 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.
**Changes**
- Added full `rngit` documentation to the manual
- Added offline `.rsm` release manifest verification
- Added the ability to fetch release updates directly from `.rsm` manifests
- Added canonical `.rsm` release structure validator to `rnid` for import
- Added `.rsm` manifest saving when using `rngit release fetch`
- Added remote `HEAD` tracking for forks and mirros to `rngit`
- Improved known destinations persist reliability
- Improved page node ref link handling in `rngit`
- Improved logging in various locations
**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.2.8-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-18: RNS 1.2.8
This release improves the `rngit` system with signed release manifest generation and automatic artifact signing. It also includes several additions to `rnid` and various minor fixes and improvements to the `rngit` system.
**Changes**
- Added signed release manifest generation to `rngit release`
- Added verified release fetching to `rngit release`
- Added automatic artifact signing to `rngit release`
- Added signed message creation from file to `rnid`
- Added signed message metadata output option to `rnid`
- Added `rsm` metadata embedding and spec validation to `rnid`
- Added identity and destination aliases to `rngit`
- Added blocked identities option to `rngit`
- Added ability to render raw micron in markdown files to `rngit`
- Added fork and mirror last sync time to repository page in `rngit`
- Better handling of silly links in `rngit`
- Fixed markdown table cell truncation not closing micron tags
- Fixed various minor bugs and inconsistencies in `rngit`
- Dropped `note` metadata field requirement from `rsg` structure
**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-17: RNS 1.2.7
This release significantly improves the `rngit` system with fork, mirroring and empty repository creation functionality, a new work document proposals feature, improvements to the transport core reliability and efficiency and various other tweaks and improvements.
+17 -4
View File
@@ -56,22 +56,35 @@ documentation:
manual:
make -C docs latexpdf epub
distcollect:
cp docs/Reticulum\ Manual.* dist
build_spkg: remove_symlinks build_sdist create_symlinks
release: test remove_symlinks build_sdist build_wheel build_pure_wheel documentation manual create_symlinks
release: test remove_symlinks build_sdist build_wheel build_pure_wheel documentation manual distcollect create_symlinks
debug: remove_symlinks build_wheel build_pure_wheel create_symlinks
upload: upload-rns upload-rnspure
local: release sign
upload-rns:
sign:
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum create $$(python setup.py --getversion):dist --name rns --local
upload:
@echo Ready to publish release over Reticulum
@read VOID
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum create $$(python setup.py --getversion):dist --name rns
upload-pip: upload-rns-pip upload-rnspure-pip
upload-rns-pip:
@echo Ready to publish rns release, hit enter to continue
@read VOID
@echo Uploading to PyPi...
twine upload dist/rns-*.whl dist/rns-*.tar.gz
@echo Release published
upload-rnspure:
upload-rnspure-pip:
@echo Ready to publish rnspure release, hit enter to continue
@read VOID
@echo Uploading to PyPi...
+89 -56
View File
@@ -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
+2
View File
@@ -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()
+26 -10
View File
@@ -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
@@ -212,8 +214,17 @@ class Identity:
RNS.log("Skipped recombining known destinations from disk, since an error occurred: "+str(e), RNS.LOG_WARNING)
RNS.log("Saving "+str(len(Identity.known_destinations))+" known destinations to storage...", RNS.LOG_VERBOSE)
with open(RNS.Reticulum.storagepath+"/known_destinations","wb") as file:
umsgpack.dump(Identity.known_destinations.copy(), file)
temp_file = RNS.Reticulum.storagepath+f"/known_destinations.tmp.{time.time()}"
try:
with open(temp_file,"wb") as file: umsgpack.dump(Identity.known_destinations.copy(), file)
os.rename(temp_file, RNS.Reticulum.storagepath+f"/known_destinations")
except Exception as e:
RNS.log(f"Error while serializing and writing known destinations: {e}", RNS.LOG_ERROR)
try: os.unlink(temp_file)
except Exception as e: RNS.log(f"Could not clean up temporary file {temp_file}: {e}", RNS.LOG_WARNING)
raise e
save_time = time.time() - save_start
if save_time < 1: time_str = str(round(save_time*1000,2))+"ms"
@@ -285,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
@@ -302,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:
+14
View File
@@ -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)
+41
View File
@@ -101,6 +101,7 @@ class ReticulumGitClient():
self.config = None
self.ready = False
self.destination_aliases = {}
self.remote_identity = None
self.destination = None
self.link = None
@@ -170,6 +171,18 @@ class ReticulumGitClient():
section = self.config["client"]
if "ref_batch_size" in section: self.ref_batch_size = max(0, min(1024, section.as_int("ref_batch_size")))
if "aliases" in self.config:
section = self.config["aliases"]
for alias in section:
alias_hexhash = section[alias]
len_ok = len(alias_hexhash) == RNS.Identity.TRUNCATED_HASHLENGTH//8*2
try: alias_hash = bytes.fromhex(alias_hexhash)
except: alias_hash = None
alias_exists = alias in self.destination_aliases
if not len_ok or not alias_hash: continue
if alias_exists: continue
self.destination_aliases[alias] = RNS.hexrep(alias_hash, delimit=False)
if not os.path.isfile(self.identitypath):
identity = RNS.Identity()
identity.to_file(self.identitypath)
@@ -185,6 +198,19 @@ class ReticulumGitClient():
else: self.identity = identity
self.destination_hexhash = self.__resolve_destination_alias(self.destination_hexhash)
def __resolve_destination_alias(self, alias):
def resolve(alias):
len_match = len(alias) == RNS.Identity.TRUNCATED_HASHLENGTH//8*2
try: hash_bytes = bytes.fromhex(alias)
except: hash_bytes = None
if len_match and hash_bytes: return alias
else: return self.destination_aliases[alias] if alias in self.destination_aliases else alias
resolved = resolve(alias)
return resolved
def abort(self, reason=None, code=255):
if not reason: reason = "Unknown reason"
print(f"git-remote-rns failed: {reason}", file=sys.stderr)
@@ -656,6 +682,21 @@ __default_rngit_config__ = '''# This is the default rngit client config file.
ref_batch_size = 25
[aliases]
# You can define aliases for commonly used destination
# hashes in this section. Each line must be in the format
# aliased_name = DESTINATION_HASH
#
# These hashes are used for resolving remote destinations.
# For rngit node permissions and identity resolution,
# aliases must be defined in ~/.rngit/config.
# my_node = 063d38912bffc850af4a1b8a270a9d85
# bobs_node = 714981d03e41deda0e4468cb274414cc
[logging]
# Valid log levels are 0 through 7:
# 0: Log only critical information
+32 -12
View File
@@ -437,6 +437,10 @@ class NomadNetworkNode():
repo = self.get_accessible_repository(remote_identity, group_name, repo_name)
if not repo:
content = self.m_heading("Not Found", 1) + "\nThe requested repository was not found.\n"
return self.render_template(content, nav_content="".join(nav_parts), st=st)
repo_source = ""; source_link = None
if repo["fork"] or repo["mirror"]:
if repo["fork"]: source_type = "fork"; source_url = repo["fork"]
@@ -457,15 +461,14 @@ class NomadNetworkNode():
source_link = f"{mu_link}"
except Exception as e: source_link = ""
synced_ago = max(0, time.time()-self.owner.last_upstream_sync(repo["path"]))
sync_time = RNS.prettytime(synced_ago, compact=True).split(" ")[0]
sync_str = f" `*{self.CLR_DIM_H}synced {sync_time} ago`f`*\n"
source_desc = f"{source_type}ed from"
source_indent = " "*(len(f"Node / {group_name} / {repo_name}")-len(source_desc))
if source_link: source_url = source_link
nav_parts.append(f"{self.CLR_DIM}{source_desc.capitalize()}{source_indent} {source_url}`f\n")
nav_parts.append(f"{self.CLR_DIM}{source_desc.capitalize()}{source_indent} {source_url}`f{sync_str}\n")
if not repo:
content = self.m_heading("Not Found", 1) + "\nThe requested repository was not found.\n"
return self.render_template(content, nav_content="".join(nav_parts), st=st)
description = self.get_repository_description(repo["path"])
if description: description = f"{description}\n\n"
else: description = ""
@@ -702,6 +705,7 @@ class NomadNetworkNode():
content = self.m_heading("Invalid Path", 1) + "\n\nNo file path specified.\n"
return self.render_template(content, st=st)
file_path = file_path.lstrip("./").replace("/./", "/")
file_ext = os.path.splitext(file_path)[1].lower()
renderable = file_ext in self.RENDERABLE_EXTS
if not renderable: raw = True; render = False
@@ -860,7 +864,7 @@ class NomadNetworkNode():
author = commit["author"]
date = self.format_absolute_time(commit["timestamp"])+" - "+self.format_relative_time(commit["timestamp"])
hash_link = self.m_link(short_hash, self.PATH_COMMIT, g=group_name, r=repo_name, h=commit["hash"])
hash_link = self.m_link(short_hash, self.PATH_COMMIT, g=group_name, r=repo_name, ref=ref, h=commit["hash"])
content_parts.append(f"`F66d{hash_link}`f {self.m_escape(author)} {self.CLR_DIM}{date}`f\n")
content_parts.append(f"{self.m_escape(subject)}\n\n")
@@ -887,6 +891,7 @@ class NomadNetworkNode():
if not data or not type(data) == dict: data = {}
group_name = data.get("var_g", "") if data else ""
repo_name = data.get("var_r", "") if data else ""
ref = data.get("var_ref", "HEAD") if data else "HEAD"
commit_hash = data.get("var_h", "") if data else ""
if not group_name or not repo_name:
@@ -900,6 +905,12 @@ class NomadNetworkNode():
repo_path = repo["path"]
# Validate ref exists
resolved_ref = self.resolve_ref(repo_path, ref)
if not resolved_ref:
content = self.m_heading("Ref Not Found", 1) + f"\n\nThe ref '{ref}' does not exist in this repository.\n"
return self.render_template(content, st=st)
# Validate commit hash
if not commit_hash or len(commit_hash) < 7:
content = self.m_heading("Error", 2) + "\nNo valid commit hash specified.\n"
@@ -913,7 +924,7 @@ class NomadNetworkNode():
# Breadcrumb navigation
nav_parts = []
breadcrumb = f"{self.m_link('Node', self.PATH_INDEX)} / {self.m_link(group_name, self.PATH_GROUP, g=group_name)} / {self.m_link(repo_name, self.PATH_REPO, g=group_name, r=repo_name)} / {resolved_hash[:7]}"
breadcrumb = f"{self.m_link('Node', self.PATH_INDEX)} / {self.m_link(group_name, self.PATH_GROUP, g=group_name)} / {self.m_link(repo_name, self.PATH_REPO, g=group_name, r=repo_name)} / {self.m_link('commits', self.PATH_COMMITS, g=group_name, r=repo_name, ref=ref)} / {resolved_hash[:7]}"
nav_parts.append(">>\n" + breadcrumb + "\n")
nav_content = "".join(nav_parts)
@@ -951,7 +962,7 @@ class NomadNetworkNode():
if commit_info.get("parents"):
parent_links = []
for parent_hash in commit_info["parents"]:
parent_link = self.m_link(parent_hash[:7], self.PATH_COMMIT, g=group_name, r=repo_name, h=parent_hash)
parent_link = self.m_link(parent_hash[:7], self.PATH_COMMIT, g=group_name, r=repo_name, ref=ref, h=parent_hash)
parent_links.append(parent_link)
content_parts.append(f"Parents: {' '.join(parent_links)}\n")
@@ -967,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
@@ -1136,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)
@@ -2334,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:
@@ -2461,7 +2481,7 @@ class NomadNetworkNode():
line = ""
for val in data:
upper_filled = val >= row_top
lower_filled = val >= row_mid
lower_filled = val >= row_mid or row == 1 and val > 0
if not upper_filled and not lower_filled: line += " "
elif upper_filled: line += f"`FT{gradient_color(grad_top)}`BT{gradient_color(grad_mid)}▀`f`b"
@@ -2571,7 +2591,7 @@ class NomadNetworkNode():
first = str(labels[0])[:12]
last = str(labels[-1])[:12]
mid_space = len(bottom) - len(first) - len(last)
lines.append(f"{self.CLR_DIM}{first}{" " * mid_space}{last}`f\n")
lines.append(f"{self.CLR_DIM}{first}{' ' * mid_space}{last}`f\n")
return "".join(lines)
File diff suppressed because it is too large Load Diff
+70 -23
View File
@@ -208,19 +208,21 @@ class MarkdownToMicron:
code_content = '\n'.join(code_buffer)
if self.syntax_highlighter and code_block_lang:
try:
highlighted = self.syntax_highlighter.highlight(code_content, language=code_block_lang)
result_lines.append(f"{self.CODE_BG}{self.CODE_FG}")
result_lines.append(highlighted)
result_lines.append(self.CODE_RESET)
if code_block_lang.lower() == "rawmu": result_lines.append(code_content)
else:
try:
highlighted = self.syntax_highlighter.highlight(code_content, language=code_block_lang)
result_lines.append(f"{self.CODE_BG}{self.CODE_FG}")
result_lines.append(highlighted)
result_lines.append(self.CODE_RESET)
except Exception:
# Fallback to plain literal block on any error
result_lines.append(f"{self.CODE_BG}{self.CODE_FG}")
result_lines.append(self.LITERAL_START)
result_lines.append(self._escape_literals(code_content))
result_lines.append(self.LITERAL_END)
result_lines.append(self.CODE_RESET)
except Exception:
# Fallback to plain literal block on any error
result_lines.append(f"{self.CODE_BG}{self.CODE_FG}")
result_lines.append(self.LITERAL_START)
result_lines.append(self._escape_literals(code_content))
result_lines.append(self.LITERAL_END)
result_lines.append(self.CODE_RESET)
else:
result_lines.append(f"{self.CODE_BG}{self.CODE_FG}")
result_lines.append(self.LITERAL_START)
@@ -684,19 +686,64 @@ class MarkdownToMicron:
return " " * left + text + " " * right
else:
return text + " " * padding
def _truncate_cell(self, text, width):
if self._visible_width(text) <= width: return text
stripped = text
stripped = re.sub(r'`[FB][0-9a-fA-F]{3}', '', stripped)
stripped = re.sub(r'`[!*_]', '', stripped)
stripped = re.sub(r'`f`b', '', stripped)
if len(stripped) <= width - 1: return text
truncated = stripped[:width - 1] + ""
return truncated
truncation_point = len(text)
while truncation_point > 0 and self._visible_width(text[0:truncation_point]) >= width:
truncation_point -= 1
truncated = text[:truncation_point]
# Yes, this is convoluted, but if someone else has
# a better idea on how to handle unclosed micron
# tags in the truncated cells, I'm all ears.
active_tags = set()
fg_active = False
bg_active = False
i = 0
while i < len(truncated):
if truncated[i] == '`':
if i + 1 < len(truncated):
tag_char = truncated[i + 1]
if tag_char in '!*_=':
if tag_char in active_tags: active_tags.remove(tag_char)
else: active_tags.add(tag_char)
i += 2
continue
elif tag_char == 'f':
fg_active = False
i += 2
continue
elif tag_char == 'b':
bg_active = False
i += 2
continue
elif tag_char == 'F':
fg_active = True
if i + 2 < len(truncated) and truncated[i + 2] == 'T': i += 8
else: i += 5
continue
elif tag_char == 'B':
bg_active = True
if i + 2 < len(truncated) and truncated[i + 2] == 'T': i += 8
else: i += 5
continue
i += 1
closers = []
if fg_active: closers.append('`f')
if bg_active: closers.append('`b')
for fmt in active_tags: closers.append(f'`{fmt}')
return truncated + ''.join(closers) + ""
def _wrap_text(self, text, width):
if not text: return [""]
+106 -13
View File
@@ -40,12 +40,15 @@ import base64
from RNS._version import __version__
from RNS.vendor import umsgpack as mp
from RNS.vendor.configobj import ConfigObj
from RNS.vendor.validate import Validator
from RNS.Cryptography.Hashes import sha256
from RNS.Cryptography.Hashes import file_sha256
APP_NAME = "rns"
DEFAULT_ASPECTS = f"{APP_NAME}.id"
NO_MESSAGE = 0x01
NO_META = 0x02
PRV_EXT = "rid"
PUB_EXT = "pub"
@@ -122,11 +125,14 @@ def main():
parser.add_argument("-e", "--encrypt", metavar="file", action="store", nargs="*", default=None, help="encrypt file")
parser.add_argument("-V", "--validate", metavar="path", action="store", nargs="*", default=None, help="validate signature")
parser.add_argument("-s", "--sign", metavar="path", action="store", nargs="*", default=None, help="sign file")
parser.add_argument("-S", "--sign-message", metavar="path", action="store", nargs="?", const=NO_MESSAGE, default=None, help="create embedded signed message")
parser.add_argument("-S", "--sign-message", metavar="text", action="store", nargs="?", const=NO_MESSAGE, default=None, help="create embedded signed message")
parser.add_argument("-E", "--embed-meta", metavar="path", action="store", nargs="?", const=NO_META, default=None, help="embed metadata structure from file")
parser.add_argument("--meta-spec", metavar="path", action="store", nargs="?", default=None, help="validate metadata for embedding with spec from file")
parser.add_argument("--raw", action="store_true", default=False, help="sign raw input data instead of hashing first")
# I/O Control
parser.add_argument("-w", "--write", metavar="file", action="store", default=None, help="output file path", type=str)
parser.add_argument("-w", "--write", metavar="path", action="store", default=None, help="output file path", type=str)
parser.add_argument("-r", "--read", metavar="path", action="store", default=None, help="input file path for operations with optional file input", type=str)
parser.add_argument("-f", "--force", action="store_true", default=None, help="write output even if it overwrites existing files")
parser.add_argument("-I", "--stdin", action="store_true", default=False, help=argparse.SUPPRESS) # help="read input from STDIN instead of file"
parser.add_argument("-O", "--stdout", action="store_true", default=False, help=argparse.SUPPRESS) # help="write output to STDOUT instead of file"
@@ -139,17 +145,18 @@ def main():
parser.add_argument("-P", "--print-private", action="store_true", default=False, help="allow displaying private keys")
# Formatting Control
parser.add_argument("-b", "--base64", action="store_true", default=False, help="Use base64-encoded input and output")
parser.add_argument("-B", "--base32", action="store_true", default=False, help="Use base32-encoded input and output")
parser.add_argument("--hex", action="store_true", default=False, help="Use hex-encoded input and output")
parser.add_argument("--base256", action="store_true", default=False, help="Use base256-encoded input and output")
parser.add_argument("-b", "--base64", action="store_true", default=False, help="Use base64-encoded input and output")
parser.add_argument("-U", "--base256", action="store_true", default=False, help="Use base256-encoded input and output")
parser.add_argument("-F", "--hex", action="store_true", default=False, help="Use hex-encoded input and output")
parser.add_argument("--meta", action="store_true", default=False, help="Display RSM metadata if available")
parser.add_argument("--version", action="version", version="rnid {version}".format(version=__version__))
args = parser.parse_args()
validate_args(args)
op_requires_identity = (args.sign or args.encrypt or args.decrypt or args.announce or args.write
op_requires_identity = (args.sign or args.sign_message or args.encrypt or args.decrypt or args.announce or args.write
or args.print_identity or args.print_identity or args.export_pub or args.export_prv)
identity = get_operating_identity(args, allow_none=not op_requires_identity, no_cache=args.no_cache); op = False
@@ -456,7 +463,6 @@ def validate_rsg(rsg, message=None, required_signer=None):
if not "meta" in signed_data: return False, None, None
if not "signer" in signed_data["meta"]: return False, None, None
if not "pubkey" in signed_data["meta"]: return False, None, None
if not "note" in signed_data["meta"]: return False, None, None
try:
if type(required_signer) == RNS.Identity:
@@ -479,7 +485,7 @@ def validate_rsg(rsg, message=None, required_signer=None):
return False, signed_data, signing_identity
def create_rsg(signer_identity, message, embed=False, note=None, meta=None, output="bin"):
def create_rsg(signer_identity, message, embed=False, meta=None, output="bin"):
if not output in ["bin", "hex", "base32", "base256", "base64"]: raise TypeError(f"Invalid output format for rsg creation")
if not type(signer_identity) == RNS.Identity: raise TypeError(f"{signer_identity} is not a Reticulum Identity")
if not signer_identity.get_private_key(): raise ValueError(f"{signer_identity} does not hold a private key")
@@ -487,7 +493,7 @@ def create_rsg(signer_identity, message, embed=False, note=None, meta=None, outp
signed_data = { "hashtype": "sha256", "hash": get_rsg_hash(message),
"meta": { "signer": signer_identity.hash,
"pubkey": signer_identity.get_public_key(),
"note" : note } }
"note" : None } } # TODO: Remove default note field in 1.2.9
if embed:
if type(message) == str: message = message.encode("utf-8")
@@ -495,7 +501,7 @@ def create_rsg(signer_identity, message, embed=False, note=None, meta=None, outp
if meta and type(meta) == dict:
for key in meta:
if not key in signed_data["meta"]: signed_data["meta"]["key"] = meta["key"]
if not key in signed_data["meta"]: signed_data["meta"][key] = meta[key]
envelope = mp.packb(signed_data)
signature = signer_identity.sign(envelope)
@@ -558,6 +564,41 @@ def unwrap_rsg(wrapped_rsg):
return unwrapped if unwrapped else None
def rsg_meta_from_file(path, spec_path=None):
if spec_path: meta_spec = ConfigObj(spec_path)
else: meta_spec = None
parsed = ConfigObj(path, configspec=meta_spec)
if meta_spec:
validation = parsed.validate(Validator())
if not validation == True: raise ValueError("Metadata did not pass spec validation")
return parsed.dict()
def rsg_meta_from_str(meta, spec=None):
if spec: meta_spec = ConfigObj(spec.splitlines())
else: meta_spec = None
parsed = ConfigObj(meta.splitlines(), configspec=meta_spec)
if meta_spec:
validation = parsed.validate(Validator())
if not validation == True: raise ValueError("Metadata did not pass spec validation")
return parsed.dict()
def check_release_rsm_structure(signed_data):
release_meta = signed_data.get("meta", None)
if not release_meta: return "No release metadata in manifest"
release_name = release_meta.get("name", None)
release_version = release_meta.get("version", None)
release_origin = release_meta.get("origin", None)
release_origin_path = release_meta.get("path", None)
if not release_name or not release_version: return "Incomplete package data in manifest"
if not release_origin or not release_origin_path: return "Incomplete release origin data in manifest"
if "/" in release_name or "/" in release_version: return "Invalid data in release manifest"
if len(release_origin) != RNS.Identity.TRUNCATED_HASHLENGTH//8: return "Invalid origin hash length in manifest"
if not type(release_origin) == bytes: return "Invalid origin hash in manifest"
return True
###################################
# Signing & Validation Operations #
@@ -656,7 +697,40 @@ def validate_message(args, identity, __recursive=False):
signer_description = f"\nThe message was NOT signed by {identity_str or signing_identity}" if identity else ""
if not valid: print(f"Invalid signature in {signature_path}{signer_description}"); exit(R_INVALID_SIGNATURE)
else:
print(f"\nSignature is valid, the following message was signed by {signing_identity}:\n")
if args.meta:
print("RSM Metadata\n============\n")
def recurse(entry, key, level=1):
try:
indent = " "*level
if type(entry) == dict:
print(f"d{indent}{key}:")
for key in entry: recurse(entry[key], key, level=level+1)
else:
maxwidth = 64
etype = "u"
if type(entry) == str: etype = "s"
elif type(entry) == bytes: etype = "b"
elif type(entry) == list: etype = "l"
elif type(entry) == dict: etype = "d"
elif type(entry) == int: etype = "i"
elif type(entry) == float: etype = "f"
elif entry == None: etype = "N"
if key == "note" and entry == None: return # TODO: Remove this check in 1.2.9
if type(entry) == bytes: entry = RNS.hexrep(entry, delimit=False)
leadin = f"{etype}{indent}{key}="; leadln = len(leadin)
entry = f"{entry}"; chunk = entry[:maxwidth]; entry = entry[maxwidth:]
print(f"{leadin}{chunk}")
while len(entry): chunk = entry[:maxwidth]; entry = entry[maxwidth:]; print(f" "*leadln+chunk)
except: print(f"E{indent}{key}=<Decode Error>")
meta = signed_data["meta"]
for key in meta: entry = meta[key]; recurse(entry, key)
print("\nValidation\n==========")
c = ":" if not args.meta else ""
f = " following" if not args.meta else ""
print(f"\nSignature is valid, the{f} message was signed by {signing_identity}{c}\n")
if args.meta: print("Message\n=======\n")
print(signed_data["message"].decode("utf-8"))
return exit(R_OK) if not __recursive else R_OK
@@ -714,6 +788,7 @@ def sign(args, identity, __recursive=False):
def sign_message(args, identity):
message = args.sign_message
meta = None
if args.base32: output = "base32"
elif args.base64: output = "base64"
@@ -722,13 +797,31 @@ def sign_message(args, identity):
else: output = "bin"
if output == "bin" and not args.write: print("No write path specified"); exit(R_INVALID_ARGS)
if not identity.get_private_key(): print(f"Cannot sign \"{sign_path}\", the identity does not hold a private key"); exit(R_NO_PRVKEY)
if not identity: print(f"Cannot sign, no working identity available"); exit(R_NO_IDENTITY)
if not identity.get_private_key(): print(f"Cannot sign, the identity does not hold a private key"); exit(R_NO_PRVKEY)
if args.read:
if message != NO_MESSAGE: print("Both an input file and command-line provided message was specified, aborting"); exit(R_INVALID_ARGS)
sign_path = os.path.expanduser(args.read)
if not os.path.isfile(sign_path): print(f"The file {sign_path} does not exist"); exit(R_NO_FILE)
with open(sign_path, "r", encoding="utf-8") as fh: message = fh.read()
if message == NO_MESSAGE: message = get_editor_content()
if not message: print("No message specified"); exit(R_INVALID_ARGS)
if args.embed_meta:
meta_path = os.path.expanduser(args.embed_meta)
meta_spec_path = meta_path+".spec" if not args.meta_spec else args.meta_spec
if not os.path.isfile(meta_path): print(f"Metadata file {meta_path} does not exist"); exit(R_NO_FILE)
if not os.path.isfile(meta_spec_path): meta_spec_path = None
spec_info = f" using spec from {meta_spec_path}" if meta_spec_path else ""
print(f"Embedding metadata from {meta_path}{spec_info}")
try: meta = rsg_meta_from_file(meta_path, spec_path=meta_spec_path)
except Exception as e: print(f"Could not load metadata from {meta_path}: {e}"); exit(R_UNKNOWN_ERROR)
try:
rsg = create_rsg(identity, message, embed=True, output=output)
rsg = create_rsg(identity, message, embed=True, meta=meta, output=output)
if not rsg: print(f"No signature created, not writing"); exit(R_UNKNOWN_ERROR)
if output == "bin":
+2
View File
@@ -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
View File
@@ -1 +1 @@
__version__ = "1.2.7"
__version__ = "1.3.0"
+537
View File
@@ -0,0 +1,537 @@
# validate.py
# -*- coding: utf-8 -*-
# pylint: disable=
#
# A Validator object.
#
# Copyright (C) 2005-2014:
# (name) : (email)
# Michael Foord: fuzzyman AT voidspace DOT org DOT uk
# Mark Andrews: mark AT la-la DOT com
# Nicola Larosa: nico AT tekNico DOT net
# Rob Dennis: rdennis AT gmail DOT com
# Eli Courtwright: eli AT courtwright DOT org
# This software is licensed under the terms of the BSD license.
# http://opensource.org/licenses/BSD-3-Clause
# ConfigObj 5 - main repository for documentation and issue tracking:
# https://github.com/DiffSK/configobj
import re
import sys
from pprint import pprint
__version__ = '1.0.1'
__all__ = (
'dottedQuadToNum',
'numToDottedQuad',
'ValidateError',
'VdtUnknownCheckError',
'VdtParamError',
'VdtTypeError',
'VdtValueError',
'VdtValueTooSmallError',
'VdtValueTooBigError',
'VdtValueTooShortError',
'VdtValueTooLongError',
'VdtMissingValue',
'Validator',
'is_integer',
'is_float',
'is_boolean',
'is_list',
'is_tuple',
'is_ip_addr',
'is_string',
'is_int_list',
'is_bool_list',
'is_float_list',
'is_string_list',
'is_ip_addr_list',
'is_mixed_list',
'is_option',
)
_list_arg = re.compile(r'''
(?:
([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\(
(
(?:
\s*
(?:
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s\)][^,\)]*?) # unquoted
)
\s*,\s*
)*
(?:
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s\)][^,\)]*?) # unquoted
)? # last one
)
\)
)
''', re.VERBOSE | re.DOTALL) # two groups
_list_members = re.compile(r'''
(
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s=][^,=]*?) # unquoted
)
(?:
(?:\s*,\s*)|(?:\s*$) # comma
)
''', re.VERBOSE | re.DOTALL) # one group
_paramstring = r'''
(?:
(
(?:
[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\(
(?:
\s*
(?:
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s\)][^,\)]*?) # unquoted
)
\s*,\s*
)*
(?:
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s\)][^,\)]*?) # unquoted
)? # last one
\)
)|
(?:
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s=][^,=]*?)| # unquoted
(?: # keyword argument
[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*
(?:
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s=][^,=]*?) # unquoted
)
)
)
)
(?:
(?:\s*,\s*)|(?:\s*$) # comma
)
)
'''
_matchstring = '^%s*' % _paramstring
def dottedQuadToNum(ip):
# import here to avoid it when ip_addr values are not used
import socket, struct
try:
return struct.unpack('!L',
socket.inet_aton(ip.strip()))[0]
except socket.error:
raise ValueError('Not a good dotted-quad IP: %s' % ip)
return
def numToDottedQuad(num):
# import here to avoid it when ip_addr values are not used
import socket, struct
# no need to intercept here, 4294967295L is fine
if num > int(4294967295) or num < 0:
raise ValueError('Not a good numeric IP: %s' % num)
try:
return socket.inet_ntoa(
struct.pack('!L', int(num)))
except (socket.error, struct.error, OverflowError):
raise ValueError('Not a good numeric IP: %s' % num)
class ValidateError(Exception):
"""
This error indicates that the check failed.
It can be the base class for more specific errors.
"""
class VdtMissingValue(ValidateError):
"""No value was supplied to a check that needed one."""
class VdtUnknownCheckError(ValidateError):
def __init__(self, value):
ValidateError.__init__(self, 'the check "{}" is unknown.'.format(value))
class VdtParamError(SyntaxError):
NOT_GIVEN = object()
def __init__(self, name_or_msg, value=NOT_GIVEN):
if value is self.NOT_GIVEN:
SyntaxError.__init__(self, name_or_msg)
else:
SyntaxError.__init__(self, 'passed an incorrect value "{}" for parameter "{}".'.format(value, name_or_msg))
class VdtTypeError(ValidateError):
def __init__(self, value):
ValidateError.__init__(self, 'the value "{}" is of the wrong type.'.format(value))
class VdtValueError(ValidateError):
def __init__(self, value):
ValidateError.__init__(self, 'the value "{}" is unacceptable.'.format(value))
class VdtValueTooSmallError(VdtValueError):
def __init__(self, value):
ValidateError.__init__(self, 'the value "{}" is too small.'.format(value))
class VdtValueTooBigError(VdtValueError):
def __init__(self, value):
ValidateError.__init__(self, 'the value "{}" is too big.'.format(value))
class VdtValueTooShortError(VdtValueError):
def __init__(self, value):
ValidateError.__init__(
self,
'the value "{}" is too short.'.format(value))
class VdtValueTooLongError(VdtValueError):
def __init__(self, value):
ValidateError.__init__(self, 'the value "{}" is too long.'.format(value))
class Validator(object):
# this regex does the initial parsing of the checks
_func_re = re.compile(r'([^\(\)]+?)\((.*)\)', re.DOTALL)
# this regex takes apart keyword arguments
_key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$', re.DOTALL)
# this regex finds keyword=list(....) type values
_list_arg = _list_arg
# this regex takes individual values out of lists - in one pass
_list_members = _list_members
# These regexes check a set of arguments for validity
# and then pull the members out
_paramfinder = re.compile(_paramstring, re.VERBOSE | re.DOTALL)
_matchfinder = re.compile(_matchstring, re.VERBOSE | re.DOTALL)
def __init__(self, functions=None):
self.functions = {
'': self._pass,
'integer': is_integer,
'float': is_float,
'boolean': is_boolean,
'ip_addr': is_ip_addr,
'string': is_string,
'list': is_list,
'tuple': is_tuple,
'int_list': is_int_list,
'float_list': is_float_list,
'bool_list': is_bool_list,
'ip_addr_list': is_ip_addr_list,
'string_list': is_string_list,
'mixed_list': is_mixed_list,
'pass': self._pass,
'option': is_option,
'force_list': force_list,
}
if functions is not None:
self.functions.update(functions)
# tekNico: for use by ConfigObj
self.baseErrorClass = ValidateError
self._cache = {}
def check(self, check, value, missing=False):
fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
if missing:
if default is None:
# no information needed here - to be handled by caller
raise VdtMissingValue()
value = self._handle_none(default)
if value is None:
return None
return self._check_value(value, fun_name, fun_args, fun_kwargs)
def _handle_none(self, value):
if value == 'None':
return None
elif value in ("'None'", '"None"'):
# Special case a quoted None
value = self._unquote(value)
return value
def _parse_with_caching(self, check):
if check in self._cache:
fun_name, fun_args, fun_kwargs, default = self._cache[check]
# We call list and dict below to work with *copies* of the data
# rather than the original (which are mutable of course)
fun_args = list(fun_args)
fun_kwargs = dict(fun_kwargs)
else:
fun_name, fun_args, fun_kwargs, default = self._parse_check(check)
fun_kwargs = {str(key): value for (key, value) in list(fun_kwargs.items())}
self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default
return fun_name, fun_args, fun_kwargs, default
def _check_value(self, value, fun_name, fun_args, fun_kwargs):
try:
fun = self.functions[fun_name]
except KeyError:
raise VdtUnknownCheckError(fun_name)
else:
return fun(value, *fun_args, **fun_kwargs)
def _parse_check(self, check):
fun_match = self._func_re.match(check)
if fun_match:
fun_name = fun_match.group(1)
arg_string = fun_match.group(2)
arg_match = self._matchfinder.match(arg_string)
if arg_match is None:
# Bad syntax
raise VdtParamError('Bad syntax in check "%s".' % check)
fun_args = []
fun_kwargs = {}
# pull out args of group 2
for arg in self._paramfinder.findall(arg_string):
# args may need whitespace removing (before removing quotes)
arg = arg.strip()
listmatch = self._list_arg.match(arg)
if listmatch:
key, val = self._list_handle(listmatch)
fun_kwargs[key] = val
continue
keymatch = self._key_arg.match(arg)
if keymatch:
val = keymatch.group(2)
if not val in ("'None'", '"None"'):
# Special case a quoted None
val = self._unquote(val)
fun_kwargs[keymatch.group(1)] = val
continue
fun_args.append(self._unquote(arg))
else:
# allows for function names without (args)
return check, (), {}, None
# Default must be deleted if the value is specified too,
# otherwise the check function will get a spurious "default" keyword arg
default = fun_kwargs.pop('default', None)
return fun_name, fun_args, fun_kwargs, default
def _unquote(self, val):
if (len(val) >= 2) and (val[0] in ("'", '"')) and (val[0] == val[-1]):
val = val[1:-1]
return val
def _list_handle(self, listmatch):
out = []
name = listmatch.group(1)
args = listmatch.group(2)
for arg in self._list_members.findall(args):
out.append(self._unquote(arg))
return name, out
def _pass(self, value):
return value
def get_default_value(self, check):
fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
if default is None:
raise KeyError('Check "%s" has no default value.' % check)
value = self._handle_none(default)
if value is None:
return value
return self._check_value(value, fun_name, fun_args, fun_kwargs)
def _is_num_param(names, values, to_float=False):
fun = to_float and float or int
out_params = []
for (name, val) in zip(names, values):
if val is None:
out_params.append(val)
elif isinstance(val, (int, float, str)):
try:
out_params.append(fun(val))
except ValueError:
raise VdtParamError(name, val)
else:
raise VdtParamError(name, val)
return out_params
# built in checks
# you can override these by setting the appropriate name
# in Validator.functions
# note: if the params are specified wrongly in your input string,
# you will also raise errors.
def is_integer(value, min=None, max=None):
(min_val, max_val) = _is_num_param( # pylint: disable=unbalanced-tuple-unpacking
('min', 'max'), (min, max))
if not isinstance(value, (int, str)):
raise VdtTypeError(value)
if isinstance(value, str):
# if it's a string - does it represent an integer ?
try:
value = int(value)
except ValueError:
raise VdtTypeError(value)
if (min_val is not None) and (value < min_val):
raise VdtValueTooSmallError(value)
if (max_val is not None) and (value > max_val):
raise VdtValueTooBigError(value)
return value
def is_float(value, min=None, max=None):
(min_val, max_val) = _is_num_param(
('min', 'max'), (min, max), to_float=True)
if not isinstance(value, (int, float, str)):
raise VdtTypeError(value)
if not isinstance(value, float):
# if it's a string - does it represent a float ?
try:
value = float(value)
except ValueError:
raise VdtTypeError(value)
if (min_val is not None) and (value < min_val):
raise VdtValueTooSmallError(value)
if (max_val is not None) and (value > max_val):
raise VdtValueTooBigError(value)
return value
bool_dict = {
True: True, 'on': True, '1': True, 'true': True, 'yes': True,
False: False, 'off': False, '0': False, 'false': False, 'no': False,
}
def is_boolean(value):
if isinstance(value, str):
try:
return bool_dict[value.lower()]
except KeyError:
raise VdtTypeError(value)
# we do an equality test rather than an identity test
# this ensures Python 2.2 compatibility
# and allows 0 and 1 to represent True and False
if value == False:
return False
elif value == True:
return True
else:
raise VdtTypeError(value)
def is_ip_addr(value):
if not isinstance(value, str):
raise VdtTypeError(value)
value = value.strip()
try:
dottedQuadToNum(value)
except ValueError:
raise VdtValueError(value)
return value
def is_list(value, min=None, max=None):
(min_len, max_len) = _is_num_param( # pylint: disable=unbalanced-tuple-unpacking
('min', 'max'), (min, max))
if isinstance(value, str):
raise VdtTypeError(value)
try:
num_members = len(value)
except TypeError:
raise VdtTypeError(value)
if min_len is not None and num_members < min_len:
raise VdtValueTooShortError(value)
if max_len is not None and num_members > max_len:
raise VdtValueTooLongError(value)
return list(value)
def is_tuple(value, min=None, max=None):
return tuple(is_list(value, min, max))
def is_string(value, min=None, max=None):
if not isinstance(value, str):
raise VdtTypeError(value)
(min_len, max_len) = _is_num_param(
('min', 'max'), (min, max))
try:
num_members = len(value)
except TypeError:
raise VdtTypeError(value)
if min_len is not None and num_members < min_len:
raise VdtValueTooShortError(value)
if max_len is not None and num_members > max_len:
raise VdtValueTooLongError(value)
return value
def is_int_list(value, min=None, max=None):
return [is_integer(mem) for mem in is_list(value, min, max)]
def is_bool_list(value, min=None, max=None):
return [is_boolean(mem) for mem in is_list(value, min, max)]
def is_float_list(value, min=None, max=None):
return [is_float(mem) for mem in is_list(value, min, max)]
def is_string_list(value, min=None, max=None):
if isinstance(value, str):
raise VdtTypeError(value)
return [is_string(mem) for mem in is_list(value, min, max)]
def is_ip_addr_list(value, min=None, max=None):
return [is_ip_addr(mem) for mem in is_list(value, min, max)]
def force_list(value, min=None, max=None):
if not isinstance(value, (list, tuple)):
value = [value]
return is_list(value, min, max)
fun_dict = {
int: is_integer,
'int': is_integer,
'integer': is_integer,
float: is_float,
'float': is_float,
'ip_addr': is_ip_addr,
str: is_string,
'str': is_string,
'string': is_string,
bool: is_boolean,
'bool': is_boolean,
'boolean': is_boolean,
}
def is_mixed_list(value, *args):
try: length = len(value)
except TypeError: raise VdtTypeError(value)
if length < len(args): raise VdtValueTooShortError(value)
elif length > len(args): raise VdtValueTooLongError(value)
try: return [fun_dict[arg](val) for arg, val in zip(args, value)]
except KeyError as cause: raise VdtParamError('mixed_list', cause)
def is_option(value, *options):
if not isinstance(value, str): raise VdtTypeError(value)
if not value in options: raise VdtValueError(value)
return value
+68
View File
@@ -0,0 +1,68 @@
Recently, and mostly from people who I've never seen before, the opinions about how this project should be run has started flooding in again. In a recent forum thread of such opinions, specifically about:
- The decision to no longer mirror release notes on GitHub.
- Some people feeling there were too many "barriers to entry" to joining RNS development.
- The project not really being "open source" because random strangers couldn't just "contribute".
Joakim posted some very relevant observations about how Reticulum operates, along with the following quote:
> The modern industrial system has a built-in tendency to grow; it cannot really work unless it is growing. The word “stability” has been struck from its dictionary and replaced by “stagnation”. Its continuous growth pursues no particular aims or objectives: it is growth for the sake of growing. No one even enquires after its final shape. There is none; there is no “saturation point”.
That E. F. Schumacher quote perfectly illustrates the ontological schism that makes it so tiresome to deal with stuff like this.
There is, in this day and age, between different people, widely different base conceptual integrations of what "open source" means. For many people, "open source" has become synonymous **not** with skilled people working together in a coordinated and careful way on complex engineering challenges, but a sort of growth- and attention-focused "free-for-all" *behavioral* codex that must be followed above all else; a *social* modus operandi of fake inclusivity where everyone "should have their voice heard", and adherence to that specific process is weighed much higher than the final results.
I do not subscribe to, and consequently do not operate the Reticulum project under *any* versions of that idea.
**Here's the statistical, boring reality:**
- Around 90% of pull requests and "recommendations" I received when people could just submit stuff via GitHub would
have *severely* broken things, introduced bugs or security issues, created roadblocks for future work, or otherwise
damaged the software. Usually just for the sake of satisfying a random newcomers "idea" or personal preference.
- Similarly, around 90% "bug reports" were actually people asking for help, because of having failed to read even the
most basic parts of the documentation.
- The people with the least amount of understanding, skill and effort invested tend to be loudest and most vocal. When
all you have is "opinions", those are iterated upon ad infinitum, apparently.
Can you imagine how much time that wasted? Can you imagine what we could have accomplished with that time instead?
The only thing that this creates is *noise* and confusion. Clogging up the mental and physical workspaces, of people who are actually investing time and effort on the project with stuff like that is objectively just taking time that could have been used on development, and replacing it with *nothing*.
I was receiving *actual* bug reports, pull requests, proper technical investigations and patches via methods outside GitHub and "public" internet-based channels *way* before GitHub interaction and similar was closed down. That was were almost *all* of the real contributions were coming from, anyway. Apparently, and not unsurprisingly, the people who has invested the time and effort to understand Reticulum also prefer to collaborate in this way. Since leaving the GitHub madhouse behind, the signal-to-noise ratio has **significantly** increased.
Managing a public "issue" tracker with global read/write access is a futile and useless endeavor. Consider this:
- User A reports a "bug" that is really just a failure of understanding.
- User B sees this and seconds is, proposing a "fix" that in continued failure of understanding would actually break functionality X.
- User C joins the bandwagon and asks why this hasn't already been implemented like that? It's obvious!
The sensible response here from the developer is closing the issue with "No. Go RTFM". Today, though, this usually results in hurt feelings, animosity towards the developer and in some cases (as experienced and documented in the case of RNS), months of perfidious personal vendetta against the developer for being so brazen as to suggest the user was wrong and wasting people's time.
When this pattern repeats, over and over, the only sensible, measured and constructive course of action is to shrug your shoulders and say:
*"This system is fundamentally broken. It ain't working. I can give up here, or I can go build something better that has a chance of working."*
So, now it's your turn. Go look at the diffs for the last six months. What does it look like I have been doing?
But I will be damned straight with you all, and say that part of that solution is **absolutely** to erect barriers to entry. You can fucking bet your arse on that. I don't want opinionated man-babies running around in my living-room at 3am. I don't want to clean up the floor after a wannabe "dev-ops stars" with LLMs and a peripheral case of influencitis has puked all over the office.
- If you want to join the fun of changing core networking code that thousands of people rely on for communication
daily, you better know what the fuck you're doing.
- I'm not here to provide validation and hugs to random strangers. I'm here to make sure the reference implementation
of Reticulum works.
- If you cannot figure out how to submit a patch or valid bug investigation over RNS, you cannot expect I will take
you seriously. At all.
If someone can't handle that, they should find their entertainment elsewhere.
I've said it before: I've provided the information and code required to make Reticulum *work*, and build networked systems, protocols and applications on top of it. That information is deep, complex, and requires you to read hundreds of pages, and put in weeks of efforts to get the *full* picture. A lot less is required to get started, but it *will* still be a steep learning experience.
This is a full networking stack, based on some pretty complex principles, for crying out loud. It's **not** a `hello_world` designed to make you feel good about yourself. It turns almost everything you know about networked systems on its head. That's **challenging** for *anyone*. Climb the mountain, and it will be satisfying in the end. Refuse to climb... Well, what do you think will happen?
As for barriers to entry of *using* RNS and related programs, utilities and clients, it's not my task to teach every single user how to do X, Y and Z. The information *is* out there. If it wasn't organized optimally for your way of learning, you can choose to "raise your concerns" about it, discuss "the fact of it" on a forum or chatroom, or: *You can choose to remedy that, and help others along*.
I sure know what *I* would have done.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -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: eebf90727067dce1facd726f357a2aab
config: 3af44598b1ae08e3d831a262fbc2ecf1
tags: 645f666f9bcd5a90fca523b33c5a78b7
+148
View File
@@ -0,0 +1,148 @@
.. _distributed-development:
***********************
Distributed Development
***********************
This chapter of the manual provides the conceptual basis for understanding *why* ``rngit`` exists, what it aims to achieve, and the kinds of spaces it seeks to reestablish. For the practical details of operating the system, refer to the :ref:`Git Over Reticulum<git-main>` chapter.
The Original Architecture
=========================
When Torvalds created Git in 2005, he designed a tool that reflected a specific philosophy of collaboration. Every copy of a repository would be a complete, sovereign instance. There was no central server, no single point of failure, no gatekeeper. Developers would be able to work independently, exchange patches directly, and maintain their own branches indefinitely. This concept was - and is - both beautiful and revolutionary. It's execution is peer-to-peer not as a marketing term, but in the most foundational sense: As fundamental, structural reality.
Such a design emerged from necessity. The Linux kernel development process operated across geographical boundaries, time zones, and organizational affiliations. Contributors did not "log in" to a shared server to do their work; they maintained their own trees, and the flow of code between these trees was negotiated through patches, reviews, and merge decisions. The architecture of Git mirrored the social architecture of the community: Autonomous, competent, and fundamentally distributed in its technical operation.
*The result of that work is, in the most direct sense, what makes it possible for you to read this today.*
There's something very important to take note of here: With Git, developers could collaborate effectively and perfectly well without any central server being present, without platform-mediated visibility into each other's work, and without a centralized authority validating their contributions. They needed *only* a protocol for exchanging differences and a mechanism for verification of authorship. Everything else - social organization, quality control, release management - was handled by careful *human judgment* operating on top of the technical substrate.
What Git provided was not a development environment, but a **language for versioning**. It specified how to represent history, how to compute differences, how to merge divergent branches. It did not specify who could participate, how they should communicate, or what workflows they should follow. These were left to the competence and discretion of the creators using the system.
The Platform Interregnum
========================
What followed represents a very familiar pattern: Tools designed to distribute power were re-centralized by platforms that offered convenience in exchange for control. GitHub, GitLab, and similar services reintroduced the centralization that Git had eliminated architecturally. The activity feed replaced durable artifacts with ephemeral notifications. The social graph and open interaction became as important as the code itself, if not more.
This re-centralization was not technical, as such. It was **ontological**. When every developer pushes to the same server, when every merge is in theory controllable by a platform, when every issue is tracked in a database controlled by a corporation, the nature of collaboration changes. The platform, and its social dynamics, becomes the ground of reality. The platform mediates not just the technical exchange of information and the programmatics, but the social recognition and codices of contribution, the future archival prospects of the work, and the very identity of the project itself.
The consequences extend beyond individual inconvenience. Centralized platforms create single points of failure for entire ecosystem. When a platform changes its terms of service, suspends accounts, removes repositories or ceases operation, entire project histories and community relationships can be disrupted or destroyed. The extractive economics of platform capitalism mean that value created by open-source communities is captured by corporations, while communities remain dependent on infrastructure they do not control. And the surveillance inherent in platform operation means that every action - every commit, every comment, every page view - is logged, analyzed, and potentially monetized or weaponized.
More insidiously, platforms have completely reshaped the culture of development itself. They have created what we could call the **Teahouse Developer**: A participant who treats engineering projects as social venues for opinion-sharing rather than sites of disciplined and careful production. These personages have no actual stakes in the projects they act as leeches upon, and only a very base consciousness of the damage they are incurring in order to feed their attention and external validation dependencies.
When platforms optimize for engagement, when growth is the only metric, when every user with an opinion must have their voice heard, when a random social process is elevated to higher importance than results, the signal-to-noise ratio collapses catastrophically. Competent engineers find themselves drowning in feedback from the incompetent, managing the emotional needs and dysregulations of drive-by commentators rather than solving technical problems.
The platform model is predicated on **unsaturable expansion**. Like almost any industrial system, it cannot function without growth. It pursues no particular aims; it is growth for the sake of growing. There is no saturation point, no concept of "enough". Every barrier to entry must be put down to the very lowest common denominator, every voice must be amplified, every interaction must be converted into content that feeds the machine. This is fundamentally incompatible with the nature of social beings itself. It is also incompatible with serious engineering, which requires focus, discernment, and the right of people who know better to say "no".
Restoration
===========
The ``rngit`` system represents a return to Git's original architectural principles, fortified with cryptographic networking capabilities that were not available in 2005. The ``rngit`` system *is* Git - but running over Reticulum. Welcome back to a world where your work is your own, but where everyone can still reach you - if you want them to.
Just as Git eliminated the need for a central version control server, ``rngit`` eliminates the need for a central hosting platform, "servers" or any kinds of middle-men between the people actually doing the work. By operating over Reticulum, it eliminates the visibility of development activity to platform operators, network observers, state actors and other malicious third-parties.
In this model, the repository node is a **sovereign entity**. It is reachable from anywhere in the Reticulum network but owned, operated, and controlled by the developer or community that runs it. It is an actual home for creative output, not an extraction mechanism to which dues are paid. The node operator decides who may contribute, what standards must be met, and which voices are worth listening to. This is not exclusion; it is **discernment**. It is the necessary exercise of judgment that separates engineering from theatrics.
I did not create this in a fit of nostalgia. I created it because it is a necessary response to the failures of the centralized model. Git's technical architecture was - and *is* - correct. It was the social and economic superstructure built atop it that introduced fragility, exploitation, and environments toxic to actual creativity. By returning to first principles - distributed version control on distributed infrastructure - we recover not just a technical capability, but a mode of collaboration that respects the autonomy of individual developers and the sovereignty of actual communities.
Protocols Over Platforms
========================
The distinction between platforms and protocols is fundamental to understanding the architecture of sovereignty in networked systems. A platform is a service you access; a protocol is a grammar you speak; actions you live. A platform requires permission to enter, a protocol requires only *comprehension* to employ. A platform can change its rules, suspend your account, or cease operation entirely, a protocol persists as long as there are participants who *understand* and *use* it. A protocol is an *idea*, a platform is a machine that turns its users into products.
Platforms operate on a client-server model that inherently creates power asymmetry. Even when platforms are built atop open-source software, the operational instance remains a black box of corporate control. You *may* be able to download *some* of your data, but you cannot download the connections to the people that are the true value-base of the platform, or take them with you if you want to leave.
Protocols, by contrast, are agreements. They specify how systems should communicate, but not who may communicate or on what terms. Email is a protocol; Gmail is a platform. HTTP is a protocol; Facebook is a platform. Git is a protocol; GitHub is a platform. The protocol persists regardless of any particular implementation's success or failure.
The power of protocols lies in their **permissionlessness**. Anyone can implement a protocol without approval. Anyone can extend it, fork it, or use it for purposes unforeseen by its creators. This creates resilience: protocols cannot be easily censored, monopolized, or shut down because they exist as shared understanding rather than centralized infrastructure.
Reticulum is a protocol in this strict sense. It specifies how packets should be formatted, how paths should be discovered, how encryption should be applied. The ``rngit`` system extends this protocol approach to development workflows. It is not an external platform that hosts your repositories; it is a protocol for exchanging repository data, release artifacts, and work documents over Reticulum's encrypted transport. But with a few commands and an old computer, it creates your own infrastructure for hosting repositories, or sharing them with who you choose. *That* is how tools should function, in case we had forgotten.
Unlike platforms, which extract value by creating dependency, there is no entity that can grant or deny you the privilege of running ``rngit``. Your Reticulum identity is not endowed by any platform; it is generated locally and certified by its own cryptographic properties. Your repositories are hosted on nodes you control or nodes operated by communities you trust. Your relationships with other developers are peer-to-peer connections established through cryptographic addressing, not social graph connections managed by recommendation algorithms.
On a platform, exit means abandonment: you lose your history, your relationships, your visibility. With protocols, exit is just migration. When you change your infrastructure, your identity and your work travel with you. There are no middlemen between you and your collaborators. If push comes to shove, you can write your entire life's work and connections to an SD card, swim across the lake, and set up camp on the other side.
Sovereignty Through Infrastructure
==================================
The concept of sovereignty - supreme authority within a territory - has traditionally been applied to nation-states. But in an age where creative work is conducted through digital infrastructure, sovereignty is essential for individuals and communities. **Creative sovereignty** means having supreme authority over the artifacts you produce, the processes by which you produce them, and the terms under which they are distributed. It means not merely legal ownership of copyright, but operational control of the infrastructure that mediates creation, collaboration, and dissemination.
Centralized development platforms strip away most layers of sovereignty. When you host code on a corporate platform, you retain *some* legal ownership of copyright, but you surrender complete operational control. The platform decides what content is acceptable, who can access it, and how it is presented. They can delete your repository, suspend your account, or change the visibility of your work without consent. In reality, legal ownership becomes meaningless as operational control is ceded.
Running your own ``rngit`` node restores this sovereignty. You control the hardware, the network configuration, the backup strategies, and the access permissions. You decide what constitutes acceptable use, who may contribute, and how contributions are evaluated. Taking this responsibility on yourself is an assertion that your creative work is not a product to be harvested by platform economics, but an autonomous activity to be conducted on your own terms.
This sovereignty and responsibility extends to the entry barriers you establish. The ``rngit`` system allows you to configure access controls that filter participants based on cryptographic identity and demonstrated competence. If, for example, someone cannot navigate a command line, or use Reticulum to submit a patch, they most likely lack the required competence to modify your code. In a world that apparently labels this as "exclusion", I would simply refer to it as a minimally acceptable level of quality control.
Such a stance protects projects from the noise that so often overwhelms and completely dilutes platform-based development, where every user with an opinion believes themselves entitled to attention and access to the decision process.
Artifact-Centered Workflows
===========================
Contemporary platform-based development has shifted focus from durable artifacts to ephemeral *activity*. It does not matter what constitutes this activity, as long as it's there. The primary interface is not the repository itself, not the produced artifacts, but the activity feed: *Notifications* of commits, comments, pull requests, and social interactions. Work is measured by velocity, throughput, and the constant stream of updates. This activity-centric model creates constant urgency, discourages discernment, encourages reactive rather than reflective work patterns, and produces vast quantities of ephemeral and useless communication that obscures actual project state and productivity.
The ``rngit`` system enables a return to **artifact-centered workflows**, where the focus is on durable, attributable, versioned outputs rather than the stream of notifications surrounding them. The fundamental unit of work is the commit - signed, immutable records of change. The fundamental unit of production is the signed artifact - a self-verifying package of functionality. The fundamental unit of discussion is the work document - a structured, threaded conversation attached to repositories.
Artifacts can persist independently of any platform's continued operation. A commit signed with your Reticulum identity is attributable to you regardless of where it is stored. A release signed with your private key is verifiable as authentic regardless of which network it traverses, and can be verified offline on any system running Reticulum. The work exists as **cryptographic fact**, distributed over the planet, not as database entries in a corporate cloud.
Such a shift has real psychological consequences. When work is measured in artifacts rather than activity, the pace changes. There is no need for constant visibility, no pressure to perform busyness. Developers can work deeply, reflectively, and submit complete solutions rather than incremental updates designed to maintain presence in an activity feed. The work becomes **substantial**, in the physical sense of the word, rather than performative.
Composable Primitives
=====================
The ``rngit`` system is not a monolithic application prescribing a specific workflow; it is a collection of **composable primitives** that can be arranged to support diverse creative processes. Understanding these primitives as separate, orthogonal capabilities enables users to construct workflows suited to their specific needs and to recombine these primitives in ways unforeseen by the system's designers.
The core primitives include:
* **Repository Hosting**: Bare Git repositories served over Reticulum links, accessible via standard Git commands through the ``rns://`` URL scheme.
* **Identity-Based Access Control**: Fine-grained permissions managed through cryptographically verifiable identity hashes, configurable at the group, repository, or document level.
* **Release Distribution**: Cryptographically signed release artifacts with embedded provenance information, verifiable offline and distributable through any Reticulum or physical path.
* **Work Document Tracking**: Structured, threaded work management attached to repositories, with precise permission controls, and the ability to contain updates or discussions.
* **Forking and Mirroring**: Automated replication of repositories from any accessible Git URL, with metadata tracking upstream relationships for synchronization.
* **Nomad Network Integration**: Page node functionality for browsing repository contents, commit history, and release information through the Nomad Network protocol.
These primitives can be composed into workflows ranging from single-developer projects to complex multi-organizational collaborations. A solo developer might use only repository hosting and release distribution. A research group might add work document tracking for structured peer review. A software distribution network might combine mirroring with cryptographic release verification to create resilient update channels.
The entire system is incredibly light-weight, and can host hundreds of repositories on a Raspberry Pi.
Composability is essential because **creative work is diverse**. Software development, academic research, technical writing, hardware design, music production and data analysis all have different requirements for collaboration, review, and distribution. A platform prescribes a single workflow and forces all users to conform. A protocol provides primitives and allows users to construct workflows appropriate to their domain.
With ``rngit``, you can re-build the system into anything you can imagine. Everything can be modified, extended and hooked into. Adding functionality or automation is never further away than a shell script, a cron-job, or a Python modification of the source.
Distribution Without Intermediaries
===================================
Creating software is only part of the work. Then comes actually getting it to the people needing to use it. Centralized platforms handle distribution through their own infrastructure: Content delivery networks, central package registries, and download servers accessed through platform-controlled interfaces. This convenience masks a fundamental dependency: Your ability to distribute depends on the platform's continued operation, their policies regarding your content, and their technical infrastructure's reach.
The ``rngit`` release system enables distribution strategies **decoupled from any single infrastructure provider**. Releases are cryptographically signed using Ed25519 signatures and packaged in signed release manifests (``.rsm`` files). These manifests contain embedded signatures for each artifact. The manifest provides full verifiability of all release information, and contains embedded release artifact lists, per-file ``.rsg`` signatures, origin information, and the creator's Reticulum Identity. It can also be used to fetch verified updates of the software package over the network, and can always be verified completely offline.
Because releases are self-verifying, they can traverse any network or physical path that Reticulum can establish. A release can travel over LoRa radio, be carried on USB drives through areas without internet connectivity, disseminated over a mirror network, or be distributed through store-and-forward mechanisms on intermittent infrastructure. Recipients can verify authenticity regardless of how they obtained the files. This is particularly valuable in low-connectivity environments where Reticulum may be the only available communication channel.
The ``rngit release`` command provides tools for creating, publishing, fetching, and verifying releases. When fetching a release using an ``.rsm`` manifest, the system validates the manifest signature against the required Reticulum Identity, extracts the origin node and repository path, connects to the origin over Reticulum, retrieves the latest release manifest, and verifies each downloaded artifact against the signatures embedded in the manifest. If any verification fails, the fetch aborts, preventing installation of corrupted or tampered files.
This cryptographic verification replaces the trust model of platform distribution. Instead of trusting that a platform has not been compromised, users verify that artifacts match the signatures created by the developer's identity. It doesn't matter *how* they obtained the artifacts, they can **always** be verified. This security model shifts from **institutional trust** (just believe in the goodness of the platform) to **cryptographic proof** (verify the signatures).
Long Archive
============
Software development is often conceived as an activity of the present only: Solving today's problems, meeting current deadlines, responding to immediate feedback. But the artifacts produced - code, documentation, releases - have lifespans extending *far* beyond their creation. They may be used for decades, studied by future developers, depended upon by systems not yet imagined, or preserved as historical records of technological development.
The ``rngit`` system is designed with this **extended timeframe** in mind, supporting the creation of archives that are durable, portable, and intelligible across generational timescales. Git repositories are always internally complete; they contain full history and can be migrated to new infrastructure without loss of information. Everything that ``rngit`` adds on top of this is stored in normal files in standard formats right next to the Git repository folders, not an esoteric database-cluster two thousand kilometers away. Because releases are cryptographically signed, they remain verifiable as authentic regardless of when or where they are retrieved. Because the system operates over Reticulum, it can function over communication mediums that may outlast the internet as we know it.
This long-term perspective influences technical decisions. The use of well-established cryptographic primitives ensures that signatures will remain verifiable for centuries. The use of standard formats ensures that repositories will remain readable by future tools. The protocol-based architecture ensures that the system can evolve without losing compatibility with existing data.
For critical infrastructure, this archival durability is not optional; it is essential. Communication systems, cryptographic libraries, and safety-critical code must remain available and verifiable for the lifespans of the systems that depend on them. The ``rngit`` system provides the tools to create such archives: distributed across multiple nodes, cryptographically verified, and independent of any corporate or governmental infrastructure, which as history has shown repeatedly, does *not* persist.
Start Of The Road
=================
Distributed development and production over Reticulum is a *different mode of existence* for creative work. It restores the autonomy originally created by Git. It provides local sovereignty over production infrastructure, composability of workflow, and durability of artifact. It lets you filter participation through competence and cryptography rather than incentives of platform operators, raising the quality and enjoyment of work, and protecting the focus of real engineering and creative expression.
This is not a system for everyone, and that is the point. It requires investment - in understanding Reticulum, in configuring infrastructure, in establishing workflows. It requires accepting responsibility for your own tools rather than delegating them to platform operators. It requires the discipline to maintain your own node, manage your own backups, and nurture your own community.
But for those who make this investment, the returns are substantial. You gain **immunity from platform failure**; your work persists regardless of corporate decisions or service outages. You gain **shelter from surveillance**; your development activity is visible only to those that *you* choose to involve. You gain **control over process**; you decide how work is conducted, reviewed, and released, unmediated by terms of service, algorithmic feeds and thousands of uninformed and irrelevant opinions.
Most importantly, though, you regain the **dignity of craft**. Development becomes an activity conducted among peers, equals among equals, mediated by skill and cryptographic proof rather than corporate permission, producing artifacts that stand as independent testimony to competence, functionality or beauty rather than as content feeding engagement metrics. The *work* becomes the point. The artifacts become durable. And the network becomes *one* of the tools you wield in this endeavor.
+363 -20
View File
@@ -4,13 +4,15 @@
Git Over Reticulum
******************
This chapter of the manual serves as the technical reference for the distributed software development and project collaboration tools included in RNS. For a conceptual overview, see the :ref:`Distributed Development<distributed-development>` chapter.
A set of utilities for distributed collaborative software development and publishing are included in RNS.
The system consists of two parts: The ``rngit`` node that hosts repositories, and the ``git-remote-rns`` helper that enables Git to communicate with rngit nodes. As soon as you have RNS installed on your system, you can transparently use Git with Reticulum-hosted repositories just like any other type of remote. Git over Reticulum uses URLs in the following format: ``rns://DESTINATION_HASH/group/repo``.
If you set a branch to track a Reticulum remote as the default upstream, you can simply use ``git`` as you normally would; all commands work transparently and as expected.
.. warning::
.. important::
**The rngit program is a new addition to RNS!** This functionality was introduced in RNS 1.2.0. While great care has been taken to design a secure, but highly configurable and flexible `permission system`_ for allowing many users to interact with many different repositories on a single node, ``rngit`` has not been tested extensively in the wild! Be careful when hosting repositories, especially if they are public or semi-public.
.. _permission system: #permissions
@@ -170,10 +172,8 @@ To fork a repository:
The source can be any valid Git URL, including:
- HTTPS URLs: ``https://github.com/user/repo.git``
- Git URLs: ``git://host.com/repo.git``
- SSH URLs: ``ssh://git@host.com/repo.git``
- Reticulum URLs: ``rns://DESTINATION_HASH/group/repo``
- Local paths: ``/path/to/repo.git``
Forks are created as bare repositories with metadata tracking their origin. The fork process:
@@ -321,8 +321,6 @@ These parameters are used by the sync system and can be queried using standard G
1716230400
Repository Structure
====================
@@ -348,6 +346,8 @@ Access permissions can be configured at the group level in the config file or pe
By default, **no** permissions are granted for anything! You will have to enable the permissions you require to be able to actually *do* something with ``rngit``.
Permissions can be modified by editing the ``rngit`` config file, individual ``.allowed`` files on disk, or remotely using the ``rngit perms`` command.
Permission Types
----------------
@@ -509,6 +509,90 @@ Permission Configuration Locations
- Repository permissions: ``<group_root>/<group_name>/<repo_name>.allowed``
- Document permissions: ``<group_root>/<group_name>.work/<doc_id>.allowed``
Remote Permission Management
============================
While permissions can be configured directly on the node by editing configuration files and ``.allowed`` files, ``rngit`` also supports remote permission management through the ``rngit perms`` command. This allows administrators to modify access controls for groups and repositories over Reticulum, without requiring shell access to the hosting node.
To use remote permission management, you must have ``admin`` permission on the target group or repository. The command opens your configured ``$EDITOR`` to modify permissions, using the same syntax and format as local ``.allowed`` files. When you save and exit the editor, the modified permissions are transmitted to the remote node and applied immediately.
Managing Group Permissions
--------------------------
To view or modify permissions for an entire repository group, specify the group URL (ending with the group name):
.. code:: text
$ rngit perms rns://50824b711717f97c2fb1166ceddd5ea9/public
This retrieves the current permission configuration from the ``public.allowed`` file and opens it in your editor. Any changes you make are validated for syntax correctness. Invalid permission rules will be rejected with an error message indicating the problematic line.
Managing Repository Permissions
-------------------------------
To manage permissions for a specific repository, include the repository name in the URL:
.. code:: text
$ rngit perms rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo
This operates on the ``myrepo.allowed`` file next to the repository. Repository-level permissions take precedence over group-level permissions, allowing fine-grained access control for individual repositories within a group.
Permission Validation
---------------------
When modifying permissions remotely, ``rngit`` validates that:
- Each permission line follows the correct ``permission:target`` syntax
- Permission types are valid (r, w, rw, c, s, rel, i, p, adm)
- Target specifications are valid (identity hashes, ``all``, or ``none``)
- Identity hashes, when specified, are the correct length (32 hexadecimal characters)
If validation fails, the editor will reopen with an error message describing the issue, allowing you to correct the problem before resubmitting.
.. caution::
Remote permission modification requires administrative access (the ``adm`` permission), which grants full control over the repository or group. The permission change request is transmitted over the encrypted Reticulum link, and the remote node verifies your identity cryptographically before applying changes. However, be aware that granting ``adm`` permissions to remote identities effectively delegates full control, including the ability to revoke your own access or modify permissions in ways you may not anticipate.
**All Command-Line Options (rngit perms)**
.. code:: text
usage: rngit perms [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
[-i PATH] [-v] [-q] [--version]
remote
Reticulum Git Permission Manager
positional arguments:
remote URL of remote group or repository
options:
-h, --help show this help message and exit
--config CONFIG path to alternative config directory
--rnsconfig RNSCONFIG
path to alternative Reticulum config directory
-i, --identity PATH path to identity
-v, --verbose
-q, --quiet
--version show program's version number and exit
Identity & Destination Aliases
==============================
To make permission and remote destination management easier, you can locally define aliases for commonly used identity and destination hashes. Identity aliases used in permissions resolution can be defined in the ``[aliases]`` section of the ``~/.rngit/config`` file, while destination aliases are defined in the ``[aliases]`` section of the ``~/.rngit/client_config`` file.
All alias definitions take the form of ``aliased_name = HASH``:
.. code:: text
[aliases]
alice = d09285e660cfe27cee6d9a0beb58b7e0
bob = ffcffb4e255e156e77f79b82c13086a6
**Aliases are always resolved locally!** If for example you fork a repository with ``rngit fork rns://bobs_node/public/repo_name rns://my_node/forks/repo_name``, the forked repository will of course still reference the full, original destination hash, and use this for subsequent upstream syncs.
Serving Pages Over Nomad Network
================================
@@ -578,6 +662,8 @@ Code blocks in Markdown can include language hints for syntax highlighting:
print("Hello, Reticulum!")
```
You can use ``rawmu`` code blocks to render raw Micron inside Markdown files. If you create a code block with the language hint ``rawmu``, everything inside it will be treated as Micron directly.
Customizing Templates
---------------------
@@ -660,6 +746,211 @@ A complete node configuration might look like this:
unicode_icons = no
Verified Releases
=================
The ``rngit`` release system provides cryptographic provenance and integrity guarantees through automatic signing of release artifacts and signed release manifests. When you create a release, ``rngit`` generates an Ed25519 signature for each artifact and embeds these signatures in a cryptographically signed release manifest (``.rsm`` file). This allows anyone who obtains the release to verify its authenticity and integrity, regardless of how the files were distributed.
.. _git-release-obtain:
Obtaining Verified Releases
---------------------------
The ``rngit`` system lets you obtain releases securely and in a verified manner, by validating cryptographically signed release manifests in the ``.rsm`` format during the retrieval process. Once a release has been published with ``rngit``, anyone that has read access to it can obtain the release with the ``rngit release`` command, for example:
.. code:: text
$ rngit release rns://remote_node/group/some_program fetch latest:all
This command will connect to the remote, retrieve the latest release manifest, verify it's signature and integrity (you can optionally specify a required signer identity with ``--signer``), and then download and sequentially verify all artifacts included in the release.
If verification succeeds, the retrieved artifact files, along with the release manifest will be saved in the current working directory. From the above example, you would end up with a number of downloaded files, and a version- and package specific release manifest, such as ``some_program_1.5.2.rsm``.
.. important::
Keeping the retrieved release manifest is a **very** good idea! It allows you to easily obtain future releases and updates to the software directly, while verifying they came from the same publisher.
**Obtaining & Updating Releases Using RSM Manifests**
One of the key features of the ``rngit`` release system is the ability to fetch and verify new releases using only a signed release manifest. This is particularly valuable for distributing software over Reticulum. Once someone has an ``.rsm`` manifest of your package, they can use it to continually retrieve and update the software.
To fetch a release using a manifest:
.. code:: text
$ rngit release some_program_1.5.2.rsm fetch latest:all
This command:
1. Validates the manifest signature to confirm authenticity
2. Extracts the origin node and repository path from the signed manifest
3. Connects to the origin node over Reticulum
4. Gets the *latest* release manifest from the developer
5. Verifies it against the existing manifest
6. Fetches each artifact listed in the manifest
7. Verifies each downloaded file against the signature embedded in the manifest
If any artifact fails signature verification, the fetch aborts with an error, preventing the installation of corrupted or tampered files.
**Specifying Required Signers**
You can require that releases be signed by specific identities. When fetching a release, use the ``--signer`` option to specify the identity hash of the required signer:
.. code:: text
$ rngit release rns://remote_node/public/myrepo fetch latest:all --signer 21a8daa6d9c3d3b8aab6e94b6bcb0e33
If the release was not signed by the specified identity, the fetch will abort before any files are downloaded. Likewise, if any downloaded artifacts were not signed by the required identity, the process will abort at the first invalid signature. This provides strong guarantees about the provenance of the software you are installing.
The signer check also works when fetching from a local manifest:
.. code:: text
$ rngit release manifest.rsm fetch latest:all --signer 21a8daa6d9c3d3b8aab6e94b6bcb0e33
**Selective & Partial Fetches**
You can fetch individual artifacts from a release by specifying the artifact name instead of ``all``:
.. code:: text
$ 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.
.. 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.
**For individual files:**
Ensure the ``.rsg`` signature is located in the same directory as the release artifact, then run:
.. code:: text
$ rnid -V myapp-1.2.0.tar.gz
This validates that the artifact file matches the signature created during the release process. Combined with the manifest's own signature, this provides end-to-end verification from the original release creation to the final installation.
**For a complete release:**
Ensure the release manifest is located in the same directory as the release artifacts, then run:
.. code:: text
$ rngit release myapp-1.2.0.rsm --offline
This will load the manifest, and verify all files currently on-disk, but will not attempt to fetch the latest release manifest from the origin, or update local files to match it.
.. _git-release-create:
Creating Signed Releases
------------------------
Reticulum and the ``rngit`` system makes it easy to create signed releases that your users can verify and update securely. When you create a release using ``rngit``, the program automatically:
1. Generates an Ed25519 signature for each artifact file using your identity's signing key
2. Creates ``.rsg`` signature files alongside each artifact in your distribution directory
3. Constructs a signed release manifest (``manifest.rsm``) containing metadata, an artifact list, and embedded signatures
4. Transmits both artifacts, signatures and manifest to the remote node specified as release origin
As an example, to create and publish a release from all files in the folder named ``dist``, simply run:
.. code:: text
$ rngit release rns://my_node/group/myrepo create 1.2.0:./dist
Everything is automatically signed and uploaded to your node, and the release manifest will now include the following signed attestation information:
- Package name and version
- The release notes for this release
- Release timestamp and commit hash
- Origin node identity and repository path
- Complete list of artifacts
- Embedded signatures for each artifact
That's it, there's nothing more to it than one command. Users can now securely obtain your release using ``rngit release fetch``.
**Release Manifest Format**
Release manifests use the ``.rsm`` format (a general-purpose, structured signed message format) and are themselves cryptographically signed documents. The manifest format embeds the signing identity's public key and a detached signature that covers the entire manifest content. This creates a chain of trust: the manifest signature proves the manifest's authenticity, and the embedded artifact signatures prove each file's integrity.
When a release is created, the manifest is stored as ``manifest.rsm`` in the release artifacts directory. You can also generate a local release manifest without uploading by using the ``--local`` flag:
.. code:: text
$ rngit release rns://f2d31b2e080e5d4e358d32822ee4a3b7/public/myrepo create 1.2.0:./dist --local
This creates the ``.rsg`` signature files and ``manifest.rsm`` in your local distribution directory without connecting to the remote node, allowing you to inspect or distribute the signed release through alternative channels.
**Signature File Format**
Individual artifact signatures use the Reticulum Signature (``.rsg``) format and contain:
- The Ed25519 signature of the file
- The signing identity's public key
- Optional metadata, such as timestamps or notes
These signature files are created automatically during the release process and can be used independently of the manifest for verification purposes. The ``rnid`` utility can create and validate RSG signatures for any file, making this signature format useful beyond the ``rngit`` release system.
**Good Practices for Signature Distribution**
While release manifests in the ``.rsm`` format *include* embedded ``.rsg`` signatures for every listed artifact, it is dependent on the situation and requirements whether individual ``.rsg`` signatures are distributed as well. It is generally a good idea to do so, since they are very light-weight, and provide an easy and convenient way to validate and authenticate *individual* files, as opposed to entire releases.
When distributing software through multiple channels (direct download, mirror networks, physical media), including the ``.rsm`` manifest allows recipients to verify authenticity regardless of how they obtained the files. This is particularly valuable in low-connectivity environments where Reticulum may be the only available communication channel, as the manifest ensures that software updates can be verified even when received via store-and-forward mechanisms or physical media transport.
**Integration with Package Management**
While this functionality is still under development, the signed release manifest format is designed to be consumed by package management systems and automated deployment tools. Because the manifest is cryptographically signed and contains all necessary metadata and integrity checks, it can serve as a trusted source of truth for software distribution, even when fetched over untrusted channels or stored for long periods.
**Release Encryption**
While API primitives and command-line tools are currently not implemented for this, the release, distribution and verification system has been designed to also support *encrypted* releases, which can be distributed securely to authorized recipients.
**Verified Package Format**
The current system is being expanded to also include an ``.rvp`` package format, which can contain packaged releases including all relevant artifacts, metadata, manifest and signatures.
**Automated Mirror Discovery**
The ``rngit`` release system is designed to support automated mirror discovery and distribution package retrieval over Reticulum networks. Since everything is cryptographically signed and verified, it is possible to create automated mirror and distribution networks, where users can obtain software and information from local sources, without risking malicious modifications to the software they rely on. This functionality is currently in development.
Release Management
==================
@@ -674,11 +965,11 @@ To create a release, specify the tag name and path to artifacts:
.. code:: text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo create v1.2.0:./dist
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo create 1.2.0:./dist
This will:
1. Verify that the tag ``v1.2.0`` exists in the repository
1. Verify that the tag ``1.2.0`` exists in the repository
2. Open your editor to write release notes
3. Upload all files from the ``./dist`` directory
4. Publish the release
@@ -709,9 +1000,9 @@ To view all releases for a repository:
Tag Status Created Objs Notes
------------------------------------------------------------------
v1.2.0 published 2025-01-15 14:32 3 Another release
v1.1.0 published 2024-12-03 09:15 2 Bug fix release
v1.0.0 published 2024-10-20 16:45 2 Initial release
1.2.0 published 2025-01-15 14:32 3 Another release
1.1.0 published 2024-12-03 09:15 2 Bug fix release
1.0.0 published 2024-10-20 16:45 2 Initial release
**Viewing Release Details**
@@ -719,9 +1010,9 @@ To see full information about a specific release:
.. code:: text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo view v1.2.0
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo view 1.2.0
Release : 0.9.2
Release : 1.2.0
Status : published
Created : 2026-05-04 23:53:09
Thanks : 5
@@ -737,16 +1028,37 @@ To see full information about a specific release:
- myapp-1.2.0.zip (1.6 MB)
- checksums.txt (256 B)
**Fetching Releases**
To fetch a release, specify the remote URL, version and artifacts:
.. code:: text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo fetch latest:all
This process is described in greater detail in the :ref:`Obtaining Verified Releases<git-release-obtain>` section.
**Creating Releases**
To fetch a release, specify the remote URL, version and artifacts:
.. code:: text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo create 1.3.9:artifacts_dir
This process is described in greater detail in the :ref:`Creating Signed Releases<git-release-create>` section.
**Deleting Releases**
To remove a release:
.. code:: text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo delete v1.2.0
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo delete 1.2.0
Are you sure you want to delete release 'v1.2.0'? [y/N]: y
Release v1.2.0 deleted
Are you sure you want to delete release '1.2.0'? [y/N]: y
Release 1.2.0 deleted
**Requirements & Validation**
@@ -774,15 +1086,16 @@ When the Nomad Network page node is enabled, releases are displayed on a dedicat
.. code:: text
usage: rngit release [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
[-i PATH] [-v] [-q] [--version]
[repository] [operation] [target]
usage: python -m RNS.Utilities.rngit.server [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
[-i PATH] [-s PATH] [-n name] [-L]
[-o] [-v] [-q] [--version]
[repository] [operation] [target]
Reticulum Git Release Manager
positional arguments:
repository URL of remote repository
operation list, view, create or delete
repository URL of remote repository, or path to RSM manifest
operation list, view, fetch, create, latest or delete
target tag and path to release artifacts directory
options:
@@ -791,6 +1104,10 @@ When the Nomad Network page node is enabled, releases are displayed on a dedicat
--rnsconfig RNSCONFIG
path to alternative Reticulum config directory
-i, --identity PATH path to release identity
-s, --signer PATH path to signing identity, if different from release identity
-n, --name name package name if different from repo name
-L, --local generate release locally, but don't upload
-o, --offline verify manifest locally, but don't fetch updates
-v, --verbose
-q, --quiet
--version show program's version number and exit
@@ -1003,6 +1320,32 @@ Each document is a numbered directory containing:
When the Nomad Network page node is enabled, work documents are viewable through the web interface. The work page lists all documents with their status, and clicking a document shows its full content and updates.
Cryptographic Attribution
-------------------------
Every work document is cryptographically signed by its creator using their Reticulum identity. When you create or edit a document, ``rngit`` generates an Ed25519 signature of the content, which is stored alongside the document contents and verified by the remote node, or locally when viewing the work document through the command-line interface. This provides two essential guarantees:
- **Attribution:** Every document and comment can be cryptographically attributed to its actual author
- **Integrity:** Any modification to the content after creation would invalidate the signature
When viewing a work document, the signature validation status is displayed:
.. code:: text
Author : 9710b86ba12c42d1d8f30f74fe509286 (not locally validated)
Signature : Document not signed
Or, for valid signatures:
.. code:: text
Author : <9710b86ba12c42d1d8f30f74fe509286>
Signature : Valid
The "Valid" status indicates that the document content matches the author's signature, and that the signing identity corresponds to the stated author. This can be used to create tamper-proof records of project decisions, investigations, and discussions that cannot be repudiated, or modified by third parties without detection.
This cryptographic provenance is particularly valuable for distributed teams operating across trust boundaries. Because signatures are verified using the author's Reticulum identity public keys - which can be recalled from any transport node on the network - work documents provide authoritative records of who said what, and when, without requiring a central authority to notarize or validate the communication. Even if the repository node hosting the documents becomes unavailable, the signed document files themselves retain validity and can be verified independently using standard Reticulum identity tools.
**All Command-Line Options (rngit work)**
.. code:: text
+1
View File
@@ -27,6 +27,7 @@ to participate in the development of Reticulum itself.
hardware
interfaces
networks
distributed
git
support
examples
+2 -2
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
const DOCUMENTATION_OPTIONS = {
VERSION: '1.2.7',
VERSION: '1.3.0',
LANGUAGE: 'en',
COLLAPSE_INDEX: false,
BUILDER: 'html',
+441
View File
@@ -0,0 +1,441 @@
<!doctype html>
<html class="no-js" lang="en" data-content_root="./">
<head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html"><link rel="search" title="Search" href="search.html"><link rel="next" title="Git Over Reticulum" href="git.html"><link rel="prev" title="Building Networks" href="networks.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>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" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
<style>
body {
--color-code-background: #f2f2f2;
--color-code-foreground: #1e1e1e;
}
@media not print {
body[data-theme="dark"] {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
--color-background-primary: #202b38;
--color-background-secondary: #161f27;
--color-foreground-primary: #dbdbdb;
--color-foreground-secondary: #a9b1ba;
--color-brand-primary: #41adff;
--color-background-hover: #161f27;
--color-api-name: #ffbe85;
--color-api-pre-name: #efae75;
}
@media (prefers-color-scheme: dark) {
body:not([data-theme="light"]) {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
--color-background-primary: #202b38;
--color-background-secondary: #161f27;
--color-foreground-primary: #dbdbdb;
--color-foreground-secondary: #a9b1ba;
--color-brand-primary: #41adff;
--color-background-hover: #161f27;
--color-api-name: #ffbe85;
--color-api-pre-name: #efae75;
}
}
}
</style></head>
<body>
<script>
document.body.dataset.theme = localStorage.getItem("theme") || "auto";
</script>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-toc" viewBox="0 0 24 24">
<title>Contents</title>
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 1024 1024">
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z"/>
</svg>
</symbol>
<symbol id="svg-menu" viewBox="0 0 24 24">
<title>Menu</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-menu">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</symbol>
<symbol id="svg-arrow-right" viewBox="0 0 24 24">
<title>Expand</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-chevron-right">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</symbol>
<symbol id="svg-sun" viewBox="0 0 24 24">
<title>Light mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="feather-sun">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
<symbol id="svg-moon" viewBox="0 0 24 24">
<title>Dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-moon">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />
</svg>
</symbol>
<symbol id="svg-sun-with-moon" viewBox="0 0 24 24">
<title>Auto light/dark, in light mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round"
class="icon-custom-derived-from-feather-sun-and-tabler-moon">
<path style="opacity: 50%" d="M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z"/>
<line x1="14.5" y1="3.25" x2="14.5" y2="1.25"/>
<line x1="14.5" y1="15.85" x2="14.5" y2="17.85"/>
<line x1="10.044" y1="5.094" x2="8.63" y2="3.68"/>
<line x1="19" y1="14.05" x2="20.414" y2="15.464"/>
<line x1="8.2" y1="9.55" x2="6.2" y2="9.55"/>
<line x1="20.8" y1="9.55" x2="22.8" y2="9.55"/>
<line x1="10.044" y1="14.006" x2="8.63" y2="15.42"/>
<line x1="19" y1="5.05" x2="20.414" y2="3.636"/>
<circle cx="14.5" cy="9.55" r="3.6"/>
</svg>
</symbol>
<symbol id="svg-moon-with-sun" viewBox="0 0 24 24">
<title>Auto light/dark, in dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round"
class="icon-custom-derived-from-feather-sun-and-tabler-moon">
<path d="M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z"/>
<line style="opacity: 50%" x1="18" y1="3.705" x2="18" y2="2.5"/>
<line style="opacity: 50%" x1="18" y1="11.295" x2="18" y2="12.5"/>
<line style="opacity: 50%" x1="15.316" y1="4.816" x2="14.464" y2="3.964"/>
<line style="opacity: 50%" x1="20.711" y1="10.212" x2="21.563" y2="11.063"/>
<line style="opacity: 50%" x1="14.205" y1="7.5" x2="13.001" y2="7.5"/>
<line style="opacity: 50%" x1="21.795" y1="7.5" x2="23" y2="7.5"/>
<line style="opacity: 50%" x1="15.316" y1="10.184" x2="14.464" y2="11.036"/>
<line style="opacity: 50%" x1="20.711" y1="4.789" x2="21.563" y2="3.937"/>
<circle style="opacity: 50%" cx="18" cy="7.5" r="2.169"/>
</svg>
</symbol>
<symbol id="svg-pencil" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-pencil-code">
<path d="M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4" />
<path d="M13.5 6.5l4 4" />
<path d="M20 21l2 -2l-2 -2" />
<path d="M17 17l-2 2l2 2" />
</svg>
</symbol>
<symbol id="svg-eye" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-eye-code">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path
d="M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008" />
<path d="M20 21l2 -2l-2 -2" />
<path d="M17 17l-2 2l2 2" />
</svg>
</symbol>
</svg>
<input type="checkbox" class="sidebar-toggle" name="__navigation" id="__navigation" aria-label="Toggle site navigation sidebar">
<input type="checkbox" class="sidebar-toggle" name="__toc" id="__toc" aria-label="Toggle table of contents sidebar">
<label class="overlay sidebar-overlay" for="__navigation"></label>
<label class="overlay toc-overlay" for="__toc"></label>
<a class="skip-to-content muted-link" href="#furo-main-content">Skip to content</a>
<div class="page">
<header class="mobile-header">
<div class="header-left">
<label class="nav-overlay-icon" for="__navigation">
<span class="icon"><svg><use href="#svg-menu"></use></svg></span>
</label>
</div>
<div class="header-center">
<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">
<button class="theme-toggle" aria-label="Toggle Light / Dark / Auto color theme">
<svg class="theme-icon-when-auto-light"><use href="#svg-sun-with-moon"></use></svg>
<svg class="theme-icon-when-auto-dark"><use href="#svg-moon-with-sun"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-header-icon" for="__toc">
<span class="icon"><svg><use href="#svg-toc"></use></svg></span>
</label>
</div>
</header>
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<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">
<input type="hidden" name="check_keywords" value="yes">
<input type="hidden" name="area" value="default">
</form>
<div id="searchbox"></div><div class="sidebar-scroll"><div class="sidebar-tree">
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="whatis.html">What is Reticulum?</a></li>
<li class="toctree-l1"><a class="reference internal" href="gettingstartedfast.html">Getting Started Fast</a></li>
<li class="toctree-l1"><a class="reference internal" href="zen.html">Zen of Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="software.html">Programs Using Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
<li class="toctree-l1"><a class="reference internal" href="license.html">Reticulum License</a></li>
</ul>
<ul>
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
</ul>
</div>
</div>
</div>
</div>
</aside>
<div class="main">
<div class="content">
<div class="article-container">
<a href="#" class="back-to-top muted-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z"></path>
</svg>
<span>Back to top</span>
</a>
<div class="content-icon-container">
<div class="theme-toggle-container theme-toggle-content">
<button class="theme-toggle" aria-label="Toggle Light / Dark / Auto color theme">
<svg class="theme-icon-when-auto-light"><use href="#svg-sun-with-moon"></use></svg>
<svg class="theme-icon-when-auto-dark"><use href="#svg-moon-with-sun"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-content-icon" for="__toc">
<span class="icon"><svg><use href="#svg-toc"></use></svg></span>
</label>
</div>
<article role="main" id="furo-main-content">
<section id="distributed-development">
<span id="id1"></span><h1>Distributed Development<a class="headerlink" href="#distributed-development" title="Link to this heading"></a></h1>
<p>This chapter of the manual provides the conceptual basis for understanding <em>why</em> <code class="docutils literal notranslate"><span class="pre">rngit</span></code> exists, what it aims to achieve, and the kinds of spaces it seeks to reestablish. For the practical details of operating the system, refer to the <a class="reference internal" href="git.html#git-main"><span class="std std-ref">Git Over Reticulum</span></a> chapter.</p>
<section id="the-original-architecture">
<h2>The Original Architecture<a class="headerlink" href="#the-original-architecture" title="Link to this heading"></a></h2>
<p>When Torvalds created Git in 2005, he designed a tool that reflected a specific philosophy of collaboration. Every copy of a repository would be a complete, sovereign instance. There was no central server, no single point of failure, no gatekeeper. Developers would be able to work independently, exchange patches directly, and maintain their own branches indefinitely. This concept was - and is - both beautiful and revolutionary. Its execution is peer-to-peer not as a marketing term, but in the most foundational sense: As fundamental, structural reality.</p>
<p>Such a design emerged from necessity. The Linux kernel development process operated across geographical boundaries, time zones, and organizational affiliations. Contributors did not “log in” to a shared server to do their work; they maintained their own trees, and the flow of code between these trees was negotiated through patches, reviews, and merge decisions. The architecture of Git mirrored the social architecture of the community: Autonomous, competent, and fundamentally distributed in its technical operation.</p>
<p><em>The result of that work is, in the most direct sense, what makes it possible for you to read this today.</em></p>
<p>Theres something very important to take note of here: With Git, developers could collaborate effectively and perfectly well without any central server being present, without platform-mediated visibility into each others work, and without a centralized authority validating their contributions. They needed <em>only</em> a protocol for exchanging differences and a mechanism for verification of authorship. Everything else - social organization, quality control, release management - was handled by careful <em>human judgment</em> operating on top of the technical substrate.</p>
<p>What Git provided was not a development environment, but a <strong>language for versioning</strong>. It specified how to represent history, how to compute differences, how to merge divergent branches. It did not specify who could participate, how they should communicate, or what workflows they should follow. These were left to the competence and discretion of the creators using the system.</p>
</section>
<section id="the-platform-interregnum">
<h2>The Platform Interregnum<a class="headerlink" href="#the-platform-interregnum" title="Link to this heading"></a></h2>
<p>What followed represents a very familiar pattern: Tools designed to distribute power were re-centralized by platforms that offered convenience in exchange for control. GitHub, GitLab, and similar services reintroduced the centralization that Git had eliminated architecturally. The activity feed replaced durable artifacts with ephemeral notifications. The social graph and open interaction became as important as the code itself, if not more.</p>
<p>This re-centralization was not technical, as such. It was <strong>ontological</strong>. When every developer pushes to the same server, when every merge is in theory controllable by a platform, when every issue is tracked in a database controlled by a corporation, the nature of collaboration changes. The platform, and its social dynamics, becomes the ground of reality. The platform mediates not just the technical exchange of information and the programmatics, but the social recognition and codices of contribution, the future archival prospects of the work, and the very identity of the project itself.</p>
<p>The consequences extend beyond individual inconvenience. Centralized platforms create single points of failure for entire ecosystem. When a platform changes its terms of service, suspends accounts, removes repositories or ceases operation, entire project histories and community relationships can be disrupted or destroyed. The extractive economics of platform capitalism mean that value created by open-source communities is captured by corporations, while communities remain dependent on infrastructure they do not control. And the surveillance inherent in platform operation means that every action - every commit, every comment, every page view - is logged, analyzed, and potentially monetized or weaponized.</p>
<p>More insidiously, platforms have completely reshaped the culture of development itself. They have created what we could call the <strong>Teahouse Developer</strong>: A participant who treats engineering projects as social venues for opinion-sharing rather than sites of disciplined and careful production. These personages have no actual stakes in the projects they act as leeches upon, and only a very base consciousness of the damage they are incurring in order to feed their attention and external validation dependencies.</p>
<p>When platforms optimize for engagement, when growth is the only metric, when every user with an opinion must have their voice heard, when a random social process is elevated to higher importance than results, the signal-to-noise ratio collapses catastrophically. Competent engineers find themselves drowning in feedback from the incompetent, managing the emotional needs and dysregulations of drive-by commentators rather than solving technical problems.</p>
<p>The platform model is predicated on <strong>unsaturable expansion</strong>. Like almost any industrial system, it cannot function without growth. It pursues no particular aims; it is growth for the sake of growing. There is no saturation point, no concept of “enough”. Every barrier to entry must be put down to the very lowest common denominator, every voice must be amplified, every interaction must be converted into content that feeds the machine. This is fundamentally incompatible with the nature of social beings itself. It is also incompatible with serious engineering, which requires focus, discernment, and the right of people who know better to say “no”.</p>
</section>
<section id="restoration">
<h2>Restoration<a class="headerlink" href="#restoration" title="Link to this heading"></a></h2>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system represents a return to Gits original architectural principles, fortified with cryptographic networking capabilities that were not available in 2005. The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system <em>is</em> Git - but running over Reticulum. Welcome back to a world where your work is your own, but where everyone can still reach you - if you want them to.</p>
<p>Just as Git eliminated the need for a central version control server, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> eliminates the need for a central hosting platform, “servers” or any kinds of middle-men between the people actually doing the work. By operating over Reticulum, it eliminates the visibility of development activity to platform operators, network observers, state actors and other malicious third-parties.</p>
<p>In this model, the repository node is a <strong>sovereign entity</strong>. It is reachable from anywhere in the Reticulum network but owned, operated, and controlled by the developer or community that runs it. It is an actual home for creative output, not an extraction mechanism to which dues are paid. The node operator decides who may contribute, what standards must be met, and which voices are worth listening to. This is not exclusion; it is <strong>discernment</strong>. It is the necessary exercise of judgment that separates engineering from theatrics.</p>
<p>I did not create this in a fit of nostalgia. I created it because it is a necessary response to the failures of the centralized model. Gits technical architecture was - and <em>is</em> - correct. It was the social and economic superstructure built atop it that introduced fragility, exploitation, and environments toxic to actual creativity. By returning to first principles - distributed version control on distributed infrastructure - we recover not just a technical capability, but a mode of collaboration that respects the autonomy of individual developers and the sovereignty of actual communities.</p>
</section>
<section id="protocols-over-platforms">
<h2>Protocols Over Platforms<a class="headerlink" href="#protocols-over-platforms" title="Link to this heading"></a></h2>
<p>The distinction between platforms and protocols is fundamental to understanding the architecture of sovereignty in networked systems. A platform is a service you access; a protocol is a grammar you speak; actions you live. A platform requires permission to enter, a protocol requires only <em>comprehension</em> to employ. A platform can change its rules, suspend your account, or cease operation entirely, a protocol persists as long as there are participants who <em>understand</em> and <em>use</em> it. A protocol is an <em>idea</em>, a platform is a machine that turns its users into products.</p>
<p>Platforms operate on a client-server model that inherently creates power asymmetry. Even when platforms are built atop open-source software, the operational instance remains a black box of corporate control. You <em>may</em> be able to download <em>some</em> of your data, but you cannot download the connections to the people that are the true value-base of the platform, or take them with you if you want to leave.</p>
<p>Protocols, by contrast, are agreements. They specify how systems should communicate, but not who may communicate or on what terms. Email is a protocol; Gmail is a platform. HTTP is a protocol; Facebook is a platform. Git is a protocol; GitHub is a platform. The protocol persists regardless of any particular implementations success or failure.</p>
<p>The power of protocols lies in their <strong>permissionlessness</strong>. Anyone can implement a protocol without approval. Anyone can extend it, fork it, or use it for purposes unforeseen by its creators. This creates resilience: protocols cannot be easily censored, monopolized, or shut down because they exist as shared understanding rather than centralized infrastructure.</p>
<p>Reticulum is a protocol in this strict sense. It specifies how packets should be formatted, how paths should be discovered, how encryption should be applied. The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system extends this protocol approach to development workflows. It is not an external platform that hosts your repositories; it is a protocol for exchanging repository data, release artifacts, and work documents over Reticulums encrypted transport. But with a few commands and an old computer, it creates your own infrastructure for hosting repositories, or sharing them with who you choose. <em>That</em> is how tools should function, in case we had forgotten.</p>
<p>Unlike platforms, which extract value by creating dependency, there is no entity that can grant or deny you the privilege of running <code class="docutils literal notranslate"><span class="pre">rngit</span></code>. Your Reticulum identity is not endowed by any platform; it is generated locally and certified by its own cryptographic properties. Your repositories are hosted on nodes you control or nodes operated by communities you trust. Your relationships with other developers are peer-to-peer connections established through cryptographic addressing, not social graph connections managed by recommendation algorithms.</p>
<p>On a platform, exit means abandonment: you lose your history, your relationships, your visibility. With protocols, exit is just migration. When you change your infrastructure, your identity and your work travel with you. There are no middlemen between you and your collaborators. If push comes to shove, you can write your entire lifes work and connections to an SD card, swim across the lake, and set up camp on the other side.</p>
</section>
<section id="sovereignty-through-infrastructure">
<h2>Sovereignty Through Infrastructure<a class="headerlink" href="#sovereignty-through-infrastructure" title="Link to this heading"></a></h2>
<p>The concept of sovereignty - supreme authority within a territory - has traditionally been applied to nation-states. But in an age where creative work is conducted through digital infrastructure, sovereignty is essential for individuals and communities. <strong>Creative sovereignty</strong> means having supreme authority over the artifacts you produce, the processes by which you produce them, and the terms under which they are distributed. It means not merely legal ownership of copyright, but operational control of the infrastructure that mediates creation, collaboration, and dissemination.</p>
<p>Centralized development platforms strip away most layers of sovereignty. When you host code on a corporate platform, you retain <em>some</em> legal ownership of copyright, but you surrender complete operational control. The platform decides what content is acceptable, who can access it, and how it is presented. They can delete your repository, suspend your account, or change the visibility of your work without consent. In reality, legal ownership becomes meaningless as operational control is ceded.</p>
<p>Running your own <code class="docutils literal notranslate"><span class="pre">rngit</span></code> node restores this sovereignty. You control the hardware, the network configuration, the backup strategies, and the access permissions. You decide what constitutes acceptable use, who may contribute, and how contributions are evaluated. Taking this responsibility on yourself is an assertion that your creative work is not a product to be harvested by platform economics, but an autonomous activity to be conducted on your own terms.</p>
<p>This sovereignty and responsibility extends to the entry barriers you establish. The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system allows you to configure access controls that filter participants based on cryptographic identity and demonstrated competence. If, for example, someone cannot navigate a command line, or use Reticulum to submit a patch, they most likely lack the required competence to modify your code. In a world that apparently labels this as “exclusion”, I would simply refer to it as a minimally acceptable level of quality control.</p>
<p>Such a stance protects projects from the noise that so often overwhelms and completely dilutes platform-based development, where every user with an opinion believes themselves entitled to attention and access to the decision process.</p>
</section>
<section id="artifact-centered-workflows">
<h2>Artifact-Centered Workflows<a class="headerlink" href="#artifact-centered-workflows" title="Link to this heading"></a></h2>
<p>Contemporary platform-based development has shifted focus from durable artifacts to ephemeral <em>activity</em>. It does not matter what constitutes this activity, as long as its there. The primary interface is not the repository itself, not the produced artifacts, but the activity feed: <em>Notifications</em> of commits, comments, pull requests, and social interactions. Work is measured by velocity, throughput, and the constant stream of updates. This activity-centric model creates constant urgency, discourages discernment, encourages reactive rather than reflective work patterns, and produces vast quantities of ephemeral and useless communication that obscures actual project state and productivity.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system enables a return to <strong>artifact-centered workflows</strong>, where the focus is on durable, attributable, versioned outputs rather than the stream of notifications surrounding them. The fundamental unit of work is the commit - signed, immutable records of change. The fundamental unit of production is the signed artifact - a self-verifying package of functionality. The fundamental unit of discussion is the work document - a structured, threaded conversation attached to repositories.</p>
<p>Artifacts can persist independently of any platforms continued operation. A commit signed with your Reticulum identity is attributable to you regardless of where it is stored. A release signed with your private key is verifiable as authentic regardless of which network it traverses, and can be verified offline on any system running Reticulum. The work exists as <strong>cryptographic fact</strong>, distributed over the planet, not as database entries in a corporate cloud.</p>
<p>Such a shift has real psychological consequences. When work is measured in artifacts rather than activity, the pace changes. There is no need for constant visibility, no pressure to perform busyness. Developers can work deeply, reflectively, and submit complete solutions rather than incremental updates designed to maintain presence in an activity feed. The work becomes <strong>substantial</strong>, in the physical sense of the word, rather than performative.</p>
</section>
<section id="composable-primitives">
<h2>Composable Primitives<a class="headerlink" href="#composable-primitives" title="Link to this heading"></a></h2>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system is not a monolithic application prescribing a specific workflow; it is a collection of <strong>composable primitives</strong> that can be arranged to support diverse creative processes. Understanding these primitives as separate, orthogonal capabilities enables users to construct workflows suited to their specific needs and to recombine these primitives in ways unforeseen by the systems designers.</p>
<p>The core primitives include:</p>
<ul class="simple">
<li><p><strong>Repository Hosting</strong>: Bare Git repositories served over Reticulum links, accessible via standard Git commands through the <code class="docutils literal notranslate"><span class="pre">rns://</span></code> URL scheme.</p></li>
<li><p><strong>Identity-Based Access Control</strong>: Fine-grained permissions managed through cryptographically verifiable identity hashes, configurable at the group, repository, or document level.</p></li>
<li><p><strong>Release Distribution</strong>: Cryptographically signed release artifacts with embedded provenance information, verifiable offline and distributable through any Reticulum or physical path.</p></li>
<li><p><strong>Work Document Tracking</strong>: Structured, threaded work management attached to repositories, with precise permission controls, and the ability to contain updates or discussions.</p></li>
<li><p><strong>Forking and Mirroring</strong>: Automated replication of repositories from any accessible Git URL, with metadata tracking upstream relationships for synchronization.</p></li>
<li><p><strong>Nomad Network Integration</strong>: Page node functionality for browsing repository contents, commit history, and release information through the Nomad Network protocol.</p></li>
</ul>
<p>These primitives can be composed into workflows ranging from single-developer projects to complex multi-organizational collaborations. A solo developer might use only repository hosting and release distribution. A research group might add work document tracking for structured peer review. A software distribution network might combine mirroring with cryptographic release verification to create resilient update channels.</p>
<p>The entire system is incredibly light-weight, and can host hundreds of repositories on a Raspberry Pi.</p>
<p>Composability is essential because <strong>creative work is diverse</strong>. Software development, academic research, technical writing, hardware design, music production and data analysis all have different requirements for collaboration, review, and distribution. A platform prescribes a single workflow and forces all users to conform. A protocol provides primitives and allows users to construct workflows appropriate to their domain.</p>
<p>With <code class="docutils literal notranslate"><span class="pre">rngit</span></code>, you can re-build the system into anything you can imagine. Everything can be modified, extended and hooked into. Adding functionality or automation is never further away than a shell script, a cron-job, or a Python modification of the source.</p>
</section>
<section id="distribution-without-intermediaries">
<h2>Distribution Without Intermediaries<a class="headerlink" href="#distribution-without-intermediaries" title="Link to this heading"></a></h2>
<p>Creating software is only part of the work. Then comes actually getting it to the people needing to use it. Centralized platforms handle distribution through their own infrastructure: Content delivery networks, central package registries, and download servers accessed through platform-controlled interfaces. This convenience masks a fundamental dependency: Your ability to distribute depends on the platforms continued operation, their policies regarding your content, and their technical infrastructures reach.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> release system enables distribution strategies <strong>decoupled from any single infrastructure provider</strong>. Releases are cryptographically signed using Ed25519 signatures and packaged in signed release manifests (<code class="docutils literal notranslate"><span class="pre">.rsm</span></code> files). These manifests contain embedded signatures for each artifact. The manifest provides full verifiability of all release information, and contains embedded release artifact lists, per-file <code class="docutils literal notranslate"><span class="pre">.rsg</span></code> signatures, origin information, and the creators Reticulum Identity. It can also be used to fetch verified updates of the software package over the network, and can always be verified completely offline.</p>
<p>Because releases are self-verifying, they can traverse any network or physical path that Reticulum can establish. A release can travel over LoRa radio, be carried on USB drives through areas without internet connectivity, disseminated over a mirror network, or be distributed through store-and-forward mechanisms on intermittent infrastructure. Recipients can verify authenticity regardless of how they obtained the files. This is particularly valuable in low-connectivity environments where Reticulum may be the only available communication channel.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span> <span class="pre">release</span></code> command provides tools for creating, publishing, fetching, and verifying releases. When fetching a release using an <code class="docutils literal notranslate"><span class="pre">.rsm</span></code> manifest, the system validates the manifest signature against the required Reticulum Identity, extracts the origin node and repository path, connects to the origin over Reticulum, retrieves the latest release manifest, and verifies each downloaded artifact against the signatures embedded in the manifest. If any verification fails, the fetch aborts, preventing installation of corrupted or tampered files.</p>
<p>This cryptographic verification replaces the trust model of platform distribution. Instead of trusting that a platform has not been compromised, users verify that artifacts match the signatures created by the developers identity. It doesnt matter <em>how</em> they obtained the artifacts, they can <strong>always</strong> be verified. This security model shifts from <strong>institutional trust</strong> (just believe in the goodness of the platform) to <strong>cryptographic proof</strong> (verify the signatures).</p>
</section>
<section id="long-archive">
<h2>Long Archive<a class="headerlink" href="#long-archive" title="Link to this heading"></a></h2>
<p>Software development is often conceived as an activity of the present only: Solving todays problems, meeting current deadlines, responding to immediate feedback. But the artifacts produced - code, documentation, releases - have lifespans extending <em>far</em> beyond their creation. They may be used for decades, studied by future developers, depended upon by systems not yet imagined, or preserved as historical records of technological development.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system is designed with this <strong>extended timeframe</strong> in mind, supporting the creation of archives that are durable, portable, and intelligible across generational timescales. Git repositories are always internally complete; they contain full history and can be migrated to new infrastructure without loss of information. Everything that <code class="docutils literal notranslate"><span class="pre">rngit</span></code> adds on top of this is stored in normal files in standard formats right next to the Git repository folders, not an esoteric database-cluster two thousand kilometers away. Because releases are cryptographically signed, they remain verifiable as authentic regardless of when or where they are retrieved. Because the system operates over Reticulum, it can function over communication mediums that may outlast the internet as we know it.</p>
<p>This long-term perspective influences technical decisions. The use of well-established cryptographic primitives ensures that signatures will remain verifiable for centuries. The use of standard formats ensures that repositories will remain readable by future tools. The protocol-based architecture ensures that the system can evolve without losing compatibility with existing data.</p>
<p>For critical infrastructure, this archival durability is not optional; it is essential. Communication systems, cryptographic libraries, and safety-critical code must remain available and verifiable for the lifespans of the systems that depend on them. The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system provides the tools to create such archives: distributed across multiple nodes, cryptographically verified, and independent of any corporate or governmental infrastructure, which as history has shown repeatedly, does <em>not</em> persist.</p>
</section>
<section id="start-of-the-road">
<h2>Start Of The Road<a class="headerlink" href="#start-of-the-road" title="Link to this heading"></a></h2>
<p>Distributed development and production over Reticulum is a <em>different mode of existence</em> for creative work. It restores the autonomy originally created by Git. It provides local sovereignty over production infrastructure, composability of workflow, and durability of artifact. It lets you filter participation through competence and cryptography rather than incentives of platform operators, raising the quality and enjoyment of work, and protecting the focus of real engineering and creative expression.</p>
<p>This is not a system for everyone, and that is the point. It requires investment - in understanding Reticulum, in configuring infrastructure, in establishing workflows. It requires accepting responsibility for your own tools rather than delegating them to platform operators. It requires the discipline to maintain your own node, manage your own backups, and nurture your own community.</p>
<p>But for those who make this investment, the returns are substantial. You gain <strong>immunity from platform failure</strong>; your work persists regardless of corporate decisions or service outages. You gain <strong>shelter from surveillance</strong>; your development activity is visible only to those that <em>you</em> choose to involve. You gain <strong>control over process</strong>; you decide how work is conducted, reviewed, and released, unmediated by terms of service, algorithmic feeds and thousands of uninformed and irrelevant opinions.</p>
<p>Most importantly, though, you regain the <strong>dignity of craft</strong>. Development becomes an activity conducted among peers, equals among equals, mediated by skill and cryptographic proof rather than corporate permission, producing artifacts that stand as independent testimony to competence, functionality or beauty rather than as content feeding engagement metrics. The <em>work</em> becomes the point. The artifacts become durable. And the network becomes <em>one</em> of the tools you wield in this endeavor.</p>
</section>
</section>
</article>
</div>
<footer>
<div class="related-pages">
<a class="next-page" href="git.html">
<div class="page-info">
<div class="context">
<span>Next</span>
</div>
<div class="title">Git Over Reticulum</div>
</div>
<svg class="furo-related-icon"><use href="#svg-arrow-right"></use></svg>
</a>
<a class="prev-page" href="networks.html">
<svg class="furo-related-icon"><use href="#svg-arrow-right"></use></svg>
<div class="page-info">
<div class="context">
<span>Previous</span>
</div>
<div class="title">Building Networks</div>
</div>
</a>
</div>
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2025, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>
</div>
<div class="right-details">
</div>
</div>
</footer>
</div>
<aside class="toc-drawer">
<div class="toc-sticky toc-scroll">
<div class="toc-title-container">
<span class="toc-title">
On this page
</span>
</div>
<div class="toc-tree-container">
<div class="toc-tree">
<ul>
<li><a class="reference internal" href="#">Distributed Development</a><ul>
<li><a class="reference internal" href="#the-original-architecture">The Original Architecture</a></li>
<li><a class="reference internal" href="#the-platform-interregnum">The Platform Interregnum</a></li>
<li><a class="reference internal" href="#restoration">Restoration</a></li>
<li><a class="reference internal" href="#protocols-over-platforms">Protocols Over Platforms</a></li>
<li><a class="reference internal" href="#sovereignty-through-infrastructure">Sovereignty Through Infrastructure</a></li>
<li><a class="reference internal" href="#artifact-centered-workflows">Artifact-Centered Workflows</a></li>
<li><a class="reference internal" href="#composable-primitives">Composable Primitives</a></li>
<li><a class="reference internal" href="#distribution-without-intermediaries">Distribution Without Intermediaries</a></li>
<li><a class="reference internal" href="#long-archive">Long Archive</a></li>
<li><a class="reference internal" href="#start-of-the-road">Start Of The Road</a></li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</aside>
</div>
</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>
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
<script src="_static/copybutton.js?v=f281be69"></script>
</body>
</html>
+5 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Code Examples</a></li>
@@ -3664,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=a5753347"></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 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -295,7 +296,7 @@
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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>
+12 -5
View File
@@ -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.7 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.7 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.7 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">
@@ -220,6 +220,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -308,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>
@@ -791,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>
@@ -839,7 +846,7 @@
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -967,7 +968,7 @@ All other available modules will still be loaded when needed.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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>
+288 -26
View File
@@ -3,11 +3,11 @@
<head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html"><link rel="search" title="Search" href="search.html"><link rel="next" title="Support Reticulum" href="support.html"><link rel="prev" title="Building Networks" href="networks.html">
<link rel="index" title="Index" href="genindex.html"><link rel="search" title="Search" href="search.html"><link rel="next" title="Support Reticulum" href="support.html"><link rel="prev" title="Distributed Development" href="distributed.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>Git Over Reticulum - Reticulum Network Stack 1.2.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -263,11 +264,12 @@
<article role="main" id="furo-main-content">
<section id="git-over-reticulum">
<span id="git-main"></span><h1>Git Over Reticulum<a class="headerlink" href="#git-over-reticulum" title="Link to this heading"></a></h1>
<p>This chapter of the manual serves as the technical reference for the distributed software development and project collaboration tools included in RNS. For a conceptual overview, see the <a class="reference internal" href="distributed.html#distributed-development"><span class="std std-ref">Distributed Development</span></a> chapter.</p>
<p>A set of utilities for distributed collaborative software development and publishing are included in RNS.</p>
<p>The system consists of two parts: The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> node that hosts repositories, and the <code class="docutils literal notranslate"><span class="pre">git-remote-rns</span></code> helper that enables Git to communicate with rngit nodes. As soon as you have RNS installed on your system, you can transparently use Git with Reticulum-hosted repositories just like any other type of remote. Git over Reticulum uses URLs in the following format: <code class="docutils literal notranslate"><span class="pre">rns://DESTINATION_HASH/group/repo</span></code>.</p>
<p>If you set a branch to track a Reticulum remote as the default upstream, you can simply use <code class="docutils literal notranslate"><span class="pre">git</span></code> as you normally would; all commands work transparently and as expected.</p>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<div class="admonition important">
<p class="admonition-title">Important</p>
<p><strong>The rngit program is a new addition to RNS!</strong> This functionality was introduced in RNS 1.2.0. While great care has been taken to design a secure, but highly configurable and flexible <a class="reference external" href="#permissions">permission system</a> for allowing many users to interact with many different repositories on a single node, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> has not been tested extensively in the wild! Be careful when hosting repositories, especially if they are public or semi-public.</p>
</div>
<section id="the-rngit-utility">
@@ -388,10 +390,8 @@ Repository forked to public/myfork
<p>The source can be any valid Git URL, including:</p>
<ul class="simple">
<li><p>HTTPS URLs: <code class="docutils literal notranslate"><span class="pre">https://github.com/user/repo.git</span></code></p></li>
<li><p>Git URLs: <code class="docutils literal notranslate"><span class="pre">git://host.com/repo.git</span></code></p></li>
<li><p>SSH URLs: <code class="docutils literal notranslate"><span class="pre">ssh://git&#64;host.com/repo.git</span></code></p></li>
<li><p>Reticulum URLs: <code class="docutils literal notranslate"><span class="pre">rns://DESTINATION_HASH/group/repo</span></code></p></li>
<li><p>Local paths: <code class="docutils literal notranslate"><span class="pre">/path/to/repo.git</span></code></p></li>
</ul>
<p>Forks are created as bare repositories with metadata tracking their origin. The fork process:</p>
<ol class="arabic simple">
@@ -539,6 +539,7 @@ $ git config --get repository.rngit.upstream.sync
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> permission system provides fine-grained access control at multiple levels: group-level, repository-level, and document-level. Permissions can be statically configured in files or dynamically generated via executable scripts.</p>
<p>Access permissions can be configured at the group level in the config file or per-group <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> files, or per-repository <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> files. The <code class="docutils literal notranslate"><span class="pre">s</span></code> (stats) permission allows viewing repository activity statistics, including views, fetches and pushes over time. To enable statistics recording, set <code class="docutils literal notranslate"><span class="pre">record_stats</span> <span class="pre">=</span> <span class="pre">yes</span></code> in the <code class="docutils literal notranslate"><span class="pre">[rngit]</span></code> section of the configuration file. You can also exclude specific identities from statistics by adding their hashes to <code class="docutils literal notranslate"><span class="pre">stats_ignore_identities</span></code>.</p>
<p>By default, <strong>no</strong> permissions are granted for anything! You will have to enable the permissions you require to be able to actually <em>do</em> something with <code class="docutils literal notranslate"><span class="pre">rngit</span></code>.</p>
<p>Permissions can be modified by editing the <code class="docutils literal notranslate"><span class="pre">rngit</span></code> config file, individual <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> files on disk, or remotely using the <code class="docutils literal notranslate"><span class="pre">rngit</span> <span class="pre">perms</span></code> command.</p>
<section id="permission-types">
<h3>Permission Types<a class="headerlink" href="#permission-types" title="Link to this heading"></a></h3>
<p>The following permissions are supported:</p>
@@ -675,6 +676,74 @@ w:none
</ul>
</section>
</section>
<section id="remote-permission-management">
<h2>Remote Permission Management<a class="headerlink" href="#remote-permission-management" title="Link to this heading"></a></h2>
<p>While permissions can be configured directly on the node by editing configuration files and <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> files, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> also supports remote permission management through the <code class="docutils literal notranslate"><span class="pre">rngit</span> <span class="pre">perms</span></code> command. This allows administrators to modify access controls for groups and repositories over Reticulum, without requiring shell access to the hosting node.</p>
<p>To use remote permission management, you must have <code class="docutils literal notranslate"><span class="pre">admin</span></code> permission on the target group or repository. The command opens your configured <code class="docutils literal notranslate"><span class="pre">$EDITOR</span></code> to modify permissions, using the same syntax and format as local <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> files. When you save and exit the editor, the modified permissions are transmitted to the remote node and applied immediately.</p>
<section id="managing-group-permissions">
<h3>Managing Group Permissions<a class="headerlink" href="#managing-group-permissions" title="Link to this heading"></a></h3>
<p>To view or modify permissions for an entire repository group, specify the group URL (ending with the group name):</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit perms rns://50824b711717f97c2fb1166ceddd5ea9/public
</pre></div>
</div>
<p>This retrieves the current permission configuration from the <code class="docutils literal notranslate"><span class="pre">public.allowed</span></code> file and opens it in your editor. Any changes you make are validated for syntax correctness. Invalid permission rules will be rejected with an error message indicating the problematic line.</p>
</section>
<section id="managing-repository-permissions">
<h3>Managing Repository Permissions<a class="headerlink" href="#managing-repository-permissions" title="Link to this heading"></a></h3>
<p>To manage permissions for a specific repository, include the repository name in the URL:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit perms rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo
</pre></div>
</div>
<p>This operates on the <code class="docutils literal notranslate"><span class="pre">myrepo.allowed</span></code> file next to the repository. Repository-level permissions take precedence over group-level permissions, allowing fine-grained access control for individual repositories within a group.</p>
</section>
<section id="permission-validation">
<h3>Permission Validation<a class="headerlink" href="#permission-validation" title="Link to this heading"></a></h3>
<p>When modifying permissions remotely, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> validates that:</p>
<ul class="simple">
<li><p>Each permission line follows the correct <code class="docutils literal notranslate"><span class="pre">permission:target</span></code> syntax</p></li>
<li><p>Permission types are valid (r, w, rw, c, s, rel, i, p, adm)</p></li>
<li><p>Target specifications are valid (identity hashes, <code class="docutils literal notranslate"><span class="pre">all</span></code>, or <code class="docutils literal notranslate"><span class="pre">none</span></code>)</p></li>
<li><p>Identity hashes, when specified, are the correct length (32 hexadecimal characters)</p></li>
</ul>
<p>If validation fails, the editor will reopen with an error message describing the issue, allowing you to correct the problem before resubmitting.</p>
<div class="admonition caution">
<p class="admonition-title">Caution</p>
<p>Remote permission modification requires administrative access (the <code class="docutils literal notranslate"><span class="pre">adm</span></code> permission), which grants full control over the repository or group. The permission change request is transmitted over the encrypted Reticulum link, and the remote node verifies your identity cryptographically before applying changes. However, be aware that granting <code class="docutils literal notranslate"><span class="pre">adm</span></code> permissions to remote identities effectively delegates full control, including the ability to revoke your own access or modify permissions in ways you may not anticipate.</p>
</div>
<p><strong>All Command-Line Options (rngit perms)</strong></p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rngit perms [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
[-i PATH] [-v] [-q] [--version]
remote
Reticulum Git Permission Manager
positional arguments:
remote URL of remote group or repository
options:
-h, --help show this help message and exit
--config CONFIG path to alternative config directory
--rnsconfig RNSCONFIG
path to alternative Reticulum config directory
-i, --identity PATH path to identity
-v, --verbose
-q, --quiet
--version show program&#39;s version number and exit
</pre></div>
</div>
</section>
</section>
<section id="identity-destination-aliases">
<h2>Identity &amp; Destination Aliases<a class="headerlink" href="#identity-destination-aliases" title="Link to this heading"></a></h2>
<p>To make permission and remote destination management easier, you can locally define aliases for commonly used identity and destination hashes. Identity aliases used in permissions resolution can be defined in the <code class="docutils literal notranslate"><span class="pre">[aliases]</span></code> section of the <code class="docutils literal notranslate"><span class="pre">~/.rngit/config</span></code> file, while destination aliases are defined in the <code class="docutils literal notranslate"><span class="pre">[aliases]</span></code> section of the <code class="docutils literal notranslate"><span class="pre">~/.rngit/client_config</span></code> file.</p>
<p>All alias definitions take the form of <code class="docutils literal notranslate"><span class="pre">aliased_name</span> <span class="pre">=</span> <span class="pre">HASH</span></code>:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[aliases]
alice = d09285e660cfe27cee6d9a0beb58b7e0
bob = ffcffb4e255e156e77f79b82c13086a6
</pre></div>
</div>
<p><strong>Aliases are always resolved locally!</strong> If for example you fork a repository with <code class="docutils literal notranslate"><span class="pre">rngit</span> <span class="pre">fork</span> <span class="pre">rns://bobs_node/public/repo_name</span> <span class="pre">rns://my_node/forks/repo_name</span></code>, the forked repository will of course still reference the full, original destination hash, and use this for subsequent upstream syncs.</p>
</section>
<section id="serving-pages-over-nomad-network">
<h2>Serving Pages Over Nomad Network<a class="headerlink" href="#serving-pages-over-nomad-network" title="Link to this heading"></a></h2>
<p>In addition to providing Git repository access via the Git remote helper protocol and command-line tools, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> can also run a <a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a> compatible page node. This allows users to browse repository information, view file contents, inspect commit history and access repository statistics through any Nomad Network client.</p>
@@ -728,6 +797,7 @@ def hello_world():
```
</pre></div>
</div>
<p>You can use <code class="docutils literal notranslate"><span class="pre">rawmu</span></code> code blocks to render raw Micron inside Markdown files. If you create a code block with the language hint <code class="docutils literal notranslate"><span class="pre">rawmu</span></code>, everything inside it will be treated as Micron directly.</p>
</section>
<section id="customizing-templates">
<h3>Customizing Templates<a class="headerlink" href="#customizing-templates" title="Link to this heading"></a></h3>
@@ -800,6 +870,148 @@ unicode_icons = yes
</div>
</section>
</section>
<section id="verified-releases">
<h2>Verified Releases<a class="headerlink" href="#verified-releases" title="Link to this heading"></a></h2>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> release system provides cryptographic provenance and integrity guarantees through automatic signing of release artifacts and signed release manifests. When you create a release, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> generates an Ed25519 signature for each artifact and embeds these signatures in a cryptographically signed release manifest (<code class="docutils literal notranslate"><span class="pre">.rsm</span></code> file). This allows anyone who obtains the release to verify its authenticity and integrity, regardless of how the files were distributed.</p>
<section id="obtaining-verified-releases">
<span id="git-release-obtain"></span><h3>Obtaining Verified Releases<a class="headerlink" href="#obtaining-verified-releases" title="Link to this heading"></a></h3>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system lets you obtain releases securely and in a verified manner, by validating cryptographically signed release manifests in the <code class="docutils literal notranslate"><span class="pre">.rsm</span></code> format during the retrieval process. Once a release has been published with <code class="docutils literal notranslate"><span class="pre">rngit</span></code>, anyone that has read access to it can obtain the release with the <code class="docutils literal notranslate"><span class="pre">rngit</span> <span class="pre">release</span></code> command, for example:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://remote_node/group/some_program fetch latest:all
</pre></div>
</div>
<p>This command will connect to the remote, retrieve the latest release manifest, verify its signature and integrity (you can optionally specify a required signer identity with <code class="docutils literal notranslate"><span class="pre">--signer</span></code>), and then download and sequentially verify all artifacts included in the release.</p>
<p>If verification succeeds, the retrieved artifact files, along with the release manifest will be saved in the current working directory. From the above example, you would end up with a number of downloaded files, and a version- and package specific release manifest, such as <code class="docutils literal notranslate"><span class="pre">some_program_1.5.2.rsm</span></code>.</p>
<div class="admonition important">
<p class="admonition-title">Important</p>
<p>Keeping the retrieved release manifest is a <strong>very</strong> good idea! It allows you to easily obtain future releases and updates to the software directly, while verifying they came from the same publisher.</p>
</div>
<p><strong>Obtaining &amp; Updating Releases Using RSM Manifests</strong></p>
<p>One of the key features of the <code class="docutils literal notranslate"><span class="pre">rngit</span></code> release system is the ability to fetch and verify new releases using only a signed release manifest. This is particularly valuable for distributing software over Reticulum. Once someone has an <code class="docutils literal notranslate"><span class="pre">.rsm</span></code> manifest of your package, they can use it to continually retrieve and update the software.</p>
<p>To fetch a release using a manifest:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release some_program_1.5.2.rsm fetch latest:all
</pre></div>
</div>
<p>This command:</p>
<ol class="arabic simple">
<li><p>Validates the manifest signature to confirm authenticity</p></li>
<li><p>Extracts the origin node and repository path from the signed manifest</p></li>
<li><p>Connects to the origin node over Reticulum</p></li>
<li><p>Gets the <em>latest</em> release manifest from the developer</p></li>
<li><p>Verifies it against the existing manifest</p></li>
<li><p>Fetches each artifact listed in the manifest</p></li>
<li><p>Verifies each downloaded file against the signature embedded in the manifest</p></li>
</ol>
<p>If any artifact fails signature verification, the fetch aborts with an error, preventing the installation of corrupted or tampered files.</p>
<p><strong>Specifying Required Signers</strong></p>
<p>You can require that releases be signed by specific identities. When fetching a release, use the <code class="docutils literal notranslate"><span class="pre">--signer</span></code> option to specify the identity hash of the required signer:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://remote_node/public/myrepo fetch latest:all --signer 21a8daa6d9c3d3b8aab6e94b6bcb0e33
</pre></div>
</div>
<p>If the release was not signed by the specified identity, the fetch will abort before any files are downloaded. Likewise, if any downloaded artifacts were not signed by the required identity, the process will abort at the first invalid signature. This provides strong guarantees about the provenance of the software you are installing.</p>
<p>The signer check also works when fetching from a local manifest:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release manifest.rsm fetch latest:all --signer 21a8daa6d9c3d3b8aab6e94b6bcb0e33
</pre></div>
</div>
<p><strong>Selective &amp; Partial Fetches</strong></p>
<p>You can fetch individual artifacts from a release by specifying the artifact name instead of <code class="docutils literal notranslate"><span class="pre">all</span></code>:</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.0.tar.gz
</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 &quot;1.2.0:*-py3-*.whl&quot;
</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 &quot;1.2.0:myapp-1.2.?-linux-x86_64.tar.gz&quot;
</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 &quot;1.2.0:source_*.tgz&quot;
</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>
<p>Ensure the <code class="docutils literal notranslate"><span class="pre">.rsg</span></code> signature is located in the same directory as the release artifact, then run:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnid -V myapp-1.2.0.tar.gz
</pre></div>
</div>
<p>This validates that the artifact file matches the signature created during the release process. Combined with the manifests own signature, this provides end-to-end verification from the original release creation to the final installation.</p>
<p><strong>For a complete release:</strong></p>
<p>Ensure the release manifest is located in the same directory as the release artifacts, then run:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release myapp-1.2.0.rsm --offline
</pre></div>
</div>
<p>This will load the manifest, and verify all files currently on-disk, but will not attempt to fetch the latest release manifest from the origin, or update local files to match it.</p>
</section>
<section id="creating-signed-releases">
<span id="git-release-create"></span><h3>Creating Signed Releases<a class="headerlink" href="#creating-signed-releases" title="Link to this heading"></a></h3>
<p>Reticulum and the <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system makes it easy to create signed releases that your users can verify and update securely. When you create a release using <code class="docutils literal notranslate"><span class="pre">rngit</span></code>, the program automatically:</p>
<ol class="arabic simple">
<li><p>Generates an Ed25519 signature for each artifact file using your identitys signing key</p></li>
<li><p>Creates <code class="docutils literal notranslate"><span class="pre">.rsg</span></code> signature files alongside each artifact in your distribution directory</p></li>
<li><p>Constructs a signed release manifest (<code class="docutils literal notranslate"><span class="pre">manifest.rsm</span></code>) containing metadata, an artifact list, and embedded signatures</p></li>
<li><p>Transmits both artifacts, signatures and manifest to the remote node specified as release origin</p></li>
</ol>
<p>As an example, to create and publish a release from all files in the folder named <code class="docutils literal notranslate"><span class="pre">dist</span></code>, simply run:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://my_node/group/myrepo create 1.2.0:./dist
</pre></div>
</div>
<p>Everything is automatically signed and uploaded to your node, and the release manifest will now include the following signed attestation information:</p>
<ul class="simple">
<li><p>Package name and version</p></li>
<li><p>The release notes for this release</p></li>
<li><p>Release timestamp and commit hash</p></li>
<li><p>Origin node identity and repository path</p></li>
<li><p>Complete list of artifacts</p></li>
<li><p>Embedded signatures for each artifact</p></li>
</ul>
<p>Thats it, theres nothing more to it than one command. Users can now securely obtain your release using <code class="docutils literal notranslate"><span class="pre">rngit</span> <span class="pre">release</span> <span class="pre">fetch</span></code>.</p>
<p><strong>Release Manifest Format</strong></p>
<p>Release manifests use the <code class="docutils literal notranslate"><span class="pre">.rsm</span></code> format (a general-purpose, structured signed message format) and are themselves cryptographically signed documents. The manifest format embeds the signing identitys public key and a detached signature that covers the entire manifest content. This creates a chain of trust: the manifest signature proves the manifests authenticity, and the embedded artifact signatures prove each files integrity.</p>
<p>When a release is created, the manifest is stored as <code class="docutils literal notranslate"><span class="pre">manifest.rsm</span></code> in the release artifacts directory. You can also generate a local release manifest without uploading by using the <code class="docutils literal notranslate"><span class="pre">--local</span></code> flag:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://f2d31b2e080e5d4e358d32822ee4a3b7/public/myrepo create 1.2.0:./dist --local
</pre></div>
</div>
<p>This creates the <code class="docutils literal notranslate"><span class="pre">.rsg</span></code> signature files and <code class="docutils literal notranslate"><span class="pre">manifest.rsm</span></code> in your local distribution directory without connecting to the remote node, allowing you to inspect or distribute the signed release through alternative channels.</p>
<p><strong>Signature File Format</strong></p>
<p>Individual artifact signatures use the Reticulum Signature (<code class="docutils literal notranslate"><span class="pre">.rsg</span></code>) format and contain:</p>
<ul class="simple">
<li><p>The Ed25519 signature of the file</p></li>
<li><p>The signing identitys public key</p></li>
<li><p>Optional metadata, such as timestamps or notes</p></li>
</ul>
<p>These signature files are created automatically during the release process and can be used independently of the manifest for verification purposes. The <code class="docutils literal notranslate"><span class="pre">rnid</span></code> utility can create and validate RSG signatures for any file, making this signature format useful beyond the <code class="docutils literal notranslate"><span class="pre">rngit</span></code> release system.</p>
<p><strong>Good Practices for Signature Distribution</strong></p>
<p>While release manifests in the <code class="docutils literal notranslate"><span class="pre">.rsm</span></code> format <em>include</em> embedded <code class="docutils literal notranslate"><span class="pre">.rsg</span></code> signatures for every listed artifact, it is dependent on the situation and requirements whether individual <code class="docutils literal notranslate"><span class="pre">.rsg</span></code> signatures are distributed as well. It is generally a good idea to do so, since they are very light-weight, and provide an easy and convenient way to validate and authenticate <em>individual</em> files, as opposed to entire releases.</p>
<p>When distributing software through multiple channels (direct download, mirror networks, physical media), including the <code class="docutils literal notranslate"><span class="pre">.rsm</span></code> manifest allows recipients to verify authenticity regardless of how they obtained the files. This is particularly valuable in low-connectivity environments where Reticulum may be the only available communication channel, as the manifest ensures that software updates can be verified even when received via store-and-forward mechanisms or physical media transport.</p>
<p><strong>Integration with Package Management</strong></p>
<p>While this functionality is still under development, the signed release manifest format is designed to be consumed by package management systems and automated deployment tools. Because the manifest is cryptographically signed and contains all necessary metadata and integrity checks, it can serve as a trusted source of truth for software distribution, even when fetched over untrusted channels or stored for long periods.</p>
<p><strong>Release Encryption</strong></p>
<p>While API primitives and command-line tools are currently not implemented for this, the release, distribution and verification system has been designed to also support <em>encrypted</em> releases, which can be distributed securely to authorized recipients.</p>
<p><strong>Verified Package Format</strong></p>
<p>The current system is being expanded to also include an <code class="docutils literal notranslate"><span class="pre">.rvp</span></code> package format, which can contain packaged releases including all relevant artifacts, metadata, manifest and signatures.</p>
<p><strong>Automated Mirror Discovery</strong></p>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> release system is designed to support automated mirror discovery and distribution package retrieval over Reticulum networks. Since everything is cryptographically signed and verified, it is possible to create automated mirror and distribution networks, where users can obtain software and information from local sources, without risking malicious modifications to the software they rely on. This functionality is currently in development.</p>
</section>
</section>
<section id="release-management">
<h2>Release Management<a class="headerlink" href="#release-management" title="Link to this heading"></a></h2>
<p>In addition to hosting Git repositories, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> provides a complete release management system. This allows you to publish versioned releases with associated artifacts, release notes and metadata. Releases are managed through the <code class="docutils literal notranslate"><span class="pre">rngit</span> <span class="pre">release</span></code> subcommand, and are also viewable through the Nomad Network page interface.</p>
@@ -807,12 +1019,12 @@ unicode_icons = yes
<h3>The Release Workflow<a class="headerlink" href="#the-release-workflow" title="Link to this heading"></a></h3>
<p>Creating a release involves specifying a Git tag and a directory containing build artifacts or other files to distribute. The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> client will open your configured <code class="docutils literal notranslate"><span class="pre">$EDITOR</span></code> to compose release notes, then upload all artifacts to the remote repository node.</p>
<p>To create a release, specify the tag name and path to artifacts:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo create v1.2.0:./dist
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo create 1.2.0:./dist
</pre></div>
</div>
<p>This will:</p>
<ol class="arabic simple">
<li><p>Verify that the tag <code class="docutils literal notranslate"><span class="pre">v1.2.0</span></code> exists in the repository</p></li>
<li><p>Verify that the tag <code class="docutils literal notranslate"><span class="pre">1.2.0</span></code> exists in the repository</p></li>
<li><p>Open your editor to write release notes</p></li>
<li><p>Upload all files from the <code class="docutils literal notranslate"><span class="pre">./dist</span></code> directory</p></li>
<li><p>Publish the release</p></li>
@@ -837,16 +1049,16 @@ unicode_icons = yes
Tag Status Created Objs Notes
------------------------------------------------------------------
v1.2.0 published 2025-01-15 14:32 3 Another release
v1.1.0 published 2024-12-03 09:15 2 Bug fix release
v1.0.0 published 2024-10-20 16:45 2 Initial release
1.2.0 published 2025-01-15 14:32 3 Another release
1.1.0 published 2024-12-03 09:15 2 Bug fix release
1.0.0 published 2024-10-20 16:45 2 Initial release
</pre></div>
</div>
<p><strong>Viewing Release Details</strong></p>
<p>To see full information about a specific release:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo view v1.2.0
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo view 1.2.0
Release : 0.9.2
Release : 1.2.0
Status : published
Created : 2026-05-04 23:53:09
Thanks : 5
@@ -863,12 +1075,24 @@ Artifacts (4)
- checksums.txt (256 B)
</pre></div>
</div>
<p><strong>Fetching Releases</strong></p>
<p>To fetch a release, specify the remote URL, version and artifacts:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo fetch latest:all
</pre></div>
</div>
<p>This process is described in greater detail in the <a class="reference internal" href="#git-release-obtain"><span class="std std-ref">Obtaining Verified Releases</span></a> section.</p>
<p><strong>Creating Releases</strong></p>
<p>To fetch a release, specify the remote URL, version and artifacts:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo create 1.3.9:artifacts_dir
</pre></div>
</div>
<p>This process is described in greater detail in the <a class="reference internal" href="#git-release-create"><span class="std std-ref">Creating Signed Releases</span></a> section.</p>
<p><strong>Deleting Releases</strong></p>
<p>To remove a release:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo delete v1.2.0
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo delete 1.2.0
Are you sure you want to delete release &#39;v1.2.0&#39;? [y/N]: y
Release v1.2.0 deleted
Are you sure you want to delete release &#39;1.2.0&#39;? [y/N]: y
Release 1.2.0 deleted
</pre></div>
</div>
<p><strong>Requirements &amp; Validation</strong></p>
@@ -889,15 +1113,16 @@ rel:none # Deny everyone
<p><strong>Nomad Network Interface</strong></p>
<p>When the Nomad Network page node is enabled, releases are displayed on a dedicated releases page for each repository. Each release is listed with its tag, creation date, artifact count and a preview of the release notes. Clicking a release shows the full details including formatted release notes and a listing of all artifacts with their sizes.</p>
<p><strong>All Command-Line Options (rngit release)</strong></p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rngit release [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
[-i PATH] [-v] [-q] [--version]
[repository] [operation] [target]
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: python -m RNS.Utilities.rngit.server [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
[-i PATH] [-s PATH] [-n name] [-L]
[-o] [-v] [-q] [--version]
[repository] [operation] [target]
Reticulum Git Release Manager
positional arguments:
repository URL of remote repository
operation list, view, create or delete
repository URL of remote repository, or path to RSM manifest
operation list, view, fetch, create, latest or delete
target tag and path to release artifacts directory
options:
@@ -906,6 +1131,10 @@ options:
--rnsconfig RNSCONFIG
path to alternative Reticulum config directory
-i, --identity PATH path to release identity
-s, --signer PATH path to signing identity, if different from release identity
-n, --name name package name if different from repo name
-L, --local generate release locally, but don&#39;t upload
-o, --offline verify manifest locally, but don&#39;t fetch updates
-v, --verbose
-q, --quiet
--version show program&#39;s version number and exit
@@ -1068,6 +1297,26 @@ adm:9710b86ba12c42d1d8f30f74fe509286
</ul>
<p><strong>Nomad Network Interface</strong></p>
<p>When the Nomad Network page node is enabled, work documents are viewable through the web interface. The work page lists all documents with their status, and clicking a document shows its full content and updates.</p>
</section>
<section id="cryptographic-attribution">
<h3>Cryptographic Attribution<a class="headerlink" href="#cryptographic-attribution" title="Link to this heading"></a></h3>
<p>Every work document is cryptographically signed by its creator using their Reticulum identity. When you create or edit a document, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> generates an Ed25519 signature of the content, which is stored alongside the document contents and verified by the remote node, or locally when viewing the work document through the command-line interface. This provides two essential guarantees:</p>
<ul class="simple">
<li><p><strong>Attribution:</strong> Every document and comment can be cryptographically attributed to its actual author</p></li>
<li><p><strong>Integrity:</strong> Any modification to the content after creation would invalidate the signature</p></li>
</ul>
<p>When viewing a work document, the signature validation status is displayed:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>Author : 9710b86ba12c42d1d8f30f74fe509286 (not locally validated)
Signature : Document not signed
</pre></div>
</div>
<p>Or, for valid signatures:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>Author : &lt;9710b86ba12c42d1d8f30f74fe509286&gt;
Signature : Valid
</pre></div>
</div>
<p>The “Valid” status indicates that the document content matches the authors signature, and that the signing identity corresponds to the stated author. This can be used to create tamper-proof records of project decisions, investigations, and discussions that cannot be repudiated, or modified by third parties without detection.</p>
<p>This cryptographic provenance is particularly valuable for distributed teams operating across trust boundaries. Because signatures are verified using the authors Reticulum identity public keys - which can be recalled from any transport node on the network - work documents provide authoritative records of who said what, and when, without requiring a central authority to notarize or validate the communication. Even if the repository node hosting the documents becomes unavailable, the signed document files themselves retain validity and can be verified independently using standard Reticulum identity tools.</p>
<p><strong>All Command-Line Options (rngit work)</strong></p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rngit work [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
[-i PATH] [--scope SCOPE] [-t TITLE] [-d ID] [-v]
@@ -1113,14 +1362,14 @@ options:
</div>
<svg class="furo-related-icon"><use href="#svg-arrow-right"></use></svg>
</a>
<a class="prev-page" href="networks.html">
<a class="prev-page" href="distributed.html">
<svg class="furo-related-icon"><use href="#svg-arrow-right"></use></svg>
<div class="page-info">
<div class="context">
<span>Previous</span>
</div>
<div class="title">Building Networks</div>
<div class="title">Distributed Development</div>
</div>
</a>
@@ -1179,6 +1428,13 @@ options:
<li><a class="reference internal" href="#permission-configuration-locations">Permission Configuration Locations</a></li>
</ul>
</li>
<li><a class="reference internal" href="#remote-permission-management">Remote Permission Management</a><ul>
<li><a class="reference internal" href="#managing-group-permissions">Managing Group Permissions</a></li>
<li><a class="reference internal" href="#managing-repository-permissions">Managing Repository Permissions</a></li>
<li><a class="reference internal" href="#permission-validation">Permission Validation</a></li>
</ul>
</li>
<li><a class="reference internal" href="#identity-destination-aliases">Identity &amp; Destination Aliases</a></li>
<li><a class="reference internal" href="#serving-pages-over-nomad-network">Serving Pages Over Nomad Network</a><ul>
<li><a class="reference internal" href="#enabling-the-git-page-node">Enabling the Git Page Node</a></li>
<li><a class="reference internal" href="#accessing-repository-pages">Accessing Repository Pages</a></li>
@@ -1188,6 +1444,11 @@ options:
<li><a class="reference internal" href="#configuration-example">Configuration Example</a></li>
</ul>
</li>
<li><a class="reference internal" href="#verified-releases">Verified Releases</a><ul>
<li><a class="reference internal" href="#obtaining-verified-releases">Obtaining Verified Releases</a></li>
<li><a class="reference internal" href="#creating-signed-releases">Creating Signed Releases</a></li>
</ul>
</li>
<li><a class="reference internal" href="#release-management">Release Management</a><ul>
<li><a class="reference internal" href="#the-release-workflow">The Release Workflow</a></li>
<li><a class="reference internal" href="#release-storage-structure">Release Storage &amp; Structure</a></li>
@@ -1199,6 +1460,7 @@ options:
<li><a class="reference internal" href="#proposing-work-documents">Proposing Work Documents</a></li>
<li><a class="reference internal" href="#state-management">State Management</a></li>
<li><a class="reference internal" href="#managing-work-document-permissions">Managing Work Document Permissions</a></li>
<li><a class="reference internal" href="#cryptographic-attribution">Cryptographic Attribution</a></li>
</ul>
</li>
</ul>
@@ -1212,7 +1474,7 @@ options:
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -675,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=a5753347"></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>
+31 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -523,6 +524,19 @@ to participate in the development of Reticulum itself.</p>
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a><ul>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#the-original-architecture">The Original Architecture</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#the-platform-interregnum">The Platform Interregnum</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#restoration">Restoration</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#protocols-over-platforms">Protocols Over Platforms</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#sovereignty-through-infrastructure">Sovereignty Through Infrastructure</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#artifact-centered-workflows">Artifact-Centered Workflows</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#composable-primitives">Composable Primitives</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#distribution-without-intermediaries">Distribution Without Intermediaries</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#long-archive">Long Archive</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#start-of-the-road">Start Of The Road</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a><ul>
<li class="toctree-l2"><a class="reference internal" href="git.html#the-rngit-utility">The rngit Utility</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#repository-creation-management">Repository Creation &amp; Management</a><ul>
@@ -549,6 +563,13 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-configuration-locations">Permission Configuration Locations</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#remote-permission-management">Remote Permission Management</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#managing-group-permissions">Managing Group Permissions</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#managing-repository-permissions">Managing Repository Permissions</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-validation">Permission Validation</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#identity-destination-aliases">Identity &amp; Destination Aliases</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#serving-pages-over-nomad-network">Serving Pages Over Nomad Network</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#enabling-the-git-page-node">Enabling the Git Page Node</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#accessing-repository-pages">Accessing Repository Pages</a></li>
@@ -558,6 +579,11 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l3"><a class="reference internal" href="git.html#configuration-example">Configuration Example</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#verified-releases">Verified Releases</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#obtaining-verified-releases">Obtaining Verified Releases</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#creating-signed-releases">Creating Signed Releases</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#release-management">Release Management</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#the-release-workflow">The Release Workflow</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#release-storage-structure">Release Storage &amp; Structure</a></li>
@@ -569,6 +595,7 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l3"><a class="reference internal" href="git.html#proposing-work-documents">Proposing Work Documents</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#state-management">State Management</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#managing-work-document-permissions">Managing Work Document Permissions</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#cryptographic-attribution">Cryptographic Attribution</a></li>
</ul>
</li>
</ul>
@@ -683,7 +710,7 @@ to participate in the development of Reticulum itself.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -1773,7 +1774,7 @@ interface basis under the relevant interface configuration section.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -344,7 +345,7 @@ SOFTWARE.
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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
View File
@@ -3,11 +3,11 @@
<head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html"><link rel="search" title="Search" href="search.html"><link rel="next" title="Git Over Reticulum" href="git.html"><link rel="prev" title="Configuring Interfaces" href="interfaces.html">
<link rel="index" title="Index" href="genindex.html"><link rel="search" title="Search" href="search.html"><link rel="next" title="Distributed Development" href="distributed.html"><link rel="prev" title="Configuring Interfaces" href="interfaces.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>Building Networks - Reticulum Network Stack 1.2.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -594,12 +595,12 @@ differently than a mobile device roaming between radio cells.</p>
<footer>
<div class="related-pages">
<a class="next-page" href="git.html">
<a class="next-page" href="distributed.html">
<div class="page-info">
<div class="context">
<span>Next</span>
</div>
<div class="title">Git Over Reticulum</div>
<div class="title">Distributed Development</div>
</div>
<svg class="furo-related-icon"><use href="#svg-arrow-right"></use></svg>
</a>
@@ -663,7 +664,7 @@ differently than a mobile device roaming between radio cells.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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.
+39 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -2233,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>
@@ -2471,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>
@@ -2484,7 +2519,7 @@ will announce it.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -303,7 +304,7 @@
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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
+5 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -512,7 +513,7 @@ plugin system for expandability.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -382,7 +383,7 @@ circumstances, so we rely on old-fashioned human feedback.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -1337,7 +1338,7 @@ those risks are acceptable to you.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -1635,7 +1636,7 @@ systemctl --user enable rnsd.service
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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 -4
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -504,7 +505,7 @@ network, and vice versa.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=a5753347"></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 -6
View File
@@ -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.7 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.7 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.7 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">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -398,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 elses megaphone and start building your own. The network is no longer something that happens to you; it is something you make happen.</p>
</section>
@@ -676,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=a5753347"></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>
+130
View File
@@ -0,0 +1,130 @@
# Distributed Development
This chapter of the manual provides the conceptual basis for understanding *why* `rngit` exists, what it aims to achieve, and the kinds of spaces it seeks to reestablish. For the practical details of operating the system, refer to the [Git Over Reticulum](git.md#git-main) chapter.
## The Original Architecture
When Torvalds created Git in 2005, he designed a tool that reflected a specific philosophy of collaboration. Every copy of a repository would be a complete, sovereign instance. There was no central server, no single point of failure, no gatekeeper. Developers would be able to work independently, exchange patches directly, and maintain their own branches indefinitely. This concept was - and is - both beautiful and revolutionary. Its execution is peer-to-peer not as a marketing term, but in the most foundational sense: As fundamental, structural reality.
Such a design emerged from necessity. The Linux kernel development process operated across geographical boundaries, time zones, and organizational affiliations. Contributors did not “log in” to a shared server to do their work; they maintained their own trees, and the flow of code between these trees was negotiated through patches, reviews, and merge decisions. The architecture of Git mirrored the social architecture of the community: Autonomous, competent, and fundamentally distributed in its technical operation.
*The result of that work is, in the most direct sense, what makes it possible for you to read this today.*
Theres something very important to take note of here: With Git, developers could collaborate effectively and perfectly well without any central server being present, without platform-mediated visibility into each others work, and without a centralized authority validating their contributions. They needed *only* a protocol for exchanging differences and a mechanism for verification of authorship. Everything else - social organization, quality control, release management - was handled by careful *human judgment* operating on top of the technical substrate.
What Git provided was not a development environment, but a **language for versioning**. It specified how to represent history, how to compute differences, how to merge divergent branches. It did not specify who could participate, how they should communicate, or what workflows they should follow. These were left to the competence and discretion of the creators using the system.
## The Platform Interregnum
What followed represents a very familiar pattern: Tools designed to distribute power were re-centralized by platforms that offered convenience in exchange for control. GitHub, GitLab, and similar services reintroduced the centralization that Git had eliminated architecturally. The activity feed replaced durable artifacts with ephemeral notifications. The social graph and open interaction became as important as the code itself, if not more.
This re-centralization was not technical, as such. It was **ontological**. When every developer pushes to the same server, when every merge is in theory controllable by a platform, when every issue is tracked in a database controlled by a corporation, the nature of collaboration changes. The platform, and its social dynamics, becomes the ground of reality. The platform mediates not just the technical exchange of information and the programmatics, but the social recognition and codices of contribution, the future archival prospects of the work, and the very identity of the project itself.
The consequences extend beyond individual inconvenience. Centralized platforms create single points of failure for entire ecosystem. When a platform changes its terms of service, suspends accounts, removes repositories or ceases operation, entire project histories and community relationships can be disrupted or destroyed. The extractive economics of platform capitalism mean that value created by open-source communities is captured by corporations, while communities remain dependent on infrastructure they do not control. And the surveillance inherent in platform operation means that every action - every commit, every comment, every page view - is logged, analyzed, and potentially monetized or weaponized.
More insidiously, platforms have completely reshaped the culture of development itself. They have created what we could call the **Teahouse Developer**: A participant who treats engineering projects as social venues for opinion-sharing rather than sites of disciplined and careful production. These personages have no actual stakes in the projects they act as leeches upon, and only a very base consciousness of the damage they are incurring in order to feed their attention and external validation dependencies.
When platforms optimize for engagement, when growth is the only metric, when every user with an opinion must have their voice heard, when a random social process is elevated to higher importance than results, the signal-to-noise ratio collapses catastrophically. Competent engineers find themselves drowning in feedback from the incompetent, managing the emotional needs and dysregulations of drive-by commentators rather than solving technical problems.
The platform model is predicated on **unsaturable expansion**. Like almost any industrial system, it cannot function without growth. It pursues no particular aims; it is growth for the sake of growing. There is no saturation point, no concept of “enough”. Every barrier to entry must be put down to the very lowest common denominator, every voice must be amplified, every interaction must be converted into content that feeds the machine. This is fundamentally incompatible with the nature of social beings itself. It is also incompatible with serious engineering, which requires focus, discernment, and the right of people who know better to say “no”.
## Restoration
The `rngit` system represents a return to Gits original architectural principles, fortified with cryptographic networking capabilities that were not available in 2005. The `rngit` system *is* Git - but running over Reticulum. Welcome back to a world where your work is your own, but where everyone can still reach you - if you want them to.
Just as Git eliminated the need for a central version control server, `rngit` eliminates the need for a central hosting platform, “servers” or any kinds of middle-men between the people actually doing the work. By operating over Reticulum, it eliminates the visibility of development activity to platform operators, network observers, state actors and other malicious third-parties.
In this model, the repository node is a **sovereign entity**. It is reachable from anywhere in the Reticulum network but owned, operated, and controlled by the developer or community that runs it. It is an actual home for creative output, not an extraction mechanism to which dues are paid. The node operator decides who may contribute, what standards must be met, and which voices are worth listening to. This is not exclusion; it is **discernment**. It is the necessary exercise of judgment that separates engineering from theatrics.
I did not create this in a fit of nostalgia. I created it because it is a necessary response to the failures of the centralized model. Gits technical architecture was - and *is* - correct. It was the social and economic superstructure built atop it that introduced fragility, exploitation, and environments toxic to actual creativity. By returning to first principles - distributed version control on distributed infrastructure - we recover not just a technical capability, but a mode of collaboration that respects the autonomy of individual developers and the sovereignty of actual communities.
## Protocols Over Platforms
The distinction between platforms and protocols is fundamental to understanding the architecture of sovereignty in networked systems. A platform is a service you access; a protocol is a grammar you speak; actions you live. A platform requires permission to enter, a protocol requires only *comprehension* to employ. A platform can change its rules, suspend your account, or cease operation entirely, a protocol persists as long as there are participants who *understand* and *use* it. A protocol is an *idea*, a platform is a machine that turns its users into products.
Platforms operate on a client-server model that inherently creates power asymmetry. Even when platforms are built atop open-source software, the operational instance remains a black box of corporate control. You *may* be able to download *some* of your data, but you cannot download the connections to the people that are the true value-base of the platform, or take them with you if you want to leave.
Protocols, by contrast, are agreements. They specify how systems should communicate, but not who may communicate or on what terms. Email is a protocol; Gmail is a platform. HTTP is a protocol; Facebook is a platform. Git is a protocol; GitHub is a platform. The protocol persists regardless of any particular implementations success or failure.
The power of protocols lies in their **permissionlessness**. Anyone can implement a protocol without approval. Anyone can extend it, fork it, or use it for purposes unforeseen by its creators. This creates resilience: protocols cannot be easily censored, monopolized, or shut down because they exist as shared understanding rather than centralized infrastructure.
Reticulum is a protocol in this strict sense. It specifies how packets should be formatted, how paths should be discovered, how encryption should be applied. The `rngit` system extends this protocol approach to development workflows. It is not an external platform that hosts your repositories; it is a protocol for exchanging repository data, release artifacts, and work documents over Reticulums encrypted transport. But with a few commands and an old computer, it creates your own infrastructure for hosting repositories, or sharing them with who you choose. *That* is how tools should function, in case we had forgotten.
Unlike platforms, which extract value by creating dependency, there is no entity that can grant or deny you the privilege of running `rngit`. Your Reticulum identity is not endowed by any platform; it is generated locally and certified by its own cryptographic properties. Your repositories are hosted on nodes you control or nodes operated by communities you trust. Your relationships with other developers are peer-to-peer connections established through cryptographic addressing, not social graph connections managed by recommendation algorithms.
On a platform, exit means abandonment: you lose your history, your relationships, your visibility. With protocols, exit is just migration. When you change your infrastructure, your identity and your work travel with you. There are no middlemen between you and your collaborators. If push comes to shove, you can write your entire lifes work and connections to an SD card, swim across the lake, and set up camp on the other side.
## Sovereignty Through Infrastructure
The concept of sovereignty - supreme authority within a territory - has traditionally been applied to nation-states. But in an age where creative work is conducted through digital infrastructure, sovereignty is essential for individuals and communities. **Creative sovereignty** means having supreme authority over the artifacts you produce, the processes by which you produce them, and the terms under which they are distributed. It means not merely legal ownership of copyright, but operational control of the infrastructure that mediates creation, collaboration, and dissemination.
Centralized development platforms strip away most layers of sovereignty. When you host code on a corporate platform, you retain *some* legal ownership of copyright, but you surrender complete operational control. The platform decides what content is acceptable, who can access it, and how it is presented. They can delete your repository, suspend your account, or change the visibility of your work without consent. In reality, legal ownership becomes meaningless as operational control is ceded.
Running your own `rngit` node restores this sovereignty. You control the hardware, the network configuration, the backup strategies, and the access permissions. You decide what constitutes acceptable use, who may contribute, and how contributions are evaluated. Taking this responsibility on yourself is an assertion that your creative work is not a product to be harvested by platform economics, but an autonomous activity to be conducted on your own terms.
This sovereignty and responsibility extends to the entry barriers you establish. The `rngit` system allows you to configure access controls that filter participants based on cryptographic identity and demonstrated competence. If, for example, someone cannot navigate a command line, or use Reticulum to submit a patch, they most likely lack the required competence to modify your code. In a world that apparently labels this as “exclusion”, I would simply refer to it as a minimally acceptable level of quality control.
Such a stance protects projects from the noise that so often overwhelms and completely dilutes platform-based development, where every user with an opinion believes themselves entitled to attention and access to the decision process.
## Artifact-Centered Workflows
Contemporary platform-based development has shifted focus from durable artifacts to ephemeral *activity*. It does not matter what constitutes this activity, as long as its there. The primary interface is not the repository itself, not the produced artifacts, but the activity feed: *Notifications* of commits, comments, pull requests, and social interactions. Work is measured by velocity, throughput, and the constant stream of updates. This activity-centric model creates constant urgency, discourages discernment, encourages reactive rather than reflective work patterns, and produces vast quantities of ephemeral and useless communication that obscures actual project state and productivity.
The `rngit` system enables a return to **artifact-centered workflows**, where the focus is on durable, attributable, versioned outputs rather than the stream of notifications surrounding them. The fundamental unit of work is the commit - signed, immutable records of change. The fundamental unit of production is the signed artifact - a self-verifying package of functionality. The fundamental unit of discussion is the work document - a structured, threaded conversation attached to repositories.
Artifacts can persist independently of any platforms continued operation. A commit signed with your Reticulum identity is attributable to you regardless of where it is stored. A release signed with your private key is verifiable as authentic regardless of which network it traverses, and can be verified offline on any system running Reticulum. The work exists as **cryptographic fact**, distributed over the planet, not as database entries in a corporate cloud.
Such a shift has real psychological consequences. When work is measured in artifacts rather than activity, the pace changes. There is no need for constant visibility, no pressure to perform busyness. Developers can work deeply, reflectively, and submit complete solutions rather than incremental updates designed to maintain presence in an activity feed. The work becomes **substantial**, in the physical sense of the word, rather than performative.
## Composable Primitives
The `rngit` system is not a monolithic application prescribing a specific workflow; it is a collection of **composable primitives** that can be arranged to support diverse creative processes. Understanding these primitives as separate, orthogonal capabilities enables users to construct workflows suited to their specific needs and to recombine these primitives in ways unforeseen by the systems designers.
The core primitives include:
* **Repository Hosting**: Bare Git repositories served over Reticulum links, accessible via standard Git commands through the `rns://` URL scheme.
* **Identity-Based Access Control**: Fine-grained permissions managed through cryptographically verifiable identity hashes, configurable at the group, repository, or document level.
* **Release Distribution**: Cryptographically signed release artifacts with embedded provenance information, verifiable offline and distributable through any Reticulum or physical path.
* **Work Document Tracking**: Structured, threaded work management attached to repositories, with precise permission controls, and the ability to contain updates or discussions.
* **Forking and Mirroring**: Automated replication of repositories from any accessible Git URL, with metadata tracking upstream relationships for synchronization.
* **Nomad Network Integration**: Page node functionality for browsing repository contents, commit history, and release information through the Nomad Network protocol.
These primitives can be composed into workflows ranging from single-developer projects to complex multi-organizational collaborations. A solo developer might use only repository hosting and release distribution. A research group might add work document tracking for structured peer review. A software distribution network might combine mirroring with cryptographic release verification to create resilient update channels.
The entire system is incredibly light-weight, and can host hundreds of repositories on a Raspberry Pi.
Composability is essential because **creative work is diverse**. Software development, academic research, technical writing, hardware design, music production and data analysis all have different requirements for collaboration, review, and distribution. A platform prescribes a single workflow and forces all users to conform. A protocol provides primitives and allows users to construct workflows appropriate to their domain.
With `rngit`, you can re-build the system into anything you can imagine. Everything can be modified, extended and hooked into. Adding functionality or automation is never further away than a shell script, a cron-job, or a Python modification of the source.
## Distribution Without Intermediaries
Creating software is only part of the work. Then comes actually getting it to the people needing to use it. Centralized platforms handle distribution through their own infrastructure: Content delivery networks, central package registries, and download servers accessed through platform-controlled interfaces. This convenience masks a fundamental dependency: Your ability to distribute depends on the platforms continued operation, their policies regarding your content, and their technical infrastructures reach.
The `rngit` release system enables distribution strategies **decoupled from any single infrastructure provider**. Releases are cryptographically signed using Ed25519 signatures and packaged in signed release manifests (`.rsm` files). These manifests contain embedded signatures for each artifact. The manifest provides full verifiability of all release information, and contains embedded release artifact lists, per-file `.rsg` signatures, origin information, and the creators Reticulum Identity. It can also be used to fetch verified updates of the software package over the network, and can always be verified completely offline.
Because releases are self-verifying, they can traverse any network or physical path that Reticulum can establish. A release can travel over LoRa radio, be carried on USB drives through areas without internet connectivity, disseminated over a mirror network, or be distributed through store-and-forward mechanisms on intermittent infrastructure. Recipients can verify authenticity regardless of how they obtained the files. This is particularly valuable in low-connectivity environments where Reticulum may be the only available communication channel.
The `rngit release` command provides tools for creating, publishing, fetching, and verifying releases. When fetching a release using an `.rsm` manifest, the system validates the manifest signature against the required Reticulum Identity, extracts the origin node and repository path, connects to the origin over Reticulum, retrieves the latest release manifest, and verifies each downloaded artifact against the signatures embedded in the manifest. If any verification fails, the fetch aborts, preventing installation of corrupted or tampered files.
This cryptographic verification replaces the trust model of platform distribution. Instead of trusting that a platform has not been compromised, users verify that artifacts match the signatures created by the developers identity. It doesnt matter *how* they obtained the artifacts, they can **always** be verified. This security model shifts from **institutional trust** (just believe in the goodness of the platform) to **cryptographic proof** (verify the signatures).
## Long Archive
Software development is often conceived as an activity of the present only: Solving todays problems, meeting current deadlines, responding to immediate feedback. But the artifacts produced - code, documentation, releases - have lifespans extending *far* beyond their creation. They may be used for decades, studied by future developers, depended upon by systems not yet imagined, or preserved as historical records of technological development.
The `rngit` system is designed with this **extended timeframe** in mind, supporting the creation of archives that are durable, portable, and intelligible across generational timescales. Git repositories are always internally complete; they contain full history and can be migrated to new infrastructure without loss of information. Everything that `rngit` adds on top of this is stored in normal files in standard formats right next to the Git repository folders, not an esoteric database-cluster two thousand kilometers away. Because releases are cryptographically signed, they remain verifiable as authentic regardless of when or where they are retrieved. Because the system operates over Reticulum, it can function over communication mediums that may outlast the internet as we know it.
This long-term perspective influences technical decisions. The use of well-established cryptographic primitives ensures that signatures will remain verifiable for centuries. The use of standard formats ensures that repositories will remain readable by future tools. The protocol-based architecture ensures that the system can evolve without losing compatibility with existing data.
For critical infrastructure, this archival durability is not optional; it is essential. Communication systems, cryptographic libraries, and safety-critical code must remain available and verifiable for the lifespans of the systems that depend on them. The `rngit` system provides the tools to create such archives: distributed across multiple nodes, cryptographically verified, and independent of any corporate or governmental infrastructure, which as history has shown repeatedly, does *not* persist.
## Start Of The Road
Distributed development and production over Reticulum is a *different mode of existence* for creative work. It restores the autonomy originally created by Git. It provides local sovereignty over production infrastructure, composability of workflow, and durability of artifact. It lets you filter participation through competence and cryptography rather than incentives of platform operators, raising the quality and enjoyment of work, and protecting the focus of real engineering and creative expression.
This is not a system for everyone, and that is the point. It requires investment - in understanding Reticulum, in configuring infrastructure, in establishing workflows. It requires accepting responsibility for your own tools rather than delegating them to platform operators. It requires the discipline to maintain your own node, manage your own backups, and nurture your own community.
But for those who make this investment, the returns are substantial. You gain **immunity from platform failure**; your work persists regardless of corporate decisions or service outages. You gain **shelter from surveillance**; your development activity is visible only to those that *you* choose to involve. You gain **control over process**; you decide how work is conducted, reviewed, and released, unmediated by terms of service, algorithmic feeds and thousands of uninformed and irrelevant opinions.
Most importantly, though, you regain the **dignity of craft**. Development becomes an activity conducted among peers, equals among equals, mediated by skill and cryptographic proof rather than corporate permission, producing artifacts that stand as independent testimony to competence, functionality or beauty rather than as content feeding engagement metrics. The *work* becomes the point. The artifacts become durable. And the network becomes *one* of the tools you wield in this endeavor.
+338 -18
View File
@@ -1,12 +1,14 @@
# Git Over Reticulum
This chapter of the manual serves as the technical reference for the distributed software development and project collaboration tools included in RNS. For a conceptual overview, see the [Distributed Development](distributed.md#distributed-development) chapter.
A set of utilities for distributed collaborative software development and publishing are included in RNS.
The system consists of two parts: The `rngit` node that hosts repositories, and the `git-remote-rns` helper that enables Git to communicate with rngit nodes. As soon as you have RNS installed on your system, you can transparently use Git with Reticulum-hosted repositories just like any other type of remote. Git over Reticulum uses URLs in the following format: `rns://DESTINATION_HASH/group/repo`.
If you set a branch to track a Reticulum remote as the default upstream, you can simply use `git` as you normally would; all commands work transparently and as expected.
#### WARNING
#### IMPORTANT
**The rngit program is a new addition to RNS!** This functionality was introduced in RNS 1.2.0. While great care has been taken to design a secure, but highly configurable and flexible [permission system](#permissions) for allowing many users to interact with many different repositories on a single node, `rngit` has not been tested extensively in the wild! Be careful when hosting repositories, especially if they are public or semi-public.
## The rngit Utility
@@ -158,10 +160,8 @@ Repository forked to public/myfork
The source can be any valid Git URL, including:
- HTTPS URLs: `https://github.com/user/repo.git`
- Git URLs: `git://host.com/repo.git`
- SSH URLs: `ssh://git@host.com/repo.git`
- Reticulum URLs: `rns://DESTINATION_HASH/group/repo`
- Local paths: `/path/to/repo.git`
Forks are created as bare repositories with metadata tracking their origin. The fork process:
@@ -325,6 +325,8 @@ Access permissions can be configured at the group level in the config file or pe
By default, **no** permissions are granted for anything! You will have to enable the permissions you require to be able to actually *do* something with `rngit`.
Permissions can be modified by editing the `rngit` config file, individual `.allowed` files on disk, or remotely using the `rngit perms` command.
### Permission Types
The following permissions are supported:
@@ -478,6 +480,80 @@ Targets can also use short forms:
- Repository permissions: `<group_root>/<group_name>/<repo_name>.allowed`
- Document permissions: `<group_root>/<group_name>.work/<doc_id>.allowed`
## Remote Permission Management
While permissions can be configured directly on the node by editing configuration files and `.allowed` files, `rngit` also supports remote permission management through the `rngit perms` command. This allows administrators to modify access controls for groups and repositories over Reticulum, without requiring shell access to the hosting node.
To use remote permission management, you must have `admin` permission on the target group or repository. The command opens your configured `$EDITOR` to modify permissions, using the same syntax and format as local `.allowed` files. When you save and exit the editor, the modified permissions are transmitted to the remote node and applied immediately.
### Managing Group Permissions
To view or modify permissions for an entire repository group, specify the group URL (ending with the group name):
```text
$ rngit perms rns://50824b711717f97c2fb1166ceddd5ea9/public
```
This retrieves the current permission configuration from the `public.allowed` file and opens it in your editor. Any changes you make are validated for syntax correctness. Invalid permission rules will be rejected with an error message indicating the problematic line.
### Managing Repository Permissions
To manage permissions for a specific repository, include the repository name in the URL:
```text
$ rngit perms rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo
```
This operates on the `myrepo.allowed` file next to the repository. Repository-level permissions take precedence over group-level permissions, allowing fine-grained access control for individual repositories within a group.
### Permission Validation
When modifying permissions remotely, `rngit` validates that:
- Each permission line follows the correct `permission:target` syntax
- Permission types are valid (r, w, rw, c, s, rel, i, p, adm)
- Target specifications are valid (identity hashes, `all`, or `none`)
- Identity hashes, when specified, are the correct length (32 hexadecimal characters)
If validation fails, the editor will reopen with an error message describing the issue, allowing you to correct the problem before resubmitting.
**All Command-Line Options (rngit perms)**
```text
usage: rngit perms [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
[-i PATH] [-v] [-q] [--version]
remote
Reticulum Git Permission Manager
positional arguments:
remote URL of remote group or repository
options:
-h, --help show this help message and exit
--config CONFIG path to alternative config directory
--rnsconfig RNSCONFIG
path to alternative Reticulum config directory
-i, --identity PATH path to identity
-v, --verbose
-q, --quiet
--version show program's version number and exit
```
## Identity & Destination Aliases
To make permission and remote destination management easier, you can locally define aliases for commonly used identity and destination hashes. Identity aliases used in permissions resolution can be defined in the `[aliases]` section of the `~/.rngit/config` file, while destination aliases are defined in the `[aliases]` section of the `~/.rngit/client_config` file.
All alias definitions take the form of `aliased_name = HASH`:
```text
[aliases]
alice = d09285e660cfe27cee6d9a0beb58b7e0
bob = ffcffb4e255e156e77f79b82c13086a6
```
**Aliases are always resolved locally!** If for example you fork a repository with `rngit fork rns://bobs_node/public/repo_name rns://my_node/forks/repo_name`, the forked repository will of course still reference the full, original destination hash, and use this for subsequent upstream syncs.
## Serving Pages Over Nomad Network
In addition to providing Git repository access via the Git remote helper protocol and command-line tools, `rngit` can also run a [Nomad Network](https://github.com/markqvist/nomadnet) compatible page node. This allows users to browse repository information, view file contents, inspect commit history and access repository statistics through any Nomad Network client.
@@ -543,6 +619,8 @@ def hello_world():
```
```
You can use `rawmu` code blocks to render raw Micron inside Markdown files. If you create a code block with the language hint `rawmu`, everything inside it will be treated as Micron directly.
### Customizing Templates
The page node uses a template system that allows complete customization of the generated pages. Templates are stored in the `~/.rngit/templates/` directory as Micron files.
@@ -621,6 +699,198 @@ A complete node configuration might look like this:
unicode_icons = no
```
## Verified Releases
The `rngit` release system provides cryptographic provenance and integrity guarantees through automatic signing of release artifacts and signed release manifests. When you create a release, `rngit` generates an Ed25519 signature for each artifact and embeds these signatures in a cryptographically signed release manifest (`.rsm` file). This allows anyone who obtains the release to verify its authenticity and integrity, regardless of how the files were distributed.
### Obtaining Verified Releases
The `rngit` system lets you obtain releases securely and in a verified manner, by validating cryptographically signed release manifests in the `.rsm` format during the retrieval process. Once a release has been published with `rngit`, anyone that has read access to it can obtain the release with the `rngit release` command, for example:
```text
$ rngit release rns://remote_node/group/some_program fetch latest:all
```
This command will connect to the remote, retrieve the latest release manifest, verify its signature and integrity (you can optionally specify a required signer identity with `--signer`), and then download and sequentially verify all artifacts included in the release.
If verification succeeds, the retrieved artifact files, along with the release manifest will be saved in the current working directory. From the above example, you would end up with a number of downloaded files, and a version- and package specific release manifest, such as `some_program_1.5.2.rsm`.
#### IMPORTANT
Keeping the retrieved release manifest is a **very** good idea! It allows you to easily obtain future releases and updates to the software directly, while verifying they came from the same publisher.
**Obtaining & Updating Releases Using RSM Manifests**
One of the key features of the `rngit` release system is the ability to fetch and verify new releases using only a signed release manifest. This is particularly valuable for distributing software over Reticulum. Once someone has an `.rsm` manifest of your package, they can use it to continually retrieve and update the software.
To fetch a release using a manifest:
```text
$ rngit release some_program_1.5.2.rsm fetch latest:all
```
This command:
1. Validates the manifest signature to confirm authenticity
2. Extracts the origin node and repository path from the signed manifest
3. Connects to the origin node over Reticulum
4. Gets the *latest* release manifest from the developer
5. Verifies it against the existing manifest
6. Fetches each artifact listed in the manifest
7. Verifies each downloaded file against the signature embedded in the manifest
If any artifact fails signature verification, the fetch aborts with an error, preventing the installation of corrupted or tampered files.
**Specifying Required Signers**
You can require that releases be signed by specific identities. When fetching a release, use the `--signer` option to specify the identity hash of the required signer:
```text
$ rngit release rns://remote_node/public/myrepo fetch latest:all --signer 21a8daa6d9c3d3b8aab6e94b6bcb0e33
```
If the release was not signed by the specified identity, the fetch will abort before any files are downloaded. Likewise, if any downloaded artifacts were not signed by the required identity, the process will abort at the first invalid signature. This provides strong guarantees about the provenance of the software you are installing.
The signer check also works when fetching from a local manifest:
```text
$ rngit release manifest.rsm fetch latest:all --signer 21a8daa6d9c3d3b8aab6e94b6bcb0e33
```
**Selective & Partial Fetches**
You can fetch individual artifacts from a release by specifying the artifact name instead of `all`:
```text
$ 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.
**For individual files:**
Ensure the `.rsg` signature is located in the same directory as the release artifact, then run:
```text
$ rnid -V myapp-1.2.0.tar.gz
```
This validates that the artifact file matches the signature created during the release process. Combined with the manifests own signature, this provides end-to-end verification from the original release creation to the final installation.
**For a complete release:**
Ensure the release manifest is located in the same directory as the release artifacts, then run:
```text
$ rngit release myapp-1.2.0.rsm --offline
```
This will load the manifest, and verify all files currently on-disk, but will not attempt to fetch the latest release manifest from the origin, or update local files to match it.
### Creating Signed Releases
Reticulum and the `rngit` system makes it easy to create signed releases that your users can verify and update securely. When you create a release using `rngit`, the program automatically:
1. Generates an Ed25519 signature for each artifact file using your identitys signing key
2. Creates `.rsg` signature files alongside each artifact in your distribution directory
3. Constructs a signed release manifest (`manifest.rsm`) containing metadata, an artifact list, and embedded signatures
4. Transmits both artifacts, signatures and manifest to the remote node specified as release origin
As an example, to create and publish a release from all files in the folder named `dist`, simply run:
```text
$ rngit release rns://my_node/group/myrepo create 1.2.0:./dist
```
Everything is automatically signed and uploaded to your node, and the release manifest will now include the following signed attestation information:
- Package name and version
- The release notes for this release
- Release timestamp and commit hash
- Origin node identity and repository path
- Complete list of artifacts
- Embedded signatures for each artifact
Thats it, theres nothing more to it than one command. Users can now securely obtain your release using `rngit release fetch`.
**Release Manifest Format**
Release manifests use the `.rsm` format (a general-purpose, structured signed message format) and are themselves cryptographically signed documents. The manifest format embeds the signing identitys public key and a detached signature that covers the entire manifest content. This creates a chain of trust: the manifest signature proves the manifests authenticity, and the embedded artifact signatures prove each files integrity.
When a release is created, the manifest is stored as `manifest.rsm` in the release artifacts directory. You can also generate a local release manifest without uploading by using the `--local` flag:
```text
$ rngit release rns://f2d31b2e080e5d4e358d32822ee4a3b7/public/myrepo create 1.2.0:./dist --local
```
This creates the `.rsg` signature files and `manifest.rsm` in your local distribution directory without connecting to the remote node, allowing you to inspect or distribute the signed release through alternative channels.
**Signature File Format**
Individual artifact signatures use the Reticulum Signature (`.rsg`) format and contain:
- The Ed25519 signature of the file
- The signing identitys public key
- Optional metadata, such as timestamps or notes
These signature files are created automatically during the release process and can be used independently of the manifest for verification purposes. The `rnid` utility can create and validate RSG signatures for any file, making this signature format useful beyond the `rngit` release system.
**Good Practices for Signature Distribution**
While release manifests in the `.rsm` format *include* embedded `.rsg` signatures for every listed artifact, it is dependent on the situation and requirements whether individual `.rsg` signatures are distributed as well. It is generally a good idea to do so, since they are very light-weight, and provide an easy and convenient way to validate and authenticate *individual* files, as opposed to entire releases.
When distributing software through multiple channels (direct download, mirror networks, physical media), including the `.rsm` manifest allows recipients to verify authenticity regardless of how they obtained the files. This is particularly valuable in low-connectivity environments where Reticulum may be the only available communication channel, as the manifest ensures that software updates can be verified even when received via store-and-forward mechanisms or physical media transport.
**Integration with Package Management**
While this functionality is still under development, the signed release manifest format is designed to be consumed by package management systems and automated deployment tools. Because the manifest is cryptographically signed and contains all necessary metadata and integrity checks, it can serve as a trusted source of truth for software distribution, even when fetched over untrusted channels or stored for long periods.
**Release Encryption**
While API primitives and command-line tools are currently not implemented for this, the release, distribution and verification system has been designed to also support *encrypted* releases, which can be distributed securely to authorized recipients.
**Verified Package Format**
The current system is being expanded to also include an `.rvp` package format, which can contain packaged releases including all relevant artifacts, metadata, manifest and signatures.
**Automated Mirror Discovery**
The `rngit` release system is designed to support automated mirror discovery and distribution package retrieval over Reticulum networks. Since everything is cryptographically signed and verified, it is possible to create automated mirror and distribution networks, where users can obtain software and information from local sources, without risking malicious modifications to the software they rely on. This functionality is currently in development.
## Release Management
In addition to hosting Git repositories, `rngit` provides a complete release management system. This allows you to publish versioned releases with associated artifacts, release notes and metadata. Releases are managed through the `rngit release` subcommand, and are also viewable through the Nomad Network page interface.
@@ -632,12 +902,12 @@ Creating a release involves specifying a Git tag and a directory containing buil
To create a release, specify the tag name and path to artifacts:
```text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo create v1.2.0:./dist
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo create 1.2.0:./dist
```
This will:
1. Verify that the tag `v1.2.0` exists in the repository
1. Verify that the tag `1.2.0` exists in the repository
2. Open your editor to write release notes
3. Upload all files from the `./dist` directory
4. Publish the release
@@ -664,9 +934,9 @@ $ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo list
Tag Status Created Objs Notes
------------------------------------------------------------------
v1.2.0 published 2025-01-15 14:32 3 Another release
v1.1.0 published 2024-12-03 09:15 2 Bug fix release
v1.0.0 published 2024-10-20 16:45 2 Initial release
1.2.0 published 2025-01-15 14:32 3 Another release
1.1.0 published 2024-12-03 09:15 2 Bug fix release
1.0.0 published 2024-10-20 16:45 2 Initial release
```
**Viewing Release Details**
@@ -674,9 +944,9 @@ v1.0.0 published 2024-10-20 16:45 2 Initial release
To see full information about a specific release:
```text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo view v1.2.0
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo view 1.2.0
Release : 0.9.2
Release : 1.2.0
Status : published
Created : 2026-05-04 23:53:09
Thanks : 5
@@ -693,15 +963,35 @@ Artifacts (4)
- checksums.txt (256 B)
```
**Fetching Releases**
To fetch a release, specify the remote URL, version and artifacts:
```text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo fetch latest:all
```
This process is described in greater detail in the [Obtaining Verified Releases](#git-release-obtain) section.
**Creating Releases**
To fetch a release, specify the remote URL, version and artifacts:
```text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo create 1.3.9:artifacts_dir
```
This process is described in greater detail in the [Creating Signed Releases](#git-release-create) section.
**Deleting Releases**
To remove a release:
```text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo delete v1.2.0
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo delete 1.2.0
Are you sure you want to delete release 'v1.2.0'? [y/N]: y
Release v1.2.0 deleted
Are you sure you want to delete release '1.2.0'? [y/N]: y
Release 1.2.0 deleted
```
**Requirements & Validation**
@@ -729,15 +1019,16 @@ When the Nomad Network page node is enabled, releases are displayed on a dedicat
**All Command-Line Options (rngit release)**
```text
usage: rngit release [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
[-i PATH] [-v] [-q] [--version]
[repository] [operation] [target]
usage: python -m RNS.Utilities.rngit.server [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
[-i PATH] [-s PATH] [-n name] [-L]
[-o] [-v] [-q] [--version]
[repository] [operation] [target]
Reticulum Git Release Manager
positional arguments:
repository URL of remote repository
operation list, view, create or delete
repository URL of remote repository, or path to RSM manifest
operation list, view, fetch, create, latest or delete
target tag and path to release artifacts directory
options:
@@ -746,6 +1037,10 @@ options:
--rnsconfig RNSCONFIG
path to alternative Reticulum config directory
-i, --identity PATH path to release identity
-s, --signer PATH path to signing identity, if different from release identity
-n, --name name package name if different from repo name
-L, --local generate release locally, but don't upload
-o, --offline verify manifest locally, but don't fetch updates
-v, --verbose
-q, --quiet
--version show program's version number and exit
@@ -950,6 +1245,31 @@ Each document is a numbered directory containing:
When the Nomad Network page node is enabled, work documents are viewable through the web interface. The work page lists all documents with their status, and clicking a document shows its full content and updates.
### Cryptographic Attribution
Every work document is cryptographically signed by its creator using their Reticulum identity. When you create or edit a document, `rngit` generates an Ed25519 signature of the content, which is stored alongside the document contents and verified by the remote node, or locally when viewing the work document through the command-line interface. This provides two essential guarantees:
- **Attribution:** Every document and comment can be cryptographically attributed to its actual author
- **Integrity:** Any modification to the content after creation would invalidate the signature
When viewing a work document, the signature validation status is displayed:
```text
Author : 9710b86ba12c42d1d8f30f74fe509286 (not locally validated)
Signature : Document not signed
```
Or, for valid signatures:
```text
Author : <9710b86ba12c42d1d8f30f74fe509286>
Signature : Valid
```
The “Valid” status indicates that the document content matches the authors signature, and that the signing identity corresponds to the stated author. This can be used to create tamper-proof records of project decisions, investigations, and discussions that cannot be repudiated, or modified by third parties without detection.
This cryptographic provenance is particularly valuable for distributed teams operating across trust boundaries. Because signatures are verified using the authors Reticulum identity public keys - which can be recalled from any transport node on the network - work documents provide authoritative records of who said what, and when, without requiring a central authority to notarize or validate the communication. Even if the repository node hosting the documents becomes unavailable, the signed document files themselves retain validity and can be verified independently using standard Reticulum identity tools.
**All Command-Line Options (rngit work)**
```text
+20
View File
@@ -189,6 +189,17 @@ to participate in the development of Reticulum itself.
* [Transport Nodes and Instances](networks.md#transport-nodes-and-instances)
* [Trustless Networking](networks.md#trustless-networking)
* [Heterogeneous Connectivity](networks.md#heterogeneous-connectivity)
* [Distributed Development](distributed.md)
* [The Original Architecture](distributed.md#the-original-architecture)
* [The Platform Interregnum](distributed.md#the-platform-interregnum)
* [Restoration](distributed.md#restoration)
* [Protocols Over Platforms](distributed.md#protocols-over-platforms)
* [Sovereignty Through Infrastructure](distributed.md#sovereignty-through-infrastructure)
* [Artifact-Centered Workflows](distributed.md#artifact-centered-workflows)
* [Composable Primitives](distributed.md#composable-primitives)
* [Distribution Without Intermediaries](distributed.md#distribution-without-intermediaries)
* [Long Archive](distributed.md#long-archive)
* [Start Of The Road](distributed.md#start-of-the-road)
* [Git Over Reticulum](git.md)
* [The rngit Utility](git.md#the-rngit-utility)
* [Repository Creation & Management](git.md#repository-creation-management)
@@ -209,6 +220,11 @@ to participate in the development of Reticulum itself.
* [Permission Examples](git.md#permission-examples)
* [Permission Short Forms](git.md#permission-short-forms)
* [Permission Configuration Locations](git.md#permission-configuration-locations)
* [Remote Permission Management](git.md#remote-permission-management)
* [Managing Group Permissions](git.md#managing-group-permissions)
* [Managing Repository Permissions](git.md#managing-repository-permissions)
* [Permission Validation](git.md#permission-validation)
* [Identity & Destination Aliases](git.md#identity-destination-aliases)
* [Serving Pages Over Nomad Network](git.md#serving-pages-over-nomad-network)
* [Enabling the Git Page Node](git.md#enabling-the-git-page-node)
* [Accessing Repository Pages](git.md#accessing-repository-pages)
@@ -216,6 +232,9 @@ to participate in the development of Reticulum itself.
* [Customizing Templates](git.md#customizing-templates)
* [Repository Statistics](git.md#repository-statistics)
* [Configuration Example](git.md#configuration-example)
* [Verified Releases](git.md#verified-releases)
* [Obtaining Verified Releases](git.md#obtaining-verified-releases)
* [Creating Signed Releases](git.md#creating-signed-releases)
* [Release Management](git.md#release-management)
* [The Release Workflow](git.md#the-release-workflow)
* [Release Storage & Structure](git.md#release-storage-structure)
@@ -225,6 +244,7 @@ to participate in the development of Reticulum itself.
* [Proposing Work Documents](git.md#proposing-work-documents)
* [State Management](git.md#state-management)
* [Managing Work Document Permissions](git.md#managing-work-document-permissions)
* [Cryptographic Attribution](git.md#cryptographic-attribution)
* [Support Reticulum](support.md)
* [Donations](support.md#donations)
* [Provide Feedback](support.md#provide-feedback)
+21 -1
View File
@@ -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*.
+2 -2
View File
@@ -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 elses megaphone and start building your own. The network is no longer something that happens to you; it is something you make happen.
+148
View File
@@ -0,0 +1,148 @@
.. _distributed-development:
***********************
Distributed Development
***********************
This chapter of the manual provides the conceptual basis for understanding *why* ``rngit`` exists, what it aims to achieve, and the kinds of spaces it seeks to reestablish. For the practical details of operating the system, refer to the :ref:`Git Over Reticulum<git-main>` chapter.
The Original Architecture
=========================
When Torvalds created Git in 2005, he designed a tool that reflected a specific philosophy of collaboration. Every copy of a repository would be a complete, sovereign instance. There was no central server, no single point of failure, no gatekeeper. Developers would be able to work independently, exchange patches directly, and maintain their own branches indefinitely. This concept was - and is - both beautiful and revolutionary. It's execution is peer-to-peer not as a marketing term, but in the most foundational sense: As fundamental, structural reality.
Such a design emerged from necessity. The Linux kernel development process operated across geographical boundaries, time zones, and organizational affiliations. Contributors did not "log in" to a shared server to do their work; they maintained their own trees, and the flow of code between these trees was negotiated through patches, reviews, and merge decisions. The architecture of Git mirrored the social architecture of the community: Autonomous, competent, and fundamentally distributed in its technical operation.
*The result of that work is, in the most direct sense, what makes it possible for you to read this today.*
There's something very important to take note of here: With Git, developers could collaborate effectively and perfectly well without any central server being present, without platform-mediated visibility into each other's work, and without a centralized authority validating their contributions. They needed *only* a protocol for exchanging differences and a mechanism for verification of authorship. Everything else - social organization, quality control, release management - was handled by careful *human judgment* operating on top of the technical substrate.
What Git provided was not a development environment, but a **language for versioning**. It specified how to represent history, how to compute differences, how to merge divergent branches. It did not specify who could participate, how they should communicate, or what workflows they should follow. These were left to the competence and discretion of the creators using the system.
The Platform Interregnum
========================
What followed represents a very familiar pattern: Tools designed to distribute power were re-centralized by platforms that offered convenience in exchange for control. GitHub, GitLab, and similar services reintroduced the centralization that Git had eliminated architecturally. The activity feed replaced durable artifacts with ephemeral notifications. The social graph and open interaction became as important as the code itself, if not more.
This re-centralization was not technical, as such. It was **ontological**. When every developer pushes to the same server, when every merge is in theory controllable by a platform, when every issue is tracked in a database controlled by a corporation, the nature of collaboration changes. The platform, and its social dynamics, becomes the ground of reality. The platform mediates not just the technical exchange of information and the programmatics, but the social recognition and codices of contribution, the future archival prospects of the work, and the very identity of the project itself.
The consequences extend beyond individual inconvenience. Centralized platforms create single points of failure for entire ecosystem. When a platform changes its terms of service, suspends accounts, removes repositories or ceases operation, entire project histories and community relationships can be disrupted or destroyed. The extractive economics of platform capitalism mean that value created by open-source communities is captured by corporations, while communities remain dependent on infrastructure they do not control. And the surveillance inherent in platform operation means that every action - every commit, every comment, every page view - is logged, analyzed, and potentially monetized or weaponized.
More insidiously, platforms have completely reshaped the culture of development itself. They have created what we could call the **Teahouse Developer**: A participant who treats engineering projects as social venues for opinion-sharing rather than sites of disciplined and careful production. These personages have no actual stakes in the projects they act as leeches upon, and only a very base consciousness of the damage they are incurring in order to feed their attention and external validation dependencies.
When platforms optimize for engagement, when growth is the only metric, when every user with an opinion must have their voice heard, when a random social process is elevated to higher importance than results, the signal-to-noise ratio collapses catastrophically. Competent engineers find themselves drowning in feedback from the incompetent, managing the emotional needs and dysregulations of drive-by commentators rather than solving technical problems.
The platform model is predicated on **unsaturable expansion**. Like almost any industrial system, it cannot function without growth. It pursues no particular aims; it is growth for the sake of growing. There is no saturation point, no concept of "enough". Every barrier to entry must be put down to the very lowest common denominator, every voice must be amplified, every interaction must be converted into content that feeds the machine. This is fundamentally incompatible with the nature of social beings itself. It is also incompatible with serious engineering, which requires focus, discernment, and the right of people who know better to say "no".
Restoration
===========
The ``rngit`` system represents a return to Git's original architectural principles, fortified with cryptographic networking capabilities that were not available in 2005. The ``rngit`` system *is* Git - but running over Reticulum. Welcome back to a world where your work is your own, but where everyone can still reach you - if you want them to.
Just as Git eliminated the need for a central version control server, ``rngit`` eliminates the need for a central hosting platform, "servers" or any kinds of middle-men between the people actually doing the work. By operating over Reticulum, it eliminates the visibility of development activity to platform operators, network observers, state actors and other malicious third-parties.
In this model, the repository node is a **sovereign entity**. It is reachable from anywhere in the Reticulum network but owned, operated, and controlled by the developer or community that runs it. It is an actual home for creative output, not an extraction mechanism to which dues are paid. The node operator decides who may contribute, what standards must be met, and which voices are worth listening to. This is not exclusion; it is **discernment**. It is the necessary exercise of judgment that separates engineering from theatrics.
I did not create this in a fit of nostalgia. I created it because it is a necessary response to the failures of the centralized model. Git's technical architecture was - and *is* - correct. It was the social and economic superstructure built atop it that introduced fragility, exploitation, and environments toxic to actual creativity. By returning to first principles - distributed version control on distributed infrastructure - we recover not just a technical capability, but a mode of collaboration that respects the autonomy of individual developers and the sovereignty of actual communities.
Protocols Over Platforms
========================
The distinction between platforms and protocols is fundamental to understanding the architecture of sovereignty in networked systems. A platform is a service you access; a protocol is a grammar you speak; actions you live. A platform requires permission to enter, a protocol requires only *comprehension* to employ. A platform can change its rules, suspend your account, or cease operation entirely, a protocol persists as long as there are participants who *understand* and *use* it. A protocol is an *idea*, a platform is a machine that turns its users into products.
Platforms operate on a client-server model that inherently creates power asymmetry. Even when platforms are built atop open-source software, the operational instance remains a black box of corporate control. You *may* be able to download *some* of your data, but you cannot download the connections to the people that are the true value-base of the platform, or take them with you if you want to leave.
Protocols, by contrast, are agreements. They specify how systems should communicate, but not who may communicate or on what terms. Email is a protocol; Gmail is a platform. HTTP is a protocol; Facebook is a platform. Git is a protocol; GitHub is a platform. The protocol persists regardless of any particular implementation's success or failure.
The power of protocols lies in their **permissionlessness**. Anyone can implement a protocol without approval. Anyone can extend it, fork it, or use it for purposes unforeseen by its creators. This creates resilience: protocols cannot be easily censored, monopolized, or shut down because they exist as shared understanding rather than centralized infrastructure.
Reticulum is a protocol in this strict sense. It specifies how packets should be formatted, how paths should be discovered, how encryption should be applied. The ``rngit`` system extends this protocol approach to development workflows. It is not an external platform that hosts your repositories; it is a protocol for exchanging repository data, release artifacts, and work documents over Reticulum's encrypted transport. But with a few commands and an old computer, it creates your own infrastructure for hosting repositories, or sharing them with who you choose. *That* is how tools should function, in case we had forgotten.
Unlike platforms, which extract value by creating dependency, there is no entity that can grant or deny you the privilege of running ``rngit``. Your Reticulum identity is not endowed by any platform; it is generated locally and certified by its own cryptographic properties. Your repositories are hosted on nodes you control or nodes operated by communities you trust. Your relationships with other developers are peer-to-peer connections established through cryptographic addressing, not social graph connections managed by recommendation algorithms.
On a platform, exit means abandonment: you lose your history, your relationships, your visibility. With protocols, exit is just migration. When you change your infrastructure, your identity and your work travel with you. There are no middlemen between you and your collaborators. If push comes to shove, you can write your entire life's work and connections to an SD card, swim across the lake, and set up camp on the other side.
Sovereignty Through Infrastructure
==================================
The concept of sovereignty - supreme authority within a territory - has traditionally been applied to nation-states. But in an age where creative work is conducted through digital infrastructure, sovereignty is essential for individuals and communities. **Creative sovereignty** means having supreme authority over the artifacts you produce, the processes by which you produce them, and the terms under which they are distributed. It means not merely legal ownership of copyright, but operational control of the infrastructure that mediates creation, collaboration, and dissemination.
Centralized development platforms strip away most layers of sovereignty. When you host code on a corporate platform, you retain *some* legal ownership of copyright, but you surrender complete operational control. The platform decides what content is acceptable, who can access it, and how it is presented. They can delete your repository, suspend your account, or change the visibility of your work without consent. In reality, legal ownership becomes meaningless as operational control is ceded.
Running your own ``rngit`` node restores this sovereignty. You control the hardware, the network configuration, the backup strategies, and the access permissions. You decide what constitutes acceptable use, who may contribute, and how contributions are evaluated. Taking this responsibility on yourself is an assertion that your creative work is not a product to be harvested by platform economics, but an autonomous activity to be conducted on your own terms.
This sovereignty and responsibility extends to the entry barriers you establish. The ``rngit`` system allows you to configure access controls that filter participants based on cryptographic identity and demonstrated competence. If, for example, someone cannot navigate a command line, or use Reticulum to submit a patch, they most likely lack the required competence to modify your code. In a world that apparently labels this as "exclusion", I would simply refer to it as a minimally acceptable level of quality control.
Such a stance protects projects from the noise that so often overwhelms and completely dilutes platform-based development, where every user with an opinion believes themselves entitled to attention and access to the decision process.
Artifact-Centered Workflows
===========================
Contemporary platform-based development has shifted focus from durable artifacts to ephemeral *activity*. It does not matter what constitutes this activity, as long as it's there. The primary interface is not the repository itself, not the produced artifacts, but the activity feed: *Notifications* of commits, comments, pull requests, and social interactions. Work is measured by velocity, throughput, and the constant stream of updates. This activity-centric model creates constant urgency, discourages discernment, encourages reactive rather than reflective work patterns, and produces vast quantities of ephemeral and useless communication that obscures actual project state and productivity.
The ``rngit`` system enables a return to **artifact-centered workflows**, where the focus is on durable, attributable, versioned outputs rather than the stream of notifications surrounding them. The fundamental unit of work is the commit - signed, immutable records of change. The fundamental unit of production is the signed artifact - a self-verifying package of functionality. The fundamental unit of discussion is the work document - a structured, threaded conversation attached to repositories.
Artifacts can persist independently of any platform's continued operation. A commit signed with your Reticulum identity is attributable to you regardless of where it is stored. A release signed with your private key is verifiable as authentic regardless of which network it traverses, and can be verified offline on any system running Reticulum. The work exists as **cryptographic fact**, distributed over the planet, not as database entries in a corporate cloud.
Such a shift has real psychological consequences. When work is measured in artifacts rather than activity, the pace changes. There is no need for constant visibility, no pressure to perform busyness. Developers can work deeply, reflectively, and submit complete solutions rather than incremental updates designed to maintain presence in an activity feed. The work becomes **substantial**, in the physical sense of the word, rather than performative.
Composable Primitives
=====================
The ``rngit`` system is not a monolithic application prescribing a specific workflow; it is a collection of **composable primitives** that can be arranged to support diverse creative processes. Understanding these primitives as separate, orthogonal capabilities enables users to construct workflows suited to their specific needs and to recombine these primitives in ways unforeseen by the system's designers.
The core primitives include:
* **Repository Hosting**: Bare Git repositories served over Reticulum links, accessible via standard Git commands through the ``rns://`` URL scheme.
* **Identity-Based Access Control**: Fine-grained permissions managed through cryptographically verifiable identity hashes, configurable at the group, repository, or document level.
* **Release Distribution**: Cryptographically signed release artifacts with embedded provenance information, verifiable offline and distributable through any Reticulum or physical path.
* **Work Document Tracking**: Structured, threaded work management attached to repositories, with precise permission controls, and the ability to contain updates or discussions.
* **Forking and Mirroring**: Automated replication of repositories from any accessible Git URL, with metadata tracking upstream relationships for synchronization.
* **Nomad Network Integration**: Page node functionality for browsing repository contents, commit history, and release information through the Nomad Network protocol.
These primitives can be composed into workflows ranging from single-developer projects to complex multi-organizational collaborations. A solo developer might use only repository hosting and release distribution. A research group might add work document tracking for structured peer review. A software distribution network might combine mirroring with cryptographic release verification to create resilient update channels.
The entire system is incredibly light-weight, and can host hundreds of repositories on a Raspberry Pi.
Composability is essential because **creative work is diverse**. Software development, academic research, technical writing, hardware design, music production and data analysis all have different requirements for collaboration, review, and distribution. A platform prescribes a single workflow and forces all users to conform. A protocol provides primitives and allows users to construct workflows appropriate to their domain.
With ``rngit``, you can re-build the system into anything you can imagine. Everything can be modified, extended and hooked into. Adding functionality or automation is never further away than a shell script, a cron-job, or a Python modification of the source.
Distribution Without Intermediaries
===================================
Creating software is only part of the work. Then comes actually getting it to the people needing to use it. Centralized platforms handle distribution through their own infrastructure: Content delivery networks, central package registries, and download servers accessed through platform-controlled interfaces. This convenience masks a fundamental dependency: Your ability to distribute depends on the platform's continued operation, their policies regarding your content, and their technical infrastructure's reach.
The ``rngit`` release system enables distribution strategies **decoupled from any single infrastructure provider**. Releases are cryptographically signed using Ed25519 signatures and packaged in signed release manifests (``.rsm`` files). These manifests contain embedded signatures for each artifact. The manifest provides full verifiability of all release information, and contains embedded release artifact lists, per-file ``.rsg`` signatures, origin information, and the creator's Reticulum Identity. It can also be used to fetch verified updates of the software package over the network, and can always be verified completely offline.
Because releases are self-verifying, they can traverse any network or physical path that Reticulum can establish. A release can travel over LoRa radio, be carried on USB drives through areas without internet connectivity, disseminated over a mirror network, or be distributed through store-and-forward mechanisms on intermittent infrastructure. Recipients can verify authenticity regardless of how they obtained the files. This is particularly valuable in low-connectivity environments where Reticulum may be the only available communication channel.
The ``rngit release`` command provides tools for creating, publishing, fetching, and verifying releases. When fetching a release using an ``.rsm`` manifest, the system validates the manifest signature against the required Reticulum Identity, extracts the origin node and repository path, connects to the origin over Reticulum, retrieves the latest release manifest, and verifies each downloaded artifact against the signatures embedded in the manifest. If any verification fails, the fetch aborts, preventing installation of corrupted or tampered files.
This cryptographic verification replaces the trust model of platform distribution. Instead of trusting that a platform has not been compromised, users verify that artifacts match the signatures created by the developer's identity. It doesn't matter *how* they obtained the artifacts, they can **always** be verified. This security model shifts from **institutional trust** (just believe in the goodness of the platform) to **cryptographic proof** (verify the signatures).
Long Archive
============
Software development is often conceived as an activity of the present only: Solving today's problems, meeting current deadlines, responding to immediate feedback. But the artifacts produced - code, documentation, releases - have lifespans extending *far* beyond their creation. They may be used for decades, studied by future developers, depended upon by systems not yet imagined, or preserved as historical records of technological development.
The ``rngit`` system is designed with this **extended timeframe** in mind, supporting the creation of archives that are durable, portable, and intelligible across generational timescales. Git repositories are always internally complete; they contain full history and can be migrated to new infrastructure without loss of information. Everything that ``rngit`` adds on top of this is stored in normal files in standard formats right next to the Git repository folders, not an esoteric database-cluster two thousand kilometers away. Because releases are cryptographically signed, they remain verifiable as authentic regardless of when or where they are retrieved. Because the system operates over Reticulum, it can function over communication mediums that may outlast the internet as we know it.
This long-term perspective influences technical decisions. The use of well-established cryptographic primitives ensures that signatures will remain verifiable for centuries. The use of standard formats ensures that repositories will remain readable by future tools. The protocol-based architecture ensures that the system can evolve without losing compatibility with existing data.
For critical infrastructure, this archival durability is not optional; it is essential. Communication systems, cryptographic libraries, and safety-critical code must remain available and verifiable for the lifespans of the systems that depend on them. The ``rngit`` system provides the tools to create such archives: distributed across multiple nodes, cryptographically verified, and independent of any corporate or governmental infrastructure, which as history has shown repeatedly, does *not* persist.
Start Of The Road
=================
Distributed development and production over Reticulum is a *different mode of existence* for creative work. It restores the autonomy originally created by Git. It provides local sovereignty over production infrastructure, composability of workflow, and durability of artifact. It lets you filter participation through competence and cryptography rather than incentives of platform operators, raising the quality and enjoyment of work, and protecting the focus of real engineering and creative expression.
This is not a system for everyone, and that is the point. It requires investment - in understanding Reticulum, in configuring infrastructure, in establishing workflows. It requires accepting responsibility for your own tools rather than delegating them to platform operators. It requires the discipline to maintain your own node, manage your own backups, and nurture your own community.
But for those who make this investment, the returns are substantial. You gain **immunity from platform failure**; your work persists regardless of corporate decisions or service outages. You gain **shelter from surveillance**; your development activity is visible only to those that *you* choose to involve. You gain **control over process**; you decide how work is conducted, reviewed, and released, unmediated by terms of service, algorithmic feeds and thousands of uninformed and irrelevant opinions.
Most importantly, though, you regain the **dignity of craft**. Development becomes an activity conducted among peers, equals among equals, mediated by skill and cryptographic proof rather than corporate permission, producing artifacts that stand as independent testimony to competence, functionality or beauty rather than as content feeding engagement metrics. The *work* becomes the point. The artifacts become durable. And the network becomes *one* of the tools you wield in this endeavor.
+363 -20
View File
@@ -4,13 +4,15 @@
Git Over Reticulum
******************
This chapter of the manual serves as the technical reference for the distributed software development and project collaboration tools included in RNS. For a conceptual overview, see the :ref:`Distributed Development<distributed-development>` chapter.
A set of utilities for distributed collaborative software development and publishing are included in RNS.
The system consists of two parts: The ``rngit`` node that hosts repositories, and the ``git-remote-rns`` helper that enables Git to communicate with rngit nodes. As soon as you have RNS installed on your system, you can transparently use Git with Reticulum-hosted repositories just like any other type of remote. Git over Reticulum uses URLs in the following format: ``rns://DESTINATION_HASH/group/repo``.
If you set a branch to track a Reticulum remote as the default upstream, you can simply use ``git`` as you normally would; all commands work transparently and as expected.
.. warning::
.. important::
**The rngit program is a new addition to RNS!** This functionality was introduced in RNS 1.2.0. While great care has been taken to design a secure, but highly configurable and flexible `permission system`_ for allowing many users to interact with many different repositories on a single node, ``rngit`` has not been tested extensively in the wild! Be careful when hosting repositories, especially if they are public or semi-public.
.. _permission system: #permissions
@@ -170,10 +172,8 @@ To fork a repository:
The source can be any valid Git URL, including:
- HTTPS URLs: ``https://github.com/user/repo.git``
- Git URLs: ``git://host.com/repo.git``
- SSH URLs: ``ssh://git@host.com/repo.git``
- Reticulum URLs: ``rns://DESTINATION_HASH/group/repo``
- Local paths: ``/path/to/repo.git``
Forks are created as bare repositories with metadata tracking their origin. The fork process:
@@ -321,8 +321,6 @@ These parameters are used by the sync system and can be queried using standard G
1716230400
Repository Structure
====================
@@ -348,6 +346,8 @@ Access permissions can be configured at the group level in the config file or pe
By default, **no** permissions are granted for anything! You will have to enable the permissions you require to be able to actually *do* something with ``rngit``.
Permissions can be modified by editing the ``rngit`` config file, individual ``.allowed`` files on disk, or remotely using the ``rngit perms`` command.
Permission Types
----------------
@@ -509,6 +509,90 @@ Permission Configuration Locations
- Repository permissions: ``<group_root>/<group_name>/<repo_name>.allowed``
- Document permissions: ``<group_root>/<group_name>.work/<doc_id>.allowed``
Remote Permission Management
============================
While permissions can be configured directly on the node by editing configuration files and ``.allowed`` files, ``rngit`` also supports remote permission management through the ``rngit perms`` command. This allows administrators to modify access controls for groups and repositories over Reticulum, without requiring shell access to the hosting node.
To use remote permission management, you must have ``admin`` permission on the target group or repository. The command opens your configured ``$EDITOR`` to modify permissions, using the same syntax and format as local ``.allowed`` files. When you save and exit the editor, the modified permissions are transmitted to the remote node and applied immediately.
Managing Group Permissions
--------------------------
To view or modify permissions for an entire repository group, specify the group URL (ending with the group name):
.. code:: text
$ rngit perms rns://50824b711717f97c2fb1166ceddd5ea9/public
This retrieves the current permission configuration from the ``public.allowed`` file and opens it in your editor. Any changes you make are validated for syntax correctness. Invalid permission rules will be rejected with an error message indicating the problematic line.
Managing Repository Permissions
-------------------------------
To manage permissions for a specific repository, include the repository name in the URL:
.. code:: text
$ rngit perms rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo
This operates on the ``myrepo.allowed`` file next to the repository. Repository-level permissions take precedence over group-level permissions, allowing fine-grained access control for individual repositories within a group.
Permission Validation
---------------------
When modifying permissions remotely, ``rngit`` validates that:
- Each permission line follows the correct ``permission:target`` syntax
- Permission types are valid (r, w, rw, c, s, rel, i, p, adm)
- Target specifications are valid (identity hashes, ``all``, or ``none``)
- Identity hashes, when specified, are the correct length (32 hexadecimal characters)
If validation fails, the editor will reopen with an error message describing the issue, allowing you to correct the problem before resubmitting.
.. caution::
Remote permission modification requires administrative access (the ``adm`` permission), which grants full control over the repository or group. The permission change request is transmitted over the encrypted Reticulum link, and the remote node verifies your identity cryptographically before applying changes. However, be aware that granting ``adm`` permissions to remote identities effectively delegates full control, including the ability to revoke your own access or modify permissions in ways you may not anticipate.
**All Command-Line Options (rngit perms)**
.. code:: text
usage: rngit perms [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
[-i PATH] [-v] [-q] [--version]
remote
Reticulum Git Permission Manager
positional arguments:
remote URL of remote group or repository
options:
-h, --help show this help message and exit
--config CONFIG path to alternative config directory
--rnsconfig RNSCONFIG
path to alternative Reticulum config directory
-i, --identity PATH path to identity
-v, --verbose
-q, --quiet
--version show program's version number and exit
Identity & Destination Aliases
==============================
To make permission and remote destination management easier, you can locally define aliases for commonly used identity and destination hashes. Identity aliases used in permissions resolution can be defined in the ``[aliases]`` section of the ``~/.rngit/config`` file, while destination aliases are defined in the ``[aliases]`` section of the ``~/.rngit/client_config`` file.
All alias definitions take the form of ``aliased_name = HASH``:
.. code:: text
[aliases]
alice = d09285e660cfe27cee6d9a0beb58b7e0
bob = ffcffb4e255e156e77f79b82c13086a6
**Aliases are always resolved locally!** If for example you fork a repository with ``rngit fork rns://bobs_node/public/repo_name rns://my_node/forks/repo_name``, the forked repository will of course still reference the full, original destination hash, and use this for subsequent upstream syncs.
Serving Pages Over Nomad Network
================================
@@ -578,6 +662,8 @@ Code blocks in Markdown can include language hints for syntax highlighting:
print("Hello, Reticulum!")
```
You can use ``rawmu`` code blocks to render raw Micron inside Markdown files. If you create a code block with the language hint ``rawmu``, everything inside it will be treated as Micron directly.
Customizing Templates
---------------------
@@ -660,6 +746,211 @@ A complete node configuration might look like this:
unicode_icons = no
Verified Releases
=================
The ``rngit`` release system provides cryptographic provenance and integrity guarantees through automatic signing of release artifacts and signed release manifests. When you create a release, ``rngit`` generates an Ed25519 signature for each artifact and embeds these signatures in a cryptographically signed release manifest (``.rsm`` file). This allows anyone who obtains the release to verify its authenticity and integrity, regardless of how the files were distributed.
.. _git-release-obtain:
Obtaining Verified Releases
---------------------------
The ``rngit`` system lets you obtain releases securely and in a verified manner, by validating cryptographically signed release manifests in the ``.rsm`` format during the retrieval process. Once a release has been published with ``rngit``, anyone that has read access to it can obtain the release with the ``rngit release`` command, for example:
.. code:: text
$ rngit release rns://remote_node/group/some_program fetch latest:all
This command will connect to the remote, retrieve the latest release manifest, verify it's signature and integrity (you can optionally specify a required signer identity with ``--signer``), and then download and sequentially verify all artifacts included in the release.
If verification succeeds, the retrieved artifact files, along with the release manifest will be saved in the current working directory. From the above example, you would end up with a number of downloaded files, and a version- and package specific release manifest, such as ``some_program_1.5.2.rsm``.
.. important::
Keeping the retrieved release manifest is a **very** good idea! It allows you to easily obtain future releases and updates to the software directly, while verifying they came from the same publisher.
**Obtaining & Updating Releases Using RSM Manifests**
One of the key features of the ``rngit`` release system is the ability to fetch and verify new releases using only a signed release manifest. This is particularly valuable for distributing software over Reticulum. Once someone has an ``.rsm`` manifest of your package, they can use it to continually retrieve and update the software.
To fetch a release using a manifest:
.. code:: text
$ rngit release some_program_1.5.2.rsm fetch latest:all
This command:
1. Validates the manifest signature to confirm authenticity
2. Extracts the origin node and repository path from the signed manifest
3. Connects to the origin node over Reticulum
4. Gets the *latest* release manifest from the developer
5. Verifies it against the existing manifest
6. Fetches each artifact listed in the manifest
7. Verifies each downloaded file against the signature embedded in the manifest
If any artifact fails signature verification, the fetch aborts with an error, preventing the installation of corrupted or tampered files.
**Specifying Required Signers**
You can require that releases be signed by specific identities. When fetching a release, use the ``--signer`` option to specify the identity hash of the required signer:
.. code:: text
$ rngit release rns://remote_node/public/myrepo fetch latest:all --signer 21a8daa6d9c3d3b8aab6e94b6bcb0e33
If the release was not signed by the specified identity, the fetch will abort before any files are downloaded. Likewise, if any downloaded artifacts were not signed by the required identity, the process will abort at the first invalid signature. This provides strong guarantees about the provenance of the software you are installing.
The signer check also works when fetching from a local manifest:
.. code:: text
$ rngit release manifest.rsm fetch latest:all --signer 21a8daa6d9c3d3b8aab6e94b6bcb0e33
**Selective & Partial Fetches**
You can fetch individual artifacts from a release by specifying the artifact name instead of ``all``:
.. code:: text
$ 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.
.. 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.
**For individual files:**
Ensure the ``.rsg`` signature is located in the same directory as the release artifact, then run:
.. code:: text
$ rnid -V myapp-1.2.0.tar.gz
This validates that the artifact file matches the signature created during the release process. Combined with the manifest's own signature, this provides end-to-end verification from the original release creation to the final installation.
**For a complete release:**
Ensure the release manifest is located in the same directory as the release artifacts, then run:
.. code:: text
$ rngit release myapp-1.2.0.rsm --offline
This will load the manifest, and verify all files currently on-disk, but will not attempt to fetch the latest release manifest from the origin, or update local files to match it.
.. _git-release-create:
Creating Signed Releases
------------------------
Reticulum and the ``rngit`` system makes it easy to create signed releases that your users can verify and update securely. When you create a release using ``rngit``, the program automatically:
1. Generates an Ed25519 signature for each artifact file using your identity's signing key
2. Creates ``.rsg`` signature files alongside each artifact in your distribution directory
3. Constructs a signed release manifest (``manifest.rsm``) containing metadata, an artifact list, and embedded signatures
4. Transmits both artifacts, signatures and manifest to the remote node specified as release origin
As an example, to create and publish a release from all files in the folder named ``dist``, simply run:
.. code:: text
$ rngit release rns://my_node/group/myrepo create 1.2.0:./dist
Everything is automatically signed and uploaded to your node, and the release manifest will now include the following signed attestation information:
- Package name and version
- The release notes for this release
- Release timestamp and commit hash
- Origin node identity and repository path
- Complete list of artifacts
- Embedded signatures for each artifact
That's it, there's nothing more to it than one command. Users can now securely obtain your release using ``rngit release fetch``.
**Release Manifest Format**
Release manifests use the ``.rsm`` format (a general-purpose, structured signed message format) and are themselves cryptographically signed documents. The manifest format embeds the signing identity's public key and a detached signature that covers the entire manifest content. This creates a chain of trust: the manifest signature proves the manifest's authenticity, and the embedded artifact signatures prove each file's integrity.
When a release is created, the manifest is stored as ``manifest.rsm`` in the release artifacts directory. You can also generate a local release manifest without uploading by using the ``--local`` flag:
.. code:: text
$ rngit release rns://f2d31b2e080e5d4e358d32822ee4a3b7/public/myrepo create 1.2.0:./dist --local
This creates the ``.rsg`` signature files and ``manifest.rsm`` in your local distribution directory without connecting to the remote node, allowing you to inspect or distribute the signed release through alternative channels.
**Signature File Format**
Individual artifact signatures use the Reticulum Signature (``.rsg``) format and contain:
- The Ed25519 signature of the file
- The signing identity's public key
- Optional metadata, such as timestamps or notes
These signature files are created automatically during the release process and can be used independently of the manifest for verification purposes. The ``rnid`` utility can create and validate RSG signatures for any file, making this signature format useful beyond the ``rngit`` release system.
**Good Practices for Signature Distribution**
While release manifests in the ``.rsm`` format *include* embedded ``.rsg`` signatures for every listed artifact, it is dependent on the situation and requirements whether individual ``.rsg`` signatures are distributed as well. It is generally a good idea to do so, since they are very light-weight, and provide an easy and convenient way to validate and authenticate *individual* files, as opposed to entire releases.
When distributing software through multiple channels (direct download, mirror networks, physical media), including the ``.rsm`` manifest allows recipients to verify authenticity regardless of how they obtained the files. This is particularly valuable in low-connectivity environments where Reticulum may be the only available communication channel, as the manifest ensures that software updates can be verified even when received via store-and-forward mechanisms or physical media transport.
**Integration with Package Management**
While this functionality is still under development, the signed release manifest format is designed to be consumed by package management systems and automated deployment tools. Because the manifest is cryptographically signed and contains all necessary metadata and integrity checks, it can serve as a trusted source of truth for software distribution, even when fetched over untrusted channels or stored for long periods.
**Release Encryption**
While API primitives and command-line tools are currently not implemented for this, the release, distribution and verification system has been designed to also support *encrypted* releases, which can be distributed securely to authorized recipients.
**Verified Package Format**
The current system is being expanded to also include an ``.rvp`` package format, which can contain packaged releases including all relevant artifacts, metadata, manifest and signatures.
**Automated Mirror Discovery**
The ``rngit`` release system is designed to support automated mirror discovery and distribution package retrieval over Reticulum networks. Since everything is cryptographically signed and verified, it is possible to create automated mirror and distribution networks, where users can obtain software and information from local sources, without risking malicious modifications to the software they rely on. This functionality is currently in development.
Release Management
==================
@@ -674,11 +965,11 @@ To create a release, specify the tag name and path to artifacts:
.. code:: text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo create v1.2.0:./dist
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo create 1.2.0:./dist
This will:
1. Verify that the tag ``v1.2.0`` exists in the repository
1. Verify that the tag ``1.2.0`` exists in the repository
2. Open your editor to write release notes
3. Upload all files from the ``./dist`` directory
4. Publish the release
@@ -709,9 +1000,9 @@ To view all releases for a repository:
Tag Status Created Objs Notes
------------------------------------------------------------------
v1.2.0 published 2025-01-15 14:32 3 Another release
v1.1.0 published 2024-12-03 09:15 2 Bug fix release
v1.0.0 published 2024-10-20 16:45 2 Initial release
1.2.0 published 2025-01-15 14:32 3 Another release
1.1.0 published 2024-12-03 09:15 2 Bug fix release
1.0.0 published 2024-10-20 16:45 2 Initial release
**Viewing Release Details**
@@ -719,9 +1010,9 @@ To see full information about a specific release:
.. code:: text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo view v1.2.0
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo view 1.2.0
Release : 0.9.2
Release : 1.2.0
Status : published
Created : 2026-05-04 23:53:09
Thanks : 5
@@ -737,16 +1028,37 @@ To see full information about a specific release:
- myapp-1.2.0.zip (1.6 MB)
- checksums.txt (256 B)
**Fetching Releases**
To fetch a release, specify the remote URL, version and artifacts:
.. code:: text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo fetch latest:all
This process is described in greater detail in the :ref:`Obtaining Verified Releases<git-release-obtain>` section.
**Creating Releases**
To fetch a release, specify the remote URL, version and artifacts:
.. code:: text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo create 1.3.9:artifacts_dir
This process is described in greater detail in the :ref:`Creating Signed Releases<git-release-create>` section.
**Deleting Releases**
To remove a release:
.. code:: text
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo delete v1.2.0
$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo delete 1.2.0
Are you sure you want to delete release 'v1.2.0'? [y/N]: y
Release v1.2.0 deleted
Are you sure you want to delete release '1.2.0'? [y/N]: y
Release 1.2.0 deleted
**Requirements & Validation**
@@ -774,15 +1086,16 @@ When the Nomad Network page node is enabled, releases are displayed on a dedicat
.. code:: text
usage: rngit release [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
[-i PATH] [-v] [-q] [--version]
[repository] [operation] [target]
usage: python -m RNS.Utilities.rngit.server [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
[-i PATH] [-s PATH] [-n name] [-L]
[-o] [-v] [-q] [--version]
[repository] [operation] [target]
Reticulum Git Release Manager
positional arguments:
repository URL of remote repository
operation list, view, create or delete
repository URL of remote repository, or path to RSM manifest
operation list, view, fetch, create, latest or delete
target tag and path to release artifacts directory
options:
@@ -791,6 +1104,10 @@ When the Nomad Network page node is enabled, releases are displayed on a dedicat
--rnsconfig RNSCONFIG
path to alternative Reticulum config directory
-i, --identity PATH path to release identity
-s, --signer PATH path to signing identity, if different from release identity
-n, --name name package name if different from repo name
-L, --local generate release locally, but don't upload
-o, --offline verify manifest locally, but don't fetch updates
-v, --verbose
-q, --quiet
--version show program's version number and exit
@@ -1003,6 +1320,32 @@ Each document is a numbered directory containing:
When the Nomad Network page node is enabled, work documents are viewable through the web interface. The work page lists all documents with their status, and clicking a document shows its full content and updates.
Cryptographic Attribution
-------------------------
Every work document is cryptographically signed by its creator using their Reticulum identity. When you create or edit a document, ``rngit`` generates an Ed25519 signature of the content, which is stored alongside the document contents and verified by the remote node, or locally when viewing the work document through the command-line interface. This provides two essential guarantees:
- **Attribution:** Every document and comment can be cryptographically attributed to its actual author
- **Integrity:** Any modification to the content after creation would invalidate the signature
When viewing a work document, the signature validation status is displayed:
.. code:: text
Author : 9710b86ba12c42d1d8f30f74fe509286 (not locally validated)
Signature : Document not signed
Or, for valid signatures:
.. code:: text
Author : <9710b86ba12c42d1d8f30f74fe509286>
Signature : Valid
The "Valid" status indicates that the document content matches the author's signature, and that the signing identity corresponds to the stated author. This can be used to create tamper-proof records of project decisions, investigations, and discussions that cannot be repudiated, or modified by third parties without detection.
This cryptographic provenance is particularly valuable for distributed teams operating across trust boundaries. Because signatures are verified using the author's Reticulum identity public keys - which can be recalled from any transport node on the network - work documents provide authoritative records of who said what, and when, without requiring a central authority to notarize or validate the communication. Even if the repository node hosting the documents becomes unavailable, the signed document files themselves retain validity and can be verified independently using standard Reticulum identity tools.
**All Command-Line Options (rngit work)**
.. code:: text
+1
View File
@@ -27,6 +27,7 @@ to participate in the development of Reticulum itself.
hardware
interfaces
networks
distributed
git
support
examples
+2 -2
View File
@@ -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.
+4
View File
@@ -13,6 +13,10 @@ exec(open("RNS/_version.py", "r").read())
with open("README.md", "r") as fh:
long_description = fh.read()
if "--getversion" in sys.argv:
print(__version__, end="")
exit(0)
if pure_python:
pkg_name = "rnspure"
requirements = []
+42 -1
View File
@@ -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")