mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-06-23 04:16:12 -07:00
Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1f522277c | |||
| af6e0c9ecf | |||
| 176567e3f1 | |||
| 15cd4268ac | |||
| 9307db16c4 | |||
| 0f29ab629a | |||
| b2a4ceb853 | |||
| 6c7f1d068b | |||
| b76beb602d | |||
| 0c68f6491a | |||
| 038981474a | |||
| df0b4a5165 | |||
| db7359f56d | |||
| 12e45b6483 | |||
| ba8fca6f87 | |||
| 9b99b72f61 | |||
| 03cfbc2eb6 | |||
| c92872a81b | |||
| f3f4d9bca3 | |||
| e7a317f0a0 | |||
| d5b64a4af3 | |||
| 5667a0bbac | |||
| 7e46422c16 | |||
| 869a803149 | |||
| f744e4d9a3 | |||
| 1a7607cba3 | |||
| d881c111f6 | |||
| bdac57ec0b | |||
| c15f566cfa | |||
| bdc79b9097 | |||
| 102eccb77d | |||
| e8b236c7d8 | |||
| d69491eb80 | |||
| 256a4d0b92 | |||
| c5add012c1 | |||
| 6ecc8933b4 | |||
| 42b5661979 | |||
| 6333fb39bf | |||
| ea27a8b8a7 | |||
| 358f9c3b0c | |||
| cb3ef69072 | |||
| eee9354657 | |||
| ff86a1d7e6 | |||
| e49f31322c | |||
| 95502e2c21 | |||
| 3dd4145e62 | |||
| 1d7ddc3f8a | |||
| d731b4396c | |||
| c186a1f6b0 | |||
| a049ec8b7b | |||
| 4c93f6c7f4 | |||
| 35c7a89b19 | |||
| c86b9c9703 | |||
| 64ebdd0ee3 | |||
| 9179b914d5 | |||
| eb5d46b20b | |||
| 54c36f515b | |||
| 5c5668a4fc | |||
| eeefb60c89 | |||
| 018df10a26 | |||
| 93ead77435 | |||
| bd0e1ad0ca | |||
| d0ceeacb37 | |||
| 7d5fb6a13f | |||
| 855ef7bfd1 | |||
| 323890021a | |||
| e004e7592b | |||
| 0ebec014e5 | |||
| 1b624cc0e2 | |||
| e8d161c0d5 | |||
| e5c7dd7ec7 | |||
| 7d6ed59e6e | |||
| 11e4e7953a | |||
| a5b292ee81 | |||
| d619bafb8d | |||
| 0119a589dc | |||
| b7346bed4d | |||
| fcea57cb8e | |||
| 8d8af5e60a | |||
| 1a732ac1c1 | |||
| f827d945be | |||
| e03c4ee455 | |||
| 35e7ccb773 | |||
| a932a10492 | |||
| c5108c3a19 | |||
| 767782e425 | |||
| 60c440a3b6 | |||
| 6551a25877 | |||
| 70db2c5369 | |||
| 8ed31d0dc8 | |||
| ef1ecb35e1 | |||
| 6768f10631 | |||
| fee6a53473 | |||
| bbfa3b0aa0 | |||
| 325ae654ef | |||
| 8655a4fb37 | |||
| b30d272ee6 | |||
| cc90ac2853 | |||
| 55473f39cb | |||
| 6d73881b07 | |||
| d107cd4b42 | |||
| 33247e21b2 | |||
| 6bdc769af3 | |||
| e923ccbf1b | |||
| d402ee33a2 | |||
| d8d420745f | |||
| 524f2068cd | |||
| 5db089ff19 | |||
| 08d6780c73 | |||
| ca3f0bba6d | |||
| 830327e4a2 | |||
| f96409dfa9 | |||
| 18e2da7d2b | |||
| dfd046afb6 | |||
| 63d7f1e295 |
@@ -1,3 +1,96 @@
|
||||
### 2026-05-17: RNS 1.2.7
|
||||
|
||||
This release significantly improves the `rngit` system with fork, mirroring and empty repository creation functionality, a new work document proposals feature, improvements to the transport core reliability and efficiency and various other tweaks and improvements.
|
||||
|
||||
**Changes**
|
||||
- Added work document proposals functionality to `rngit`
|
||||
- Added fork and mirroring support to `rngit`
|
||||
- Added ability to create new repositories remotely to `rngit`
|
||||
- Added latest release management to `rngit`
|
||||
- Added download stats to `rngit`
|
||||
- Improved shared instance RPC error handling
|
||||
- Improved announce cache cleaning
|
||||
- Improved `rngit` page node link handling
|
||||
- Improved stats pages `rngit`
|
||||
- Improved transfer completed feedback in `rncp`, thanks to **neutral**
|
||||
- Improved interface transport insertion and removal
|
||||
|
||||
**Release Signatures**
|
||||
Release artifacts include `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rnid`. To verify files, download the `rsg` signatures, make sure they are in the same folder as the release artifact, and run `rnid` signature verification with the release identity as the required signer:
|
||||
|
||||
```sh
|
||||
rnid -i bc7291552be7a58f361522990465165c -V rns*.whl
|
||||
```
|
||||
|
||||
The `rnid` utility will then verify the signatures, and display whether it is valid. If the signature cannot be verified, the file has been tampered with and should be thrown very far away in a jiffy.
|
||||
|
||||
### 2026-05-14: RNS 1.2.6
|
||||
|
||||
This release adds further improvements to the `rnid` and `rngit` utilities, and includes several bugfixes and other improvements.
|
||||
|
||||
**Changes**
|
||||
- Added embedded message signing, validation and viewing to `rnid`
|
||||
- Added file encryption for multiple file path inputs and shell expansions to `rnid`
|
||||
- Added file decryption for multiple file path inputs and shell expansions to `rnid`
|
||||
- Added signature creation for multiple file path inputs and shell expansions to `rnid`
|
||||
- Added signature validation of multiple file path inputs and shell expansions to `rnid`
|
||||
- Added workdoc signing and validation to `rngit`
|
||||
- Added ability to edit workdoc titles to `rngit`
|
||||
- Added ability to download workdocs via the `nomadnet` interface to `rngit`
|
||||
- Added local URL resolution to the `rngit` repository frontpage markdown readme renderer
|
||||
- Improved `rnstatus` remote monitor loop
|
||||
- Improved `rngit` workdoc page handling
|
||||
- Improved `rngit` release page rendering
|
||||
- Fixed missing none check in interface discovery sanitizer thanks to PAzter1101
|
||||
- Fixed potential race condition in interface discovery
|
||||
- Fixed `rngit` remote helper hanging on startup if no client config had been created previously, and RNS loglevel was configured at debug or higher
|
||||
|
||||
**Release Signatures**
|
||||
Release artifacts include `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rnid`. To verify files, download the `rsg` signatures, make sure they are in the same folder as the release artifact, and run `rnid` signature verification with the release identity as the required signer:
|
||||
|
||||
```sh
|
||||
rnid -i bc7291552be7a58f361522990465165c -V rns*.whl
|
||||
```
|
||||
|
||||
The `rnid` utility will then verify the signatures, and display whether it is valid. If the signature cannot be verified, the file has been tampered with and should be thrown very far away in a jiffy.
|
||||
|
||||
### 2026-05-09: RNS 1.2.5
|
||||
|
||||
This release brings substantial improvements to path request handling, and should significantly reduce overall network and local transport node processing loads. Path requests are now automatically ingress and egress limited per interface and sub-interface. Although the defaults are effective and sane, and should work right out of the box bring an end to practically all the PR and announce spam going on lately, the backend is fully configurable for both defaults and per interface, if you want to fiddle with the settings.
|
||||
|
||||
People who have written (ahem... *prompted into existence*) strange applications, that believed sending 25 random path requests every 10 seconds to try and punch holes through announce limiting, will now most likely find any potential users of such applications complaining that they are losing the ability to resolve paths alltogether, which is (entirely) by design, of course. Seriously, don't do crap like that.
|
||||
|
||||
You can read more about how the new ingress and egress controls work in the updated manual sections, in the Interfaces chapter.
|
||||
|
||||
For all node ops out there, I'd recomment updating to this at some sort of semi-expedient, but of course not un-leisurely pace, so peace and order on the networks can be restored.
|
||||
|
||||
**Changes**
|
||||
- Added path request ingress and egress control with sane defaults for transport nodes
|
||||
- Added full configurability of ingress and egress controls per interface and for instance-wide defaults
|
||||
- Significantly improved transport logic for path request and announce handling
|
||||
- Added path request frequency display to `rnstatus`
|
||||
- Added AutoInterface per-peer announe rate display to `rnstatus`
|
||||
- Added abilit to filter interfaces by burst state to `rnstatus`
|
||||
- Added hex/base32/base64 ASCII-wrapped output to `rnid` signature generator
|
||||
- Tuned default ingress control parameters
|
||||
- Fixed regression in link close handling in `rnstatus` and `rnpath` remote management handling
|
||||
- Fixed invalid handling of corrupted interface discovery files
|
||||
- Fixed announce processing edge case handling if path was cleaned while waiting for rebroadcast
|
||||
- Improved `rngit` error logging
|
||||
- Improved transport background jobs error handling
|
||||
- Fixed various edge-cases and inconsistencies in markdown rendering in `rngit`
|
||||
- Ensured canonical validation functions in `rngit`
|
||||
- Lots of other small fixes and stability improvements to `rngit`
|
||||
|
||||
**Release Signatures**
|
||||
Release artifacts include `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rnid`. To verify files, download the `rsg` signatures, make sure they are in the same folder as the release artifact, and run `rnid` signature verification with the release identity as the required signer:
|
||||
|
||||
```sh
|
||||
rnid -i bc7291552be7a58f361522990465165c -V rns-1.2.5-py3-none-any.whl
|
||||
```
|
||||
|
||||
The `rnid` utility will then verify the signatures, and display whether it is valid. If the signature cannot be verified, the file has been tampered with and should be thrown very far away in a jiffy.
|
||||
|
||||
### 2026-05-07: RNS 1.2.4
|
||||
|
||||
This release brings a complete rewrite and update to the `rnid` utility, which is now a lot more useful, and better at finding and saving identities. It also includes a bunch of other improvements, such as expanded `rngit` functionality, better transport performance and a few bugfixes. Enjoy!
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
>> Reticulum Network Stack
|
||||
|
||||
To understand the foundational philosophy and goals of this system, read the `!`[Zen of Reticulum`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=Zen+of+Reticulum.md]`!.
|
||||
To understand the foundational philosophy and goals of this system, read the `_`!`[Zen of Reticulum`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=Zen+of+Reticulum.md]`!`_.
|
||||
|
||||
Reticulum is the cryptography-based networking stack for building local and wide-area networks with readily available hardware. It can operate even with very high latency and extremely low bandwidth. Reticulum allows you to build wide-area networks with off-the-shelf tools, and offers end-to-end encryption and connectivity, initiator anonymity, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable delivery acknowledgements and more.
|
||||
|
||||
The vision of Reticulum is to allow anyone to be their own network operator, and to make it cheap and easy to cover vast areas with a myriad of independent, inter-connectable and autonomous networks. Reticulum **is not** *one* network. It is **a tool** for building *thousands of networks*. Networks without kill-switches, surveillance, censorship and control. Networks that can freely interoperate, associate and disassociate with each other, and require no central oversight. Networks for human beings. *Networks for the people*.
|
||||
The vision of Reticulum is to allow anyone to be their own network operator, and to make it cheap and easy to cover vast areas with a myriad of independent, inter-connectable and autonomous networks. Reticulum `!is not`! `*one`* network. It is `!a tool`! for building `*thousands of networks`*. Networks without kill-switches, surveillance, censorship and control. Networks that can freely interoperate, associate and disassociate with each other, and require no central oversight. Networks for human beings. `*Networks for the people`*.
|
||||
|
||||
Reticulum is a complete networking stack, and does not rely on IP or higher layers, but it is possible to use IP as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks.
|
||||
|
||||
Having no dependencies on traditional networking stacks frees up overhead that has been used to implement a networking stack built directly on cryptographic principles, allowing resilience and stable functionality, even in open and trustless networks.
|
||||
|
||||
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.
|
||||
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python.
|
||||
|
||||
>> Read The Manual
|
||||
|
||||
The full documentation for Reticulum is available on `!`[this node`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!.
|
||||
The full documentation for Reticulum is available on `_`!`[this node`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!`_.
|
||||
|
||||
You can also download the `!`[Reticulum manual as a PDF`:/file/download`g=reticulum|r=reticulum|ref=HEAD|path=docs%2FReticulum+Manual.pdf]`! or `!`[as an e-book in EPUB format`:/file/download`g=reticulum|r=reticulum|ref=HEAD|path=docs%2FReticulum+Manual.pdf]`!.
|
||||
You can also download the `_`!`[Reticulum manual as a PDF`:/file/download`g=reticulum|r=reticulum|ref=HEAD|path=docs%2FReticulum+Manual.pdf]`!`_ or `_`!`[as an e-book in EPUB format`:/file/download`g=reticulum|r=reticulum|ref=HEAD|path=docs%2FReticulum+Manual.pdf]`!`_.
|
||||
|
||||
>> Notable Features
|
||||
|
||||
@@ -66,33 +66,31 @@ The Reticulum Protocol was dedicated to the Public Domain in 2016.
|
||||
|
||||
>> Examples of Reticulum Applications
|
||||
|
||||
If you want to quickly get an idea of what Reticulum can do, take a look at the [Programs Using Reticulum](https://reticulum.network/manual/software.html) section of the manual, or the following resources:
|
||||
If you want to quickly get an idea of what Reticulum can do, take a look at the `_`!`[Programs Using Reticulum`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/software.md]`!`_ section of the manual, or the following resources:
|
||||
|
||||
• [LXMF](https://github.com/markqvist/lxmf) is a distributed, delay and disruption tolerant message transfer
|
||||
protocol built on Reticulum
|
||||
• `_`!`[LXMF`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=lxmf]`!`_ is a distributed, delay and disruption tolerant message transfer protocol built on Reticulum
|
||||
|
||||
• The [LXST](https://github.com/markqvist/lxst) protocol and framework provides real-time audio and signals
|
||||
transport over Reticulum. It includes primitives and utilities for building voice-based applications and
|
||||
hardware devices, such as the `B333rnphone`b program, that can be used to build hardware telephones.
|
||||
• The `_`!`[LXST`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=lxst]`!`_ protocol and framework provides real-time audio and signals transport over Reticulum. It
|
||||
includes primitives and utilities for building voice-based applications and hardware devices,
|
||||
such as the `B333rnphone`b program, that can be used to build hardware telephones.
|
||||
|
||||
• For an off-grid, encrypted and resilient mesh communications platform, see [Nomad Network](https://github.com/markqvist/NomadNet)
|
||||
• For an off-grid, encrypted and resilient mesh communications platform, see `_`!`[Nomad Network`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=nomadnet]`!`_.
|
||||
|
||||
• The Android, Linux, macOS and Windows app [Sideband](https://github.com/markqvist/Sideband) has a graphical
|
||||
interface and many advanced features, such as file transfers, image and voice messages, real-time voice calls,
|
||||
a distributed telemetry system, mapping capabilities and full plugin extensibility.
|
||||
• The Android, Linux, macOS and Windows app `_`!`[Sideband`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=sideband]`!`_ has a graphical interface and many advanced
|
||||
features, such as file transfers, image and voice messages, real-time voice calls, a distributed
|
||||
telemetry system, mapping capabilities and full plugin extensibility.
|
||||
|
||||
• [MeshChatX](https://git.quad4.io/RNS-Things/MeshChatX) is a full-featured LXMF client with many built-in tools
|
||||
and functionalities, that also supports image and voice messages, file transfers and voice calls. It also
|
||||
includes a built-in page browser for browsing Nomad Network nodes.
|
||||
• `_`!`[MeshChatX`c10d80b1a42fa958c37a6cc30dc04f53]`!`_ (`_`!`[source`5399f5a0212477618821e91e88ce053b:/page/repo.mu`g=quad4|r=MeshChatX]`_`!) is a full-featured LXMF client with many built-in tools and functionalities,
|
||||
that also supports image and voice messages, file transfers and voice calls. It also includes a
|
||||
built-in page browser for browsing Nomad Network nodes.
|
||||
|
||||
• You can use the included [rnsh](https://reticulum.network/manual/using.html#the-rnsh-utility) program to
|
||||
establish remote shell sessions over Reticulum.
|
||||
• You can use the included `_`!`[rnsh`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/using.md|anchor=the-rnsh-utility]`!`_ program to establish remote shell sessions over Reticulum.
|
||||
|
||||
>> Where can Reticulum be used?
|
||||
|
||||
Over practically any medium that can support at least a half-duplex channel with greater throughput than 5 bits per second, and an MTU of 500 bytes. Data radios, modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes, WiFi and Ethernet devices, free-space optical links, and similar systems are all examples of the types of physical devices Reticulum can use.
|
||||
|
||||
An open-source LoRa-based interface called [RNode](https://markqvist.github.io/Reticulum/manual/hardware.html#rnode) has been designed specifically for use with Reticulum. It is possible to build yourself, or it can be purchased as a complete transceiver that just needs a USB connection to the host.
|
||||
An open-source LoRa-based interface called `_`!`[RNode`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/hardware.md|anchor=rnode]`!`_ has been designed specifically for use with Reticulum. It is possible to build yourself, or it can be purchased as a complete transceiver that just needs a USB connection to the host.
|
||||
|
||||
Reticulum can also be encapsulated over existing IP networks, so there's nothing stopping you from using it over wired Ethernet, your local WiFi network or the Internet, where it'll work just as well. In fact, one of the strengths of Reticulum is how easily it allows you to connect different mediums into a self-configuring, resilient and encrypted mesh, using any available mixture of available infrastructure.
|
||||
|
||||
@@ -100,9 +98,9 @@ As an example, it's possible to set up a Raspberry Pi connected to both a LoRa r
|
||||
|
||||
>> How do I get started?
|
||||
|
||||
The best way to get started with the Reticulum Network Stack depends on what you want to do. For full details and examples, have a look at the [Getting Started Fast](https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
|
||||
The best way to get started with the Reticulum Network Stack depends on what you want to do. For full details and examples, have a look at the `_`!`[Getting Started Fast`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/gettingstartedfast.md]`!`_ section of the `_`!`[Reticulum Manual`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!`_.
|
||||
|
||||
To simply install Reticulum and related utilities on your system, the easiest way is via `B333pip`b. You can then start any program that uses Reticulum, or start Reticulum as a system service with [the rnsd utility](https://markqvist.github.io/Reticulum/manual/using.html#the-rnsd-utility).
|
||||
To simply install Reticulum and related utilities on your system, the easiest way is via `B333pip`b. You can then start any program that uses Reticulum, or start Reticulum as a system service with `_`!`[the rnsd utility`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/using.md|anchor=the-rnsd-utility]`!`_.
|
||||
|
||||
`B333
|
||||
`=
|
||||
@@ -131,10 +129,11 @@ When first started, Reticulum will create a default configuration file, providin
|
||||
|
||||
If you have an old version of `B333pip`b on your system, you may need to upgrade it first with `B333pip install pip --upgrade`b. If you no not already have `B333pip`b installed, you can install it using the package manager of your system with `B333sudo apt install python3-pip`b or similar.
|
||||
|
||||
For more detailed examples on how to expand communication over many mediums such as packet radio or LoRa, serial ports, or over fast IP links and the Internet using the UDP and TCP interfaces, take a look at the [Supported Interfaces](https://markqvist.github.io/Reticulum/manual/interfaces.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
|
||||
For more detailed examples on how to expand communication over many mediums such as packet radio or LoRa, serial ports, or over fast IP links and the Internet using the UDP and TCP interfaces, take a look at the `_`!`[Supported Interfaces`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/interfaces.md]`!`_ section of the `_`!`[Reticulum Manual`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!`_.
|
||||
|
||||
>> Included Utilities
|
||||
Reticulum includes a range of useful utilities for managing your networks, viewing status and information, and other tasks. You can read more about these programs in the [Included Utility Programs](https://markqvist.github.io/Reticulum/manual/using.html#included-utility-programs) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
|
||||
|
||||
Reticulum includes a range of useful utilities for managing your networks, viewing status and information, and other tasks. You can read more about these programs in the `_`!`[Included Utility Programs`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/using.md|anchor=included-utility-programs]`!`_ section of the `_`!`[Reticulum Manual`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!`_.
|
||||
|
||||
• The system daemon `B333rnsd`b for running Reticulum as an always-available service
|
||||
• An interface status utility called `B333rnstatus`b, that displays information about interfaces
|
||||
@@ -147,16 +146,14 @@ Reticulum includes a range of useful utilities for managing your networks, viewi
|
||||
• The `B333rngit`b program provides a full multi-repository Git node for serving repositories over Reticulum
|
||||
• The included `B333git-remote-rns`b helper allows you to interact with Git repositories over Reticulum
|
||||
|
||||
All tools, including `B333rnx`b and `B333rncp`b, work reliably and well even over very low-bandwidth links like LoRa or Packet Radio. For full-featured remote shells over Reticulum, also have a look at the [rnsh](https://github.com/acehoss/rnsh) program.
|
||||
|
||||
>> Supported interface types and devices
|
||||
|
||||
Reticulum implements a range of generalised interface types that covers most of the communications hardware that Reticulum can run over. If your hardware is not supported, it's [simple to implement a custom interface module](https://markqvist.github.io/Reticulum/manual/interfaces.html#custom-interfaces).
|
||||
Reticulum implements a range of generalised interface types that covers most of the communications hardware that Reticulum can run over. If your hardware is not supported, it's `_`!`[simple to implement a custom interface module`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/interfaces.md|anchor=custom-interfaces]`!`_.
|
||||
|
||||
Currently, the following built-in interfaces are supported:
|
||||
|
||||
• Any Ethernet device
|
||||
• LoRa using [RNode](https://unsigned.io/rnode/)
|
||||
• LoRa using `_`!`[RNode`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=rnode_firmware]`!`_
|
||||
• Packet Radio TNCs (with or without AX.25)
|
||||
• KISS-compatible hardware and software modems
|
||||
• Any device with a serial port
|
||||
@@ -166,64 +163,72 @@ Currently, the following built-in interfaces are supported:
|
||||
• Custom hardware via stdio or pipes
|
||||
|
||||
>> Performance
|
||||
Reticulum targets a *very* wide usable performance envelope, but prioritises functionality and performance on low-bandwidth mediums. The goal is to provide a dynamic performance envelope from 250 bits per second, to 1 gigabit per second on normal hardware.
|
||||
|
||||
Reticulum targets a `*very`* wide usable performance envelope, but prioritises functionality and performance on low-bandwidth mediums. The goal is to provide a dynamic performance envelope from 250 bits per second, to 1 gigabit per second on normal hardware.
|
||||
|
||||
Currently, the usable performance envelope is approximately 150 bits per second to 500 megabits per second, with physical mediums faster than that not being saturated. Performance beyond the current level is intended for future upgrades, but not highly prioritised at this point in time.
|
||||
|
||||
>> Current Status
|
||||
|
||||
All core protocol features are implemented and functioning, but additions will probably occur as real-world use is explored and understood. The API and wire-format can be considered stable.
|
||||
|
||||
>> Dependencies
|
||||
|
||||
The installation of the default `B333rns`b package requires only two external dependencies, listed below. Almost all systems and distributions have readily available packages for these dependencies, and when the `B333rns`b package is installed with `B333pip`b, they will be downloaded and installed as well.
|
||||
|
||||
• [PyCA/cryptography](https://github.com/pyca/cryptography)
|
||||
• [pyserial](https://github.com/pyserial/pyserial)
|
||||
• PyCA/cryptography
|
||||
• pyserial
|
||||
|
||||
On more unusual systems, and in some rare cases, it might not be possible to install or even compile one or more of the above modules. In such situations, you can use the `B333rnspure`b package instead, which require no external dependencies for installation. Please note that the contents of the `B333rns`b and `B333rnspure`b packages are *identical*. The only difference is that the `B333rnspure`b package lists no dependencies required for installation.
|
||||
On more unusual systems, and in some rare cases, it might not be possible to install or even compile one or more of the above modules. In such situations, you can use the `B333rnspure`b package instead, which require no external dependencies for installation. Please note that the contents of the `B333rns`b and `B333rnspure`b packages are `*identical`*. The only difference is that the `B333rnspure`b package lists no dependencies required for installation.
|
||||
|
||||
No matter how Reticulum is installed and started, it will load external dependencies only if they are *needed* and *available*. If for example you want to use Reticulum on a system that cannot support [pyserial](https://github.com/pyserial/pyserial), it is perfectly possible to do so using the `B333rnspure`b package, but Reticulum will not be able to use serial-based interfaces. All other available modules will still be loaded when needed.
|
||||
No matter how Reticulum is installed and started, it will load external dependencies only if they are `*needed`* and `*available`*. If for example you want to use Reticulum on a system that cannot support `B333pyserial`b, it is perfectly possible to do so using the `B333rnspure`b package, but Reticulum will not be able to use serial-based interfaces. All other available modules will still be loaded when needed.
|
||||
|
||||
**Please Note!** If you use the `B333rnspure`b package to run Reticulum on systems that do not support [PyCA/cryptography](https://github.com/pyca/cryptography), it is important that you read and understand the [Cryptographic Primitives](#cryptographic-primitives) section of this document.
|
||||
`!Please Note!`! If you use the `B333rnspure`b package to run Reticulum on systems that do not support PyCA/cryptography, it is important that you read and understand the `!Cryptographic Primitives`! section of this document.
|
||||
|
||||
>> Bootstrapping Connectivity
|
||||
|
||||
Reticulum is not a service you subscribe to, nor is it a single global network you "join". Reticulum provides functionality for discovering available public interfaces over the network itself, and the broader community has provided various directories of publicly available entrypoints to bootstrap connectivity.
|
||||
|
||||
To learn how to establish initial connectivity over Reticulum, read the [Bootstrapping Connectivity](https://reticulum.network/manual/gettingstartedfast.html#bootstrapping-connectivity) section of the manual.
|
||||
To learn how to establish initial connectivity over Reticulum, read the `_`!`[Bootstrapping Connectivity`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/gettingstartedfast.md|anchor=bootstrapping-connectivity]`!`_ section of the manual.
|
||||
|
||||
If you already have a general idea of how this works, you can use community-run sites such as [directory.rns.recipes](https://directory.rns.recipes/) and [rmap.world](https://rmap.world) to find interface definitions for initial connectivity to the global distributed Reticulum backbone.
|
||||
If you already have a general idea of how this works, you can use community-run sites such as `_`!`[rns.recipes`9ce92808be498e9e05590ff27cbfdfe4]`!`_ and `_`!`[rmap.world`a4a5e861626ce97c9aa544d9ecdf6d22]`!`_ to find interface definitions for initial connectivity to the global distributed Reticulum backbone.
|
||||
|
||||
>> Public Testnet
|
||||
***Important!** Historically, a developer-targeted testnet was made available by the Reticulum project itself. As the amount of global Reticulum nodes and entrypoints have grown to a substantial quantity, this public testnet, including the Amsterdam Testnet entrypoint, has now been decommissioned. If your still have instances that relied on this entrypoint for connectivity, transition to using the distributed backbone instead. Reticulum now includes a full on-network interface discovery and connectivity bootstrapping system. Read the [Bootstrapping Connectivity](https://reticulum.network/manual/gettingstartedfast.html#bootstrapping-connectivity) section of the manual for pointers.*
|
||||
|
||||
`!`*Important!`! Historically, a developer-targeted testnet was made available by the Reticulum project itself. As the amount of global Reticulum nodes and entrypoints have grown to a substantial quantity, this public testnet, including the Amsterdam Testnet entrypoint, has now been decommissioned. If you still have instances that relied on this entrypoint for connectivity, transition to using the distributed backbone instead. Reticulum now includes a full on-network interface discovery and connectivity bootstrapping system. Read the `_`[Bootstrapping Connectivity`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/gettingstartedfast.md|anchor=bootstrapping-connectivity]`_ section of the manual for pointers.`*
|
||||
|
||||
>> Support Reticulum
|
||||
|
||||
For this to be possible, I need your help. Please support the continued development of open, free and private communications systems by donating via one of the following channels:
|
||||
|
||||
• Monero:
|
||||
• `!Monero`!
|
||||
84FpY1QbxHcgdseePYNmhTHcrgMX4nFfBYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD19b3B8NiLCGVxzKV17UMmmeEsCrPyA5w
|
||||
|
||||
• Bitcoin
|
||||
• `!Bitcoin`!
|
||||
bc1pgqgu8h8xvj4jtafslq396v7ju7hkgymyrzyqft4llfslz5vp99psqfk3a6
|
||||
|
||||
• Ethereum
|
||||
• `!Ethereum`!
|
||||
0x91C421DdfB8a30a49A71d63447ddb54cEBe3465E
|
||||
|
||||
• Liberapay: https://liberapay.com/Reticulum/
|
||||
• `!Liberapay`!
|
||||
`[https://liberapay.com/Reticulum/]
|
||||
|
||||
• Ko-Fi: https://ko-fi.com/markqvist
|
||||
• `!Ko-Fi`!
|
||||
`[https://ko-fi.com/markqvist]
|
||||
|
||||
>> Cryptographic Primitives
|
||||
|
||||
Reticulum uses a simple suite of efficient, strong and well-tested cryptographic primitives, with widely available implementations that can be used both on general-purpose CPUs and on microcontrollers.
|
||||
|
||||
One of the primary considerations for choosing this particular set of primitives is that they can be implemented *safely* with relatively few pitfalls, on practically all current computing platforms.
|
||||
One of the primary considerations for choosing this particular set of primitives is that they can be implemented `*safely`* with relatively few pitfalls, on practically all current computing platforms.
|
||||
|
||||
The primitives listed here **are authoritative**. Anything claiming to be Reticulum, but not using these exact primitives **is not** Reticulum, and possibly an intentionally compromised or weakened clone. The utilised primitives are:
|
||||
The primitives listed here `!are authoritative`!. Anything `*claiming`* to be Reticulum, but not using these exact primitives `FTA35050`!is not`!`f Reticulum, and possibly an intentionally compromised or weakened clone. The utilised primitives are:
|
||||
|
||||
• Reticulum Identity Keys are 512-bit Curve25519 keysets
|
||||
• A 256-bit Ed25519 key for signatures
|
||||
• A 256-bit X22519 key for ECDH key exchanges
|
||||
• HKDF for key derivation
|
||||
• Encrypted tokens are based on the [Fernet spec](https://github.com/fernet/spec/)
|
||||
• Encrypted tokens are based on the `_`!`[Fernet spec`https://github.com/fernet/spec/]`!`_
|
||||
• Ephemeral keys derived from an ECDH key exchange on Curve25519
|
||||
• HMAC using SHA256 for message authentication
|
||||
• IVs must be generated through `B333os.urandom()`b or better
|
||||
@@ -232,36 +237,37 @@ The primitives listed here **are authoritative**. Anything claiming to be Reticu
|
||||
• SHA-256
|
||||
• SHA-512
|
||||
|
||||
In the default installation configuration, the `B333X25519`b, `B333Ed25519`b, and `B333AES-256-CBC`b primitives are provided by [OpenSSL](https://www.openssl.org/) (via the [PyCA/cryptography](https://github.com/pyca/cryptography) package). The hashing functions `B333SHA-256`b and `B333SHA-512`b are provided by the standard Python [hashlib](https://docs.python.org/3/library/hashlib.html). The `B333HKDF`b, `B333HMAC`b, `B333Token`b primitives, and the `B333PKCS7`b padding function are always provided by the following internal implementations:
|
||||
In the default installation configuration, the `B333X25519`b, `B333Ed25519`b, and `B333AES-256-CBC`b primitives are provided by `_`!`[OpenSSL`https://www.openssl.org/]`!`_ (via the `_`!`[PyCA/cryptography`https://github.com/pyca/cryptography]`!`_ package). The hashing functions `B333SHA-256`b and `B333SHA-512`b are provided by the standard Python `_`!`[hashlib`https://docs.python.org/3/library/hashlib.html]`!`_. The `B333HKDF`b, `B333HMAC`b, `B333Token`b primitives, and the `B333PKCS7`b padding function are always provided by the following internal implementations:
|
||||
|
||||
• [HKDF.py](RNS/Cryptography/HKDF.py)
|
||||
• [HMAC.py](RNS/Cryptography/HMAC.py)
|
||||
• [Token.py](RNS/Cryptography/Token.py)
|
||||
• [PKCS7.py](RNS/Cryptography/PKCS7.py)
|
||||
• `_`!`[HKDF.py`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=RNS/Cryptography/HKDF.py]`!`_
|
||||
• `_`!`[HMAC.py`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=RNS/Cryptography/HMAC.py]`!`_
|
||||
• `_`!`[Token.py`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=RNS/Cryptography/Token.py]`!`_
|
||||
• `_`!`[PKCS7.py`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=RNS/Cryptography/PKCS7.py]`!`_
|
||||
|
||||
Reticulum also includes a complete implementation of all necessary primitives in pure Python. If OpenSSL and PyCA are not available on the system when Reticulum is started, Reticulum will instead use the internal pure-python primitives. A trivial consequence of this is performance, with the OpenSSL backend being *much* faster. The most important consequence however, is the potential loss of security by using primitives that has not seen the same amount of scrutiny, testing and review as those from OpenSSL.
|
||||
Reticulum also includes a complete implementation of all necessary primitives in pure Python. If OpenSSL and PyCA are not available on the system when Reticulum is started, Reticulum will instead use the internal pure-python primitives. A trivial consequence of this is performance, with the OpenSSL backend being `*much`* faster. The most important consequence however, is the potential loss of security by using primitives that has not seen the same amount of scrutiny, testing and review as those from OpenSSL.
|
||||
|
||||
Please note that by default, installing Reticulum will **require** OpenSSL and PyCA to also be automatically installed if not already available. It is only possible to use the pure-python primitives if this requirement is specifically overridden by the user, for example by installing the `B333rnspure`b package instead of the normal `B333rns`b package, or by running directly from local source-code.
|
||||
Please note that by default, installing Reticulum will `!require`! OpenSSL and PyCA to also be automatically installed if not already available. It is only possible to use the pure-python primitives if this requirement is specifically overridden by the user, for example by installing the `B333rnspure`b package instead of the normal `B333rns`b package, or by running directly from local source-code.
|
||||
|
||||
If you want to use the internal pure-python primitives, it is **highly advisable** that you have a good understanding of the risks that this pose, and make an informed decision on whether those risks are acceptable to you.
|
||||
If you want to use the internal pure-python primitives, it is `!highly advisable`! that you have a good understanding of the risks that this pose, and make an informed decision on whether those risks are acceptable to you.
|
||||
|
||||
Reticulum is relatively young software, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it _has not_ been externally security audited, and there could very well be privacy or security breaking bugs. If you want to help out, or help sponsor an audit, please do get in touch.
|
||||
|
||||
>> Acknowledgements & Credits
|
||||
|
||||
Reticulum can only exist because of the mountain of Open Source work it was built on top of, the contributions of everyone involved, and everyone that has supported the project through the years. To everyone who has helped, thank you so much.
|
||||
|
||||
A number of other modules and projects are either part of, or used by Reticulum. Sincere thanks to the authors and contributors of the following projects:
|
||||
|
||||
• [PyCA/cryptography](https://github.com/pyca/cryptography), *BSD License*
|
||||
• [Pure-25519](https://github.com/warner/python-pure25519) by [Brian Warner](https://github.com/warner), *MIT License*
|
||||
• [Pysha2](https://github.com/thomdixon/pysha2) by [Thom Dixon](https://github.com/thomdixon), *MIT License*
|
||||
• [Python AES-128](https://github.com/orgurar/python-aes) by [Or Gur Arie](https://github.com/orgurar), *MIT License*
|
||||
• [Python AES-256](https://github.com/boppreh/aes) by [BoppreH](https://github.com/boppreh), *MIT License*
|
||||
• [Curve25519.py](https://gist.github.com/nickovs/cc3c22d15f239a2640c185035c06f8a3#file-curve25519-py) by [Nicko van Someren](https://gist.github.com/nickovs), *Public Domain*
|
||||
• [I2Plib](https://github.com/l-n-s/i2plib) by [Viktor Villainov](https://github.com/l-n-s)
|
||||
• [PySerial](https://github.com/pyserial/pyserial) by Chris Liechti, *BSD License*
|
||||
• [Configobj](https://github.com/DiffSK/configobj) by Michael Foord, Nicola Larosa, Rob Dennis & Eli Courtwright, *BSD License*
|
||||
• [ifaddr](https://github.com/pydron/ifaddr) by Stefan C. Mueller, *MIT License*
|
||||
• [Umsgpack.py](https://github.com/vsergeev/u-msgpack-python) by [Ivan A. Sergeev](https://github.com/vsergeev)
|
||||
• [rnsh](https://github.com/acehoss/rnsh) by [Aaron Heise](https://github.com/acehoss)
|
||||
• [Python](https://www.python.org)
|
||||
• `_`!`[PyCA/cryptography`https://github.com/pyca/cryptography]`!`_, `*BSD License`*
|
||||
• `_`!`[Pure-25519`https://github.com/warner/python-pure25519]`!`_, by `_`!`[Brian Warner`https://github.com/warner]`!`_, `*MIT License`*
|
||||
• `_`!`[Pysha2`https://github.com/thomdixon/pysha2]`!`_ by `_`!`[Thom Dixon`https://github.com/thomdixon]`!`_, `*MIT License`*
|
||||
• `_`!`[Python AES-128`https://github.com/orgurar/python-aes]`!`_ by `_`!`[Or Gur Arie`https://github.com/orgurar]`!`_, `*MIT License`*
|
||||
• `_`!`[Python AES-256`https://github.com/boppreh/aes]`!`_ by `_`!`[BoppreH`https://github.com/boppreh]`!`_, `*MIT License`*
|
||||
• `_`!`[Curve25519.py`https://gist.github.com/nickovs/cc3c22d15f239a2640c185035c06f8a3]`!`_ by `_`!`[Nicko van Someren`https://gist.github.com/nickovs]`!`_, `*Public Domain`*
|
||||
• `_`!`[I2Plib`https://github.com/l-n-s/i2plib]`!`_ by `_`!`[Viktor Villainov`https://github.com/l-n-s]`!`_
|
||||
• `_`!`[PySerial`https://github.com/pyserial/pyserial]`!`_ by Chris Liechti, `*BSD License`*
|
||||
• `_`!`[Configobj`https://github.com/DiffSK/configobj]`!`_ by Michael Foord, Nicola Larosa, Rob Dennis & Eli Courtwright, `*BSD License`*
|
||||
• `_`!`[ifaddr`https://github.com/pydron/ifaddr]`!`_ by Stefan C. Mueller, `*MIT License`*
|
||||
• `_`!`[Umsgpack.py`https://github.com/vsergeev/u-msgpack-python]`!`_ by `_`!`[Ivan A. Sergeev`https://github.com/vsergeev]`!`_
|
||||
• `_`!`[rnsh`https://github.com/acehoss/rnsh]`!`_ by `_`!`[Aaron Heise`https://github.com/acehoss]`!`_
|
||||
• `_`!`[Python`https://www.python.org]`!`_
|
||||
|
||||
+48
-37
@@ -6,6 +6,7 @@ import random
|
||||
import threading
|
||||
import ipaddress
|
||||
import subprocess
|
||||
from threading import Lock
|
||||
from .vendor import umsgpack as msgpack
|
||||
|
||||
NAME = 0xFF
|
||||
@@ -86,6 +87,7 @@ class InterfaceAnnouncer():
|
||||
RNS.trace_exception(e)
|
||||
|
||||
def sanitize(self, in_str):
|
||||
if in_str == None: return None
|
||||
sanitized = in_str.replace("\n", "")
|
||||
sanitized = sanitized.replace("\r", "")
|
||||
sanitized = sanitized.strip()
|
||||
@@ -374,6 +376,8 @@ class InterfaceDiscovery():
|
||||
AUTOCONNECT_TYPES = ["BackboneInterface", "TCPServerInterface"]
|
||||
DISCOVERABLE_TYPES = ["BackboneInterface", "TCPServerInterface", "I2PInterface", "RNodeInterface", "WeaveInterface", "KISSInterface"]
|
||||
|
||||
discovery_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
|
||||
|
||||
@@ -401,8 +405,10 @@ class InterfaceDiscovery():
|
||||
discovery_sources = RNS.Reticulum.interface_discovery_sources()
|
||||
for filename in os.listdir(self.storagepath):
|
||||
try:
|
||||
filepath = os.path.join(self.storagepath, filename)
|
||||
with open(filepath, "rb") as f: info = msgpack.unpackb(f.read())
|
||||
with self.discovery_lock:
|
||||
filepath = os.path.join(self.storagepath, filename)
|
||||
with open(filepath, "rb") as f: info = msgpack.unpackb(f.read())
|
||||
|
||||
should_remove = False
|
||||
heard_delta = now-info["last_heard"]
|
||||
info["name"] = InterfaceAnnounceHandler.sanitize_name(info["name"])
|
||||
@@ -434,8 +440,8 @@ class InterfaceDiscovery():
|
||||
if should_append: discovered_interfaces.append(info)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while loading discovered interface data: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"The interface data file {os.path.join(self.storagepath, filename)} may be corrupt", RNS.LOG_ERROR)
|
||||
RNS.log(f"Error while loading discovered interface data: {e}", RNS.LOG_WARNING)
|
||||
RNS.log(f"The interface data file {os.path.join(self.storagepath, filename)} may be corrupt", RNS.LOG_WARNING)
|
||||
RNS.trace_exception(e)
|
||||
|
||||
discovered_interfaces.sort(key=lambda info: (info["status_code"], info["value"], info["last_heard"]), reverse=True)
|
||||
@@ -453,41 +459,45 @@ class InterfaceDiscovery():
|
||||
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)
|
||||
if not os.path.isfile(filepath):
|
||||
try:
|
||||
with open(filepath, "wb") as f:
|
||||
info["discovered"] = info["received"]
|
||||
info["last_heard"] = info["received"]
|
||||
info["heard_count"] = 0
|
||||
f.write(msgpack.packb(info))
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while persisting discovered interface data: {e}", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
return
|
||||
with self.discovery_lock:
|
||||
if not os.path.isfile(filepath):
|
||||
try:
|
||||
with open(filepath, "wb") as f:
|
||||
info["discovered"] = info["received"]
|
||||
info["last_heard"] = info["received"]
|
||||
info["heard_count"] = 0
|
||||
f.write(msgpack.packb(info))
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while persisting discovered interface data: {e}", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
return
|
||||
|
||||
else:
|
||||
discovered = None
|
||||
heard_count = None
|
||||
try:
|
||||
with open(filepath, "rb") as f:
|
||||
last_info = msgpack.unpackb(f.read())
|
||||
discovered = last_info["discovered"]
|
||||
heard_count = last_info["heard_count"]
|
||||
else:
|
||||
discovered = None
|
||||
heard_count = None
|
||||
try:
|
||||
try:
|
||||
with open(filepath, "rb") as f:
|
||||
last_info = msgpack.unpackb(f.read())
|
||||
discovered = last_info["discovered"]
|
||||
heard_count = last_info["heard_count"]
|
||||
|
||||
if discovered == None: discovered = info["discovered"]
|
||||
if heard_count == None: heard_count = 0
|
||||
except Exception as e: RNS.log(f"Error while reading existing data for discovered interface, re-creating data", RNS.LOG_ERROR)
|
||||
|
||||
with open(filepath, "wb") as f:
|
||||
info["discovered"] = discovered
|
||||
info["last_heard"] = info["received"]
|
||||
info["heard_count"] = heard_count+1
|
||||
f.write(msgpack.packb(info))
|
||||
if discovered == None: discovered = info["received"]
|
||||
if heard_count == None: heard_count = 0
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while persisting discovered interface data: {e}", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
return
|
||||
with open(filepath, "wb") as f:
|
||||
info["discovered"] = discovered
|
||||
info["last_heard"] = info["received"]
|
||||
info["heard_count"] = heard_count+1
|
||||
f.write(msgpack.packb(info))
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while persisting discovered interface data: {e}", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error processing discovered interface data: {e}", RNS.LOG_ERROR)
|
||||
@@ -566,7 +576,7 @@ class InterfaceDiscovery():
|
||||
|
||||
def teardown_interface(self, interface):
|
||||
interface.detach()
|
||||
if interface in RNS.Transport.interfaces: RNS.Transport.interfaces.remove(interface)
|
||||
RNS.Transport.remove_interface(interface)
|
||||
if interface in self.monitored_interfaces: self.monitored_interfaces.remove(interface)
|
||||
|
||||
def autoconnect_count(self):
|
||||
@@ -658,10 +668,11 @@ class InterfaceDiscovery():
|
||||
RNS.log(f"Auto-connecting discovered {interface_type} {interface_name}")
|
||||
interface.autoconnect_hash = endpoint_hash
|
||||
interface.autoconnect_source = info["network_id"]
|
||||
mode = RNS.Interfaces.Interface.Interface.MODE_GATEWAY if RNS.Reticulum.transport_enabled() else None
|
||||
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, ifac_netname=ifac_netname, ifac_netkey=ifac_netkey, configured_bitrate=5E6,
|
||||
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)
|
||||
self.monitor_interface(interface)
|
||||
|
||||
|
||||
@@ -539,6 +539,11 @@ class AutoInterface(Interface):
|
||||
spawned_interface.ic_new_time = self.ic_new_time
|
||||
spawned_interface.ic_burst_penalty = self.ic_burst_penalty
|
||||
spawned_interface.ic_held_release_interval = self.ic_held_release_interval
|
||||
|
||||
spawned_interface.egress_control = self.egress_control
|
||||
spawned_interface.ec_pr_freq = self.ec_pr_freq
|
||||
spawned_interface.ic_pr_burst_freq_new = self.ic_pr_burst_freq_new
|
||||
spawned_interface.ic_pr_burst_freq = self.ic_pr_burst_freq
|
||||
|
||||
spawned_interface.parent_interface = self
|
||||
spawned_interface.bitrate = self.bitrate
|
||||
@@ -569,7 +574,7 @@ class AutoInterface(Interface):
|
||||
spawned_interface.mode = self.mode
|
||||
spawned_interface.HW_MTU = self.HW_MTU
|
||||
spawned_interface.online = True
|
||||
RNS.Transport.interfaces.append(spawned_interface)
|
||||
RNS.Transport.add_interface(spawned_interface)
|
||||
if addr in self.spawned_interfaces:
|
||||
self.spawned_interfaces[addr].detach()
|
||||
self.spawned_interfaces[addr].teardown()
|
||||
@@ -661,7 +666,7 @@ class AutoInterfacePeer(Interface):
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not remove {self} from parent interface on detach. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
|
||||
if self in RNS.Transport.interfaces: RNS.Transport.interfaces.remove(self)
|
||||
RNS.Transport.remove_interface(self)
|
||||
|
||||
class AutoInterfaceHandler(socketserver.BaseRequestHandler):
|
||||
def __init__(self, callback, *args, **keys):
|
||||
|
||||
@@ -156,6 +156,58 @@ class BackboneInterface(Interface):
|
||||
else:
|
||||
raise SystemError("Insufficient parameters to create listener")
|
||||
|
||||
|
||||
__last_ic_burst_check = 0
|
||||
__last_ic_burst_state = False
|
||||
@property
|
||||
def ic_burst_active(self):
|
||||
if time.time() > self.__last_ic_burst_check + 2:
|
||||
self.__last_ic_burst_state = any(i.ic_burst_active for i in self.spawned_interfaces)
|
||||
|
||||
return self.__last_ic_burst_state
|
||||
|
||||
@ic_burst_active.setter
|
||||
def ic_burst_active(self, value): pass
|
||||
|
||||
__ic_burst_activated_check = 0
|
||||
__ic_burst_activated = 0
|
||||
@property
|
||||
def ic_burst_activated(self):
|
||||
if time.time() > self.__ic_burst_activated_check + 2:
|
||||
activated = [i.ic_burst_activated for i in self.spawned_interfaces if i.ic_burst_active]
|
||||
if activated: self.__ic_burst_activated = min(activated)
|
||||
|
||||
return self.__ic_burst_activated
|
||||
|
||||
@ic_burst_activated.setter
|
||||
def ic_burst_activated(self, value): pass
|
||||
|
||||
|
||||
__last_ic_pr_burst_check = 0
|
||||
__last_ic_pr_burst_state = False
|
||||
@property
|
||||
def ic_pr_burst_active(self):
|
||||
if time.time() > self.__last_ic_pr_burst_check + 2:
|
||||
self.__last_ic_pr_burst_state = any(i.ic_pr_burst_active for i in self.spawned_interfaces)
|
||||
|
||||
return self.__last_ic_pr_burst_state
|
||||
|
||||
@ic_pr_burst_active.setter
|
||||
def ic_pr_burst_active(self, value): pass
|
||||
|
||||
__ic_pr_burst_activated_check = 0
|
||||
__ic_pr_burst_activated = 0
|
||||
@property
|
||||
def ic_pr_burst_activated(self):
|
||||
if time.time() > self.__ic_pr_burst_activated_check + 2:
|
||||
activated = [i.ic_pr_burst_activated for i in self.spawned_interfaces if i.ic_pr_burst_active]
|
||||
if activated: self.__ic_pr_burst_activated = min(activated)
|
||||
|
||||
return self.__ic_pr_burst_activated
|
||||
|
||||
@ic_pr_burst_activated.setter
|
||||
def ic_pr_burst_activated(self, value): pass
|
||||
|
||||
@staticmethod
|
||||
def start():
|
||||
if not BackboneInterface._job_active: threading.Thread(target=BackboneInterface.__job, daemon=True).start()
|
||||
@@ -196,17 +248,17 @@ class BackboneInterface(Interface):
|
||||
@staticmethod
|
||||
def register_in(fileno):
|
||||
if fileno < 0:
|
||||
RNS.log(f"Attempt to register invalid file descriptor {fileno}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Attempt to register invalid file descriptor {fileno}", RNS.LOG_WARNING)
|
||||
return
|
||||
|
||||
try: BackboneInterface.epoll.register(fileno, select.EPOLLIN)
|
||||
except Exception as e:
|
||||
RNS.log(f"An error occurred while registering EPOLL_IN for file descriptor {fileno}: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"An error occurred while registering EPOLL_IN for file descriptor {fileno}: {e}", RNS.LOG_WARNING)
|
||||
|
||||
@staticmethod
|
||||
def deregister_fileno(fileno):
|
||||
if fileno < 0:
|
||||
RNS.log(f"Attempt to deregister invalid file descriptor {fileno}", RNS.LOG_ERROR)
|
||||
RNS.log(f"Attempt to deregister invalid file descriptor {fileno}", RNS.LOG_DEBUG)
|
||||
return
|
||||
|
||||
try: BackboneInterface.epoll.unregister(fileno)
|
||||
@@ -288,7 +340,7 @@ class BackboneInterface(Interface):
|
||||
except Exception as e: RNS.log(f"Error while removing spawned interface from {pif}: {e}", RNS.LOG_ERROR)
|
||||
|
||||
try: client_socket.close()
|
||||
except Exception as e: RNS.log(f"Error while closing socket for {spawned_interface}: {e}", RNS.LOG_ERROR)
|
||||
except Exception as e: RNS.log(f"Error while closing socket for {spawned_interface}: {e}", RNS.LOG_WARNING)
|
||||
spawned_interface.receive(b"")
|
||||
|
||||
spawned_interface.transmit_buffer = spawned_interface.transmit_buffer[written:]
|
||||
@@ -320,18 +372,24 @@ class BackboneInterface(Interface):
|
||||
elif fileno in BackboneInterface.listener_filenos:
|
||||
owner_interface, server_socket = BackboneInterface.listener_filenos[fileno]
|
||||
if fileno == server_socket.fileno() and (event & select.EPOLLIN):
|
||||
client_socket, address = server_socket.accept()
|
||||
client_socket.setblocking(0)
|
||||
if not owner_interface.incoming_connection(client_socket):
|
||||
try:
|
||||
client_socket, address = server_socket.accept()
|
||||
client_socket.setblocking(0)
|
||||
if not owner_interface.incoming_connection(client_socket):
|
||||
try: client_socket.close()
|
||||
except Exception as e: RNS.log(f"Error while closing socket for failed incoming connection: {e}", RNS.LOG_WARNING)
|
||||
|
||||
except:
|
||||
RNS.log(f"Accepting socket failed for incoming connection: {e}", RNS.LOG_WARNING)
|
||||
try: client_socket.close()
|
||||
except Exception as e: RNS.log(f"Error while closing socket for failed incoming connection: {e}", RNS.LOG_ERROR)
|
||||
except Exception as e: RNS.log(f"Error while closing socket for failed incoming socket accept: {e}", RNS.LOG_WARNING)
|
||||
|
||||
elif fileno == server_socket.fileno() and (event & select.EPOLLHUP):
|
||||
try: BackboneInterface.deregister_fileno(fileno)
|
||||
except Exception as e: RNS.log(f"Error while deregistering listener file descriptor {fileno}: {e}", RNS.LOG_ERROR)
|
||||
|
||||
try: server_socket.close()
|
||||
except Exception as e: RNS.log(f"Error while closing listener socket for {server_socket}: {e}", RNS.LOG_ERROR)
|
||||
except Exception as e: RNS.log(f"Error while closing listener socket for {server_socket}: {e}", RNS.LOG_WARNING)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"BackboneInterface error: {e}", RNS.LOG_ERROR)
|
||||
@@ -356,6 +414,11 @@ class BackboneInterface(Interface):
|
||||
spawned_interface.ic_new_time = self.ic_new_time
|
||||
spawned_interface.ic_burst_penalty = self.ic_burst_penalty
|
||||
spawned_interface.ic_held_release_interval = self.ic_held_release_interval
|
||||
|
||||
spawned_interface.egress_control = self.egress_control
|
||||
spawned_interface.ec_pr_freq = self.ec_pr_freq
|
||||
spawned_interface.ic_pr_burst_freq_new = self.ic_pr_burst_freq_new
|
||||
spawned_interface.ic_pr_burst_freq = self.ic_pr_burst_freq
|
||||
|
||||
spawned_interface.socket = socket
|
||||
spawned_interface.target_ip = socket.getpeername()[0]
|
||||
@@ -391,7 +454,7 @@ class BackboneInterface(Interface):
|
||||
spawned_interface.HW_MTU = self.HW_MTU
|
||||
spawned_interface.online = True
|
||||
RNS.log("Spawned new BackboneClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
|
||||
RNS.Transport.interfaces.append(spawned_interface)
|
||||
RNS.Transport.add_interface(spawned_interface)
|
||||
while spawned_interface in self.spawned_interfaces: self.spawned_interfaces.remove(spawned_interface)
|
||||
self.spawned_interfaces.append(spawned_interface)
|
||||
BackboneInterface.add_client_socket(socket, spawned_interface)
|
||||
@@ -408,6 +471,12 @@ class BackboneInterface(Interface):
|
||||
def sent_announce(self, from_spawned=False):
|
||||
if from_spawned: self.oa_freq_deque.append(time.time())
|
||||
|
||||
def received_path_request(self, from_spawned=False):
|
||||
if from_spawned: self.ip_freq_deque.append(time.time())
|
||||
|
||||
def sent_path_request(self, from_spawned=False):
|
||||
if from_spawned: self.op_freq_deque.append(time.time())
|
||||
|
||||
def process_outgoing(self, data):
|
||||
pass
|
||||
|
||||
@@ -578,8 +647,8 @@ class BackboneClientInterface(Interface):
|
||||
|
||||
except Exception as e:
|
||||
if initial:
|
||||
RNS.log("Initial connection for "+str(self)+" could not be established: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("Leaving unconnected and retrying connection in "+str(BackboneClientInterface.RECONNECT_WAIT)+" seconds.", RNS.LOG_ERROR)
|
||||
RNS.log("Initial connection for "+str(self)+" could not be established: "+str(e), RNS.LOG_WARNING)
|
||||
RNS.log("Leaving unconnected and retrying connection in "+str(BackboneClientInterface.RECONNECT_WAIT)+" seconds.", RNS.LOG_WARNING)
|
||||
return False
|
||||
|
||||
else:
|
||||
@@ -602,7 +671,7 @@ class BackboneClientInterface(Interface):
|
||||
attempts += 1
|
||||
|
||||
if self.max_reconnect_tries != None and attempts > self.max_reconnect_tries:
|
||||
RNS.log("Max reconnection attempts reached for "+str(self), RNS.LOG_ERROR)
|
||||
RNS.log("Max reconnection attempts reached for "+str(self), RNS.LOG_WARNING)
|
||||
self.teardown()
|
||||
break
|
||||
|
||||
@@ -700,9 +769,8 @@ class BackboneClientInterface(Interface):
|
||||
while self in self.parent_interface.spawned_interfaces:
|
||||
self.parent_interface.spawned_interfaces.remove(self)
|
||||
|
||||
if self in RNS.Transport.interfaces:
|
||||
if not self.initiator:
|
||||
RNS.Transport.interfaces.remove(self)
|
||||
if not self.initiator:
|
||||
RNS.Transport.remove_interface(self)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -826,9 +826,8 @@ class I2PInterfacePeer(Interface):
|
||||
while self in self.parent_interface.spawned_interfaces:
|
||||
self.parent_interface.spawned_interfaces.remove(self)
|
||||
|
||||
if self in RNS.Transport.interfaces:
|
||||
if not self.initiator:
|
||||
RNS.Transport.interfaces.remove(self)
|
||||
if not self.initiator:
|
||||
RNS.Transport.remove_interface(self)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
@@ -940,7 +939,7 @@ class I2PInterface(Interface):
|
||||
peer_interface.IN = True
|
||||
peer_interface.parent_interface = self
|
||||
peer_interface.parent_count = False
|
||||
RNS.Transport.interfaces.append(peer_interface)
|
||||
RNS.Transport.add_interface(peer_interface)
|
||||
|
||||
def incoming_connection(self, handler):
|
||||
RNS.log("Accepting incoming I2P connection", RNS.LOG_VERBOSE)
|
||||
@@ -957,6 +956,11 @@ class I2PInterface(Interface):
|
||||
spawned_interface.ic_new_time = self.ic_new_time
|
||||
spawned_interface.ic_burst_penalty = self.ic_burst_penalty
|
||||
spawned_interface.ic_held_release_interval = self.ic_held_release_interval
|
||||
|
||||
spawned_interface.egress_control = self.egress_control
|
||||
spawned_interface.ec_pr_freq = self.ec_pr_freq
|
||||
spawned_interface.ic_pr_burst_freq_new = self.ic_pr_burst_freq_new
|
||||
spawned_interface.ic_pr_burst_freq = self.ic_pr_burst_freq
|
||||
|
||||
spawned_interface.parent_interface = self
|
||||
spawned_interface.online = True
|
||||
@@ -988,7 +992,7 @@ class I2PInterface(Interface):
|
||||
spawned_interface.mode = self.mode
|
||||
spawned_interface.HW_MTU = self.HW_MTU
|
||||
RNS.log("Spawned new I2PInterface Peer: "+str(spawned_interface), RNS.LOG_VERBOSE)
|
||||
RNS.Transport.interfaces.append(spawned_interface)
|
||||
RNS.Transport.add_interface(spawned_interface)
|
||||
while spawned_interface in self.spawned_interfaces:
|
||||
self.spawned_interfaces.remove(spawned_interface)
|
||||
self.spawned_interfaces.append(spawned_interface)
|
||||
@@ -1003,6 +1007,12 @@ class I2PInterface(Interface):
|
||||
def sent_announce(self, from_spawned=False):
|
||||
if from_spawned: self.oa_freq_deque.append(time.time())
|
||||
|
||||
def received_path_request(self, from_spawned=False):
|
||||
if from_spawned: self.ip_freq_deque.append(time.time())
|
||||
|
||||
def sent_path_request(self, from_spawned=False):
|
||||
if from_spawned: self.op_freq_deque.append(time.time())
|
||||
|
||||
def detach(self):
|
||||
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
|
||||
self.i2p.stop()
|
||||
|
||||
+115
-29
@@ -55,8 +55,15 @@ class Interface:
|
||||
|
||||
# How many samples to use for announce
|
||||
# frequency calculations
|
||||
IA_FREQ_SAMPLES = 128
|
||||
OA_FREQ_SAMPLES = 128
|
||||
IA_FREQ_SAMPLES = 48
|
||||
OA_FREQ_SAMPLES = 48
|
||||
IP_FREQ_SAMPLES = 48
|
||||
OP_FREQ_SAMPLES = 48
|
||||
|
||||
AR_MINFREQ_HZ = 0.1
|
||||
PR_MINFREQ_HZ = 0.1
|
||||
AR_FREQ_DECAY = 1/AR_MINFREQ_HZ
|
||||
PR_FREQ_DECAY = 1/PR_MINFREQ_HZ
|
||||
|
||||
# Maximum amount of ingress limited announces
|
||||
# to hold at any given time.
|
||||
@@ -66,12 +73,17 @@ class Interface:
|
||||
# considered to be newly created. Two
|
||||
# hours by default.
|
||||
IC_NEW_TIME = 2*60*60
|
||||
IC_BURST_FREQ_NEW = 6
|
||||
IC_BURST_FREQ = 35
|
||||
IC_BURST_HOLD = 1*60
|
||||
IC_BURST_FREQ_NEW = 3
|
||||
IC_BURST_FREQ = 10
|
||||
IC_PR_BURST_FREQ_NEW = 3
|
||||
IC_PR_BURST_FREQ = 8
|
||||
IC_BURST_HOLD = 15
|
||||
IC_BURST_PENALTY = 15
|
||||
IC_HELD_RELEASE_INTERVAL = 2
|
||||
IC_DEQUE_MIN_SAMPLE = 32
|
||||
IC_HELD_RELEASE_INTERVAL = 5
|
||||
IC_DEQUE_MIN_SAMPLE = 2
|
||||
IC_BURST_MIN_SAMPLES = 6
|
||||
EC_PR_FREQ = 5
|
||||
EGRESS_CONTROL = False
|
||||
|
||||
# Default announce rate targets
|
||||
DEFAULT_AR_TARGET = 3600
|
||||
@@ -90,29 +102,38 @@ class Interface:
|
||||
self.bitrate = 62500
|
||||
self.HW_MTU = None
|
||||
|
||||
self.supports_discovery = False
|
||||
self.discoverable = False
|
||||
self.last_discovery_announce = 0
|
||||
self.bootstrap_only = False
|
||||
self.parent_interface = None
|
||||
self.spawned_interfaces = None
|
||||
self.tunnel_id = None
|
||||
self.ingress_control = True
|
||||
self.phy_keepalive = False
|
||||
self.ic_max_held_announces = Interface.MAX_HELD_ANNOUNCES
|
||||
self.ic_burst_hold = Interface.IC_BURST_HOLD
|
||||
self.ic_burst_active = False
|
||||
self.ic_burst_activated = 0
|
||||
self.ic_held_release = 0
|
||||
self.ic_burst_freq_new = Interface.IC_BURST_FREQ_NEW
|
||||
self.ic_burst_freq = Interface.IC_BURST_FREQ
|
||||
self.ic_new_time = Interface.IC_NEW_TIME
|
||||
self.ic_burst_penalty = Interface.IC_BURST_PENALTY
|
||||
self.ic_held_release_interval = Interface.IC_HELD_RELEASE_INTERVAL
|
||||
self.held_announces = {}
|
||||
self.supports_discovery = False
|
||||
self.discoverable = False
|
||||
self.last_discovery_announce = 0
|
||||
self.bootstrap_only = False
|
||||
self.parent_interface = None
|
||||
self.spawned_interfaces = None
|
||||
self.tunnel_id = None
|
||||
self.ingress_control = True
|
||||
self.phy_keepalive = False
|
||||
|
||||
self.ic_burst_active = False
|
||||
self.ic_burst_activated = 0
|
||||
self.ic_pr_burst_active = False
|
||||
self.ic_pr_burst_activated = 0
|
||||
self.ic_held_release = 0
|
||||
self.ic_max_held_announces = RNS.Reticulum.get_instance()._default_ic_max_held_announces()
|
||||
self.ic_burst_hold = RNS.Reticulum.get_instance()._default_ic_burst_hold()
|
||||
self.ic_burst_freq_new = RNS.Reticulum.get_instance()._default_ic_burst_freq_new()
|
||||
self.ic_burst_freq = RNS.Reticulum.get_instance()._default_ic_burst_freq()
|
||||
self.ic_pr_burst_freq_new = RNS.Reticulum.get_instance()._default_ic_pr_burst_freq_new()
|
||||
self.ic_pr_burst_freq = RNS.Reticulum.get_instance()._default_ic_pr_burst_freq()
|
||||
self.ic_new_time = RNS.Reticulum.get_instance()._default_ic_new_time()
|
||||
self.ic_burst_penalty = RNS.Reticulum.get_instance()._default_ic_burst_penalty()
|
||||
self.ic_held_release_interval = RNS.Reticulum.get_instance()._default_ic_held_release_interval()
|
||||
self.ec_pr_freq = RNS.Reticulum.get_instance()._default_ec_pr_freq()
|
||||
self.egress_control = RNS.Reticulum.get_instance()._default_egress_control()
|
||||
self.held_announces = {}
|
||||
|
||||
self.ia_freq_deque = deque(maxlen=Interface.IA_FREQ_SAMPLES)
|
||||
self.oa_freq_deque = deque(maxlen=Interface.OA_FREQ_SAMPLES)
|
||||
self.ip_freq_deque = deque(maxlen=Interface.IA_FREQ_SAMPLES)
|
||||
self.op_freq_deque = deque(maxlen=Interface.OA_FREQ_SAMPLES)
|
||||
|
||||
def get_hash(self):
|
||||
return RNS.Identity.full_hash(str(self).encode("utf-8"))
|
||||
@@ -128,7 +149,7 @@ class Interface:
|
||||
|
||||
if self.ic_burst_active:
|
||||
if ia_freq < freq_threshold and time.time() > self.ic_burst_activated+self.ic_burst_hold:
|
||||
self.ic_burst_active = False
|
||||
if len(self.ia_freq_deque) >= self.IC_BURST_MIN_SAMPLES: self.ic_burst_active = False
|
||||
|
||||
return True
|
||||
|
||||
@@ -143,6 +164,37 @@ class Interface:
|
||||
|
||||
else: return False
|
||||
|
||||
def should_ingress_limit_pr(self):
|
||||
if self.ingress_control:
|
||||
freq_threshold = self.ic_pr_burst_freq_new if self.age() < self.ic_new_time else self.ic_pr_burst_freq
|
||||
ip_freq = self.incoming_pr_frequency()
|
||||
|
||||
if self.ic_pr_burst_active:
|
||||
if ip_freq < freq_threshold and time.time() > self.ic_pr_burst_activated+self.ic_burst_hold:
|
||||
self.ic_pr_burst_active = False
|
||||
|
||||
return True
|
||||
|
||||
else:
|
||||
if ip_freq > freq_threshold:
|
||||
self.ic_pr_burst_active = True
|
||||
self.ic_pr_burst_activated = time.time()
|
||||
return True
|
||||
|
||||
else: return False
|
||||
|
||||
else: return False
|
||||
|
||||
def should_egress_limit_pr(self):
|
||||
if self.egress_control:
|
||||
freq_threshold = self.ec_pr_freq
|
||||
op_freq = self.outgoing_pr_frequency()
|
||||
|
||||
if op_freq > freq_threshold:
|
||||
if len(self.op_freq_deque) >= self.IC_BURST_MIN_SAMPLES: return True
|
||||
|
||||
return False
|
||||
|
||||
def optimise_mtu(self):
|
||||
if self.AUTOCONFIGURE_MTU:
|
||||
if self.bitrate >= 1_000_000_000:
|
||||
@@ -168,7 +220,7 @@ class Interface:
|
||||
else:
|
||||
self.HW_MTU = None
|
||||
|
||||
RNS.log(f"{self} hardware MTU set to {self.HW_MTU}", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
RNS.log(f"{self} hardware MTU set to {self.HW_MTU}", RNS.LOG_DEBUG)
|
||||
|
||||
def age(self):
|
||||
return time.time()-self.created
|
||||
@@ -214,12 +266,23 @@ class Interface:
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.sent_announce(from_spawned=True)
|
||||
|
||||
def received_path_request(self, from_spawned=False):
|
||||
self.ip_freq_deque.append(time.time())
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.received_path_request(from_spawned=True)
|
||||
|
||||
def sent_path_request(self, from_spawned=False):
|
||||
self.op_freq_deque.append(time.time())
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.sent_path_request(from_spawned=True)
|
||||
|
||||
def incoming_announce_frequency(self):
|
||||
n = len(self.ia_freq_deque)
|
||||
if not n > self.IC_DEQUE_MIN_SAMPLE: return 0
|
||||
else:
|
||||
oldest = self.ia_freq_deque[0]
|
||||
span = time.time() - oldest
|
||||
if span > self.AR_FREQ_DECAY: self.ia_freq_deque.popleft()
|
||||
if span <= 0: return 0
|
||||
hz = n / span
|
||||
return hz
|
||||
@@ -230,6 +293,29 @@ class Interface:
|
||||
else:
|
||||
oldest = self.oa_freq_deque[0]
|
||||
span = time.time() - oldest
|
||||
if span > self.AR_FREQ_DECAY: self.oa_freq_deque.popleft()
|
||||
if span <= 0: return 0
|
||||
hz = n / span
|
||||
return hz
|
||||
|
||||
def incoming_pr_frequency(self):
|
||||
n = len(self.ip_freq_deque)
|
||||
if not n > self.IC_DEQUE_MIN_SAMPLE: return 0
|
||||
else:
|
||||
oldest = self.ip_freq_deque[0]
|
||||
span = time.time() - oldest
|
||||
if span > self.PR_FREQ_DECAY: self.ip_freq_deque.popleft()
|
||||
if span <= 0: return 0
|
||||
hz = n / span
|
||||
return hz
|
||||
|
||||
def outgoing_pr_frequency(self):
|
||||
n = len(self.op_freq_deque)
|
||||
if not len(self.op_freq_deque) > 1: return 0
|
||||
else:
|
||||
oldest = self.op_freq_deque[0]
|
||||
span = time.time() - oldest
|
||||
if span > self.PR_FREQ_DECAY: self.op_freq_deque.popleft()
|
||||
if span <= 0: return 0
|
||||
hz = n / span
|
||||
return hz
|
||||
|
||||
@@ -347,8 +347,7 @@ class LocalClientInterface(Interface):
|
||||
self.OUT = False
|
||||
self.IN = False
|
||||
|
||||
if self in RNS.Transport.interfaces:
|
||||
RNS.Transport.interfaces.remove(self)
|
||||
RNS.Transport.remove_interface(self)
|
||||
|
||||
if self in RNS.Transport.local_client_interfaces:
|
||||
RNS.Transport.local_client_interfaces.remove(self)
|
||||
@@ -458,7 +457,7 @@ class LocalServerInterface(Interface):
|
||||
spawned_interface.socket_path = self.socket_path
|
||||
|
||||
if hasattr(self, "_force_bitrate"): spawned_interface._force_bitrate = self._force_bitrate
|
||||
RNS.Transport.interfaces.append(spawned_interface)
|
||||
RNS.Transport.add_interface(spawned_interface)
|
||||
RNS.Transport.local_client_interfaces.append(spawned_interface)
|
||||
BackboneInterface.add_client_socket(client_socket, spawned_interface)
|
||||
self.clients += 1
|
||||
@@ -474,7 +473,7 @@ class LocalServerInterface(Interface):
|
||||
spawned_interface.parent_interface = self
|
||||
spawned_interface.bitrate = self.bitrate
|
||||
if hasattr(self, "_force_bitrate"): spawned_interface._force_bitrate = self._force_bitrate
|
||||
RNS.Transport.interfaces.append(spawned_interface)
|
||||
RNS.Transport.add_interface(spawned_interface)
|
||||
RNS.Transport.local_client_interfaces.append(spawned_interface)
|
||||
self.clients += 1
|
||||
spawned_interface.read_loop()
|
||||
@@ -488,6 +487,12 @@ class LocalServerInterface(Interface):
|
||||
def sent_announce(self, from_spawned=False):
|
||||
if from_spawned: self.oa_freq_deque.append(time.time())
|
||||
|
||||
def received_path_request(self, from_spawned=False):
|
||||
if from_spawned: self.ip_freq_deque.append(time.time())
|
||||
|
||||
def sent_path_request(self, from_spawned=False):
|
||||
if from_spawned: self.op_freq_deque.append(time.time())
|
||||
|
||||
def __str__(self):
|
||||
if self.socket_path: return "Shared Instance["+str(self.socket_path.replace("\0", ""))+"]"
|
||||
else: return "Shared Instance["+str(self.bind_port)+"]"
|
||||
|
||||
@@ -375,7 +375,7 @@ class RNodeMultiInterface(Interface):
|
||||
interface.mode = self.mode
|
||||
interface.HW_MTU = self.HW_MTU
|
||||
interface.detected = True
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
RNS.Transport.add_interface(interface)
|
||||
RNS.log("Spawned new RNode subinterface: "+str(interface), RNS.LOG_VERBOSE)
|
||||
|
||||
self.clients += 1
|
||||
@@ -549,6 +549,12 @@ class RNodeMultiInterface(Interface):
|
||||
def sent_announce(self, from_spawned=False):
|
||||
if from_spawned: self.oa_freq_deque.append(time.time())
|
||||
|
||||
def received_path_request(self, from_spawned=False):
|
||||
if from_spawned: self.ip_freq_deque.append(time.time())
|
||||
|
||||
def sent_path_request(self, from_spawned=False):
|
||||
if from_spawned: self.op_freq_deque.append(time.time())
|
||||
|
||||
def readLoop(self):
|
||||
try:
|
||||
in_frame = False
|
||||
@@ -903,8 +909,7 @@ class RNodeMultiInterface(Interface):
|
||||
def teardown_subinterfaces(self):
|
||||
for interface in self.subinterfaces:
|
||||
if interface != 0:
|
||||
if interface in RNS.Transport.interfaces:
|
||||
RNS.Transport.interfaces.remove(interface)
|
||||
RNS.Transport.remove_interface(interface)
|
||||
self.subinterfaces[interface.index] = 0
|
||||
|
||||
def should_ingress_limit(self):
|
||||
|
||||
@@ -436,9 +436,8 @@ class TCPClientInterface(Interface):
|
||||
while self in self.parent_interface.spawned_interfaces:
|
||||
self.parent_interface.spawned_interfaces.remove(self)
|
||||
|
||||
if self in RNS.Transport.interfaces:
|
||||
if not self.initiator:
|
||||
RNS.Transport.interfaces.remove(self)
|
||||
if not self.initiator:
|
||||
RNS.Transport.remove_interface(self)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
@@ -589,6 +588,11 @@ class TCPServerInterface(Interface):
|
||||
spawned_interface.ic_burst_penalty = self.ic_burst_penalty
|
||||
spawned_interface.ic_held_release_interval = self.ic_held_release_interval
|
||||
|
||||
spawned_interface.egress_control = self.egress_control
|
||||
spawned_interface.ec_pr_freq = self.ec_pr_freq
|
||||
spawned_interface.ic_pr_burst_freq_new = self.ic_pr_burst_freq_new
|
||||
spawned_interface.ic_pr_burst_freq = self.ic_pr_burst_freq
|
||||
|
||||
spawned_interface.target_ip = handler.client_address[0]
|
||||
spawned_interface.target_port = str(handler.client_address[1])
|
||||
spawned_interface.parent_interface = self
|
||||
@@ -622,7 +626,7 @@ class TCPServerInterface(Interface):
|
||||
spawned_interface.HW_MTU = self.HW_MTU
|
||||
spawned_interface.online = True
|
||||
RNS.log("Spawned new TCPClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
|
||||
RNS.Transport.interfaces.append(spawned_interface)
|
||||
RNS.Transport.add_interface(spawned_interface)
|
||||
while spawned_interface in self.spawned_interfaces:
|
||||
self.spawned_interfaces.remove(spawned_interface)
|
||||
self.spawned_interfaces.append(spawned_interface)
|
||||
@@ -634,6 +638,12 @@ class TCPServerInterface(Interface):
|
||||
def sent_announce(self, from_spawned=False):
|
||||
if from_spawned: self.oa_freq_deque.append(time.time())
|
||||
|
||||
def received_path_request(self, from_spawned=False):
|
||||
if from_spawned: self.ip_freq_deque.append(time.time())
|
||||
|
||||
def sent_path_request(self, from_spawned=False):
|
||||
if from_spawned: self.op_freq_deque.append(time.time())
|
||||
|
||||
def process_outgoing(self, data):
|
||||
pass
|
||||
|
||||
|
||||
@@ -981,7 +981,7 @@ class WeaveInterface(Interface):
|
||||
spawned_interface.mode = self.mode
|
||||
spawned_interface.HW_MTU = self.HW_MTU
|
||||
spawned_interface._online = True
|
||||
RNS.Transport.interfaces.append(spawned_interface)
|
||||
RNS.Transport.add_interface(spawned_interface)
|
||||
if endpoint_addr in self.spawned_interfaces:
|
||||
self.spawned_interfaces[endpoint_addr].detach()
|
||||
self.spawned_interfaces[endpoint_addr].teardown()
|
||||
@@ -1097,5 +1097,4 @@ class WeaveInterfacePeer(Interface):
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not remove {self} from parent interface on detach. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
|
||||
if self in RNS.Transport.interfaces:
|
||||
RNS.Transport.interfaces.remove(self)
|
||||
RNS.Transport.remove_interface(self)
|
||||
|
||||
+2
-1
@@ -117,7 +117,7 @@ class Packet:
|
||||
__slots__ = "hops", "header", "header_type", "packet_type", "transport_type", "context", "context_flag", "destination"
|
||||
__slots__ += "transport_id", "data", "flags", "raw", "packed", "sent", "create_receipt", "receipt", "fromPacked", "MTU"
|
||||
__slots__ += "sent_at", "packet_hash", "ratchet_id", "attached_interface", "receiving_interface", "rssi", "snr", "q"
|
||||
__slots__ += "ciphertext", "plaintext", "destination_hash", "destination_type", "link", "map_hash"
|
||||
__slots__ += "ciphertext", "plaintext", "destination_hash", "destination_type", "link", "map_hash", "is_outbound_pr"
|
||||
|
||||
def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST,
|
||||
header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True, context_flag=FLAG_UNSET):
|
||||
@@ -161,6 +161,7 @@ class Packet:
|
||||
|
||||
self.attached_interface = attached_interface
|
||||
self.receiving_interface = None
|
||||
self.is_outbound_pr = False
|
||||
self.rssi = None
|
||||
self.snr = None
|
||||
self.q = None
|
||||
|
||||
+162
-35
@@ -249,22 +249,33 @@ class Reticulum:
|
||||
Reticulum.blackholepath = Reticulum.configdir+"/storage/blackhole"
|
||||
Reticulum.interfacepath = Reticulum.configdir+"/interfaces"
|
||||
|
||||
Reticulum.__network_identity = None
|
||||
Reticulum.__transport_enabled = False
|
||||
Reticulum.__link_mtu_discovery = Reticulum.LINK_MTU_DISCOVERY
|
||||
Reticulum.__remote_management_enabled = False
|
||||
Reticulum.__use_implicit_proof = True
|
||||
Reticulum.__allow_probes = False
|
||||
Reticulum.__discovery_enabled = False
|
||||
Reticulum.__discover_interfaces = False
|
||||
Reticulum.__network_identity = None
|
||||
Reticulum.__transport_enabled = False
|
||||
Reticulum.__link_mtu_discovery = Reticulum.LINK_MTU_DISCOVERY
|
||||
Reticulum.__remote_management_enabled = False
|
||||
Reticulum.__use_implicit_proof = True
|
||||
Reticulum.__allow_probes = False
|
||||
Reticulum.__discovery_enabled = False
|
||||
Reticulum.__discover_interfaces = False
|
||||
Reticulum.__autoconnect_discovered_interfaces = False
|
||||
Reticulum.__required_discovery_value = None
|
||||
Reticulum.__publish_blackhole = False
|
||||
Reticulum.__blackhole_sources = []
|
||||
Reticulum.__interface_sources = []
|
||||
Reticulum.__default_ar_target = None
|
||||
Reticulum.__default_ar_penalty = None
|
||||
Reticulum.__default_ar_grace = None
|
||||
Reticulum.__required_discovery_value = None
|
||||
Reticulum.__publish_blackhole = False
|
||||
Reticulum.__blackhole_sources = []
|
||||
Reticulum.__interface_sources = []
|
||||
Reticulum.__default_ar_target = None
|
||||
Reticulum.__default_ar_penalty = None
|
||||
Reticulum.__default_ar_grace = None
|
||||
Reticulum.__ic_max_held_announces = None
|
||||
Reticulum.__ic_burst_hold = None
|
||||
Reticulum.__ic_burst_freq_new = None
|
||||
Reticulum.__ic_burst_freq = None
|
||||
Reticulum.__ic_pr_burst_freq_new = None
|
||||
Reticulum.__ic_pr_burst_freq = None
|
||||
Reticulum.__ic_new_time = None
|
||||
Reticulum.__ic_burst_penalty = None
|
||||
Reticulum.__ic_held_release_interval = None
|
||||
Reticulum.__ec_pr_freq = None
|
||||
Reticulum.__egress_control = None
|
||||
|
||||
Reticulum.panic_on_interface_error = False
|
||||
|
||||
@@ -391,7 +402,7 @@ class Reticulum:
|
||||
RNS.log("Existing shared instance required, but this instance started as shared instance. Aborting startup.", RNS.LOG_VERBOSE)
|
||||
|
||||
else:
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
RNS.Transport.add_interface(interface)
|
||||
self.shared_instance_interface = interface
|
||||
self.is_shared_instance = True
|
||||
RNS.log("Started shared instance interface: "+str(interface), RNS.LOG_DEBUG)
|
||||
@@ -411,7 +422,7 @@ class Reticulum:
|
||||
interface._force_bitrate = True
|
||||
RNS.log(f"Forcing shared instance bitrate of {RNS.prettyspeed(interface.bitrate)}", RNS.LOG_WARNING)
|
||||
interface.optimise_mtu()
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
RNS.Transport.add_interface(interface)
|
||||
self.is_shared_instance = False
|
||||
self.is_standalone_instance = False
|
||||
self.is_connected_to_shared_instance = True
|
||||
@@ -596,6 +607,51 @@ class Reticulum:
|
||||
v = self.config["reticulum"].as_int(option)
|
||||
if v >= 0: Reticulum.__default_ar_grace = v
|
||||
|
||||
if option == "ic_max_held_announces":
|
||||
v = self.config["reticulum"].as_int(option)
|
||||
if v >= 0: Reticulum.__ic_max_held_announces = v
|
||||
|
||||
if option == "ic_burst_hold":
|
||||
v = self.config["reticulum"].as_float(option)
|
||||
if v >= 0: Reticulum.__ic_burst_hold = v
|
||||
|
||||
if option == "ic_burst_freq_new":
|
||||
v = self.config["reticulum"].as_float(option)
|
||||
if v >= 0: Reticulum.__ic_burst_freq_new = v
|
||||
|
||||
if option == "ic_burst_freq":
|
||||
v = self.config["reticulum"].as_float(option)
|
||||
if v >= 0: Reticulum.__ic_burst_freq = v
|
||||
|
||||
if option == "ic_pr_burst_freq_new":
|
||||
v = self.config["reticulum"].as_float(option)
|
||||
if v >= 0: Reticulum.__ic_pr_burst_freq_new = v
|
||||
|
||||
if option == "ic_pr_burst_freq":
|
||||
v = self.config["reticulum"].as_float(option)
|
||||
if v >= 0: Reticulum.__ic_pr_burst_freq = v
|
||||
|
||||
if option == "ec_pr_freq":
|
||||
v = self.config["reticulum"].as_float(option)
|
||||
if v >= 0: Reticulum.__ec_pr_freq = v
|
||||
|
||||
if option == "egress_control":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v >= 0: Reticulum.__egress_control = v
|
||||
|
||||
if option == "ic_new_time":
|
||||
v = self.config["reticulum"].as_float(option)
|
||||
if v >= 0: Reticulum.__ic_new_time = v
|
||||
|
||||
if option == "ic_burst_penalty":
|
||||
v = self.config["reticulum"].as_float(option)
|
||||
if v >= 0: Reticulum.__ic_burst_penalty = v
|
||||
|
||||
if option == "ic_held_release_interval":
|
||||
v = self.config["reticulum"].as_float(option)
|
||||
if v >= 0: Reticulum.__ic_held_release_interval = v
|
||||
|
||||
|
||||
if RNS.compiled: RNS.log("Reticulum running in compiled mode", RNS.LOG_DEBUG)
|
||||
else: RNS.log("Reticulum running in interpreted mode", RNS.LOG_DEBUG)
|
||||
|
||||
@@ -683,6 +739,8 @@ class Reticulum:
|
||||
|
||||
ingress_control = True
|
||||
if "ingress_control" in c: ingress_control = c.as_bool("ingress_control")
|
||||
egress_control = None
|
||||
if "egress_control" in c: egress_control = c.as_bool("egress_control")
|
||||
ic_max_held_announces = None
|
||||
if "ic_max_held_announces" in c: ic_max_held_announces = c.as_int("ic_max_held_announces")
|
||||
ic_burst_hold = None
|
||||
@@ -691,6 +749,12 @@ class Reticulum:
|
||||
if "ic_burst_freq_new" in c: ic_burst_freq_new = c.as_float("ic_burst_freq_new")
|
||||
ic_burst_freq = None
|
||||
if "ic_burst_freq" in c: ic_burst_freq = c.as_float("ic_burst_freq")
|
||||
ic_pr_burst_freq_new = None
|
||||
if "ic_pr_burst_freq_new" in c: ic_pr_burst_freq_new = c.as_float("ic_pr_burst_freq_new")
|
||||
ic_pr_burst_freq = None
|
||||
if "ic_pr_burst_freq" in c: ic_pr_burst_freq = c.as_float("ic_pr_burst_freq")
|
||||
ec_pr_freq = None
|
||||
if "ec_pr_freq" in c: ec_pr_freq = c.as_float("ec_pr_freq")
|
||||
ic_new_time = None
|
||||
if "ic_new_time" in c: ic_new_time = c.as_float("ic_new_time")
|
||||
ic_burst_penalty = None
|
||||
@@ -816,10 +880,14 @@ class Reticulum:
|
||||
interface.announce_rate_grace = announce_rate_grace
|
||||
interface.announce_rate_penalty = announce_rate_penalty
|
||||
interface.ingress_control = ingress_control
|
||||
if egress_control != None: interface.egress_control = egress_control
|
||||
if ic_max_held_announces != None: interface.ic_max_held_announces = ic_max_held_announces
|
||||
if ic_burst_hold != None: interface.ic_burst_hold = ic_burst_hold
|
||||
if ic_burst_freq_new != None: interface.ic_burst_freq_new = ic_burst_freq_new
|
||||
if ic_burst_freq != None: interface.ic_burst_freq = ic_burst_freq
|
||||
if ic_pr_burst_freq_new != None: interface.ic_pr_burst_freq_new = ic_pr_burst_freq_new
|
||||
if ic_pr_burst_freq != None: interface.ic_pr_burst_freq = ic_pr_burst_freq
|
||||
if ec_pr_freq != None: interface.ec_pr_freq = ec_pr_freq
|
||||
if ic_new_time != None: interface.ic_new_time = ic_new_time
|
||||
if ic_burst_penalty != None: interface.ic_burst_penalty = ic_burst_penalty
|
||||
if ic_held_release_interval != None: interface.ic_held_release_interval = ic_held_release_interval
|
||||
@@ -847,7 +915,7 @@ class Reticulum:
|
||||
interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key)
|
||||
interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key))
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
RNS.Transport.add_interface(interface)
|
||||
interface.final_init()
|
||||
|
||||
interface = None
|
||||
@@ -1009,7 +1077,7 @@ class Reticulum:
|
||||
interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key)
|
||||
interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key))
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
RNS.Transport.add_interface(interface)
|
||||
interface.final_init()
|
||||
|
||||
def _default_ar_target(self):
|
||||
@@ -1021,6 +1089,39 @@ class Reticulum:
|
||||
def _default_ar_grace(self):
|
||||
return self.__default_ar_grace or RNS.Interfaces.Interface.Interface.DEFAULT_AR_GRACE
|
||||
|
||||
def _default_ic_max_held_announces(self):
|
||||
return self.__ic_max_held_announces or RNS.Interfaces.Interface.Interface.MAX_HELD_ANNOUNCES
|
||||
|
||||
def _default_ic_burst_hold(self):
|
||||
return self.__ic_burst_hold or RNS.Interfaces.Interface.Interface.IC_BURST_HOLD
|
||||
|
||||
def _default_ic_burst_freq_new(self):
|
||||
return self.__ic_burst_freq_new or RNS.Interfaces.Interface.Interface.IC_BURST_FREQ_NEW
|
||||
|
||||
def _default_ic_burst_freq(self):
|
||||
return self.__ic_burst_freq or RNS.Interfaces.Interface.Interface.IC_BURST_FREQ
|
||||
|
||||
def _default_ic_pr_burst_freq_new(self):
|
||||
return self.__ic_pr_burst_freq_new or RNS.Interfaces.Interface.Interface.IC_PR_BURST_FREQ_NEW
|
||||
|
||||
def _default_ic_pr_burst_freq(self):
|
||||
return self.__ic_pr_burst_freq or RNS.Interfaces.Interface.Interface.IC_PR_BURST_FREQ
|
||||
|
||||
def _default_ec_pr_freq(self):
|
||||
return self.__ec_pr_freq or RNS.Interfaces.Interface.Interface.EC_PR_FREQ
|
||||
|
||||
def _default_egress_control(self):
|
||||
return self.__egress_control or RNS.Interfaces.Interface.Interface.EGRESS_CONTROL
|
||||
|
||||
def _default_ic_new_time(self):
|
||||
return self.__ic_new_time or RNS.Interfaces.Interface.Interface.IC_NEW_TIME
|
||||
|
||||
def _default_ic_burst_penalty(self):
|
||||
return self.__ic_burst_penalty or RNS.Interfaces.Interface.Interface.IC_BURST_PENALTY
|
||||
|
||||
def _default_ic_held_release_interval(self):
|
||||
return self.__ic_held_release_interval or RNS.Interfaces.Interface.Interface.IC_HELD_RELEASE_INTERVAL
|
||||
|
||||
def _should_persist_data(self, background=False):
|
||||
if time.time() > self.last_data_persist+Reticulum.GRACIOUS_PERSIST_INTERVAL:
|
||||
def job(): self.__persist_data(background=background)
|
||||
@@ -1133,28 +1234,43 @@ class Reticulum:
|
||||
|
||||
def _used_destination_data(self, destination_hash):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send({"destination_data": "used", "destination_hash": destination_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
try:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send({"destination_data": "used", "destination_hash": destination_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Shared instance RPC failed while setting destination data use: {e}", RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
else: return RNS.Identity._used_destination_data(destination_hash)
|
||||
|
||||
def _retain_destination_data(self, destination_hash):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send({"destination_data": "retain", "destination_hash": destination_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
try:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send({"destination_data": "retain", "destination_hash": destination_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Shared instance RPC failed while retaining destination data: {e}", RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
else: return RNS.Identity._retain_destination_data(destination_hash)
|
||||
|
||||
def _unretain_destination_data(self, destination_hash):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send({"destination_data": "unretain", "destination_hash": destination_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
try:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send({"destination_data": "unretain", "destination_hash": destination_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Shared instance RPC failed while unretaining destination data: {e}", RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
else: return RNS.Identity._unretain_destination_data(destination_hash)
|
||||
|
||||
@@ -1163,10 +1279,15 @@ class Reticulum:
|
||||
raise TypeError("Cannot retain identity, not a valid identity hash")
|
||||
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send({"identity_data": "retain", "identity_hash": identity_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
try:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send({"identity_data": "retain", "identity_hash": identity_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Shared instance RPC failed while retaining identity: {e}", RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
else: return RNS.Identity._retain_identity(identity_hash)
|
||||
|
||||
@@ -1321,10 +1442,16 @@ class Reticulum:
|
||||
ifstats["txb"] = interface.txb
|
||||
ifstats["incoming_announce_frequency"] = interface.incoming_announce_frequency()
|
||||
ifstats["outgoing_announce_frequency"] = interface.outgoing_announce_frequency()
|
||||
ifstats["incoming_pr_frequency"] = interface.incoming_pr_frequency()
|
||||
ifstats["outgoing_pr_frequency"] = interface.outgoing_pr_frequency()
|
||||
ifstats["announce_rate_target"] = interface.announce_rate_target
|
||||
ifstats["announce_rate_penalty"] = interface.announce_rate_penalty
|
||||
ifstats["announce_rate_grace"] = interface.announce_rate_grace
|
||||
ifstats["held_announces"] = len(interface.held_announces)
|
||||
ifstats["burst_active"] = interface.ic_burst_active
|
||||
ifstats["burst_activated"] = interface.ic_burst_activated
|
||||
ifstats["pr_burst_active"] = interface.ic_pr_burst_active
|
||||
ifstats["pr_burst_activated"] = interface.ic_pr_burst_activated
|
||||
ifstats["status"] = interface.online
|
||||
ifstats["mode"] = interface.mode
|
||||
|
||||
|
||||
+143
-73
@@ -38,6 +38,7 @@ import inspect
|
||||
import threading
|
||||
from time import sleep
|
||||
from threading import Lock
|
||||
from collections import deque
|
||||
from .vendor import umsgpack as umsgpack
|
||||
from RNS.Interfaces.BackboneInterface import BackboneInterface
|
||||
|
||||
@@ -124,6 +125,7 @@ class Transport:
|
||||
discovery_path_requests = {} # A table for keeping track of path requests on behalf of other nodes
|
||||
discovery_pr_tags = [] # A table for keeping track of tagged path requests
|
||||
max_pr_tags = 32000 # Maximum amount of unique path request tags to remember
|
||||
max_queued_discovery_prs = 32 # Maximum amount of queued discovery path requests
|
||||
|
||||
interfaces_lock = Lock()
|
||||
destinations_lock = Lock()
|
||||
@@ -415,6 +417,18 @@ class Transport:
|
||||
|
||||
gc.collect()
|
||||
|
||||
@staticmethod
|
||||
def add_interface(interface):
|
||||
with Transport.interfaces_lock:
|
||||
if not interface in Transport.interfaces:
|
||||
Transport.interfaces.append(interface)
|
||||
|
||||
@staticmethod
|
||||
def remove_interface(interface):
|
||||
with Transport.interfaces_lock:
|
||||
if interface in Transport.interfaces:
|
||||
Transport.interfaces.remove(interface)
|
||||
|
||||
@staticmethod
|
||||
def set_network_identity(identity):
|
||||
if not Transport.network_identity:
|
||||
@@ -580,40 +594,41 @@ class Transport:
|
||||
if block_rebroadcasts: announce_context = RNS.Packet.PATH_RESPONSE
|
||||
announce_data = packet.data
|
||||
announce_identity = RNS.Identity.recall(packet.destination_hash, _no_use=True)
|
||||
announce_destination = RNS.Destination(announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown");
|
||||
announce_destination.hash = packet.destination_hash
|
||||
announce_destination.hexhash = announce_destination.hash.hex()
|
||||
|
||||
new_packet = RNS.Packet(
|
||||
announce_destination,
|
||||
announce_data,
|
||||
RNS.Packet.ANNOUNCE,
|
||||
context = announce_context,
|
||||
header_type = RNS.Packet.HEADER_2,
|
||||
transport_type = Transport.TRANSPORT,
|
||||
transport_id = Transport.identity.hash,
|
||||
attached_interface = attached_interface,
|
||||
context_flag = packet.context_flag,
|
||||
)
|
||||
if not announce_identity:
|
||||
RNS.log("Completed announce processing for "+RNS.prettyhexrep(destination_hash)+", the path was cleaned while waiting for announce rebroadcast", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
completed_announces.append(destination_hash)
|
||||
|
||||
new_packet.hops = announce_entry[4]
|
||||
if block_rebroadcasts:
|
||||
RNS.log("Rebroadcasting announce as path response for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
else:
|
||||
RNS.log("Rebroadcasting announce for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
|
||||
outgoing.append(new_packet)
|
||||
announce_destination = RNS.Destination(announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown");
|
||||
announce_destination.hash = packet.destination_hash
|
||||
announce_destination.hexhash = announce_destination.hash.hex()
|
||||
|
||||
new_packet = RNS.Packet(announce_destination,
|
||||
announce_data,
|
||||
RNS.Packet.ANNOUNCE,
|
||||
context = announce_context,
|
||||
header_type = RNS.Packet.HEADER_2,
|
||||
transport_type = Transport.TRANSPORT,
|
||||
transport_id = Transport.identity.hash,
|
||||
attached_interface = attached_interface,
|
||||
context_flag = packet.context_flag)
|
||||
|
||||
# This handles an edge case where a peer sends a past
|
||||
# request for a destination just after an announce for
|
||||
# said destination has arrived, but before it has been
|
||||
# rebroadcast locally. In such a case the actual announce
|
||||
# is temporarily held, and then reinserted when the path
|
||||
# request has been served to the peer.
|
||||
if destination_hash in Transport.held_announces:
|
||||
held_entry = Transport.held_announces.pop(destination_hash)
|
||||
Transport.announce_table[destination_hash] = held_entry
|
||||
RNS.log("Reinserting held announce into table", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
new_packet.hops = announce_entry[4]
|
||||
if block_rebroadcasts: RNS.log("Rebroadcasting announce as path response for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
else: RNS.log("Rebroadcasting announce for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
|
||||
outgoing.append(new_packet)
|
||||
|
||||
# This handles an edge case where a peer sends a past
|
||||
# request for a destination just after an announce for
|
||||
# said destination has arrived, but before it has been
|
||||
# rebroadcast locally. In such a case the actual announce
|
||||
# is temporarily held, and then reinserted when the path
|
||||
# request has been served to the peer.
|
||||
if destination_hash in Transport.held_announces:
|
||||
held_entry = Transport.held_announces.pop(destination_hash)
|
||||
Transport.announce_table[destination_hash] = held_entry
|
||||
RNS.log("Reinserting held announce into table", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
|
||||
for destination_hash in completed_announces:
|
||||
if destination_hash in Transport.announce_table: Transport.announce_table.pop(destination_hash)
|
||||
@@ -772,10 +787,14 @@ class Transport:
|
||||
# Cull the pending path requests table
|
||||
stale_path_requests = []
|
||||
with Transport.path_requests_lock:
|
||||
for destination_hash in Transport.path_requests:
|
||||
if time.time() > Transport.path_requests[destination_hash] + Transport.PATH_REQUEST_GATE_TIMEOUT:
|
||||
stale_path_requests.append(destination_hash)
|
||||
RNS.log("Path request entry for "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
||||
try:
|
||||
for destination_hash in Transport.path_requests:
|
||||
if time.time() > Transport.path_requests[destination_hash] + Transport.PATH_REQUEST_GATE_TIMEOUT:
|
||||
stale_path_requests.append(destination_hash)
|
||||
RNS.log("Path request entry for "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not complete stale path request enumeration in this job round, retrying later: {e}", RNS.LOG_WARNING)
|
||||
|
||||
# Cull the pending discovery path requests table
|
||||
stale_discovery_path_requests = []
|
||||
@@ -917,6 +936,8 @@ class Transport:
|
||||
Transport.prioritize_interfaces()
|
||||
try:
|
||||
for interface in Transport.interfaces:
|
||||
interface.should_ingress_limit()
|
||||
interface.should_ingress_limit_pr()
|
||||
interface.process_held_announces()
|
||||
if interface.phy_keepalive: interface.send_keepalive()
|
||||
Transport.interface_last_jobs = time.time()
|
||||
@@ -980,15 +1001,50 @@ class Transport:
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e) # TODO: Remove
|
||||
|
||||
for packet in outgoing: packet.send()
|
||||
if outgoing:
|
||||
def job(): Transport.handle_outgoing_announces(outgoing)
|
||||
threading.Thread(target=job).start()
|
||||
|
||||
for destination_hash in path_requests:
|
||||
blocked_if = path_requests[destination_hash]
|
||||
if blocked_if == None: Transport.request_path(destination_hash)
|
||||
else:
|
||||
for interface in Transport.interfaces:
|
||||
if interface != blocked_if: Transport.request_path(destination_hash, on_interface=interface)
|
||||
else: pass
|
||||
if path_requests:
|
||||
with Transport.discovery_pr_tx_lock:
|
||||
for destination_hash in path_requests:
|
||||
if not destination_hash in Transport.pending_discovery_prs:
|
||||
if not len(Transport.pending_discovery_prs) >= Transport.max_queued_discovery_prs:
|
||||
Transport.pending_discovery_prs.append([destination_hash, path_requests[destination_hash]])
|
||||
|
||||
if len(Transport.pending_discovery_prs):
|
||||
def job(): Transport.handle_disovery_path_requests()
|
||||
threading.Thread(target=job).start()
|
||||
|
||||
|
||||
discovery_pr_tx_throttle = 0.5
|
||||
discovery_pr_tx_lock = Lock()
|
||||
discovery_pr_handle_lock = Lock()
|
||||
pending_discovery_prs = deque(maxlen=max_queued_discovery_prs)
|
||||
@staticmethod
|
||||
def handle_disovery_path_requests():
|
||||
if Transport.discovery_pr_handle_lock.locked(): return
|
||||
with Transport.discovery_pr_handle_lock:
|
||||
while len(Transport.pending_discovery_prs):
|
||||
time.sleep(Transport.discovery_pr_tx_throttle)
|
||||
destination_hash = None
|
||||
blocked_if = None
|
||||
with Transport.discovery_pr_tx_lock:
|
||||
entry = Transport.pending_discovery_prs.popleft()
|
||||
destination_hash = entry[0]
|
||||
blocked_if = entry[1]
|
||||
|
||||
if destination_hash:
|
||||
if blocked_if == None: Transport.request_path(destination_hash)
|
||||
else:
|
||||
for interface in Transport.interfaces:
|
||||
if interface != blocked_if: Transport.request_path(destination_hash, on_interface=interface)
|
||||
else: pass
|
||||
|
||||
|
||||
@staticmethod
|
||||
def handle_outgoing_announces(outgoing):
|
||||
for packet in sorted(outgoing, key=lambda p: p.hops): packet.send()
|
||||
|
||||
@staticmethod
|
||||
def transmit(interface, raw):
|
||||
@@ -1263,6 +1319,7 @@ class Transport:
|
||||
|
||||
Transport.transmit(interface, packet.raw)
|
||||
if packet.packet_type == RNS.Packet.ANNOUNCE: interface.sent_announce()
|
||||
if packet.destination.type == RNS.Destination.PLAIN and packet.is_outbound_pr: interface.sent_path_request()
|
||||
packet_sent(packet)
|
||||
sent = True
|
||||
|
||||
@@ -1549,7 +1606,7 @@ class Transport:
|
||||
RNS.log(f"Clamping link MTU to {RNS.prettysize(path_mtu)}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
|
||||
except Exception as e:
|
||||
RNS.log(f"Dropping link request packet. The contained exception was: {e}", RNS.LOG_WARNING)
|
||||
RNS.log(f"Dropping link request packet. The contained exception was: {e}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
return
|
||||
|
||||
# Entry format is
|
||||
@@ -1629,10 +1686,12 @@ class Transport:
|
||||
# announces, queueing rebroadcasts of these, and removal
|
||||
# of queued announce rebroadcasts once handed to the next node.
|
||||
if packet.packet_type == RNS.Packet.ANNOUNCE:
|
||||
if interface != None and RNS.Identity.validate_announce(packet, only_validate_signature=True):
|
||||
interface.received_announce()
|
||||
announce_signature_valid = RNS.Identity.validate_announce(packet, only_validate_signature=True)
|
||||
if not announce_signature_valid: return
|
||||
elif interface != None: interface.received_announce()
|
||||
announced_destination_known = packet.destination_hash in Transport.path_table
|
||||
|
||||
if not packet.destination_hash in Transport.path_table:
|
||||
if not announced_destination_known:
|
||||
# This is an unknown destination, and we'll apply
|
||||
# potential ingress limiting. Already known
|
||||
# destinations will have re-announces controlled
|
||||
@@ -1694,7 +1753,7 @@ class Transport:
|
||||
random_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10]
|
||||
random_blobs = []
|
||||
with Transport.inbound_announce_lock:
|
||||
if packet.destination_hash in Transport.path_table:
|
||||
if announced_destination_known:
|
||||
random_blobs = Transport.path_table[packet.destination_hash][IDX_PT_RANDBLOBS]
|
||||
|
||||
# If we already have a path to the announced
|
||||
@@ -2140,13 +2199,9 @@ class Transport:
|
||||
else:
|
||||
RNS.log("Invalid link request proof in transport for link "+RNS.prettyhexrep(packet.destination_hash)+", dropping proof.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while transporting link request proof. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
else:
|
||||
RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
else:
|
||||
RNS.log("Received link request proof with hop mismatch, not transporting it", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
except Exception as e: RNS.log("Could not transport link request proof. The contained exception was: "+str(e), RNS.LOG_DEBUG) if RNS.sl(LOG_DEBUG) else None
|
||||
else: RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
else: RNS.log("Received link request proof with hop mismatch, not transporting it", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
|
||||
else:
|
||||
# Check if we can deliver it to a local
|
||||
@@ -2475,16 +2530,17 @@ class Transport:
|
||||
def clean_announce_cache():
|
||||
st = time.time()
|
||||
target_path = os.path.join(RNS.Reticulum.cachepath, "announces")
|
||||
with Transport.path_table_lock: active_paths = [Transport.path_table[dst_hash][6] for dst_hash in Transport.path_table]
|
||||
with Transport.tunnels_lock: tunnel_paths = list(set([path_dict[dst_hash][6] for path_dict in [Transport.tunnels[tunnel_id][2] for tunnel_id in Transport.tunnels] for dst_hash in path_dict]))
|
||||
cached_announce_hashes = os.listdir(target_path)
|
||||
with Transport.path_table_lock: active_path_hashes = list(set([Transport.path_table[dst_hash][IDX_PT_PACKET] for dst_hash in Transport.path_table]))
|
||||
with Transport.tunnels_lock: tunnel_path_hashes = list(set([path_dict[dst_hash][IDX_PT_PACKET] for path_dict in [Transport.tunnels[tunnel_id][IDX_TT_PATHS] for tunnel_id in Transport.tunnels] for dst_hash in path_dict]))
|
||||
removed = 0; total = 0
|
||||
for packet_hash in os.listdir(target_path):
|
||||
for packet_hash in cached_announce_hashes:
|
||||
remove = False
|
||||
full_path = os.path.join(target_path, packet_hash)
|
||||
if os.path.isfile(full_path):
|
||||
try: target_hash = bytes.fromhex(packet_hash)
|
||||
except: remove = True
|
||||
if (not target_hash in active_paths) and (not target_hash in tunnel_paths): remove = True
|
||||
if (not target_hash in active_path_hashes) and (not target_hash in tunnel_path_hashes): remove = True
|
||||
if remove: os.unlink(full_path); removed += 1
|
||||
total += 1
|
||||
|
||||
@@ -2747,8 +2803,10 @@ class Transport:
|
||||
wait_time = (tx_time / on_interface.announce_cap)
|
||||
on_interface.announce_allowed_at = now + wait_time
|
||||
|
||||
packet.is_outbound_pr = True
|
||||
packet.send()
|
||||
Transport.path_requests[destination_hash] = time.time()
|
||||
|
||||
with Transport.path_requests_lock: Transport.path_requests[destination_hash] = time.time()
|
||||
|
||||
@staticmethod
|
||||
def remote_status_handler(path, data, request_id, link_id, remote_identity, requested_at):
|
||||
@@ -2832,6 +2890,7 @@ class Transport:
|
||||
|
||||
unique_tag = destination_hash+tag_bytes
|
||||
|
||||
if packet.receiving_interface: packet.receiving_interface.received_path_request()
|
||||
with Transport.discovery_pr_tags_lock:
|
||||
if not unique_tag in Transport.discovery_pr_tags:
|
||||
Transport.discovery_pr_tags.append(unique_tag)
|
||||
@@ -2843,23 +2902,22 @@ class Transport:
|
||||
tag=tag_bytes)
|
||||
|
||||
else: RNS.log("Ignoring duplicate path request for "+RNS.prettyhexrep(destination_hash)+" with tag "+RNS.prettyhexrep(unique_tag), RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
||||
|
||||
else: RNS.log("Ignoring tagless path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
|
||||
except Exception as e: RNS.log("Error while handling path request. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
except Exception as e: RNS.log(f"Error while handling path request. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
|
||||
@staticmethod
|
||||
def path_request(destination_hash, is_from_local_client, attached_interface, requestor_transport_id=None, tag=None):
|
||||
should_search_for_unknown = False
|
||||
should_ingress_limit = False
|
||||
|
||||
if attached_interface != None:
|
||||
if RNS.Reticulum.transport_enabled() and attached_interface.mode in RNS.Interfaces.Interface.Interface.DISCOVER_PATHS_FOR:
|
||||
should_search_for_unknown = True
|
||||
interface_str = " on "+str(attached_interface)
|
||||
|
||||
else: interface_str = ""
|
||||
should_ingress_limit = attached_interface.should_ingress_limit_pr()
|
||||
if RNS.Reticulum.transport_enabled():
|
||||
if attached_interface.mode in RNS.Interfaces.Interface.Interface.DISCOVER_PATHS_FOR: should_search_for_unknown = True
|
||||
|
||||
RNS.log("Path request for "+RNS.prettyhexrep(destination_hash)+interface_str, RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
if RNS.sl(RNS.LOG_DEBUG):
|
||||
interface_str = f" on {attached_interface}"
|
||||
RNS.log(f"Path request for {RNS.prettyhexrep(destination_hash)}{interface_str}", RNS.LOG_DEBUG)
|
||||
|
||||
destination_exists_on_local_client = False
|
||||
if len(Transport.local_client_interfaces) > 0:
|
||||
@@ -2886,7 +2944,7 @@ class Transport:
|
||||
received_from = Transport.path_table[destination_hash][IDX_PT_RVCD_IF]
|
||||
|
||||
if packet == None:
|
||||
RNS.log("Could not retrieve announce packet from cache while answering path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_ERROR)
|
||||
RNS.log(f"Could not retrieve announce packet from cache while answering path request for {RNS.prettyhexrep(destination_hash)}, ignoring path request", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
|
||||
elif attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING and attached_interface == received_from:
|
||||
RNS.log("Not answering path request on roaming-mode interface, since next hop is on same roaming-mode interface", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
@@ -2956,6 +3014,15 @@ class Transport:
|
||||
if destination_hash in Transport.discovery_path_requests:
|
||||
RNS.log("There is already a waiting path request for "+RNS.prettyhexrep(destination_hash)+" on behalf of path request"+interface_str, RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
else:
|
||||
# Abort recursive path request if receiving
|
||||
# interface has PR burst active, or should
|
||||
# otherwise ingress limit path requests.
|
||||
if should_ingress_limit:
|
||||
if RNS.sl(RNS.LOG_DEBUG):
|
||||
interface_str = f" for {attached_interface}" if attached_interface else ""
|
||||
RNS.log(f"Not sending recursive path request{interface_str} due to active ingress limiting", RNS.LOG_DEBUG)
|
||||
return
|
||||
|
||||
# Forward path request on all interfaces
|
||||
# except the requestor interface
|
||||
RNS.log("Attempting to discover unknown path to "+RNS.prettyhexrep(destination_hash)+" on behalf of path request"+interface_str, RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||
@@ -2964,9 +3031,12 @@ class Transport:
|
||||
|
||||
for interface in Transport.interfaces:
|
||||
if not interface == attached_interface:
|
||||
# Use the previously extracted tag from this path request
|
||||
# on the new path requests as well, to avoid potential loops
|
||||
Transport.request_path(destination_hash, on_interface=interface, tag=tag, recursive=True)
|
||||
if interface.should_egress_limit_pr():
|
||||
RNS.log(f"Not sending recursive path request on {interface} due to active egress limiting", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
||||
else:
|
||||
# Use the previously extracted tag from this path request
|
||||
# on the new path requests as well, to avoid potential loops
|
||||
Transport.request_path(destination_hash, on_interface=interface, tag=tag, recursive=True)
|
||||
|
||||
elif not is_from_local_client and len(Transport.local_client_interfaces) > 0:
|
||||
# Forward the path request on all local
|
||||
|
||||
@@ -163,11 +163,11 @@ def listen(configdir, identitypath = None, verbosity = 0, quietness = 0, allowed
|
||||
except Exception as e:
|
||||
raise ValueError("Invalid destination entered. Check your input.")
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
RNS.log(f"Could not apply allowed identity: {e}", RNS.LOG_ERROR)
|
||||
RNS.exit(1)
|
||||
|
||||
if len(allowed_identity_hashes) < 1 and not disable_auth:
|
||||
print("Warning: No allowed identities configured, rncp will not accept any files!")
|
||||
RNS.log("No allowed identities configured, rncp will not accept any files!", RNS.LOG_WARNING)
|
||||
|
||||
def fetch_request(path, data, request_id, link_id, remote_identity, requested_at):
|
||||
global allow_fetch, fetch_jail, fetch_auto_compress
|
||||
@@ -217,7 +217,7 @@ def listen(configdir, identitypath = None, verbosity = 0, quietness = 0, allowed
|
||||
else:
|
||||
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_LIST, allowed_list=allowed_identity_hashes)
|
||||
|
||||
print("rncp listening on "+RNS.prettyhexrep(destination.hash))
|
||||
RNS.log("rncp listening on "+RNS.prettyhexrep(destination.hash), RNS.LOG_INFO)
|
||||
|
||||
if announce >= 0:
|
||||
def job():
|
||||
@@ -271,15 +271,15 @@ def receive_resource_started(resource):
|
||||
else:
|
||||
id_str = ""
|
||||
|
||||
print("Starting resource transfer "+RNS.prettyhexrep(resource.hash)+id_str)
|
||||
RNS.log("Starting resource transfer "+RNS.prettyhexrep(resource.hash)+id_str, RNS.LOG_INFO)
|
||||
|
||||
def receive_resource_concluded(resource):
|
||||
global save_path, allow_overwrite_on_receive
|
||||
if resource.status == RNS.Resource.COMPLETE:
|
||||
print(str(resource)+" completed")
|
||||
RNS.log(f"Incoming resource {resource} completed", RNS.LOG_INFO)
|
||||
|
||||
if resource.metadata == None:
|
||||
print("Invalid data received, ignoring resource")
|
||||
RNS.log("Invalid data received, ignoring resource", RNS.LOG_WARNING)
|
||||
return
|
||||
|
||||
else:
|
||||
@@ -306,13 +306,14 @@ def receive_resource_concluded(resource):
|
||||
full_save_path = saved_filename+"."+str(counter)
|
||||
|
||||
shutil.move(resource.data.name, full_save_path)
|
||||
RNS.log("Saved received file to "+full_save_path, RNS.LOG_NOTICE)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"An error occurred while saving received resource: {e}", RNS.LOG_ERROR)
|
||||
return
|
||||
|
||||
else:
|
||||
print("Resource failed")
|
||||
RNS.log("Resource failed", RNS.LOG_INFO)
|
||||
|
||||
resource_done = False
|
||||
current_resource = None
|
||||
|
||||
@@ -138,12 +138,6 @@ class ReticulumGitClient():
|
||||
self.configpath = self.configdir+"/client_config"
|
||||
self.identitypath = self.configdir+"/client_identity"
|
||||
|
||||
RNS.logfile = self.logfile
|
||||
try: self.reticulum = RNS.Reticulum(configdir=rnsconfigdir, logdest=RNS.LOG_FILE)
|
||||
except Exception as e:
|
||||
print(f"Failed to initialize Reticulum: {e}", file=sys.stderr)
|
||||
return
|
||||
|
||||
if os.path.isfile(self.configpath):
|
||||
try: self.config = ConfigObj(self.configpath)
|
||||
except Exception as e:
|
||||
@@ -152,6 +146,12 @@ class ReticulumGitClient():
|
||||
|
||||
else: self.__create_default_config()
|
||||
|
||||
RNS.logfile = self.logfile
|
||||
try: self.reticulum = RNS.Reticulum(configdir=rnsconfigdir, logdest=RNS.LOG_FILE)
|
||||
except Exception as e:
|
||||
print(f"Failed to initialize Reticulum: {e}", file=sys.stderr)
|
||||
return
|
||||
|
||||
self.__apply_config()
|
||||
self.ready = True
|
||||
|
||||
|
||||
@@ -165,15 +165,15 @@ class SyntaxHighlighter:
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Pygments highlighting failed, falling back: {e}", RNS.LOG_WARNING)
|
||||
return self._plain_text(content)
|
||||
return self._plain_text(content).replace("\\", "\\\\")
|
||||
|
||||
# TODO: Implement Python tokenize fallback for .py files.
|
||||
# For now, route to plain text
|
||||
if filename and filename.endswith(".py"):
|
||||
return self._plain_text(content)
|
||||
return self._plain_text(content).replace("\\", "\\\\")
|
||||
|
||||
# Universal fallback
|
||||
return self._plain_text(content)
|
||||
return self._plain_text(content).replace("\\", "\\\\")
|
||||
|
||||
def _highlight_pygments(self, content, filename=None, language=None):
|
||||
from pygments.lexers import get_lexer_for_filename, guess_lexer, get_lexer_by_name
|
||||
@@ -301,7 +301,8 @@ class MicronFormatter:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _escape_value(value: str) -> str: return value.replace("`", "\\`")
|
||||
def _escape_value(value):
|
||||
return value.replace("\\", "\\\\").replace("`", "\\`")
|
||||
|
||||
# Required by Pygments formatter API, returns None for Micron
|
||||
def get_style_defs(self, arg=None): return None
|
||||
|
||||
+470
-149
File diff suppressed because it is too large
Load Diff
+1365
-259
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,51 @@
|
||||
import re
|
||||
import RNS
|
||||
|
||||
# Validate ref names according to https://git-scm.com/docs/git-check-ref-format
|
||||
# This may be a bit overkill, since git validates names as well, but why not.
|
||||
def san_ref(ref):
|
||||
if ref.startswith("-"): return None
|
||||
if ref.startswith("/"): return None
|
||||
if ref.endswith("/"): return None
|
||||
if ref.endswith("."): return None
|
||||
|
||||
if " " in ref: return None
|
||||
if not "/" in ref: return None
|
||||
if ".." in ref: return None
|
||||
if "/." in ref: return None
|
||||
if "//" in ref: return None
|
||||
if "\\" in ref: return None
|
||||
|
||||
for comp in ref.split("/"):
|
||||
if comp.endswith(".lock"): return None
|
||||
|
||||
if not all(ord(c) >= 40 for c in ref): return None # Any control character
|
||||
if "\x7f" in ref: return None # ASCII DEL (177)
|
||||
if "~" in ref: return None
|
||||
if "^" in ref: return None
|
||||
if ":" in ref: return None
|
||||
if "?" in ref: return None
|
||||
if "*" in ref: return None
|
||||
if "[" in ref: return None
|
||||
if "@{" in ref: return None
|
||||
if "@" == ref: return None
|
||||
|
||||
return ref
|
||||
|
||||
def san_refs(refs):
|
||||
if not type(refs) == list: return None
|
||||
for ref in refs:
|
||||
if not san_ref(ref): return None
|
||||
|
||||
return refs
|
||||
|
||||
# Git SHA format validation
|
||||
def san_sha(sha):
|
||||
if len(sha) < 40: return None
|
||||
try: bytes.fromhex(sha)
|
||||
except: return None
|
||||
return sha
|
||||
|
||||
class MarkdownToMicron:
|
||||
BOLD = "`!"
|
||||
BOLD_END = "`!"
|
||||
@@ -88,6 +133,10 @@ class MarkdownToMicron:
|
||||
self.__local_url_scope = self.local_url_scope
|
||||
self.syntax_highlighter = syntax_highlighter
|
||||
self.wcwidth = None
|
||||
|
||||
self.bold_links = True
|
||||
self.underline_links = True
|
||||
self.link_color = None
|
||||
|
||||
try:
|
||||
import wcwidth
|
||||
@@ -107,6 +156,7 @@ class MarkdownToMicron:
|
||||
return w if w is not None and w >= 0 else len(text)
|
||||
|
||||
def format_block(self, text, url_scope=None):
|
||||
# text = text.replace("\\", "\\\\") # Now handled in format_line instead
|
||||
lines = text.split('\n')
|
||||
result_lines = []
|
||||
in_code_block = False
|
||||
@@ -183,10 +233,6 @@ class MarkdownToMicron:
|
||||
for line in lines:
|
||||
is_fence, lang_hint = self._detect_code_fence(line)
|
||||
|
||||
if line.startswith("-") and not line.startswith("---") and not line.startswith("- "): line = f"\\{line}"
|
||||
if line.startswith(">"): line = f"\\{line}"
|
||||
if line.startswith("<"): line = f"\\{line}"
|
||||
|
||||
if is_fence:
|
||||
# Flush any pending structures before code fence
|
||||
flush_quote_buffer()
|
||||
@@ -255,6 +301,11 @@ class MarkdownToMicron:
|
||||
|
||||
def format_line(self, line, mode="normal"):
|
||||
if mode == "codeblock": return self._escape_literals(line)
|
||||
line = line.replace("\\", "\\\\")
|
||||
|
||||
if line.startswith("-") and not line.startswith("---") and not line.startswith("- "): line = f"\\{line}"
|
||||
if line.startswith("<"): line = f"\\{line}"
|
||||
# if line.startswith(">"): line = f"\\{line}" # Now handled by blockquotes
|
||||
|
||||
if self.HORIZONTAL_RULE_RE.match(line): return self._format_horizontal_rule()
|
||||
|
||||
@@ -296,8 +347,15 @@ class MarkdownToMicron:
|
||||
url = f"{self.local_url_scope}{url}"
|
||||
if anchor: url = f"{url}|anchor={anchor}"
|
||||
|
||||
undl = "`_" if self.underline_links else ""
|
||||
bold = "`!" if self.bold_links else ""
|
||||
text = text.replace('`', '')
|
||||
return f"`!`[{text}`{url}]`!"
|
||||
link = f"{undl}{bold}`[{text}`{url}]{bold}{undl}"
|
||||
|
||||
if self.link_color and len(self.link_color) == 3: link = f"`F{self.link_color}{link}`f"
|
||||
if self.link_color and len(self.link_color) == 6: link = f"`FT{self.link_color}{link}`f"
|
||||
|
||||
return link
|
||||
|
||||
text = re.sub(r'\x00LINK(\d+)\x00', restore_link, text)
|
||||
|
||||
|
||||
+269
-28
@@ -45,10 +45,12 @@ from RNS.Cryptography.Hashes import file_sha256
|
||||
|
||||
APP_NAME = "rns"
|
||||
DEFAULT_ASPECTS = f"{APP_NAME}.id"
|
||||
NO_MESSAGE = 0x01
|
||||
|
||||
PRV_EXT = "rid"
|
||||
PUB_EXT = "pub"
|
||||
SIG_EXT = "rsg"
|
||||
MSG_EXT = "rsm"
|
||||
ENCRYPT_EXT = "rfe"
|
||||
CHUNK_BLOCKS = 1024*1024
|
||||
ENC_CHUNK = CHUNK_BLOCKS*RNS.Identity.AES256_BLOCKSIZE
|
||||
@@ -69,6 +71,8 @@ R_INVALID_ASPECTS = 9
|
||||
R_INVALID_SIGNATURE = 10
|
||||
R_FILE_EXISTS = 11
|
||||
R_DECRYPT_FAILED = 12
|
||||
R_INVALID_ARGS = 250
|
||||
R_SEQUENCE_ERROR = 251
|
||||
R_READ_ERROR = 252
|
||||
R_WRITE_ERROR = 253
|
||||
R_UNKNOWN_ERROR = 254
|
||||
@@ -78,7 +82,7 @@ reticulum = None
|
||||
|
||||
def validate_args(args):
|
||||
ops = 0;
|
||||
for o in [args.encrypt, args.decrypt, args.validate, args.sign]:
|
||||
for o in [args.encrypt, args.decrypt, args.validate, args.sign, args.sign_message]:
|
||||
if o: ops += 1
|
||||
if ops > 1: print("This utility currently only supports one of the encrypt, decrypt, sign or verify operations per invocation"); exit(1)
|
||||
|
||||
@@ -88,9 +92,9 @@ def validate_args(args):
|
||||
if g > 1: print("The -i, -g, -m and -M args are mutually exclusive"); exit(1)
|
||||
|
||||
g = 0;
|
||||
for a in [args.base64, args.base32]:
|
||||
for a in [args.base64, args.base32, args.base256, args.hex]:
|
||||
if a: g += 1
|
||||
if g > 1: print("The -b and -B args are mutually exclusive"); exit(1)
|
||||
if g > 1: print("The -b, -B, --hex and --base256 args are mutually exclusive"); exit(1)
|
||||
|
||||
return True
|
||||
|
||||
@@ -114,10 +118,11 @@ def main():
|
||||
# Operations
|
||||
parser.add_argument("-a", "--announce", metavar="aspects", action="store", nargs="?", const=DEFAULT_ASPECTS, default=None, help="announce a destination based on this Identity")
|
||||
parser.add_argument("-H", "--hash", metavar="aspects", action="store", default=None, help="show destination hashes for other aspects for this Identity")
|
||||
parser.add_argument("-d", "--decrypt", metavar="file", action="store", default=None, help="decrypt file")
|
||||
parser.add_argument("-e", "--encrypt", metavar="file", action="store", default=None, help="encrypt file")
|
||||
parser.add_argument("-V", "--validate", metavar="path", action="store", default=None, help="validate signature")
|
||||
parser.add_argument("-s", "--sign", metavar="path", action="store", default=None, help="sign file")
|
||||
parser.add_argument("-d", "--decrypt", metavar="file", action="store", nargs="*", default=None, help="decrypt file")
|
||||
parser.add_argument("-e", "--encrypt", metavar="file", action="store", nargs="*", default=None, help="encrypt file")
|
||||
parser.add_argument("-V", "--validate", metavar="path", action="store", nargs="*", default=None, help="validate signature")
|
||||
parser.add_argument("-s", "--sign", metavar="path", action="store", nargs="*", default=None, help="sign file")
|
||||
parser.add_argument("-S", "--sign-message", metavar="path", action="store", nargs="?", const=NO_MESSAGE, default=None, help="create embedded signed message")
|
||||
parser.add_argument("--raw", action="store_true", default=False, help="sign raw input data instead of hashing first")
|
||||
|
||||
# I/O Control
|
||||
@@ -136,6 +141,8 @@ def main():
|
||||
# Formatting Control
|
||||
parser.add_argument("-b", "--base64", action="store_true", default=False, help="Use base64-encoded input and output")
|
||||
parser.add_argument("-B", "--base32", action="store_true", default=False, help="Use base32-encoded input and output")
|
||||
parser.add_argument("--hex", action="store_true", default=False, help="Use hex-encoded input and output")
|
||||
parser.add_argument("--base256", action="store_true", default=False, help="Use base256-encoded input and output")
|
||||
|
||||
parser.add_argument("--version", action="version", version="rnid {version}".format(version=__version__))
|
||||
|
||||
@@ -154,6 +161,7 @@ def main():
|
||||
if args.announce: announce(args, identity); op = True
|
||||
if args.validate: validate(args, identity or args.identity); op = True
|
||||
if args.sign: sign(args, identity); op = True
|
||||
if args.sign_message: sign_message(args, identity); op = True
|
||||
if args.encrypt: encrypt(args, identity); op = True
|
||||
if args.decrypt: decrypt(args, identity); op = True
|
||||
if args.write: write_identity(args, identity); op = True
|
||||
@@ -375,9 +383,9 @@ def announce(args, identity):
|
||||
except Exception as e: print(f"An error ocurred while attempting to send the announce: {e}"); exit(R_UNKNOWN_ERROR)
|
||||
|
||||
|
||||
###################################
|
||||
# Signing & Validation Operations #
|
||||
###################################
|
||||
########################################
|
||||
# Canonical RSG Manipulation Functions #
|
||||
########################################
|
||||
|
||||
def get_rsg_data(rsg):
|
||||
rsg_data = None
|
||||
@@ -386,11 +394,23 @@ def get_rsg_data(rsg):
|
||||
elif type(rsg) == str:
|
||||
try: rsg_data = base64.urlsafe_b64decode(rsg)
|
||||
except: pass
|
||||
try: rsg_data = base64.b32decode(rsg)
|
||||
try: rsg_data = base64.b32decode(rsg.strip(RSG_PADDING))
|
||||
except: pass
|
||||
try: rsg_data = bytes.fromhex(rsg.strip(RSG_PADDING))
|
||||
except: pass
|
||||
try: rsg_data = RNS.b256_to_bytes(rsg.strip(RSG_PADDING.decode("utf-8")))
|
||||
except: pass
|
||||
|
||||
return rsg_data
|
||||
|
||||
def extract_signed_rsg_data(rsg):
|
||||
siglen = RNS.Identity.SIGLENGTH//8
|
||||
rsg_data = get_rsg_data(rsg)
|
||||
envelope = rsg_data[siglen:]
|
||||
|
||||
try: return mp.unpackb(envelope)
|
||||
except: return None
|
||||
|
||||
def get_rsg_hash(message):
|
||||
sha = None
|
||||
if type(message) == bytes: sha = sha256(message)
|
||||
@@ -459,34 +479,114 @@ def validate_rsg(rsg, message=None, required_signer=None):
|
||||
|
||||
return False, signed_data, signing_identity
|
||||
|
||||
def create_rsg(signer_identity, message, note=None, meta=None, output="bin"):
|
||||
if not output in ["bin", "hex", "base32", "base64"]: raise TypeError(f"Invalid output format for rsg creation")
|
||||
if not type(signer_identity) == RNS.Identity: raise TypeError(f"{signer_identity} is not a Reticulum Identity")
|
||||
if not signer_identity.get_private_key(): raise ValueError(f"{signer_identity} does not hold a private key")
|
||||
def create_rsg(signer_identity, message, embed=False, note=None, meta=None, output="bin"):
|
||||
if not output in ["bin", "hex", "base32", "base256", "base64"]: raise TypeError(f"Invalid output format for rsg creation")
|
||||
if not type(signer_identity) == RNS.Identity: raise TypeError(f"{signer_identity} is not a Reticulum Identity")
|
||||
if not signer_identity.get_private_key(): raise ValueError(f"{signer_identity} does not hold a private key")
|
||||
|
||||
signed_data = { "hashtype": "sha256", "hash": get_rsg_hash(message),
|
||||
"meta": { "signer": signer_identity.hash,
|
||||
"pubkey": signer_identity.get_public_key(),
|
||||
"note" : note } }
|
||||
|
||||
if embed:
|
||||
if type(message) == str: message = message.encode("utf-8")
|
||||
signed_data["message"] = message
|
||||
|
||||
if meta and type(meta) == dict:
|
||||
for key in meta:
|
||||
if not key in signed_data["meta"]: signed_data["meta"]["key"] = meta["key"]
|
||||
|
||||
envelope = mp.packb(signed_data)
|
||||
signature = signer_identity.sign(envelope)
|
||||
rsg_data = signature+envelope
|
||||
|
||||
return signature+envelope
|
||||
if output == "bin": rsg = rsg_data
|
||||
elif output == "hex": rsg = RNS.hexrep(rsg_data, delimit=False).encode("ascii")
|
||||
elif output == "base32": rsg = base64.b32encode(rsg_data)
|
||||
elif output == "base64": rsg = base64.urlsafe_b64encode(rsg_data)
|
||||
elif output == "base256": rsg = RNS.b256rep(rsg_data)
|
||||
else: return None
|
||||
|
||||
def validate(args, identity):
|
||||
return rsg
|
||||
|
||||
RSG_ASCII_HEADER = b"#### Start of rsg data "
|
||||
RSG_ASCII_FOOTER = b" End of rsg data ####"
|
||||
RSG_ASCII_ROW_WIDTH = 64
|
||||
RSG_PADDING = b"="
|
||||
def wrap_rsg(rsg):
|
||||
if type(rsg) == str: return wrap_rsg_str(rsg)
|
||||
def pad(chunk): return chunk+(RSG_ASCII_ROW_WIDTH-len(chunk))*RSG_PADDING
|
||||
header = RSG_ASCII_HEADER+b"#"*(RSG_ASCII_ROW_WIDTH-len(RSG_ASCII_HEADER))
|
||||
footer = b"#"*(RSG_ASCII_ROW_WIDTH-len(RSG_ASCII_FOOTER))+RSG_ASCII_FOOTER
|
||||
wrapped = header+b"\n"
|
||||
read = 0
|
||||
while len(rsg):
|
||||
chunk = rsg[:RSG_ASCII_ROW_WIDTH]
|
||||
if len(chunk) < RSG_ASCII_ROW_WIDTH: chunk = pad(chunk)
|
||||
wrapped += chunk+b"\n"; read += len(chunk)
|
||||
rsg = rsg[len(chunk):]
|
||||
|
||||
wrapped += footer
|
||||
return wrapped.decode("ascii")
|
||||
|
||||
def wrap_rsg_str(rsg):
|
||||
def pad(chunk): return chunk+(RSG_ASCII_ROW_WIDTH-len(chunk))*RSG_PADDING.decode("utf-8")
|
||||
header = RSG_ASCII_HEADER.decode("utf-8")+"#"*(RSG_ASCII_ROW_WIDTH-len(RSG_ASCII_HEADER.decode("utf-8")))
|
||||
footer = "#"*(RSG_ASCII_ROW_WIDTH-len(RSG_ASCII_FOOTER.decode("utf-8")))+RSG_ASCII_FOOTER.decode("utf-8")
|
||||
wrapped = header+"\n"
|
||||
read = 0
|
||||
while len(rsg):
|
||||
chunk = rsg[:RSG_ASCII_ROW_WIDTH]
|
||||
if len(chunk) < RSG_ASCII_ROW_WIDTH: chunk = pad(chunk)
|
||||
wrapped += chunk+"\n"; read += len(chunk)
|
||||
rsg = rsg[len(chunk):]
|
||||
|
||||
wrapped += footer
|
||||
return wrapped
|
||||
|
||||
def unwrap_rsg(wrapped_rsg):
|
||||
unwrapped = ""
|
||||
if type(wrapped_rsg) == bytes: wrapped_rsg = wrapped_rsg.decode("ascii")
|
||||
elif type(wrapped_rsg) == str: pass
|
||||
else: return None
|
||||
|
||||
for line in wrapped_rsg.splitlines():
|
||||
if not line.strip(): continue
|
||||
if line.startswith("#"): continue
|
||||
unwrapped += line
|
||||
|
||||
return unwrapped if unwrapped else None
|
||||
|
||||
|
||||
###################################
|
||||
# Signing & Validation Operations #
|
||||
###################################
|
||||
|
||||
def validate(args, identity, __recursive=False):
|
||||
if type(args.validate) == list:
|
||||
paths = args.validate.copy()
|
||||
validated = 0
|
||||
for path in paths:
|
||||
args.validate = path
|
||||
code = validate(args, identity, __recursive=True)
|
||||
if code != 0: print(f"Sequence error on recursive signature validation"); exit(R_SEQUENCE_ERROR)
|
||||
else: validated += 1
|
||||
|
||||
if len(paths) != validated: print(f"Sequence error on recursive signature validation"); exit(R_SEQUENCE_ERROR)
|
||||
else: exit(R_OK)
|
||||
|
||||
msg_ext = f".{MSG_EXT}"
|
||||
sig_ext = f".{SIG_EXT}"
|
||||
validate_path = os.path.expanduser(args.validate)
|
||||
path_is_msgfile = validate_path.lower().endswith(msg_ext)
|
||||
path_is_sigfile = validate_path.lower().endswith(sig_ext)
|
||||
if path_is_sigfile: signature_path = validate_path; file_path = validate_path[:-len(sig_ext)]
|
||||
else: signature_path = f"{validate_path}{sig_ext}"; file_path = validate_path
|
||||
signature_exists = os.path.isfile(signature_path)
|
||||
file_exists = os.path.isfile(file_path)
|
||||
|
||||
if path_is_msgfile: return validate_message(args, identity, __recursive=__recursive)
|
||||
if not file_exists: print(f"The validation target \"{file_path}\" does not exist"); exit(R_NO_FILE)
|
||||
if not signature_exists: print(f"No signature file exists for \"{file_path}\""); exit(R_NO_FILE)
|
||||
|
||||
@@ -511,7 +611,7 @@ def validate(args, identity):
|
||||
identity_str = RNS.prettyhexrep(identity) if type(identity) == bytes else f"{identity}"
|
||||
signer_description = f"\nThis file was NOT signed by {identity_str or signing_identity}" if identity else ""
|
||||
if not valid: print(f"Invalid signature {signature_path} for file {file_path}{signer_description}"); exit(R_INVALID_SIGNATURE)
|
||||
else: print(f"Signature is valid, the file {file_path} was signed by {signing_identity}"); exit(R_OK)
|
||||
else: print(f"Signature is valid, the file {file_path} was signed by {signing_identity}"); return exit(R_OK) if not __recursive else R_OK
|
||||
|
||||
except Exception as e: print(f"Error while validating {signature_path}: {e}"); exit(R_UNKNOWN_ERROR)
|
||||
|
||||
@@ -530,16 +630,67 @@ def validate(args, identity):
|
||||
|
||||
except Exception as e: print(f"Could not validate signature: {e}"); exit(R_READ_ERROR)
|
||||
|
||||
def sign(args, identity):
|
||||
def validate_message(args, identity, __recursive=False):
|
||||
msg_ext = f".{MSG_EXT}"
|
||||
validate_path = os.path.expanduser(args.validate)
|
||||
path_is_msgfile = validate_path.lower().endswith(msg_ext)
|
||||
if path_is_msgfile: signature_path = validate_path
|
||||
signature_exists = os.path.isfile(signature_path)
|
||||
|
||||
if not signature_exists: print(f"The signature file \"{signature_path}\" does not exist"); exit(R_NO_FILE)
|
||||
|
||||
try:
|
||||
with open(signature_path, "rb") as fh: rsg = fh.read()
|
||||
except Exception as e: print(f"Could not read rsg: {e}"); exit(R_READ_ERROR)
|
||||
|
||||
if type(identity) == str:
|
||||
if not len(identity) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8*2: print("Invalid identity hash length"); exit(R_INVALID_IDENTITY)
|
||||
try: identity = bytes.fromhex(identity)
|
||||
except Exception as e: print(f"Invalid identity hash: {e}"); exit(R_INVALID_IDENTITY)
|
||||
|
||||
try:
|
||||
rsm_contents = extract_signed_rsg_data(rsg)
|
||||
if not "message" in rsm_contents: print(f"No embedded message in {signature_path}"); exit(R_INVALID_SIGNATURE)
|
||||
valid, signed_data, signing_identity = validate_rsg(rsg, message=rsm_contents["message"], required_signer=identity)
|
||||
identity_str = RNS.prettyhexrep(identity) if type(identity) == bytes else f"{identity}"
|
||||
signer_description = f"\nThe message was NOT signed by {identity_str or signing_identity}" if identity else ""
|
||||
if not valid: print(f"Invalid signature in {signature_path}{signer_description}"); exit(R_INVALID_SIGNATURE)
|
||||
else:
|
||||
print(f"\nSignature is valid, the following message was signed by {signing_identity}:\n")
|
||||
print(signed_data["message"].decode("utf-8"))
|
||||
|
||||
return exit(R_OK) if not __recursive else R_OK
|
||||
|
||||
except Exception as e: print(f"Error while validating {signature_path}: {e}"); exit(R_UNKNOWN_ERROR)
|
||||
|
||||
def sign(args, identity, __recursive=False):
|
||||
if type(args.sign) == list:
|
||||
paths = args.sign.copy()
|
||||
signed = 0
|
||||
for path in paths:
|
||||
args.sign = path
|
||||
code = sign(args, identity, __recursive=True)
|
||||
if code != 0: print(f"Sequence error on recursive signature creation"); exit(R_SEQUENCE_ERROR)
|
||||
else: signed += 1
|
||||
|
||||
if len(paths) != signed: print(f"Sequence error on recursive signature creation"); exit(R_SEQUENCE_ERROR)
|
||||
else: exit(R_OK)
|
||||
|
||||
sig_ext = f".{SIG_EXT}"
|
||||
sign_path = os.path.expanduser(args.sign)
|
||||
rsg_path = f"{sign_path}{sig_ext}"
|
||||
file_exists = os.path.isfile(sign_path)
|
||||
signature_exists = os.path.isfile(rsg_path)
|
||||
|
||||
if args.base32: output = "base32"
|
||||
elif args.base64: output = "base64"
|
||||
elif args.base256: output = "base256"
|
||||
elif args.hex: output = "hex"
|
||||
else: output = "bin"
|
||||
|
||||
if not identity.get_private_key(): print(f"Cannot sign \"{sign_path}\", the identity does not hold a private key"); exit(R_NO_PRVKEY)
|
||||
if not file_exists: print(f"The file \"{sign_path}\" does not exist"); exit(R_NO_FILE)
|
||||
if signature_exists and not args.force:
|
||||
if output == "bin" and signature_exists and not args.force:
|
||||
print(f"The signature file \"{rsg_path}\" already exists, not overwriting"); exit(R_FILE_EXISTS)
|
||||
|
||||
try:
|
||||
@@ -548,21 +699,72 @@ def sign(args, identity):
|
||||
with open(rsg_path, "wb") as fh: fh.write(identity.sign(data))
|
||||
|
||||
else:
|
||||
with open(sign_path, "rb") as in_file:
|
||||
rsg = create_rsg(identity, in_file)
|
||||
if not rsg: print(f"No signature created, not writing"); exit(R_UNKNOWN_ERROR)
|
||||
with open(sign_path, "rb") as in_file: rsg = create_rsg(identity, in_file, output=output)
|
||||
if not rsg: print(f"No signature created, not writing"); exit(R_UNKNOWN_ERROR)
|
||||
|
||||
if output == "bin":
|
||||
with open(rsg_path, "wb") as out_file: out_file.write(rsg)
|
||||
|
||||
print(f"Signed file {sign_path} with {identity}"); exit(R_OK)
|
||||
elif output in ["base32", "base64", "base256", "hex"]: print(f"\n{wrap_rsg(rsg)}\n")
|
||||
else: print("No valid output format specified"); exit(R_INVALID_ARGS)
|
||||
|
||||
print(f"Signed file {sign_path} with {identity}"); return exit(R_OK) if not __recursive else R_OK
|
||||
|
||||
except Exception as e: print(f"Could not sign {sign_path}: {e}"); exit(R_UNKNOWN_ERROR)
|
||||
|
||||
def sign_message(args, identity):
|
||||
message = args.sign_message
|
||||
|
||||
if args.base32: output = "base32"
|
||||
elif args.base64: output = "base64"
|
||||
elif args.base256: output = "base256"
|
||||
elif args.hex: output = "hex"
|
||||
else: output = "bin"
|
||||
|
||||
if output == "bin" and not args.write: print("No write path specified"); exit(R_INVALID_ARGS)
|
||||
if not identity.get_private_key(): print(f"Cannot sign \"{sign_path}\", the identity does not hold a private key"); exit(R_NO_PRVKEY)
|
||||
|
||||
if message == NO_MESSAGE: message = get_editor_content()
|
||||
if not message: print("No message specified"); exit(R_INVALID_ARGS)
|
||||
|
||||
try:
|
||||
rsg = create_rsg(identity, message, embed=True, output=output)
|
||||
if not rsg: print(f"No signature created, not writing"); exit(R_UNKNOWN_ERROR)
|
||||
|
||||
if output == "bin":
|
||||
sig_ext = f".{MSG_EXT}"
|
||||
rsg_path = os.path.expanduser(args.write)
|
||||
rsg_path = f"{rsg_path}{sig_ext}" if not rsg_path.endswith(sig_ext) else rsg_path
|
||||
signature_exists = os.path.isfile(rsg_path)
|
||||
if signature_exists and not args.force: print(f"The signature file \"{rsg_path}\" already exists, not overwriting"); exit(R_FILE_EXISTS)
|
||||
with open(rsg_path, "wb") as out_file: out_file.write(rsg)
|
||||
print(f"Message signed with {identity} saved to {rsg_path}"); exit(R_OK)
|
||||
|
||||
elif output in ["base32", "base64", "base256", "hex"]: print(f"\n{wrap_rsg(rsg)}\n")
|
||||
else: print("No valid output format specified"); exit(R_INVALID_ARGS)
|
||||
|
||||
print(f"Message signed with {identity}"); exit(R_OK)
|
||||
|
||||
except Exception as e: print(f"Could not sign message: {e}"); exit(R_UNKNOWN_ERROR)
|
||||
|
||||
|
||||
######################################
|
||||
# Encryption & Decryption Operations #
|
||||
######################################
|
||||
|
||||
def encrypt(args, identity):
|
||||
def encrypt(args, identity, __recursive=False):
|
||||
if type(args.encrypt) == list:
|
||||
paths = args.encrypt.copy()
|
||||
encrypted = 0
|
||||
for path in paths:
|
||||
args.encrypt = path
|
||||
code = encrypt(args, identity, __recursive=True)
|
||||
if code != 0: print(f"Sequence error on recursive file encryption"); exit(R_SEQUENCE_ERROR)
|
||||
else: encrypted += 1
|
||||
|
||||
if len(paths) != encrypted: print(f"Sequence error on recursive file encryption"); exit(R_SEQUENCE_ERROR)
|
||||
else: exit(R_OK)
|
||||
|
||||
enc_ext = f".{ENCRYPT_EXT}"
|
||||
encrypt_path = os.path.expanduser(args.encrypt)
|
||||
rfe_path = args.write if args.write else f"{encrypt_path}{enc_ext}"
|
||||
@@ -591,9 +793,21 @@ def encrypt(args, identity):
|
||||
except Exception as e: print(f"\nError writing encrypted output to {rfe_path}: {e}"); exit(R_WRITE_ERROR)
|
||||
except Exception as e: print(f"\nError reading {encrypt_path} for encryption: {e}"); exit(R_WRITE_ERROR)
|
||||
|
||||
print(f"\nFile {encrypt_path} encrypted for {identity} to {rfe_path}"); exit(R_OK)
|
||||
print(f"\nFile {encrypt_path} encrypted for {identity} to {rfe_path}"); return exit(R_OK) if not __recursive else R_OK
|
||||
|
||||
def decrypt(args, identity, __recursive=False):
|
||||
if type(args.decrypt) == list:
|
||||
paths = args.decrypt.copy()
|
||||
decrypted = 0
|
||||
for path in paths:
|
||||
args.decrypt = path
|
||||
code = decrypt(args, identity, __recursive=True)
|
||||
if code != 0: print(f"Sequence error on recursive file decryption"); exit(R_SEQUENCE_ERROR)
|
||||
else: decrypted += 1
|
||||
|
||||
if len(paths) != decrypted: print(f"Sequence error on recursive file decryption"); exit(R_SEQUENCE_ERROR)
|
||||
else: exit(R_OK)
|
||||
|
||||
def decrypt(args, identity):
|
||||
enc_ext = f".{ENCRYPT_EXT}"
|
||||
rfe_path = os.path.expanduser(args.decrypt)
|
||||
if not rfe_path.endswith(enc_ext): print(f"The file {rfe_path} does not appear to be a Reticulum encrypted file"); exit(R_INVALID_FILE)
|
||||
@@ -630,7 +844,7 @@ def decrypt(args, identity):
|
||||
except Exception as e: print(f"\nError writing decrypted output to {decrypt_path}: {e}"); exit(R_WRITE_ERROR)
|
||||
except Exception as e: print(f"\nError reading {rfe_path} for decryption: {e}"); exit(R_WRITE_ERROR)
|
||||
|
||||
print(f"\nFile {rfe_path} decrypted to {decrypt_path}"); exit(R_OK)
|
||||
print(f"\nFile {rfe_path} decrypted to {decrypt_path}"); return exit(R_OK) if not __recursive else R_OK
|
||||
|
||||
|
||||
################
|
||||
@@ -725,6 +939,33 @@ def export_prv_identity(args, identity):
|
||||
# Helper & Utility Functions #
|
||||
##############################
|
||||
|
||||
def get_editor_content():
|
||||
import subprocess
|
||||
from tempfile import NamedTemporaryFile
|
||||
template = ""
|
||||
editor = os.environ.get("EDITOR", "")
|
||||
if not editor:
|
||||
for fallback in ["nano", "vim", "vi"]:
|
||||
try:
|
||||
subprocess.run(["which", fallback], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
editor = fallback
|
||||
break
|
||||
except subprocess.CalledProcessError: continue
|
||||
|
||||
if not editor: print("Could not launch editor"); exit(R_READ_ERROR);
|
||||
try:
|
||||
with NamedTemporaryFile(mode="w+", suffix=".tmp", delete=False) as tmp:
|
||||
tmp_path = tmp.name
|
||||
tmp.write(template)
|
||||
|
||||
result = subprocess.run([editor, tmp_path])
|
||||
if result.returncode != 0: print(f"Editor exited with error code {result.returncode}"); os.unlink(tmp_path); exit(R_READ_ERROR)
|
||||
with open(tmp_path, "r") as f: content = f.read()
|
||||
os.unlink(tmp_path)
|
||||
return content.encode("utf-8")
|
||||
|
||||
except Exception as e: print(f"Could not get content from editor: {e}"); exit(R_READ_ERROR)
|
||||
|
||||
def spin(until=None, msg=None, timeout=None):
|
||||
i = 0
|
||||
syms = "⢄⢂⢁⡁⡈⡐⡠"
|
||||
|
||||
@@ -59,7 +59,8 @@ def connect_remote(destination_hash, auth_identity, timeout, no_output = False,
|
||||
remote_identity = RNS.Identity.recall(destination_hash)
|
||||
|
||||
def remote_link_closed(link):
|
||||
if link.teardown_reason == RNS.Link.TIMEOUT:
|
||||
if link.teardown_reason == RNS.Link.INITIATOR_CLOSED: return
|
||||
elif link.teardown_reason == RNS.Link.TIMEOUT:
|
||||
if not no_output:
|
||||
print(output_rst_str, end="")
|
||||
print("The link timed out, exiting now")
|
||||
|
||||
+122
-53
@@ -60,8 +60,11 @@ def size_str(num, suffix='B'):
|
||||
|
||||
request_result = None
|
||||
request_concluded = False
|
||||
first_remote_req = True
|
||||
remote_destination = None
|
||||
remote_link = None
|
||||
def get_remote_status(destination_hash, include_lstats, identity, no_output=False, timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
|
||||
global request_result, request_concluded
|
||||
global request_result, request_concluded, first_remote_req, remote_destination, remote_link
|
||||
link_count = None
|
||||
|
||||
if not RNS.Transport.has_path(destination_hash):
|
||||
@@ -81,7 +84,8 @@ def get_remote_status(destination_hash, include_lstats, identity, no_output=Fals
|
||||
remote_identity = RNS.Identity.recall(destination_hash)
|
||||
|
||||
def remote_link_closed(link):
|
||||
if link.teardown_reason == RNS.Link.TIMEOUT:
|
||||
if link.teardown_reason == RNS.Link.INITIATOR_CLOSED: return
|
||||
elif link.teardown_reason == RNS.Link.TIMEOUT:
|
||||
if not no_output:
|
||||
print("\r \r", end="")
|
||||
print("The link timed out, exiting now")
|
||||
@@ -107,44 +111,50 @@ def get_remote_status(destination_hash, include_lstats, identity, no_output=Fals
|
||||
response = request_receipt.response
|
||||
if isinstance(response, list):
|
||||
status = response[0]
|
||||
if len(response) > 1:
|
||||
link_count = response[1]
|
||||
else:
|
||||
link_count = None
|
||||
if len(response) > 1: link_count = response[1]
|
||||
else: link_count = None
|
||||
|
||||
request_result = (status, link_count)
|
||||
|
||||
request_concluded = True
|
||||
|
||||
def remote_link_established(link):
|
||||
if not no_output:
|
||||
global first_remote_req
|
||||
if not no_output and first_remote_req:
|
||||
print("\r \r", end="")
|
||||
print("Sending request...", end=" ")
|
||||
sys.stdout.flush()
|
||||
link.identify(identity)
|
||||
link.request("/status", data = [include_lstats], response_callback = got_response, failed_callback = request_failed)
|
||||
first_remote_req = False
|
||||
|
||||
if not no_output:
|
||||
if not remote_link and not no_output:
|
||||
print("\r \r", end="")
|
||||
print("Establishing link with remote transport instance...", end=" ")
|
||||
sys.stdout.flush()
|
||||
|
||||
remote_destination = RNS.Destination(remote_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "rnstransport", "remote", "management")
|
||||
link = RNS.Link(remote_destination)
|
||||
link.set_link_established_callback(remote_link_established)
|
||||
link.set_link_closed_callback(remote_link_closed)
|
||||
if not remote_destination:
|
||||
remote_destination = RNS.Destination(remote_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "rnstransport", "remote", "management")
|
||||
|
||||
if remote_link and remote_link.status == RNS.Link.ACTIVE:
|
||||
request_concluded = False
|
||||
remote_link.request("/status", data = [include_lstats], response_callback = got_response, failed_callback = request_failed)
|
||||
|
||||
while not request_concluded:
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
remote_link = RNS.Link(remote_destination)
|
||||
remote_link.set_link_established_callback(remote_link_established)
|
||||
remote_link.set_link_closed_callback(remote_link_closed)
|
||||
|
||||
while not request_concluded: time.sleep(0.1)
|
||||
|
||||
if request_result != None:
|
||||
print("\r \r", end="")
|
||||
|
||||
return request_result
|
||||
|
||||
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=False, astats=False, lstats=False, sorting=None, sort_reverse=False,
|
||||
remote=None, management_identity=None, remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT, must_exit=True, rns_instance=None,
|
||||
traffic_totals=False, discovered_interfaces=False, config_entries=False):
|
||||
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, remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT, must_exit=True,
|
||||
rns_instance=None, traffic_totals=False, discovered_interfaces=False, config_entries=False, burst_filter=False):
|
||||
|
||||
if remote: require_shared = False
|
||||
else: require_shared = True
|
||||
@@ -300,28 +310,22 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
||||
|
||||
if remote:
|
||||
try:
|
||||
if management_identity is None:
|
||||
raise ValueError("Remote management requires an identity file. Use -i to specify the path to a management identity.")
|
||||
if management_identity is None: raise ValueError("Remote management requires an identity file. Use -i to specify the path to a management identity.")
|
||||
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||
if len(remote) != dest_len:
|
||||
raise ValueError("Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
|
||||
if len(remote) != dest_len: raise ValueError("Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
|
||||
try:
|
||||
identity_hash = bytes.fromhex(remote)
|
||||
destination_hash = RNS.Destination.hash_from_name_and_identity("rnstransport.remote.management", identity_hash)
|
||||
except Exception as e:
|
||||
raise ValueError("Invalid destination entered. Check your input.")
|
||||
except Exception as e: raise ValueError("Invalid destination entered. Check your input.")
|
||||
|
||||
identity = RNS.Identity.from_file(os.path.expanduser(management_identity))
|
||||
if identity == None:
|
||||
raise ValueError("Could not load management identity from "+str(management_identity))
|
||||
if identity == None: raise ValueError("Could not load management identity from "+str(management_identity))
|
||||
|
||||
try:
|
||||
remote_status = get_remote_status(destination_hash, lstats, identity, no_output=json, timeout=remote_timeout)
|
||||
if remote_status != None:
|
||||
stats, link_count = remote_status
|
||||
except Exception as e:
|
||||
raise e
|
||||
if remote_status != None: stats, link_count = remote_status
|
||||
except Exception as e: raise e
|
||||
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
@@ -375,6 +379,10 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
||||
interfaces.sort(key=lambda i: i["incoming_announce_frequency"], reverse=not sort_reverse)
|
||||
if sorting == "atx":
|
||||
interfaces.sort(key=lambda i: i["outgoing_announce_frequency"], reverse=not sort_reverse)
|
||||
if sorting == "prx":
|
||||
interfaces.sort(key=lambda i: i["incoming_pr_frequency"], reverse=not sort_reverse)
|
||||
if sorting == "ptx":
|
||||
interfaces.sort(key=lambda i: i["outgoing_pr_frequency"], reverse=not sort_reverse)
|
||||
if sorting == "held":
|
||||
interfaces.sort(key=lambda i: i["held_announces"], reverse=not sort_reverse)
|
||||
|
||||
@@ -393,7 +401,18 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
||||
):
|
||||
|
||||
if not (name.startswith("I2PInterface[") and ("i2p_connectable" in ifstat and ifstat["i2p_connectable"] == False)):
|
||||
if name_filter == None or name_filter.lower() in name.lower():
|
||||
if name_filter == None and burst_filter == None: show_if = True
|
||||
elif not burst_filter:
|
||||
if not name_filter or name_filter.lower() in name.lower(): show_if = True
|
||||
else: show_if = False
|
||||
elif burst_filter:
|
||||
burst_act = True if ("burst_active" in ifstat and "pr_burst_active" in ifstat) and (ifstat["burst_active"] or ifstat["pr_burst_active"]) else False
|
||||
nfilt = name_filter.lower() in name.lower() if name_filter else False
|
||||
if burst_act or nfilt: show_if = True
|
||||
else: show_if = False
|
||||
else: show_if = True
|
||||
|
||||
if show_if:
|
||||
print("")
|
||||
|
||||
if ifstat["status"]: ss = "Up"
|
||||
@@ -542,26 +561,71 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
||||
elif art and arp != None: art_str = f"(t:{RNS.prettytime(art)}/p:{RNS.prettytime(arp)})"
|
||||
elif art: art_str = f"(t:{RNS.prettytime(art)})"
|
||||
else: art_str = ""
|
||||
|
||||
burst_str = ""
|
||||
if "burst_active" in ifstat and ifstat["burst_active"]:
|
||||
for_str = RNS.prettytime(time.time()-ifstat["burst_activated"])
|
||||
burst_str = f" burst for {for_str}"
|
||||
|
||||
pburst_str = ""
|
||||
if "pr_burst_active" in ifstat and ifstat["pr_burst_active"]:
|
||||
for_str = RNS.prettytime(time.time()-ifstat["pr_burst_activated"])
|
||||
pburst_str = f"burst for {for_str}"
|
||||
|
||||
rxb_str = "↓"+RNS.prettysize(ifstat["rxb"])
|
||||
txb_str = "↑"+RNS.prettysize(ifstat["txb"])
|
||||
strdiff = len(rxb_str)-len(txb_str)
|
||||
if strdiff > 0: txb_str += " "*strdiff
|
||||
elif strdiff < 0: rxb_str += " "*-strdiff
|
||||
|
||||
|
||||
asr = False
|
||||
if astats and "incoming_announce_frequency" in ifstat and ifstat["incoming_announce_frequency"] != None:
|
||||
oaf = RNS.prettyfrequency(ifstat["outgoing_announce_frequency"])+"↑"
|
||||
iaf = RNS.prettyfrequency(ifstat["incoming_announce_frequency"])+"↓"
|
||||
if clients != None and clients > 0: pc_str = f"{RNS.prettyfrequency(ifstat["outgoing_announce_frequency"]/clients)}/c"
|
||||
oan = ifstat["outgoing_announce_frequency"]
|
||||
ian = ifstat["incoming_announce_frequency"]
|
||||
if name.startswith("Shared Instance[") and clients and clients > 0: oan = oan-(oan/clients) # Sub rnstatus own part
|
||||
oaf = RNS.prettyfrequency(oan, d=1, lpf=True)
|
||||
iaf = RNS.prettyfrequency(ian, d=1, lpf=True)
|
||||
|
||||
cspec = "c"
|
||||
if clients == None and "peers" in ifstat and ifstat["peers"]: clients = ifstat["peers"]; cspec = "p"
|
||||
if clients != None and clients > 0: pc_str = f"{RNS.prettyfrequency(ifstat['outgoing_announce_frequency']/clients, d=1, lpf=True)}/{cspec}"
|
||||
else: pc_str = ""
|
||||
strdiff = len(oaf)-len(iaf)
|
||||
if strdiff > 0: iaf += " "*strdiff
|
||||
elif strdiff < 0: oaf += " "*-strdiff
|
||||
strdiff = len(rxb_str)-len(oaf)
|
||||
if strdiff > 0: oaf += " "*strdiff
|
||||
elif strdiff < 0: txb_str += " "*-strdiff; rxb_str += " "*-strdiff
|
||||
asr = True
|
||||
|
||||
psr = False
|
||||
if pstats and "incoming_pr_frequency" in ifstat and ifstat["incoming_pr_frequency"] != None:
|
||||
opn = ifstat["outgoing_pr_frequency"]
|
||||
ipn = ifstat["incoming_pr_frequency"]
|
||||
if name.startswith("Shared Instance[") and clients and clients > 0: opn = opn-(opn/clients) # Sub rnstatus own part
|
||||
if astats:
|
||||
opf = "↑"+RNS.prettyfrequency(opn, d=1, lpf=True)
|
||||
ipf = "↓"+RNS.prettyfrequency(ipn, d=1, lpf=True)
|
||||
else:
|
||||
opf = RNS.prettyfrequency(opn,d=1, lpf=True)+"↑"
|
||||
ipf = RNS.prettyfrequency(ipn,d=1, lpf=True)+"↓"
|
||||
cspec = "c"
|
||||
if clients == None and "peers" in ifstat and ifstat["peers"]: clients = ifstat["peers"]; cspec = "p"
|
||||
if clients != None and clients > 0: rpc_str = f"{RNS.prettyfrequency(ifstat['outgoing_pr_frequency']/clients, d=1, lpf=True)}/{cspec}"
|
||||
else: rpc_str = ""
|
||||
psr = True
|
||||
|
||||
if not asr: iaf = ""; oaf = ""
|
||||
if not psr: ipf = ""; opf = ""
|
||||
amlen = max(len(iaf), len(oaf))
|
||||
iaf += (amlen-len(iaf))*" "+"↓"
|
||||
oaf += (amlen-len(oaf))*" "+"↑"
|
||||
mlen = max(max(len(iaf), len(oaf), len(rxb_str), len(txb_str), len(ipf), len(opf)), 10)
|
||||
iaf += (mlen-len(iaf))*" "
|
||||
oaf += (mlen-len(oaf))*" "
|
||||
ipf += (mlen-len(ipf))*" "
|
||||
opf += (mlen-len(opf))*" "
|
||||
rxb_str += (mlen-len(rxb_str))*" "
|
||||
txb_str += (mlen-len(txb_str))*" "
|
||||
|
||||
if psr:
|
||||
print(f" Path Rqs. : {opf} {rpc_str}")
|
||||
print(f" {ipf} {pburst_str}")
|
||||
|
||||
if asr:
|
||||
print(f" Announces : {oaf} {pc_str}")
|
||||
print(f" {iaf} {art_str}")
|
||||
print(f" {iaf} {art_str}{burst_str}")
|
||||
|
||||
rxstat = rxb_str
|
||||
txstat = txb_str
|
||||
@@ -624,9 +688,11 @@ def main(must_exit=True, rns_instance=None):
|
||||
|
||||
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)
|
||||
parser.add_argument("-l", "--link-stats", action="store_true", help="show link stats", default=False)
|
||||
parser.add_argument("-B", "--burst", action="store_true", help="only show interfaces with active bursts", default=False)
|
||||
parser.add_argument("-t", "--totals", action="store_true", help="display traffic totals", default=False)
|
||||
parser.add_argument("-s", "--sort", action="store", help="sort interfaces by [rate, traffic, rx, tx, rxs, txs, announces, arx, atx, held]", default=None, type=str)
|
||||
parser.add_argument("-s", "--sort", action="store", help="sort interfaces by [rate, traffic, rx, tx, rxs, txs, announces, arx, atx, prx, ptx, held]", default=None, type=str)
|
||||
parser.add_argument("-r", "--reverse", action="store_true", help="reverse sorting", default=False)
|
||||
parser.add_argument("-j", "--json", action="store_true", help="output in JSON format", default=False)
|
||||
parser.add_argument("-R", action="store", metavar="hash", help="transport identity hash of remote instance to get status from", default=None, type=str)
|
||||
@@ -654,15 +720,16 @@ def main(must_exit=True, rns_instance=None):
|
||||
exit(1)
|
||||
|
||||
while True:
|
||||
st = time.time()
|
||||
buffer = io.StringIO()
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = buffer
|
||||
|
||||
try:
|
||||
program_setup(configdir = configarg, dispall = args.all, verbosity=args.verbose, name_filter=args.filter, json=args.json,
|
||||
astats=args.announce_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)
|
||||
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)
|
||||
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
@@ -670,14 +737,16 @@ def main(must_exit=True, rns_instance=None):
|
||||
output = buffer.getvalue()
|
||||
print("\033[H\033[2J", end="")
|
||||
print(output, end="", flush=True)
|
||||
|
||||
time.sleep(args.monitor_interval)
|
||||
|
||||
td = time.time()-st
|
||||
sleeptime = max(args.monitor_interval-td, 0.2)
|
||||
time.sleep(sleeptime)
|
||||
|
||||
else:
|
||||
program_setup(configdir = configarg, dispall = args.all, verbosity=args.verbose, name_filter=args.filter, json=args.json,
|
||||
astats=args.announce_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)
|
||||
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)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
|
||||
+95
-114
@@ -94,22 +94,14 @@ _always_override_destination = False
|
||||
logging_lock = threading.Lock()
|
||||
|
||||
def loglevelname(level):
|
||||
if (level == LOG_CRITICAL):
|
||||
return "[Critical]"
|
||||
if (level == LOG_ERROR):
|
||||
return "[Error] "
|
||||
if (level == LOG_WARNING):
|
||||
return "[Warning] "
|
||||
if (level == LOG_NOTICE):
|
||||
return "[Notice] "
|
||||
if (level == LOG_INFO):
|
||||
return "[Info] "
|
||||
if (level == LOG_VERBOSE):
|
||||
return "[Verbose] "
|
||||
if (level == LOG_DEBUG):
|
||||
return "[Debug] "
|
||||
if (level == LOG_EXTREME):
|
||||
return "[Extra] "
|
||||
if (level == LOG_CRITICAL): return "[Critical]"
|
||||
if (level == LOG_ERROR): return "[Error] "
|
||||
if (level == LOG_WARNING): return "[Warning] "
|
||||
if (level == LOG_NOTICE): return "[Notice] "
|
||||
if (level == LOG_INFO): return "[Info] "
|
||||
if (level == LOG_VERBOSE): return "[Verbose] "
|
||||
if (level == LOG_DEBUG): return "[Debug] "
|
||||
if (level == LOG_EXTREME): return "[Extra] "
|
||||
|
||||
return "Unknown"
|
||||
|
||||
@@ -133,13 +125,10 @@ def log(msg, level=3, _override_destination = False, pt=False):
|
||||
global _always_override_destination, compact_log_fmt
|
||||
msg = str(msg)
|
||||
if loglevel >= level:
|
||||
if pt:
|
||||
logstring = "["+precise_timestamp_str(time.time())+"] "+loglevelname(level)+" "+msg
|
||||
if pt: logstring = "["+precise_timestamp_str(time.time())+"] "+loglevelname(level)+" "+msg
|
||||
else:
|
||||
if not compact_log_fmt:
|
||||
logstring = "["+timestamp_str(time.time())+"] "+loglevelname(level)+" "+msg
|
||||
else:
|
||||
logstring = "["+timestamp_str(time.time())+"] "+msg
|
||||
if not compact_log_fmt: logstring = "["+timestamp_str(time.time())+"] "+loglevelname(level)+" "+msg
|
||||
else: logstring = "["+timestamp_str(time.time())+"] "+msg
|
||||
|
||||
with logging_lock:
|
||||
if (logdest == LOG_STDOUT or _always_override_destination or _override_destination):
|
||||
@@ -182,14 +171,11 @@ def trace_exception(e):
|
||||
log(exception_info, LOG_ERROR)
|
||||
|
||||
def hexrep(data, delimit=True):
|
||||
try:
|
||||
iter(data)
|
||||
except TypeError:
|
||||
data = [data]
|
||||
try: iter(data)
|
||||
except TypeError: data = [data]
|
||||
|
||||
delimiter = ":"
|
||||
if not delimit:
|
||||
delimiter = ""
|
||||
if not delimit: delimiter = ""
|
||||
hexrep = delimiter.join("{:02x}".format(c) for c in data)
|
||||
return hexrep
|
||||
|
||||
@@ -198,11 +184,6 @@ def prettyhexrep(data):
|
||||
hexrep = "<"+delimiter.join("{:02x}".format(c) for c in data)+">"
|
||||
return hexrep
|
||||
|
||||
def prettyb256rep(data):
|
||||
delimiter = ""
|
||||
b256rep = "<"+delimiter.join(b256_rep(c) for c in data)+">"
|
||||
return b256rep
|
||||
|
||||
def prettyspeed(num, suffix="b"):
|
||||
return prettysize(num/8, suffix=suffix)+"ps"
|
||||
|
||||
@@ -217,23 +198,24 @@ def prettysize(num, suffix='B'):
|
||||
|
||||
for unit in units:
|
||||
if abs(num) < 1000.0:
|
||||
if unit == "":
|
||||
return "%.0f %s%s" % (num, unit, suffix)
|
||||
else:
|
||||
return "%.2f %s%s" % (num, unit, suffix)
|
||||
if unit == "": return "%.0f %s%s" % (num, unit, suffix)
|
||||
else: return "%.2f %s%s" % (num, unit, suffix)
|
||||
num /= 1000.0
|
||||
|
||||
return "%.2f%s%s" % (num, last_unit, suffix)
|
||||
|
||||
def prettyfrequency(hz, suffix="Hz"):
|
||||
def prettyfrequency(hz, suffix="Hz", d=2, lpf=False):
|
||||
if hz == 0: return "0 Hz"
|
||||
num = hz*1e6
|
||||
units = ["µ", "m", "", "K","M","G","T","P","E","Z"]
|
||||
if not lpf: num = hz*1e6
|
||||
else: num = hz
|
||||
if not lpf: units = ["µ", "m", "", "K","M","G","T","P","E","Z"]
|
||||
else: units = ["", "K","M","G","T","P","E","Z"]
|
||||
last_unit = "Y"
|
||||
|
||||
for unit in units:
|
||||
if abs(num) < 1000.0:
|
||||
return "%.2f %s%s" % (num, unit, suffix)
|
||||
if d == 2: return "%.2f %s%s" % (num, unit, suffix)
|
||||
else: return "%s %s%s" % (str(round(num,d)), unit, suffix)
|
||||
num /= 1000.0
|
||||
|
||||
return "%.2f%s%s" % (num, last_unit, suffix)
|
||||
@@ -248,8 +230,7 @@ def prettydistance(m, suffix="m"):
|
||||
if unit == "m": divisor = 10
|
||||
if unit == "c": divisor = 100
|
||||
|
||||
if abs(num) < divisor:
|
||||
return "%.2f %s%s" % (num, unit, suffix)
|
||||
if abs(num) < divisor: return "%.2f %s%s" % (num, unit, suffix)
|
||||
num /= divisor
|
||||
|
||||
return "%.2f %s%s" % (num, last_unit, suffix)
|
||||
@@ -266,10 +247,8 @@ def prettytime(time, verbose=False, compact=False):
|
||||
time %= 3600
|
||||
minutes = int(time // 60)
|
||||
time %= 60
|
||||
if compact:
|
||||
seconds = int(time)
|
||||
else:
|
||||
seconds = round(time, 2)
|
||||
if compact: seconds = int(time)
|
||||
else: seconds = round(time, 2)
|
||||
|
||||
ss = "" if seconds == 1 else "s"
|
||||
sm = "" if minutes == 1 else "s"
|
||||
@@ -298,22 +277,16 @@ def prettytime(time, verbose=False, compact=False):
|
||||
tstr = ""
|
||||
for c in components:
|
||||
i += 1
|
||||
if i == 1:
|
||||
pass
|
||||
elif i < len(components):
|
||||
tstr += ", "
|
||||
elif i == len(components):
|
||||
tstr += " and "
|
||||
if i == 1: pass
|
||||
elif i < len(components): tstr += ", "
|
||||
elif i == len(components): tstr += " and "
|
||||
|
||||
tstr += c
|
||||
|
||||
if tstr == "":
|
||||
return "0s"
|
||||
if tstr == "": return "0s"
|
||||
else:
|
||||
if not neg:
|
||||
return tstr
|
||||
else:
|
||||
return f"-{tstr}"
|
||||
if not neg: return tstr
|
||||
else: return f"-{tstr}"
|
||||
|
||||
def prettyshorttime(time, verbose=False, compact=False):
|
||||
neg = False
|
||||
@@ -325,10 +298,8 @@ def prettyshorttime(time, verbose=False, compact=False):
|
||||
seconds = int(time // 1e6); time %= 1e6
|
||||
milliseconds = int(time // 1e3); time %= 1e3
|
||||
|
||||
if compact:
|
||||
microseconds = int(time)
|
||||
else:
|
||||
microseconds = round(time, 2)
|
||||
if compact: microseconds = int(time)
|
||||
else: microseconds = round(time, 2)
|
||||
|
||||
ss = "" if seconds == 1 else "s"
|
||||
sms = "" if milliseconds == 1 else "s"
|
||||
@@ -352,22 +323,16 @@ def prettyshorttime(time, verbose=False, compact=False):
|
||||
tstr = ""
|
||||
for c in components:
|
||||
i += 1
|
||||
if i == 1:
|
||||
pass
|
||||
elif i < len(components):
|
||||
tstr += ", "
|
||||
elif i == len(components):
|
||||
tstr += " and "
|
||||
if i == 1: pass
|
||||
elif i < len(components): tstr += ", "
|
||||
elif i == len(components): tstr += " and "
|
||||
|
||||
tstr += c
|
||||
|
||||
if tstr == "":
|
||||
return "0us"
|
||||
if tstr == "": return "0us"
|
||||
else:
|
||||
if not neg:
|
||||
return tstr
|
||||
else:
|
||||
return f"-{tstr}"
|
||||
if not neg: return tstr
|
||||
else: return f"-{tstr}"
|
||||
|
||||
def phyparams():
|
||||
print("Required Physical Layer MTU : "+str(Reticulum.MTU)+" bytes")
|
||||
@@ -378,8 +343,7 @@ def phyparams():
|
||||
print("Link Public Key Size : "+str(Link.ECPUBSIZE*8)+" bits")
|
||||
print("Link Private Key Size : "+str(Link.KEYSIZE*8)+" bits")
|
||||
|
||||
def panic():
|
||||
os._exit(255)
|
||||
def panic(): os._exit(255)
|
||||
|
||||
exit_called = False
|
||||
def exit(code=0):
|
||||
@@ -400,8 +364,7 @@ class Profiler:
|
||||
|
||||
@staticmethod
|
||||
def get_profiler(tag=None, super_tag=None):
|
||||
if tag in Profiler.profilers:
|
||||
return Profiler.profilers[tag]
|
||||
if tag in Profiler.profilers: return Profiler.profilers[tag]
|
||||
else:
|
||||
profiler = Profiler(tag, super_tag)
|
||||
Profiler.profilers[tag] = profiler
|
||||
@@ -413,13 +376,14 @@ class Profiler:
|
||||
self.pause_started = None
|
||||
self.tag = tag
|
||||
self.super_tag = super_tag
|
||||
|
||||
if self.super_tag in Profiler.profilers:
|
||||
self.super_profiler = Profiler.profilers[self.super_tag]
|
||||
self.pause_super = self.super_profiler.pause
|
||||
self.resume_super = self.super_profiler.resume
|
||||
|
||||
else:
|
||||
def noop(self=None):
|
||||
pass
|
||||
def noop(self=None): pass
|
||||
self.super_profiler = None
|
||||
self.pause_super = noop
|
||||
self.resume_super = noop
|
||||
@@ -429,8 +393,7 @@ class Profiler:
|
||||
tag = self.tag
|
||||
super_tag = self.super_tag
|
||||
thread_ident = threading.get_ident()
|
||||
if not tag in Profiler.tags:
|
||||
Profiler.tags[tag] = {"threads": {}, "super": super_tag}
|
||||
if not tag in Profiler.tags: Profiler.tags[tag] = {"threads": {}, "super": super_tag}
|
||||
if not thread_ident in Profiler.tags[tag]["threads"]:
|
||||
Profiler.tags[tag]["threads"][thread_ident] = {"current_start": None, "captures": []}
|
||||
|
||||
@@ -466,8 +429,7 @@ class Profiler:
|
||||
self.resume_super()
|
||||
|
||||
@staticmethod
|
||||
def ran():
|
||||
return Profiler._ran
|
||||
def ran(): return Profiler._ran
|
||||
|
||||
@staticmethod
|
||||
def results():
|
||||
@@ -484,41 +446,35 @@ class Profiler:
|
||||
sample_count = len(thread_captures)
|
||||
|
||||
if sample_count > 1:
|
||||
thread_results = {
|
||||
"count": sample_count,
|
||||
"mean": mean(thread_captures),
|
||||
"median": median(thread_captures),
|
||||
"stdev": stdev(thread_captures)
|
||||
}
|
||||
thread_results = { "count": sample_count,
|
||||
"mean": mean(thread_captures),
|
||||
"median": median(thread_captures),
|
||||
"stdev": stdev(thread_captures) }
|
||||
|
||||
elif sample_count == 1:
|
||||
thread_results = {
|
||||
"count": sample_count,
|
||||
"mean": mean(thread_captures),
|
||||
"median": median(thread_captures),
|
||||
"stdev": None
|
||||
}
|
||||
thread_results = { "count": sample_count,
|
||||
"mean": mean(thread_captures),
|
||||
"median": median(thread_captures),
|
||||
"stdev": None }
|
||||
|
||||
tag_captures.extend(thread_captures)
|
||||
|
||||
sample_count = len(tag_captures)
|
||||
if sample_count > 1:
|
||||
tag_results = {
|
||||
"name": tag,
|
||||
"super": tag_entry["super"],
|
||||
"count": len(tag_captures),
|
||||
"mean": mean(tag_captures),
|
||||
"median": median(tag_captures),
|
||||
"stdev": stdev(tag_captures)
|
||||
}
|
||||
tag_results = { "name": tag,
|
||||
"super": tag_entry["super"],
|
||||
"count": len(tag_captures),
|
||||
"mean": mean(tag_captures),
|
||||
"median": median(tag_captures),
|
||||
"stdev": stdev(tag_captures) }
|
||||
|
||||
elif sample_count == 1:
|
||||
tag_results = {
|
||||
"name": tag,
|
||||
"super": tag_entry["super"],
|
||||
"count": len(tag_captures),
|
||||
"mean": mean(tag_captures),
|
||||
"median": median(tag_captures),
|
||||
"stdev": None
|
||||
}
|
||||
tag_results = { "name": tag,
|
||||
"super": tag_entry["super"],
|
||||
"count": len(tag_captures),
|
||||
"mean": mean(tag_captures),
|
||||
"median": median(tag_captures),
|
||||
"stdev": None }
|
||||
|
||||
results[tag] = tag_results
|
||||
|
||||
@@ -552,6 +508,8 @@ class Profiler:
|
||||
|
||||
profile = Profiler.get_profiler
|
||||
|
||||
# The base-256 table is likely to change. Currently, it is just
|
||||
# experimental, so don't count on it too much just yet.
|
||||
b256 = [
|
||||
# 0 1 2 3 4 5 6 7 8 9 A B C D F F
|
||||
"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p", # 0x0 Latin & numerals
|
||||
@@ -572,4 +530,27 @@ b256 = [
|
||||
"𐌳","𐌸","𐌾","𐐀","𐐁","𐐂","𐐆","𐐇","𐐈","𐐉","𐐊","𐐋","𐐌","𐐍","𐐎","𐐏", # 0xF Gothic & Deseret
|
||||
]
|
||||
|
||||
def b256_rep(input_byte): return b256[int(input_byte)]
|
||||
def b256rep(data): return "".join(bytes_to_b256(data))
|
||||
def prettyb256rep(data): return f"<{b256rep(data)}>"
|
||||
|
||||
def b256_to_byte(point):
|
||||
if not type(point) == str or not len(point) == 1: raise TypeError("Invalid input data for base256 byte decode")
|
||||
try: return b256.index(point)
|
||||
except Exception as e: raise ValueError(f"Could not decode base256 byte: {e}")
|
||||
|
||||
def b256_to_bytes(b256rep):
|
||||
if not type(b256rep) == str: raise TypeError("Invalid input data for base256 decode")
|
||||
try: return bytes([b256.index(c) for c in b256rep])
|
||||
except Exception as e: raise ValueError(f"Could not decode base256: {e}")
|
||||
|
||||
def byte_to_b256(input_byte):
|
||||
if type(input_byte) == bytes and not len(input_byte) == 1: TypeError("Invalid input data for base256 byte encode")
|
||||
if type(input_byte) == bytes and len(input_byte) == 1: input_byte = ord(input_byte)
|
||||
if not type(input_byte) == int: raise TypeError("Invalid input data for base256 byte encode")
|
||||
try: return b256[int(input_byte)]
|
||||
except Exception as e: raise TypeError(f"Could not encode byte to base256: {e}")
|
||||
|
||||
def bytes_to_b256(data):
|
||||
if not type(data) == bytes: raise TypeError("Invalid input data for base256 encode")
|
||||
try: return [byte_to_b256(c) for c in data]
|
||||
except Exception as e: raise TypeError(f"Could not encode to base256: {e}")
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "1.2.4"
|
||||
__version__ = "1.2.7"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: 33b573b4fa6e799ddf8fd65e522e0b14
|
||||
config: eebf90727067dce1facd726f357a2aab
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 75 KiB |
@@ -4,14 +4,16 @@
|
||||
Git Over Reticulum
|
||||
******************
|
||||
|
||||
A set of utilities for distributed collaborative software development and publishing is included in RNS.
|
||||
A set of utilities for distributed collaborative software development and publishing are included in RNS.
|
||||
|
||||
The system consists of two parts: The ``rngit`` node that hosts repositories, and the ``git-remote-rns`` helper that enables Git to communicate with rngit nodes. As soon as you have RNS installed on your system, you can transparently use Git with Reticulum-hosted repositories just like any other type of remote. Git over Reticulum uses URLs in the following format: ``rns://DESTINATION_HASH/group/repo``.
|
||||
|
||||
If you set a branch to track a Reticulum remote as the default upstream, you can simply use ``git`` as you normally would; all commands work transparently and as expected.
|
||||
|
||||
.. warning::
|
||||
**The rngit program is a new addition to RNS!** This functionality was introduced in RNS 1.2.0. While great care has been taken to design a secure, but highly configurable and flexible permission system for allowing many users to interact with many different repositories on a single node, ``rngit`` has not been tested extensively in the wild! Be careful when hosting repositories, especially if they are public or semi-public.
|
||||
**The rngit program is a new addition to RNS!** This functionality was introduced in RNS 1.2.0. While great care has been taken to design a secure, but highly configurable and flexible `permission system`_ for allowing many users to interact with many different repositories on a single node, ``rngit`` has not been tested extensively in the wild! Be careful when hosting repositories, especially if they are public or semi-public.
|
||||
|
||||
.. _permission system: #permissions
|
||||
|
||||
The rngit Utility
|
||||
=================
|
||||
@@ -31,7 +33,7 @@ Run ``rngit`` to start a repository node:
|
||||
|
||||
On the first run, ``rngit`` will create a default configuration file. You will then need to edit this, to point to your repository locations, configure access permissions, and perform any other necessary configuration.
|
||||
|
||||
View your identity and destination hashes:
|
||||
Them, view your identity and destination hashes:
|
||||
|
||||
.. code:: text
|
||||
|
||||
@@ -73,6 +75,13 @@ Get changes from a remote repository:
|
||||
|
||||
$ git pull rns_remote master
|
||||
|
||||
Fork an existing repository from a remote to your ``rngit`` node:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit fork rns://8a37cdd16938ce79861561adbd59023a/reticulum/lxmf rns://50824b711717f97c2fb1166ceddd5ea9/public/myfork
|
||||
|
||||
|
||||
**All Command-Line Options (rngit)**
|
||||
|
||||
.. code:: text
|
||||
@@ -104,12 +113,223 @@ The ``git-remote-rns`` helper is automatically invoked by Git when interacting w
|
||||
The client configuration file is located at ``~/.rngit/client_config`` and allows adjusting parameters such as the reference batch size for transfers.
|
||||
|
||||
|
||||
Repository Creation & Management
|
||||
================================
|
||||
|
||||
The ``rngit`` utility provides several ways to create and manage repositories on a node: creating empty repositories, forking from existing repositories, and mirroring remote repositories.
|
||||
|
||||
Creating Empty Repositories
|
||||
---------------------------
|
||||
|
||||
To create a new empty repository on a remote node:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit create rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo
|
||||
|
||||
Repository public/myrepo created
|
||||
|
||||
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.
|
||||
|
||||
**All Command-Line Options (rngit create)**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rngit create [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
repository
|
||||
|
||||
Reticulum Git Repository Creation
|
||||
|
||||
positional arguments:
|
||||
repository URL of repository to create
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
Forking Repositories
|
||||
--------------------
|
||||
|
||||
Forking creates a copy of an existing repository (from any accessible Git URL) on your ``rngit`` node. Forks maintain a reference to their upstream source for later synchronization.
|
||||
|
||||
To fork a repository:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit fork https://github.com/user/original rns://50824b711717f97c2fb1166ceddd5ea9/public/myfork
|
||||
|
||||
Repository forked to public/myfork
|
||||
|
||||
The source can be any valid Git URL, including:
|
||||
|
||||
- HTTPS URLs: ``https://github.com/user/repo.git``
|
||||
- Git URLs: ``git://host.com/repo.git``
|
||||
- SSH URLs: ``ssh://git@host.com/repo.git``
|
||||
- Reticulum URLs: ``rns://DESTINATION_HASH/group/repo``
|
||||
- Local paths: ``/path/to/repo.git``
|
||||
|
||||
Forks are created as bare repositories with metadata tracking their origin. The fork process:
|
||||
|
||||
1. Creates a new bare repository
|
||||
2. Fetches all refs (``+refs/*:refs/*``) from the source
|
||||
3. Sets ``repository.rngit.type`` to ``fork``
|
||||
4. Sets ``repository.rngit.upstream.source`` to the source URL
|
||||
5. Grants creator admin permissions
|
||||
|
||||
**All Command-Line Options (rngit fork)**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rngit fork [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
source target
|
||||
|
||||
Reticulum Git Repository Forker
|
||||
|
||||
positional arguments:
|
||||
source URL of source repository
|
||||
target URL of target repository
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
Mirroring Repositories
|
||||
----------------------
|
||||
|
||||
Mirrors are similar to forks but are designed for keeping a local copy synchronized with an upstream repository. Mirrors can be automatically updated on a configurable schedule.
|
||||
|
||||
To create a mirror:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit mirror https://github.com/user/upstream rns://50824b711717f97c2fb1166ceddd5ea9/public/mymirror
|
||||
|
||||
Repository mirrored to public/mymirror
|
||||
|
||||
Mirrors are created with the same process as forks, but with ``repository.rngit.type`` set to ``mirror`` and an additional ``repository.rngit.upstream.sync`` timestamp tracking the last successful synchronization.
|
||||
|
||||
**All Command-Line Options (rngit mirror)**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rngit mirror [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
source target
|
||||
|
||||
Reticulum Git Mirror Management
|
||||
|
||||
positional arguments:
|
||||
source URL of source repository
|
||||
target URL of target repository
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
Automatic Mirror Synchronization
|
||||
--------------------------------
|
||||
|
||||
The ``rngit`` node can automatically keep mirrors synchronized with their upstream sources. This is configured in the main configuration file:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[rngit]
|
||||
mirror_interval = 24
|
||||
|
||||
The ``mirror_interval`` specifies the synchronization interval in hours (default: 24). The node checks for mirrors needing sync every 15 minutes, and fetches updates from upstream if the configured interval has elapsed since the last sync.
|
||||
|
||||
For automatic sync to happen, the repository must have been created with ``rngit mirror``. Sync failures are logged but do not prevent future retry attempts. The sync timestamp is only updated on successful completion.
|
||||
|
||||
Manual Synchronization
|
||||
----------------------
|
||||
|
||||
Both forks and mirrors can be manually synchronized on demand using the ``sync`` command:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit sync rns://50824b711717f97c2fb1166ceddd5ea9/public/myfork
|
||||
|
||||
Repository synced
|
||||
|
||||
This fetches all refs from the upstream source configured when the repository was created. You must have ``read`` and ``write`` permissions for the repository to perform a manual sync.
|
||||
|
||||
For mirrors, manual sync also updates the sync timestamp, effectively resetting the automatic sync timer.
|
||||
|
||||
**All Command-Line Options (rngit sync)**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rngit sync [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
repository
|
||||
|
||||
Reticulum Git Repository Syncer
|
||||
|
||||
positional arguments:
|
||||
repository URL of repository
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
Git Configuration Parameters
|
||||
----------------------------
|
||||
|
||||
Repositories created through ``rngit`` store metadata in Git configuration:
|
||||
|
||||
- ``repository.rngit.type`` - Either ``fork`` or ``mirror``
|
||||
- ``repository.rngit.upstream.source`` - The source URL used during creation
|
||||
- ``repository.rngit.upstream.sync`` - Unix timestamp of last successful sync for mirrors
|
||||
|
||||
These parameters are used by the sync system and can be queried using standard Git commands:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ git config --get repository.rngit.type
|
||||
mirror
|
||||
|
||||
$ git config --get repository.rngit.upstream.source
|
||||
https://github.com/user/upstream
|
||||
|
||||
$ git config --get repository.rngit.upstream.sync
|
||||
1716230400
|
||||
|
||||
|
||||
|
||||
|
||||
Repository Structure
|
||||
====================
|
||||
|
||||
The ``rngit`` node organizes repositories into groups. Each group is a directory containing bare Git repositories. The repository path format is ``group_name/repo_name``. For example, a repository at ``/var/git/public/myrepo`` would be accessible as ``public/myrepo`` via the URL ``rns://DESTINATION_HASH/public/myrepo``.
|
||||
|
||||
**Configuration**
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The ``rngit`` node configuration file is located at ``~/.rngit/config`` (or ``/etc/rngit/config`` for system-wide installations). The default configuration includes:
|
||||
|
||||
@@ -118,20 +338,186 @@ The ``rngit`` node configuration file is located at ``~/.rngit/config`` (or ``/e
|
||||
- Announce intervals for network visibility
|
||||
- Optional statistics recording for repository activity
|
||||
|
||||
Access permissions can be configured at the group level in the config file, or per-repository using ``.allowed`` files. Permissions use the format ``permission:target`` where permission is ``r`` (read), ``w`` (write), ``rw`` (read/write), ``c`` (create) or ``s`` (stats) and target is ``all``, ``none``, or a specific identity hash.
|
||||
|
||||
The ``s`` (stats) permission allows viewing repository activity statistics, including views, fetches and pushes over time. To enable statistics recording, set ``record_stats = yes`` in the ``[rngit]`` section of the configuration file. You can also exclude specific identities from statistics by adding their hashes to ``stats_ignore_identities``.
|
||||
Permissions
|
||||
===========
|
||||
|
||||
Repository-specific ``.allowed`` files can be static text files or executable scripts that output permission rules to stdout. A ``group.allowed`` file in a repository group directory applies to all repositories within that group.
|
||||
The ``rngit`` permission system provides fine-grained access control at multiple levels: group-level, repository-level, and document-level. Permissions can be statically configured in files or dynamically generated via executable scripts.
|
||||
|
||||
Access permissions can be configured at the group level in the config file or per-group ``.allowed`` files, or per-repository ``.allowed`` files. The ``s`` (stats) permission allows viewing repository activity statistics, including views, fetches and pushes over time. To enable statistics recording, set ``record_stats = yes`` in the ``[rngit]`` section of the configuration file. You can also exclude specific identities from statistics by adding their hashes to ``stats_ignore_identities``.
|
||||
|
||||
By default, **no** permissions are granted for anything! You will have to enable the permissions you require to be able to actually *do* something with ``rngit``.
|
||||
|
||||
Permission Types
|
||||
----------------
|
||||
|
||||
The following permissions are supported:
|
||||
|
||||
- ``r`` (read) - Clone, fetch, and view repositories and work documents
|
||||
- ``w`` (write) - Push changes and manage work documents
|
||||
- ``rw`` (read/write) - Combined read and write access
|
||||
- ``c`` (create) - Create, fork or mirror new repositories within a group
|
||||
- ``s`` (stats) - View repository activity statistics
|
||||
- ``rel`` (release) - Create and manage releases
|
||||
- ``i`` (interact) - Comment on and interact with work documents
|
||||
- ``p`` (propose) - Propose new work documents (without full write access)
|
||||
- ``adm`` (admin) - Full access
|
||||
|
||||
Permission targets can be:
|
||||
|
||||
- ``all`` or ``a`` - Everyone
|
||||
- ``none`` or ``n`` - Nobody
|
||||
- A specific Reticulum identity hash
|
||||
|
||||
Permission Hierarchy
|
||||
--------------------
|
||||
|
||||
Permissions are resolved in the following hierarchy:
|
||||
|
||||
1. **Repository-level permissions** - Checked first, if none exists group permissions are checked
|
||||
2. **Group-level permissions** - Used as fallback if no repository-level permissions are set
|
||||
3. **Admin override** - Finally, potential admin rights are checked
|
||||
|
||||
For work documents, work document specific permissions are always checked first, and work documents have additional specific checks such as modifications only being possible by the document author.
|
||||
|
||||
Configuration Methods
|
||||
---------------------
|
||||
|
||||
**Group-Level Configuration**
|
||||
|
||||
Group permissions can be configured in the ``[access]`` section of the main config file:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[access]
|
||||
public = r:all, w:9710b86ba12c42d1d8f30f74fe509286
|
||||
internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
collaborative = r:all, i:all, p:all, w:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
Additionally, they can be configured in a group ``group_name.allowed`` file, placed next to the ``group_name`` group directory.
|
||||
|
||||
**Repository-Level Configuration**
|
||||
|
||||
Repository-specific permissions are set in ``.allowed`` files placed next to the repository directory (for example, ``myrepo.allowed`` for ``myrepo``):
|
||||
|
||||
.. code:: text
|
||||
|
||||
# myrepo.allowed
|
||||
r:all
|
||||
w:9710b86ba12c42d1d8f30f74fe509286
|
||||
rel:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
**Dynamic Permissions**
|
||||
|
||||
Permission files can be made executable to generate permissions dynamically:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ chmod +x myrepo.allowed
|
||||
|
||||
When executable, the script is run and its stdout is parsed as permission rules. This allows integration with external authentication systems.
|
||||
|
||||
Work Document Permissions
|
||||
-------------------------
|
||||
|
||||
Work documents support additional permission granularity through ``.allowed`` files in the work directory (e.g., ``42.allowed`` for document #42). These files use the same permission syntax but only support:
|
||||
|
||||
- ``r`` (read) - View the document
|
||||
- ``w`` (write) - Edit the document
|
||||
- ``i`` (interact) - Comment on the document
|
||||
- ``p`` (propose) - Propose changes (future use)
|
||||
- ``adm`` (admin) - Full control over the document
|
||||
|
||||
Document permissions override repository permissions for that specific document. Work document permissions can be updated simply by editing the ``.allowed`` file, or remotely by using the ``rngit work`` command.
|
||||
|
||||
Creator Permissions
|
||||
-------------------
|
||||
|
||||
When a user creates a repository (via ``create``, ``fork``, or ``mirror``), they are automatically granted ``adm`` (admin) permissions on that repository.
|
||||
|
||||
When a user creates a work document, they automatically receive ``interact`` and ``write`` permissions on that document.
|
||||
|
||||
Permission Examples
|
||||
-------------------
|
||||
|
||||
**Example 1: Public Read, Restricted Write**
|
||||
|
||||
.. code:: text
|
||||
|
||||
r:all
|
||||
w:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
Everyone can read, only the specified identity can write.
|
||||
|
||||
**Example 2: Collaborative Development**
|
||||
|
||||
.. code:: text
|
||||
|
||||
r:all
|
||||
i:all
|
||||
p:all
|
||||
w:9710b86ba12c42d1d8f30f74fe509286
|
||||
rel:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
Everyone can read, interact (comment), and propose work documents. Only the specified identity can write, create releases, and manage work documents fully.
|
||||
|
||||
**Example 3: Private Repository**
|
||||
|
||||
.. code:: text
|
||||
|
||||
rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
rw:a1b2c3d4e5f686ba12c42d1ba12ef1aa
|
||||
|
||||
Only the two specified identities have any access (read or write).
|
||||
|
||||
**Example 4: Mirror with Stats**
|
||||
|
||||
.. code:: text
|
||||
|
||||
r:all
|
||||
s:all
|
||||
w:none
|
||||
|
||||
Everyone can read and view stats, but nobody can push (mirror is read-only from upstream).
|
||||
|
||||
Permission Short Forms
|
||||
----------------------
|
||||
|
||||
Permissions can be specified using short or long forms:
|
||||
|
||||
- ``r`` = ``read``
|
||||
- ``w`` = ``write``
|
||||
- ``rw`` = ``readwrite``
|
||||
- ``c`` = ``create``
|
||||
- ``s`` = ``stats``
|
||||
- ``rel`` = ``release``
|
||||
- ``i`` = ``interact``
|
||||
- ``p`` = ``propose``
|
||||
- ``adm`` = ``admin``
|
||||
|
||||
Targets can also use short forms:
|
||||
|
||||
- ``a`` = ``all`` = ``everyone``
|
||||
- ``n`` = ``none`` = ``nobody``
|
||||
|
||||
Permission Configuration Locations
|
||||
----------------------------------
|
||||
|
||||
- User install: ``~/.rngit/config``
|
||||
- System install: ``/etc/rngit/config``
|
||||
- Group permissions: ``<group_root>/<group_name>.allowed``
|
||||
- Repository permissions: ``<group_root>/<group_name>/<repo_name>.allowed``
|
||||
- Document permissions: ``<group_root>/<group_name>.work/<doc_id>.allowed``
|
||||
|
||||
Serving Pages Over Nomad Network
|
||||
================================
|
||||
|
||||
In addition to providing Git repository access via the Git remote helper protocol, ``rngit`` can also run a `Nomad Network <https://github.com/markqvist/nomadnet>`_ compatible page node. This allows users to browse repository information, view file contents, inspect commit history and access repository statistics through any Nomad Network client.
|
||||
In addition to providing Git repository access via the Git remote helper protocol and command-line tools, ``rngit`` can also run a `Nomad Network <https://github.com/markqvist/nomadnet>`_ compatible page node. This allows users to browse repository information, view file contents, inspect commit history and access repository statistics through any Nomad Network client.
|
||||
|
||||
When enabled, the page node provides a complete interface to your repositories, with automatic Markdown to Micron conversion, syntax-highlighted code browsing, and detailed commit, diff and statistics views.
|
||||
|
||||
**Enabling the Git Page Node**
|
||||
Enabling the Git Page Node
|
||||
--------------------------
|
||||
|
||||
To enable the page node, add the following to your ``~/.rngit/config`` file:
|
||||
|
||||
@@ -151,7 +537,8 @@ When the page node is enabled, ``rngit`` will listen on a Nomad Network node des
|
||||
Repositories Destination : <0d7334d411d00120cbad24edf355fdd2>
|
||||
Nomad Network Destination : <50824b711717f97c2fb1166ceddd5ea9>
|
||||
|
||||
**Accessing Repository Pages**
|
||||
Accessing Repository Pages
|
||||
--------------------------
|
||||
|
||||
Once the page node is running, you can access it from any Nomad Network client by connecting to the Nomad Network destination. The page node provides the following views:
|
||||
|
||||
@@ -168,7 +555,7 @@ Once the page node is running, you can access it from any Nomad Network client b
|
||||
All pages respect the same permission system used for Git access. If an identity does not have read access to a repository, they will not be able to view its pages.
|
||||
|
||||
Formatting & Syntax Highlighting
|
||||
================================
|
||||
--------------------------------
|
||||
|
||||
If the ``pygments`` Python module is installed on your system, the page node will automatically apply syntax highlighting to code files. The highlighting supports a wide range of programming languages and uses a color theme optimized for terminal display.
|
||||
|
||||
@@ -192,7 +579,7 @@ Code blocks in Markdown can include language hints for syntax highlighting:
|
||||
```
|
||||
|
||||
Customizing Templates
|
||||
=====================
|
||||
---------------------
|
||||
|
||||
The page node uses a template system that allows complete customization of the generated pages. Templates are stored in the ``~/.rngit/templates/`` directory as Micron files.
|
||||
|
||||
@@ -233,11 +620,12 @@ By default, the page node uses Nerd Font icons. If you prefer simpler icons or y
|
||||
serve_nomadnet = yes
|
||||
unicode_icons = yes
|
||||
|
||||
**Repository Statistics**
|
||||
Repository Statistics
|
||||
---------------------
|
||||
|
||||
When statistics recording is enabled (see the ``record_stats`` configuration option), the page node can display activity charts for each repository. The statistics page shows:
|
||||
|
||||
- Total and peak views, fetches and pushes
|
||||
- Total and peak views, downloads, fetches and pushes
|
||||
- Daily activity charts over a 90-day period
|
||||
- Combined activity visualization
|
||||
|
||||
@@ -247,28 +635,29 @@ To view statistics, a user must have the ``s`` (stats) permission for the reposi
|
||||
|
||||
The page node includes a "Thanks" feature that allows users to express appreciation for a repository. On each repository page, a "Thanks" link is displayed showing the current thanks count. Clicking this link registers a thank you for the repository.
|
||||
|
||||
**Configuration Example**
|
||||
Configuration Example
|
||||
---------------------
|
||||
|
||||
A complete page node configuration might look like this:
|
||||
A complete node configuration might look like this:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[rngit]
|
||||
node_name = My Git Node
|
||||
announce_interval = 360
|
||||
record_stats = yes
|
||||
node_name = My Git Node
|
||||
announce_interval = 360
|
||||
record_stats = yes
|
||||
|
||||
[repositories]
|
||||
public = /var/git/public
|
||||
internal = /var/git/internal
|
||||
public = /var/git/public
|
||||
internal = /var/git/internal
|
||||
|
||||
[access]
|
||||
public = r:all
|
||||
internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
public = r:all
|
||||
internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
[pages]
|
||||
serve_nomadnet = yes
|
||||
unicode_icons = no
|
||||
serve_nomadnet = yes
|
||||
unicode_icons = no
|
||||
|
||||
|
||||
Release Management
|
||||
@@ -276,7 +665,8 @@ Release Management
|
||||
|
||||
In addition to hosting Git repositories, ``rngit`` provides a complete release management system. This allows you to publish versioned releases with associated artifacts, release notes and metadata. Releases are managed through the ``rngit release`` subcommand, and are also viewable through the Nomad Network page interface.
|
||||
|
||||
**The Release Workflow**
|
||||
The Release Workflow
|
||||
--------------------
|
||||
|
||||
Creating a release involves specifying a Git tag and a directory containing build artifacts or other files to distribute. The ``rngit`` client will open your configured ``$EDITOR`` to compose release notes, then upload all artifacts to the remote repository node.
|
||||
|
||||
@@ -295,7 +685,8 @@ This will:
|
||||
|
||||
If no ``$EDITOR`` environment variable is set, ``rngit`` will try to use ``nano``, ``vim`` or ``vi``. The editor will show a template with instructions. Lines starting with ``#`` will be ignored, and if the remaining content is empty after stripping comments, the release creation will be cancelled.
|
||||
|
||||
**Release Storage & Structure**
|
||||
Release Storage & Structure
|
||||
---------------------------
|
||||
|
||||
Releases are stored on the node in a directory named ``repo_name.releases`` next to the bare repository. Each release is a subdirectory containing:
|
||||
|
||||
@@ -304,6 +695,10 @@ Releases are stored on the node in a directory named ``repo_name.releases`` next
|
||||
- ``artifacts/`` - All uploaded files
|
||||
- ``THANKS`` - Appreciation count from users
|
||||
|
||||
|
||||
Command-Line Interaction
|
||||
------------------------
|
||||
|
||||
**Listing Releases**
|
||||
|
||||
To view all releases for a repository:
|
||||
@@ -375,8 +770,6 @@ Release management requires the ``release`` permission, configured the same way
|
||||
|
||||
When the Nomad Network page node is enabled, releases are displayed on a dedicated releases page for each repository. Each release is listed with its tag, creation date, artifact count and a preview of the release notes. Clicking a release shows the full details including formatted release notes and a listing of all artifacts with their sizes.
|
||||
|
||||
Only releases with ``published`` status are visible through the Nomad Network interface. Draft releases (if supported in future implementations) would only be visible through the command-line interface.
|
||||
|
||||
**All Command-Line Options (rngit release)**
|
||||
|
||||
.. code:: text
|
||||
@@ -402,12 +795,18 @@ Only releases with ``published`` status are visible through the Nomad Network in
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
|
||||
Work Documents
|
||||
==============
|
||||
|
||||
In addition to releases, ``rngit`` provides a work document management system for tracking tasks, investigations, issues and progress related to repositories. Work documents are stored as structured msgpack data and support threaded updates and comments.
|
||||
|
||||
Working With Work Documents
|
||||
---------------------------
|
||||
|
||||
**Listing Work Documents**
|
||||
|
||||
To view work documents for a repository:
|
||||
@@ -424,7 +823,7 @@ To view work documents for a repository:
|
||||
1 Implemented new feature 9710b86ba12c4f2e… 2025-01-15 14:32 3
|
||||
2 Fixed bug in parser 8f3a21c9d84e927b… 2025-01-14 09:15 1
|
||||
|
||||
Use ``--scope completed`` to view completed work documents, or ``--scope all`` to see both active and completed.
|
||||
Use ``--scope completed`` to view completed work documents, ``--scope proposed`` to view proposed documents, or ``--scope all`` to see all scopes.
|
||||
|
||||
**Viewing a Work Document**
|
||||
|
||||
@@ -482,6 +881,34 @@ To add an update to a work document:
|
||||
|
||||
This opens your editor to compose the update.
|
||||
|
||||
Proposing Work Documents
|
||||
------------------------
|
||||
|
||||
Users with ``propose`` permission can create work document proposals without full ``write`` access. Proposals are created in a "proposed" state and must be activated by a user with appropriate permissions before becoming active.
|
||||
|
||||
To propose a work document:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo propose --title "Feature proposal"
|
||||
|
||||
This opens your editor to compose the proposal content. When saved, the document is created in the "proposed" scope. The creator automatically receives ``interact`` and ``write`` permissions on the proposed document.
|
||||
|
||||
Proposed documents are visible through ``--scope proposed`` or ``--scope all``:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo list --scope proposed
|
||||
|
||||
**Permissions for Proposals**
|
||||
|
||||
- Creating proposals requires ``propose`` permission on the repository
|
||||
- The creator automatically gets ``interact`` and ``write`` on their proposed document
|
||||
- Activating a proposal requires ``write`` and ``interact`` permissions
|
||||
|
||||
State Management
|
||||
----------------
|
||||
|
||||
**Completing Work Documents**
|
||||
|
||||
To mark a work document as completed (moving it from ``active`` to ``completed``):
|
||||
@@ -513,20 +940,51 @@ To delete a work document and all its comments:
|
||||
Are you sure you want to delete active work document #1? [y/N]: y
|
||||
Work document #1 deleted
|
||||
|
||||
**Permissions**
|
||||
Managing Work Document Permissions
|
||||
----------------------------------
|
||||
|
||||
Users can view work documents and updates if the have ``read`` permission for the repository. If users have ``read`` and ``interact``, they can also post updates/comments on existing work documents. Work document management requires having ``write`` and ``interact`` permission to the repository. These permissions are configured the same way as any other repository permissions. In the config file or ``.allowed`` files, use ``i:target`` to grant work document interaction rights:
|
||||
Users with administrative access to a work document can manage its specific permissions. This allows fine-grained control over who can read, write, comment on, or administer individual work documents.
|
||||
|
||||
To view or edit permissions for a work document:
|
||||
|
||||
.. code:: text
|
||||
|
||||
# In .allowed file or config
|
||||
i:all # Allow everyone
|
||||
i:9710b86... # Allow specific identity
|
||||
i:none # Deny everyone
|
||||
$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo perms -d 1
|
||||
|
||||
**Author Verification**
|
||||
This opens your editor with the current permission configuration:
|
||||
|
||||
Users can only edit or delete work documents and updates they created. The author is cryptographically verified from the interacting link's ``remote_identity``.
|
||||
.. code:: text
|
||||
|
||||
r:all
|
||||
i:9710b86ba12c42d1d8f30f74fe509286
|
||||
adm:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
Permission rules follow the same format as repository permissions:
|
||||
|
||||
- ``r:target`` - Grant read access
|
||||
- ``w:target`` - Grant write access
|
||||
- ``i:target`` - Grant interact (comment) access
|
||||
- ``adm:target`` - Grant admin access
|
||||
|
||||
Targets can be ``all``, ``none``, or a specific identity hash.
|
||||
|
||||
**Who Can Edit Permissions**
|
||||
|
||||
Document permissions can be edited by:
|
||||
|
||||
- The original author (if they also have ``interact`` and ``write`` on the repository)
|
||||
- Any user with ``admin`` permission on the document
|
||||
- Repository admins (through inherited permissions)
|
||||
|
||||
**Permission Precedence**
|
||||
|
||||
Document-specific permissions override repository-level permissions for that document. If document permissions exist, they are checked first; if access is not granted there, repository permissions are checked.
|
||||
|
||||
**Author Rights:**
|
||||
|
||||
- Users can only edit or delete work documents they created
|
||||
- The author is cryptographically verified from the interacting link's ``remote_identity``
|
||||
- Document creators automatically receive ``interact`` and ``write`` on their documents
|
||||
|
||||
**Storage Format**
|
||||
|
||||
@@ -534,6 +992,7 @@ Work documents are stored in a ``repo_name.work`` directory next to the reposito
|
||||
|
||||
- ``active/`` - Active work documents
|
||||
- ``completed/`` - Completed work documents
|
||||
- ``proposed/`` - Proposed work documents
|
||||
|
||||
Each document is a numbered directory containing:
|
||||
|
||||
@@ -557,7 +1016,8 @@ When the Nomad Network page node is enabled, work documents are viewable through
|
||||
|
||||
positional arguments:
|
||||
repository URL of remote repository
|
||||
operation list, view, create, edit, delete, update or complete
|
||||
operation list, view, create, propose, edit, delete,
|
||||
update, complete, activate or perms
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
@@ -565,8 +1025,8 @@ When the Nomad Network page node is enabled, work documents are viewable through
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
--scope SCOPE document scope: active, completed or all
|
||||
-t, --title TITLE document title for create
|
||||
--scope SCOPE document scope: active, completed, proposed or all
|
||||
-t, --title TITLE document title for create/propose
|
||||
-d, --id ID document ID
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
|
||||
@@ -1372,11 +1372,12 @@ a large amount of bogus destinations, and then disconnect, these destination wil
|
||||
never make it into path tables and waste network bandwidth on retransmitted
|
||||
announces.
|
||||
|
||||
**It's important to note** that the ingress control works at the level of *individual
|
||||
sub-interfaces*. As an example, this means that one client on a :ref:`TCP Server Interface<interfaces-tcps>`
|
||||
cannot disrupt processing of incoming announces for other connected clients on the same
|
||||
:ref:`TCP Server Interface<interfaces-tcps>`. All other clients on the same interface will still have new announces
|
||||
processed without interruption.
|
||||
.. note::
|
||||
It's important to remember that the ingress control works at the level of *individual
|
||||
sub-interfaces*. As an example, this means that one client on a :ref:`TCP Server Interface<interfaces-tcps>`
|
||||
cannot disrupt processing of incoming announces for other connected clients on the same
|
||||
:ref:`TCP Server Interface<interfaces-tcps>`. All other clients on the same interface
|
||||
will still have new announces processed without interruption.
|
||||
|
||||
By default, Reticulum will handle this automatically, and ingress announce
|
||||
control will be enabled on interface where it is sensible to do so. It should
|
||||
@@ -1384,8 +1385,7 @@ generally not be neccessary to modify the ingress control configuration,
|
||||
but all the parameters are exposed for configuration if needed.
|
||||
|
||||
* | The ``ingress_control`` option tells Reticulum whether or not
|
||||
to enable announce ingress control on the interface. Defaults to
|
||||
``True``.
|
||||
to enable ingress control on the interface. Defaults to ``True``.
|
||||
|
||||
* | The ``ic_new_time`` option configures how long (in seconds) an
|
||||
interface is considered newly spawned. Defaults to ``2*60*60`` seconds. This
|
||||
@@ -1422,3 +1422,59 @@ but all the parameters are exposed for configuration if needed.
|
||||
must pass between releasing each held announce from the queue. Defaults
|
||||
to ``30`` seconds.
|
||||
|
||||
All of the above settings can be configured both as instance-wide defaults
|
||||
under the ``[reticulum]`` section of the configuration file, or on a per-
|
||||
interface basis under the relevant interface configuration section.
|
||||
|
||||
|
||||
Path Request Burst Control
|
||||
==========================
|
||||
|
||||
In addition the announce controls for newly created destination, Reticulum will also
|
||||
monitor incoming path request activity, and enforce burst controls if per-client rates
|
||||
exceed configured limits. Once path request burst control is activated on an
|
||||
interface, path requests will no longer be propagated further on the network.
|
||||
As with announce burst control, this happens on a per sub-interface basis. One
|
||||
client connecting to a public gateway will not be able to disrupt path request
|
||||
processing for other clients.
|
||||
|
||||
.. warning::
|
||||
Applications that send large amounts of unnecessary path requests will very
|
||||
quickly get rate limited by transport nodes, and the entire system they are
|
||||
running on will not be able to resolve any paths on the network, until the
|
||||
burst subsides and hold period expires. **Do not** write applications like
|
||||
this. Only request paths for destinations you need to communicate with.
|
||||
|
||||
By default, Reticulum will handle this automatically, and ingress path request
|
||||
control will be enabled on interface where it is sensible to do so. It should
|
||||
generally not be neccessary to modify the ingress control configuration,
|
||||
but all the parameters are exposed for configuration if needed.
|
||||
|
||||
* | The ``ingress_control`` option tells Reticulum whether or not
|
||||
to enable ingress control on the interface. Defaults to ``True``.
|
||||
|
||||
* | The ``ic_new_time`` option configures how long (in seconds) an
|
||||
interface is considered newly spawned. Defaults to ``2*60*60`` seconds. This
|
||||
option is useful on publicly accessible interfaces that spawn new
|
||||
sub-interfaces when a new client connects.
|
||||
|
||||
* | The ``ic_pr_burst_freq_new`` option sets the maximum path request
|
||||
ingress frequency for newly spawned interfaces. Defaults to ``3``
|
||||
path requests per second.
|
||||
|
||||
* | The ``ic_pr_burst_freq`` option sets the maximum path request
|
||||
ingress frequency for other interfaces. Defaults to ``8`` path requests
|
||||
per second.
|
||||
|
||||
*If an interface exceeds its burst frequency, incoming path requests
|
||||
from that system will not traverse the network further.*
|
||||
|
||||
* | The ``egress_control`` option enables hard-limiting path request egress
|
||||
control per-interface. Defaults to ``False``
|
||||
|
||||
* | The ``ec_pr_freq`` option sets the hard limit for outbound path requests
|
||||
per second on a given interface.
|
||||
|
||||
All of the above settings can be configured both as instance-wide defaults
|
||||
under the ``[reticulum]`` section of the configuration file, or on a per-
|
||||
interface basis under the relevant interface configuration section.
|
||||
@@ -110,7 +110,7 @@ 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 project, and is not affiliated with the original project.
|
||||
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.
|
||||
|
||||
.. only:: html
|
||||
|
||||
@@ -127,56 +127,6 @@ A `Reticulum MeshChat fork from the future <https://git.quad4.io/RNS-Things/Mesh
|
||||
|
||||
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.
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
|
||||
MeshChat
|
||||
^^^^^^^^
|
||||
|
||||
The `Reticulum MeshChat <https://github.com/liamcottle/reticulum-meshchat>`_ application
|
||||
is a user-friendly LXMF client for Linux, macOS and Windows, that also includes a Nomad Network
|
||||
page browser and other interesting functionality.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/meshchat_1.webp
|
||||
:align: center
|
||||
:target: https://github.com/liamcottle/reticulum-meshchat
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/meshchat_1.png
|
||||
:align: center
|
||||
:target: https://github.com/liamcottle/reticulum-meshchat
|
||||
|
||||
Reticulum MeshChat is of course also compatible with Sideband and Nomad Network, or
|
||||
any other LXMF client.
|
||||
|
||||
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.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/columba.webp
|
||||
:align: center
|
||||
:width: 25%
|
||||
:target: https://github.com/torlando-tech/columba/
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/columba.png
|
||||
:align: center
|
||||
:width: 25%
|
||||
:target: https://github.com/torlando-tech/columba/
|
||||
|
||||
While still in early and very active development, it is of course also compatible
|
||||
with all other LXMF clients, and allows you to message seamlessly with anyone else
|
||||
using LXMF.
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
|
||||
@@ -31,6 +31,10 @@ Donations are gratefully accepted via the following channels:
|
||||
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
|
||||
|
||||
Provide Feedback
|
||||
================
|
||||
Feedback on the usage, functioning and potential dysfunctioning of any and
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const DOCUMENTATION_OPTIONS = {
|
||||
VERSION: '1.2.4',
|
||||
VERSION: '1.2.7',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Code Examples - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>Code Examples - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -3664,7 +3664,7 @@ will be fully on-par with natively included interfaces, including all supported
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -295,7 +295,7 @@
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#"><link rel="search" title="Search" href="search.html">
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 --><title>Index - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 --><title>Index - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -178,7 +178,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -202,7 +202,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -839,7 +839,7 @@
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Getting Started Fast - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -967,7 +967,7 @@ All other available modules will still be loaded when needed.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></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>
|
||||
|
||||
+478
-48
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Git Over Reticulum - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>Git Over Reticulum - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -263,12 +263,12 @@
|
||||
<article role="main" id="furo-main-content">
|
||||
<section id="git-over-reticulum">
|
||||
<span id="git-main"></span><h1>Git Over Reticulum<a class="headerlink" href="#git-over-reticulum" title="Link to this heading">¶</a></h1>
|
||||
<p>A set of utilities for distributed collaborative software development and publishing is included in RNS.</p>
|
||||
<p>A set of utilities for distributed collaborative software development and publishing are included in RNS.</p>
|
||||
<p>The system consists of two parts: The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> node that hosts repositories, and the <code class="docutils literal notranslate"><span class="pre">git-remote-rns</span></code> helper that enables Git to communicate with rngit nodes. As soon as you have RNS installed on your system, you can transparently use Git with Reticulum-hosted repositories just like any other type of remote. Git over Reticulum uses URLs in the following format: <code class="docutils literal notranslate"><span class="pre">rns://DESTINATION_HASH/group/repo</span></code>.</p>
|
||||
<p>If you set a branch to track a Reticulum remote as the default upstream, you can simply use <code class="docutils literal notranslate"><span class="pre">git</span></code> as you normally would; all commands work transparently and as expected.</p>
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Warning</p>
|
||||
<p><strong>The rngit program is a new addition to RNS!</strong> This functionality was introduced in RNS 1.2.0. While great care has been taken to design a secure, but highly configurable and flexible permission system for allowing many users to interact with many different repositories on a single node, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> has not been tested extensively in the wild! Be careful when hosting repositories, especially if they are public or semi-public.</p>
|
||||
<p><strong>The rngit program is a new addition to RNS!</strong> This functionality was introduced in RNS 1.2.0. While great care has been taken to design a secure, but highly configurable and flexible <a class="reference external" href="#permissions">permission system</a> for allowing many users to interact with many different repositories on a single node, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> has not been tested extensively in the wild! Be careful when hosting repositories, especially if they are public or semi-public.</p>
|
||||
</div>
|
||||
<section id="the-rngit-utility">
|
||||
<h2>The rngit Utility<a class="headerlink" href="#the-rngit-utility" title="Link to this heading">¶</a></h2>
|
||||
@@ -282,7 +282,7 @@
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>On the first run, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> will create a default configuration file. You will then need to edit this, to point to your repository locations, configure access permissions, and perform any other necessary configuration.</p>
|
||||
<p>View your identity and destination hashes:</p>
|
||||
<p>Them, view your identity and destination hashes:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit --print-identity
|
||||
|
||||
Git Peer Identity : <959e10e5efc1bd9d97a4083babe51dea>
|
||||
@@ -311,6 +311,10 @@ Repositories Destination : <0d7334d411d00120cbad24edf355fdd2>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ git pull rns_remote master
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Fork an existing repository from a remote to your <code class="docutils literal notranslate"><span class="pre">rngit</span></code> node:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit fork rns://8a37cdd16938ce79861561adbd59023a/reticulum/lxmf rns://50824b711717f97c2fb1166ceddd5ea9/public/myfork
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>All Command-Line Options (rngit)</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rngit.py [-h] [--config CONFIG] [--rnsconfig RNSCONFIG] [-s] [-i] [-v]
|
||||
[-q] [--version]
|
||||
@@ -338,10 +342,189 @@ options:
|
||||
</ul>
|
||||
<p>The client configuration file is located at <code class="docutils literal notranslate"><span class="pre">~/.rngit/client_config</span></code> and allows adjusting parameters such as the reference batch size for transfers.</p>
|
||||
</section>
|
||||
<section id="repository-creation-management">
|
||||
<h2>Repository Creation & Management<a class="headerlink" href="#repository-creation-management" title="Link to this heading">¶</a></h2>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> utility provides several ways to create and manage repositories on a node: creating empty repositories, forking from existing repositories, and mirroring remote repositories.</p>
|
||||
<section id="creating-empty-repositories">
|
||||
<h3>Creating Empty Repositories<a class="headerlink" href="#creating-empty-repositories" title="Link to this heading">¶</a></h3>
|
||||
<p>To create a new empty repository on a remote node:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit create rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo
|
||||
|
||||
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>
|
||||
<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]
|
||||
repository
|
||||
|
||||
Reticulum Git Repository Creation
|
||||
|
||||
positional arguments:
|
||||
repository URL of repository to create
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="forking-repositories">
|
||||
<h3>Forking Repositories<a class="headerlink" href="#forking-repositories" title="Link to this heading">¶</a></h3>
|
||||
<p>Forking creates a copy of an existing repository (from any accessible Git URL) on your <code class="docutils literal notranslate"><span class="pre">rngit</span></code> node. Forks maintain a reference to their upstream source for later synchronization.</p>
|
||||
<p>To fork a repository:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit fork https://github.com/user/original rns://50824b711717f97c2fb1166ceddd5ea9/public/myfork
|
||||
|
||||
Repository forked to public/myfork
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The source can be any valid Git URL, including:</p>
|
||||
<ul class="simple">
|
||||
<li><p>HTTPS URLs: <code class="docutils literal notranslate"><span class="pre">https://github.com/user/repo.git</span></code></p></li>
|
||||
<li><p>Git URLs: <code class="docutils literal notranslate"><span class="pre">git://host.com/repo.git</span></code></p></li>
|
||||
<li><p>SSH URLs: <code class="docutils literal notranslate"><span class="pre">ssh://git@host.com/repo.git</span></code></p></li>
|
||||
<li><p>Reticulum URLs: <code class="docutils literal notranslate"><span class="pre">rns://DESTINATION_HASH/group/repo</span></code></p></li>
|
||||
<li><p>Local paths: <code class="docutils literal notranslate"><span class="pre">/path/to/repo.git</span></code></p></li>
|
||||
</ul>
|
||||
<p>Forks are created as bare repositories with metadata tracking their origin. The fork process:</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p>Creates a new bare repository</p></li>
|
||||
<li><p>Fetches all refs (<code class="docutils literal notranslate"><span class="pre">+refs/*:refs/*</span></code>) from the source</p></li>
|
||||
<li><p>Sets <code class="docutils literal notranslate"><span class="pre">repository.rngit.type</span></code> to <code class="docutils literal notranslate"><span class="pre">fork</span></code></p></li>
|
||||
<li><p>Sets <code class="docutils literal notranslate"><span class="pre">repository.rngit.upstream.source</span></code> to the source URL</p></li>
|
||||
<li><p>Grants creator admin permissions</p></li>
|
||||
</ol>
|
||||
<p><strong>All Command-Line Options (rngit fork)</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rngit fork [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
source target
|
||||
|
||||
Reticulum Git Repository Forker
|
||||
|
||||
positional arguments:
|
||||
source URL of source repository
|
||||
target URL of target repository
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="mirroring-repositories">
|
||||
<h3>Mirroring Repositories<a class="headerlink" href="#mirroring-repositories" title="Link to this heading">¶</a></h3>
|
||||
<p>Mirrors are similar to forks but are designed for keeping a local copy synchronized with an upstream repository. Mirrors can be automatically updated on a configurable schedule.</p>
|
||||
<p>To create a mirror:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit mirror https://github.com/user/upstream rns://50824b711717f97c2fb1166ceddd5ea9/public/mymirror
|
||||
|
||||
Repository mirrored to public/mymirror
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Mirrors are created with the same process as forks, but with <code class="docutils literal notranslate"><span class="pre">repository.rngit.type</span></code> set to <code class="docutils literal notranslate"><span class="pre">mirror</span></code> and an additional <code class="docutils literal notranslate"><span class="pre">repository.rngit.upstream.sync</span></code> timestamp tracking the last successful synchronization.</p>
|
||||
<p><strong>All Command-Line Options (rngit mirror)</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rngit mirror [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
source target
|
||||
|
||||
Reticulum Git Mirror Management
|
||||
|
||||
positional arguments:
|
||||
source URL of source repository
|
||||
target URL of target repository
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="automatic-mirror-synchronization">
|
||||
<h3>Automatic Mirror Synchronization<a class="headerlink" href="#automatic-mirror-synchronization" title="Link to this heading">¶</a></h3>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> node can automatically keep mirrors synchronized with their upstream sources. This is configured in the main configuration file:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[rngit]
|
||||
mirror_interval = 24
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">mirror_interval</span></code> specifies the synchronization interval in hours (default: 24). The node checks for mirrors needing sync every 15 minutes, and fetches updates from upstream if the configured interval has elapsed since the last sync.</p>
|
||||
<p>For automatic sync to happen, the repository must have been created with <code class="docutils literal notranslate"><span class="pre">rngit</span> <span class="pre">mirror</span></code>. Sync failures are logged but do not prevent future retry attempts. The sync timestamp is only updated on successful completion.</p>
|
||||
</section>
|
||||
<section id="manual-synchronization">
|
||||
<h3>Manual Synchronization<a class="headerlink" href="#manual-synchronization" title="Link to this heading">¶</a></h3>
|
||||
<p>Both forks and mirrors can be manually synchronized on demand using the <code class="docutils literal notranslate"><span class="pre">sync</span></code> command:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit sync rns://50824b711717f97c2fb1166ceddd5ea9/public/myfork
|
||||
|
||||
Repository synced
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This fetches all refs from the upstream source configured when the repository was created. You must have <code class="docutils literal notranslate"><span class="pre">read</span></code> and <code class="docutils literal notranslate"><span class="pre">write</span></code> permissions for the repository to perform a manual sync.</p>
|
||||
<p>For mirrors, manual sync also updates the sync timestamp, effectively resetting the automatic sync timer.</p>
|
||||
<p><strong>All Command-Line Options (rngit sync)</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rngit sync [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
repository
|
||||
|
||||
Reticulum Git Repository Syncer
|
||||
|
||||
positional arguments:
|
||||
repository URL of repository
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="git-configuration-parameters">
|
||||
<h3>Git Configuration Parameters<a class="headerlink" href="#git-configuration-parameters" title="Link to this heading">¶</a></h3>
|
||||
<p>Repositories created through <code class="docutils literal notranslate"><span class="pre">rngit</span></code> store metadata in Git configuration:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">repository.rngit.type</span></code> - Either <code class="docutils literal notranslate"><span class="pre">fork</span></code> or <code class="docutils literal notranslate"><span class="pre">mirror</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">repository.rngit.upstream.source</span></code> - The source URL used during creation</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">repository.rngit.upstream.sync</span></code> - Unix timestamp of last successful sync for mirrors</p></li>
|
||||
</ul>
|
||||
<p>These parameters are used by the sync system and can be queried using standard Git commands:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ git config --get repository.rngit.type
|
||||
mirror
|
||||
|
||||
$ git config --get repository.rngit.upstream.source
|
||||
https://github.com/user/upstream
|
||||
|
||||
$ git config --get repository.rngit.upstream.sync
|
||||
1716230400
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<section id="repository-structure">
|
||||
<h2>Repository Structure<a class="headerlink" href="#repository-structure" title="Link to this heading">¶</a></h2>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> node organizes repositories into groups. Each group is a directory containing bare Git repositories. The repository path format is <code class="docutils literal notranslate"><span class="pre">group_name/repo_name</span></code>. For example, a repository at <code class="docutils literal notranslate"><span class="pre">/var/git/public/myrepo</span></code> would be accessible as <code class="docutils literal notranslate"><span class="pre">public/myrepo</span></code> via the URL <code class="docutils literal notranslate"><span class="pre">rns://DESTINATION_HASH/public/myrepo</span></code>.</p>
|
||||
<p><strong>Configuration</strong></p>
|
||||
<section id="configuration">
|
||||
<h3>Configuration<a class="headerlink" href="#configuration" title="Link to this heading">¶</a></h3>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> node configuration file is located at <code class="docutils literal notranslate"><span class="pre">~/.rngit/config</span></code> (or <code class="docutils literal notranslate"><span class="pre">/etc/rngit/config</span></code> for system-wide installations). The default configuration includes:</p>
|
||||
<ul class="simple">
|
||||
<li><p>Repository group paths defining where to find bare repositories</p></li>
|
||||
@@ -349,15 +532,155 @@ options:
|
||||
<li><p>Announce intervals for network visibility</p></li>
|
||||
<li><p>Optional statistics recording for repository activity</p></li>
|
||||
</ul>
|
||||
<p>Access permissions can be configured at the group level in the config file, or per-repository using <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> files. Permissions use the format <code class="docutils literal notranslate"><span class="pre">permission:target</span></code> where permission is <code class="docutils literal notranslate"><span class="pre">r</span></code> (read), <code class="docutils literal notranslate"><span class="pre">w</span></code> (write), <code class="docutils literal notranslate"><span class="pre">rw</span></code> (read/write), <code class="docutils literal notranslate"><span class="pre">c</span></code> (create) or <code class="docutils literal notranslate"><span class="pre">s</span></code> (stats) and target is <code class="docutils literal notranslate"><span class="pre">all</span></code>, <code class="docutils literal notranslate"><span class="pre">none</span></code>, or a specific identity hash.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">s</span></code> (stats) permission allows viewing repository activity statistics, including views, fetches and pushes over time. To enable statistics recording, set <code class="docutils literal notranslate"><span class="pre">record_stats</span> <span class="pre">=</span> <span class="pre">yes</span></code> in the <code class="docutils literal notranslate"><span class="pre">[rngit]</span></code> section of the configuration file. You can also exclude specific identities from statistics by adding their hashes to <code class="docutils literal notranslate"><span class="pre">stats_ignore_identities</span></code>.</p>
|
||||
<p>Repository-specific <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> files can be static text files or executable scripts that output permission rules to stdout. A <code class="docutils literal notranslate"><span class="pre">group.allowed</span></code> file in a repository group directory applies to all repositories within that group.</p>
|
||||
</section>
|
||||
</section>
|
||||
<section id="permissions">
|
||||
<h2>Permissions<a class="headerlink" href="#permissions" title="Link to this heading">¶</a></h2>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> permission system provides fine-grained access control at multiple levels: group-level, repository-level, and document-level. Permissions can be statically configured in files or dynamically generated via executable scripts.</p>
|
||||
<p>Access permissions can be configured at the group level in the config file or per-group <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> files, or per-repository <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> files. The <code class="docutils literal notranslate"><span class="pre">s</span></code> (stats) permission allows viewing repository activity statistics, including views, fetches and pushes over time. To enable statistics recording, set <code class="docutils literal notranslate"><span class="pre">record_stats</span> <span class="pre">=</span> <span class="pre">yes</span></code> in the <code class="docutils literal notranslate"><span class="pre">[rngit]</span></code> section of the configuration file. You can also exclude specific identities from statistics by adding their hashes to <code class="docutils literal notranslate"><span class="pre">stats_ignore_identities</span></code>.</p>
|
||||
<p>By default, <strong>no</strong> permissions are granted for anything! You will have to enable the permissions you require to be able to actually <em>do</em> something with <code class="docutils literal notranslate"><span class="pre">rngit</span></code>.</p>
|
||||
<section id="permission-types">
|
||||
<h3>Permission Types<a class="headerlink" href="#permission-types" title="Link to this heading">¶</a></h3>
|
||||
<p>The following permissions are supported:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">r</span></code> (read) - Clone, fetch, and view repositories and work documents</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">w</span></code> (write) - Push changes and manage work documents</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">rw</span></code> (read/write) - Combined read and write access</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">c</span></code> (create) - Create, fork or mirror new repositories within a group</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">s</span></code> (stats) - View repository activity statistics</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">rel</span></code> (release) - Create and manage releases</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">i</span></code> (interact) - Comment on and interact with work documents</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">p</span></code> (propose) - Propose new work documents (without full write access)</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">adm</span></code> (admin) - Full access</p></li>
|
||||
</ul>
|
||||
<p>Permission targets can be:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">all</span></code> or <code class="docutils literal notranslate"><span class="pre">a</span></code> - Everyone</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">none</span></code> or <code class="docutils literal notranslate"><span class="pre">n</span></code> - Nobody</p></li>
|
||||
<li><p>A specific Reticulum identity hash</p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="permission-hierarchy">
|
||||
<h3>Permission Hierarchy<a class="headerlink" href="#permission-hierarchy" title="Link to this heading">¶</a></h3>
|
||||
<p>Permissions are resolved in the following hierarchy:</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p><strong>Repository-level permissions</strong> - Checked first, if none exists group permissions are checked</p></li>
|
||||
<li><p><strong>Group-level permissions</strong> - Used as fallback if no repository-level permissions are set</p></li>
|
||||
<li><p><strong>Admin override</strong> - Finally, potential admin rights are checked</p></li>
|
||||
</ol>
|
||||
<p>For work documents, work document specific permissions are always checked first, and work documents have additional specific checks such as modifications only being possible by the document author.</p>
|
||||
</section>
|
||||
<section id="configuration-methods">
|
||||
<h3>Configuration Methods<a class="headerlink" href="#configuration-methods" title="Link to this heading">¶</a></h3>
|
||||
<p><strong>Group-Level Configuration</strong></p>
|
||||
<p>Group permissions can be configured in the <code class="docutils literal notranslate"><span class="pre">[access]</span></code> section of the main config file:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[access]
|
||||
public = r:all, w:9710b86ba12c42d1d8f30f74fe509286
|
||||
internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
collaborative = r:all, i:all, p:all, w:9710b86ba12c42d1d8f30f74fe509286
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Additionally, they can be configured in a group <code class="docutils literal notranslate"><span class="pre">group_name.allowed</span></code> file, placed next to the <code class="docutils literal notranslate"><span class="pre">group_name</span></code> group directory.</p>
|
||||
<p><strong>Repository-Level Configuration</strong></p>
|
||||
<p>Repository-specific permissions are set in <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> files placed next to the repository directory (for example, <code class="docutils literal notranslate"><span class="pre">myrepo.allowed</span></code> for <code class="docutils literal notranslate"><span class="pre">myrepo</span></code>):</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># myrepo.allowed
|
||||
r:all
|
||||
w:9710b86ba12c42d1d8f30f74fe509286
|
||||
rel:9710b86ba12c42d1d8f30f74fe509286
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Dynamic Permissions</strong></p>
|
||||
<p>Permission files can be made executable to generate permissions dynamically:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ chmod +x myrepo.allowed
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>When executable, the script is run and its stdout is parsed as permission rules. This allows integration with external authentication systems.</p>
|
||||
</section>
|
||||
<section id="work-document-permissions">
|
||||
<h3>Work Document Permissions<a class="headerlink" href="#work-document-permissions" title="Link to this heading">¶</a></h3>
|
||||
<p>Work documents support additional permission granularity through <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> files in the work directory (e.g., <code class="docutils literal notranslate"><span class="pre">42.allowed</span></code> for document #42). These files use the same permission syntax but only support:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">r</span></code> (read) - View the document</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">w</span></code> (write) - Edit the document</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">i</span></code> (interact) - Comment on the document</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">p</span></code> (propose) - Propose changes (future use)</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">adm</span></code> (admin) - Full control over the document</p></li>
|
||||
</ul>
|
||||
<p>Document permissions override repository permissions for that specific document. Work document permissions can be updated simply by editing the <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> file, or remotely by using the <code class="docutils literal notranslate"><span class="pre">rngit</span> <span class="pre">work</span></code> command.</p>
|
||||
</section>
|
||||
<section id="creator-permissions">
|
||||
<h3>Creator Permissions<a class="headerlink" href="#creator-permissions" title="Link to this heading">¶</a></h3>
|
||||
<p>When a user creates a repository (via <code class="docutils literal notranslate"><span class="pre">create</span></code>, <code class="docutils literal notranslate"><span class="pre">fork</span></code>, or <code class="docutils literal notranslate"><span class="pre">mirror</span></code>), they are automatically granted <code class="docutils literal notranslate"><span class="pre">adm</span></code> (admin) permissions on that repository.</p>
|
||||
<p>When a user creates a work document, they automatically receive <code class="docutils literal notranslate"><span class="pre">interact</span></code> and <code class="docutils literal notranslate"><span class="pre">write</span></code> permissions on that document.</p>
|
||||
</section>
|
||||
<section id="permission-examples">
|
||||
<h3>Permission Examples<a class="headerlink" href="#permission-examples" title="Link to this heading">¶</a></h3>
|
||||
<p><strong>Example 1: Public Read, Restricted Write</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>r:all
|
||||
w:9710b86ba12c42d1d8f30f74fe509286
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Everyone can read, only the specified identity can write.</p>
|
||||
<p><strong>Example 2: Collaborative Development</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>r:all
|
||||
i:all
|
||||
p:all
|
||||
w:9710b86ba12c42d1d8f30f74fe509286
|
||||
rel:9710b86ba12c42d1d8f30f74fe509286
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Everyone can read, interact (comment), and propose work documents. Only the specified identity can write, create releases, and manage work documents fully.</p>
|
||||
<p><strong>Example 3: Private Repository</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
rw:a1b2c3d4e5f686ba12c42d1ba12ef1aa
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Only the two specified identities have any access (read or write).</p>
|
||||
<p><strong>Example 4: Mirror with Stats</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>r:all
|
||||
s:all
|
||||
w:none
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Everyone can read and view stats, but nobody can push (mirror is read-only from upstream).</p>
|
||||
</section>
|
||||
<section id="permission-short-forms">
|
||||
<h3>Permission Short Forms<a class="headerlink" href="#permission-short-forms" title="Link to this heading">¶</a></h3>
|
||||
<p>Permissions can be specified using short or long forms:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">r</span></code> = <code class="docutils literal notranslate"><span class="pre">read</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">w</span></code> = <code class="docutils literal notranslate"><span class="pre">write</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">rw</span></code> = <code class="docutils literal notranslate"><span class="pre">readwrite</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">c</span></code> = <code class="docutils literal notranslate"><span class="pre">create</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">s</span></code> = <code class="docutils literal notranslate"><span class="pre">stats</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">rel</span></code> = <code class="docutils literal notranslate"><span class="pre">release</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">i</span></code> = <code class="docutils literal notranslate"><span class="pre">interact</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">p</span></code> = <code class="docutils literal notranslate"><span class="pre">propose</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">adm</span></code> = <code class="docutils literal notranslate"><span class="pre">admin</span></code></p></li>
|
||||
</ul>
|
||||
<p>Targets can also use short forms:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">a</span></code> = <code class="docutils literal notranslate"><span class="pre">all</span></code> = <code class="docutils literal notranslate"><span class="pre">everyone</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">n</span></code> = <code class="docutils literal notranslate"><span class="pre">none</span></code> = <code class="docutils literal notranslate"><span class="pre">nobody</span></code></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="permission-configuration-locations">
|
||||
<h3>Permission Configuration Locations<a class="headerlink" href="#permission-configuration-locations" title="Link to this heading">¶</a></h3>
|
||||
<ul class="simple">
|
||||
<li><p>User install: <code class="docutils literal notranslate"><span class="pre">~/.rngit/config</span></code></p></li>
|
||||
<li><p>System install: <code class="docutils literal notranslate"><span class="pre">/etc/rngit/config</span></code></p></li>
|
||||
<li><p>Group permissions: <code class="docutils literal notranslate"><span class="pre"><group_root>/<group_name>.allowed</span></code></p></li>
|
||||
<li><p>Repository permissions: <code class="docutils literal notranslate"><span class="pre"><group_root>/<group_name>/<repo_name>.allowed</span></code></p></li>
|
||||
<li><p>Document permissions: <code class="docutils literal notranslate"><span class="pre"><group_root>/<group_name>.work/<doc_id>.allowed</span></code></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
<section id="serving-pages-over-nomad-network">
|
||||
<h2>Serving Pages Over Nomad Network<a class="headerlink" href="#serving-pages-over-nomad-network" title="Link to this heading">¶</a></h2>
|
||||
<p>In addition to providing Git repository access via the Git remote helper protocol, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> can also run a <a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a> compatible page node. This allows users to browse repository information, view file contents, inspect commit history and access repository statistics through any Nomad Network client.</p>
|
||||
<p>In addition to providing Git repository access via the Git remote helper protocol and command-line tools, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> can also run a <a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a> compatible page node. This allows users to browse repository information, view file contents, inspect commit history and access repository statistics through any Nomad Network client.</p>
|
||||
<p>When enabled, the page node provides a complete interface to your repositories, with automatic Markdown to Micron conversion, syntax-highlighted code browsing, and detailed commit, diff and statistics views.</p>
|
||||
<p><strong>Enabling the Git Page Node</strong></p>
|
||||
<section id="enabling-the-git-page-node">
|
||||
<h3>Enabling the Git Page Node<a class="headerlink" href="#enabling-the-git-page-node" title="Link to this heading">¶</a></h3>
|
||||
<p>To enable the page node, add the following to your <code class="docutils literal notranslate"><span class="pre">~/.rngit/config</span></code> file:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[pages]
|
||||
serve_nomadnet = yes
|
||||
@@ -372,7 +695,9 @@ Repositories Destination : <0d7334d411d00120cbad24edf355fdd2>
|
||||
Nomad Network Destination : <50824b711717f97c2fb1166ceddd5ea9>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Accessing Repository Pages</strong></p>
|
||||
</section>
|
||||
<section id="accessing-repository-pages">
|
||||
<h3>Accessing Repository Pages<a class="headerlink" href="#accessing-repository-pages" title="Link to this heading">¶</a></h3>
|
||||
<p>Once the page node is running, you can access it from any Nomad Network client by connecting to the Nomad Network destination. The page node provides the following views:</p>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Front Page</strong> - Lists all repository groups accessible to your identity</p></li>
|
||||
@@ -388,7 +713,7 @@ Nomad Network Destination : <50824b711717f97c2fb1166ceddd5ea9>
|
||||
<p>All pages respect the same permission system used for Git access. If an identity does not have read access to a repository, they will not be able to view its pages.</p>
|
||||
</section>
|
||||
<section id="formatting-syntax-highlighting">
|
||||
<h2>Formatting & Syntax Highlighting<a class="headerlink" href="#formatting-syntax-highlighting" title="Link to this heading">¶</a></h2>
|
||||
<h3>Formatting & Syntax Highlighting<a class="headerlink" href="#formatting-syntax-highlighting" title="Link to this heading">¶</a></h3>
|
||||
<p>If the <code class="docutils literal notranslate"><span class="pre">pygments</span></code> Python module is installed on your system, the page node will automatically apply syntax highlighting to code files. The highlighting supports a wide range of programming languages and uses a color theme optimized for terminal display.</p>
|
||||
<p>To enable syntax highlighting, install pygments:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>pip install pygments
|
||||
@@ -405,7 +730,7 @@ def hello_world():
|
||||
</div>
|
||||
</section>
|
||||
<section id="customizing-templates">
|
||||
<h2>Customizing Templates<a class="headerlink" href="#customizing-templates" title="Link to this heading">¶</a></h2>
|
||||
<h3>Customizing Templates<a class="headerlink" href="#customizing-templates" title="Link to this heading">¶</a></h3>
|
||||
<p>The page node uses a template system that allows complete customization of the generated pages. Templates are stored in the <code class="docutils literal notranslate"><span class="pre">~/.rngit/templates/</span></code> directory as Micron files.</p>
|
||||
<p>The following template files are supported:</p>
|
||||
<ul class="simple">
|
||||
@@ -439,41 +764,47 @@ serve_nomadnet = yes
|
||||
unicode_icons = yes
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Repository Statistics</strong></p>
|
||||
</section>
|
||||
<section id="repository-statistics">
|
||||
<h3>Repository Statistics<a class="headerlink" href="#repository-statistics" title="Link to this heading">¶</a></h3>
|
||||
<p>When statistics recording is enabled (see the <code class="docutils literal notranslate"><span class="pre">record_stats</span></code> configuration option), the page node can display activity charts for each repository. The statistics page shows:</p>
|
||||
<ul class="simple">
|
||||
<li><p>Total and peak views, fetches and pushes</p></li>
|
||||
<li><p>Total and peak views, downloads, fetches and pushes</p></li>
|
||||
<li><p>Daily activity charts over a 90-day period</p></li>
|
||||
<li><p>Combined activity visualization</p></li>
|
||||
</ul>
|
||||
<p>To view statistics, a user must have the <code class="docutils literal notranslate"><span class="pre">s</span></code> (stats) permission for the repository. See the Access Configuration section for details on setting permissions.</p>
|
||||
<p><strong>Repository Thanks</strong></p>
|
||||
<p>The page node includes a “Thanks” feature that allows users to express appreciation for a repository. On each repository page, a “Thanks” link is displayed showing the current thanks count. Clicking this link registers a thank you for the repository.</p>
|
||||
<p><strong>Configuration Example</strong></p>
|
||||
<p>A complete page node configuration might look like this:</p>
|
||||
</section>
|
||||
<section id="configuration-example">
|
||||
<h3>Configuration Example<a class="headerlink" href="#configuration-example" title="Link to this heading">¶</a></h3>
|
||||
<p>A complete node configuration might look like this:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[rngit]
|
||||
node_name = My Git Node
|
||||
announce_interval = 360
|
||||
record_stats = yes
|
||||
node_name = My Git Node
|
||||
announce_interval = 360
|
||||
record_stats = yes
|
||||
|
||||
[repositories]
|
||||
public = /var/git/public
|
||||
internal = /var/git/internal
|
||||
public = /var/git/public
|
||||
internal = /var/git/internal
|
||||
|
||||
[access]
|
||||
public = r:all
|
||||
internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
public = r:all
|
||||
internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
[pages]
|
||||
serve_nomadnet = yes
|
||||
unicode_icons = no
|
||||
serve_nomadnet = yes
|
||||
unicode_icons = no
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<section id="release-management">
|
||||
<h2>Release Management<a class="headerlink" href="#release-management" title="Link to this heading">¶</a></h2>
|
||||
<p>In addition to hosting Git repositories, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> provides a complete release management system. This allows you to publish versioned releases with associated artifacts, release notes and metadata. Releases are managed through the <code class="docutils literal notranslate"><span class="pre">rngit</span> <span class="pre">release</span></code> subcommand, and are also viewable through the Nomad Network page interface.</p>
|
||||
<p><strong>The Release Workflow</strong></p>
|
||||
<section id="the-release-workflow">
|
||||
<h3>The Release Workflow<a class="headerlink" href="#the-release-workflow" title="Link to this heading">¶</a></h3>
|
||||
<p>Creating a release involves specifying a Git tag and a directory containing build artifacts or other files to distribute. The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> client will open your configured <code class="docutils literal notranslate"><span class="pre">$EDITOR</span></code> to compose release notes, then upload all artifacts to the remote repository node.</p>
|
||||
<p>To create a release, specify the tag name and path to artifacts:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo create v1.2.0:./dist
|
||||
@@ -487,7 +818,9 @@ unicode_icons = no
|
||||
<li><p>Publish the release</p></li>
|
||||
</ol>
|
||||
<p>If no <code class="docutils literal notranslate"><span class="pre">$EDITOR</span></code> environment variable is set, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> will try to use <code class="docutils literal notranslate"><span class="pre">nano</span></code>, <code class="docutils literal notranslate"><span class="pre">vim</span></code> or <code class="docutils literal notranslate"><span class="pre">vi</span></code>. The editor will show a template with instructions. Lines starting with <code class="docutils literal notranslate"><span class="pre">#</span></code> will be ignored, and if the remaining content is empty after stripping comments, the release creation will be cancelled.</p>
|
||||
<p><strong>Release Storage & Structure</strong></p>
|
||||
</section>
|
||||
<section id="release-storage-structure">
|
||||
<h3>Release Storage & Structure<a class="headerlink" href="#release-storage-structure" title="Link to this heading">¶</a></h3>
|
||||
<p>Releases are stored on the node in a directory named <code class="docutils literal notranslate"><span class="pre">repo_name.releases</span></code> next to the bare repository. Each release is a subdirectory containing:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">META</span></code> - Release metadata in ConfigObj format</p></li>
|
||||
@@ -495,6 +828,9 @@ unicode_icons = no
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">artifacts/</span></code> - All uploaded files</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">THANKS</span></code> - Appreciation count from users</p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="command-line-interaction">
|
||||
<h3>Command-Line Interaction<a class="headerlink" href="#command-line-interaction" title="Link to this heading">¶</a></h3>
|
||||
<p><strong>Listing Releases</strong></p>
|
||||
<p>To view all releases for a repository:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit release rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo list
|
||||
@@ -552,7 +888,6 @@ rel:none # Deny everyone
|
||||
</div>
|
||||
<p><strong>Nomad Network Interface</strong></p>
|
||||
<p>When the Nomad Network page node is enabled, releases are displayed on a dedicated releases page for each repository. Each release is listed with its tag, creation date, artifact count and a preview of the release notes. Clicking a release shows the full details including formatted release notes and a listing of all artifacts with their sizes.</p>
|
||||
<p>Only releases with <code class="docutils literal notranslate"><span class="pre">published</span></code> status are visible through the Nomad Network interface. Draft releases (if supported in future implementations) would only be visible through the command-line interface.</p>
|
||||
<p><strong>All Command-Line Options (rngit release)</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rngit release [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
@@ -577,9 +912,12 @@ options:
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<section id="work-documents">
|
||||
<h2>Work Documents<a class="headerlink" href="#work-documents" title="Link to this heading">¶</a></h2>
|
||||
<p>In addition to releases, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> provides a work document management system for tracking tasks, investigations, issues and progress related to repositories. Work documents are stored as structured msgpack data and support threaded updates and comments.</p>
|
||||
<section id="working-with-work-documents">
|
||||
<h3>Working With Work Documents<a class="headerlink" href="#working-with-work-documents" title="Link to this heading">¶</a></h3>
|
||||
<p><strong>Listing Work Documents</strong></p>
|
||||
<p>To view work documents for a repository:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo list
|
||||
@@ -593,7 +931,7 @@ ID Title Author Created Comments
|
||||
2 Fixed bug in parser 8f3a21c9d84e927b… 2025-01-14 09:15 1
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Use <code class="docutils literal notranslate"><span class="pre">--scope</span> <span class="pre">completed</span></code> to view completed work documents, or <code class="docutils literal notranslate"><span class="pre">--scope</span> <span class="pre">all</span></code> to see both active and completed.</p>
|
||||
<p>Use <code class="docutils literal notranslate"><span class="pre">--scope</span> <span class="pre">completed</span></code> to view completed work documents, <code class="docutils literal notranslate"><span class="pre">--scope</span> <span class="pre">proposed</span></code> to view proposed documents, or <code class="docutils literal notranslate"><span class="pre">--scope</span> <span class="pre">all</span></code> to see all scopes.</p>
|
||||
<p><strong>Viewing a Work Document</strong></p>
|
||||
<p>To view a specific work document with all its comments:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo view -d 1
|
||||
@@ -635,6 +973,28 @@ Initial analysis complete
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This opens your editor to compose the update.</p>
|
||||
</section>
|
||||
<section id="proposing-work-documents">
|
||||
<h3>Proposing Work Documents<a class="headerlink" href="#proposing-work-documents" title="Link to this heading">¶</a></h3>
|
||||
<p>Users with <code class="docutils literal notranslate"><span class="pre">propose</span></code> permission can create work document proposals without full <code class="docutils literal notranslate"><span class="pre">write</span></code> access. Proposals are created in a “proposed” state and must be activated by a user with appropriate permissions before becoming active.</p>
|
||||
<p>To propose a work document:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo propose --title "Feature proposal"
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This opens your editor to compose the proposal content. When saved, the document is created in the “proposed” scope. The creator automatically receives <code class="docutils literal notranslate"><span class="pre">interact</span></code> and <code class="docutils literal notranslate"><span class="pre">write</span></code> permissions on the proposed document.</p>
|
||||
<p>Proposed documents are visible through <code class="docutils literal notranslate"><span class="pre">--scope</span> <span class="pre">proposed</span></code> or <code class="docutils literal notranslate"><span class="pre">--scope</span> <span class="pre">all</span></code>:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo list --scope proposed
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Permissions for Proposals</strong></p>
|
||||
<ul class="simple">
|
||||
<li><p>Creating proposals requires <code class="docutils literal notranslate"><span class="pre">propose</span></code> permission on the repository</p></li>
|
||||
<li><p>The creator automatically gets <code class="docutils literal notranslate"><span class="pre">interact</span></code> and <code class="docutils literal notranslate"><span class="pre">write</span></code> on their proposed document</p></li>
|
||||
<li><p>Activating a proposal requires <code class="docutils literal notranslate"><span class="pre">write</span></code> and <code class="docutils literal notranslate"><span class="pre">interact</span></code> permissions</p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="state-management">
|
||||
<h3>State Management<a class="headerlink" href="#state-management" title="Link to this heading">¶</a></h3>
|
||||
<p><strong>Completing Work Documents</strong></p>
|
||||
<p>To mark a work document as completed (moving it from <code class="docutils literal notranslate"><span class="pre">active</span></code> to <code class="docutils literal notranslate"><span class="pre">completed</span></code>):</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo complete -d 1
|
||||
@@ -657,21 +1017,49 @@ Are you sure you want to delete active work document #1? [y/N]: y
|
||||
Work document #1 deleted
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Permissions</strong></p>
|
||||
<p>Users can view work documents and updates if the have <code class="docutils literal notranslate"><span class="pre">read</span></code> permission for the repository. If users have <code class="docutils literal notranslate"><span class="pre">read</span></code> and <code class="docutils literal notranslate"><span class="pre">interact</span></code>, they can also post updates/comments on existing work documents. Work document management requires having <code class="docutils literal notranslate"><span class="pre">write</span></code> and <code class="docutils literal notranslate"><span class="pre">interact</span></code> permission to the repository. These permissions are configured the same way as any other repository permissions. In the config file or <code class="docutils literal notranslate"><span class="pre">.allowed</span></code> files, use <code class="docutils literal notranslate"><span class="pre">i:target</span></code> to grant work document interaction rights:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># In .allowed file or config
|
||||
i:all # Allow everyone
|
||||
i:9710b86... # Allow specific identity
|
||||
i:none # Deny everyone
|
||||
</section>
|
||||
<section id="managing-work-document-permissions">
|
||||
<h3>Managing Work Document Permissions<a class="headerlink" href="#managing-work-document-permissions" title="Link to this heading">¶</a></h3>
|
||||
<p>Users with administrative access to a work document can manage its specific permissions. This allows fine-grained control over who can read, write, comment on, or administer individual work documents.</p>
|
||||
<p>To view or edit permissions for a work document:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo perms -d 1
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Author Verification</strong></p>
|
||||
<p>Users can only edit or delete work documents and updates they created. The author is cryptographically verified from the interacting link’s <code class="docutils literal notranslate"><span class="pre">remote_identity</span></code>.</p>
|
||||
<p>This opens your editor with the current permission configuration:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>r:all
|
||||
i:9710b86ba12c42d1d8f30f74fe509286
|
||||
adm:9710b86ba12c42d1d8f30f74fe509286
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Permission rules follow the same format as repository permissions:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">r:target</span></code> - Grant read access</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">w:target</span></code> - Grant write access</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">i:target</span></code> - Grant interact (comment) access</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">adm:target</span></code> - Grant admin access</p></li>
|
||||
</ul>
|
||||
<p>Targets can be <code class="docutils literal notranslate"><span class="pre">all</span></code>, <code class="docutils literal notranslate"><span class="pre">none</span></code>, or a specific identity hash.</p>
|
||||
<p><strong>Who Can Edit Permissions</strong></p>
|
||||
<p>Document permissions can be edited by:</p>
|
||||
<ul class="simple">
|
||||
<li><p>The original author (if they also have <code class="docutils literal notranslate"><span class="pre">interact</span></code> and <code class="docutils literal notranslate"><span class="pre">write</span></code> on the repository)</p></li>
|
||||
<li><p>Any user with <code class="docutils literal notranslate"><span class="pre">admin</span></code> permission on the document</p></li>
|
||||
<li><p>Repository admins (through inherited permissions)</p></li>
|
||||
</ul>
|
||||
<p><strong>Permission Precedence</strong></p>
|
||||
<p>Document-specific permissions override repository-level permissions for that document. If document permissions exist, they are checked first; if access is not granted there, repository permissions are checked.</p>
|
||||
<p><strong>Author Rights:</strong></p>
|
||||
<ul class="simple">
|
||||
<li><p>Users can only edit or delete work documents they created</p></li>
|
||||
<li><p>The author is cryptographically verified from the interacting link’s <code class="docutils literal notranslate"><span class="pre">remote_identity</span></code></p></li>
|
||||
<li><p>Document creators automatically receive <code class="docutils literal notranslate"><span class="pre">interact</span></code> and <code class="docutils literal notranslate"><span class="pre">write</span></code> on their documents</p></li>
|
||||
</ul>
|
||||
<p><strong>Storage Format</strong></p>
|
||||
<p>Work documents are stored in a <code class="docutils literal notranslate"><span class="pre">repo_name.work</span></code> directory next to the repository, containing:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">active/</span></code> - Active work documents</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">completed/</span></code> - Completed work documents</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">proposed/</span></code> - Proposed work documents</p></li>
|
||||
</ul>
|
||||
<p>Each document is a numbered directory containing:</p>
|
||||
<ul class="simple">
|
||||
@@ -690,7 +1078,8 @@ Reticulum Git Work Document Manager
|
||||
|
||||
positional arguments:
|
||||
repository URL of remote repository
|
||||
operation list, view, create, edit, delete, update or complete
|
||||
operation list, view, create, propose, edit, delete,
|
||||
update, complete, activate or perms
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
@@ -698,8 +1087,8 @@ options:
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
--scope SCOPE document scope: active, completed or all
|
||||
-t, --title TITLE document title for create
|
||||
--scope SCOPE document scope: active, completed, proposed or all
|
||||
-t, --title TITLE document title for create/propose
|
||||
-d, --id ID document ID
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
@@ -707,6 +1096,7 @@ options:
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
</article>
|
||||
@@ -765,12 +1155,52 @@ options:
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Git Over Reticulum</a><ul>
|
||||
<li><a class="reference internal" href="#the-rngit-utility">The rngit Utility</a></li>
|
||||
<li><a class="reference internal" href="#repository-structure">Repository Structure</a></li>
|
||||
<li><a class="reference internal" href="#serving-pages-over-nomad-network">Serving Pages Over Nomad Network</a></li>
|
||||
<li><a class="reference internal" href="#repository-creation-management">Repository Creation & Management</a><ul>
|
||||
<li><a class="reference internal" href="#creating-empty-repositories">Creating Empty Repositories</a></li>
|
||||
<li><a class="reference internal" href="#forking-repositories">Forking Repositories</a></li>
|
||||
<li><a class="reference internal" href="#mirroring-repositories">Mirroring Repositories</a></li>
|
||||
<li><a class="reference internal" href="#automatic-mirror-synchronization">Automatic Mirror Synchronization</a></li>
|
||||
<li><a class="reference internal" href="#manual-synchronization">Manual Synchronization</a></li>
|
||||
<li><a class="reference internal" href="#git-configuration-parameters">Git Configuration Parameters</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#repository-structure">Repository Structure</a><ul>
|
||||
<li><a class="reference internal" href="#configuration">Configuration</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#permissions">Permissions</a><ul>
|
||||
<li><a class="reference internal" href="#permission-types">Permission Types</a></li>
|
||||
<li><a class="reference internal" href="#permission-hierarchy">Permission Hierarchy</a></li>
|
||||
<li><a class="reference internal" href="#configuration-methods">Configuration Methods</a></li>
|
||||
<li><a class="reference internal" href="#work-document-permissions">Work Document Permissions</a></li>
|
||||
<li><a class="reference internal" href="#creator-permissions">Creator Permissions</a></li>
|
||||
<li><a class="reference internal" href="#permission-examples">Permission Examples</a></li>
|
||||
<li><a class="reference internal" href="#permission-short-forms">Permission Short Forms</a></li>
|
||||
<li><a class="reference internal" href="#permission-configuration-locations">Permission Configuration Locations</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#serving-pages-over-nomad-network">Serving Pages Over Nomad Network</a><ul>
|
||||
<li><a class="reference internal" href="#enabling-the-git-page-node">Enabling the Git Page Node</a></li>
|
||||
<li><a class="reference internal" href="#accessing-repository-pages">Accessing Repository Pages</a></li>
|
||||
<li><a class="reference internal" href="#formatting-syntax-highlighting">Formatting & Syntax Highlighting</a></li>
|
||||
<li><a class="reference internal" href="#customizing-templates">Customizing Templates</a></li>
|
||||
<li><a class="reference internal" href="#release-management">Release Management</a></li>
|
||||
<li><a class="reference internal" href="#work-documents">Work Documents</a></li>
|
||||
<li><a class="reference internal" href="#repository-statistics">Repository Statistics</a></li>
|
||||
<li><a class="reference internal" href="#configuration-example">Configuration Example</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#release-management">Release Management</a><ul>
|
||||
<li><a class="reference internal" href="#the-release-workflow">The Release Workflow</a></li>
|
||||
<li><a class="reference internal" href="#release-storage-structure">Release Storage & Structure</a></li>
|
||||
<li><a class="reference internal" href="#command-line-interaction">Command-Line Interaction</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#work-documents">Work Documents</a><ul>
|
||||
<li><a class="reference internal" href="#working-with-work-documents">Working With Work Documents</a></li>
|
||||
<li><a class="reference internal" href="#proposing-work-documents">Proposing Work Documents</a></li>
|
||||
<li><a class="reference internal" href="#state-management">State Management</a></li>
|
||||
<li><a class="reference internal" href="#managing-work-document-permissions">Managing Work Document Permissions</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -782,7 +1212,7 @@ options:
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Communications Hardware - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>Communications Hardware - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -675,7 +675,7 @@ can be used with Reticulum. This includes virtual software modems such as
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></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>
|
||||
|
||||
+52
-13
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -378,8 +378,6 @@ to participate in the development of Reticulum itself.</p>
|
||||
<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#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#meshchat">MeshChat</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="software.html#columba">Columba</a></li>
|
||||
<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>
|
||||
@@ -394,7 +392,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
</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#id17">LXST</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#rrc">RRC</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -511,6 +509,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#interfaces-modes">Interface Modes</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#announce-rate-control">Announce Rate Control</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#new-destination-rate-limiting">New Destination Rate Limiting</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#path-request-burst-control">Path Request Burst Control</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a><ul>
|
||||
@@ -526,12 +525,52 @@ to participate in the development of Reticulum itself.</p>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="git.html#the-rngit-utility">The rngit Utility</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="git.html#repository-structure">Repository Structure</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="git.html#serving-pages-over-nomad-network">Serving Pages Over Nomad Network</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="git.html#formatting-syntax-highlighting">Formatting & Syntax Highlighting</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="git.html#customizing-templates">Customizing Templates</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="git.html#release-management">Release Management</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="git.html#work-documents">Work Documents</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="git.html#repository-creation-management">Repository Creation & Management</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#creating-empty-repositories">Creating Empty Repositories</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#forking-repositories">Forking Repositories</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#mirroring-repositories">Mirroring Repositories</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#automatic-mirror-synchronization">Automatic Mirror Synchronization</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#manual-synchronization">Manual Synchronization</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#git-configuration-parameters">Git Configuration Parameters</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="git.html#repository-structure">Repository Structure</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#configuration">Configuration</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="git.html#permissions">Permissions</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-types">Permission Types</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-hierarchy">Permission Hierarchy</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#configuration-methods">Configuration Methods</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#work-document-permissions">Work Document Permissions</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#creator-permissions">Creator Permissions</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-examples">Permission Examples</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-short-forms">Permission Short Forms</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-configuration-locations">Permission Configuration Locations</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="git.html#serving-pages-over-nomad-network">Serving Pages Over Nomad Network</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#enabling-the-git-page-node">Enabling the Git Page Node</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#accessing-repository-pages">Accessing Repository Pages</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#formatting-syntax-highlighting">Formatting & Syntax Highlighting</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#customizing-templates">Customizing Templates</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#repository-statistics">Repository Statistics</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#configuration-example">Configuration Example</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="git.html#release-management">Release Management</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#the-release-workflow">The Release Workflow</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#release-storage-structure">Release Storage & Structure</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#command-line-interaction">Command-Line Interaction</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="git.html#work-documents">Work Documents</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#working-with-work-documents">Working With Work Documents</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#proposing-work-documents">Proposing Work Documents</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#state-management">State Management</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="git.html#managing-work-document-permissions">Managing Work Document Permissions</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a><ul>
|
||||
@@ -644,7 +683,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -1533,11 +1533,14 @@ also means, that should a node decide to connect to a public interface, announce
|
||||
a large amount of bogus destinations, and then disconnect, these destination will
|
||||
never make it into path tables and waste network bandwidth on retransmitted
|
||||
announces.</p>
|
||||
<p><strong>It’s important to note</strong> that the ingress control works at the level of <em>individual
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>It’s important to remember that the ingress control works at the level of <em>individual
|
||||
sub-interfaces</em>. As an example, this means that one client on a <a class="reference internal" href="#interfaces-tcps"><span class="std std-ref">TCP Server Interface</span></a>
|
||||
cannot disrupt processing of incoming announces for other connected clients on the same
|
||||
<a class="reference internal" href="#interfaces-tcps"><span class="std std-ref">TCP Server Interface</span></a>. All other clients on the same interface will still have new announces
|
||||
processed without interruption.</p>
|
||||
<a class="reference internal" href="#interfaces-tcps"><span class="std std-ref">TCP Server Interface</span></a>. All other clients on the same interface
|
||||
will still have new announces processed without interruption.</p>
|
||||
</div>
|
||||
<p>By default, Reticulum will handle this automatically, and ingress announce
|
||||
control will be enabled on interface where it is sensible to do so. It should
|
||||
generally not be neccessary to modify the ingress control configuration,
|
||||
@@ -1546,8 +1549,7 @@ but all the parameters are exposed for configuration if needed.</p>
|
||||
<div><ul>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ingress_control</span></code> option tells Reticulum whether or not
|
||||
to enable announce ingress control on the interface. Defaults to
|
||||
<code class="docutils literal notranslate"><span class="pre">True</span></code>.</div>
|
||||
to enable ingress control on the interface. Defaults to <code class="docutils literal notranslate"><span class="pre">True</span></code>.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
@@ -1602,6 +1604,76 @@ to <code class="docutils literal notranslate"><span class="pre">30</span></code>
|
||||
</li>
|
||||
</ul>
|
||||
</div></blockquote>
|
||||
<p>All of the above settings can be configured both as instance-wide defaults
|
||||
under the <code class="docutils literal notranslate"><span class="pre">[reticulum]</span></code> section of the configuration file, or on a per-
|
||||
interface basis under the relevant interface configuration section.</p>
|
||||
</section>
|
||||
<section id="path-request-burst-control">
|
||||
<h2>Path Request Burst Control<a class="headerlink" href="#path-request-burst-control" title="Link to this heading">¶</a></h2>
|
||||
<p>In addition the announce controls for newly created destination, Reticulum will also
|
||||
monitor incoming path request activity, and enforce burst controls if per-client rates
|
||||
exceed configured limits. Once path request burst control is activated on an
|
||||
interface, path requests will no longer be propagated further on the network.
|
||||
As with announce burst control, this happens on a per sub-interface basis. One
|
||||
client connecting to a public gateway will not be able to disrupt path request
|
||||
processing for other clients.</p>
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Warning</p>
|
||||
<p>Applications that send large amounts of unnecessary path requests will very
|
||||
quickly get rate limited by transport nodes, and the entire system they are
|
||||
running on will not be able to resolve any paths on the network, until the
|
||||
burst subsides and hold period expires. <strong>Do not</strong> write applications like
|
||||
this. Only request paths for destinations you need to communicate with.</p>
|
||||
</div>
|
||||
<p>By default, Reticulum will handle this automatically, and ingress path request
|
||||
control will be enabled on interface where it is sensible to do so. It should
|
||||
generally not be neccessary to modify the ingress control configuration,
|
||||
but all the parameters are exposed for configuration if needed.</p>
|
||||
<blockquote>
|
||||
<div><ul>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ingress_control</span></code> option tells Reticulum whether or not
|
||||
to enable ingress control on the interface. Defaults to <code class="docutils literal notranslate"><span class="pre">True</span></code>.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ic_new_time</span></code> option configures how long (in seconds) an
|
||||
interface is considered newly spawned. Defaults to <code class="docutils literal notranslate"><span class="pre">2*60*60</span></code> seconds. This
|
||||
option is useful on publicly accessible interfaces that spawn new
|
||||
sub-interfaces when a new client connects.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ic_pr_burst_freq_new</span></code> option sets the maximum path request
|
||||
ingress frequency for newly spawned interfaces. Defaults to <code class="docutils literal notranslate"><span class="pre">3</span></code>
|
||||
path requests per second.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ic_pr_burst_freq</span></code> option sets the maximum path request
|
||||
ingress frequency for other interfaces. Defaults to <code class="docutils literal notranslate"><span class="pre">8</span></code> path requests
|
||||
per second.</div>
|
||||
</div>
|
||||
<blockquote>
|
||||
<div><p><em>If an interface exceeds its burst frequency, incoming path requests
|
||||
from that system will not traverse the network further.</em></p>
|
||||
</div></blockquote>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">egress_control</span></code> option enables hard-limiting path request egress
|
||||
control per-interface. Defaults to <code class="docutils literal notranslate"><span class="pre">False</span></code></div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ec_pr_freq</span></code> option sets the hard limit for outbound path requests
|
||||
per second on a given interface.</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div></blockquote>
|
||||
<p>All of the above settings can be configured both as instance-wide defaults
|
||||
under the <code class="docutils literal notranslate"><span class="pre">[reticulum]</span></code> section of the configuration file, or on a per-
|
||||
interface basis under the relevant interface configuration section.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -1689,6 +1761,7 @@ to <code class="docutils literal notranslate"><span class="pre">30</span></code>
|
||||
<li><a class="reference internal" href="#interfaces-modes">Interface Modes</a></li>
|
||||
<li><a class="reference internal" href="#announce-rate-control">Announce Rate Control</a></li>
|
||||
<li><a class="reference internal" href="#new-destination-rate-limiting">New Destination Rate Limiting</a></li>
|
||||
<li><a class="reference internal" href="#path-request-burst-control">Path Request Burst Control</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -1700,7 +1773,7 @@ to <code class="docutils literal notranslate"><span class="pre">30</span></code>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Reticulum License - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>Reticulum License - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -344,7 +344,7 @@ SOFTWARE.
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Building Networks - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>Building Networks - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -663,7 +663,7 @@ differently than a mobile device roaming between radio cells.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
Binary file not shown.
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>API Reference - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>API Reference - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -2484,7 +2484,7 @@ will announce it.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<meta name="robots" content="noindex" />
|
||||
<title>Search - Reticulum Network Stack 1.2.4 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<title>Search - Reticulum Network Stack 1.2.7 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="#" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -303,7 +303,7 @@
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Programs Using Reticulum - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>Programs Using Reticulum - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -328,31 +328,11 @@ 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 Reticulum MeshChat project, and is not affiliated with the original project.</p>
|
||||
<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" />
|
||||
</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>
|
||||
</section>
|
||||
<section id="meshchat">
|
||||
<h3>MeshChat<a class="headerlink" href="#meshchat" title="Link to this heading">¶</a></h3>
|
||||
<p>The <a class="reference external" href="https://github.com/liamcottle/reticulum-meshchat">Reticulum MeshChat</a> application
|
||||
is a user-friendly LXMF client for Linux, macOS and Windows, that also includes a Nomad Network
|
||||
page browser and other interesting functionality.</p>
|
||||
<a class="reference external image-reference" href="https://github.com/liamcottle/reticulum-meshchat"><img alt="_images/meshchat_1.webp" class="align-center" src="_images/meshchat_1.webp" />
|
||||
</a>
|
||||
<p>Reticulum MeshChat is of course also compatible with Sideband and Nomad Network, or
|
||||
any other LXMF client.</p>
|
||||
</section>
|
||||
<section id="columba">
|
||||
<h3>Columba<a class="headerlink" href="#columba" 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.</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" style="width: 25%;" />
|
||||
</a>
|
||||
<p>While still in early and very active development, it is of course also compatible
|
||||
with all other LXMF clients, and allows you to message seamlessly with anyone else
|
||||
using LXMF.</p>
|
||||
</section>
|
||||
<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>
|
||||
@@ -417,8 +397,8 @@ using LXMF.</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="id17">
|
||||
<h3>LXST<a class="headerlink" href="#id17" title="Link to this heading">¶</a></h3>
|
||||
<section id="id16">
|
||||
<h3>LXST<a class="headerlink" href="#id16" 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">
|
||||
@@ -502,8 +482,6 @@ using LXMF.</p>
|
||||
<li><a class="reference internal" href="#retipedia">Retipedia</a></li>
|
||||
<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="#meshchat">MeshChat</a></li>
|
||||
<li><a class="reference internal" href="#columba">Columba</a></li>
|
||||
<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>
|
||||
@@ -518,7 +496,7 @@ using LXMF.</p>
|
||||
</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="#id17">LXST</a></li>
|
||||
<li><a class="reference internal" href="#id16">LXST</a></li>
|
||||
<li><a class="reference internal" href="#rrc">RRC</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -534,7 +512,7 @@ using LXMF.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Support Reticulum - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>Support Reticulum - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -382,7 +382,7 @@ circumstances, so we rely on old-fashioned human feedback.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -1337,7 +1337,7 @@ those risks are acceptable to you.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -1635,7 +1635,7 @@ systemctl --user enable rnsd.service
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>What is Reticulum? - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 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">
|
||||
@@ -504,7 +504,7 @@ network, and vice versa.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Zen of Reticulum - Reticulum Network Stack 1.2.4 documentation</title>
|
||||
<title>Zen of Reticulum - Reticulum Network Stack 1.2.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -676,7 +676,7 @@ 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=928db92d"></script>
|
||||
</div><script src="_static/documentation_options.js?v=a5753347"></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>
|
||||
|
||||
+462
-41
@@ -1,13 +1,13 @@
|
||||
# Git Over Reticulum
|
||||
|
||||
A set of utilities for distributed collaborative software development and publishing is included in RNS.
|
||||
A set of utilities for distributed collaborative software development and publishing are included in RNS.
|
||||
|
||||
The system consists of two parts: The `rngit` node that hosts repositories, and the `git-remote-rns` helper that enables Git to communicate with rngit nodes. As soon as you have RNS installed on your system, you can transparently use Git with Reticulum-hosted repositories just like any other type of remote. Git over Reticulum uses URLs in the following format: `rns://DESTINATION_HASH/group/repo`.
|
||||
|
||||
If you set a branch to track a Reticulum remote as the default upstream, you can simply use `git` as you normally would; all commands work transparently and as expected.
|
||||
|
||||
#### WARNING
|
||||
**The rngit program is a new addition to RNS!** This functionality was introduced in RNS 1.2.0. While great care has been taken to design a secure, but highly configurable and flexible permission system for allowing many users to interact with many different repositories on a single node, `rngit` has not been tested extensively in the wild! Be careful when hosting repositories, especially if they are public or semi-public.
|
||||
**The rngit program is a new addition to RNS!** This functionality was introduced in RNS 1.2.0. While great care has been taken to design a secure, but highly configurable and flexible [permission system](#permissions) for allowing many users to interact with many different repositories on a single node, `rngit` has not been tested extensively in the wild! Be careful when hosting repositories, especially if they are public or semi-public.
|
||||
|
||||
## The rngit Utility
|
||||
|
||||
@@ -26,7 +26,7 @@ $ rngit
|
||||
|
||||
On the first run, `rngit` will create a default configuration file. You will then need to edit this, to point to your repository locations, configure access permissions, and perform any other necessary configuration.
|
||||
|
||||
View your identity and destination hashes:
|
||||
Them, view your identity and destination hashes:
|
||||
|
||||
```text
|
||||
$ rngit --print-identity
|
||||
@@ -68,6 +68,12 @@ Get changes from a remote repository:
|
||||
$ git pull rns_remote master
|
||||
```
|
||||
|
||||
Fork an existing repository from a remote to your `rngit` node:
|
||||
|
||||
```text
|
||||
$ rngit fork rns://8a37cdd16938ce79861561adbd59023a/reticulum/lxmf rns://50824b711717f97c2fb1166ceddd5ea9/public/myfork
|
||||
```
|
||||
|
||||
**All Command-Line Options (rngit)**
|
||||
|
||||
```text
|
||||
@@ -98,11 +104,211 @@ The `git-remote-rns` helper is automatically invoked by Git when interacting wit
|
||||
|
||||
The client configuration file is located at `~/.rngit/client_config` and allows adjusting parameters such as the reference batch size for transfers.
|
||||
|
||||
## Repository Creation & Management
|
||||
|
||||
The `rngit` utility provides several ways to create and manage repositories on a node: creating empty repositories, forking from existing repositories, and mirroring remote repositories.
|
||||
|
||||
### Creating Empty Repositories
|
||||
|
||||
To create a new empty repository on a remote node:
|
||||
|
||||
```text
|
||||
$ rngit create rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo
|
||||
|
||||
Repository public/myrepo created
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
**All Command-Line Options (rngit create)**
|
||||
|
||||
```text
|
||||
usage: rngit create [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
repository
|
||||
|
||||
Reticulum Git Repository Creation
|
||||
|
||||
positional arguments:
|
||||
repository URL of repository to create
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
```
|
||||
|
||||
### Forking Repositories
|
||||
|
||||
Forking creates a copy of an existing repository (from any accessible Git URL) on your `rngit` node. Forks maintain a reference to their upstream source for later synchronization.
|
||||
|
||||
To fork a repository:
|
||||
|
||||
```text
|
||||
$ rngit fork https://github.com/user/original rns://50824b711717f97c2fb1166ceddd5ea9/public/myfork
|
||||
|
||||
Repository forked to public/myfork
|
||||
```
|
||||
|
||||
The source can be any valid Git URL, including:
|
||||
|
||||
- HTTPS URLs: `https://github.com/user/repo.git`
|
||||
- Git URLs: `git://host.com/repo.git`
|
||||
- SSH URLs: `ssh://git@host.com/repo.git`
|
||||
- Reticulum URLs: `rns://DESTINATION_HASH/group/repo`
|
||||
- Local paths: `/path/to/repo.git`
|
||||
|
||||
Forks are created as bare repositories with metadata tracking their origin. The fork process:
|
||||
|
||||
1. Creates a new bare repository
|
||||
2. Fetches all refs (`+refs/*:refs/*`) from the source
|
||||
3. Sets `repository.rngit.type` to `fork`
|
||||
4. Sets `repository.rngit.upstream.source` to the source URL
|
||||
5. Grants creator admin permissions
|
||||
|
||||
**All Command-Line Options (rngit fork)**
|
||||
|
||||
```text
|
||||
usage: rngit fork [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
source target
|
||||
|
||||
Reticulum Git Repository Forker
|
||||
|
||||
positional arguments:
|
||||
source URL of source repository
|
||||
target URL of target repository
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
```
|
||||
|
||||
### Mirroring Repositories
|
||||
|
||||
Mirrors are similar to forks but are designed for keeping a local copy synchronized with an upstream repository. Mirrors can be automatically updated on a configurable schedule.
|
||||
|
||||
To create a mirror:
|
||||
|
||||
```text
|
||||
$ rngit mirror https://github.com/user/upstream rns://50824b711717f97c2fb1166ceddd5ea9/public/mymirror
|
||||
|
||||
Repository mirrored to public/mymirror
|
||||
```
|
||||
|
||||
Mirrors are created with the same process as forks, but with `repository.rngit.type` set to `mirror` and an additional `repository.rngit.upstream.sync` timestamp tracking the last successful synchronization.
|
||||
|
||||
**All Command-Line Options (rngit mirror)**
|
||||
|
||||
```text
|
||||
usage: rngit mirror [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
source target
|
||||
|
||||
Reticulum Git Mirror Management
|
||||
|
||||
positional arguments:
|
||||
source URL of source repository
|
||||
target URL of target repository
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
```
|
||||
|
||||
### Automatic Mirror Synchronization
|
||||
|
||||
The `rngit` node can automatically keep mirrors synchronized with their upstream sources. This is configured in the main configuration file:
|
||||
|
||||
```text
|
||||
[rngit]
|
||||
mirror_interval = 24
|
||||
```
|
||||
|
||||
The `mirror_interval` specifies the synchronization interval in hours (default: 24). The node checks for mirrors needing sync every 15 minutes, and fetches updates from upstream if the configured interval has elapsed since the last sync.
|
||||
|
||||
For automatic sync to happen, the repository must have been created with `rngit mirror`. Sync failures are logged but do not prevent future retry attempts. The sync timestamp is only updated on successful completion.
|
||||
|
||||
### Manual Synchronization
|
||||
|
||||
Both forks and mirrors can be manually synchronized on demand using the `sync` command:
|
||||
|
||||
```text
|
||||
$ rngit sync rns://50824b711717f97c2fb1166ceddd5ea9/public/myfork
|
||||
|
||||
Repository synced
|
||||
```
|
||||
|
||||
This fetches all refs from the upstream source configured when the repository was created. You must have `read` and `write` permissions for the repository to perform a manual sync.
|
||||
|
||||
For mirrors, manual sync also updates the sync timestamp, effectively resetting the automatic sync timer.
|
||||
|
||||
**All Command-Line Options (rngit sync)**
|
||||
|
||||
```text
|
||||
usage: rngit sync [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
repository
|
||||
|
||||
Reticulum Git Repository Syncer
|
||||
|
||||
positional arguments:
|
||||
repository URL of repository
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
```
|
||||
|
||||
### Git Configuration Parameters
|
||||
|
||||
Repositories created through `rngit` store metadata in Git configuration:
|
||||
|
||||
- `repository.rngit.type` - Either `fork` or `mirror`
|
||||
- `repository.rngit.upstream.source` - The source URL used during creation
|
||||
- `repository.rngit.upstream.sync` - Unix timestamp of last successful sync for mirrors
|
||||
|
||||
These parameters are used by the sync system and can be queried using standard Git commands:
|
||||
|
||||
```text
|
||||
$ git config --get repository.rngit.type
|
||||
mirror
|
||||
|
||||
$ git config --get repository.rngit.upstream.source
|
||||
https://github.com/user/upstream
|
||||
|
||||
$ git config --get repository.rngit.upstream.sync
|
||||
1716230400
|
||||
```
|
||||
|
||||
## Repository Structure
|
||||
|
||||
The `rngit` node organizes repositories into groups. Each group is a directory containing bare Git repositories. The repository path format is `group_name/repo_name`. For example, a repository at `/var/git/public/myrepo` would be accessible as `public/myrepo` via the URL `rns://DESTINATION_HASH/public/myrepo`.
|
||||
|
||||
**Configuration**
|
||||
### Configuration
|
||||
|
||||
The `rngit` node configuration file is located at `~/.rngit/config` (or `/etc/rngit/config` for system-wide installations). The default configuration includes:
|
||||
|
||||
@@ -111,19 +317,174 @@ The `rngit` node configuration file is located at `~/.rngit/config` (or `/etc/rn
|
||||
- Announce intervals for network visibility
|
||||
- Optional statistics recording for repository activity
|
||||
|
||||
Access permissions can be configured at the group level in the config file, or per-repository using `.allowed` files. Permissions use the format `permission:target` where permission is `r` (read), `w` (write), `rw` (read/write), `c` (create) or `s` (stats) and target is `all`, `none`, or a specific identity hash.
|
||||
## Permissions
|
||||
|
||||
The `s` (stats) permission allows viewing repository activity statistics, including views, fetches and pushes over time. To enable statistics recording, set `record_stats = yes` in the `[rngit]` section of the configuration file. You can also exclude specific identities from statistics by adding their hashes to `stats_ignore_identities`.
|
||||
The `rngit` permission system provides fine-grained access control at multiple levels: group-level, repository-level, and document-level. Permissions can be statically configured in files or dynamically generated via executable scripts.
|
||||
|
||||
Repository-specific `.allowed` files can be static text files or executable scripts that output permission rules to stdout. A `group.allowed` file in a repository group directory applies to all repositories within that group.
|
||||
Access permissions can be configured at the group level in the config file or per-group `.allowed` files, or per-repository `.allowed` files. The `s` (stats) permission allows viewing repository activity statistics, including views, fetches and pushes over time. To enable statistics recording, set `record_stats = yes` in the `[rngit]` section of the configuration file. You can also exclude specific identities from statistics by adding their hashes to `stats_ignore_identities`.
|
||||
|
||||
By default, **no** permissions are granted for anything! You will have to enable the permissions you require to be able to actually *do* something with `rngit`.
|
||||
|
||||
### Permission Types
|
||||
|
||||
The following permissions are supported:
|
||||
|
||||
- `r` (read) - Clone, fetch, and view repositories and work documents
|
||||
- `w` (write) - Push changes and manage work documents
|
||||
- `rw` (read/write) - Combined read and write access
|
||||
- `c` (create) - Create, fork or mirror new repositories within a group
|
||||
- `s` (stats) - View repository activity statistics
|
||||
- `rel` (release) - Create and manage releases
|
||||
- `i` (interact) - Comment on and interact with work documents
|
||||
- `p` (propose) - Propose new work documents (without full write access)
|
||||
- `adm` (admin) - Full access
|
||||
|
||||
Permission targets can be:
|
||||
|
||||
- `all` or `a` - Everyone
|
||||
- `none` or `n` - Nobody
|
||||
- A specific Reticulum identity hash
|
||||
|
||||
### Permission Hierarchy
|
||||
|
||||
Permissions are resolved in the following hierarchy:
|
||||
|
||||
1. **Repository-level permissions** - Checked first, if none exists group permissions are checked
|
||||
2. **Group-level permissions** - Used as fallback if no repository-level permissions are set
|
||||
3. **Admin override** - Finally, potential admin rights are checked
|
||||
|
||||
For work documents, work document specific permissions are always checked first, and work documents have additional specific checks such as modifications only being possible by the document author.
|
||||
|
||||
### Configuration Methods
|
||||
|
||||
**Group-Level Configuration**
|
||||
|
||||
Group permissions can be configured in the `[access]` section of the main config file:
|
||||
|
||||
```text
|
||||
[access]
|
||||
public = r:all, w:9710b86ba12c42d1d8f30f74fe509286
|
||||
internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
collaborative = r:all, i:all, p:all, w:9710b86ba12c42d1d8f30f74fe509286
|
||||
```
|
||||
|
||||
Additionally, they can be configured in a group `group_name.allowed` file, placed next to the `group_name` group directory.
|
||||
|
||||
**Repository-Level Configuration**
|
||||
|
||||
Repository-specific permissions are set in `.allowed` files placed next to the repository directory (for example, `myrepo.allowed` for `myrepo`):
|
||||
|
||||
```text
|
||||
# myrepo.allowed
|
||||
r:all
|
||||
w:9710b86ba12c42d1d8f30f74fe509286
|
||||
rel:9710b86ba12c42d1d8f30f74fe509286
|
||||
```
|
||||
|
||||
**Dynamic Permissions**
|
||||
|
||||
Permission files can be made executable to generate permissions dynamically:
|
||||
|
||||
```text
|
||||
$ chmod +x myrepo.allowed
|
||||
```
|
||||
|
||||
When executable, the script is run and its stdout is parsed as permission rules. This allows integration with external authentication systems.
|
||||
|
||||
### Work Document Permissions
|
||||
|
||||
Work documents support additional permission granularity through `.allowed` files in the work directory (e.g., `42.allowed` for document #42). These files use the same permission syntax but only support:
|
||||
|
||||
- `r` (read) - View the document
|
||||
- `w` (write) - Edit the document
|
||||
- `i` (interact) - Comment on the document
|
||||
- `p` (propose) - Propose changes (future use)
|
||||
- `adm` (admin) - Full control over the document
|
||||
|
||||
Document permissions override repository permissions for that specific document. Work document permissions can be updated simply by editing the `.allowed` file, or remotely by using the `rngit work` command.
|
||||
|
||||
### Creator Permissions
|
||||
|
||||
When a user creates a repository (via `create`, `fork`, or `mirror`), they are automatically granted `adm` (admin) permissions on that repository.
|
||||
|
||||
When a user creates a work document, they automatically receive `interact` and `write` permissions on that document.
|
||||
|
||||
### Permission Examples
|
||||
|
||||
**Example 1: Public Read, Restricted Write**
|
||||
|
||||
```text
|
||||
r:all
|
||||
w:9710b86ba12c42d1d8f30f74fe509286
|
||||
```
|
||||
|
||||
Everyone can read, only the specified identity can write.
|
||||
|
||||
**Example 2: Collaborative Development**
|
||||
|
||||
```text
|
||||
r:all
|
||||
i:all
|
||||
p:all
|
||||
w:9710b86ba12c42d1d8f30f74fe509286
|
||||
rel:9710b86ba12c42d1d8f30f74fe509286
|
||||
```
|
||||
|
||||
Everyone can read, interact (comment), and propose work documents. Only the specified identity can write, create releases, and manage work documents fully.
|
||||
|
||||
**Example 3: Private Repository**
|
||||
|
||||
```text
|
||||
rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
rw:a1b2c3d4e5f686ba12c42d1ba12ef1aa
|
||||
```
|
||||
|
||||
Only the two specified identities have any access (read or write).
|
||||
|
||||
**Example 4: Mirror with Stats**
|
||||
|
||||
```text
|
||||
r:all
|
||||
s:all
|
||||
w:none
|
||||
```
|
||||
|
||||
Everyone can read and view stats, but nobody can push (mirror is read-only from upstream).
|
||||
|
||||
### Permission Short Forms
|
||||
|
||||
Permissions can be specified using short or long forms:
|
||||
|
||||
- `r` = `read`
|
||||
- `w` = `write`
|
||||
- `rw` = `readwrite`
|
||||
- `c` = `create`
|
||||
- `s` = `stats`
|
||||
- `rel` = `release`
|
||||
- `i` = `interact`
|
||||
- `p` = `propose`
|
||||
- `adm` = `admin`
|
||||
|
||||
Targets can also use short forms:
|
||||
|
||||
- `a` = `all` = `everyone`
|
||||
- `n` = `none` = `nobody`
|
||||
|
||||
### Permission Configuration Locations
|
||||
|
||||
- User install: `~/.rngit/config`
|
||||
- System install: `/etc/rngit/config`
|
||||
- Group permissions: `<group_root>/<group_name>.allowed`
|
||||
- Repository permissions: `<group_root>/<group_name>/<repo_name>.allowed`
|
||||
- Document permissions: `<group_root>/<group_name>.work/<doc_id>.allowed`
|
||||
|
||||
## Serving Pages Over Nomad Network
|
||||
|
||||
In addition to providing Git repository access via the Git remote helper protocol, `rngit` can also run a [Nomad Network](https://github.com/markqvist/nomadnet) compatible page node. This allows users to browse repository information, view file contents, inspect commit history and access repository statistics through any Nomad Network client.
|
||||
In addition to providing Git repository access via the Git remote helper protocol and command-line tools, `rngit` can also run a [Nomad Network](https://github.com/markqvist/nomadnet) compatible page node. This allows users to browse repository information, view file contents, inspect commit history and access repository statistics through any Nomad Network client.
|
||||
|
||||
When enabled, the page node provides a complete interface to your repositories, with automatic Markdown to Micron conversion, syntax-highlighted code browsing, and detailed commit, diff and statistics views.
|
||||
|
||||
**Enabling the Git Page Node**
|
||||
### Enabling the Git Page Node
|
||||
|
||||
To enable the page node, add the following to your `~/.rngit/config` file:
|
||||
|
||||
@@ -143,7 +504,7 @@ Repositories Destination : <0d7334d411d00120cbad24edf355fdd2>
|
||||
Nomad Network Destination : <50824b711717f97c2fb1166ceddd5ea9>
|
||||
```
|
||||
|
||||
**Accessing Repository Pages**
|
||||
### Accessing Repository Pages
|
||||
|
||||
Once the page node is running, you can access it from any Nomad Network client by connecting to the Nomad Network destination. The page node provides the following views:
|
||||
|
||||
@@ -159,7 +520,7 @@ Once the page node is running, you can access it from any Nomad Network client b
|
||||
|
||||
All pages respect the same permission system used for Git access. If an identity does not have read access to a repository, they will not be able to view its pages.
|
||||
|
||||
## Formatting & Syntax Highlighting
|
||||
### Formatting & Syntax Highlighting
|
||||
|
||||
If the `pygments` Python module is installed on your system, the page node will automatically apply syntax highlighting to code files. The highlighting supports a wide range of programming languages and uses a color theme optimized for terminal display.
|
||||
|
||||
@@ -182,7 +543,7 @@ def hello_world():
|
||||
```
|
||||
```
|
||||
|
||||
## Customizing Templates
|
||||
### Customizing Templates
|
||||
|
||||
The page node uses a template system that allows complete customization of the generated pages. Templates are stored in the `~/.rngit/templates/` directory as Micron files.
|
||||
|
||||
@@ -223,11 +584,11 @@ serve_nomadnet = yes
|
||||
unicode_icons = yes
|
||||
```
|
||||
|
||||
**Repository Statistics**
|
||||
### Repository Statistics
|
||||
|
||||
When statistics recording is enabled (see the `record_stats` configuration option), the page node can display activity charts for each repository. The statistics page shows:
|
||||
|
||||
- Total and peak views, fetches and pushes
|
||||
- Total and peak views, downloads, fetches and pushes
|
||||
- Daily activity charts over a 90-day period
|
||||
- Combined activity visualization
|
||||
|
||||
@@ -237,34 +598,34 @@ To view statistics, a user must have the `s` (stats) permission for the reposito
|
||||
|
||||
The page node includes a “Thanks” feature that allows users to express appreciation for a repository. On each repository page, a “Thanks” link is displayed showing the current thanks count. Clicking this link registers a thank you for the repository.
|
||||
|
||||
**Configuration Example**
|
||||
### Configuration Example
|
||||
|
||||
A complete page node configuration might look like this:
|
||||
A complete node configuration might look like this:
|
||||
|
||||
```text
|
||||
[rngit]
|
||||
node_name = My Git Node
|
||||
announce_interval = 360
|
||||
record_stats = yes
|
||||
node_name = My Git Node
|
||||
announce_interval = 360
|
||||
record_stats = yes
|
||||
|
||||
[repositories]
|
||||
public = /var/git/public
|
||||
internal = /var/git/internal
|
||||
public = /var/git/public
|
||||
internal = /var/git/internal
|
||||
|
||||
[access]
|
||||
public = r:all
|
||||
internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
public = r:all
|
||||
internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
[pages]
|
||||
serve_nomadnet = yes
|
||||
unicode_icons = no
|
||||
serve_nomadnet = yes
|
||||
unicode_icons = no
|
||||
```
|
||||
|
||||
## Release Management
|
||||
|
||||
In addition to hosting Git repositories, `rngit` provides a complete release management system. This allows you to publish versioned releases with associated artifacts, release notes and metadata. Releases are managed through the `rngit release` subcommand, and are also viewable through the Nomad Network page interface.
|
||||
|
||||
**The Release Workflow**
|
||||
### The Release Workflow
|
||||
|
||||
Creating a release involves specifying a Git tag and a directory containing build artifacts or other files to distribute. The `rngit` client will open your configured `$EDITOR` to compose release notes, then upload all artifacts to the remote repository node.
|
||||
|
||||
@@ -283,7 +644,7 @@ This will:
|
||||
|
||||
If no `$EDITOR` environment variable is set, `rngit` will try to use `nano`, `vim` or `vi`. The editor will show a template with instructions. Lines starting with `#` will be ignored, and if the remaining content is empty after stripping comments, the release creation will be cancelled.
|
||||
|
||||
**Release Storage & Structure**
|
||||
### Release Storage & Structure
|
||||
|
||||
Releases are stored on the node in a directory named `repo_name.releases` next to the bare repository. Each release is a subdirectory containing:
|
||||
|
||||
@@ -292,6 +653,8 @@ Releases are stored on the node in a directory named `repo_name.releases` next t
|
||||
- `artifacts/` - All uploaded files
|
||||
- `THANKS` - Appreciation count from users
|
||||
|
||||
### Command-Line Interaction
|
||||
|
||||
**Listing Releases**
|
||||
|
||||
To view all releases for a repository:
|
||||
@@ -363,8 +726,6 @@ rel:none # Deny everyone
|
||||
|
||||
When the Nomad Network page node is enabled, releases are displayed on a dedicated releases page for each repository. Each release is listed with its tag, creation date, artifact count and a preview of the release notes. Clicking a release shows the full details including formatted release notes and a listing of all artifacts with their sizes.
|
||||
|
||||
Only releases with `published` status are visible through the Nomad Network interface. Draft releases (if supported in future implementations) would only be visible through the command-line interface.
|
||||
|
||||
**All Command-Line Options (rngit release)**
|
||||
|
||||
```text
|
||||
@@ -394,6 +755,8 @@ options:
|
||||
|
||||
In addition to releases, `rngit` provides a work document management system for tracking tasks, investigations, issues and progress related to repositories. Work documents are stored as structured msgpack data and support threaded updates and comments.
|
||||
|
||||
### Working With Work Documents
|
||||
|
||||
**Listing Work Documents**
|
||||
|
||||
To view work documents for a repository:
|
||||
@@ -410,7 +773,7 @@ ID Title Author Created Comments
|
||||
2 Fixed bug in parser 8f3a21c9d84e927b… 2025-01-14 09:15 1
|
||||
```
|
||||
|
||||
Use `--scope completed` to view completed work documents, or `--scope all` to see both active and completed.
|
||||
Use `--scope completed` to view completed work documents, `--scope proposed` to view proposed documents, or `--scope all` to see all scopes.
|
||||
|
||||
**Viewing a Work Document**
|
||||
|
||||
@@ -468,6 +831,32 @@ $ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo update -d 1
|
||||
|
||||
This opens your editor to compose the update.
|
||||
|
||||
### Proposing Work Documents
|
||||
|
||||
Users with `propose` permission can create work document proposals without full `write` access. Proposals are created in a “proposed” state and must be activated by a user with appropriate permissions before becoming active.
|
||||
|
||||
To propose a work document:
|
||||
|
||||
```text
|
||||
$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo propose --title "Feature proposal"
|
||||
```
|
||||
|
||||
This opens your editor to compose the proposal content. When saved, the document is created in the “proposed” scope. The creator automatically receives `interact` and `write` permissions on the proposed document.
|
||||
|
||||
Proposed documents are visible through `--scope proposed` or `--scope all`:
|
||||
|
||||
```text
|
||||
$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo list --scope proposed
|
||||
```
|
||||
|
||||
**Permissions for Proposals**
|
||||
|
||||
- Creating proposals requires `propose` permission on the repository
|
||||
- The creator automatically gets `interact` and `write` on their proposed document
|
||||
- Activating a proposal requires `write` and `interact` permissions
|
||||
|
||||
### State Management
|
||||
|
||||
**Completing Work Documents**
|
||||
|
||||
To mark a work document as completed (moving it from `active` to `completed`):
|
||||
@@ -499,20 +888,50 @@ Are you sure you want to delete active work document #1? [y/N]: y
|
||||
Work document #1 deleted
|
||||
```
|
||||
|
||||
**Permissions**
|
||||
### Managing Work Document Permissions
|
||||
|
||||
Users can view work documents and updates if the have `read` permission for the repository. If users have `read` and `interact`, they can also post updates/comments on existing work documents. Work document management requires having `write` and `interact` permission to the repository. These permissions are configured the same way as any other repository permissions. In the config file or `.allowed` files, use `i:target` to grant work document interaction rights:
|
||||
Users with administrative access to a work document can manage its specific permissions. This allows fine-grained control over who can read, write, comment on, or administer individual work documents.
|
||||
|
||||
To view or edit permissions for a work document:
|
||||
|
||||
```text
|
||||
# In .allowed file or config
|
||||
i:all # Allow everyone
|
||||
i:9710b86... # Allow specific identity
|
||||
i:none # Deny everyone
|
||||
$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo perms -d 1
|
||||
```
|
||||
|
||||
**Author Verification**
|
||||
This opens your editor with the current permission configuration:
|
||||
|
||||
Users can only edit or delete work documents and updates they created. The author is cryptographically verified from the interacting link’s `remote_identity`.
|
||||
```text
|
||||
r:all
|
||||
i:9710b86ba12c42d1d8f30f74fe509286
|
||||
adm:9710b86ba12c42d1d8f30f74fe509286
|
||||
```
|
||||
|
||||
Permission rules follow the same format as repository permissions:
|
||||
|
||||
- `r:target` - Grant read access
|
||||
- `w:target` - Grant write access
|
||||
- `i:target` - Grant interact (comment) access
|
||||
- `adm:target` - Grant admin access
|
||||
|
||||
Targets can be `all`, `none`, or a specific identity hash.
|
||||
|
||||
**Who Can Edit Permissions**
|
||||
|
||||
Document permissions can be edited by:
|
||||
|
||||
- The original author (if they also have `interact` and `write` on the repository)
|
||||
- Any user with `admin` permission on the document
|
||||
- Repository admins (through inherited permissions)
|
||||
|
||||
**Permission Precedence**
|
||||
|
||||
Document-specific permissions override repository-level permissions for that document. If document permissions exist, they are checked first; if access is not granted there, repository permissions are checked.
|
||||
|
||||
**Author Rights:**
|
||||
|
||||
- Users can only edit or delete work documents they created
|
||||
- The author is cryptographically verified from the interacting link’s `remote_identity`
|
||||
- Document creators automatically receive `interact` and `write` on their documents
|
||||
|
||||
**Storage Format**
|
||||
|
||||
@@ -520,6 +939,7 @@ Work documents are stored in a `repo_name.work` directory next to the repository
|
||||
|
||||
- `active/` - Active work documents
|
||||
- `completed/` - Completed work documents
|
||||
- `proposed/` - Proposed work documents
|
||||
|
||||
Each document is a numbered directory containing:
|
||||
|
||||
@@ -542,7 +962,8 @@ Reticulum Git Work Document Manager
|
||||
|
||||
positional arguments:
|
||||
repository URL of remote repository
|
||||
operation list, view, create, edit, delete, update or complete
|
||||
operation list, view, create, propose, edit, delete,
|
||||
update, complete, activate or perms
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
@@ -550,8 +971,8 @@ options:
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
--scope SCOPE document scope: active, completed or all
|
||||
-t, --title TITLE document title for create
|
||||
--scope SCOPE document scope: active, completed, proposed or all
|
||||
-t, --title TITLE document title for create/propose
|
||||
-d, --id ID document ID
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
|
||||
+32
-5
@@ -82,8 +82,6 @@ to participate in the development of Reticulum itself.
|
||||
* [Retipedia](software.md#retipedia)
|
||||
* [Sideband](software.md#sideband)
|
||||
* [MeshChatX](software.md#meshchatx)
|
||||
* [MeshChat](software.md#meshchat)
|
||||
* [Columba](software.md#columba)
|
||||
* [Reticulum Relay Chat](software.md#reticulum-relay-chat)
|
||||
* [RetiBBS](software.md#retibbs)
|
||||
* [RBrowser](software.md#rbrowser)
|
||||
@@ -96,7 +94,7 @@ to participate in the development of Reticulum itself.
|
||||
* [RNMon](software.md#rnmon)
|
||||
* [Protocols](software.md#protocols)
|
||||
* [LXMF](software.md#lxmf)
|
||||
* [LXST](software.md#id17)
|
||||
* [LXST](software.md#id16)
|
||||
* [RRC](software.md#rrc)
|
||||
* [Interface Modules & Connectivity Resources](software.md#interface-modules-connectivity-resources)
|
||||
* [Using Reticulum on Your System](using.md)
|
||||
@@ -183,6 +181,7 @@ to participate in the development of Reticulum itself.
|
||||
* [Interface Modes](interfaces.md#interfaces-modes)
|
||||
* [Announce Rate Control](interfaces.md#announce-rate-control)
|
||||
* [New Destination Rate Limiting](interfaces.md#new-destination-rate-limiting)
|
||||
* [Path Request Burst Control](interfaces.md#path-request-burst-control)
|
||||
* [Building Networks](networks.md)
|
||||
* [Concepts & Overview](networks.md#concepts-overview)
|
||||
* [Introductory Considerations](networks.md#introductory-considerations)
|
||||
@@ -192,12 +191,40 @@ to participate in the development of Reticulum itself.
|
||||
* [Heterogeneous Connectivity](networks.md#heterogeneous-connectivity)
|
||||
* [Git Over Reticulum](git.md)
|
||||
* [The rngit Utility](git.md#the-rngit-utility)
|
||||
* [Repository Creation & Management](git.md#repository-creation-management)
|
||||
* [Creating Empty Repositories](git.md#creating-empty-repositories)
|
||||
* [Forking Repositories](git.md#forking-repositories)
|
||||
* [Mirroring Repositories](git.md#mirroring-repositories)
|
||||
* [Automatic Mirror Synchronization](git.md#automatic-mirror-synchronization)
|
||||
* [Manual Synchronization](git.md#manual-synchronization)
|
||||
* [Git Configuration Parameters](git.md#git-configuration-parameters)
|
||||
* [Repository Structure](git.md#repository-structure)
|
||||
* [Configuration](git.md#configuration)
|
||||
* [Permissions](git.md#permissions)
|
||||
* [Permission Types](git.md#permission-types)
|
||||
* [Permission Hierarchy](git.md#permission-hierarchy)
|
||||
* [Configuration Methods](git.md#configuration-methods)
|
||||
* [Work Document Permissions](git.md#work-document-permissions)
|
||||
* [Creator Permissions](git.md#creator-permissions)
|
||||
* [Permission Examples](git.md#permission-examples)
|
||||
* [Permission Short Forms](git.md#permission-short-forms)
|
||||
* [Permission Configuration Locations](git.md#permission-configuration-locations)
|
||||
* [Serving Pages Over Nomad Network](git.md#serving-pages-over-nomad-network)
|
||||
* [Formatting & Syntax Highlighting](git.md#formatting-syntax-highlighting)
|
||||
* [Customizing Templates](git.md#customizing-templates)
|
||||
* [Enabling the Git Page Node](git.md#enabling-the-git-page-node)
|
||||
* [Accessing Repository Pages](git.md#accessing-repository-pages)
|
||||
* [Formatting & Syntax Highlighting](git.md#formatting-syntax-highlighting)
|
||||
* [Customizing Templates](git.md#customizing-templates)
|
||||
* [Repository Statistics](git.md#repository-statistics)
|
||||
* [Configuration Example](git.md#configuration-example)
|
||||
* [Release Management](git.md#release-management)
|
||||
* [The Release Workflow](git.md#the-release-workflow)
|
||||
* [Release Storage & Structure](git.md#release-storage-structure)
|
||||
* [Command-Line Interaction](git.md#command-line-interaction)
|
||||
* [Work Documents](git.md#work-documents)
|
||||
* [Working With Work Documents](git.md#working-with-work-documents)
|
||||
* [Proposing Work Documents](git.md#proposing-work-documents)
|
||||
* [State Management](git.md#state-management)
|
||||
* [Managing Work Document Permissions](git.md#managing-work-document-permissions)
|
||||
* [Support Reticulum](support.md)
|
||||
* [Donations](support.md#donations)
|
||||
* [Provide Feedback](support.md#provide-feedback)
|
||||
|
||||
@@ -1295,11 +1295,12 @@ a large amount of bogus destinations, and then disconnect, these destination wil
|
||||
never make it into path tables and waste network bandwidth on retransmitted
|
||||
announces.
|
||||
|
||||
**It’s important to note** that the ingress control works at the level of *individual
|
||||
#### NOTE
|
||||
It’s important to remember that the ingress control works at the level of *individual
|
||||
sub-interfaces*. As an example, this means that one client on a [TCP Server Interface](#interfaces-tcps)
|
||||
cannot disrupt processing of incoming announces for other connected clients on the same
|
||||
[TCP Server Interface](#interfaces-tcps). All other clients on the same interface will still have new announces
|
||||
processed without interruption.
|
||||
[TCP Server Interface](#interfaces-tcps). All other clients on the same interface
|
||||
will still have new announces processed without interruption.
|
||||
|
||||
By default, Reticulum will handle this automatically, and ingress announce
|
||||
control will be enabled on interface where it is sensible to do so. It should
|
||||
@@ -1307,8 +1308,7 @@ generally not be neccessary to modify the ingress control configuration,
|
||||
but all the parameters are exposed for configuration if needed.
|
||||
|
||||
> * The `ingress_control` option tells Reticulum whether or not
|
||||
> to enable announce ingress control on the interface. Defaults to
|
||||
> `True`.
|
||||
> to enable ingress control on the interface. Defaults to `True`.
|
||||
> <br/>
|
||||
> * The `ic_new_time` option configures how long (in seconds) an
|
||||
> interface is considered newly spawned. Defaults to `2*60*60` seconds. This
|
||||
@@ -1343,4 +1343,59 @@ but all the parameters are exposed for configuration if needed.
|
||||
> * The `ic_held_release_interval` option sets how much time (in seconds)
|
||||
> must pass between releasing each held announce from the queue. Defaults
|
||||
> to `30` seconds.
|
||||
> <br/>
|
||||
> <br/>
|
||||
|
||||
All of the above settings can be configured both as instance-wide defaults
|
||||
under the `[reticulum]` section of the configuration file, or on a per-
|
||||
interface basis under the relevant interface configuration section.
|
||||
|
||||
## Path Request Burst Control
|
||||
|
||||
In addition the announce controls for newly created destination, Reticulum will also
|
||||
monitor incoming path request activity, and enforce burst controls if per-client rates
|
||||
exceed configured limits. Once path request burst control is activated on an
|
||||
interface, path requests will no longer be propagated further on the network.
|
||||
As with announce burst control, this happens on a per sub-interface basis. One
|
||||
client connecting to a public gateway will not be able to disrupt path request
|
||||
processing for other clients.
|
||||
|
||||
#### WARNING
|
||||
Applications that send large amounts of unnecessary path requests will very
|
||||
quickly get rate limited by transport nodes, and the entire system they are
|
||||
running on will not be able to resolve any paths on the network, until the
|
||||
burst subsides and hold period expires. **Do not** write applications like
|
||||
this. Only request paths for destinations you need to communicate with.
|
||||
|
||||
By default, Reticulum will handle this automatically, and ingress path request
|
||||
control will be enabled on interface where it is sensible to do so. It should
|
||||
generally not be neccessary to modify the ingress control configuration,
|
||||
but all the parameters are exposed for configuration if needed.
|
||||
|
||||
> * The `ingress_control` option tells Reticulum whether or not
|
||||
> to enable ingress control on the interface. Defaults to `True`.
|
||||
> <br/>
|
||||
> * The `ic_new_time` option configures how long (in seconds) an
|
||||
> interface is considered newly spawned. Defaults to `2*60*60` seconds. This
|
||||
> option is useful on publicly accessible interfaces that spawn new
|
||||
> sub-interfaces when a new client connects.
|
||||
> <br/>
|
||||
> * The `ic_pr_burst_freq_new` option sets the maximum path request
|
||||
> ingress frequency for newly spawned interfaces. Defaults to `3`
|
||||
> path requests per second.
|
||||
> <br/>
|
||||
> * The `ic_pr_burst_freq` option sets the maximum path request
|
||||
> ingress frequency for other interfaces. Defaults to `8` path requests
|
||||
> per second.
|
||||
> <br/>
|
||||
> > *If an interface exceeds its burst frequency, incoming path requests
|
||||
> > from that system will not traverse the network further.*
|
||||
> * The `egress_control` option enables hard-limiting path request egress
|
||||
> control per-interface. Defaults to `False`
|
||||
> <br/>
|
||||
> * The `ec_pr_freq` option sets the hard limit for outbound path requests
|
||||
> per second on a given interface.
|
||||
> <br/>
|
||||
|
||||
All of the above settings can be configured both as instance-wide defaults
|
||||
under the `[reticulum]` section of the configuration file, or on a per-
|
||||
interface basis under the relevant interface configuration section.
|
||||
@@ -72,28 +72,10 @@ 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 project, and is not affiliated with the original project.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### MeshChat
|
||||
|
||||
The [Reticulum MeshChat](https://github.com/liamcottle/reticulum-meshchat) application
|
||||
is a user-friendly LXMF client for Linux, macOS and Windows, that also includes a Nomad Network
|
||||
page browser and other interesting functionality.
|
||||
|
||||
Reticulum MeshChat is of course also compatible with Sideband and Nomad Network, or
|
||||
any other LXMF client.
|
||||
|
||||
### 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.
|
||||
|
||||
While still in early and very active development, it is of course also compatible
|
||||
with all other LXMF clients, and allows you to message seamlessly with anyone else
|
||||
using LXMF.
|
||||
|
||||
### 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.
|
||||
|
||||
+501
-41
@@ -4,14 +4,16 @@
|
||||
Git Over Reticulum
|
||||
******************
|
||||
|
||||
A set of utilities for distributed collaborative software development and publishing is included in RNS.
|
||||
A set of utilities for distributed collaborative software development and publishing are included in RNS.
|
||||
|
||||
The system consists of two parts: The ``rngit`` node that hosts repositories, and the ``git-remote-rns`` helper that enables Git to communicate with rngit nodes. As soon as you have RNS installed on your system, you can transparently use Git with Reticulum-hosted repositories just like any other type of remote. Git over Reticulum uses URLs in the following format: ``rns://DESTINATION_HASH/group/repo``.
|
||||
|
||||
If you set a branch to track a Reticulum remote as the default upstream, you can simply use ``git`` as you normally would; all commands work transparently and as expected.
|
||||
|
||||
.. warning::
|
||||
**The rngit program is a new addition to RNS!** This functionality was introduced in RNS 1.2.0. While great care has been taken to design a secure, but highly configurable and flexible permission system for allowing many users to interact with many different repositories on a single node, ``rngit`` has not been tested extensively in the wild! Be careful when hosting repositories, especially if they are public or semi-public.
|
||||
**The rngit program is a new addition to RNS!** This functionality was introduced in RNS 1.2.0. While great care has been taken to design a secure, but highly configurable and flexible `permission system`_ for allowing many users to interact with many different repositories on a single node, ``rngit`` has not been tested extensively in the wild! Be careful when hosting repositories, especially if they are public or semi-public.
|
||||
|
||||
.. _permission system: #permissions
|
||||
|
||||
The rngit Utility
|
||||
=================
|
||||
@@ -31,7 +33,7 @@ Run ``rngit`` to start a repository node:
|
||||
|
||||
On the first run, ``rngit`` will create a default configuration file. You will then need to edit this, to point to your repository locations, configure access permissions, and perform any other necessary configuration.
|
||||
|
||||
View your identity and destination hashes:
|
||||
Them, view your identity and destination hashes:
|
||||
|
||||
.. code:: text
|
||||
|
||||
@@ -73,6 +75,13 @@ Get changes from a remote repository:
|
||||
|
||||
$ git pull rns_remote master
|
||||
|
||||
Fork an existing repository from a remote to your ``rngit`` node:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit fork rns://8a37cdd16938ce79861561adbd59023a/reticulum/lxmf rns://50824b711717f97c2fb1166ceddd5ea9/public/myfork
|
||||
|
||||
|
||||
**All Command-Line Options (rngit)**
|
||||
|
||||
.. code:: text
|
||||
@@ -104,12 +113,223 @@ The ``git-remote-rns`` helper is automatically invoked by Git when interacting w
|
||||
The client configuration file is located at ``~/.rngit/client_config`` and allows adjusting parameters such as the reference batch size for transfers.
|
||||
|
||||
|
||||
Repository Creation & Management
|
||||
================================
|
||||
|
||||
The ``rngit`` utility provides several ways to create and manage repositories on a node: creating empty repositories, forking from existing repositories, and mirroring remote repositories.
|
||||
|
||||
Creating Empty Repositories
|
||||
---------------------------
|
||||
|
||||
To create a new empty repository on a remote node:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit create rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo
|
||||
|
||||
Repository public/myrepo created
|
||||
|
||||
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.
|
||||
|
||||
**All Command-Line Options (rngit create)**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rngit create [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
repository
|
||||
|
||||
Reticulum Git Repository Creation
|
||||
|
||||
positional arguments:
|
||||
repository URL of repository to create
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
Forking Repositories
|
||||
--------------------
|
||||
|
||||
Forking creates a copy of an existing repository (from any accessible Git URL) on your ``rngit`` node. Forks maintain a reference to their upstream source for later synchronization.
|
||||
|
||||
To fork a repository:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit fork https://github.com/user/original rns://50824b711717f97c2fb1166ceddd5ea9/public/myfork
|
||||
|
||||
Repository forked to public/myfork
|
||||
|
||||
The source can be any valid Git URL, including:
|
||||
|
||||
- HTTPS URLs: ``https://github.com/user/repo.git``
|
||||
- Git URLs: ``git://host.com/repo.git``
|
||||
- SSH URLs: ``ssh://git@host.com/repo.git``
|
||||
- Reticulum URLs: ``rns://DESTINATION_HASH/group/repo``
|
||||
- Local paths: ``/path/to/repo.git``
|
||||
|
||||
Forks are created as bare repositories with metadata tracking their origin. The fork process:
|
||||
|
||||
1. Creates a new bare repository
|
||||
2. Fetches all refs (``+refs/*:refs/*``) from the source
|
||||
3. Sets ``repository.rngit.type`` to ``fork``
|
||||
4. Sets ``repository.rngit.upstream.source`` to the source URL
|
||||
5. Grants creator admin permissions
|
||||
|
||||
**All Command-Line Options (rngit fork)**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rngit fork [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
source target
|
||||
|
||||
Reticulum Git Repository Forker
|
||||
|
||||
positional arguments:
|
||||
source URL of source repository
|
||||
target URL of target repository
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
Mirroring Repositories
|
||||
----------------------
|
||||
|
||||
Mirrors are similar to forks but are designed for keeping a local copy synchronized with an upstream repository. Mirrors can be automatically updated on a configurable schedule.
|
||||
|
||||
To create a mirror:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit mirror https://github.com/user/upstream rns://50824b711717f97c2fb1166ceddd5ea9/public/mymirror
|
||||
|
||||
Repository mirrored to public/mymirror
|
||||
|
||||
Mirrors are created with the same process as forks, but with ``repository.rngit.type`` set to ``mirror`` and an additional ``repository.rngit.upstream.sync`` timestamp tracking the last successful synchronization.
|
||||
|
||||
**All Command-Line Options (rngit mirror)**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rngit mirror [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
source target
|
||||
|
||||
Reticulum Git Mirror Management
|
||||
|
||||
positional arguments:
|
||||
source URL of source repository
|
||||
target URL of target repository
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
Automatic Mirror Synchronization
|
||||
--------------------------------
|
||||
|
||||
The ``rngit`` node can automatically keep mirrors synchronized with their upstream sources. This is configured in the main configuration file:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[rngit]
|
||||
mirror_interval = 24
|
||||
|
||||
The ``mirror_interval`` specifies the synchronization interval in hours (default: 24). The node checks for mirrors needing sync every 15 minutes, and fetches updates from upstream if the configured interval has elapsed since the last sync.
|
||||
|
||||
For automatic sync to happen, the repository must have been created with ``rngit mirror``. Sync failures are logged but do not prevent future retry attempts. The sync timestamp is only updated on successful completion.
|
||||
|
||||
Manual Synchronization
|
||||
----------------------
|
||||
|
||||
Both forks and mirrors can be manually synchronized on demand using the ``sync`` command:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit sync rns://50824b711717f97c2fb1166ceddd5ea9/public/myfork
|
||||
|
||||
Repository synced
|
||||
|
||||
This fetches all refs from the upstream source configured when the repository was created. You must have ``read`` and ``write`` permissions for the repository to perform a manual sync.
|
||||
|
||||
For mirrors, manual sync also updates the sync timestamp, effectively resetting the automatic sync timer.
|
||||
|
||||
**All Command-Line Options (rngit sync)**
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rngit sync [-h] [--config CONFIG] [--rnsconfig RNSCONFIG]
|
||||
[-i PATH] [-v] [-q] [--version]
|
||||
repository
|
||||
|
||||
Reticulum Git Repository Syncer
|
||||
|
||||
positional arguments:
|
||||
repository URL of repository
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative config directory
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
Git Configuration Parameters
|
||||
----------------------------
|
||||
|
||||
Repositories created through ``rngit`` store metadata in Git configuration:
|
||||
|
||||
- ``repository.rngit.type`` - Either ``fork`` or ``mirror``
|
||||
- ``repository.rngit.upstream.source`` - The source URL used during creation
|
||||
- ``repository.rngit.upstream.sync`` - Unix timestamp of last successful sync for mirrors
|
||||
|
||||
These parameters are used by the sync system and can be queried using standard Git commands:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ git config --get repository.rngit.type
|
||||
mirror
|
||||
|
||||
$ git config --get repository.rngit.upstream.source
|
||||
https://github.com/user/upstream
|
||||
|
||||
$ git config --get repository.rngit.upstream.sync
|
||||
1716230400
|
||||
|
||||
|
||||
|
||||
|
||||
Repository Structure
|
||||
====================
|
||||
|
||||
The ``rngit`` node organizes repositories into groups. Each group is a directory containing bare Git repositories. The repository path format is ``group_name/repo_name``. For example, a repository at ``/var/git/public/myrepo`` would be accessible as ``public/myrepo`` via the URL ``rns://DESTINATION_HASH/public/myrepo``.
|
||||
|
||||
**Configuration**
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The ``rngit`` node configuration file is located at ``~/.rngit/config`` (or ``/etc/rngit/config`` for system-wide installations). The default configuration includes:
|
||||
|
||||
@@ -118,20 +338,186 @@ The ``rngit`` node configuration file is located at ``~/.rngit/config`` (or ``/e
|
||||
- Announce intervals for network visibility
|
||||
- Optional statistics recording for repository activity
|
||||
|
||||
Access permissions can be configured at the group level in the config file, or per-repository using ``.allowed`` files. Permissions use the format ``permission:target`` where permission is ``r`` (read), ``w`` (write), ``rw`` (read/write), ``c`` (create) or ``s`` (stats) and target is ``all``, ``none``, or a specific identity hash.
|
||||
|
||||
The ``s`` (stats) permission allows viewing repository activity statistics, including views, fetches and pushes over time. To enable statistics recording, set ``record_stats = yes`` in the ``[rngit]`` section of the configuration file. You can also exclude specific identities from statistics by adding their hashes to ``stats_ignore_identities``.
|
||||
Permissions
|
||||
===========
|
||||
|
||||
Repository-specific ``.allowed`` files can be static text files or executable scripts that output permission rules to stdout. A ``group.allowed`` file in a repository group directory applies to all repositories within that group.
|
||||
The ``rngit`` permission system provides fine-grained access control at multiple levels: group-level, repository-level, and document-level. Permissions can be statically configured in files or dynamically generated via executable scripts.
|
||||
|
||||
Access permissions can be configured at the group level in the config file or per-group ``.allowed`` files, or per-repository ``.allowed`` files. The ``s`` (stats) permission allows viewing repository activity statistics, including views, fetches and pushes over time. To enable statistics recording, set ``record_stats = yes`` in the ``[rngit]`` section of the configuration file. You can also exclude specific identities from statistics by adding their hashes to ``stats_ignore_identities``.
|
||||
|
||||
By default, **no** permissions are granted for anything! You will have to enable the permissions you require to be able to actually *do* something with ``rngit``.
|
||||
|
||||
Permission Types
|
||||
----------------
|
||||
|
||||
The following permissions are supported:
|
||||
|
||||
- ``r`` (read) - Clone, fetch, and view repositories and work documents
|
||||
- ``w`` (write) - Push changes and manage work documents
|
||||
- ``rw`` (read/write) - Combined read and write access
|
||||
- ``c`` (create) - Create, fork or mirror new repositories within a group
|
||||
- ``s`` (stats) - View repository activity statistics
|
||||
- ``rel`` (release) - Create and manage releases
|
||||
- ``i`` (interact) - Comment on and interact with work documents
|
||||
- ``p`` (propose) - Propose new work documents (without full write access)
|
||||
- ``adm`` (admin) - Full access
|
||||
|
||||
Permission targets can be:
|
||||
|
||||
- ``all`` or ``a`` - Everyone
|
||||
- ``none`` or ``n`` - Nobody
|
||||
- A specific Reticulum identity hash
|
||||
|
||||
Permission Hierarchy
|
||||
--------------------
|
||||
|
||||
Permissions are resolved in the following hierarchy:
|
||||
|
||||
1. **Repository-level permissions** - Checked first, if none exists group permissions are checked
|
||||
2. **Group-level permissions** - Used as fallback if no repository-level permissions are set
|
||||
3. **Admin override** - Finally, potential admin rights are checked
|
||||
|
||||
For work documents, work document specific permissions are always checked first, and work documents have additional specific checks such as modifications only being possible by the document author.
|
||||
|
||||
Configuration Methods
|
||||
---------------------
|
||||
|
||||
**Group-Level Configuration**
|
||||
|
||||
Group permissions can be configured in the ``[access]`` section of the main config file:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[access]
|
||||
public = r:all, w:9710b86ba12c42d1d8f30f74fe509286
|
||||
internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
collaborative = r:all, i:all, p:all, w:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
Additionally, they can be configured in a group ``group_name.allowed`` file, placed next to the ``group_name`` group directory.
|
||||
|
||||
**Repository-Level Configuration**
|
||||
|
||||
Repository-specific permissions are set in ``.allowed`` files placed next to the repository directory (for example, ``myrepo.allowed`` for ``myrepo``):
|
||||
|
||||
.. code:: text
|
||||
|
||||
# myrepo.allowed
|
||||
r:all
|
||||
w:9710b86ba12c42d1d8f30f74fe509286
|
||||
rel:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
**Dynamic Permissions**
|
||||
|
||||
Permission files can be made executable to generate permissions dynamically:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ chmod +x myrepo.allowed
|
||||
|
||||
When executable, the script is run and its stdout is parsed as permission rules. This allows integration with external authentication systems.
|
||||
|
||||
Work Document Permissions
|
||||
-------------------------
|
||||
|
||||
Work documents support additional permission granularity through ``.allowed`` files in the work directory (e.g., ``42.allowed`` for document #42). These files use the same permission syntax but only support:
|
||||
|
||||
- ``r`` (read) - View the document
|
||||
- ``w`` (write) - Edit the document
|
||||
- ``i`` (interact) - Comment on the document
|
||||
- ``p`` (propose) - Propose changes (future use)
|
||||
- ``adm`` (admin) - Full control over the document
|
||||
|
||||
Document permissions override repository permissions for that specific document. Work document permissions can be updated simply by editing the ``.allowed`` file, or remotely by using the ``rngit work`` command.
|
||||
|
||||
Creator Permissions
|
||||
-------------------
|
||||
|
||||
When a user creates a repository (via ``create``, ``fork``, or ``mirror``), they are automatically granted ``adm`` (admin) permissions on that repository.
|
||||
|
||||
When a user creates a work document, they automatically receive ``interact`` and ``write`` permissions on that document.
|
||||
|
||||
Permission Examples
|
||||
-------------------
|
||||
|
||||
**Example 1: Public Read, Restricted Write**
|
||||
|
||||
.. code:: text
|
||||
|
||||
r:all
|
||||
w:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
Everyone can read, only the specified identity can write.
|
||||
|
||||
**Example 2: Collaborative Development**
|
||||
|
||||
.. code:: text
|
||||
|
||||
r:all
|
||||
i:all
|
||||
p:all
|
||||
w:9710b86ba12c42d1d8f30f74fe509286
|
||||
rel:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
Everyone can read, interact (comment), and propose work documents. Only the specified identity can write, create releases, and manage work documents fully.
|
||||
|
||||
**Example 3: Private Repository**
|
||||
|
||||
.. code:: text
|
||||
|
||||
rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
rw:a1b2c3d4e5f686ba12c42d1ba12ef1aa
|
||||
|
||||
Only the two specified identities have any access (read or write).
|
||||
|
||||
**Example 4: Mirror with Stats**
|
||||
|
||||
.. code:: text
|
||||
|
||||
r:all
|
||||
s:all
|
||||
w:none
|
||||
|
||||
Everyone can read and view stats, but nobody can push (mirror is read-only from upstream).
|
||||
|
||||
Permission Short Forms
|
||||
----------------------
|
||||
|
||||
Permissions can be specified using short or long forms:
|
||||
|
||||
- ``r`` = ``read``
|
||||
- ``w`` = ``write``
|
||||
- ``rw`` = ``readwrite``
|
||||
- ``c`` = ``create``
|
||||
- ``s`` = ``stats``
|
||||
- ``rel`` = ``release``
|
||||
- ``i`` = ``interact``
|
||||
- ``p`` = ``propose``
|
||||
- ``adm`` = ``admin``
|
||||
|
||||
Targets can also use short forms:
|
||||
|
||||
- ``a`` = ``all`` = ``everyone``
|
||||
- ``n`` = ``none`` = ``nobody``
|
||||
|
||||
Permission Configuration Locations
|
||||
----------------------------------
|
||||
|
||||
- User install: ``~/.rngit/config``
|
||||
- System install: ``/etc/rngit/config``
|
||||
- Group permissions: ``<group_root>/<group_name>.allowed``
|
||||
- Repository permissions: ``<group_root>/<group_name>/<repo_name>.allowed``
|
||||
- Document permissions: ``<group_root>/<group_name>.work/<doc_id>.allowed``
|
||||
|
||||
Serving Pages Over Nomad Network
|
||||
================================
|
||||
|
||||
In addition to providing Git repository access via the Git remote helper protocol, ``rngit`` can also run a `Nomad Network <https://github.com/markqvist/nomadnet>`_ compatible page node. This allows users to browse repository information, view file contents, inspect commit history and access repository statistics through any Nomad Network client.
|
||||
In addition to providing Git repository access via the Git remote helper protocol and command-line tools, ``rngit`` can also run a `Nomad Network <https://github.com/markqvist/nomadnet>`_ compatible page node. This allows users to browse repository information, view file contents, inspect commit history and access repository statistics through any Nomad Network client.
|
||||
|
||||
When enabled, the page node provides a complete interface to your repositories, with automatic Markdown to Micron conversion, syntax-highlighted code browsing, and detailed commit, diff and statistics views.
|
||||
|
||||
**Enabling the Git Page Node**
|
||||
Enabling the Git Page Node
|
||||
--------------------------
|
||||
|
||||
To enable the page node, add the following to your ``~/.rngit/config`` file:
|
||||
|
||||
@@ -151,7 +537,8 @@ When the page node is enabled, ``rngit`` will listen on a Nomad Network node des
|
||||
Repositories Destination : <0d7334d411d00120cbad24edf355fdd2>
|
||||
Nomad Network Destination : <50824b711717f97c2fb1166ceddd5ea9>
|
||||
|
||||
**Accessing Repository Pages**
|
||||
Accessing Repository Pages
|
||||
--------------------------
|
||||
|
||||
Once the page node is running, you can access it from any Nomad Network client by connecting to the Nomad Network destination. The page node provides the following views:
|
||||
|
||||
@@ -168,7 +555,7 @@ Once the page node is running, you can access it from any Nomad Network client b
|
||||
All pages respect the same permission system used for Git access. If an identity does not have read access to a repository, they will not be able to view its pages.
|
||||
|
||||
Formatting & Syntax Highlighting
|
||||
================================
|
||||
--------------------------------
|
||||
|
||||
If the ``pygments`` Python module is installed on your system, the page node will automatically apply syntax highlighting to code files. The highlighting supports a wide range of programming languages and uses a color theme optimized for terminal display.
|
||||
|
||||
@@ -192,7 +579,7 @@ Code blocks in Markdown can include language hints for syntax highlighting:
|
||||
```
|
||||
|
||||
Customizing Templates
|
||||
=====================
|
||||
---------------------
|
||||
|
||||
The page node uses a template system that allows complete customization of the generated pages. Templates are stored in the ``~/.rngit/templates/`` directory as Micron files.
|
||||
|
||||
@@ -233,11 +620,12 @@ By default, the page node uses Nerd Font icons. If you prefer simpler icons or y
|
||||
serve_nomadnet = yes
|
||||
unicode_icons = yes
|
||||
|
||||
**Repository Statistics**
|
||||
Repository Statistics
|
||||
---------------------
|
||||
|
||||
When statistics recording is enabled (see the ``record_stats`` configuration option), the page node can display activity charts for each repository. The statistics page shows:
|
||||
|
||||
- Total and peak views, fetches and pushes
|
||||
- Total and peak views, downloads, fetches and pushes
|
||||
- Daily activity charts over a 90-day period
|
||||
- Combined activity visualization
|
||||
|
||||
@@ -247,28 +635,29 @@ To view statistics, a user must have the ``s`` (stats) permission for the reposi
|
||||
|
||||
The page node includes a "Thanks" feature that allows users to express appreciation for a repository. On each repository page, a "Thanks" link is displayed showing the current thanks count. Clicking this link registers a thank you for the repository.
|
||||
|
||||
**Configuration Example**
|
||||
Configuration Example
|
||||
---------------------
|
||||
|
||||
A complete page node configuration might look like this:
|
||||
A complete node configuration might look like this:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[rngit]
|
||||
node_name = My Git Node
|
||||
announce_interval = 360
|
||||
record_stats = yes
|
||||
node_name = My Git Node
|
||||
announce_interval = 360
|
||||
record_stats = yes
|
||||
|
||||
[repositories]
|
||||
public = /var/git/public
|
||||
internal = /var/git/internal
|
||||
public = /var/git/public
|
||||
internal = /var/git/internal
|
||||
|
||||
[access]
|
||||
public = r:all
|
||||
internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
public = r:all
|
||||
internal = rw:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
[pages]
|
||||
serve_nomadnet = yes
|
||||
unicode_icons = no
|
||||
serve_nomadnet = yes
|
||||
unicode_icons = no
|
||||
|
||||
|
||||
Release Management
|
||||
@@ -276,7 +665,8 @@ Release Management
|
||||
|
||||
In addition to hosting Git repositories, ``rngit`` provides a complete release management system. This allows you to publish versioned releases with associated artifacts, release notes and metadata. Releases are managed through the ``rngit release`` subcommand, and are also viewable through the Nomad Network page interface.
|
||||
|
||||
**The Release Workflow**
|
||||
The Release Workflow
|
||||
--------------------
|
||||
|
||||
Creating a release involves specifying a Git tag and a directory containing build artifacts or other files to distribute. The ``rngit`` client will open your configured ``$EDITOR`` to compose release notes, then upload all artifacts to the remote repository node.
|
||||
|
||||
@@ -295,7 +685,8 @@ This will:
|
||||
|
||||
If no ``$EDITOR`` environment variable is set, ``rngit`` will try to use ``nano``, ``vim`` or ``vi``. The editor will show a template with instructions. Lines starting with ``#`` will be ignored, and if the remaining content is empty after stripping comments, the release creation will be cancelled.
|
||||
|
||||
**Release Storage & Structure**
|
||||
Release Storage & Structure
|
||||
---------------------------
|
||||
|
||||
Releases are stored on the node in a directory named ``repo_name.releases`` next to the bare repository. Each release is a subdirectory containing:
|
||||
|
||||
@@ -304,6 +695,10 @@ Releases are stored on the node in a directory named ``repo_name.releases`` next
|
||||
- ``artifacts/`` - All uploaded files
|
||||
- ``THANKS`` - Appreciation count from users
|
||||
|
||||
|
||||
Command-Line Interaction
|
||||
------------------------
|
||||
|
||||
**Listing Releases**
|
||||
|
||||
To view all releases for a repository:
|
||||
@@ -375,8 +770,6 @@ Release management requires the ``release`` permission, configured the same way
|
||||
|
||||
When the Nomad Network page node is enabled, releases are displayed on a dedicated releases page for each repository. Each release is listed with its tag, creation date, artifact count and a preview of the release notes. Clicking a release shows the full details including formatted release notes and a listing of all artifacts with their sizes.
|
||||
|
||||
Only releases with ``published`` status are visible through the Nomad Network interface. Draft releases (if supported in future implementations) would only be visible through the command-line interface.
|
||||
|
||||
**All Command-Line Options (rngit release)**
|
||||
|
||||
.. code:: text
|
||||
@@ -402,12 +795,18 @@ Only releases with ``published`` status are visible through the Nomad Network in
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
|
||||
Work Documents
|
||||
==============
|
||||
|
||||
In addition to releases, ``rngit`` provides a work document management system for tracking tasks, investigations, issues and progress related to repositories. Work documents are stored as structured msgpack data and support threaded updates and comments.
|
||||
|
||||
Working With Work Documents
|
||||
---------------------------
|
||||
|
||||
**Listing Work Documents**
|
||||
|
||||
To view work documents for a repository:
|
||||
@@ -424,7 +823,7 @@ To view work documents for a repository:
|
||||
1 Implemented new feature 9710b86ba12c4f2e… 2025-01-15 14:32 3
|
||||
2 Fixed bug in parser 8f3a21c9d84e927b… 2025-01-14 09:15 1
|
||||
|
||||
Use ``--scope completed`` to view completed work documents, or ``--scope all`` to see both active and completed.
|
||||
Use ``--scope completed`` to view completed work documents, ``--scope proposed`` to view proposed documents, or ``--scope all`` to see all scopes.
|
||||
|
||||
**Viewing a Work Document**
|
||||
|
||||
@@ -482,6 +881,34 @@ To add an update to a work document:
|
||||
|
||||
This opens your editor to compose the update.
|
||||
|
||||
Proposing Work Documents
|
||||
------------------------
|
||||
|
||||
Users with ``propose`` permission can create work document proposals without full ``write`` access. Proposals are created in a "proposed" state and must be activated by a user with appropriate permissions before becoming active.
|
||||
|
||||
To propose a work document:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo propose --title "Feature proposal"
|
||||
|
||||
This opens your editor to compose the proposal content. When saved, the document is created in the "proposed" scope. The creator automatically receives ``interact`` and ``write`` permissions on the proposed document.
|
||||
|
||||
Proposed documents are visible through ``--scope proposed`` or ``--scope all``:
|
||||
|
||||
.. code:: text
|
||||
|
||||
$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo list --scope proposed
|
||||
|
||||
**Permissions for Proposals**
|
||||
|
||||
- Creating proposals requires ``propose`` permission on the repository
|
||||
- The creator automatically gets ``interact`` and ``write`` on their proposed document
|
||||
- Activating a proposal requires ``write`` and ``interact`` permissions
|
||||
|
||||
State Management
|
||||
----------------
|
||||
|
||||
**Completing Work Documents**
|
||||
|
||||
To mark a work document as completed (moving it from ``active`` to ``completed``):
|
||||
@@ -513,20 +940,51 @@ To delete a work document and all its comments:
|
||||
Are you sure you want to delete active work document #1? [y/N]: y
|
||||
Work document #1 deleted
|
||||
|
||||
**Permissions**
|
||||
Managing Work Document Permissions
|
||||
----------------------------------
|
||||
|
||||
Users can view work documents and updates if the have ``read`` permission for the repository. If users have ``read`` and ``interact``, they can also post updates/comments on existing work documents. Work document management requires having ``write`` and ``interact`` permission to the repository. These permissions are configured the same way as any other repository permissions. In the config file or ``.allowed`` files, use ``i:target`` to grant work document interaction rights:
|
||||
Users with administrative access to a work document can manage its specific permissions. This allows fine-grained control over who can read, write, comment on, or administer individual work documents.
|
||||
|
||||
To view or edit permissions for a work document:
|
||||
|
||||
.. code:: text
|
||||
|
||||
# In .allowed file or config
|
||||
i:all # Allow everyone
|
||||
i:9710b86... # Allow specific identity
|
||||
i:none # Deny everyone
|
||||
$ rngit work rns://50824b711717f97c2fb1166ceddd5ea9/public/myrepo perms -d 1
|
||||
|
||||
**Author Verification**
|
||||
This opens your editor with the current permission configuration:
|
||||
|
||||
Users can only edit or delete work documents and updates they created. The author is cryptographically verified from the interacting link's ``remote_identity``.
|
||||
.. code:: text
|
||||
|
||||
r:all
|
||||
i:9710b86ba12c42d1d8f30f74fe509286
|
||||
adm:9710b86ba12c42d1d8f30f74fe509286
|
||||
|
||||
Permission rules follow the same format as repository permissions:
|
||||
|
||||
- ``r:target`` - Grant read access
|
||||
- ``w:target`` - Grant write access
|
||||
- ``i:target`` - Grant interact (comment) access
|
||||
- ``adm:target`` - Grant admin access
|
||||
|
||||
Targets can be ``all``, ``none``, or a specific identity hash.
|
||||
|
||||
**Who Can Edit Permissions**
|
||||
|
||||
Document permissions can be edited by:
|
||||
|
||||
- The original author (if they also have ``interact`` and ``write`` on the repository)
|
||||
- Any user with ``admin`` permission on the document
|
||||
- Repository admins (through inherited permissions)
|
||||
|
||||
**Permission Precedence**
|
||||
|
||||
Document-specific permissions override repository-level permissions for that document. If document permissions exist, they are checked first; if access is not granted there, repository permissions are checked.
|
||||
|
||||
**Author Rights:**
|
||||
|
||||
- Users can only edit or delete work documents they created
|
||||
- The author is cryptographically verified from the interacting link's ``remote_identity``
|
||||
- Document creators automatically receive ``interact`` and ``write`` on their documents
|
||||
|
||||
**Storage Format**
|
||||
|
||||
@@ -534,6 +992,7 @@ Work documents are stored in a ``repo_name.work`` directory next to the reposito
|
||||
|
||||
- ``active/`` - Active work documents
|
||||
- ``completed/`` - Completed work documents
|
||||
- ``proposed/`` - Proposed work documents
|
||||
|
||||
Each document is a numbered directory containing:
|
||||
|
||||
@@ -557,7 +1016,8 @@ When the Nomad Network page node is enabled, work documents are viewable through
|
||||
|
||||
positional arguments:
|
||||
repository URL of remote repository
|
||||
operation list, view, create, edit, delete, update or complete
|
||||
operation list, view, create, propose, edit, delete,
|
||||
update, complete, activate or perms
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
@@ -565,8 +1025,8 @@ When the Nomad Network page node is enabled, work documents are viewable through
|
||||
--rnsconfig RNSCONFIG
|
||||
path to alternative Reticulum config directory
|
||||
-i, --identity PATH path to identity
|
||||
--scope SCOPE document scope: active, completed or all
|
||||
-t, --title TITLE document title for create
|
||||
--scope SCOPE document scope: active, completed, proposed or all
|
||||
-t, --title TITLE document title for create/propose
|
||||
-d, --id ID document ID
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
|
||||
@@ -1372,11 +1372,12 @@ a large amount of bogus destinations, and then disconnect, these destination wil
|
||||
never make it into path tables and waste network bandwidth on retransmitted
|
||||
announces.
|
||||
|
||||
**It's important to note** that the ingress control works at the level of *individual
|
||||
sub-interfaces*. As an example, this means that one client on a :ref:`TCP Server Interface<interfaces-tcps>`
|
||||
cannot disrupt processing of incoming announces for other connected clients on the same
|
||||
:ref:`TCP Server Interface<interfaces-tcps>`. All other clients on the same interface will still have new announces
|
||||
processed without interruption.
|
||||
.. note::
|
||||
It's important to remember that the ingress control works at the level of *individual
|
||||
sub-interfaces*. As an example, this means that one client on a :ref:`TCP Server Interface<interfaces-tcps>`
|
||||
cannot disrupt processing of incoming announces for other connected clients on the same
|
||||
:ref:`TCP Server Interface<interfaces-tcps>`. All other clients on the same interface
|
||||
will still have new announces processed without interruption.
|
||||
|
||||
By default, Reticulum will handle this automatically, and ingress announce
|
||||
control will be enabled on interface where it is sensible to do so. It should
|
||||
@@ -1384,8 +1385,7 @@ generally not be neccessary to modify the ingress control configuration,
|
||||
but all the parameters are exposed for configuration if needed.
|
||||
|
||||
* | The ``ingress_control`` option tells Reticulum whether or not
|
||||
to enable announce ingress control on the interface. Defaults to
|
||||
``True``.
|
||||
to enable ingress control on the interface. Defaults to ``True``.
|
||||
|
||||
* | The ``ic_new_time`` option configures how long (in seconds) an
|
||||
interface is considered newly spawned. Defaults to ``2*60*60`` seconds. This
|
||||
@@ -1422,3 +1422,59 @@ but all the parameters are exposed for configuration if needed.
|
||||
must pass between releasing each held announce from the queue. Defaults
|
||||
to ``30`` seconds.
|
||||
|
||||
All of the above settings can be configured both as instance-wide defaults
|
||||
under the ``[reticulum]`` section of the configuration file, or on a per-
|
||||
interface basis under the relevant interface configuration section.
|
||||
|
||||
|
||||
Path Request Burst Control
|
||||
==========================
|
||||
|
||||
In addition the announce controls for newly created destination, Reticulum will also
|
||||
monitor incoming path request activity, and enforce burst controls if per-client rates
|
||||
exceed configured limits. Once path request burst control is activated on an
|
||||
interface, path requests will no longer be propagated further on the network.
|
||||
As with announce burst control, this happens on a per sub-interface basis. One
|
||||
client connecting to a public gateway will not be able to disrupt path request
|
||||
processing for other clients.
|
||||
|
||||
.. warning::
|
||||
Applications that send large amounts of unnecessary path requests will very
|
||||
quickly get rate limited by transport nodes, and the entire system they are
|
||||
running on will not be able to resolve any paths on the network, until the
|
||||
burst subsides and hold period expires. **Do not** write applications like
|
||||
this. Only request paths for destinations you need to communicate with.
|
||||
|
||||
By default, Reticulum will handle this automatically, and ingress path request
|
||||
control will be enabled on interface where it is sensible to do so. It should
|
||||
generally not be neccessary to modify the ingress control configuration,
|
||||
but all the parameters are exposed for configuration if needed.
|
||||
|
||||
* | The ``ingress_control`` option tells Reticulum whether or not
|
||||
to enable ingress control on the interface. Defaults to ``True``.
|
||||
|
||||
* | The ``ic_new_time`` option configures how long (in seconds) an
|
||||
interface is considered newly spawned. Defaults to ``2*60*60`` seconds. This
|
||||
option is useful on publicly accessible interfaces that spawn new
|
||||
sub-interfaces when a new client connects.
|
||||
|
||||
* | The ``ic_pr_burst_freq_new`` option sets the maximum path request
|
||||
ingress frequency for newly spawned interfaces. Defaults to ``3``
|
||||
path requests per second.
|
||||
|
||||
* | The ``ic_pr_burst_freq`` option sets the maximum path request
|
||||
ingress frequency for other interfaces. Defaults to ``8`` path requests
|
||||
per second.
|
||||
|
||||
*If an interface exceeds its burst frequency, incoming path requests
|
||||
from that system will not traverse the network further.*
|
||||
|
||||
* | The ``egress_control`` option enables hard-limiting path request egress
|
||||
control per-interface. Defaults to ``False``
|
||||
|
||||
* | The ``ec_pr_freq`` option sets the hard limit for outbound path requests
|
||||
per second on a given interface.
|
||||
|
||||
All of the above settings can be configured both as instance-wide defaults
|
||||
under the ``[reticulum]`` section of the configuration file, or on a per-
|
||||
interface basis under the relevant interface configuration section.
|
||||
@@ -110,7 +110,7 @@ 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 project, and is not affiliated with the original project.
|
||||
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.
|
||||
|
||||
.. only:: html
|
||||
|
||||
@@ -127,56 +127,6 @@ A `Reticulum MeshChat fork from the future <https://git.quad4.io/RNS-Things/Mesh
|
||||
|
||||
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.
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
|
||||
MeshChat
|
||||
^^^^^^^^
|
||||
|
||||
The `Reticulum MeshChat <https://github.com/liamcottle/reticulum-meshchat>`_ application
|
||||
is a user-friendly LXMF client for Linux, macOS and Windows, that also includes a Nomad Network
|
||||
page browser and other interesting functionality.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/meshchat_1.webp
|
||||
:align: center
|
||||
:target: https://github.com/liamcottle/reticulum-meshchat
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/meshchat_1.png
|
||||
:align: center
|
||||
:target: https://github.com/liamcottle/reticulum-meshchat
|
||||
|
||||
Reticulum MeshChat is of course also compatible with Sideband and Nomad Network, or
|
||||
any other LXMF client.
|
||||
|
||||
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.
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. image:: screenshots/columba.webp
|
||||
:align: center
|
||||
:width: 25%
|
||||
:target: https://github.com/torlando-tech/columba/
|
||||
|
||||
.. only:: latex
|
||||
|
||||
.. image:: screenshots/columba.png
|
||||
:align: center
|
||||
:width: 25%
|
||||
:target: https://github.com/torlando-tech/columba/
|
||||
|
||||
While still in early and very active development, it is of course also compatible
|
||||
with all other LXMF clients, and allows you to message seamlessly with anyone else
|
||||
using LXMF.
|
||||
|
||||
.. raw:: latex
|
||||
|
||||
\newpage
|
||||
|
||||
@@ -31,6 +31,10 @@ Donations are gratefully accepted via the following channels:
|
||||
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
|
||||
|
||||
Provide Feedback
|
||||
================
|
||||
Feedback on the usage, functioning and potential dysfunctioning of any and
|
||||
|
||||
Reference in New Issue
Block a user