Compare commits
125 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e40191b3d1 | ||
|
|
7f2b3b9b52 | ||
|
|
0a25e1a9c8 | ||
|
|
ddeb44b1de | ||
|
|
39a888c1e2 | ||
|
|
1f74990752 | ||
|
|
a714c20043 | ||
|
|
942434b288 | ||
|
|
47318034dd | ||
|
|
bb1320913a | ||
|
|
fc95caf519 | ||
|
|
6d1409be1c | ||
|
|
0a0e661beb | ||
|
|
26aec0040e | ||
|
|
135e941fa2 | ||
|
|
1d9ebe8c38 | ||
|
|
4f79110995 | ||
|
|
5cdff28703 | ||
|
|
06dc4ad550 | ||
|
|
6c9d717cd6 | ||
|
|
f25b31702b | ||
|
|
73c60e6a49 | ||
|
|
cce96d38c6 | ||
|
|
71583c5c2d | ||
|
|
d5962d14eb | ||
|
|
d3153bd778 | ||
|
|
69c425d9ae | ||
|
|
a93c6ba5be | ||
|
|
1cf176f6e5 | ||
|
|
b899389956 | ||
|
|
3ad70c63ea | ||
|
|
3b7429149e | ||
|
|
e2ba876ebf | ||
|
|
6ecda49394 | ||
|
|
d23261c8d9 | ||
|
|
84709ccf09 | ||
|
|
7283cb417a | ||
|
|
2c30a88e85 | ||
|
|
24b1ac521e | ||
|
|
e68f4ff118 | ||
|
|
c95fd8e6ca | ||
|
|
3f95b47282 | ||
|
|
aeccf69fc7 | ||
|
|
8a7ad40d64 | ||
|
|
c4be063650 | ||
|
|
87a75c2553 | ||
|
|
c963581a6f | ||
|
|
1f7dbe6dde | ||
|
|
0af42a6885 | ||
|
|
ff4e04099c | ||
|
|
780b7e6bfc | ||
|
|
192898864c | ||
|
|
c7be675739 | ||
|
|
21a2365045 | ||
|
|
a22de54b4a | ||
|
|
99de23c040 | ||
|
|
395241b190 | ||
|
|
15e6dd4a88 | ||
|
|
34b36362ab | ||
|
|
ddd55d50cc | ||
|
|
cb66b3d10f | ||
|
|
8743ddcbe5 | ||
|
|
23101e45f6 | ||
|
|
2289a8effa | ||
|
|
3c479ddb65 | ||
|
|
9d85931c9d | ||
|
|
58881af307 | ||
|
|
7373f81349 | ||
|
|
29f6bc8652 | ||
|
|
abf355b4d7 | ||
|
|
484cf2d1a5 | ||
|
|
e699bb23f1 | ||
|
|
badd850088 | ||
|
|
ffad91431a | ||
|
|
d0231f6ee3 | ||
|
|
b4c6deee78 | ||
|
|
900b4c607c | ||
|
|
9397114f56 | ||
|
|
16ba292bd0 | ||
|
|
032e14384f | ||
|
|
b79f6e0b1c | ||
|
|
3b0425c8a8 | ||
|
|
b043414add | ||
|
|
1e7432af39 | ||
|
|
b67872d946 | ||
|
|
99bfde84bf | ||
|
|
0bc132d7df | ||
|
|
28eba69900 | ||
|
|
1565126ffd | ||
|
|
0dbc9e90a3 | ||
|
|
9199cc9e4b | ||
|
|
7785fd277a | ||
|
|
851c18bc27 | ||
|
|
996d822393 | ||
|
|
0c97853554 | ||
|
|
fae64abf05 | ||
|
|
7330c20c6d | ||
|
|
e7a7c48c0a | ||
|
|
f231fdcb4d | ||
|
|
87823c091f | ||
|
|
36a62e7b1a | ||
|
|
859dc81805 | ||
|
|
409380a458 | ||
|
|
b0933d5c6f | ||
|
|
0bb41bf948 | ||
|
|
27910f25a1 | ||
|
|
602d52f178 | ||
|
|
7396e3994e | ||
|
|
8a82a50fc9 | ||
|
|
5c1141d1ce | ||
|
|
6b6dd73bc8 | ||
|
|
1a7e9e31a1 | ||
|
|
2f29b56e96 | ||
|
|
3bc149e3d5 | ||
|
|
eb8414bee9 | ||
|
|
ea98db4f53 | ||
|
|
ad3c06636b | ||
|
|
9ce4502961 | ||
|
|
a3cd84111f | ||
|
|
d5b2fc5609 | ||
|
|
83a30b187a | ||
|
|
943771a3f9 | ||
|
|
5b4117dadc | ||
|
|
6bc0481cdf | ||
|
|
4eebf0b685 |
114
Changelog.md
|
|
@ -1,3 +1,89 @@
|
|||
### 2026-09-29: RNS 1.5.5
|
||||
|
||||
This release adds live interface attach, detach and reload, as well as significant improvements to interface discovery. Auto-connecting interfaces is now also supported on Windows and macOS.
|
||||
|
||||
It also brings improvements to `rngit` and fixes discovered `I2PInterface` configuration snippets, along with a number of other bugs.
|
||||
|
||||
**Changes**
|
||||
- Added ability to `--attach`, `--detach` and `--reload` interfaces to the `rnstatus` utility
|
||||
- Added interface discovery auto-connect support on Windows and macOS
|
||||
- Added ability to download micron-converted versions of markdown files to the `rngit` page node
|
||||
- Added workdoc counts to filter scope links to the `rngit` page node
|
||||
- Added version info to `rnstatus` discovered interfaces output
|
||||
- Added `--show-stale` and `--show-unknown` options to `rnstatus`
|
||||
- Added sequential naming of auto-connected interfaces on announced name collisions
|
||||
- Added auto-connect implementation and version criteria filtering
|
||||
- Added configuration option for allowing auto-connects to discovered interfaces with missing version info
|
||||
- Improved sanitization of nonsensical interface discovery IFAC announce data
|
||||
- Fixed `I2PInterface` discovery config snippets not including `.b32.i2p`
|
||||
- Fixed timeout and response rejects not being processed for single-packet requests, by **JRG**
|
||||
- Fixed missing configurable property propagations on spawned interfaces, by **JRG**
|
||||
- Fixed ingress control producer comprehension at mid-watermark using incorrect variable name for interface type check, by **JRG**
|
||||
- Fixed instances of `announce_cap` property ensurance logic not storing announce cap value as a fraction, by **JRG**
|
||||
- Fixed a potential race condition in `LocalInterface` initialization.
|
||||
- Updated documentation and manual
|
||||
|
||||
**Verified Retrieval**
|
||||
You can retrieve and verify this release over Reticulum using the built-in `rngit release` utility. To retrieve only the installation `.whl` package, and the release manifest for future updates, you can use:
|
||||
|
||||
```sh
|
||||
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch "latest:rns-*.whl" --signer bc7291552be7a58f361522990465165c
|
||||
```
|
||||
|
||||
To download all artifacts, including the documentation and source archive, you can use the following command:
|
||||
|
||||
```sh
|
||||
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch latest:all --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 `rngit` or `rnid`. To perform an offline verification of all release artifacts using a manifest:
|
||||
|
||||
```sh
|
||||
rngit release rns_*.rsm verify --signer bc7291552be7a58f361522990465165c
|
||||
```
|
||||
|
||||
To verify release artifacts using individual `rsg` files, while also verifying the manifest itself, 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 rns_*.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-09-11: RNS 1.5.4
|
||||
|
||||
This release improves RNode BLE connectivity reliability on desktop operating systems.
|
||||
|
||||
**Changes**
|
||||
- Fixed RNode BLE device address acquisition on windows, by **Nickie Deuxyeux**
|
||||
- Fixed RNode BLE re-connection deadlock on desktop
|
||||
- Improved RNode BLE reconnect reliability
|
||||
|
||||
### 2026-09-10: RNS 1.5.3
|
||||
|
||||
This maintenance release adds functionality to, and fixes bugs in `rngit`, and includes a few other optimizations and improvements.
|
||||
|
||||
**Changes**
|
||||
- Added media request handler to `rngit`, for serving images to `nomadnet` clients
|
||||
- Added media conversion handler to `rngit`, auto-converting images to webp
|
||||
- Added importable helper method for using the `rngit` media converter in other programs
|
||||
- Added a `no_ident` template to the `rngit` page node
|
||||
- Added immediate activation of changed permissions with `rngit perms`
|
||||
- Added optimized HDLC framer
|
||||
- Fixed `rngit` repo admins not being able activate/complete workdocs from other users, by **Bergie**
|
||||
- Fixed a typo, I'm positively certain this is the last one
|
||||
|
||||
### 2026-08-28: RNS 1.5.2
|
||||
|
||||
This maintenance release fixes a regression in resource transfers that was introduced in 1.5.1, an `I2PInterface` bug, and sets dataplane control parameters to default values that are actually sensible. If you installed `1.5.1`, it's update time again, baby.
|
||||
|
||||
**Changes**
|
||||
- Tuned dataplane control parameters
|
||||
- Added example for blocking unidentified page node peers to `rngit` default config
|
||||
- Fixed regression in resource transfers causing some `rngit` file downloads to fail
|
||||
- Fixed keepalive frames being passed to transport core on `I2PInterface`
|
||||
|
||||
### 2026-08-28: RNS 1.5.1
|
||||
|
||||
This release focuses on dataplane control, memory bounding and transport throughput, with adaptive ingress and egress control for interfaces, a new (and very efficient) zero-copy coalescing transmit buffer, an optimized HDLC deframer, and a range of other improvements to the inbound packet processing paths. It also introduces support for live profiling, and various new diagnostics output in `rnstatus`, along with a number of bug fixes and general improvements.
|
||||
|
|
@ -44,34 +130,6 @@ This release focuses on dataplane control, memory bounding and transport through
|
|||
- Fixed a latent bug in the AES module, where an exception would not resolve it's exception description correctly, and instead raise another exception.
|
||||
- Removed dead Python 2 code from `umsgpack`
|
||||
|
||||
**Verified Retrieval**
|
||||
You can retrieve and verify this release over Reticulum using the built-in `rngit release` utility. To retrieve only the installation `.whl` package, and the release manifest for future updates, you can use:
|
||||
|
||||
```sh
|
||||
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch "latest:rns-*.whl" --signer bc7291552be7a58f361522990465165c
|
||||
```
|
||||
|
||||
To download all artifacts, including the documentation and source archive, you can use the following command:
|
||||
|
||||
```sh
|
||||
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch latest:all --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 `rngit` or `rnid`. To perform an offline verification of all release artifacts using a manifest:
|
||||
|
||||
```sh
|
||||
rngit release rns_*.rsm verify --signer bc7291552be7a58f361522990465165c
|
||||
```
|
||||
|
||||
To verify release artifacts using individual `rsg` files, while also verifying the manifest itself, 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 rns_*.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-08-22: RNS 1.5.0
|
||||
|
||||
This release significantly improves the core Transport handling of RNS, with a priority-based ingress queue backend, substantially improved efficiency of both data and management traffic handling, more efficient ingress/egress limiting, and a long list of other improvements and bugfixes.
|
||||
|
|
|
|||
|
|
@ -2,57 +2,66 @@
|
|||
|
||||
Welcome, and thank you for your interest in contributing to Reticulum!
|
||||
|
||||
Apart from writing code, there are many ways in which you can contribute. Before interacting with this community, read these short and simple guidelines.
|
||||
Before interacting with this community, read these short and simple guidelines.
|
||||
|
||||
If you didn't shit your pants after reading this document, you may well find that we are all really quite cuddly and nice to be around. Mostly.
|
||||
|
||||
## Expected Conduct
|
||||
|
||||
First and foremost, there is one simple requirement for taking part in this community: While we primarily interact virtually, your actions matter and have real consequences. Therefore: **Act like a responsible, civilized person** - especially in the face of disputes and heated disagreements. Speak your mind here; discussions are welcome. Just do so in the spirit of being face-to-face with everyone else. Thank you.
|
||||
First and foremost, there is one simple requirement for taking part in the development of this technology: While we primarily interact virtually, your actions matter and have real consequences. Therefore: **Act like a responsible, civilized person** - especially in the face of disputes and heated disagreements. Speak your mind here; discussions are welcome. Just do so in the spirit of being face-to-face with everyone else. Thank you.
|
||||
|
||||
In order to keep the discussion forums and issue trackers navigable and useful, the following types of posts will be deleted without notice:
|
||||
In order to keep the discourse navigable and useful, the following types of posts and inquiries will be deleted and ignored:
|
||||
|
||||
- Spam.
|
||||
- Questions that have already been adequately answered elsewhere. Use the search function.
|
||||
- Low-effort posts or comments that contain no actual information or useful content. This is not a tea-house.
|
||||
- Questions that have already been adequately answered elsewhere. Utilize search functions.
|
||||
- Low-effort posts or comments that contain no actual information or useful substance. [This is not a tea-house](./This%20Is%20Not%20a%20Teahouse.md).
|
||||
- Post or comments solely containing personal opinions or beliefs without adding anything to the discussion. Facebook and X exists.
|
||||
- Content that simply waste the developer's / maintainer's time with completely obvious "ideas", "insights" or "recommendations". Yes, we have *at least* 8 neurons ourselves.
|
||||
- Posts that fail to understand that developing a highly complex software project with a very small amount of resources and people takes time. Imagining perfection on our behalf is useless.
|
||||
|
||||
If you're new to the community and start out your engagement with any of the above transgressions, you will simply be banned without notice or explanation, and your post will be deleted.
|
||||
If you're new to the community and start out your engagement with any of the above transgressions, you will simply be banned permanently without notice or explanation, and any further communication from you will be ignored.
|
||||
|
||||
Except for the case of real, well-grounded, clearly documented, human-written security advisories, technical investigations or bug reports, if your inquiry or communication is not attached to a Reticulum Identity, expect it to be ignored.
|
||||
|
||||
If you find this "harsh", "unfair" or "unwelcoming", go somewhere else. This is not social club, but a work environment for the people contributing to the project.
|
||||
|
||||
## Asking Questions
|
||||
## Reporting Bugs
|
||||
|
||||
If you want to ask a question, **do not open an issue**. The issue tracker is used by people *working on Reticulum* to track bugs, issues and improvements. Instead, ask away on the [discussions](https://github.com/markqvist/Reticulum/discussions).
|
||||
|
||||
Do not post feature requests or general ideas on the issue tracker, or in direct messages to the primary developers. You are much more likely to get a response and start a constructive discussion by posting your ideas in the public channels created for these purposes.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
If you have found a bug or issue in this project, please report it using the [issue tracker](https://github.com/markqvist/Reticulum/issues). Be sure to include details on how to reproduce the bug.
|
||||
|
||||
Anything submitted to the issue tracker that does not follow these guidelines will be closed and removed without comments or explanation.
|
||||
If you have found a bug or issue in this project, please report it to the maintainer over LXMF, or if strictly not possible, over email. Detailed and serious bug reports can also be posted in the various on-network forums and RRC channels that the developers frequent. Due to the realities described in [This Is Not a Teahouse](./This%20Is%20Not%20a%20Teahouse.md), there is no way to report issues or request features over the internet.
|
||||
|
||||
## Writing Code
|
||||
|
||||
If you are interested in contributing code, fixing open issues or adding features, please coordinate the effort with the maintainer or one of the main developers **before** submitting a pull request. Before deciding to contribute, it is also a good idea to ensure your efforts are in alignment with the [Roadmap](./Roadmap.md) and current development focus.
|
||||
Patches and code contributions are *generally* only welcomed from developers already known to the community, with a proven track-record of competence, skill, careful and considerate design and responsible conduct. If we do not know you yet, but you have created something that you believe lives up to such standards, we are eager to hear from you.
|
||||
|
||||
Pull requests have a high chance of being accepted if they are:
|
||||
Current development priorities and work documents are not publicly available on the internet, but shared and coordinated directly between the developers over Reticulum itself, following the principles outlined in [Distributed Development](https://reticulum.network/manual/distributed.html).
|
||||
|
||||
- In alignment with the [Roadmap](./Roadmap.md) or solve an open issue or feature request
|
||||
- Sufficiently tested to work with all API functions, and pass the standard test suite
|
||||
- Functionally and conceptually complete and well-designed
|
||||
- Not simply formatting or code style changes
|
||||
- Well-documented
|
||||
## Commit Signing
|
||||
|
||||
Even new ideas and proposals that have not been approved by a maintainer, or fall outside the established roadmap, are *occasionally* accepted - if they possess the remaining of the above qualities. If not, they will be closed and removed without comments or explanation.
|
||||
All commits in Reticulum and related projects **must** be signed with a Reticulum identity. If you want your author credentials to be included directly in the commit log, ensure that you have [configured commit signing](https://reticulum.network/manual/git.html#commit-signing). Bare patches submitted manually or fetched remote commits without signatures are still very welcome, but we will review, squash merge and sign with the key of the maintainer that approved the changes. Attribution will be provided in the commit message instead (unless you wish to remain anonymous).
|
||||
|
||||
## Generative AI Policy
|
||||
## Machine Assistance & LLMs
|
||||
|
||||
Contributions written using large language models (LLMs) or other generative 'AI' programs are prohibited. LLMs produce errors so frequently and in a way that is so unlike human error that such issues are incredibly time-consuming to spot and fix. This is not a worthwhile tradeoff for Reticulum.
|
||||
For a highly complex, critical piece of cryptographically based networking infrastructure, we should not need to state the following, but alas, experience has proven otherwise. Therefore:
|
||||
|
||||
This applies to all Reticulum-related projects and documentation, as well as all submitted issues and discussion in official channels, except in cases where language translation and/or speech recogntion technologies are required for communication.
|
||||
**Including material generated using LLMs or other "AI" programs in Reticulum is, almost without exception, prohibited.**
|
||||
|
||||
Before even *thinking* about using a generative software system to "create" something for Reticulum, read [Brandolini's Reference](https://reticulum.network/manual/brandolinis.html), in full. If you do not have the capacity for that, you have failed the first requirement for contributing: Basic literacy.
|
||||
|
||||
*If* you are not already a known contributor with a proven record of being able to responsibly handle these tools, a **full** audit trail of all ML-utilizing workflows, including prompts, LLM reasoning traces, tool calls and outputs (that is, the *entire*, unaltered session log) will need to be provided with any proposed contribution. Without that, we will not even *consider* looking at it.
|
||||
|
||||
This applies to all Reticulum-related projects and documentation, as well as all submitted issues, reports and discussion, **except** of course in cases where language translation, speech recogntion, or other assistive technologies are required.
|
||||
|
||||
In **all** circumstances, clear disclosure is **mandatory**. Any piece of material, file, code snippet, method, function or similar that was generated with machine assistance must be prepended with a disclosure header or comment that states the framework, exact model used and reviewer, such as:
|
||||
|
||||
```python
|
||||
# Generated with lc/deepseek_v4_flash_q8_k_xl
|
||||
# Reviewed and corrected by Mark Qvist
|
||||
```
|
||||
|
||||
Additionally, any explanatory comments or doc-strings generated by software must be manually re-written in clear, concise language by the reviewer stated in the disclosure header.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
By contributing code to this project, you agree that copyright for the code is transferred to the Reticulum maintainers and that the code is irrevocably placed under the [Reticulum License](./LICENSE).
|
||||
|
||||
For a more in-depth elaboration on the current CLA position, see [this post](https://rns.recipes/forum/general/brandolinis-reference-or-alternatively-agentsmd?page=1#post-3002).
|
||||
|
|
|
|||
266
README.md
|
|
@ -1,40 +1,32 @@
|
|||
Reticulum Network Stack <img align="right" src="https://static.pepy.tech/personalized-badge/rns?period=month&units=international_system&left_color=grey&right_color=blue&left_text=Installs/month" style="padding-left:10px"/><a href="https://github.com/markqvist/Reticulum/actions/workflows/build.yml"><img align="right" src="https://github.com/markqvist/Reticulum/actions/workflows/build.yml/badge.svg"/></a>
|
||||
==========
|
||||
|
||||
> [!WARNING]
|
||||
> A significant number of hastily launched, incorrect, dubious, directly harmful and, in most cases, entirely LLM-generated fakes of Reticulum implementations, "documentation", "guides" and user-facing applications are currently being marketed under polished, but ultimately misleading or outright deceptive claims.
|
||||
>
|
||||
> For more detailed information, see the relevant sections of the [Brandolini's Reference](https://reticulum.network/manual/brandolinis.html) chapter of the manual. Known good community implementations are listed in the [Community Implementations](#community-implementations) section of this readme. For software and projects recognized by the Reticulum community, see the [Programs Using Reticulum](https://reticulum.network/manual/software.html) chapter of the manual.
|
||||
>
|
||||
> Machine-generated content derived from the RNS source code or documentation (such as "implementations", "guides" for sale, et cetera), are in several, well-documented cases violating the open source licenses that granted rights of copying and distribution.
|
||||
>
|
||||
> Any claimed assertion of copyright or grant of license (Apache, GPL, et cetera) that such projects are making are [void grants](https://reticulum.network/manual/brandolinis.html#void-grants-legal-foundations-of-machine-generated-code). Both the violators themselves, and all downstream projects using the infringing derivatives, are **directly** and personally liable for the legal consequences.
|
||||
>
|
||||
> Do **not** use these "projects", they are a hazard to the entire ecosystem we have been carefully building over the last ten years.
|
||||
|
||||
<p align="center"><img width="200" src="https://raw.githubusercontent.com/markqvist/Reticulum/master/docs/source/graphics/rns_logo_512.png"></p>
|
||||
|
||||
*This repository is [a public mirror](./MIRROR.md). All development is happening elsewhere.*
|
||||
|
||||
To understand the foundational philosophy and goals of this system, read the [Zen of Reticulum](Zen%20of%20Reticulum.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.
|
||||
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.
|
||||
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.
|
||||
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 3.
|
||||
|
||||
## Read The Manual
|
||||
The full documentation for Reticulum is available at [markqvist.github.io/Reticulum/manual/](https://markqvist.github.io/Reticulum/manual/).
|
||||
|
|
@ -78,12 +70,20 @@ For more info, see [reticulum.network](https://reticulum.network/) and [the FAQ
|
|||
- Low cost of keeping links open at only 0.44 bits per second
|
||||
- Reliable sequential delivery with Channel and Buffer mechanisms
|
||||
|
||||
## Community Resources
|
||||
|
||||
The references listed here are not run, managed or moderated by the Reticulum project or its maintainer. They are provided by dedicated, independent volunteers in the community, for the benefit of anyone interested in learning more about Reticulum.
|
||||
|
||||
- [Reticulum Community Wiki](https://reticulum.miraheze.org/wiki)
|
||||
- [RNS Recipes](https://rns.recipes/)
|
||||
- [RNS Moscow](https://rns.moscow/)
|
||||
- [RMAP.world](https://rmap.world/)
|
||||
|
||||
If you find anything that is no longer accurate, incorrect or simply not quite up to date, do not demand or complain towards the maintainers of these resources. Instead, take the task upon yourself to help out and improve things by working on, and offering the contributions you would like to see, like the creators of the above did themselves.
|
||||
|
||||
## Reference Implementation
|
||||
|
||||
The Python code in this repository is the Reference Implementation of Reticulum.
|
||||
The Reticulum Protocol is defined entirely and authoritatively by this reference
|
||||
implementation, and its associated manual. It is maintained by Mark Qvist,
|
||||
identified by the Reticulum Identity `<bc7291552be7a58f361522990465165c>`.
|
||||
The Python code in this repository is the Reference Implementation of Reticulum. The Reticulum Protocol is defined entirely and authoritatively by this reference implementation, and its associated manual. It is maintained by Mark Qvist, identified by the Reticulum Identity `<bc7291552be7a58f361522990465165c>`.
|
||||
|
||||
No second-order protocol specification derivatives currently do, or will ever exist. You can't get a better specification than one that is executable, functional, testable, deployed and operating 24/7 across the planet.
|
||||
|
||||
|
|
@ -91,18 +91,39 @@ There is not, and never will be a separate, derived "formal spec", "RFC" or anyt
|
|||
|
||||
**The reference implementation *is* the final, authoritative specification.**
|
||||
|
||||
Compatibility with the Reticulum Protocol is defined as having full interoperability,
|
||||
and sufficient functional parity with this reference implementation. Any specific protocol
|
||||
implementation that achieves this is Reticulum. Any that does not is not Reticulum.
|
||||
Compatibility with the Reticulum Protocol is defined as having full interoperability, and sufficient functional parity with this reference implementation. Any specific protocol implementation that achieves this is Reticulum. Any that does not is not Reticulum.
|
||||
|
||||
The reference implementation is licensed under the Reticulum License.
|
||||
|
||||
The Reticulum Protocol was dedicated to the Public Domain in 2016.
|
||||
|
||||
## Community Implementations
|
||||
|
||||
The implementations listed here have demonstrated wire-compatibility, sufficient feature parity, sensible security and development practices, long-term commitment and care for implementation quality. Additionally, they have a track record of at least 18 months of active development.
|
||||
|
||||
Most importantly, their developers and maintainers are recognized by the community to actually know what they are doing, and as having a commitment to improving the overall Reticulum ecosystem.
|
||||
|
||||
- [Chad Atterman's](https://github.com/attermann) [microReticulum](https://github.com/attermann/microReticulum/) is a C++ implementation targeting 32-bit micro-controllers.
|
||||
- [Quad4's](https://quad4.io/) [Reticulum-Go](https://reticulum-go.quad4.io/) is a Go implementation targeting both desktop, mobile and embedded systems.
|
||||
|
||||
*If you are the maintainer of an implementation not listed above, and believe that it deserves inclusion by following the same standards of rigor and commitment, your proposal will be welcomed.*
|
||||
|
||||
---
|
||||
|
||||
**Warning!** A significant number of hastily launched, incorrect, dubious, directly harmful and, in most cases, entirely LLM-generated fakes of Reticulum implementations (and user facing applications) are currently being marketed and pushed under polished, but ultimately misleading or outright deceptive claims. Most of these are either quick attention or money grabs, completely unreviewed, incompatible, without maintenance or human oversight, actively trying to fragment the community, or a combination thereof.
|
||||
|
||||
---
|
||||
|
||||
For more extensive information on this topic, read the [Brandolini's Reference](https://reticulum.network/manual/brandolinis.html) chapter of the manual (for the legal foundations, see [Void Grants](https://reticulum.network/manual/brandolinis.html#void-grants-legal-foundations-of-machine-generated-code) specifically). The chapter is very long, very detailed, and is the polite, measured analysis, intended as an evolving, sectioned reference on these issues.
|
||||
|
||||
If you would rather just have the short, unfiltered and quite angry version, which consists of an enacted response to one particular manifestation of the phenomenon as a whole, used as an example, you can read [Yes, I am Angry](docs/history/2026_09_18_Yes_I_am_Angry.md). It gets the same point across, in a very different language, if you can handle that sort of communication.
|
||||
|
||||
The core Reticulum maintainers and developers **strongly advise against using any such "implementation"**. The [deceitful and self-serving behavior](https://reticulum.network/manual/brandolinis.html#a-persistent-but-fragile-triad) of a small number of either malintented, attention-seeking or incompetent individuals has caused significant [damage and waste of time and resources](https://reticulum.network/manual/brandolinis.html#intent-consequence) for both developers, node operators, users and the community and Reticulum project at large.
|
||||
|
||||
We kindly, but urgently ask all users to excercise caution, common sense and healthy skepticism when [evaluating](https://reticulum.network/manual/brandolinis.html#evaluating-a-reticulum-implementation) the potential use of any implementation not listed above; and to accept that **choosing to do so regardless translates the responsibility for potential damage from an irresponsible or malicious individual onto yourself**.
|
||||
|
||||
## 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](https://reticulum.network/manual/software.html) 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
|
||||
- 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 `rnphone` program, that can be used to build hardware telephones.
|
||||
|
|
@ -110,50 +131,27 @@ section of the manual, or the following resources:
|
|||
- 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.
|
||||
- [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.
|
||||
- You can use the included [rnsh](https://reticulum.network/manual/using.html#the-rnsh-utility) program to establish remote shell sessions over Reticulum.
|
||||
- The included [rngit](https://reticulum.network/manual/git.html) system provides a full solution for managing, hosting, collaborating on and interacting with Git repositories 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.
|
||||
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](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, WiFi or Bluetooth 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.
|
||||
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.
|
||||
|
||||
As an example, it's possible to set up a Raspberry Pi connected to both a LoRa
|
||||
radio, a packet radio TNC and a WiFi network. Once the interfaces are
|
||||
configured, Reticulum will take care of the rest, and any device on the WiFi
|
||||
network can communicate with nodes on the LoRa and packet radio sides of the
|
||||
network, and vice versa.
|
||||
As an example, it's possible to set up a Raspberry Pi connected to both a LoRa radio, a packet radio TNC and a WiFi network. Once the interfaces are configured, Reticulum will take care of the rest, and any device on the WiFi network can communicate with nodes on the LoRa and packet radio sides of the network, and vice versa.
|
||||
|
||||
## 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](https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
|
||||
|
||||
To simply install Reticulum and related utilities on your system, the easiest way is via `pip`.
|
||||
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 `pip`. 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).
|
||||
|
||||
```bash
|
||||
pip install rns
|
||||
```
|
||||
|
||||
If you are using an operating system that blocks normal user package installation via `pip`,
|
||||
you can return `pip` to normal behaviour by editing the `~/.config/pip/pip.conf` file,
|
||||
and adding the following directive in the `[global]` section:
|
||||
If you are using an operating system that blocks normal user package installation via `pip`, you can return `pip` to normal behaviour by editing the `~/.config/pip/pip.conf` file, and adding the following directive in the `[global]` section:
|
||||
|
||||
```text
|
||||
[global]
|
||||
|
|
@ -166,23 +164,14 @@ Alternatively, you can use the `pipx` tool to install Reticulum in an isolated e
|
|||
pipx install rns
|
||||
```
|
||||
|
||||
When first started, Reticulum will create a default configuration file,
|
||||
providing basic connectivity to other Reticulum peers that might be locally
|
||||
reachable. The default config file contains a few examples, and references for
|
||||
creating a more complex configuration.
|
||||
When first started, Reticulum will create a default configuration file, providing basic connectivity to other Reticulum peers that might be locally reachable. The default config file contains a few examples, and references for creating a more complex configuration.
|
||||
|
||||
If you have an old version of `pip` on your system, you may need to upgrade it first with `pip install pip --upgrade`. If you no not already have `pip` installed, you can install it using the package manager of your system with `sudo apt install python3-pip` 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](https://markqvist.github.io/Reticulum/manual/interfaces.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
|
||||
|
||||
## 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](https://markqvist.github.io/Reticulum/manual/using.html#included-utility-programs) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
|
||||
|
||||
- The system daemon `rnsd` for running Reticulum as an always-available service
|
||||
- An interface status utility called `rnstatus`, that displays information about interfaces
|
||||
|
|
@ -195,19 +184,13 @@ section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/)
|
|||
- The `rngit` program provides a full multi-repository Git node for serving repositories over Reticulum
|
||||
- The included `git-remote-rns` helper allows you to interact with Git repositories over Reticulum
|
||||
|
||||
All tools, including `rnx` and `rncp`, 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.
|
||||
All tools, including `rnx` and `rncp`, 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](https://markqvist.github.io/Reticulum/manual/interfaces.html#custom-interfaces).
|
||||
|
||||
Pull requests for custom interfaces are gratefully accepted, provided they are
|
||||
generally useful and well-tested in real-world usage.
|
||||
Contributions of custom interfaces are gratefully accepted, provided they are generally useful and well-tested in real-world usage.
|
||||
|
||||
Currently, the following built-in interfaces are supported:
|
||||
|
||||
|
|
@ -222,62 +205,42 @@ 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.
|
||||
*All performance numbers here assume `rnsd` running in fully interpreted Python mode, limited to a single CPU core.*
|
||||
|
||||
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.
|
||||
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 100 bits per second, to 10 gigabits per second on normal hardware.
|
||||
|
||||
Currently, the on-network, usable performance envelope is approximately 150 bits per second to 1 gigabit per second on modest hardware. Real-world performance will vary widely with the speed of your system, availability of cryptographic hardware acceleration, and other factors.
|
||||
|
||||
Raw routing throughput for a transport node hosted on a Raspberry Pi 5 is approximately 5 gigabits per second. Systems with modern crypto hardware acceleration can achieve transport throughput of 15 gigabits per second and higher.
|
||||
|
||||
While the reference implementation is perfectly capable of reaching 100+ Gbps, by utilizing multiple CPU cores and vectorized transport, performance beyond the current level is not higly prioritised, since RNS is already able to saturate available physical medium bandwidth in almost all real-world situations.
|
||||
|
||||
The primary objectives for RNS are *reliability*, *correctness*, *security*, *proper traffic management* and *actually practical performance*, not hollow on-paper "benchmarks" with little to no bearing on real-world usability.
|
||||
|
||||
All alternative-implementation maintainers are *highly* encouraged to adopt a similar approach, and especially to focus on getting their implementations working properly and reliably before publishing empirically dubious or factually wrong performance claims.
|
||||
|
||||
## 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.
|
||||
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 `rns` package requires only two external dependencies, listed
|
||||
below. Almost all systems and distributions have readily available packages for
|
||||
these dependencies, and when the `rns` package is installed with `pip`, they
|
||||
will be downloaded and installed as well.
|
||||
The installation of the default `rns` package requires only two external dependencies, listed below. Almost all systems and distributions have readily available packages for these dependencies, and when the `rns` package is installed with `pip`, they will be downloaded and installed as well.
|
||||
|
||||
- [PyCA/cryptography](https://github.com/pyca/cryptography)
|
||||
- [pyserial](https://github.com/pyserial/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 `rnspure` package instead, which require no external
|
||||
dependencies for installation. Please note that the contents of the `rns` and
|
||||
`rnspure` packages are *identical*. The only difference is that the `rnspure`
|
||||
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 `rnspure` package instead, which require no external dependencies for installation. Please note that the contents of the `rns` and `rnspure` packages are *identical*. The only difference is that the `rnspure` 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 `rnspure` 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 [pyserial](https://github.com/pyserial/pyserial), it is perfectly possible to do so using the `rnspure` 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 `rnspure` 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 `rnspure` 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.
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
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 [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.
|
||||
|
||||
## 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.*
|
||||
|
|
@ -302,17 +265,11 @@ For this to be possible, I need your help. Please support the continued developm
|
|||
- 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.
|
||||
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 **is not** 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
|
||||
|
|
@ -327,13 +284,7 @@ intentionally compromised or weakened clone. The utilised primitives are:
|
|||
- SHA-256
|
||||
- SHA-512
|
||||
|
||||
In the default installation configuration, the `X25519`, `Ed25519`,
|
||||
and `AES-256-CBC` primitives are provided by [OpenSSL](https://www.openssl.org/)
|
||||
(via the [PyCA/cryptography](https://github.com/pyca/cryptography) package).
|
||||
The hashing functions `SHA-256` and `SHA-512` are provided by the standard
|
||||
Python [hashlib](https://docs.python.org/3/library/hashlib.html). The `HKDF`,
|
||||
`HMAC`, `Token` primitives, and the `PKCS7` padding function are always
|
||||
provided by the following internal implementations:
|
||||
In the default installation configuration, the `X25519`, `Ed25519`, and `AES-256-CBC` primitives are provided by [OpenSSL](https://www.openssl.org/) (via the [PyCA/cryptography](https://github.com/pyca/cryptography) package). The hashing functions `SHA-256` and `SHA-512` are provided by the standard Python [hashlib](https://docs.python.org/3/library/hashlib.html). The `HKDF`, `HMAC`, `Token` primitives, and the `PKCS7` padding function are always provided by the following internal implementations:
|
||||
|
||||
- [HKDF.py](RNS/Cryptography/HKDF.py)
|
||||
- [HMAC.py](RNS/Cryptography/HMAC.py)
|
||||
|
|
@ -341,39 +292,18 @@ provided by the following internal implementations:
|
|||
- [PKCS7.py](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 `rnspure` package instead
|
||||
of the normal `rns` 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 `rnspure` package instead of the normal `rns` 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.
|
||||
Having been in development for ten, and wider deployment for around five years, Reticulum is relatively young software, and should be considered as such. While it has been carefully built with cryptography best-practices very foremost in mind, there could very well still be privacy or security breaking bugs.
|
||||
|
||||
## 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.
|
||||
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:
|
||||
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*
|
||||
|
|
|
|||
18
README.mu
|
|
@ -4,6 +4,8 @@ To understand the foundational philosophy and goals of this system, read the `_`
|
|||
|
||||
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.
|
||||
|
||||
`(RNS Logo`w=18`a=c`:/media/reticulum/reticulum/HEAD/docs/source/graphics/rns_logo_256.webp)
|
||||
|
||||
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.
|
||||
|
|
@ -170,9 +172,19 @@ Currently, the following built-in interfaces are supported:
|
|||
|
||||
>> 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.
|
||||
`*All performance numbers here assume `B333rnsd`b running in fully interpreted Python mode, limited to a single CPU core.`*
|
||||
|
||||
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.
|
||||
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 100 bits per second, to 10 gigabits per second on normal hardware.
|
||||
|
||||
Currently, the on-network, usable performance envelope is approximately 150 bits per second to 1 gigabit per second on modest hardware. Real-world performance will vary widely with the speed of your system, availability of cryptographic hardware acceleration, and other factors.
|
||||
|
||||
Raw routing throughput for a transport node hosted on a Raspberry Pi 5 is approximately 4 gigabits per second. Systems with modern crypto hardware acceleration can achieve transport throughput of 15 gigabits per second and higher.
|
||||
|
||||
While the reference implementation is perfectly capable of reaching 100+ Gbps, by utilizing multiple CPU cores and vectorized transport, performance beyond the current level is not higly prioritised, since RNS is already able to saturate available physical medium bandwidth in almost all real-world situations.
|
||||
|
||||
The primary objectives for RNS are `*reliability`*, `*correctness`*, `*security`*, `*proper traffic management`* and `*actually practical performance`*, not hollow on-paper "benchmarks" with little to no bearing on real-world usability.
|
||||
|
||||
All alternative-implementation maintainers are `*highly`* encouraged to adopt a similar approach, and especially to focus on getting their implementations working properly before publishing empirically dubious or factually wrong performance claims.
|
||||
|
||||
>> Current Status
|
||||
|
||||
|
|
@ -256,7 +268,7 @@ Please note that by default, installing Reticulum will `!require`! OpenSSL and P
|
|||
|
||||
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.
|
||||
Having been in development for ten, and wider deployment for around five years, Reticulum is relatively young software, and should be considered as such. While it has been carefully built with cryptography best-practices very foremost in mind, there could very well still be privacy or security breaking bugs.
|
||||
|
||||
>> Acknowledgements & Credits
|
||||
|
||||
|
|
|
|||
219
RNS/Discovery.py
|
|
@ -1,3 +1,33 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import os
|
||||
import re
|
||||
import RNS
|
||||
|
|
@ -204,8 +234,8 @@ class InterfaceAnnouncer():
|
|||
info[MODULATION] = self.sanitize(interface.discovery_modulation)
|
||||
|
||||
if interface.discovery_publish_ifac == True:
|
||||
info[IFAC_NETNAME] = self.sanitize(interface.ifac_netname)
|
||||
info[IFAC_NETKEY] = self.sanitize(interface.ifac_netkey)
|
||||
if interface.ifac_netname: info[IFAC_NETNAME] = self.sanitize(interface.ifac_netname)
|
||||
if interface.ifac_netkey: info[IFAC_NETKEY] = self.sanitize(interface.ifac_netkey)
|
||||
|
||||
packed = msgpack.packb(info)
|
||||
infohash = RNS.Identity.full_hash(packed)
|
||||
|
|
@ -315,6 +345,8 @@ class InterfaceAnnounceHandler:
|
|||
if INTERFACE_TYPE in unpacked:
|
||||
interface_type = unpacked[INTERFACE_TYPE]
|
||||
name = self.sanitize_name(unpacked[NAME])
|
||||
impl_name = unpacked[TRANSPORT_IMPL] if TRANSPORT_IMPL in unpacked else None
|
||||
impl_version = unpacked[TRANSPORT_VERS] if TRANSPORT_VERS in unpacked else None
|
||||
|
||||
if type(unpacked[TRANSPORT]) != bool: raise ValueError("Invalid data in transport field of announce")
|
||||
if type(unpacked[LATITUDE]) not in [type(None), float]: raise ValueError("Invalid data in latitude field of announce")
|
||||
|
|
@ -329,6 +361,8 @@ class InterfaceAnnounceHandler:
|
|||
raise ValueError("Invalid data in reachable_on field of announce")
|
||||
|
||||
info = {"type": interface_type,
|
||||
"impl_name": impl_name,
|
||||
"version": impl_version,
|
||||
"transport": unpacked[TRANSPORT],
|
||||
"name": name or f"Discovered {interface_type}",
|
||||
"received": time.time(),
|
||||
|
|
@ -341,8 +375,8 @@ class InterfaceAnnounceHandler:
|
|||
"longitude": unpacked[LONGITUDE],
|
||||
"height": unpacked[HEIGHT]}
|
||||
|
||||
if IFAC_NETNAME in unpacked: info["ifac_netname"] = str(unpacked[IFAC_NETNAME])
|
||||
if IFAC_NETKEY in unpacked: info["ifac_netkey"] = str(unpacked[IFAC_NETKEY])
|
||||
if IFAC_NETNAME in unpacked and unpacked[IFAC_NETNAME] and type(unpacked[IFAC_NETNAME]) == str: info["ifac_netname"] = str(unpacked[IFAC_NETNAME])
|
||||
if IFAC_NETKEY in unpacked and unpacked[IFAC_NETKEY] and type(unpacked[IFAC_NETKEY]) == str: info["ifac_netkey"] = str(unpacked[IFAC_NETKEY])
|
||||
|
||||
if interface_type in ["BackboneInterface", "TCPServerInterface"]:
|
||||
backbone_support = not RNS.vendor.platformutils.is_windows() and not RNS.vendor.platformutils.is_darwin()
|
||||
|
|
@ -371,7 +405,7 @@ class InterfaceAnnounceHandler:
|
|||
cfg_netname_str = f"\n network_name = {cfg_netname}" if cfg_netname else ""
|
||||
cfg_netkey_str = f"\n passphrase = {cfg_netkey}" if cfg_netkey else ""
|
||||
cfg_identity_str = f"\n transport_identity = {cfg_identity}"
|
||||
info["config_entry"] = f"[[{cfg_name}]]\n type = I2PInterface\n enabled = yes\n peers = {cfg_remote}{cfg_identity_str}{cfg_netname_str}{cfg_netkey_str}"
|
||||
info["config_entry"] = f"[[{cfg_name}]]\n type = I2PInterface\n enabled = yes\n peers = {cfg_remote}.b32.i2p{cfg_identity_str}{cfg_netname_str}{cfg_netkey_str}"
|
||||
|
||||
if interface_type == "RNodeInterface":
|
||||
info["frequency"] = unpacked[FREQUENCY]
|
||||
|
|
@ -446,13 +480,17 @@ class InterfaceDiscovery():
|
|||
STATUS_UNKNOWN = 100
|
||||
STATUS_AVAILABLE = 1000
|
||||
STATUS_CODE_MAP = {"available": STATUS_AVAILABLE, "unknown": STATUS_UNKNOWN, "stale": STATUS_STALE}
|
||||
AUTOCONNECT_TYPES = ["BackboneInterface", "TCPServerInterface"]
|
||||
AUTOCONNECT_TYPES = ["BackboneInterface"]
|
||||
DISCOVERABLE_TYPES = ["BackboneInterface", "TCPServerInterface", "I2PInterface", "RNodeInterface", "WeaveInterface", "KISSInterface"]
|
||||
|
||||
AUTOCONNECT_IMPLS = ["RNS"]
|
||||
AUTOCONNECT_MIN_V = "1.5.2"
|
||||
|
||||
AC_TRANSPORT_MODE = RNS.Interfaces.Interface.Interface.MODE_GATEWAY
|
||||
AC_GRAVITY = 0
|
||||
|
||||
discovery_lock = Lock()
|
||||
autoconnect_lock = Lock()
|
||||
|
||||
def __init__(self, required_value=InterfaceAnnouncer.DEFAULT_STAMP_VALUE, callback=None, discover_interfaces=True):
|
||||
if not required_value: required_value = InterfaceAnnouncer.DEFAULT_STAMP_VALUE
|
||||
|
|
@ -498,7 +536,16 @@ class InterfaceDiscovery():
|
|||
should_remove = False
|
||||
heard_delta = now-info["last_heard"]
|
||||
info["name"] = InterfaceAnnounceHandler.sanitize_name(info["name"])
|
||||
|
||||
|
||||
# TODO: Temporary measure to sanitize invalidly persisted "None" values
|
||||
# for incorrect IFAC parameters published due to unguarded nonsensical
|
||||
# node-side configuration. Potentially remove once nodes upgrade to RNS
|
||||
# versions that guard against the configuration mismatch.
|
||||
try:
|
||||
if "ifac_netname" in info and info["ifac_netname"] == "None": info.pop("ifac_netname")
|
||||
if "ifac_netkey" in info and info["ifac_netkey"] == "None": info.pop("ifac_netkey")
|
||||
except Exception as e: RNS.log(f"Error while sanitizing discovery info: {e}", RNS.LOG_ERROR)
|
||||
|
||||
if heard_delta > self.THRESHOLD_REMOVE: should_remove = True
|
||||
elif not "transport_id" in info or not info["transport_id"]: should_remove = True
|
||||
elif not "network_id" in info or not info["network_id"]: should_remove = True
|
||||
|
|
@ -544,11 +591,13 @@ class InterfaceDiscovery():
|
|||
interface_type = info["type"]
|
||||
discovery_hash = info["discovery_hash"]
|
||||
discovered_type = info["type"]
|
||||
has_impl_info = "impl_name" in info and "version" in info and info["impl_name"] and info["version"]
|
||||
version_str = f" ({info['impl_name']} {info['version']})" if has_impl_info else " (unknown implementation)"
|
||||
if not discovered_type in self.DISCOVERABLE_TYPES: return
|
||||
hops = info["hops"]; ms = "" if hops == 1 else "s"
|
||||
filename = RNS.hexrep(discovery_hash, delimit=False)
|
||||
filepath = os.path.join(self.storagepath, filename)
|
||||
RNS.log(f"Discovered {interface_type} {hops} hop{ms} away with stamp value {value}: {name}", RNS.LOG_DEBUG)
|
||||
RNS.log(f"Discovered {interface_type}{version_str} {hops} hop{ms} away with stamp value {value}: {name}", RNS.LOG_DEBUG)
|
||||
with self.discovery_lock:
|
||||
if not os.path.isfile(filepath):
|
||||
try:
|
||||
|
|
@ -616,22 +665,27 @@ class InterfaceDiscovery():
|
|||
autoconnected_interfaces = self.autoconnect_count()
|
||||
for interface in self.monitored_interfaces:
|
||||
try:
|
||||
if interface.online:
|
||||
online_interfaces += 1
|
||||
if hasattr(interface, "autoconnect_down") and interface.autoconnect_down != None:
|
||||
RNS.log(f"Auto-discovered interface {interface} reconnected")
|
||||
interface.autoconnect_down = None
|
||||
if not interface in RNS.Transport.interfaces:
|
||||
RNS.log(f"A monitored auto-connected interface was manually detached, removing from monitoring", RNS.LOG_DEBUG)
|
||||
detached_interfaces.append(interface)
|
||||
|
||||
else:
|
||||
if not hasattr(interface, "autoconnect_down") or interface.autoconnect_down == None:
|
||||
RNS.log(f"Auto-discovered interface {interface} disconnected", RNS.LOG_DEBUG)
|
||||
interface.autoconnect_down = time.time()
|
||||
if interface.online:
|
||||
online_interfaces += 1
|
||||
if hasattr(interface, "autoconnect_down") and interface.autoconnect_down != None:
|
||||
RNS.log(f"Auto-discovered interface {interface} reconnected")
|
||||
interface.autoconnect_down = None
|
||||
|
||||
else:
|
||||
down_for = time.time()-interface.autoconnect_down
|
||||
if down_for >= self.detach_threshold:
|
||||
RNS.log(f"Auto-discovered interface {interface} has been down for {RNS.prettytime(down_for)}, detaching", RNS.LOG_DEBUG)
|
||||
detached_interfaces.append(interface)
|
||||
if not hasattr(interface, "autoconnect_down") or interface.autoconnect_down == None:
|
||||
RNS.log(f"Auto-discovered interface {interface} disconnected", RNS.LOG_DEBUG)
|
||||
interface.autoconnect_down = time.time()
|
||||
|
||||
else:
|
||||
down_for = time.time()-interface.autoconnect_down
|
||||
if down_for >= self.detach_threshold:
|
||||
RNS.log(f"Auto-discovered interface {interface} has been down for {RNS.prettytime(down_for)}, detaching", RNS.LOG_DEBUG)
|
||||
detached_interfaces.append(interface)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while checking auto-connected interface state for {interface}: {e}", RNS.LOG_ERROR)
|
||||
|
|
@ -665,8 +719,10 @@ class InterfaceDiscovery():
|
|||
RNS.log(f"Error while de-registering auto-connected interface from transport: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def teardown_interface(self, interface):
|
||||
interface.detach()
|
||||
RNS.Transport.remove_interface(interface)
|
||||
if interface in RNS.Transport.interfaces:
|
||||
was_detached = RNS.Reticulum.get_instance()._detach_interface(interface.name, internal_forced=True)
|
||||
if not was_detached: RNS.log(f"Detach of auto-connected interface {interface} did not succeed", RNS.LOG_ERROR)
|
||||
|
||||
if interface in self.monitored_interfaces: self.monitored_interfaces.remove(interface)
|
||||
|
||||
def autoconnect_count(self):
|
||||
|
|
@ -713,6 +769,26 @@ class InterfaceDiscovery():
|
|||
|
||||
return exists
|
||||
|
||||
def autoconnect_interface_name(self, name):
|
||||
names = {interface.name for interface in RNS.Transport.interfaces}
|
||||
if not name in names: return name
|
||||
else:
|
||||
n = 2; seq_name = f"{name} ({n})"
|
||||
while seq_name in names:
|
||||
n += 1; seq_name = f"{name} ({n})"
|
||||
|
||||
return seq_name
|
||||
|
||||
def autoconnect_qualified(self, info):
|
||||
if RNS.Reticulum.should_autoconnect_unverified_implementations(): return True
|
||||
if not "impl_name" in info: return False
|
||||
if not info["impl_name"] in self.AUTOCONNECT_IMPLS: return False
|
||||
if not "version" in info or not info["version"]: return False
|
||||
|
||||
version = version_tuple(info["version"])
|
||||
if version == None: return False
|
||||
return version >= version_tuple(self.AUTOCONNECT_MIN_V)
|
||||
|
||||
def autoconnect(self, info):
|
||||
try:
|
||||
if RNS.Reticulum.should_autoconnect_discovered_interfaces():
|
||||
|
|
@ -720,17 +796,16 @@ class InterfaceDiscovery():
|
|||
if autoconnected_count < RNS.Reticulum.max_autoconnected_interfaces():
|
||||
interface_type = info["type"]
|
||||
if interface_type in self.AUTOCONNECT_TYPES:
|
||||
if not self.autoconnect_qualified(info):
|
||||
impl = f"{info['impl_name']} {info['version']}" if ("impl_name" in info and "version" in info and info["impl_name"] and info["version"]) else "unknown implementation"
|
||||
RNS.log(f"Not auto-connecting discovered {interface_type} {info['name']} ({impl}), auto-connect criteria not satisfied", RNS.LOG_DEBUG)
|
||||
return
|
||||
|
||||
endpoint_hash = self.endpoint_hash(info)
|
||||
exists = self.interface_exists(info)
|
||||
|
||||
if exists: RNS.log(f"Discovered {interface_type} already exists, not auto-connecting", RNS.LOG_DEBUG)
|
||||
else:
|
||||
if interface_type == "TCPClientInterface":
|
||||
RNS.log(f"Your operating system does not support the Backbone interface type, and must degrade to using TCPClientInterface instead", RNS.LOG_WARNING)
|
||||
RNS.log(f"Auto-connecting discovered TCPClient interfaces is not yet implemented, aborting auto-connect", RNS.LOG_WARNING)
|
||||
RNS.log(f"You can obtain the configuration entry and add this interface manually instead using rnstatus -D", RNS.LOG_WARNING)
|
||||
return
|
||||
|
||||
if interface_type == "I2PInterface":
|
||||
RNS.log(f"Auto-connecting discovered I2P interfaces is not yet implemented, aborting auto-connect", RNS.LOG_DEBUG)
|
||||
RNS.log(f"You can obtain the configuration entry and add this interface manually instead using rnstatus -D", RNS.LOG_DEBUG)
|
||||
|
|
@ -748,35 +823,57 @@ class InterfaceDiscovery():
|
|||
RNS.log(f"Not auto-connecting discovered interface with invalid IP address: {info['reachable_on']}", RNS.LOG_DEBUG)
|
||||
return
|
||||
|
||||
interface_name = info["name"]
|
||||
config_entry = info["config_entry"]
|
||||
interface_config = {}
|
||||
interface_config["name"] = f"{interface_name}"
|
||||
ifac_netname = info["ifac_netname"] if "ifac_netname" in info else None
|
||||
ifac_netkey = info["ifac_netkey"] if "ifac_netkey" in info else None
|
||||
interface = None
|
||||
with self.autoconnect_lock:
|
||||
if self.interface_exists(info): return
|
||||
backbone_support = (not RNS.vendor.platformutils.is_windows() and not RNS.vendor.platformutils.is_darwin())
|
||||
|
||||
if interface_type == "BackboneInterface":
|
||||
from RNS.Interfaces import BackboneInterface
|
||||
interface_config["target_host"] = info["reachable_on"]
|
||||
interface_config["target_port"] = info["port"]
|
||||
interface = BackboneInterface.BackboneClientInterface(RNS.Transport, interface_config)
|
||||
interface_name = self.autoconnect_interface_name(info["name"])
|
||||
if interface_name != info["name"]:
|
||||
RNS.log(f"Auto-connect name collision for \"{info['name']}\", connecting as \"{interface_name}\"", RNS.LOG_NOTICE)
|
||||
|
||||
if interface:
|
||||
RNS.log(f"Auto-connecting discovered {interface_type} {interface_name}")
|
||||
interface.autoconnect_hash = endpoint_hash
|
||||
interface.autoconnect_source = info["network_id"]
|
||||
if RNS.Reticulum.autoconnect_interface_mode(): mode = RNS.Reticulum.autoconnect_interface_mode()
|
||||
else: mode = self.AC_TRANSPORT_MODE if RNS.Reticulum.transport_enabled() else None
|
||||
internal_a = True if RNS.Reticulum.autoconnect_announces_to_internal() else None
|
||||
gravity = RNS.Reticulum.autoconnect_interface_gravity() or self.AC_GRAVITY
|
||||
ar_target = RNS.Reticulum.get_instance()._default_ar_target() if RNS.Reticulum.transport_enabled() else None
|
||||
ar_penalty = RNS.Reticulum.get_instance()._default_ar_penalty() if RNS.Reticulum.transport_enabled() else None
|
||||
ar_grace = RNS.Reticulum.get_instance()._default_ar_grace() if RNS.Reticulum.transport_enabled() else None
|
||||
RNS.Reticulum.get_instance()._add_interface(interface, mode=mode, ifac_netname=ifac_netname, ifac_netkey=ifac_netkey, configured_bitrate=5E6,
|
||||
announce_rate_target=ar_target, announce_rate_grace=ar_grace, announce_rate_penalty=ar_penalty,
|
||||
announces_to_internal=internal_a, gravity=gravity)
|
||||
self.monitor_interface(interface)
|
||||
config_entry = info["config_entry"]
|
||||
interface_config = {}
|
||||
interface_config["name"] = f"{interface_name}"
|
||||
|
||||
# TODO: Temporary measure to sanitize invalidly persisted "None" values
|
||||
# for incorrect IFAC parameters published due to unguarded nonsensical
|
||||
# node-side configuration. Potentially remove once nodes upgrade to RNS
|
||||
# versions that guard against the configuration mismatch.
|
||||
if "ifac_netname" in info and info["ifac_netname"] == "None": info["ifac_netname"] = None
|
||||
if "ifac_netkey" in info and info["ifac_netkey"] == "None": info["ifac_netkey"] = None
|
||||
|
||||
ifac_netname = info["ifac_netname"] if "ifac_netname" in info and info["ifac_netname"] else None
|
||||
ifac_netkey = info["ifac_netkey"] if "ifac_netkey" in info and info["ifac_netkey"] else None
|
||||
interface = None
|
||||
|
||||
if interface_type == "BackboneInterface":
|
||||
if backbone_support:
|
||||
from RNS.Interfaces import BackboneInterface
|
||||
interface_class = BackboneInterface.BackboneClientInterface
|
||||
else:
|
||||
from RNS.Interfaces import TCPInterface
|
||||
interface_class = TCPInterface.TCPClientInterface
|
||||
RNS.log(f"BackboneInterface is not yet supported on this operating system, auto-connecting discovered {interface_type} {interface_name} using TCPClientInterface", RNS.LOG_NOTICE)
|
||||
|
||||
interface_config["target_host"] = info["reachable_on"]
|
||||
interface_config["target_port"] = info["port"]
|
||||
interface = interface_class(RNS.Transport, interface_config)
|
||||
|
||||
if interface:
|
||||
RNS.log(f"Auto-connecting discovered {interface_type} {interface_name}")
|
||||
interface.autoconnect_hash = endpoint_hash
|
||||
interface.autoconnect_source = info["network_id"]
|
||||
if RNS.Reticulum.autoconnect_interface_mode(): mode = RNS.Reticulum.autoconnect_interface_mode()
|
||||
else: mode = self.AC_TRANSPORT_MODE if RNS.Reticulum.transport_enabled() else None
|
||||
internal_a = True if RNS.Reticulum.autoconnect_announces_to_internal() else None
|
||||
gravity = RNS.Reticulum.autoconnect_interface_gravity() or self.AC_GRAVITY
|
||||
ar_target = RNS.Reticulum.get_instance()._default_ar_target() if RNS.Reticulum.transport_enabled() else None
|
||||
ar_penalty = RNS.Reticulum.get_instance()._default_ar_penalty() if RNS.Reticulum.transport_enabled() else None
|
||||
ar_grace = RNS.Reticulum.get_instance()._default_ar_grace() if RNS.Reticulum.transport_enabled() else None
|
||||
RNS.Reticulum.get_instance()._add_interface(interface, mode=mode, ifac_netname=ifac_netname, ifac_netkey=ifac_netkey, configured_bitrate=5E6,
|
||||
announce_rate_target=ar_target, announce_rate_grace=ar_grace, announce_rate_penalty=ar_penalty,
|
||||
announces_to_internal=internal_a, gravity=gravity)
|
||||
self.monitor_interface(interface)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while auto-connecting discovered interface: {e}", RNS.LOG_ERROR)
|
||||
|
|
@ -895,6 +992,18 @@ def is_hostname(hostname):
|
|||
allowed = re.compile(r"(?!-)[a-z0-9-]{1,63}(?<!-)$", re.IGNORECASE)
|
||||
return all(allowed.match(label) for label in components)
|
||||
|
||||
def version_tuple(version_string):
|
||||
if type(version_string) != str: return None
|
||||
try:
|
||||
components = []
|
||||
for component in version_string.strip().split("."):
|
||||
match = re.match(r"[0-9]+", component)
|
||||
if match == None: break
|
||||
components.append(int(match.group(0)))
|
||||
if len(components) == 0: return None
|
||||
return tuple(components)
|
||||
except Exception as e: return None
|
||||
|
||||
san_map = ""
|
||||
for i in range(48, 58): san_map += bytes([i]).decode("ascii")
|
||||
for i in range(65, 91): san_map += bytes([i]).decode("ascii")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -130,6 +130,7 @@ class AX25KISSInterface(Interface):
|
|||
self.flow_control = flow_control
|
||||
self.shared_medium = True
|
||||
self.interface_ready = False
|
||||
self.reconnecting = False
|
||||
self.flow_control_timeout = 5
|
||||
self.flow_control_locked = time.time()
|
||||
|
||||
|
|
@ -152,14 +153,18 @@ class AX25KISSInterface(Interface):
|
|||
|
||||
try:
|
||||
self.open_port()
|
||||
if self.serial.is_open: self.configure_device()
|
||||
else: raise IOError("Could not open serial port")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||
raise e
|
||||
RNS.log(f"Could not open serial port for {self}", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting:
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE)
|
||||
|
|
@ -177,6 +182,12 @@ class AX25KISSInterface(Interface):
|
|||
dsrdtr = False,
|
||||
)
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.online = False
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self} at detach: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def configure_device(self):
|
||||
# Allow time for interface to initialise before config
|
||||
sleep(2.0)
|
||||
|
|
@ -373,30 +384,28 @@ class AX25KISSInterface(Interface):
|
|||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
if RNS.Reticulum.panic_on_interface_error: RNS.panic()
|
||||
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self}", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting: self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
self.reconnecting = True
|
||||
while not self.online and not self.detached:
|
||||
try:
|
||||
time.sleep(5)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
self.open_port()
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
if self.serial.is_open: self.configure_device()
|
||||
except Exception as e: RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
self.reconnecting = False
|
||||
if self.online: RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
def should_ingress_limit(self): return False
|
||||
|
||||
def __str__(self):
|
||||
return "AX25KISSInterface["+self.name+"]"
|
||||
def __str__(self): return "AX25KISSInterface["+self.name+"]"
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -128,6 +128,7 @@ class KISSInterface(Interface):
|
|||
self.beacon_d = beacon_data.encode("utf-8")
|
||||
self.first_tx = None
|
||||
self.bitrate = KISSInterface.BITRATE_GUESS
|
||||
self.reconnecting = False
|
||||
|
||||
self.packet_queue = []
|
||||
self.flow_control = flow_control
|
||||
|
|
@ -149,15 +150,23 @@ class KISSInterface(Interface):
|
|||
|
||||
try:
|
||||
self.open_port()
|
||||
if self.serial.is_open: self.configure_device()
|
||||
else: raise IOError("Could not open serial port")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port "+self.port, RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
RNS.log(f"Could not open serial port for {self}", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting:
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.online = False
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self} at detach: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
|
|
@ -417,24 +426,23 @@ class KISSInterface(Interface):
|
|||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self}", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting: self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
self.reconnecting = True
|
||||
while not self.online:
|
||||
try:
|
||||
time.sleep(5)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
self.open_port()
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
if self.serial.is_open: self.configure_device()
|
||||
except Exception as e: RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
self.reconnecting = False
|
||||
if self.online: RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
def should_ingress_limit(self): return False
|
||||
|
||||
def __str__(self):
|
||||
return "KISSInterface["+self.name+"]"
|
||||
def __str__(self): return "KISSInterface["+self.name+"]"
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -1504,13 +1504,13 @@ class RNodeInterface(Interface):
|
|||
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
|
||||
if not self.detached:
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
if RNS.Reticulum.panic_on_interface_error: RNS.panic()
|
||||
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
|
||||
|
|
@ -1570,10 +1570,14 @@ class RNodeInterface(Interface):
|
|||
except Exception as e:
|
||||
RNS.log(f"An error occurred while detaching {self}: {e}", RNS.LOG_ERROR)
|
||||
|
||||
if self.use_ble: self.ble.close()
|
||||
if self.use_tcp:
|
||||
if self.use_ble:
|
||||
self.ble.close()
|
||||
self.ble.cleanup()
|
||||
elif self.use_tcp:
|
||||
time.sleep(0.5)
|
||||
self.tcp.close()
|
||||
else:
|
||||
self.serial.close()
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
|
@ -1652,6 +1656,9 @@ class BLEConnection(BluetoothDispatcher):
|
|||
self.owner.ble_rx_queue = b""
|
||||
return data
|
||||
|
||||
def cleanup(self):
|
||||
self.should_run = False
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
if self.connected:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -109,6 +109,7 @@ class SerialInterface(Interface):
|
|||
self.timeout = 100
|
||||
self.online = False
|
||||
self.bitrate = self.speed
|
||||
self.reconnecting = False
|
||||
|
||||
if parity.lower() == "e" or parity.lower() == "even":
|
||||
self.parity = "E"
|
||||
|
|
@ -118,15 +119,23 @@ class SerialInterface(Interface):
|
|||
|
||||
try:
|
||||
self.open_port()
|
||||
if self.serial.is_open: self.configure_device()
|
||||
else: raise IOError("Could not open serial port")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
RNS.log(f"Could not open serial port for {self}", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting:
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.online = False
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self} at detach: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
|
|
@ -258,24 +267,23 @@ class SerialInterface(Interface):
|
|||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self}", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting: self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
self.reconnecting = True
|
||||
while not self.online and not self.detached:
|
||||
try:
|
||||
time.sleep(5)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
self.open_port()
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
if self.serial.is_open: self.configure_device()
|
||||
except Exception as e: RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
self.reconnecting = False
|
||||
if self.online: RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
def should_ingress_limit(self): return False
|
||||
|
||||
def __str__(self):
|
||||
return "SerialInterface["+self.name+"]"
|
||||
def __str__(self): return "SerialInterface["+self.name+"]"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -127,6 +127,7 @@ class AutoInterface(Interface):
|
|||
self.link_local_addresses = []
|
||||
self.adopted_interfaces = {}
|
||||
self.interface_servers = {}
|
||||
self.discovery_sockets = []
|
||||
self.multicast_echoes = {}
|
||||
self.initial_echoes = {}
|
||||
self.timed_out_interfaces = {}
|
||||
|
|
@ -268,6 +269,8 @@ class AutoInterface(Interface):
|
|||
addr_info = socket.getaddrinfo(link_local_addr+"%"+ifname, self.unicast_discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
unicast_discovery_socket.bind(addr_info[0][4])
|
||||
|
||||
self.discovery_sockets.append(unicast_discovery_socket)
|
||||
|
||||
mcast_addr = self.mcast_discovery_address
|
||||
RNS.log(str(self)+" Creating multicast discovery listener on "+str(ifname)+" with address "+str(mcast_addr), RNS.LOG_EXTREME)
|
||||
|
||||
|
|
@ -296,6 +299,8 @@ class AutoInterface(Interface):
|
|||
|
||||
discovery_socket.bind(addr_info[0][4])
|
||||
|
||||
self.discovery_sockets.append(discovery_socket)
|
||||
|
||||
# Set up thread for multicast discovery packets
|
||||
def discovery_loop(): self.discovery_handler(discovery_socket, ifname)
|
||||
thread = threading.Thread(target=discovery_loop, daemon=True).start()
|
||||
|
|
@ -358,9 +363,9 @@ class AutoInterface(Interface):
|
|||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
while True:
|
||||
while not self.detached:
|
||||
data, ipv6_src = socket.recvfrom(1024)
|
||||
if self.final_init_done:
|
||||
if self.final_init_done and data and ipv6_src:
|
||||
peering_hash = data[:RNS.Identity.HASHLENGTH//8]
|
||||
expected_hash = RNS.Identity.full_hash(self.group_id+ipv6_src[0].encode("utf-8"))
|
||||
if peering_hash == expected_hash:
|
||||
|
|
@ -369,7 +374,7 @@ class AutoInterface(Interface):
|
|||
RNS.log(str(self)+" received peering packet on "+str(ifname)+" from "+str(ipv6_src[0])+", but authentication hash was incorrect.", RNS.LOG_DEBUG)
|
||||
|
||||
def peer_jobs(self):
|
||||
while True:
|
||||
while not self.detached:
|
||||
time.sleep(self.peer_job_interval)
|
||||
now = time.time()
|
||||
timed_out_peers = []
|
||||
|
|
@ -478,7 +483,7 @@ class AutoInterface(Interface):
|
|||
|
||||
|
||||
def announce_handler(self, ifname):
|
||||
while True:
|
||||
while not self.detached:
|
||||
self.peer_announce(ifname)
|
||||
time.sleep(self.announce_interval)
|
||||
|
||||
|
|
@ -581,6 +586,10 @@ class AutoInterface(Interface):
|
|||
spawned_interface.announce_rate_penalty = self.announce_rate_penalty
|
||||
spawned_interface.mode = self.mode
|
||||
spawned_interface.gravity = self.gravity
|
||||
spawned_interface.recursive_prs = self.recursive_prs
|
||||
spawned_interface.announces_from_internal = self.announces_from_internal
|
||||
spawned_interface.announces_to_internal = self.announces_to_internal
|
||||
spawned_interface.announce_cap = self.announce_cap
|
||||
spawned_interface.HW_MTU = self.HW_MTU
|
||||
spawned_interface.online = True
|
||||
RNS.Transport.add_interface(spawned_interface)
|
||||
|
|
@ -604,7 +613,20 @@ class AutoInterface(Interface):
|
|||
|
||||
def process_outgoing(self, data): pass
|
||||
|
||||
def detach(self): self.online = False
|
||||
def detach(self):
|
||||
self.online = False
|
||||
self.detached = True
|
||||
|
||||
RNS.log(f"Shutting down all UDP listeners for {self}", RNS.LOG_NOTICE)
|
||||
for ifname in self.interface_servers:
|
||||
try:
|
||||
self.interface_servers[ifname].server_close()
|
||||
self.interface_servers[ifname].shutdown()
|
||||
except Exception as e: RNS.log(f"Could not shut down UDP listener {ifname} for {self}: {e}", RNS.LOG_ERROR)
|
||||
|
||||
for s in self.discovery_sockets:
|
||||
try: s.close()
|
||||
except Exception as e: RNS.log(f"Error while shutting down discovery socket {s} for {self}: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def __str__(self): return f"AutoInterface[{self.name}]"
|
||||
|
||||
|
|
|
|||
|
|
@ -52,9 +52,9 @@ class BackboneInterface(Interface):
|
|||
fast_flapping_lock = threading.Lock()
|
||||
fast_flapping = {}
|
||||
|
||||
DP_IC_HIGH_WM_PCT = 85
|
||||
DP_IC_MID_WM_PCT = 10
|
||||
DP_IC_LOW_WM_PCT = 1
|
||||
DP_IC_HIGH_WM_PCT = 90
|
||||
DP_IC_MID_WM_PCT = 68
|
||||
DP_IC_LOW_WM_PCT = 10
|
||||
DP_IC_INTERVAL = 0.250
|
||||
DP_IC_TRIGGER = 1.5
|
||||
DP_IC_RCVBUF = 32768
|
||||
|
|
@ -424,7 +424,7 @@ class BackboneInterface(Interface):
|
|||
return False
|
||||
|
||||
if now - interface._dp_ec_last_drain >= BackboneInterface.DP_EC_DEAD_TIME:
|
||||
RNS.log(f"No egress control drain progress for {RNS.prettyshorttime(BackboneInterface.DP_EC_DEAD_TIME, compact=True)} on {interface}, tearing down", RNS.LOG_WARNING)
|
||||
RNS.log(f"No egress control drain progress for {RNS.prettyshorttime(BackboneInterface.DP_EC_DEAD_TIME, compact=True)} on {interface}, tearing down", RNS.LOG_NOTICE)
|
||||
try:
|
||||
if hasattr(interface, "socket") and interface.socket:
|
||||
fileno = interface.socket.fileno()
|
||||
|
|
@ -482,6 +482,7 @@ class BackboneInterface(Interface):
|
|||
|
||||
for interface in interfaces:
|
||||
if interface.detached: continue
|
||||
if isinstance(interface, RNS.Interfaces.LocalInterface.LocalClientInterface): continue
|
||||
BackboneInterface._dp_ec_evaluate(interface, now)
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -508,7 +509,7 @@ class BackboneInterface(Interface):
|
|||
interfaces = list(BackboneInterface.spawned_interface_filenos.values())
|
||||
|
||||
if q_depth > BackboneInterface.DP_IC_MID_WM:
|
||||
producers = [iface for iface in interfaces if iface.dp_ingress_packets > 0]
|
||||
producers = [iface for iface in interfaces if iface.dp_ingress_packets > 0 and not isinstance(iface, RNS.Interfaces.LocalInterface.LocalClientInterface)]
|
||||
if producers:
|
||||
producers.sort(key=lambda p: p.dp_ingress_packets, reverse=True)
|
||||
selected = producers[0]
|
||||
|
|
@ -737,6 +738,10 @@ class BackboneInterface(Interface):
|
|||
spawned_interface.announce_rate_penalty = self.announce_rate_penalty
|
||||
spawned_interface.mode = self.mode
|
||||
spawned_interface.gravity = self.gravity
|
||||
spawned_interface.recursive_prs = self.recursive_prs
|
||||
spawned_interface.announces_from_internal = self.announces_from_internal
|
||||
spawned_interface.announces_to_internal = self.announces_to_internal
|
||||
spawned_interface.announce_cap = self.announce_cap
|
||||
spawned_interface.HW_MTU = self.HW_MTU
|
||||
RNS.log("Spawned new BackboneClient Interface: "+str(spawned_interface), RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||
RNS.Transport.add_interface(spawned_interface)
|
||||
|
|
@ -925,11 +930,10 @@ class BackboneClientInterface(Interface):
|
|||
|
||||
def detach(self):
|
||||
self.online = False
|
||||
self.detached = True
|
||||
if self.socket != None:
|
||||
if hasattr(self.socket, "close"):
|
||||
if callable(self.socket.close):
|
||||
self.detached = True
|
||||
|
||||
try:
|
||||
if self.socket != None: self.socket.shutdown(socket.SHUT_RDWR)
|
||||
except Exception as e:
|
||||
|
|
@ -1030,7 +1034,7 @@ class BackboneClientInterface(Interface):
|
|||
def process_outgoing(self, data):
|
||||
if self.online and not self.detached:
|
||||
try:
|
||||
frame = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
frame = HDLC.frame(data)
|
||||
if self.tx_stalled or not self.transmit_buffer.append(frame, self.tx_hwm):
|
||||
self.tx_drops += 1
|
||||
self.tx_dropped_bytes += len(frame)
|
||||
|
|
|
|||
|
|
@ -537,6 +537,7 @@ class I2PInterfacePeer(Interface):
|
|||
raise IOError("Attempt to reconnect on a non-initiator I2P interface")
|
||||
|
||||
def process_incoming(self, data):
|
||||
if not data: return
|
||||
self.rxb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None and self.parent_count:
|
||||
self.parent_interface.rxb += len(data)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -114,6 +114,7 @@ class Interface:
|
|||
self.HW_MTU = None
|
||||
self.__hash = None
|
||||
|
||||
self.announce_cap = RNS.Reticulum.ANNOUNCE_CAP/100.0
|
||||
self.supports_discovery = False
|
||||
self.discoverable = False
|
||||
self.last_discovery_announce = 0
|
||||
|
|
@ -389,7 +390,7 @@ class Interface:
|
|||
|
||||
def process_announce_queue(self):
|
||||
if not hasattr(self, "announce_cap"):
|
||||
self.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
|
||||
self.announce_cap = RNS.Reticulum.ANNOUNCE_CAP/100.0
|
||||
|
||||
if hasattr(self, "announce_queue"):
|
||||
try:
|
||||
|
|
@ -436,6 +437,9 @@ class Interface:
|
|||
def detach(self):
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_config_obj(config_in):
|
||||
if type(config_in) == ConfigObj: return config_in
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -124,6 +124,7 @@ class KISSInterface(Interface):
|
|||
self.flow_control = flow_control
|
||||
self.shared_medium = True
|
||||
self.interface_ready = False
|
||||
self.reconnecting = False
|
||||
self.flow_control_timeout = 5
|
||||
self.flow_control_locked = time.time()
|
||||
|
||||
|
|
@ -140,15 +141,17 @@ class KISSInterface(Interface):
|
|||
|
||||
try:
|
||||
self.open_port()
|
||||
if self.serial.is_open: self.configure_device()
|
||||
else: raise IOError("Could not open serial port")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port "+self.port, RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
|
||||
RNS.log(f"Could not open serial port for {self}", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting:
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE)
|
||||
|
|
@ -166,6 +169,11 @@ class KISSInterface(Interface):
|
|||
dsrdtr = False,
|
||||
)
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.online = False
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self} at detach: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def configure_device(self):
|
||||
# Allow time for interface to initialise before config
|
||||
|
|
@ -246,6 +254,7 @@ class KISSInterface(Interface):
|
|||
|
||||
|
||||
def process_incoming(self, data):
|
||||
if not data: return
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
|
@ -360,30 +369,28 @@ class KISSInterface(Interface):
|
|||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
if RNS.Reticulum.panic_on_interface_error: RNS.panic()
|
||||
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self}", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting: self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
self.reconnecting = True
|
||||
while not self.online and not self.detached:
|
||||
try:
|
||||
time.sleep(5)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
self.open_port()
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
if self.serial.is_open: self.configure_device()
|
||||
except Exception as e: RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
self.reconnecting = False
|
||||
if self.online: RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
def should_ingress_limit(self): return False
|
||||
|
||||
def __str__(self):
|
||||
return "KISSInterface["+self.name+"]"
|
||||
def __str__(self): return "KISSInterface["+self.name+"]"
|
||||
|
|
@ -58,10 +58,12 @@ class LocalClientInterface(Interface):
|
|||
def __init__(self, owner, name, target_port = None, connected_socket=None, socket_path=None):
|
||||
super().__init__()
|
||||
|
||||
self.epoll_backend = False
|
||||
self.HW_MTU = 262144
|
||||
self.online = False
|
||||
self.ifac_size = self.DEFAULT_IFAC_SIZE
|
||||
self.owner = owner
|
||||
self.bitrate = 1_000_000_000
|
||||
self.epoll_backend = False
|
||||
self.HW_MTU = 262144
|
||||
self.online = False
|
||||
self.ifac_size = self.DEFAULT_IFAC_SIZE
|
||||
|
||||
if socket_path != None and RNS.Reticulum.get_instance().use_af_unix: self.socket_path = f"\0rns/{socket_path}"
|
||||
else: self.socket_path = None
|
||||
|
|
@ -110,8 +112,6 @@ class LocalClientInterface(Interface):
|
|||
self.target_port = target_port
|
||||
self.connect()
|
||||
|
||||
self.owner = owner
|
||||
self.bitrate = 1_000_000_000
|
||||
self.online = True
|
||||
self.writing = False
|
||||
|
||||
|
|
@ -218,7 +218,7 @@ class LocalClientInterface(Interface):
|
|||
if self.online:
|
||||
try:
|
||||
if self.epoll_backend:
|
||||
frame = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
frame = HDLC.frame(data)
|
||||
if self.tx_stalled or not self.transmit_buffer.append(frame, self.tx_hwm):
|
||||
self.tx_drops += 1
|
||||
self.tx_dropped_bytes += len(frame)
|
||||
|
|
@ -235,7 +235,7 @@ class LocalClientInterface(Interface):
|
|||
s = len(data) / self.bitrate * 8
|
||||
time.sleep(s)
|
||||
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
data = HDLC.frame(data)
|
||||
self.socket.sendall(data)
|
||||
self.writing = False
|
||||
self.txb += len(data)
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ class PipeInterface(Interface):
|
|||
|
||||
|
||||
def process_incoming(self, data):
|
||||
if not data: return
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -386,6 +386,7 @@ class RNodeInterface(Interface):
|
|||
RNS.log(f"Opening BLE connection for {self}...")
|
||||
self.timeout = 1250
|
||||
if self.ble != None and self.ble.running == False:
|
||||
RNS.log(f"Cleaning up previous BLE connection for {self}...")
|
||||
self.ble.close()
|
||||
self.ble.cleanup()
|
||||
self.ble = None
|
||||
|
|
@ -442,7 +443,11 @@ class RNodeInterface(Interface):
|
|||
ble_detect_timeout = 5.0
|
||||
detect_time = time.time()
|
||||
while not self.detected and time.time() < detect_time + ble_detect_timeout: time.sleep(0.1)
|
||||
if not self.detected: RNS.log(f"RNode detect timed out over BLE", RNS.LOG_ERROR)
|
||||
if not self.detected:
|
||||
RNS.log(f"RNode detect timed out over BLE", RNS.LOG_ERROR)
|
||||
if self.ble:
|
||||
self.ble.should_run = False
|
||||
self.ble.must_disconnect = True
|
||||
else:
|
||||
sleep(0.2)
|
||||
|
||||
|
|
@ -480,8 +485,7 @@ class RNodeInterface(Interface):
|
|||
self.setLTALock()
|
||||
self.setRadioState(KISS.RADIO_STATE_ON)
|
||||
|
||||
if self.use_ble:
|
||||
time.sleep(2)
|
||||
if self.use_ble: time.sleep(2)
|
||||
|
||||
def detect(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_DETECT, KISS.DETECT_REQ, KISS.FEND, KISS.CMD_FW_VERSION, 0x00, KISS.FEND, KISS.CMD_PLATFORM, 0x00, KISS.FEND, KISS.CMD_MCU, 0x00, KISS.FEND])
|
||||
|
|
@ -687,10 +691,8 @@ class RNodeInterface(Interface):
|
|||
RNS.log("Radio state mismatch", RNS.LOG_ERROR)
|
||||
self.validcfg = False
|
||||
|
||||
if (self.validcfg):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
if (self.validcfg): return True
|
||||
else: return False
|
||||
|
||||
|
||||
def updateBitrate(self):
|
||||
|
|
@ -702,6 +704,7 @@ class RNodeInterface(Interface):
|
|||
self.bitrate = 0
|
||||
|
||||
def process_incoming(self, data):
|
||||
if not data: return
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
|
@ -1154,19 +1157,17 @@ class RNodeInterface(Interface):
|
|||
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
|
||||
if not self.detached:
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
if RNS.Reticulum.panic_on_interface_error: RNS.panic()
|
||||
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
try:
|
||||
self.serial.close()
|
||||
except Exception as e:
|
||||
pass
|
||||
try: self.serial.close()
|
||||
except Exception as e: pass
|
||||
|
||||
if not self.detached and not self.reconnecting:
|
||||
self.reconnect_port()
|
||||
|
|
@ -1192,14 +1193,20 @@ class RNodeInterface(Interface):
|
|||
self.disable_external_framebuffer()
|
||||
self.setRadioState(KISS.RADIO_STATE_OFF)
|
||||
self.leave()
|
||||
self.serial.close()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"An error occurred while detaching {self}: {e}", RNS.LOG_ERROR)
|
||||
|
||||
if self.use_ble: self.ble.close()
|
||||
if self.use_tcp:
|
||||
if self.use_ble:
|
||||
self.ble.close()
|
||||
self.ble.cleanup()
|
||||
self.ble = None
|
||||
elif self.use_tcp:
|
||||
time.sleep(0.5)
|
||||
self.tcp.close()
|
||||
else:
|
||||
self.serial.close()
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
|
@ -1297,6 +1304,7 @@ class BLEConnection():
|
|||
self.must_disconnect = False
|
||||
self.connect_job_running = False
|
||||
self.device_disappeared = False
|
||||
self._windows_paired_addrs = None
|
||||
|
||||
import importlib.util
|
||||
if BLEConnection.bleak == None:
|
||||
|
|
@ -1307,8 +1315,8 @@ class BLEConnection():
|
|||
import asyncio
|
||||
BLEConnection.asyncio = asyncio
|
||||
else:
|
||||
RNS.log("Using the RNode interface over BLE requires a the \"bleak\" module to be installed.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install one with the command: python3 -m pip install bleak", RNS.LOG_CRITICAL)
|
||||
RNS.log("Using the RNode interface over BLE requires the \"bleak\" module to be installed.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install bleak", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
self.should_run = True
|
||||
|
|
@ -1324,6 +1332,7 @@ class BLEConnection():
|
|||
self.should_run = False
|
||||
|
||||
def connection_job(self):
|
||||
self.running = True
|
||||
while self.should_run:
|
||||
if self.ble_device == None:
|
||||
self.ble_device = self.find_target_device()
|
||||
|
|
@ -1350,6 +1359,7 @@ class BLEConnection():
|
|||
self.owner.ble_receive(data)
|
||||
|
||||
self.connected = True
|
||||
self.device_disappeared = False
|
||||
self.ble_device = ble_client
|
||||
self.last_client = ble_client
|
||||
self.owner.port = str(f"ble://{ble_client.address}")
|
||||
|
|
@ -1384,6 +1394,8 @@ class BLEConnection():
|
|||
|
||||
def find_target_device(self):
|
||||
RNS.log(f"Searching for attachable BLE device for {self.owner}...", RNS.LOG_EXTREME)
|
||||
if RNS.vendor.platformutils.is_windows():
|
||||
self._windows_paired_addrs = self._get_windows_paired_ble_addresses()
|
||||
def device_filter(device: self.bleak.backends.device.BLEDevice, adv: self.bleak.backends.scanner.AdvertisementData):
|
||||
if BLEConnection.UART_SERVICE_UUID.lower() in adv.service_uuids:
|
||||
if self.device_bonded(device):
|
||||
|
|
@ -1415,6 +1427,9 @@ class BLEConnection():
|
|||
|
||||
def device_bonded(self, device):
|
||||
try:
|
||||
if self._windows_paired_addrs is not None:
|
||||
return device.address is not None and device.address.lower() in self._windows_paired_addrs
|
||||
|
||||
if hasattr(device, "details"):
|
||||
if "props" in device.details and "Bonded" in device.details["props"]:
|
||||
if device.details["props"]["Bonded"] == True:
|
||||
|
|
@ -1425,6 +1440,15 @@ class BLEConnection():
|
|||
|
||||
return False
|
||||
|
||||
def _get_windows_paired_ble_addresses(self):
|
||||
from winrt.windows.devices.bluetooth import BluetoothLEDevice
|
||||
from winrt.windows.devices.enumeration import DeviceInformation
|
||||
async def _query():
|
||||
selector = BluetoothLEDevice.get_device_selector_from_pairing_state(True)
|
||||
infos = await DeviceInformation.find_all_async_aqs_filter(selector)
|
||||
return set(info.id.split("-")[-1].lower() for info in infos)
|
||||
return self.asyncio.run(_query())
|
||||
|
||||
class TCPConnection():
|
||||
TARGET_PORT = 7633
|
||||
CONNECT_TIMEOUT = 5.0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -916,6 +916,7 @@ class RNodeMultiInterface(Interface):
|
|||
if interface != 0:
|
||||
self.setRadioState(KISS.RADIO_STATE_OFF, interface)
|
||||
self.leave()
|
||||
self.serial.close()
|
||||
|
||||
def teardown_subinterfaces(self):
|
||||
for interface in self.subinterfaces:
|
||||
|
|
@ -1128,6 +1129,7 @@ class RNodeSubInterface(Interface):
|
|||
self.bitrate = 0
|
||||
|
||||
def process_incoming(self, data):
|
||||
if not data: return
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -97,6 +97,7 @@ class SerialInterface(Interface):
|
|||
self.timeout = 100
|
||||
self.online = False
|
||||
self.bitrate = self.speed
|
||||
self.reconnecting = False
|
||||
|
||||
if parity.lower() == "e" or parity.lower() == "even":
|
||||
self.parity = serial.PARITY_EVEN
|
||||
|
|
@ -106,15 +107,17 @@ class SerialInterface(Interface):
|
|||
|
||||
try:
|
||||
self.open_port()
|
||||
if self.serial.is_open: self.configure_device()
|
||||
else: raise IOError("Could not open serial port")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
|
||||
RNS.log(f"Could not open serial port for {self}", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting:
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE)
|
||||
|
|
@ -132,6 +135,11 @@ class SerialInterface(Interface):
|
|||
dsrdtr = False,
|
||||
)
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.online = False
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self} at detach: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def configure_device(self):
|
||||
sleep(0.5)
|
||||
|
|
@ -141,12 +149,11 @@ class SerialInterface(Interface):
|
|||
self.online = True
|
||||
RNS.log("Serial port "+self.port+" is now open", RNS.LOG_VERBOSE)
|
||||
|
||||
|
||||
def process_incoming(self, data):
|
||||
if not data: return
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
||||
def process_outgoing(self,data):
|
||||
if self.online:
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
|
|
@ -155,7 +162,6 @@ class SerialInterface(Interface):
|
|||
if written != len(data):
|
||||
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
||||
|
||||
|
||||
def readLoop(self):
|
||||
try:
|
||||
in_frame = False
|
||||
|
|
@ -199,30 +205,28 @@ class SerialInterface(Interface):
|
|||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
if RNS.Reticulum.panic_on_interface_error: RNS.panic()
|
||||
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
try: self.serial.close()
|
||||
except Exception as e: RNS.log(f"Could not close port for {self}", RNS.LOG_ERROR)
|
||||
if not self.detached and not self.reconnecting: self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
self.reconnecting = True
|
||||
while not self.online and not self.detached:
|
||||
try:
|
||||
time.sleep(5)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
self.open_port()
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
if self.serial.is_open: self.configure_device()
|
||||
except Exception as e: RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
self.reconnecting = False
|
||||
if self.online: RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
def should_ingress_limit(self): return False
|
||||
|
||||
def __str__(self):
|
||||
return "SerialInterface["+self.name+"]"
|
||||
def __str__(self): return "SerialInterface["+self.name+"]"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -208,6 +208,7 @@ class TCPClientInterface(Interface):
|
|||
|
||||
def detach(self):
|
||||
self.online = False
|
||||
self.detached = True
|
||||
if self.socket != None:
|
||||
if hasattr(self.socket, "close"):
|
||||
if callable(self.socket.close):
|
||||
|
|
@ -272,7 +273,7 @@ class TCPClientInterface(Interface):
|
|||
if not self.reconnecting:
|
||||
self.reconnecting = True
|
||||
attempts = 0
|
||||
while not self.online:
|
||||
while not self.online and not self.detached:
|
||||
time.sleep(TCPClientInterface.RECONNECT_WAIT)
|
||||
attempts += 1
|
||||
|
||||
|
|
@ -302,6 +303,7 @@ class TCPClientInterface(Interface):
|
|||
raise IOError("Attempt to reconnect on a non-initiator TCP interface")
|
||||
|
||||
def process_incoming(self, data):
|
||||
if not data: return
|
||||
if self.online and not self.detached:
|
||||
self.rxb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
|
|
@ -426,7 +428,7 @@ class TCPClientInterface(Interface):
|
|||
self.online = False
|
||||
RNS.log("An interface error occurred for "+str(self)+", the contained exception was: "+str(e), RNS.LOG_WARNING)
|
||||
|
||||
if self.initiator:
|
||||
if self.initiator and not self.detached:
|
||||
RNS.log("Attempting to reconnect...", RNS.LOG_WARNING)
|
||||
self.reconnect()
|
||||
else:
|
||||
|
|
@ -452,12 +454,9 @@ class TCPClientInterface(Interface):
|
|||
if not self.initiator:
|
||||
RNS.Transport.remove_interface(self)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
if ":" in self.target_ip:
|
||||
ip_str = f"[{self.target_ip}]"
|
||||
else:
|
||||
ip_str = f"{self.target_ip}"
|
||||
if ":" in self.target_ip: ip_str = f"[{self.target_ip}]"
|
||||
else: ip_str = f"{self.target_ip}"
|
||||
|
||||
return "TCPInterface["+str(self.name)+"/"+ip_str+":"+str(self.target_port)+"]"
|
||||
|
||||
|
|
@ -637,6 +636,10 @@ class TCPServerInterface(Interface):
|
|||
spawned_interface.announce_rate_penalty = self.announce_rate_penalty
|
||||
spawned_interface.mode = self.mode
|
||||
spawned_interface.gravity = self.gravity
|
||||
spawned_interface.recursive_prs = self.recursive_prs
|
||||
spawned_interface.announces_from_internal = self.announces_from_internal
|
||||
spawned_interface.announces_to_internal = self.announces_to_internal
|
||||
spawned_interface.announce_cap = self.announce_cap
|
||||
spawned_interface.HW_MTU = self.HW_MTU
|
||||
spawned_interface.online = True
|
||||
RNS.log("Spawned new TCPClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
|
||||
|
|
@ -686,10 +689,8 @@ class TCPServerInterface(Interface):
|
|||
|
||||
|
||||
def __str__(self):
|
||||
if ":" in self.bind_ip:
|
||||
ip_str = f"[{self.bind_ip}]"
|
||||
else:
|
||||
ip_str = f"{self.bind_ip}"
|
||||
if ":" in self.bind_ip: ip_str = f"[{self.bind_ip}]"
|
||||
else: ip_str = f"{self.bind_ip}"
|
||||
|
||||
return "TCPServerInterface["+self.name+"/"+ip_str+":"+str(self.bind_port)+"]"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -113,8 +113,17 @@ class UDPInterface(Interface):
|
|||
self.forward_ip = forwardip
|
||||
self.forward_port = forwardport
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.online = False
|
||||
if self.server:
|
||||
try:
|
||||
self.server.server_close()
|
||||
self.server.shutdown()
|
||||
except Exception as e: RNS.log(f"Could not shut down UDP listener for {self}: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def process_incoming(self, data):
|
||||
if not data: return
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,15 @@ class HDLC():
|
|||
frame = frame.replace(HDLC.ESCPD_ESC, HDLC.ESC_B)
|
||||
return frame
|
||||
|
||||
@staticmethod
|
||||
def frame(data):
|
||||
esc = HDLC.escape(data)
|
||||
frame = bytearray(len(esc)+2)
|
||||
frame[0] = HDLC.FLAG
|
||||
frame[1:-1] = esc
|
||||
frame[-1] = HDLC.FLAG
|
||||
return frame
|
||||
|
||||
class ReceiveBuffer():
|
||||
def __init__(self, mtu, min_frame_len, max_frame_len=None, on_frame=None, on_invalid=None):
|
||||
self._mtu = mtu
|
||||
|
|
|
|||
|
|
@ -593,7 +593,7 @@ class Link:
|
|||
|
||||
def get_expected_rate(self):
|
||||
"""
|
||||
:returns: The packet expected in-flight data rate of an established link.
|
||||
:returns: The expected in-flight data rate of an established link.
|
||||
"""
|
||||
if self.status == Link.ACTIVE: return self.expected_rate
|
||||
else: return None
|
||||
|
|
@ -1395,7 +1395,7 @@ class RequestReceipt():
|
|||
time.sleep(0.1)
|
||||
|
||||
def request_timed_out(self, packet_receipt):
|
||||
if self in self.link.pending_requests and self.status == RequestReceipt.DELIVERED:
|
||||
if self in self.link.pending_requests and self.status in [RequestReceipt.DELIVERED, RequestReceipt.SENT]:
|
||||
self.status = RequestReceipt.FAILED
|
||||
self.concluded_at = time.time()
|
||||
self.link.pending_requests.remove(self)
|
||||
|
|
@ -1405,7 +1405,7 @@ class RequestReceipt():
|
|||
except Exception as e: RNS.log("Error while executing request timed out callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def response_rejected(self):
|
||||
if self in self.link.pending_requests and self.status == RequestReceipt.DELIVERED:
|
||||
if self in self.link.pending_requests and self.status in [RequestReceipt.DELIVERED, RequestReceipt.SENT]:
|
||||
self.status = RequestReceipt.FAILED
|
||||
self.concluded_at = time.time()
|
||||
self.link.pending_requests.remove(self)
|
||||
|
|
|
|||
|
|
@ -305,7 +305,9 @@ class Packet:
|
|||
|
||||
if RNS.Transport.outbound(self): return self.receipt
|
||||
else:
|
||||
RNS.log(f"No interfaces could process the outbound {self.hops} hop, type {self.packet_type} packet for {self.destination}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
if not self.attached_interface:
|
||||
RNS.log(f"No interfaces could process the outbound {self.hops} hop, type {self.packet_type} packet for {self.destination}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
|
||||
self.sent = False
|
||||
self.receipt = None
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
import RNS
|
||||
import os
|
||||
import io
|
||||
import bz2
|
||||
import math
|
||||
import time
|
||||
|
|
@ -280,15 +281,18 @@ class Resource:
|
|||
|
||||
if hasattr(data, "read"):
|
||||
if data_size == None: data_size = os.stat(data.name).st_size
|
||||
|
||||
if data_size == 0:
|
||||
RNS.log(f"Zero-sized stat at resource init, probably stream. Attempting to proxy stream data...", RNS.LOG_DEBUG)
|
||||
stream_proxy = tempfile.TemporaryFile()
|
||||
stream_proxy.write(data.read())
|
||||
data_size = os.stat(stream_proxy.name).st_size
|
||||
if data_size: RNS.log(f"Stream proxy succeeded, final data size is {RNS.prettysize(data_size)}", RNS.LOG_DEBUG)
|
||||
else: RNS.log(f"Resource initialisation received an invalid readable input, or stream data that could not be proxied, the transfer will likely fail.", RNS.LOG_WARNING)
|
||||
data = stream_proxy
|
||||
if isinstance(data, io.BufferedReader):
|
||||
RNS.log(f"Attempting to proxy stream data...", RNS.LOG_DEBUG)
|
||||
stream_proxy = tempfile.TemporaryFile()
|
||||
stream_proxy.write(data.read())
|
||||
stream_proxy.flush(); stream_proxy.seek(0)
|
||||
data_size = os.stat(stream_proxy.name).st_size
|
||||
if data_size: RNS.log(f"Stream proxy succeeded, final data size is {RNS.prettysize(data_size)}", RNS.LOG_DEBUG)
|
||||
else: RNS.log(f"Resource initialisation received an invalid readable input, or stream data that could not be proxied, the transfer will likely fail.", RNS.LOG_WARNING)
|
||||
data = stream_proxy
|
||||
|
||||
else: RNS.log(f"Got zero-sized readable at resource initialisation, the transfer will likely.", RNS.LOG_WARNING)
|
||||
|
||||
self.total_size = data_size + self.metadata_size
|
||||
|
||||
|
|
|
|||
163
RNS/Reticulum.py
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -257,11 +257,13 @@ class Reticulum:
|
|||
Reticulum.__local_hops_delta = False
|
||||
Reticulum.__link_mtu_discovery = Reticulum.LINK_MTU_DISCOVERY
|
||||
Reticulum.__remote_management_enabled = False
|
||||
Reticulum.__interface_management_enabled = True
|
||||
Reticulum.__use_implicit_proof = True
|
||||
Reticulum.__allow_probes = False
|
||||
Reticulum.__discovery_enabled = False
|
||||
Reticulum.__discover_interfaces = False
|
||||
Reticulum.__autoconnect_discovered_interfaces = False
|
||||
Reticulum.__autoconnect_unverified = False
|
||||
Reticulum.__autoconnect_interface_mode = None
|
||||
Reticulum.__autoconnect_interface_gravity = None
|
||||
Reticulum.__autoconnect_announces_to_internal = None
|
||||
|
|
@ -548,6 +550,11 @@ class Reticulum:
|
|||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True: Reticulum.__remote_management_enabled = True
|
||||
|
||||
if option == "enable_interface_management":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == False: Reticulum.__interface_management_enabled = False
|
||||
else: Reticulum.__interface_management_enabled = True
|
||||
|
||||
if option == "remote_management_allowed":
|
||||
v = self.config["reticulum"].as_list(option)
|
||||
for hexhash in v:
|
||||
|
|
@ -622,6 +629,10 @@ class Reticulum:
|
|||
v = self.config["reticulum"].as_int(option)
|
||||
if v > 0: Reticulum.__autoconnect_discovered_interfaces = v
|
||||
|
||||
if option == "autoconnect_unverified_implementations":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
Reticulum.__autoconnect_unverified = v
|
||||
|
||||
if option == "autoconnect_interface_mode":
|
||||
v = None; dmode = str(self.config["reticulum"]["autoconnect_interface_mode"]).lower()
|
||||
if dmode == "full": v = Interface.Interface.MODE_FULL
|
||||
|
|
@ -757,7 +768,79 @@ class Reticulum:
|
|||
|
||||
RNS.log("System interfaces are ready", RNS.LOG_VERBOSE)
|
||||
|
||||
def _synthesize_interface(self, config, name, instance_init=False):
|
||||
def _attach_interface(self, name, internal_forced=False):
|
||||
if not internal_forced and not Reticulum.__interface_management_enabled: return False
|
||||
try:
|
||||
interfaces = { iface.name: iface for iface in RNS.Transport.interfaces }
|
||||
if name in interfaces:
|
||||
RNS.log(f"Attempt to attach existing interface \"{name}\"", RNS.LOG_WARNING)
|
||||
return False
|
||||
|
||||
else:
|
||||
try: config = ConfigObj(self.configpath)
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not parse the configuration at {self.configpath} during interface attach", RNS.LOG_ERROR)
|
||||
return None
|
||||
|
||||
if "interfaces" in config:
|
||||
if not name in config["interfaces"]:
|
||||
RNS.log(f"Cannot attach interface \"{name}\", no configuration entry exists", RNS.LOG_ERROR)
|
||||
return None
|
||||
|
||||
else:
|
||||
interface_config = config["interfaces"][name]
|
||||
self._synthesize_interface(interface_config, name, force_attach=True)
|
||||
RNS.log(f"Interface \"{name}\" was attached", RNS.LOG_NOTICE)
|
||||
return True
|
||||
|
||||
except Exception as e: RNS.log(f"Error while detaching interface \"{name}\": {e}", RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
def _detach_interface(self, name, internal_forced=False):
|
||||
if not internal_forced and not Reticulum.__interface_management_enabled: return False
|
||||
try:
|
||||
interfaces = { iface.name: iface for iface in RNS.Transport.interfaces }
|
||||
if not name in interfaces:
|
||||
RNS.log(f"Attempt to detach non-existing interface \"{name}\"", RNS.LOG_WARNING)
|
||||
return None
|
||||
|
||||
else:
|
||||
interface = interfaces[name]
|
||||
if isinstance(interface, I2PInterface.I2PInterface): return False
|
||||
if isinstance(interface, LocalInterface.LocalClientInterface): return False
|
||||
if isinstance(interface, LocalInterface.LocalServerInterface): return False
|
||||
if interface.spawned_interfaces:
|
||||
if type(interface.spawned_interfaces) == dict: spawned = list(interface.spawned_interfaces.values())
|
||||
elif type(interface.spawned_interfaces) == list: spawned = interface.spawned_interfaces.copy()
|
||||
for s in spawned:
|
||||
s.detach()
|
||||
s.teardown()
|
||||
RNS.Transport.remove_interface(s)
|
||||
|
||||
interface.detach()
|
||||
RNS.Transport.remove_interface(interface)
|
||||
RNS.log(f"Interface \"{name}\" was detached", RNS.LOG_NOTICE)
|
||||
return True
|
||||
|
||||
except Exception as e: RNS.log(f"Error while detaching interface \"{name}\": {e}", RNS.LOG_ERROR); RNS.trace_exception(e)
|
||||
return False
|
||||
|
||||
def _reload_interface(self, name, internal_forced=False):
|
||||
if not internal_forced and not Reticulum.__interface_management_enabled: return False
|
||||
interfaces = { iface.name: iface for iface in RNS.Transport.interfaces }
|
||||
if not name in interfaces:
|
||||
RNS.log(f"Attempt to reload non-existing interface \"{name}\"", RNS.LOG_WARNING)
|
||||
return None
|
||||
|
||||
if self._detach_interface(name, internal_forced=internal_forced) and self._attach_interface(name, internal_forced=internal_forced):
|
||||
RNS.log(f"Interface \"{name}\" was reloaded", RNS.LOG_NOTICE)
|
||||
return True
|
||||
|
||||
else:
|
||||
RNS.log(f"Could not reload interface \"{name}\"", RNS.LOG_NOTICE)
|
||||
return False
|
||||
|
||||
def _synthesize_interface(self, config, name, instance_init=False, force_attach=False):
|
||||
c = config
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
|
||||
|
|
@ -804,15 +887,19 @@ class Reticulum:
|
|||
|
||||
ifac_netname = None
|
||||
if "networkname" in c:
|
||||
if c["networkname"] != "": ifac_netname = c["networkname"]
|
||||
if c["networkname"] == "None": RNS.log("Ambiguous IFAC network name \"None\", this value is ignored and an IFAC network name has NOT been set", RNS.LOG_WARNING)
|
||||
if c["networkname"] != "" and c["networkname"] != "None": ifac_netname = c["networkname"]
|
||||
if "network_name" in c:
|
||||
if c["network_name"] != "": ifac_netname = c["network_name"]
|
||||
if c["network_name"] == "None": RNS.log("Ambiguous IFAC network name \"None\", this value is ignored and an IFAC network name has NOT been set", RNS.LOG_WARNING)
|
||||
if c["network_name"] != "" and c["network_name"] != "None": ifac_netname = c["network_name"]
|
||||
|
||||
ifac_netkey = None
|
||||
if "passphrase" in c:
|
||||
if c["passphrase"] != "": ifac_netkey = c["passphrase"]
|
||||
if c["passphrase"] == "None": RNS.log("Ambiguous IFAC passphrase \"None\", this value is ignored and an IFAC passphrase has NOT been set", RNS.LOG_WARNING)
|
||||
if c["passphrase"] != "" and c["passphrase"] != "None": ifac_netkey = c["passphrase"]
|
||||
if "pass_phrase" in c:
|
||||
if c["pass_phrase"] != "": ifac_netkey = c["pass_phrase"]
|
||||
if c["pass_phrase"] == "None": RNS.log("Ambiguous IFAC passphrase \"None\", this value is ignored and an IFAC passphrase has NOT been set", RNS.LOG_WARNING)
|
||||
if c["pass_phrase"] != "" and c["pass_phrase"] != "None": ifac_netkey = c["pass_phrase"]
|
||||
|
||||
ingress_control = True
|
||||
if "ingress_control" in c: ingress_control = c.as_bool("ingress_control")
|
||||
|
|
@ -1005,11 +1092,16 @@ 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))
|
||||
|
||||
elif interface.discoverable and interface.discovery_publish_ifac:
|
||||
RNS.log(f"IFAC publishing was enabled for discoverable interface {interface}, but neither IFAC netname nor passphrase is configured", RNS.LOG_WARNING)
|
||||
RNS.log(f"Disabling IFAC publishing for {interface}", RNS.LOG_WARNING)
|
||||
interface.discovery_publish_ifac = False
|
||||
|
||||
RNS.Transport.add_interface(interface)
|
||||
interface.final_init()
|
||||
|
||||
interface = None
|
||||
if (("interface_enabled" in c) and c.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
|
||||
if (("interface_enabled" in c) and c.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True) or force_attach:
|
||||
interface_config = c
|
||||
interface_config["name"] = name
|
||||
interface_config["selected_interface_mode"] = interface_mode
|
||||
|
|
@ -1113,7 +1205,6 @@ class Reticulum:
|
|||
except Exception as e:
|
||||
RNS.log(f"External interface initialisation failed for {interface_type} / {name}", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
|
||||
else:
|
||||
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_DEBUG)
|
||||
|
||||
|
|
@ -1300,6 +1391,12 @@ class Reticulum:
|
|||
if path == "blackholed_identities": self.rpc_return(conn, self.get_blackholed_identities())
|
||||
if path == "is_blackholed": self.rpc_return(conn, self.is_blackholed(call["identity_hash"]))
|
||||
|
||||
if "manage" in call:
|
||||
path = call["manage"]
|
||||
if path == "attach_interface": self.rpc_return(conn, self.attach_interface(call["name"]))
|
||||
if path == "detach_interface": self.rpc_return(conn, self.detach_interface(call["name"]))
|
||||
if path == "reload_interface": self.rpc_return(conn, self.reload_interface(call["name"]))
|
||||
|
||||
if "drop" in call:
|
||||
path = call["drop"]
|
||||
if path == "path": self.rpc_return(conn, self.drop_path(call["destination_hash"]))
|
||||
|
|
@ -1867,21 +1964,39 @@ class Reticulum:
|
|||
if RNS.Profiler.ran(): return RNS.Profiler.results()
|
||||
else: return None
|
||||
|
||||
def halt_interface(self, interface):
|
||||
pass
|
||||
def attach_interface(self, interface_name):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"manage": "attach_interface", "name": interface_name}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
|
||||
else: return self._attach_interface(interface_name)
|
||||
|
||||
def resume_interface(self, interface):
|
||||
pass
|
||||
def detach_interface(self, interface_name):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"manage": "detach_interface", "name": interface_name}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
|
||||
else: return self._detach_interface(interface_name)
|
||||
|
||||
def reload_interface(self, interface):
|
||||
pass
|
||||
def reload_interface(self, interface_name):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"manage": "reload_interface", "name": interface_name}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
|
||||
else: return self._reload_interface(interface_name)
|
||||
|
||||
def get_blackholed_identities(self):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"get": "blackholed_identities"}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"get": "blackholed_identities"}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
|
||||
else: return RNS.Transport.blackholed_identities
|
||||
|
||||
|
|
@ -1892,10 +2007,10 @@ class Reticulum:
|
|||
if len(identity_hash) != RNS.Reticulum.TRUNCATED_HASHLENGTH//8: raise ValueError("Invalid identity hash length for blackhole check")
|
||||
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"get": "is_blackholed", "identity_hash": identity_hash}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"get": "is_blackholed", "identity_hash": identity_hash}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
|
||||
else: return identity_hash in RNS.Transport.blackholed_identities
|
||||
|
||||
|
|
@ -2032,6 +2147,10 @@ class Reticulum:
|
|||
def should_autoconnect_discovered_interfaces():
|
||||
return Reticulum.__autoconnect_discovered_interfaces > 0
|
||||
|
||||
@staticmethod
|
||||
def should_autoconnect_unverified_implementations():
|
||||
return Reticulum.__autoconnect_unverified
|
||||
|
||||
@staticmethod
|
||||
def autoconnect_interface_mode():
|
||||
return Reticulum.__autoconnect_interface_mode
|
||||
|
|
|
|||
|
|
@ -1521,7 +1521,7 @@ class Transport:
|
|||
# TODO: Rethink whether this is actually optimal.
|
||||
if packet.hops > 0:
|
||||
|
||||
if not hasattr(interface, "announce_cap"): interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
|
||||
if not hasattr(interface, "announce_cap"): interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP/100.0
|
||||
if not hasattr(interface, "announce_allowed_at"): interface.announce_allowed_at = 0
|
||||
if not hasattr(interface, "announce_queue"): interface.announce_queue = []
|
||||
|
||||
|
|
@ -3299,7 +3299,7 @@ class Transport:
|
|||
packet = RNS.Packet(path_request_dst, path_request_data, packet_type = RNS.Packet.DATA, transport_type = RNS.Transport.BROADCAST, header_type = RNS.Packet.HEADER_1, attached_interface = on_interface)
|
||||
|
||||
if on_interface != None and recursive:
|
||||
if not hasattr(on_interface, "announce_cap"): on_interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
|
||||
if not hasattr(on_interface, "announce_cap"): on_interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP/100.0
|
||||
if not hasattr(on_interface, "announce_allowed_at"): on_interface.announce_allowed_at = 0
|
||||
if not hasattr(on_interface, "announce_queue"): on_interface.announce_queue = []
|
||||
|
||||
|
|
|
|||
239
RNS/Utilities/rngit/media.py
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# - The Software shall not be used in any kind of system which includes amongst
|
||||
# its functions the ability to purposefully do harm to human beings.
|
||||
#
|
||||
# - The Software shall not be used, directly or indirectly, in the creation of
|
||||
# an artificial intelligence, machine learning or language model training
|
||||
# dataset, including but not limited to any use that contributes to the
|
||||
# training or development of such a model or algorithm.
|
||||
#
|
||||
# - The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import os
|
||||
import time
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import RNS
|
||||
|
||||
CONVERSION_TIMEOUT = 8
|
||||
|
||||
# WebP encoding backends, in preference order. All supported
|
||||
# encoder families handle the common still formats (PNG, JPEG,
|
||||
# TIFF, BMP, GIF, WebP).
|
||||
#
|
||||
# Environment overrides:
|
||||
# RNGIT_MEDIA_BACKEND force a specific backend
|
||||
|
||||
BACKENDS = ( ("magick", ["magick", "-", "webp:-"]),
|
||||
("convert", ["convert", "-", "webp:-"]),
|
||||
("gm", ["gm", "convert", "-", "webp:-"]),
|
||||
("ffmpeg", ["ffmpeg", "-y", "-loglevel", "error", "-i", "-", "-f", "webp", "pipe:1"]),
|
||||
("avconv", ["avconv", "-y", "-loglevel", "error", "-i", "-", "-f", "webp", "pipe:1"]) )
|
||||
|
||||
_ENV_BACKEND = os.environ.get("RNGIT_MEDIA_BACKEND")
|
||||
_winner = None
|
||||
_no_backend_logged = False
|
||||
|
||||
def available_backends():
|
||||
out = []
|
||||
for name, argv in BACKENDS: out.append((name, shutil.which(argv[0]) is not None))
|
||||
return out
|
||||
|
||||
def _selected_backend():
|
||||
global _winner
|
||||
if _ENV_BACKEND:
|
||||
for name, argv in BACKENDS:
|
||||
if name == _ENV_BACKEND and shutil.which(argv[0]) is not None:
|
||||
return (name, argv)
|
||||
return None
|
||||
|
||||
order = list(BACKENDS)
|
||||
if _winner is not None: order.sort(key=lambda backend: backend[0] != _winner)
|
||||
|
||||
for name, argv in order:
|
||||
if shutil.which(argv[0]) is not None:
|
||||
_winner = name
|
||||
return (name, argv)
|
||||
|
||||
return None
|
||||
|
||||
def _terminate(proc):
|
||||
try: proc.kill()
|
||||
except Exception: pass
|
||||
try: proc.wait()
|
||||
except Exception: pass
|
||||
|
||||
def _stderr_tail(proc, limit=1024):
|
||||
try:
|
||||
data = proc.stderr.read(limit)
|
||||
return data.decode("utf-8", "replace").strip()
|
||||
except Exception: return ""
|
||||
|
||||
def _webp_info(data):
|
||||
if len(data) < 30 or data[:4] != b"RIFF" or data[8:12] != b"WEBP": return None
|
||||
fourcc = data[12:16]
|
||||
if fourcc == b"VP8X":
|
||||
width = int.from_bytes(data[24:27], "little") + 1
|
||||
height = int.from_bytes(data[27:30], "little") + 1
|
||||
elif fourcc == b"VP8 ":
|
||||
width = int.from_bytes(data[26:28], "little") & 0x3FFF
|
||||
height = int.from_bytes(data[28:30], "little") & 0x3FFF
|
||||
elif fourcc == b"VP8L":
|
||||
bits = int.from_bytes(data[21:25], "little")
|
||||
width = (bits & 0x3FFF) + 1
|
||||
height = ((bits >> 14) & 0x3FFF) + 1
|
||||
else: return None
|
||||
if width > 0 and height > 0: return (width, height)
|
||||
return None
|
||||
|
||||
def _valid_webp(path):
|
||||
try:
|
||||
with open(path, "rb") as fh: return _webp_info(fh.read(30)) is not None
|
||||
except Exception: return False
|
||||
|
||||
def _unlink_output(path):
|
||||
try: os.unlink(path)
|
||||
except Exception: pass
|
||||
|
||||
def _configured_backend(quality=None, max_dimension=None):
|
||||
global _no_backend_logged
|
||||
backend = _selected_backend()
|
||||
if backend is None:
|
||||
if not _no_backend_logged:
|
||||
_no_backend_logged = True
|
||||
RNS.log("No WebP encoding backend available for media conversion. You can Install ImageMagick or ffmpeg to enable image conversion.", RNS.LOG_WARNING)
|
||||
return None
|
||||
|
||||
backend_name, encoder_argv = backend
|
||||
_no_backend_logged = False
|
||||
|
||||
quality_arg = None
|
||||
dimension_arg = None
|
||||
if quality is not None:
|
||||
try: quality_arg = max(1, min(100, int(quality)))
|
||||
except (ValueError, TypeError): pass
|
||||
if max_dimension is not None:
|
||||
try: dimension_arg = int(max_dimension)
|
||||
except (ValueError, TypeError): pass
|
||||
if dimension_arg is not None and dimension_arg < 1: dimension_arg = None
|
||||
|
||||
encoder_argv = list(encoder_argv)
|
||||
if backend_name in ("magick", "convert", "gm"):
|
||||
options = []
|
||||
if quality_arg is not None: options += ["-quality", str(quality_arg)]
|
||||
if dimension_arg is not None: options += ["-resize", f"{dimension_arg}x{dimension_arg}>"]
|
||||
encoder_argv = encoder_argv[:-1] + options + encoder_argv[-1:]
|
||||
elif backend_name in ("ffmpeg", "avconv"):
|
||||
options = []
|
||||
if quality_arg is not None: options += ["-quality", str(quality_arg)]
|
||||
if dimension_arg is not None: options += ["-vf", f"scale='min(iw,{dimension_arg})':'min(ih,{dimension_arg})':force_original_aspect_ratio=decrease"]
|
||||
try: format_index = encoder_argv.index("-f")
|
||||
except ValueError: format_index = len(encoder_argv)
|
||||
encoder_argv = encoder_argv[:format_index] + options + encoder_argv[format_index:]
|
||||
|
||||
return backend_name, encoder_argv
|
||||
|
||||
def _await(backend_name, encoder_proc, input_proc=None, timeout=None):
|
||||
if timeout is None: timeout = CONVERSION_TIMEOUT
|
||||
|
||||
deadline = time.time() + timeout
|
||||
try:
|
||||
encoder_proc.wait(timeout=max(0.0, deadline - time.time()))
|
||||
if input_proc is not None: input_proc.wait(timeout=max(0.0, deadline - time.time()))
|
||||
except subprocess.TimeoutExpired:
|
||||
_terminate(input_proc)
|
||||
_terminate(encoder_proc)
|
||||
RNS.log(f"Media conversion via {backend_name} timed out after {timeout} seconds", RNS.LOG_WARNING)
|
||||
return False
|
||||
|
||||
if encoder_proc.returncode != 0:
|
||||
detail = ""
|
||||
encoder_tail = _stderr_tail(encoder_proc)
|
||||
input_tail = _stderr_tail(input_proc) if input_proc is not None else ""
|
||||
if encoder_tail: detail += f" Encoder: {encoder_tail}"
|
||||
if input_tail: detail += f" Input: {input_tail}"
|
||||
RNS.log(f"Media conversion via {backend_name} failed.{detail}", RNS.LOG_WARNING)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def convert_to_webp(input_argv, output_path, cwd=None, timeout=None, quality=None, max_dimension=None):
|
||||
configured = _configured_backend(quality=quality, max_dimension=max_dimension)
|
||||
if configured is None: return False
|
||||
|
||||
backend_name, encoder_argv = configured
|
||||
|
||||
try:
|
||||
with open(output_path, "wb") as output_fh:
|
||||
input_proc = subprocess.Popen(input_argv, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
encoder_proc = subprocess.Popen(encoder_argv, stdin=input_proc.stdout, stdout=output_fh, stderr=subprocess.PIPE)
|
||||
|
||||
# The parent must not retain a copy of the input pipe's write
|
||||
# end, or the input process would never see EOF.
|
||||
input_proc.stdout.close()
|
||||
if not _await(backend_name, encoder_proc, input_proc=input_proc, timeout=timeout): return False
|
||||
|
||||
if not _valid_webp(output_path):
|
||||
RNS.log(f"Media conversion via {backend_name} produced invalid WebP output", RNS.LOG_WARNING)
|
||||
return False
|
||||
|
||||
RNS.log(f"Media converted to WebP with {backend_name}", RNS.LOG_DEBUG)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error during media conversion: {e}", RNS.LOG_WARNING)
|
||||
return False
|
||||
|
||||
def convert_file_to_webp(source_path, quality=85, max_dimension=None, timeout=None):
|
||||
configured = _configured_backend(quality=quality, max_dimension=max_dimension)
|
||||
if configured is None: return False
|
||||
backend_name, encoder_argv = configured
|
||||
|
||||
tmp_path = None
|
||||
success = False
|
||||
try:
|
||||
if not os.path.isfile(source_path):
|
||||
RNS.log(f"Cannot convert media: source file does not exist: {source_path}", RNS.LOG_WARNING)
|
||||
return False
|
||||
|
||||
fd, tmp_path = tempfile.mkstemp(prefix="rns_media_", suffix=".webp")
|
||||
os.close(fd)
|
||||
|
||||
with open(source_path, "rb") as input_fh, open(tmp_path, "wb") as output_fh:
|
||||
encoder_proc = subprocess.Popen(encoder_argv, stdin=input_fh, stdout=output_fh, stderr=subprocess.PIPE)
|
||||
if not _await(backend_name, encoder_proc, timeout=timeout): return False
|
||||
|
||||
if not _valid_webp(tmp_path):
|
||||
RNS.log(f"Invalid media conversion output from {backend_name}", RNS.LOG_WARNING)
|
||||
return False
|
||||
|
||||
success = True
|
||||
RNS.log(f"Media converted to WebP with {backend_name}", RNS.LOG_DEBUG)
|
||||
return tmp_path
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error during media conversion: {e}", RNS.LOG_WARNING)
|
||||
return False
|
||||
|
||||
finally:
|
||||
if not success and tmp_path is not None: _unlink_output(tmp_path)
|
||||
|
|
@ -32,13 +32,14 @@ import os
|
|||
import time
|
||||
import threading
|
||||
import subprocess
|
||||
import tempfile
|
||||
import urllib.parse
|
||||
import RNS
|
||||
import struct
|
||||
import base64
|
||||
from collections import deque
|
||||
from datetime import datetime
|
||||
from RNS.Utilities.rngit import APP_NAME
|
||||
from RNS.Utilities.rngit import APP_NAME, media
|
||||
from RNS.Utilities.rngit.util import MarkdownToMicron, san_sha
|
||||
from RNS.Utilities.rngit.highlight import SyntaxHighlighter
|
||||
from RNS.vendor.configobj import ConfigObj
|
||||
|
|
@ -50,6 +51,7 @@ from RNS.Utilities.rngit.commitsigs import unarmor_ssh_signature, parse_ssh_sign
|
|||
class NomadNetworkNode():
|
||||
APP_NAME = "nomadnetwork"
|
||||
JOBS_INTERVAL = 5
|
||||
LINK_CLEAN_INTERVAL = 60
|
||||
|
||||
PATH_INDEX = "/page/index.mu"
|
||||
PATH_GROUP = "/page/group.mu"
|
||||
|
|
@ -64,6 +66,7 @@ class NomadNetworkNode():
|
|||
PATH_RELEASE = "/page/release.mu"
|
||||
PATH_WORK = "/page/work.mu"
|
||||
PATH_WORK_DOC = "/page/work_doc.mu"
|
||||
PATH_MEDIA = "/media"
|
||||
FILE_ARTIFACT = "/file/artifact"
|
||||
FILE_DOWNLOAD = "/file/download"
|
||||
FILE_WORKDOC = "/file/workdoc"
|
||||
|
|
@ -120,8 +123,10 @@ class NomadNetworkNode():
|
|||
# want to use tabs, three spaces is all you get.
|
||||
TAB_WIDTH = " "
|
||||
|
||||
RENDERABLE_EXTS = [".md", ".mu"]
|
||||
RENDER_DEFAULT = [".md", ".mu"]
|
||||
CONVERTABLE_EXTS = [".md"]
|
||||
RENDERABLE_EXTS = [".md", ".mu"]
|
||||
RENDER_DEFAULT = [".md", ".mu"]
|
||||
IMAGE_EXTS = [".webp", ".png", ".jpg", ".jpeg", ".gif", ".tiff", ".tif", ".bmp"]
|
||||
|
||||
def __init__(self, owner=None):
|
||||
if not owner: raise TypeError(f"Invalid owner {owner} for {self}")
|
||||
|
|
@ -150,12 +155,16 @@ class NomadNetworkNode():
|
|||
self.templates["stats"] = DEFAULT_STATS_TEMPLATE
|
||||
self.templates["work"] = DEFAULT_WORK_TEMPLATE
|
||||
self.templates["work_doc"] = DEFAULT_WORK_DOC_TEMPLATE
|
||||
self.templates["no_ident"] = DEFAULT_NO_IDENT_TEMPLATE
|
||||
self.templatesdir = self.owner.configdir+"/templates"
|
||||
self.use_nerdfonts = self.USE_NERDFONTS
|
||||
self.media_conversion = True
|
||||
self.highlight_syntax = True
|
||||
self.highlighter = SyntaxHighlighter()
|
||||
self.mdc = MarkdownToMicron(max_width=self.MAX_RENDER_WIDTH, syntax_highlighter=self.highlighter)
|
||||
self.thanks_deque = deque(maxlen=256)
|
||||
self.active_links = {}
|
||||
self.last_link_clean = 0
|
||||
|
||||
if not os.path.isdir(self.templatesdir):
|
||||
try: os.makedirs(self.templatesdir)
|
||||
|
|
@ -164,6 +173,8 @@ class NomadNetworkNode():
|
|||
if "pages" in self.owner.config:
|
||||
if "unicode_icons" in self.owner.config["pages"]:
|
||||
if self.owner.config["pages"].as_bool("unicode_icons"): self.use_nerdfonts = False
|
||||
if "media_conversion" in self.owner.config["pages"]:
|
||||
self.media_conversion = self.owner.config["pages"].as_bool("media_conversion")
|
||||
|
||||
self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, self.APP_NAME, "node")
|
||||
self.destination.set_link_established_callback(self.remote_connected)
|
||||
|
|
@ -210,6 +221,10 @@ class NomadNetworkNode():
|
|||
try:
|
||||
if self.announce_interval and time.time() > self.last_announce + self.announce_interval: self.announce()
|
||||
|
||||
if time.time() > self.last_link_clean + self.LINK_CLEAN_INTERVAL:
|
||||
self.clean_links()
|
||||
self.last_link_clean = time.time()
|
||||
|
||||
except Exception as e: RNS.log(f"Error while running periodic jobs: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def get_announce_app_data(self): return self.node_name.encode("utf-8")
|
||||
|
|
@ -246,6 +261,7 @@ class NomadNetworkNode():
|
|||
self.destination.register_request_handler(self.PATH_RELEASE, response_generator=self.serve_release_page, allow=RNS.Destination.ALLOW_ALL)
|
||||
self.destination.register_request_handler(self.PATH_WORK, response_generator=self.serve_work_page, allow=RNS.Destination.ALLOW_ALL)
|
||||
self.destination.register_request_handler(self.PATH_WORK_DOC, response_generator=self.serve_work_doc_page, allow=RNS.Destination.ALLOW_ALL)
|
||||
self.destination.register_request_handler(self.PATH_MEDIA, response_generator=self.serve_media, allow=RNS.Destination.ALLOW_ALL, auto_compress=False)
|
||||
self.destination.register_request_handler(self.FILE_ARTIFACT, response_generator=self.serve_artifact, allow=RNS.Destination.ALLOW_ALL)
|
||||
self.destination.register_request_handler(self.FILE_DOWNLOAD, response_generator=self.serve_download, allow=RNS.Destination.ALLOW_ALL)
|
||||
self.destination.register_request_handler(self.FILE_WORKDOC, response_generator=self.serve_wd_download, allow=RNS.Destination.ALLOW_ALL)
|
||||
|
|
@ -372,6 +388,10 @@ class NomadNetworkNode():
|
|||
self.owner.view_succeeded(None, None, remote_identity)
|
||||
page_content = "".join(content_parts)
|
||||
nav_content = "".join(nav_parts)
|
||||
|
||||
if not remote_identity and self.null_ident.hash in self.owner.blocked_identities:
|
||||
return self.render_template("", template="no_ident", nav_content=nav_content, st=st)
|
||||
|
||||
return self.render_template(page_content, nav_content=nav_content, template="front", st=st)
|
||||
|
||||
def serve_group_page(self, path, data, request_id, link_id, remote_identity, requested_at):
|
||||
|
|
@ -392,6 +412,9 @@ class NomadNetworkNode():
|
|||
nav_parts.append(breadcrumb + "\n")
|
||||
nav_content = "".join(nav_parts)
|
||||
|
||||
if not remote_identity and self.null_ident.hash in self.owner.blocked_identities:
|
||||
return self.render_template("", template="no_ident", nav_content=nav_content, st=st)
|
||||
|
||||
accessible_repos = self.get_accessible_repositories(remote_identity, group_name)
|
||||
|
||||
if not group_name or group_name not in self.owner.groups or not accessible_repos:
|
||||
|
|
@ -441,6 +464,9 @@ class NomadNetworkNode():
|
|||
|
||||
repo = self.get_accessible_repository(remote_identity, group_name, repo_name)
|
||||
|
||||
if not remote_identity and self.null_ident.hash in self.owner.blocked_identities:
|
||||
return self.render_template("", template="no_ident", nav_content="".join(nav_parts), st=st)
|
||||
|
||||
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)
|
||||
|
|
@ -554,6 +580,9 @@ class NomadNetworkNode():
|
|||
|
||||
except (ValueError, TypeError): page_num = 0
|
||||
|
||||
if not remote_identity and self.null_ident.hash in self.owner.blocked_identities:
|
||||
return self.render_template("", template="no_ident", st=st)
|
||||
|
||||
repo = self.get_accessible_repository(remote_identity, group_name, repo_name)
|
||||
if not repo:
|
||||
content = self.m_heading("Not Found", 1) + "\n\nThe requested repository does not exist or you do not have access to it.\n"
|
||||
|
|
@ -691,6 +720,9 @@ class NomadNetworkNode():
|
|||
render = True if render else False
|
||||
raw = True if raw else False
|
||||
|
||||
if not remote_identity and self.null_ident.hash in self.owner.blocked_identities:
|
||||
return self.render_template("", template="no_ident", st=st)
|
||||
|
||||
repo = self.get_accessible_repository(remote_identity, group_name, repo_name)
|
||||
if not repo:
|
||||
content = self.m_heading("Not Found", 1) + "\n\nThe requested repository does not exist or you do not have access to it.\n"
|
||||
|
|
@ -712,6 +744,7 @@ class NomadNetworkNode():
|
|||
file_path = file_path.removeprefix("./").replace("/./", "/")
|
||||
file_ext = os.path.splitext(file_path)[1].lower()
|
||||
renderable = file_ext in self.RENDERABLE_EXTS
|
||||
convertable = file_ext in self.CONVERTABLE_EXTS
|
||||
if not renderable: raw = True; render = False
|
||||
else:
|
||||
if raw: render = False
|
||||
|
|
@ -738,14 +771,16 @@ class NomadNetworkNode():
|
|||
nav_parts.append(">>\n" + breadcrumb + "\n")
|
||||
sep = self.icon("sep")
|
||||
|
||||
dl_link = self.m_link("Download", self.FILE_DOWNLOAD, g=group_name, r=repo_name, ref=ref, path=file_path)
|
||||
dl_link = self.m_link("Download", self.FILE_DOWNLOAD, g=group_name, r=repo_name, ref=ref, path=file_path)
|
||||
if not renderable: nav_parts.append(f"\nDisplaying Raw {sep} {dl_link}\n")
|
||||
else:
|
||||
rnd_link = self.m_link("View rendered", self.PATH_BLOB, g=group_name, r=repo_name, ref=ref, path=file_path, render="y")
|
||||
raw_link = self.m_link("View raw", self.PATH_BLOB, g=group_name, r=repo_name, ref=ref, path=file_path, raw="y")
|
||||
mu_link = self.m_link("as micron", self.FILE_DOWNLOAD, g=group_name, r=repo_name, ref=ref, path=file_path, fmt="mu")
|
||||
if render: render_controls = f"Displaying Rendered {sep} {raw_link}"
|
||||
else: render_controls = f"Displaying Raw {sep} {rnd_link}"
|
||||
nav_parts.append(f"\n{render_controls} {sep} {dl_link}\n")
|
||||
if convertable: nav_parts.append(f"\n{render_controls} {sep} {dl_link} {self.CLR_DIM}{mu_link}`f\n")
|
||||
else: nav_parts.append(f"\n{render_controls} {sep} {dl_link}\n")
|
||||
|
||||
# Get blob info
|
||||
blob_info = self.get_blob_info(repo_path, resolved_ref, file_path)
|
||||
|
|
@ -771,8 +806,9 @@ class NomadNetworkNode():
|
|||
content_parts.append(f"`*{self.m_escape(symlink_target or 'unknown')}`*\n")
|
||||
|
||||
elif is_binary:
|
||||
content_parts.append("This file appears to be binary and cannot be displayed as text.\n")
|
||||
# TODO: Implement raw file downloads
|
||||
if file_ext in self.IMAGE_EXTS:
|
||||
content_parts.append(f"`(Image file`w=n`a=c`:/media/{group_name}/{repo_name}/{ref}/{urllib.parse.quote_plus(file_path)})\n")
|
||||
else: content_parts.append("This file appears to be binary and cannot be displayed as text.\n")
|
||||
|
||||
elif size > self.BLOB_SIZE_LIMIT:
|
||||
content_parts.append(f"This file is {RNS.prettysize(size)}, which exceeds the display limit of {RNS.prettysize(self.BLOB_SIZE_LIMIT)}.\n")
|
||||
|
|
@ -823,6 +859,9 @@ class NomadNetworkNode():
|
|||
|
||||
except (ValueError, TypeError): page_num = 0
|
||||
|
||||
if not remote_identity and self.null_ident.hash in self.owner.blocked_identities:
|
||||
return self.render_template("", template="no_ident", st=st)
|
||||
|
||||
repo = self.get_accessible_repository(remote_identity, group_name, repo_name)
|
||||
if not repo:
|
||||
content = self.m_heading("Not Found", 1) + "\n\nThe requested repository does not exist or you do not have access to it.\n"
|
||||
|
|
@ -902,6 +941,9 @@ class NomadNetworkNode():
|
|||
content = self.m_heading("Error", 2) + "\nInvalid request\n"
|
||||
return self.render_template(content, st=st)
|
||||
|
||||
if not remote_identity and self.null_ident.hash in self.owner.blocked_identities:
|
||||
return self.render_template("", template="no_ident", st=st)
|
||||
|
||||
repo = self.get_accessible_repository(remote_identity, group_name, repo_name)
|
||||
if not repo:
|
||||
content = self.m_heading("Error", 2) + "\nThe requested repository was not found.\n"
|
||||
|
|
@ -1057,6 +1099,9 @@ class NomadNetworkNode():
|
|||
nav_parts.append(">>\n" + breadcrumb + "\n")
|
||||
nav_content = "".join(nav_parts)
|
||||
|
||||
if not remote_identity and self.null_ident.hash in self.owner.blocked_identities:
|
||||
return self.render_template("", template="no_ident", st=st)
|
||||
|
||||
if not group_name or not repo_name:
|
||||
content = self.m_heading("Error", 2) + "\nInvalid request\n"
|
||||
return self.render_template(content, st=st)
|
||||
|
|
@ -1155,6 +1200,9 @@ class NomadNetworkNode():
|
|||
if not group_name or not repo_name:
|
||||
content = self.m_heading("Error", 2) + "\nInvalid request\n"
|
||||
return self.render_template(content, st=st)
|
||||
|
||||
if not remote_identity and self.null_ident.hash in self.owner.blocked_identities:
|
||||
return self.render_template("", template="no_ident", st=st)
|
||||
|
||||
content_parts = []
|
||||
nav_parts = []
|
||||
|
|
@ -1256,6 +1304,9 @@ class NomadNetworkNode():
|
|||
content = self.m_heading("Error", 2) + "\nInvalid request\n"
|
||||
return self.render_template(content, st=st)
|
||||
|
||||
if not remote_identity and self.null_ident.hash in self.owner.blocked_identities:
|
||||
return self.render_template("", template="no_ident", st=st)
|
||||
|
||||
repo = self.get_accessible_repository(remote_identity, group_name, repo_name)
|
||||
if not repo:
|
||||
content = self.m_heading("Error", 2) + "\nThe requested repository was not found.\n"
|
||||
|
|
@ -1320,6 +1371,9 @@ class NomadNetworkNode():
|
|||
content = self.m_heading("Error", 2) + "\nInvalid request\n"
|
||||
return self.render_template(content, st=st)
|
||||
|
||||
if not remote_identity and self.null_ident.hash in self.owner.blocked_identities:
|
||||
return self.render_template("", template="no_ident", st=st)
|
||||
|
||||
repo = self.get_accessible_repository(remote_identity, group_name, repo_name)
|
||||
if not repo:
|
||||
content = self.m_heading("Error", 2) + "\nThe requested repository was not found.\n"
|
||||
|
|
@ -1422,11 +1476,17 @@ class NomadNetworkNode():
|
|||
content = self.m_heading("Error", 2) + "\nInvalid request\n"
|
||||
return self.render_template(content, st=st)
|
||||
|
||||
if not remote_identity and self.null_ident.hash in self.owner.blocked_identities:
|
||||
return self.render_template("", template="no_ident", st=st)
|
||||
|
||||
repo = self.get_accessible_repository(remote_identity, group_name, repo_name)
|
||||
if not repo:
|
||||
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"
|
||||
scopes_to_show = ["active", "completed", "proposed"] if scope == "all" else [scope]
|
||||
|
||||
content_parts = []
|
||||
nav_parts = []
|
||||
|
||||
|
|
@ -1441,17 +1501,46 @@ class NomadNetworkNode():
|
|||
cmplt_s = "`_" if scope == "completed" else ""
|
||||
prpsd_s = "`_" if scope == "proposed" else ""
|
||||
all_s = "`_" if scope == "all" else ""
|
||||
|
||||
# Count work documents
|
||||
scope_counts = {"active": 0, "completed": 0, "proposed": 0}
|
||||
for s in ["active", "completed", "proposed"]:
|
||||
try:
|
||||
folder_path = os.path.join(work_path, s)
|
||||
if os.path.isdir(folder_path):
|
||||
for entry in os.listdir(folder_path):
|
||||
try:
|
||||
doc_dir = os.path.join(folder_path, entry)
|
||||
if not os.path.isdir(doc_dir): continue
|
||||
|
||||
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
|
||||
scope_counts[s] += 1
|
||||
except Exception as e: RNS.trace_exception(e)
|
||||
except Exception as e: RNS.trace_exception(e)
|
||||
|
||||
force_counts = True
|
||||
adc = scope_counts['active']
|
||||
cdc = scope_counts['completed']
|
||||
pdc = scope_counts['proposed']
|
||||
tdc = adc+cdc+pdc
|
||||
adc_str = f" ({adc})" if force_counts or (not scope == "active" and adc) else ""
|
||||
cdc_str = f" ({cdc})" if force_counts or (not scope == "completed" and cdc) else ""
|
||||
pdc_str = f" ({pdc})" if force_counts or (not scope == "proposed" and pdc) else ""
|
||||
tdc_str = f" ({tdc})" if force_counts or (not scope == "all" and tdc) 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)
|
||||
filter_links.append(active_s+self.m_link("Active", self.PATH_WORK, g=group_name, r=repo_name, scope="active")+active_s+adc_str)
|
||||
filter_links.append(cmplt_s+self.m_link("Completed", self.PATH_WORK, g=group_name, r=repo_name, scope="completed")+cmplt_s+cdc_str)
|
||||
filter_links.append(prpsd_s+self.m_link("Proposed", self.PATH_WORK, g=group_name, r=repo_name, scope="proposed")+prpsd_s+pdc_str)
|
||||
filter_links.append(all_s+self.m_link("All", self.PATH_WORK, g=group_name, r=repo_name, scope="all")+all_s+tdc_str)
|
||||
content_parts.append(f" {sep} ".join(filter_links) + "\n\n")
|
||||
|
||||
# Load work documents
|
||||
work_path = f"{repo['path']}.work"
|
||||
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)
|
||||
|
||||
|
|
@ -1483,11 +1572,11 @@ class NomadNetworkNode():
|
|||
docs.sort(key=lambda x: max(x["created"], x["edited"]), reverse=True)
|
||||
|
||||
if not docs:
|
||||
content_parts.append(self.m_heading(f"{s.capitalize()} ({len(docs)})", 2)+f"\n`*No {s} work documents`*\n")
|
||||
content_parts.append(self.m_heading(f"{s.capitalize()}", 2)+f"\n`*No {s} work documents`*\n")
|
||||
content_parts.append("\n")
|
||||
|
||||
else:
|
||||
content_parts.append(self.m_heading(f"{s.capitalize()} ({len(docs)})", 2))
|
||||
content_parts.append(self.m_heading(f"{s.capitalize()}", 2))
|
||||
content_parts.append("\n")
|
||||
|
||||
for doc in docs:
|
||||
|
|
@ -1522,6 +1611,9 @@ class NomadNetworkNode():
|
|||
content = self.m_heading("Error", 2) + "\nInvalid request\n"
|
||||
return self.render_template(content, st=st)
|
||||
|
||||
if not remote_identity and self.null_ident.hash in self.owner.blocked_identities:
|
||||
return self.render_template("", template="no_ident", st=st)
|
||||
|
||||
try: doc_id = int(doc_id)
|
||||
except:
|
||||
content = self.m_heading("Error", 2) + "\nInvalid document ID\n"
|
||||
|
|
@ -1671,7 +1763,7 @@ class NomadNetworkNode():
|
|||
|
||||
repo = self.get_accessible_repository(remote_identity, group_name, repo_name)
|
||||
if not repo:
|
||||
RNS.log(f"Repository not found or no access for artifact request {group_name}/{repo_name}/{tag}/{artifact}", RNS.LOG_WARNING)
|
||||
RNS.log(f"Repository not found or no access for artifact request {group_name}/{repo_name}/{tag}/{artifact}", RNS.LOG_DEBUG)
|
||||
return None
|
||||
|
||||
releases_path = f"{repo['path']}.releases"
|
||||
|
|
@ -1715,6 +1807,69 @@ class NomadNetworkNode():
|
|||
self.owner.release_download_succeeded(group_name, repo_name, remote_identity)
|
||||
return [open(artifact_path, "rb"), {"name": artifact.encode("utf-8")}]
|
||||
|
||||
def serve_media(self, path, data, request_id, link_id, remote_identity, requested_at):
|
||||
st = time.time()
|
||||
RNS.log(f"Media request from {remote_identity}", RNS.LOG_DEBUG)
|
||||
|
||||
if not data or not type(data) == dict: data = {}
|
||||
if not "key" in data:
|
||||
RNS.log(f"Missing request key in media request", RNS.LOG_DEBUG)
|
||||
return False
|
||||
|
||||
media_path = data.get("path", None)
|
||||
if not media_path:
|
||||
RNS.log(f"Missing path in media request", RNS.LOG_DEBUG)
|
||||
return False
|
||||
|
||||
comps = media_path.removeprefix("/media").lstrip("/").split("/")
|
||||
if len(comps) < 4:
|
||||
RNS.log(f"Insufficient path components in media request", RNS.LOG_DEBUG)
|
||||
return False
|
||||
|
||||
group_name = comps[0]
|
||||
repo_name = comps[1]
|
||||
ref = comps[2]
|
||||
file_path = "/".join(comps[3:])
|
||||
file_path = urllib.parse.unquote_plus(file_path)
|
||||
file_name = os.path.basename(file_path)
|
||||
|
||||
repo = self.get_accessible_repository(remote_identity, group_name, repo_name)
|
||||
if not repo:
|
||||
RNS.log(f"Repository not found or no access for media request {group_name}/{repo_name}/{ref}/{file_path}", RNS.LOG_DEBUG)
|
||||
return False
|
||||
|
||||
repo_path = repo["path"]
|
||||
|
||||
resolved_ref = self.resolve_ref(repo_path, ref)
|
||||
if not resolved_ref:
|
||||
RNS.log(f"Ref not found for media request {group_name}/{repo_name}/{ref}/{file_path}", RNS.LOG_WARNING)
|
||||
return False
|
||||
|
||||
if not file_path:
|
||||
RNS.log(f"No file path for media request {group_name}/{repo_name}/{ref}/{file_path}", RNS.LOG_WARNING)
|
||||
return False
|
||||
|
||||
blob_info = self.get_blob_info(repo_path, resolved_ref, file_path)
|
||||
if blob_info is None:
|
||||
RNS.log(f"File not found at ref for media request {group_name}/{repo_name}/{ref}/{file_path}", RNS.LOG_WARNING)
|
||||
return False
|
||||
|
||||
else:
|
||||
stream = None
|
||||
response_name = file_name
|
||||
file_ext = os.path.splitext(file_path)[1].lower()
|
||||
if self.media_conversion and file_ext in self.IMAGE_EXTS and file_ext != ".webp":
|
||||
converted = self.get_webp_stream(repo_path, resolved_ref, file_path, link_id)
|
||||
if converted: stream, response_name = converted
|
||||
|
||||
if not stream: stream = self.get_blob_stream(repo_path, resolved_ref, file_path)
|
||||
if stream: return [stream, {"name": response_name.encode("utf-8")}]
|
||||
else:
|
||||
RNS.log(f"Could not resolve blob stream for media request {group_name}/{repo_name}/{ref}/{file_path}", RNS.LOG_WARNING)
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
def serve_download(self, path, data, request_id, link_id, remote_identity, requested_at):
|
||||
st = time.time()
|
||||
RNS.log(f"File download request from {remote_identity}", RNS.LOG_DEBUG)
|
||||
|
|
@ -1723,13 +1878,19 @@ class NomadNetworkNode():
|
|||
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"
|
||||
file_fmt = data.get("var_fmt", "") if data else ""
|
||||
file_path = data.get("var_path", "") if data else ""
|
||||
file_path = urllib.parse.unquote_plus(file_path)
|
||||
file_name = os.path.basename(file_path)
|
||||
file_ext = os.path.splitext(file_path)[1].lower()
|
||||
convertable = file_ext in self.CONVERTABLE_EXTS
|
||||
if file_fmt and not convertable:
|
||||
RNS.log(f"Download conversion request for non-convertable file", RNS.LOG_DEBUG)
|
||||
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 download request {group_name}/{repo_name}/{ref}/{file_path}", RNS.LOG_WARNING)
|
||||
RNS.log(f"Repository not found or no access for download request {group_name}/{repo_name}/{ref}/{file_path}", RNS.LOG_DEBUG)
|
||||
return None
|
||||
|
||||
repo_path = repo["path"]
|
||||
|
|
@ -1752,7 +1913,47 @@ class NomadNetworkNode():
|
|||
stream = self.get_blob_stream(repo_path, resolved_ref, file_path)
|
||||
if stream is not None:
|
||||
self.owner.download_succeeded(group_name, repo_name, remote_identity)
|
||||
return [stream, {"name": file_name.encode("utf-8")}]
|
||||
if not file_fmt: return [stream, {"name": file_name.encode("utf-8")}]
|
||||
elif file_fmt == "mu":
|
||||
link = self.active_links.get(link_id)
|
||||
if not link:
|
||||
RNS.log(f"Could not resolve link for file conversion of {file_path}", RNS.LOG_WARNING)
|
||||
return None
|
||||
|
||||
if not hasattr(link, "temporary_directories"): link.temporary_directories = []
|
||||
tmpdir = tempfile.TemporaryDirectory()
|
||||
link.temporary_directories.append(tmpdir)
|
||||
|
||||
stem = os.path.splitext(os.path.basename(file_path))[0]
|
||||
response_name = stem + ".mu"
|
||||
|
||||
try:
|
||||
path_components = file_path.strip("/").split("/")
|
||||
path = "/".join(path_components[:-1])+"/" if len(path_components) > 1 else ""
|
||||
url_scope = f":/page/blob.mu`g={group_name}|r={repo_name}|ref={ref}|path={path}"
|
||||
mdc = MarkdownToMicron(max_width=self.MAX_RENDER_WIDTH, syntax_highlighter=self.highlighter, url_scope=url_scope)
|
||||
mu = mdc.format_block(stream.read().decode("utf-8")).rstrip().encode("utf-8")
|
||||
|
||||
if not mu:
|
||||
if tmpdir in link.temporary_directories: link.temporary_directories.remove(tmpdir)
|
||||
tmpdir.cleanup()
|
||||
return None
|
||||
|
||||
fd, spool_path = tempfile.mkstemp(prefix=stem+".", suffix=".mu", dir=tmpdir.name)
|
||||
os.close(fd)
|
||||
with open(spool_path, "wb") as f: f.write(mu)
|
||||
|
||||
spool = open(spool_path, "rb")
|
||||
return [spool, {"name": f"{os.path.splitext(file_name)[0]}.mu".encode("utf-8")}]
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error during file conversion of {file_path} for {link}: {e}", RNS.LOG_WARNING)
|
||||
if tmpdir in link.temporary_directories: link.temporary_directories.remove(tmpdir)
|
||||
try: tmpdir.cleanup()
|
||||
except Exception: pass
|
||||
return None
|
||||
|
||||
else: return None
|
||||
|
||||
else:
|
||||
RNS.log(f"Could not resolve blob stream for download request {group_name}/{repo_name}/{ref}/{file_path}", RNS.LOG_WARNING)
|
||||
|
|
@ -1783,7 +1984,7 @@ class NomadNetworkNode():
|
|||
|
||||
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}", 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_DEBUG)
|
||||
return None
|
||||
|
||||
doc_access = self.resolve_doc_permission(remote_identity, group_name, repo_name, doc_id, self.owner.PERM_READ)
|
||||
|
|
@ -2084,6 +2285,40 @@ class NomadNetworkNode():
|
|||
|
||||
return None
|
||||
|
||||
def get_webp_stream(self, repo_path, ref, file_path, link_id):
|
||||
file_path = file_path.strip("/")
|
||||
link = self.active_links.get(link_id)
|
||||
if not link:
|
||||
RNS.log(f"Could not resolve link for media conversion of {file_path}", RNS.LOG_WARNING)
|
||||
return None
|
||||
|
||||
if not hasattr(link, "temporary_directories"): link.temporary_directories = []
|
||||
tmpdir = tempfile.TemporaryDirectory()
|
||||
link.temporary_directories.append(tmpdir)
|
||||
|
||||
stem = os.path.splitext(os.path.basename(file_path))[0]
|
||||
response_name = stem + ".webp"
|
||||
|
||||
try:
|
||||
fd, spool_path = tempfile.mkstemp(prefix=stem+".", suffix=".webp", dir=tmpdir.name)
|
||||
os.close(fd)
|
||||
|
||||
converted = media.convert_to_webp(["git", "show", f"{ref}:{file_path}"], spool_path, cwd=repo_path)
|
||||
if not converted:
|
||||
if tmpdir in link.temporary_directories: link.temporary_directories.remove(tmpdir)
|
||||
tmpdir.cleanup()
|
||||
return None
|
||||
|
||||
spool = open(spool_path, "rb")
|
||||
return spool, response_name
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error during media conversion of {file_path} for {link}: {e}", RNS.LOG_WARNING)
|
||||
if tmpdir in link.temporary_directories: link.temporary_directories.remove(tmpdir)
|
||||
try: tmpdir.cleanup()
|
||||
except Exception: pass
|
||||
return None
|
||||
|
||||
def get_refs_info(self, repo_path, default_branch=None):
|
||||
refs = {"heads": [], "tags": []}
|
||||
|
||||
|
|
@ -2695,11 +2930,38 @@ class NomadNetworkNode():
|
|||
|
||||
def remote_connected(self, link):
|
||||
RNS.log(f"Peer connected to {self.destination}", RNS.LOG_DEBUG)
|
||||
self.active_links[link.link_id] = link
|
||||
link.set_remote_identified_callback(self.remote_identified)
|
||||
link.set_link_closed_callback(self.remote_disconnected)
|
||||
|
||||
def remote_disconnected(self, link):
|
||||
RNS.log(f"Peer disconnected from {self.destination}", RNS.LOG_DEBUG)
|
||||
if link.link_id in self.active_links: self.active_links.pop(link.link_id)
|
||||
self.cleanup_link_temporary_resources(link)
|
||||
|
||||
def clean_links(self):
|
||||
stale_links = []
|
||||
for link_id, link in self.active_links.items():
|
||||
if not link.status == RNS.Link.ACTIVE: stale_links.append(link_id)
|
||||
|
||||
cleaned_links = 0
|
||||
for link_id in stale_links:
|
||||
link = self.active_links.pop(link_id, None)
|
||||
if link:
|
||||
self.cleanup_link_temporary_resources(link)
|
||||
cleaned_links += 1
|
||||
|
||||
if cleaned_links > 0: RNS.log(f"Cleaned {cleaned_links} stale link{'s' if cleaned_links != 1 else ''}", RNS.LOG_DEBUG)
|
||||
|
||||
def cleanup_link_temporary_resources(self, link):
|
||||
if hasattr(link, "temporary_directories"):
|
||||
for tmpdir in link.temporary_directories:
|
||||
try:
|
||||
tmpdir.cleanup()
|
||||
RNS.log(f"Cleaned up {tmpdir.name}", RNS.LOG_DEBUG)
|
||||
except Exception as e: RNS.log(f"Error while cleaning temporary directory: {e}", RNS.LOG_ERROR)
|
||||
|
||||
link.temporary_directories = []
|
||||
|
||||
def remote_identified(self, link, identity):
|
||||
RNS.log(f"Peer identified as {link.get_remote_identity()} on {link}", RNS.LOG_DEBUG)
|
||||
|
|
@ -2795,5 +3057,8 @@ DEFAULT_WORK_TEMPLATE = """{PAGE_CONTENT}"""
|
|||
|
||||
DEFAULT_WORK_DOC_TEMPLATE = """{PAGE_CONTENT}"""
|
||||
|
||||
# Error state templates
|
||||
DEFAULT_NO_IDENT_TEMPLATE = """>>No Identity\n\nThis page requires identification, and none was received.\n"""
|
||||
|
||||
# Fallback template
|
||||
FALLBACK_TEMPLATE = """{PAGE_CONTENT}"""
|
||||
|
|
@ -1999,6 +1999,7 @@ class ReticulumGitNode():
|
|||
self.active_links_lock = Lock()
|
||||
self.stats_lock = Lock()
|
||||
self.sync_lock = Lock()
|
||||
self.perms_lock = Lock()
|
||||
self.stats_ignored = {}
|
||||
self.stats_push_ignored = {}
|
||||
self.node_name = "Anonymous Git Node"
|
||||
|
|
@ -2507,6 +2508,22 @@ class ReticulumGitNode():
|
|||
|
||||
return permissions
|
||||
|
||||
def load_allowed_permissions(self, allowed_path):
|
||||
allowed_input = ""
|
||||
dynamic_perms = False
|
||||
if os.path.isfile(allowed_path):
|
||||
if os.access(allowed_path, os.X_OK):
|
||||
allowed_result = subprocess.run([allowed_path], stdout=subprocess.PIPE)
|
||||
allowed_input = allowed_result.stdout.decode("utf-8")
|
||||
dynamic_perms = True
|
||||
|
||||
else:
|
||||
fh = open(allowed_path, "rb")
|
||||
allowed_input = fh.read().decode("utf-8")
|
||||
fh.close()
|
||||
|
||||
return self.permissions_from_allowed_input(allowed_input), dynamic_perms
|
||||
|
||||
def load_repository_group(self, group_name, group_path):
|
||||
if not group_name in self.groups: self.groups[group_name] = { "path": group_path, "name": group_name, "repositories": {}, "dynamic_perms": False,
|
||||
"read": [], "write": [], "create": [], "stats": [], "release": [],
|
||||
|
|
@ -2532,8 +2549,8 @@ class ReticulumGitNode():
|
|||
RNS.log(f"Attempt to set group permissions for non-existing group {group_name}, aborting", RNS.LOG_WARNING)
|
||||
return
|
||||
|
||||
# Clear permissions before update
|
||||
for perm in self.ALL_PERMS: self.groups[group_name][perm] = []
|
||||
new_permissions = {}
|
||||
for perm in self.ALL_PERMS: new_permissions[perm] = []
|
||||
|
||||
# Apply permissions from allowed file if present
|
||||
group_path = self.groups[group_name]["path"]
|
||||
|
|
@ -2541,22 +2558,14 @@ class ReticulumGitNode():
|
|||
if os.path.isfile(allowed_path):
|
||||
RNS.log(f"Applying group permissions for {group_name} from {allowed_path}", RNS.LOG_DEBUG)
|
||||
try:
|
||||
allowed_input = ""
|
||||
if os.access(allowed_path, os.X_OK):
|
||||
allowed_result = subprocess.run([allowed_path], stdout=subprocess.PIPE)
|
||||
allowed_input = allowed_result.stdout.decode("utf-8")
|
||||
self.groups[group_name]["dynamic_perms"] = True
|
||||
|
||||
else:
|
||||
fh = open(allowed_path, "rb")
|
||||
allowed_input = fh.read().decode("utf-8")
|
||||
fh.close()
|
||||
|
||||
group_permissions = self.permissions_from_allowed_input(allowed_input)
|
||||
for perm in group_permissions: self.groups[group_name][perm] = group_permissions[perm]
|
||||
group_permissions, dynamic_perms = self.load_allowed_permissions(allowed_path)
|
||||
for perm in self.ALL_PERMS: new_permissions[perm] = group_permissions[perm]
|
||||
self.groups[group_name]["dynamic_perms"] = dynamic_perms
|
||||
|
||||
except Exception as e: RNS.log(f"Could not load group permissions from {allowed_path}: {e}", RNS.LOG_ERROR)
|
||||
|
||||
else: self.groups[group_name]["dynamic_perms"] = False
|
||||
|
||||
# Apply permissions from config file if present
|
||||
if "access" in self.config:
|
||||
section = self.config["access"]
|
||||
|
|
@ -2580,14 +2589,33 @@ class ReticulumGitNode():
|
|||
if perm == self.PERM_PROPOSE: propose = True
|
||||
if perm == self.PERM_ADMIN: admin = True
|
||||
|
||||
if read and not target in self.groups[group_name]["read"]: self.groups[group_name]["read"].append(target)
|
||||
if write and not target in self.groups[group_name]["write"]: self.groups[group_name]["write"].append(target)
|
||||
if create and not target in self.groups[group_name]["create"]: self.groups[group_name]["create"].append(target)
|
||||
if stats and not target in self.groups[group_name]["stats"]: self.groups[group_name]["stats"].append(target)
|
||||
if release and not target in self.groups[group_name]["release"]: self.groups[group_name]["release"].append(target)
|
||||
if interact and not target in self.groups[group_name]["interact"]: self.groups[group_name]["interact"].append(target)
|
||||
if propose and not target in self.groups[group_name]["propose"]: self.groups[group_name]["propose"].append(target)
|
||||
if admin and not target in self.groups[group_name]["admin"]: self.groups[group_name]["admin"].append(target)
|
||||
if read and not target in new_permissions["read"]: new_permissions["read"].append(target)
|
||||
if write and not target in new_permissions["write"]: new_permissions["write"].append(target)
|
||||
if create and not target in new_permissions["create"]: new_permissions["create"].append(target)
|
||||
if stats and not target in new_permissions["stats"]: new_permissions["stats"].append(target)
|
||||
if release and not target in new_permissions["release"]: new_permissions["release"].append(target)
|
||||
if interact and not target in new_permissions["interact"]: new_permissions["interact"].append(target)
|
||||
if propose and not target in new_permissions["propose"]: new_permissions["propose"].append(target)
|
||||
if admin and not target in new_permissions["admin"]: new_permissions["admin"].append(target)
|
||||
|
||||
for perm in self.ALL_PERMS: self.groups[group_name][perm] = new_permissions[perm]
|
||||
|
||||
def update_repository_permissions(self, group_name, repository_name):
|
||||
if not group_name in self.groups:
|
||||
RNS.log(f"Attempt to update permissions for non-existing group {group_name}, aborting", RNS.LOG_WARNING)
|
||||
return
|
||||
|
||||
if not repository_name in self.groups[group_name]["repositories"]:
|
||||
RNS.log(f"Attempt to update permissions for non-existing repository {group_name}/{repository_name}, aborting", RNS.LOG_WARNING)
|
||||
return
|
||||
|
||||
repo = self.groups[group_name]["repositories"][repository_name]
|
||||
allowed_path = repo["path"]+".allowed"
|
||||
|
||||
try:
|
||||
repo_permissions, dynamic_perms = self.load_allowed_permissions(allowed_path)
|
||||
for perm in self.ALL_PERMS: repo[perm] = repo_permissions[perm] if perm in repo_permissions else []
|
||||
except Exception as e: RNS.log(f"Could not update repository permissions for {group_name}/{repository_name} from {allowed_path}: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def load_repository(self, group, path):
|
||||
if not group or not path: return False
|
||||
|
|
@ -2601,27 +2629,15 @@ class ReticulumGitNode():
|
|||
RNS.log(f"You can change it to a bare repository using \"git config --bool core.bare true\".", RNS.LOG_WARNING)
|
||||
|
||||
else:
|
||||
repository_name = os.path.basename(path)
|
||||
allowed_path = f"{path}.allowed"
|
||||
allowed_input = ""
|
||||
dynamic_perms = False
|
||||
if os.path.isfile(allowed_path):
|
||||
if os.access(allowed_path, os.X_OK):
|
||||
allowed_result = subprocess.run([allowed_path], stdout=subprocess.PIPE)
|
||||
allowed_input = allowed_result.stdout.decode("utf-8")
|
||||
dynamic_perms = True
|
||||
|
||||
else:
|
||||
fh = open(allowed_path, "rb")
|
||||
allowed_input = fh.read().decode("utf-8")
|
||||
fh.close()
|
||||
repository_name = os.path.basename(path)
|
||||
allowed_path = f"{path}.allowed"
|
||||
repo_permissions, dynamic_perms = self.load_allowed_permissions(allowed_path)
|
||||
|
||||
fork = self.__is_fork(path)
|
||||
mirror = self.__is_mirror(path)
|
||||
|
||||
p = self.permissions_from_allowed_input(allowed_input)
|
||||
group["repositories"][repository_name] = { "name": repository_name, "group": group_name, "path": path, "fork": fork, "mirror": mirror }
|
||||
for perm in self.ALL_PERMS: group["repositories"][repository_name][perm] = p[perm] if perm in p else []
|
||||
for perm in self.ALL_PERMS: group["repositories"][repository_name][perm] = repo_permissions[perm] if perm in repo_permissions else []
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -4231,6 +4247,7 @@ class ReticulumGitNode():
|
|||
return self.RES_REMOTE_FAIL.to_bytes(1, "big") + b"Remote error"
|
||||
|
||||
def _work_complete(self, work_path, data, remote_identity):
|
||||
group_name, repository_name = self.parse_request_repository_path(data[self.IDX_REPOSITORY])
|
||||
doc_id = data.get("doc_id")
|
||||
|
||||
if doc_id is None: return self.RES_INVALID_REQ.to_bytes(1, "big") + b"No document ID specified"
|
||||
|
|
@ -4246,7 +4263,9 @@ class ReticulumGitNode():
|
|||
doc = self._work_load_document(root_path)
|
||||
if not doc: return self.RES_REMOTE_FAIL.to_bytes(1, "big") + b"Error loading document"
|
||||
|
||||
if doc.get("meta", {}).get("author") != remote_identity.hash: return self.RES_DISALLOWED.to_bytes(1, "big") + b"No access, not author"
|
||||
is_author = doc.get("meta", {}).get("author") == remote_identity.hash
|
||||
admin_access = self.resolve_doc_permission(remote_identity, group_name, repository_name, doc_id, self.PERM_ADMIN)
|
||||
if not is_author and not admin_access: return self.RES_DISALLOWED.to_bytes(1, "big") + b"Not allowed"
|
||||
|
||||
try:
|
||||
completed_dir = os.path.join(completed_base, str(doc_id))
|
||||
|
|
@ -4260,26 +4279,35 @@ class ReticulumGitNode():
|
|||
return self.RES_REMOTE_FAIL.to_bytes(1, "big") + b"Remote error"
|
||||
|
||||
def _work_activate(self, work_path, data, remote_identity):
|
||||
group_name, repository_name = self.parse_request_repository_path(data[self.IDX_REPOSITORY])
|
||||
doc_id = data.get("doc_id")
|
||||
|
||||
if doc_id is None: return self.RES_INVALID_REQ.to_bytes(1, "big") + b"No document ID specified"
|
||||
try: doc_id = int(doc_id)
|
||||
except: return self.RES_INVALID_REQ.to_bytes(1, "big") + b"Invalid document ID"
|
||||
|
||||
completed_dir = os.path.join(work_path, "completed", str(doc_id))
|
||||
active_base = os.path.join(work_path, "active")
|
||||
|
||||
if not os.path.isdir(completed_dir): return self.RES_NOT_FOUND.to_bytes(1, "big") + b"Document not found"
|
||||
doc_dir = None
|
||||
for scope in ["completed", "proposed"]:
|
||||
d = os.path.join(work_path, scope, str(doc_id))
|
||||
if os.path.isdir(d):
|
||||
doc_dir = d
|
||||
break
|
||||
|
||||
if not doc_dir: return self.RES_NOT_FOUND.to_bytes(1, "big") + b"Document not found"
|
||||
|
||||
root_path = os.path.join(completed_dir, "root")
|
||||
root_path = os.path.join(doc_dir, "root")
|
||||
doc = self._work_load_document(root_path)
|
||||
if not doc: return self.RES_REMOTE_FAIL.to_bytes(1, "big") + b"Error loading document"
|
||||
|
||||
if doc.get("meta", {}).get("author") != remote_identity.hash: return self.RES_DISALLOWED.to_bytes(1, "big") + b"No access, not author"
|
||||
is_author = doc.get("meta", {}).get("author") == remote_identity.hash
|
||||
admin_access = self.resolve_doc_permission(remote_identity, group_name, repository_name, doc_id, self.PERM_ADMIN)
|
||||
if not is_author and not admin_access: return self.RES_DISALLOWED.to_bytes(1, "big") + b"Not allowed"
|
||||
|
||||
try:
|
||||
active_dir = os.path.join(active_base, str(doc_id))
|
||||
shutil.move(completed_dir, active_dir)
|
||||
shutil.move(doc_dir, active_dir)
|
||||
|
||||
RNS.log(f"Activated work document {doc_id} by {RNS.prettyhexrep(remote_identity.hash)}", RNS.LOG_VERBOSE)
|
||||
return b"\x00" + mp.packb({"id": doc_id, "scope": "active"})
|
||||
|
|
@ -4505,10 +4533,17 @@ class ReticulumGitNode():
|
|||
try:
|
||||
group_path = self.groups[group_name]["path"]
|
||||
allowed_path = group_path + ".allowed"
|
||||
|
||||
if os.access(allowed_path, os.X_OK): return self.RES_DISALLOWED.to_bytes(1, "big") + b"Executable permission resolvers can only be modified node-side"
|
||||
|
||||
tmp_path = allowed_path + ".tmp"
|
||||
with open(tmp_path, "w", encoding="utf-8") as f: f.write(content)
|
||||
os.rename(tmp_path, allowed_path)
|
||||
|
||||
with self.perms_lock:
|
||||
try: self.update_group_permissions(group_name)
|
||||
except Exception as e: RNS.log(f"Error while refreshing permissions for group {group_name}: {e}", RNS.LOG_ERROR)
|
||||
|
||||
RNS.log(f"Permissions for group {group_name} updated by {RNS.prettyhexrep(remote_identity.hash)}", RNS.LOG_VERBOSE)
|
||||
return b"\x00"
|
||||
|
||||
|
|
@ -4580,10 +4615,17 @@ class ReticulumGitNode():
|
|||
try:
|
||||
repo_path = self.groups[group_name]["repositories"][repository_name]["path"]
|
||||
allowed_path = repo_path + ".allowed"
|
||||
|
||||
if os.access(allowed_path, os.X_OK): return self.RES_DISALLOWED.to_bytes(1, "big") + b"Executable permission resolvers can only be modified node-side"
|
||||
|
||||
tmp_path = allowed_path + ".tmp"
|
||||
with open(tmp_path, "w", encoding="utf-8") as f: f.write(content)
|
||||
os.rename(tmp_path, allowed_path)
|
||||
|
||||
with self.perms_lock:
|
||||
try: self.update_repository_permissions(group_name, repository_name)
|
||||
except Exception as e: RNS.log(f"Error while refreshing permissions for repository {group_name}/{repository_name}: {e}", RNS.LOG_ERROR)
|
||||
|
||||
RNS.log(f"Permissions for repository {group_name}/{repository_name} updated by {RNS.prettyhexrep(remote_identity.hash)}", RNS.LOG_VERBOSE)
|
||||
return b"\x00"
|
||||
|
||||
|
|
@ -4897,6 +4939,11 @@ announce_interval = 360
|
|||
|
||||
# blocked_identities = d31aeea49873006f13b3415520666a4e
|
||||
|
||||
# To make it easier to handle scrapers, crawlers, slopware
|
||||
# and other annoyances, you can block unidentified peers by
|
||||
# adding the null_ident hash to to blocked identities.
|
||||
|
||||
# blocked_identities = d7db22f63b453c23bb0688dde565b7c1
|
||||
|
||||
[repositories]
|
||||
|
||||
|
|
@ -4996,14 +5043,26 @@ internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
|||
|
||||
# serve_nomadnet = no
|
||||
|
||||
# It is possible to disable Nerd Font icons and instead
|
||||
# It is possible to disable Nerd Font icons and instead
|
||||
# use simpler (but more compatible) unicode icons.
|
||||
|
||||
# unicode_icons = yes
|
||||
|
||||
# You can configure whether the page server should try
|
||||
# to convert media files to WebP on the fly, for serving
|
||||
# to nomadnet clients. Enabled by default, but will
|
||||
# require an available encoding backend installed on
|
||||
# your system. Supported backends utilities are "magick",
|
||||
# "convert", "gm", "ffmpeg" and "avconv". If any one is
|
||||
# installed, rngit will auto-detect and use it, but you
|
||||
# can force a specific backend with the environment
|
||||
# variable RNGIT_MEDIA_BACKEND.
|
||||
|
||||
# media_conversion = yes
|
||||
|
||||
|
||||
[logging]
|
||||
# Valid log levels are 0 through 7:
|
||||
# Valid log levels are 0 through 8:
|
||||
# 0: Log only critical information
|
||||
# 1: Log errors and lower log levels
|
||||
# 2: Log warnings and lower log levels
|
||||
|
|
@ -5011,7 +5070,8 @@ internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
|||
# 4: Log info and lower (this is the default)
|
||||
# 5: Verbose logging
|
||||
# 6: Debug logging
|
||||
# 7: Extreme logging
|
||||
# 7: Pathing logging
|
||||
# 8: Extreme logging
|
||||
|
||||
loglevel = 4
|
||||
|
||||
|
|
|
|||
|
|
@ -215,6 +215,14 @@ instance_name = default
|
|||
# required_discovery_value = 14
|
||||
|
||||
|
||||
# You can disable allowing interfaces to be attached, de-
|
||||
# tached or reloaded via rnstatus, for example if running
|
||||
# on a shared system where users with normal RPC access
|
||||
# should not be allowed to control interface states.
|
||||
|
||||
# enable_interface_management = no
|
||||
|
||||
|
||||
# You can configure Reticulum to panic and forcibly close
|
||||
# if an unrecoverable interface error occurs, such as the
|
||||
# hardware device for an interface disappearing. This is
|
||||
|
|
|
|||
|
|
@ -159,7 +159,8 @@ def get_remote_status(destination_hash, include_lstats, include_profiling, ident
|
|||
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=False, astats=False, pstats=False, lstats=False,
|
||||
sorting=None, sort_reverse=False, remote=None, management_identity=None, must_exit=True, rns_instance=None,
|
||||
traffic_totals=False, discovered_interfaces=False, config_entries=False, burst_filter=False, blocked_ips=False,
|
||||
queue_stats=False, pps=False, profiling=False, remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
|
||||
queue_stats=False, pps=False, profiling=False, remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT,
|
||||
attach=None, detach=None, reload=None, show_stale=False, show_unknown=False):
|
||||
|
||||
if remote: require_shared = False
|
||||
else: require_shared = True
|
||||
|
|
@ -176,6 +177,37 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
|||
if must_exit: exit(1)
|
||||
else: return
|
||||
|
||||
if attach:
|
||||
result = reticulum.attach_interface(attach)
|
||||
if result == True: print(f"Interface {attach} was attached")
|
||||
elif result == False: print(f"Could not attach interface {attach}")
|
||||
elif result == None: print(f"The interface {attach} does not exist")
|
||||
else: print(f"Unknown error while attaching interface {attach}")
|
||||
|
||||
if result == True: exit(0)
|
||||
else: exit(1)
|
||||
|
||||
if detach:
|
||||
result = reticulum.detach_interface(detach)
|
||||
if result == True: print(f"Interface {detach} was detached")
|
||||
elif result == False: print(f"Could not detach interface {detach}")
|
||||
elif result == None: print(f"The interface {detach} does not exist")
|
||||
else: print(f"Unknown error while detaching interface {detach}")
|
||||
|
||||
if result == True: exit(0)
|
||||
else: exit(1)
|
||||
|
||||
if reload:
|
||||
result = reticulum.reload_interface(reload)
|
||||
if result == True: print(f"Interface {reload} was reloaded")
|
||||
elif result == False: print(f"Could not reload interface {reload}")
|
||||
elif result == None: print(f"The interface {reload} does not exist")
|
||||
else: print(f"Unknown error while reloading interface {reload}")
|
||||
|
||||
if result == True: exit(0)
|
||||
else: exit(1)
|
||||
|
||||
|
||||
link_count = None
|
||||
active_link_count = None
|
||||
stats = None
|
||||
|
|
@ -211,12 +243,17 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
|||
name = i["name"]
|
||||
if_type = i["type"]
|
||||
status = i["status"]
|
||||
has_impl_info = "impl_name" in i and "version" in i and i["impl_name"] and i["version"]
|
||||
impl_str = f"{i['impl_name']} {i['version']}" if has_impl_info else "Unknown"
|
||||
|
||||
if status == "available": status_display = "Available"
|
||||
elif status == "unknown": status_display = "Unknown"
|
||||
elif status == "stale": status_display = "Stale"
|
||||
else: status_display = status
|
||||
|
||||
if status == "stale" and not show_stale: continue
|
||||
if not has_impl_info and not show_unknown: continue
|
||||
|
||||
now = time.time()
|
||||
dago = now-i["discovered"]
|
||||
hago = now-i["last_heard"]
|
||||
|
|
@ -247,6 +284,7 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
|||
|
||||
print(f"Name : {name}")
|
||||
print(f"Type : {if_type}")
|
||||
print(f"Stack : {impl_str}")
|
||||
print(f"Status : {status_display}")
|
||||
print(f"Transport : {transport_str}")
|
||||
print(f"Distance : {i['hops']} hop{'' if i['hops'] == 1 else 's'}")
|
||||
|
|
@ -271,12 +309,15 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
|||
except Exception as e: pass
|
||||
|
||||
else:
|
||||
print(f"{'Name':<25} {'Type':<12} {'Status':<12} {'Last Heard':<12} {'Value':<8} {'Location':<15}")
|
||||
print("-" * 89)
|
||||
print(f"{'Name':<25} {'Type':<10} {'Status':<10} {'Last Heard':<12} {'Value':<7} {'Running':<16} {'Location':<15}")
|
||||
print("-" * 110)
|
||||
|
||||
for i in filtered_ifs:
|
||||
try:
|
||||
name = i["name"][:24] + "…" if len(i["name"]) > 24 else i["name"]
|
||||
has_impl_info = "impl_name" in i and "version" in i and i["impl_name"] and i["version"]
|
||||
impl_str = f"{i['impl_name']} {i['version']}" if has_impl_info else "Unknown"
|
||||
if len(impl_str) > 16: impl_str = impl_str[:15]+"…"
|
||||
|
||||
if_type = i["type"].replace("Interface", "")
|
||||
|
||||
|
|
@ -285,6 +326,9 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
|||
elif status == "unknown": status_display = "? Unknown"
|
||||
elif status == "stale": status_display = "× Stale"
|
||||
else: status_display = status
|
||||
|
||||
if status == "stale" and not show_stale: continue
|
||||
if not has_impl_info and not show_unknown: continue
|
||||
|
||||
now = time.time()
|
||||
last_heard = i["last_heard"]
|
||||
|
|
@ -309,7 +353,7 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
|||
location = f"{lat}, {lon}"
|
||||
else: location = "N/A"
|
||||
|
||||
print(f"{name:<25} {if_type:<12} {status_display:<12} {last_heard_display:<12} {value:<8} {location:<15}")
|
||||
print(f"{name:<25} {if_type:<10} {status_display:<12} {last_heard_display:<12} {value:<7} {impl_str:<16} {location:<15}")
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
|
|
@ -832,6 +876,10 @@ def main(must_exit=True, rns_instance=None):
|
|||
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||
parser.add_argument("--version", action="version", version="rnstatus {version}".format(version=__version__))
|
||||
|
||||
parser.add_argument("--attach", action="store", metavar="name", help="Attach interface by name", default=None, type=str)
|
||||
parser.add_argument("--detach", action="store", metavar="name", help="Detach interface by name", default=None, type=str)
|
||||
parser.add_argument("--reload", action="store", metavar="name", help="Reload interface by name", default=None, type=str)
|
||||
|
||||
parser.add_argument("-a", "--all", action="store_true", help="show all interfaces", default=False)
|
||||
parser.add_argument("-A", "--announce-stats", action="store_true", help="show announce stats", default=False)
|
||||
parser.add_argument("-P", "--pr-stats", action="store_true", help="show path request stats", default=False)
|
||||
|
|
@ -850,6 +898,8 @@ def main(must_exit=True, rns_instance=None):
|
|||
parser.add_argument("-w", action="store", metavar="seconds", type=float, help="timeout before giving up on remote queries", default=RNS.Transport.PATH_REQUEST_TIMEOUT)
|
||||
parser.add_argument("-d", "--discovered", action="store_true", help="list discovered interfaces", default=False)
|
||||
parser.add_argument("-D", action="store_true", help="show details and config entries for discovered interfaces", default=False)
|
||||
parser.add_argument("--show-stale", action="store_true", help="show stale discovery entries", default=False)
|
||||
parser.add_argument("--show-unknown", action="store_true", help="show discovery entries without version info", default=False)
|
||||
parser.add_argument("-m", "--monitor", action="store_true", help="continuously monitor status", default=False)
|
||||
parser.add_argument("-I", "--monitor-interval", action="store", metavar="seconds", type=float, help="refresh interval for monitor mode (default: 1)", default=1.0)
|
||||
parser.add_argument('-v', '--verbose', action='count', default=0)
|
||||
|
|
@ -880,7 +930,8 @@ def main(must_exit=True, rns_instance=None):
|
|||
astats=args.announce_stats, pstats=args.pr_stats, lstats=args.link_stats, sorting=args.sort, sort_reverse=args.reverse,
|
||||
remote=args.R, management_identity=args.i, remote_timeout=args.w, must_exit=False, rns_instance=reticulum,
|
||||
traffic_totals=args.totals, discovered_interfaces=args.discovered, config_entries=args.D, burst_filter=args.burst,
|
||||
blocked_ips=args.blocked_ips, queue_stats=args.queues, pps=args.pps, profiling=args.profiling)
|
||||
blocked_ips=args.blocked_ips, queue_stats=args.queues, pps=args.pps, profiling=args.profiling,
|
||||
show_stale=args.show_stale, show_unknown=args.show_unknown)
|
||||
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
|
|
@ -898,7 +949,8 @@ def main(must_exit=True, rns_instance=None):
|
|||
astats=args.announce_stats, pstats=args.pr_stats, lstats=args.link_stats, sorting=args.sort, sort_reverse=args.reverse,
|
||||
remote=args.R, management_identity=args.i, remote_timeout=args.w, must_exit=must_exit, rns_instance=rns_instance,
|
||||
traffic_totals=args.totals, discovered_interfaces=args.discovered, config_entries=args.D, burst_filter=args.burst,
|
||||
blocked_ips=args.blocked_ips, queue_stats=args.queues, pps=args.pps, profiling=args.profiling)
|
||||
blocked_ips=args.blocked_ips, queue_stats=args.queues, pps=args.pps, profiling=args.profiling,
|
||||
attach=args.attach, detach=args.detach, reload=args.reload, show_stale=args.show_stale, show_unknown=args.show_unknown)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
__version__ = "1.5.1"
|
||||
__version__ = "1.5.5"
|
||||
|
|
|
|||
94
Roadmap.md
|
|
@ -1,94 +0,0 @@
|
|||
# Reticulum Development Roadmap
|
||||
This document outlines the currently established development roadmap for Reticulum.
|
||||
|
||||
1. [Currently Active Work Areas](#currently-active-work-areas)
|
||||
2. [Primary Efforts](#primary-efforts)
|
||||
- [Comprehensibility](#comprehensibility)
|
||||
- [Universality](#universality)
|
||||
- [Functionality](#functionality)
|
||||
- [Usability & Utility](#usability--utility)
|
||||
- [Interfaceability](#interfaceability)
|
||||
3. [Auxillary Efforts](#auxillary-efforts)
|
||||
4. [Release History](#release-history)
|
||||
|
||||
## Currently Active Work Areas
|
||||
For each release cycle of Reticulum, improvements and additions from the five [Primary Efforts](#primary-efforts) are selected as active work areas, and can be expected to be included in the upcoming releases within that cycle. While not entirely set in stone for each release cycle, they serve as a pointer of what to expect in the near future.
|
||||
|
||||
## Primary Efforts
|
||||
The development path for Reticulum is currently laid out in five distinct areas: *Comprehensibility*, *Universality*, *Functionality*, *Usability & Utility* and *Interfaceability*. Conceptualising the development of Reticulum into these areas serves to advance the implementation and work towards the Foundational Goals & Values of Reticulum.
|
||||
|
||||
### Comprehensibility
|
||||
These efforts are aimed at improving the ease of which Reticulum is understood, and lowering the barrier to entry for people who wish to start building systems on Reticulum.
|
||||
|
||||
- Improving [the manual](https://markqvist.github.io/Reticulum/manual/) with tutorials specifically for beginners
|
||||
- Updating the documentation to reflect recent changes and improvements
|
||||
- Update descriptions of protocol mechanics
|
||||
- Update announce description
|
||||
- Add in-depth explanation of the IFAC system
|
||||
- Software
|
||||
- Update software descriptions and screenshots
|
||||
- Communications hardware section
|
||||
- Add information about RNode external displays.
|
||||
- Possibly add other relevant types here as well.
|
||||
- Setup *Best Practices For...* / *Installation Examples* section.
|
||||
- Home or office (example)
|
||||
- Vehicles (example)
|
||||
- No-grid/solar/remote sites (example)
|
||||
|
||||
### Universality
|
||||
These efforts seek to broaden the universality of the Reticulum software and hardware ecosystem by continously diversifying platform support, and by improving the overall availability and ease of deployment of the Reticulum stack.
|
||||
|
||||
- Create a standalone RNS Daemon app for Android
|
||||
- A lightweight and portable C implementation for microcontrollers, µRNS
|
||||
- A portable, high-performance Reticulum implementation in C/C++, see [#21](https://github.com/markqvist/Reticulum/discussions/21)
|
||||
- Bindings for other programming languages
|
||||
|
||||
### Functionality
|
||||
These efforts aim to expand and improve the core functionality and reliability of Reticulum.
|
||||
|
||||
- Add interface hot-plug and live up/down control to running instances
|
||||
- Add automatic retries to all use cases of the `Request` API
|
||||
- Network-wide path balancing
|
||||
- Distributed Destination Naming System
|
||||
- Globally routable multicast
|
||||
- Destination proxying
|
||||
- [Metric-based path selection and multiple paths](https://github.com/markqvist/Reticulum/discussions/86)
|
||||
|
||||
### Usability & Utility
|
||||
These efforts seek to make Reticulum easier to use and operate, and to expand the utility of the stack on deployed systems.
|
||||
|
||||
- Easy way to share interface configurations, see [#19](https://github.com/markqvist/Reticulum/discussions/19)
|
||||
- Transit traffic display in `rnstatus`
|
||||
- `rnsconfig` utility
|
||||
|
||||
### Interfaceability
|
||||
These efforts aim to expand the types of physical and virtual interfaces that Reticulum can natively use to transport data.
|
||||
|
||||
- Plain ESP32 devices (ESP-Now, WiFi, Bluetooth, etc.)
|
||||
- More LoRa transceivers
|
||||
- AT-compatible modems
|
||||
- Filesystem interface
|
||||
- Direct SDR Support
|
||||
- Optical mediums
|
||||
- IR Transceivers
|
||||
- AWDL / OWL
|
||||
- HF Modems
|
||||
- GNU Radio
|
||||
- CAN-bus
|
||||
- Raw SPI
|
||||
- Raw i²c
|
||||
- MQTT
|
||||
- XBee
|
||||
- Tor
|
||||
|
||||
## Auxillary Efforts
|
||||
The Reticulum ecosystem is enriched by several other software and hardware projects, and the support and improvement of these, in symbiosis with the core Reticulum project helps expand the reach and utility of Reticulum itself.
|
||||
|
||||
This section lists, in no particular order, various important efforts that would be beneficial to the goals of Reticulum.
|
||||
|
||||
- The [RNode](https://unsigned.io/rnode/) project
|
||||
- [x] Create a WebUSB-based bootstrapping utility, and integrate this directly into the [RNode Bootstrap Console](#), both on-device, and on an Internet-reachable copy. This will make it much easier to create new RNodes for average users.
|
||||
|
||||
## Release History
|
||||
|
||||
Please see the [Changelog](./Changelog.md) for a complete release history and changelog of Reticulum.
|
||||
5
SECURITY.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Reporting Security Issues
|
||||
|
||||
It goes without saying: If you have discovered a security issue in Reticulum or any related project, please report it responsibly to the maintainer over LXMF at `<8dd57a738226809646089335a6b03695>`. If you wish to remain anonymous, please create a disposable identity to send from, and notify us of any such wishes.
|
||||
|
||||
If you are not able to use LXMF, you can send an email to the address currently posted on the maintainer's profile on this site.
|
||||
|
|
@ -2,7 +2,7 @@ Recently, and mostly from people who I've never seen before, the opinions 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".
|
||||
- The project not really being "[open source](https://reticulum.network/manual/brandolinis.html#open-source-means-open-source)" because random strangers couldn't just "contribute".
|
||||
|
||||
Joakim posted some very relevant observations about how Reticulum operates, along with the following quote:
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ Joakim posted some very relevant observations about how Reticulum operates, alon
|
|||
|
||||
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.
|
||||
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.
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ I do not subscribe to, and consequently do not operate the Reticulum project und
|
|||
|
||||
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*.
|
||||
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.
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ When this pattern repeats, over and over, the only sensible, measured and constr
|
|||
|
||||
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.
|
||||
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 star" 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.
|
||||
|
|
|
|||
339
docs/history/2026_09_18_Yes_I_am_Angry.md
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
# Yes, I am Angry
|
||||
|
||||
Polite, but deeply malicious bullshit is a much worse evil than the coarse variant, so you will have to excuse my bluntness here.
|
||||
|
||||
Unfortunately, the only way to respond, as a normal, healthy human being, to something so astronomically tasteless and disgraceful as this, is not in kind, but in honest outrage, modulated to some degree with sarcasm and humor (the dark variant, yes).
|
||||
|
||||
Because *this* shit, this *is* outrageous.
|
||||
|
||||
The man that produced the "article" in question here is either so disposessed of intelligence that his head is teetering on the brink of collapsing into a singularity, or he is deeply malicious.
|
||||
|
||||
If any rational explanation can produce an alternative, I am all ears.
|
||||
|
||||
**To the footsoldiers of the "cause" the article represents**
|
||||
|
||||
Let's get this show on. Let's see the barrage of new smear pieces you can prompt forth from the fuel I provide you here. It'll fit straight into your theater production, so: Be my fucking guest, and dig your own hole of delusion, mistrust and ridicule even deeper than it already is. I can then hurl it right back at you, and we can all have a crap-fight till the end of days. *Or* we could take responsibility and ownership for our words and actions, and actually build something together. Ball's in your court.
|
||||
|
||||
**To anyone else**
|
||||
|
||||
Consider this a paranthetical, sarcastic (and for me, cathartic) aside in the whole of the corpus of writings I have published, and weigh it in the context of the rest of those 200,000 words or so, together with the frustration and exhaustion that inevitably entails from dealing with this same fabricated campaign and its regurgitated nonsense for *years* on end, while trying to do constructive and helpful work; and not expecting anything else than basic decency in return.
|
||||
|
||||
I am no angel, not perfect, flawed in many ways, but I certainly ain't afraid to show that I am a real human being, with the capacity for anger, and that in this case, the anger is fed by the most bitter form of disappointment and sadness in what people will stoop to for influence and attention.
|
||||
|
||||
[This is the article in question](https://gaggl.com/blogs/2026-09-05-enclosure-by-good-intentions/)
|
||||
|
||||
[This is the polite, measured analysis of the *generalized* phenomenon, all 18,000 words of it](https://reticulum.network/manual/brandolinis.html)
|
||||
|
||||
And here is a very short, but central point from that analysis:
|
||||
|
||||
> In whole, this is the description of three fronts of the same operation:
|
||||
>
|
||||
> * The license attack and other urgency-engineered narratives are the *cover*: They supply
|
||||
> the ideological justification and battering ram for treating the reference as illegitimate,
|
||||
> and needing replacement.
|
||||
> * The ports are the *inventory*: they supply the “replacement” material that the narrative
|
||||
> claims the ecosystem needs.
|
||||
> * The takeover narrative is the *objective function*: the repeated, persistent, escalating claim that
|
||||
> the reference should be replaced, and that the speakers themself are the natural heirs.
|
||||
|
||||
---
|
||||
|
||||
*This*, right here, is the real, human and honest response. And the only sane one there could ever be, lest madness be allowed to descend wholly.
|
||||
|
||||
While this is *written* as a response to you, Leo Gaggl, you absolute degenerate piece of fecal matter, it is really a response to the phenomenon *as a whole*.
|
||||
|
||||
I chose your article because it is such a complete and rich example of the madness. What I attack here is *conduct*.
|
||||
|
||||
Leo, according to his own evident preference, is the actor on the theatre stage, and I will treat him as such, in the play that follows.
|
||||
|
||||
---
|
||||
|
||||
> So when Mark Qvist, the person behind the [Reticulum](https://gaggl.com/blogs/2026-02-25-lpwan-meshes-reticulum-deep-dive/) mesh networking stack
|
||||
|
||||
Color me surprised, the link to "Reticulum" doesn't even point to the repo, but to *another* one of your sloporated "articles".
|
||||
|
||||
Don't wanna accidentially lead users to anywhere they could discover the truth about things now, would we? Like commit histories, the *actual* Reticulum community, that kind of stuff. Nah, nah...
|
||||
|
||||
> ... replaced its MIT licence with a custom one built to keep the project out of corporate AI training sets and out of weapons systems
|
||||
|
||||
Which happened 18 months ago. *Fuuuunny* timing bringing it up again now, right where it's [mighty fucking convenient](https://reticulum.network/manual/brandolinis.html#a-persistent-but-fragile-triad) for your slop-coding license-violating friends, don't you think?
|
||||
|
||||
Interesting you don't bring up *any* of the extensive argumentation made against the same old points you recycle here, but act as they simply never existed. Or maybe you just chose to completely ignore them in the service of your God-given mission?
|
||||
|
||||
> Reticulum is now marked non-free by Debian
|
||||
|
||||
Which is [excellent](https://reticulum.network/manual/brandolinis.html#the-distribution-argument), since debian users will not have to use old, and potentially outdated versions of a package that updates *fast* everytime it is required.
|
||||
|
||||
And "now"? Weasel wording.
|
||||
|
||||
> kept out of the main F-Droid repository
|
||||
|
||||
RNS? In the F-Droid repository? How would that even work? People install the base stack as an *app* and run it... how? Are we going to bundle a command line or `systemd` with that? Do you have any idea about how software actually works?
|
||||
|
||||
If people want to do that, open Termux and type `pip install rns`. Done. Has been possible and documented since the dawn of the project.
|
||||
|
||||
All actual *apps* that use Reticulum are distributed as APKs, ready to install and download, distribute offline, fetch from their websites, over Reticulum itself, or whatever platform they prefer. Commercially or for free. That's the point.
|
||||
|
||||
> It cannot be combined with GPL-licensed code
|
||||
|
||||
[Cry me a river](https://reticulum.network/manual/brandolinis.html#on-freedom). Not my fault that people choose to use viral licenses that absolutely must subsume everything and their aunt under their own restrictive terms.
|
||||
|
||||
> It is not eligible for most open-source grant funding.
|
||||
|
||||
Exactly, and I specifically rejected that, several times, to keep the license right and avoid political takeover. That's 50.000€ I said no to, right there, while I barely had enough to make it three months into the future. Because I value integrity and *real* sustainability a lot higher.
|
||||
|
||||
Do *you* have anything to show for it, in that regard? Who's footing your pay-check? A varied community of people from all walks of life who use the things you created? No? Didn't think so. I wonder who, then.
|
||||
|
||||
> And the clause that triggered all of this is very likely unenforceable.
|
||||
|
||||
"Very likely" is very likely a lot of handwaving, Leo. It's certainly not an argument. [This is](https://reticulum.network/manual/brandolinis.html#what-the-reticulum-license-is).
|
||||
|
||||
> As one Debian developer put it in the project’s own discussion thread, people who build harmful systems are not going to be stopped by a term they were always going to ignore.
|
||||
|
||||
It turns out [that most do](https://reticulum.network/manual/brandolinis.html#the-prediction-and-what-happened), because of a thing called "the law".
|
||||
|
||||
Or are you proposing we live in a state of complete and utter anarchy already? Or maybe that your cult is simply above the law?
|
||||
|
||||
> The moment you add a “not for harm” or “not for AI” condition, you have left open source behind
|
||||
|
||||
Strike up the organ, start the hymns.
|
||||
|
||||
> The Open Source Definition rules out licences that discriminate against people or groups, or against fields of endeavour
|
||||
|
||||
[Erratum](https://reticulum.network/manual/brandolinis.html#open-source-means-open-source): *Your* "open source" religion, not "The Open Source Definition" (Capitals Of Authority, bravo).
|
||||
|
||||
Yes, let's not discriminate against people, **BY FUCKING MURDERING THEM WITH DRONES**. Time for a party Leo!
|
||||
|
||||
Beauuuutiful way to hide [the true nonsense](https://reticulum.network/manual/brandolinis.html#what-the-argument-is-actually-doing) of the "fields of endeavour" by slyly insinuating *I* am being discriminatory.
|
||||
|
||||
Side-note: The overall patronizing, better-than-thy and fakely knowledgable tone is almost hilarious, if it wasn't so malicious.
|
||||
|
||||
> The tool was already on the shelf
|
||||
|
||||
Oh for Christ's sake, this one [again](https://reticulum.network/manual/brandolinis.html#recycled-wine-fermenting)...
|
||||
|
||||
> Kaonic tactical mesh radios are sold for military command and control, drone and autonomous-systems networking, and operation in “austere, contested environments”, and which has since been selected for NATO’s DIANA defence-innovation accelerator. It is MIT-licensed, it is an independent implementation of a protocol that is itself public domain, and its repository predates Qvist’s harm clause by nine months.
|
||||
|
||||
Those fuckers came to me and presented themselves as wanting to create a rust port for the good of the ecosystem, the world and the community. That was a blatant lie and front for their actual goal of acquiring my goodwill and expertise so they could quickly create a shallow clone, hardfork it with their own sloppy "Post-Quantum" crypto, and get big money from the military. They deliberately lied and manipulated to extract from a whole community and someone they believed to be a naive, vulnerable developer.
|
||||
|
||||
Why do you think a non-harm clause is needed? Their implementation is still not *working*, by the way, and has provided **nothing** to the community or world at large.
|
||||
|
||||
I cut ties with those bastards when I found out what their end-game was, but not until I had spent every ounce of energy I had slowing them down as much as humanly possible, while *I was still working on the actual Reticulum implementation and providing updates to users, managing a community, and living on next to nothing in a camper*.
|
||||
|
||||
Holy fuck I hate people like you Leo, you willfully ignorant, destructive, spoiled piece of shit.
|
||||
|
||||
> His fix trades a door nobody has worked out how to close for the loss of every distribution channel
|
||||
|
||||
Every distribution channel that [we never used, and never needed](https://reticulum.network/manual/brandolinis.html#the-distribution-argument)? I haven't seen PyPi acting up about our apparently infuriating conduct yet.
|
||||
|
||||
Moot point, even if they do, since we can distribute over Reticulum just fine. With full cryptographic authentication, and no centralized infrastructure or PKI required. Also, no governance required. *That's* resilience.
|
||||
|
||||
> and every downstream collaborator who needs a known-good licence.
|
||||
|
||||
We are definitely not short of downstream collaborators currently, so no thanks, you don't have much to sell me there.
|
||||
|
||||
> You pay the full price in distribution and collaboration
|
||||
|
||||
The quality of collaboration has gone up *significantly* since that decision. Most of the wannabes stay away and sob in their Stallman handkerchiefs, while we keep working, dilligently as always. Great success.
|
||||
|
||||
(Sorry Stallman, I love you)
|
||||
|
||||
Reticulum is available everywhere. It is one of the leanest, most dependency free software packages there is, and it ships an entire networking stack, a plethora of management utilities, a device flashing utility, a full GitHub replacement running over Reticulum, remote shell utilities, and supports a whole ecosystem of application-layer protocols and user-facing applications, including messaging (LXMF), voice (LXST), chat (RRC), content hosting and browsing (nomadnet), and many others.
|
||||
|
||||
*In a cross-platform, portable wheel of 537 kilobytes* that installs or embeds on every platform known to man. Drop it on a floppy, baby.
|
||||
|
||||
The delusions run so deep here I cannot even begin to fathom it.
|
||||
|
||||
> and you get an AI-training restriction a determined actor routes around in an afternoon.
|
||||
|
||||
In reality, of the 10 sampled language models (both proprietary and open-weights) that were tested for traces of Reticulum source material, *all* stopped training on the Reticulum source after the license change.
|
||||
|
||||
Turns out, *it worked*, Leo.
|
||||
|
||||
Did *you* check? No you didn't, handwaving and inventions again.
|
||||
|
||||
> A written answer to “what happens when the maintainer walks away”, because for Reticulum that stopped being hypothetical in December 2025.
|
||||
|
||||
Did you check the [commit logs](https://github.com/markqvist/Reticulum/commits/) you dimwit? No, you just randomly rolled around in the personal puddle of phantasmogoria you constructed from wishful thinking.
|
||||
|
||||
Have you seen what I have produced in those ten months? I'd wager it's a lot more than you, you lazy fuck, not even doing the most basic fact-checking of your smear-job propaganda pieces.
|
||||
|
||||
Try that on for a "written answer".
|
||||
|
||||
> There is now a Reticulum_CE, a “community edition” fork that exists mainly to carry a redistributable licence.
|
||||
|
||||
What psychosis are you getting these from? [Reticulum_CE](https://github.com/Reticulum-Community/Reticulum_CE) is published under the Reticulum License. Did you read their repo? No, you did not:
|
||||
|
||||
*"This is a community maintained fork of the Reticulum Network Stack and is intended as a public forum for discussion and issue reporting, as well as a repository to collect community enhancements to RNS."*
|
||||
|
||||
> There is RetiNet, an AGPL fork of the Python stack.
|
||||
|
||||
Which has been [defunct and abandoned for 8 months](https://codeberg.org/SkyGuy/RetiNet). Are you not equipped with visual perception or basic literacy?
|
||||
|
||||
> There are side-loaded F-Droid repositories so people can install the apps at all.
|
||||
|
||||
What nonsense is this? You are literally so horny to paint a picture of a dead system that you have to assume people cannot think for two seconds straight?
|
||||
|
||||
There has never been a point in time where people could not install Reticulum-based apps on Android. These have always, intentionally, been distributed as *re-distributable* APK files so people could share them offline. Sideband can host and share it's own APK. This is the point, Archimedes.
|
||||
|
||||
The F-droid repository was made as service to users by a volunteer in the *real* Reticulum community (not your abhorrent confabulation of what it is) who *has never once objected to the license and project directions*, and who just wanted to help make things easier!
|
||||
|
||||
Don't use them as ammunition in your perverse fantasies!
|
||||
|
||||
> Every one of those is real maintenance work, done by volunteers, spent entirely on the problem of being allowed to share the software.
|
||||
|
||||
A copy of my repo, then pasting my (Reticulum Licensed) work into four files, then abandoning the project is not "real maintenance work".
|
||||
|
||||
What I have been doing, for over a decade, is real maintenance work.
|
||||
|
||||
> four independent teams have rebuilt Reticulum from scratch in Rust.
|
||||
|
||||
Those [have not "rebuild"](https://reticulum.network/manual/brandolinis.html#copyright-the-two-legal-layers) anything ["from scratch"](https://reticulum.network/manual/brandolinis.html#the-clean-room-delusion).
|
||||
|
||||
They are feeding the source code of RNS directly into an LLM, running automated software loops, and copying it, blatantly, bit for bit, without any modicum of creative input, work or understanding.
|
||||
|
||||
Such a connoisseur of the commons you are.
|
||||
|
||||
> The people rebuilding the project to keep it alive
|
||||
|
||||
The project is already alive, and has been for a decade. You are lying so hard that it's fucking raining.
|
||||
|
||||
Those people, are in their own words, "Killing Reticulum" (quote, Lew Palm, "author" of "Leviculum"), so they can appropriate it for their own profit and control.
|
||||
|
||||
> The two that put enclosure resistance first, Leviculum and the rsReticulum stack behind Ratspeak, both chose AGPLv3.
|
||||
|
||||
And all of those are [void and null](https://reticulum.network/manual/brandolinis.html#void-grants-legal-foundations-of-machine-generated-code), because what they *really* chose is **copyright violation**, and complete disrespect ([as is now becoming tradition](https://reticulum.network/manual/brandolinis.html#licensing-analysis)) for open source development practices, attribution and basic decency.
|
||||
|
||||
> Underneath the licence choice sits a harder problem, and it is the one this site keeps coming back to: a system can be decentralised in its architecture and completely centralised in who decides things.
|
||||
|
||||
Oooooh... Tension. Drama. Tell me more, let's have the juicy details, huh? Outrage incoming, I can feel it! Ramp up your audience like a good boy, Leo!
|
||||
|
||||
The only thing that keeps coming back here is nonsense.
|
||||
|
||||
The decisions follow those responsible, over years, and the actual work. Not cry-babies like you. Get used to it.
|
||||
|
||||
> Relicensing a codebase other people contributed to, under MIT terms, with a single unilateral commit, is itself a small act of enclosure.
|
||||
|
||||
I am responsible for over 98% of commits in that repository. Not a single one of the other contributors disagreed to the license decision.
|
||||
|
||||
But your ideology runs so deep now, *that we cannot ourselves decide how we license our own software*? Yes, let's consider for a moment **who the fucking dictator is here**...
|
||||
|
||||
It's *almost* like you've decided you already personally own Reticulum.
|
||||
|
||||
> The pattern shows up off the codebase too. In late August 2026, after a badly-behaved crawler hammered his NomadNet git node with tens of thousands of requests
|
||||
|
||||
Ah yes, so I got personally mad because it hit *my* node, and had a tantrum?
|
||||
|
||||
The scraper was hitting *every node across the entire fucking intermesh*, from a high-bandwidth VPS, with no throttling, no regards for people's radio links, airtime concerns, metered connections or limited compute resources.
|
||||
|
||||
For what reason? To gouge tens of **millions** of requests to a broken piece of shit software that an idiot deployed and left running without oversight, trying to infintely download **everything that everyone had ever put on their nodes several times over** on a recursive loop.
|
||||
|
||||
Who made that stop? I did, because I [contacted](https://github.com/roogle-dev/reticulum-phantom/issues/4) the idiot and talked him out of it.
|
||||
|
||||
Let's hear more of your brilliant ideas about how to "protect the commons" you fucking ape.
|
||||
|
||||
> Qvist removed the volunteer moderator of the Reticulum subreddit, deleted roughly three years of posts, and closed it with a notice that ended “Well done, vibecoders. You ruined it for everyone.”
|
||||
|
||||
The moderator was not moderating anything, and hadn't been for months. The precise original agreement with him for allowing him moderator rights was exactly the arrangement, that if the space was not moderated, it would be closed, and that I would maintain the final decision on that.
|
||||
|
||||
This is now being spun into yet another [bottle of piss](https://reticulum.network/manual/brandolinis.html#recycled-wine-fermenting) by the dipshits like you. Well done, moron.
|
||||
|
||||
> Around the same window the project’s Matrix room shut down and the GitHub issue tracker was hidden.
|
||||
|
||||
Does time not exist in your alternate universe? The events you allude to are separated by month or years, and the rationales were clearly stated. You deliberately ignored all of that.
|
||||
|
||||
Further, I did not manage or administrate the Matrix room. It was a community room, run by volunteers, flooded with spam and bad-faith actors. I had *zero* admin rights on it. The moderators of the room made that decision themselves, long after I had left - *to focus on actual work*.
|
||||
|
||||
The development was *never* centered on the GitHub issue tracker, it ran over Reticulum long before the preposterous madhouse of GitHub was left behind for good, as it continues to do today, as is documented and stated several places (including the front-page of the repository you refuse to link to).
|
||||
|
||||
Signal to noise ratio has gone up *significantly* because of that. The community is *more* alive, and more productive.
|
||||
|
||||
You are literally making things up as you go.
|
||||
|
||||
> One community member’s summary: for a supposedly decentralised network, the founder holds and uses a lot of centralised levers.
|
||||
|
||||
Yes, let's just [invent](https://reticulum.network/manual/brandolinis.html#the-movement-of-a-dozen) another concerned "community member". Who? Some random nutjob on reddit?
|
||||
|
||||
A random stranger of your choosing is not a "community member" that gets to decide how a highly complex engineering project is run. But damn man, you wish it worked that way. You wish it badly.
|
||||
|
||||
> None of this is meant as a pile-on.
|
||||
|
||||
Oh, *thanks*, you're SO *considerate* and full of empathy Leo!
|
||||
|
||||
> The Reticulum Licence is presented as a shield for the commons
|
||||
|
||||
**Because that is what it actually is.**
|
||||
|
||||
How about trying to actually *prove* otherwise, with *a coherent argument*, instead of getting an LLM to spin a load of high-strung nonsense to further your deranged religion?
|
||||
|
||||
> If your users are European public bodies
|
||||
|
||||
Oh, those poor administrative organizations! Do you think I dedicated my life to empowering European Public Bodies, or actual people?
|
||||
|
||||
> Then spend the effort you saved on the part that is actually hard: governance that survives the founder losing interest.
|
||||
|
||||
[Ze Governance](https://reticulum.network/manual/brandolinis.html#a-history-of-what-works), again, I see.
|
||||
|
||||
Maybe when you've worked 12 hours a day on a project for ten years, which is persistently flooded with this kind of profound idiocy, and kept going, can you begin to consider expounding your finer points on "losing interest".
|
||||
|
||||
> The commons is not protected by a clause forbidding bad uses.
|
||||
|
||||
It sure as hell ain't protected by pretending the world is made of candy-floss either, like you seem to prefer.
|
||||
|
||||
> It is protected by how many people can fork it, ship it, and keep it running without asking anyone’s permission.
|
||||
|
||||
Oh isn't that convenient now? How many can do that today? 99.999999% of the worlds population. What's the equation if we change to your beloved AGPL again, you moron?
|
||||
|
||||
> Reticulum’s licence made that number smaller.
|
||||
|
||||
[Mathematical failure](https://reticulum.network/manual/brandolinis.html#on-freedom).
|
||||
|
||||
> The intention was to guard the commons. The effect was to fence off a piece of it.
|
||||
|
||||
On the intent, [correct](https://reticulum.network/manual/brandolinis.html#what-the-reticulum-license-is).
|
||||
|
||||
On the effect, no: The effect apparently was to induce the uncomfortable feeling of grinding against your own inconsistent dogmas in dishonest, propagandistic nutjobs like you, because *you* now feel *fenced out*.
|
||||
|
||||
There's an important difference there.
|
||||
|
||||
The self-referential circle-jerk you've got going on here is *exactly* the definition of a fence; one made of hysteria and propaganda with the ulterior motive of usurping power away from a community of thousands of people, and putting it, squarely, into your own, trembling, wanting and lazy little hands.
|
||||
|
||||
> Reticulum is also not a toy. People run it over LoRa and packet radio for emergency and off-grid communication, so “someone degrades the network” is a safety question, not an inconvenience.
|
||||
|
||||
**Bingo**, Leo.
|
||||
|
||||
Which is why you need a *real* [responsible, accountable and competent person](https://reticulum.network/manual/brandolinis.html#a-history-of-what-works), with a decade-long track-record to run the project, not a bunch of kids with a fixation on Claude, and their sugardaddies like you.
|
||||
|
||||
> Here is the part that frustrates me.
|
||||
|
||||
Yes, you must *surely* be frustrated Leo, how very frustrating this must be for you and your ChatGPT subscription to have to go through all of this.
|
||||
|
||||
Tell me about frustration once you have actually done something that required more than pillow-fucking your own sense of grandiosity.
|
||||
|
||||
All your github repos are minor personal projects with up to... count them... coooount them... five stars. No users. You must surely know a lot about software development, distribution, and these commons you are protecting so heroically.
|
||||
|
||||
I can see you have a lot of skin in the game here, and a lot of work and experience on your back to bolster your authority.
|
||||
|
||||
Fuck off, you fraud.
|
||||
|
||||
> Every few years a piece of infrastructure I care about gets a new licence
|
||||
|
||||
You don't care one fucking bit about infrastructure Leo. *You don't even know what it is*, least of all what it takes to create it.
|
||||
|
||||
I do. Because I did it.
|
||||
|
||||
Go home, you're drunk.
|
||||
|
||||
Thanks for the material, though. You've provided a textbook example of the incessant bullshit, appropriation, deliberate enclosure and intentionally weaponized manipulation that [this project is up against](https://reticulum.network/manual/brandolinis.html#intent-consequence).
|
||||
|
||||
*In ideology and war, self-justification wears the same coat, and feeds the same destruction, perpetually.*
|
||||
|
||||
Yours truly,
|
||||
|
||||
Mark Qvist
|
||||
|
||||
September 18th, 2026
|
||||
|
||||
---
|
||||
|
||||
That it is, I am no longer angry. Back to work.
|
||||
253
docs/history/2026_09_21_Void_Grants.md
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
## Void Grants: Legal Foundations of Machine-Generated Code
|
||||
*From the [Brandolini's Reference](https://reticulum.network/manual/brandolinis.html) chapter of the Reticulum Manual.*
|
||||
|
||||
A grant of rights is only as real as the rights behind it. A license conveys what its issuer holds, and nothing more. A derivative may be distributed only under whatever license covers what it took. And over a work that a machine originated, no human holds anything at all.
|
||||
|
||||
Each of these statements is a matter of settled statute and case law, and this document sets out the authorities. The conclusions they support are:
|
||||
|
||||
1. A software implementation generated from a licensed work by a machine, published under its operator's claimed copyright and license, **conveys rights the operator never possessed**.
|
||||
2. **Its grant is void**. The projects, products, and promises built upon such grants stand on nothing. A field of void grants is a minefield for everyone who crosses it.
|
||||
|
||||
This document is a general examination of copyright law as it applies to machine-generated code. It is not legal advice.
|
||||
|
||||
### I. The Foundational Statute: Translation Is Copying
|
||||
|
||||
The starting point is the statutory definition of the class of works at issue. Title 17, United States Code, section 101 provides the definition of a derivative work:
|
||||
|
||||
> A "derivative work" is a work based upon one or more preexisting works, such as a *translation*, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or *any other form in which a work may be recast, transformed, or adapted*.
|
||||
|
||||
Section 101 further provides that *"the terms 'including' and 'such as' are illustrative and not limitative"*. As such, the list is a list of *examples*, and the very first example is translation. The definition also reaches, in its own words, *"any other form in which a work may be recast, transformed, or adapted"*.
|
||||
|
||||
The exclusive right at issue is defined in section 106:
|
||||
|
||||
> Subject to sections 107 through 122, the owner of copyright under this title has the exclusive rights to do and to authorize any of the following:
|
||||
> (1) to reproduce the copyrighted work in copies or phonorecords;
|
||||
> (2) to prepare derivative works based upon the copyrighted work;
|
||||
> (3) to distribute copies or phonorecords of the copyrighted work to the public by sale or other transfer of ownership, or by rental, lease, or lending;
|
||||
|
||||
Violation of any of these rights is infringement. 17 U.S.C. § 501(a).
|
||||
|
||||
Taken together, the provisions yield a construction that is difficult to improve upon and impossible to escape:
|
||||
|
||||
1. **A port is a translation.** A work that takes a program written in one language and from it produces a program written in another language, implementing the same behavior, is a translation of the original - whatever mechanism performed it, and however the surface presentation differs.
|
||||
2. **A translation is a derivative work.** Section 101 says so, explicitly.
|
||||
3. **Preparing a derivative work is the copyright owner's exclusive right.** Section 106(2) says so explicitly. A derivative work prepared without authorization, or outside the scope of any authorization, infringes that right.
|
||||
|
||||
The common objection - "the code is in a different language, so it is completely different" - is not a legal argument; it is the definition of what the statute already classifies.
|
||||
|
||||
Translation *is the paradigm* of derivative authorship, not an exception to it. If translating a work without permission was a defense, the derivative-work right would be meaningless; every infringing adaptation would simply need to be "different enough" to count as a translation of its source.
|
||||
|
||||
Nor does the identity of the translator change the analysis. The statute asks whether the work is "based upon" a preexisting work. It does not ask who or what performed the translation. A machine that ingests a licensed work and reproduces a translated transformation of it has performed mechanical translation; the output is a derivative of the ingested work, and the law governing derivative works applies to it exactly as it applies to a translation made by hand.
|
||||
|
||||
### II. Ideas Are Free, Expressions Protected
|
||||
|
||||
The second foundation is the distinction between what a work *is* - its ideas, its processes, its systems - and the *expression* in which those ideas are embodied. The distinction is codified in section 102(b):
|
||||
|
||||
> In no case does copyright protection for an original work of authorship extend to any idea, procedure, process, system, method of operation, concept, principle, or discovery, regardless of the form in which it is described, explained, illustrated, or embodied in such work.
|
||||
|
||||
The principle is older than the statute, and its application to software is settled. An author may own the particular code in which a method is expressed; the method itself, the process, the algorithm, belongs to no one - because *"the sine qua non of copyright is originality"* (*Feist Publications, Inc. v. Rural Telephone Service Co.*, 499 U.S. 340, 345. 1991). And ideas are not original to anyone in particular, *Baker v. Selden*, 101 U.S. 99 (1879), established the line: Copyright in a book describing a system does not prevent others from practicing the system.
|
||||
|
||||
Applied to a communications protocol, the structure is clean:
|
||||
|
||||
---
|
||||
|
||||
**An implementation of a public-domain protocol, honestly reasoned and written by humans, is welcome and unencumbered, and owes nothing to any license. An implementation generated from a licensed implementation is a derivative of licensed work, subject to its conditions and to the ordinary copyright of its source.**
|
||||
|
||||
---
|
||||
|
||||
The public-domain status of the protocol's ideas changes nothing about the copyright in an implementation's expression. A person who copies an implementation - by machine or by hand - cannot defend the copying by pointing to the protocol, because the protocol covers the ideas, not the symbols in which someone expressed them.
|
||||
|
||||
A further misconception can be dismissed here; the recently popularized "clean-room" claim about the absence of copying. A [clean-room](https://reticulum.network/manual/brandolinis.html#the-clean-room-delusion) is a not a magic label achievable by LLM, but a specific, institutionalized procedure for proving to a court that copying did not occur: Physically separated teams, written functional specifications, attestable non-exposure, and no access to the original by the implementers (*Altai*, 982 F.2d at 709–11).
|
||||
|
||||
*"I created a clean-room implementation by feeding the original source code into a machine"* attempts to *claim* the certified, legally established procedure for proving the absence of copying by describing, in the same breath, a process **composed entirely of copying**. The word is deployed because the procedure it names is what the claimant would *need to have happened*.
|
||||
|
||||
### III. Machine Output Has No Author
|
||||
|
||||
The third foundation is the human authorship requirement. It is settled at both levels of the federal courts and in the Copyright Office's administering practice.
|
||||
|
||||
In *Thaler v. Perlmutter*, the district court held that *"copyright law protects only works of human creation,"* and that *"human authorship is a bedrock requirement of copyright"* (687 F. Supp. 3d 140, 146, D.D.C. 2023). On appeal, the United States Court of Appeals for the District of Columbia Circuit affirmed, holding that the Copyright Act *"requires all eligible works to be authored in the first instance by a human being"* (No. 23-5233, D.C. Cir. Mar. 18, 2025).
|
||||
|
||||
The requirement traces to the ordinary meaning of the statutory term. In *Burrow-Giles Lithographic Co. v. Sarony*, the Supreme Court defined an "author" as *"he to whom anything owes its origin; originator; maker; one who completes a work of science or literature"* (111 U.S. 53, 57–58, 1884), and more recently described the author as *"the person who translates an idea into a fixed, tangible expression entitled to copyright protection"* (*Community for Creative Non-Violence v. Reid*, 490 U.S. 730, 737, 1989).
|
||||
|
||||
---
|
||||
|
||||
**A machine does not owe expression its origin; it reproduces it. No court has recognized copyright in material created by non-humans, and the courts that have addressed the question have rejected it. If you create by machine generation, you do not own the output.**
|
||||
|
||||
---
|
||||
|
||||
The Copyright Office's registration guidance applies the requirement to generative systems in terms that are directly on point. The Office asks:
|
||||
|
||||
> whether the "work" is basically one of human authorship, with the computer [or other device] merely being an assisting instrument, or whether the traditional elements of authorship in the work (literary, artistic, or musical expression or elements of selection, arrangement, etc.) were actually conceived and executed not by man but by a machine.
|
||||
|
||||
- *Copyright Registration Guidance: Works Containing Material Generated by Artificial Intelligence*, 88 Fed. Reg. 16190, 16192 (Mar. 16, 2023)
|
||||
|
||||
And it states the consequence: *"If a work's traditional elements of authorship were produced by a machine, the work lacks human authorship and the Office will not register it"* (*Id.*).
|
||||
|
||||
On the role of prompts, the Office is equally explicit: when a system receives a prompt and produces a work in response, *"these prompts function more like instructions to a commissioned artist - they identify what the prompter wishes to have depicted, but the machine determines how those instructions are implemented in its output"* (*Id.*).
|
||||
|
||||
The agency applied this standard and excluded machine-originated material from registration in the leading administrative case. In *Zarya of the Dawn* (Cancellation Decision, Feb. 21, 2023, Registration No. VAu001480196), the Office cancelled registration of images generated by the Midjourney service, holding that *"it was Midjourney-not [the author]-that originated the 'traditional elements of authorship' in the images"*, while preserving protection for the human-authored text and the human selection and arrangement of the work.
|
||||
|
||||
The Copyright Office's 2025 report on AI copyrightability reaches the same conclusion:
|
||||
|
||||
> Based on the functioning of current generally available technology, prompts do not alone provide sufficient control.
|
||||
|
||||
- U.S. Copyright Office, *Copyright and Artificial Intelligence, Part 2: Copyrightability*, at iii (Jan. 29, 2025)
|
||||
|
||||
The report's analysis is explicit: *"The Office concludes that, given current generally available technology, prompts alone do not provide sufficient human control to make users of an AI system the authors of the output. Prompts essentially function as instructions that convey unprotectible ideas"* (*Id.* at 18).
|
||||
|
||||
Its guiding distinction is stated plainly: *"The use of AI tools to assist rather than stand in for human creativity does not affect the availability of copyright protection for the output"* while *"Copyright does not extend to purely AI-generated material, or material where there is insufficient human control over the expressive elements"* (*Id.* at iii).
|
||||
|
||||
The boundary is therefore not "AI or no AI". It is *assistance or substitution*: Whether a human exercised sufficient creative control over the expressive elements and can answer for them, or whether the machine originated them and the human merely supplied instructions and a signature. That is a question of fact in each case, determined from the record of how the work was made.
|
||||
|
||||
Two facts follow, and stated together, they form the base legal reality:
|
||||
|
||||
1. **A work whose expressive elements were originated by a machine contains no valid new copyright: It has no human author. To the extent that the same work reproduces a preexisting copyrighted work without explicit license, it is at the same time an unauthorized derivative. It is unowned for anything new it contains, and unlicensed for everything taken.**
|
||||
2. **Its operator therefore holds no copyright in it and no grant over it - and a person who holds neither copyright nor grant can convey neither.**
|
||||
|
||||
The first fact makes the work **unownable** by its operator. The second makes it infringing against its source. Neither replaces the other:
|
||||
|
||||
- The absence of copyright in machine output does not make the output free for anyone to take; it makes it unowned.
|
||||
- This is a different thing entirely, especially when what it contains is someone else's expression.
|
||||
|
||||
### IV. License Conditions Bound the Grant
|
||||
|
||||
The fourth foundation governs what a license *is*. A copyright owner may grant permission to use the work on conditions. The conditions define the scope of the permission. Use outside the scope is use that was never authorized.
|
||||
|
||||
In *Jacobsen v. Katzer*, 535 F.3d 1373 (Fed. Cir. 2008), the Federal Circuit held that the conditions of an open-source license are *conditions of the copyright license itself*, not mere contractual promises, and that a licensee operating outside them is an infringer:
|
||||
|
||||
> Copyright holders who engage in open source licensing have the right to control the modification and distribution of copyrighted material.
|
||||
|
||||
> The choice to exact consideration in the form of compliance with the open source requirements of disclosure and explanation of changes, rather than as a dollar-denominated fee, is entitled to no less legal recognition.
|
||||
|
||||
> It is outside the scope of the [license] to modify and distribute the copyrighted materials without copyright notices and a tracking of modifications from the original computer files.
|
||||
|
||||
- *Id.* at 1381–82
|
||||
|
||||
The doctrinal mechanism is settled: *"A copyright owner who grants a nonexclusive license to use his copyrighted material waives his right to sue the licensee for copyright infringement"* within the scope of the license. If, however *"the licensee acts outside the scope, the licensor can bring an action for copyright infringement"*: An act outside the granted scope **is infringement** (*Sun Microsystems, Inc. v. Microsoft Corp.*, 188 F.3d 1115, 1121, 9th Cir. 1999; *Graham v. James*, 144 F.3d 229, 236, 2d Cir. 1998; *S.O.S., Inc. v. Payday, Inc.*, 886 F.2d 1081, 1087, 9th Cir. 1989).
|
||||
|
||||
Whether a given license term is a condition of the grant or a mere covenant is determined from the license's own language; conditions that limit what may be copied, modified, and distributed - the attribution terms, the notice terms, the modification-tracking terms, the field-of-use terms - are the boundaries of the copyright grant (*MDY Industries, LLC v. Blizzard Entertainment, Inc.*, 629 F.3d 928, 940, 9th Cir. 2010).
|
||||
|
||||
Three consequences follow for the analysis at hand. They are not hypothetical.
|
||||
|
||||
**First, the conditions are measured at the moment of copying**
|
||||
|
||||
A work is licensed under whatever conditions applied when the reproduction, adaptation, and distribution occurred. There is **no** period in which copying a licensed work is "free" because the copy was made by a machine; the machine is the *licensee's tool*, and the licensee is bound by the conditions regardless. A derivative generated without satisfying the conditions - without the attribution, without authorization for the manner of use - was never within the scope of the license, from the first instant of its existence.
|
||||
|
||||
**Second, breach does not restore rights that never attached**
|
||||
|
||||
The district court in *Artifex Software, Inc. v. Hancom, Inc.*, No. 16-cv-06982-JSC (N.D. Cal. Sept. 12, 2017), analyzing the GPL's termination provisions, observed that *"the language of the GPL suggests that [the licensee's] obligations persisted beyond termination of its rights to propagate software using Ghostscript,"* because the source-code obligation recurs each time a covered work is conveyed. The same structure operates a fortiori where a derivative was produced *outside* the license entirely: No grant ever attached, and no subsequent event creates one.
|
||||
|
||||
**Third, the settled conditions are the enforceable core**
|
||||
|
||||
The attribution and notice conditions rest on the settled doctrine above. Novel conditions (restrictions on field of use, restrictions on how the work may be consumed) are enforceable as conditions of the grant when clearly drafted, but their enforcement history is thinner. The argument that a derivative is unlicensed does *not* depend on the novel conditions being vindicated; it needs **only** the settled ones, and the ordinary law of derivative works, to stand.
|
||||
|
||||
### V. The Void Grant
|
||||
|
||||
The preceding sections assemble into a single structure. Let us apply it to an example situation:
|
||||
|
||||
- A project presented as an independent implementation of a protocol; "ground-up", "from scratch" or "clean-room" work.
|
||||
- The actual history shows continuous, machine-mediated ingestion of a licensed reference implementation.
|
||||
- The output is generated at a scale no human could produce or review, with the provenance of the process obscured.
|
||||
|
||||
**Step One: The derivative**
|
||||
|
||||
The project's output is a translation of the licensed implementation: A derivative work, produced by a copy process. 17 U.S.C. §§ 101, 106(2). Preparing it without authorization infringed from the first moment of its existence. *Id.* § 501(a).
|
||||
|
||||
**Step Two: The license**
|
||||
|
||||
The derivative was produced outside the conditions of the license; without the notices, without the attribution. The license never attached (*Jacobsen*, 535 F.3d at 1381–82).
|
||||
|
||||
**Step Three: Authorship**
|
||||
|
||||
The expression was originated by a machine. No human can account for its creative decisions, and no human authored it. It contains no valid new copyright (*Thaler*, 687 F. Supp. 3d at 146; No. 23-5233, D.C. Cir. 2025; 88 Fed. Reg. at 16192).
|
||||
|
||||
**Consequence: Void Grant**
|
||||
|
||||
The statute supplies the consequence. Section 103(a) provides:
|
||||
|
||||
> The subject matter of copyright as specified by section 102 includes compilations and derivative works, but protection for a work employing preexisting material in which copyright subsists does not extend to any part of the work in which such material has been used unlawfully.
|
||||
|
||||
And section 103(b) provides that copyright in a derivative work *"extends only to the material contributed by the author of such work."* The legislative history states the point without ambiguity: *"An unauthorized translation of a novel could not be copyrighted at all"* (H.R. Rep. No. 94-1476, at 57, 1976).
|
||||
|
||||
In our example, the operator of the derivative's copy process decides to assert copyright over the output, and licenses it to the world under terms of the operator's choosing, for example the Apache License.
|
||||
|
||||
But the operator holds no copyright in machine-originated content (section III) and no license over the source from which it was derived (sections I and IV).
|
||||
|
||||
For the derived material, the operator's copyright claim **is an assertion of a right that never existed**. The material was used unlawfully, and section 103(a) withholds protection from *every* part of the work in which it appears. For the machine-originated remainder, the operator holds no copyright either, for lack of any human author.
|
||||
|
||||
The operator's license to the world is a promise the operator had no power to make. Every person who receives the work believing they received a valid grant has received **a void grant**, and stands, as a matter of settled law, in the position of a downstream user of an unauthorized derivative, whatever they were told at the time.
|
||||
|
||||
This is a cascading consequence, and it is why the pattern is a hazard. No combination of self-granted licenses - "MIT," "Apache," "public domain," anything at all - can transfer rights the operator never held. Every downstream project, product, or deployment built on the derivative is built on unlicensed reproduction of a third party's work, whether or not it knew, whether or not it was told, and whether or not the marketing was plausible.
|
||||
|
||||
The longer the derivative circulates unexamined, the more it becomes embedded, forked, vendored, depended upon, and the more expensive its correction becomes for everyone who trusted it. A field of superficially real projects, repositories, and licenses that **in reality confer nothing on anyone who relies on them is a minefield**, in the most precise sense the law allows: The rights they purport to grant *were never possessed*.
|
||||
|
||||
The law defines what may be asserted, not what will be. The copyright owner may choose whether and how to assert these rights, and at what point in time. The exposure exists regardless, and for *every* downstream that trusted the void grant. This is the scope of the hazard that the conduct exemplified here produces.
|
||||
|
||||
### VI. Fair Use Does Not Rescue This Pattern
|
||||
|
||||
Every defendant raises fair use. It is an essential and important limitation in the service of free speech, accountability, criticism, education and other important domains. Therefore, it must be considered, and in the case exemplified here, it fails.
|
||||
|
||||
Fair use is a limitation on the exclusive rights, evaluated case by case under four factors: The purpose and character of the use; the nature of the copyrighted work; the amount and substantiality of the portion used; and the effect of the use on the potential market for or value of the work (17 U.S.C. § 107).
|
||||
|
||||
On the first factor, the governing standard is *Andy Warhol Foundation for the Visual Arts, Inc. v. Goldsmith*, 598 U.S. 508 (2023), in which the Supreme Court held that:
|
||||
|
||||
> In sum, the first fair use factor considers whether the use of a copyrighted work has a further purpose or different character, which is a matter of degree, and the degree of difference must be balanced against the commercial nature of the use
|
||||
|
||||
The question is whether the use serves a further purpose, not whether the surface changed. A derivative that exists to serve as the same thing as the original, marketed to the people who would otherwise use the original, has not acquired a further purpose by being translated; the surface changed, the function did not, and the use is commercial.
|
||||
|
||||
The remaining factors resolve the same way. Source code is expressive creative work, not bare fact. The pattern under examination copies the whole; a complete implementation, ingested and translated in its entirety. And the market effect is the decisive factor, because it is engineered: A derivative marketed as a drop-in replacement for the work it was copied from is not a complement to the original's market, it is a substitute for it. The Supreme Court has long held that use of a work to satisfy the market demand for the original is the paradigm of an *unfair* use (*Harper & Row, Publishers, Inc. v. Nation Enterprises*, 471 U.S. 539, 566–67, 1985).
|
||||
|
||||
The one case that is always raised in response, *Google LLC v. Oracle America, Inc.*, 593 U.S. 1 (2021), holds less than it is believed to. The Court there assumed *"for argument's sake, that the material was copyrightable"* and held *"that the copying here at issue nonetheless constituted a fair use"* (*Id.* at 11).
|
||||
|
||||
The material in question was a thin slice of *declaring code*, which the Court found sits, *"if copyrightable at all, further than are most computer programs (such as the implementing code) from the core of copyright"* (*id.*), in a use that built a new platform for a new environment, with the copier **having written the vast majority of its own code**. By comparison: A wholesale translation of an entire implementation, marketed as a substitute for it, is **not** that case; it is the other end of every factor.
|
||||
|
||||
The valuable zones of fair use remain, and they are important. **Commentary and criticism**, quoting from a work to review, discuss or criticize it. **Teaching and scholarship**, explaining concepts and quoting excerpts, is its traditional beneficiary.
|
||||
|
||||
### Conclusion
|
||||
|
||||
The law is neither obscure nor new. The conclusion it yields can now be stated:
|
||||
|
||||
- A machine translation of a protected implementation **is a derivative work**, and an unauthorized derivative **is an infringement**, whatever language it speaks and whatever machinery produced it.
|
||||
- A work whose expression a machine originated has no human author, no valid new copyright, and without valid copyright **no license can be granted on it**.
|
||||
- § 103 withholds protection from every part of a derivative in which protected material was used unlawfully. A license is a grant with conditions, and a derivative made outside the conditions **never received a grant**.
|
||||
- A person who holds no copyright and no grant **can convey neither**, and *every* downstream consumer who received from them received a **void grant**.
|
||||
|
||||
This is a *catastrophic* failure state for all who want to create things good and useful: The projects that inherit only void grants and liability believing they have inherited foundations, the users who depend on code that no one on earth can answer for, the honest builders whose work is buried under a field of plausible-looking impostors.
|
||||
|
||||
### Sources and Authorities
|
||||
|
||||
Statutory references are to Title 17, United States Code. Case citations are to the official or generally cited reporters. Online links are for reference only. Section numbers refer to the sections of this document in which each authority is principally relied upon.
|
||||
|
||||
**Statutes**
|
||||
|
||||
- [17 U.S.C. § 101 (definitions: "derivative work," "including," "publication") - I, II](https://www.law.cornell.edu/uscode/text/17/101)
|
||||
- [17 U.S.C. § 102(b) (idea/expression) - II](https://www.law.cornell.edu/uscode/text/17/102)
|
||||
- [17 U.S.C. § 103(a)–(b) (derivative works employing unlawfully used preexisting material) - V](https://www.law.cornell.edu/uscode/text/17/103)
|
||||
- [17 U.S.C. § 106(1)–(3) (exclusive rights: reproduction, derivative works, distribution) - I](https://www.law.cornell.edu/uscode/text/17/106)
|
||||
- [17 U.S.C. § 107 (fair use) - VI](https://www.law.cornell.edu/uscode/text/17/107)
|
||||
- [17 U.S.C. § 501(a) (infringement) - I, V](https://www.law.cornell.edu/uscode/text/17/501)
|
||||
|
||||
**Judicial decisions**
|
||||
|
||||
- [*Jacobsen v. Katzer*, 535 F.3d 1373 (Fed. Cir. 2008) - IV, V](https://www.casemine.com/judgement/us/5914b290add7b04934760a54)
|
||||
- [*Sun Microsystems, Inc. v. Microsoft Corp.*, 188 F.3d 1115 (9th Cir. 1999) - IV](https://law.justia.com/cases/federal/appellate-courts/F3/188/1115/629501/)
|
||||
- [*Graham v. James*, 144 F.3d 229 (2d Cir. 1998) - IV](https://caselaw.findlaw.com/court/us-2nd-circuit/1011522.html)
|
||||
- [*S.O.S., Inc. v. Payday, Inc.*, 886 F.2d 1081 (9th Cir. 1989) - IV](https://law.justia.com/cases/federal/appellate-courts/F2/886/1081/19091/)
|
||||
- [*MDY Industries, LLC v. Blizzard Entertainment, Inc.*, 629 F.3d 928 (9th Cir. 2010) - IV](https://law.justia.com/cases/federal/appellate-courts/ca9/09-15932/09-15932-2011-02-25.html)
|
||||
- [*Artifex Software, Inc. v. Hancom, Inc.*, No. 16-cv-06982-JSC (N.D. Cal. Sept. 12, 2017) (settled; dismissed with prejudice Jan. 2018) - IV](https://storage.courtlistener.com/recap/gov.uscourts.cand.305835/gov.uscourts.cand.305835.54.0_1.pdf)
|
||||
- [*Thaler v. Perlmutter*, 687 F. Supp. 3d 140 (D.D.C. 2023), aff'd, No. 23-5233 (D.C. Cir. Mar. 18, 2025) - III, V](https://www.bitlaw.com/source/cases/copyright/Thaler-Perlmutter-Dct.html)
|
||||
- [*Burrow-Giles Lithographic Co. v. Sarony*, 111 U.S. 53 (1884) - III](https://supreme.justia.com/cases/federal/us/111/53/)
|
||||
- [*Community for Creative Non-Violence v. Reid*, 490 U.S. 730 (1989) - III](https://supreme.justia.com/cases/federal/us/490/730/)
|
||||
- [*Feist Publications, Inc. v. Rural Telephone Service Co.*, 499 U.S. 340 (1991) - II](https://supreme.justia.com/cases/federal/us/499/340/)
|
||||
- [*Baker v. Selden*, 101 U.S. 99 (1879) - II](https://supreme.justia.com/cases/federal/us/101/99/)
|
||||
- [*Computer Associates International, Inc. v. Altai, Inc.*, 982 F.2d 693 (2d Cir. 1992) - II](https://law.justia.com/cases/federal/appellate-courts/F2/982/693/137252/)
|
||||
- [*Andy Warhol Foundation for the Visual Arts, Inc. v. Goldsmith*, 598 U.S. 508 (2023) - VI](https://www.supremecourt.gov/opinions/22pdf/21-869_87ad.pdf)
|
||||
- [*Google LLC v. Oracle America, Inc.*, 593 U.S. 1 (2021) - VI](https://supreme.justia.com/cases/federal/us/593/18-956/)
|
||||
- [*Harper & Row, Publishers, Inc. v. Nation Enterprises*, 471 U.S. 539 (1985) - VI](https://supreme.justia.com/cases/federal/us/471/539/)
|
||||
|
||||
**Administrative and policy authorities**
|
||||
|
||||
- [*Copyright Registration Guidance: Works Containing Material Generated by Artificial Intelligence*, 88 Fed. Reg. 16190 (Mar. 16, 2023) - III](https://www.federalregister.gov/documents/2023/03/16/2023-05321/copyright-registration-guidance-works-containing-material-generated-by-artificial-intelligence)
|
||||
- [*Zarya of the Dawn*, U.S. Copyright Office (Cancellation Decision, Feb. 21, 2023), Registration No. VAu001480196 - III](https://www.copyright.gov/docs/zarya-of-the-dawn.pdf)
|
||||
- [U.S. Copyright Office, *Copyright and Artificial Intelligence, Part 2: Copyrightability* (Jan. 29, 2025) - III](https://www.copyright.gov/ai/Copyright-and-Artificial-Intelligence-Part-2-Copyrightability-Report.pdf)
|
||||
- [H.R. Rep. No. 94-1476 (1976) - V](https://law.resource.org/pub/us/works/aba/ibr/H.Rep.94-1476.pdf)
|
||||
|
|
@ -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: c647a5f163ca6db1b075e5978b13a8fb
|
||||
config: a83095d108892439ad6027acd44f7c0a
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
|
|
|||
BIN
docs/manual/_images/columba.webp
Normal file
|
After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 54 KiB |
BIN
docs/manual/_images/modem73.webp
Normal file
|
After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 86 KiB |
BIN
docs/manual/_images/nomadnet_6.webp
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
docs/manual/_images/partyline.webp
Normal file
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 121 KiB |
BIN
docs/manual/_images/ren_browser.webp
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
docs/manual/_images/rentui.webp
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
docs/manual/_images/reticulated.webp
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/manual/_images/retipedia.webp
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/manual/_images/rngit.webp
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
docs/manual/_images/rrc.webp
Normal file
|
After Width: | Height: | Size: 225 KiB |
1625
docs/manual/_sources/brandolinis.rst.txt
Normal file
|
|
@ -8,6 +8,16 @@ This chapter of the manual serves as the technical reference for the distributed
|
|||
|
||||
A set of utilities for distributed collaborative software development and publishing are included in RNS.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/rngit.webp
|
||||
:align: center
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/rngit.png
|
||||
:align: center
|
||||
|
||||
The system consists of two parts: The ``rngit`` node that hosts repositories, and the ``git-remote-rns`` helper that enables Git to communicate with rngit nodes. As soon as you have RNS installed on your system, you can transparently use Git with Reticulum-hosted repositories just like any other type of remote. Git over Reticulum uses URLs in the following format: ``rns://DESTINATION_HASH/group/repo``.
|
||||
|
||||
If you set a branch to track a Reticulum remote as the default upstream, you can simply use ``git`` as you normally would; all commands work transparently and as expected.
|
||||
|
|
@ -133,6 +143,9 @@ To create a new empty repository on a remote node:
|
|||
|
||||
This creates a bare Git repository at the specified path. You must have ``create`` permission for the target group. When a repository is created, the creator automatically receives ``adm`` (admin) permissions on the repository through an auto-generated ``.allowed`` file.
|
||||
|
||||
.. tip::
|
||||
If you want to use a different primary branch name than ``master``, you can change it inside the bare git repository directory with ``git symbolic-ref HEAD refs/heads/name_of_other_branch``.
|
||||
|
||||
**All Command-Line Options (rngit create)**
|
||||
|
||||
.. code:: text
|
||||
|
|
@ -683,6 +696,7 @@ The following template files are supported:
|
|||
- ``commit.mu`` - Individual commit detail page
|
||||
- ``refs.mu`` - Branches and tags listing
|
||||
- ``stats.mu`` - Statistics page
|
||||
- ``no_ident.mu`` - Displayed to non-identified initiators if blocked
|
||||
|
||||
Templates can include the following variables:
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ to participate in the development of Reticulum itself.
|
|||
support
|
||||
examples
|
||||
license
|
||||
brandolinis
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
|
|
|||
|
|
@ -4,146 +4,225 @@
|
|||
Programs Using Reticulum
|
||||
************************
|
||||
|
||||
This chapter provides a non-exhaustive list of notable programs, systems and application-layer
|
||||
protocols that have been built using Reticulum.
|
||||
This chapter provides a non-exhaustive list of notable programs, systems and application-layer protocols that have been built using Reticulum.
|
||||
|
||||
These programs will let you get a feel for how Reticulum works. Most of them have been designed
|
||||
to run well even over slow networks based on LoRa or packet radio, but all can also be used over fast
|
||||
links, such as local WiFi, wired Ethernet, the Internet, or any combination.
|
||||
Many different applications using Reticulum already exist, serving a wide variety of purposes from day-to-day communication and information sharing to systems administration and tackling advanced networking and communications challenges.
|
||||
|
||||
As such, it is easy to get started experimenting, without having to set up any radio
|
||||
transceivers or infrastructure just to try it out. Launching the programs on separate
|
||||
devices connected to the same WiFi network is enough to get started, and physical
|
||||
radio interfaces can then be added later.
|
||||
.. tip::
|
||||
For ease of discovery while reading this manual over the web or as an e-book, links provided in this chapter primarily point to repositories and pages located on internet servers. In many cases, these are public web mirrors, with the upstream repositories residing on Reticulum nodes. For the latest source, updates and information regarding any project, be sure to check their Nomad Network or ``rngit`` nodes.
|
||||
|
||||
Programs & Utilities
|
||||
====================
|
||||
These programs will let you get a feel for how Reticulum works. Most of them have been designed to run well even over slow networks based on LoRa or packet radio, but all can also be used over fast links, such as local WiFi, wired Ethernet, the Internet, or any combination.
|
||||
|
||||
Many different applications using Reticulum already exist, serving a wide variety of purposes
|
||||
from day-to-day communication and information sharing to systems administration and tackling
|
||||
advanced networking and communications challenges.
|
||||
As such, it is easy to get started experimenting, without having to set up any radio transceivers or infrastructure just to try it out. Launching the programs on separate devices connected to the same WiFi network is enough to get started, and physical radio interfaces can then be added later.
|
||||
|
||||
Development of Reticulum-based applications and systems is ongoing, so consider this list
|
||||
a non-exhaustive starting point of *some* of the options available. With a bit of searching,
|
||||
primarily over Reticulum itself, you will find many more interesting things.
|
||||
Development of Reticulum-based applications and systems is ongoing, so consider this list a non-exhaustive starting point of *some* of the options available. With a bit of searching, primarily over Reticulum itself, you will find many more interesting things.
|
||||
|
||||
Remote Shell
|
||||
^^^^^^^^^^^^
|
||||
Browsing & Publishing
|
||||
=====================
|
||||
|
||||
The `rnsh <https://github.com/acehoss/rnsh>`_ program lets you establish fully interactive
|
||||
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
|
||||
remote system, and is similar to how ``ssh`` works. The ``rnsh`` program is very efficient, and
|
||||
can facilitate fully interactive shell sessions, even over extremely low-bandwidth links,
|
||||
such as LoRa or packet radio.
|
||||
In addition to the programs listed in this section, other Nomad Network clients include:
|
||||
|
||||
In addition to the default, fully interactive terminal mode,
|
||||
for extremely limited links, ``rnsh`` offers line-interactive mode, allowing you to interact
|
||||
with remote systems, even when link throughput is counted in a few hundreds of bits per second.
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
- `MeshChatX <https://meshchatx.com/>`_
|
||||
- `Columba <https://github.com/torlando-tech/columba/>`_
|
||||
|
||||
Nomad Network
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
The terminal-based program `Nomad Network <https://github.com/markqvist/nomadnet>`_
|
||||
provides a complete encrypted communications suite built with Reticulum. It features
|
||||
encrypted messaging (both direct and delayed-delivery for offline users), file sharing,
|
||||
and has a built-in text-browser and page server with support for dynamically rendered pages,
|
||||
user authentication and more.
|
||||
The terminal-based program `Nomad Network <https://github.com/markqvist/nomadnet>`_ provides a complete encrypted communications suite built with Reticulum.
|
||||
|
||||
.. image:: screenshots/nomadnet_3.png
|
||||
:target: https://github.com/markqvist/nomadnet
|
||||
.. only:: html
|
||||
|
||||
`Nomad Network <https://github.com/markqvist/nomadnet>`_ is a user-facing client
|
||||
for the messaging and information-sharing protocol LXMF.
|
||||
.. image:: screenshots/nomadnet_6.webp
|
||||
:align: center
|
||||
:target: https://github.com/markqvist/nomadnet
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/nomadnet_6.png
|
||||
:align: center
|
||||
:target: https://github.com/markqvist/nomadnet
|
||||
|
||||
It features encrypted LXMF messaging (both direct and delayed-delivery for offline users), file sharing, and has a built-in text-browser and page server with support for dynamically rendered pages, user authentication and more. It also includes an RRC client and a suite of network utilities.
|
||||
|
||||
|
||||
Ren Browser
|
||||
^^^^^^^^^^^
|
||||
|
||||
`Ren Browser <https://github.com/Quad4-Software/Ren-Browser>`_ is a modern, standalone browser for Nomad Network using the `Reticulum-Go <https://github.com/Quad4-Software/Reticulum-Go>`_ implementation. The project is actively being developed, and should be considered alpha level software, but much of the functionality is already working. If you want to help expanding the ecosystem, this is an excellent project to take a look at.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/ren_browser.webp
|
||||
:align: center
|
||||
:target: https://github.com/fr33n0w/rBrowser
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/ren_browser.png
|
||||
:align: center
|
||||
:target: https://github.com/fr33n0w/rBrowser
|
||||
|
||||
Ren Browser is great example of an application aiming for true, seamless interoperability with the existing software ecosystem. It can operate fully standalone using it's own embedded `Go implementation <https://github.com/Quad4-Software/Reticulum-Go>`_ of Reticulum, or seamlessly utilise a shared RNS instance already running on the system.
|
||||
|
||||
RNS Page Node
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
`RNS Page Node <https://git.quad4.io/RNS-Things/rns-page-node>`_ is a simple way to serve pages and files to any other Nomad Network compatible client. Drop-in replacement for NomadNet nodes that primarily serve pages and files.
|
||||
`RNS Page Node <https://github.com/Quad4-Software/rns-page-node>`_ is a simple way to serve pages and files to any other Nomad Network compatible client. Drop-in replacement for NomadNet nodes that primarily serve pages and files.
|
||||
|
||||
|
||||
Retipedia
|
||||
^^^^^^^^^
|
||||
|
||||
You can host the entirity of Wikipedia (or any ``.zim``) file to other Nomad Network clients using `Retipedia <https://github.com/RFnexus/Retipedia>`_.
|
||||
You can host the entirity of Wikipedia, StackExchange or any other ``.zim`` file, to Nomad Network clients using `Retipedia <https://github.com/RFnexus/Retipedia>`_.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/retipedia.webp
|
||||
:align: center
|
||||
:target: https://github.com/RFnexus/Retipedia
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/retipedia.png
|
||||
:align: center
|
||||
:target: https://github.com/RFnexus/Retipedia
|
||||
|
||||
|
||||
Git Over Reticulum
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The included :ref:`rngit<git-main>` system provides a fully distributed solution for managing, hosting, collaborating on and interacting with Git repositories over Reticulum.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/rngit.webp
|
||||
:align: center
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/rngit.png
|
||||
:align: center
|
||||
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
|
||||
Messaging & Voice
|
||||
=================
|
||||
|
||||
Sideband
|
||||
^^^^^^^^
|
||||
|
||||
If you would rather use an LXMF client with a graphical user interface, you can take
|
||||
a look at `Sideband <https://unsigned.io/sideband>`_, which is available for Android,
|
||||
Linux, macOS and Windows. Sideband is an advanced LXMF and LXST client, and a multi-purpose Reticulum
|
||||
utility, with features and functionality targeted at advanced users.
|
||||
`Sideband <https://github.com/markqvist/Sideband>`_ is a technical LXMF and LXST client, for Android, Linux, macOS and Windows. It also serves as a multi-purpose Reticulum utility, with features and functionality targeted at advanced users.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/sideband_devices.webp
|
||||
:align: center
|
||||
:target: https://unsigned.io/sideband
|
||||
:target: https://github.com/markqvist/Sideband
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/sideband_devices.png
|
||||
:align: center
|
||||
:target: https://unsigned.io/sideband
|
||||
:target: https://github.com/markqvist/Sideband
|
||||
|
||||
Sideband allows you to communicate with other people or LXMF-compatible
|
||||
systems over Reticulum networks using LoRa, Packet Radio, WiFi, I2P, Encrypted QR
|
||||
Paper Messages, or anything else Reticulum supports.
|
||||
Sideband allows you to communicate with other people or LXMF-compatible systems over Reticulum networks using LoRa, Packet Radio, WiFi, I2P, Encrypted QR Paper Messages, or anything else Reticulum supports.
|
||||
|
||||
It also interoperates with all other LXMF clients, and provides advanced features such as voice messaging,
|
||||
real-time voice calls, file attachments, private telemetry sharing, and a full
|
||||
plugin system for expandability.
|
||||
It interoperates with all other LXMF clients, and provides advanced features such as PTT and voice messaging, real-time voice calls, file attachments, private telemetry sharing, and a full plugin system for expandability.
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
|
||||
MeshChatX
|
||||
^^^^^^^^
|
||||
^^^^^^^^^
|
||||
|
||||
A `Reticulum MeshChat fork from the future <https://git.quad4.io/RNS-Things/MeshChatX>`_, with the goal of providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application. This project is separate from the original `Reticulum MeshChat <https://github.com/liamcottle/reticulum-meshchat>`_ project, and is not affiliated with the original project, but is a much more up-to-date, comprehensive and well-maintained fork.
|
||||
`MeshChatX <https://meshchatx.com/>`_ is an all-in-one Reticulum client with messaging, calling, and a NomadNet browser, providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/meshchatx.webp
|
||||
:align: center
|
||||
:target: https://git.quad4.io/RNS-Things/MeshChatX
|
||||
:target: https://meshchatx.com/
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/meshchatx.png
|
||||
:align: center
|
||||
:target: https://git.quad4.io/RNS-Things/MeshChatX
|
||||
:target: https://meshchatx.com/
|
||||
|
||||
|
||||
Features include full LXST support, custom voicemail, phonebook, contact sharing, and ringtone support, multi-identity handling, modern UI/UX, offline documentation, expanded tools, page archiving, integrated maps, telemetry and improved application security.
|
||||
Features include full LXST support, custom voicemail, phonebook, contact sharing, ringtone support, multi-identity handling, modern UI/UX, offline documentation, expanded tools, page archiving, integrated maps, telemetry and improved application security.
|
||||
|
||||
.. raw:: latex
|
||||
Columba
|
||||
^^^^^^^
|
||||
|
||||
\newpage
|
||||
`Columba <https://github.com/torlando-tech/columba/>`_ is a simple and familiar LXMF messaging app Android, built with a native Android interface and Material Design 3. It also includes LXST voice call functionality, and a mobile Nomad Network page browser.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/columba.webp
|
||||
:align: center
|
||||
:target: https://github.com/torlando-tech/columba/
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/columba.png
|
||||
:align: center
|
||||
:target: https://github.com/torlando-tech/columba/
|
||||
|
||||
|
||||
Ren TUI
|
||||
^^^^^^^
|
||||
|
||||
`Ren TUI <https://github.com/Quad4-Software/Ren-TUI>`_ is an early prototype terminal LXMF client for Reticulum built with Odin on librns (Reticulum-Go). It aims for NomadNet-like messaging/browsing without urwid or ncurses.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/rentui.webp
|
||||
:align: center
|
||||
:target: https://github.com/Quad4-Software/Ren-TUI
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/rentui.png
|
||||
:align: center
|
||||
:target: https://github.com/Quad4-Software/Ren-TUI
|
||||
|
||||
Chat
|
||||
====
|
||||
|
||||
Reticulum Relay Chat
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
`Reticulum Relay Chat <https://rrc.kc1awv.net/>`_ is a live chat system built on top of the Reticulum Network Stack. It exists to let people talk to each other in real time over Reticulum without dragging in message databases, synchronization engines, or architectural commitments they did not ask for.
|
||||
|
||||
The `rrcd <https://github.com/kc1awv/rrcd>`_ program provides a functional, reference RRC hub-server daemon implementation. RRC user clients include `rrc-gui <https://github.com/kc1awv/rrc-gui>`_ and `rrc-web <https://github.com/kc1awv/rrc-web>`_.
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/rrc.webp
|
||||
:align: center
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/rrc.png
|
||||
:align: center
|
||||
|
||||
RRC is closer in spirit to IRC than to modern “everything platforms.” You connect, you join a room, you talk, and then you leave. If you were present, you saw the conversation. If you were not, the conversation did not wait for you. This is not an accident. This is the entire design.
|
||||
|
||||
The `rrcd <https://github.com/kc1awv/rrcd>`_ program provides a functional, reference RRC hub-server daemon implementation. RRC user clients include:
|
||||
|
||||
- `Nomad Network <https://github.com/markqvist/NomadNet/>`_
|
||||
- `MeshChatX <https://meshchatx.com/>`_
|
||||
- `Eridanus <https://github.com/torlando-tech/eridanus>`_
|
||||
- `rrc-gui <https://github.com/kc1awv/rrc-gui>`_
|
||||
- `rrc-web <https://github.com/kc1awv/rrc-web>`_
|
||||
|
||||
RetiBBS
|
||||
^^^^^^^
|
||||
|
||||
`RetiBBS <https://github.com/kc1awv/RetiBBS>`_ is a bulletin board system implementation for Reticulum networks.
|
||||
`RetiBBS <https://github.com/kc1awv/RetiBBS>`_ is an old-school bulletin board system implementation for Reticulum networks.
|
||||
|
||||
.. only:: html
|
||||
|
||||
|
|
@ -163,29 +242,14 @@ RetiBBS allows users to communicate through message boards in a secure manner.
|
|||
|
||||
\newpage
|
||||
|
||||
RBrowser
|
||||
^^^^^^^^
|
||||
Voice & Telephony
|
||||
=================
|
||||
|
||||
The `rBrowser <https://github.com/fr33n0w/rBrowser>`_ program is a cross-platform, standalone, web-based browser for exploring NomadNetwork Nodes over Reticulum Network. It automatically discovers NomadNet nodes through network announces and provides a user-friendly interface for browsing distributed content with Micron markup support.
|
||||
In addition to the programs listed in this section, other LXST voice clients include:
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/rbrowser.webp
|
||||
:align: center
|
||||
:target: https://github.com/fr33n0w/rBrowser
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/rbrowser.png
|
||||
:align: center
|
||||
:target: https://github.com/fr33n0w/rBrowser
|
||||
|
||||
Includes useful features like automatic listening for announce, adding nodes to favorites, browsing and rendering any kind of NomadNet links, downloading files from remote nodes, a unique local NomadNet Search Engine and more.
|
||||
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
- `Sideband <https://github.com/markqvist/Sideband>`_
|
||||
- `MeshChatX <https://meshchatx.com/>`_
|
||||
- `Columba <https://github.com/torlando-tech/columba/>`_
|
||||
|
||||
Reticulum Network Telephone
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
@ -206,14 +270,31 @@ The ``rnphone`` program, included as part of the `LXST <https://github.com/markq
|
|||
|
||||
It supports interfacing directly with hardware peripherals such as GPIO keypads and LCD displays, providing a modular system for building secure hardware telephones.
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
Partyline
|
||||
^^^^^^^^^
|
||||
|
||||
`Partyline <https://github.com/RFnexus/partyline>`_ is a group voice application for Reticulum, built on `LXST <https://github.com/markqvist/LXST>`_, inspired by `Mumble <https://github.com/mumble-voip/mumble>`_.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/partyline.webp
|
||||
:align: center
|
||||
:target: https://github.com/markqvist/LXST
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/partyline.png
|
||||
:align: center
|
||||
:target: https://github.com/markqvist/LXST
|
||||
|
||||
It provides both realtime group voice over any Reticulum transport capable of >6 kilobits per second, LXST dial-in support, listen-only music/broadcasting rooms, and many other useful functions.
|
||||
|
||||
|
||||
LXST Phone
|
||||
^^^^^^^^^^
|
||||
|
||||
The `LXST Phone <https://github.com/kc1awv/lxst_phone>`_ program is a cross-platform desktop application for performing LXST voice calls over Reticulum.
|
||||
The `LXST Phone <https://github.com/kc1awv/lxst_phone>`_ program is a cross-platform desktop application for performing LXST voice calls over Reticulum. It supports various advanced features such as SAS verification, peer blocking, rate limiting, encrypted call history storage and contact management.
|
||||
|
||||
.. only:: html
|
||||
|
||||
|
|
@ -227,28 +308,60 @@ The `LXST Phone <https://github.com/kc1awv/lxst_phone>`_ program is a cross-plat
|
|||
:align: center
|
||||
:target: https://github.com/kc1awv/lxst_phone
|
||||
|
||||
It supports various advanced features such as SAS verification, peer blocking, rate limiting, encrypted call history storage and contact management.
|
||||
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
|
||||
LXMFy
|
||||
^^^^^
|
||||
System Administration
|
||||
=====================
|
||||
|
||||
`LXMFy <https://lxmfy.quad4.io/>`_ is a comprehensive and advanced bot creation framework for LXMF, that allows building any kind of automation or bot system running over LXMF and Reticulum. `Bot implementations exist <https://github.com/lxmfy/awesome-lxmfy-bots>`_ for Home Assistant control, LLM integrations, and various other purposes.
|
||||
Remote Shell
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The included :ref:`rnsh<using-rnsh>` program lets you establish fully interactive
|
||||
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
|
||||
remote system, and is similar to how ``ssh`` works. The ``rnsh`` program is very efficient, and
|
||||
can facilitate fully interactive shell sessions, even over extremely low-bandwidth links,
|
||||
such as LoRa or packet radio.
|
||||
|
||||
LXMF Interactive Client
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
`LXMF Interactive Client <https://github.com/fr33n0w/lxmf-cli>`_ is a feature-rich, terminal-based LXMF messaging client with many advanced features and an extensible plugin architecture.
|
||||
In addition to the default, fully interactive terminal mode, for extremely limited links,
|
||||
``rnsh`` offers line-interactive mode, allowing you to interact with remote systems, even
|
||||
when link throughput is counted in a few hundreds of bits per second.
|
||||
|
||||
RNS FileSync
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The `RNS FileSync <https://git.quad4.io/RNS-Things/RNS-Filesync>`_ program enables automatic file synchronization between devices without requiring central servers, internet connectivity, or cloud services. It works over any network medium supported by Reticulum, including radio, LoRa, WiFi, or the internet, making it ideal for off-grid, privacy-focused, and resilient file sharing.
|
||||
The `RNS FileSync <https://github.com/Quad4-Software/RNS-Filesync>`_ program enables automatic file synchronization between devices without requiring central servers, internet connectivity, or cloud services. It works over any network medium supported by Reticulum, including radio, LoRa, WiFi, or the internet, making it ideal for off-grid, privacy-focused, and resilient file sharing.
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
|
||||
Utilities & Components
|
||||
======================
|
||||
|
||||
Modem73
|
||||
^^^^^^^
|
||||
|
||||
`MODEM73 <https://github.com/RFnexus/modem73>`_ and its companion `Modem73Interface <https://github.com/RFnexus/modem73interface>`_ is an open source software modem that works with any HF, VHF, or UHF radio capable of 2400 Hz of bandwidth. All you need is a sound card and audio cable for your radio.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/modem73.webp
|
||||
:align: center
|
||||
:target: https://github.com/RFnexus/modem73
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/modem73.jpg
|
||||
:align: center
|
||||
:target: https://github.com/RFnexus/modem73
|
||||
|
||||
LXMFy
|
||||
^^^^^
|
||||
|
||||
`LXMFy <https://lxmfy.quad4.io/>`_ is a comprehensive and advanced bot creation framework for LXMF, that allows building any kind of automation or bot system running over LXMF and Reticulum. `Bot implementations exist <https://lxmfy.quad4.io/latest/creating-bots/>`_ for Home Assistant control, LLM integrations, and various other purposes.
|
||||
|
||||
|
||||
Micron Parser JS
|
||||
|
|
@ -256,6 +369,25 @@ Micron Parser JS
|
|||
|
||||
`Micron Parser JS <https://github.com/RFnexus/micron-parser-js>`_ is the JavaScript-based parser for the Micron markup language, that most web-based Nomad Network browsers use. If you want to make utilities or tools that display Micron pages, this library is essential.
|
||||
|
||||
Reticulated
|
||||
^^^^^^^^^^^
|
||||
|
||||
`Reticulated <https://github.com/RFnexus/Reticulated>`_ is a Reticulum Network simulator that spawns live, interactive Reticulum instances controllable from a node-based Web UI.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/reticulated.webp
|
||||
:align: center
|
||||
:target: https://github.com/RFnexus/Reticulated
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/reticulated.jpg
|
||||
:align: center
|
||||
:target: https://github.com/RFnexus/Reticulated
|
||||
|
||||
You can create networks of any topology and set bitrate, MTU, and configure path loss and propagation behavior. It provides a top-down view to a live network. You can connect any Reticulum application to the simulated network. It also has built in utilities like a basic LXMF chat function and Resource transfer included.
|
||||
|
||||
|
||||
RNMon
|
||||
^^^^^
|
||||
|
|
@ -267,6 +399,16 @@ RNMon
|
|||
|
||||
\newpage
|
||||
|
||||
Device Flashers
|
||||
===============
|
||||
|
||||
In addition to the included command-line ``rnodeconf`` flasher, several community-provided RNode device flashers exist:
|
||||
|
||||
- `RNS Moscow <https://flasher.rns.moscow/>`_
|
||||
- `Aetherlab <https://flasher.aetherlab.org/>`_
|
||||
|
||||
Several programs, such as `Sideband <https://github.com/markqvist/Sideband>`_ and `MeshChatX <https://meshchatx.com/>`_ also include built-in device flashers that can be used fully offline.
|
||||
|
||||
Protocols
|
||||
=========
|
||||
|
||||
|
|
@ -298,8 +440,8 @@ Interface Modules & Connectivity Resources
|
|||
|
||||
This section provides a list of various community-provided interface modules, guides and resources for creating Reticulum networks over special or challenging mediums.
|
||||
|
||||
* Custom interface module for running `RNS over HTTP <https://git.quad4.io/RNS-Things/RNS-over-HTTP>`_
|
||||
* `Modem73 <https://github.com/RFnexus/modem73>`_ is an efficient software modem that can be used with Reticulum
|
||||
* Custom interface module for running `RNS over HTTP <https://github.com/Quad4-Software/RNS-over-HTTP>`_
|
||||
* Guide for running `Reticulum over ICMP <https://github.com/matvik22000/rns-over-icmp>`_ using ``PipeInterface``
|
||||
* Guide for running `Reticulum over DNS <https://github.com/markqvist/Reticulum/discussions/1002>`_ with Iodine
|
||||
* Guide for running `Reticulum over HF radio <https://github.com/RFnexus/reticulum-over-hf>`_
|
||||
* `Modem73 <https://github.com/RFnexus/modem73>`_ is a KISS TNC OFDM modem frontend that can be used with Reticulum
|
||||
* Guide for running `Reticulum over HF radio <https://github.com/RFnexus/reticulum-over-hf>`_
|
||||
|
|
@ -28,9 +28,6 @@ Donations are gratefully accepted via the following channels:
|
|||
Ko-Fi:
|
||||
https://ko-fi.com/markqvist
|
||||
|
||||
Are certain features in the development roadmap are important to you or your
|
||||
organisation? Make them a reality quickly by sponsoring their implementation.
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
|
|
|
|||
|
|
@ -274,8 +274,7 @@ You can easily add ``rnsd`` as an always-on service by :ref:`configuring a servi
|
|||
The rnstatus Utility
|
||||
====================
|
||||
|
||||
Using the ``rnstatus`` utility, you can view the status of configured Reticulum
|
||||
interfaces, similar to the ``ifconfig`` program.
|
||||
Using the ``rnstatus`` utility, you can view the status of and control configured Reticulum interfaces, similar to the ``ifconfig`` program.
|
||||
|
||||
**Usage Examples**
|
||||
|
||||
|
|
@ -350,6 +349,9 @@ Filter output to only show some interfaces:
|
|||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
--attach name Attach interface by name
|
||||
--detach name Detach interface by name
|
||||
--reload name Reload interface by name
|
||||
-a, --all show all interfaces
|
||||
-A, --announce-stats show announce stats
|
||||
-P, --pr-stats show path request stats
|
||||
|
|
@ -367,6 +369,8 @@ Filter output to only show some interfaces:
|
|||
-w seconds timeout before giving up on remote queries
|
||||
-d, --discovered list discovered interfaces
|
||||
-D show details and config entries for discovered interfaces
|
||||
--show-stale show stale discovery entries
|
||||
--show-unknown show discovery entries without version info
|
||||
-m, --monitor continuously monitor status
|
||||
-I, --monitor-interval seconds
|
||||
refresh interval for monitor mode (default: 1)
|
||||
|
|
@ -705,7 +709,7 @@ The rnx Utility
|
|||
The ``rnx`` utility is a basic remote command execution program. It allows you to
|
||||
execute commands on remote systems over Reticulum, and to view returned command
|
||||
output. For a fully interactive remote shell solution, be sure to also take a look
|
||||
at the `rnsh <https://github.com/acehoss/rnsh>`_ program.
|
||||
at the :ref:`rnsh<using-rnsh>` program.
|
||||
|
||||
**Usage Examples**
|
||||
|
||||
|
|
@ -771,6 +775,8 @@ another one, which will be created if it does not already exist
|
|||
--version show program's version number and exit
|
||||
|
||||
|
||||
.. _using-rnsh:
|
||||
|
||||
The rnsh Utility
|
||||
================
|
||||
|
||||
|
|
@ -1230,6 +1236,8 @@ For more fine-grained control over how discovered interfaces are auto-connected,
|
|||
|
||||
* The ``autoconnect_announces_to_internal`` option allows you to specify that auto-connected interfaces should propagate announces to ``internal`` mode interfaces, even if the auto-connected interface's mode would normally not allow for this.
|
||||
|
||||
* The ``autoconnect_unverified_implementations`` option will allow the interface discovery system to auto-connect to *any* announced interface, even those coming from unverified, experimental or potentially broken implementations. Be very careful with this option.
|
||||
|
||||
Remote Management
|
||||
-----------------
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
const DOCUMENTATION_OPTIONS = {
|
||||
VERSION: '1.5.1',
|
||||
VERSION: '1.5.5',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
|
|
|||
1611
docs/manual/brandolinis.html
Normal 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>Distributed Development - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Distributed Development - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -431,7 +432,7 @@
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Code Examples - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Code Examples - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="license.html">Reticulum License</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -3648,7 +3649,7 @@
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -296,7 +297,7 @@
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#"><link rel="search" title="Search" href="search.html">
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 --><title>Index - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 --><title>Index - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -225,6 +225,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -856,7 +857,7 @@
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Getting Started Fast - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -776,7 +777,7 @@ section of this manual.</p>
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Git Over Reticulum - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Git Over Reticulum - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -266,6 +267,7 @@
|
|||
<span id="git-main"></span><h1>Git Over Reticulum<a class="headerlink" href="#git-over-reticulum" title="Link to this heading">¶</a></h1>
|
||||
<p>This chapter of the manual serves as the technical reference for the distributed software development and project collaboration tools included in RNS. For a conceptual overview, see the <a class="reference internal" href="distributed.html#distributed-development"><span class="std std-ref">Distributed Development</span></a> chapter.</p>
|
||||
<p>A set of utilities for distributed collaborative software development and publishing are included in RNS.</p>
|
||||
<img alt="_images/rngit.webp" class="align-center" src="_images/rngit.webp" />
|
||||
<p>The system consists of two parts: The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> node that hosts repositories, and the <code class="docutils literal notranslate"><span class="pre">git-remote-rns</span></code> helper that enables Git to communicate with rngit nodes. As soon as you have RNS installed on your system, you can transparently use Git with Reticulum-hosted repositories just like any other type of remote. Git over Reticulum uses URLs in the following format: <code class="docutils literal notranslate"><span class="pre">rns://DESTINATION_HASH/group/repo</span></code>.</p>
|
||||
<p>If you set a branch to track a Reticulum remote as the default upstream, you can simply use <code class="docutils literal notranslate"><span class="pre">git</span></code> as you normally would; all commands work transparently and as expected.</p>
|
||||
<div class="admonition important">
|
||||
|
|
@ -356,6 +358,10 @@ Repository public/myrepo created
|
|||
</pre></div>
|
||||
</div>
|
||||
<p>This creates a bare Git repository at the specified path. You must have <code class="docutils literal notranslate"><span class="pre">create</span></code> permission for the target group. When a repository is created, the creator automatically receives <code class="docutils literal notranslate"><span class="pre">adm</span></code> (admin) permissions on the repository through an auto-generated <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> file.</p>
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
<p>If you want to use a different primary branch name than <code class="docutils literal notranslate"><span class="pre">master</span></code>, you can change it inside the bare git repository directory with <code class="docutils literal notranslate"><span class="pre">git</span> <span class="pre">symbolic-ref</span> <span class="pre">HEAD</span> <span class="pre">refs/heads/name_of_other_branch</span></code>.</p>
|
||||
</div>
|
||||
<p><strong>All Command-Line Options (rngit create)</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rngit create [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
|
|
@ -816,6 +822,7 @@ def hello_world():
|
|||
<li><p><code class="docutils literal notranslate"><span class="pre">commit.mu</span></code> - Individual commit detail page</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">refs.mu</span></code> - Branches and tags listing</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">stats.mu</span></code> - Statistics page</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">no_ident.mu</span></code> - Displayed to non-identified initiators if blocked</p></li>
|
||||
</ul>
|
||||
<p>Templates can include the following variables:</p>
|
||||
<ul class="simple">
|
||||
|
|
@ -1594,7 +1601,7 @@ done
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Communications Hardware - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Communications Hardware - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -676,7 +677,7 @@ can be used with Reticulum. This includes virtual software modems such as
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -372,28 +373,49 @@ to participate in the development of Reticulum itself.</p>
|
|||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="software.html">Programs Using Reticulum</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="software.html#programs-utilities">Programs & Utilities</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#remote-shell">Remote Shell</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="software.html#browsing-publishing">Browsing & Publishing</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#nomad-network">Nomad Network</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#ren-browser">Ren Browser</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#rns-page-node">RNS Page Node</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#retipedia">Retipedia</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#git-over-reticulum">Git Over Reticulum</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="software.html#messaging-voice">Messaging & Voice</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#sideband">Sideband</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#meshchatx">MeshChatX</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#id6">MeshChatX</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#id8">Columba</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#ren-tui">Ren TUI</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="software.html#chat">Chat</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#reticulum-relay-chat">Reticulum Relay Chat</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#retibbs">RetiBBS</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#rbrowser">RBrowser</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="software.html#voice-telephony">Voice & Telephony</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#reticulum-network-telephone">Reticulum Network Telephone</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#partyline">Partyline</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#lxst-phone">LXST Phone</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#lxmfy">LXMFy</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#lxmf-interactive-client">LXMF Interactive Client</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="software.html#system-administration">System Administration</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#remote-shell">Remote Shell</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#rns-filesync">RNS FileSync</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="software.html#utilities-components">Utilities & Components</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#modem73">Modem73</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#lxmfy">LXMFy</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#micron-parser-js">Micron Parser JS</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#reticulated">Reticulated</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#rnmon">RNMon</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="software.html#device-flashers">Device Flashers</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="software.html#protocols">Protocols</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#lxmf">LXMF</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#id16">LXST</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#id30">LXST</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#rrc">RRC</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
@ -631,6 +653,93 @@ to participate in the development of Reticulum itself.</p>
|
|||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="license.html">Reticulum License</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="brandolinis.html#what-the-reticulum-license-is">What The Reticulum License Is</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#why-this-license-exists">Why This License Exists</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#on-freedom">On Freedom</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-prediction-and-what-happened">The Prediction, And What Happened</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#what-this-means-for-other-implementations">What This Means For Other Implementations</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#copyright-the-two-legal-layers">Copyright & The Two Legal Layers</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-clean-room-delusion">The “Clean-Room” Delusion</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="brandolinis.html#open-source-means-open-source">Open Source Means Open Source</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#what-the-argument-is-actually-doing">What The Argument Is Actually Doing</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#who-is-restricted-by-which-license">Who Is Restricted By Which License?</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-consequences-of-the-not-open-source-claim">The Consequences Of The “Not Open Source” Claim</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-distribution-argument">The Distribution Argument</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#on-the-institutions-in-fairness">On The Institutions, In Fairness</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#what-open-means-positively">What “Open” Means Positively</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="brandolinis.html#assistance-versus-machine-substitution">Assistance Versus Machine-Substitution</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#false-dichotomy">False Dichotomy</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-line">The Line</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#what-assistance-looks-like">What assistance looks like</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#what-substitution-looks-like">What substitution looks like</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#infrastructure-matters">Infrastructure Matters</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-fluency-trap">The Fluency Trap</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#disclosed-assistance-or-laundered-generation">Disclosed Assistance Or Laundered Generation</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-license-in-this-context">The License In This Context</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-general-principle">The General Principle</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="brandolinis.html#void-grants-legal-foundations-of-machine-generated-code">Void Grants: Legal Foundations of Machine-Generated Code</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#i-the-foundational-statute-translation-is-copying">I. The Foundational Statute: Translation Is Copying</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#ii-ideas-are-free-expressions-protected">II. Ideas Are Free, Expressions Protected</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#iii-machine-output-has-no-author">III. Machine Output Has No Author</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#iv-license-conditions-bound-the-grant">IV. License Conditions Bound the Grant</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#v-the-void-grant">V. The Void Grant</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#vi-fair-use-does-not-rescue-this-pattern">VI. Fair Use Does Not Rescue This Pattern</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#conclusion">Conclusion</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#sources-and-authorities">Sources and Authorities</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="brandolinis.html#evaluating-a-reticulum-implementation">Evaluating a Reticulum Implementation</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-intention-of-this-guide">The Intention Of This Guide</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#two-questions-that-resolve-most-cases">Two Questions That Resolve Most Cases</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#other-signals-to-look-for">Other Signals To Look For</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-conformal-surface">The Conformal Surface</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#possible-verdicts">Possible Verdicts</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#these-standards-are-reasonable">These Standards Are Reasonable</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="brandolinis.html#the-movement-of-a-dozen">The Movement Of A Dozen</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#presentation-elongated">Presentation, Elongated</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#headcount">Headcount</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#recycled-wine-fermenting">Recycled Wine, Fermenting</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-method-of-madness">The Method of Madness</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#a-persistent-but-fragile-triad">A Persistent, But Fragile Triad</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#what-the-pattern-is-not">What The Pattern Is Not</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#intent-consequence">Intent & Consequence</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#recognition-response">Recognition & Response</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="brandolinis.html#network-health-coexistence">Network Health & Coexistence</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#substance-of-the-networks">Substance Of The Networks</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#health-is-a-property-of-the-whole">Health Is A Property Of The Whole</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#a-history-of-what-works">A History Of What Works</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-role-of-the-individual">The Role Of The Individual</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-place-of-new-implementations">The Place Of New Implementations</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#expanding-the-commons">Expanding The Commons</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#defence-of-the-commons">Defence Of The Commons</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="brandolinis.html#addendum-a-the-prns-case">Addendum A: The Prns Case</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#purpose-scope">Purpose & Scope</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-project-in-brief">The Project In Brief</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#question-1-is-this-a-clean-implementation-or-was-it-directly-generated-from-the-reference">Question 1: Is this a clean implementation, or was it directly generated from the reference?</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#question-2-who-or-what-is-the-agent-of-the-work">Question 2: Who, or what, is the agent of the work?</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#the-performance-claims">The performance claims</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#overall-evaluation">Overall Evaluation</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#licensing-analysis">Licensing Analysis</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#consequences">Consequences</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#verdict">Verdict</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="brandolinis.html#dating">Dating</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="toctree-wrapper compound">
|
||||
|
|
@ -721,7 +830,7 @@ to participate in the development of Reticulum itself.</p>
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -1908,7 +1909,7 @@ queue. Defaults to <code class="docutils literal notranslate"><span class="pre">
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></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>
|
||||
|
|
|
|||
|
|
@ -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="API Reference" href="reference.html"><link rel="prev" title="Code Examples" href="examples.html">
|
||||
<link rel="index" title="Index" href="genindex.html"><link rel="search" title="Search" href="search.html"><link rel="next" title="Brandolini’s Reference" href="brandolinis.html"><link rel="prev" title="Code Examples" href="examples.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>Reticulum License - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Reticulum License - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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 current current-page"><a class="current reference internal" href="#">Reticulum License</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -302,12 +303,12 @@ SOFTWARE.
|
|||
<footer>
|
||||
|
||||
<div class="related-pages">
|
||||
<a class="next-page" href="reference.html">
|
||||
<a class="next-page" href="brandolinis.html">
|
||||
<div class="page-info">
|
||||
<div class="context">
|
||||
<span>Next</span>
|
||||
</div>
|
||||
<div class="title">API Reference</div>
|
||||
<div class="title">Brandolini’s Reference</div>
|
||||
</div>
|
||||
<svg class="furo-related-icon"><use href="#svg-arrow-right"></use></svg>
|
||||
</a>
|
||||
|
|
@ -345,7 +346,7 @@ SOFTWARE.
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Building Networks - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Building Networks - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -664,7 +665,7 @@ differently than a mobile device roaming between radio cells.</p>
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></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>
|
||||
|
|
|
|||
|
|
@ -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="prev" title="Reticulum License" href="license.html">
|
||||
<link rel="index" title="Index" href="genindex.html"><link rel="search" title="Search" href="search.html"><link rel="prev" title="Brandolini’s Reference" href="brandolinis.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>API Reference - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>API Reference - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul class="current">
|
||||
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">API Reference</a></li>
|
||||
|
|
@ -1502,7 +1503,7 @@ statistics, you will be able to retrieve stats such as <em>RSSI</em>, <em>SNR</e
|
|||
<span class="sig-name descname"><span class="pre">get_expected_rate</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.get_expected_rate" title="Link to this definition">¶</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p>The packet expected in-flight data rate of an established link.</p>
|
||||
<dd class="field-odd"><p>The expected in-flight data rate of an established link.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
|
@ -2340,14 +2341,14 @@ will announce it.</p>
|
|||
|
||||
<div class="related-pages">
|
||||
|
||||
<a class="prev-page" href="license.html">
|
||||
<a class="prev-page" href="brandolinis.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">Reticulum License</div>
|
||||
<div class="title">Brandolini’s Reference</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
|
|
@ -2588,7 +2589,7 @@ will announce it.</p>
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<meta name="robots" content="noindex" />
|
||||
<title>Search - Reticulum Network Stack 1.5.1 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<title>Search - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="#" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -304,7 +305,7 @@
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Programs Using Reticulum - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Programs Using Reticulum - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -264,96 +265,114 @@
|
|||
<article role="main" id="furo-main-content">
|
||||
<section id="programs-using-reticulum">
|
||||
<span id="software-main"></span><h1>Programs Using Reticulum<a class="headerlink" href="#programs-using-reticulum" title="Link to this heading">¶</a></h1>
|
||||
<p>This chapter provides a non-exhaustive list of notable programs, systems and application-layer
|
||||
protocols that have been built using Reticulum.</p>
|
||||
<p>These programs will let you get a feel for how Reticulum works. Most of them have been designed
|
||||
to run well even over slow networks based on LoRa or packet radio, but all can also be used over fast
|
||||
links, such as local WiFi, wired Ethernet, the Internet, or any combination.</p>
|
||||
<p>As such, it is easy to get started experimenting, without having to set up any radio
|
||||
transceivers or infrastructure just to try it out. Launching the programs on separate
|
||||
devices connected to the same WiFi network is enough to get started, and physical
|
||||
radio interfaces can then be added later.</p>
|
||||
<section id="programs-utilities">
|
||||
<h2>Programs & Utilities<a class="headerlink" href="#programs-utilities" title="Link to this heading">¶</a></h2>
|
||||
<p>Many different applications using Reticulum already exist, serving a wide variety of purposes
|
||||
from day-to-day communication and information sharing to systems administration and tackling
|
||||
advanced networking and communications challenges.</p>
|
||||
<p>Development of Reticulum-based applications and systems is ongoing, so consider this list
|
||||
a non-exhaustive starting point of <em>some</em> of the options available. With a bit of searching,
|
||||
primarily over Reticulum itself, you will find many more interesting things.</p>
|
||||
<section id="remote-shell">
|
||||
<h3>Remote Shell<a class="headerlink" href="#remote-shell" title="Link to this heading">¶</a></h3>
|
||||
<p>The <a class="reference external" href="https://github.com/acehoss/rnsh">rnsh</a> program lets you establish fully interactive
|
||||
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
|
||||
remote system, and is similar to how <code class="docutils literal notranslate"><span class="pre">ssh</span></code> works. The <code class="docutils literal notranslate"><span class="pre">rnsh</span></code> program is very efficient, and
|
||||
can facilitate fully interactive shell sessions, even over extremely low-bandwidth links,
|
||||
such as LoRa or packet radio.</p>
|
||||
<p>In addition to the default, fully interactive terminal mode,
|
||||
for extremely limited links, <code class="docutils literal notranslate"><span class="pre">rnsh</span></code> offers line-interactive mode, allowing you to interact
|
||||
with remote systems, even when link throughput is counted in a few hundreds of bits per second.</p>
|
||||
</section>
|
||||
<p>This chapter provides a non-exhaustive list of notable programs, systems and application-layer protocols that have been built using Reticulum.</p>
|
||||
<p>Many different applications using Reticulum already exist, serving a wide variety of purposes from day-to-day communication and information sharing to systems administration and tackling advanced networking and communications challenges.</p>
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
<p>For ease of discovery while reading this manual over the web or as an e-book, links provided in this chapter primarily point to repositories and pages located on internet servers. In many cases, these are public web mirrors, with the upstream repositories residing on Reticulum nodes. For the latest source, updates and information regarding any project, be sure to check their Nomad Network or <code class="docutils literal notranslate"><span class="pre">rngit</span></code> nodes.</p>
|
||||
</div>
|
||||
<p>These programs will let you get a feel for how Reticulum works. Most of them have been designed to run well even over slow networks based on LoRa or packet radio, but all can also be used over fast links, such as local WiFi, wired Ethernet, the Internet, or any combination.</p>
|
||||
<p>As such, it is easy to get started experimenting, without having to set up any radio transceivers or infrastructure just to try it out. Launching the programs on separate devices connected to the same WiFi network is enough to get started, and physical radio interfaces can then be added later.</p>
|
||||
<p>Development of Reticulum-based applications and systems is ongoing, so consider this list a non-exhaustive starting point of <em>some</em> of the options available. With a bit of searching, primarily over Reticulum itself, you will find many more interesting things.</p>
|
||||
<section id="browsing-publishing">
|
||||
<h2>Browsing & Publishing<a class="headerlink" href="#browsing-publishing" title="Link to this heading">¶</a></h2>
|
||||
<p>In addition to the programs listed in this section, other Nomad Network clients include:</p>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://meshchatx.com/">MeshChatX</a></p></li>
|
||||
<li><p><a class="reference external" href="https://github.com/torlando-tech/columba/">Columba</a></p></li>
|
||||
</ul>
|
||||
<section id="nomad-network">
|
||||
<h3>Nomad Network<a class="headerlink" href="#nomad-network" title="Link to this heading">¶</a></h3>
|
||||
<p>The terminal-based program <a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a>
|
||||
provides a complete encrypted communications suite built with Reticulum. It features
|
||||
encrypted messaging (both direct and delayed-delivery for offline users), file sharing,
|
||||
and has a built-in text-browser and page server with support for dynamically rendered pages,
|
||||
user authentication and more.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/markqvist/nomadnet"><img alt="_images/nomadnet_3.png" src="_images/nomadnet_3.png" />
|
||||
<p>The terminal-based program <a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a> provides a complete encrypted communications suite built with Reticulum.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/markqvist/nomadnet"><img alt="_images/nomadnet_6.webp" class="align-center" src="_images/nomadnet_6.webp" />
|
||||
</a>
|
||||
<p><a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a> is a user-facing client
|
||||
for the messaging and information-sharing protocol LXMF.</p>
|
||||
<p>It features encrypted LXMF messaging (both direct and delayed-delivery for offline users), file sharing, and has a built-in text-browser and page server with support for dynamically rendered pages, user authentication and more. It also includes an RRC client and a suite of network utilities.</p>
|
||||
</section>
|
||||
<section id="ren-browser">
|
||||
<h3>Ren Browser<a class="headerlink" href="#ren-browser" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://github.com/Quad4-Software/Ren-Browser">Ren Browser</a> is a modern, standalone browser for Nomad Network using the <a class="reference external" href="https://github.com/Quad4-Software/Reticulum-Go">Reticulum-Go</a> implementation. The project is actively being developed, and should be considered alpha level software, but much of the functionality is already working. If you want to help expanding the ecosystem, this is an excellent project to take a look at.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/fr33n0w/rBrowser"><img alt="_images/ren_browser.webp" class="align-center" src="_images/ren_browser.webp" />
|
||||
</a>
|
||||
<p>Ren Browser is great example of an application aiming for true, seamless interoperability with the existing software ecosystem. It can operate fully standalone using it’s own embedded <a class="reference external" href="https://github.com/Quad4-Software/Reticulum-Go">Go implementation</a> of Reticulum, or seamlessly utilise a shared RNS instance already running on the system.</p>
|
||||
</section>
|
||||
<section id="rns-page-node">
|
||||
<h3>RNS Page Node<a class="headerlink" href="#rns-page-node" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://git.quad4.io/RNS-Things/rns-page-node">RNS Page Node</a> is a simple way to serve pages and files to any other Nomad Network compatible client. Drop-in replacement for NomadNet nodes that primarily serve pages and files.</p>
|
||||
<p><a class="reference external" href="https://github.com/Quad4-Software/rns-page-node">RNS Page Node</a> is a simple way to serve pages and files to any other Nomad Network compatible client. Drop-in replacement for NomadNet nodes that primarily serve pages and files.</p>
|
||||
</section>
|
||||
<section id="retipedia">
|
||||
<h3>Retipedia<a class="headerlink" href="#retipedia" title="Link to this heading">¶</a></h3>
|
||||
<p>You can host the entirity of Wikipedia (or any <code class="docutils literal notranslate"><span class="pre">.zim</span></code>) file to other Nomad Network clients using <a class="reference external" href="https://github.com/RFnexus/Retipedia">Retipedia</a>.</p>
|
||||
<p>You can host the entirity of Wikipedia, StackExchange or any other <code class="docutils literal notranslate"><span class="pre">.zim</span></code> file, to Nomad Network clients using <a class="reference external" href="https://github.com/RFnexus/Retipedia">Retipedia</a>.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/RFnexus/Retipedia"><img alt="_images/retipedia.webp" class="align-center" src="_images/retipedia.webp" />
|
||||
</a>
|
||||
</section>
|
||||
<section id="git-over-reticulum">
|
||||
<h3>Git Over Reticulum<a class="headerlink" href="#git-over-reticulum" title="Link to this heading">¶</a></h3>
|
||||
<p>The included <a class="reference internal" href="git.html#git-main"><span class="std std-ref">rngit</span></a> system provides a fully distributed solution for managing, hosting, collaborating on and interacting with Git repositories over Reticulum.</p>
|
||||
<img alt="_images/rngit.webp" class="align-center" src="_images/rngit.webp" />
|
||||
</section>
|
||||
</section>
|
||||
<section id="messaging-voice">
|
||||
<h2>Messaging & Voice<a class="headerlink" href="#messaging-voice" title="Link to this heading">¶</a></h2>
|
||||
<section id="sideband">
|
||||
<h3>Sideband<a class="headerlink" href="#sideband" title="Link to this heading">¶</a></h3>
|
||||
<p>If you would rather use an LXMF client with a graphical user interface, you can take
|
||||
a look at <a class="reference external" href="https://unsigned.io/sideband">Sideband</a>, which is available for Android,
|
||||
Linux, macOS and Windows. Sideband is an advanced LXMF and LXST client, and a multi-purpose Reticulum
|
||||
utility, with features and functionality targeted at advanced users.</p>
|
||||
<a class="reference external image-reference" href="https://unsigned.io/sideband"><img alt="_images/sideband_devices.webp" class="align-center" src="_images/sideband_devices.webp" />
|
||||
<p><a class="reference external" href="https://github.com/markqvist/Sideband">Sideband</a> is a technical LXMF and LXST client, for Android, Linux, macOS and Windows. It also serves as a multi-purpose Reticulum utility, with features and functionality targeted at advanced users.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/markqvist/Sideband"><img alt="_images/sideband_devices.webp" class="align-center" src="_images/sideband_devices.webp" />
|
||||
</a>
|
||||
<p>Sideband allows you to communicate with other people or LXMF-compatible
|
||||
systems over Reticulum networks using LoRa, Packet Radio, WiFi, I2P, Encrypted QR
|
||||
Paper Messages, or anything else Reticulum supports.</p>
|
||||
<p>It also interoperates with all other LXMF clients, and provides advanced features such as voice messaging,
|
||||
real-time voice calls, file attachments, private telemetry sharing, and a full
|
||||
plugin system for expandability.</p>
|
||||
<p>Sideband allows you to communicate with other people or LXMF-compatible systems over Reticulum networks using LoRa, Packet Radio, WiFi, I2P, Encrypted QR Paper Messages, or anything else Reticulum supports.</p>
|
||||
<p>It interoperates with all other LXMF clients, and provides advanced features such as PTT and voice messaging, real-time voice calls, file attachments, private telemetry sharing, and a full plugin system for expandability.</p>
|
||||
</section>
|
||||
<section id="meshchatx">
|
||||
<h3>MeshChatX<a class="headerlink" href="#meshchatx" title="Link to this heading">¶</a></h3>
|
||||
<p>A <a class="reference external" href="https://git.quad4.io/RNS-Things/MeshChatX">Reticulum MeshChat fork from the future</a>, with the goal of providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application. This project is separate from the original <a class="reference external" href="https://github.com/liamcottle/reticulum-meshchat">Reticulum MeshChat</a> project, and is not affiliated with the original project, but is a much more up-to-date, comprehensive and well-maintained fork.</p>
|
||||
<a class="reference external image-reference" href="https://git.quad4.io/RNS-Things/MeshChatX"><img alt="_images/meshchatx.webp" class="align-center" src="_images/meshchatx.webp" />
|
||||
<section id="id6">
|
||||
<h3>MeshChatX<a class="headerlink" href="#id6" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://meshchatx.com/">MeshChatX</a> is an all-in-one Reticulum client with messaging, calling, and a NomadNet browser, providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application.</p>
|
||||
<a class="reference external image-reference" href="https://meshchatx.com/"><img alt="_images/meshchatx.webp" class="align-center" src="_images/meshchatx.webp" />
|
||||
</a>
|
||||
<p>Features include full LXST support, custom voicemail, phonebook, contact sharing, and ringtone support, multi-identity handling, modern UI/UX, offline documentation, expanded tools, page archiving, integrated maps, telemetry and improved application security.</p>
|
||||
<p>Features include full LXST support, custom voicemail, phonebook, contact sharing, ringtone support, multi-identity handling, modern UI/UX, offline documentation, expanded tools, page archiving, integrated maps, telemetry and improved application security.</p>
|
||||
</section>
|
||||
<section id="id8">
|
||||
<h3>Columba<a class="headerlink" href="#id8" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://github.com/torlando-tech/columba/">Columba</a> is a simple and familiar LXMF messaging app Android, built with a native Android interface and Material Design 3. It also includes LXST voice call functionality, and a mobile Nomad Network page browser.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/torlando-tech/columba/"><img alt="_images/columba.webp" class="align-center" src="_images/columba.webp" />
|
||||
</a>
|
||||
</section>
|
||||
<section id="ren-tui">
|
||||
<h3>Ren TUI<a class="headerlink" href="#ren-tui" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://github.com/Quad4-Software/Ren-TUI">Ren TUI</a> is an early prototype terminal LXMF client for Reticulum built with Odin on librns (Reticulum-Go). It aims for NomadNet-like messaging/browsing without urwid or ncurses.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/Quad4-Software/Ren-TUI"><img alt="_images/rentui.webp" class="align-center" src="_images/rentui.webp" />
|
||||
</a>
|
||||
</section>
|
||||
</section>
|
||||
<section id="chat">
|
||||
<h2>Chat<a class="headerlink" href="#chat" title="Link to this heading">¶</a></h2>
|
||||
<section id="reticulum-relay-chat">
|
||||
<h3>Reticulum Relay Chat<a class="headerlink" href="#reticulum-relay-chat" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://rrc.kc1awv.net/">Reticulum Relay Chat</a> is a live chat system built on top of the Reticulum Network Stack. It exists to let people talk to each other in real time over Reticulum without dragging in message databases, synchronization engines, or architectural commitments they did not ask for.</p>
|
||||
<p>The <a class="reference external" href="https://github.com/kc1awv/rrcd">rrcd</a> program provides a functional, reference RRC hub-server daemon implementation. RRC user clients include <a class="reference external" href="https://github.com/kc1awv/rrc-gui">rrc-gui</a> and <a class="reference external" href="https://github.com/kc1awv/rrc-web">rrc-web</a>.</p>
|
||||
<img alt="_images/rrc.webp" class="align-center" src="_images/rrc.webp" />
|
||||
<p>RRC is closer in spirit to IRC than to modern “everything platforms.” You connect, you join a room, you talk, and then you leave. If you were present, you saw the conversation. If you were not, the conversation did not wait for you. This is not an accident. This is the entire design.</p>
|
||||
<p>The <a class="reference external" href="https://github.com/kc1awv/rrcd">rrcd</a> program provides a functional, reference RRC hub-server daemon implementation. RRC user clients include:</p>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://github.com/markqvist/NomadNet/">Nomad Network</a></p></li>
|
||||
<li><p><a class="reference external" href="https://meshchatx.com/">MeshChatX</a></p></li>
|
||||
<li><p><a class="reference external" href="https://github.com/torlando-tech/eridanus">Eridanus</a></p></li>
|
||||
<li><p><a class="reference external" href="https://github.com/kc1awv/rrc-gui">rrc-gui</a></p></li>
|
||||
<li><p><a class="reference external" href="https://github.com/kc1awv/rrc-web">rrc-web</a></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="retibbs">
|
||||
<h3>RetiBBS<a class="headerlink" href="#retibbs" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://github.com/kc1awv/RetiBBS">RetiBBS</a> is a bulletin board system implementation for Reticulum networks.</p>
|
||||
<p><a class="reference external" href="https://github.com/kc1awv/RetiBBS">RetiBBS</a> is an old-school bulletin board system implementation for Reticulum networks.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/kc1awv/RetiBBS"><img alt="_images/retibbs.webp" class="align-center" src="_images/retibbs.webp" />
|
||||
</a>
|
||||
<p>RetiBBS allows users to communicate through message boards in a secure manner.</p>
|
||||
</section>
|
||||
<section id="rbrowser">
|
||||
<h3>RBrowser<a class="headerlink" href="#rbrowser" title="Link to this heading">¶</a></h3>
|
||||
<p>The <a class="reference external" href="https://github.com/fr33n0w/rBrowser">rBrowser</a> program is a cross-platform, standalone, web-based browser for exploring NomadNetwork Nodes over Reticulum Network. It automatically discovers NomadNet nodes through network announces and provides a user-friendly interface for browsing distributed content with Micron markup support.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/fr33n0w/rBrowser"><img alt="_images/rbrowser.webp" class="align-center" src="_images/rbrowser.webp" />
|
||||
</a>
|
||||
<p>Includes useful features like automatic listening for announce, adding nodes to favorites, browsing and rendering any kind of NomadNet links, downloading files from remote nodes, a unique local NomadNet Search Engine and more.</p>
|
||||
</section>
|
||||
<section id="voice-telephony">
|
||||
<h2>Voice & Telephony<a class="headerlink" href="#voice-telephony" title="Link to this heading">¶</a></h2>
|
||||
<p>In addition to the programs listed in this section, other LXST voice clients include:</p>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://github.com/markqvist/Sideband">Sideband</a></p></li>
|
||||
<li><p><a class="reference external" href="https://meshchatx.com/">MeshChatX</a></p></li>
|
||||
<li><p><a class="reference external" href="https://github.com/torlando-tech/columba/">Columba</a></p></li>
|
||||
</ul>
|
||||
<section id="reticulum-network-telephone">
|
||||
<h3>Reticulum Network Telephone<a class="headerlink" href="#reticulum-network-telephone" title="Link to this heading">¶</a></h3>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">rnphone</span></code> program, included as part of the <a class="reference external" href="https://github.com/markqvist/LXST">LXST</a> package is a command-line Reticulum telephone utility and daemon, that allows building physical, hardware telephones for LXST and Reticulum, as well as simply performing calls via the command line.</p>
|
||||
|
|
@ -361,34 +380,75 @@ plugin system for expandability.</p>
|
|||
</a>
|
||||
<p>It supports interfacing directly with hardware peripherals such as GPIO keypads and LCD displays, providing a modular system for building secure hardware telephones.</p>
|
||||
</section>
|
||||
<section id="partyline">
|
||||
<h3>Partyline<a class="headerlink" href="#partyline" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://github.com/RFnexus/partyline">Partyline</a> is a group voice application for Reticulum, built on <a class="reference external" href="https://github.com/markqvist/LXST">LXST</a>, inspired by <a class="reference external" href="https://github.com/mumble-voip/mumble">Mumble</a>.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/markqvist/LXST"><img alt="_images/partyline.webp" class="align-center" src="_images/partyline.webp" />
|
||||
</a>
|
||||
<p>It provides both realtime group voice over any Reticulum transport capable of >6 kilobits per second, LXST dial-in support, listen-only music/broadcasting rooms, and many other useful functions.</p>
|
||||
</section>
|
||||
<section id="lxst-phone">
|
||||
<h3>LXST Phone<a class="headerlink" href="#lxst-phone" title="Link to this heading">¶</a></h3>
|
||||
<p>The <a class="reference external" href="https://github.com/kc1awv/lxst_phone">LXST Phone</a> program is a cross-platform desktop application for performing LXST voice calls over Reticulum.</p>
|
||||
<p>The <a class="reference external" href="https://github.com/kc1awv/lxst_phone">LXST Phone</a> program is a cross-platform desktop application for performing LXST voice calls over Reticulum. It supports various advanced features such as SAS verification, peer blocking, rate limiting, encrypted call history storage and contact management.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/kc1awv/lxst_phone"><img alt="_images/lxst_phone.webp" class="align-center" src="_images/lxst_phone.webp" />
|
||||
</a>
|
||||
<p>It supports various advanced features such as SAS verification, peer blocking, rate limiting, encrypted call history storage and contact management.</p>
|
||||
</section>
|
||||
<section id="lxmfy">
|
||||
<h3>LXMFy<a class="headerlink" href="#lxmfy" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://lxmfy.quad4.io/">LXMFy</a> is a comprehensive and advanced bot creation framework for LXMF, that allows building any kind of automation or bot system running over LXMF and Reticulum. <a class="reference external" href="https://github.com/lxmfy/awesome-lxmfy-bots">Bot implementations exist</a> for Home Assistant control, LLM integrations, and various other purposes.</p>
|
||||
</section>
|
||||
<section id="lxmf-interactive-client">
|
||||
<h3>LXMF Interactive Client<a class="headerlink" href="#lxmf-interactive-client" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://github.com/fr33n0w/lxmf-cli">LXMF Interactive Client</a> is a feature-rich, terminal-based LXMF messaging client with many advanced features and an extensible plugin architecture.</p>
|
||||
<section id="system-administration">
|
||||
<h2>System Administration<a class="headerlink" href="#system-administration" title="Link to this heading">¶</a></h2>
|
||||
<section id="remote-shell">
|
||||
<h3>Remote Shell<a class="headerlink" href="#remote-shell" title="Link to this heading">¶</a></h3>
|
||||
<p>The included <a class="reference internal" href="using.html#using-rnsh"><span class="std std-ref">rnsh</span></a> program lets you establish fully interactive
|
||||
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
|
||||
remote system, and is similar to how <code class="docutils literal notranslate"><span class="pre">ssh</span></code> works. The <code class="docutils literal notranslate"><span class="pre">rnsh</span></code> program is very efficient, and
|
||||
can facilitate fully interactive shell sessions, even over extremely low-bandwidth links,
|
||||
such as LoRa or packet radio.</p>
|
||||
<p>In addition to the default, fully interactive terminal mode, for extremely limited links,
|
||||
<code class="docutils literal notranslate"><span class="pre">rnsh</span></code> offers line-interactive mode, allowing you to interact with remote systems, even
|
||||
when link throughput is counted in a few hundreds of bits per second.</p>
|
||||
</section>
|
||||
<section id="rns-filesync">
|
||||
<h3>RNS FileSync<a class="headerlink" href="#rns-filesync" title="Link to this heading">¶</a></h3>
|
||||
<p>The <a class="reference external" href="https://git.quad4.io/RNS-Things/RNS-Filesync">RNS FileSync</a> program enables automatic file synchronization between devices without requiring central servers, internet connectivity, or cloud services. It works over any network medium supported by Reticulum, including radio, LoRa, WiFi, or the internet, making it ideal for off-grid, privacy-focused, and resilient file sharing.</p>
|
||||
<p>The <a class="reference external" href="https://github.com/Quad4-Software/RNS-Filesync">RNS FileSync</a> program enables automatic file synchronization between devices without requiring central servers, internet connectivity, or cloud services. It works over any network medium supported by Reticulum, including radio, LoRa, WiFi, or the internet, making it ideal for off-grid, privacy-focused, and resilient file sharing.</p>
|
||||
</section>
|
||||
</section>
|
||||
<section id="utilities-components">
|
||||
<h2>Utilities & Components<a class="headerlink" href="#utilities-components" title="Link to this heading">¶</a></h2>
|
||||
<section id="modem73">
|
||||
<h3>Modem73<a class="headerlink" href="#modem73" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://github.com/RFnexus/modem73">MODEM73</a> and its companion <a class="reference external" href="https://github.com/RFnexus/modem73interface">Modem73Interface</a> is an open source software modem that works with any HF, VHF, or UHF radio capable of 2400 Hz of bandwidth. All you need is a sound card and audio cable for your radio.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/RFnexus/modem73"><img alt="_images/modem73.webp" class="align-center" src="_images/modem73.webp" />
|
||||
</a>
|
||||
</section>
|
||||
<section id="lxmfy">
|
||||
<h3>LXMFy<a class="headerlink" href="#lxmfy" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://lxmfy.quad4.io/">LXMFy</a> is a comprehensive and advanced bot creation framework for LXMF, that allows building any kind of automation or bot system running over LXMF and Reticulum. <a class="reference external" href="https://lxmfy.quad4.io/latest/creating-bots/">Bot implementations exist</a> for Home Assistant control, LLM integrations, and various other purposes.</p>
|
||||
</section>
|
||||
<section id="micron-parser-js">
|
||||
<h3>Micron Parser JS<a class="headerlink" href="#micron-parser-js" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://github.com/RFnexus/micron-parser-js">Micron Parser JS</a> is the JavaScript-based parser for the Micron markup language, that most web-based Nomad Network browsers use. If you want to make utilities or tools that display Micron pages, this library is essential.</p>
|
||||
</section>
|
||||
<section id="reticulated">
|
||||
<h3>Reticulated<a class="headerlink" href="#reticulated" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://github.com/RFnexus/Reticulated">Reticulated</a> is a Reticulum Network simulator that spawns live, interactive Reticulum instances controllable from a node-based Web UI.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/RFnexus/Reticulated"><img alt="_images/reticulated.webp" class="align-center" src="_images/reticulated.webp" />
|
||||
</a>
|
||||
<p>You can create networks of any topology and set bitrate, MTU, and configure path loss and propagation behavior. It provides a top-down view to a live network. You can connect any Reticulum application to the simulated network. It also has built in utilities like a basic LXMF chat function and Resource transfer included.</p>
|
||||
</section>
|
||||
<section id="rnmon">
|
||||
<h3>RNMon<a class="headerlink" href="#rnmon" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://github.com/lbatalha/rnmon">RNMon</a> is a monitoring daemon designed to monitor the status of multiple RNS applications and push the metrics to an InfluxDB instance over the influx line protocol.</p>
|
||||
</section>
|
||||
</section>
|
||||
<section id="device-flashers">
|
||||
<h2>Device Flashers<a class="headerlink" href="#device-flashers" title="Link to this heading">¶</a></h2>
|
||||
<p>In addition to the included command-line <code class="docutils literal notranslate"><span class="pre">rnodeconf</span></code> flasher, several community-provided RNode device flashers exist:</p>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://flasher.rns.moscow/">RNS Moscow</a></p></li>
|
||||
<li><p><a class="reference external" href="https://flasher.aetherlab.org/">Aetherlab</a></p></li>
|
||||
</ul>
|
||||
<p>Several programs, such as <a class="reference external" href="https://github.com/markqvist/Sideband">Sideband</a> and <a class="reference external" href="https://meshchatx.com/">MeshChatX</a> also include built-in device flashers that can be used fully offline.</p>
|
||||
</section>
|
||||
<section id="protocols">
|
||||
<h2>Protocols<a class="headerlink" href="#protocols" title="Link to this heading">¶</a></h2>
|
||||
<p>A number of standard protocols have emerged through real-world usage and testing in the Reticulum community. While you may sometimes want to use completely custom protocols and implementations when writing Reticulum-based software, using these protocols provides application developers with an easy way to implement advanced functionality quickly and effortlessly. Using them also ensures compatibility and interoperability between many different client applications, creating an open communications ecosystem where users are free to choose the applications that suit their needs, while remaining connected to everyone else.</p>
|
||||
|
|
@ -398,8 +458,8 @@ plugin system for expandability.</p>
|
|||
<p>LXMF is efficient enough that it can deliver messages over extremely low-bandwidth systems such as packet radio or LoRa. Encrypted LXMF messages can also be encoded as QR-codes or text-based URIs, allowing completely analog paper message transport.</p>
|
||||
<p>Using Propagation Nodes, LXMF also offer a way to store and forward messages to users or endpoints that are not directly reachable at the time of message emission.</p>
|
||||
</section>
|
||||
<section id="id16">
|
||||
<h3>LXST<a class="headerlink" href="#id16" title="Link to this heading">¶</a></h3>
|
||||
<section id="id30">
|
||||
<h3>LXST<a class="headerlink" href="#id30" title="Link to this heading">¶</a></h3>
|
||||
<p><a class="reference external" href="https://github.com/markqvist/lxst">LXST</a> is a simple and flexible real-time streaming format and delivery protocol that allows a wide variety of applications, while using as little bandwidth as possible. It is built on top of Reticulum and offers zero-conf stream routing, end-to-end encryption and Forward Secrecy, and can be transported over any kind of medium that Reticulum supports. It currently powers real-time voice and telephony applications over Reticulum.</p>
|
||||
</section>
|
||||
<section id="rrc">
|
||||
|
|
@ -412,11 +472,11 @@ plugin system for expandability.</p>
|
|||
<h2>Interface Modules & Connectivity Resources<a class="headerlink" href="#interface-modules-connectivity-resources" title="Link to this heading">¶</a></h2>
|
||||
<p>This section provides a list of various community-provided interface modules, guides and resources for creating Reticulum networks over special or challenging mediums.</p>
|
||||
<ul class="simple">
|
||||
<li><p>Custom interface module for running <a class="reference external" href="https://git.quad4.io/RNS-Things/RNS-over-HTTP">RNS over HTTP</a></p></li>
|
||||
<li><p><a class="reference external" href="https://github.com/RFnexus/modem73">Modem73</a> is an efficient software modem that can be used with Reticulum</p></li>
|
||||
<li><p>Custom interface module for running <a class="reference external" href="https://github.com/Quad4-Software/RNS-over-HTTP">RNS over HTTP</a></p></li>
|
||||
<li><p>Guide for running <a class="reference external" href="https://github.com/matvik22000/rns-over-icmp">Reticulum over ICMP</a> using <code class="docutils literal notranslate"><span class="pre">PipeInterface</span></code></p></li>
|
||||
<li><p>Guide for running <a class="reference external" href="https://github.com/markqvist/Reticulum/discussions/1002">Reticulum over DNS</a> with Iodine</p></li>
|
||||
<li><p>Guide for running <a class="reference external" href="https://github.com/RFnexus/reticulum-over-hf">Reticulum over HF radio</a></p></li>
|
||||
<li><p><a class="reference external" href="https://github.com/RFnexus/modem73">Modem73</a> is a KISS TNC OFDM modem frontend that can be used with Reticulum</p></li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
|
|
@ -476,28 +536,49 @@ plugin system for expandability.</p>
|
|||
<div class="toc-tree">
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Programs Using Reticulum</a><ul>
|
||||
<li><a class="reference internal" href="#programs-utilities">Programs & Utilities</a><ul>
|
||||
<li><a class="reference internal" href="#remote-shell">Remote Shell</a></li>
|
||||
<li><a class="reference internal" href="#browsing-publishing">Browsing & Publishing</a><ul>
|
||||
<li><a class="reference internal" href="#nomad-network">Nomad Network</a></li>
|
||||
<li><a class="reference internal" href="#ren-browser">Ren Browser</a></li>
|
||||
<li><a class="reference internal" href="#rns-page-node">RNS Page Node</a></li>
|
||||
<li><a class="reference internal" href="#retipedia">Retipedia</a></li>
|
||||
<li><a class="reference internal" href="#git-over-reticulum">Git Over Reticulum</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#messaging-voice">Messaging & Voice</a><ul>
|
||||
<li><a class="reference internal" href="#sideband">Sideband</a></li>
|
||||
<li><a class="reference internal" href="#meshchatx">MeshChatX</a></li>
|
||||
<li><a class="reference internal" href="#id6">MeshChatX</a></li>
|
||||
<li><a class="reference internal" href="#id8">Columba</a></li>
|
||||
<li><a class="reference internal" href="#ren-tui">Ren TUI</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#chat">Chat</a><ul>
|
||||
<li><a class="reference internal" href="#reticulum-relay-chat">Reticulum Relay Chat</a></li>
|
||||
<li><a class="reference internal" href="#retibbs">RetiBBS</a></li>
|
||||
<li><a class="reference internal" href="#rbrowser">RBrowser</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#voice-telephony">Voice & Telephony</a><ul>
|
||||
<li><a class="reference internal" href="#reticulum-network-telephone">Reticulum Network Telephone</a></li>
|
||||
<li><a class="reference internal" href="#partyline">Partyline</a></li>
|
||||
<li><a class="reference internal" href="#lxst-phone">LXST Phone</a></li>
|
||||
<li><a class="reference internal" href="#lxmfy">LXMFy</a></li>
|
||||
<li><a class="reference internal" href="#lxmf-interactive-client">LXMF Interactive Client</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#system-administration">System Administration</a><ul>
|
||||
<li><a class="reference internal" href="#remote-shell">Remote Shell</a></li>
|
||||
<li><a class="reference internal" href="#rns-filesync">RNS FileSync</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#utilities-components">Utilities & Components</a><ul>
|
||||
<li><a class="reference internal" href="#modem73">Modem73</a></li>
|
||||
<li><a class="reference internal" href="#lxmfy">LXMFy</a></li>
|
||||
<li><a class="reference internal" href="#micron-parser-js">Micron Parser JS</a></li>
|
||||
<li><a class="reference internal" href="#reticulated">Reticulated</a></li>
|
||||
<li><a class="reference internal" href="#rnmon">RNMon</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#device-flashers">Device Flashers</a></li>
|
||||
<li><a class="reference internal" href="#protocols">Protocols</a><ul>
|
||||
<li><a class="reference internal" href="#lxmf">LXMF</a></li>
|
||||
<li><a class="reference internal" href="#id16">LXST</a></li>
|
||||
<li><a class="reference internal" href="#id30">LXST</a></li>
|
||||
<li><a class="reference internal" href="#rrc">RRC</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
@ -513,7 +594,7 @@ plugin system for expandability.</p>
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Support Reticulum - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Support Reticulum - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="license.html">Reticulum License</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -285,8 +286,6 @@ Ko-Fi:
|
|||
https://ko-fi.com/markqvist
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Are certain features in the development roadmap are important to you or your
|
||||
organisation? Make them a reality quickly by sponsoring their implementation.</p>
|
||||
</section>
|
||||
<section id="provide-feedback">
|
||||
<h2>Provide Feedback<a class="headerlink" href="#provide-feedback" title="Link to this heading">¶</a></h2>
|
||||
|
|
@ -383,7 +382,7 @@ circumstances, so we rely on old-fashioned human feedback.</p>
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -1119,7 +1120,7 @@ those risks are acceptable to you.</p>
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -504,8 +505,7 @@ options:
|
|||
</section>
|
||||
<section id="the-rnstatus-utility">
|
||||
<h3>The rnstatus Utility<a class="headerlink" href="#the-rnstatus-utility" title="Link to this heading">¶</a></h3>
|
||||
<p>Using the <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code> utility, you can view the status of configured Reticulum
|
||||
interfaces, similar to the <code class="docutils literal notranslate"><span class="pre">ifconfig</span></code> program.</p>
|
||||
<p>Using the <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code> utility, you can view the status of and control configured Reticulum interfaces, similar to the <code class="docutils literal notranslate"><span class="pre">ifconfig</span></code> program.</p>
|
||||
<p><strong>Usage Examples</strong></p>
|
||||
<p>Run <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code>:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnstatus
|
||||
|
|
@ -571,6 +571,9 @@ options:
|
|||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
--attach name Attach interface by name
|
||||
--detach name Detach interface by name
|
||||
--reload name Reload interface by name
|
||||
-a, --all show all interfaces
|
||||
-A, --announce-stats show announce stats
|
||||
-P, --pr-stats show path request stats
|
||||
|
|
@ -588,6 +591,8 @@ options:
|
|||
-w seconds timeout before giving up on remote queries
|
||||
-d, --discovered list discovered interfaces
|
||||
-D show details and config entries for discovered interfaces
|
||||
--show-stale show stale discovery entries
|
||||
--show-unknown show discovery entries without version info
|
||||
-m, --monitor continuously monitor status
|
||||
-I, --monitor-interval seconds
|
||||
refresh interval for monitor mode (default: 1)
|
||||
|
|
@ -872,7 +877,7 @@ options:
|
|||
<p>The <code class="docutils literal notranslate"><span class="pre">rnx</span></code> utility is a basic remote command execution program. It allows you to
|
||||
execute commands on remote systems over Reticulum, and to view returned command
|
||||
output. For a fully interactive remote shell solution, be sure to also take a look
|
||||
at the <a class="reference external" href="https://github.com/acehoss/rnsh">rnsh</a> program.</p>
|
||||
at the <a class="reference internal" href="#using-rnsh"><span class="std std-ref">rnsh</span></a> program.</p>
|
||||
<p><strong>Usage Examples</strong></p>
|
||||
<p>Run rnx on the listening system, specifying which identities are allowed to execute commands:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rnx --listen -a 941bed5e228775e5a8079fc38b1ccf3f -a 1b03013c25f1c2ca068a4f080b844a10
|
||||
|
|
@ -927,7 +932,7 @@ optional arguments:
|
|||
</div>
|
||||
</section>
|
||||
<section id="the-rnsh-utility">
|
||||
<h3>The rnsh Utility<a class="headerlink" href="#the-rnsh-utility" title="Link to this heading">¶</a></h3>
|
||||
<span id="using-rnsh"></span><h3>The rnsh Utility<a class="headerlink" href="#the-rnsh-utility" title="Link to this heading">¶</a></h3>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">rnsh</span></code> utility provides a fully interactive remote shell over Reticulum.
|
||||
It allows you to establish encrypted, authenticated shell sessions on remote
|
||||
systems, complete with terminal emulation, pipe support, and window resizing.</p>
|
||||
|
|
@ -1311,6 +1316,7 @@ network_identity = ~/.reticulum/storage/identities/my_network
|
|||
<ul class="simple">
|
||||
<li><p>The <code class="docutils literal notranslate"><span class="pre">autoconnect_interface_mode</span></code> options specifies which mode discovered interfaces should be created with when auto-connecting.</p></li>
|
||||
<li><p>The <code class="docutils literal notranslate"><span class="pre">autoconnect_announces_to_internal</span></code> option allows you to specify that auto-connected interfaces should propagate announces to <code class="docutils literal notranslate"><span class="pre">internal</span></code> mode interfaces, even if the auto-connected interface’s mode would normally not allow for this.</p></li>
|
||||
<li><p>The <code class="docutils literal notranslate"><span class="pre">autoconnect_unverified_implementations</span></code> option will allow the interface discovery system to auto-connect to <em>any</em> announced interface, even those coming from unverified, experimental or potentially broken implementations. Be very careful with this option.</p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="remote-management">
|
||||
|
|
@ -1644,7 +1650,7 @@ systemctl --user enable rnsd.service
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>What is Reticulum? - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -505,7 +506,7 @@ network, and vice versa.</p>
|
|||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Zen of Reticulum - Reticulum Network Stack 1.5.1 documentation</title>
|
||||
<title>Zen of Reticulum - Reticulum Network Stack 1.5.5 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.5.1 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.5.5 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.5.1 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.5.5 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">
|
||||
|
|
@ -227,6 +227,7 @@
|
|||
<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>
|
||||
<li class="toctree-l1"><a class="reference internal" href="brandolinis.html">Brandolini’s Reference</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
|
|
@ -677,7 +678,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=44dfd65d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=b8aeccfa"></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>
|
||||
|
|
|
|||
1386
docs/markdown/brandolinis.md
Normal file
|
|
@ -639,6 +639,7 @@ The following template files are supported:
|
|||
- `commit.mu` - Individual commit detail page
|
||||
- `refs.mu` - Branches and tags listing
|
||||
- `stats.mu` - Statistics page
|
||||
- `no_ident.mu` - Displayed to non-identified initiators if blocked
|
||||
|
||||
Templates can include the following variables:
|
||||
|
||||
|
|
|
|||
|
|
@ -75,26 +75,37 @@ to participate in the development of Reticulum itself.
|
|||
* [The Work Is Finished](zen.md#the-work-is-finished)
|
||||
* [Open Sky](zen.md#open-sky)
|
||||
* [Programs Using Reticulum](software.md)
|
||||
* [Programs & Utilities](software.md#programs-utilities)
|
||||
* [Remote Shell](software.md#remote-shell)
|
||||
* [Browsing & Publishing](software.md#browsing-publishing)
|
||||
* [Nomad Network](software.md#nomad-network)
|
||||
* [Ren Browser](software.md#ren-browser)
|
||||
* [RNS Page Node](software.md#rns-page-node)
|
||||
* [Retipedia](software.md#retipedia)
|
||||
* [Git Over Reticulum](software.md#git-over-reticulum)
|
||||
* [Messaging & Voice](software.md#messaging-voice)
|
||||
* [Sideband](software.md#sideband)
|
||||
* [MeshChatX](software.md#meshchatx)
|
||||
* [MeshChatX](software.md#id6)
|
||||
* [Columba](software.md#id8)
|
||||
* [Ren TUI](software.md#ren-tui)
|
||||
* [Chat](software.md#chat)
|
||||
* [Reticulum Relay Chat](software.md#reticulum-relay-chat)
|
||||
* [RetiBBS](software.md#retibbs)
|
||||
* [RBrowser](software.md#rbrowser)
|
||||
* [Voice & Telephony](software.md#voice-telephony)
|
||||
* [Reticulum Network Telephone](software.md#reticulum-network-telephone)
|
||||
* [Partyline](software.md#partyline)
|
||||
* [LXST Phone](software.md#lxst-phone)
|
||||
* [LXMFy](software.md#lxmfy)
|
||||
* [LXMF Interactive Client](software.md#lxmf-interactive-client)
|
||||
* [System Administration](software.md#system-administration)
|
||||
* [Remote Shell](software.md#remote-shell)
|
||||
* [RNS FileSync](software.md#rns-filesync)
|
||||
* [Utilities & Components](software.md#utilities-components)
|
||||
* [Modem73](software.md#modem73)
|
||||
* [LXMFy](software.md#lxmfy)
|
||||
* [Micron Parser JS](software.md#micron-parser-js)
|
||||
* [Reticulated](software.md#reticulated)
|
||||
* [RNMon](software.md#rnmon)
|
||||
* [Device Flashers](software.md#device-flashers)
|
||||
* [Protocols](software.md#protocols)
|
||||
* [LXMF](software.md#lxmf)
|
||||
* [LXST](software.md#id16)
|
||||
* [LXST](software.md#id30)
|
||||
* [RRC](software.md#rrc)
|
||||
* [Interface Modules & Connectivity Resources](software.md#interface-modules-connectivity-resources)
|
||||
* [Using Reticulum on Your System](using.md)
|
||||
|
|
@ -270,6 +281,75 @@ to participate in the development of Reticulum itself.
|
|||
* [Filetransfer](examples.md#filetransfer)
|
||||
* [Custom Interfaces](examples.md#custom-interfaces)
|
||||
* [Reticulum License](license.md)
|
||||
* [Brandolini’s Reference](brandolinis.md)
|
||||
* [What The Reticulum License Is](brandolinis.md#what-the-reticulum-license-is)
|
||||
* [Why This License Exists](brandolinis.md#why-this-license-exists)
|
||||
* [On Freedom](brandolinis.md#on-freedom)
|
||||
* [The Prediction, And What Happened](brandolinis.md#the-prediction-and-what-happened)
|
||||
* [What This Means For Other Implementations](brandolinis.md#what-this-means-for-other-implementations)
|
||||
* [Copyright & The Two Legal Layers](brandolinis.md#copyright-the-two-legal-layers)
|
||||
* [The “Clean-Room” Delusion](brandolinis.md#the-clean-room-delusion)
|
||||
* [Open Source Means Open Source](brandolinis.md#open-source-means-open-source)
|
||||
* [What The Argument Is Actually Doing](brandolinis.md#what-the-argument-is-actually-doing)
|
||||
* [Who Is Restricted By Which License?](brandolinis.md#who-is-restricted-by-which-license)
|
||||
* [The Consequences Of The “Not Open Source” Claim](brandolinis.md#the-consequences-of-the-not-open-source-claim)
|
||||
* [The Distribution Argument](brandolinis.md#the-distribution-argument)
|
||||
* [On The Institutions, In Fairness](brandolinis.md#on-the-institutions-in-fairness)
|
||||
* [What “Open” Means Positively](brandolinis.md#what-open-means-positively)
|
||||
* [Assistance Versus Machine-Substitution](brandolinis.md#assistance-versus-machine-substitution)
|
||||
* [False Dichotomy](brandolinis.md#false-dichotomy)
|
||||
* [The Line](brandolinis.md#the-line)
|
||||
* [What assistance looks like](brandolinis.md#what-assistance-looks-like)
|
||||
* [What substitution looks like](brandolinis.md#what-substitution-looks-like)
|
||||
* [Infrastructure Matters](brandolinis.md#infrastructure-matters)
|
||||
* [The Fluency Trap](brandolinis.md#the-fluency-trap)
|
||||
* [Disclosed Assistance Or Laundered Generation](brandolinis.md#disclosed-assistance-or-laundered-generation)
|
||||
* [The License In This Context](brandolinis.md#the-license-in-this-context)
|
||||
* [The General Principle](brandolinis.md#the-general-principle)
|
||||
* [Void Grants: Legal Foundations of Machine-Generated Code](brandolinis.md#void-grants-legal-foundations-of-machine-generated-code)
|
||||
* [I. The Foundational Statute: Translation Is Copying](brandolinis.md#i-the-foundational-statute-translation-is-copying)
|
||||
* [II. Ideas Are Free, Expressions Protected](brandolinis.md#ii-ideas-are-free-expressions-protected)
|
||||
* [III. Machine Output Has No Author](brandolinis.md#iii-machine-output-has-no-author)
|
||||
* [IV. License Conditions Bound the Grant](brandolinis.md#iv-license-conditions-bound-the-grant)
|
||||
* [V. The Void Grant](brandolinis.md#v-the-void-grant)
|
||||
* [VI. Fair Use Does Not Rescue This Pattern](brandolinis.md#vi-fair-use-does-not-rescue-this-pattern)
|
||||
* [Conclusion](brandolinis.md#conclusion)
|
||||
* [Sources and Authorities](brandolinis.md#sources-and-authorities)
|
||||
* [Evaluating a Reticulum Implementation](brandolinis.md#evaluating-a-reticulum-implementation)
|
||||
* [The Intention Of This Guide](brandolinis.md#the-intention-of-this-guide)
|
||||
* [Two Questions That Resolve Most Cases](brandolinis.md#two-questions-that-resolve-most-cases)
|
||||
* [Other Signals To Look For](brandolinis.md#other-signals-to-look-for)
|
||||
* [The Conformal Surface](brandolinis.md#the-conformal-surface)
|
||||
* [Possible Verdicts](brandolinis.md#possible-verdicts)
|
||||
* [These Standards Are Reasonable](brandolinis.md#these-standards-are-reasonable)
|
||||
* [The Movement Of A Dozen](brandolinis.md#the-movement-of-a-dozen)
|
||||
* [Presentation, Elongated](brandolinis.md#presentation-elongated)
|
||||
* [Headcount](brandolinis.md#headcount)
|
||||
* [Recycled Wine, Fermenting](brandolinis.md#recycled-wine-fermenting)
|
||||
* [The Method of Madness](brandolinis.md#the-method-of-madness)
|
||||
* [A Persistent, But Fragile Triad](brandolinis.md#a-persistent-but-fragile-triad)
|
||||
* [What The Pattern Is Not](brandolinis.md#what-the-pattern-is-not)
|
||||
* [Intent & Consequence](brandolinis.md#intent-consequence)
|
||||
* [Recognition & Response](brandolinis.md#recognition-response)
|
||||
* [Network Health & Coexistence](brandolinis.md#network-health-coexistence)
|
||||
* [Substance Of The Networks](brandolinis.md#substance-of-the-networks)
|
||||
* [Health Is A Property Of The Whole](brandolinis.md#health-is-a-property-of-the-whole)
|
||||
* [A History Of What Works](brandolinis.md#a-history-of-what-works)
|
||||
* [The Role Of The Individual](brandolinis.md#the-role-of-the-individual)
|
||||
* [The Place Of New Implementations](brandolinis.md#the-place-of-new-implementations)
|
||||
* [Expanding The Commons](brandolinis.md#expanding-the-commons)
|
||||
* [Defence Of The Commons](brandolinis.md#defence-of-the-commons)
|
||||
* [Addendum A: The Prns Case](brandolinis.md#addendum-a-the-prns-case)
|
||||
* [Purpose & Scope](brandolinis.md#purpose-scope)
|
||||
* [The Project In Brief](brandolinis.md#the-project-in-brief)
|
||||
* [Question 1: Is this a clean implementation, or was it directly generated from the reference?](brandolinis.md#question-1-is-this-a-clean-implementation-or-was-it-directly-generated-from-the-reference)
|
||||
* [Question 2: Who, or what, is the agent of the work?](brandolinis.md#question-2-who-or-what-is-the-agent-of-the-work)
|
||||
* [The performance claims](brandolinis.md#the-performance-claims)
|
||||
* [Overall Evaluation](brandolinis.md#overall-evaluation)
|
||||
* [Licensing Analysis](brandolinis.md#licensing-analysis)
|
||||
* [Consequences](brandolinis.md#consequences)
|
||||
* [Verdict](brandolinis.md#verdict)
|
||||
* [Dating](brandolinis.md#dating)
|
||||
|
||||
* [API Reference](reference.md)
|
||||
* [`Reticulum`](reference.md#RNS.Reticulum)
|
||||
|
|
|
|||
|
|
@ -860,7 +860,7 @@ statistics, you will be able to retrieve stats such as *RSSI*, *SNR* and physica
|
|||
#### `get_expected_rate()`
|
||||
|
||||
* **Returns:**
|
||||
The packet expected in-flight data rate of an established link.
|
||||
The expected in-flight data rate of an established link.
|
||||
|
||||
#### `get_mode()`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,100 +1,99 @@
|
|||
# Programs Using Reticulum
|
||||
|
||||
This chapter provides a non-exhaustive list of notable programs, systems and application-layer
|
||||
protocols that have been built using Reticulum.
|
||||
This chapter provides a non-exhaustive list of notable programs, systems and application-layer protocols that have been built using Reticulum.
|
||||
|
||||
These programs will let you get a feel for how Reticulum works. Most of them have been designed
|
||||
to run well even over slow networks based on LoRa or packet radio, but all can also be used over fast
|
||||
links, such as local WiFi, wired Ethernet, the Internet, or any combination.
|
||||
Many different applications using Reticulum already exist, serving a wide variety of purposes from day-to-day communication and information sharing to systems administration and tackling advanced networking and communications challenges.
|
||||
|
||||
As such, it is easy to get started experimenting, without having to set up any radio
|
||||
transceivers or infrastructure just to try it out. Launching the programs on separate
|
||||
devices connected to the same WiFi network is enough to get started, and physical
|
||||
radio interfaces can then be added later.
|
||||
These programs will let you get a feel for how Reticulum works. Most of them have been designed to run well even over slow networks based on LoRa or packet radio, but all can also be used over fast links, such as local WiFi, wired Ethernet, the Internet, or any combination.
|
||||
|
||||
## Programs & Utilities
|
||||
As such, it is easy to get started experimenting, without having to set up any radio transceivers or infrastructure just to try it out. Launching the programs on separate devices connected to the same WiFi network is enough to get started, and physical radio interfaces can then be added later.
|
||||
|
||||
Many different applications using Reticulum already exist, serving a wide variety of purposes
|
||||
from day-to-day communication and information sharing to systems administration and tackling
|
||||
advanced networking and communications challenges.
|
||||
Development of Reticulum-based applications and systems is ongoing, so consider this list a non-exhaustive starting point of *some* of the options available. With a bit of searching, primarily over Reticulum itself, you will find many more interesting things.
|
||||
|
||||
Development of Reticulum-based applications and systems is ongoing, so consider this list
|
||||
a non-exhaustive starting point of *some* of the options available. With a bit of searching,
|
||||
primarily over Reticulum itself, you will find many more interesting things.
|
||||
## Browsing & Publishing
|
||||
|
||||
### Remote Shell
|
||||
In addition to the programs listed in this section, other Nomad Network clients include:
|
||||
|
||||
The [rnsh](https://github.com/acehoss/rnsh) program lets you establish fully interactive
|
||||
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
|
||||
remote system, and is similar to how `ssh` works. The `rnsh` program is very efficient, and
|
||||
can facilitate fully interactive shell sessions, even over extremely low-bandwidth links,
|
||||
such as LoRa or packet radio.
|
||||
|
||||
In addition to the default, fully interactive terminal mode,
|
||||
for extremely limited links, `rnsh` offers line-interactive mode, allowing you to interact
|
||||
with remote systems, even when link throughput is counted in a few hundreds of bits per second.
|
||||
- [MeshChatX](https://meshchatx.com/)
|
||||
- [Columba](https://github.com/torlando-tech/columba/)
|
||||
|
||||
### Nomad Network
|
||||
|
||||
The terminal-based program [Nomad Network](https://github.com/markqvist/nomadnet)
|
||||
provides a complete encrypted communications suite built with Reticulum. It features
|
||||
encrypted messaging (both direct and delayed-delivery for offline users), file sharing,
|
||||
and has a built-in text-browser and page server with support for dynamically rendered pages,
|
||||
user authentication and more.
|
||||
The terminal-based program [Nomad Network](https://github.com/markqvist/nomadnet) provides a complete encrypted communications suite built with Reticulum.
|
||||
|
||||
[](https://github.com/markqvist/nomadnet)
|
||||
It features encrypted LXMF messaging (both direct and delayed-delivery for offline users), file sharing, and has a built-in text-browser and page server with support for dynamically rendered pages, user authentication and more. It also includes an RRC client and a suite of network utilities.
|
||||
|
||||
[Nomad Network](https://github.com/markqvist/nomadnet) is a user-facing client
|
||||
for the messaging and information-sharing protocol LXMF.
|
||||
### Ren Browser
|
||||
|
||||
[Ren Browser](https://github.com/Quad4-Software/Ren-Browser) is a modern, standalone browser for Nomad Network using the [Reticulum-Go](https://github.com/Quad4-Software/Reticulum-Go) implementation. The project is actively being developed, and should be considered alpha level software, but much of the functionality is already working. If you want to help expanding the ecosystem, this is an excellent project to take a look at.
|
||||
|
||||
Ren Browser is great example of an application aiming for true, seamless interoperability with the existing software ecosystem. It can operate fully standalone using it’s own embedded [Go implementation](https://github.com/Quad4-Software/Reticulum-Go) of Reticulum, or seamlessly utilise a shared RNS instance already running on the system.
|
||||
|
||||
### RNS Page Node
|
||||
|
||||
[RNS Page Node](https://git.quad4.io/RNS-Things/rns-page-node) is a simple way to serve pages and files to any other Nomad Network compatible client. Drop-in replacement for NomadNet nodes that primarily serve pages and files.
|
||||
[RNS Page Node](https://github.com/Quad4-Software/rns-page-node) is a simple way to serve pages and files to any other Nomad Network compatible client. Drop-in replacement for NomadNet nodes that primarily serve pages and files.
|
||||
|
||||
### Retipedia
|
||||
|
||||
You can host the entirity of Wikipedia (or any `.zim`) file to other Nomad Network clients using [Retipedia](https://github.com/RFnexus/Retipedia).
|
||||
You can host the entirity of Wikipedia, StackExchange or any other `.zim` file, to Nomad Network clients using [Retipedia](https://github.com/RFnexus/Retipedia).
|
||||
|
||||
### Git Over Reticulum
|
||||
|
||||
The included [rngit](git.md#git-main) system provides a fully distributed solution for managing, hosting, collaborating on and interacting with Git repositories over Reticulum.
|
||||
|
||||
## Messaging & Voice
|
||||
|
||||
### Sideband
|
||||
|
||||
If you would rather use an LXMF client with a graphical user interface, you can take
|
||||
a look at [Sideband](https://unsigned.io/sideband), which is available for Android,
|
||||
Linux, macOS and Windows. Sideband is an advanced LXMF and LXST client, and a multi-purpose Reticulum
|
||||
utility, with features and functionality targeted at advanced users.
|
||||
[Sideband](https://github.com/markqvist/Sideband) is a technical LXMF and LXST client, for Android, Linux, macOS and Windows. It also serves as a multi-purpose Reticulum utility, with features and functionality targeted at advanced users.
|
||||
|
||||
Sideband allows you to communicate with other people or LXMF-compatible
|
||||
systems over Reticulum networks using LoRa, Packet Radio, WiFi, I2P, Encrypted QR
|
||||
Paper Messages, or anything else Reticulum supports.
|
||||
Sideband allows you to communicate with other people or LXMF-compatible systems over Reticulum networks using LoRa, Packet Radio, WiFi, I2P, Encrypted QR Paper Messages, or anything else Reticulum supports.
|
||||
|
||||
It also interoperates with all other LXMF clients, and provides advanced features such as voice messaging,
|
||||
real-time voice calls, file attachments, private telemetry sharing, and a full
|
||||
plugin system for expandability.
|
||||
It interoperates with all other LXMF clients, and provides advanced features such as PTT and voice messaging, real-time voice calls, file attachments, private telemetry sharing, and a full plugin system for expandability.
|
||||
|
||||
### MeshChatX
|
||||
|
||||
A [Reticulum MeshChat fork from the future](https://git.quad4.io/RNS-Things/MeshChatX), with the goal of providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application. This project is separate from the original [Reticulum MeshChat](https://github.com/liamcottle/reticulum-meshchat) project, and is not affiliated with the original project, but is a much more up-to-date, comprehensive and well-maintained fork.
|
||||
[MeshChatX](https://meshchatx.com/) is an all-in-one Reticulum client with messaging, calling, and a NomadNet browser, providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application.
|
||||
|
||||
Features include full LXST support, custom voicemail, phonebook, contact sharing, and ringtone support, multi-identity handling, modern UI/UX, offline documentation, expanded tools, page archiving, integrated maps, telemetry and improved application security.
|
||||
Features include full LXST support, custom voicemail, phonebook, contact sharing, ringtone support, multi-identity handling, modern UI/UX, offline documentation, expanded tools, page archiving, integrated maps, telemetry and improved application security.
|
||||
|
||||
### Columba
|
||||
|
||||
[Columba](https://github.com/torlando-tech/columba/) is a simple and familiar LXMF messaging app Android, built with a native Android interface and Material Design 3. It also includes LXST voice call functionality, and a mobile Nomad Network page browser.
|
||||
|
||||
### Ren TUI
|
||||
|
||||
[Ren TUI](https://github.com/Quad4-Software/Ren-TUI) is an early prototype terminal LXMF client for Reticulum built with Odin on librns (Reticulum-Go). It aims for NomadNet-like messaging/browsing without urwid or ncurses.
|
||||
|
||||
## Chat
|
||||
|
||||
### Reticulum Relay Chat
|
||||
|
||||
[Reticulum Relay Chat](https://rrc.kc1awv.net/) is a live chat system built on top of the Reticulum Network Stack. It exists to let people talk to each other in real time over Reticulum without dragging in message databases, synchronization engines, or architectural commitments they did not ask for.
|
||||
|
||||
The [rrcd](https://github.com/kc1awv/rrcd) program provides a functional, reference RRC hub-server daemon implementation. RRC user clients include [rrc-gui](https://github.com/kc1awv/rrc-gui) and [rrc-web](https://github.com/kc1awv/rrc-web).
|
||||
|
||||
RRC is closer in spirit to IRC than to modern “everything platforms.” You connect, you join a room, you talk, and then you leave. If you were present, you saw the conversation. If you were not, the conversation did not wait for you. This is not an accident. This is the entire design.
|
||||
|
||||
The [rrcd](https://github.com/kc1awv/rrcd) program provides a functional, reference RRC hub-server daemon implementation. RRC user clients include:
|
||||
|
||||
- [Nomad Network](https://github.com/markqvist/NomadNet/)
|
||||
- [MeshChatX](https://meshchatx.com/)
|
||||
- [Eridanus](https://github.com/torlando-tech/eridanus)
|
||||
- [rrc-gui](https://github.com/kc1awv/rrc-gui)
|
||||
- [rrc-web](https://github.com/kc1awv/rrc-web)
|
||||
|
||||
### RetiBBS
|
||||
|
||||
[RetiBBS](https://github.com/kc1awv/RetiBBS) is a bulletin board system implementation for Reticulum networks.
|
||||
[RetiBBS](https://github.com/kc1awv/RetiBBS) is an old-school bulletin board system implementation for Reticulum networks.
|
||||
|
||||
RetiBBS allows users to communicate through message boards in a secure manner.
|
||||
|
||||
### RBrowser
|
||||
## Voice & Telephony
|
||||
|
||||
The [rBrowser](https://github.com/fr33n0w/rBrowser) program is a cross-platform, standalone, web-based browser for exploring NomadNetwork Nodes over Reticulum Network. It automatically discovers NomadNet nodes through network announces and provides a user-friendly interface for browsing distributed content with Micron markup support.
|
||||
In addition to the programs listed in this section, other LXST voice clients include:
|
||||
|
||||
Includes useful features like automatic listening for announce, adding nodes to favorites, browsing and rendering any kind of NomadNet links, downloading files from remote nodes, a unique local NomadNet Search Engine and more.
|
||||
- [Sideband](https://github.com/markqvist/Sideband)
|
||||
- [MeshChatX](https://meshchatx.com/)
|
||||
- [Columba](https://github.com/torlando-tech/columba/)
|
||||
|
||||
### Reticulum Network Telephone
|
||||
|
||||
|
|
@ -102,32 +101,67 @@ The `rnphone` program, included as part of the [LXST](https://github.com/markqvi
|
|||
|
||||
It supports interfacing directly with hardware peripherals such as GPIO keypads and LCD displays, providing a modular system for building secure hardware telephones.
|
||||
|
||||
### Partyline
|
||||
|
||||
[Partyline](https://github.com/RFnexus/partyline) is a group voice application for Reticulum, built on [LXST](https://github.com/markqvist/LXST), inspired by [Mumble](https://github.com/mumble-voip/mumble).
|
||||
|
||||
It provides both realtime group voice over any Reticulum transport capable of >6 kilobits per second, LXST dial-in support, listen-only music/broadcasting rooms, and many other useful functions.
|
||||
|
||||
### LXST Phone
|
||||
|
||||
The [LXST Phone](https://github.com/kc1awv/lxst_phone) program is a cross-platform desktop application for performing LXST voice calls over Reticulum.
|
||||
The [LXST Phone](https://github.com/kc1awv/lxst_phone) program is a cross-platform desktop application for performing LXST voice calls over Reticulum. It supports various advanced features such as SAS verification, peer blocking, rate limiting, encrypted call history storage and contact management.
|
||||
|
||||
It supports various advanced features such as SAS verification, peer blocking, rate limiting, encrypted call history storage and contact management.
|
||||
## System Administration
|
||||
|
||||
### LXMFy
|
||||
### Remote Shell
|
||||
|
||||
[LXMFy](https://lxmfy.quad4.io/) is a comprehensive and advanced bot creation framework for LXMF, that allows building any kind of automation or bot system running over LXMF and Reticulum. [Bot implementations exist](https://github.com/lxmfy/awesome-lxmfy-bots) for Home Assistant control, LLM integrations, and various other purposes.
|
||||
The included [rnsh](using.md#using-rnsh) program lets you establish fully interactive
|
||||
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
|
||||
remote system, and is similar to how `ssh` works. The `rnsh` program is very efficient, and
|
||||
can facilitate fully interactive shell sessions, even over extremely low-bandwidth links,
|
||||
such as LoRa or packet radio.
|
||||
|
||||
### LXMF Interactive Client
|
||||
|
||||
[LXMF Interactive Client](https://github.com/fr33n0w/lxmf-cli) is a feature-rich, terminal-based LXMF messaging client with many advanced features and an extensible plugin architecture.
|
||||
In addition to the default, fully interactive terminal mode, for extremely limited links,
|
||||
`rnsh` offers line-interactive mode, allowing you to interact with remote systems, even
|
||||
when link throughput is counted in a few hundreds of bits per second.
|
||||
|
||||
### RNS FileSync
|
||||
|
||||
The [RNS FileSync](https://git.quad4.io/RNS-Things/RNS-Filesync) program enables automatic file synchronization between devices without requiring central servers, internet connectivity, or cloud services. It works over any network medium supported by Reticulum, including radio, LoRa, WiFi, or the internet, making it ideal for off-grid, privacy-focused, and resilient file sharing.
|
||||
The [RNS FileSync](https://github.com/Quad4-Software/RNS-Filesync) program enables automatic file synchronization between devices without requiring central servers, internet connectivity, or cloud services. It works over any network medium supported by Reticulum, including radio, LoRa, WiFi, or the internet, making it ideal for off-grid, privacy-focused, and resilient file sharing.
|
||||
|
||||
## Utilities & Components
|
||||
|
||||
### Modem73
|
||||
|
||||
[MODEM73](https://github.com/RFnexus/modem73) and its companion [Modem73Interface](https://github.com/RFnexus/modem73interface) is an open source software modem that works with any HF, VHF, or UHF radio capable of 2400 Hz of bandwidth. All you need is a sound card and audio cable for your radio.
|
||||
|
||||
### LXMFy
|
||||
|
||||
[LXMFy](https://lxmfy.quad4.io/) is a comprehensive and advanced bot creation framework for LXMF, that allows building any kind of automation or bot system running over LXMF and Reticulum. [Bot implementations exist](https://lxmfy.quad4.io/latest/creating-bots/) for Home Assistant control, LLM integrations, and various other purposes.
|
||||
|
||||
### Micron Parser JS
|
||||
|
||||
[Micron Parser JS](https://github.com/RFnexus/micron-parser-js) is the JavaScript-based parser for the Micron markup language, that most web-based Nomad Network browsers use. If you want to make utilities or tools that display Micron pages, this library is essential.
|
||||
|
||||
### Reticulated
|
||||
|
||||
[Reticulated](https://github.com/RFnexus/Reticulated) is a Reticulum Network simulator that spawns live, interactive Reticulum instances controllable from a node-based Web UI.
|
||||
|
||||
You can create networks of any topology and set bitrate, MTU, and configure path loss and propagation behavior. It provides a top-down view to a live network. You can connect any Reticulum application to the simulated network. It also has built in utilities like a basic LXMF chat function and Resource transfer included.
|
||||
|
||||
### RNMon
|
||||
|
||||
[RNMon](https://github.com/lbatalha/rnmon) is a monitoring daemon designed to monitor the status of multiple RNS applications and push the metrics to an InfluxDB instance over the influx line protocol.
|
||||
|
||||
## Device Flashers
|
||||
|
||||
In addition to the included command-line `rnodeconf` flasher, several community-provided RNode device flashers exist:
|
||||
|
||||
- [RNS Moscow](https://flasher.rns.moscow/)
|
||||
- [Aetherlab](https://flasher.aetherlab.org/)
|
||||
|
||||
Several programs, such as [Sideband](https://github.com/markqvist/Sideband) and [MeshChatX](https://meshchatx.com/) also include built-in device flashers that can be used fully offline.
|
||||
|
||||
## Protocols
|
||||
|
||||
A number of standard protocols have emerged through real-world usage and testing in the Reticulum community. While you may sometimes want to use completely custom protocols and implementations when writing Reticulum-based software, using these protocols provides application developers with an easy way to implement advanced functionality quickly and effortlessly. Using them also ensures compatibility and interoperability between many different client applications, creating an open communications ecosystem where users are free to choose the applications that suit their needs, while remaining connected to everyone else.
|
||||
|
|
@ -154,8 +188,8 @@ RRC is intentionally simple. It does not pretend to be email, a mailbox, or a di
|
|||
|
||||
This section provides a list of various community-provided interface modules, guides and resources for creating Reticulum networks over special or challenging mediums.
|
||||
|
||||
* Custom interface module for running [RNS over HTTP](https://git.quad4.io/RNS-Things/RNS-over-HTTP)
|
||||
* [Modem73](https://github.com/RFnexus/modem73) is an efficient software modem that can be used with Reticulum
|
||||
* Custom interface module for running [RNS over HTTP](https://github.com/Quad4-Software/RNS-over-HTTP)
|
||||
* Guide for running [Reticulum over ICMP](https://github.com/matvik22000/rns-over-icmp) using `PipeInterface`
|
||||
* Guide for running [Reticulum over DNS](https://github.com/markqvist/Reticulum/discussions/1002) with Iodine
|
||||
* Guide for running [Reticulum over HF radio](https://github.com/RFnexus/reticulum-over-hf)
|
||||
* [Modem73](https://github.com/RFnexus/modem73) is a KISS TNC OFDM modem frontend that can be used with Reticulum
|
||||
* Guide for running [Reticulum over HF radio](https://github.com/RFnexus/reticulum-over-hf)
|
||||
|
|
@ -24,9 +24,6 @@ Ko-Fi:
|
|||
https://ko-fi.com/markqvist
|
||||
```
|
||||
|
||||
Are certain features in the development roadmap are important to you or your
|
||||
organisation? Make them a reality quickly by sponsoring their implementation.
|
||||
|
||||
## Provide Feedback
|
||||
|
||||
Feedback on the usage, functioning and potential dysfunctioning of any and
|
||||
|
|
|
|||
|
|
@ -256,8 +256,7 @@ You can easily add `rnsd` as an always-on service by [configuring a service](#us
|
|||
|
||||
### The rnstatus Utility
|
||||
|
||||
Using the `rnstatus` utility, you can view the status of configured Reticulum
|
||||
interfaces, similar to the `ifconfig` program.
|
||||
Using the `rnstatus` utility, you can view the status of and control configured Reticulum interfaces, similar to the `ifconfig` program.
|
||||
|
||||
**Usage Examples**
|
||||
|
||||
|
|
@ -331,6 +330,9 @@ options:
|
|||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
--attach name Attach interface by name
|
||||
--detach name Detach interface by name
|
||||
--reload name Reload interface by name
|
||||
-a, --all show all interfaces
|
||||
-A, --announce-stats show announce stats
|
||||
-P, --pr-stats show path request stats
|
||||
|
|
@ -348,6 +350,8 @@ options:
|
|||
-w seconds timeout before giving up on remote queries
|
||||
-d, --discovered list discovered interfaces
|
||||
-D show details and config entries for discovered interfaces
|
||||
--show-stale show stale discovery entries
|
||||
--show-unknown show discovery entries without version info
|
||||
-m, --monitor continuously monitor status
|
||||
-I, --monitor-interval seconds
|
||||
refresh interval for monitor mode (default: 1)
|
||||
|
|
@ -674,7 +678,7 @@ For the full documentation on the rngit system, see the [Git Over Reticulum](git
|
|||
The `rnx` utility is a basic remote command execution program. It allows you to
|
||||
execute commands on remote systems over Reticulum, and to view returned command
|
||||
output. For a fully interactive remote shell solution, be sure to also take a look
|
||||
at the [rnsh](https://github.com/acehoss/rnsh) program.
|
||||
at the [rnsh](#using-rnsh) program.
|
||||
|
||||
**Usage Examples**
|
||||
|
||||
|
|
@ -1183,6 +1187,7 @@ For more fine-grained control over how discovered interfaces are auto-connected,
|
|||
|
||||
* The `autoconnect_interface_mode` options specifies which mode discovered interfaces should be created with when auto-connecting.
|
||||
* The `autoconnect_announces_to_internal` option allows you to specify that auto-connected interfaces should propagate announces to `internal` mode interfaces, even if the auto-connected interface’s mode would normally not allow for this.
|
||||
* The `autoconnect_unverified_implementations` option will allow the interface discovery system to auto-connect to *any* announced interface, even those coming from unverified, experimental or potentially broken implementations. Be very careful with this option.
|
||||
|
||||
## Remote Management
|
||||
|
||||
|
|
|
|||
1625
docs/source/brandolinis.rst
Normal file
|
|
@ -8,6 +8,16 @@ This chapter of the manual serves as the technical reference for the distributed
|
|||
|
||||
A set of utilities for distributed collaborative software development and publishing are included in RNS.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/rngit.webp
|
||||
:align: center
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/rngit.png
|
||||
:align: center
|
||||
|
||||
The system consists of two parts: The ``rngit`` node that hosts repositories, and the ``git-remote-rns`` helper that enables Git to communicate with rngit nodes. As soon as you have RNS installed on your system, you can transparently use Git with Reticulum-hosted repositories just like any other type of remote. Git over Reticulum uses URLs in the following format: ``rns://DESTINATION_HASH/group/repo``.
|
||||
|
||||
If you set a branch to track a Reticulum remote as the default upstream, you can simply use ``git`` as you normally would; all commands work transparently and as expected.
|
||||
|
|
@ -133,6 +143,9 @@ To create a new empty repository on a remote node:
|
|||
|
||||
This creates a bare Git repository at the specified path. You must have ``create`` permission for the target group. When a repository is created, the creator automatically receives ``adm`` (admin) permissions on the repository through an auto-generated ``.allowed`` file.
|
||||
|
||||
.. tip::
|
||||
If you want to use a different primary branch name than ``master``, you can change it inside the bare git repository directory with ``git symbolic-ref HEAD refs/heads/name_of_other_branch``.
|
||||
|
||||
**All Command-Line Options (rngit create)**
|
||||
|
||||
.. code:: text
|
||||
|
|
@ -683,6 +696,7 @@ The following template files are supported:
|
|||
- ``commit.mu`` - Individual commit detail page
|
||||
- ``refs.mu`` - Branches and tags listing
|
||||
- ``stats.mu`` - Statistics page
|
||||
- ``no_ident.mu`` - Displayed to non-identified initiators if blocked
|
||||
|
||||
Templates can include the following variables:
|
||||
|
||||
|
|
|
|||
BIN
docs/source/graphics/rns_logo_256.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/source/graphics/rns_logo_512.webp
Normal file
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -32,6 +32,7 @@ to participate in the development of Reticulum itself.
|
|||
support
|
||||
examples
|
||||
license
|
||||
brandolinis
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 500 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 268 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 54 KiB |
BIN
docs/source/screenshots/modem73.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
docs/source/screenshots/modem73.png
Normal file
|
After Width: | Height: | Size: 776 KiB |