IranTalk context pack: Context pack: operator / بستهٔ زمینه: اپراتور It holds the node-abroad and gateway guides, the Reticulum node, dashboard, operations and security docs, and the node bundle's README and AGENTS.md. Built from the signed list version 1 (issued 2026-10-01, valid until 2026-10-31). Project key fingerprint: (not generated yet) Farsi first, then English. Source: https://docs.irantalk.com/ai ===== fa: راه‌اندازی و ثبت گره در خارج از ایران ===== # راه‌اندازی و ثبت گره در خارج از ایران در پایان این صفحه یک گرهٔ ایران تاک روی سروری خارج از ایران دارید، در داشبورد ثبت شده، و می‌دانید هر ۹۰ روز چه کاری لازم است تا در فهرست امضاشده بماند. اینترنت باز این راهنما برای کسی است که در خارج از ایران یک سرور کوچک اجاره می‌کند تا شبکه یک درِ دیگر داشته باشد: یک گرهٔ ترانزیت Reticulum که دیگران از آن رد می‌شوند، و اگر بخواهید، یک صندوق آفلاین و یک گرهٔ NomadNet. هر چه بیشتر، شبکه با بسته شدن یک سرور نمی‌افتد. فقط سرور خارج از ایران. گره‌ای داخل ایران هرگز ثبت نمی‌شود و هرگز در فهرست نمی‌آید؛ داشبورد می‌پرسد و شما باید راست بگویید. محدوده‌های بعضی ارائه‌دهنده‌ها از داخل ایران فیلتر است و برای ترانزیت به درد نمی‌خورد؛ پیش از اجاره در اتاق `help` بپرسید. ## ۱. سرور - Ubuntu 24.04 تازه، یک IPv4 عمومی، دسترسی root. یک نام دامنه بهتر از IP است؛ همان چیزی است که در فهرست می‌آید. - در دیوار آتش ارائه‌دهنده: SSH و درگاه ۴۲۴۲ (TCP) باز. ## ۲. نصب همهٔ چیزی که روی گره اجرا می‌شود در پوشهٔ `node/` مخزن ایران تاک است و با یک اسکریپت نصب می‌شود؛ روی سرور هیچ چیز با دست ویرایش نمی‌شود. 1. مخزن را روی سرور کپی کنید و نسخهٔ منتشرشده (تگ) را انتخاب کنید. 2. به‌عنوان root: ``` PRIVATE_NET_CIDR=127.0.0.1/32 bash node/scripts/install.sh ``` اسکریپت i2pd، کاربر `reticulum`، یک محیط پایتون با نسخه‌های قفل‌شده، تنظیمات، سرویس‌ها (`rnsd`، `lxmd`، `nomadnet`، `rrcd`، `bridge`) و دیوار آتش را نصب می‌کند. `PRIVATE_NET_CIDR` محدوده‌ای است که API داخلی از آن قابل دسترسی است؛ برای گرهٔ جامعه که آن را نمی‌خواهد، نشانی محلی کافی است. 3. تصمیم بگیرید گره چه چیزی ارائه می‌دهد. برای فقط ترانزیت، بقیه را خاموش کنید تا یک اتاق گفتگو یا صفحهٔ خبر دوم به نام ایران تاک اعلان نشود: ``` systemctl disable --now rrcd nomadnet ``` (و `lxmd` اگر صندوق آفلاین نمی‌خواهید.) 4. لینک به شبکه: در `node/config/reticulum.conf` دو ورودی `TCPClientInterface` را با گره‌های ترانزیت عمومی شناخته‌شده پر کنید (هرگز میزبانی داخل ایران) و اسکریپت را دوباره اجرا کنید. گره‌ای بدون لینک، جزیره است. 5. بررسی: `runuser -u reticulum -- /opt/irantalk/venv/bin/rnstatus` باید رابط TCP را بالا و لینک‌ها را وصل نشان دهد؛ `journalctl -u rnsd -n 50` بی‌خطا. ## ۳. ثبت در داشبورد ثبت حساب نمی‌خواهد: مالکیت گره را با امضای هویت خودِ گره ثابت می‌کنید. 1. dashboard.irantalk.com/register را باز کنید. نوع را انتخاب کنید (ورودی TCP، ورودی I2P، گره انتشار، گره NomadNet یا اتاق گفتگو)، نشانی‌ای که باید فهرست شود، و منطقه: «خارج از ایران». یک رشتهٔ چالش می‌گیرید. 2. روی سرور، چالش را با هویتِ پشتِ همان نشانی امضا کنید: ``` runuser -u reticulum -- /opt/irantalk/venv/bin/rnid -i IDENTITY -S "CHALLENGE" -b ``` برای ورودی TCP یا I2P، هویت ترانزیت زیر `/home/reticulum/.reticulum/storage/`؛ برای گره انتشار، هویت lxmd؛ برای اتاق گفتگو، هویت هاب. خود پروندهٔ هویت را هرگز جایی نچسبانید. 3. همهٔ خروجی را، با خط‌های آغاز و پایان، در داشبورد بچسبانید. امضا بررسی می‌شود و گره «در انتظار بازبینی» می‌ماند؛ یک نگه‌دارنده تأیید می‌کند و گره در فهرست امضاشدهٔ بعدی می‌آید. 4. هر ۹۰ روز همین کار را با همان هویت تکرار کنید، وگرنه گره از فهرست می‌افتد. ## ۴. با یک عامل هوش مصنوعی همین مراحل، با قاعده‌های سختِ راز نگه داشتن، در پروندهٔ `node/AGENTS.md` مخزن برای عامل‌های کدنویسی نوشته شده و در [کتابخانهٔ پرامپت](/ai) با نام «یک گرهٔ ترانزیت در خارج بساز» آماده است. عامل نصب می‌کند و امضا می‌دهد؛ ثبت و تأیید همیشه با انسان است. پرونده‌های هویت زیر `/home/reticulum/` کلید خصوصی گره‌اند. از آن‌ها نسخهٔ پشتیبان رمزنگاری‌شده بگیرید؛ با گم شدنشان، هش گره عوض می‌شود و باید از نو ثبت کنید. ===== fa: دروازهٔ محله: Pi، RNode و یک لینک بالا ===== # دروازهٔ محله: Pi، RNode و یک لینک بالا در پایان این صفحه یک دروازه دارید: رایانه‌ای کوچک که مِش LoRa محله را به گره‌های ایران تاک وصل می‌کند، و پیام‌ها را تا وقتی لینک قطع است در صندوق خودش نگه می‌دارد. بی‌اینترنت یک دروازه مدرک است، بزرگ‌تر از یک دستگاه دستی: آنتن بیرونی، همیشه روشن، با یک لینک بالا. پیش از خرید [پیش از روشن کردن رادیو](/safety) را بخوانید و تصمیم بگیرید چه کسی می‌داند کجاست. دروازه‌ای که داخل ایران است هرگز در داشبورد ثبت نمی‌شود و هرگز در فهرست نمی‌آید. ## ۱. قطعه‌ها | قطعه | انتخاب | |---|---| | رایانه | Raspberry Pi 4 یا 5، یا هر بردِ تک‌برد لینوکسی | | رادیو | یک برد RNode ([Heltec V3 یا T-Beam](/devices/companion-radio)) روی USB | | آنتن | آنتن بیرونی همان فرکانس، هرچه بالاتر بهتر؛ ارتفاع از توان مهم‌تر است | | لینک بالا | Starlink Mini، یا هر اینترنتی که در آن لحظه هست؛ دروازه بدون لینک هم کار می‌کند، فقط چیزی بیرون نمی‌رود | | برق | باتری ۱۲ ولت و پنل خورشیدی، یا برق شهر با UPS | ## ۲. رادیو برد را روی همان Pi یا روی لپ‌تاپ با `rnodeconf --autoinstall` آماده کنید ([رادیوی همراه گوشی](/devices/companion-radio)، بخش ۲). روی Pi، برد با USB وصل می‌ماند. ## ۳. نرم‌افزار روی Raspberry Pi OS (نسخهٔ Lite کافی است): 1. Reticulum و LXMF را نصب کنید: `pipx install rns lxmf`، یا با pip در یک محیط مجازی. پرونده‌های نصب در بستهٔ آفلاین هم هست. 2. یک بار `rnsd` را اجرا و ببندید تا `~/.reticulum/config` ساخته شود. سپس آن را ویرایش کنید: ``` [reticulum] enable_transport = Yes [interfaces] [[LoRa]] type = RNodeInterface enabled = yes port = /dev/ttyUSB0 frequency = 868000000 bandwidth = 125000 txpower = 7 spreadingfactor = 8 codingrate = 5 [[IranTalk]] type = TCPClientInterface enabled = yes target_host = HOST target_port = PORT ``` درگاه USB را با `ls /dev/tty*` پیدا کنید (گاهی `/dev/ttyACM0`). فرکانس، پهنای باند، توان، ضریب گسترش و نرخ کدگذاری باید **دقیقاً** همان باشد که رادیوهای محله دارند؛ مقادیر بالا فقط نمونه است. میزبان و درگاه از ردیف «Reticulum ‏(TCP)» در [راه‌های ارتباط](/connect). 3. `rnsd` را به‌عنوان سرویس اجرا کنید تا با هر روشن شدن بالا بیاید: `rnsd --service` در یک واحد systemd که خودتان می‌سازید. با `rnstatus` باید هر دو رابط بالا باشند. 4. صندوق محله: در `~/.lxmd/config`، بخش `[propagation]`، `enable_node = yes`؛ سپس `lxmd --service` به‌عنوان سرویس. از این پس پیامی که در مِش فرستاده می‌شود و گیرنده‌اش دور است، در Pi می‌ماند و با وصل شدن لینک به گره‌های ما می‌رسد. با `lxmd --status` می‌بینید چند پیام منتظر است. هر سوئیچ و هر کلید تنظیمات را با `rnsd --help`، `lxmd --help` و `lxmd --exampleconfig` همان نسخه بررسی کنید، نه با حافظه. ## ۴. جای آنتن نزدیک پنجره یا روی پشت‌بام، دور از دیوارهای فلزی. آنتن یک طبقه بالاتر بیش از چند برابر توان، برد می‌دهد. با یک دستگاه دستی در خیابان‌های اطراف امتحان کنید و ببینید تا کجا پیام می‌رسد. ## ۵. چه کسی به آن اعتماد می‌کند دروازه پیام‌های مِش را رله می‌کند؛ محتوا رمزنگاری شده است و Pi نمی‌تواند بخواند، ولی می‌بیند چه دستگاه‌هایی در برد هستند. کسی که دروازه را نگه می‌دارد باید این را بداند. فهرست آنچه یک گره می‌بیند و نگه می‌دارد در [پیش از روشن کردن رادیو](/safety) آمده است. برای یک گرهٔ عمومی در خارج از ایران، که در فهرست امضاشده می‌آید، راهنمای [راه‌اندازی و ثبت گره در خارج از ایران](/operators/node-abroad) است. دروازهٔ محله هرگز در آن فهرست نمی‌آید. ===== en: Run and register a node outside Iran ===== # Run and register a node outside Iran By the end of this page you have an IranTalk node on a server outside Iran, registered on the dashboard, and you know what to do every 90 days to stay in the signed list. Open internet This guide is for someone outside Iran who rents a small server so the network has one more door: a Reticulum transport node others route through and, if you want, an Offline Inbox and a NomadNet node. The more there are, the less the network falls when one server is blocked. Only a server outside Iran. A node inside Iran is never registered and never listed; the dashboard asks, and you must answer truthfully. Some providers' address ranges are filtered inside Iran and are useless for transit; ask in the `help` room before renting. ## 1. The server - A fresh Ubuntu 24.04, one public IPv4, root access. A domain name is better than an IP; it is what goes into the list. - At the provider's firewall: SSH and port 4242 (TCP) open. ## 2. Install Everything that runs on a node is in the `node/` folder of the IranTalk repository and is installed by one script; nothing on the server is edited by hand. 1. Copy the repository to the server and check out the released version (a tag). 2. As root: ``` PRIVATE_NET_CIDR=127.0.0.1/32 bash node/scripts/install.sh ``` The script installs i2pd, the `reticulum` user, a Python environment with locked versions, the configs, the services (`rnsd`, `lxmd`, `nomadnet`, `rrcd`, `bridge`) and the firewall. `PRIVATE_NET_CIDR` is the range the internal API may be reached from; for a community node that does not want it, the local address is enough. 3. Decide what the node offers. For transit only, switch the rest off so no second chat room or news page announces itself under the IranTalk name: ``` systemctl disable --now rrcd nomadnet ``` (and `lxmd` if you do not want an Offline Inbox.) 4. Links into the network: in `node/config/reticulum.conf`, fill the two `TCPClientInterface` entries with well-known public transport nodes (never a host inside Iran) and run the script again. A node without links is an island. 5. Check: `runuser -u reticulum -- /opt/irantalk/venv/bin/rnstatus` must show the TCP interface up and the links connected; `journalctl -u rnsd -n 50` with no errors. ## 3. Register on the dashboard No account is needed: you prove the node is yours by signing with the node's own identity. 1. Open dashboard.irantalk.com/register. Choose the kind (TCP entry, I2P entry, propagation node, NomadNet node or chat hub), the address to list, and the region: "outside Iran". You get a challenge string. 2. On the server, sign the challenge with the identity behind that address: ``` runuser -u reticulum -- /opt/irantalk/venv/bin/rnid -i IDENTITY -S "CHALLENGE" -b ``` For a TCP or I2P entry, the transport identity under `/home/reticulum/.reticulum/storage/`; for a propagation node, the lxmd identity; for a chat hub, the hub identity. Never paste the identity file itself anywhere. 3. Paste the whole output, with its start and end lines, into the dashboard. The signature is checked and the node waits as "pending"; a maintainer approves it and it goes into the next signed list. 4. Every 90 days, repeat this with the same identity, or the node drops out of the list. ## 4. With an AI agent The same steps, with the hard rules about keeping secrets, are written for coding agents in the repository's `node/AGENTS.md` and ready in the [prompt library](/ai) as "Build a transit node abroad". The agent installs and signs; registering and approving are always a person's job. The identity files under `/home/reticulum/` are the node's private keys. Keep an encrypted backup; losing them changes the node's hash and means registering again. ===== en: The neighbourhood gateway: Pi, RNode and an uplink ===== # The neighbourhood gateway: Pi, RNode and an uplink By the end of this page you have a gateway: a small computer that joins the neighbourhood's LoRa mesh to the IranTalk nodes, and keeps messages in its own inbox while the uplink is down. No internet A gateway is evidence, more than a handheld: an outdoor antenna, always on, with an uplink. Before buying, read [Before you switch on a radio](/safety) and decide who may know where it is. A gateway inside Iran is never registered on the dashboard and never appears in the list. ## 1. Parts | Part | Choice | |---|---| | Computer | Raspberry Pi 4 or 5, or any Linux single-board computer | | Radio | an RNode board ([Heltec V3 or T-Beam](/devices/companion-radio)) on USB | | Antenna | an outdoor antenna for the frequency, as high as possible; height matters more than power | | Uplink | Starlink Mini, or whatever internet exists at the time; the gateway works without one, nothing just leaves | | Power | a 12 V battery and a solar panel, or mains with a UPS | ## 2. The radio Prepare the board on the Pi itself or on a laptop with `rnodeconf --autoinstall` ([A radio for your phone](/devices/companion-radio), section 2). On the Pi, the board stays connected by USB. ## 3. Software On Raspberry Pi OS (the Lite edition is enough): 1. Install Reticulum and LXMF: `pipx install rns lxmf`, or with pip in a virtual environment. The install files are in the offline pack too. 2. Run `rnsd` once and stop it so `~/.reticulum/config` is created. Then edit it: ``` [reticulum] enable_transport = Yes [interfaces] [[LoRa]] type = RNodeInterface enabled = yes port = /dev/ttyUSB0 frequency = 868000000 bandwidth = 125000 txpower = 7 spreadingfactor = 8 codingrate = 5 [[IranTalk]] type = TCPClientInterface enabled = yes target_host = HOST target_port = PORT ``` Find the USB port with `ls /dev/tty*` (sometimes `/dev/ttyACM0`). The frequency, bandwidth, power, spreading factor and coding rate must be **exactly** what the neighbourhood's radios use; the values above are only an example. Host and port from the "Reticulum (TCP)" row on [Ways to connect](/connect). 3. Run `rnsd` as a service so it comes up with every boot: `rnsd --service` in a systemd unit you write. `rnstatus` should show both interfaces up. 4. The neighbourhood's inbox: in `~/.lxmd/config`, section `[propagation]`, `enable_node = yes`; then `lxmd --service` as a service. From now on a message sent on the mesh for someone far away stays on the Pi and reaches our nodes when the uplink comes up. `lxmd --status` shows how many messages are waiting. Check every switch and every configuration key against `rnsd --help`, `lxmd --help` and `lxmd --exampleconfig` of that version, not against memory. ## 4. Where the antenna goes Near a window or on the roof, away from metal walls. An antenna one floor higher gives more range than several times the power. Test with a handheld in the surrounding streets and see how far a message reaches. ## 5. Who trusts it The gateway relays the mesh's messages; the content is encrypted and the Pi cannot read it, but it sees which devices are in range. Whoever keeps the gateway must know this. What a node sees and keeps is listed on [Before you switch on a radio](/safety). For a public node outside Iran, one that goes into the signed list, the guide is [Run and register a node outside Iran](/operators/node-abroad). A neighbourhood gateway never goes into that list. ===== node/AGENTS.md ===== # Standing up a node with a coding agent For a coding agent (Claude Code, Codex, Gemini CLI or similar) that has been given a shell on a fresh server by a helper outside Iran, and for the human who checks its work. It is the "Build a transport node abroad" prompt of the library (docs/23); read by a person, it is the operator guide. What the node is, and why it is configured this way, is docs/04. **UNTESTED with an agent.** `scripts/install.sh` runs in the VM (`make vm`, docs/21) and on the project's own box (since 2026-10-25), not yet on a server driven by an agent. Rehearse in the VM first. ## Rules that come before every step 1. **Nothing is edited by hand on the box.** Every file comes from this repository through `scripts/install.sh`. To change a config, change it in the checkout and re-run the script. 2. **Secrets never enter the transcript.** The transcript leaves the machine and lives with the AI provider. Never `cat`, `echo`, `print` or paste: anything under `/home/reticulum/.reticulum/storage/` (identities), `/home/reticulum/.rrcd/hub_identity`, `/etc/default/irantalk`, a bridge line, a `sops` plaintext. Write secrets to files and name the path. Never open the private ops repository at all (CONTRIBUTING rule 2). 3. **Ask before anything destructive**: deleting, overwriting a config, `ufw --force`, re-running the installer on a box that already serves people. 4. **Only out-of-country infrastructure.** This node must be outside Iran, at a provider allowed by docs/16. If the human says otherwise, stop. 5. **The manifest is not yours.** Never write to `/opt/irantalk/manifest/`; the signed manifest comes from the release (docs/11). Registration goes through the dashboard (docs/12), never by editing anything here. 6. **Commands are checked, not remembered.** Before using a flag of `rnsd`, `rnstatus`, `rnid`, `lxmd`, run the program with `--help` from the venv and read it (CLAUDE.md). ## Preconditions to confirm first - Fresh Ubuntu 24.04, root or sudo, a public IPv4, the hostname the operator intends to publish (a DNS name is better than an IP; it is what the manifest will list). - `PRIVATE_NET_CIDR`: the private network the bridge API may be reached from. A community node that runs no bridge API still has to pass one; use the box's private network range, or `127.0.0.1/32` if there is none (**verify** in the VM that the installer accepts it). - Outbound HTTPS and the Reticulum port open at the provider's firewall (TCP 4242 in). - The helper has an account on the dashboard? No: node registration needs no account (docs/12); the helper needs only a browser. ## Steps 1. **Checkout.** `git clone` the repository and check out the release the operator names (a tag). Read `node/README.md` and `docs/04`. Do not change `node/config/*` yet. 2. **Install.** As root, from the checkout: `PRIVATE_NET_CIDR= bash node/scripts/install.sh`. It installs i2pd (SAM), the `reticulum` user, a venv pinned by hash (`node/requirements.lock.txt`), the configs, the systemd units (`rnsd`, `lxmd`, `nomadnet`, `rrcd`, `bridge`, the news timer) and the ufw policy (22, 4242, the bridge port from the private range only). Without a verified manifest in `/opt/irantalk/manifest/` the NomadNet connect page lists no addresses; that is correct. 3. **Decide what this node offers**, with the human, and stop the rest. The installer brings up the whole bundle; a helper registering only a transport node disables what they will not run and will not register: `systemctl disable --now rrcd nomadnet` (and `lxmd` if no propagation node), so no second chat hub or news page announces itself (**verify** that rnsd keeps running cleanly with them stopped). 4. **Uplinks.** The node must not be an island (docs/04, step 4). In the checkout, in `node/config/reticulum.conf`, fill the two commented `TCPClientInterface` uplinks with well-known public transport nodes the human picks (never a host inside Iran), then re-run the installer. Record the choice for the maintainers (docs/16). 5. **Check.** As the service user, from the venv: - `runuser -u reticulum -- /opt/irantalk/venv/bin/rnstatus`: the TCP interface up, the I2P interface with a b32 address, the uplinks connected. - `systemctl status rnsd` and `journalctl -u rnsd -n 50 --no-pager`: no errors. - `ss -ltn | grep 4242`: listening. From another machine the human owns: a Reticulum client with a `TCPClientInterface` to this host reaches it (`rnpath`, `rnprobe`; **verify** the exact probe once the lab has a second box). - If `lxmd` runs: `runuser -u reticulum -- /opt/irantalk/venv/bin/lxmd --status`. 6. **Register** (docs/12 "Node registry"). The human opens the dashboard, chooses the kind (`rns_tcp`, `rns_i2p`, `lxmf_propagation`, `nomadnet_node` or `rrc_hub`), the address to list and the region class, and gets a challenge string. The agent signs it on the box with the identity behind that address and hands back **only the signature output**: `runuser -u reticulum -- /opt/irantalk/venv/bin/rnid -i -S "" -b` (checked against RNS 1.5.5 in docs/12). For `rns_tcp` and `rns_i2p` that is the transport identity under `/home/reticulum/.reticulum/storage/` (**verify** the file name against the installed RNS with `rnid --help`); for `lxmf_propagation` the lxmd identity; for `rrc_hub` the hub identity. The identity file itself is never shown. The human pastes the output into the dashboard; a maintainer approves; the node appears in the next signed manifest. 7. **Hand back.** Give the human the checklist below. Do not keep working on the box after that unless asked. ## Human checklist (the agent cannot do these) - [ ] The transcript holds no identity, key, env file or bridge line. If it does, treat that identity as burned: generate a new one and register again. - [ ] `ufw status`: only 22, 4242 and the bridge port from the private range. - [ ] Unattended security upgrades are on (docs/17 "Servers"); SSH is keys only. - [ ] A backup of `/home/reticulum/.reticulum/storage/` (and `.lxmd`, `.rrcd` if used), encrypted, kept off the box. - [ ] The dashboard shows the registration as pending; a reminder is set to re-sign before 90 days (docs/12). - [ ] The chosen uplinks are reported to the maintainers (docs/16). - [ ] The node's address is told to nobody until it is in a signed manifest (docs/11). ## Never - Never put a real name, a city or the provider's name in `node_name`, `display_name`, an announce or a commit. - Never raise the log level above notice (`loglevel = 3`): docs/17 publishes what is kept. - Never add an interface to a host inside Iran, and never list this node anywhere but the dashboard. - Never disable signature checks, install from an address not in the signed list, or "fix" the installer's pins. ===== node/README.md ===== # node: the Reticulum box Everything that runs on the dedicated Reticulum box ([docs/04](../docs/04-reticulum-node.md)): upstream `rnsd`, `lxmd` and `nomadnet` plus our glue. One venv at `/opt/irantalk/venv`, one `reticulum` user, nothing edited by hand on the box. | Path | Installed to | |---|---| | `config/reticulum.conf` | `~reticulum/.reticulum/config` | | `config/lxmd.conf` | `~reticulum/.lxmd/config` | | `config/nomadnet-node.conf` | merged over NomadNet's defaults → `~reticulum/.nomadnetwork/config` | | `config/rrcd.toml`, `config/rooms.toml` | `~reticulum/.rrcd/` (rooms seeded once into an empty registry) | | `config/irantalk.env.example` | `/etc/default/irantalk` (values come from the ops repo) | | `systemd/` | `/etc/systemd/system/` | | — | `/opt/irantalk/node/pages`: `index.mu` and `connect.mu` rendered from the signed manifest in `/opt/irantalk/manifest/`, `news.mu` by `pipeline/` | | `requirements.lock.txt` | hash-pinned deps, generated from `uv.lock` by `make lock` | | `requirements.git.txt` | deps not on PyPI (the chat hub), pinned by commit SHA | | `src/irantalk_node/bridge/` | bridge API: `GET /v1/status`, `POST /v1/verify` (token), `GET /healthz` | | `src/irantalk_node/rrc.py` | chat hub helper: seed rooms, print the hub's destination | | `lab/`, `scripts/lab.sh` | the laptop lab (`make lab`, docs/21) | | `AGENTS.md` | standing up a node with a coding agent, and the human checklist (docs/23) | Deploy: `make node-deploy` from the laptop; the header of `scripts/deploy.sh` is the how-to, docs/04 the why. On the box since 2026-10-25. ## Checked against RNS 1.5.5 / LXMF 1.2.0 / NomadNet 1.4.4 - `rnstatus -j` exists and its JSON shape is captured in `tests/fixtures/rnstatus.json`. - `lxmd -s` means `--service`, **not** status. Status is `lxmd --status`. - The lxmd size cap is `propagation_message_max_accepted_size`. - Log level 3 is *notice*. RNS's default of 4 is *info*. - Any Reticulum program that starts while no shared instance exists becomes it, interfaces and all; `require_shared_instance` is a Python argument only, not a flag of `lxmd` or `nomadnet`. The systemd units make rnsd the only shared instance (`systemd/rnsd.service`). ## Next - Report inbox daemon (Phase 2, docs/08). - A systemd path unit that re-renders the pages when a new manifest lands in `/opt/irantalk/manifest/`, instead of re-running `install.sh`. ===== node/config/reticulum.conf ===== # IranTalk transport node — installed as ~reticulum/.reticulum/config (docs/04). # Edit here, review, deploy with scripts/install.sh. Never edit on the box. # Reference: `rnsd --exampleconfig` (checked against RNS 1.5.5). [reticulum] enable_transport = yes share_instance = yes panic_on_interface_error = no # Strip transport headers and randomise hop counts for the programs on this shared instance # (lxmd, nomadnet, the chat hub). Without it, Ratspeak clients one hop away can never link # to them: Ratspeak wraps one-hop link requests in a transport header addressed to rnsd, # rnsd forwards it unchanged, and the local program ignores it (docs/04, 2026-10-24). local_hops_delta = yes [logging] # 3 = notice (RNS default is 4 = info). docs/16 and docs/17 publish notice-level logs. loglevel = 3 [interfaces] # Public TCP entry on the community-convention port. DNS: rns.irantalk.com (DNS-only, not proxied). [[IranTalk TCP]] type = TCPServerInterface enabled = yes listen_ip = 0.0.0.0 listen_port = 4242 # Tier 1 door: reachable over I2P with no inbound port. Needs i2pd with SAM on 127.0.0.1:7656. # After first start, read the b32 address from `rnstatus` and publish it via the manifest. [[IranTalk I2P]] type = I2PInterface enabled = yes connectable = yes # Uplinks so the node is not an island (docs/04 step 4): two public transport nodes from # directory.rns.recipes, recorded in docs/16-operations.md. Both accept plain TCP clients. [[Uplink A]] type = TCPClientInterface enabled = yes target_host = rmap.world target_port = 4242 [[Uplink B]] type = TCPClientInterface enabled = yes target_host = rns.arborisis.com target_port = 4242 ===== node/config/lxmd.conf ===== # IranTalk LXMF propagation node — installed as ~reticulum/.lxmd/config (docs/04). # Reference: `lxmd --exampleconfig` (checked against LXMF 1.2.0). [propagation] enable_node = yes node_name = IranTalk announce_interval = 360 announce_at_start = yes autopeer = yes autopeer_maxdepth = 6 # Megabytes. Raise as disk allows; disk alert at 80% (docs/04). message_storage_limit = 2000 # Kilobytes per incoming message. Keep modest: it bounds what one sender can park here. propagation_message_max_accepted_size = 256 # Identity hashes allowed to query status (the bridge API, once it reads lxmd stats). # control_allowed = auth_required = no [lxmf] display_name = IranTalk announce_at_start = no [logging] loglevel = 3 ===== docs/04-reticulum-node.md ===== # 04 — Reticulum node ## Purpose The Reticulum box is IranTalk's backbone on the mesh side: a transport node others can route through, a propagation node that holds messages for peers who are offline, and a NomadNet node that serves the connect page and news as micron pages on the mesh itself. ## Decisions - We run the Python reference stack (`rns`, `lxmf`, `nomadnet`) in production because it is the most battle-tested and the one Mark Qvist's docs describe. The Rust stack (rsReticulum / rsLXMF) is evaluated on `lab` for a possible second node. - We run **one** shared `rnsd` instance per box; `lxmd` and `nomadnet` attach to it. This keeps interfaces and routing in one place. - The box is **dedicated**: no Docker, no Coolify, no other services. Reticulum's TCP interface and I2P need stable, simple networking. - TCP entry on **4242** (community convention), I2P entry with no inbound port, and an AutoInterface for a future second box on the private network. - **`local_hops_delta = yes` on every box.** Found in the lab on 2026-10-24: Ratspeak sends a link request to a destination one hop away wrapped in a transport header addressed to the node, where Python Reticulum sends it bare. `rnsd` forwards the wrapped request unchanged to its local programs (`lxmd`, `nomadnet`, the chat hub) and they ignore it, so Offline Inbox and hub joins from Ratspeak hang forever while direct messages between clients work. With this option `rnsd` strips the header for local programs. Side effect: those programs show a random 2 to 7 hop count to clients, which also hides that they share the transport's box. - The box is public and will be blocked from inside Iran. Its job is the diaspora and the global mesh; the I2P interface is the Tier 1 door. ## Components | Component | Role | Config | Service | |---|---|---|---| | `rnsd` | transport node, interfaces | `~/.reticulum/config` | `rnsd.service` | | `lxmd` | LXMF propagation node | `~/.lxmd/config` | `lxmd.service` | | `nomadnet --daemon` | NomadNet node, pages + files | `~/.nomadnetwork/config` | `nomadnet.service` | | `i2pd` | I2P router with SAM | `/etc/i2pd/i2pd.conf` | `i2pd.service` | | `rrcd` | RRC chat hub, the mesh chat rooms (07) | `~/.rrcd/rrcd.toml`, `~/.rrcd/rooms.toml` | `rrcd.service` | | bridge API | read-only JSON for the web side: interfaces, the node's own destinations, lxmd stats; `POST /v1/verify` for the dashboard's node registry | `/etc/default/irantalk` | `bridge.service` | | news timer | persian.news → micron pages | same env file | `news2micron.timer` | | pages | `index.mu`, `connect.mu` rendered from the signed manifest (11) by `install.sh` | `/opt/irantalk/manifest/` | — | All of this is in the `node/` directory of the repo, with an install script. ## Development - Local work happens against a `lab` instance, never prod. `rnsd -v` in a terminal, with `rnstatus`, `rnpath`, `rnprobe` for inspection. - Config changes are made in the repo, reviewed, then copied to the box by the install script. Nothing is edited by hand on the box. - Bridge API: FastAPI, read-only, runs `rnstatus -j` and parses it. If RNS changes its output format, the bridge returns `{"raw": …}` and the web side shows "No data" rather than breaking. The node's own programs are read from the path table (`rnpath -t -j`, zero-hop entries), and propagation stats from lxmd's status request, run as a subprocess with lxmd's own identity, which lxmd always allows. Only aggregate counts are returned. - Upgrades: bump the pins in `uv.lock` (`make lock`), deploy to `lab` first, run for a week, then prod. The box installs from `node/requirements.lock.txt` with `pip --require-hashes`. ## Deployment 1. Fresh Ubuntu 24.04, arm64 or x86, dedicated box, on the same Hetzner private network as the Coolify box: that network is how the bridge API is reached, and the only place it answers. 2. `make node-deploy` from the laptop (`node/scripts/deploy.sh`, whose header is the complete how-to): it syncs `node/`, `pipeline/` and the project key over SSH and runs `node/scripts/install.sh` on the box. Re-run after any change under `node/`. Nothing is edited by hand on the box, and the box holds no git credential. 3. Secrets on the box, in `/etc/default/irantalk` and nowhere else: `BRIDGE_TOKEN`, `BRIDGE_BIND` (the private IP), and the news pair once docs/09 has an endpoint. 4. Two uplinks to public transport nodes in `node/config/reticulum.conf`, recorded in `16-operations.md`, never a host inside Iran. 5. `rns.irantalk.com` (DNS-only) at the box's public IPv4. The I2P b32 from `rnstatus` is published through the manifest (see 11). 6. The identity folders backed up off the box, encrypted: every address derives from them. 7. A helper standing up a community node with a coding agent follows `node/AGENTS.md` (docs/23), the same procedure with the secrets discipline spelled out. ## Operations - Health: `rnstatus` (interfaces up, I2P reachable), `lxmd --status` (stored messages, peers), `journalctl -u rnsd -f`. - Capacity: `message_storage_limit` in lxmd config; raise as disk allows. Disk alert at 80%. - Logs: loglevel 3 (notice). Reticulum logs destination hashes, not content; still, retention 7 days. - Abuse: LXMF stamps (proof-of-work) and tickets are the upstream spam controls; keep `propagation_message_max_accepted_size` modest; watch for a single peer dominating storage. - Backups: the identity files under `~/.reticulum/storage`, `~/.lxmd`, `~/.nomadnetwork` are the node's identity. Encrypted offsite backup (see 16). Losing them means a new node hash and a manifest update. ## Second node A second transport node in another region and provider (Tier 2, same bundle) gives the mesh a path if Hetzner is blocked or down. It peers with the first over TCP and announces the same NomadNet content from its own node hash. Target: Phase 2. ## Open questions - Report the Ratspeak one-hop link-request behaviour upstream (Ratspeak and RNS), so the `local_hops_delta` requirement can go away. Draft with the packet capture: [upstream/ratspeak-one-hop-link-requests.md](upstream/ratspeak-one-hop-link-requests.md). - Reticulum-over-HTTPS/WebSocket as an interface that survives protocol whitelisting — does anything upstream exist yet, or is this a bounty (see 19)? - Should the propagation node be split from the transport node once storage grows? ## Changelog - 2026-10-01 — Initial version. - 2026-10-01 — Checked against RNS 1.5.5 / LXMF 1.2.0: `rnstatus -j` confirmed; `lxmd -s` is `--service`, status is `lxmd --status`; size key is `propagation_message_max_accepted_size`; loglevel 3 (notice) to match docs 16 and 17. Bundle moved to `node/`; hash-pinned installs. - 2026-10-24 — `local_hops_delta = yes`: required for Ratspeak clients to reach the box's local programs. Chat hub (`rrcd`, docs/07) joins the box bundle after the lab test. - 2026-10-24 — Chat hub `rrcd` joins the box (unit, config, seeded rooms; pinned by commit in `node/requirements.git.txt`). Bridge reports local destinations and lxmd stats. NomadNet pages are rendered from the verified manifest; `page_refresh_interval`/`file_refresh_interval` = 5 so pages written after start are served. - 2026-10-24 — First run of `install.sh` on Ubuntu 24.04 (`make vm`, docs/21). Fixed: repo files read by the service user are staged in `/opt/irantalk/share` (a checkout in a home directory was unreadable, and NomadNet silently fell back to upstream defaults; `irantalk-nomadnet-config` now fails on a missing overrides file); empty `BRIDGE_BIND`/`BRIDGE_PORT` fall back to safe defaults; the news timer is enabled only once `NEWS_API_URL` and `NEWS_API_TOKEN` are set. - 2026-10-25 — First real box (Hetzner, arm64, Ubuntu 24.04). `make node-deploy` (`node/scripts/deploy.sh`) syncs the checkout and runs the installer from the laptop; its header is now the how-to, this section keeps the decisions. Uplinks recorded in docs/16. - 2026-10-25 — rnsd is the only shared instance, enforced by the units: `rnsd.service` is started only once it owns `@rns/default`, `lxmd`, `nomadnet` and `rrcd` are `BindsTo` it and started after it, and the installer restarts rnsd first and alone. Found on the real box: a concurrent restart let lxmd become the shared instance, and the I2P interface never came up in it. - 2026-10-24 — `POST /v1/verify`: validates an `rnid -S` signed registration challenge with RNS and returns the identity's destinations (docs/12). - 2026-10-25 — `node/AGENTS.md`: the deployment as a runbook for coding agents and helpers (docs/23). ===== docs/12-dashboard.md ===== # 12 — Dashboard `dashboard.irantalk.com` — Next.js on Coolify, Postgres on the Coolify box. ## Purpose Where the community gives input and where staff work: register an out-of-country node, request a bridge, follow the status of services, and (staff only) run the newsroom queue and build the manifest. It is the only app that writes to a database. ## Decisions - **No user accounts.** Public features are either anonymous (bridge request) or proven by a cryptographic signature from the thing being registered (node registration). The one login that exists is for staff (newsroom, manifest), with hardware-key 2FA. - **Only out-of-country infrastructure may be listed.** The registration form asks for the node's region as "outside Iran / prefer not to say"; anything that looks like an in-country location is refused with an explanation of why. Nothing collected can locate a person. - The dashboard is a separate Next.js app from the landing page: the landing only reads, the dashboard writes. - Postgres schema is small and public-shaped: if the whole registry table leaked, it would contain only what is already in the manifest. ## Features ### Node registry (public, signature-proven) 1. Operator chooses the kind (`rns_tcp`, `rns_i2p`, `lxmf_propagation`, `nomadnet_node`, `rrc_hub`), the address to list and the region class, and gets a challenge string. 2. Operator signs it with the identity behind that address: `rnid -i -S "" -b` (checked against RNS 1.5.5), and pastes the whole output, header and footer included. 3. Dashboard sends it to the Reticulum box's `POST /v1/verify` (private net). The box validates it with RNS itself: the signed message carries the signer's public key, so no announce is needed. It returns the identity hash and the destinations that identity owns (`lxmf.propagation`, `nomadnetwork.node`, `rrc.hub`); for those kinds the listed address must be one of them, which proves the operator controls the destination, not just some identity. For `rns_tcp` and `rns_i2p` the signature proves the transport identity; a reachability probe is Phase 2. 4. Verified entries enter the registry as *pending*. A maintainer approves; the next manifest build includes the node. Entries expire unless re-signed every 90 days (same flow, same identity). ### Bridge request (public, anonymous) - A form with no fields except a captcha-free rate limit (per-IP token bucket plus a proof-of-work in the browser). Returns one bridge line from the pool. - The rate limiter keys on an HMAC of the IP with a key that rotates daily and lives only in memory: no IP is stored, not even hashed in the database. - The same pool feeds the Telegram bot (docs/22) through `POST /api/bot/bridge` with the bot's secret and a pseudonymous weekly key, so a quota per person holds without knowing who they are; the dashboard stores that key, the line handed out and an expiry, nothing else. The NomadNet node is Phase 2. - Pool management (staff): add lines, set quota per line, mark burned. - Bridge lines are encrypted at rest (AES-256-GCM, key `BRIDGE_POOL_KEY` from the ops repo), so a database dump alone does not burn the pool. ### Service status (public) - Reads the bridge API and the uptime checks; shows per-service state; feeds the landing page and `status.irantalk.com` (which runs elsewhere for independence, see 16). ### Newsroom (staff) - Queue from the report inbox (08): triage, state changes, notes, export to the newsroom. ### Manifest builder (staff) - Assembles `manifest.json` from the approved registry plus a base (mirrors, fallback domains, software, Matrix, notes) that staff edit as JSON, validates it with `@irantalk/manifest`, shows a diff from the current signed version, exports it for offline signing (docs/runbooks/key-ceremony.md), accepts the `.minisig`, checks it against the embedded project key, and publishes. Version is always the last published + 1. ## Data model (sketch) ``` nodes(id, identity_hash, kind[rns_tcp|rns_i2p|lxmf_prop|nomadnet|mqtt], address, region_class, status[pending|approved|retired], signed_until, created_at) bridges(id, transport[webtunnel|obfs4|snowflake], line_enc, quota, handed_out, burned_at) reports(id, sender_hash, received_at, state, payload_ref, notes) -- see 08 manifests(version, json, signature, issued, expires, published_at) staff(id, name, webauthn_credentials) -- the only "users" ``` No table has an email, phone, IP (beyond the rate limiter's short-lived in-memory keys), or free-text identifying field. ## Development - **Built 2026-10-24** (`apps/dashboard/`): registry, bridge pool, manifest builder, staff passkeys, team and audit log, status page, bot endpoint. Not yet: the newsroom (Phase 2, docs/08) and the independent status box (16). - Next.js app router, server actions for forms, Tailwind with the tokens, lockup and theme toggle from `packages/ui`, the same as the landing page and the guides (docs/13). The dashboard is a server app, so the static-export limits of the landing page do not apply to it. - **Plain SQL, no ORM**: migrations are embedded SQL, applied once in order; queries go through one small interface over the `postgres` driver (porsager, no dependencies). **One Postgres everywhere**: development, tests and production all run against a real Postgres, so what runs locally is what runs on Coolify. Development uses a database of the developer's choosing (`DATABASE_URL` in `.env.development.local`), the tests the same database in a schema of their own (`irantalk_test`, created and dropped by the run), production the Postgres on the private network. Nothing is defaulted or generated at runtime: every variable is set the same way in every mode (`apps/dashboard/.env.example`). - **Staff log in with passkeys only** (WebAuthn, `@simplewebauthn/server`). We take that library's dependency tree rather than hand-roll authentication crypto. The first staff member is enrolled with a one-time invite made by a CLI on the server; further staff are invited from the dashboard. Sessions are rows with a 12-hour expiry, cookie `HttpOnly`, `Secure`, `SameSite=Strict`. - API routes under `/api/*` used by the landing page, the Telegram bot and the status page; all read endpoints are public and cached; write endpoints require the staff session or the private-net token. - `lab` database for development; migrations in git. ## Deployment - Coolify app built by Railpack from the repository root (base directory `/`, so the workspace packages resolve): install `pnpm install --frozen-lockfile`, build `pnpm --filter @irantalk/dashboard build`, start `pnpm --filter @irantalk/dashboard start`, port 3002. `pnpm --filter @irantalk/dashboard db:migrate` creates the tables, and the app also migrates on its first request. Postgres over the private network, in a database and role owned by IranTalk; daily encrypted dump offsite. - `railpack.json` at the repository root pins Railpack's provider to `node`. The root also holds the uv workspace, and Railpack's auto-detection takes Python over Node, which gives a build image with `uv` but no `pnpm`. The same file serves the three Next.js apps and the bot; nothing Coolify builds needs Python. - Env: `DATABASE_URL`, `BRIDGE_URL` and `BRIDGE_TOKEN` (the Reticulum box: status and `/v1/verify`), `BOT_SECRET`, `BRIDGE_POOL_KEY`, `SESSION_SECRET`, `WEBAUTHN_RP_ID` and `WEBAUTHN_ORIGIN`. All set in Coolify from the ops repo; `apps/dashboard/.env.example` documents each one, and no `.env` file is ever deployed. ## Operations - Audit log of staff actions (who approved which node, who published which manifest). - Weekly review of pending nodes; monthly review of burned bridges. ## Changelog - 2026-10-01 — Initial version. - 2026-10-24 — Design review before building: registration by `rnid -S` signed challenge verified on the box (no announce needed), destination ownership check, plain SQL with Postgres/PGlite, passkey-only staff with invites, encrypted bridge pool, daily-rotating rate-limit key, bot quota by weekly pseudonymous key, manifest builder from registry plus a staff-edited base. - 2026-10-24 — Built and tested end to end against the lab: four lab addresses registered with real `rnid` signatures, approved, built into a manifest, signed with the lab key and published; pool hand-outs with proof-of-work and through the bot. - 2026-10-25 — The look comes from `packages/ui`: palette and dark set, square corners, a header with the lockup (Farsi first on the public pages, English on the staff pages), the theme toggle and a favicon. - 2026-10-25 — Deployment: Railpack from the repository root instead of a Dockerfile; Postgres over the private network rather than necessarily on the same box; `.env.example` documents the variables. - 2026-10-25 — `railpack.json` at the repository root pins the Railpack provider to `node`: auto-detection chose Python from the uv workspace and the first Coolify build failed with `pnpm: not found`. - 2026-10-25 — PGlite removed: development, tests and production run on the one Postgres in `DATABASE_URL` (the tests in their own schema), and the dashboard has no development-only defaults; every variable comes from `.env.development.local` or Coolify. `db:migrate` script. ===== docs/16-operations.md ===== # 16 — Operations ## Providers and placement | Tier | Provider rule | Why | |---|---|---| | 2 (web, dashboard, docs, RNS) | Hetzner (Coolify box + dedicated Reticulum box) | existing infrastructure | | 2 (Matrix, fediverse) | provider B and C | one takedown ≠ everything | | 1 (bridges, mirrors, MQTT) | ≥2 providers, none Hetzner, many small VPS | Hetzner ranges are often filtered inside Iran; bridges get burned individually | | status page | a provider used by nothing else | must be up when we are down | Everything is provisioned by Ansible from a private ops repo; a fresh Tier 1 VPS is productive within an hour. Uplinks of the Reticulum box (docs/04 step 4), both from directory.rns.recipes, chosen 2026-10-25: `rmap.world:4242` (RMAP, the project behind rmap.world) and `rns.arborisis.com:4242` (Belgium). Swap one if it stays down for a week. ## Monitoring - **Uptime**: self-hosted Uptime Kuma on the status box checks every public endpoint (HTTPS, TCP 4242, MQTT, Matrix federation, onion via a Tor-enabled probe). - **Mesh**: the bridge API exposes interface state and propagation stats; the status box polls it and alerts if RNS TCP or I2P is down > 5 min, or the news page is > 60 min old. - **Blocking detection**: OONI probes from inside (volunteers running OONI Probe with our test list) plus a weekly check of OONI Explorer data for our domains and mirrors. Alert rule: a mirror unreachable from inside for 24 h while the origin is fine → presumed blocked. - **Logs**: minimal, 7-day retention, no client IPs on Tier 1/2 user-facing services beyond what the rate limiter holds in memory. Reticulum logs at notice level. - **Alerts** go to a Matrix room and one phone; no email. ## Status page `status.irantalk.com` on the independent box: per-service state, last manifest version and its issue time, last news page time, OONI reachability per mirror. Also published as a NomadNet page and a Telegram bot command. ## Backups - What: Reticulum/LXMF/NomadNet identities and configs; Postgres (registry, reports queue); Matrix DB + media; fediverse DB; F-Droid repo signing material (offline, separately). - How: `restic` to an object store at a provider not otherwise used; encrypted; daily; monthly restore test on `lab`. - Private keys are **not** in the backup set; they have their own custody (11, 17). ## Infrastructure as code - Ops repo: Ansible roles per component, inventory per provider, a `burn-list.md`, and the `lab`/`prod` environment definitions. - Secrets in `sops`-encrypted files keyed to maintainer hardware keys. - Every box can be rebuilt from the repo; the test is doing it quarterly for one box. ## Continuity (bus factor) - Two maintainers can each deploy everything; three hold shares of the signing keys. - The ops repo contains a `SUCCESSION.md`: who can do what, in which order, if the founder is unreachable for 30 days. - Fallback domains are registered under the same rotation rule (03) and tested annually. ## Capacity and cost (rough, review quarterly) | Item | Monthly | |---|---| | Reticulum box (dedicated, Hetzner) | existing | | Coolify box share | existing | | Matrix VPS, fediverse VPS | 2 × small | | Tier 1: 6–10 small VPS across two providers | 6–10 × small | | MQTT VPS, status VPS | 2 × small | | Object storage for backups | small | | Domains (fallbacks) | portfolio | ## Changelog - 2026-10-01 — Initial version. - 2026-10-25 — The Reticulum box's two uplinks recorded: `rmap.world:4242` and `rns.arborisis.com:4242`. ===== docs/17-security-and-opsec.md ===== # 17 — Security and opsec ## Operators - **Separate identities.** Project work uses a project email, project handles, project payment methods where possible. WHOIS privacy on every domain. No personal names in registries, commit metadata or hosting accounts beyond what the provider legally requires. - **Hardware keys** for every provider login, Git host, Cloudflare, Coolify, Matrix admin. No SMS 2FA anywhere. - **Signing keys** (11) live on hardware keys or an offline machine; never on a server. - **Shared accounts** are forbidden; each maintainer has their own, with least privilege. - **Devices**: full-disk encryption, auto-lock, no project credentials on phones that travel to high-risk jurisdictions. - **Phishing** is the realistic attack: the project will be targeted with fake "Hetzner/Cloudflare/GitHub" mails. Rule: never act on a link in an email; log in from a bookmark. ## Servers - SSH keys only, non-standard port optional, fail2ban, unattended security upgrades, ufw default deny, services bound to the minimum interface (bridge API on the private IP only). - Tier 2 web behind Cloudflare for DDoS; Tier 1 hosts are expendable and rebuildable. - Secrets via `sops`; `/etc/default/*` files are mode 640, root:service. - Each box has one job; a compromise of the Matrix box must not reach the Reticulum box. ## Logging policy (published) | Service | What we keep | How long | |---|---|---| | Landing/docs/dashboard | aggregate counts; rate-limiter keys in memory | 24 h / none persisted | | Bridge request | nothing identifying | — | | Node registry | identity hash, addresses, region class, approval audit | until retired + 90 d | | Report inbox | sender hash, timestamp, payload | 7 d / 30 d after terminal state | | Reticulum/LXMF | notice-level logs | 7 d | | Chat hub (RRC) | warnings only: no joins, nicknames or messages; room registry (names, topics, operator hashes) | until the room is removed | | Matrix | what the homeserver needs to function; no IP logs beyond 24 h | per retention config | | MQTT | no client IP logging | — | This table is published on the docs site. If a court order arrives, this is what exists. ## Users The user safety guide (docs site, Farsi) covers, in this order: 1. The device is evidence; decide who knows you have it. 2. Identity hygiene: no real name in display names or announces; a new identity per role if needed; how to back up and how to wipe. 3. Radio hygiene: transmit less, announce rarely, antenna placement vs exposure. 4. Verification: the fingerprint, how to check the manifest, why a wrong fingerprint means stop. 5. What IranTalk knows about you (the logging table above) and what it cannot protect against (the threat model, in plain words). 6. Reporting: what to send, what to delete afterwards. 7. Asking an AI: a cloud AI sees what you ask it, like Telegram sees what you ask the bot; a chat with a local model is still a file on your device; the privacy ladder of docs/23 says what never to paste. ## Incident response - Classes: key compromise, server compromise, impostor site/binary, operator targeted, domain seized. Each has a runbook (`runbooks/`). - First action in every class: post a signed notice in the Matrix announcements room and issue a new manifest if any address or key changed. - Post-incident: a public write-up within 14 days unless it endangers someone. ## Changelog - 2026-10-01 — Initial version. - 2026-10-24 — Logging policy row for the mesh chat hub (docs/07). - 2026-10-25 — The user safety guide gets an item on asking an AI (docs/23).