diff --git a/docs/lora_ota_automation.md b/docs/lora_ota_automation.md
index 647a27be3..9cd3abe4b 100644
--- a/docs/lora_ota_automation.md
+++ b/docs/lora_ota_automation.md
@@ -4,6 +4,9 @@
# Scripted LoRa OTA from start to finish
+For a manual example using a Pi-connected repeater and the app's admin console,
+see [Station G2 to Ikoka Stick](ota_station_g2_to_ikoka_stick.md).
+
For **[1.18.0.1 USA Cascade](releases/1.18.0.1.md)**, download the exact board,
role and storage profile from the [firmware picker](firmware_picker.md).
For a new nRF52 installation, use the matching
diff --git a/docs/ota_easy.md b/docs/ota_easy.md
index fe5e120b8..0251224ae 100644
--- a/docs/ota_easy.md
+++ b/docs/ota_easy.md
@@ -22,6 +22,10 @@ LoRa. Choose the package type for the **destination** node:
For an end-to-end controller that accepts a release ZIP or ready mOTA, see
[Scripted LoRa OTA from start to finish](lora_ota_automation.md).
+For a complete manual example with one Pi-connected seeder, see
+[Station G2 to Ikoka Stick](ota_station_g2_to_ikoka_stick.md), including both
+bootloader and firmware installation.
+
| Destination | Update type | Files needed to build the `.mota` | Installer |
| --- | --- | --- | --- |
| ESP32 | Full firmware | New non-merged application `.bin` | ESP32 A/B firmware slots |
diff --git a/docs/ota_station_g2_to_ikoka_stick.md b/docs/ota_station_g2_to_ikoka_stick.md
new file mode 100644
index 000000000..04df9d3be
--- /dev/null
+++ b/docs/ota_station_g2_to_ikoka_stick.md
@@ -0,0 +1,459 @@
+
+
View this page on MeshCore Docs.
+
+
+# LoRa OTA example: Station G2 on a Pi to an Ikoka Stick
+
+Update the Ikoka Stick's bootloader to **OTAFIX 2.4.12**, then install
+**1.18.0.1 Full Repeater** firmware. The Station G2 stays connected to the Pi
+by USB and serves the files. Use the app's authenticated Repeater Admin console for the Ikoka
+Stick, through your existing Companion.
+
+| Board | Job |
+| --- | --- |
+| Raspberry Pi | Runs the file server |
+| Station G2 Repeater | USB-connected LoRa seeder |
+| Ikoka Stick 33 dBm Repeater | Receives and installs both updates |
+| Companion | Keeps the app connected to the Ikoka Stick's admin console |
+
+This example requires a **direct LoRa link** between the Station G2 and Ikoka
+Stick. It keeps their existing main-channel settings during a four-hour
+TempRadio window. The Companion stays on that same channel. Only the Station
+G2 needs a USB connection to the Pi. For a route through intermediate
+repeaters, every relay also needs an overlapping TempRadio window; see the
+[scripted update guide](lora_ota_automation.md).
+
+## 1. Check the Ikoka Stick
+
+Run these in **Ikoka Stick — Repeater Admin** and save the replies:
+
+```text
+ver
+get bootloader.ver
+ota self
+ota qspi
+ota bootloader status
+ota config
+ota key list
+get radio
+get radio.rxps.config
+```
+
+The example starts with these reported values:
+
+| Check | Required value for this example |
+| --- | --- |
+| Application hardware / target | `ikoka_stick_nrf_33dbm` / `E5331F02` |
+| Installed bootloader | OTAFIX 2.4.11 |
+| Bootloader identity | Board and target `28860044`, device `XIAO_DFU` |
+| Bootloader support | `abi=3 caps=0E`, `QSPI apply OK`, `codecs=0x5` |
+| Storage | `QSPI store:2048K`, working `ota qspi` probe |
+
+These values support a remote bootloader upgrade. A stock bootloader or
+missing QSPI apply support needs the matching local installation first.
+The Stick uses its onboard flash; no additional storage is needed.
+
+If `get radio.rxps.config` is unavailable, record `get radio.rxps` instead.
+Keep each board's original RXPS and `ota config` replies for cleanup.
+
+## 2. Install the Pi tools
+
+On **Raspberry Pi — terminal**, install the host dependencies:
+
+```bash
+sudo apt update
+sudo apt install -y build-essential pkg-config libudev-dev libssl-dev curl unzip pipx python3-venv
+pipx ensurepath
+pipx install 'meshcore-cli>=1.6.0'
+```
+
+Install stable [Rust](https://rustup.rs/) if it is not already installed:
+
+```bash
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/meshcore-rustup.sh
+sh /tmp/meshcore-rustup.sh -y --profile minimal
+```
+
+Open a new terminal so the Pi finds `meshcli` and Cargo. Check that both Rust
+and Cargo are **1.86 or newer**, then install the supported motatool revision:
+
+```bash
+rustc --version
+cargo --version
+cargo install --git https://github.com/mikecarper/motatool.git \
+ --rev 8c38369e7d35ad50cf74261869676d52dd24adf7 --locked --bin motatool
+meshcli -v
+motatool --version
+mkdir -p ~/ikoka-stick-ota/downloads ~/ikoka-stick-ota/inputs ~/ikoka-stick-ota/serve
+cd ~/ikoka-stick-ota
+```
+
+## 3. Prepare the two update files
+
+On **Raspberry Pi — terminal**, download the signed bootloader bundle:
+
+```bash
+BOOT_URL=https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/download/v0.11.0-OTAFIX2.4.12
+curl -fL "$BOOT_URL/OTAFIX-2.4.12-bootloader-mota.zip" -o downloads/bootloader-2.4.12.zip
+unzip -q downloads/bootloader-2.4.12.zip -d inputs/bootloader-2.4.12
+cp inputs/bootloader-2.4.12/mota/update-xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.12.mota serve/
+BOOT_MOTA=serve/update-xiao_nrf52840_ble_bootloader-v0.11.0-OTAFIX2.4.12.mota
+OTAFIX_PUBLIC_KEY=272564CC588D3D122285A15E6E2566D2ABE7177BB7EA1D1E41B23B29F0F85D2D
+BOOT_KEY_FILE=inputs/bootloader-2.4.12/OTAFIX_MOTA_SIGNING_PUBLIC_KEY.txt
+test "$(tr -d '[:space:]' < "$BOOT_KEY_FILE")" = "$OTAFIX_PUBLIC_KEY"
+motatool verify "$BOOT_MOTA" --pub "$BOOT_KEY_FILE"
+motatool inspect "$BOOT_MOTA"
+```
+
+The bootloader package must be signed, format 3, for **XIAO nRF52840 BLE** /
+`XIAO_DFU`, target `28860044`, version 2.4.12. The key comparison and package
+verification must both pass before continuing.
+
+Download the exact ordinary **Ikoka Stick 33 dBm Full Repeater** application
+DFU ZIP on **Raspberry Pi — terminal**:
+
+```bash
+APP_URL=https://github.com/mikecarper/MeshCore/releases/download/lora-ota-2-v1.18.0.1-halo-keymind-cascade-dev-03214a16
+curl -fL "$APP_URL/ikoka_stick_nrf_33dbm_repeater-full-ota-v1.18.0.1-halo-keymind-cascade-dev-03214a16.zip" \
+ -o downloads/ikoka-stick-33dbm-repeater.zip
+```
+
+For a later release, use the [firmware picker](firmware_picker.md) to select
+the same board, Repeater role, Full profile, and application DFU ZIP.
+
+On **Raspberry Pi — terminal**, extract the application binary named by the
+DFU manifest and check its firmware identity trailer:
+
+```bash
+python3 - <<'PY'
+import hashlib, json, struct, zipfile
+from pathlib import Path
+with zipfile.ZipFile('downloads/ikoka-stick-33dbm-repeater.zip') as archive:
+ app = json.loads(archive.read('manifest.json'))['manifest']['application']
+ image = archive.read(app['bin_file'])
+trailer = image[-56:]
+assert len(trailer) == 56 and trailer[:4] == b'EndF', 'Missing EndF'
+assert struct.unpack_from('
+Optional: differential firmware upgrade (smaller download)
+
+A differential update sends a patch from the Ikoka Stick's exact running
+application to the new application. It can reduce LoRa transfer time. The
+bootloader update in step 7 still uses the signed full bootloader package.
+
+Prepare the new binary and full package above first. Use this option only
+when you also have the **original application DFU ZIP for the firmware
+currently running on the Ikoka Stick**. A matching version label or a rebuild
+of the same commit is not enough. If that original file is unavailable, use
+the full update above.
+
+On **Ikoka Stick — Repeater Admin**, get its current identity:
+
+```text
+ver
+ota self
+```
+
+Copy the original application DFU ZIP onto **Raspberry Pi** as
+`downloads/ikoka-stick-33dbm-repeater-running.zip`. In the Pi terminal, paste
+the 16-character `base_hash` from that fresh `ota self` reply when prompted:
+
+```bash
+read -r -p 'Current Ikoka Stick base_hash (16 hex characters): ' IKOKA_BASE_HASH
+export IKOKA_BASE_HASH
+python3 - <<'PY'
+import hashlib, json, os, re, struct, zipfile
+from pathlib import Path
+expected = os.environ['IKOKA_BASE_HASH'].strip().upper()
+assert re.fullmatch(r'[0-9A-F]{16}', expected), 'Invalid base_hash'
+with zipfile.ZipFile('downloads/ikoka-stick-33dbm-repeater-running.zip') as archive:
+ app = json.loads(archive.read('manifest.json'))['manifest']['application']
+ image = archive.read(app['bin_file'])
+trailer = image[-56:]
+assert len(trailer) == 56 and trailer[:4] == b'EndF', 'Missing EndF'
+assert struct.unpack_from(' flash`,
+then `ota install` when ready. The target's `base_hash` must still match
+immediately before that pull. Updating only the bootloader in step 7 leaves
+the running application unchanged, so it does not invalidate the delta base.
+After installation, check the same new firmware version and expected
+post-install body hash printed earlier in step 3.
+
+
+
+## 4. Identify the Station G2's USB port
+
+On **Raspberry Pi — terminal**:
+
+```bash
+ls -l /dev/serial/by-id/
+```
+
+Select the Station G2's actual entry. Set `G2_PORT` to that complete path;
+`/dev/ttyUSB0` below is only an example:
+
+```bash
+G2_PORT=/dev/ttyUSB0
+meshcli -r -s "$G2_PORT" -b 115200 "board"
+meshcli -r -s "$G2_PORT" -b 115200 "ver"
+meshcli -r -s "$G2_PORT" -b 115200 "ota status"
+meshcli -r -s "$G2_PORT" -b 115200 "get radio"
+meshcli -r -s "$G2_PORT" -b 115200 "get radio.rxps.config"
+meshcli -r -s "$G2_PORT" -b 115200 "ota config"
+```
+
+Confirm that this is the Station G2 and that its firmware includes LoRa OTA.
+Save its radio, RXPS, and OTA settings. If access is denied, add your Pi login
+to `dialout` with `sudo usermod -aG dialout "$(id -un)"`, then log out and in.
+Close other programs using that USB port.
+
+## 5. Open the OTA windows and trust the bootloader signer
+
+The commands below assume the Station G2, Ikoka Stick, and Companion already
+use **910.525 MHz, BW 62.5 kHz, SF7, CR5**. If yours differ, use their current
+shared tuple in every TempRadio command below. Preserve an explicit preamble
+if one is configured by adding it after the duration. This keeps admin access
+on the existing channel; TempRadio is still required to enable LoRa OTA.
+
+On **Ikoka Stick — Repeater Admin**:
+
+```text
+ota key add 272564CC588D3D122285A15E6E2566D2ABE7177BB7EA1D1E41B23B29F0F85D2D
+ota key list
+ota config autofetch off
+ota config autoinstall off
+ota config hops 0
+set radio.rxps off
+get radio.rxps
+tempradio 910.525,62.5,7,5,240
+get tempradio
+```
+
+On **Raspberry Pi — Station G2 USB CLI**:
+
+```bash
+meshcli -r -s "$G2_PORT" -b 115200 "ota config autofetch off"
+meshcli -r -s "$G2_PORT" -b 115200 "ota config autoinstall off"
+meshcli -r -s "$G2_PORT" -b 115200 "ota config hops 0"
+meshcli -r -s "$G2_PORT" -b 115200 "set radio.rxps off"
+meshcli -r -s "$G2_PORT" -b 115200 "get radio.rxps"
+meshcli -r -s "$G2_PORT" -b 115200 "tempradio 910.525,62.5,7,5,240"
+meshcli -r -s "$G2_PORT" -b 115200 "get tempradio"
+```
+
+Both receivers must report RXPS off and an active temporary tuple. Wait if
+`get tempradio` says `pending`; do not start serving while it says `off`.
+The `hops 0` setting is for this direct link. These configuration changes are
+saved; the TempRadio window is temporary.
+
+## 6. Start the Station G2 seeder
+
+On **Raspberry Pi — terminal**, leave this running in the foreground:
+
+```bash
+motatool serve --dir ./serve --serial "$G2_PORT" --baud 115200 -v
+```
+
+It attaches the folder and the Station G2 advertises the two packages. Wait
+for a successful folder attachment/catalog count. While motatool owns USB,
+send later commands through **Ikoka Stick — Repeater Admin**. Keep the Pi and
+both repeaters powered through each transfer and installation.
+
+## 7. Update the Ikoka Stick bootloader
+
+On **Ikoka Stick — Repeater Admin**:
+
+```text
+ota ls
+```
+
+Discovery is asynchronous. Wait a few seconds and repeat it. Select the
+**bootloader** row for XIAO target `28860044`, version 2.4.12. Copy its
+eight-character manifest ID into `BOOT_MID8` below; placeholders must be
+replaced with the actual values, without angle brackets:
+
+```text
+ota pull flash
+ota status
+```
+
+Check progress until it reports **ready to install**, then run:
+
+```text
+ota bootloader status
+```
+
+Confirm `staged:ready`. Copy that reply's exact `mid` and 16-character `hash`:
+
+```text
+ota bootloader install
+```
+
+Wait for the reboot, reconnect in the app, and check:
+
+```text
+get bootloader.ver
+ota bootloader status
+ota status
+ver
+```
+
+Expect **OTAFIX 2.4.12**, the same `XIAO_DFU` identity, ABI 3 / capabilities
+`0E`, and `blup:C8`. The application version should still be the old version.
+
+## 8. Update the Ikoka Stick firmware
+
+The bootloader reboot cleared the Ikoka Stick's temporary window. On
+**Ikoka Stick — Repeater Admin**, reopen it before downloading firmware:
+
+```text
+get radio.rxps
+tempradio 910.525,62.5,7,5,240
+get tempradio
+```
+
+Wait until `get tempradio` shows the active tuple, repeating the query if it
+says `pending`. Then run:
+
+```text
+ota ls
+```
+
+Keep the Station G2 seeder running. Select the **application, full** row with
+target `E5331F02` and the version inspected in step 3. Use its stable manifest
+ID, not its list position or the bootloader ID:
+
+```text
+ota pull flash
+ota status
+```
+
+Repeat `ota status` until **ready to install**, then run:
+
+```text
+ota install
+```
+
+After the application reboots, reconnect and check:
+
+```text
+ver
+get bootloader.ver
+ota self
+ota status
+```
+
+Confirm the selected firmware version, bootloader **2.4.12**, working QSPI
+apply support, and the `base_hash` recorded from the application package.
+The Ikoka Stick has returned to its saved main radio settings.
+
+## 9. Stop serving and restore settings
+
+On **Raspberry Pi — terminal**, press **Ctrl+C** and wait for motatool to
+detach the folder and return to the shell. Then run:
+
+```bash
+meshcli -r -s "$G2_PORT" -b 115200 "ota folder"
+meshcli -r -s "$G2_PORT" -b 115200 "normalradio"
+meshcli -r -s "$G2_PORT" -b 115200 "get tempradio"
+```
+
+Confirm the host folder detached. Restore each board's original RXPS, hop
+limit, autofetch, and autoinstall
+settings from steps 1 and 4. For the example starting values, on **Ikoka
+Stick — Repeater Admin**:
+
+```text
+set radio.rxps level 8 preamble 16
+ota config hops 3
+ota config autofetch off
+ota config autoinstall off
+get radio.rxps.config
+ota config
+get tempradio
+```
+
+Use the Station G2's own recorded values through `meshcli -r` as in step 5.
+An original RXPS value of `off` stays off; for an automatic preamble (`0`),
+restore with `set radio.rxps level N`; fixed manual timings use
+`set radio.rxps RX_US SLEEP_US`. Retain the official signing key for future
+bootloader updates. Both boards should now report `get tempradio` as `off`.
+
+## If a transfer stops
+
+- **No update rows:** check the seeder attachment, matching radio settings,
+ direct reachability, and active TempRadio windows on both boards.
+- **Four-hour window expired:** stop motatool before reopening the G2 USB
+ CLI, renew both windows, restart the same seeder, and pull the same MID.
+ Keep the same package; current firmware can resume committed blocks.
+- **Signature/key error:** verify the signed package against its trusted
+ publisher key. Do not substitute an arbitrary key from an update row.
+- **Download failed or wrong selection:** inspect `ota status`; use
+ `ota cancel` to discard that download before selecting another package.
+- **No completed install:** keep the running firmware until the destination
+ reports a verified, ready package. See the
+ [bootloader guide](ota_nrf52_bootloader_update.md) or
+ [firmware guide](ota_user_guide.md) for the reported error.
diff --git a/mkdocs.yml b/mkdocs.yml
index 2c3b9014c..bd9545c08 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -65,6 +65,7 @@ nav:
- ota_user_guide.md
- ota_easy.md
- lora_ota_automation.md
+ - ota_station_g2_to_ikoka_stick.md
- ota_nrf52_bootloader_update.md
- ota_nrf52_qspi.md
- ota_meshtower_v2_sdcard.md
@@ -111,6 +112,9 @@ nav:
- Older releases: old-releases/index.md
- Test Results: test-results/index.md
+markdown_extensions:
+ - md_in_html
+
theme:
name: material
logo: _assets/meshcore.svg