Compare commits

..

117 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
Mark Qvist b1f522277c Prepare release 2026-05-17 00:56:08 +02:00
Mark Qvist af6e0c9ecf Updated changelog 2026-05-17 00:47:15 +02:00
Mark Qvist 176567e3f1 Updated version 2026-05-17 00:39:15 +02:00
Mark Qvist 15cd4268ac Cleanup 2026-05-17 00:38:51 +02:00
Mark Qvist 9307db16c4 Allow disabling mirroring interval 2026-05-17 00:17:12 +02:00
Mark Qvist 0f29ab629a Updated rngit documentation 2026-05-17 00:07:16 +02:00
Mark Qvist b2a4ceb853 Updated default config 2026-05-16 23:37:45 +02:00
Mark Qvist 6c7f1d068b Implemented fork and mirror sync from upstreams 2026-05-16 23:02:45 +02:00
Mark Qvist b76beb602d Added scaffolding for periodic upstream mirror sync and manual fork/mirror sync 2026-05-16 22:06:16 +02:00
Mark Qvist 0c68f6491a Added fork and mirror indications to rngit page node 2026-05-16 21:14:02 +02:00
Mark Qvist 038981474a Added fork and mirroring support to rngit CLI and node 2026-05-16 20:21:01 +02:00
Mark Qvist df0b4a5165 Implemented rngit remote repo create 2026-05-16 17:35:00 +02:00
Mark Qvist db7359f56d Preparation for create, fork and mirror functionality. Refactored and expanded permissions system. Added group .allowed files. Prepared dynamic permissions resolution. Basic functional scaffolding for create/fork/mirror. 2026-05-16 16:16:10 +02:00
Mark Qvist 12e45b6483 Added work document proposals 2026-05-16 02:11:00 +02:00
Mark Qvist ba8fca6f87 Nicer stats page 2026-05-15 23:21:56 +02:00
Mark Qvist 9b99b72f61 Cleanup 2026-05-15 22:15:17 +02:00
Mark Qvist 03cfbc2eb6 Added half-block chart rendering 2026-05-15 22:09:27 +02:00
Mark Qvist c92872a81b Added download stats to rngit 2026-05-15 20:12:07 +02:00
Mark Qvist f3f4d9bca3 Cleanup 2026-05-15 17:32:10 +02:00
Mark Qvist e7a317f0a0 Use canonical Transport interface list add/removes. Improved announce cache cleaning. Adjusted logging. 2026-05-15 17:08:22 +02:00
Mark Qvist d5b64a4af3 Cleaned up log/print consistency for listener/initiator modes in rncp 2026-05-15 14:40:55 +02:00
Mark Qvist 5667a0bbac Better transfer completed feedback in rncp, thanks to neutral 2026-05-15 14:27:17 +02:00
Mark Qvist 7e46422c16 Auto-set latest release on creation 2026-05-15 00:58:17 +02:00
Mark Qvist 869a803149 Updated logging 2026-05-14 23:55:01 +02:00
Mark Qvist f744e4d9a3 Updated logging 2026-05-14 23:32:33 +02:00
Mark Qvist 1a7607cba3 Improved shared instance RPC error handling 2026-05-14 19:16:52 +02:00
Mark Qvist d881c111f6 Added latest release management to rngit 2026-05-14 14:13:42 +02:00
Mark Qvist bdac57ec0b Readme formatting 2026-05-14 12:02:54 +02:00
Mark Qvist c15f566cfa Updated readme 2026-05-14 11:58:05 +02:00
Mark Qvist bdc79b9097 Updated readme 2026-05-14 11:55:11 +02:00
Mark Qvist 102eccb77d Updated readme 2026-05-14 11:54:36 +02:00
Mark Qvist e8b236c7d8 Updated readme 2026-05-14 11:54:05 +02:00
Mark Qvist d69491eb80 Updated readme 2026-05-14 11:52:18 +02:00
Mark Qvist 256a4d0b92 Cleanup 2026-05-14 11:48:44 +02:00
Mark Qvist c5add012c1 Updated readme 2026-05-14 11:46:39 +02:00
Mark Qvist 6ecc8933b4 Updated readme 2026-05-14 11:44:07 +02:00
Mark Qvist 42b5661979 Updated readme 2026-05-14 11:35:30 +02:00
Mark Qvist 6333fb39bf Updated readme 2026-05-14 10:45:48 +02:00
Mark Qvist ea27a8b8a7 Updated readme 2026-05-14 10:43:57 +02:00
Mark Qvist 358f9c3b0c Updated readme 2026-05-14 10:42:33 +02:00
Mark Qvist cb3ef69072 Updated readme 2026-05-14 10:33:36 +02:00
Mark Qvist eee9354657 Updated readme 2026-05-14 10:26:21 +02:00
Mark Qvist ff86a1d7e6 Updated readme 2026-05-14 10:19:53 +02:00
Mark Qvist e49f31322c Redirect blob to tree page if target is a tree 2026-05-14 10:07:03 +02:00
66 changed files with 8229 additions and 986 deletions
+128
View File
@@ -1,3 +1,131 @@
### 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.
**Changes**
- Added work document proposals functionality to `rngit`
- Added fork and mirroring support to `rngit`
- Added ability to create new repositories remotely to `rngit`
- Added latest release management to `rngit`
- Added download stats to `rngit`
- Improved shared instance RPC error handling
- Improved announce cache cleaning
- Improved `rngit` page node link handling
- Improved stats pages `rngit`
- Improved transfer completed feedback in `rncp`, thanks to **neutral**
- Improved interface transport insertion and removal
**Release Signatures**
Release artifacts include `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rnid`. To verify files, download the `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 rns*.whl
```
The `rnid` utility will then verify the signatures, and display whether it is valid. If the signature cannot be verified, the file has been tampered with and should be thrown very far away in a jiffy.
### 2026-05-14: RNS 1.2.6
This release adds further improvements to the `rnid` and `rngit` utilities, and includes several bugfixes and other 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...
+73 -67
View File
@@ -1,22 +1,22 @@
>> Reticulum Network Stack
To understand the foundational philosophy and goals of this system, read the `!`[Zen of Reticulum`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=Zen+of+Reticulum.md]`!.
To understand the foundational philosophy and goals of this system, read the `_`!`[Zen of Reticulum`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=Zen+of+Reticulum.md]`!`_.
Reticulum is the cryptography-based networking stack for building local and wide-area networks with readily available hardware. It can operate even with very high latency and extremely low bandwidth. Reticulum allows you to build wide-area networks with off-the-shelf tools, and offers end-to-end encryption and connectivity, initiator anonymity, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable delivery acknowledgements and more.
The vision of Reticulum is to allow anyone to be their own network operator, and to make it cheap and easy to cover vast areas with a myriad of independent, inter-connectable and autonomous networks. Reticulum **is not** *one* network. It is **a tool** for building *thousands of networks*. Networks without kill-switches, surveillance, censorship and control. Networks that can freely interoperate, associate and disassociate with each other, and require no central oversight. Networks for human beings. *Networks for the people*.
The vision of Reticulum is to allow anyone to be their own network operator, and to make it cheap and easy to cover vast areas with a myriad of independent, inter-connectable and autonomous networks. Reticulum `!is not`! `*one`* network. It is `!a tool`! for building `*thousands of networks`*. Networks without kill-switches, surveillance, censorship and control. Networks that can freely interoperate, associate and disassociate with each other, and require no central oversight. Networks for human beings. `*Networks for the people`*.
Reticulum is a complete networking stack, and does not rely on IP or higher layers, but it is possible to use IP as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks.
Having no dependencies on traditional networking stacks frees up overhead that has been used to implement a networking stack built directly on cryptographic principles, allowing resilience and stable functionality, even in open and trustless networks.
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python.
>> Read The Manual
The full documentation for Reticulum is available on `!`[this node`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!.
The full documentation for Reticulum is available on `_`!`[this node`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!`_.
You can also download the `!`[Reticulum manual as a PDF`:/file/download`g=reticulum|r=reticulum|ref=HEAD|path=docs%2FReticulum+Manual.pdf]`! or `!`[as an e-book in EPUB format`:/file/download`g=reticulum|r=reticulum|ref=HEAD|path=docs%2FReticulum+Manual.pdf]`!.
You can also download the `_`!`[Reticulum manual as a PDF`:/file/download`g=reticulum|r=reticulum|ref=HEAD|path=docs%2FReticulum+Manual.pdf]`!`_ or `_`!`[as an e-book in EPUB format`:/file/download`g=reticulum|r=reticulum|ref=HEAD|path=docs%2FReticulum+Manual.pdf]`!`_.
>> Notable Features
@@ -66,33 +66,31 @@ The Reticulum Protocol was dedicated to the Public Domain in 2016.
>> Examples of Reticulum Applications
If you want to quickly get an idea of what Reticulum can do, take a look at the [Programs Using Reticulum](https://reticulum.network/manual/software.html) section of the manual, or the following resources:
If you want to quickly get an idea of what Reticulum can do, take a look at the `_`!`[Programs Using Reticulum`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/software.md]`!`_ section of the manual, or the following resources:
[LXMF](https://github.com/markqvist/lxmf) is a distributed, delay and disruption tolerant message transfer
protocol built on Reticulum
`_`!`[LXMF`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=lxmf]`!`_ is a distributed, delay and disruption tolerant message transfer protocol built on Reticulum
• The [LXST](https://github.com/markqvist/lxst) protocol and framework provides real-time audio and signals
transport over Reticulum. It includes primitives and utilities for building voice-based applications and
hardware devices, such as the `B333rnphone`b program, that can be used to build hardware telephones.
• The `_`!`[LXST`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=lxst]`!`_ protocol and framework provides real-time audio and signals transport over Reticulum. It
includes primitives and utilities for building voice-based applications and hardware devices,
such as the `B333rnphone`b program, that can be used to build hardware telephones.
• For an off-grid, encrypted and resilient mesh communications platform, see [Nomad Network](https://github.com/markqvist/NomadNet)
• For an off-grid, encrypted and resilient mesh communications platform, see `_`!`[Nomad Network`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=nomadnet]`!`_.
• The Android, Linux, macOS and Windows app [Sideband](https://github.com/markqvist/Sideband) has a graphical
interface and many advanced features, such as file transfers, image and voice messages, real-time voice calls,
a distributed telemetry system, mapping capabilities and full plugin extensibility.
• The Android, Linux, macOS and Windows app `_`!`[Sideband`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=sideband]`!`_ has a graphical interface and many advanced
features, such as file transfers, image and voice messages, real-time voice calls, a distributed
telemetry system, mapping capabilities and full plugin extensibility.
• [MeshChatX](https://git.quad4.io/RNS-Things/MeshChatX) is a full-featured LXMF client with many built-in tools
and functionalities, that also supports image and voice messages, file transfers and voice calls. It also
includes a built-in page browser for browsing Nomad Network nodes.
`_`!`[MeshChatX`c10d80b1a42fa958c37a6cc30dc04f53]`!`_ (`_`!`[source`5399f5a0212477618821e91e88ce053b:/page/repo.mu`g=quad4|r=MeshChatX]`_`!) is a full-featured LXMF client with many built-in tools and functionalities,
that also supports image and voice messages, file transfers and voice calls. It also includes a
built-in page browser for browsing Nomad Network nodes.
• You can use the included [rnsh](https://reticulum.network/manual/using.html#the-rnsh-utility) program to
establish remote shell sessions over Reticulum.
• You can use the included `_`!`[rnsh`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/using.md|anchor=the-rnsh-utility]`!`_ program to establish remote shell sessions over Reticulum.
>> Where can Reticulum be used?
Over practically any medium that can support at least a half-duplex channel with greater throughput than 5 bits per second, and an MTU of 500 bytes. Data radios, modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes, WiFi and Ethernet devices, free-space optical links, and similar systems are all examples of the types of physical devices Reticulum can use.
An open-source LoRa-based interface called [RNode](https://markqvist.github.io/Reticulum/manual/hardware.html#rnode) has been designed specifically for use with Reticulum. It is possible to build yourself, or it can be purchased as a complete transceiver that just needs a USB connection to the host.
An open-source LoRa-based interface called `_`!`[RNode`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/hardware.md|anchor=rnode]`!`_ has been designed specifically for use with Reticulum. It is possible to build yourself, or it can be purchased as a complete transceiver that just needs a USB connection to the host.
Reticulum can also be encapsulated over existing IP networks, so there's nothing stopping you from using it over wired Ethernet, your local WiFi network or the Internet, where it'll work just as well. In fact, one of the strengths of Reticulum is how easily it allows you to connect different mediums into a self-configuring, resilient and encrypted mesh, using any available mixture of available infrastructure.
@@ -100,9 +98,9 @@ As an example, it's possible to set up a Raspberry Pi connected to both a LoRa r
>> How do I get started?
The best way to get started with the Reticulum Network Stack depends on what you want to do. For full details and examples, have a look at the [Getting Started Fast](https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
The best way to get started with the Reticulum Network Stack depends on what you want to do. For full details and examples, have a look at the `_`!`[Getting Started Fast`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/gettingstartedfast.md]`!`_ section of the `_`!`[Reticulum Manual`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!`_.
To simply install Reticulum and related utilities on your system, the easiest way is via `B333pip`b. You can then start any program that uses Reticulum, or start Reticulum as a system service with [the rnsd utility](https://markqvist.github.io/Reticulum/manual/using.html#the-rnsd-utility).
To simply install Reticulum and related utilities on your system, the easiest way is via `B333pip`b. You can then start any program that uses Reticulum, or start Reticulum as a system service with `_`!`[the rnsd utility`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/using.md|anchor=the-rnsd-utility]`!`_.
`B333
`=
@@ -131,10 +129,11 @@ When first started, Reticulum will create a default configuration file, providin
If you have an old version of `B333pip`b on your system, you may need to upgrade it first with `B333pip install pip --upgrade`b. If you no not already have `B333pip`b installed, you can install it using the package manager of your system with `B333sudo apt install python3-pip`b or similar.
For more detailed examples on how to expand communication over many mediums such as packet radio or LoRa, serial ports, or over fast IP links and the Internet using the UDP and TCP interfaces, take a look at the [Supported Interfaces](https://markqvist.github.io/Reticulum/manual/interfaces.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
For more detailed examples on how to expand communication over many mediums such as packet radio or LoRa, serial ports, or over fast IP links and the Internet using the UDP and TCP interfaces, take a look at the `_`!`[Supported Interfaces`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/interfaces.md]`!`_ section of the `_`!`[Reticulum Manual`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!`_.
>> Included Utilities
Reticulum includes a range of useful utilities for managing your networks, viewing status and information, and other tasks. You can read more about these programs in the [Included Utility Programs](https://markqvist.github.io/Reticulum/manual/using.html#included-utility-programs) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
Reticulum includes a range of useful utilities for managing your networks, viewing status and information, and other tasks. You can read more about these programs in the `_`!`[Included Utility Programs`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/using.md|anchor=included-utility-programs]`!`_ section of the `_`!`[Reticulum Manual`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!`_.
• The system daemon `B333rnsd`b for running Reticulum as an always-available service
• An interface status utility called `B333rnstatus`b, that displays information about interfaces
@@ -147,16 +146,14 @@ Reticulum includes a range of useful utilities for managing your networks, viewi
• The `B333rngit`b program provides a full multi-repository Git node for serving repositories over Reticulum
• The included `B333git-remote-rns`b helper allows you to interact with Git repositories over Reticulum
All tools, including `B333rnx`b and `B333rncp`b, work reliably and well even over very low-bandwidth links like LoRa or Packet Radio. For full-featured remote shells over Reticulum, also have a look at the [rnsh](https://github.com/acehoss/rnsh) program.
>> Supported interface types and devices
Reticulum implements a range of generalised interface types that covers most of the communications hardware that Reticulum can run over. If your hardware is not supported, it's [simple to implement a custom interface module](https://markqvist.github.io/Reticulum/manual/interfaces.html#custom-interfaces).
Reticulum implements a range of generalised interface types that covers most of the communications hardware that Reticulum can run over. If your hardware is not supported, it's `_`!`[simple to implement a custom interface module`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/interfaces.md|anchor=custom-interfaces]`!`_.
Currently, the following built-in interfaces are supported:
• Any Ethernet device
• LoRa using [RNode](https://unsigned.io/rnode/)
• LoRa using `_`!`[RNode`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=rnode_firmware]`!`_
• Packet Radio TNCs (with or without AX.25)
• KISS-compatible hardware and software modems
• Any device with a serial port
@@ -166,64 +163,72 @@ Currently, the following built-in interfaces are supported:
• Custom hardware via stdio or pipes
>> Performance
Reticulum targets a *very* wide usable performance envelope, but prioritises functionality and performance on low-bandwidth mediums. The goal is to provide a dynamic performance envelope from 250 bits per second, to 1 gigabit per second on normal hardware.
Reticulum targets a `*very`* wide usable performance envelope, but prioritises functionality and performance on low-bandwidth mediums. The goal is to provide a dynamic performance envelope from 250 bits per second, to 1 gigabit per second on normal hardware.
Currently, the usable performance envelope is approximately 150 bits per second to 500 megabits per second, with physical mediums faster than that not being saturated. Performance beyond the current level is intended for future upgrades, but not highly prioritised at this point in time.
>> Current Status
All core protocol features are implemented and functioning, but additions will probably occur as real-world use is explored and understood. The API and wire-format can be considered stable.
>> Dependencies
The installation of the default `B333rns`b package requires only two external dependencies, listed below. Almost all systems and distributions have readily available packages for these dependencies, and when the `B333rns`b package is installed with `B333pip`b, they will be downloaded and installed as well.
[PyCA/cryptography](https://github.com/pyca/cryptography)
[pyserial](https://github.com/pyserial/pyserial)
• PyCA/cryptography
• pyserial
On more unusual systems, and in some rare cases, it might not be possible to install or even compile one or more of the above modules. In such situations, you can use the `B333rnspure`b package instead, which require no external dependencies for installation. Please note that the contents of the `B333rns`b and `B333rnspure`b packages are *identical*. The only difference is that the `B333rnspure`b package lists no dependencies required for installation.
On more unusual systems, and in some rare cases, it might not be possible to install or even compile one or more of the above modules. In such situations, you can use the `B333rnspure`b package instead, which require no external dependencies for installation. Please note that the contents of the `B333rns`b and `B333rnspure`b packages are `*identical`*. The only difference is that the `B333rnspure`b package lists no dependencies required for installation.
No matter how Reticulum is installed and started, it will load external dependencies only if they are *needed* and *available*. If for example you want to use Reticulum on a system that cannot support [pyserial](https://github.com/pyserial/pyserial), it is perfectly possible to do so using the `B333rnspure`b package, but Reticulum will not be able to use serial-based interfaces. All other available modules will still be loaded when needed.
No matter how Reticulum is installed and started, it will load external dependencies only if they are `*needed`* and `*available`*. If for example you want to use Reticulum on a system that cannot support `B333pyserial`b, it is perfectly possible to do so using the `B333rnspure`b package, but Reticulum will not be able to use serial-based interfaces. All other available modules will still be loaded when needed.
**Please Note!** If you use the `B333rnspure`b package to run Reticulum on systems that do not support [PyCA/cryptography](https://github.com/pyca/cryptography), it is important that you read and understand the [Cryptographic Primitives](#cryptographic-primitives) section of this document.
`!Please Note!`! If you use the `B333rnspure`b package to run Reticulum on systems that do not support PyCA/cryptography, it is important that you read and understand the `!Cryptographic Primitives`! section of this document.
>> Bootstrapping Connectivity
Reticulum is not a service you subscribe to, nor is it a single global network you "join". Reticulum provides functionality for discovering available public interfaces over the network itself, and the broader community has provided various directories of publicly available entrypoints to bootstrap connectivity.
To learn how to establish initial connectivity over Reticulum, read the [Bootstrapping Connectivity](https://reticulum.network/manual/gettingstartedfast.html#bootstrapping-connectivity) section of the manual.
To learn how to establish initial connectivity over Reticulum, read the `_`!`[Bootstrapping Connectivity`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/gettingstartedfast.md|anchor=bootstrapping-connectivity]`!`_ section of the manual.
If you already have a general idea of how this works, you can use community-run sites such as [directory.rns.recipes](https://directory.rns.recipes/) and [rmap.world](https://rmap.world) to find interface definitions for initial connectivity to the global distributed Reticulum backbone.
If you already have a general idea of how this works, you can use community-run sites such as `_`!`[rns.recipes`9ce92808be498e9e05590ff27cbfdfe4]`!`_ and `_`!`[rmap.world`a4a5e861626ce97c9aa544d9ecdf6d22]`!`_ to find interface definitions for initial connectivity to the global distributed Reticulum backbone.
>> Public Testnet
***Important!** Historically, a developer-targeted testnet was made available by the Reticulum project itself. As the amount of global Reticulum nodes and entrypoints have grown to a substantial quantity, this public testnet, including the Amsterdam Testnet entrypoint, has now been decommissioned. If your still have instances that relied on this entrypoint for connectivity, transition to using the distributed backbone instead. Reticulum now includes a full on-network interface discovery and connectivity bootstrapping system. Read the [Bootstrapping Connectivity](https://reticulum.network/manual/gettingstartedfast.html#bootstrapping-connectivity) section of the manual for pointers.*
`!`*Important!`! Historically, a developer-targeted testnet was made available by the Reticulum project itself. As the amount of global Reticulum nodes and entrypoints have grown to a substantial quantity, this public testnet, including the Amsterdam Testnet entrypoint, has now been decommissioned. If you still have instances that relied on this entrypoint for connectivity, transition to using the distributed backbone instead. Reticulum now includes a full on-network interface discovery and connectivity bootstrapping system. Read the `_`[Bootstrapping Connectivity`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/gettingstartedfast.md|anchor=bootstrapping-connectivity]`_ section of the manual for pointers.`*
>> Support Reticulum
For this to be possible, I need your help. Please support the continued development of open, free and private communications systems by donating via one of the following channels:
• Monero:
`!Monero`!
84FpY1QbxHcgdseePYNmhTHcrgMX4nFfBYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD19b3B8NiLCGVxzKV17UMmmeEsCrPyA5w
• Bitcoin
`!Bitcoin`!
bc1pgqgu8h8xvj4jtafslq396v7ju7hkgymyrzyqft4llfslz5vp99psqfk3a6
• Ethereum
`!Ethereum`!
0x91C421DdfB8a30a49A71d63447ddb54cEBe3465E
• Liberapay: https://liberapay.com/Reticulum/
`!Liberapay`!
`[https://liberapay.com/Reticulum/]
• Ko-Fi: https://ko-fi.com/markqvist
`!Ko-Fi`!
`[https://ko-fi.com/markqvist]
>> Cryptographic Primitives
Reticulum uses a simple suite of efficient, strong and well-tested cryptographic primitives, with widely available implementations that can be used both on general-purpose CPUs and on microcontrollers.
One of the primary considerations for choosing this particular set of primitives is that they can be implemented *safely* with relatively few pitfalls, on practically all current computing platforms.
One of the primary considerations for choosing this particular set of primitives is that they can be implemented `*safely`* with relatively few pitfalls, on practically all current computing platforms.
The primitives listed here **are authoritative**. Anything claiming to be Reticulum, but not using these exact primitives **is not** Reticulum, and possibly an intentionally compromised or weakened clone. The utilised primitives are:
The primitives listed here `!are authoritative`!. Anything `*claiming`* to be Reticulum, but not using these exact primitives `FTA35050`!is not`!`f Reticulum, and possibly an intentionally compromised or weakened clone. The utilised primitives are:
• Reticulum Identity Keys are 512-bit Curve25519 keysets
• A 256-bit Ed25519 key for signatures
• A 256-bit X22519 key for ECDH key exchanges
• HKDF for key derivation
• Encrypted tokens are based on the [Fernet spec](https://github.com/fernet/spec/)
• Encrypted tokens are based on the `_`!`[Fernet spec`https://github.com/fernet/spec/]`!`_
• Ephemeral keys derived from an ECDH key exchange on Curve25519
• HMAC using SHA256 for message authentication
• IVs must be generated through `B333os.urandom()`b or better
@@ -232,36 +237,37 @@ The primitives listed here **are authoritative**. Anything claiming to be Reticu
• SHA-256
• SHA-512
In the default installation configuration, the `B333X25519`b, `B333Ed25519`b, and `B333AES-256-CBC`b primitives are provided by [OpenSSL](https://www.openssl.org/) (via the [PyCA/cryptography](https://github.com/pyca/cryptography) package). The hashing functions `B333SHA-256`b and `B333SHA-512`b are provided by the standard Python [hashlib](https://docs.python.org/3/library/hashlib.html). The `B333HKDF`b, `B333HMAC`b, `B333Token`b primitives, and the `B333PKCS7`b padding function are always provided by the following internal implementations:
In the default installation configuration, the `B333X25519`b, `B333Ed25519`b, and `B333AES-256-CBC`b primitives are provided by `_`!`[OpenSSL`https://www.openssl.org/]`!`_ (via the `_`!`[PyCA/cryptography`https://github.com/pyca/cryptography]`!`_ package). The hashing functions `B333SHA-256`b and `B333SHA-512`b are provided by the standard Python `_`!`[hashlib`https://docs.python.org/3/library/hashlib.html]`!`_. The `B333HKDF`b, `B333HMAC`b, `B333Token`b primitives, and the `B333PKCS7`b padding function are always provided by the following internal implementations:
• [HKDF.py](RNS/Cryptography/HKDF.py)
• [HMAC.py](RNS/Cryptography/HMAC.py)
• [Token.py](RNS/Cryptography/Token.py)
• [PKCS7.py](RNS/Cryptography/PKCS7.py)
`_`!`[HKDF.py`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=RNS/Cryptography/HKDF.py]`!`_
`_`!`[HMAC.py`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=RNS/Cryptography/HMAC.py]`!`_
`_`!`[Token.py`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=RNS/Cryptography/Token.py]`!`_
`_`!`[PKCS7.py`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=RNS/Cryptography/PKCS7.py]`!`_
Reticulum also includes a complete implementation of all necessary primitives in pure Python. If OpenSSL and PyCA are not available on the system when Reticulum is started, Reticulum will instead use the internal pure-python primitives. A trivial consequence of this is performance, with the OpenSSL backend being *much* faster. The most important consequence however, is the potential loss of security by using primitives that has not seen the same amount of scrutiny, testing and review as those from OpenSSL.
Reticulum also includes a complete implementation of all necessary primitives in pure Python. If OpenSSL and PyCA are not available on the system when Reticulum is started, Reticulum will instead use the internal pure-python primitives. A trivial consequence of this is performance, with the OpenSSL backend being `*much`* faster. The most important consequence however, is the potential loss of security by using primitives that has not seen the same amount of scrutiny, testing and review as those from OpenSSL.
Please note that by default, installing Reticulum will **require** OpenSSL and PyCA to also be automatically installed if not already available. It is only possible to use the pure-python primitives if this requirement is specifically overridden by the user, for example by installing the `B333rnspure`b package instead of the normal `B333rns`b package, or by running directly from local source-code.
Please note that by default, installing Reticulum will `!require`! OpenSSL and PyCA to also be automatically installed if not already available. It is only possible to use the pure-python primitives if this requirement is specifically overridden by the user, for example by installing the `B333rnspure`b package instead of the normal `B333rns`b package, or by running directly from local source-code.
If you want to use the internal pure-python primitives, it is **highly advisable** that you have a good understanding of the risks that this pose, and make an informed decision on whether those risks are acceptable to you.
If you want to use the internal pure-python primitives, it is `!highly advisable`! that you have a good understanding of the risks that this pose, and make an informed decision on whether those risks are acceptable to you.
Reticulum is relatively young software, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it _has not_ been externally security audited, and there could very well be privacy or security breaking bugs. If you want to help out, or help sponsor an audit, please do get in touch.
>> Acknowledgements & Credits
Reticulum can only exist because of the mountain of Open Source work it was built on top of, the contributions of everyone involved, and everyone that has supported the project through the years. To everyone who has helped, thank you so much.
A number of other modules and projects are either part of, or used by Reticulum. Sincere thanks to the authors and contributors of the following projects:
• [PyCA/cryptography](https://github.com/pyca/cryptography), *BSD License*
• [Pure-25519](https://github.com/warner/python-pure25519) by [Brian Warner](https://github.com/warner), *MIT License*
• [Pysha2](https://github.com/thomdixon/pysha2) by [Thom Dixon](https://github.com/thomdixon), *MIT License*
• [Python AES-128](https://github.com/orgurar/python-aes) by [Or Gur Arie](https://github.com/orgurar), *MIT License*
• [Python AES-256](https://github.com/boppreh/aes) by [BoppreH](https://github.com/boppreh), *MIT License*
• [Curve25519.py](https://gist.github.com/nickovs/cc3c22d15f239a2640c185035c06f8a3#file-curve25519-py) by [Nicko van Someren](https://gist.github.com/nickovs), *Public Domain*
• [I2Plib](https://github.com/l-n-s/i2plib) by [Viktor Villainov](https://github.com/l-n-s)
• [PySerial](https://github.com/pyserial/pyserial) by Chris Liechti, *BSD License*
• [Configobj](https://github.com/DiffSK/configobj) by Michael Foord, Nicola Larosa, Rob Dennis & Eli Courtwright, *BSD License*
• [ifaddr](https://github.com/pydron/ifaddr) by Stefan C. Mueller, *MIT License*
• [Umsgpack.py](https://github.com/vsergeev/u-msgpack-python) by [Ivan A. Sergeev](https://github.com/vsergeev)
• [rnsh](https://github.com/acehoss/rnsh) by [Aaron Heise](https://github.com/acehoss)
• [Python](https://www.python.org)
`_`!`[PyCA/cryptography`https://github.com/pyca/cryptography]`!`_, `*BSD License`*
`_`!`[Pure-25519`https://github.com/warner/python-pure25519]`!`_, by `_`!`[Brian Warner`https://github.com/warner]`!`_, `*MIT License`*
`_`!`[Pysha2`https://github.com/thomdixon/pysha2]`!`_ by `_`!`[Thom Dixon`https://github.com/thomdixon]`!`_, `*MIT License`*
`_`!`[Python AES-128`https://github.com/orgurar/python-aes]`!`_ by `_`!`[Or Gur Arie`https://github.com/orgurar]`!`_, `*MIT License`*
`_`!`[Python AES-256`https://github.com/boppreh/aes]`!`_ by `_`!`[BoppreH`https://github.com/boppreh]`!`_, `*MIT License`*
`_`!`[Curve25519.py`https://gist.github.com/nickovs/cc3c22d15f239a2640c185035c06f8a3]`!`_ by `_`!`[Nicko van Someren`https://gist.github.com/nickovs]`!`_, `*Public Domain`*
`_`!`[I2Plib`https://github.com/l-n-s/i2plib]`!`_ by `_`!`[Viktor Villainov`https://github.com/l-n-s]`!`_
`_`!`[PySerial`https://github.com/pyserial/pyserial]`!`_ by Chris Liechti, `*BSD License`*
`_`!`[Configobj`https://github.com/DiffSK/configobj]`!`_ by Michael Foord, Nicola Larosa, Rob Dennis & Eli Courtwright, `*BSD License`*
`_`!`[ifaddr`https://github.com/pydron/ifaddr]`!`_ by Stefan C. Mueller, `*MIT License`*
`_`!`[Umsgpack.py`https://github.com/vsergeev/u-msgpack-python]`!`_ by `_`!`[Ivan A. Sergeev`https://github.com/vsergeev]`!`_
`_`!`[rnsh`https://github.com/acehoss/rnsh]`!`_ by `_`!`[Aaron Heise`https://github.com/acehoss]`!`_
`_`!`[Python`https://www.python.org]`!`_
+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()
+1 -1
View File
@@ -576,7 +576,7 @@ class InterfaceDiscovery():
def teardown_interface(self, interface):
interface.detach()
if interface in RNS.Transport.interfaces: RNS.Transport.interfaces.remove(interface)
RNS.Transport.remove_interface(interface)
if interface in self.monitored_interfaces: self.monitored_interfaces.remove(interface)
def autoconnect_count(self):
+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:
+2 -2
View File
@@ -574,7 +574,7 @@ class AutoInterface(Interface):
spawned_interface.mode = self.mode
spawned_interface.HW_MTU = self.HW_MTU
spawned_interface.online = True
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
if addr in self.spawned_interfaces:
self.spawned_interfaces[addr].detach()
self.spawned_interfaces[addr].teardown()
@@ -666,7 +666,7 @@ class AutoInterfacePeer(Interface):
except Exception as e:
RNS.log(f"Could not remove {self} from parent interface on detach. The contained exception was: {e}", RNS.LOG_ERROR)
if self in RNS.Transport.interfaces: RNS.Transport.interfaces.remove(self)
RNS.Transport.remove_interface(self)
class AutoInterfaceHandler(socketserver.BaseRequestHandler):
def __init__(self, callback, *args, **keys):
+4 -5
View File
@@ -258,7 +258,7 @@ class BackboneInterface(Interface):
@staticmethod
def deregister_fileno(fileno):
if fileno < 0:
RNS.log(f"Attempt to deregister invalid file descriptor {fileno}", RNS.LOG_WARNING)
RNS.log(f"Attempt to deregister invalid file descriptor {fileno}", RNS.LOG_DEBUG)
return
try: BackboneInterface.epoll.unregister(fileno)
@@ -454,7 +454,7 @@ class BackboneInterface(Interface):
spawned_interface.HW_MTU = self.HW_MTU
spawned_interface.online = True
RNS.log("Spawned new BackboneClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
while spawned_interface in self.spawned_interfaces: self.spawned_interfaces.remove(spawned_interface)
self.spawned_interfaces.append(spawned_interface)
BackboneInterface.add_client_socket(socket, spawned_interface)
@@ -769,9 +769,8 @@ class BackboneClientInterface(Interface):
while self in self.parent_interface.spawned_interfaces:
self.parent_interface.spawned_interfaces.remove(self)
if self in RNS.Transport.interfaces:
if not self.initiator:
RNS.Transport.interfaces.remove(self)
if not self.initiator:
RNS.Transport.remove_interface(self)
def __str__(self):
+4 -5
View File
@@ -826,9 +826,8 @@ class I2PInterfacePeer(Interface):
while self in self.parent_interface.spawned_interfaces:
self.parent_interface.spawned_interfaces.remove(self)
if self in RNS.Transport.interfaces:
if not self.initiator:
RNS.Transport.interfaces.remove(self)
if not self.initiator:
RNS.Transport.remove_interface(self)
def __str__(self):
@@ -940,7 +939,7 @@ class I2PInterface(Interface):
peer_interface.IN = True
peer_interface.parent_interface = self
peer_interface.parent_count = False
RNS.Transport.interfaces.append(peer_interface)
RNS.Transport.add_interface(peer_interface)
def incoming_connection(self, handler):
RNS.log("Accepting incoming I2P connection", RNS.LOG_VERBOSE)
@@ -993,7 +992,7 @@ class I2PInterface(Interface):
spawned_interface.mode = self.mode
spawned_interface.HW_MTU = self.HW_MTU
RNS.log("Spawned new I2PInterface Peer: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
while spawned_interface in self.spawned_interfaces:
self.spawned_interfaces.remove(spawned_interface)
self.spawned_interfaces.append(spawned_interface)
+3 -4
View File
@@ -347,8 +347,7 @@ class LocalClientInterface(Interface):
self.OUT = False
self.IN = False
if self in RNS.Transport.interfaces:
RNS.Transport.interfaces.remove(self)
RNS.Transport.remove_interface(self)
if self in RNS.Transport.local_client_interfaces:
RNS.Transport.local_client_interfaces.remove(self)
@@ -458,7 +457,7 @@ class LocalServerInterface(Interface):
spawned_interface.socket_path = self.socket_path
if hasattr(self, "_force_bitrate"): spawned_interface._force_bitrate = self._force_bitrate
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
RNS.Transport.local_client_interfaces.append(spawned_interface)
BackboneInterface.add_client_socket(client_socket, spawned_interface)
self.clients += 1
@@ -474,7 +473,7 @@ class LocalServerInterface(Interface):
spawned_interface.parent_interface = self
spawned_interface.bitrate = self.bitrate
if hasattr(self, "_force_bitrate"): spawned_interface._force_bitrate = self._force_bitrate
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
RNS.Transport.local_client_interfaces.append(spawned_interface)
self.clients += 1
spawned_interface.read_loop()
+2 -3
View File
@@ -375,7 +375,7 @@ class RNodeMultiInterface(Interface):
interface.mode = self.mode
interface.HW_MTU = self.HW_MTU
interface.detected = True
RNS.Transport.interfaces.append(interface)
RNS.Transport.add_interface(interface)
RNS.log("Spawned new RNode subinterface: "+str(interface), RNS.LOG_VERBOSE)
self.clients += 1
@@ -909,8 +909,7 @@ class RNodeMultiInterface(Interface):
def teardown_subinterfaces(self):
for interface in self.subinterfaces:
if interface != 0:
if interface in RNS.Transport.interfaces:
RNS.Transport.interfaces.remove(interface)
RNS.Transport.remove_interface(interface)
self.subinterfaces[interface.index] = 0
def should_ingress_limit(self):
+3 -4
View File
@@ -436,9 +436,8 @@ class TCPClientInterface(Interface):
while self in self.parent_interface.spawned_interfaces:
self.parent_interface.spawned_interfaces.remove(self)
if self in RNS.Transport.interfaces:
if not self.initiator:
RNS.Transport.interfaces.remove(self)
if not self.initiator:
RNS.Transport.remove_interface(self)
def __str__(self):
@@ -627,7 +626,7 @@ class TCPServerInterface(Interface):
spawned_interface.HW_MTU = self.HW_MTU
spawned_interface.online = True
RNS.log("Spawned new TCPClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
while spawned_interface in self.spawned_interfaces:
self.spawned_interfaces.remove(spawned_interface)
self.spawned_interfaces.append(spawned_interface)
+2 -3
View File
@@ -981,7 +981,7 @@ class WeaveInterface(Interface):
spawned_interface.mode = self.mode
spawned_interface.HW_MTU = self.HW_MTU
spawned_interface._online = True
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
if endpoint_addr in self.spawned_interfaces:
self.spawned_interfaces[endpoint_addr].detach()
self.spawned_interfaces[endpoint_addr].teardown()
@@ -1097,5 +1097,4 @@ class WeaveInterfacePeer(Interface):
except Exception as e:
RNS.log(f"Could not remove {self} from parent interface on detach. The contained exception was: {e}", RNS.LOG_ERROR)
if self in RNS.Transport.interfaces:
RNS.Transport.interfaces.remove(self)
RNS.Transport.remove_interface(self)
+40 -20
View File
@@ -402,7 +402,7 @@ class Reticulum:
RNS.log("Existing shared instance required, but this instance started as shared instance. Aborting startup.", RNS.LOG_VERBOSE)
else:
RNS.Transport.interfaces.append(interface)
RNS.Transport.add_interface(interface)
self.shared_instance_interface = interface
self.is_shared_instance = True
RNS.log("Started shared instance interface: "+str(interface), RNS.LOG_DEBUG)
@@ -422,7 +422,7 @@ class Reticulum:
interface._force_bitrate = True
RNS.log(f"Forcing shared instance bitrate of {RNS.prettyspeed(interface.bitrate)}", RNS.LOG_WARNING)
interface.optimise_mtu()
RNS.Transport.interfaces.append(interface)
RNS.Transport.add_interface(interface)
self.is_shared_instance = False
self.is_standalone_instance = False
self.is_connected_to_shared_instance = True
@@ -915,7 +915,7 @@ class Reticulum:
interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key)
interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key))
RNS.Transport.interfaces.append(interface)
RNS.Transport.add_interface(interface)
interface.final_init()
interface = None
@@ -1077,7 +1077,7 @@ class Reticulum:
interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key)
interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key))
RNS.Transport.interfaces.append(interface)
RNS.Transport.add_interface(interface)
interface.final_init()
def _default_ar_target(self):
@@ -1234,28 +1234,43 @@ class Reticulum:
def _used_destination_data(self, destination_hash):
if self.is_connected_to_shared_instance:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"destination_data": "used", "destination_hash": destination_hash})
response = rpc_connection.recv()
return response
try:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"destination_data": "used", "destination_hash": destination_hash})
response = rpc_connection.recv()
return response
except Exception as e:
RNS.log(f"Shared instance RPC failed while setting destination data use: {e}", RNS.LOG_ERROR)
return False
else: return RNS.Identity._used_destination_data(destination_hash)
def _retain_destination_data(self, destination_hash):
if self.is_connected_to_shared_instance:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"destination_data": "retain", "destination_hash": destination_hash})
response = rpc_connection.recv()
return response
try:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"destination_data": "retain", "destination_hash": destination_hash})
response = rpc_connection.recv()
return response
except Exception as e:
RNS.log(f"Shared instance RPC failed while retaining destination data: {e}", RNS.LOG_ERROR)
return False
else: return RNS.Identity._retain_destination_data(destination_hash)
def _unretain_destination_data(self, destination_hash):
if self.is_connected_to_shared_instance:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"destination_data": "unretain", "destination_hash": destination_hash})
response = rpc_connection.recv()
return response
try:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"destination_data": "unretain", "destination_hash": destination_hash})
response = rpc_connection.recv()
return response
except Exception as e:
RNS.log(f"Shared instance RPC failed while unretaining destination data: {e}", RNS.LOG_ERROR)
return False
else: return RNS.Identity._unretain_destination_data(destination_hash)
@@ -1264,10 +1279,15 @@ class Reticulum:
raise TypeError("Cannot retain identity, not a valid identity hash")
if self.is_connected_to_shared_instance:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"identity_data": "retain", "identity_hash": identity_hash})
response = rpc_connection.recv()
return response
try:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"identity_data": "retain", "identity_hash": identity_hash})
response = rpc_connection.recv()
return response
except Exception as e:
RNS.log(f"Shared instance RPC failed while retaining identity: {e}", RNS.LOG_ERROR)
return False
else: return RNS.Identity._retain_identity(identity_hash)
+37 -15
View File
@@ -417,6 +417,18 @@ class Transport:
gc.collect()
@staticmethod
def add_interface(interface):
with Transport.interfaces_lock:
if not interface in Transport.interfaces:
Transport.interfaces.append(interface)
@staticmethod
def remove_interface(interface):
with Transport.interfaces_lock:
if interface in Transport.interfaces:
Transport.interfaces.remove(interface)
@staticmethod
def set_network_identity(identity):
if not Transport.network_identity:
@@ -1594,7 +1606,7 @@ class Transport:
RNS.log(f"Clamping link MTU to {RNS.prettysize(path_mtu)}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
except Exception as e:
RNS.log(f"Dropping link request packet. The contained exception was: {e}", RNS.LOG_WARNING)
RNS.log(f"Dropping link request packet. The contained exception was: {e}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
return
# Entry format is
@@ -2187,12 +2199,9 @@ class Transport:
else:
RNS.log("Invalid link request proof in transport for link "+RNS.prettyhexrep(packet.destination_hash)+", dropping proof.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
except Exception as e:
RNS.log("Could not transport link request proof. The contained exception was: "+str(e), RNS.LOG_WARNING)
else:
RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
else:
RNS.log("Received link request proof with hop mismatch, not transporting it", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
except Exception as e: RNS.log("Could not transport link request proof. The contained exception was: "+str(e), RNS.LOG_DEBUG) if RNS.sl(LOG_DEBUG) else None
else: RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
else: RNS.log("Received link request proof with hop mismatch, not transporting it", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
else:
# Check if we can deliver it to a local
@@ -2521,16 +2530,17 @@ class Transport:
def clean_announce_cache():
st = time.time()
target_path = os.path.join(RNS.Reticulum.cachepath, "announces")
with Transport.path_table_lock: active_paths = [Transport.path_table[dst_hash][6] for dst_hash in Transport.path_table]
with Transport.tunnels_lock: tunnel_paths = list(set([path_dict[dst_hash][6] for path_dict in [Transport.tunnels[tunnel_id][2] for tunnel_id in Transport.tunnels] for dst_hash in path_dict]))
cached_announce_hashes = os.listdir(target_path)
with Transport.path_table_lock: active_path_hashes = list(set([Transport.path_table[dst_hash][IDX_PT_PACKET] for dst_hash in Transport.path_table]))
with Transport.tunnels_lock: tunnel_path_hashes = list(set([path_dict[dst_hash][IDX_PT_PACKET] for path_dict in [Transport.tunnels[tunnel_id][IDX_TT_PATHS] for tunnel_id in Transport.tunnels] for dst_hash in path_dict]))
removed = 0; total = 0
for packet_hash in os.listdir(target_path):
for packet_hash in cached_announce_hashes:
remove = False
full_path = os.path.join(target_path, packet_hash)
if os.path.isfile(full_path):
try: target_hash = bytes.fromhex(packet_hash)
except: remove = True
if (not target_hash in active_paths) and (not target_hash in tunnel_paths): remove = True
if (not target_hash in active_path_hashes) and (not target_hash in tunnel_path_hashes): remove = True
if remove: os.unlink(full_path); removed += 1
total += 1
@@ -2892,10 +2902,8 @@ class Transport:
tag=tag_bytes)
else: RNS.log("Ignoring duplicate path request for "+RNS.prettyhexrep(destination_hash)+" with tag "+RNS.prettyhexrep(unique_tag), RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
else: RNS.log("Ignoring tagless path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
except Exception as e: RNS.log("Error while handling path request. The contained exception was: "+str(e), RNS.LOG_ERROR)
except Exception as e: RNS.log(f"Error while handling path request. The contained exception was: {e}", RNS.LOG_ERROR)
@staticmethod
def path_request(destination_hash, is_from_local_client, attached_interface, requestor_transport_id=None, tag=None):
@@ -2936,7 +2944,7 @@ class Transport:
received_from = Transport.path_table[destination_hash][IDX_PT_RVCD_IF]
if packet == None:
RNS.log("Could not retrieve announce packet from cache while answering path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_WARNING)
RNS.log(f"Could not retrieve announce packet from cache while answering path request for {RNS.prettyhexrep(destination_hash)}, ignoring path request", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
elif attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING and attached_interface == received_from:
RNS.log("Not answering path request on roaming-mode interface, since next hop is on same roaming-mode interface", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
@@ -3397,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}
@@ -3414,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)
+8 -7
View File
@@ -163,11 +163,11 @@ def listen(configdir, identitypath = None, verbosity = 0, quietness = 0, allowed
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
RNS.log(f"Could not apply allowed identity: {e}", RNS.LOG_ERROR)
RNS.exit(1)
if len(allowed_identity_hashes) < 1 and not disable_auth:
print("Warning: No allowed identities configured, rncp will not accept any files!")
RNS.log("No allowed identities configured, rncp will not accept any files!", RNS.LOG_WARNING)
def fetch_request(path, data, request_id, link_id, remote_identity, requested_at):
global allow_fetch, fetch_jail, fetch_auto_compress
@@ -217,7 +217,7 @@ def listen(configdir, identitypath = None, verbosity = 0, quietness = 0, allowed
else:
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_LIST, allowed_list=allowed_identity_hashes)
print("rncp listening on "+RNS.prettyhexrep(destination.hash))
RNS.log("rncp listening on "+RNS.prettyhexrep(destination.hash), RNS.LOG_INFO)
if announce >= 0:
def job():
@@ -271,15 +271,15 @@ def receive_resource_started(resource):
else:
id_str = ""
print("Starting resource transfer "+RNS.prettyhexrep(resource.hash)+id_str)
RNS.log("Starting resource transfer "+RNS.prettyhexrep(resource.hash)+id_str, RNS.LOG_INFO)
def receive_resource_concluded(resource):
global save_path, allow_overwrite_on_receive
if resource.status == RNS.Resource.COMPLETE:
print(str(resource)+" completed")
RNS.log(f"Incoming resource {resource} completed", RNS.LOG_INFO)
if resource.metadata == None:
print("Invalid data received, ignoring resource")
RNS.log("Invalid data received, ignoring resource", RNS.LOG_WARNING)
return
else:
@@ -306,13 +306,14 @@ def receive_resource_concluded(resource):
full_save_path = saved_filename+"."+str(counter)
shutil.move(resource.data.name, full_save_path)
RNS.log("Saved received file to "+full_save_path, RNS.LOG_NOTICE)
except Exception as e:
RNS.log(f"An error occurred while saving received resource: {e}", RNS.LOG_ERROR)
return
else:
print("Resource failed")
RNS.log("Resource failed", RNS.LOG_INFO)
resource_done = False
current_resource = None
+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
+347 -107
View File
@@ -98,6 +98,19 @@ class NomadNetworkNode():
CLR_FILE = "`F66d"
CLR_DIM = "`F666"
CLR_DIM_H = "`F444"
CLR_OK_DIM = "`FT537855"
CLR_DIFF_A = "`F0a0"
CLR_DIFF_R = "`F900"
CLR_DIFF_P = "`F0aa"
RCLR_PUSH = "B9A810"
RCLR_PUSH_G = "791212"
RCLR_FETCH = "10b981"
RCLR_FETCH_G = "1c5e71"
RCLR_VIEW = "3b82f6"
RCLR_VIEW_G = "13428A"
RCLR_DOWNLOAD = "7831E0"
RCLR_DOWNLOAD_G = "c5754d"
# Yes, I'm being intentionally weird here. If you
# want to use tabs, three spaces is all you get.
@@ -208,6 +221,13 @@ class NomadNetworkNode():
if not remote_identity: remote_identity = self.null_ident
return self.owner.resolve_permission(remote_identity, group_name, repository_name, permission)
def resolve_doc_permission(self, remote_identity, group_name, repository_name, doc_id, permission):
# Since the nomadnet page protocol doesn't *require* authentication,
# we use null_ident in case the remote hasn't identified.
if not remote_identity: remote_identity = self.null_ident
return self.owner.resolve_doc_permission(remote_identity, group_name, repository_name, doc_id, permission)
def register_request_handlers(self):
self.destination.register_request_handler(self.PATH_INDEX, response_generator=self.serve_front_page, allow=RNS.Destination.ALLOW_ALL)
self.destination.register_request_handler(self.PATH_GROUP, response_generator=self.serve_group_page, allow=RNS.Destination.ALLOW_ALL)
@@ -294,6 +314,16 @@ class NomadNetworkNode():
field_str = "`" + "|".join(field_parts)
return f"`[{sanitize_label(_label)}`:{_path}{field_str}]"
def m_link_e(self, _label, remote, _path, **fields):
def sanitize_v(value): return urllib.parse.quote_plus(str(value).encode("utf-8"))
def sanitize_label(value): return value.replace("[", "").replace("]", "").replace("`", "")
field_str = ""
if fields:
field_parts = []
for k, v in fields.items(): field_parts.append(f"{k}={sanitize_v(v)}")
field_str = "`" + "|".join(field_parts)
return f"`!`[{sanitize_label(_label)}`{remote}:{_path}{field_str}]`!"
def m_link(self, _label, _path, **fields):
def sanitize_v(value): return urllib.parse.quote_plus(str(value).encode("utf-8"))
def sanitize_label(value): return value.replace("[", "").replace("]", "").replace("`", "")
@@ -410,7 +440,35 @@ class NomadNetworkNode():
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"]
elif repo["mirror"]: source_type = "mirror"; source_url = repo["mirror"]
else: source_type = "retriev"; source_url = "unknown source"
if not source_url.lower().startswith("rns://"): source_link = ""
else:
try:
url_components = source_url.split("/")
if len(url_components) == 5 and len(url_components[2]) == RNS.Identity.TRUNCATED_HASHLENGTH//8*2:
source_repo_dest = bytes.fromhex(url_components[2])
source_group_name = url_components[3]
source_repo_name = url_components[4]
source_identity = RNS.Identity.recall(source_repo_dest)
if source_identity:
source_page_dest = RNS.Destination.hash_from_name_and_identity("nomadnetwork.node", source_identity)
mu_link = self.m_link_e(source_url, RNS.hexrep(source_page_dest, delimit=False), self.PATH_REPO, g=source_group_name, r=source_repo_name)
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{sync_str}\n")
description = self.get_repository_description(repo["path"])
if description: description = f"{description}\n\n"
else: description = ""
@@ -434,7 +492,7 @@ class NomadNetworkNode():
# Get releases count
releases_path = f"{repo['path']}.releases"
releases_count = 0
releases = self.owner.releases_list_data(releases_path)
releases, latest_release = self.owner.releases_list_data(releases_path)
if releases: releases_count = len([r for r in releases if r.get("status") == "published"])
sep = self.icon("sep")
@@ -461,7 +519,7 @@ class NomadNetworkNode():
converted = mdc.format_block(readme_content)
content_parts.append(converted)
else: content_parts.append(f"\n{readme_content}")
else: content_parts.append(f"\n{readme_content.rstrip()}\n")
else:
content_parts.append(self.m_divider())
@@ -647,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
@@ -689,6 +748,10 @@ class NomadNetworkNode():
if blob_info is None: content_parts.append("File not found at this ref.\n")
else:
# Redirect to tree page if this is a tree
if blob_info.get("is_tree", None) == True:
return self.serve_tree_page(path, data, request_id, link_id, remote_identity, requested_at)
size = blob_info.get("size", 0)
is_binary = blob_info.get("is_binary", False)
is_symlink = blob_info.get("is_symlink", False)
@@ -801,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")
@@ -828,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:
@@ -841,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"
@@ -854,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)
@@ -892,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")
@@ -908,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
@@ -1077,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)
@@ -1103,40 +1173,56 @@ class NomadNetworkNode():
content_parts.append(self.m_heading(f"Stats for {repo_name}", 2))
v_total = stats["views"]["total"]
v_peak = stats["views"]["peak"]
v_peak = stats["views"]["peak"]
v_tday = stats["views"]["daily"][-1] if len(stats["views"]["daily"]) else 0
f_total = stats["fetches"]["total"]
f_peak = stats["fetches"]["peak"]
p_total = stats["pushes"]["total"]
p_peak = stats["pushes"]["peak"]
f_peak = stats["fetches"]["peak"]
f_tday = stats["fetches"]["daily"][-1] if len(stats["fetches"]["daily"]) else 0
content_parts.append(f"\n`F66dViews`f : {v_total:>5} total {self.CLR_DIM}(peak: {v_peak:>3})`f\n")
content_parts.append(f"`F0a0Fetches`f : {f_total:>5} total {self.CLR_DIM}(peak: {f_peak:>3})\n`f")
content_parts.append(f"`Faa0Pushes`f : {p_total:>5} total {self.CLR_DIM}(peak: {p_peak:>3})\n`f")
content_parts.append(f"`F0aaActivity`f : {stats['activity_score']:>5} points\n\n")
p_total = stats["pushes"]["total"]
p_peak = stats["pushes"]["peak"]
p_tday = stats["pushes"]["daily"][-1] if len(stats["pushes"]["daily"]) else 0
d_total = stats["downloads_combined"]["total"]
d_peak = stats["downloads_combined"]["peak"]
d_tday = stats["downloads_combined"]["daily"][-1] if len(stats["downloads_combined"]["daily"]) else 0
content_parts.append( f"\n`FT{self.RCLR_FETCH}Fetches`f : {f_total:>5} total {self.CLR_DIM} today: {f_tday:>3} peak: {f_peak:>3} \n`f")
content_parts.append( f"`FT{self.RCLR_PUSH}Pushes`f : {p_total:>5} total {self.CLR_DIM} today: {p_tday:>3} peak: {p_peak:>3} \n`f")
content_parts.append( f"`FT{self.RCLR_VIEW}Views`f : {v_total:>5} total {self.CLR_DIM} today: {v_tday:>3} peak: {v_peak:>3} `f\n")
content_parts.append(f"`FT{self.RCLR_DOWNLOAD}Downloads`f : {d_total:>5} total {self.CLR_DIM} today: {d_tday:>3} peak: {d_peak:>3} `f\n")
content_parts.append( f"`F0aaActivity`f : {stats['activity_score']:>5} points\n\n")
content_parts.append(f"{act_color}{act_label}`f over the last {stats['actual_days']} days ({stats['date_range']})\n\n")
if v_total > 0:
content_parts.append(self.m_heading(f"Views", 2))
content_parts.append("\n")
content_parts.append(self.render_chart(stats["views"]["daily"], stats["timeline_labels"], color="66d"))
content_parts.append("\n")
if f_total > 0:
content_parts.append(self.m_heading(f"Fetches", 2))
content_parts.append("\n")
content_parts.append(self.render_chart(stats["fetches"]["daily"], stats["timeline_labels"], color="0a0"))
content_parts.append(self.render_chart(stats["fetches"]["daily"], stats["timeline_labels"], color=self.RCLR_FETCH, secondary_color=self.RCLR_FETCH_G))
content_parts.append("\n")
if p_total > 0:
content_parts.append(self.m_heading(f"Pushes", 2))
content_parts.append("\n")
content_parts.append(self.render_chart(stats["pushes"]["daily"], stats["timeline_labels"], color="aa0"))
content_parts.append(self.render_chart(stats["pushes"]["daily"], stats["timeline_labels"], color=self.RCLR_PUSH, secondary_color=self.RCLR_PUSH_G))
content_parts.append("\n")
if v_total > 0:
content_parts.append(self.m_heading(f"Views", 2))
content_parts.append("\n")
content_parts.append(self.render_chart(stats["views"]["daily"], stats["timeline_labels"], color=self.RCLR_VIEW, secondary_color=self.RCLR_VIEW_G))
content_parts.append("\n")
if d_total > 0:
content_parts.append(self.m_heading(f"Downloads", 2))
content_parts.append("\n")
content_parts.append(self.render_chart(stats["downloads_combined"]["daily"], stats["timeline_labels"], color=self.RCLR_DOWNLOAD, secondary_color=self.RCLR_DOWNLOAD_G, gradient_factor=1.7))
content_parts.append("\n")
if stats["activity_score"] > 0:
content_parts.append(self.m_heading("Combined Activity", 2))
content_parts.append("\n")
content_parts.append(self.render_combined_chart(stats["views"]["daily"], stats["fetches"]["daily"], stats["pushes"]["daily"], stats["timeline_labels"]))
content_parts.append(self.render_combined_chart(stats["views"]["daily"], stats["fetches"]["daily"], stats["pushes"]["daily"], stats["downloads_combined"]["daily"], stats["timeline_labels"]))
else: content_parts.append(self.m_italic("\nNo development activity recorded for this repository in the selected time period.\n\n"))
@@ -1170,7 +1256,7 @@ class NomadNetworkNode():
nav_content = "".join(nav_parts)
releases_path = f"{repo['path']}.releases"
releases = self.owner.releases_list_data(releases_path)
releases, latest_release = self.owner.releases_list_data(releases_path)
if not releases:
content_parts.append(self.m_heading("Releases", 2))
content_parts.append("\nNo releases available for this repository.\n")
@@ -1194,8 +1280,9 @@ class NomadNetworkNode():
link = self.m_link(tag, self.PATH_RELEASE, g=group_name, r=repo_name, t=tag)
sep = self.icon("sep")
latest_str = f" {sep} {self.CLR_OK_DIM}`*Latest`*`f" if tag == latest_release else ""
artifacts_str = f"`*{artifacts} artifact{'s' if artifacts != 1 else ''}`*"
content_parts.append(f"{link} {self.CLR_DIM}{date_str} {sep} {artifacts_str}`f\n")
content_parts.append(f"{link} {self.CLR_DIM}{date_str} {sep} {artifacts_str}{latest_str}`f\n")
if preview:
if rel_format == "markdown": content_parts.append(f"{self.mdc.format_block(preview)}\n")
elif rel_format == "micron": content_parts.append(f"{preview}\n")
@@ -1226,13 +1313,16 @@ class NomadNetworkNode():
releases_path = f"{repo['path']}.releases"
if tag == "latest":
releases = self.owner.releases_list_data(releases_path)
releases, latest_release = self.owner.releases_list_data(releases_path)
if not releases:
content = self.m_heading("Release Not Found", 2) + f"\nNo latest release exist.\n"
content = self.m_heading("Release Not Found", 2) + f"\nNo releases exist.\n"
return self.render_template(content, nav_content=nav_content, st=st)
recent_releases = sorted(releases, key=lambda x: x['created'], reverse=True)
tag = recent_releases[0]["tag"]
if not latest_release:
recent_releases = sorted(releases, key=lambda x: x['created'], reverse=True)
tag = recent_releases[0]["tag"]
else: tag = latest_release
content_parts = []
nav_parts = []
@@ -1312,7 +1402,7 @@ class NomadNetworkNode():
group_name = data.get("var_g", "") if data else ""
repo_name = data.get("var_r", "") if data else ""
scope = data.get("var_scope", "active") if data else "active"
if scope not in ["active", "completed", "all"]: scope = "active"
if scope not in ["active", "completed", "proposed", "all"]: scope = "active"
if not group_name or not repo_name:
content = self.m_heading("Error", 2) + "\nInvalid request\n"
@@ -1335,16 +1425,18 @@ class NomadNetworkNode():
sep = self.icon("sep")
active_s = "`_" if scope == "active" else ""
cmplt_s = "`_" if scope == "completed" else ""
prpsd_s = "`_" if scope == "proposed" else ""
all_s = "`_" if scope == "all" else ""
filter_links = []
filter_links.append(active_s+self.m_link("Active", self.PATH_WORK, g=group_name, r=repo_name, scope="active")+active_s)
filter_links.append(cmplt_s+self.m_link("Completed", self.PATH_WORK, g=group_name, r=repo_name, scope="completed")+cmplt_s)
filter_links.append(prpsd_s+self.m_link("Proposed", self.PATH_WORK, g=group_name, r=repo_name, scope="proposed")+prpsd_s)
filter_links.append(all_s+self.m_link("All", self.PATH_WORK, g=group_name, r=repo_name, scope="all")+all_s)
content_parts.append(f" {sep} ".join(filter_links) + "\n\n")
# Load work documents
work_path = f"{repo['path']}.work"
scopes_to_show = ["active", "completed"] if scope == "all" else [scope]
scopes_to_show = ["active", "completed", "proposed"] if scope == "all" else [scope]
for s in scopes_to_show:
folder_path = os.path.join(work_path, s)
@@ -1356,6 +1448,9 @@ class NomadNetworkNode():
if not os.path.isdir(doc_dir): continue
try:
doc_id = int(entry)
read_access = self.resolve_doc_permission(remote_identity, group_name, repo_name, doc_id, self.owner.PERM_READ)
if not read_access: continue
root_path = os.path.join(doc_dir, "root")
if not os.path.isfile(root_path): continue
@@ -1407,7 +1502,7 @@ class NomadNetworkNode():
repo_name = data.get("var_r", "") if data else ""
doc_id = data.get("var_id", "") if data else ""
scope = data.get("var_scope", "all") if data else "all"
if scope not in ["active", "completed", "all"]: scope = "active"
if scope not in ["active", "completed", "proposed", "all"]: scope = "active"
if not group_name or not repo_name or not doc_id:
content = self.m_heading("Error", 2) + "\nInvalid request\n"
@@ -1423,20 +1518,31 @@ class NomadNetworkNode():
content = self.m_heading("Error", 2) + "\nThe requested repository was not found\n"
return self.render_template(content, st=st)
work_path = f"{repo['path']}.work"
active_dir = os.path.join(work_path, "active", str(doc_id))
read_access = self.resolve_doc_permission(remote_identity, group_name, repo_name, doc_id, self.owner.PERM_READ)
if not read_access:
content = self.m_heading("Error", 2) + "\nThe requested work document was not found\n"
return self.render_template(content, st=st)
work_path = f"{repo['path']}.work"
active_dir = os.path.join(work_path, "active", str(doc_id))
completed_dir = os.path.join(work_path, "completed", str(doc_id))
proposed_dir = os.path.join(work_path, "proposed", str(doc_id))
if scope == "active": doc_dir = active_dir
elif scope == "completed": doc_dir = completed_dir
elif scope == "proposed": doc_dir = proposed_dir
elif scope == "all":
if os.path.isdir(active_dir):
doc_dir = active_dir
scope = "active"
else:
elif os.path.isdir(completed_dir):
doc_dir = completed_dir
scope = "completed"
else:
doc_dir = proposed_dir
scope = "proposed"
root_path = os.path.join(doc_dir, "root")
if not os.path.isfile(root_path):
@@ -1557,10 +1663,13 @@ class NomadNetworkNode():
releases_path = f"{repo['path']}.releases"
if tag == "latest":
releases = self.owner.releases_list_data(releases_path)
releases, latest_release = self.owner.releases_list_data(releases_path)
if not releases: return None
recent_releases = sorted(releases, key=lambda x: x['created'], reverse=True)
tag = recent_releases[0]["tag"]
if not latest_release:
recent_releases = sorted(releases, key=lambda x: x['created'], reverse=True)
tag = recent_releases[0]["tag"]
else: tag = latest_release
release_dir = os.path.join(releases_path, tag)
artifacts_dir = os.path.join(release_dir, "artifacts")
@@ -1589,6 +1698,7 @@ class NomadNetworkNode():
RNS.log(f"Artifact file resolved for artifact request {group_name}/{repo_name}/{tag}/{artifact}", RNS.LOG_DEBUG)
self.owner.release_download_succeeded(group_name, repo_name, remote_identity)
return [open(artifact_path, "rb"), {"name": artifact.encode("utf-8")}]
def serve_download(self, path, data, request_id, link_id, remote_identity, requested_at):
@@ -1626,7 +1736,10 @@ class NomadNetworkNode():
else:
stream = self.get_blob_stream(repo_path, resolved_ref, file_path)
if stream is not None: return [stream, {"name": file_name.encode("utf-8")}]
if stream is not None:
self.owner.download_succeeded(group_name, repo_name, remote_identity)
return [stream, {"name": file_name.encode("utf-8")}]
else:
RNS.log(f"Could not resolve blob stream for download request {group_name}/{repo_name}/{ref}/{file_path}", RNS.LOG_WARNING)
return None
@@ -1650,28 +1763,40 @@ class NomadNetworkNode():
try: doc_id = int(doc_id)
except:
if not type(doc_id) == str or not type(doc_id) == bytes: doc_id = f"{doc_id}"
RNS.log(f"Could not parse document ID for workdoc download request {group_name[:128]}/{repo_name[:128]}/{doc_id[:128]}", RNS.LOG_WARNING)
return None
repo = self.get_accessible_repository(remote_identity, group_name, repo_name)
if not repo:
RNS.log(f"Repository not found or no access for workdoc download request {group_name[:128]}/{repo_name[:128]}/{doc_id[:128]}", RNS.LOG_WARNING)
RNS.log(f"Repository not found or no access for workdoc download request {group_name[:128]}/{repo_name[:128]}/{doc_id}", RNS.LOG_WARNING)
return None
work_path = f"{repo['path']}.work"
active_dir = os.path.join(work_path, "active", str(doc_id))
doc_access = self.resolve_doc_permission(remote_identity, group_name, repo_name, doc_id, self.owner.PERM_READ)
if not doc_access:
RNS.log(f"No access for workdoc download request {group_name[:128]}/{repo_name[:128]}/{doc_id}", RNS.LOG_WARNING)
return None
work_path = f"{repo['path']}.work"
active_dir = os.path.join(work_path, "active", str(doc_id))
completed_dir = os.path.join(work_path, "completed", str(doc_id))
proposed_dir = os.path.join(work_path, "proposed", str(doc_id))
if scope == "active": doc_dir = active_dir
elif scope == "completed": doc_dir = completed_dir
elif scope == "proposed": doc_dir = proposed_dir
elif scope == "all":
if os.path.isdir(active_dir):
doc_dir = active_dir
scope = "active"
else:
elif os.path.isdir(completed_dir):
doc_dir = completed_dir
scope = "completed"
else:
doc_dir = proposed_dir
scope = "proposed"
root_path = os.path.join(doc_dir, "root")
if not os.path.isfile(root_path):
@@ -1691,6 +1816,7 @@ class NomadNetworkNode():
if content:
if fmt == "micron": file_name = f"{title}.mu"
else: file_name = f"{title}.md"
self.owner.download_succeeded(group_name, repo_name, remote_identity)
return [file_name, content.encode("utf-8")]
return None
@@ -1846,6 +1972,15 @@ class NomadNetworkNode():
size = int(result.stdout.strip())
# Check if it's a tree via ls-tree
check_tree_path = f"{ref}:{file_path}" if file_path else ref
result = subprocess.run(["git", "ls-tree", check_tree_path],
cwd=repo_path, capture_output=True, text=True,
timeout=self.GIT_COMMAND_TIMEOUT, check=False)
if result.returncode == 0: is_tree = True
else: is_tree = False
# Check if it's a symlink via ls-tree
parent_dir = "/".join(file_path.split("/")[:-1])
filename = file_path.split("/")[-1]
@@ -1894,6 +2029,7 @@ class NomadNetworkNode():
if first_line.startswith("-"): is_binary = True
return { "size": size,
"is_tree": is_tree,
"is_binary": is_binary,
"is_symlink": is_symlink,
"symlink_target": symlink_target }
@@ -2193,22 +2329,31 @@ class NomadNetworkNode():
for line in lines:
if line.startswith("+"):
if line.startswith("+++"): formatted_lines.append(self.m_escape(line))
else: formatted_lines.append(f"`F0a0{self.m_escape(line)}`f")
else: formatted_lines.append(f"{self.CLR_DIFF_A}{self.m_escape(line)}`f")
elif line.startswith("-"):
if line.startswith("---"): formatted_lines.append(self.m_escape(f"\\{line}"))
else: formatted_lines.append(f"`F900{self.m_escape(line)}`f")
else: formatted_lines.append(f"{self.CLR_DIFF_R}{self.m_escape(line)}`f")
elif line.startswith("@@"):
formatted_lines.append(f"`F0aa{self.m_escape(line)}`f")
formatted_lines.append(f"{self.CLR_DIFF_P}{self.m_escape(line)}`f")
elif line.startswith("diff ") or line.startswith("index ") or line.startswith("new file") or line.startswith("deleted file"):
if line.startswith("diff --git a"): formatted_lines.append("")
formatted_lines.append(f"`F666{self.m_escape(line)}`f")
formatted_lines.append(f"{self.CLR_DIM}{self.m_escape(line)}`f")
else: formatted_lines.append(self.m_escape(line))
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:
@@ -2264,7 +2409,10 @@ class NomadNetworkNode():
# Stats Renderers #
###################
def render_chart(self, data, labels, color="666", height=10):
def render_chart(self, data, labels, color="666", height=10, secondary_color=None, gradient_factor=None):
return self.render_chart_halfblock(data, labels, color=color, height=height, secondary_color=secondary_color, gradient_factor=gradient_factor)
def render_chart_full_block(self, data, labels, color="666", height=10):
if not data or all(d == 0 for d in data): return "No data available\n"
max_val = max(data) if max(data) > 0 else 1
num_points = len(data)
@@ -2273,8 +2421,8 @@ class NomadNetworkNode():
indent = ""
bar_width = 1
chart_lines = []
chart_lines.append(f"{indent}`F{color}Peak: {max_val}`f\n")
lines = []
lines.append(f"{indent}`F{color}Peak: {max_val}`f\n")
for row in range(height, 0, -1):
threshold = (row - 1) / height * max_val
row_line = f"{indent}"
@@ -2286,58 +2434,6 @@ class NomadNetworkNode():
else: row_line += f"`F{color}{''*bar_width}`f{hsep}"
else: row_line += f"{' '*bar_width}{hsep}"
row_line += "\n"
chart_lines.append(row_line)
hsj = ""*len(hsep)
bottom_border = "" + hsj.join(["" * bar_width] * num_points) + ""
chart_lines.append(indent + bottom_border + "\n")
chart_width = len(bottom_border)
first_label = f"{labels[0][:12]:<12}"
final_label = f"{labels[-1][:12]:>12}"
middle_space = chart_width-len(first_label)-len(final_label)
label_line = f"{indent}`F666{first_label}`f"
label_line += " " * middle_space
label_line += f"`F666{final_label}`f\n"
chart_lines.append(label_line)
return "".join(chart_lines)
# TODO: This is a weird idea, really. Probably redo it to something else.
def render_combined_chart(self, views, fetches, pushes, labels, height=4):
if not views or not labels: return "No data available\n"
all_data = [v + f + p for v, f, p in zip(views, fetches, pushes)]
max_val = max(all_data) if all(all_data) > 0 else 1
num_points = len(views)
hsep = ""
indent = ""
bar_width = 1
lines = []
lines.append(f"{indent}`F66d██`f Views `F0a0██`f Fetches `Faa0██`f Pushes\n\n")
for row in range(height, 0, -1):
threshold = (row - 1) / height * max_val
row_line = f"{indent}"
for i in range(num_points):
v, f, p = views[i], fetches[i], pushes[i]
total = v + f + p
if total > threshold:
# Determine which "layer" this row represents
# Priority: Pushes > fetches > views for display
if p > 0 and threshold < (v + f + p) and threshold >= (v + f): row_line += f"`Faa0{''*bar_width}`f{hsep}"
elif f > 0 and threshold < (v + f) and threshold >= v: row_line += f"`F0a0{''*bar_width}`f{hsep}"
elif v > 0 and threshold < v: row_line += f"`F66d{''*bar_width}`f{hsep}"
else:
# Mixed or partial, show dominant
if p >= f and p >= v: row_line += f"`Faa0{''*bar_width}`f{hsep}"
elif f >= v: row_line += f"`F0a0{''*bar_width}`f{hsep}"
else: row_line += f"`F66d{''*bar_width}`f{hsep}"
else: row_line += f"{' '*bar_width}{hsep}"
row_line += "\n"
lines.append(row_line)
hsj = ""*len(hsep)
@@ -2349,13 +2445,157 @@ class NomadNetworkNode():
final_label = f"{labels[-1][:12]:>12}"
middle_space = chart_width-len(first_label)-len(final_label)
label_line = f"{indent}`F666{first_label}`f"
label_line = f"{indent}{self.CLR_DIM}{first_label}`f"
label_line += " " * middle_space
label_line += f"`F666{final_label}`f\n"
label_line += f"{self.CLR_DIM}{final_label}`f\n"
lines.append(label_line)
return "".join(lines)
def render_chart_halfblock(self, data, labels, color="666", height=10, secondary_color=None, gradient_factor=None):
if not gradient_factor: gradient_factor = 1.3
if not data or all(d == 0 for d in data): return "No data available\n"
max_val = max(data) if max(data) > 0 else 1
num_points = len(data)
def hex_to_rgb(h): return tuple(int(h[i:i+2], 16) for i in (0, 2, 4))
def expand_color(c): return ''.join(ch * 2 for ch in c) if len(c) == 3 else c[:6]
def gradient_color(t): return ''.join(f"{int(secondary_rgb[i] + (primary_rgb[i] - secondary_rgb[i]) * min(1, t*gradient_factor)):02x}" for i in range(3))
primary = expand_color(color)
if secondary_color: secondary = expand_color(secondary_color)
else: secondary = ''.join(f"{int(int(primary[i:i+2], 16) * 0.42):02x}" for i in (0, 2, 4))
primary_rgb = hex_to_rgb(primary)
secondary_rgb = hex_to_rgb(secondary)
lines = [f"`FT{primary}Peak: {max_val}`f\n"]
for row in range(height, 0, -1):
row_top = (row / height) * max_val
row_bottom = ((row - 1) / height) * max_val
row_mid = (row_top + row_bottom) / 2
grad_top = row / height
grad_mid = (row - 0.5) / height
line = ""
for val in data:
upper_filled = val >= row_top
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"
else: line += f"`FT{gradient_color(grad_mid)}▄`f"
lines.append(line + "\n")
hsep = ""
indent = ""
bar_width = 1
hsj = ""*len(hsep)
bottom_border = "" + hsj.join(["" * bar_width] * num_points) + ""
lines.append(indent + bottom_border + "\n")
chart_width = len(bottom_border)
first_label = f"{labels[0][:12]:<12}"
final_label = f"{labels[-1][:12]:>12}"
middle_space = chart_width-len(first_label)-len(final_label)
label_line = f"{indent}{self.CLR_DIM}{first_label}`f"
label_line += " " * middle_space
label_line += f"{self.CLR_DIM}{final_label}`f\n"
lines.append(label_line)
return "".join(lines)
def render_combined_chart(self, views, fetches, pushes, downloads, labels, height=6, colors=None, dim=0.87):
if not views or not all([views, fetches, pushes, downloads]): return "No data available\n"
if colors is None:
colors = { 'views': self.RCLR_VIEW,
'fetches': self.RCLR_FETCH,
'pushes': self.RCLR_PUSH,
'downloads': self.RCLR_DOWNLOAD }
def expand(c): return ''.join(ch*2 for ch in c) if len(c) == 3 else c[:6]
def hex_to_rgb(h): return tuple(int(h[i:i+2], 16) for i in (0, 2, 4))
def gradient_color(c, g, t, f=1.0): c = hex_to_rgb(c); g = hex_to_rgb(g); return ''.join(f"{int(g[i] + (c[i] - g[i]) * min(1, t*f)):02x}" for i in range(3))
cat_colors = {'views': gradient_color(expand(colors.get('views', self.RCLR_VIEW)), "000000", dim),
'fetches': gradient_color(expand(colors.get('fetches', self.RCLR_FETCH)), "000000", dim),
'pushes': gradient_color(expand(colors.get('pushes', self.RCLR_PUSH)), "000000", dim),
'downloads': gradient_color(expand(colors.get('downloads', self.RCLR_DOWNLOAD)), "000000", dim) }
# Stack order
categories = ['pushes', 'fetches', 'views', 'downloads']
cat_data = [pushes, fetches, views, downloads]
num_points = len(views)
legend = " ".join(f"`FT{cat_colors[cat]}`BT{cat_colors[cat]}██`f`b {cat.capitalize()}" for cat in categories)
lines = [f"{legend}\n\n"]
for row in range(height, 0, -1):
lower_min = (row - 1) / height
lower_max = (row - 0.5) / height
upper_min = (row - 0.5) / height
upper_max = row / height
line = ""
for i in range(num_points):
total = sum(d[i] for d in cat_data)
if total == 0:
line += " "
continue
cumsum = 0
cat_ranges = {}
for cat, data in zip(categories, cat_data):
start = cumsum / total
cumsum += data[i]
end = cumsum / total
cat_ranges[cat] = (start, end)
def pixel_to_cat(pmin, pmax):
for cat in categories:
cstart, cend = cat_ranges[cat]
# Check if pixel overlaps this category
# Return the category (they're mutually exclusive in stacked chart)
if pmin < cend and pmax > cstart: return cat
return None
upper_cat = pixel_to_cat(upper_min, upper_max)
lower_cat = pixel_to_cat(lower_min, lower_max)
if upper_cat is None and lower_cat is None: line += " "
elif upper_cat == lower_cat and upper_cat is not None:
col = cat_colors[upper_cat]
line += f"`FT{col}`BT{col}█`f`b"
elif upper_cat is not None and lower_cat is not None:
upper_col = cat_colors[upper_cat]
lower_col = cat_colors[lower_cat]
line += f"`FT{upper_col}`BT{lower_col}▀`f`b"
elif upper_cat is not None:
col = cat_colors[upper_cat]
line += f"`FT{col}▀`f"
else:
col = cat_colors[lower_cat]
line += f"`FT{col}▄`f"
lines.append(line + "\n")
bottom = "" + "" * num_points + ""
lines.append(bottom + "\n")
if labels:
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")
return "".join(lines)
#######################
# Connection Handlers #
#######################
File diff suppressed because it is too large Load Diff
+82 -24
View File
@@ -133,6 +133,10 @@ class MarkdownToMicron:
self.__local_url_scope = self.local_url_scope
self.syntax_highlighter = syntax_highlighter
self.wcwidth = None
self.bold_links = True
self.underline_links = True
self.link_color = None
try:
import wcwidth
@@ -204,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)
@@ -343,8 +349,15 @@ class MarkdownToMicron:
url = f"{self.local_url_scope}{url}"
if anchor: url = f"{url}|anchor={anchor}"
undl = "`_" if self.underline_links else ""
bold = "`!" if self.bold_links else ""
text = text.replace('`', '')
return f"`!`[{text}`{url}]`!"
link = f"{undl}{bold}`[{text}`{url}]{bold}{undl}"
if self.link_color and len(self.link_color) == 3: link = f"`F{self.link_color}{link}`f"
if self.link_color and len(self.link_color) == 6: link = f"`FT{self.link_color}{link}`f"
return link
text = re.sub(r'\x00LINK(\d+)\x00', restore_link, text)
@@ -673,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.6"
__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: 6d7f4aac8313ba495ab156ec11ab15c0
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.
File diff suppressed because it is too large Load Diff
+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.6',
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.6 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.6 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.6 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=010db75e"></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.6 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.6 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.6 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=010db75e"></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.6 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.6 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.6 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=010db75e"></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.6 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.6 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.6 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=010db75e"></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>
+760 -68
View File
File diff suppressed because it is too large Load Diff
+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.6 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.6 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.6 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=010db75e"></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>
+77 -10
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.6 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.6 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.6 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,14 +524,80 @@ 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-structure">Repository Structure</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#serving-pages-over-nomad-network">Serving Pages Over Nomad Network</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#formatting-syntax-highlighting">Formatting &amp; Syntax Highlighting</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#customizing-templates">Customizing Templates</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#release-management">Release Management</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#work-documents">Work Documents</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#repository-creation-management">Repository Creation &amp; Management</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#creating-empty-repositories">Creating Empty Repositories</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#forking-repositories">Forking Repositories</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#mirroring-repositories">Mirroring Repositories</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#automatic-mirror-synchronization">Automatic Mirror Synchronization</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#manual-synchronization">Manual Synchronization</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#git-configuration-parameters">Git Configuration Parameters</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#repository-structure">Repository Structure</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#configuration">Configuration</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#permissions">Permissions</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-types">Permission Types</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-hierarchy">Permission Hierarchy</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#configuration-methods">Configuration Methods</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#work-document-permissions">Work Document Permissions</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#creator-permissions">Creator Permissions</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-examples">Permission Examples</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-short-forms">Permission Short Forms</a></li>
<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>
<li class="toctree-l3"><a class="reference internal" href="git.html#formatting-syntax-highlighting">Formatting &amp; Syntax Highlighting</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#customizing-templates">Customizing Templates</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#repository-statistics">Repository Statistics</a></li>
<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>
<li class="toctree-l3"><a class="reference internal" href="git.html#command-line-interaction">Command-Line Interaction</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#work-documents">Work Documents</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#working-with-work-documents">Working With Work Documents</a></li>
<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>
</li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a><ul>
@@ -643,7 +710,7 @@ to participate in the development of Reticulum itself.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=010db75e"></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.6 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.6 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.6 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=010db75e"></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.6 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.6 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.6 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=010db75e"></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.6 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.6 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.6 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=010db75e"></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.6 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.6 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.6 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=010db75e"></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.6 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.6 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.6 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=010db75e"></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.6 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.6 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.6 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=010db75e"></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.6 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.6 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.6 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=010db75e"></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.6 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.6 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.6 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=010db75e"></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.6 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.6 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.6 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=010db75e"></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.6 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.6 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.6 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=010db75e"></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.6 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.6 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.6 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=010db75e"></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.
+798 -57
View File
File diff suppressed because it is too large Load Diff
+50 -2
View File
@@ -189,14 +189,62 @@ 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)
* [Creating Empty Repositories](git.md#creating-empty-repositories)
* [Forking Repositories](git.md#forking-repositories)
* [Mirroring Repositories](git.md#mirroring-repositories)
* [Automatic Mirror Synchronization](git.md#automatic-mirror-synchronization)
* [Manual Synchronization](git.md#manual-synchronization)
* [Git Configuration Parameters](git.md#git-configuration-parameters)
* [Repository Structure](git.md#repository-structure)
* [Configuration](git.md#configuration)
* [Permissions](git.md#permissions)
* [Permission Types](git.md#permission-types)
* [Permission Hierarchy](git.md#permission-hierarchy)
* [Configuration Methods](git.md#configuration-methods)
* [Work Document Permissions](git.md#work-document-permissions)
* [Creator Permissions](git.md#creator-permissions)
* [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)
* [Formatting & Syntax Highlighting](git.md#formatting-syntax-highlighting)
* [Customizing Templates](git.md#customizing-templates)
* [Enabling the Git Page Node](git.md#enabling-the-git-page-node)
* [Accessing Repository Pages](git.md#accessing-repository-pages)
* [Formatting & Syntax Highlighting](git.md#formatting-syntax-highlighting)
* [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)
* [Command-Line Interaction](git.md#command-line-interaction)
* [Work Documents](git.md#work-documents)
* [Working With Work Documents](git.md#working-with-work-documents)
* [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.
+857 -57
View File
File diff suppressed because it is too large Load Diff
+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")