mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-08-10 19:03:22 +02:00
refactor:rename-project-to-openhop
This commit is contained in:
+5
-5
@@ -3,17 +3,17 @@
|
|||||||
|
|
||||||
# Published image to run. Use a different repository or tag if you are testing
|
# Published image to run. Use a different repository or tag if you are testing
|
||||||
# a fork or a specific release.
|
# a fork or a specific release.
|
||||||
PYMC_REPEATER_IMAGE=pymcdev/pymc-repeater:main
|
PYMC_REPEATER_IMAGE=pymcdev/openhop-repeater:main
|
||||||
|
|
||||||
# Storage defaults to Docker named volumes. This is the safest option for
|
# Storage defaults to Docker named volumes. This is the safest option for
|
||||||
# Portainer and fresh installs because Docker preserves the image ownership.
|
# Portainer and fresh installs because Docker preserves the image ownership.
|
||||||
# To use host bind mounts instead, create the folders first and make them
|
# To use host bind mounts instead, create the folders first and make them
|
||||||
# writable by UID/GID 15888:
|
# writable by UID/GID 15888:
|
||||||
# sudo mkdir -p /opt/pymc-repeater/config /opt/pymc-repeater/data
|
# sudo mkdir -p /opt/openhop-repeater/config /opt/openhop-repeater/data
|
||||||
# sudo chown -R 15888:15888 /opt/pymc-repeater/config /opt/pymc-repeater/data
|
# sudo chown -R 15888:15888 /opt/openhop-repeater/config /opt/openhop-repeater/data
|
||||||
# Then uncomment and adjust these paths:
|
# Then uncomment and adjust these paths:
|
||||||
# PYMC_CONFIG_VOLUME=/opt/pymc-repeater/config
|
# PYMC_CONFIG_VOLUME=/opt/openhop-repeater/config
|
||||||
# PYMC_DATA_VOLUME=/opt/pymc-repeater/data
|
# PYMC_DATA_VOLUME=/opt/openhop-repeater/data
|
||||||
|
|
||||||
# Serial/SPI/GPIO access uses the host's numeric group IDs. Check your host with:
|
# Serial/SPI/GPIO access uses the host's numeric group IDs. Check your host with:
|
||||||
# getent group dialout
|
# getent group dialout
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ on:
|
|||||||
image_repository:
|
image_repository:
|
||||||
description: "Docker image repository to publish to"
|
description: "Docker image repository to publish to"
|
||||||
required: false
|
required: false
|
||||||
default: "pymcdev/pymc-repeater"
|
default: "pymcdev/openhop-repeater"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
if: |
|
if: |
|
||||||
github.event_name == 'workflow_dispatch' ||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
github.repository == 'pyMC-dev/pyMC_Repeater' ||
|
github.repository == 'pyMC-dev/openhop-repeater' ||
|
||||||
github.repository == 'yellowcooln/pyMC_Repeater'
|
github.repository == 'yellowcooln/openhop-repeater'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -58,10 +58,10 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ inputs.image_repository }}" ]; then
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ inputs.image_repository }}" ]; then
|
||||||
image_repository="${{ inputs.image_repository }}"
|
image_repository="${{ inputs.image_repository }}"
|
||||||
elif [ "${{ github.repository }}" = "yellowcooln/pyMC_Repeater" ]; then
|
elif [ "${{ github.repository }}" = "yellowcooln/openhop-repeater" ]; then
|
||||||
image_repository="yellowcooln/pymc-repeater"
|
image_repository="yellowcooln/openhop-repeater"
|
||||||
else
|
else
|
||||||
image_repository="pymcdev/pymc-repeater"
|
image_repository="pymcdev/openhop-repeater"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Using image repository: ${image_repository}"
|
echo "Using image repository: ${image_repository}"
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
- name: Notify Home Assistant add-on repository
|
- name: Notify Home Assistant add-on repository
|
||||||
if: github.repository == 'pyMC-dev/pyMC_Repeater'
|
if: github.repository == 'pyMC-dev/openhop-repeater'
|
||||||
env:
|
env:
|
||||||
DISPATCH_TOKEN: ${{ secrets.HA_ADDON_REPO_DISPATCH_TOKEN }}
|
DISPATCH_TOKEN: ${{ secrets.HA_ADDON_REPO_DISPATCH_TOKEN }}
|
||||||
CHANNEL: ${{ github.ref_name }}
|
CHANNEL: ${{ github.ref_name }}
|
||||||
|
|||||||
+3
-3
@@ -28,9 +28,9 @@ share/python-wheels/
|
|||||||
DEBIAN/
|
DEBIAN/
|
||||||
debian/files
|
debian/files
|
||||||
debian/.debhelper/
|
debian/.debhelper/
|
||||||
debian/pymc-repeater/
|
debian/openhop-repeater/
|
||||||
debian/pymc-repeater.debhelper.log
|
debian/openhop-repeater.debhelper.log
|
||||||
debian/pymc-repeater.substvars
|
debian/openhop-repeater.substvars
|
||||||
|
|
||||||
# Virtual environments
|
# Virtual environments
|
||||||
.venv/
|
.venv/
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# pyMC Repeater
|
# openHop Repeater
|
||||||
|
|
||||||
Lightweight Python MeshCore repeater daemon built on `pymc_core`.
|
Lightweight Python MeshCore repeater daemon built on `openhop_core`.
|
||||||
|
|
||||||
pyMC Repeater is designed to run continuously on low-power Linux hardware such
|
openHop Repeater is designed to run continuously on low-power Linux hardware such
|
||||||
as Raspberry Pi-class devices, Proxmox LXC containers, and network-attached
|
as Raspberry Pi-class devices, Proxmox LXC containers, and network-attached
|
||||||
radio modems. It forwards LoRa packets, exposes a web dashboard, and provides
|
radio modems. It forwards LoRa packets, exposes a web dashboard, and provides
|
||||||
configuration tools for radio setup, policy management, monitoring, and
|
configuration tools for radio setup, policy management, monitoring, and
|
||||||
@@ -29,7 +29,7 @@ integrations.
|
|||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The repeater daemon runs as a background service and forwards LoRa packets using
|
The repeater daemon runs as a background service and forwards LoRa packets using
|
||||||
the `pymc_core` dispatcher and routing stack. The project favors a simple,
|
the `openhop_core` dispatcher and routing stack. The project favors a simple,
|
||||||
hackable architecture:
|
hackable architecture:
|
||||||
|
|
||||||
- CherryPy provides a lightweight HTTP server for the web UI and API.
|
- CherryPy provides a lightweight HTTP server for the web UI and API.
|
||||||
@@ -59,7 +59,7 @@ Historical statistics and performance metrics.
|
|||||||
|
|
||||||
## Supported Hardware
|
## Supported Hardware
|
||||||
|
|
||||||
pyMC Repeater supports these radio backends:
|
openHop Repeater supports these radio backends:
|
||||||
|
|
||||||
- **SX1262 over Linux SPI**: set `radio_type: sx1262`
|
- **SX1262 over Linux SPI**: set `radio_type: sx1262`
|
||||||
- **SX1262 over CH341 USB-to-SPI**: set `radio_type: sx1262_ch341`
|
- **SX1262 over CH341 USB-to-SPI**: set `radio_type: sx1262_ch341`
|
||||||
@@ -111,8 +111,8 @@ sudo apt install git -y
|
|||||||
### Clone The Repository
|
### Clone The Repository
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/pyMC-dev/pyMC_Repeater.git
|
git clone https://github.com/pyMC-dev/openhop-repeater.git
|
||||||
cd pyMC_Repeater
|
cd openhop-repeater
|
||||||
```
|
```
|
||||||
|
|
||||||
### Quick Install
|
### Quick Install
|
||||||
@@ -124,17 +124,17 @@ sudo bash ./manage.sh install
|
|||||||
The installer will:
|
The installer will:
|
||||||
|
|
||||||
- Create a dedicated `repeater` service user with hardware access
|
- Create a dedicated `repeater` service user with hardware access
|
||||||
- Install application files to `/opt/pymc_repeater`
|
- Install application files to `/opt/openhop_repeater`
|
||||||
- Create the configuration directory at `/etc/pymc_repeater`
|
- Create the configuration directory at `/etc/openhop_repeater`
|
||||||
- Create the log directory at `/var/log/pymc_repeater`
|
- Create the log directory at `/var/log/openhop_repeater`
|
||||||
- Launch the interactive radio and hardware setup wizard
|
- Launch the interactive radio and hardware setup wizard
|
||||||
- Install and enable the `pymc-repeater` systemd service
|
- Install and enable the `openhop-repeater` systemd service
|
||||||
|
|
||||||
After installation:
|
After installation:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# View live logs
|
# View live logs
|
||||||
sudo journalctl -u pymc-repeater -f
|
sudo journalctl -u openhop-repeater -f
|
||||||
```
|
```
|
||||||
|
|
||||||
Open the web dashboard at:
|
Open the web dashboard at:
|
||||||
@@ -160,7 +160,7 @@ pip install -e ".[dev]"
|
|||||||
The main configuration file is created during installation:
|
The main configuration file is created during installation:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/etc/pymc_repeater/config.yaml
|
/etc/openhop_repeater/config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### Setup Wizard
|
### Setup Wizard
|
||||||
@@ -207,19 +207,19 @@ TX power defaults to 14 dBm and can be changed later.
|
|||||||
To reconfigure radio and hardware settings after installation:
|
To reconfigure radio and hardware settings after installation:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo bash setup-radio-config.sh /etc/pymc_repeater
|
sudo bash setup-radio-config.sh /etc/openhop_repeater
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also launch the management menu:
|
You can also launch the management menu:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo ./manage.sh
|
sudo ./manage.sh
|
||||||
sudo systemctl restart pymc-repeater
|
sudo systemctl restart openhop-repeater
|
||||||
```
|
```
|
||||||
|
|
||||||
### Optional pyMC_Glass Integration
|
### Optional pyMC_Glass Integration
|
||||||
|
|
||||||
pyMC Repeater supports an optional `glass` configuration section for
|
openHop Repeater supports an optional `glass` configuration section for
|
||||||
pyMC_Glass control-plane integration. When enabled, the repeater sends periodic
|
pyMC_Glass control-plane integration. When enabled, the repeater sends periodic
|
||||||
`/inform` payloads to pyMC_Glass, receives queued commands, and reports command
|
`/inform` payloads to pyMC_Glass, receives queued commands, and reports command
|
||||||
results on the next inform cycle.
|
results on the next inform cycle.
|
||||||
@@ -258,7 +258,7 @@ The web interface can upgrade an installation or switch branches.
|
|||||||
### CLI
|
### CLI
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd pyMC_Repeater
|
cd openhop-repeater
|
||||||
sudo bash ./manage.sh upgrade
|
sudo bash ./manage.sh upgrade
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -272,7 +272,7 @@ The upgrade script will:
|
|||||||
|
|
||||||
## Proxmox LXC Installation
|
## Proxmox LXC Installation
|
||||||
|
|
||||||
pyMC Repeater can run inside a Proxmox LXC container using a CH341 USB-to-SPI
|
openHop Repeater can run inside a Proxmox LXC container using a CH341 USB-to-SPI
|
||||||
adapter or a TCP modem. This is useful for headless, always-on deployments
|
adapter or a TCP modem. This is useful for headless, always-on deployments
|
||||||
without dedicating a full Raspberry Pi.
|
without dedicating a full Raspberry Pi.
|
||||||
|
|
||||||
@@ -295,7 +295,7 @@ Hardware, choose one:
|
|||||||
Run this command on the Proxmox host, not inside a container:
|
Run this command on the Proxmox host, not inside a container:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash -c "$(curl -fsSL https://raw.githubusercontent.com/pyMC-dev/pyMC_Repeater/main/scripts/proxmox-install.sh)"
|
bash -c "$(curl -fsSL https://raw.githubusercontent.com/pyMC-dev/openhop-repeater/main/scripts/proxmox-install.sh)"
|
||||||
```
|
```
|
||||||
|
|
||||||
Replace `main` in the URL with another branch name if needed.
|
Replace `main` in the URL with another branch name if needed.
|
||||||
@@ -315,7 +315,7 @@ disk, bridge, etc.) and then:
|
|||||||
| Setting | Default |
|
| Setting | Default |
|
||||||
|---------|---------|
|
|---------|---------|
|
||||||
| Container ID | Next available |
|
| Container ID | Next available |
|
||||||
| Hostname | `pymc-repeater` |
|
| Hostname | `openhop-repeater` |
|
||||||
| RAM | 1024 MB |
|
| RAM | 1024 MB |
|
||||||
| Disk | 4 GB |
|
| Disk | 4 GB |
|
||||||
| CPU cores | 2 |
|
| CPU cores | 2 |
|
||||||
@@ -330,10 +330,10 @@ disk, bridge, etc.) and then:
|
|||||||
pct enter <CTID>
|
pct enter <CTID>
|
||||||
|
|
||||||
# View service logs
|
# View service logs
|
||||||
journalctl -u pymc-repeater -f
|
journalctl -u openhop-repeater -f
|
||||||
|
|
||||||
# Manage the repeater
|
# Manage the repeater
|
||||||
cd /opt/pymc_repeater
|
cd /opt/openhop_repeater
|
||||||
bash manage.sh
|
bash manage.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -394,7 +394,7 @@ The script prompts before each optional removal step.
|
|||||||
|
|
||||||
## Docker Compose
|
## Docker Compose
|
||||||
|
|
||||||
You can run pyMC Repeater in Docker using the published image.
|
You can run openHop Repeater in Docker using the published image.
|
||||||
|
|
||||||
Copy `.env.example` to `.env` before starting:
|
Copy `.env.example` to `.env` before starting:
|
||||||
|
|
||||||
@@ -410,7 +410,7 @@ root-owned `./config` and `./data` bind mount folders on first start. If you
|
|||||||
want host bind mounts, use absolute host paths and pre-create/chown them to
|
want host bind mounts, use absolute host paths and pre-create/chown them to
|
||||||
`15888:15888`.
|
`15888:15888`.
|
||||||
|
|
||||||
Do not mount `./config.yaml:/etc/pymc_repeater/config.yaml`; Docker can create
|
Do not mount `./config.yaml:/etc/openhop_repeater/config.yaml`; Docker can create
|
||||||
that source as a directory, which breaks startup.
|
that source as a directory, which breaks startup.
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
@@ -430,9 +430,9 @@ docker compose up -d
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
pymc-repeater:
|
openhop-repeater:
|
||||||
image: ${PYMC_REPEATER_IMAGE:-pymcdev/pymc-repeater:main}
|
image: ${PYMC_REPEATER_IMAGE:-pymcdev/openhop-repeater:main}
|
||||||
container_name: pymc-repeater
|
container_name: openhop-repeater
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- 8000:8000
|
- 8000:8000
|
||||||
@@ -455,12 +455,12 @@ services:
|
|||||||
- plugdev
|
- plugdev
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ${PYMC_CONFIG_VOLUME:-pymc-repeater-config}:/etc/pymc_repeater
|
- ${PYMC_CONFIG_VOLUME:-openhop-repeater-config}:/etc/openhop_repeater
|
||||||
- ${PYMC_DATA_VOLUME:-pymc-repeater-data}:/var/lib/pymc_repeater
|
- ${PYMC_DATA_VOLUME:-openhop-repeater-data}:/var/lib/openhop_repeater
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pymc-repeater-config:
|
openhop-repeater-config:
|
||||||
pymc-repeater-data:
|
openhop-repeater-data:
|
||||||
```
|
```
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
@@ -509,7 +509,7 @@ pre-commit run --all-files
|
|||||||
```
|
```
|
||||||
|
|
||||||
Hardware support for LoRa radio drivers is included in the base installation
|
Hardware support for LoRa radio drivers is included in the base installation
|
||||||
through `pymc_core[hardware]`.
|
through `openhop_core[hardware]`.
|
||||||
|
|
||||||
Pre-commit hooks will automatically:
|
Pre-commit hooks will automatically:
|
||||||
- Lint and auto-fix Python issues with Ruff
|
- Lint and auto-fix Python issues with Ruff
|
||||||
@@ -518,7 +518,7 @@ Pre-commit hooks will automatically:
|
|||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
- [pyMC Core](https://github.com/pyMC-dev/pyMC_core)
|
- [pyMC Core](https://github.com/pyMC-dev/openhop-core)
|
||||||
- [MeshCore Discord](https://meshcore.gg)
|
- [MeshCore Discord](https://meshcore.gg)
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|||||||
+24
-24
@@ -1,28 +1,28 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Buildroot/Luckfox management entrypoint for pyMC Repeater
|
# Buildroot/Luckfox management entrypoint for openHop Repeater
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
INSTALL_DIR="/opt/pymc_repeater"
|
INSTALL_DIR="/opt/openhop_repeater"
|
||||||
VENV_DIR="$INSTALL_DIR/venv"
|
VENV_DIR="$INSTALL_DIR/venv"
|
||||||
VENV_PIP="$VENV_DIR/bin/pip"
|
VENV_PIP="$VENV_DIR/bin/pip"
|
||||||
VENV_PYTHON="$VENV_DIR/bin/python"
|
VENV_PYTHON="$VENV_DIR/bin/python"
|
||||||
CONFIG_DIR="/etc/pymc_repeater"
|
CONFIG_DIR="/etc/openhop_repeater"
|
||||||
LOG_DIR="/var/log/pymc_repeater"
|
LOG_DIR="/var/log/openhop_repeater"
|
||||||
DATA_DIR="/var/lib/pymc_repeater"
|
DATA_DIR="/var/lib/openhop_repeater"
|
||||||
SERVICE_USER="root"
|
SERVICE_USER="root"
|
||||||
INIT_SCRIPT="/etc/init.d/S80pymc-repeater"
|
INIT_SCRIPT="/etc/init.d/S80openhop-repeater"
|
||||||
PIDFILE="/var/run/pymc-repeater.pid"
|
PIDFILE="/var/run/openhop-repeater.pid"
|
||||||
LOGFILE="$LOG_DIR/repeater.log"
|
LOGFILE="$LOG_DIR/repeater.log"
|
||||||
SERVICE_NAME="pymc-repeater"
|
SERVICE_NAME="openhop-repeater"
|
||||||
SILENT_MODE="${PYMC_SILENT:-${SILENT:-}}"
|
SILENT_MODE="${PYMC_SILENT:-${SILENT:-}}"
|
||||||
|
|
||||||
R2_BASE_URL="https://wheel.pymc.dev/pymc_build_deps"
|
R2_BASE_URL="https://wheel.pymc.dev/pymc_build_deps"
|
||||||
PIWHEELS_INDEX_URL="https://www.piwheels.org/simple"
|
PIWHEELS_INDEX_URL="https://www.piwheels.org/simple"
|
||||||
R2_ENABLED=1
|
R2_ENABLED=1
|
||||||
YQ_VERSION="${YQ_VERSION:-v4.44.3}"
|
YQ_VERSION="${YQ_VERSION:-v4.44.3}"
|
||||||
PYMC_CORE_REPO="${PYMC_CORE_REPO:-https://github.com/rightup/pyMC_core.git}"
|
PYMC_CORE_REPO="${PYMC_CORE_REPO:-https://github.com/rightup/openhop-core.git}"
|
||||||
PYMC_CORE_REF="${PYMC_CORE_REF:-}"
|
PYMC_CORE_REF="${PYMC_CORE_REF:-}"
|
||||||
PYMC_CORE_LOCAL_DIR="${PYMC_CORE_LOCAL_DIR:-}"
|
PYMC_CORE_LOCAL_DIR="${PYMC_CORE_LOCAL_DIR:-}"
|
||||||
PYMC_SKIP_BUILDROOT_DEP_INSTALL="${PYMC_SKIP_BUILDROOT_DEP_INSTALL:-0}"
|
PYMC_SKIP_BUILDROOT_DEP_INSTALL="${PYMC_SKIP_BUILDROOT_DEP_INSTALL:-0}"
|
||||||
@@ -486,9 +486,9 @@ cleanup_legacy_install_state() {
|
|||||||
|
|
||||||
for path in \
|
for path in \
|
||||||
"$INSTALL_DIR/repeater" \
|
"$INSTALL_DIR/repeater" \
|
||||||
"$INSTALL_DIR/pymc_core" \
|
"$INSTALL_DIR/openhop_core" \
|
||||||
"$INSTALL_DIR/pyMC_Repeater" \
|
"$INSTALL_DIR/openhop-repeater" \
|
||||||
"$INSTALL_DIR/pyMC_core"
|
"$INSTALL_DIR/openhop-core"
|
||||||
do
|
do
|
||||||
if [ -e "$path" ]; then
|
if [ -e "$path" ]; then
|
||||||
rm -rf "$path"
|
rm -rf "$path"
|
||||||
@@ -641,8 +641,8 @@ install_core_into_venv() {
|
|||||||
local core_repo core_ref core_spec
|
local core_repo core_ref core_spec
|
||||||
|
|
||||||
if [ -n "$PYMC_CORE_LOCAL_DIR" ]; then
|
if [ -n "$PYMC_CORE_LOCAL_DIR" ]; then
|
||||||
[ -d "$PYMC_CORE_LOCAL_DIR" ] || fail "Missing local pyMC_core checkout: $PYMC_CORE_LOCAL_DIR"
|
[ -d "$PYMC_CORE_LOCAL_DIR" ] || fail "Missing local openhop-core checkout: $PYMC_CORE_LOCAL_DIR"
|
||||||
stage "Installing pyMC_core"
|
stage "Installing openhop-core"
|
||||||
info "Local dir: ${PYMC_CORE_LOCAL_DIR}"
|
info "Local dir: ${PYMC_CORE_LOCAL_DIR}"
|
||||||
"$VENV_PIP" install --upgrade --no-cache-dir --no-deps --no-build-isolation "$PYMC_CORE_LOCAL_DIR"
|
"$VENV_PIP" install --upgrade --no-cache-dir --no-deps --no-build-isolation "$PYMC_CORE_LOCAL_DIR"
|
||||||
return 0
|
return 0
|
||||||
@@ -654,8 +654,8 @@ install_core_into_venv() {
|
|||||||
*) core_repo="${core_repo}.git" ;;
|
*) core_repo="${core_repo}.git" ;;
|
||||||
esac
|
esac
|
||||||
core_ref=$(resolve_core_ref)
|
core_ref=$(resolve_core_ref)
|
||||||
core_spec="pyMC_core[hardware] @ git+${core_repo}@${core_ref}"
|
core_spec="openhop-core[hardware] @ git+${core_repo}@${core_ref}"
|
||||||
stage "Installing pyMC_core"
|
stage "Installing openhop-core"
|
||||||
info "Repo: ${PYMC_CORE_REPO}"
|
info "Repo: ${PYMC_CORE_REPO}"
|
||||||
info "Ref: ${core_ref}"
|
info "Ref: ${core_ref}"
|
||||||
"$VENV_PIP" install --upgrade --no-cache-dir --no-deps --no-build-isolation "$core_spec"
|
"$VENV_PIP" install --upgrade --no-cache-dir --no-deps --no-build-isolation "$core_spec"
|
||||||
@@ -667,7 +667,7 @@ import glob
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
matches = glob.glob("/opt/pymc_repeater/venv/lib/python*/site-packages/pymc_core-*.dist-info/direct_url.json")
|
matches = glob.glob("/opt/openhop_repeater/venv/lib/python*/site-packages/openhop_core-*.dist-info/direct_url.json")
|
||||||
for path in matches:
|
for path in matches:
|
||||||
try:
|
try:
|
||||||
with open(path, "r", encoding="utf-8") as fh:
|
with open(path, "r", encoding="utf-8") as fh:
|
||||||
@@ -693,7 +693,7 @@ resolve_core_commit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
install_repeater_package() {
|
install_repeater_package() {
|
||||||
stage "Installing pyMC Repeater into venv"
|
stage "Installing openHop Repeater into venv"
|
||||||
info "Installing checked-out repo without re-resolving dependencies"
|
info "Installing checked-out repo without re-resolving dependencies"
|
||||||
"$VENV_PIP" install --upgrade --no-cache-dir --no-deps --no-build-isolation "$SCRIPT_DIR"
|
"$VENV_PIP" install --upgrade --no-cache-dir --no-deps --no-build-isolation "$SCRIPT_DIR"
|
||||||
}
|
}
|
||||||
@@ -1232,7 +1232,7 @@ start_or_restart_service() {
|
|||||||
|
|
||||||
get_version() {
|
get_version() {
|
||||||
if [ -x "$VENV_PYTHON" ]; then
|
if [ -x "$VENV_PYTHON" ]; then
|
||||||
"$VENV_PYTHON" -c "from importlib.metadata import version; print(version('pymc_repeater'))" 2>/dev/null || echo "not installed"
|
"$VENV_PYTHON" -c "from importlib.metadata import version; print(version('openhop_repeater'))" 2>/dev/null || echo "not installed"
|
||||||
else
|
else
|
||||||
echo "not installed"
|
echo "not installed"
|
||||||
fi
|
fi
|
||||||
@@ -1355,7 +1355,7 @@ install_repeater() {
|
|||||||
info "Install dir: $INSTALL_DIR"
|
info "Install dir: $INSTALL_DIR"
|
||||||
info "Config dir: $CONFIG_DIR"
|
info "Config dir: $CONFIG_DIR"
|
||||||
info "Data dir: $DATA_DIR"
|
info "Data dir: $DATA_DIR"
|
||||||
mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" "$DATA_DIR" "$DATA_DIR/.config/pymc_repeater"
|
mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" "$DATA_DIR" "$DATA_DIR/.config/openhop_repeater"
|
||||||
chown -R root:root "$CONFIG_DIR" "$LOG_DIR" "$DATA_DIR"
|
chown -R root:root "$CONFIG_DIR" "$LOG_DIR" "$DATA_DIR"
|
||||||
chmod 755 "$INSTALL_DIR" "$DATA_DIR"
|
chmod 755 "$INSTALL_DIR" "$DATA_DIR"
|
||||||
chmod 750 "$CONFIG_DIR" "$LOG_DIR"
|
chmod 750 "$CONFIG_DIR" "$LOG_DIR"
|
||||||
@@ -1417,7 +1417,7 @@ upgrade_repeater() {
|
|||||||
git_version=$(prepare_git_version)
|
git_version=$(prepare_git_version)
|
||||||
preinstall_r2_wheels
|
preinstall_r2_wheels
|
||||||
|
|
||||||
stage "Upgrading pyMC Repeater"
|
stage "Upgrading openHop Repeater"
|
||||||
if [ "${PYMC_FORCE_DEPS:-0}" = "1" ]; then
|
if [ "${PYMC_FORCE_DEPS:-0}" = "1" ]; then
|
||||||
info "Forcing dependency reinstall"
|
info "Forcing dependency reinstall"
|
||||||
install_buildroot_dependencies
|
install_buildroot_dependencies
|
||||||
@@ -1431,13 +1431,13 @@ upgrade_repeater() {
|
|||||||
target_core_commit=$(resolve_core_commit)
|
target_core_commit=$(resolve_core_commit)
|
||||||
installed_core_commit=$(get_installed_core_commit)
|
installed_core_commit=$(get_installed_core_commit)
|
||||||
if [ -n "$target_core_commit" ] && [ "$installed_core_commit" = "$target_core_commit" ] && [ "${PYMC_FORCE_CORE:-0}" != "1" ]; then
|
if [ -n "$target_core_commit" ] && [ "$installed_core_commit" = "$target_core_commit" ] && [ "${PYMC_FORCE_CORE:-0}" != "1" ]; then
|
||||||
info "pyMC_core is already at ${target_core_commit}; skipping reinstall"
|
info "openhop-core is already at ${target_core_commit}; skipping reinstall"
|
||||||
else
|
else
|
||||||
install_core_into_venv
|
install_core_into_venv
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$current_version" = "$git_version" ] && [ "${PYMC_FORCE_REPEATER:-0}" != "1" ]; then
|
if [ "$current_version" = "$git_version" ] && [ "${PYMC_FORCE_REPEATER:-0}" != "1" ]; then
|
||||||
info "pyMC Repeater is already at ${git_version}; skipping reinstall"
|
info "openHop Repeater is already at ${git_version}; skipping reinstall"
|
||||||
else
|
else
|
||||||
ensure_yq >/dev/null 2>&1 || true
|
ensure_yq >/dev/null 2>&1 || true
|
||||||
install_repeater_package
|
install_repeater_package
|
||||||
@@ -1521,7 +1521,7 @@ Usage: bash buildroot-manage.sh <command>
|
|||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
doctor Check Buildroot/Luckfox prerequisites
|
doctor Check Buildroot/Luckfox prerequisites
|
||||||
install Install pyMC Repeater on the Buildroot image
|
install Install openHop Repeater on the Buildroot image
|
||||||
upgrade Upgrade the Buildroot installation from the checked-out repo
|
upgrade Upgrade the Buildroot installation from the checked-out repo
|
||||||
config Prompt for repeater settings and rewrite config.yaml
|
config Prompt for repeater settings and rewrite config.yaml
|
||||||
configure Same as config
|
configure Same as config
|
||||||
|
|||||||
+8
-8
@@ -151,7 +151,7 @@ gps:
|
|||||||
# File source settings (used when source: file)
|
# File source settings (used when source: file)
|
||||||
# The file may contain raw NMEA lines or JSON with a "sentences" list /
|
# The file may contain raw NMEA lines or JSON with a "sentences" list /
|
||||||
# "last_sentence" field.
|
# "last_sentence" field.
|
||||||
source_path: "/var/lib/pymc_repeater/gps_nmea.txt"
|
source_path: "/var/lib/openhop_repeater/gps_nmea.txt"
|
||||||
poll_interval_seconds: 2.0
|
poll_interval_seconds: 2.0
|
||||||
|
|
||||||
# Modem HTTP source settings (used when source: modem_http)
|
# Modem HTTP source settings (used when source: modem_http)
|
||||||
@@ -406,7 +406,7 @@ radio:
|
|||||||
# Use implicit header mode
|
# Use implicit header mode
|
||||||
implicit_header: false
|
implicit_header: false
|
||||||
|
|
||||||
# KISS modem (when radio_type: kiss). Requires pyMC_core with KISS support.
|
# KISS modem (when radio_type: kiss). Requires openhop-core with KISS support.
|
||||||
# kiss:
|
# kiss:
|
||||||
# port: "/dev/ttyUSB0"
|
# port: "/dev/ttyUSB0"
|
||||||
# baud_rate: 9600
|
# baud_rate: 9600
|
||||||
@@ -422,7 +422,7 @@ radio:
|
|||||||
# # kiss_full_duplex: false # disable carrier-sense/CSMA entirely (not recommended)
|
# # kiss_full_duplex: false # disable carrier-sense/CSMA entirely (not recommended)
|
||||||
|
|
||||||
# pymc_usb firmware modem over Wi-Fi/TCP (when radio_type: pymc_tcp).
|
# pymc_usb firmware modem over Wi-Fi/TCP (when radio_type: pymc_tcp).
|
||||||
# Requires pyMC_core with the TCPLoRaRadio driver
|
# Requires openhop-core with the TCPLoRaRadio driver
|
||||||
# pymc_tcp:
|
# pymc_tcp:
|
||||||
# host: "pymc-3e2834.local" # modem hostname / mDNS name / LAN IP
|
# host: "pymc-3e2834.local" # modem hostname / mDNS name / LAN IP
|
||||||
# port: 5055 # firmware default
|
# port: 5055 # firmware default
|
||||||
@@ -432,7 +432,7 @@ radio:
|
|||||||
# lbt_max_attempts: 5
|
# lbt_max_attempts: 5
|
||||||
|
|
||||||
# pymc_usb firmware modem over USB-CDC (when radio_type: pymc_usb).
|
# pymc_usb firmware modem over USB-CDC (when radio_type: pymc_usb).
|
||||||
# Requires pyMC_core with the USBLoRaRadio driver
|
# Requires openhop-core with the USBLoRaRadio driver
|
||||||
# pymc_usb:
|
# pymc_usb:
|
||||||
# port: "/dev/ttyACM0" # USB-CDC device; udev rule may symlink to /dev/lora-modem
|
# port: "/dev/ttyACM0" # USB-CDC device; udev rule may symlink to /dev/lora-modem
|
||||||
# baudrate: 921600 # must match firmware monitor_speed
|
# baudrate: 921600 # must match firmware monitor_speed
|
||||||
@@ -492,8 +492,8 @@ duty_cycle:
|
|||||||
# Storage Configuration
|
# Storage Configuration
|
||||||
storage:
|
storage:
|
||||||
# Directory for persistent storage files (SQLite, RRD).
|
# Directory for persistent storage files (SQLite, RRD).
|
||||||
# Use a writable path for local/dev (e.g. "./var/pymc_repeater" or "~/var/pymc_repeater").
|
# Use a writable path for local/dev (e.g. "./var/openhop_repeater" or "~/var/openhop_repeater").
|
||||||
storage_dir: "/var/lib/pymc_repeater"
|
storage_dir: "/var/lib/openhop_repeater"
|
||||||
|
|
||||||
# Data retention settings
|
# Data retention settings
|
||||||
retention:
|
retention:
|
||||||
@@ -526,7 +526,7 @@ mqtt_brokers:
|
|||||||
# format: meshcoretomqtt|letsmesh|waev|mqtt
|
# format: meshcoretomqtt|letsmesh|waev|mqtt
|
||||||
# meshcoretomqtt - canonical open-source MC2MQTT topic structure
|
# meshcoretomqtt - canonical open-source MC2MQTT topic structure
|
||||||
# letsmesh, waev - MC2MQTT family flavors (same topic structure, network-specific identity)
|
# letsmesh, waev - MC2MQTT family flavors (same topic structure, network-specific identity)
|
||||||
# mqtt - legacy pyMC_Repeater local-broker convention (custom topic, singular 'packet')
|
# mqtt - legacy openhop-repeater local-broker convention (custom topic, singular 'packet')
|
||||||
# retain_status: true|false # Sets MQTT "retain" on status messages so they remain on the broker when disconnected. Also enforces a QOS of 1 (guaranteed delivery)
|
# retain_status: true|false # Sets MQTT "retain" on status messages so they remain on the broker when disconnected. Also enforces a QOS of 1 (guaranteed delivery)
|
||||||
# tls:
|
# tls:
|
||||||
# enabled: true|false # Enable TLS. If the endpoint's certificate is self-signed, the Root CA should be added to the OS's certificate store.
|
# enabled: true|false # Enable TLS. If the endpoint's certificate is self-signed, the Root CA should be added to the OS's certificate store.
|
||||||
@@ -602,7 +602,7 @@ glass:
|
|||||||
api_token: ""
|
api_token: ""
|
||||||
|
|
||||||
# Where cert_renewal payloads are written
|
# Where cert_renewal payloads are written
|
||||||
cert_store_dir: "/etc/pymc_repeater/glass"
|
cert_store_dir: "/etc/openhop_repeater/glass"
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
# Log level: DEBUG, INFO, WARNING, ERROR
|
# Log level: DEBUG, INFO, WARNING, ERROR
|
||||||
|
|||||||
+20
-20
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Convert MeshCore firmware 64-byte private key to pyMC_Repeater format
|
# Convert MeshCore firmware 64-byte private key to openhop-repeater format
|
||||||
#
|
#
|
||||||
# Usage: sudo ./convert_firmware_key.sh <64-byte-hex-key> [--output-format=<yaml|identity>] [config-path]
|
# Usage: sudo ./convert_firmware_key.sh <64-byte-hex-key> [--output-format=<yaml|identity>] [config-path]
|
||||||
# Example: sudo ./convert_firmware_key.sh 987BDA619630197351F2B3040FD19B2EE0DEE357DD69BBEEE295786FA78A4D5F298B0BF1B7DE73CBC23257CDB2C562F5033DF58C232916432948B0F6BA4448F2
|
# Example: sudo ./convert_firmware_key.sh 987BDA619630197351F2B3040FD19B2EE0DEE357DD69BBEEE295786FA78A4D5F298B0BF1B7DE73CBC23257CDB2C562F5033DF58C232916432948B0F6BA4448F2
|
||||||
@@ -12,18 +12,18 @@ if [ $# -eq 0 ]; then
|
|||||||
echo "Usage: sudo $0 <64-byte-hex-key> [--output-format=<yaml|identity>] [config-path]"
|
echo "Usage: sudo $0 <64-byte-hex-key> [--output-format=<yaml|identity>] [config-path]"
|
||||||
echo ""
|
echo ""
|
||||||
echo "This script imports a 64-byte MeshCore firmware private key into"
|
echo "This script imports a 64-byte MeshCore firmware private key into"
|
||||||
echo "pyMC_Repeater for full identity compatibility."
|
echo "openhop-repeater for full identity compatibility."
|
||||||
echo ""
|
echo ""
|
||||||
echo "The 64-byte key format: [32-byte scalar][32-byte nonce]"
|
echo "The 64-byte key format: [32-byte scalar][32-byte nonce]"
|
||||||
echo " - Enables same node address as firmware device"
|
echo " - Enables same node address as firmware device"
|
||||||
echo " - Supports signing using MeshCore/orlp ed25519 algorithm"
|
echo " - Supports signing using MeshCore/orlp ed25519 algorithm"
|
||||||
echo " - Fully compatible with pyMC_core LocalIdentity"
|
echo " - Fully compatible with openhop-core LocalIdentity"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Arguments:"
|
echo "Arguments:"
|
||||||
echo " --output-format: Optional output format (yaml|identity, default: yaml)"
|
echo " --output-format: Optional output format (yaml|identity, default: yaml)"
|
||||||
echo " yaml - Store in config.yaml (embedded binary)"
|
echo " yaml - Store in config.yaml (embedded binary)"
|
||||||
echo " identity - Save to identity.key file (base64 encoded)"
|
echo " identity - Save to identity.key file (base64 encoded)"
|
||||||
echo " config-path: Optional path to config.yaml (default: /etc/pymc_repeater/config.yaml)"
|
echo " config-path: Optional path to config.yaml (default: /etc/openhop_repeater/config.yaml)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Examples:"
|
echo "Examples:"
|
||||||
echo " # Save to config.yaml (default)"
|
echo " # Save to config.yaml (default)"
|
||||||
@@ -67,7 +67,7 @@ fi
|
|||||||
|
|
||||||
# Set default config path if not provided
|
# Set default config path if not provided
|
||||||
if [ -z "$CONFIG_PATH" ]; then
|
if [ -z "$CONFIG_PATH" ]; then
|
||||||
CONFIG_PATH="/etc/pymc_repeater/config.yaml"
|
CONFIG_PATH="/etc/openhop_repeater/config.yaml"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate hex string
|
# Validate hex string
|
||||||
@@ -92,7 +92,7 @@ if [ "$OUTPUT_FORMAT" = "yaml" ]; then
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# For identity format, use system-wide location matching config.yaml
|
# For identity format, use system-wide location matching config.yaml
|
||||||
IDENTITY_DIR="/etc/pymc_repeater"
|
IDENTITY_DIR="/etc/openhop_repeater"
|
||||||
IDENTITY_PATH="$IDENTITY_DIR/identity.key"
|
IDENTITY_PATH="$IDENTITY_DIR/identity.key"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -261,39 +261,39 @@ fi
|
|||||||
|
|
||||||
# Offer to restart service (only relevant for yaml format)
|
# Offer to restart service (only relevant for yaml format)
|
||||||
if [ "$OUTPUT_FORMAT" = "yaml" ]; then
|
if [ "$OUTPUT_FORMAT" = "yaml" ]; then
|
||||||
if systemctl is-active --quiet pymc-repeater 2>/dev/null; then
|
if systemctl is-active --quiet openhop-repeater 2>/dev/null; then
|
||||||
read -p "Restart pymc-repeater service now? (yes/no): " RESTART
|
read -p "Restart openhop-repeater service now? (yes/no): " RESTART
|
||||||
if [ "$RESTART" = "yes" ]; then
|
if [ "$RESTART" = "yes" ]; then
|
||||||
systemctl restart pymc-repeater
|
systemctl restart openhop-repeater
|
||||||
echo "✓ Service restarted"
|
echo "✓ Service restarted"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Check logs for new identity:"
|
echo "Check logs for new identity:"
|
||||||
echo " sudo journalctl -u pymc-repeater -f | grep -i 'identity\|hash'"
|
echo " sudo journalctl -u openhop-repeater -f | grep -i 'identity\|hash'"
|
||||||
else
|
else
|
||||||
echo "Remember to restart the service:"
|
echo "Remember to restart the service:"
|
||||||
echo " sudo systemctl restart pymc-repeater"
|
echo " sudo systemctl restart openhop-repeater"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Note: pymc-repeater service is not running"
|
echo "Note: openhop-repeater service is not running"
|
||||||
echo "Start it with: sudo systemctl start pymc-repeater"
|
echo "Start it with: sudo systemctl start openhop-repeater"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Identity key saved to file."
|
echo "Identity key saved to file."
|
||||||
echo ""
|
echo ""
|
||||||
if systemctl is-active --quiet pymc-repeater 2>/dev/null; then
|
if systemctl is-active --quiet openhop-repeater 2>/dev/null; then
|
||||||
read -p "Restart pymc-repeater service now? (yes/no): " RESTART
|
read -p "Restart openhop-repeater service now? (yes/no): " RESTART
|
||||||
if [ "$RESTART" = "yes" ]; then
|
if [ "$RESTART" = "yes" ]; then
|
||||||
systemctl restart pymc-repeater
|
systemctl restart openhop-repeater
|
||||||
echo "✓ Service restarted"
|
echo "✓ Service restarted"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Check logs for new identity:"
|
echo "Check logs for new identity:"
|
||||||
echo " sudo journalctl -u pymc-repeater -f | grep -i 'identity\|hash'"
|
echo " sudo journalctl -u openhop-repeater -f | grep -i 'identity\|hash'"
|
||||||
else
|
else
|
||||||
echo "Remember to restart the service:"
|
echo "Remember to restart the service:"
|
||||||
echo " sudo systemctl restart pymc-repeater"
|
echo " sudo systemctl restart openhop-repeater"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Note: pymc-repeater service is not running"
|
echo "Note: openhop-repeater service is not running"
|
||||||
echo "Start it with: sudo systemctl start pymc-repeater"
|
echo "Start it with: sudo systemctl start openhop-repeater"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@
|
|||||||
*.substvars
|
*.substvars
|
||||||
.debhelper/
|
.debhelper/
|
||||||
files
|
files
|
||||||
pymc-repeater/
|
openhop-repeater/
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
pymc-repeater (1.0.5~dev0) unstable; urgency=medium
|
openhop-repeater (1.0.5~dev0) unstable; urgency=medium
|
||||||
|
|
||||||
* Development build from git commit 7112da9
|
* Development build from git commit 7112da9
|
||||||
* Version: 1.0.5.post0
|
* Version: 1.0.5.post0
|
||||||
|
|||||||
Vendored
+5
-5
@@ -1,4 +1,4 @@
|
|||||||
Source: pymc-repeater
|
Source: openhop-repeater
|
||||||
Section: net
|
Section: net
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Rightup <rightup@pymc.dev>
|
Maintainer: Rightup <rightup@pymc.dev>
|
||||||
@@ -15,10 +15,10 @@ Build-Depends: debhelper-compat (= 13),
|
|||||||
python3-psutil,
|
python3-psutil,
|
||||||
git
|
git
|
||||||
Standards-Version: 4.6.2
|
Standards-Version: 4.6.2
|
||||||
Homepage: https://github.com/rightup/pyMC_Repeater
|
Homepage: https://github.com/rightup/openhop-repeater
|
||||||
X-Python3-Version: >= 3.9
|
X-Python3-Version: >= 3.9
|
||||||
|
|
||||||
Package: pymc-repeater
|
Package: openhop-repeater
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: ${python3:Depends},
|
Depends: ${python3:Depends},
|
||||||
${misc:Depends},
|
${misc:Depends},
|
||||||
@@ -36,8 +36,8 @@ Recommends: python3-periphery,
|
|||||||
Description: PyMC Repeater Daemon
|
Description: PyMC Repeater Daemon
|
||||||
A mesh networking repeater daemon for LoRa devices.
|
A mesh networking repeater daemon for LoRa devices.
|
||||||
.
|
.
|
||||||
This package provides the pymc-repeater service for managing
|
This package provides the openhop-repeater service for managing
|
||||||
mesh network repeater functionality with a web interface.
|
mesh network repeater functionality with a web interface.
|
||||||
.
|
.
|
||||||
Note: This package will install pymc_core, cherrypy-cors, and ws4py
|
Note: This package will install openhop_core, cherrypy-cors, and ws4py
|
||||||
from PyPI during postinst as they are not available in Debian repos.
|
from PyPI during postinst as they are not available in Debian repos.
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
pymc-repeater
|
openhop-repeater
|
||||||
|
|||||||
Vendored
-3
@@ -1,3 +0,0 @@
|
|||||||
etc/pymc_repeater
|
|
||||||
var/log/pymc_repeater
|
|
||||||
usr/share/pymc_repeater
|
|
||||||
Vendored
-3
@@ -1,3 +0,0 @@
|
|||||||
config.yaml.example usr/share/pymc_repeater/
|
|
||||||
radio-presets.json usr/share/pymc_repeater/
|
|
||||||
radio-settings.json usr/share/pymc_repeater/
|
|
||||||
Vendored
-57
@@ -1,57 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
configure)
|
|
||||||
# Create system user
|
|
||||||
if ! getent passwd pymc-repeater >/dev/null; then
|
|
||||||
adduser --system --group --home /var/lib/pymc-repeater \
|
|
||||||
--gecos "PyMC Repeater Service" pymc-repeater
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add user to gpio and spi groups for hardware access
|
|
||||||
if getent group gpio >/dev/null; then
|
|
||||||
usermod -a -G gpio pymc-repeater
|
|
||||||
fi
|
|
||||||
if getent group spi >/dev/null; then
|
|
||||||
usermod -a -G spi pymc-repeater
|
|
||||||
fi
|
|
||||||
# Create and set permissions on data directory
|
|
||||||
mkdir -p /var/lib/pymc_repeater
|
|
||||||
chown -R pymc-repeater:pymc-repeater /var/lib/pymc_repeater
|
|
||||||
chmod 750 /var/lib/pymc_repeater
|
|
||||||
# Set permissions
|
|
||||||
chown -R pymc-repeater:pymc-repeater /etc/pymc_repeater
|
|
||||||
chown -R pymc-repeater:pymc-repeater /var/log/pymc-repeater
|
|
||||||
chmod 750 /etc/pymc_repeater
|
|
||||||
chmod 750 /var/log/pymc-repeater
|
|
||||||
|
|
||||||
# Copy example config if no config exists
|
|
||||||
if [ ! -f /etc/pymc_repeater/config.yaml ]; then
|
|
||||||
cp /usr/share/pymc_repeater/config.yaml.example /etc/pymc_repeater/config.yaml
|
|
||||||
chown pymc-repeater:pymc-repeater /etc/pymc_repeater/config.yaml
|
|
||||||
chmod 640 /etc/pymc_repeater/config.yaml
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install pymc_core from PyPI if not already installed
|
|
||||||
if ! python3 -c "import pymc_core" 2>/dev/null; then
|
|
||||||
echo "Installing pymc_core[hardware] from PyPI..."
|
|
||||||
python3 -m pip install --break-system-packages 'pymc_core[hardware]>=1.0.7' || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install packages not available in Debian repos
|
|
||||||
if ! python3 -c "import cherrypy_cors" 2>/dev/null; then
|
|
||||||
echo "Installing cherrypy-cors from PyPI..."
|
|
||||||
python3 -m pip install --break-system-packages 'cherrypy-cors==1.7.0' || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! python3 -c "import ws4py" 2>/dev/null; then
|
|
||||||
echo "Installing ws4py from PyPI..."
|
|
||||||
python3 -m pip install --break-system-packages 'ws4py>=0.5.1' || true
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
#DEBHELPER#
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
Vendored
-18
@@ -1,18 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
purge)
|
|
||||||
# Remove user and directories
|
|
||||||
if getent passwd pymc-repeater >/dev/null; then
|
|
||||||
deluser --system pymc-repeater || true
|
|
||||||
fi
|
|
||||||
rm -rf /etc/pymc-repeater
|
|
||||||
rm -rf /var/log/pymc-repeater
|
|
||||||
rm -rf /var/lib/pymc-repeater
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
#DEBHELPER#
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
Vendored
-19
@@ -1,19 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=PyMC Repeater Daemon
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=pymc-repeater
|
|
||||||
Group=pymc-repeater
|
|
||||||
WorkingDirectory=/etc/pymc-repeater
|
|
||||||
ExecStart=/usr/bin/pymc-repeater
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
|
|
||||||
# Allow GPS time sync to update CLOCK_REALTIME without running as root
|
|
||||||
CapabilityBoundingSet=CAP_SYS_TIME
|
|
||||||
AmbientCapabilities=CAP_SYS_TIME
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/make -f
|
#!/usr/bin/make -f
|
||||||
# -*- makefile -*-
|
# -*- makefile -*-
|
||||||
|
|
||||||
export PYBUILD_NAME=pymc-repeater
|
export PYBUILD_NAME=openhop-repeater
|
||||||
export DH_VERBOSE=1
|
export DH_VERBOSE=1
|
||||||
|
|
||||||
%:
|
%:
|
||||||
@@ -19,4 +19,4 @@ override_dh_auto_test:
|
|||||||
rm -f repeater/_version.py
|
rm -f repeater/_version.py
|
||||||
|
|
||||||
override_dh_installsystemd:
|
override_dh_installsystemd:
|
||||||
dh_installsystemd --name=pymc-repeater
|
dh_installsystemd --name=openhop-repeater
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
pymc-repeater:
|
openhop-repeater:
|
||||||
image: pymc-repeater:local
|
image: openhop-repeater:local
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: dockerfile
|
dockerfile: dockerfile
|
||||||
|
|||||||
+7
-7
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
pymc-repeater:
|
openhop-repeater:
|
||||||
image: ${PYMC_REPEATER_IMAGE:-pymcdev/pymc-repeater:main}
|
image: ${PYMC_REPEATER_IMAGE:-pymcdev/openhop-repeater:main}
|
||||||
container_name: pymc-repeater
|
container_name: openhop-repeater
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- 8000:8000
|
- 8000:8000
|
||||||
@@ -21,9 +21,9 @@ services:
|
|||||||
- "${SPI_GID:-989}"
|
- "${SPI_GID:-989}"
|
||||||
- plugdev
|
- plugdev
|
||||||
volumes:
|
volumes:
|
||||||
- ${PYMC_CONFIG_VOLUME:-pymc-repeater-config}:/etc/pymc_repeater
|
- ${PYMC_CONFIG_VOLUME:-openhop-repeater-config}:/etc/openhop_repeater
|
||||||
- ${PYMC_DATA_VOLUME:-pymc-repeater-data}:/var/lib/pymc_repeater
|
- ${PYMC_DATA_VOLUME:-openhop-repeater-data}:/var/lib/openhop_repeater
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pymc-repeater-config:
|
openhop-repeater-config:
|
||||||
pymc-repeater-data:
|
openhop-repeater-data:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
INSTALL_DIR="${INSTALL_DIR:-/opt/pymc_repeater}"
|
INSTALL_DIR="${INSTALL_DIR:-/opt/openhop_repeater}"
|
||||||
CONFIG_DIR="${CONFIG_DIR:-/etc/pymc_repeater}"
|
CONFIG_DIR="${CONFIG_DIR:-/etc/openhop_repeater}"
|
||||||
CONFIG_PATH="${PYMC_REPEATER_CONFIG:-${CONFIG_DIR}/config.yaml}"
|
CONFIG_PATH="${PYMC_REPEATER_CONFIG:-${CONFIG_DIR}/config.yaml}"
|
||||||
EXAMPLE_PATH="${CONFIG_DIR}/config.yaml.example"
|
EXAMPLE_PATH="${CONFIG_DIR}/config.yaml.example"
|
||||||
BUNDLED_EXAMPLE_PATH="${INSTALL_DIR}/config.yaml.example"
|
BUNDLED_EXAMPLE_PATH="${INSTALL_DIR}/config.yaml.example"
|
||||||
@@ -22,7 +22,7 @@ fail_bad_config_mount() {
|
|||||||
echo "Invalid Docker config mount: ${CONFIG_PATH} is a directory, but it must be the config file." >&2
|
echo "Invalid Docker config mount: ${CONFIG_PATH} is a directory, but it must be the config file." >&2
|
||||||
echo "This usually happens when ./config.yaml is bind-mounted before that host file exists." >&2
|
echo "This usually happens when ./config.yaml is bind-mounted before that host file exists." >&2
|
||||||
echo "Use the supported folder mount instead:" >&2
|
echo "Use the supported folder mount instead:" >&2
|
||||||
echo " - ./config:/etc/pymc_repeater" >&2
|
echo " - ./config:/etc/openhop_repeater" >&2
|
||||||
echo "Then place the config at ./config/config.yaml." >&2
|
echo "Then place the config at ./config/config.yaml." >&2
|
||||||
print_permission_help
|
print_permission_help
|
||||||
exit 1
|
exit 1
|
||||||
@@ -40,7 +40,7 @@ copy_or_die() {
|
|||||||
|
|
||||||
use_runtime_merged_config() {
|
use_runtime_merged_config() {
|
||||||
src="$1"
|
src="$1"
|
||||||
runtime_dir="$(mktemp -d /tmp/pymc-repeater-config.XXXXXX)"
|
runtime_dir="$(mktemp -d /tmp/openhop-repeater-config.XXXXXX)"
|
||||||
runtime_config="${runtime_dir}/config.yaml"
|
runtime_config="${runtime_dir}/config.yaml"
|
||||||
|
|
||||||
if ! cp "${src}" "${runtime_config}"; then
|
if ! cp "${src}" "${runtime_config}"; then
|
||||||
|
|||||||
+3
-3
@@ -11,9 +11,9 @@ ARG SPI_GID=989
|
|||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG YQ_VERSION=v4.40.5
|
ARG YQ_VERSION=v4.40.5
|
||||||
|
|
||||||
ENV INSTALL_DIR=/opt/pymc_repeater \
|
ENV INSTALL_DIR=/opt/openhop_repeater \
|
||||||
CONFIG_DIR=/etc/pymc_repeater \
|
CONFIG_DIR=/etc/openhop_repeater \
|
||||||
DATA_DIR=/var/lib/pymc_repeater \
|
DATA_DIR=/var/lib/openhop_repeater \
|
||||||
HOME_DIR=/home/${USER} \
|
HOME_DIR=/home/${USER} \
|
||||||
PATH=/home/${USER}/.local/bin:${PATH} \
|
PATH=/home/${USER}/.local/bin:${PATH} \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Adding a New Sensor Plug-in
|
# Adding a New Sensor Plug-in
|
||||||
|
|
||||||
Sensors in pyMC_Repeater are self-contained modules that live in `repeater/sensors/`. The subsystem is plug-in based: adding a new sensor requires only one new file. The manager discovers and loads it automatically at runtime by importing the module named after the sensor type.
|
Sensors in openhop-repeater are self-contained modules that live in `repeater/sensors/`. The subsystem is plug-in based: adding a new sensor requires only one new file. The manager discovers and loads it automatically at runtime by importing the module named after the sensor type.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -3,17 +3,17 @@
|
|||||||
|
|
||||||
# Published image to run. Use a different repository or tag if you are testing
|
# Published image to run. Use a different repository or tag if you are testing
|
||||||
# a fork or a specific release.
|
# a fork or a specific release.
|
||||||
PYMC_REPEATER_IMAGE=pymcdev/pymc-repeater:main
|
PYMC_REPEATER_IMAGE=pymcdev/openhop-repeater:main
|
||||||
|
|
||||||
# Storage defaults to Docker named volumes. This is the safest option for
|
# Storage defaults to Docker named volumes. This is the safest option for
|
||||||
# Portainer and fresh installs because Docker preserves the image ownership.
|
# Portainer and fresh installs because Docker preserves the image ownership.
|
||||||
# To use host bind mounts instead, create the folders first and make them
|
# To use host bind mounts instead, create the folders first and make them
|
||||||
# writable by UID/GID 15888:
|
# writable by UID/GID 15888:
|
||||||
# sudo mkdir -p /opt/pymc-repeater/config /opt/pymc-repeater/data
|
# sudo mkdir -p /opt/openhop-repeater/config /opt/openhop-repeater/data
|
||||||
# sudo chown -R 15888:15888 /opt/pymc-repeater/config /opt/pymc-repeater/data
|
# sudo chown -R 15888:15888 /opt/openhop-repeater/config /opt/openhop-repeater/data
|
||||||
# Then uncomment and adjust these paths:
|
# Then uncomment and adjust these paths:
|
||||||
# PYMC_CONFIG_VOLUME=/opt/pymc-repeater/config
|
# PYMC_CONFIG_VOLUME=/opt/openhop-repeater/config
|
||||||
# PYMC_DATA_VOLUME=/opt/pymc-repeater/data
|
# PYMC_DATA_VOLUME=/opt/openhop-repeater/data
|
||||||
|
|
||||||
# SPI/GPIO access uses the host's numeric group IDs. Check your host with:
|
# SPI/GPIO access uses the host's numeric group IDs. Check your host with:
|
||||||
# getent group gpio
|
# getent group gpio
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# pyMC Repeater Management Script - Deploy, Upgrade, Uninstall
|
# openHop Repeater Management Script - Deploy, Upgrade, Uninstall
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
INSTALL_DIR="/opt/pymc_repeater"
|
INSTALL_DIR="/opt/openhop_repeater"
|
||||||
VENV_DIR="$INSTALL_DIR/venv"
|
VENV_DIR="$INSTALL_DIR/venv"
|
||||||
VENV_PIP="$VENV_DIR/bin/pip"
|
VENV_PIP="$VENV_DIR/bin/pip"
|
||||||
VENV_PYTHON="$VENV_DIR/bin/python"
|
VENV_PYTHON="$VENV_DIR/bin/python"
|
||||||
CONFIG_DIR="/etc/pymc_repeater"
|
CONFIG_DIR="/etc/openhop_repeater"
|
||||||
LOG_DIR="/var/log/pymc_repeater"
|
LOG_DIR="/var/log/openhop_repeater"
|
||||||
|
DATA_DIR="/var/lib/openhop_repeater"
|
||||||
SERVICE_USER="repeater"
|
SERVICE_USER="repeater"
|
||||||
SERVICE_NAME="pymc-repeater"
|
SERVICE_NAME="openhop-repeater"
|
||||||
SILENT_MODE="${PYMC_SILENT:-${SILENT:-}}"
|
SILENT_MODE="${PYMC_SILENT:-${SILENT:-}}"
|
||||||
|
|
||||||
|
LEGACY_INSTALL_DIR="/opt/openhop-repeater"
|
||||||
|
LEGACY_CONFIG_DIR="/etc/openhop-repeater"
|
||||||
|
LEGACY_LOG_DIR="/var/log/openhop-repeater"
|
||||||
|
LEGACY_DATA_DIR="/var/lib/openhop-repeater"
|
||||||
|
|
||||||
# R2 Wheels Configuration improves install speed on ARM devices
|
# R2 Wheels Configuration improves install speed on ARM devices
|
||||||
R2_BASE_URL="https://wheel.pymc.dev/pymc_build_deps"
|
R2_BASE_URL="https://wheel.pymc.dev/pymc_build_deps"
|
||||||
R2_ENABLED=1 # Set to 0 to disable R2 wheels and always build from source
|
R2_ENABLED=1 # Set to 0 to disable R2 wheels and always build from source
|
||||||
@@ -21,7 +27,68 @@ R2_ENABLED=1 # Set to 0 to disable R2 wheels and always build from source
|
|||||||
# Virtual-environment helpers
|
# Virtual-environment helpers
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
# Create (or re-create) the dedicated venv for pymc_repeater
|
cleanup_stale_source_trees() {
|
||||||
|
local removed=0
|
||||||
|
local path
|
||||||
|
|
||||||
|
for path in \
|
||||||
|
"$INSTALL_DIR/repeater" \
|
||||||
|
"$INSTALL_DIR/openhop_core" \
|
||||||
|
"$INSTALL_DIR/openhop-repeater" \
|
||||||
|
"$INSTALL_DIR/openhop-core" \
|
||||||
|
"$LEGACY_INSTALL_DIR/repeater" \
|
||||||
|
"$LEGACY_INSTALL_DIR/openhop_core" \
|
||||||
|
"$LEGACY_INSTALL_DIR/openhop-repeater" \
|
||||||
|
"$LEGACY_INSTALL_DIR/openhop-core"
|
||||||
|
do
|
||||||
|
if [ -e "$path" ]; then
|
||||||
|
rm -rf "$path"
|
||||||
|
removed=1
|
||||||
|
echo " ✓ Removed stale source tree at $path"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$removed" -eq 0 ]; then
|
||||||
|
echo " ✓ No stale source-tree paths found"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_legacy_paths() {
|
||||||
|
local timestamp legacy current label backup_path
|
||||||
|
timestamp="$(date +%Y%m%d_%H%M%S)"
|
||||||
|
|
||||||
|
migrate_one_path() {
|
||||||
|
legacy="$1"
|
||||||
|
current="$2"
|
||||||
|
label="$3"
|
||||||
|
|
||||||
|
if [ ! -e "$legacy" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$current" 2>/dev/null || true
|
||||||
|
|
||||||
|
if [ ! -e "$current" ] || [ -z "$(ls -A "$current" 2>/dev/null)" ]; then
|
||||||
|
rm -rf "$current" 2>/dev/null || true
|
||||||
|
mv "$legacy" "$current"
|
||||||
|
echo " ✓ Migrated legacy $label path: $legacy -> $current"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp -an "$legacy"/. "$current"/ 2>/dev/null || true
|
||||||
|
backup_path="${legacy}.migrated.${timestamp}"
|
||||||
|
mv "$legacy" "$backup_path"
|
||||||
|
echo " ✓ Merged legacy $label data into $current"
|
||||||
|
echo " ✓ Archived legacy $label path at $backup_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate_one_path "$LEGACY_CONFIG_DIR" "$CONFIG_DIR" "config"
|
||||||
|
migrate_one_path "$LEGACY_LOG_DIR" "$LOG_DIR" "log"
|
||||||
|
migrate_one_path "$LEGACY_DATA_DIR" "$DATA_DIR" "data"
|
||||||
|
migrate_one_path "$LEGACY_INSTALL_DIR" "$INSTALL_DIR" "install"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create (or re-create) the dedicated venv for openhop_repeater
|
||||||
ensure_venv() {
|
ensure_venv() {
|
||||||
if [ ! -x "$VENV_PYTHON" ]; then
|
if [ ! -x "$VENV_PYTHON" ]; then
|
||||||
echo ">>> Creating virtual environment at $VENV_DIR ..."
|
echo ">>> Creating virtual environment at $VENV_DIR ..."
|
||||||
@@ -40,15 +107,16 @@ migrate_to_venv() {
|
|||||||
ensure_venv
|
ensure_venv
|
||||||
|
|
||||||
# 2. Remove legacy PYTHONPATH from the service unit
|
# 2. Remove legacy PYTHONPATH from the service unit
|
||||||
local svc_unit="/etc/systemd/system/pymc-repeater.service"
|
local svc_unit="/etc/systemd/system/openhop-repeater.service"
|
||||||
if [ -f "$svc_unit" ]; then
|
if [ -f "$svc_unit" ]; then
|
||||||
if grep -q 'PYTHONPATH' "$svc_unit" 2>/dev/null; then
|
if grep -q 'PYTHONPATH' "$svc_unit" 2>/dev/null; then
|
||||||
sed -i '/^Environment=.*PYTHONPATH/d' "$svc_unit"
|
sed -i '/^Environment=.*PYTHONPATH/d' "$svc_unit"
|
||||||
echo " ✓ Removed legacy PYTHONPATH from service unit"
|
echo " ✓ Removed legacy PYTHONPATH from service unit"
|
||||||
fi
|
fi
|
||||||
# 3. Fix WorkingDirectory if still pointing at old source
|
# 3. Fix WorkingDirectory if still pointing at old source
|
||||||
if grep -q 'WorkingDirectory=/opt/pymc_repeater' "$svc_unit" 2>/dev/null; then
|
if grep -q 'WorkingDirectory=/opt/openhop_repeater\|WorkingDirectory=/opt/openhop-repeater' "$svc_unit" 2>/dev/null; then
|
||||||
sed -i 's|WorkingDirectory=/opt/pymc_repeater|WorkingDirectory=/var/lib/pymc_repeater|' "$svc_unit"
|
sed -i 's|WorkingDirectory=/opt/openhop_repeater|WorkingDirectory=/var/lib/openhop_repeater|' "$svc_unit"
|
||||||
|
sed -i 's|WorkingDirectory=/opt/openhop-repeater|WorkingDirectory=/var/lib/openhop_repeater|' "$svc_unit"
|
||||||
echo " ✓ Fixed WorkingDirectory in service unit"
|
echo " ✓ Fixed WorkingDirectory in service unit"
|
||||||
fi
|
fi
|
||||||
# 4. Ensure ExecStart uses the venv python
|
# 4. Ensure ExecStart uses the venv python
|
||||||
@@ -60,15 +128,12 @@ migrate_to_venv() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# 5. Remove the package from system python (best-effort)
|
# 5. Remove the package from system python (best-effort)
|
||||||
python3 -m pip uninstall -y pymc_repeater 2>/dev/null || true
|
python3 -m pip uninstall -y openhop_repeater 2>/dev/null || true
|
||||||
python3 -m pip uninstall -y pymc_core 2>/dev/null || true
|
python3 -m pip uninstall -y openhop_core 2>/dev/null || true
|
||||||
echo " ✓ Cleaned up system-level packages (if any)"
|
echo " ✓ Cleaned up system-level packages (if any)"
|
||||||
|
|
||||||
# 6. Remove stale source trees that could shadow the venv package
|
# 6. Remove stale source trees that could shadow the venv package
|
||||||
if [ -d "$INSTALL_DIR/repeater" ]; then
|
cleanup_stale_source_trees
|
||||||
rm -rf "$INSTALL_DIR/repeater"
|
|
||||||
echo " ✓ Removed stale source tree from $INSTALL_DIR/repeater"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is_silent_flag() {
|
is_silent_flag() {
|
||||||
@@ -117,22 +182,22 @@ fi
|
|||||||
|
|
||||||
# Function to show info box
|
# Function to show info box
|
||||||
show_info() {
|
show_info() {
|
||||||
$DIALOG --backtitle "pyMC Repeater Management" --title "$1" --msgbox "$2" 12 70
|
$DIALOG --backtitle "openHop Repeater Management" --title "$1" --msgbox "$2" 12 70
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to show error box
|
# Function to show error box
|
||||||
show_error() {
|
show_error() {
|
||||||
$DIALOG --backtitle "pyMC Repeater Management" --title "Error" --msgbox "$1" 8 60
|
$DIALOG --backtitle "openHop Repeater Management" --title "Error" --msgbox "$1" 8 60
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to ask yes/no question
|
# Function to ask yes/no question
|
||||||
ask_yes_no() {
|
ask_yes_no() {
|
||||||
$DIALOG --backtitle "pyMC Repeater Management" --title "$1" --yesno "$2" 10 70
|
$DIALOG --backtitle "openHop Repeater Management" --title "$1" --yesno "$2" 10 70
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to show progress
|
# Function to show progress
|
||||||
show_progress() {
|
show_progress() {
|
||||||
echo "$2" | $DIALOG --backtitle "pyMC Repeater Management" --title "$1" --gauge "$3" 8 70 0
|
echo "$2" | $DIALOG --backtitle "openHop Repeater Management" --title "$1" --gauge "$3" 8 70 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to check if service exists
|
# Function to check if service exists
|
||||||
@@ -159,11 +224,11 @@ is_enabled() {
|
|||||||
get_version() {
|
get_version() {
|
||||||
# Read version from the pip-installed package in the venv
|
# Read version from the pip-installed package in the venv
|
||||||
if [ -x "$VENV_PYTHON" ]; then
|
if [ -x "$VENV_PYTHON" ]; then
|
||||||
"$VENV_PYTHON" -c "from importlib.metadata import version; print(version('pymc_repeater'))" 2>/dev/null \
|
"$VENV_PYTHON" -c "from importlib.metadata import version; print(version('openhop_repeater'))" 2>/dev/null \
|
||||||
|| echo "not installed"
|
|| echo "not installed"
|
||||||
else
|
else
|
||||||
# Fallback: try system python for pre-migration installs
|
# Fallback: try system python for pre-migration installs
|
||||||
python3 -c "from importlib.metadata import version; print(version('pymc_repeater'))" 2>/dev/null \
|
python3 -c "from importlib.metadata import version; print(version('openhop_repeater'))" 2>/dev/null \
|
||||||
|| echo "not installed"
|
|| echo "not installed"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -183,11 +248,11 @@ get_status_display() {
|
|||||||
show_main_menu() {
|
show_main_menu() {
|
||||||
local status=$(get_status_display)
|
local status=$(get_status_display)
|
||||||
|
|
||||||
CHOICE=$($DIALOG --backtitle "pyMC Repeater Management" --title "pyMC Repeater Management" --menu "\nCurrent Status: $status\n\nChoose an action:" 18 70 9 \
|
CHOICE=$($DIALOG --backtitle "openHop Repeater Management" --title "openHop Repeater Management" --menu "\nCurrent Status: $status\n\nChoose an action:" 18 70 9 \
|
||||||
"install" "Install pyMC Repeater" \
|
"install" "Install openHop Repeater" \
|
||||||
"upgrade" "Upgrade existing installation" \
|
"upgrade" "Upgrade existing installation" \
|
||||||
"reset" "reset existing installation to defaults" \
|
"reset" "reset existing installation to defaults" \
|
||||||
"uninstall" "Remove pyMC Repeater completely" \
|
"uninstall" "Remove openHop Repeater completely" \
|
||||||
"config" "Configure radio settings" \
|
"config" "Configure radio settings" \
|
||||||
"start" "Start the service" \
|
"start" "Start the service" \
|
||||||
"stop" "Stop the service" \
|
"stop" "Stop the service" \
|
||||||
@@ -199,7 +264,7 @@ show_main_menu() {
|
|||||||
case $CHOICE in
|
case $CHOICE in
|
||||||
"install")
|
"install")
|
||||||
if is_installed; then
|
if is_installed; then
|
||||||
show_error "pyMC Repeater is already installed!\n\nUse 'upgrade' to update or 'uninstall' first."
|
show_error "openHop Repeater is already installed!\n\nUse 'upgrade' to update or 'uninstall' first."
|
||||||
else
|
else
|
||||||
install_repeater
|
install_repeater
|
||||||
fi
|
fi
|
||||||
@@ -208,21 +273,21 @@ show_main_menu() {
|
|||||||
if is_installed; then
|
if is_installed; then
|
||||||
upgrade_repeater "false"
|
upgrade_repeater "false"
|
||||||
else
|
else
|
||||||
show_error "pyMC Repeater is not installed!\n\nUse 'install' first."
|
show_error "openHop Repeater is not installed!\n\nUse 'install' first."
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"reset")
|
"reset")
|
||||||
if is_installed; then
|
if is_installed; then
|
||||||
reset_repeater
|
reset_repeater
|
||||||
else
|
else
|
||||||
show_error "pyMC Repeater is not installed!\n\nUse 'install' first."
|
show_error "openHop Repeater is not installed!\n\nUse 'install' first."
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"uninstall")
|
"uninstall")
|
||||||
if is_installed; then
|
if is_installed; then
|
||||||
uninstall_repeater
|
uninstall_repeater
|
||||||
else
|
else
|
||||||
show_error "pyMC Repeater is not installed."
|
show_error "openHop Repeater is not installed."
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"config")
|
"config")
|
||||||
@@ -240,7 +305,7 @@ show_main_menu() {
|
|||||||
"logs")
|
"logs")
|
||||||
clear
|
clear
|
||||||
echo -e "\033[1;36m╔══════════════════════════════════════════════════════════════════════╗\033[0m"
|
echo -e "\033[1;36m╔══════════════════════════════════════════════════════════════════════╗\033[0m"
|
||||||
echo -e "\033[1;36m║\033[0m \033[1;37mpyMC Repeater - Live Logs\033[0m \033[1;36m║\033[0m"
|
echo -e "\033[1;36m║\033[0m \033[1;37mopenHop Repeater - Live Logs\033[0m \033[1;36m║\033[0m"
|
||||||
echo -e "\033[1;36m║\033[0m \033[0;90m(Press Ctrl+C to return)\033[0m \033[1;36m║\033[0m"
|
echo -e "\033[1;36m║\033[0m \033[0;90m(Press Ctrl+C to return)\033[0m \033[1;36m║\033[0m"
|
||||||
echo -e "\033[1;36m╚══════════════════════════════════════════════════════════════════════╝\033[0m"
|
echo -e "\033[1;36m╚══════════════════════════════════════════════════════════════════════╝\033[0m"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -265,7 +330,7 @@ install_repeater() {
|
|||||||
|
|
||||||
# Welcome screen (Bypass if the script was passd with the "install" option, assume we want a silent install)
|
# Welcome screen (Bypass if the script was passd with the "install" option, assume we want a silent install)
|
||||||
if [[ "${1:-}" != "install" ]]; then
|
if [[ "${1:-}" != "install" ]]; then
|
||||||
$DIALOG --backtitle "pyMC Repeater Management" --title "Welcome" --msgbox "\nWelcome to pyMC Repeater Setup\n\nThis installer will configure your Linux system as a LoRa mesh network repeater.\n\nPress OK to continue..." 12 70
|
$DIALOG --backtitle "openHop Repeater Management" --title "Welcome" --msgbox "\nWelcome to openHop Repeater Setup\n\nThis installer will configure your Linux system as a LoRa mesh network repeater.\n\nPress OK to continue..." 12 70
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# SPI Check - Universal approach that works on all boards (skip for CH341 USB-SPI adapter)
|
# SPI Check - Universal approach that works on all boards (skip for CH341 USB-SPI adapter)
|
||||||
@@ -321,13 +386,13 @@ install_repeater() {
|
|||||||
# Installation progress
|
# Installation progress
|
||||||
echo ""
|
echo ""
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
echo " Installing pyMC Repeater"
|
echo " Installing openHop Repeater"
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
echo ">>> Creating service user..."
|
echo ">>> Creating service user..."
|
||||||
if ! id "$SERVICE_USER" &>/dev/null; then
|
if ! id "$SERVICE_USER" &>/dev/null; then
|
||||||
useradd --system --home /var/lib/pymc_repeater --shell /sbin/nologin "$SERVICE_USER"
|
useradd --system --home "$DATA_DIR" --shell /sbin/nologin "$SERVICE_USER"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
(
|
(
|
||||||
@@ -336,8 +401,12 @@ install_repeater() {
|
|||||||
getent group "$grp" >/dev/null 2>&1 && usermod -a -G "$grp" "$SERVICE_USER" 2>/dev/null || true
|
getent group "$grp" >/dev/null 2>&1 && usermod -a -G "$grp" "$SERVICE_USER" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "20"; echo "# Creating directories..."
|
echo "20"; echo "# Migrating legacy paths..."
|
||||||
mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater
|
migrate_legacy_paths
|
||||||
|
cleanup_stale_source_trees
|
||||||
|
|
||||||
|
echo "23"; echo "# Creating directories..."
|
||||||
|
mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" "$DATA_DIR"
|
||||||
|
|
||||||
echo "25"; echo "# Installing system dependencies..."
|
echo "25"; echo "# Installing system dependencies..."
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
@@ -367,9 +436,9 @@ install_repeater() {
|
|||||||
|
|
||||||
echo "29"; echo "# Installing files..."
|
echo "29"; echo "# Installing files..."
|
||||||
cp "$SCRIPT_DIR/manage.sh" "$INSTALL_DIR/" 2>/dev/null || true
|
cp "$SCRIPT_DIR/manage.sh" "$INSTALL_DIR/" 2>/dev/null || true
|
||||||
cp "$SCRIPT_DIR/pymc-repeater.service" "$INSTALL_DIR/" 2>/dev/null || true
|
cp "$SCRIPT_DIR/openhop-repeater.service" "$INSTALL_DIR/" 2>/dev/null || true
|
||||||
cp "$SCRIPT_DIR/radio-settings.json" /var/lib/pymc_repeater/ 2>/dev/null || true
|
cp "$SCRIPT_DIR/radio-settings.json" "$DATA_DIR/" 2>/dev/null || true
|
||||||
cp "$SCRIPT_DIR/radio-presets.json" /var/lib/pymc_repeater/ 2>/dev/null || true
|
cp "$SCRIPT_DIR/radio-presets.json" "$DATA_DIR/" 2>/dev/null || true
|
||||||
|
|
||||||
echo "45"; echo "# Installing configuration..."
|
echo "45"; echo "# Installing configuration..."
|
||||||
cp "$SCRIPT_DIR/config.yaml.example" "$CONFIG_DIR/config.yaml.example"
|
cp "$SCRIPT_DIR/config.yaml.example" "$CONFIG_DIR/config.yaml.example"
|
||||||
@@ -378,28 +447,28 @@ install_repeater() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "55"; echo "# Installing systemd service..."
|
echo "55"; echo "# Installing systemd service..."
|
||||||
cp "$SCRIPT_DIR/pymc-repeater.service" /etc/systemd/system/
|
cp "$SCRIPT_DIR/openhop-repeater.service" /etc/systemd/system/
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
|
||||||
echo "58"; echo "# Installing udev rules for CH341..."
|
echo "58"; echo "# Installing udev rules for CH341..."
|
||||||
if [ -f "$SCRIPT_DIR/../pyMC_core/99-ch341.rules" ]; then
|
if [ -f "$SCRIPT_DIR/../openhop-core/99-ch341.rules" ]; then
|
||||||
cp "$SCRIPT_DIR/../pyMC_core/99-ch341.rules" /etc/udev/rules.d/99-ch341.rules
|
cp "$SCRIPT_DIR/../openhop-core/99-ch341.rules" /etc/udev/rules.d/99-ch341.rules
|
||||||
udevadm control --reload-rules 2>/dev/null || true
|
udevadm control --reload-rules 2>/dev/null || true
|
||||||
udevadm trigger 2>/dev/null || true
|
udevadm trigger 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "65"; echo "# Setting permissions..."
|
echo "65"; echo "# Setting permissions..."
|
||||||
# Venv stays root-owned (pip runs as root); service user only needs read+execute
|
# Venv stays root-owned (pip runs as root); service user only needs read+execute
|
||||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater
|
chown -R "$SERVICE_USER:$SERVICE_USER" "$CONFIG_DIR" "$LOG_DIR" "$DATA_DIR"
|
||||||
chmod 750 "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater
|
chmod 750 "$CONFIG_DIR" "$LOG_DIR" "$DATA_DIR"
|
||||||
# Ensure manage.sh and support files in INSTALL_DIR are accessible
|
# Ensure manage.sh and support files in INSTALL_DIR are accessible
|
||||||
chown root:root "$INSTALL_DIR"
|
chown root:root "$INSTALL_DIR"
|
||||||
chmod 755 "$INSTALL_DIR"
|
chmod 755 "$INSTALL_DIR"
|
||||||
# Ensure the service user can create subdirectories in their home directory
|
# Ensure the service user can create subdirectories in their home directory
|
||||||
chmod 755 /var/lib/pymc_repeater
|
chmod 755 "$DATA_DIR"
|
||||||
# Pre-create the .config directory that the service will need
|
# Pre-create the .config directory that the service will need
|
||||||
mkdir -p /var/lib/pymc_repeater/.config/pymc_repeater
|
mkdir -p "$DATA_DIR/.config/openhop_repeater"
|
||||||
chown -R "$SERVICE_USER:$SERVICE_USER" /var/lib/pymc_repeater/.config
|
chown -R "$SERVICE_USER:$SERVICE_USER" "$DATA_DIR/.config"
|
||||||
|
|
||||||
# Configure polkit for passwordless service restart
|
# Configure polkit for passwordless service restart
|
||||||
|
|
||||||
@@ -410,38 +479,38 @@ install_repeater() {
|
|||||||
echo "Polkit 0.106 or greater detected, using rules file"
|
echo "Polkit 0.106 or greater detected, using rules file"
|
||||||
echo ">>> Configuring polkit for service management..."
|
echo ">>> Configuring polkit for service management..."
|
||||||
mkdir -p /etc/polkit-1/rules.d
|
mkdir -p /etc/polkit-1/rules.d
|
||||||
cat > /etc/polkit-1/rules.d/10-pymc-repeater.rules <<'EOF'
|
cat > /etc/polkit-1/rules.d/10-openhop-repeater.rules <<'EOF'
|
||||||
polkit.addRule(function(action, subject) {
|
polkit.addRule(function(action, subject) {
|
||||||
if (action.id == "org.freedesktop.systemd1.manage-units" &&
|
if (action.id == "org.freedesktop.systemd1.manage-units" &&
|
||||||
action.lookup("unit") == "pymc-repeater.service" &&
|
action.lookup("unit") == "openhop-repeater.service" &&
|
||||||
subject.user == "repeater") {
|
subject.user == "repeater") {
|
||||||
return polkit.Result.YES;
|
return polkit.Result.YES;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
EOF
|
EOF
|
||||||
chmod 0644 /etc/polkit-1/rules.d/10-pymc-repeater.rules
|
chmod 0644 /etc/polkit-1/rules.d/10-openhop-repeater.rules
|
||||||
else
|
else
|
||||||
echo "Polkit 0.105 or less detected, using pkla file"
|
echo "Polkit 0.105 or less detected, using pkla file"
|
||||||
mkdir -p /etc/polkit-1/localauthority/50-local.d
|
mkdir -p /etc/polkit-1/localauthority/50-local.d
|
||||||
cat > /etc/polkit-1/localauthority/50-local.d/10-pymc-repeater.pkla <<'EOF'
|
cat > /etc/polkit-1/localauthority/50-local.d/10-openhop-repeater.pkla <<'EOF'
|
||||||
[Allow repeater to restart pymc-repeater service]
|
[Allow repeater to restart openhop-repeater service]
|
||||||
Identity=unix-user:repeater
|
Identity=unix-user:repeater
|
||||||
Action=org.freedesktop.systemd1.manage-units
|
Action=org.freedesktop.systemd1.manage-units
|
||||||
ResultAny=yes
|
ResultAny=yes
|
||||||
ResultInactive=yes
|
ResultInactive=yes
|
||||||
ResultActive=yes
|
ResultActive=yes
|
||||||
EOF
|
EOF
|
||||||
chmod 0644 /etc/polkit-1/localauthority/50-local.d/10-pymc-repeater.pkla
|
chmod 0644 /etc/polkit-1/localauthority/50-local.d/10-openhop-repeater.pkla
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Also configure sudoers as fallback for service restart
|
# Also configure sudoers as fallback for service restart
|
||||||
echo ">>> Configuring sudoers for service management..."
|
echo ">>> Configuring sudoers for service management..."
|
||||||
mkdir -p /etc/sudoers.d
|
mkdir -p /etc/sudoers.d
|
||||||
cat > /etc/sudoers.d/pymc-repeater <<'EOF'
|
cat > /etc/sudoers.d/openhop-repeater <<'EOF'
|
||||||
# Allow repeater user to manage the pymc-repeater service without password
|
# Allow repeater user to manage the openhop-repeater service without password
|
||||||
repeater ALL=(root) NOPASSWD: /usr/bin/systemctl restart pymc-repeater, /usr/bin/systemctl stop pymc-repeater, /usr/bin/systemctl start pymc-repeater, /usr/bin/systemctl status pymc-repeater, /usr/local/bin/pymc-do-upgrade
|
repeater ALL=(root) NOPASSWD: /usr/bin/systemctl restart openhop-repeater, /usr/bin/systemctl stop openhop-repeater, /usr/bin/systemctl start openhop-repeater, /usr/bin/systemctl status openhop-repeater, /usr/local/bin/pymc-do-upgrade
|
||||||
EOF
|
EOF
|
||||||
chmod 0440 /etc/sudoers.d/pymc-repeater
|
chmod 0440 /etc/sudoers.d/openhop-repeater
|
||||||
|
|
||||||
echo ">>> Installing OTA upgrade wrapper..."
|
echo ">>> Installing OTA upgrade wrapper..."
|
||||||
cat > /usr/local/bin/pymc-do-upgrade <<'UPGRADEEOF'
|
cat > /usr/local/bin/pymc-do-upgrade <<'UPGRADEEOF'
|
||||||
@@ -451,7 +520,7 @@ EOF
|
|||||||
set -e
|
set -e
|
||||||
CHANNEL="${1:-main}"
|
CHANNEL="${1:-main}"
|
||||||
PRETEND_VERSION="${2:-}"
|
PRETEND_VERSION="${2:-}"
|
||||||
VENV_DIR="/opt/pymc_repeater/venv"
|
VENV_DIR="/opt/openhop_repeater/venv"
|
||||||
VENV_PIP="$VENV_DIR/bin/pip"
|
VENV_PIP="$VENV_DIR/bin/pip"
|
||||||
VENV_PYTHON="$VENV_DIR/bin/python"
|
VENV_PYTHON="$VENV_DIR/bin/python"
|
||||||
# Validate: only allow safe git ref characters
|
# Validate: only allow safe git ref characters
|
||||||
@@ -468,14 +537,22 @@ if [ ! -x "$VENV_PYTHON" ]; then
|
|||||||
python3 -m venv --system-site-packages "$VENV_DIR"
|
python3 -m venv --system-site-packages "$VENV_DIR"
|
||||||
"$VENV_PIP" install --upgrade pip setuptools wheel >/dev/null 2>&1 || true
|
"$VENV_PIP" install --upgrade pip setuptools wheel >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
# ---- Legacy path migration: openhop-repeater -> openhop_repeater ----
|
||||||
|
if [ -d /opt/openhop-repeater ] && [ ! -e /opt/openhop_repeater ]; then
|
||||||
|
mv /opt/openhop-repeater /opt/openhop_repeater
|
||||||
|
fi
|
||||||
# ---- Migration: clean up legacy service unit issues ----
|
# ---- Migration: clean up legacy service unit issues ----
|
||||||
SVC_UNIT=/etc/systemd/system/pymc-repeater.service
|
SVC_UNIT=/etc/systemd/system/openhop-repeater.service
|
||||||
if grep -q 'PYTHONPATH' "$SVC_UNIT" 2>/dev/null; then
|
if grep -q 'PYTHONPATH' "$SVC_UNIT" 2>/dev/null; then
|
||||||
sed -i '/^Environment=.*PYTHONPATH/d' "$SVC_UNIT"
|
sed -i '/^Environment=.*PYTHONPATH/d' "$SVC_UNIT"
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
fi
|
fi
|
||||||
if grep -q 'WorkingDirectory=/opt/pymc_repeater' "$SVC_UNIT" 2>/dev/null; then
|
if grep -q 'WorkingDirectory=/opt/openhop_repeater' "$SVC_UNIT" 2>/dev/null; then
|
||||||
sed -i 's|WorkingDirectory=/opt/pymc_repeater|WorkingDirectory=/var/lib/pymc_repeater|' "$SVC_UNIT"
|
sed -i 's|WorkingDirectory=/opt/openhop_repeater|WorkingDirectory=/var/lib/openhop_repeater|' "$SVC_UNIT"
|
||||||
|
systemctl daemon-reload
|
||||||
|
fi
|
||||||
|
if grep -q 'WorkingDirectory=/opt/openhop-repeater' "$SVC_UNIT" 2>/dev/null; then
|
||||||
|
sed -i 's|WorkingDirectory=/opt/openhop-repeater|WorkingDirectory=/var/lib/openhop_repeater|' "$SVC_UNIT"
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
fi
|
fi
|
||||||
if grep -q 'ExecStart=/usr/bin/python3' "$SVC_UNIT" 2>/dev/null; then
|
if grep -q 'ExecStart=/usr/bin/python3' "$SVC_UNIT" 2>/dev/null; then
|
||||||
@@ -483,10 +560,13 @@ if grep -q 'ExecStart=/usr/bin/python3' "$SVC_UNIT" 2>/dev/null; then
|
|||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
fi
|
fi
|
||||||
# ---- Remove stale source trees that shadow the venv package ----
|
# ---- Remove stale source trees that shadow the venv package ----
|
||||||
[ -d /opt/pymc_repeater/repeater ] && rm -rf /opt/pymc_repeater/repeater
|
[ -d /opt/openhop_repeater/repeater ] && rm -rf /opt/openhop_repeater/repeater
|
||||||
|
[ -d /opt/openhop-repeater/repeater ] && rm -rf /opt/openhop-repeater/repeater
|
||||||
|
[ -d /opt/openhop_repeater/openhop-repeater ] && rm -rf /opt/openhop_repeater/openhop-repeater
|
||||||
|
[ -d /opt/openhop-repeater/openhop-repeater ] && rm -rf /opt/openhop-repeater/openhop-repeater
|
||||||
# ---- Remove old system-level packages to avoid confusion ----
|
# ---- Remove old system-level packages to avoid confusion ----
|
||||||
python3 -m pip uninstall -y pymc_repeater 2>/dev/null || true
|
python3 -m pip uninstall -y openhop_repeater 2>/dev/null || true
|
||||||
python3 -m pip uninstall -y pymc_core 2>/dev/null || true
|
python3 -m pip uninstall -y openhop_core 2>/dev/null || true
|
||||||
# ---- Try R2 wheels first for faster OTA upgrades ----
|
# ---- Try R2 wheels first for faster OTA upgrades ----
|
||||||
R2_BASE_URL="https://wheel.pymc.dev/pymc_build_deps"
|
R2_BASE_URL="https://wheel.pymc.dev/pymc_build_deps"
|
||||||
MACHINE_ARCH=$(uname -m)
|
MACHINE_ARCH=$(uname -m)
|
||||||
@@ -502,14 +582,14 @@ if [ -n "$ARCH_TAG" ]; then
|
|||||||
echo "[pymc-do-upgrade] Trying dependencies from R2 wheels..."
|
echo "[pymc-do-upgrade] Trying dependencies from R2 wheels..."
|
||||||
"$VENV_PIP" install --find-links "${WHEEL_BASE}/index.html" --no-cache-dir "pycryptodome>=3.23.0" "PyNaCl>=1.5.0" cffi "pyyaml>=6.0.0" 2>/dev/null || true
|
"$VENV_PIP" install --find-links "${WHEEL_BASE}/index.html" --no-cache-dir "pycryptodome>=3.23.0" "PyNaCl>=1.5.0" cffi "pyyaml>=6.0.0" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
# ---- Install pymc_repeater from git ----
|
# ---- Install openhop_repeater from git ----
|
||||||
if "$VENV_PIP" install \
|
if "$VENV_PIP" install \
|
||||||
--upgrade \
|
--upgrade \
|
||||||
--no-cache-dir \
|
--no-cache-dir \
|
||||||
"pymc_repeater[hardware] @ git+https://github.com/rightup/pyMC_Repeater.git@${CHANNEL}"; then
|
"openhop_repeater[hardware] @ git+https://github.com/rightup/openhop-repeater.git@${CHANNEL}"; then
|
||||||
# Keep web/OTA updates aligned with manage.sh install/upgrade defaults.
|
# Keep web/OTA updates aligned with manage.sh install/upgrade defaults.
|
||||||
RADIO_BASE_URL="https://raw.githubusercontent.com/rightup/pyMC_Repeater/${CHANNEL}"
|
RADIO_BASE_URL="https://raw.githubusercontent.com/rightup/openhop-repeater/${CHANNEL}"
|
||||||
RADIO_STORAGE_DIR="/var/lib/pymc_repeater"
|
RADIO_STORAGE_DIR="/var/lib/openhop_repeater"
|
||||||
mkdir -p "$RADIO_STORAGE_DIR"
|
mkdir -p "$RADIO_STORAGE_DIR"
|
||||||
wget -qO "$RADIO_STORAGE_DIR/radio-settings.json" "${RADIO_BASE_URL}/radio-settings.json" 2>/dev/null || true
|
wget -qO "$RADIO_STORAGE_DIR/radio-settings.json" "${RADIO_BASE_URL}/radio-settings.json" 2>/dev/null || true
|
||||||
wget -qO "$RADIO_STORAGE_DIR/radio-presets.json" "${RADIO_BASE_URL}/radio-presets.json" 2>/dev/null || true
|
wget -qO "$RADIO_STORAGE_DIR/radio-presets.json" "${RADIO_BASE_URL}/radio-presets.json" 2>/dev/null || true
|
||||||
@@ -523,13 +603,13 @@ UPGRADEEOF
|
|||||||
systemctl enable "$SERVICE_NAME"
|
systemctl enable "$SERVICE_NAME"
|
||||||
|
|
||||||
echo "90"; echo "# Installation files complete..."
|
echo "90"; echo "# Installation files complete..."
|
||||||
) | $DIALOG --backtitle "pyMC Repeater Management" --title "Installing" --gauge "Setting up pyMC Repeater..." 8 70 0
|
) | $DIALOG --backtitle "openHop Repeater Management" --title "Installing" --gauge "Setting up openHop Repeater..." 8 70 0
|
||||||
|
|
||||||
# Install Python package outside of progress gauge for better error handling
|
# Install Python package outside of progress gauge for better error handling
|
||||||
clear
|
clear
|
||||||
echo "=== Installing Python Dependencies ==="
|
echo "=== Installing Python Dependencies ==="
|
||||||
echo ""
|
echo ""
|
||||||
echo "Installing pymc_repeater and dependencies (including pymc_core from PyPI)..."
|
echo "Installing openhop_repeater and dependencies (including openhop_core from PyPI)..."
|
||||||
echo "This may take a few minutes..."
|
echo "This may take a few minutes..."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -556,7 +636,7 @@ UPGRADEEOF
|
|||||||
# Ensure venv exists
|
# Ensure venv exists
|
||||||
ensure_venv
|
ensure_venv
|
||||||
|
|
||||||
echo "Installing pymc_repeater into venv ($VENV_DIR)..."
|
echo "Installing openhop_repeater into venv ($VENV_DIR)..."
|
||||||
|
|
||||||
# Attempt R2 wheels first for faster installation
|
# Attempt R2 wheels first for faster installation
|
||||||
if [ "$R2_ENABLED" -eq 1 ]; then
|
if [ "$R2_ENABLED" -eq 1 ]; then
|
||||||
@@ -660,7 +740,7 @@ reset_repeater() {
|
|||||||
|
|
||||||
local current_version=$(get_version)
|
local current_version=$(get_version)
|
||||||
|
|
||||||
if ask_yes_no "Confirm Reset of pyMC Repeater restoring to default configuration.\n\nContinue?"; then
|
if ask_yes_no "Confirm Reset of openHop Repeater restoring to default configuration.\n\nContinue?"; then
|
||||||
|
|
||||||
# Show info that upgrade is starting
|
# Show info that upgrade is starting
|
||||||
show_info "Reseting" "Starting reset process...\n\nProgress will be shown in the terminal."
|
show_info "Reseting" "Starting reset process...\n\nProgress will be shown in the terminal."
|
||||||
@@ -731,7 +811,7 @@ upgrade_repeater() {
|
|||||||
local current_version=$(get_version)
|
local current_version=$(get_version)
|
||||||
|
|
||||||
if [[ "$silent" != "true" ]]; then
|
if [[ "$silent" != "true" ]]; then
|
||||||
if ! ask_yes_no "Confirm Upgrade" "Current version: $current_version\n\nThis will upgrade pyMC Repeater while preserving your configuration.\n\nContinue?"; then
|
if ! ask_yes_no "Confirm Upgrade" "Current version: $current_version\n\nThis will upgrade openHop Repeater while preserving your configuration.\n\nContinue?"; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -746,6 +826,10 @@ upgrade_repeater() {
|
|||||||
echo "[1/9] Stopping service..."
|
echo "[1/9] Stopping service..."
|
||||||
systemctl stop "$SERVICE_NAME" 2>/dev/null || true
|
systemctl stop "$SERVICE_NAME" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "[1.5/9] Migrating legacy paths..."
|
||||||
|
migrate_legacy_paths
|
||||||
|
cleanup_stale_source_trees
|
||||||
|
|
||||||
echo "[2/9] Backing up configuration..."
|
echo "[2/9] Backing up configuration..."
|
||||||
if [ -d "$CONFIG_DIR" ]; then
|
if [ -d "$CONFIG_DIR" ]; then
|
||||||
cp -r "$CONFIG_DIR" "$CONFIG_DIR.backup.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
|
cp -r "$CONFIG_DIR" "$CONFIG_DIR.backup.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
|
||||||
@@ -777,11 +861,11 @@ upgrade_repeater() {
|
|||||||
|
|
||||||
echo "[4/9] Installing files..."
|
echo "[4/9] Installing files..."
|
||||||
SCRIPT_DIR="$(dirname "$0")"
|
SCRIPT_DIR="$(dirname "$0")"
|
||||||
if ! cp "$SCRIPT_DIR/pymc-repeater.service" /etc/systemd/system/; then
|
if ! cp "$SCRIPT_DIR/openhop-repeater.service" /etc/systemd/system/; then
|
||||||
echo " ⚠ Warning: Failed to update service file – old service file may remain"
|
echo " ⚠ Warning: Failed to update service file – old service file may remain"
|
||||||
fi
|
fi
|
||||||
cp "$SCRIPT_DIR/radio-settings.json" /var/lib/pymc_repeater/ 2>/dev/null || true
|
cp "$SCRIPT_DIR/radio-settings.json" "$DATA_DIR/" 2>/dev/null || true
|
||||||
cp "$SCRIPT_DIR/radio-presets.json" /var/lib/pymc_repeater/ 2>/dev/null || true
|
cp "$SCRIPT_DIR/radio-presets.json" "$DATA_DIR/" 2>/dev/null || true
|
||||||
echo " ✓ Files updated"
|
echo " ✓ Files updated"
|
||||||
|
|
||||||
echo "[5/9] Validating and updating configuration..."
|
echo "[5/9] Validating and updating configuration..."
|
||||||
@@ -797,8 +881,8 @@ upgrade_repeater() {
|
|||||||
done
|
done
|
||||||
# Install/update CH341 udev rules
|
# Install/update CH341 udev rules
|
||||||
SCRIPT_DIR_UPGRADE="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR_UPGRADE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
if [ -f "$SCRIPT_DIR_UPGRADE/../pyMC_core/99-ch341.rules" ]; then
|
if [ -f "$SCRIPT_DIR_UPGRADE/../openhop-core/99-ch341.rules" ]; then
|
||||||
cp "$SCRIPT_DIR_UPGRADE/../pyMC_core/99-ch341.rules" /etc/udev/rules.d/99-ch341.rules
|
cp "$SCRIPT_DIR_UPGRADE/../openhop-core/99-ch341.rules" /etc/udev/rules.d/99-ch341.rules
|
||||||
udevadm control --reload-rules 2>/dev/null || true
|
udevadm control --reload-rules 2>/dev/null || true
|
||||||
udevadm trigger 2>/dev/null || true
|
udevadm trigger 2>/dev/null || true
|
||||||
echo " ✓ CH341 udev rules updated"
|
echo " ✓ CH341 udev rules updated"
|
||||||
@@ -810,15 +894,15 @@ upgrade_repeater() {
|
|||||||
echo "[6/9] Fixing permissions..."
|
echo "[6/9] Fixing permissions..."
|
||||||
|
|
||||||
# Venv stays root-owned (pip runs as root); service user only needs read+execute
|
# Venv stays root-owned (pip runs as root); service user only needs read+execute
|
||||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater 2>/dev/null || true
|
chown -R "$SERVICE_USER:$SERVICE_USER" "$CONFIG_DIR" "$LOG_DIR" "$DATA_DIR" 2>/dev/null || true
|
||||||
chown root:root "$INSTALL_DIR" 2>/dev/null || true
|
chown root:root "$INSTALL_DIR" 2>/dev/null || true
|
||||||
chmod 755 "$INSTALL_DIR" 2>/dev/null || true
|
chmod 755 "$INSTALL_DIR" 2>/dev/null || true
|
||||||
chmod 750 "$CONFIG_DIR" "$LOG_DIR" 2>/dev/null || true
|
chmod 750 "$CONFIG_DIR" "$LOG_DIR" 2>/dev/null || true
|
||||||
chmod 755 /var/lib/pymc_repeater 2>/dev/null || true
|
chmod 755 "$DATA_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
# Pre-create the .config directory that the service will need
|
# Pre-create the .config directory that the service will need
|
||||||
mkdir -p /var/lib/pymc_repeater/.config/pymc_repeater 2>/dev/null || true
|
mkdir -p "$DATA_DIR/.config/openhop_repeater" 2>/dev/null || true
|
||||||
chown -R "$SERVICE_USER:$SERVICE_USER" /var/lib/pymc_repeater/.config 2>/dev/null || true
|
chown -R "$SERVICE_USER:$SERVICE_USER" "$DATA_DIR/.config" 2>/dev/null || true
|
||||||
|
|
||||||
# Configure polkit for passwordless service restart
|
# Configure polkit for passwordless service restart
|
||||||
POLKIT_VERSION=$(pkaction --version 2>/dev/null | awk '{print $NF}')
|
POLKIT_VERSION=$(pkaction --version 2>/dev/null | awk '{print $NF}')
|
||||||
@@ -826,36 +910,36 @@ upgrade_repeater() {
|
|||||||
echo "Polkit 0.106 or greater detected, using rules file"
|
echo "Polkit 0.106 or greater detected, using rules file"
|
||||||
echo ">>> Configuring polkit for service management..."
|
echo ">>> Configuring polkit for service management..."
|
||||||
mkdir -p /etc/polkit-1/rules.d
|
mkdir -p /etc/polkit-1/rules.d
|
||||||
cat > /etc/polkit-1/rules.d/10-pymc-repeater.rules <<'EOF'
|
cat > /etc/polkit-1/rules.d/10-openhop-repeater.rules <<'EOF'
|
||||||
polkit.addRule(function(action, subject) {
|
polkit.addRule(function(action, subject) {
|
||||||
if (action.id == "org.freedesktop.systemd1.manage-units" &&
|
if (action.id == "org.freedesktop.systemd1.manage-units" &&
|
||||||
action.lookup("unit") == "pymc-repeater.service" &&
|
action.lookup("unit") == "openhop-repeater.service" &&
|
||||||
subject.user == "repeater") {
|
subject.user == "repeater") {
|
||||||
return polkit.Result.YES;
|
return polkit.Result.YES;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
EOF
|
EOF
|
||||||
chmod 0644 /etc/polkit-1/rules.d/10-pymc-repeater.rules
|
chmod 0644 /etc/polkit-1/rules.d/10-openhop-repeater.rules
|
||||||
else
|
else
|
||||||
echo "Polkit 0.105 or less detected, using pkla file"
|
echo "Polkit 0.105 or less detected, using pkla file"
|
||||||
mkdir -p /etc/polkit-1/localauthority/50-local.d
|
mkdir -p /etc/polkit-1/localauthority/50-local.d
|
||||||
cat > /etc/polkit-1/localauthority/50-local.d/10-pymc-repeater.pkla <<'EOF'
|
cat > /etc/polkit-1/localauthority/50-local.d/10-openhop-repeater.pkla <<'EOF'
|
||||||
[Allow repeater to restart pymc-repeater service]
|
[Allow repeater to restart openhop-repeater service]
|
||||||
Identity=unix-user:repeater
|
Identity=unix-user:repeater
|
||||||
Action=org.freedesktop.systemd1.manage-units
|
Action=org.freedesktop.systemd1.manage-units
|
||||||
ResultAny=yes
|
ResultAny=yes
|
||||||
ResultInactive=yes
|
ResultInactive=yes
|
||||||
ResultActive=yes
|
ResultActive=yes
|
||||||
EOF
|
EOF
|
||||||
chmod 0644 /etc/polkit-1/localauthority/50-local.d/10-pymc-repeater.pkla
|
chmod 0644 /etc/polkit-1/localauthority/50-local.d/10-openhop-repeater.pkla
|
||||||
fi
|
fi
|
||||||
# Also configure sudoers as fallback for service restart
|
# Also configure sudoers as fallback for service restart
|
||||||
mkdir -p /etc/sudoers.d
|
mkdir -p /etc/sudoers.d
|
||||||
cat > /etc/sudoers.d/pymc-repeater <<'EOF'
|
cat > /etc/sudoers.d/openhop-repeater <<'EOF'
|
||||||
# Allow repeater user to manage the pymc-repeater service without password
|
# Allow repeater user to manage the openhop-repeater service without password
|
||||||
repeater ALL=(root) NOPASSWD: /usr/bin/systemctl restart pymc-repeater, /usr/bin/systemctl stop pymc-repeater, /usr/bin/systemctl start pymc-repeater, /usr/bin/systemctl status pymc-repeater, /usr/local/bin/pymc-do-upgrade
|
repeater ALL=(root) NOPASSWD: /usr/bin/systemctl restart openhop-repeater, /usr/bin/systemctl stop openhop-repeater, /usr/bin/systemctl start openhop-repeater, /usr/bin/systemctl status openhop-repeater, /usr/local/bin/pymc-do-upgrade
|
||||||
EOF
|
EOF
|
||||||
chmod 0440 /etc/sudoers.d/pymc-repeater
|
chmod 0440 /etc/sudoers.d/openhop-repeater
|
||||||
# Install / refresh OTA upgrade wrapper
|
# Install / refresh OTA upgrade wrapper
|
||||||
cat > /usr/local/bin/pymc-do-upgrade <<'UPGRADEEOF'
|
cat > /usr/local/bin/pymc-do-upgrade <<'UPGRADEEOF'
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
@@ -864,7 +948,7 @@ EOF
|
|||||||
set -e
|
set -e
|
||||||
CHANNEL="${1:-main}"
|
CHANNEL="${1:-main}"
|
||||||
PRETEND_VERSION="${2:-}"
|
PRETEND_VERSION="${2:-}"
|
||||||
VENV_DIR="/opt/pymc_repeater/venv"
|
VENV_DIR="/opt/openhop_repeater/venv"
|
||||||
VENV_PIP="$VENV_DIR/bin/pip"
|
VENV_PIP="$VENV_DIR/bin/pip"
|
||||||
VENV_PYTHON="$VENV_DIR/bin/python"
|
VENV_PYTHON="$VENV_DIR/bin/python"
|
||||||
# Validate: only allow safe git ref characters
|
# Validate: only allow safe git ref characters
|
||||||
@@ -881,14 +965,22 @@ if [ ! -x "$VENV_PYTHON" ]; then
|
|||||||
python3 -m venv --system-site-packages "$VENV_DIR"
|
python3 -m venv --system-site-packages "$VENV_DIR"
|
||||||
"$VENV_PIP" install --upgrade pip setuptools wheel >/dev/null 2>&1 || true
|
"$VENV_PIP" install --upgrade pip setuptools wheel >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
# ---- Legacy path migration: openhop-repeater -> openhop_repeater ----
|
||||||
|
if [ -d /opt/openhop-repeater ] && [ ! -e /opt/openhop_repeater ]; then
|
||||||
|
mv /opt/openhop-repeater /opt/openhop_repeater
|
||||||
|
fi
|
||||||
# ---- Migration: clean up legacy service unit issues ----
|
# ---- Migration: clean up legacy service unit issues ----
|
||||||
SVC_UNIT=/etc/systemd/system/pymc-repeater.service
|
SVC_UNIT=/etc/systemd/system/openhop-repeater.service
|
||||||
if grep -q 'PYTHONPATH' "$SVC_UNIT" 2>/dev/null; then
|
if grep -q 'PYTHONPATH' "$SVC_UNIT" 2>/dev/null; then
|
||||||
sed -i '/^Environment=.*PYTHONPATH/d' "$SVC_UNIT"
|
sed -i '/^Environment=.*PYTHONPATH/d' "$SVC_UNIT"
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
fi
|
fi
|
||||||
if grep -q 'WorkingDirectory=/opt/pymc_repeater' "$SVC_UNIT" 2>/dev/null; then
|
if grep -q 'WorkingDirectory=/opt/openhop_repeater' "$SVC_UNIT" 2>/dev/null; then
|
||||||
sed -i 's|WorkingDirectory=/opt/pymc_repeater|WorkingDirectory=/var/lib/pymc_repeater|' "$SVC_UNIT"
|
sed -i 's|WorkingDirectory=/opt/openhop_repeater|WorkingDirectory=/var/lib/openhop_repeater|' "$SVC_UNIT"
|
||||||
|
systemctl daemon-reload
|
||||||
|
fi
|
||||||
|
if grep -q 'WorkingDirectory=/opt/openhop-repeater' "$SVC_UNIT" 2>/dev/null; then
|
||||||
|
sed -i 's|WorkingDirectory=/opt/openhop-repeater|WorkingDirectory=/var/lib/openhop_repeater|' "$SVC_UNIT"
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
fi
|
fi
|
||||||
if grep -q 'ExecStart=/usr/bin/python3' "$SVC_UNIT" 2>/dev/null; then
|
if grep -q 'ExecStart=/usr/bin/python3' "$SVC_UNIT" 2>/dev/null; then
|
||||||
@@ -896,10 +988,13 @@ if grep -q 'ExecStart=/usr/bin/python3' "$SVC_UNIT" 2>/dev/null; then
|
|||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
fi
|
fi
|
||||||
# ---- Remove stale source trees that shadow the venv package ----
|
# ---- Remove stale source trees that shadow the venv package ----
|
||||||
[ -d /opt/pymc_repeater/repeater ] && rm -rf /opt/pymc_repeater/repeater
|
[ -d /opt/openhop_repeater/repeater ] && rm -rf /opt/openhop_repeater/repeater
|
||||||
|
[ -d /opt/openhop-repeater/repeater ] && rm -rf /opt/openhop-repeater/repeater
|
||||||
|
[ -d /opt/openhop_repeater/openhop-repeater ] && rm -rf /opt/openhop_repeater/openhop-repeater
|
||||||
|
[ -d /opt/openhop-repeater/openhop-repeater ] && rm -rf /opt/openhop-repeater/openhop-repeater
|
||||||
# ---- Remove old system-level packages to avoid confusion ----
|
# ---- Remove old system-level packages to avoid confusion ----
|
||||||
python3 -m pip uninstall -y pymc_repeater 2>/dev/null || true
|
python3 -m pip uninstall -y openhop_repeater 2>/dev/null || true
|
||||||
python3 -m pip uninstall -y pymc_core 2>/dev/null || true
|
python3 -m pip uninstall -y openhop_core 2>/dev/null || true
|
||||||
# ---- Try R2 wheels first for faster OTA upgrades ----
|
# ---- Try R2 wheels first for faster OTA upgrades ----
|
||||||
R2_BASE_URL="https://wheel.pymc.dev/pymc_build_deps"
|
R2_BASE_URL="https://wheel.pymc.dev/pymc_build_deps"
|
||||||
MACHINE_ARCH=$(uname -m)
|
MACHINE_ARCH=$(uname -m)
|
||||||
@@ -915,14 +1010,14 @@ python3 -m pip uninstall -y pymc_core 2>/dev/null || true
|
|||||||
echo "[pymc-do-upgrade] Trying dependencies from R2 wheels..."
|
echo "[pymc-do-upgrade] Trying dependencies from R2 wheels..."
|
||||||
"$VENV_PIP" install --find-links "${WHEEL_BASE}/index.html" --no-cache-dir "pycryptodome>=3.23.0" "PyNaCl>=1.5.0" cffi "pyyaml>=6.0.0" 2>/dev/null || true
|
"$VENV_PIP" install --find-links "${WHEEL_BASE}/index.html" --no-cache-dir "pycryptodome>=3.23.0" "PyNaCl>=1.5.0" cffi "pyyaml>=6.0.0" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
# ---- Install pymc_repeater from git ----
|
# ---- Install openhop_repeater from git ----
|
||||||
if "$VENV_PIP" install \
|
if "$VENV_PIP" install \
|
||||||
--upgrade \
|
--upgrade \
|
||||||
--no-cache-dir \
|
--no-cache-dir \
|
||||||
"pymc_repeater[hardware] @ git+https://github.com/rightup/pyMC_Repeater.git@${CHANNEL}"; then
|
"openhop_repeater[hardware] @ git+https://github.com/rightup/openhop-repeater.git@${CHANNEL}"; then
|
||||||
# Keep web/OTA updates aligned with manage.sh install/upgrade defaults.
|
# Keep web/OTA updates aligned with manage.sh install/upgrade defaults.
|
||||||
RADIO_BASE_URL="https://raw.githubusercontent.com/rightup/pyMC_Repeater/${CHANNEL}"
|
RADIO_BASE_URL="https://raw.githubusercontent.com/rightup/openhop-repeater/${CHANNEL}"
|
||||||
RADIO_STORAGE_DIR="/var/lib/pymc_repeater"
|
RADIO_STORAGE_DIR="/var/lib/openhop_repeater"
|
||||||
mkdir -p "$RADIO_STORAGE_DIR"
|
mkdir -p "$RADIO_STORAGE_DIR"
|
||||||
wget -qO "$RADIO_STORAGE_DIR/radio-settings.json" "${RADIO_BASE_URL}/radio-settings.json" 2>/dev/null || true
|
wget -qO "$RADIO_STORAGE_DIR/radio-settings.json" "${RADIO_BASE_URL}/radio-settings.json" 2>/dev/null || true
|
||||||
wget -qO "$RADIO_STORAGE_DIR/radio-presets.json" "${RADIO_BASE_URL}/radio-presets.json" 2>/dev/null || true
|
wget -qO "$RADIO_STORAGE_DIR/radio-presets.json" "${RADIO_BASE_URL}/radio-presets.json" 2>/dev/null || true
|
||||||
@@ -939,7 +1034,7 @@ UPGRADEEOF
|
|||||||
|
|
||||||
echo "=== Installing Python Dependencies ==="
|
echo "=== Installing Python Dependencies ==="
|
||||||
echo ""
|
echo ""
|
||||||
echo "Updating pymc_repeater and dependencies (including pymc_core from PyPI)..."
|
echo "Updating openhop_repeater and dependencies (including openhop_core from PyPI)..."
|
||||||
echo "This may take a few minutes..."
|
echo "This may take a few minutes..."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -969,7 +1064,7 @@ UPGRADEEOF
|
|||||||
migrate_to_venv
|
migrate_to_venv
|
||||||
|
|
||||||
# Install into the venv (clean, no system-packages flags needed)
|
# Install into the venv (clean, no system-packages flags needed)
|
||||||
echo "Upgrading pymc_repeater into venv ($VENV_DIR)..."
|
echo "Upgrading openhop_repeater into venv ($VENV_DIR)..."
|
||||||
|
|
||||||
# Attempt R2 wheels first for faster installation
|
# Attempt R2 wheels first for faster installation
|
||||||
if [ "$R2_ENABLED" -eq 1 ]; then
|
if [ "$R2_ENABLED" -eq 1 ]; then
|
||||||
@@ -1091,10 +1186,10 @@ uninstall_repeater() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ask_yes_no "Confirm Uninstall" "This will completely remove pyMC Repeater including:\n\n- Service and files\n- Configuration (backup will be created)\n- Logs and data\n\nThis action cannot be undone!\n\nContinue?"; then
|
if ask_yes_no "Confirm Uninstall" "This will completely remove openHop Repeater including:\n\n- Service and files\n- Configuration (backup will be created)\n- Logs and data\n\nThis action cannot be undone!\n\nContinue?"; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
echo " Uninstalling pyMC Repeater"
|
echo " Uninstalling openHop Repeater"
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -1105,24 +1200,28 @@ uninstall_repeater() {
|
|||||||
(
|
(
|
||||||
echo "20"; echo "# Backing up configuration..."
|
echo "20"; echo "# Backing up configuration..."
|
||||||
if [ -d "$CONFIG_DIR" ]; then
|
if [ -d "$CONFIG_DIR" ]; then
|
||||||
cp -r "$CONFIG_DIR" "/tmp/pymc_repeater_config_backup_$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
|
cp -r "$CONFIG_DIR" "/tmp/openhop_repeater_config_backup_$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "40"; echo "# Removing service files..."
|
echo "40"; echo "# Removing service files..."
|
||||||
rm -f /etc/systemd/system/pymc-repeater.service
|
rm -f /etc/systemd/system/openhop-repeater.service
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
|
||||||
echo "50"; echo "# Removing polkit and sudoers rules..."
|
echo "50"; echo "# Removing polkit and sudoers rules..."
|
||||||
rm -f /etc/polkit-1/rules.d/10-pymc-repeater.rules || true
|
rm -f /etc/polkit-1/rules.d/10-openhop-repeater.rules || true
|
||||||
rm -f /etc/polkit-1/localauthority/50-local.d/10-pymc-repeater.pkla || true
|
rm -f /etc/polkit-1/localauthority/50-local.d/10-openhop-repeater.pkla || true
|
||||||
rm -f /etc/sudoers.d/pymc-repeater
|
rm -f /etc/sudoers.d/openhop-repeater
|
||||||
rm -f /usr/local/bin/pymc-do-upgrade
|
rm -f /usr/local/bin/pymc-do-upgrade
|
||||||
|
|
||||||
echo "60"; echo "# Removing installation..."
|
echo "60"; echo "# Removing installation..."
|
||||||
rm -rf "$INSTALL_DIR"
|
rm -rf "$INSTALL_DIR"
|
||||||
rm -rf "$CONFIG_DIR"
|
rm -rf "$CONFIG_DIR"
|
||||||
rm -rf "$LOG_DIR"
|
rm -rf "$LOG_DIR"
|
||||||
rm -rf /var/lib/pymc_repeater
|
rm -rf "$DATA_DIR"
|
||||||
|
rm -rf "$LEGACY_INSTALL_DIR"
|
||||||
|
rm -rf "$LEGACY_CONFIG_DIR"
|
||||||
|
rm -rf "$LEGACY_LOG_DIR"
|
||||||
|
rm -rf "$LEGACY_DATA_DIR"
|
||||||
|
|
||||||
echo "80"; echo "# Removing service user..."
|
echo "80"; echo "# Removing service user..."
|
||||||
if id "$SERVICE_USER" &>/dev/null; then
|
if id "$SERVICE_USER" &>/dev/null; then
|
||||||
@@ -1130,9 +1229,9 @@ uninstall_repeater() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "100"; echo "# Uninstall complete!"
|
echo "100"; echo "# Uninstall complete!"
|
||||||
) | $DIALOG --backtitle "pyMC Repeater Management" --title "Uninstalling" --gauge "Removing pyMC Repeater..." 8 70 0
|
) | $DIALOG --backtitle "openHop Repeater Management" --title "Uninstalling" --gauge "Removing openHop Repeater..." 8 70 0
|
||||||
|
|
||||||
show_info "Uninstall Complete" "\npyMC Repeater has been completely removed.\n\nConfiguration backup saved to /tmp/\n\nThank you for using pyMC Repeater!"
|
show_info "Uninstall Complete" "\nopenHop Repeater has been completely removed.\n\nConfiguration backup saved to /tmp/\n\nThank you for using openHop Repeater!"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1167,9 +1266,9 @@ manage_service() {
|
|||||||
systemctl start "$SERVICE_NAME"
|
systemctl start "$SERVICE_NAME"
|
||||||
if is_running; then
|
if is_running; then
|
||||||
if [[ "$silent" == "true" ]]; then
|
if [[ "$silent" == "true" ]]; then
|
||||||
echo "✓ pyMC Repeater service has been started successfully."
|
echo "✓ openHop Repeater service has been started successfully."
|
||||||
else
|
else
|
||||||
show_info "Service Started" "\n✓ pyMC Repeater service has been started successfully."
|
show_info "Service Started" "\n✓ openHop Repeater service has been started successfully."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ "$silent" == "true" ]]; then
|
if [[ "$silent" == "true" ]]; then
|
||||||
@@ -1183,18 +1282,18 @@ manage_service() {
|
|||||||
"stop")
|
"stop")
|
||||||
systemctl stop "$SERVICE_NAME"
|
systemctl stop "$SERVICE_NAME"
|
||||||
if [[ "$silent" == "true" ]]; then
|
if [[ "$silent" == "true" ]]; then
|
||||||
echo "✓ pyMC Repeater service has been stopped."
|
echo "✓ openHop Repeater service has been stopped."
|
||||||
else
|
else
|
||||||
show_info "Service Stopped" "\n✓ pyMC Repeater service has been stopped."
|
show_info "Service Stopped" "\n✓ openHop Repeater service has been stopped."
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"restart")
|
"restart")
|
||||||
systemctl restart "$SERVICE_NAME"
|
systemctl restart "$SERVICE_NAME"
|
||||||
if is_running; then
|
if is_running; then
|
||||||
if [[ "$silent" == "true" ]]; then
|
if [[ "$silent" == "true" ]]; then
|
||||||
echo "✓ pyMC Repeater service has been restarted successfully."
|
echo "✓ openHop Repeater service has been restarted successfully."
|
||||||
else
|
else
|
||||||
show_info "Service Restarted" "\n✓ pyMC Repeater service has been restarted successfully."
|
show_info "Service Restarted" "\n✓ openHop Repeater service has been restarted successfully."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ "$silent" == "true" ]]; then
|
if [[ "$silent" == "true" ]]; then
|
||||||
@@ -1324,14 +1423,14 @@ validate_and_update_config() {
|
|||||||
|
|
||||||
# Main script logic
|
# Main script logic
|
||||||
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||||
echo "pyMC Repeater Management Script"
|
echo "openHop Repeater Management Script"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Usage: $0 [action]"
|
echo "Usage: $0 [action]"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Actions:"
|
echo "Actions:"
|
||||||
echo " install - Install pyMC Repeater"
|
echo " install - Install openHop Repeater"
|
||||||
echo " upgrade - Upgrade existing installation (CLI is silent by default; use --interactive to show dialogs)"
|
echo " upgrade - Upgrade existing installation (CLI is silent by default; use --interactive to show dialogs)"
|
||||||
echo " uninstall - Remove pyMC Repeater"
|
echo " uninstall - Remove openHop Repeater"
|
||||||
echo " config - Configure radio settings"
|
echo " config - Configure radio settings"
|
||||||
echo " start - Start the service (CLI is silent by default; use --interactive to show dialogs)"
|
echo " start - Start the service (CLI is silent by default; use --interactive to show dialogs)"
|
||||||
echo " stop - Stop the service (CLI is silent by default; use --interactive to show dialogs)"
|
echo " stop - Stop the service (CLI is silent by default; use --interactive to show dialogs)"
|
||||||
@@ -1355,7 +1454,7 @@ if [ "$1" = "debug" ]; then
|
|||||||
echo "Script: $0"
|
echo "Script: $0"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Testing dialog..."
|
echo "Testing dialog..."
|
||||||
$DIALOG --backtitle "pyMC Repeater Management" --title "Test" --msgbox "Dialog test successful!" 8 40
|
$DIALOG --backtitle "openHop Repeater Management" --title "Test" --msgbox "Dialog test successful!" 8 40
|
||||||
echo "Dialog test completed."
|
echo "Dialog test completed."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -1393,7 +1492,7 @@ case "$1" in
|
|||||||
"logs")
|
"logs")
|
||||||
clear
|
clear
|
||||||
echo -e "\033[1;36m╔══════════════════════════════════════════════════════════════════════╗\033[0m"
|
echo -e "\033[1;36m╔══════════════════════════════════════════════════════════════════════╗\033[0m"
|
||||||
echo -e "\033[1;36m║\033[0m \033[1;37mpyMC Repeater - Live Logs\033[0m \033[1;36m║\033[0m"
|
echo -e "\033[1;36m║\033[0m \033[1;37mopenHop Repeater - Live Logs\033[0m \033[1;36m║\033[0m"
|
||||||
echo -e "\033[1;36m║\033[0m \033[0;90m(Press Ctrl+C to return)\033[0m \033[1;36m║\033[0m"
|
echo -e "\033[1;36m║\033[0m \033[0;90m(Press Ctrl+C to return)\033[0m \033[1;36m║\033[0m"
|
||||||
echo -e "\033[1;36m╚══════════════════════════════════════════════════════════════════════╝\033[0m"
|
echo -e "\033[1;36m╚══════════════════════════════════════════════════════════════════════╝\033[0m"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#Systemd service file template for Py MC - Meshcore Repeater Daemon.
|
#Systemd service file template for openHop - Meshcore Repeater Daemon.
|
||||||
#Install as /etc/systemd/system/pymc-repeater.service
|
#Install as /etc/systemd/system/openhop-repeater.service
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=pyMC Repeater Daemon
|
Description=openHop Repeater Daemon
|
||||||
After=network-online.target dbus.socket
|
After=network-online.target dbus.socket
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
Requires=dbus.socket
|
Requires=dbus.socket
|
||||||
@@ -11,10 +11,10 @@ Requires=dbus.socket
|
|||||||
Type=simple
|
Type=simple
|
||||||
User=repeater
|
User=repeater
|
||||||
Group=repeater
|
Group=repeater
|
||||||
WorkingDirectory=/var/lib/pymc_repeater
|
WorkingDirectory=/var/lib/openhop_repeater
|
||||||
|
|
||||||
# Start command - use venv python to avoid system package conflicts
|
# Start command - use venv python to avoid system package conflicts
|
||||||
ExecStart=/opt/pymc_repeater/venv/bin/python -m repeater.main --config /etc/pymc_repeater/config.yaml
|
ExecStart=/opt/openhop_repeater/venv/bin/python -m repeater.main --config /etc/openhop_repeater/config.yaml
|
||||||
|
|
||||||
# Restart on failure
|
# Restart on failure
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
@@ -29,10 +29,10 @@ MemoryHigh=256M
|
|||||||
# Logging
|
# Logging
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
StandardError=journal
|
StandardError=journal
|
||||||
SyslogIdentifier=pymc-repeater
|
SyslogIdentifier=openhop-repeater
|
||||||
|
|
||||||
# Security (relaxed for service self-restart via sudo)
|
# Security (relaxed for service self-restart via sudo)
|
||||||
ReadWritePaths=/var/log/pymc_repeater /var/lib/pymc_repeater /etc/pymc_repeater
|
ReadWritePaths=/var/log/openhop_repeater /var/lib/openhop_repeater /etc/openhop_repeater
|
||||||
SupplementaryGroups=plugdev dialout
|
SupplementaryGroups=plugdev dialout
|
||||||
|
|
||||||
# Allow GPS time sync to update CLOCK_REALTIME without running as root, also allow changing user via polkit or sudo
|
# Allow GPS time sync to update CLOCK_REALTIME without running as root, also allow changing user via polkit or sudo
|
||||||
+4
-4
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.0", "wheel", "setuptools_scm>=8.0"]
|
|||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pymc_repeater"
|
name = "openhop_repeater"
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
authors = [
|
authors = [
|
||||||
{name = "Rightup", email = "rightup@pymc.dev"},
|
{name = "Rightup", email = "rightup@pymc.dev"},
|
||||||
@@ -29,7 +29,7 @@ keywords = ["mesh", "networking", "lora", "repeater", "daemon", "iot"]
|
|||||||
|
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pymc_core[hardware] @ git+https://github.com/rightup/pyMC_core.git@dev",
|
"openhop_core[hardware]",
|
||||||
"pyyaml>=6.0.0",
|
"pyyaml>=6.0.0",
|
||||||
"cherrypy>=18.0.0",
|
"cherrypy>=18.0.0",
|
||||||
"paho-mqtt>=1.6.0",
|
"paho-mqtt>=1.6.0",
|
||||||
@@ -45,7 +45,7 @@ dependencies = [
|
|||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
# SX1262/SPI support (Linux only; required for Raspberry Pi HATs)
|
# SX1262/SPI support (Linux only; required for Raspberry Pi HATs)
|
||||||
hardware = [
|
hardware = [
|
||||||
"pymc_core[hardware] @ git+https://github.com/rightup/pyMC_core.git@dev",
|
"openhop_core[hardware] @ git+https://github.com/rightup/openhop-core.git@dev",
|
||||||
]
|
]
|
||||||
# RRD metrics (Performance Metrics chart); system librrd required (e.g. apt install rrdtool)
|
# RRD metrics (Performance Metrics chart); system librrd required (e.g. apt install rrdtool)
|
||||||
rrd = [
|
rrd = [
|
||||||
@@ -59,7 +59,7 @@ dev = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
pymc-repeater = "repeater.main:main"
|
openhop-repeater = "repeater.main:main"
|
||||||
pymc-cli = "repeater.local_cli:main"
|
pymc-cli = "repeater.local_cli:main"
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
|
|||||||
+1
-1
@@ -411,7 +411,7 @@
|
|||||||
},
|
},
|
||||||
"kiss": {
|
"kiss": {
|
||||||
"name": "KISS modem (serial)",
|
"name": "KISS modem (serial)",
|
||||||
"description": "MeshCore KISS modem over serial - requires pyMC_core with KISS support.",
|
"description": "MeshCore KISS modem over serial - requires openhop-core with KISS support.",
|
||||||
"connection_type": "usb",
|
"connection_type": "usb",
|
||||||
"radio_type": "kiss",
|
"radio_type": "kiss",
|
||||||
"tx_power": 14,
|
"tx_power": 14,
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ except ImportError:
|
|||||||
try:
|
try:
|
||||||
from importlib.metadata import version
|
from importlib.metadata import version
|
||||||
|
|
||||||
__version__ = version("pymc_repeater")
|
__version__ = version("openhop_repeater")
|
||||||
except Exception:
|
except Exception:
|
||||||
__version__ = "unknown"
|
__version__ = "unknown"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Companion identity support for pyMC Repeater.
|
"""Companion identity support for openHop Repeater.
|
||||||
|
|
||||||
Exposes the MeshCore companion frame protocol over TCP for standard clients.
|
Exposes the MeshCore companion frame protocol over TCP for standard clients.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import logging
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Callable, Optional
|
from typing import Any, Callable, Optional
|
||||||
|
|
||||||
from pymc_core.companion import CompanionBridge
|
from openhop_core.companion import CompanionBridge
|
||||||
|
|
||||||
logger = logging.getLogger("RepeaterCompanionBridge")
|
logger = logging.getLogger("RepeaterCompanionBridge")
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"""Companion frame protocol constants — re-exported from pyMC_core.
|
"""Companion frame protocol constants — re-exported from openhop-core.
|
||||||
|
|
||||||
All protocol constants now live in :mod:`pymc_core.companion.constants`.
|
All protocol constants now live in :mod:`openhop_core.companion.constants`.
|
||||||
This module re-exports them so existing repeater imports continue to work.
|
This module re-exports them so existing repeater imports continue to work.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Re-exports; F401 ignored for re-exported names.
|
# Re-exports; F401 ignored for re-exported names.
|
||||||
from pymc_core.companion.constants import ( # noqa: F401
|
from openhop_core.companion.constants import ( # noqa: F401
|
||||||
ADV_TYPE_CHAT,
|
ADV_TYPE_CHAT,
|
||||||
ADV_TYPE_REPEATER,
|
ADV_TYPE_REPEATER,
|
||||||
ADV_TYPE_ROOM,
|
ADV_TYPE_ROOM,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Repeater-specific CompanionFrameServer with SQLite persistence.
|
Repeater-specific CompanionFrameServer with SQLite persistence.
|
||||||
|
|
||||||
Thin subclass of :class:`pymc_core.companion.frame_server.CompanionFrameServer`
|
Thin subclass of :class:`openhop_core.companion.frame_server.CompanionFrameServer`
|
||||||
that adds SQLite-backed message, contact, and channel persistence via a
|
that adds SQLite-backed message, contact, and channel persistence via a
|
||||||
``sqlite_handler`` dependency.
|
``sqlite_handler`` dependency.
|
||||||
"""
|
"""
|
||||||
@@ -12,9 +12,9 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from pymc_core.companion.constants import RESP_CODE_NO_MORE_MESSAGES
|
from openhop_core.companion.constants import RESP_CODE_NO_MORE_MESSAGES
|
||||||
from pymc_core.companion.frame_server import CompanionFrameServer as _BaseFrameServer
|
from openhop_core.companion.frame_server import CompanionFrameServer as _BaseFrameServer
|
||||||
from pymc_core.companion.models import QueuedMessage
|
from openhop_core.companion.models import QueuedMessage
|
||||||
|
|
||||||
logger = logging.getLogger("CompanionFrameServer")
|
logger = logging.getLogger("CompanionFrameServer")
|
||||||
|
|
||||||
@@ -45,8 +45,8 @@ class CompanionFrameServer(_BaseFrameServer):
|
|||||||
port=port,
|
port=port,
|
||||||
bind_address=bind_address,
|
bind_address=bind_address,
|
||||||
client_idle_timeout_sec=client_idle_timeout_sec,
|
client_idle_timeout_sec=client_idle_timeout_sec,
|
||||||
device_model="pyMC-Repeater-Companion",
|
device_model="openHop-Repeater-Companion",
|
||||||
device_version=None, # use FIRMWARE_VER_CODE from pyMC_core
|
device_version=None, # use FIRMWARE_VER_CODE from openhop-core
|
||||||
build_date="13 Feb 2026",
|
build_date="13 Feb 2026",
|
||||||
local_hash=local_hash,
|
local_hash=local_hash,
|
||||||
stats_getter=stats_getter,
|
stats_getter=stats_getter,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ def derive_companion_public_key_hex(identity_key: Any) -> Optional[str]:
|
|||||||
if raw is None:
|
if raw is None:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
from pymc_core import LocalIdentity
|
from openhop_core import LocalIdentity
|
||||||
|
|
||||||
identity = LocalIdentity(seed=raw)
|
identity = LocalIdentity(seed=raw)
|
||||||
return identity.get_public_key().hex()
|
return identity.get_public_key().hex()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from pymc_core.companion.constants import DEFAULT_MAX_CONTACTS
|
from openhop_core.companion.constants import DEFAULT_MAX_CONTACTS
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ _INVALID_NODE_NAME_CHARS = "\n\r\x00"
|
|||||||
# Optional per-companion RepeaterCompanionBridge constructor settings (power-user).
|
# Optional per-companion RepeaterCompanionBridge constructor settings (power-user).
|
||||||
COMPANION_BRIDGE_SETTING_KEYS = frozenset({"max_contacts", "offline_queue_size"})
|
COMPANION_BRIDGE_SETTING_KEYS = frozenset({"max_contacts", "offline_queue_size"})
|
||||||
|
|
||||||
# Settings that must not be applied from config (fixed at pymc_core defaults).
|
# Settings that must not be applied from config (fixed at openhop_core defaults).
|
||||||
_COMPANION_IGNORED_BRIDGE_KEYS = frozenset({"max_channels", "adv_type"})
|
_COMPANION_IGNORED_BRIDGE_KEYS = frozenset({"max_channels", "adv_type"})
|
||||||
|
|
||||||
# Contact flag bit 0 marks a favourite (protected from forced-trim eviction).
|
# Contact flag bit 0 marks a favourite (protected from forced-trim eviction).
|
||||||
@@ -102,7 +102,7 @@ def parse_companion_bridge_kwargs(settings: dict) -> Dict[str, int]:
|
|||||||
|
|
||||||
|
|
||||||
def effective_max_contacts(bridge_kwargs: Dict[str, int]) -> int:
|
def effective_max_contacts(bridge_kwargs: Dict[str, int]) -> int:
|
||||||
"""Return max_contacts from parsed kwargs or pymc_core default."""
|
"""Return max_contacts from parsed kwargs or openhop_core default."""
|
||||||
return bridge_kwargs.get("max_contacts", DEFAULT_MAX_CONTACTS)
|
return bridge_kwargs.get("max_contacts", DEFAULT_MAX_CONTACTS)
|
||||||
|
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ def format_companion_bridge_limits(bridge_kwargs: Dict[str, int]) -> str:
|
|||||||
|
|
||||||
def companion_hash_str_from_identity_key(identity_key: Any) -> str:
|
def companion_hash_str_from_identity_key(identity_key: Any) -> str:
|
||||||
"""Derive companion_hash storage key (0xHH) from an identity_key config value."""
|
"""Derive companion_hash storage key (0xHH) from an identity_key config value."""
|
||||||
from pymc_core import LocalIdentity
|
from openhop_core import LocalIdentity
|
||||||
|
|
||||||
if isinstance(identity_key, str):
|
if isinstance(identity_key, str):
|
||||||
key_bytes = bytes.fromhex(normalize_companion_identity_key(identity_key))
|
key_bytes = bytes.fromhex(normalize_companion_identity_key(identity_key))
|
||||||
|
|||||||
+21
-21
@@ -140,7 +140,7 @@ def resolve_storage_dir(
|
|||||||
config: Dict[str, Any],
|
config: Dict[str, Any],
|
||||||
*,
|
*,
|
||||||
config_path: Optional[str] = None,
|
config_path: Optional[str] = None,
|
||||||
default: str = "/var/lib/pymc_repeater",
|
default: str = "/var/lib/openhop_repeater",
|
||||||
) -> Path:
|
) -> Path:
|
||||||
|
|
||||||
storage_dir_cfg = (
|
storage_dir_cfg = (
|
||||||
@@ -195,7 +195,7 @@ def get_node_info(config: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
|
|
||||||
def load_config(config_path: Optional[str] = None) -> Dict[str, Any]:
|
def load_config(config_path: Optional[str] = None) -> Dict[str, Any]:
|
||||||
if config_path is None:
|
if config_path is None:
|
||||||
config_path = os.getenv("PYMC_REPEATER_CONFIG", "/etc/pymc_repeater/config.yaml")
|
config_path = os.getenv("PYMC_REPEATER_CONFIG", "/etc/openhop_repeater/config.yaml")
|
||||||
|
|
||||||
# Check if config file exists
|
# Check if config file exists
|
||||||
if not Path(config_path).exists():
|
if not Path(config_path).exists():
|
||||||
@@ -235,7 +235,7 @@ def load_config(config_path: Optional[str] = None) -> Dict[str, Any]:
|
|||||||
"request_timeout_seconds": 10,
|
"request_timeout_seconds": 10,
|
||||||
"verify_tls": True,
|
"verify_tls": True,
|
||||||
"api_token": None,
|
"api_token": None,
|
||||||
"cert_store_dir": "/etc/pymc_repeater/glass",
|
"cert_store_dir": "/etc/openhop_repeater/glass",
|
||||||
}
|
}
|
||||||
|
|
||||||
if "gps" not in config:
|
if "gps" not in config:
|
||||||
@@ -323,7 +323,7 @@ def save_config(config_data: Dict[str, Any], config_path: Optional[str] = None)
|
|||||||
True if successful, False otherwise
|
True if successful, False otherwise
|
||||||
"""
|
"""
|
||||||
if config_path is None:
|
if config_path is None:
|
||||||
config_path = os.getenv("PYMC_REPEATER_CONFIG", "/etc/pymc_repeater/config.yaml")
|
config_path = os.getenv("PYMC_REPEATER_CONFIG", "/etc/openhop_repeater/config.yaml")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Create backup of existing config
|
# Create backup of existing config
|
||||||
@@ -387,16 +387,16 @@ def _load_or_create_identity_key(path: Optional[str] = None) -> bytes:
|
|||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
# Check system-wide location first (matches config.yaml location)
|
# Check system-wide location first (matches config.yaml location)
|
||||||
system_key_path = Path("/etc/pymc_repeater/identity.key")
|
system_key_path = Path("/etc/openhop_repeater/identity.key")
|
||||||
if system_key_path.exists():
|
if system_key_path.exists():
|
||||||
key_path = system_key_path
|
key_path = system_key_path
|
||||||
else:
|
else:
|
||||||
# Follow XDG spec
|
# Follow XDG spec
|
||||||
xdg_config_home = os.environ.get("XDG_CONFIG_HOME")
|
xdg_config_home = os.environ.get("XDG_CONFIG_HOME")
|
||||||
if xdg_config_home:
|
if xdg_config_home:
|
||||||
config_dir = Path(xdg_config_home) / "pymc_repeater"
|
config_dir = Path(xdg_config_home) / "openhop_repeater"
|
||||||
else:
|
else:
|
||||||
config_dir = Path.home() / ".config" / "pymc_repeater"
|
config_dir = Path.home() / ".config" / "openhop_repeater"
|
||||||
key_path = config_dir / "identity.key"
|
key_path = config_dir / "identity.key"
|
||||||
else:
|
else:
|
||||||
key_path = Path(path)
|
key_path = Path(path)
|
||||||
@@ -475,7 +475,7 @@ def get_radio_for_board(board_config: dict):
|
|||||||
radio_type = "kiss"
|
radio_type = "kiss"
|
||||||
|
|
||||||
if radio_type in ("sx1262", "sx1262_ch341"):
|
if radio_type in ("sx1262", "sx1262_ch341"):
|
||||||
from pymc_core.hardware.sx1262_wrapper import SX1262Radio
|
from openhop_core.hardware.sx1262_wrapper import SX1262Radio
|
||||||
|
|
||||||
# Get radio and SPI configuration - all settings must be in config file
|
# Get radio and SPI configuration - all settings must be in config file
|
||||||
spi_config = board_config.get("sx1262")
|
spi_config = board_config.get("sx1262")
|
||||||
@@ -492,8 +492,8 @@ def get_radio_for_board(board_config: dict):
|
|||||||
if not ch341_cfg:
|
if not ch341_cfg:
|
||||||
raise ValueError("Missing 'ch341' section in configuration file")
|
raise ValueError("Missing 'ch341' section in configuration file")
|
||||||
|
|
||||||
from pymc_core.hardware.lora.LoRaRF.SX126x import set_spi_transport
|
from openhop_core.hardware.lora.LoRaRF.SX126x import set_spi_transport
|
||||||
from pymc_core.hardware.transports.ch341_spi_transport import CH341SPITransport
|
from openhop_core.hardware.transports.ch341_spi_transport import CH341SPITransport
|
||||||
|
|
||||||
vid = _parse_int(ch341_cfg.get("vid"), default=0x1A86)
|
vid = _parse_int(ch341_cfg.get("vid"), default=0x1A86)
|
||||||
pid = _parse_int(ch341_cfg.get("pid"), default=0x5512)
|
pid = _parse_int(ch341_cfg.get("pid"), default=0x5512)
|
||||||
@@ -534,7 +534,7 @@ def get_radio_for_board(board_config: dict):
|
|||||||
combined_config["en_pins"] = en_pins
|
combined_config["en_pins"] = en_pins
|
||||||
|
|
||||||
# Add optional GPIO parameters if specified in config
|
# Add optional GPIO parameters if specified in config
|
||||||
# These wont be supported by older versions of pymc_core
|
# These wont be supported by older versions of openhop_core
|
||||||
if "gpio_chip" in spi_config:
|
if "gpio_chip" in spi_config:
|
||||||
combined_config["gpio_chip"] = _parse_int(spi_config["gpio_chip"], default=0)
|
combined_config["gpio_chip"] = _parse_int(spi_config["gpio_chip"], default=0)
|
||||||
if "use_gpiod_backend" in spi_config:
|
if "use_gpiod_backend" in spi_config:
|
||||||
@@ -554,16 +554,16 @@ def get_radio_for_board(board_config: dict):
|
|||||||
|
|
||||||
elif radio_type == "kiss":
|
elif radio_type == "kiss":
|
||||||
try:
|
try:
|
||||||
from pymc_core.hardware.kiss_modem_wrapper import KissModemWrapper
|
from openhop_core.hardware.kiss_modem_wrapper import KissModemWrapper
|
||||||
except ImportError:
|
except ImportError:
|
||||||
try:
|
try:
|
||||||
from pymc_core.hardware.kiss_serial_wrapper import (
|
from openhop_core.hardware.kiss_serial_wrapper import (
|
||||||
KissSerialWrapper as KissModemWrapper,
|
KissSerialWrapper as KissModemWrapper,
|
||||||
)
|
)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"KISS modem support requires pyMC_core with KISS support. "
|
"KISS modem support requires openhop-core with KISS support. "
|
||||||
"Install your fork with: pip install -e /path/to/pyMC_core"
|
"Install your fork with: pip install -e /path/to/openhop-core"
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
kiss_config = board_config.get("kiss")
|
kiss_config = board_config.get("kiss")
|
||||||
@@ -613,11 +613,11 @@ def get_radio_for_board(board_config: dict):
|
|||||||
|
|
||||||
elif radio_type == "pymc_tcp":
|
elif radio_type == "pymc_tcp":
|
||||||
try:
|
try:
|
||||||
from pymc_core.hardware.tcp_radio import TCPLoRaRadio
|
from openhop_core.hardware.tcp_radio import TCPLoRaRadio
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"pymc_tcp radio requires pyMC_core >= the release that includes "
|
"pymc_tcp radio requires openhop-core >= the release that includes "
|
||||||
"PR pyMC-dev/pyMC_core#68 (merged 2026-05-13). "
|
"PR pyMC-dev/openhop-core#68 (merged 2026-05-13). "
|
||||||
"Reinstall the [hardware] extra to pick it up."
|
"Reinstall the [hardware] extra to pick it up."
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
@@ -657,11 +657,11 @@ def get_radio_for_board(board_config: dict):
|
|||||||
|
|
||||||
elif radio_type == "pymc_usb":
|
elif radio_type == "pymc_usb":
|
||||||
try:
|
try:
|
||||||
from pymc_core.hardware.usb_radio import USBLoRaRadio
|
from openhop_core.hardware.usb_radio import USBLoRaRadio
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"pymc_usb radio requires pyMC_core >= the release that includes "
|
"pymc_usb radio requires openhop-core >= the release that includes "
|
||||||
"PR pyMC-dev/pyMC_core#68 (merged 2026-05-13). "
|
"PR pyMC-dev/openhop-core#68 (merged 2026-05-13). "
|
||||||
"Reinstall the [hardware] extra to pick it up."
|
"Reinstall the [hardware] extra to pick it up."
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class GlassHandler:
|
|||||||
self.verify_tls = True
|
self.verify_tls = True
|
||||||
self.api_token = "" # nosec - runtime config value, not a hardcoded credential
|
self.api_token = "" # nosec - runtime config value, not a hardcoded credential
|
||||||
self.inform_interval_seconds = 30
|
self.inform_interval_seconds = 30
|
||||||
self.cert_store_dir = "/etc/pymc_repeater/glass"
|
self.cert_store_dir = "/etc/openhop_repeater/glass"
|
||||||
self._cert_expires_at: Optional[str] = None
|
self._cert_expires_at: Optional[str] = None
|
||||||
self.mqtt_enabled = False
|
self.mqtt_enabled = False
|
||||||
self.mqtt_broker_host = "localhost"
|
self.mqtt_broker_host = "localhost"
|
||||||
@@ -132,8 +132,8 @@ class GlassHandler:
|
|||||||
int(glass_cfg.get("inform_interval_seconds", self.inform_interval_seconds))
|
int(glass_cfg.get("inform_interval_seconds", self.inform_interval_seconds))
|
||||||
)
|
)
|
||||||
self.cert_store_dir = str(
|
self.cert_store_dir = str(
|
||||||
glass_cfg.get("cert_store_dir", "/etc/pymc_repeater/glass")
|
glass_cfg.get("cert_store_dir", "/etc/openhop_repeater/glass")
|
||||||
or "/etc/pymc_repeater/glass"
|
or "/etc/openhop_repeater/glass"
|
||||||
)
|
)
|
||||||
self.client_cert_path = (
|
self.client_cert_path = (
|
||||||
str(glass_cfg.get("client_cert_path")).strip()
|
str(glass_cfg.get("client_cert_path")).strip()
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ class _BrokerConnection:
|
|||||||
)
|
)
|
||||||
self.base_topic = f"meshcore/{self.iata_code}/{self.public_key}"
|
self.base_topic = f"meshcore/{self.iata_code}/{self.public_key}"
|
||||||
|
|
||||||
from pymc_core.protocol.utils import PAYLOAD_TYPES
|
from openhop_core.protocol.utils import PAYLOAD_TYPES
|
||||||
|
|
||||||
disallowed_types = broker.get("disallowed_packet_types", [])
|
disallowed_types = broker.get("disallowed_packet_types", [])
|
||||||
type_name_map = {name: code for code, name in PAYLOAD_TYPES.items()}
|
type_name_map = {name: code for code, name in PAYLOAD_TYPES.items()}
|
||||||
@@ -973,7 +973,7 @@ class MeshCoreToMqttPusher:
|
|||||||
"model": "PyMC-Repeater",
|
"model": "PyMC-Repeater",
|
||||||
"firmware_version": self.app_version,
|
"firmware_version": self.app_version,
|
||||||
"radio": radio_config or self.radio_config,
|
"radio": radio_config or self.radio_config,
|
||||||
"client_version": f"pyMC_repeater/{self.app_version}",
|
"client_version": f"openhop_repeater/{self.app_version}",
|
||||||
"stats": {**live_stats, "errors": 0, "queue_len": 0, **(extra_stats or {})},
|
"stats": {**live_stats, "errors": 0, "queue_len": 0, **(extra_stats or {})},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1201,9 +1201,9 @@ class SQLiteHandler:
|
|||||||
return cached["value"]
|
return cached["value"]
|
||||||
cutoff = now - (hours * 3600)
|
cutoff = now - (hours * 3600)
|
||||||
|
|
||||||
# Align with pyMC_core feat/newRadios PAYLOAD_TYPES (0x0B = CONTROL)
|
# Align with openhop-core feat/newRadios PAYLOAD_TYPES (0x0B = CONTROL)
|
||||||
try:
|
try:
|
||||||
from pymc_core.protocol.utils import PAYLOAD_TYPES as _PT
|
from openhop_core.protocol.utils import PAYLOAD_TYPES as _PT
|
||||||
|
|
||||||
_human = {
|
_human = {
|
||||||
"REQ": "Request",
|
"REQ": "Request",
|
||||||
@@ -1764,7 +1764,7 @@ class SQLiteHandler:
|
|||||||
A base64-encoded transport key derived from the name
|
A base64-encoded transport key derived from the name
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from pymc_core.protocol.transport_keys import get_auto_key_for
|
from openhop_core.protocol.transport_keys import get_auto_key_for
|
||||||
|
|
||||||
key_bytes = get_auto_key_for(name)
|
key_bytes = get_auto_key_for(name)
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -6,9 +6,9 @@ import time
|
|||||||
from collections import OrderedDict, deque
|
from collections import OrderedDict, deque
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from pymc_core.node.handlers.base import BaseHandler
|
from openhop_core.node.handlers.base import BaseHandler
|
||||||
from pymc_core.protocol import Packet
|
from openhop_core.protocol import Packet
|
||||||
from pymc_core.protocol.constants import (
|
from openhop_core.protocol.constants import (
|
||||||
MAX_PATH_SIZE,
|
MAX_PATH_SIZE,
|
||||||
PAYLOAD_TYPE_ADVERT,
|
PAYLOAD_TYPE_ADVERT,
|
||||||
PAYLOAD_TYPE_ANON_REQ,
|
PAYLOAD_TYPE_ANON_REQ,
|
||||||
@@ -19,7 +19,7 @@ from pymc_core.protocol.constants import (
|
|||||||
ROUTE_TYPE_TRANSPORT_DIRECT,
|
ROUTE_TYPE_TRANSPORT_DIRECT,
|
||||||
ROUTE_TYPE_TRANSPORT_FLOOD,
|
ROUTE_TYPE_TRANSPORT_FLOOD,
|
||||||
)
|
)
|
||||||
from pymc_core.protocol.packet_utils import PacketHeaderUtils, PathUtils
|
from openhop_core.protocol.packet_utils import PacketHeaderUtils, PathUtils
|
||||||
|
|
||||||
from repeater.airtime import AirtimeManager
|
from repeater.airtime import AirtimeManager
|
||||||
from repeater.data_acquisition import StorageCollector
|
from repeater.data_acquisition import StorageCollector
|
||||||
@@ -820,7 +820,7 @@ class RepeaterHandler(BaseHandler):
|
|||||||
return False, "No storage available for transport key validation"
|
return False, "No storage available for transport key validation"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pymc_core.protocol.transport_keys import calc_transport_code
|
from openhop_core.protocol.transport_keys import calc_transport_code
|
||||||
|
|
||||||
# Check cache validity
|
# Check cache validity
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Handler helper modules for pyMC Repeater."""
|
"""Handler helper modules for openHop Repeater."""
|
||||||
|
|
||||||
from .advert import AdvertHelper
|
from .advert import AdvertHelper
|
||||||
from .discovery import DiscoveryHelper
|
from .discovery import DiscoveryHelper
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import logging
|
|||||||
import time
|
import time
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from pymc_core.protocol import Identity
|
from openhop_core.protocol import Identity
|
||||||
from pymc_core.protocol.constants import PUB_KEY_SIZE
|
from openhop_core.protocol.constants import PUB_KEY_SIZE
|
||||||
|
|
||||||
logger = logging.getLogger("ACL")
|
logger = logging.getLogger("ACL")
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Advertisement packet handling helper for pyMC Repeater.
|
Advertisement packet handling helper for openHop Repeater.
|
||||||
|
|
||||||
This module processes advertisement packets for neighbor tracking and discovery.
|
This module processes advertisement packets for neighbor tracking and discovery.
|
||||||
Includes adaptive rate limiting based on mesh activity.
|
Includes adaptive rate limiting based on mesh activity.
|
||||||
@@ -13,7 +13,7 @@ from collections import OrderedDict, deque
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, Optional, Tuple
|
from typing import Dict, Optional, Tuple
|
||||||
|
|
||||||
from pymc_core.node.handlers.advert import AdvertHandler
|
from openhop_core.node.handlers.advert import AdvertHandler
|
||||||
|
|
||||||
logger = logging.getLogger("AdvertHelper")
|
logger = logging.getLogger("AdvertHelper")
|
||||||
|
|
||||||
@@ -603,7 +603,7 @@ class AdvertHelper:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Get route type from packet header
|
# Get route type from packet header
|
||||||
from pymc_core.protocol.constants import PH_ROUTE_MASK
|
from openhop_core.protocol.constants import PH_ROUTE_MASK
|
||||||
|
|
||||||
route_type = packet.header & PH_ROUTE_MASK
|
route_type = packet.header & PH_ROUTE_MASK
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Discovery request/response handling helper for pyMC Repeater.
|
Discovery request/response handling helper for openHop Repeater.
|
||||||
|
|
||||||
This module handles the processing and response to discovery requests,
|
This module handles the processing and response to discovery requests,
|
||||||
allowing other nodes to discover repeaters on the mesh network.
|
allowing other nodes to discover repeaters on the mesh network.
|
||||||
@@ -9,7 +9,7 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
from pymc_core.node.handlers.control import ControlHandler
|
from openhop_core.node.handlers.control import ControlHandler
|
||||||
|
|
||||||
logger = logging.getLogger("DiscoveryHelper")
|
logger = logging.getLogger("DiscoveryHelper")
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ class DiscoveryHelper:
|
|||||||
try:
|
try:
|
||||||
our_pub_key = self.local_identity.get_public_key()
|
our_pub_key = self.local_identity.get_public_key()
|
||||||
|
|
||||||
from pymc_core.protocol.packet_builder import PacketBuilder
|
from openhop_core.protocol.packet_builder import PacketBuilder
|
||||||
|
|
||||||
response_packet = PacketBuilder.create_discovery_response(
|
response_packet = PacketBuilder.create_discovery_response(
|
||||||
tag=tag,
|
tag=tag,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Login/ANON_REQ packet handling helper for pyMC Repeater.
|
Login/ANON_REQ packet handling helper for openHop Repeater.
|
||||||
|
|
||||||
This module processes login requests and manages authentication for all identities.
|
This module processes login requests and manages authentication for all identities.
|
||||||
"""
|
"""
|
||||||
@@ -8,9 +8,9 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from pymc_core.node.handlers.anon_request import AnonRateLimiter, AnonRequestHandler
|
from openhop_core.node.handlers.anon_request import AnonRateLimiter, AnonRequestHandler
|
||||||
from pymc_core.node.handlers.login_server import LoginServerHandler
|
from openhop_core.node.handlers.login_server import LoginServerHandler
|
||||||
from pymc_core.protocol.constants import PAYLOAD_TYPE_ANON_REQ
|
from openhop_core.protocol.constants import PAYLOAD_TYPE_ANON_REQ
|
||||||
|
|
||||||
logger = logging.getLogger("LoginHelper")
|
logger = logging.getLogger("LoginHelper")
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ class LoginHelper:
|
|||||||
emit the ``*`` wildcard region first (unless unscoped flood is denied),
|
emit the ``*`` wildcard region first (unless unscoped flood is denied),
|
||||||
then each allow-flood named region with a leading ``#`` stripped, with no
|
then each allow-flood named region with a leading ``#`` stripped, with no
|
||||||
trailing comma. The firmware wildcard is the always-present default flood
|
trailing comma. The firmware wildcard is the always-present default flood
|
||||||
scope; pyMC_repeater models that via ``mesh.unscoped_flood_allow``
|
scope; openhop_repeater models that via ``mesh.unscoped_flood_allow``
|
||||||
(falling back to ``mesh.global_flood_allow``, default allow).
|
(falling back to ``mesh.global_flood_allow``, default allow).
|
||||||
"""
|
"""
|
||||||
parts = []
|
parts = []
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class PathHelper:
|
|||||||
|
|
||||||
async def process_path_packet(self, packet):
|
async def process_path_packet(self, packet):
|
||||||
|
|
||||||
from pymc_core.protocol.crypto import CryptoUtils
|
from openhop_core.protocol.crypto import CryptoUtils
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(packet.payload) < 2:
|
if len(packet.payload) < 2:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Protocol request (REQ) handling helper for pyMC Repeater.
|
Protocol request (REQ) handling helper for openHop Repeater.
|
||||||
|
|
||||||
Provides repeater-specific callbacks for status and telemetry requests.
|
Provides repeater-specific callbacks for status and telemetry requests.
|
||||||
"""
|
"""
|
||||||
@@ -9,7 +9,7 @@ import logging
|
|||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from pymc_core.node.handlers.protocol_request import (
|
from openhop_core.node.handlers.protocol_request import (
|
||||||
REQ_TYPE_GET_ACCESS_LIST,
|
REQ_TYPE_GET_ACCESS_LIST,
|
||||||
REQ_TYPE_GET_NEIGHBOURS,
|
REQ_TYPE_GET_NEIGHBOURS,
|
||||||
REQ_TYPE_GET_OWNER_INFO,
|
REQ_TYPE_GET_OWNER_INFO,
|
||||||
@@ -368,14 +368,14 @@ class ProtocolRequestHelper:
|
|||||||
Matches C++ simple_repeater: sprintf("%s\\n%s\\n%s", FIRMWARE_VERSION, node_name, owner_info)
|
Matches C++ simple_repeater: sprintf("%s\\n%s\\n%s", FIRMWARE_VERSION, node_name, owner_info)
|
||||||
"""
|
"""
|
||||||
repeater_cfg = self.config.get("repeater", {})
|
repeater_cfg = self.config.get("repeater", {})
|
||||||
node_name = repeater_cfg.get("node_name", "pyMC_Repeater")
|
node_name = repeater_cfg.get("node_name", "openhop-repeater")
|
||||||
owner_info = repeater_cfg.get("owner_info", "")
|
owner_info = repeater_cfg.get("owner_info", "")
|
||||||
|
|
||||||
# Version: use package version if available, fallback to "pyMC"
|
# Version: use package version if available, fallback to "pyMC"
|
||||||
try:
|
try:
|
||||||
from importlib.metadata import version as pkg_version
|
from importlib.metadata import version as pkg_version
|
||||||
|
|
||||||
fw_version = pkg_version("pymc-repeater")
|
fw_version = pkg_version("openhop-repeater")
|
||||||
except Exception:
|
except Exception:
|
||||||
fw_version = "pyMC"
|
fw_version = "pyMC"
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import secrets
|
|||||||
import time
|
import time
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from pymc_core.protocol import CryptoUtils, PacketBuilder
|
from openhop_core.protocol import CryptoUtils, PacketBuilder
|
||||||
from pymc_core.protocol.constants import PAYLOAD_TYPE_TXT_MSG
|
from openhop_core.protocol.constants import PAYLOAD_TYPE_TXT_MSG
|
||||||
|
|
||||||
logger = logging.getLogger("RoomServer")
|
logger = logging.getLogger("RoomServer")
|
||||||
|
|
||||||
@@ -102,8 +102,8 @@ class RoomServer:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pymc_core.protocol import PacketBuilder
|
from openhop_core.protocol import PacketBuilder
|
||||||
from pymc_core.protocol.constants import (
|
from openhop_core.protocol.constants import (
|
||||||
ADVERT_FLAG_HAS_NAME,
|
ADVERT_FLAG_HAS_NAME,
|
||||||
ADVERT_FLAG_IS_ROOM_SERVER,
|
ADVERT_FLAG_IS_ROOM_SERVER,
|
||||||
)
|
)
|
||||||
@@ -357,7 +357,7 @@ class RoomServer:
|
|||||||
timestamp.to_bytes(4, "little") + bytes([flags]) + author_prefix + message_bytes
|
timestamp.to_bytes(4, "little") + bytes([flags]) + author_prefix + message_bytes
|
||||||
)
|
)
|
||||||
|
|
||||||
# Calculate expected ACK (same algorithm as pymc_core)
|
# Calculate expected ACK (same algorithm as openhop_core)
|
||||||
attempt = 0
|
attempt = 0
|
||||||
pack_data = PacketBuilder._pack_timestamp_data(timestamp, attempt, message_bytes)
|
pack_data = PacketBuilder._pack_timestamp_data(timestamp, attempt, message_bytes)
|
||||||
ack_hash = CryptoUtils.sha256(pack_data + client_info.id.get_public_key())[:4]
|
ack_hash = CryptoUtils.sha256(pack_data + client_info.id.get_public_key())[:4]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Text message (TXT_MSG) handling helper for pyMC Repeater.
|
Text message (TXT_MSG) handling helper for openHop Repeater.
|
||||||
|
|
||||||
This module processes incoming text messages for all managed identities
|
This module processes incoming text messages for all managed identities
|
||||||
(repeater identity + identity manager identities).
|
(repeater identity + identity manager identities).
|
||||||
@@ -10,8 +10,8 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from pymc_core.node.handlers.text import TextMessageHandler
|
from openhop_core.node.handlers.text import TextMessageHandler
|
||||||
from pymc_core.protocol import CryptoUtils, Identity
|
from openhop_core.protocol import CryptoUtils, Identity
|
||||||
|
|
||||||
from .mesh_cli import MeshCLI
|
from .mesh_cli import MeshCLI
|
||||||
from .room_server import RoomServer
|
from .room_server import RoomServer
|
||||||
@@ -603,8 +603,8 @@ class TextHelper:
|
|||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from pymc_core.protocol import PacketBuilder
|
from openhop_core.protocol import PacketBuilder
|
||||||
from pymc_core.protocol.constants import PAYLOAD_TYPE_TXT_MSG
|
from openhop_core.protocol.constants import PAYLOAD_TYPE_TXT_MSG
|
||||||
|
|
||||||
try:
|
try:
|
||||||
src_hash = original_packet.payload[1]
|
src_hash = original_packet.payload[1]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Trace packet handling helper for pyMC Repeater.
|
Trace packet handling helper for openHop Repeater.
|
||||||
|
|
||||||
This module handles the processing and forwarding of trace packets,
|
This module handles the processing and forwarding of trace packets,
|
||||||
which are used for network diagnostics to track the path and SNR
|
which are used for network diagnostics to track the path and SNR
|
||||||
@@ -11,10 +11,10 @@ import logging
|
|||||||
import time
|
import time
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from pymc_core.hardware.signal_utils import snr_register_to_db
|
from openhop_core.hardware.signal_utils import snr_register_to_db
|
||||||
from pymc_core.node.handlers.trace import TraceHandler
|
from openhop_core.node.handlers.trace import TraceHandler
|
||||||
from pymc_core.protocol.constants import MAX_PATH_SIZE, ROUTE_TYPE_DIRECT
|
from openhop_core.protocol.constants import MAX_PATH_SIZE, ROUTE_TYPE_DIRECT
|
||||||
from pymc_core.protocol.packet_utils import PathUtils
|
from openhop_core.protocol.packet_utils import PathUtils
|
||||||
|
|
||||||
logger = logging.getLogger("TraceHelper")
|
logger = logging.getLogger("TraceHelper")
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
CLI client for pyMC Repeater.
|
CLI client for openHop Repeater.
|
||||||
Connects to an already-running repeater daemon via its HTTP API.
|
Connects to an already-running repeater daemon via its HTTP API.
|
||||||
Reads admin password and HTTP port from the local config.yaml automatically.
|
Reads admin password and HTTP port from the local config.yaml automatically.
|
||||||
"""
|
"""
|
||||||
@@ -9,7 +9,7 @@ from typing import Optional
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
CONFIG_PATHS = [
|
CONFIG_PATHS = [
|
||||||
"/etc/pymc_repeater/config.yaml",
|
"/etc/openhop_repeater/config.yaml",
|
||||||
"config.yaml",
|
"config.yaml",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ def run_client_cli(host: str = "127.0.0.1", port: int = 8000, password: Optional
|
|||||||
print("Error: Authentication failed. Check password or repeater status.")
|
print("Error: Authentication failed. Check password or repeater status.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print(f"\npyMC Repeater CLI (connected to {base_url})")
|
print(f"\nopenHop Repeater CLI (connected to {base_url})")
|
||||||
print("Type 'help' for available commands, 'exit' to quit.\n")
|
print("Type 'help' for available commands, 'exit' to quit.\n")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -123,7 +123,7 @@ def main():
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Connect to a running pyMC Repeater and issue CLI commands"
|
description="Connect to a running openHop Repeater and issue CLI commands"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config",
|
"--config",
|
||||||
|
|||||||
+19
-19
@@ -175,8 +175,8 @@ class RepeaterDaemon:
|
|||||||
self.radio = NullRadio()
|
self.radio = NullRadio()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pymc_core import LocalIdentity
|
from openhop_core import LocalIdentity
|
||||||
from pymc_core.node.dispatcher import Dispatcher
|
from openhop_core.node.dispatcher import Dispatcher
|
||||||
|
|
||||||
self.dispatcher = Dispatcher(self.radio)
|
self.dispatcher = Dispatcher(self.radio)
|
||||||
logger.info("Dispatcher initialized")
|
logger.info("Dispatcher initialized")
|
||||||
@@ -307,7 +307,7 @@ class RepeaterDaemon:
|
|||||||
|
|
||||||
# Initialize ConfigManager for centralized config management
|
# Initialize ConfigManager for centralized config management
|
||||||
self.config_manager = ConfigManager(
|
self.config_manager = ConfigManager(
|
||||||
config_path=getattr(self, "config_path", "/etc/pymc_repeater/config.yaml"),
|
config_path=getattr(self, "config_path", "/etc/openhop_repeater/config.yaml"),
|
||||||
config=self.config,
|
config=self.config,
|
||||||
daemon_instance=self,
|
daemon_instance=self,
|
||||||
)
|
)
|
||||||
@@ -395,7 +395,7 @@ class RepeaterDaemon:
|
|||||||
# Load companion identities (CompanionBridge + frame server per companion)
|
# Load companion identities (CompanionBridge + frame server per companion)
|
||||||
await self._load_companion_identities()
|
await self._load_companion_identities()
|
||||||
|
|
||||||
# Subscribe to raw RX in pyMC_core so we can push PUSH_CODE_LOG_RX_DATA to companion clients
|
# Subscribe to raw RX in openhop-core so we can push PUSH_CODE_LOG_RX_DATA to companion clients
|
||||||
self.dispatcher.add_raw_rx_subscriber(self._on_raw_rx_for_companions)
|
self.dispatcher.add_raw_rx_subscriber(self._on_raw_rx_for_companions)
|
||||||
n = len(getattr(self, "companion_frame_servers", []))
|
n = len(getattr(self, "companion_frame_servers", []))
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -431,7 +431,7 @@ class RepeaterDaemon:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
async def _load_additional_identities(self):
|
async def _load_additional_identities(self):
|
||||||
from pymc_core import LocalIdentity
|
from openhop_core import LocalIdentity
|
||||||
|
|
||||||
identities_config = self.config.get("identities", {})
|
identities_config = self.config.get("identities", {})
|
||||||
|
|
||||||
@@ -493,8 +493,8 @@ class RepeaterDaemon:
|
|||||||
|
|
||||||
async def _load_companion_identities(self) -> None:
|
async def _load_companion_identities(self) -> None:
|
||||||
"""Load companion identities from config and create CompanionBridge + frame server for each."""
|
"""Load companion identities from config and create CompanionBridge + frame server for each."""
|
||||||
from pymc_core import LocalIdentity
|
from openhop_core import LocalIdentity
|
||||||
from pymc_core.companion.models import Channel
|
from openhop_core.companion.models import Channel
|
||||||
|
|
||||||
from repeater.companion import CompanionFrameServer, RepeaterCompanionBridge
|
from repeater.companion import CompanionFrameServer, RepeaterCompanionBridge
|
||||||
|
|
||||||
@@ -651,7 +651,7 @@ class RepeaterDaemon:
|
|||||||
for msg_dict in sqlite_handler.companion_load_messages(
|
for msg_dict in sqlite_handler.companion_load_messages(
|
||||||
companion_hash_str, limit=retention or 100
|
companion_hash_str, limit=retention or 100
|
||||||
):
|
):
|
||||||
from pymc_core.companion.models import QueuedMessage
|
from openhop_core.companion.models import QueuedMessage
|
||||||
|
|
||||||
sk = msg_dict.get("sender_key", b"")
|
sk = msg_dict.get("sender_key", b"")
|
||||||
if isinstance(sk, str):
|
if isinstance(sk, str):
|
||||||
@@ -717,8 +717,8 @@ class RepeaterDaemon:
|
|||||||
Creates RepeaterCompanionBridge, CompanionFrameServer, starts the server,
|
Creates RepeaterCompanionBridge, CompanionFrameServer, starts the server,
|
||||||
and registers with identity_manager. Raises on error.
|
and registers with identity_manager. Raises on error.
|
||||||
"""
|
"""
|
||||||
from pymc_core import LocalIdentity
|
from openhop_core import LocalIdentity
|
||||||
from pymc_core.companion.models import Channel
|
from openhop_core.companion.models import Channel
|
||||||
|
|
||||||
from repeater.companion import CompanionFrameServer, RepeaterCompanionBridge
|
from repeater.companion import CompanionFrameServer, RepeaterCompanionBridge
|
||||||
from repeater.companion.constants import DEFAULT_PUBLIC_CHANNEL_SECRET
|
from repeater.companion.constants import DEFAULT_PUBLIC_CHANNEL_SECRET
|
||||||
@@ -837,7 +837,7 @@ class RepeaterDaemon:
|
|||||||
for msg_dict in sqlite_handler.companion_load_messages(
|
for msg_dict in sqlite_handler.companion_load_messages(
|
||||||
companion_hash_str, limit=retention or 100
|
companion_hash_str, limit=retention or 100
|
||||||
):
|
):
|
||||||
from pymc_core.companion.models import QueuedMessage
|
from openhop_core.companion.models import QueuedMessage
|
||||||
|
|
||||||
sk = msg_dict.get("sender_key", b"")
|
sk = msg_dict.get("sender_key", b"")
|
||||||
if isinstance(sk, str):
|
if isinstance(sk, str):
|
||||||
@@ -1139,8 +1139,8 @@ class RepeaterDaemon:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pymc_core.protocol import PacketBuilder
|
from openhop_core.protocol import PacketBuilder
|
||||||
from pymc_core.protocol.constants import ADVERT_FLAG_HAS_NAME, ADVERT_FLAG_IS_REPEATER
|
from openhop_core.protocol.constants import ADVERT_FLAG_HAS_NAME, ADVERT_FLAG_IS_REPEATER
|
||||||
|
|
||||||
# Get node name and location from config
|
# Get node name and location from config
|
||||||
repeater_config = self.config.get("repeater", {})
|
repeater_config = self.config.get("repeater", {})
|
||||||
@@ -1332,7 +1332,7 @@ class RepeaterDaemon:
|
|||||||
radio_type_raw = self.config.get("radio_type")
|
radio_type_raw = self.config.get("radio_type")
|
||||||
radio_type = "" if radio_type_raw is None else str(radio_type_raw).lower()
|
radio_type = "" if radio_type_raw is None else str(radio_type_raw).lower()
|
||||||
if radio_type == "sx1262_ch341":
|
if radio_type == "sx1262_ch341":
|
||||||
from pymc_core.hardware.ch341.ch341_async import CH341Async
|
from openhop_core.hardware.ch341.ch341_async import CH341Async
|
||||||
|
|
||||||
CH341Async.reset_instance()
|
CH341Async.reset_instance()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1402,7 +1402,7 @@ class RepeaterDaemon:
|
|||||||
config=self.config,
|
config=self.config,
|
||||||
event_loop=current_loop,
|
event_loop=current_loop,
|
||||||
daemon_instance=self,
|
daemon_instance=self,
|
||||||
config_path=getattr(self, "config_path", "/etc/pymc_repeater/config.yaml"),
|
config_path=getattr(self, "config_path", "/etc/openhop_repeater/config.yaml"),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1410,7 +1410,7 @@ class RepeaterDaemon:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to start HTTP server: {e}")
|
logger.error(f"Failed to start HTTP server: {e}")
|
||||||
|
|
||||||
# Run dispatcher (handles RX/TX via pymc_core)
|
# Run dispatcher (handles RX/TX via openhop_core)
|
||||||
try:
|
try:
|
||||||
await self.dispatcher.run_forever()
|
await self.dispatcher.run_forever()
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
@@ -1441,10 +1441,10 @@ def main():
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="pyMC Repeater Daemon")
|
parser = argparse.ArgumentParser(description="openHop Repeater Daemon")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config",
|
"--config",
|
||||||
help="Path to config file (default: /etc/pymc_repeater/config.yaml)",
|
help="Path to config file (default: /etc/openhop_repeater/config.yaml)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--log-level",
|
"--log-level",
|
||||||
@@ -1456,7 +1456,7 @@ def main():
|
|||||||
|
|
||||||
# Load configuration
|
# Load configuration
|
||||||
config = load_config(args.config)
|
config = load_config(args.config)
|
||||||
config_path = args.config if args.config else "/etc/pymc_repeater/config.yaml"
|
config_path = args.config if args.config else "/etc/openhop_repeater/config.yaml"
|
||||||
|
|
||||||
if args.log_level:
|
if args.log_level:
|
||||||
if "logging" not in config:
|
if "logging" not in config:
|
||||||
|
|||||||
+12
-12
@@ -2,18 +2,18 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from pymc_core.node.handlers.ack import AckHandler
|
from openhop_core.node.handlers.ack import AckHandler
|
||||||
from pymc_core.node.handlers.advert import AdvertHandler
|
from openhop_core.node.handlers.advert import AdvertHandler
|
||||||
from pymc_core.node.handlers.control import ControlHandler
|
from openhop_core.node.handlers.control import ControlHandler
|
||||||
from pymc_core.node.handlers.group_text import GroupTextHandler
|
from openhop_core.node.handlers.group_text import GroupTextHandler
|
||||||
from pymc_core.node.handlers.login_response import LoginResponseHandler
|
from openhop_core.node.handlers.login_response import LoginResponseHandler
|
||||||
from pymc_core.node.handlers.login_server import LoginServerHandler
|
from openhop_core.node.handlers.login_server import LoginServerHandler
|
||||||
from pymc_core.node.handlers.path import PathHandler
|
from openhop_core.node.handlers.path import PathHandler
|
||||||
from pymc_core.node.handlers.protocol_request import ProtocolRequestHandler
|
from openhop_core.node.handlers.protocol_request import ProtocolRequestHandler
|
||||||
from pymc_core.node.handlers.protocol_response import ProtocolResponseHandler
|
from openhop_core.node.handlers.protocol_response import ProtocolResponseHandler
|
||||||
from pymc_core.node.handlers.text import TextMessageHandler
|
from openhop_core.node.handlers.text import TextMessageHandler
|
||||||
from pymc_core.node.handlers.trace import TraceHandler
|
from openhop_core.node.handlers.trace import TraceHandler
|
||||||
from pymc_core.protocol.constants import (
|
from openhop_core.protocol.constants import (
|
||||||
PH_ROUTE_MASK,
|
PH_ROUTE_MASK,
|
||||||
ROUTE_TYPE_DIRECT,
|
ROUTE_TYPE_DIRECT,
|
||||||
ROUTE_TYPE_TRANSPORT_DIRECT,
|
ROUTE_TYPE_TRANSPORT_DIRECT,
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import logging
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from pymc_core.protocol.constants import PAYLOAD_TYPE_GRP_DATA, PAYLOAD_TYPE_GRP_TXT
|
from openhop_core.protocol.constants import PAYLOAD_TYPE_GRP_DATA, PAYLOAD_TYPE_GRP_TXT
|
||||||
from pymc_core.protocol.crypto import CryptoUtils
|
from openhop_core.protocol.crypto import CryptoUtils
|
||||||
|
|
||||||
logger = logging.getLogger("PolicyEngine")
|
logger = logging.getLogger("PolicyEngine")
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Service management utilities for pyMC Repeater.
|
Service management utilities for openHop Repeater.
|
||||||
Provides functions for service control operations like restart.
|
Provides functions for service control operations like restart.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ import time
|
|||||||
from typing import Dict, Optional, Tuple
|
from typing import Dict, Optional, Tuple
|
||||||
|
|
||||||
logger = logging.getLogger("ServiceUtils")
|
logger = logging.getLogger("ServiceUtils")
|
||||||
INIT_SCRIPT = "/etc/init.d/S80pymc-repeater"
|
INIT_SCRIPT = "/etc/init.d/S80openhop-repeater"
|
||||||
BUILDROOT_METADATA_PATH = "/etc/pymc-image-build-id"
|
BUILDROOT_METADATA_PATH = "/etc/pymc-image-build-id"
|
||||||
_CONTAINER_RESTART_DELAY_SECONDS = 1.0
|
_CONTAINER_RESTART_DELAY_SECONDS = 1.0
|
||||||
_SH_BIN = shutil.which("sh") or "sh"
|
_SH_BIN = shutil.which("sh") or "sh"
|
||||||
@@ -91,14 +91,14 @@ def get_container_restart_message() -> str:
|
|||||||
"""Return the user-facing restart message for containerized installs."""
|
"""Return the user-facing restart message for containerized installs."""
|
||||||
return (
|
return (
|
||||||
"Container restart initiated. "
|
"Container restart initiated. "
|
||||||
"If you are running pyMC Repeater via Docker or Home Assistant, pull or rebuild "
|
"If you are running openHop Repeater via Docker or Home Assistant, pull or rebuild "
|
||||||
"a newer image for packaged image updates to take effect."
|
"a newer image for packaged image updates to take effect."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def restart_service() -> Tuple[bool, str]:
|
def restart_service() -> Tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
Restart the pymc-repeater service.
|
Restart the openhop-repeater service.
|
||||||
|
|
||||||
On Buildroot/Luckfox, use the shipped init script directly.
|
On Buildroot/Luckfox, use the shipped init script directly.
|
||||||
On systemd hosts, try polkit-based restart first (plain systemctl), then
|
On systemd hosts, try polkit-based restart first (plain systemctl), then
|
||||||
@@ -135,7 +135,7 @@ def restart_service() -> Tuple[bool, str]:
|
|||||||
# Try polkit-based restart first (works on bare metal / VMs with polkit running)
|
# Try polkit-based restart first (works on bare metal / VMs with polkit running)
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[_SYSTEMCTL_BIN, "restart", "pymc-repeater"],
|
[_SYSTEMCTL_BIN, "restart", "openhop-repeater"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=5,
|
timeout=5,
|
||||||
@@ -162,10 +162,10 @@ def restart_service() -> Tuple[bool, str]:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Polkit restart attempt failed: {e}")
|
logger.warning(f"Polkit restart attempt failed: {e}")
|
||||||
|
|
||||||
# Fallback: use sudo (requires /etc/sudoers.d/pymc-repeater rule)
|
# Fallback: use sudo (requires /etc/sudoers.d/openhop-repeater rule)
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[_SUDO_BIN, "--non-interactive", _SYSTEMCTL_BIN, "restart", "pymc-repeater"],
|
[_SUDO_BIN, "--non-interactive", _SYSTEMCTL_BIN, "restart", "openhop-repeater"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=5,
|
timeout=5,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from typing import Callable, Optional
|
|||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
import yaml
|
import yaml
|
||||||
from pymc_core.protocol import CryptoUtils
|
from openhop_core.protocol import CryptoUtils
|
||||||
|
|
||||||
from repeater import __version__
|
from repeater import __version__
|
||||||
from repeater.companion.identity_resolve import (
|
from repeater.companion.identity_resolve import (
|
||||||
@@ -198,7 +198,7 @@ class APIEndpoints:
|
|||||||
self.config = config or {}
|
self.config = config or {}
|
||||||
self.event_loop = event_loop
|
self.event_loop = event_loop
|
||||||
self.daemon_instance = daemon_instance
|
self.daemon_instance = daemon_instance
|
||||||
self._config_path = config_path or "/etc/pymc_repeater/config.yaml"
|
self._config_path = config_path or "/etc/openhop_repeater/config.yaml"
|
||||||
self.cad_calibration = CADCalibrationEngine(daemon_instance, event_loop)
|
self.cad_calibration = CADCalibrationEngine(daemon_instance, event_loop)
|
||||||
|
|
||||||
# Initialize ConfigManager for centralized config management
|
# Initialize ConfigManager for centralized config management
|
||||||
@@ -1427,9 +1427,9 @@ class APIEndpoints:
|
|||||||
stats["site_name"] = self.config.get("web", {}).get("site_name", "")
|
stats["site_name"] = self.config.get("web", {}).get("site_name", "")
|
||||||
stats["version"] = __version__
|
stats["version"] = __version__
|
||||||
try:
|
try:
|
||||||
import pymc_core
|
import openhop_core
|
||||||
|
|
||||||
stats["core_version"] = pymc_core.__version__
|
stats["core_version"] = openhop_core.__version__
|
||||||
except ImportError:
|
except ImportError:
|
||||||
stats["core_version"] = "unknown"
|
stats["core_version"] = "unknown"
|
||||||
image_info = get_buildroot_image_info()
|
image_info = get_buildroot_image_info()
|
||||||
@@ -2162,7 +2162,7 @@ class APIEndpoints:
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@cherrypy.tools.json_in()
|
@cherrypy.tools.json_in()
|
||||||
def restart_service(self):
|
def restart_service(self):
|
||||||
"""Restart the pymc-repeater service via systemctl."""
|
"""Restart the openhop-repeater service via systemctl."""
|
||||||
# Enable CORS for this endpoint only if configured
|
# Enable CORS for this endpoint only if configured
|
||||||
self._set_cors_headers()
|
self._set_cors_headers()
|
||||||
|
|
||||||
@@ -3819,7 +3819,7 @@ class APIEndpoints:
|
|||||||
self.config["mesh"]["unscoped_flood_allow"] = unscoped_flood_allow
|
self.config["mesh"]["unscoped_flood_allow"] = unscoped_flood_allow
|
||||||
|
|
||||||
# Get the actual config path from daemon instance (same as CAD settings)
|
# Get the actual config path from daemon instance (same as CAD settings)
|
||||||
config_path = getattr(self, "_config_path", "/etc/pymc_repeater/config.yaml")
|
config_path = getattr(self, "_config_path", "/etc/openhop_repeater/config.yaml")
|
||||||
if self.daemon_instance and hasattr(self.daemon_instance, "config_path"):
|
if self.daemon_instance and hasattr(self.daemon_instance, "config_path"):
|
||||||
config_path = self.daemon_instance.config_path
|
config_path = self.daemon_instance.config_path
|
||||||
|
|
||||||
@@ -3932,7 +3932,7 @@ class APIEndpoints:
|
|||||||
trace_tag = secrets.randbits(32)
|
trace_tag = secrets.randbits(32)
|
||||||
|
|
||||||
# Create trace packet
|
# Create trace packet
|
||||||
from pymc_core.protocol import PacketBuilder
|
from openhop_core.protocol import PacketBuilder
|
||||||
|
|
||||||
path_bytes = list(target_hash.to_bytes(byte_count, "big"))
|
path_bytes = list(target_hash.to_bytes(byte_count, "big"))
|
||||||
packet = PacketBuilder.create_trace(
|
packet = PacketBuilder.create_trace(
|
||||||
@@ -4349,7 +4349,7 @@ class APIEndpoints:
|
|||||||
companion_activation_error = None
|
companion_activation_error = None
|
||||||
if identity_type == "room_server" and self.daemon_instance:
|
if identity_type == "room_server" and self.daemon_instance:
|
||||||
try:
|
try:
|
||||||
from pymc_core import LocalIdentity
|
from openhop_core import LocalIdentity
|
||||||
|
|
||||||
# Create LocalIdentity from the key (convert hex string to bytes)
|
# Create LocalIdentity from the key (convert hex string to bytes)
|
||||||
if isinstance(identity_key, bytes):
|
if isinstance(identity_key, bytes):
|
||||||
@@ -4673,7 +4673,7 @@ class APIEndpoints:
|
|||||||
|
|
||||||
if needs_reload and self.daemon_instance:
|
if needs_reload and self.daemon_instance:
|
||||||
try:
|
try:
|
||||||
from pymc_core import LocalIdentity
|
from openhop_core import LocalIdentity
|
||||||
|
|
||||||
final_name = identity["name"] # Could be new_name
|
final_name = identity["name"] # Could be new_name
|
||||||
identity_key = identity["identity_key"]
|
identity_key = identity["identity_key"]
|
||||||
@@ -4961,8 +4961,8 @@ class APIEndpoints:
|
|||||||
):
|
):
|
||||||
"""Send advert for a room server identity"""
|
"""Send advert for a room server identity"""
|
||||||
try:
|
try:
|
||||||
from pymc_core.protocol import PacketBuilder
|
from openhop_core.protocol import PacketBuilder
|
||||||
from pymc_core.protocol.constants import (
|
from openhop_core.protocol.constants import (
|
||||||
ADVERT_FLAG_HAS_NAME,
|
ADVERT_FLAG_HAS_NAME,
|
||||||
ADVERT_FLAG_IS_ROOM_SERVER,
|
ADVERT_FLAG_IS_ROOM_SERVER,
|
||||||
)
|
)
|
||||||
@@ -6588,7 +6588,7 @@ class APIEndpoints:
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>pyMC Repeater API Documentation</title>
|
<title>openHop Repeater API Documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.10.0/swagger-ui.css">
|
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.10.0/swagger-ui.css">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import time
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from pymc_core.companion.constants import DEFAULT_OFFLINE_QUEUE_SIZE
|
from openhop_core.companion.constants import DEFAULT_OFFLINE_QUEUE_SIZE
|
||||||
|
|
||||||
from repeater.companion.utils import (
|
from repeater.companion.utils import (
|
||||||
trim_companion_contacts_to_fit,
|
trim_companion_contacts_to_fit,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
openapi: 3.0.0
|
openapi: 3.0.0
|
||||||
info:
|
info:
|
||||||
title: pyMC Repeater API
|
title: openHop Repeater API
|
||||||
description: |
|
description: |
|
||||||
REST API for pyMC Repeater - LoRa mesh network repeater with room server functionality.
|
REST API for openHop Repeater - LoRa mesh network repeater with room server functionality.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- System statistics and monitoring
|
- System statistics and monitoring
|
||||||
@@ -14,8 +14,8 @@ info:
|
|||||||
- Noise floor monitoring
|
- Noise floor monitoring
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
contact:
|
contact:
|
||||||
name: pyMC Repeater
|
name: openHop Repeater
|
||||||
url: https://github.com/rightup/pyMC_Repeater
|
url: https://github.com/rightup/openhop-repeater
|
||||||
|
|
||||||
servers:
|
servers:
|
||||||
- url: /api
|
- url: /api
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
OTA Update endpoints for pyMC Repeater.
|
OTA Update endpoints for openHop Repeater.
|
||||||
|
|
||||||
Provides server-side GitHub version checks, background pip-based upgrades with
|
Provides server-side GitHub version checks, background pip-based upgrades with
|
||||||
SSE progress streaming, and release-channel switching.
|
SSE progress streaming, and release-channel switching.
|
||||||
@@ -19,6 +19,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import ssl
|
import ssl
|
||||||
|
|
||||||
# Required for fixed internal maintenance commands.
|
# Required for fixed internal maintenance commands.
|
||||||
@@ -41,10 +42,10 @@ logger = logging.getLogger("HTTPServer")
|
|||||||
# Repository constants
|
# Repository constants
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
GITHUB_OWNER = "rightup"
|
GITHUB_OWNER = "rightup"
|
||||||
GITHUB_REPO = "pyMC_Repeater"
|
GITHUB_REPO = "openhop-repeater"
|
||||||
GITHUB_RAW_BASE = f"https://raw.githubusercontent.com/{GITHUB_OWNER}/{GITHUB_REPO}"
|
GITHUB_RAW_BASE = f"https://raw.githubusercontent.com/{GITHUB_OWNER}/{GITHUB_REPO}"
|
||||||
GITHUB_API_BASE = f"https://api.github.com/repos/{GITHUB_OWNER}/{GITHUB_REPO}"
|
GITHUB_API_BASE = f"https://api.github.com/repos/{GITHUB_OWNER}/{GITHUB_REPO}"
|
||||||
PACKAGE_NAME = "pymc_repeater"
|
PACKAGE_NAME = "openhop_repeater"
|
||||||
|
|
||||||
# How long (seconds) before a cached check result expires
|
# How long (seconds) before a cached check result expires
|
||||||
CHECK_CACHE_TTL = 600 # 10 minutes
|
CHECK_CACHE_TTL = 600 # 10 minutes
|
||||||
@@ -52,6 +53,8 @@ _RM_BIN = "/bin/rm"
|
|||||||
_SED_BIN = "/usr/bin/sed"
|
_SED_BIN = "/usr/bin/sed"
|
||||||
_SYSTEMCTL_BIN = "/bin/systemctl"
|
_SYSTEMCTL_BIN = "/bin/systemctl"
|
||||||
_SUDO_BIN = "/usr/bin/sudo"
|
_SUDO_BIN = "/usr/bin/sudo"
|
||||||
|
_INSTALL_DIR = "/opt/openhop_repeater"
|
||||||
|
_LEGACY_INSTALL_DIR = "/opt/openhop-repeater"
|
||||||
|
|
||||||
_github_ssl_ctx: Optional[ssl.SSLContext] = None
|
_github_ssl_ctx: Optional[ssl.SSLContext] = None
|
||||||
_disk_version_mismatch_logged: Optional[tuple] = None
|
_disk_version_mismatch_logged: Optional[tuple] = None
|
||||||
@@ -69,8 +72,9 @@ def _get_github_ssl_context() -> ssl.SSLContext:
|
|||||||
|
|
||||||
def _find_buildroot_upgrade_helper() -> Optional[str]:
|
def _find_buildroot_upgrade_helper() -> Optional[str]:
|
||||||
candidates = [
|
candidates = [
|
||||||
"/root/pyMC_Repeater/buildroot-manage.sh",
|
"/root/openhop-repeater/buildroot-manage.sh",
|
||||||
"/opt/pymc_repeater/pyMC_Repeater/buildroot-manage.sh",
|
"/opt/openhop_repeater/openhop-repeater/buildroot-manage.sh",
|
||||||
|
"/opt/openhop-repeater/openhop-repeater/buildroot-manage.sh",
|
||||||
"/root/scripts/buildroot-manage.sh",
|
"/root/scripts/buildroot-manage.sh",
|
||||||
]
|
]
|
||||||
for path in candidates:
|
for path in candidates:
|
||||||
@@ -89,7 +93,7 @@ class _RateLimitError(Exception):
|
|||||||
|
|
||||||
def _get_installed_version(force_refresh: bool = False) -> str:
|
def _get_installed_version(force_refresh: bool = False) -> str:
|
||||||
"""
|
"""
|
||||||
Return the highest dist-info version found for pymc_repeater across all
|
Return the highest dist-info version found for openhop_repeater across all
|
||||||
directories the running interpreter actually uses.
|
directories the running interpreter actually uses.
|
||||||
|
|
||||||
Search strategy (union of all three to cover venvs, system, dist-packages):
|
Search strategy (union of all three to cover venvs, system, dist-packages):
|
||||||
@@ -135,7 +139,7 @@ def _get_installed_version(force_refresh: bool = False) -> str:
|
|||||||
if p and ("site-packages" in p or "dist-packages" in p) and p not in dirs:
|
if p and ("site-packages" in p or "dist-packages" in p) and p not in dirs:
|
||||||
dirs.append(p)
|
dirs.append(p)
|
||||||
# Explicitly include the dedicated venv's site-packages
|
# Explicitly include the dedicated venv's site-packages
|
||||||
_venv_site = "/opt/pymc_repeater/venv/lib"
|
_venv_site = "/opt/openhop_repeater/venv/lib"
|
||||||
if os.path.isdir(_venv_site):
|
if os.path.isdir(_venv_site):
|
||||||
for child in os.listdir(_venv_site):
|
for child in os.listdir(_venv_site):
|
||||||
sp = os.path.join(_venv_site, child, "site-packages")
|
sp = os.path.join(_venv_site, child, "site-packages")
|
||||||
@@ -228,7 +232,7 @@ def _get_installed_version(force_refresh: bool = False) -> str:
|
|||||||
|
|
||||||
|
|
||||||
# Channels file – persisted so the choice survives daemon restarts
|
# Channels file – persisted so the choice survives daemon restarts
|
||||||
_CHANNELS_FILE = "/var/lib/pymc_repeater/.update_channel"
|
_CHANNELS_FILE = "/var/lib/openhop_repeater/.update_channel"
|
||||||
|
|
||||||
|
|
||||||
def _detect_channel_from_dist_info() -> Optional[str]:
|
def _detect_channel_from_dist_info() -> Optional[str]:
|
||||||
@@ -466,7 +470,7 @@ def _fetch_url(url: str, timeout: int = 10) -> str:
|
|||||||
Raises _RateLimitError on HTTP 403 so callers can back off gracefully.
|
Raises _RateLimitError on HTTP 403 so callers can back off gracefully.
|
||||||
"""
|
"""
|
||||||
installed = _get_installed_version()
|
installed = _get_installed_version()
|
||||||
headers = {"User-Agent": f"pymc-repeater/{installed}"}
|
headers = {"User-Agent": f"openhop-repeater/{installed}"}
|
||||||
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
||||||
if token:
|
if token:
|
||||||
headers["Authorization"] = f"Bearer {token}"
|
headers["Authorization"] = f"Bearer {token}"
|
||||||
@@ -565,7 +569,7 @@ def _cleanup_stale_dist_info(allow_sudo: bool = True) -> None:
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
# Also scan the dedicated venv's site-packages
|
# Also scan the dedicated venv's site-packages
|
||||||
_venv_site = "/opt/pymc_repeater/venv/lib"
|
_venv_site = "/opt/openhop_repeater/venv/lib"
|
||||||
if os.path.isdir(_venv_site):
|
if os.path.isdir(_venv_site):
|
||||||
for child in os.listdir(_venv_site):
|
for child in os.listdir(_venv_site):
|
||||||
sp = os.path.join(_venv_site, child, "site-packages")
|
sp = os.path.join(_venv_site, child, "site-packages")
|
||||||
@@ -574,7 +578,7 @@ def _cleanup_stale_dist_info(allow_sudo: bool = True) -> None:
|
|||||||
|
|
||||||
pkg_glob = PACKAGE_NAME.replace("-", "_") + "-*.dist-info"
|
pkg_glob = PACKAGE_NAME.replace("-", "_") + "-*.dist-info"
|
||||||
|
|
||||||
# Collect {path: version_str} for every pymc_repeater dist-info we find
|
# Collect {path: version_str} for every openhop_repeater dist-info we find
|
||||||
found: dict = {}
|
found: dict = {}
|
||||||
for site_dir in dirs:
|
for site_dir in dirs:
|
||||||
for meta_dir in glob.glob(os.path.join(site_dir, pkg_glob)):
|
for meta_dir in glob.glob(os.path.join(site_dir, pkg_glob)):
|
||||||
@@ -794,15 +798,24 @@ def _migrate_service_unit() -> None:
|
|||||||
logger.info("[Update] Buildroot image detected, skipping systemd unit migration.")
|
logger.info("[Update] Buildroot image detected, skipping systemd unit migration.")
|
||||||
return
|
return
|
||||||
|
|
||||||
_SVC_UNIT = "/etc/systemd/system/pymc-repeater.service"
|
_SVC_UNIT = "/etc/systemd/system/openhop-repeater.service"
|
||||||
_VENV_PYTHON = "/opt/pymc_repeater/venv/bin/python"
|
_VENV_PYTHON = "/opt/openhop_repeater/venv/bin/python"
|
||||||
try:
|
try:
|
||||||
subprocess.run([_SED_BIN, "-i", "/^Environment=.*PYTHONPATH/d", _SVC_UNIT], check=False) # nosec B603
|
subprocess.run([_SED_BIN, "-i", "/^Environment=.*PYTHONPATH/d", _SVC_UNIT], check=False) # nosec B603
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[
|
[
|
||||||
_SED_BIN,
|
_SED_BIN,
|
||||||
"-i",
|
"-i",
|
||||||
"s|WorkingDirectory=/opt/pymc_repeater|WorkingDirectory=/var/lib/pymc_repeater|",
|
"s|WorkingDirectory=/opt/openhop_repeater|WorkingDirectory=/var/lib/openhop_repeater|",
|
||||||
|
_SVC_UNIT,
|
||||||
|
],
|
||||||
|
check=False,
|
||||||
|
) # nosec B603
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
_SED_BIN,
|
||||||
|
"-i",
|
||||||
|
"s|WorkingDirectory=/opt/openhop-repeater|WorkingDirectory=/var/lib/openhop_repeater|",
|
||||||
_SVC_UNIT,
|
_SVC_UNIT,
|
||||||
],
|
],
|
||||||
check=False,
|
check=False,
|
||||||
@@ -817,6 +830,68 @@ def _migrate_service_unit() -> None:
|
|||||||
logger.warning(f"[Update] Service unit migration failed: {exc}")
|
logger.warning(f"[Update] Service unit migration failed: {exc}")
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_tree_missing_only(src: str, dst: str) -> None:
|
||||||
|
"""Merge src into dst without overwriting existing files."""
|
||||||
|
if not os.path.isdir(src):
|
||||||
|
return
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(src):
|
||||||
|
rel = os.path.relpath(root, src)
|
||||||
|
target_root = dst if rel == "." else os.path.join(dst, rel)
|
||||||
|
os.makedirs(target_root, exist_ok=True)
|
||||||
|
|
||||||
|
for d in dirs:
|
||||||
|
os.makedirs(os.path.join(target_root, d), exist_ok=True)
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
src_file = os.path.join(root, f)
|
||||||
|
dst_file = os.path.join(target_root, f)
|
||||||
|
if not os.path.exists(dst_file):
|
||||||
|
shutil.copy2(src_file, dst_file)
|
||||||
|
|
||||||
|
|
||||||
|
def _migrate_legacy_install_path() -> None:
|
||||||
|
"""Handle /opt/openhop-repeater -> /opt/openhop_repeater migration safely."""
|
||||||
|
if not os.path.exists(_LEGACY_INSTALL_DIR):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not os.path.exists(_INSTALL_DIR):
|
||||||
|
os.rename(_LEGACY_INSTALL_DIR, _INSTALL_DIR)
|
||||||
|
_state.append_line(
|
||||||
|
f"[pyMC updater] Migrated legacy install path: {_LEGACY_INSTALL_DIR} -> {_INSTALL_DIR}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
_merge_tree_missing_only(_LEGACY_INSTALL_DIR, _INSTALL_DIR)
|
||||||
|
backup_path = f"{_LEGACY_INSTALL_DIR}.migrated.{time.strftime('%Y%m%d_%H%M%S')}"
|
||||||
|
os.rename(_LEGACY_INSTALL_DIR, backup_path)
|
||||||
|
_state.append_line(f"[pyMC updater] Merged legacy install data into {_INSTALL_DIR}")
|
||||||
|
_state.append_line(f"[pyMC updater] Archived legacy install path at {backup_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def _cleanup_stale_source_trees() -> None:
|
||||||
|
stale_paths = [
|
||||||
|
"/opt/openhop_repeater/repeater",
|
||||||
|
"/opt/openhop_repeater/openhop_core",
|
||||||
|
"/opt/openhop_repeater/openhop-repeater",
|
||||||
|
"/opt/openhop_repeater/openhop-core",
|
||||||
|
"/opt/openhop-repeater/repeater",
|
||||||
|
"/opt/openhop-repeater/openhop_core",
|
||||||
|
"/opt/openhop-repeater/openhop-repeater",
|
||||||
|
"/opt/openhop-repeater/openhop-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
removed_any = False
|
||||||
|
for stale_src in stale_paths:
|
||||||
|
if os.path.isdir(stale_src):
|
||||||
|
removed_any = True
|
||||||
|
_state.append_line(f"[pyMC updater] Removing stale source tree: {stale_src}")
|
||||||
|
shutil.rmtree(stale_src, ignore_errors=True)
|
||||||
|
|
||||||
|
if not removed_any:
|
||||||
|
_state.append_line("[pyMC updater] No stale source-tree paths found")
|
||||||
|
|
||||||
|
|
||||||
def _do_install() -> None:
|
def _do_install() -> None:
|
||||||
|
|
||||||
channel = _state.channel
|
channel = _state.channel
|
||||||
@@ -850,7 +925,7 @@ def _do_install() -> None:
|
|||||||
env = _os.environ.copy()
|
env = _os.environ.copy()
|
||||||
env["SETUPTOOLS_SCM_PRETEND_VERSION"] = _state.latest_version or "1.0.0"
|
env["SETUPTOOLS_SCM_PRETEND_VERSION"] = _state.latest_version or "1.0.0"
|
||||||
|
|
||||||
_VENV_DIR = "/opt/pymc_repeater/venv"
|
_VENV_DIR = "/opt/openhop_repeater/venv"
|
||||||
_VENV_PIP = os.path.join(_VENV_DIR, "bin", "pip")
|
_VENV_PIP = os.path.join(_VENV_DIR, "bin", "pip")
|
||||||
_VENV_PYTHON = os.path.join(_VENV_DIR, "bin", "python")
|
_VENV_PYTHON = os.path.join(_VENV_DIR, "bin", "python")
|
||||||
|
|
||||||
@@ -874,6 +949,7 @@ def _do_install() -> None:
|
|||||||
)
|
)
|
||||||
cmd = ["/bin/sh", _BUILDROOT_UPGRADE_HELPER, "upgrade"]
|
cmd = ["/bin/sh", _BUILDROOT_UPGRADE_HELPER, "upgrade"]
|
||||||
elif is_root:
|
elif is_root:
|
||||||
|
_migrate_legacy_install_path()
|
||||||
_migrate_service_unit()
|
_migrate_service_unit()
|
||||||
|
|
||||||
# Ensure venv exists (migration from system-pip era)
|
# Ensure venv exists (migration from system-pip era)
|
||||||
@@ -883,18 +959,13 @@ def _do_install() -> None:
|
|||||||
_run([_VENV_PIP, "install", "--upgrade", "pip", "setuptools", "wheel"], env=env)
|
_run([_VENV_PIP, "install", "--upgrade", "pip", "setuptools", "wheel"], env=env)
|
||||||
|
|
||||||
# Clean up system-level packages to avoid shadowing
|
# Clean up system-level packages to avoid shadowing
|
||||||
_run(["/usr/bin/python3", "-m", "pip", "uninstall", "-y", "pymc_repeater"], env=env)
|
_run(["/usr/bin/python3", "-m", "pip", "uninstall", "-y", "openhop_repeater"], env=env)
|
||||||
_run(["/usr/bin/python3", "-m", "pip", "uninstall", "-y", "pymc_core"], env=env)
|
_run(["/usr/bin/python3", "-m", "pip", "uninstall", "-y", "openhop_core"], env=env)
|
||||||
|
|
||||||
# Remove stale source tree that could shadow the venv package
|
# Remove stale source trees that could shadow the venv package
|
||||||
stale_src = "/opt/pymc_repeater/repeater"
|
_cleanup_stale_source_trees()
|
||||||
if os.path.isdir(stale_src):
|
|
||||||
_state.append_line("[pyMC updater] Removing stale source tree…")
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
shutil.rmtree(stale_src, ignore_errors=True)
|
install_spec = f"openhop_repeater[hardware] @ git+https://github.com/{GITHUB_OWNER}/{GITHUB_REPO}.git@{channel}"
|
||||||
|
|
||||||
install_spec = f"pymc_repeater[hardware] @ git+https://github.com/{GITHUB_OWNER}/{GITHUB_REPO}.git@{channel}"
|
|
||||||
_state.append_line("[pyMC updater] Running as root – venv pip install")
|
_state.append_line("[pyMC updater] Running as root – venv pip install")
|
||||||
_state.append_line(f"[pyMC updater] Target: {install_spec}")
|
_state.append_line(f"[pyMC updater] Target: {install_spec}")
|
||||||
cmd = [
|
cmd = [
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Build development .deb package for pyMC_Repeater
|
# Build development .deb package for openhop-repeater
|
||||||
# Allows building from untagged commits with ~dev version suffix
|
# Allows building from untagged commits with ~dev version suffix
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -31,7 +31,7 @@ log_step() {
|
|||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
PROJECT_ROOT=$(pwd)
|
PROJECT_ROOT=$(pwd)
|
||||||
|
|
||||||
log_info "Building development .deb package for pyMC_Repeater..."
|
log_info "Building development .deb package for openhop-repeater..."
|
||||||
log_info "Project root: $PROJECT_ROOT"
|
log_info "Project root: $PROJECT_ROOT"
|
||||||
|
|
||||||
# Check if we're in a git repository
|
# Check if we're in a git repository
|
||||||
@@ -69,7 +69,7 @@ log_info "Debian version: $DEBIAN_VERSION"
|
|||||||
log_step "Updating debian/changelog..."
|
log_step "Updating debian/changelog..."
|
||||||
CHANGELOG_DATE=$(date -R)
|
CHANGELOG_DATE=$(date -R)
|
||||||
cat > debian/changelog << EOF
|
cat > debian/changelog << EOF
|
||||||
pymc-repeater ($DEBIAN_VERSION) unstable; urgency=medium
|
openhop-repeater ($DEBIAN_VERSION) unstable; urgency=medium
|
||||||
|
|
||||||
* Development build from git commit $(git rev-parse --short HEAD)
|
* Development build from git commit $(git rev-parse --short HEAD)
|
||||||
* Version: $VERSION
|
* Version: $VERSION
|
||||||
@@ -81,11 +81,11 @@ log_info "Changelog updated with version $DEBIAN_VERSION"
|
|||||||
|
|
||||||
# Clean previous builds
|
# Clean previous builds
|
||||||
log_step "Cleaning previous builds..."
|
log_step "Cleaning previous builds..."
|
||||||
rm -rf debian/pymc-repeater/
|
rm -rf debian/openhop-repeater/
|
||||||
rm -rf debian/.debhelper/
|
rm -rf debian/.debhelper/
|
||||||
rm -rf debian/files
|
rm -rf debian/files
|
||||||
rm -f debian/pymc-repeater.*.debhelper
|
rm -f debian/openhop-repeater.*.debhelper
|
||||||
rm -f debian/pymc-repeater.substvars
|
rm -f debian/openhop-repeater.substvars
|
||||||
rm -f debian/*.log
|
rm -f debian/*.log
|
||||||
rm -rf .pybuild/
|
rm -rf .pybuild/
|
||||||
rm -rf build/
|
rm -rf build/
|
||||||
@@ -108,7 +108,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Find and display the built package
|
# Find and display the built package
|
||||||
DEB_FILE=$(find .. -maxdepth 1 -name "pymc-repeater_${DEBIAN_VERSION}_*.deb" -type f | head -n 1)
|
DEB_FILE=$(find .. -maxdepth 1 -name "openhop-repeater_${DEBIAN_VERSION}_*.deb" -type f | head -n 1)
|
||||||
|
|
||||||
if [ -n "$DEB_FILE" ]; then
|
if [ -n "$DEB_FILE" ]; then
|
||||||
log_info ""
|
log_info ""
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Build production .deb package for pyMC_Repeater
|
# Build production .deb package for openhop-repeater
|
||||||
# Requires a clean git tag - fails if not on a tagged commit
|
# Requires a clean git tag - fails if not on a tagged commit
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -31,7 +31,7 @@ log_step() {
|
|||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
PROJECT_ROOT=$(pwd)
|
PROJECT_ROOT=$(pwd)
|
||||||
|
|
||||||
log_info "Building production .deb package for pyMC_Repeater..."
|
log_info "Building production .deb package for openhop-repeater..."
|
||||||
log_info "Project root: $PROJECT_ROOT"
|
log_info "Project root: $PROJECT_ROOT"
|
||||||
|
|
||||||
# Check if we're in a git repository
|
# Check if we're in a git repository
|
||||||
@@ -106,7 +106,7 @@ log_info "Debian version: $DEBIAN_VERSION"
|
|||||||
log_step "Updating debian/changelog..."
|
log_step "Updating debian/changelog..."
|
||||||
CHANGELOG_DATE=$(date -R)
|
CHANGELOG_DATE=$(date -R)
|
||||||
cat > debian/changelog << EOF
|
cat > debian/changelog << EOF
|
||||||
pymc-repeater ($DEBIAN_VERSION) stable; urgency=medium
|
openhop-repeater ($DEBIAN_VERSION) stable; urgency=medium
|
||||||
|
|
||||||
* Production release $VERSION
|
* Production release $VERSION
|
||||||
* Git tag: $TAG
|
* Git tag: $TAG
|
||||||
@@ -119,11 +119,11 @@ log_info "Changelog updated with version $DEBIAN_VERSION"
|
|||||||
|
|
||||||
# Clean previous builds
|
# Clean previous builds
|
||||||
log_step "Cleaning previous builds..."
|
log_step "Cleaning previous builds..."
|
||||||
rm -rf debian/pymc-repeater/
|
rm -rf debian/openhop-repeater/
|
||||||
rm -rf debian/.debhelper/
|
rm -rf debian/.debhelper/
|
||||||
rm -rf debian/files
|
rm -rf debian/files
|
||||||
rm -f debian/pymc-repeater.*.debhelper
|
rm -f debian/openhop-repeater.*.debhelper
|
||||||
rm -f debian/pymc-repeater.substvars
|
rm -f debian/openhop-repeater.substvars
|
||||||
rm -f debian/*.log
|
rm -f debian/*.log
|
||||||
rm -rf .pybuild/
|
rm -rf .pybuild/
|
||||||
rm -rf build/
|
rm -rf build/
|
||||||
@@ -146,7 +146,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Find and display the built package
|
# Find and display the built package
|
||||||
DEB_FILE=$(find .. -maxdepth 1 -name "pymc-repeater_${DEBIAN_VERSION}_*.deb" -type f | head -n 1)
|
DEB_FILE=$(find .. -maxdepth 1 -name "openhop-repeater_${DEBIAN_VERSION}_*.deb" -type f | head -n 1)
|
||||||
|
|
||||||
if [ -n "$DEB_FILE" ]; then
|
if [ -n "$DEB_FILE" ]; then
|
||||||
# Run lintian to check package quality
|
# Run lintian to check package quality
|
||||||
|
|||||||
+27
-27
@@ -1,24 +1,24 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# pyMC Repeater - Proxmox LXC Installer
|
# openHop Repeater - Proxmox LXC Installer
|
||||||
# Creates an LXC container with USB passthrough and installs pyMC Repeater
|
# Creates an LXC container with USB passthrough and installs openHop Repeater
|
||||||
#
|
#
|
||||||
# Usage (run on the Proxmox host):
|
# Usage (run on the Proxmox host):
|
||||||
# bash -c "$(curl -fsSL https://raw.githubusercontent.com/pyMC-dev/pyMC_Repeater/main/scripts/proxmox-install.sh)"
|
# bash -c "$(curl -fsSL https://raw.githubusercontent.com/pyMC-dev/openhop-repeater/main/scripts/proxmox-install.sh)"
|
||||||
#
|
#
|
||||||
# License: MIT
|
# License: MIT
|
||||||
# Source: https://github.com/pyMC-dev/pyMC_Repeater
|
# Source: https://github.com/pyMC-dev/openhop-repeater
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# ── Defaults ───────────────────────────────────────────────────────────────
|
# ── Defaults ───────────────────────────────────────────────────────────────
|
||||||
REPO="https://github.com/pyMC-dev/pyMC_Repeater.git"
|
REPO="https://github.com/pyMC-dev/openhop-repeater.git"
|
||||||
BRANCH="dev"
|
BRANCH="dev"
|
||||||
CT_TEMPLATE="debian-12-standard"
|
CT_TEMPLATE="debian-12-standard"
|
||||||
CT_RAM=1024
|
CT_RAM=1024
|
||||||
CT_SWAP=512
|
CT_SWAP=512
|
||||||
CT_DISK=4
|
CT_DISK=4
|
||||||
CT_CORES=2
|
CT_CORES=2
|
||||||
CT_HOSTNAME="pymc-repeater"
|
CT_HOSTNAME="openhop-repeater"
|
||||||
CT_BRIDGE="vmbr0"
|
CT_BRIDGE="vmbr0"
|
||||||
CT_STORAGE="local-lvm"
|
CT_STORAGE="local-lvm"
|
||||||
CT_TEMPLATE_STORAGE="local"
|
CT_TEMPLATE_STORAGE="local"
|
||||||
@@ -37,7 +37,7 @@ header() {
|
|||||||
clear
|
clear
|
||||||
echo -e "${BLD}"
|
echo -e "${BLD}"
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
echo " pyMC Repeater - Proxmox LXC Installer"
|
echo " openHop Repeater - Proxmox LXC Installer"
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
echo -e "${CL}"
|
echo -e "${CL}"
|
||||||
}
|
}
|
||||||
@@ -154,7 +154,7 @@ msg_ok "Container created"
|
|||||||
msg_info "Configuring USB passthrough..."
|
msg_info "Configuring USB passthrough..."
|
||||||
cat >> "/etc/pve/lxc/${CTID}.conf" <<'EOF'
|
cat >> "/etc/pve/lxc/${CTID}.conf" <<'EOF'
|
||||||
|
|
||||||
# CH341 USB passthrough for pyMC Repeater
|
# CH341 USB passthrough for openHop Repeater
|
||||||
lxc.cgroup2.devices.allow: c 189:* rwm
|
lxc.cgroup2.devices.allow: c 189:* rwm
|
||||||
lxc.mount.entry: /dev/bus/usb dev/bus/usb none bind,optional,create=dir 0 0
|
lxc.mount.entry: /dev/bus/usb dev/bus/usb none bind,optional,create=dir 0 0
|
||||||
EOF
|
EOF
|
||||||
@@ -209,41 +209,41 @@ IP=\$(hostname -I | awk '{print \$1}')
|
|||||||
OS=\$(. /etc/os-release && echo \"\$NAME\")
|
OS=\$(. /etc/os-release && echo \"\$NAME\")
|
||||||
VER=\$(. /etc/os-release && echo \"\$VERSION_ID\")
|
VER=\$(. /etc/os-release && echo \"\$VERSION_ID\")
|
||||||
echo \"\"
|
echo \"\"
|
||||||
echo \" pyMC Repeater LXC Container\"
|
echo \" openHop Repeater LXC Container\"
|
||||||
echo \" 🌐 GitHub: https://github.com/pyMC-dev/pyMC_Repeater\"
|
echo \" 🌐 GitHub: https://github.com/pyMC-dev/openhop-repeater\"
|
||||||
echo \"\"
|
echo \"\"
|
||||||
echo \" 🖥️ OS: \$OS - Version: \$VER\"
|
echo \" 🖥️ OS: \$OS - Version: \$VER\"
|
||||||
echo \" 🏠 Hostname: \$HOSTNAME\"
|
echo \" 🏠 Hostname: \$HOSTNAME\"
|
||||||
echo \" 💡 IP Address: \$IP\"
|
echo \" 💡 IP Address: \$IP\"
|
||||||
echo \" 📡 Dashboard: http://\$IP:8000\"
|
echo \" 📡 Dashboard: http://\$IP:8000\"
|
||||||
echo \"\"
|
echo \"\"
|
||||||
echo \" Management: cd /opt/pymc_repeater && bash manage.sh\"
|
echo \" Management: cd /opt/openhop_repeater && bash manage.sh\"
|
||||||
echo \"\"
|
echo \"\"
|
||||||
MOTD
|
MOTD
|
||||||
chmod +x /etc/profile.d/pymc-motd.sh
|
chmod +x /etc/profile.d/pymc-motd.sh
|
||||||
"
|
"
|
||||||
msg_ok "curl/git installed, locale fixed, console auto-login enabled"
|
msg_ok "curl/git installed, locale fixed, console auto-login enabled"
|
||||||
|
|
||||||
msg_info "Cloning pyMC_Repeater (branch: ${BRANCH})..."
|
msg_info "Cloning openhop-repeater (branch: ${BRANCH})..."
|
||||||
pct exec "$CTID" -- bash -c "git clone --branch ${BRANCH} ${REPO} /root/pyMC_Repeater"
|
pct exec "$CTID" -- bash -c "git clone --branch ${BRANCH} ${REPO} /root/openhop-repeater"
|
||||||
msg_ok "Repository cloned"
|
msg_ok "Repository cloned"
|
||||||
|
|
||||||
# Pre-seed config with CH341 radio type and correct GPIO pins
|
# Pre-seed config with CH341 radio type and correct GPIO pins
|
||||||
pct exec "$CTID" -- bash -c "
|
pct exec "$CTID" -- bash -c "
|
||||||
mkdir -p /etc/pymc_repeater
|
mkdir -p /etc/openhop_repeater
|
||||||
if [ -f /root/pyMC_Repeater/config.yaml.example ]; then
|
if [ -f /root/openhop-repeater/config.yaml.example ]; then
|
||||||
cp /root/pyMC_Repeater/config.yaml.example /etc/pymc_repeater/config.yaml
|
cp /root/openhop-repeater/config.yaml.example /etc/openhop_repeater/config.yaml
|
||||||
# Set radio type to CH341
|
# Set radio type to CH341
|
||||||
sed -i 's/^radio_type: sx1262$/radio_type: sx1262_ch341/' /etc/pymc_repeater/config.yaml
|
sed -i 's/^radio_type: sx1262$/radio_type: sx1262_ch341/' /etc/openhop_repeater/config.yaml
|
||||||
# Replace Pi BCM GPIO pins with CH341 GPIO pin numbers (0-7)
|
# Replace Pi BCM GPIO pins with CH341 GPIO pin numbers (0-7)
|
||||||
sed -i 's/cs_pin: 21/cs_pin: 0/' /etc/pymc_repeater/config.yaml
|
sed -i 's/cs_pin: 21/cs_pin: 0/' /etc/openhop_repeater/config.yaml
|
||||||
sed -i 's/reset_pin: 18/reset_pin: 2/' /etc/pymc_repeater/config.yaml
|
sed -i 's/reset_pin: 18/reset_pin: 2/' /etc/openhop_repeater/config.yaml
|
||||||
sed -i 's/busy_pin: 20/busy_pin: 4/' /etc/pymc_repeater/config.yaml
|
sed -i 's/busy_pin: 20/busy_pin: 4/' /etc/openhop_repeater/config.yaml
|
||||||
sed -i 's/irq_pin: 16/irq_pin: 6/' /etc/pymc_repeater/config.yaml
|
sed -i 's/irq_pin: 16/irq_pin: 6/' /etc/openhop_repeater/config.yaml
|
||||||
sed -i 's/rxen_pin: -1/rxen_pin: 1/' /etc/pymc_repeater/config.yaml
|
sed -i 's/rxen_pin: -1/rxen_pin: 1/' /etc/openhop_repeater/config.yaml
|
||||||
# Enable TCXO and DIO2 RF switch for E22 module
|
# Enable TCXO and DIO2 RF switch for E22 module
|
||||||
sed -i 's/use_dio3_tcxo: false/use_dio3_tcxo: true/' /etc/pymc_repeater/config.yaml
|
sed -i 's/use_dio3_tcxo: false/use_dio3_tcxo: true/' /etc/openhop_repeater/config.yaml
|
||||||
sed -i 's/use_dio2_rf: false/use_dio2_rf: true/' /etc/pymc_repeater/config.yaml
|
sed -i 's/use_dio2_rf: false/use_dio2_rf: true/' /etc/openhop_repeater/config.yaml
|
||||||
fi
|
fi
|
||||||
"
|
"
|
||||||
|
|
||||||
@@ -251,7 +251,7 @@ pct exec "$CTID" -- bash -c "
|
|||||||
msg_info "Running manage.sh install (this will take several minutes)..."
|
msg_info "Running manage.sh install (this will take several minutes)..."
|
||||||
echo ""
|
echo ""
|
||||||
# Use lxc-attach with a pty so manage.sh gets an interactive terminal
|
# Use lxc-attach with a pty so manage.sh gets an interactive terminal
|
||||||
lxc-attach -n "$CTID" -- bash -c "cd /root/pyMC_Repeater && TERM=xterm bash manage.sh install"
|
lxc-attach -n "$CTID" -- bash -c "cd /root/openhop-repeater && TERM=xterm bash manage.sh install"
|
||||||
echo ""
|
echo ""
|
||||||
msg_ok "manage.sh install completed"
|
msg_ok "manage.sh install completed"
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ CT_IP=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}')
|
|||||||
echo ""
|
echo ""
|
||||||
echo -e "${BLD}"
|
echo -e "${BLD}"
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
echo " ✓ pyMC Repeater Installation Complete!"
|
echo " ✓ openHop Repeater Installation Complete!"
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
echo -e "${CL}"
|
echo -e "${CL}"
|
||||||
echo -e " Container: ${GN}${CTID}${CL} (${CT_HOSTNAME})"
|
echo -e " Container: ${GN}${CTID}${CL} (${CT_HOSTNAME})"
|
||||||
@@ -271,6 +271,6 @@ echo -e " IP Address: ${GN}${CT_IP:-unknown}${CL}"
|
|||||||
echo -e " Dashboard: ${GN}http://${CT_IP:-<ip>}:8000${CL}"
|
echo -e " Dashboard: ${GN}http://${CT_IP:-<ip>}:8000${CL}"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Next: open the dashboard and complete the setup wizard"
|
echo " Next: open the dashboard and complete the setup wizard"
|
||||||
echo " Management: pct enter ${CTID}, then: cd /opt/pymc_repeater && bash manage.sh"
|
echo " Management: pct enter ${CTID}, then: cd /opt/openhop_repeater && bash manage.sh"
|
||||||
echo ""
|
echo ""
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Setup Debian/Ubuntu build environment for pyMC_Repeater
|
# Setup Debian/Ubuntu build environment for openhop-repeater
|
||||||
# This script installs all required build dependencies using apt
|
# This script installs all required build dependencies using apt
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -28,7 +28,7 @@ if [ "$EUID" -ne 0 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "Setting up build environment for pyMC_Repeater..."
|
log_info "Setting up build environment for openhop-repeater..."
|
||||||
|
|
||||||
# Update package list
|
# Update package list
|
||||||
log_info "Updating package lists..."
|
log_info "Updating package lists..."
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Radio configuration setup script for pyMC Repeater
|
# Radio configuration setup script for openHop Repeater
|
||||||
|
|
||||||
CONFIG_DIR="${1:-.}"
|
CONFIG_DIR="${1:-.}"
|
||||||
CONFIG_FILE="$CONFIG_DIR/config.yaml"
|
CONFIG_FILE="$CONFIG_DIR/config.yaml"
|
||||||
@@ -15,7 +15,7 @@ else
|
|||||||
SED_OPTS=(-i)
|
SED_OPTS=(-i)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "=== pyMC Repeater Radio Configuration ==="
|
echo "=== openHop Repeater Radio Configuration ==="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Step 0: Repeater Name
|
# Step 0: Repeater Name
|
||||||
@@ -48,7 +48,7 @@ echo ""
|
|||||||
echo "=== Step 0.5: Select Radio Type ==="
|
echo "=== Step 0.5: Select Radio Type ==="
|
||||||
echo ""
|
echo ""
|
||||||
echo " 1) SX1262 hardware (SPI LoRa module - Raspberry Pi HAT, etc.)"
|
echo " 1) SX1262 hardware (SPI LoRa module - Raspberry Pi HAT, etc.)"
|
||||||
echo " 2) KISS modem (serial TNC - requires pyMC_core with KISS support)"
|
echo " 2) KISS modem (serial TNC - requires openhop-core with KISS support)"
|
||||||
echo ""
|
echo ""
|
||||||
read -p "Select radio type (1 or 2): " radio_type_sel
|
read -p "Select radio type (1 or 2): " radio_type_sel
|
||||||
|
|
||||||
@@ -435,7 +435,7 @@ if [ "$RADIO_TYPE" = "sx1262" ] && [ -n "$bus_id" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Enable and start the service
|
# Enable and start the service
|
||||||
SERVICE_NAME="pymc-repeater"
|
SERVICE_NAME="openhop-repeater"
|
||||||
if systemctl list-unit-files | grep -q "^$SERVICE_NAME\.service"; then
|
if systemctl list-unit-files | grep -q "^$SERVICE_NAME\.service"; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Enabling and starting the $SERVICE_NAME service..."
|
echo "Enabling and starting the $SERVICE_NAME service..."
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Setup script for pymc_repeater
|
Setup script for openhop_repeater
|
||||||
Version is managed by setuptools_scm from git tags
|
Version is managed by setuptools_scm from git tags
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from typing import Any, cast
|
|||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pymc_core.companion.constants import RESP_CODE_NO_MORE_MESSAGES
|
from openhop_core.companion.constants import RESP_CODE_NO_MORE_MESSAGES
|
||||||
|
|
||||||
from repeater.companion.bridge import RepeaterCompanionBridge, _to_json_safe
|
from repeater.companion.bridge import RepeaterCompanionBridge, _to_json_safe
|
||||||
from repeater.companion.frame_server import CompanionFrameServer
|
from repeater.companion.frame_server import CompanionFrameServer
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pymc_core import LocalIdentity
|
from openhop_core import LocalIdentity
|
||||||
|
|
||||||
from repeater.companion.bridge import RepeaterCompanionBridge, _prefs_bytes_from_json
|
from repeater.companion.bridge import RepeaterCompanionBridge, _prefs_bytes_from_json
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from repeater.companion.utils import (
|
|||||||
validate_companion_config_capacity,
|
validate_companion_config_capacity,
|
||||||
)
|
)
|
||||||
|
|
||||||
# pymc_core defaults (CompanionBridge / ContactStore)
|
# openhop_core defaults (CompanionBridge / ContactStore)
|
||||||
_DEFAULT_MAX_CONTACTS = 1000
|
_DEFAULT_MAX_CONTACTS = 1000
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
tests for pyMC_Repeater engine.py — RepeaterHandler.
|
tests for openhop-repeater engine.py — RepeaterHandler.
|
||||||
|
|
||||||
Covers: flood_forward, direct_forward, process_packet, duplicate detection,
|
Covers: flood_forward, direct_forward, process_packet, duplicate detection,
|
||||||
mark_seen, validate_packet, packet scoring, TX delay, cache management,
|
mark_seen, validate_packet, packet scoring, TX delay, cache management,
|
||||||
@@ -12,8 +12,8 @@ import time
|
|||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pymc_core.protocol import Packet, PacketBuilder
|
from openhop_core.protocol import Packet, PacketBuilder
|
||||||
from pymc_core.protocol.constants import (
|
from openhop_core.protocol.constants import (
|
||||||
MAX_PATH_SIZE,
|
MAX_PATH_SIZE,
|
||||||
PH_ROUTE_MASK,
|
PH_ROUTE_MASK,
|
||||||
PH_TYPE_SHIFT,
|
PH_TYPE_SHIFT,
|
||||||
@@ -1754,7 +1754,7 @@ class TestMissedEngineBranches:
|
|||||||
pkt = _make_transport_flood_packet(payload=b"\x01\x02", path=b"")
|
pkt = _make_transport_flood_packet(payload=b"\x01\x02", path=b"")
|
||||||
pkt.transport_codes = [0xCAFE, 0xBEEF]
|
pkt.transport_codes = [0xCAFE, 0xBEEF]
|
||||||
|
|
||||||
with patch("pymc_core.protocol.transport_keys.calc_transport_code", return_value=0xCAFE):
|
with patch("openhop_core.protocol.transport_keys.calc_transport_code", return_value=0xCAFE):
|
||||||
allowed_1, reason_1 = handler._check_transport_codes(pkt)
|
allowed_1, reason_1 = handler._check_transport_codes(pkt)
|
||||||
allowed_2, reason_2 = handler._check_transport_codes(pkt)
|
allowed_2, reason_2 = handler._check_transport_codes(pkt)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Tests for flood packet loop detection and duplicate suppression.
|
Tests for flood packet loop detection and duplicate suppression.
|
||||||
|
|
||||||
Exercises the real RepeaterHandler engine with real pymc_core Packet/PathUtils
|
Exercises the real RepeaterHandler engine with real openhop_core Packet/PathUtils
|
||||||
objects to verify:
|
objects to verify:
|
||||||
- Duplicate packet suppression via calculate_packet_hash (SHA256-based)
|
- Duplicate packet suppression via calculate_packet_hash (SHA256-based)
|
||||||
- Loop detection modes (off, minimal, moderate, strict) with real path bytes
|
- Loop detection modes (off, minimal, moderate, strict) with real path bytes
|
||||||
@@ -15,8 +15,8 @@ objects to verify:
|
|||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
|
||||||
from pymc_core.protocol import Packet, PathUtils
|
from openhop_core.protocol import Packet, PathUtils
|
||||||
from pymc_core.protocol.constants import (
|
from openhop_core.protocol.constants import (
|
||||||
ROUTE_TYPE_FLOOD,
|
ROUTE_TYPE_FLOOD,
|
||||||
ROUTE_TYPE_TRANSPORT_FLOOD,
|
ROUTE_TYPE_TRANSPORT_FLOOD,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ async def test_path_helper_updates_client_out_path_on_valid_decrypt():
|
|||||||
packet = _PathPacket(payload=b"\x11\x22\xaa\xbb\xcc")
|
packet = _PathPacket(payload=b"\x11\x22\xaa\xbb\xcc")
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"pymc_core.protocol.crypto.CryptoUtils.mac_then_decrypt", return_value=b"\x02\x99\x88\x01"
|
"openhop_core.protocol.crypto.CryptoUtils.mac_then_decrypt", return_value=b"\x02\x99\x88\x01"
|
||||||
):
|
):
|
||||||
handled = await helper.process_path_packet(packet)
|
handled = await helper.process_path_packet(packet)
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ async def test_path_helper_returns_false_for_non_matching_or_invalid_inputs():
|
|||||||
is False
|
is False
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("pymc_core.protocol.crypto.CryptoUtils.mac_then_decrypt", return_value=None):
|
with patch("openhop_core.protocol.crypto.CryptoUtils.mac_then_decrypt", return_value=None):
|
||||||
assert await helper.process_path_packet(_PathPacket(payload=b"\x11\x22\xaa\xbb")) is False
|
assert await helper.process_path_packet(_PathPacket(payload=b"\x11\x22\xaa\xbb")) is False
|
||||||
|
|
||||||
|
|
||||||
@@ -356,7 +356,7 @@ async def test_text_helper_send_cli_reply_uses_direct_path_from_client():
|
|||||||
reply_packet = SimpleNamespace(path=bytearray(), path_len=0)
|
reply_packet = SimpleNamespace(path=bytearray(), path_len=0)
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch("pymc_core.protocol.PacketBuilder.create_datagram", return_value=reply_packet),
|
patch("openhop_core.protocol.PacketBuilder.create_datagram", return_value=reply_packet),
|
||||||
patch("repeater.handler_helpers.text.asyncio.sleep", new_callable=AsyncMock),
|
patch("repeater.handler_helpers.text.asyncio.sleep", new_callable=AsyncMock),
|
||||||
):
|
):
|
||||||
await helper._send_cli_reply(
|
await helper._send_cli_reply(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pymc_core.protocol.constants import PAYLOAD_TYPE_ANON_REQ, ROUTE_TYPE_DIRECT
|
from openhop_core.protocol.constants import PAYLOAD_TYPE_ANON_REQ, ROUTE_TYPE_DIRECT
|
||||||
from repeater.handler_helpers.discovery import DiscoveryHelper
|
from repeater.handler_helpers.discovery import DiscoveryHelper
|
||||||
from repeater.handler_helpers.login import LoginHelper
|
from repeater.handler_helpers.login import LoginHelper
|
||||||
from repeater.handler_helpers.trace import TraceHelper
|
from repeater.handler_helpers.trace import TraceHelper
|
||||||
@@ -258,7 +258,7 @@ def test_discovery_send_response_without_injector_is_safe():
|
|||||||
helper = DiscoveryHelper(local_identity=FakeIdentity(0x42), packet_injector=None)
|
helper = DiscoveryHelper(local_identity=FakeIdentity(0x42), packet_injector=None)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"pymc_core.protocol.packet_builder.PacketBuilder.create_discovery_response",
|
"openhop_core.protocol.packet_builder.PacketBuilder.create_discovery_response",
|
||||||
return_value=object(),
|
return_value=object(),
|
||||||
):
|
):
|
||||||
helper._send_discovery_response(tag=5, node_type=2, inbound_snr=1.0, prefix_only=False)
|
helper._send_discovery_response(tag=5, node_type=2, inbound_snr=1.0, prefix_only=False)
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ async def test_send_advert_branches_and_success_path():
|
|||||||
)
|
)
|
||||||
|
|
||||||
packet = SimpleNamespace(calculate_packet_hash=lambda: b"\xab" * 16)
|
packet = SimpleNamespace(calculate_packet_hash=lambda: b"\xab" * 16)
|
||||||
with patch("pymc_core.protocol.PacketBuilder.create_advert", return_value=packet):
|
with patch("openhop_core.protocol.PacketBuilder.create_advert", return_value=packet):
|
||||||
ok = await daemon.send_advert()
|
ok = await daemon.send_advert()
|
||||||
|
|
||||||
assert ok is True
|
assert ok is True
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ async def test_load_additional_identities_valid_and_invalid_entries():
|
|||||||
daemon.identity_manager = SimpleNamespace(list_identities=lambda: [1, 2])
|
daemon.identity_manager = SimpleNamespace(list_identities=lambda: [1, 2])
|
||||||
daemon._register_identity_everywhere = MagicMock(return_value=True)
|
daemon._register_identity_everywhere = MagicMock(return_value=True)
|
||||||
|
|
||||||
with patch("pymc_core.LocalIdentity", _FakeLocalIdentity):
|
with patch("openhop_core.LocalIdentity", _FakeLocalIdentity):
|
||||||
await daemon._load_additional_identities()
|
await daemon._load_additional_identities()
|
||||||
|
|
||||||
# Only valid entries should be registered (including 64-byte firmware keys).
|
# Only valid entries should be registered (including 64-byte firmware keys).
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class _FakeIdentity:
|
|||||||
|
|
||||||
|
|
||||||
def _make_config(format_value: str = "letsmesh", iata_code: str = "LAX") -> dict:
|
def _make_config(format_value: str = "letsmesh", iata_code: str = "LAX") -> dict:
|
||||||
"""Minimal pyMC_Repeater config sufficient to construct MeshCoreToMqttPusher."""
|
"""Minimal openhop-repeater config sufficient to construct MeshCoreToMqttPusher."""
|
||||||
return {
|
return {
|
||||||
"repeater": {"node_name": "test-node"},
|
"repeater": {"node_name": "test-node"},
|
||||||
"radio": {
|
"radio": {
|
||||||
|
|||||||
+12
-12
@@ -19,18 +19,18 @@ import time
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
from pymc_core.node.handlers.ack import AckHandler
|
from openhop_core.node.handlers.ack import AckHandler
|
||||||
from pymc_core.node.handlers.advert import AdvertHandler
|
from openhop_core.node.handlers.advert import AdvertHandler
|
||||||
from pymc_core.node.handlers.control import ControlHandler
|
from openhop_core.node.handlers.control import ControlHandler
|
||||||
from pymc_core.node.handlers.group_text import GroupTextHandler
|
from openhop_core.node.handlers.group_text import GroupTextHandler
|
||||||
from pymc_core.node.handlers.login_response import LoginResponseHandler
|
from openhop_core.node.handlers.login_response import LoginResponseHandler
|
||||||
from pymc_core.node.handlers.login_server import LoginServerHandler
|
from openhop_core.node.handlers.login_server import LoginServerHandler
|
||||||
from pymc_core.node.handlers.path import PathHandler
|
from openhop_core.node.handlers.path import PathHandler
|
||||||
from pymc_core.node.handlers.protocol_request import ProtocolRequestHandler
|
from openhop_core.node.handlers.protocol_request import ProtocolRequestHandler
|
||||||
from pymc_core.node.handlers.protocol_response import ProtocolResponseHandler
|
from openhop_core.node.handlers.protocol_response import ProtocolResponseHandler
|
||||||
from pymc_core.node.handlers.text import TextMessageHandler
|
from openhop_core.node.handlers.text import TextMessageHandler
|
||||||
from pymc_core.node.handlers.trace import TraceHandler
|
from openhop_core.node.handlers.trace import TraceHandler
|
||||||
from pymc_core.protocol.constants import ROUTE_TYPE_DIRECT
|
from openhop_core.protocol.constants import ROUTE_TYPE_DIRECT
|
||||||
|
|
||||||
from repeater.packet_router import (
|
from repeater.packet_router import (
|
||||||
PacketRouter,
|
PacketRouter,
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ returns 2 or 3. The dispatcher applies this in send_packet() before transmit.
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pymc_core.node.dispatcher import Dispatcher
|
from openhop_core.node.dispatcher import Dispatcher
|
||||||
from pymc_core.protocol import Packet
|
from openhop_core.protocol import Packet
|
||||||
from pymc_core.protocol.constants import (
|
from openhop_core.protocol.constants import (
|
||||||
PAYLOAD_TYPE_ADVERT,
|
PAYLOAD_TYPE_ADVERT,
|
||||||
PH_TYPE_SHIFT,
|
PH_TYPE_SHIFT,
|
||||||
ROUTE_TYPE_FLOOD,
|
ROUTE_TYPE_FLOOD,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Integration tests for multi-byte path hash support using real pymc_core protocol objects.
|
Integration tests for multi-byte path hash support using real openhop_core protocol objects.
|
||||||
|
|
||||||
Exercises actual Packet, PathUtils, PacketBuilder, and engine forwarding
|
Exercises actual Packet, PathUtils, PacketBuilder, and engine forwarding
|
||||||
rather than mocking the protocol layer. Covers:
|
rather than mocking the protocol layer. Covers:
|
||||||
@@ -16,9 +16,9 @@ import struct
|
|||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pymc_core.node.handlers.trace import TraceHandler
|
from openhop_core.node.handlers.trace import TraceHandler
|
||||||
from pymc_core.protocol import Packet, PacketBuilder, PathUtils
|
from openhop_core.protocol import Packet, PacketBuilder, PathUtils
|
||||||
from pymc_core.protocol.constants import (
|
from openhop_core.protocol.constants import (
|
||||||
MAX_PATH_SIZE,
|
MAX_PATH_SIZE,
|
||||||
PATH_HASH_COUNT_MASK,
|
PATH_HASH_COUNT_MASK,
|
||||||
PATH_HASH_SIZE_SHIFT,
|
PATH_HASH_SIZE_SHIFT,
|
||||||
@@ -730,7 +730,7 @@ class TestTraceHelperMultibyte:
|
|||||||
"""TraceHelper._should_forward_trace with 2-byte TRACE payload hashes."""
|
"""TraceHelper._should_forward_trace with 2-byte TRACE payload hashes."""
|
||||||
|
|
||||||
def test_should_forward_when_next_hop_matches_pubkey_prefix(self):
|
def test_should_forward_when_next_hop_matches_pubkey_prefix(self):
|
||||||
from pymc_core.protocol import LocalIdentity
|
from openhop_core.protocol import LocalIdentity
|
||||||
|
|
||||||
from repeater.handler_helpers.trace import TraceHelper
|
from repeater.handler_helpers.trace import TraceHelper
|
||||||
|
|
||||||
@@ -752,7 +752,7 @@ class TestTraceHelperMultibyte:
|
|||||||
assert th._should_forward_trace(pkt, trace_bytes, flags, hash_width)
|
assert th._should_forward_trace(pkt, trace_bytes, flags, hash_width)
|
||||||
|
|
||||||
def test_should_not_forward_when_next_hop_mismatch(self):
|
def test_should_not_forward_when_next_hop_mismatch(self):
|
||||||
from pymc_core.protocol import LocalIdentity
|
from openhop_core.protocol import LocalIdentity
|
||||||
|
|
||||||
from repeater.handler_helpers.trace import TraceHelper
|
from repeater.handler_helpers.trace import TraceHelper
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from pymc_core.protocol.constants import PAYLOAD_TYPE_GRP_TXT
|
from openhop_core.protocol.constants import PAYLOAD_TYPE_GRP_TXT
|
||||||
from pymc_core.protocol.identity import LocalIdentity
|
from openhop_core.protocol.identity import LocalIdentity
|
||||||
from pymc_core.protocol.packet_builder import PacketBuilder
|
from openhop_core.protocol.packet_builder import PacketBuilder
|
||||||
|
|
||||||
from repeater.policy_engine import PolicyEngine
|
from repeater.policy_engine import PolicyEngine
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Regression tests guarding against Python 3.10 compatibility breakage.
|
"""Regression tests guarding against Python 3.10 compatibility breakage.
|
||||||
|
|
||||||
pyMC Repeater supports Python 3.10+ (LuckFox Pico Ultra ships with 3.10).
|
openHop Repeater supports Python 3.10+ (LuckFox Pico Ultra ships with 3.10).
|
||||||
These tests scan the source tree statically so regressions are caught in CI
|
These tests scan the source tree statically so regressions are caught in CI
|
||||||
without needing a 3.10 interpreter in the test environment.
|
without needing a 3.10 interpreter in the test environment.
|
||||||
"""
|
"""
|
||||||
|
|||||||
+11
-11
@@ -20,7 +20,7 @@ def test_get_radio_for_board_passes_en_pins(monkeypatch):
|
|||||||
return _DummyRadio()
|
return _DummyRadio()
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"pymc_core.hardware.sx1262_wrapper.SX1262Radio",
|
"openhop_core.hardware.sx1262_wrapper.SX1262Radio",
|
||||||
_DummySX1262Radio,
|
_DummySX1262Radio,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ def _pymc_radio_cfg():
|
|||||||
|
|
||||||
|
|
||||||
def test_get_radio_for_board_pymc_tcp(monkeypatch):
|
def test_get_radio_for_board_pymc_tcp(monkeypatch):
|
||||||
pytest.importorskip("pymc_core.hardware.tcp_radio")
|
pytest.importorskip("openhop_core.hardware.tcp_radio")
|
||||||
captured = {}
|
captured = {}
|
||||||
|
|
||||||
class _DummyTCPLoRaRadio(_DummyRadio):
|
class _DummyTCPLoRaRadio(_DummyRadio):
|
||||||
@@ -89,7 +89,7 @@ def test_get_radio_for_board_pymc_tcp(monkeypatch):
|
|||||||
captured.update(kwargs)
|
captured.update(kwargs)
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"pymc_core.hardware.tcp_radio.TCPLoRaRadio",
|
"openhop_core.hardware.tcp_radio.TCPLoRaRadio",
|
||||||
_DummyTCPLoRaRadio,
|
_DummyTCPLoRaRadio,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -119,10 +119,10 @@ def test_get_radio_for_board_pymc_tcp(monkeypatch):
|
|||||||
|
|
||||||
|
|
||||||
def test_get_radio_for_board_pymc_tcp_requires_host(monkeypatch):
|
def test_get_radio_for_board_pymc_tcp_requires_host(monkeypatch):
|
||||||
pytest.importorskip("pymc_core.hardware.tcp_radio")
|
pytest.importorskip("openhop_core.hardware.tcp_radio")
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"pymc_core.hardware.tcp_radio.TCPLoRaRadio",
|
"openhop_core.hardware.tcp_radio.TCPLoRaRadio",
|
||||||
lambda **kwargs: _DummyRadio(),
|
lambda **kwargs: _DummyRadio(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ def test_get_radio_for_board_pymc_tcp_requires_host(monkeypatch):
|
|||||||
|
|
||||||
|
|
||||||
def test_get_radio_for_board_pymc_usb(monkeypatch):
|
def test_get_radio_for_board_pymc_usb(monkeypatch):
|
||||||
pytest.importorskip("pymc_core.hardware.usb_radio")
|
pytest.importorskip("openhop_core.hardware.usb_radio")
|
||||||
captured = {}
|
captured = {}
|
||||||
|
|
||||||
class _DummyUSBLoRaRadio(_DummyRadio):
|
class _DummyUSBLoRaRadio(_DummyRadio):
|
||||||
@@ -145,7 +145,7 @@ def test_get_radio_for_board_pymc_usb(monkeypatch):
|
|||||||
captured.update(kwargs)
|
captured.update(kwargs)
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"pymc_core.hardware.usb_radio.USBLoRaRadio",
|
"openhop_core.hardware.usb_radio.USBLoRaRadio",
|
||||||
_DummyUSBLoRaRadio,
|
_DummyUSBLoRaRadio,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -170,10 +170,10 @@ def test_get_radio_for_board_pymc_usb(monkeypatch):
|
|||||||
|
|
||||||
|
|
||||||
def test_get_radio_for_board_pymc_usb_requires_port(monkeypatch):
|
def test_get_radio_for_board_pymc_usb_requires_port(monkeypatch):
|
||||||
pytest.importorskip("pymc_core.hardware.usb_radio")
|
pytest.importorskip("openhop_core.hardware.usb_radio")
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"pymc_core.hardware.usb_radio.USBLoRaRadio",
|
"openhop_core.hardware.usb_radio.USBLoRaRadio",
|
||||||
lambda **kwargs: _DummyRadio(),
|
lambda **kwargs: _DummyRadio(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ def test_get_radio_for_board_pymc_usb_requires_port(monkeypatch):
|
|||||||
|
|
||||||
def _kiss_capture_radio_config(monkeypatch):
|
def _kiss_capture_radio_config(monkeypatch):
|
||||||
"""Patch KissModemWrapper to capture the radio_config it is built with."""
|
"""Patch KissModemWrapper to capture the radio_config it is built with."""
|
||||||
pytest.importorskip("pymc_core.hardware.kiss_modem_wrapper")
|
pytest.importorskip("openhop_core.hardware.kiss_modem_wrapper")
|
||||||
captured = {}
|
captured = {}
|
||||||
|
|
||||||
class _DummyKissWrapper(_DummyRadio):
|
class _DummyKissWrapper(_DummyRadio):
|
||||||
@@ -203,7 +203,7 @@ def _kiss_capture_radio_config(monkeypatch):
|
|||||||
captured["kwargs"] = kwargs
|
captured["kwargs"] = kwargs
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"pymc_core.hardware.kiss_modem_wrapper.KissModemWrapper",
|
"openhop_core.hardware.kiss_modem_wrapper.KissModemWrapper",
|
||||||
_DummyKissWrapper,
|
_DummyKissWrapper,
|
||||||
)
|
)
|
||||||
return captured
|
return captured
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ def test_generate_transport_key_uses_implicit_hashtag_region(tmp_path, monkeypat
|
|||||||
h = _make_handler(tmp_path)
|
h = _make_handler(tmp_path)
|
||||||
|
|
||||||
captured = {}
|
captured = {}
|
||||||
fake_transport_keys = types.ModuleType("pymc_core.protocol.transport_keys")
|
fake_transport_keys = types.ModuleType("openhop_core.protocol.transport_keys")
|
||||||
|
|
||||||
def _fake_get_auto_key_for(name: str) -> bytes:
|
def _fake_get_auto_key_for(name: str) -> bytes:
|
||||||
captured["name"] = name
|
captured["name"] = name
|
||||||
@@ -76,15 +76,15 @@ def test_generate_transport_key_uses_implicit_hashtag_region(tmp_path, monkeypat
|
|||||||
|
|
||||||
fake_transport_keys.get_auto_key_for = _fake_get_auto_key_for
|
fake_transport_keys.get_auto_key_for = _fake_get_auto_key_for
|
||||||
|
|
||||||
fake_protocol = types.ModuleType("pymc_core.protocol")
|
fake_protocol = types.ModuleType("openhop_core.protocol")
|
||||||
fake_protocol.transport_keys = fake_transport_keys
|
fake_protocol.transport_keys = fake_transport_keys
|
||||||
|
|
||||||
fake_core = types.ModuleType("pymc_core")
|
fake_core = types.ModuleType("openhop_core")
|
||||||
fake_core.protocol = fake_protocol
|
fake_core.protocol = fake_protocol
|
||||||
|
|
||||||
monkeypatch.setitem(sys.modules, "pymc_core", fake_core)
|
monkeypatch.setitem(sys.modules, "openhop_core", fake_core)
|
||||||
monkeypatch.setitem(sys.modules, "pymc_core.protocol", fake_protocol)
|
monkeypatch.setitem(sys.modules, "openhop_core.protocol", fake_protocol)
|
||||||
monkeypatch.setitem(sys.modules, "pymc_core.protocol.transport_keys", fake_transport_keys)
|
monkeypatch.setitem(sys.modules, "openhop_core.protocol.transport_keys", fake_transport_keys)
|
||||||
|
|
||||||
generated = h.generate_transport_key("eu")
|
generated = h.generate_transport_key("eu")
|
||||||
generated_bytes = base64.b64decode(generated)
|
generated_bytes = base64.b64decode(generated)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ def _make_collector() -> StorageCollector:
|
|||||||
patch("repeater.data_acquisition.storage_collector.RRDToolHandler"),
|
patch("repeater.data_acquisition.storage_collector.RRDToolHandler"),
|
||||||
patch("repeater.data_acquisition.hardware_stats.HardwareStatsCollector"),
|
patch("repeater.data_acquisition.hardware_stats.HardwareStatsCollector"),
|
||||||
):
|
):
|
||||||
collector = StorageCollector(config={"storage": {"storage_dir": "/tmp/pymc_repeater_test"}})
|
collector = StorageCollector(config={"storage": {"storage_dir": "/tmp/openhop_repeater_test"}})
|
||||||
|
|
||||||
# Stop any real stats-broadcast thread started during construction so the tests
|
# Stop any real stats-broadcast thread started during construction so the tests
|
||||||
# drive the loop deterministically.
|
# drive the loop deterministically.
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ def _fake_thread(*args, **kwargs):
|
|||||||
|
|
||||||
def test_jwt_warning_fix_guard():
|
def test_jwt_warning_fix_guard():
|
||||||
# Guard test file import path and ensure this module executes in suite.
|
# Guard test file import path and ensure this module executes in suite.
|
||||||
assert ue.PACKAGE_NAME == "pymc_repeater"
|
assert ue.PACKAGE_NAME == "openhop_repeater"
|
||||||
|
|
||||||
|
|
||||||
def test_has_update_paths():
|
def test_has_update_paths():
|
||||||
|
|||||||
Reference in New Issue
Block a user