mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-06-26 04:51:34 +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
|
||||
# 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
|
||||
# Portainer and fresh installs because Docker preserves the image ownership.
|
||||
# To use host bind mounts instead, create the folders first and make them
|
||||
# writable by UID/GID 15888:
|
||||
# sudo mkdir -p /opt/pymc-repeater/config /opt/pymc-repeater/data
|
||||
# sudo chown -R 15888:15888 /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/openhop-repeater/config /opt/openhop-repeater/data
|
||||
# Then uncomment and adjust these paths:
|
||||
# PYMC_CONFIG_VOLUME=/opt/pymc-repeater/config
|
||||
# PYMC_DATA_VOLUME=/opt/pymc-repeater/data
|
||||
# PYMC_CONFIG_VOLUME=/opt/openhop-repeater/config
|
||||
# PYMC_DATA_VOLUME=/opt/openhop-repeater/data
|
||||
|
||||
# Serial/SPI/GPIO access uses the host's numeric group IDs. Check your host with:
|
||||
# getent group dialout
|
||||
|
||||
@@ -10,14 +10,14 @@ on:
|
||||
image_repository:
|
||||
description: "Docker image repository to publish to"
|
||||
required: false
|
||||
default: "pymcdev/pymc-repeater"
|
||||
default: "pymcdev/openhop-repeater"
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.repository == 'pyMC-dev/pyMC_Repeater' ||
|
||||
github.repository == 'yellowcooln/pyMC_Repeater'
|
||||
github.repository == 'pyMC-dev/openhop-repeater' ||
|
||||
github.repository == 'yellowcooln/openhop-repeater'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -58,10 +58,10 @@ jobs:
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ inputs.image_repository }}" ]; then
|
||||
image_repository="${{ inputs.image_repository }}"
|
||||
elif [ "${{ github.repository }}" = "yellowcooln/pyMC_Repeater" ]; then
|
||||
image_repository="yellowcooln/pymc-repeater"
|
||||
elif [ "${{ github.repository }}" = "yellowcooln/openhop-repeater" ]; then
|
||||
image_repository="yellowcooln/openhop-repeater"
|
||||
else
|
||||
image_repository="pymcdev/pymc-repeater"
|
||||
image_repository="pymcdev/openhop-repeater"
|
||||
fi
|
||||
|
||||
echo "Using image repository: ${image_repository}"
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Notify Home Assistant add-on repository
|
||||
if: github.repository == 'pyMC-dev/pyMC_Repeater'
|
||||
if: github.repository == 'pyMC-dev/openhop-repeater'
|
||||
env:
|
||||
DISPATCH_TOKEN: ${{ secrets.HA_ADDON_REPO_DISPATCH_TOKEN }}
|
||||
CHANNEL: ${{ github.ref_name }}
|
||||
|
||||
+3
-3
@@ -28,9 +28,9 @@ share/python-wheels/
|
||||
DEBIAN/
|
||||
debian/files
|
||||
debian/.debhelper/
|
||||
debian/pymc-repeater/
|
||||
debian/pymc-repeater.debhelper.log
|
||||
debian/pymc-repeater.substvars
|
||||
debian/openhop-repeater/
|
||||
debian/openhop-repeater.debhelper.log
|
||||
debian/openhop-repeater.substvars
|
||||
|
||||
# Virtual environments
|
||||
.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
|
||||
radio modems. It forwards LoRa packets, exposes a web dashboard, and provides
|
||||
configuration tools for radio setup, policy management, monitoring, and
|
||||
@@ -29,7 +29,7 @@ integrations.
|
||||
## Overview
|
||||
|
||||
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:
|
||||
|
||||
- CherryPy provides a lightweight HTTP server for the web UI and API.
|
||||
@@ -59,7 +59,7 @@ Historical statistics and performance metrics.
|
||||
|
||||
## Supported Hardware
|
||||
|
||||
pyMC Repeater supports these radio backends:
|
||||
openHop Repeater supports these radio backends:
|
||||
|
||||
- **SX1262 over Linux SPI**: set `radio_type: sx1262`
|
||||
- **SX1262 over CH341 USB-to-SPI**: set `radio_type: sx1262_ch341`
|
||||
@@ -111,8 +111,8 @@ sudo apt install git -y
|
||||
### Clone The Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/pyMC-dev/pyMC_Repeater.git
|
||||
cd pyMC_Repeater
|
||||
git clone https://github.com/pyMC-dev/openhop-repeater.git
|
||||
cd openhop-repeater
|
||||
```
|
||||
|
||||
### Quick Install
|
||||
@@ -124,17 +124,17 @@ sudo bash ./manage.sh install
|
||||
The installer will:
|
||||
|
||||
- Create a dedicated `repeater` service user with hardware access
|
||||
- Install application files to `/opt/pymc_repeater`
|
||||
- Create the configuration directory at `/etc/pymc_repeater`
|
||||
- Create the log directory at `/var/log/pymc_repeater`
|
||||
- Install application files to `/opt/openhop_repeater`
|
||||
- Create the configuration directory at `/etc/openhop_repeater`
|
||||
- Create the log directory at `/var/log/openhop_repeater`
|
||||
- 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:
|
||||
|
||||
```bash
|
||||
# View live logs
|
||||
sudo journalctl -u pymc-repeater -f
|
||||
sudo journalctl -u openhop-repeater -f
|
||||
```
|
||||
|
||||
Open the web dashboard at:
|
||||
@@ -160,7 +160,7 @@ pip install -e ".[dev]"
|
||||
The main configuration file is created during installation:
|
||||
|
||||
```text
|
||||
/etc/pymc_repeater/config.yaml
|
||||
/etc/openhop_repeater/config.yaml
|
||||
```
|
||||
|
||||
### 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:
|
||||
|
||||
```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:
|
||||
|
||||
```bash
|
||||
sudo ./manage.sh
|
||||
sudo systemctl restart pymc-repeater
|
||||
sudo systemctl restart openhop-repeater
|
||||
```
|
||||
|
||||
### 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
|
||||
`/inform` payloads to pyMC_Glass, receives queued commands, and reports command
|
||||
results on the next inform cycle.
|
||||
@@ -258,7 +258,7 @@ The web interface can upgrade an installation or switch branches.
|
||||
### CLI
|
||||
|
||||
```bash
|
||||
cd pyMC_Repeater
|
||||
cd openhop-repeater
|
||||
sudo bash ./manage.sh upgrade
|
||||
```
|
||||
|
||||
@@ -272,7 +272,7 @@ The upgrade script will:
|
||||
|
||||
## 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
|
||||
without dedicating a full Raspberry Pi.
|
||||
|
||||
@@ -295,7 +295,7 @@ Hardware, choose one:
|
||||
Run this command on the Proxmox host, not inside a container:
|
||||
|
||||
```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.
|
||||
@@ -315,7 +315,7 @@ disk, bridge, etc.) and then:
|
||||
| Setting | Default |
|
||||
|---------|---------|
|
||||
| Container ID | Next available |
|
||||
| Hostname | `pymc-repeater` |
|
||||
| Hostname | `openhop-repeater` |
|
||||
| RAM | 1024 MB |
|
||||
| Disk | 4 GB |
|
||||
| CPU cores | 2 |
|
||||
@@ -330,10 +330,10 @@ disk, bridge, etc.) and then:
|
||||
pct enter <CTID>
|
||||
|
||||
# View service logs
|
||||
journalctl -u pymc-repeater -f
|
||||
journalctl -u openhop-repeater -f
|
||||
|
||||
# Manage the repeater
|
||||
cd /opt/pymc_repeater
|
||||
cd /opt/openhop_repeater
|
||||
bash manage.sh
|
||||
```
|
||||
|
||||
@@ -394,7 +394,7 @@ The script prompts before each optional removal step.
|
||||
|
||||
## 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:
|
||||
|
||||
@@ -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
|
||||
`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.
|
||||
|
||||
### Setup
|
||||
@@ -430,9 +430,9 @@ docker compose up -d
|
||||
|
||||
```yaml
|
||||
services:
|
||||
pymc-repeater:
|
||||
image: ${PYMC_REPEATER_IMAGE:-pymcdev/pymc-repeater:main}
|
||||
container_name: pymc-repeater
|
||||
openhop-repeater:
|
||||
image: ${PYMC_REPEATER_IMAGE:-pymcdev/openhop-repeater:main}
|
||||
container_name: openhop-repeater
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8000:8000
|
||||
@@ -455,12 +455,12 @@ services:
|
||||
- plugdev
|
||||
|
||||
volumes:
|
||||
- ${PYMC_CONFIG_VOLUME:-pymc-repeater-config}:/etc/pymc_repeater
|
||||
- ${PYMC_DATA_VOLUME:-pymc-repeater-data}:/var/lib/pymc_repeater
|
||||
- ${PYMC_CONFIG_VOLUME:-openhop-repeater-config}:/etc/openhop_repeater
|
||||
- ${PYMC_DATA_VOLUME:-openhop-repeater-data}:/var/lib/openhop_repeater
|
||||
|
||||
volumes:
|
||||
pymc-repeater-config:
|
||||
pymc-repeater-data:
|
||||
openhop-repeater-config:
|
||||
openhop-repeater-data:
|
||||
```
|
||||
|
||||
## Roadmap
|
||||
@@ -509,7 +509,7 @@ pre-commit run --all-files
|
||||
```
|
||||
|
||||
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:
|
||||
- Lint and auto-fix Python issues with Ruff
|
||||
@@ -518,7 +518,7 @@ Pre-commit hooks will automatically:
|
||||
|
||||
## Support
|
||||
|
||||
- [pyMC Core](https://github.com/pyMC-dev/pyMC_core)
|
||||
- [pyMC Core](https://github.com/pyMC-dev/openhop-core)
|
||||
- [MeshCore Discord](https://meshcore.gg)
|
||||
|
||||
## Disclaimer
|
||||
|
||||
+24
-24
@@ -1,28 +1,28 @@
|
||||
#!/bin/bash
|
||||
# Buildroot/Luckfox management entrypoint for pyMC Repeater
|
||||
# Buildroot/Luckfox management entrypoint for openHop Repeater
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
INSTALL_DIR="/opt/pymc_repeater"
|
||||
INSTALL_DIR="/opt/openhop_repeater"
|
||||
VENV_DIR="$INSTALL_DIR/venv"
|
||||
VENV_PIP="$VENV_DIR/bin/pip"
|
||||
VENV_PYTHON="$VENV_DIR/bin/python"
|
||||
CONFIG_DIR="/etc/pymc_repeater"
|
||||
LOG_DIR="/var/log/pymc_repeater"
|
||||
DATA_DIR="/var/lib/pymc_repeater"
|
||||
CONFIG_DIR="/etc/openhop_repeater"
|
||||
LOG_DIR="/var/log/openhop_repeater"
|
||||
DATA_DIR="/var/lib/openhop_repeater"
|
||||
SERVICE_USER="root"
|
||||
INIT_SCRIPT="/etc/init.d/S80pymc-repeater"
|
||||
PIDFILE="/var/run/pymc-repeater.pid"
|
||||
INIT_SCRIPT="/etc/init.d/S80openhop-repeater"
|
||||
PIDFILE="/var/run/openhop-repeater.pid"
|
||||
LOGFILE="$LOG_DIR/repeater.log"
|
||||
SERVICE_NAME="pymc-repeater"
|
||||
SERVICE_NAME="openhop-repeater"
|
||||
SILENT_MODE="${PYMC_SILENT:-${SILENT:-}}"
|
||||
|
||||
R2_BASE_URL="https://wheel.pymc.dev/pymc_build_deps"
|
||||
PIWHEELS_INDEX_URL="https://www.piwheels.org/simple"
|
||||
R2_ENABLED=1
|
||||
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_LOCAL_DIR="${PYMC_CORE_LOCAL_DIR:-}"
|
||||
PYMC_SKIP_BUILDROOT_DEP_INSTALL="${PYMC_SKIP_BUILDROOT_DEP_INSTALL:-0}"
|
||||
@@ -486,9 +486,9 @@ cleanup_legacy_install_state() {
|
||||
|
||||
for path in \
|
||||
"$INSTALL_DIR/repeater" \
|
||||
"$INSTALL_DIR/pymc_core" \
|
||||
"$INSTALL_DIR/pyMC_Repeater" \
|
||||
"$INSTALL_DIR/pyMC_core"
|
||||
"$INSTALL_DIR/openhop_core" \
|
||||
"$INSTALL_DIR/openhop-repeater" \
|
||||
"$INSTALL_DIR/openhop-core"
|
||||
do
|
||||
if [ -e "$path" ]; then
|
||||
rm -rf "$path"
|
||||
@@ -641,8 +641,8 @@ install_core_into_venv() {
|
||||
local core_repo core_ref core_spec
|
||||
|
||||
if [ -n "$PYMC_CORE_LOCAL_DIR" ]; then
|
||||
[ -d "$PYMC_CORE_LOCAL_DIR" ] || fail "Missing local pyMC_core checkout: $PYMC_CORE_LOCAL_DIR"
|
||||
stage "Installing pyMC_core"
|
||||
[ -d "$PYMC_CORE_LOCAL_DIR" ] || fail "Missing local openhop-core checkout: $PYMC_CORE_LOCAL_DIR"
|
||||
stage "Installing openhop-core"
|
||||
info "Local dir: ${PYMC_CORE_LOCAL_DIR}"
|
||||
"$VENV_PIP" install --upgrade --no-cache-dir --no-deps --no-build-isolation "$PYMC_CORE_LOCAL_DIR"
|
||||
return 0
|
||||
@@ -654,8 +654,8 @@ install_core_into_venv() {
|
||||
*) core_repo="${core_repo}.git" ;;
|
||||
esac
|
||||
core_ref=$(resolve_core_ref)
|
||||
core_spec="pyMC_core[hardware] @ git+${core_repo}@${core_ref}"
|
||||
stage "Installing pyMC_core"
|
||||
core_spec="openhop-core[hardware] @ git+${core_repo}@${core_ref}"
|
||||
stage "Installing openhop-core"
|
||||
info "Repo: ${PYMC_CORE_REPO}"
|
||||
info "Ref: ${core_ref}"
|
||||
"$VENV_PIP" install --upgrade --no-cache-dir --no-deps --no-build-isolation "$core_spec"
|
||||
@@ -667,7 +667,7 @@ import glob
|
||||
import json
|
||||
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:
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as fh:
|
||||
@@ -693,7 +693,7 @@ resolve_core_commit() {
|
||||
}
|
||||
|
||||
install_repeater_package() {
|
||||
stage "Installing pyMC Repeater into venv"
|
||||
stage "Installing openHop Repeater into venv"
|
||||
info "Installing checked-out repo without re-resolving dependencies"
|
||||
"$VENV_PIP" install --upgrade --no-cache-dir --no-deps --no-build-isolation "$SCRIPT_DIR"
|
||||
}
|
||||
@@ -1232,7 +1232,7 @@ start_or_restart_service() {
|
||||
|
||||
get_version() {
|
||||
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
|
||||
echo "not installed"
|
||||
fi
|
||||
@@ -1355,7 +1355,7 @@ install_repeater() {
|
||||
info "Install dir: $INSTALL_DIR"
|
||||
info "Config dir: $CONFIG_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"
|
||||
chmod 755 "$INSTALL_DIR" "$DATA_DIR"
|
||||
chmod 750 "$CONFIG_DIR" "$LOG_DIR"
|
||||
@@ -1417,7 +1417,7 @@ upgrade_repeater() {
|
||||
git_version=$(prepare_git_version)
|
||||
preinstall_r2_wheels
|
||||
|
||||
stage "Upgrading pyMC Repeater"
|
||||
stage "Upgrading openHop Repeater"
|
||||
if [ "${PYMC_FORCE_DEPS:-0}" = "1" ]; then
|
||||
info "Forcing dependency reinstall"
|
||||
install_buildroot_dependencies
|
||||
@@ -1431,13 +1431,13 @@ upgrade_repeater() {
|
||||
target_core_commit=$(resolve_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
|
||||
info "pyMC_core is already at ${target_core_commit}; skipping reinstall"
|
||||
info "openhop-core is already at ${target_core_commit}; skipping reinstall"
|
||||
else
|
||||
install_core_into_venv
|
||||
fi
|
||||
|
||||
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
|
||||
ensure_yq >/dev/null 2>&1 || true
|
||||
install_repeater_package
|
||||
@@ -1521,7 +1521,7 @@ Usage: bash buildroot-manage.sh <command>
|
||||
|
||||
Commands:
|
||||
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
|
||||
config Prompt for repeater settings and rewrite config.yaml
|
||||
configure Same as config
|
||||
|
||||
+8
-8
@@ -151,7 +151,7 @@ gps:
|
||||
# File source settings (used when source: file)
|
||||
# The file may contain raw NMEA lines or JSON with a "sentences" list /
|
||||
# "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
|
||||
|
||||
# Modem HTTP source settings (used when source: modem_http)
|
||||
@@ -406,7 +406,7 @@ radio:
|
||||
# Use implicit header mode
|
||||
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:
|
||||
# port: "/dev/ttyUSB0"
|
||||
# baud_rate: 9600
|
||||
@@ -422,7 +422,7 @@ radio:
|
||||
# # kiss_full_duplex: false # disable carrier-sense/CSMA entirely (not recommended)
|
||||
|
||||
# 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:
|
||||
# host: "pymc-3e2834.local" # modem hostname / mDNS name / LAN IP
|
||||
# port: 5055 # firmware default
|
||||
@@ -432,7 +432,7 @@ radio:
|
||||
# lbt_max_attempts: 5
|
||||
|
||||
# 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:
|
||||
# port: "/dev/ttyACM0" # USB-CDC device; udev rule may symlink to /dev/lora-modem
|
||||
# baudrate: 921600 # must match firmware monitor_speed
|
||||
@@ -492,8 +492,8 @@ duty_cycle:
|
||||
# Storage Configuration
|
||||
storage:
|
||||
# Directory for persistent storage files (SQLite, RRD).
|
||||
# Use a writable path for local/dev (e.g. "./var/pymc_repeater" or "~/var/pymc_repeater").
|
||||
storage_dir: "/var/lib/pymc_repeater"
|
||||
# Use a writable path for local/dev (e.g. "./var/openhop_repeater" or "~/var/openhop_repeater").
|
||||
storage_dir: "/var/lib/openhop_repeater"
|
||||
|
||||
# Data retention settings
|
||||
retention:
|
||||
@@ -526,7 +526,7 @@ mqtt_brokers:
|
||||
# format: meshcoretomqtt|letsmesh|waev|mqtt
|
||||
# meshcoretomqtt - canonical open-source MC2MQTT topic structure
|
||||
# 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)
|
||||
# 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.
|
||||
@@ -602,7 +602,7 @@ glass:
|
||||
api_token: ""
|
||||
|
||||
# Where cert_renewal payloads are written
|
||||
cert_store_dir: "/etc/pymc_repeater/glass"
|
||||
cert_store_dir: "/etc/openhop_repeater/glass"
|
||||
|
||||
logging:
|
||||
# Log level: DEBUG, INFO, WARNING, ERROR
|
||||
|
||||
+20
-20
@@ -1,5 +1,5 @@
|
||||
#!/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]
|
||||
# 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 ""
|
||||
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 "The 64-byte key format: [32-byte scalar][32-byte nonce]"
|
||||
echo " - Enables same node address as firmware device"
|
||||
echo " - Supports signing using MeshCore/orlp ed25519 algorithm"
|
||||
echo " - Fully compatible with pyMC_core LocalIdentity"
|
||||
echo " - Fully compatible with openhop-core LocalIdentity"
|
||||
echo ""
|
||||
echo "Arguments:"
|
||||
echo " --output-format: Optional output format (yaml|identity, default: yaml)"
|
||||
echo " yaml - Store in config.yaml (embedded binary)"
|
||||
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 "Examples:"
|
||||
echo " # Save to config.yaml (default)"
|
||||
@@ -67,7 +67,7 @@ fi
|
||||
|
||||
# Set default config path if not provided
|
||||
if [ -z "$CONFIG_PATH" ]; then
|
||||
CONFIG_PATH="/etc/pymc_repeater/config.yaml"
|
||||
CONFIG_PATH="/etc/openhop_repeater/config.yaml"
|
||||
fi
|
||||
|
||||
# Validate hex string
|
||||
@@ -92,7 +92,7 @@ if [ "$OUTPUT_FORMAT" = "yaml" ]; then
|
||||
fi
|
||||
else
|
||||
# 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"
|
||||
fi
|
||||
|
||||
@@ -261,39 +261,39 @@ fi
|
||||
|
||||
# Offer to restart service (only relevant for yaml format)
|
||||
if [ "$OUTPUT_FORMAT" = "yaml" ]; then
|
||||
if systemctl is-active --quiet pymc-repeater 2>/dev/null; then
|
||||
read -p "Restart pymc-repeater service now? (yes/no): " RESTART
|
||||
if systemctl is-active --quiet openhop-repeater 2>/dev/null; then
|
||||
read -p "Restart openhop-repeater service now? (yes/no): " RESTART
|
||||
if [ "$RESTART" = "yes" ]; then
|
||||
systemctl restart pymc-repeater
|
||||
systemctl restart openhop-repeater
|
||||
echo "✓ Service restarted"
|
||||
echo ""
|
||||
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
|
||||
echo "Remember to restart the service:"
|
||||
echo " sudo systemctl restart pymc-repeater"
|
||||
echo " sudo systemctl restart openhop-repeater"
|
||||
fi
|
||||
else
|
||||
echo "Note: pymc-repeater service is not running"
|
||||
echo "Start it with: sudo systemctl start pymc-repeater"
|
||||
echo "Note: openhop-repeater service is not running"
|
||||
echo "Start it with: sudo systemctl start openhop-repeater"
|
||||
fi
|
||||
else
|
||||
echo "Identity key saved to file."
|
||||
echo ""
|
||||
if systemctl is-active --quiet pymc-repeater 2>/dev/null; then
|
||||
read -p "Restart pymc-repeater service now? (yes/no): " RESTART
|
||||
if systemctl is-active --quiet openhop-repeater 2>/dev/null; then
|
||||
read -p "Restart openhop-repeater service now? (yes/no): " RESTART
|
||||
if [ "$RESTART" = "yes" ]; then
|
||||
systemctl restart pymc-repeater
|
||||
systemctl restart openhop-repeater
|
||||
echo "✓ Service restarted"
|
||||
echo ""
|
||||
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
|
||||
echo "Remember to restart the service:"
|
||||
echo " sudo systemctl restart pymc-repeater"
|
||||
echo " sudo systemctl restart openhop-repeater"
|
||||
fi
|
||||
else
|
||||
echo "Note: pymc-repeater service is not running"
|
||||
echo "Start it with: sudo systemctl start pymc-repeater"
|
||||
echo "Note: openhop-repeater service is not running"
|
||||
echo "Start it with: sudo systemctl start openhop-repeater"
|
||||
fi
|
||||
fi
|
||||
|
||||
+1
-1
@@ -3,4 +3,4 @@
|
||||
*.substvars
|
||||
.debhelper/
|
||||
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
|
||||
* Version: 1.0.5.post0
|
||||
|
||||
Vendored
+5
-5
@@ -1,4 +1,4 @@
|
||||
Source: pymc-repeater
|
||||
Source: openhop-repeater
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: Rightup <rightup@pymc.dev>
|
||||
@@ -15,10 +15,10 @@ Build-Depends: debhelper-compat (= 13),
|
||||
python3-psutil,
|
||||
git
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://github.com/rightup/pyMC_Repeater
|
||||
Homepage: https://github.com/rightup/openhop-repeater
|
||||
X-Python3-Version: >= 3.9
|
||||
|
||||
Package: pymc-repeater
|
||||
Package: openhop-repeater
|
||||
Architecture: all
|
||||
Depends: ${python3:Depends},
|
||||
${misc:Depends},
|
||||
@@ -36,8 +36,8 @@ Recommends: python3-periphery,
|
||||
Description: PyMC Repeater Daemon
|
||||
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.
|
||||
.
|
||||
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.
|
||||
|
||||
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
|
||||
# -*- makefile -*-
|
||||
|
||||
export PYBUILD_NAME=pymc-repeater
|
||||
export PYBUILD_NAME=openhop-repeater
|
||||
export DH_VERBOSE=1
|
||||
|
||||
%:
|
||||
@@ -19,4 +19,4 @@ override_dh_auto_test:
|
||||
rm -f repeater/_version.py
|
||||
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd --name=pymc-repeater
|
||||
dh_installsystemd --name=openhop-repeater
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
pymc-repeater:
|
||||
image: pymc-repeater:local
|
||||
openhop-repeater:
|
||||
image: openhop-repeater:local
|
||||
build:
|
||||
context: .
|
||||
dockerfile: dockerfile
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
pymc-repeater:
|
||||
image: ${PYMC_REPEATER_IMAGE:-pymcdev/pymc-repeater:main}
|
||||
container_name: pymc-repeater
|
||||
openhop-repeater:
|
||||
image: ${PYMC_REPEATER_IMAGE:-pymcdev/openhop-repeater:main}
|
||||
container_name: openhop-repeater
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8000:8000
|
||||
@@ -21,9 +21,9 @@ services:
|
||||
- "${SPI_GID:-989}"
|
||||
- plugdev
|
||||
volumes:
|
||||
- ${PYMC_CONFIG_VOLUME:-pymc-repeater-config}:/etc/pymc_repeater
|
||||
- ${PYMC_DATA_VOLUME:-pymc-repeater-data}:/var/lib/pymc_repeater
|
||||
- ${PYMC_CONFIG_VOLUME:-openhop-repeater-config}:/etc/openhop_repeater
|
||||
- ${PYMC_DATA_VOLUME:-openhop-repeater-data}:/var/lib/openhop_repeater
|
||||
|
||||
volumes:
|
||||
pymc-repeater-config:
|
||||
pymc-repeater-data:
|
||||
openhop-repeater-config:
|
||||
openhop-repeater-data:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
INSTALL_DIR="${INSTALL_DIR:-/opt/pymc_repeater}"
|
||||
CONFIG_DIR="${CONFIG_DIR:-/etc/pymc_repeater}"
|
||||
INSTALL_DIR="${INSTALL_DIR:-/opt/openhop_repeater}"
|
||||
CONFIG_DIR="${CONFIG_DIR:-/etc/openhop_repeater}"
|
||||
CONFIG_PATH="${PYMC_REPEATER_CONFIG:-${CONFIG_DIR}/config.yaml}"
|
||||
EXAMPLE_PATH="${CONFIG_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 "This usually happens when ./config.yaml is bind-mounted before that host file exists." >&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
|
||||
print_permission_help
|
||||
exit 1
|
||||
@@ -40,7 +40,7 @@ copy_or_die() {
|
||||
|
||||
use_runtime_merged_config() {
|
||||
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"
|
||||
|
||||
if ! cp "${src}" "${runtime_config}"; then
|
||||
|
||||
+3
-3
@@ -11,9 +11,9 @@ ARG SPI_GID=989
|
||||
ARG TARGETARCH
|
||||
ARG YQ_VERSION=v4.40.5
|
||||
|
||||
ENV INSTALL_DIR=/opt/pymc_repeater \
|
||||
CONFIG_DIR=/etc/pymc_repeater \
|
||||
DATA_DIR=/var/lib/pymc_repeater \
|
||||
ENV INSTALL_DIR=/opt/openhop_repeater \
|
||||
CONFIG_DIR=/etc/openhop_repeater \
|
||||
DATA_DIR=/var/lib/openhop_repeater \
|
||||
HOME_DIR=/home/${USER} \
|
||||
PATH=/home/${USER}/.local/bin:${PATH} \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
# 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
|
||||
# Portainer and fresh installs because Docker preserves the image ownership.
|
||||
# To use host bind mounts instead, create the folders first and make them
|
||||
# writable by UID/GID 15888:
|
||||
# sudo mkdir -p /opt/pymc-repeater/config /opt/pymc-repeater/data
|
||||
# sudo chown -R 15888:15888 /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/openhop-repeater/config /opt/openhop-repeater/data
|
||||
# Then uncomment and adjust these paths:
|
||||
# PYMC_CONFIG_VOLUME=/opt/pymc-repeater/config
|
||||
# PYMC_DATA_VOLUME=/opt/pymc-repeater/data
|
||||
# PYMC_CONFIG_VOLUME=/opt/openhop-repeater/config
|
||||
# PYMC_DATA_VOLUME=/opt/openhop-repeater/data
|
||||
|
||||
# SPI/GPIO access uses the host's numeric group IDs. Check your host with:
|
||||
# getent group gpio
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
#!/bin/bash
|
||||
# pyMC Repeater Management Script - Deploy, Upgrade, Uninstall
|
||||
# openHop Repeater Management Script - Deploy, Upgrade, Uninstall
|
||||
|
||||
set -e
|
||||
|
||||
INSTALL_DIR="/opt/pymc_repeater"
|
||||
INSTALL_DIR="/opt/openhop_repeater"
|
||||
VENV_DIR="$INSTALL_DIR/venv"
|
||||
VENV_PIP="$VENV_DIR/bin/pip"
|
||||
VENV_PYTHON="$VENV_DIR/bin/python"
|
||||
CONFIG_DIR="/etc/pymc_repeater"
|
||||
LOG_DIR="/var/log/pymc_repeater"
|
||||
CONFIG_DIR="/etc/openhop_repeater"
|
||||
LOG_DIR="/var/log/openhop_repeater"
|
||||
DATA_DIR="/var/lib/openhop_repeater"
|
||||
SERVICE_USER="repeater"
|
||||
SERVICE_NAME="pymc-repeater"
|
||||
SERVICE_NAME="openhop-repeater"
|
||||
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_BASE_URL="https://wheel.pymc.dev/pymc_build_deps"
|
||||
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
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# 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() {
|
||||
if [ ! -x "$VENV_PYTHON" ]; then
|
||||
echo ">>> Creating virtual environment at $VENV_DIR ..."
|
||||
@@ -40,15 +107,16 @@ migrate_to_venv() {
|
||||
ensure_venv
|
||||
|
||||
# 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 grep -q 'PYTHONPATH' "$svc_unit" 2>/dev/null; then
|
||||
sed -i '/^Environment=.*PYTHONPATH/d' "$svc_unit"
|
||||
echo " ✓ Removed legacy PYTHONPATH from service unit"
|
||||
fi
|
||||
# 3. Fix WorkingDirectory if still pointing at old source
|
||||
if grep -q 'WorkingDirectory=/opt/pymc_repeater' "$svc_unit" 2>/dev/null; then
|
||||
sed -i 's|WorkingDirectory=/opt/pymc_repeater|WorkingDirectory=/var/lib/pymc_repeater|' "$svc_unit"
|
||||
if grep -q 'WorkingDirectory=/opt/openhop_repeater\|WorkingDirectory=/opt/openhop-repeater' "$svc_unit" 2>/dev/null; then
|
||||
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"
|
||||
fi
|
||||
# 4. Ensure ExecStart uses the venv python
|
||||
@@ -60,15 +128,12 @@ migrate_to_venv() {
|
||||
fi
|
||||
|
||||
# 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 pymc_core 2>/dev/null || true
|
||||
python3 -m pip uninstall -y openhop_repeater 2>/dev/null || true
|
||||
python3 -m pip uninstall -y openhop_core 2>/dev/null || true
|
||||
echo " ✓ Cleaned up system-level packages (if any)"
|
||||
|
||||
# 6. Remove stale source trees that could shadow the venv package
|
||||
if [ -d "$INSTALL_DIR/repeater" ]; then
|
||||
rm -rf "$INSTALL_DIR/repeater"
|
||||
echo " ✓ Removed stale source tree from $INSTALL_DIR/repeater"
|
||||
fi
|
||||
cleanup_stale_source_trees
|
||||
}
|
||||
|
||||
is_silent_flag() {
|
||||
@@ -117,22 +182,22 @@ fi
|
||||
|
||||
# Function to show info box
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
@@ -159,11 +224,11 @@ is_enabled() {
|
||||
get_version() {
|
||||
# Read version from the pip-installed package in the venv
|
||||
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"
|
||||
else
|
||||
# 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"
|
||||
fi
|
||||
}
|
||||
@@ -183,11 +248,11 @@ get_status_display() {
|
||||
show_main_menu() {
|
||||
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 \
|
||||
"install" "Install pyMC Repeater" \
|
||||
CHOICE=$($DIALOG --backtitle "openHop Repeater Management" --title "openHop Repeater Management" --menu "\nCurrent Status: $status\n\nChoose an action:" 18 70 9 \
|
||||
"install" "Install openHop Repeater" \
|
||||
"upgrade" "Upgrade existing installation" \
|
||||
"reset" "reset existing installation to defaults" \
|
||||
"uninstall" "Remove pyMC Repeater completely" \
|
||||
"uninstall" "Remove openHop Repeater completely" \
|
||||
"config" "Configure radio settings" \
|
||||
"start" "Start the service" \
|
||||
"stop" "Stop the service" \
|
||||
@@ -199,7 +264,7 @@ show_main_menu() {
|
||||
case $CHOICE in
|
||||
"install")
|
||||
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
|
||||
install_repeater
|
||||
fi
|
||||
@@ -208,21 +273,21 @@ show_main_menu() {
|
||||
if is_installed; then
|
||||
upgrade_repeater "false"
|
||||
else
|
||||
show_error "pyMC Repeater is not installed!\n\nUse 'install' first."
|
||||
show_error "openHop Repeater is not installed!\n\nUse 'install' first."
|
||||
fi
|
||||
;;
|
||||
"reset")
|
||||
if is_installed; then
|
||||
reset_repeater
|
||||
else
|
||||
show_error "pyMC Repeater is not installed!\n\nUse 'install' first."
|
||||
show_error "openHop Repeater is not installed!\n\nUse 'install' first."
|
||||
fi
|
||||
;;
|
||||
"uninstall")
|
||||
if is_installed; then
|
||||
uninstall_repeater
|
||||
else
|
||||
show_error "pyMC Repeater is not installed."
|
||||
show_error "openHop Repeater is not installed."
|
||||
fi
|
||||
;;
|
||||
"config")
|
||||
@@ -240,7 +305,7 @@ show_main_menu() {
|
||||
"logs")
|
||||
clear
|
||||
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"
|
||||
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)
|
||||
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
|
||||
|
||||
# SPI Check - Universal approach that works on all boards (skip for CH341 USB-SPI adapter)
|
||||
@@ -321,13 +386,13 @@ install_repeater() {
|
||||
# Installation progress
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " Installing pyMC Repeater"
|
||||
echo " Installing openHop Repeater"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
echo ">>> Creating service user..."
|
||||
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
|
||||
|
||||
(
|
||||
@@ -336,8 +401,12 @@ install_repeater() {
|
||||
getent group "$grp" >/dev/null 2>&1 && usermod -a -G "$grp" "$SERVICE_USER" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo "20"; echo "# Creating directories..."
|
||||
mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater
|
||||
echo "20"; echo "# Migrating legacy paths..."
|
||||
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..."
|
||||
apt-get update -qq
|
||||
@@ -367,9 +436,9 @@ install_repeater() {
|
||||
|
||||
echo "29"; echo "# Installing files..."
|
||||
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/radio-settings.json" /var/lib/pymc_repeater/ 2>/dev/null || true
|
||||
cp "$SCRIPT_DIR/radio-presets.json" /var/lib/pymc_repeater/ 2>/dev/null || true
|
||||
cp "$SCRIPT_DIR/openhop-repeater.service" "$INSTALL_DIR/" 2>/dev/null || true
|
||||
cp "$SCRIPT_DIR/radio-settings.json" "$DATA_DIR/" 2>/dev/null || true
|
||||
cp "$SCRIPT_DIR/radio-presets.json" "$DATA_DIR/" 2>/dev/null || true
|
||||
|
||||
echo "45"; echo "# Installing configuration..."
|
||||
cp "$SCRIPT_DIR/config.yaml.example" "$CONFIG_DIR/config.yaml.example"
|
||||
@@ -378,28 +447,28 @@ install_repeater() {
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
echo "58"; echo "# Installing udev rules for CH341..."
|
||||
if [ -f "$SCRIPT_DIR/../pyMC_core/99-ch341.rules" ]; then
|
||||
cp "$SCRIPT_DIR/../pyMC_core/99-ch341.rules" /etc/udev/rules.d/99-ch341.rules
|
||||
if [ -f "$SCRIPT_DIR/../openhop-core/99-ch341.rules" ]; then
|
||||
cp "$SCRIPT_DIR/../openhop-core/99-ch341.rules" /etc/udev/rules.d/99-ch341.rules
|
||||
udevadm control --reload-rules 2>/dev/null || true
|
||||
udevadm trigger 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "65"; echo "# Setting permissions..."
|
||||
# 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
|
||||
chmod 750 "$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" "$DATA_DIR"
|
||||
# Ensure manage.sh and support files in INSTALL_DIR are accessible
|
||||
chown root:root "$INSTALL_DIR"
|
||||
chmod 755 "$INSTALL_DIR"
|
||||
# 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
|
||||
mkdir -p /var/lib/pymc_repeater/.config/pymc_repeater
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" /var/lib/pymc_repeater/.config
|
||||
mkdir -p "$DATA_DIR/.config/openhop_repeater"
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$DATA_DIR/.config"
|
||||
|
||||
# Configure polkit for passwordless service restart
|
||||
|
||||
@@ -410,38 +479,38 @@ install_repeater() {
|
||||
echo "Polkit 0.106 or greater detected, using rules file"
|
||||
echo ">>> Configuring polkit for service management..."
|
||||
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) {
|
||||
if (action.id == "org.freedesktop.systemd1.manage-units" &&
|
||||
action.lookup("unit") == "pymc-repeater.service" &&
|
||||
action.lookup("unit") == "openhop-repeater.service" &&
|
||||
subject.user == "repeater") {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
EOF
|
||||
chmod 0644 /etc/polkit-1/rules.d/10-pymc-repeater.rules
|
||||
chmod 0644 /etc/polkit-1/rules.d/10-openhop-repeater.rules
|
||||
else
|
||||
echo "Polkit 0.105 or less detected, using pkla file"
|
||||
mkdir -p /etc/polkit-1/localauthority/50-local.d
|
||||
cat > /etc/polkit-1/localauthority/50-local.d/10-pymc-repeater.pkla <<'EOF'
|
||||
[Allow repeater to restart pymc-repeater service]
|
||||
cat > /etc/polkit-1/localauthority/50-local.d/10-openhop-repeater.pkla <<'EOF'
|
||||
[Allow repeater to restart openhop-repeater service]
|
||||
Identity=unix-user:repeater
|
||||
Action=org.freedesktop.systemd1.manage-units
|
||||
ResultAny=yes
|
||||
ResultInactive=yes
|
||||
ResultActive=yes
|
||||
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
|
||||
|
||||
# Also configure sudoers as fallback for service restart
|
||||
echo ">>> Configuring sudoers for service management..."
|
||||
mkdir -p /etc/sudoers.d
|
||||
cat > /etc/sudoers.d/pymc-repeater <<'EOF'
|
||||
# Allow repeater user to manage the pymc-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
|
||||
cat > /etc/sudoers.d/openhop-repeater <<'EOF'
|
||||
# Allow repeater user to manage the openhop-repeater service without password
|
||||
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
|
||||
chmod 0440 /etc/sudoers.d/pymc-repeater
|
||||
chmod 0440 /etc/sudoers.d/openhop-repeater
|
||||
|
||||
echo ">>> Installing OTA upgrade wrapper..."
|
||||
cat > /usr/local/bin/pymc-do-upgrade <<'UPGRADEEOF'
|
||||
@@ -451,7 +520,7 @@ EOF
|
||||
set -e
|
||||
CHANNEL="${1:-main}"
|
||||
PRETEND_VERSION="${2:-}"
|
||||
VENV_DIR="/opt/pymc_repeater/venv"
|
||||
VENV_DIR="/opt/openhop_repeater/venv"
|
||||
VENV_PIP="$VENV_DIR/bin/pip"
|
||||
VENV_PYTHON="$VENV_DIR/bin/python"
|
||||
# Validate: only allow safe git ref characters
|
||||
@@ -468,14 +537,22 @@ if [ ! -x "$VENV_PYTHON" ]; then
|
||||
python3 -m venv --system-site-packages "$VENV_DIR"
|
||||
"$VENV_PIP" install --upgrade pip setuptools wheel >/dev/null 2>&1 || true
|
||||
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 ----
|
||||
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
|
||||
sed -i '/^Environment=.*PYTHONPATH/d' "$SVC_UNIT"
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
if grep -q 'WorkingDirectory=/opt/pymc_repeater' "$SVC_UNIT" 2>/dev/null; then
|
||||
sed -i 's|WorkingDirectory=/opt/pymc_repeater|WorkingDirectory=/var/lib/pymc_repeater|' "$SVC_UNIT"
|
||||
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
|
||||
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
|
||||
fi
|
||||
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
|
||||
fi
|
||||
# ---- 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 ----
|
||||
python3 -m pip uninstall -y pymc_repeater 2>/dev/null || true
|
||||
python3 -m pip uninstall -y pymc_core 2>/dev/null || true
|
||||
python3 -m pip uninstall -y openhop_repeater 2>/dev/null || true
|
||||
python3 -m pip uninstall -y openhop_core 2>/dev/null || true
|
||||
# ---- Try R2 wheels first for faster OTA upgrades ----
|
||||
R2_BASE_URL="https://wheel.pymc.dev/pymc_build_deps"
|
||||
MACHINE_ARCH=$(uname -m)
|
||||
@@ -502,14 +582,14 @@ if [ -n "$ARCH_TAG" ]; then
|
||||
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
|
||||
fi
|
||||
# ---- Install pymc_repeater from git ----
|
||||
# ---- Install openhop_repeater from git ----
|
||||
if "$VENV_PIP" install \
|
||||
--upgrade \
|
||||
--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.
|
||||
RADIO_BASE_URL="https://raw.githubusercontent.com/rightup/pyMC_Repeater/${CHANNEL}"
|
||||
RADIO_STORAGE_DIR="/var/lib/pymc_repeater"
|
||||
RADIO_BASE_URL="https://raw.githubusercontent.com/rightup/openhop-repeater/${CHANNEL}"
|
||||
RADIO_STORAGE_DIR="/var/lib/openhop_repeater"
|
||||
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-presets.json" "${RADIO_BASE_URL}/radio-presets.json" 2>/dev/null || true
|
||||
@@ -523,13 +603,13 @@ UPGRADEEOF
|
||||
systemctl enable "$SERVICE_NAME"
|
||||
|
||||
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
|
||||
clear
|
||||
echo "=== Installing Python Dependencies ==="
|
||||
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 ""
|
||||
|
||||
@@ -556,7 +636,7 @@ UPGRADEEOF
|
||||
# Ensure venv exists
|
||||
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
|
||||
if [ "$R2_ENABLED" -eq 1 ]; then
|
||||
@@ -660,7 +740,7 @@ reset_repeater() {
|
||||
|
||||
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 "Reseting" "Starting reset process...\n\nProgress will be shown in the terminal."
|
||||
@@ -731,7 +811,7 @@ upgrade_repeater() {
|
||||
local current_version=$(get_version)
|
||||
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -746,6 +826,10 @@ upgrade_repeater() {
|
||||
echo "[1/9] Stopping service..."
|
||||
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..."
|
||||
if [ -d "$CONFIG_DIR" ]; then
|
||||
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..."
|
||||
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"
|
||||
fi
|
||||
cp "$SCRIPT_DIR/radio-settings.json" /var/lib/pymc_repeater/ 2>/dev/null || true
|
||||
cp "$SCRIPT_DIR/radio-presets.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" "$DATA_DIR/" 2>/dev/null || true
|
||||
echo " ✓ Files updated"
|
||||
|
||||
echo "[5/9] Validating and updating configuration..."
|
||||
@@ -797,8 +881,8 @@ upgrade_repeater() {
|
||||
done
|
||||
# Install/update CH341 udev rules
|
||||
SCRIPT_DIR_UPGRADE="$(cd "$(dirname "$0")" && pwd)"
|
||||
if [ -f "$SCRIPT_DIR_UPGRADE/../pyMC_core/99-ch341.rules" ]; then
|
||||
cp "$SCRIPT_DIR_UPGRADE/../pyMC_core/99-ch341.rules" /etc/udev/rules.d/99-ch341.rules
|
||||
if [ -f "$SCRIPT_DIR_UPGRADE/../openhop-core/99-ch341.rules" ]; then
|
||||
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 trigger 2>/dev/null || true
|
||||
echo " ✓ CH341 udev rules updated"
|
||||
@@ -810,15 +894,15 @@ upgrade_repeater() {
|
||||
echo "[6/9] Fixing permissions..."
|
||||
|
||||
# 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
|
||||
chmod 755 "$INSTALL_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
|
||||
mkdir -p /var/lib/pymc_repeater/.config/pymc_repeater 2>/dev/null || true
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" /var/lib/pymc_repeater/.config 2>/dev/null || true
|
||||
mkdir -p "$DATA_DIR/.config/openhop_repeater" 2>/dev/null || true
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$DATA_DIR/.config" 2>/dev/null || true
|
||||
|
||||
# Configure polkit for passwordless service restart
|
||||
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 ">>> Configuring polkit for service management..."
|
||||
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) {
|
||||
if (action.id == "org.freedesktop.systemd1.manage-units" &&
|
||||
action.lookup("unit") == "pymc-repeater.service" &&
|
||||
action.lookup("unit") == "openhop-repeater.service" &&
|
||||
subject.user == "repeater") {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
EOF
|
||||
chmod 0644 /etc/polkit-1/rules.d/10-pymc-repeater.rules
|
||||
chmod 0644 /etc/polkit-1/rules.d/10-openhop-repeater.rules
|
||||
else
|
||||
echo "Polkit 0.105 or less detected, using pkla file"
|
||||
mkdir -p /etc/polkit-1/localauthority/50-local.d
|
||||
cat > /etc/polkit-1/localauthority/50-local.d/10-pymc-repeater.pkla <<'EOF'
|
||||
[Allow repeater to restart pymc-repeater service]
|
||||
cat > /etc/polkit-1/localauthority/50-local.d/10-openhop-repeater.pkla <<'EOF'
|
||||
[Allow repeater to restart openhop-repeater service]
|
||||
Identity=unix-user:repeater
|
||||
Action=org.freedesktop.systemd1.manage-units
|
||||
ResultAny=yes
|
||||
ResultInactive=yes
|
||||
ResultActive=yes
|
||||
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
|
||||
# Also configure sudoers as fallback for service restart
|
||||
mkdir -p /etc/sudoers.d
|
||||
cat > /etc/sudoers.d/pymc-repeater <<'EOF'
|
||||
# Allow repeater user to manage the pymc-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
|
||||
cat > /etc/sudoers.d/openhop-repeater <<'EOF'
|
||||
# Allow repeater user to manage the openhop-repeater service without password
|
||||
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
|
||||
chmod 0440 /etc/sudoers.d/pymc-repeater
|
||||
chmod 0440 /etc/sudoers.d/openhop-repeater
|
||||
# Install / refresh OTA upgrade wrapper
|
||||
cat > /usr/local/bin/pymc-do-upgrade <<'UPGRADEEOF'
|
||||
#!/bin/bash
|
||||
@@ -864,7 +948,7 @@ EOF
|
||||
set -e
|
||||
CHANNEL="${1:-main}"
|
||||
PRETEND_VERSION="${2:-}"
|
||||
VENV_DIR="/opt/pymc_repeater/venv"
|
||||
VENV_DIR="/opt/openhop_repeater/venv"
|
||||
VENV_PIP="$VENV_DIR/bin/pip"
|
||||
VENV_PYTHON="$VENV_DIR/bin/python"
|
||||
# Validate: only allow safe git ref characters
|
||||
@@ -881,14 +965,22 @@ if [ ! -x "$VENV_PYTHON" ]; then
|
||||
python3 -m venv --system-site-packages "$VENV_DIR"
|
||||
"$VENV_PIP" install --upgrade pip setuptools wheel >/dev/null 2>&1 || true
|
||||
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 ----
|
||||
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
|
||||
sed -i '/^Environment=.*PYTHONPATH/d' "$SVC_UNIT"
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
if grep -q 'WorkingDirectory=/opt/pymc_repeater' "$SVC_UNIT" 2>/dev/null; then
|
||||
sed -i 's|WorkingDirectory=/opt/pymc_repeater|WorkingDirectory=/var/lib/pymc_repeater|' "$SVC_UNIT"
|
||||
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
|
||||
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
|
||||
fi
|
||||
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
|
||||
fi
|
||||
# ---- 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 ----
|
||||
python3 -m pip uninstall -y pymc_repeater 2>/dev/null || true
|
||||
python3 -m pip uninstall -y pymc_core 2>/dev/null || true
|
||||
python3 -m pip uninstall -y openhop_repeater 2>/dev/null || true
|
||||
python3 -m pip uninstall -y openhop_core 2>/dev/null || true
|
||||
# ---- Try R2 wheels first for faster OTA upgrades ----
|
||||
R2_BASE_URL="https://wheel.pymc.dev/pymc_build_deps"
|
||||
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..."
|
||||
"$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
|
||||
# ---- Install pymc_repeater from git ----
|
||||
# ---- Install openhop_repeater from git ----
|
||||
if "$VENV_PIP" install \
|
||||
--upgrade \
|
||||
--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.
|
||||
RADIO_BASE_URL="https://raw.githubusercontent.com/rightup/pyMC_Repeater/${CHANNEL}"
|
||||
RADIO_STORAGE_DIR="/var/lib/pymc_repeater"
|
||||
RADIO_BASE_URL="https://raw.githubusercontent.com/rightup/openhop-repeater/${CHANNEL}"
|
||||
RADIO_STORAGE_DIR="/var/lib/openhop_repeater"
|
||||
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-presets.json" "${RADIO_BASE_URL}/radio-presets.json" 2>/dev/null || true
|
||||
@@ -939,7 +1034,7 @@ UPGRADEEOF
|
||||
|
||||
echo "=== Installing Python Dependencies ==="
|
||||
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 ""
|
||||
|
||||
@@ -969,7 +1064,7 @@ UPGRADEEOF
|
||||
migrate_to_venv
|
||||
|
||||
# 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
|
||||
if [ "$R2_ENABLED" -eq 1 ]; then
|
||||
@@ -1091,10 +1186,10 @@ uninstall_repeater() {
|
||||
return
|
||||
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 " Uninstalling pyMC Repeater"
|
||||
echo " Uninstalling openHop Repeater"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
@@ -1105,24 +1200,28 @@ uninstall_repeater() {
|
||||
(
|
||||
echo "20"; echo "# Backing up configuration..."
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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/localauthority/50-local.d/10-pymc-repeater.pkla || true
|
||||
rm -f /etc/sudoers.d/pymc-repeater
|
||||
rm -f /etc/polkit-1/rules.d/10-openhop-repeater.rules || true
|
||||
rm -f /etc/polkit-1/localauthority/50-local.d/10-openhop-repeater.pkla || true
|
||||
rm -f /etc/sudoers.d/openhop-repeater
|
||||
rm -f /usr/local/bin/pymc-do-upgrade
|
||||
|
||||
echo "60"; echo "# Removing installation..."
|
||||
rm -rf "$INSTALL_DIR"
|
||||
rm -rf "$CONFIG_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..."
|
||||
if id "$SERVICE_USER" &>/dev/null; then
|
||||
@@ -1130,9 +1229,9 @@ uninstall_repeater() {
|
||||
fi
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1167,9 +1266,9 @@ manage_service() {
|
||||
systemctl start "$SERVICE_NAME"
|
||||
if is_running; then
|
||||
if [[ "$silent" == "true" ]]; then
|
||||
echo "✓ pyMC Repeater service has been started successfully."
|
||||
echo "✓ openHop Repeater service has been started successfully."
|
||||
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
|
||||
else
|
||||
if [[ "$silent" == "true" ]]; then
|
||||
@@ -1183,18 +1282,18 @@ manage_service() {
|
||||
"stop")
|
||||
systemctl stop "$SERVICE_NAME"
|
||||
if [[ "$silent" == "true" ]]; then
|
||||
echo "✓ pyMC Repeater service has been stopped."
|
||||
echo "✓ openHop Repeater service has been stopped."
|
||||
else
|
||||
show_info "Service Stopped" "\n✓ pyMC Repeater service has been stopped."
|
||||
show_info "Service Stopped" "\n✓ openHop Repeater service has been stopped."
|
||||
fi
|
||||
;;
|
||||
"restart")
|
||||
systemctl restart "$SERVICE_NAME"
|
||||
if is_running; then
|
||||
if [[ "$silent" == "true" ]]; then
|
||||
echo "✓ pyMC Repeater service has been restarted successfully."
|
||||
echo "✓ openHop Repeater service has been restarted successfully."
|
||||
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
|
||||
else
|
||||
if [[ "$silent" == "true" ]]; then
|
||||
@@ -1324,14 +1423,14 @@ validate_and_update_config() {
|
||||
|
||||
# Main script logic
|
||||
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
echo "pyMC Repeater Management Script"
|
||||
echo "openHop Repeater Management Script"
|
||||
echo ""
|
||||
echo "Usage: $0 [action]"
|
||||
echo ""
|
||||
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 " uninstall - Remove pyMC Repeater"
|
||||
echo " uninstall - Remove openHop Repeater"
|
||||
echo " config - Configure radio settings"
|
||||
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)"
|
||||
@@ -1355,7 +1454,7 @@ if [ "$1" = "debug" ]; then
|
||||
echo "Script: $0"
|
||||
echo ""
|
||||
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."
|
||||
exit 0
|
||||
fi
|
||||
@@ -1393,7 +1492,7 @@ case "$1" in
|
||||
"logs")
|
||||
clear
|
||||
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"
|
||||
echo ""
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Systemd service file template for Py MC - Meshcore Repeater Daemon.
|
||||
#Install as /etc/systemd/system/pymc-repeater.service
|
||||
#Systemd service file template for openHop - Meshcore Repeater Daemon.
|
||||
#Install as /etc/systemd/system/openhop-repeater.service
|
||||
|
||||
[Unit]
|
||||
Description=pyMC Repeater Daemon
|
||||
Description=openHop Repeater Daemon
|
||||
After=network-online.target dbus.socket
|
||||
Wants=network-online.target
|
||||
Requires=dbus.socket
|
||||
@@ -11,10 +11,10 @@ Requires=dbus.socket
|
||||
Type=simple
|
||||
User=repeater
|
||||
Group=repeater
|
||||
WorkingDirectory=/var/lib/pymc_repeater
|
||||
WorkingDirectory=/var/lib/openhop_repeater
|
||||
|
||||
# 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
|
||||
@@ -29,10 +29,10 @@ MemoryHigh=256M
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=pymc-repeater
|
||||
SyslogIdentifier=openhop-repeater
|
||||
|
||||
# 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
|
||||
|
||||
# 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"
|
||||
|
||||
[project]
|
||||
name = "pymc_repeater"
|
||||
name = "openhop_repeater"
|
||||
dynamic = ["version"]
|
||||
authors = [
|
||||
{name = "Rightup", email = "rightup@pymc.dev"},
|
||||
@@ -29,7 +29,7 @@ keywords = ["mesh", "networking", "lora", "repeater", "daemon", "iot"]
|
||||
|
||||
|
||||
dependencies = [
|
||||
"pymc_core[hardware] @ git+https://github.com/rightup/pyMC_core.git@dev",
|
||||
"openhop_core[hardware]",
|
||||
"pyyaml>=6.0.0",
|
||||
"cherrypy>=18.0.0",
|
||||
"paho-mqtt>=1.6.0",
|
||||
@@ -45,7 +45,7 @@ dependencies = [
|
||||
[project.optional-dependencies]
|
||||
# SX1262/SPI support (Linux only; required for Raspberry Pi HATs)
|
||||
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 = [
|
||||
@@ -59,7 +59,7 @@ dev = [
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
pymc-repeater = "repeater.main:main"
|
||||
openhop-repeater = "repeater.main:main"
|
||||
pymc-cli = "repeater.local_cli:main"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
|
||||
+1
-1
@@ -411,7 +411,7 @@
|
||||
},
|
||||
"kiss": {
|
||||
"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",
|
||||
"radio_type": "kiss",
|
||||
"tx_power": 14,
|
||||
|
||||
@@ -4,6 +4,6 @@ except ImportError:
|
||||
try:
|
||||
from importlib.metadata import version
|
||||
|
||||
__version__ = version("pymc_repeater")
|
||||
__version__ = version("openhop_repeater")
|
||||
except Exception:
|
||||
__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.
|
||||
"""
|
||||
|
||||
@@ -13,7 +13,7 @@ import logging
|
||||
from enum import Enum
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
from pymc_core.companion import CompanionBridge
|
||||
from openhop_core.companion import CompanionBridge
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
# 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_REPEATER,
|
||||
ADV_TYPE_ROOM,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
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
|
||||
``sqlite_handler`` dependency.
|
||||
"""
|
||||
@@ -12,9 +12,9 @@ import asyncio
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from pymc_core.companion.constants import RESP_CODE_NO_MORE_MESSAGES
|
||||
from pymc_core.companion.frame_server import CompanionFrameServer as _BaseFrameServer
|
||||
from pymc_core.companion.models import QueuedMessage
|
||||
from openhop_core.companion.constants import RESP_CODE_NO_MORE_MESSAGES
|
||||
from openhop_core.companion.frame_server import CompanionFrameServer as _BaseFrameServer
|
||||
from openhop_core.companion.models import QueuedMessage
|
||||
|
||||
logger = logging.getLogger("CompanionFrameServer")
|
||||
|
||||
@@ -45,8 +45,8 @@ class CompanionFrameServer(_BaseFrameServer):
|
||||
port=port,
|
||||
bind_address=bind_address,
|
||||
client_idle_timeout_sec=client_idle_timeout_sec,
|
||||
device_model="pyMC-Repeater-Companion",
|
||||
device_version=None, # use FIRMWARE_VER_CODE from pyMC_core
|
||||
device_model="openHop-Repeater-Companion",
|
||||
device_version=None, # use FIRMWARE_VER_CODE from openhop-core
|
||||
build_date="13 Feb 2026",
|
||||
local_hash=local_hash,
|
||||
stats_getter=stats_getter,
|
||||
|
||||
@@ -50,7 +50,7 @@ def derive_companion_public_key_hex(identity_key: Any) -> Optional[str]:
|
||||
if raw is None:
|
||||
return None
|
||||
try:
|
||||
from pymc_core import LocalIdentity
|
||||
from openhop_core import LocalIdentity
|
||||
|
||||
identity = LocalIdentity(seed=raw)
|
||||
return identity.get_public_key().hex()
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
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__)
|
||||
|
||||
@@ -14,7 +14,7 @@ _INVALID_NODE_NAME_CHARS = "\n\r\x00"
|
||||
# Optional per-companion RepeaterCompanionBridge constructor settings (power-user).
|
||||
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"})
|
||||
|
||||
# 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:
|
||||
"""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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
"""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):
|
||||
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_path: Optional[str] = None,
|
||||
default: str = "/var/lib/pymc_repeater",
|
||||
default: str = "/var/lib/openhop_repeater",
|
||||
) -> Path:
|
||||
|
||||
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]:
|
||||
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
|
||||
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,
|
||||
"verify_tls": True,
|
||||
"api_token": None,
|
||||
"cert_store_dir": "/etc/pymc_repeater/glass",
|
||||
"cert_store_dir": "/etc/openhop_repeater/glass",
|
||||
}
|
||||
|
||||
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
|
||||
"""
|
||||
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:
|
||||
# Create backup of existing config
|
||||
@@ -387,16 +387,16 @@ def _load_or_create_identity_key(path: Optional[str] = None) -> bytes:
|
||||
|
||||
if path is None:
|
||||
# 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():
|
||||
key_path = system_key_path
|
||||
else:
|
||||
# Follow XDG spec
|
||||
xdg_config_home = os.environ.get("XDG_CONFIG_HOME")
|
||||
if xdg_config_home:
|
||||
config_dir = Path(xdg_config_home) / "pymc_repeater"
|
||||
config_dir = Path(xdg_config_home) / "openhop_repeater"
|
||||
else:
|
||||
config_dir = Path.home() / ".config" / "pymc_repeater"
|
||||
config_dir = Path.home() / ".config" / "openhop_repeater"
|
||||
key_path = config_dir / "identity.key"
|
||||
else:
|
||||
key_path = Path(path)
|
||||
@@ -475,7 +475,7 @@ def get_radio_for_board(board_config: dict):
|
||||
radio_type = "kiss"
|
||||
|
||||
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
|
||||
spi_config = board_config.get("sx1262")
|
||||
@@ -492,8 +492,8 @@ def get_radio_for_board(board_config: dict):
|
||||
if not ch341_cfg:
|
||||
raise ValueError("Missing 'ch341' section in configuration file")
|
||||
|
||||
from pymc_core.hardware.lora.LoRaRF.SX126x import set_spi_transport
|
||||
from pymc_core.hardware.transports.ch341_spi_transport import CH341SPITransport
|
||||
from openhop_core.hardware.lora.LoRaRF.SX126x import set_spi_transport
|
||||
from openhop_core.hardware.transports.ch341_spi_transport import CH341SPITransport
|
||||
|
||||
vid = _parse_int(ch341_cfg.get("vid"), default=0x1A86)
|
||||
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
|
||||
|
||||
# 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:
|
||||
combined_config["gpio_chip"] = _parse_int(spi_config["gpio_chip"], default=0)
|
||||
if "use_gpiod_backend" in spi_config:
|
||||
@@ -554,16 +554,16 @@ def get_radio_for_board(board_config: dict):
|
||||
|
||||
elif radio_type == "kiss":
|
||||
try:
|
||||
from pymc_core.hardware.kiss_modem_wrapper import KissModemWrapper
|
||||
from openhop_core.hardware.kiss_modem_wrapper import KissModemWrapper
|
||||
except ImportError:
|
||||
try:
|
||||
from pymc_core.hardware.kiss_serial_wrapper import (
|
||||
from openhop_core.hardware.kiss_serial_wrapper import (
|
||||
KissSerialWrapper as KissModemWrapper,
|
||||
)
|
||||
except ImportError:
|
||||
raise RuntimeError(
|
||||
"KISS modem support requires pyMC_core with KISS support. "
|
||||
"Install your fork with: pip install -e /path/to/pyMC_core"
|
||||
"KISS modem support requires openhop-core with KISS support. "
|
||||
"Install your fork with: pip install -e /path/to/openhop-core"
|
||||
) from None
|
||||
|
||||
kiss_config = board_config.get("kiss")
|
||||
@@ -613,11 +613,11 @@ def get_radio_for_board(board_config: dict):
|
||||
|
||||
elif radio_type == "pymc_tcp":
|
||||
try:
|
||||
from pymc_core.hardware.tcp_radio import TCPLoRaRadio
|
||||
from openhop_core.hardware.tcp_radio import TCPLoRaRadio
|
||||
except ImportError:
|
||||
raise RuntimeError(
|
||||
"pymc_tcp radio requires pyMC_core >= the release that includes "
|
||||
"PR pyMC-dev/pyMC_core#68 (merged 2026-05-13). "
|
||||
"pymc_tcp radio requires openhop-core >= the release that includes "
|
||||
"PR pyMC-dev/openhop-core#68 (merged 2026-05-13). "
|
||||
"Reinstall the [hardware] extra to pick it up."
|
||||
) from None
|
||||
|
||||
@@ -657,11 +657,11 @@ def get_radio_for_board(board_config: dict):
|
||||
|
||||
elif radio_type == "pymc_usb":
|
||||
try:
|
||||
from pymc_core.hardware.usb_radio import USBLoRaRadio
|
||||
from openhop_core.hardware.usb_radio import USBLoRaRadio
|
||||
except ImportError:
|
||||
raise RuntimeError(
|
||||
"pymc_usb radio requires pyMC_core >= the release that includes "
|
||||
"PR pyMC-dev/pyMC_core#68 (merged 2026-05-13). "
|
||||
"pymc_usb radio requires openhop-core >= the release that includes "
|
||||
"PR pyMC-dev/openhop-core#68 (merged 2026-05-13). "
|
||||
"Reinstall the [hardware] extra to pick it up."
|
||||
) from None
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class GlassHandler:
|
||||
self.verify_tls = True
|
||||
self.api_token = "" # nosec - runtime config value, not a hardcoded credential
|
||||
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.mqtt_enabled = False
|
||||
self.mqtt_broker_host = "localhost"
|
||||
@@ -132,8 +132,8 @@ class GlassHandler:
|
||||
int(glass_cfg.get("inform_interval_seconds", self.inform_interval_seconds))
|
||||
)
|
||||
self.cert_store_dir = str(
|
||||
glass_cfg.get("cert_store_dir", "/etc/pymc_repeater/glass")
|
||||
or "/etc/pymc_repeater/glass"
|
||||
glass_cfg.get("cert_store_dir", "/etc/openhop_repeater/glass")
|
||||
or "/etc/openhop_repeater/glass"
|
||||
)
|
||||
self.client_cert_path = (
|
||||
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}"
|
||||
|
||||
from pymc_core.protocol.utils import PAYLOAD_TYPES
|
||||
from openhop_core.protocol.utils import PAYLOAD_TYPES
|
||||
|
||||
disallowed_types = broker.get("disallowed_packet_types", [])
|
||||
type_name_map = {name: code for code, name in PAYLOAD_TYPES.items()}
|
||||
@@ -973,7 +973,7 @@ class MeshCoreToMqttPusher:
|
||||
"model": "PyMC-Repeater",
|
||||
"firmware_version": self.app_version,
|
||||
"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 {})},
|
||||
}
|
||||
|
||||
|
||||
@@ -1201,9 +1201,9 @@ class SQLiteHandler:
|
||||
return cached["value"]
|
||||
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:
|
||||
from pymc_core.protocol.utils import PAYLOAD_TYPES as _PT
|
||||
from openhop_core.protocol.utils import PAYLOAD_TYPES as _PT
|
||||
|
||||
_human = {
|
||||
"REQ": "Request",
|
||||
@@ -1764,7 +1764,7 @@ class SQLiteHandler:
|
||||
A base64-encoded transport key derived from the name
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
+5
-5
@@ -6,9 +6,9 @@ import time
|
||||
from collections import OrderedDict, deque
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from pymc_core.node.handlers.base import BaseHandler
|
||||
from pymc_core.protocol import Packet
|
||||
from pymc_core.protocol.constants import (
|
||||
from openhop_core.node.handlers.base import BaseHandler
|
||||
from openhop_core.protocol import Packet
|
||||
from openhop_core.protocol.constants import (
|
||||
MAX_PATH_SIZE,
|
||||
PAYLOAD_TYPE_ADVERT,
|
||||
PAYLOAD_TYPE_ANON_REQ,
|
||||
@@ -19,7 +19,7 @@ from pymc_core.protocol.constants import (
|
||||
ROUTE_TYPE_TRANSPORT_DIRECT,
|
||||
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.data_acquisition import StorageCollector
|
||||
@@ -820,7 +820,7 @@ class RepeaterHandler(BaseHandler):
|
||||
return False, "No storage available for transport key validation"
|
||||
|
||||
try:
|
||||
from pymc_core.protocol.transport_keys import calc_transport_code
|
||||
from openhop_core.protocol.transport_keys import calc_transport_code
|
||||
|
||||
# Check cache validity
|
||||
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 .discovery import DiscoveryHelper
|
||||
|
||||
@@ -2,8 +2,8 @@ import logging
|
||||
import time
|
||||
from typing import Dict, Optional
|
||||
|
||||
from pymc_core.protocol import Identity
|
||||
from pymc_core.protocol.constants import PUB_KEY_SIZE
|
||||
from openhop_core.protocol import Identity
|
||||
from openhop_core.protocol.constants import PUB_KEY_SIZE
|
||||
|
||||
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.
|
||||
Includes adaptive rate limiting based on mesh activity.
|
||||
@@ -13,7 +13,7 @@ from collections import OrderedDict, deque
|
||||
from enum import Enum
|
||||
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")
|
||||
|
||||
@@ -603,7 +603,7 @@ class AdvertHelper:
|
||||
return
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
@@ -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,
|
||||
allowing other nodes to discover repeaters on the mesh network.
|
||||
@@ -9,7 +9,7 @@ import asyncio
|
||||
import logging
|
||||
import secrets
|
||||
|
||||
from pymc_core.node.handlers.control import ControlHandler
|
||||
from openhop_core.node.handlers.control import ControlHandler
|
||||
|
||||
logger = logging.getLogger("DiscoveryHelper")
|
||||
|
||||
@@ -133,7 +133,7 @@ class DiscoveryHelper:
|
||||
try:
|
||||
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(
|
||||
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.
|
||||
"""
|
||||
@@ -8,9 +8,9 @@ import asyncio
|
||||
import logging
|
||||
import time
|
||||
|
||||
from pymc_core.node.handlers.anon_request import AnonRateLimiter, AnonRequestHandler
|
||||
from pymc_core.node.handlers.login_server import LoginServerHandler
|
||||
from pymc_core.protocol.constants import PAYLOAD_TYPE_ANON_REQ
|
||||
from openhop_core.node.handlers.anon_request import AnonRateLimiter, AnonRequestHandler
|
||||
from openhop_core.node.handlers.login_server import LoginServerHandler
|
||||
from openhop_core.protocol.constants import PAYLOAD_TYPE_ANON_REQ
|
||||
|
||||
logger = logging.getLogger("LoginHelper")
|
||||
|
||||
@@ -167,7 +167,7 @@ class LoginHelper:
|
||||
emit the ``*`` wildcard region first (unless unscoped flood is denied),
|
||||
then each allow-flood named region with a leading ``#`` stripped, with no
|
||||
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).
|
||||
"""
|
||||
parts = []
|
||||
|
||||
@@ -12,7 +12,7 @@ class PathHelper:
|
||||
|
||||
async def process_path_packet(self, packet):
|
||||
|
||||
from pymc_core.protocol.crypto import CryptoUtils
|
||||
from openhop_core.protocol.crypto import CryptoUtils
|
||||
|
||||
try:
|
||||
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.
|
||||
"""
|
||||
@@ -9,7 +9,7 @@ import logging
|
||||
import struct
|
||||
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_NEIGHBOURS,
|
||||
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)
|
||||
"""
|
||||
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", "")
|
||||
|
||||
# Version: use package version if available, fallback to "pyMC"
|
||||
try:
|
||||
from importlib.metadata import version as pkg_version
|
||||
|
||||
fw_version = pkg_version("pymc-repeater")
|
||||
fw_version = pkg_version("openhop-repeater")
|
||||
except Exception:
|
||||
fw_version = "pyMC"
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import secrets
|
||||
import time
|
||||
from typing import Dict
|
||||
|
||||
from pymc_core.protocol import CryptoUtils, PacketBuilder
|
||||
from pymc_core.protocol.constants import PAYLOAD_TYPE_TXT_MSG
|
||||
from openhop_core.protocol import CryptoUtils, PacketBuilder
|
||||
from openhop_core.protocol.constants import PAYLOAD_TYPE_TXT_MSG
|
||||
|
||||
logger = logging.getLogger("RoomServer")
|
||||
|
||||
@@ -102,8 +102,8 @@ class RoomServer:
|
||||
return False
|
||||
|
||||
try:
|
||||
from pymc_core.protocol import PacketBuilder
|
||||
from pymc_core.protocol.constants import (
|
||||
from openhop_core.protocol import PacketBuilder
|
||||
from openhop_core.protocol.constants import (
|
||||
ADVERT_FLAG_HAS_NAME,
|
||||
ADVERT_FLAG_IS_ROOM_SERVER,
|
||||
)
|
||||
@@ -357,7 +357,7 @@ class RoomServer:
|
||||
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
|
||||
pack_data = PacketBuilder._pack_timestamp_data(timestamp, attempt, message_bytes)
|
||||
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
|
||||
(repeater identity + identity manager identities).
|
||||
@@ -10,8 +10,8 @@ import asyncio
|
||||
import logging
|
||||
import time
|
||||
|
||||
from pymc_core.node.handlers.text import TextMessageHandler
|
||||
from pymc_core.protocol import CryptoUtils, Identity
|
||||
from openhop_core.node.handlers.text import TextMessageHandler
|
||||
from openhop_core.protocol import CryptoUtils, Identity
|
||||
|
||||
from .mesh_cli import MeshCLI
|
||||
from .room_server import RoomServer
|
||||
@@ -603,8 +603,8 @@ class TextHelper:
|
||||
"""
|
||||
import time
|
||||
|
||||
from pymc_core.protocol import PacketBuilder
|
||||
from pymc_core.protocol.constants import PAYLOAD_TYPE_TXT_MSG
|
||||
from openhop_core.protocol import PacketBuilder
|
||||
from openhop_core.protocol.constants import PAYLOAD_TYPE_TXT_MSG
|
||||
|
||||
try:
|
||||
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,
|
||||
which are used for network diagnostics to track the path and SNR
|
||||
@@ -11,10 +11,10 @@ import logging
|
||||
import time
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from pymc_core.hardware.signal_utils import snr_register_to_db
|
||||
from pymc_core.node.handlers.trace import TraceHandler
|
||||
from pymc_core.protocol.constants import MAX_PATH_SIZE, ROUTE_TYPE_DIRECT
|
||||
from pymc_core.protocol.packet_utils import PathUtils
|
||||
from openhop_core.hardware.signal_utils import snr_register_to_db
|
||||
from openhop_core.node.handlers.trace import TraceHandler
|
||||
from openhop_core.protocol.constants import MAX_PATH_SIZE, ROUTE_TYPE_DIRECT
|
||||
from openhop_core.protocol.packet_utils import PathUtils
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
CONFIG_PATHS = [
|
||||
"/etc/pymc_repeater/config.yaml",
|
||||
"/etc/openhop_repeater/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.")
|
||||
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")
|
||||
|
||||
while True:
|
||||
@@ -123,7 +123,7 @@ def main():
|
||||
import argparse
|
||||
|
||||
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(
|
||||
"--config",
|
||||
|
||||
+19
-19
@@ -175,8 +175,8 @@ class RepeaterDaemon:
|
||||
self.radio = NullRadio()
|
||||
|
||||
try:
|
||||
from pymc_core import LocalIdentity
|
||||
from pymc_core.node.dispatcher import Dispatcher
|
||||
from openhop_core import LocalIdentity
|
||||
from openhop_core.node.dispatcher import Dispatcher
|
||||
|
||||
self.dispatcher = Dispatcher(self.radio)
|
||||
logger.info("Dispatcher initialized")
|
||||
@@ -307,7 +307,7 @@ class RepeaterDaemon:
|
||||
|
||||
# Initialize ConfigManager for centralized config management
|
||||
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,
|
||||
daemon_instance=self,
|
||||
)
|
||||
@@ -395,7 +395,7 @@ class RepeaterDaemon:
|
||||
# Load companion identities (CompanionBridge + frame server per companion)
|
||||
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)
|
||||
n = len(getattr(self, "companion_frame_servers", []))
|
||||
logger.info(
|
||||
@@ -431,7 +431,7 @@ class RepeaterDaemon:
|
||||
raise
|
||||
|
||||
async def _load_additional_identities(self):
|
||||
from pymc_core import LocalIdentity
|
||||
from openhop_core import LocalIdentity
|
||||
|
||||
identities_config = self.config.get("identities", {})
|
||||
|
||||
@@ -493,8 +493,8 @@ class RepeaterDaemon:
|
||||
|
||||
async def _load_companion_identities(self) -> None:
|
||||
"""Load companion identities from config and create CompanionBridge + frame server for each."""
|
||||
from pymc_core import LocalIdentity
|
||||
from pymc_core.companion.models import Channel
|
||||
from openhop_core import LocalIdentity
|
||||
from openhop_core.companion.models import Channel
|
||||
|
||||
from repeater.companion import CompanionFrameServer, RepeaterCompanionBridge
|
||||
|
||||
@@ -651,7 +651,7 @@ class RepeaterDaemon:
|
||||
for msg_dict in sqlite_handler.companion_load_messages(
|
||||
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"")
|
||||
if isinstance(sk, str):
|
||||
@@ -717,8 +717,8 @@ class RepeaterDaemon:
|
||||
Creates RepeaterCompanionBridge, CompanionFrameServer, starts the server,
|
||||
and registers with identity_manager. Raises on error.
|
||||
"""
|
||||
from pymc_core import LocalIdentity
|
||||
from pymc_core.companion.models import Channel
|
||||
from openhop_core import LocalIdentity
|
||||
from openhop_core.companion.models import Channel
|
||||
|
||||
from repeater.companion import CompanionFrameServer, RepeaterCompanionBridge
|
||||
from repeater.companion.constants import DEFAULT_PUBLIC_CHANNEL_SECRET
|
||||
@@ -837,7 +837,7 @@ class RepeaterDaemon:
|
||||
for msg_dict in sqlite_handler.companion_load_messages(
|
||||
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"")
|
||||
if isinstance(sk, str):
|
||||
@@ -1139,8 +1139,8 @@ class RepeaterDaemon:
|
||||
return False
|
||||
|
||||
try:
|
||||
from pymc_core.protocol import PacketBuilder
|
||||
from pymc_core.protocol.constants import ADVERT_FLAG_HAS_NAME, ADVERT_FLAG_IS_REPEATER
|
||||
from openhop_core.protocol import PacketBuilder
|
||||
from openhop_core.protocol.constants import ADVERT_FLAG_HAS_NAME, ADVERT_FLAG_IS_REPEATER
|
||||
|
||||
# Get node name and location from config
|
||||
repeater_config = self.config.get("repeater", {})
|
||||
@@ -1332,7 +1332,7 @@ class RepeaterDaemon:
|
||||
radio_type_raw = self.config.get("radio_type")
|
||||
radio_type = "" if radio_type_raw is None else str(radio_type_raw).lower()
|
||||
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()
|
||||
except Exception as e:
|
||||
@@ -1402,7 +1402,7 @@ class RepeaterDaemon:
|
||||
config=self.config,
|
||||
event_loop=current_loop,
|
||||
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:
|
||||
@@ -1410,7 +1410,7 @@ class RepeaterDaemon:
|
||||
except Exception as 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:
|
||||
await self.dispatcher.run_forever()
|
||||
except asyncio.CancelledError:
|
||||
@@ -1441,10 +1441,10 @@ def main():
|
||||
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="pyMC Repeater Daemon")
|
||||
parser = argparse.ArgumentParser(description="openHop Repeater Daemon")
|
||||
parser.add_argument(
|
||||
"--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(
|
||||
"--log-level",
|
||||
@@ -1456,7 +1456,7 @@ def main():
|
||||
|
||||
# Load configuration
|
||||
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 "logging" not in config:
|
||||
|
||||
+12
-12
@@ -2,18 +2,18 @@ import asyncio
|
||||
import logging
|
||||
import time
|
||||
|
||||
from pymc_core.node.handlers.ack import AckHandler
|
||||
from pymc_core.node.handlers.advert import AdvertHandler
|
||||
from pymc_core.node.handlers.control import ControlHandler
|
||||
from pymc_core.node.handlers.group_text import GroupTextHandler
|
||||
from pymc_core.node.handlers.login_response import LoginResponseHandler
|
||||
from pymc_core.node.handlers.login_server import LoginServerHandler
|
||||
from pymc_core.node.handlers.path import PathHandler
|
||||
from pymc_core.node.handlers.protocol_request import ProtocolRequestHandler
|
||||
from pymc_core.node.handlers.protocol_response import ProtocolResponseHandler
|
||||
from pymc_core.node.handlers.text import TextMessageHandler
|
||||
from pymc_core.node.handlers.trace import TraceHandler
|
||||
from pymc_core.protocol.constants import (
|
||||
from openhop_core.node.handlers.ack import AckHandler
|
||||
from openhop_core.node.handlers.advert import AdvertHandler
|
||||
from openhop_core.node.handlers.control import ControlHandler
|
||||
from openhop_core.node.handlers.group_text import GroupTextHandler
|
||||
from openhop_core.node.handlers.login_response import LoginResponseHandler
|
||||
from openhop_core.node.handlers.login_server import LoginServerHandler
|
||||
from openhop_core.node.handlers.path import PathHandler
|
||||
from openhop_core.node.handlers.protocol_request import ProtocolRequestHandler
|
||||
from openhop_core.node.handlers.protocol_response import ProtocolResponseHandler
|
||||
from openhop_core.node.handlers.text import TextMessageHandler
|
||||
from openhop_core.node.handlers.trace import TraceHandler
|
||||
from openhop_core.protocol.constants import (
|
||||
PH_ROUTE_MASK,
|
||||
ROUTE_TYPE_DIRECT,
|
||||
ROUTE_TYPE_TRANSPORT_DIRECT,
|
||||
|
||||
@@ -5,8 +5,8 @@ import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Optional
|
||||
|
||||
from pymc_core.protocol.constants import PAYLOAD_TYPE_GRP_DATA, PAYLOAD_TYPE_GRP_TXT
|
||||
from pymc_core.protocol.crypto import CryptoUtils
|
||||
from openhop_core.protocol.constants import PAYLOAD_TYPE_GRP_DATA, PAYLOAD_TYPE_GRP_TXT
|
||||
from openhop_core.protocol.crypto import CryptoUtils
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
@@ -12,7 +12,7 @@ import time
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
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"
|
||||
_CONTAINER_RESTART_DELAY_SECONDS = 1.0
|
||||
_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 (
|
||||
"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."
|
||||
)
|
||||
|
||||
|
||||
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 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:
|
||||
result = subprocess.run(
|
||||
[_SYSTEMCTL_BIN, "restart", "pymc-repeater"],
|
||||
[_SYSTEMCTL_BIN, "restart", "openhop-repeater"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
@@ -162,10 +162,10 @@ def restart_service() -> Tuple[bool, str]:
|
||||
except Exception as 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:
|
||||
result = subprocess.run(
|
||||
[_SUDO_BIN, "--non-interactive", _SYSTEMCTL_BIN, "restart", "pymc-repeater"],
|
||||
[_SUDO_BIN, "--non-interactive", _SYSTEMCTL_BIN, "restart", "openhop-repeater"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import Callable, Optional
|
||||
|
||||
import cherrypy
|
||||
import yaml
|
||||
from pymc_core.protocol import CryptoUtils
|
||||
from openhop_core.protocol import CryptoUtils
|
||||
|
||||
from repeater import __version__
|
||||
from repeater.companion.identity_resolve import (
|
||||
@@ -198,7 +198,7 @@ class APIEndpoints:
|
||||
self.config = config or {}
|
||||
self.event_loop = event_loop
|
||||
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)
|
||||
|
||||
# Initialize ConfigManager for centralized config management
|
||||
@@ -1427,9 +1427,9 @@ class APIEndpoints:
|
||||
stats["site_name"] = self.config.get("web", {}).get("site_name", "")
|
||||
stats["version"] = __version__
|
||||
try:
|
||||
import pymc_core
|
||||
import openhop_core
|
||||
|
||||
stats["core_version"] = pymc_core.__version__
|
||||
stats["core_version"] = openhop_core.__version__
|
||||
except ImportError:
|
||||
stats["core_version"] = "unknown"
|
||||
image_info = get_buildroot_image_info()
|
||||
@@ -2162,7 +2162,7 @@ class APIEndpoints:
|
||||
@cherrypy.tools.json_out()
|
||||
@cherrypy.tools.json_in()
|
||||
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
|
||||
self._set_cors_headers()
|
||||
|
||||
@@ -3819,7 +3819,7 @@ class APIEndpoints:
|
||||
self.config["mesh"]["unscoped_flood_allow"] = unscoped_flood_allow
|
||||
|
||||
# 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"):
|
||||
config_path = self.daemon_instance.config_path
|
||||
|
||||
@@ -3932,7 +3932,7 @@ class APIEndpoints:
|
||||
trace_tag = secrets.randbits(32)
|
||||
|
||||
# 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"))
|
||||
packet = PacketBuilder.create_trace(
|
||||
@@ -4349,7 +4349,7 @@ class APIEndpoints:
|
||||
companion_activation_error = None
|
||||
if identity_type == "room_server" and self.daemon_instance:
|
||||
try:
|
||||
from pymc_core import LocalIdentity
|
||||
from openhop_core import LocalIdentity
|
||||
|
||||
# Create LocalIdentity from the key (convert hex string to bytes)
|
||||
if isinstance(identity_key, bytes):
|
||||
@@ -4673,7 +4673,7 @@ class APIEndpoints:
|
||||
|
||||
if needs_reload and self.daemon_instance:
|
||||
try:
|
||||
from pymc_core import LocalIdentity
|
||||
from openhop_core import LocalIdentity
|
||||
|
||||
final_name = identity["name"] # Could be new_name
|
||||
identity_key = identity["identity_key"]
|
||||
@@ -4961,8 +4961,8 @@ class APIEndpoints:
|
||||
):
|
||||
"""Send advert for a room server identity"""
|
||||
try:
|
||||
from pymc_core.protocol import PacketBuilder
|
||||
from pymc_core.protocol.constants import (
|
||||
from openhop_core.protocol import PacketBuilder
|
||||
from openhop_core.protocol.constants import (
|
||||
ADVERT_FLAG_HAS_NAME,
|
||||
ADVERT_FLAG_IS_ROOM_SERVER,
|
||||
)
|
||||
@@ -6588,7 +6588,7 @@ class APIEndpoints:
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<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">
|
||||
<style>
|
||||
body {
|
||||
|
||||
@@ -15,7 +15,7 @@ import time
|
||||
from typing import Optional
|
||||
|
||||
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 (
|
||||
trim_companion_contacts_to_fit,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: pyMC Repeater API
|
||||
title: openHop Repeater API
|
||||
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
|
||||
- System statistics and monitoring
|
||||
@@ -14,8 +14,8 @@ info:
|
||||
- Noise floor monitoring
|
||||
version: 1.0.0
|
||||
contact:
|
||||
name: pyMC Repeater
|
||||
url: https://github.com/rightup/pyMC_Repeater
|
||||
name: openHop Repeater
|
||||
url: https://github.com/rightup/openhop-repeater
|
||||
|
||||
servers:
|
||||
- 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
|
||||
SSE progress streaming, and release-channel switching.
|
||||
@@ -19,6 +19,7 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import ssl
|
||||
|
||||
# Required for fixed internal maintenance commands.
|
||||
@@ -41,10 +42,10 @@ logger = logging.getLogger("HTTPServer")
|
||||
# Repository constants
|
||||
# ---------------------------------------------------------------------------
|
||||
GITHUB_OWNER = "rightup"
|
||||
GITHUB_REPO = "pyMC_Repeater"
|
||||
GITHUB_REPO = "openhop-repeater"
|
||||
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}"
|
||||
PACKAGE_NAME = "pymc_repeater"
|
||||
PACKAGE_NAME = "openhop_repeater"
|
||||
|
||||
# How long (seconds) before a cached check result expires
|
||||
CHECK_CACHE_TTL = 600 # 10 minutes
|
||||
@@ -52,6 +53,8 @@ _RM_BIN = "/bin/rm"
|
||||
_SED_BIN = "/usr/bin/sed"
|
||||
_SYSTEMCTL_BIN = "/bin/systemctl"
|
||||
_SUDO_BIN = "/usr/bin/sudo"
|
||||
_INSTALL_DIR = "/opt/openhop_repeater"
|
||||
_LEGACY_INSTALL_DIR = "/opt/openhop-repeater"
|
||||
|
||||
_github_ssl_ctx: Optional[ssl.SSLContext] = 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]:
|
||||
candidates = [
|
||||
"/root/pyMC_Repeater/buildroot-manage.sh",
|
||||
"/opt/pymc_repeater/pyMC_Repeater/buildroot-manage.sh",
|
||||
"/root/openhop-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",
|
||||
]
|
||||
for path in candidates:
|
||||
@@ -89,7 +93,7 @@ class _RateLimitError(Exception):
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
dirs.append(p)
|
||||
# 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):
|
||||
for child in os.listdir(_venv_site):
|
||||
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 = "/var/lib/pymc_repeater/.update_channel"
|
||||
_CHANNELS_FILE = "/var/lib/openhop_repeater/.update_channel"
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
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")
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
@@ -565,7 +569,7 @@ def _cleanup_stale_dist_info(allow_sudo: bool = True) -> None:
|
||||
except AttributeError:
|
||||
pass
|
||||
# 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):
|
||||
for child in os.listdir(_venv_site):
|
||||
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"
|
||||
|
||||
# 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 = {}
|
||||
for site_dir in dirs:
|
||||
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.")
|
||||
return
|
||||
|
||||
_SVC_UNIT = "/etc/systemd/system/pymc-repeater.service"
|
||||
_VENV_PYTHON = "/opt/pymc_repeater/venv/bin/python"
|
||||
_SVC_UNIT = "/etc/systemd/system/openhop-repeater.service"
|
||||
_VENV_PYTHON = "/opt/openhop_repeater/venv/bin/python"
|
||||
try:
|
||||
subprocess.run([_SED_BIN, "-i", "/^Environment=.*PYTHONPATH/d", _SVC_UNIT], check=False) # nosec B603
|
||||
subprocess.run(
|
||||
[
|
||||
_SED_BIN,
|
||||
"-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,
|
||||
],
|
||||
check=False,
|
||||
@@ -817,6 +830,68 @@ def _migrate_service_unit() -> None:
|
||||
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:
|
||||
|
||||
channel = _state.channel
|
||||
@@ -850,7 +925,7 @@ def _do_install() -> None:
|
||||
env = _os.environ.copy()
|
||||
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_PYTHON = os.path.join(_VENV_DIR, "bin", "python")
|
||||
|
||||
@@ -874,6 +949,7 @@ def _do_install() -> None:
|
||||
)
|
||||
cmd = ["/bin/sh", _BUILDROOT_UPGRADE_HELPER, "upgrade"]
|
||||
elif is_root:
|
||||
_migrate_legacy_install_path()
|
||||
_migrate_service_unit()
|
||||
|
||||
# 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)
|
||||
|
||||
# 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", "pymc_core"], env=env)
|
||||
_run(["/usr/bin/python3", "-m", "pip", "uninstall", "-y", "openhop_repeater"], env=env)
|
||||
_run(["/usr/bin/python3", "-m", "pip", "uninstall", "-y", "openhop_core"], env=env)
|
||||
|
||||
# Remove stale source tree that could shadow the venv package
|
||||
stale_src = "/opt/pymc_repeater/repeater"
|
||||
if os.path.isdir(stale_src):
|
||||
_state.append_line("[pyMC updater] Removing stale source tree…")
|
||||
import shutil
|
||||
# Remove stale source trees that could shadow the venv package
|
||||
_cleanup_stale_source_trees()
|
||||
|
||||
shutil.rmtree(stale_src, ignore_errors=True)
|
||||
|
||||
install_spec = f"pymc_repeater[hardware] @ git+https://github.com/{GITHUB_OWNER}/{GITHUB_REPO}.git@{channel}"
|
||||
install_spec = f"openhop_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(f"[pyMC updater] Target: {install_spec}")
|
||||
cmd = [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/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
|
||||
|
||||
set -euo pipefail
|
||||
@@ -31,7 +31,7 @@ log_step() {
|
||||
cd "$(dirname "$0")/.."
|
||||
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"
|
||||
|
||||
# Check if we're in a git repository
|
||||
@@ -69,7 +69,7 @@ log_info "Debian version: $DEBIAN_VERSION"
|
||||
log_step "Updating debian/changelog..."
|
||||
CHANGELOG_DATE=$(date -R)
|
||||
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)
|
||||
* Version: $VERSION
|
||||
@@ -81,11 +81,11 @@ log_info "Changelog updated with version $DEBIAN_VERSION"
|
||||
|
||||
# Clean previous builds
|
||||
log_step "Cleaning previous builds..."
|
||||
rm -rf debian/pymc-repeater/
|
||||
rm -rf debian/openhop-repeater/
|
||||
rm -rf debian/.debhelper/
|
||||
rm -rf debian/files
|
||||
rm -f debian/pymc-repeater.*.debhelper
|
||||
rm -f debian/pymc-repeater.substvars
|
||||
rm -f debian/openhop-repeater.*.debhelper
|
||||
rm -f debian/openhop-repeater.substvars
|
||||
rm -f debian/*.log
|
||||
rm -rf .pybuild/
|
||||
rm -rf build/
|
||||
@@ -108,7 +108,7 @@ else
|
||||
fi
|
||||
|
||||
# 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
|
||||
log_info ""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/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
|
||||
|
||||
set -euo pipefail
|
||||
@@ -31,7 +31,7 @@ log_step() {
|
||||
cd "$(dirname "$0")/.."
|
||||
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"
|
||||
|
||||
# Check if we're in a git repository
|
||||
@@ -106,7 +106,7 @@ log_info "Debian version: $DEBIAN_VERSION"
|
||||
log_step "Updating debian/changelog..."
|
||||
CHANGELOG_DATE=$(date -R)
|
||||
cat > debian/changelog << EOF
|
||||
pymc-repeater ($DEBIAN_VERSION) stable; urgency=medium
|
||||
openhop-repeater ($DEBIAN_VERSION) stable; urgency=medium
|
||||
|
||||
* Production release $VERSION
|
||||
* Git tag: $TAG
|
||||
@@ -119,11 +119,11 @@ log_info "Changelog updated with version $DEBIAN_VERSION"
|
||||
|
||||
# Clean previous builds
|
||||
log_step "Cleaning previous builds..."
|
||||
rm -rf debian/pymc-repeater/
|
||||
rm -rf debian/openhop-repeater/
|
||||
rm -rf debian/.debhelper/
|
||||
rm -rf debian/files
|
||||
rm -f debian/pymc-repeater.*.debhelper
|
||||
rm -f debian/pymc-repeater.substvars
|
||||
rm -f debian/openhop-repeater.*.debhelper
|
||||
rm -f debian/openhop-repeater.substvars
|
||||
rm -f debian/*.log
|
||||
rm -rf .pybuild/
|
||||
rm -rf build/
|
||||
@@ -146,7 +146,7 @@ else
|
||||
fi
|
||||
|
||||
# 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
|
||||
# Run lintian to check package quality
|
||||
|
||||
+27
-27
@@ -1,24 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# pyMC Repeater - Proxmox LXC Installer
|
||||
# Creates an LXC container with USB passthrough and installs pyMC Repeater
|
||||
# openHop Repeater - Proxmox LXC Installer
|
||||
# Creates an LXC container with USB passthrough and installs openHop Repeater
|
||||
#
|
||||
# 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
|
||||
# Source: https://github.com/pyMC-dev/pyMC_Repeater
|
||||
# Source: https://github.com/pyMC-dev/openhop-repeater
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Defaults ───────────────────────────────────────────────────────────────
|
||||
REPO="https://github.com/pyMC-dev/pyMC_Repeater.git"
|
||||
REPO="https://github.com/pyMC-dev/openhop-repeater.git"
|
||||
BRANCH="dev"
|
||||
CT_TEMPLATE="debian-12-standard"
|
||||
CT_RAM=1024
|
||||
CT_SWAP=512
|
||||
CT_DISK=4
|
||||
CT_CORES=2
|
||||
CT_HOSTNAME="pymc-repeater"
|
||||
CT_HOSTNAME="openhop-repeater"
|
||||
CT_BRIDGE="vmbr0"
|
||||
CT_STORAGE="local-lvm"
|
||||
CT_TEMPLATE_STORAGE="local"
|
||||
@@ -37,7 +37,7 @@ header() {
|
||||
clear
|
||||
echo -e "${BLD}"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " pyMC Repeater - Proxmox LXC Installer"
|
||||
echo " openHop Repeater - Proxmox LXC Installer"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo -e "${CL}"
|
||||
}
|
||||
@@ -154,7 +154,7 @@ msg_ok "Container created"
|
||||
msg_info "Configuring USB passthrough..."
|
||||
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.mount.entry: /dev/bus/usb dev/bus/usb none bind,optional,create=dir 0 0
|
||||
EOF
|
||||
@@ -209,41 +209,41 @@ IP=\$(hostname -I | awk '{print \$1}')
|
||||
OS=\$(. /etc/os-release && echo \"\$NAME\")
|
||||
VER=\$(. /etc/os-release && echo \"\$VERSION_ID\")
|
||||
echo \"\"
|
||||
echo \" pyMC Repeater LXC Container\"
|
||||
echo \" 🌐 GitHub: https://github.com/pyMC-dev/pyMC_Repeater\"
|
||||
echo \" openHop Repeater LXC Container\"
|
||||
echo \" 🌐 GitHub: https://github.com/pyMC-dev/openhop-repeater\"
|
||||
echo \"\"
|
||||
echo \" 🖥️ OS: \$OS - Version: \$VER\"
|
||||
echo \" 🏠 Hostname: \$HOSTNAME\"
|
||||
echo \" 💡 IP Address: \$IP\"
|
||||
echo \" 📡 Dashboard: http://\$IP:8000\"
|
||||
echo \"\"
|
||||
echo \" Management: cd /opt/pymc_repeater && bash manage.sh\"
|
||||
echo \" Management: cd /opt/openhop_repeater && bash manage.sh\"
|
||||
echo \"\"
|
||||
MOTD
|
||||
chmod +x /etc/profile.d/pymc-motd.sh
|
||||
"
|
||||
msg_ok "curl/git installed, locale fixed, console auto-login enabled"
|
||||
|
||||
msg_info "Cloning pyMC_Repeater (branch: ${BRANCH})..."
|
||||
pct exec "$CTID" -- bash -c "git clone --branch ${BRANCH} ${REPO} /root/pyMC_Repeater"
|
||||
msg_info "Cloning openhop-repeater (branch: ${BRANCH})..."
|
||||
pct exec "$CTID" -- bash -c "git clone --branch ${BRANCH} ${REPO} /root/openhop-repeater"
|
||||
msg_ok "Repository cloned"
|
||||
|
||||
# Pre-seed config with CH341 radio type and correct GPIO pins
|
||||
pct exec "$CTID" -- bash -c "
|
||||
mkdir -p /etc/pymc_repeater
|
||||
if [ -f /root/pyMC_Repeater/config.yaml.example ]; then
|
||||
cp /root/pyMC_Repeater/config.yaml.example /etc/pymc_repeater/config.yaml
|
||||
mkdir -p /etc/openhop_repeater
|
||||
if [ -f /root/openhop-repeater/config.yaml.example ]; then
|
||||
cp /root/openhop-repeater/config.yaml.example /etc/openhop_repeater/config.yaml
|
||||
# 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)
|
||||
sed -i 's/cs_pin: 21/cs_pin: 0/' /etc/pymc_repeater/config.yaml
|
||||
sed -i 's/reset_pin: 18/reset_pin: 2/' /etc/pymc_repeater/config.yaml
|
||||
sed -i 's/busy_pin: 20/busy_pin: 4/' /etc/pymc_repeater/config.yaml
|
||||
sed -i 's/irq_pin: 16/irq_pin: 6/' /etc/pymc_repeater/config.yaml
|
||||
sed -i 's/rxen_pin: -1/rxen_pin: 1/' /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/openhop_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/openhop_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
|
||||
sed -i 's/use_dio3_tcxo: false/use_dio3_tcxo: true/' /etc/pymc_repeater/config.yaml
|
||||
sed -i 's/use_dio2_rf: false/use_dio2_rf: 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/openhop_repeater/config.yaml
|
||||
fi
|
||||
"
|
||||
|
||||
@@ -251,7 +251,7 @@ pct exec "$CTID" -- bash -c "
|
||||
msg_info "Running manage.sh install (this will take several minutes)..."
|
||||
echo ""
|
||||
# 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 ""
|
||||
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 -e "${BLD}"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " ✓ pyMC Repeater Installation Complete!"
|
||||
echo " ✓ openHop Repeater Installation Complete!"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo -e "${CL}"
|
||||
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 ""
|
||||
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 "═══════════════════════════════════════════════════════════════"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/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
|
||||
|
||||
set -euo pipefail
|
||||
@@ -28,7 +28,7 @@ if [ "$EUID" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Setting up build environment for pyMC_Repeater..."
|
||||
log_info "Setting up build environment for openhop-repeater..."
|
||||
|
||||
# Update package list
|
||||
log_info "Updating package lists..."
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Radio configuration setup script for pyMC Repeater
|
||||
# Radio configuration setup script for openHop Repeater
|
||||
|
||||
CONFIG_DIR="${1:-.}"
|
||||
CONFIG_FILE="$CONFIG_DIR/config.yaml"
|
||||
@@ -15,7 +15,7 @@ else
|
||||
SED_OPTS=(-i)
|
||||
fi
|
||||
|
||||
echo "=== pyMC Repeater Radio Configuration ==="
|
||||
echo "=== openHop Repeater Radio Configuration ==="
|
||||
echo ""
|
||||
|
||||
# Step 0: Repeater Name
|
||||
@@ -48,7 +48,7 @@ echo ""
|
||||
echo "=== Step 0.5: Select Radio Type ==="
|
||||
echo ""
|
||||
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 ""
|
||||
read -p "Select radio type (1 or 2): " radio_type_sel
|
||||
|
||||
@@ -435,7 +435,7 @@ if [ "$RADIO_TYPE" = "sx1262" ] && [ -n "$bus_id" ]; then
|
||||
fi
|
||||
|
||||
# Enable and start the service
|
||||
SERVICE_NAME="pymc-repeater"
|
||||
SERVICE_NAME="openhop-repeater"
|
||||
if systemctl list-unit-files | grep -q "^$SERVICE_NAME\.service"; then
|
||||
echo ""
|
||||
echo "Enabling and starting the $SERVICE_NAME service..."
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Setup script for pymc_repeater
|
||||
Setup script for openhop_repeater
|
||||
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
|
||||
|
||||
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.frame_server import CompanionFrameServer
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from pymc_core import LocalIdentity
|
||||
from openhop_core import LocalIdentity
|
||||
|
||||
from repeater.companion.bridge import RepeaterCompanionBridge, _prefs_bytes_from_json
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from repeater.companion.utils import (
|
||||
validate_companion_config_capacity,
|
||||
)
|
||||
|
||||
# pymc_core defaults (CompanionBridge / ContactStore)
|
||||
# openhop_core defaults (CompanionBridge / ContactStore)
|
||||
_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,
|
||||
mark_seen, validate_packet, packet scoring, TX delay, cache management,
|
||||
@@ -12,8 +12,8 @@ import time
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from pymc_core.protocol import Packet, PacketBuilder
|
||||
from pymc_core.protocol.constants import (
|
||||
from openhop_core.protocol import Packet, PacketBuilder
|
||||
from openhop_core.protocol.constants import (
|
||||
MAX_PATH_SIZE,
|
||||
PH_ROUTE_MASK,
|
||||
PH_TYPE_SHIFT,
|
||||
@@ -1754,7 +1754,7 @@ class TestMissedEngineBranches:
|
||||
pkt = _make_transport_flood_packet(payload=b"\x01\x02", path=b"")
|
||||
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_2, reason_2 = handler._check_transport_codes(pkt)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
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:
|
||||
- Duplicate packet suppression via calculate_packet_hash (SHA256-based)
|
||||
- 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 pymc_core.protocol import Packet, PathUtils
|
||||
from pymc_core.protocol.constants import (
|
||||
from openhop_core.protocol import Packet, PathUtils
|
||||
from openhop_core.protocol.constants import (
|
||||
ROUTE_TYPE_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")
|
||||
|
||||
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)
|
||||
|
||||
@@ -82,7 +82,7 @@ async def test_path_helper_returns_false_for_non_matching_or_invalid_inputs():
|
||||
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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
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),
|
||||
):
|
||||
await helper._send_cli_reply(
|
||||
|
||||
@@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
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.login import LoginHelper
|
||||
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)
|
||||
|
||||
with patch(
|
||||
"pymc_core.protocol.packet_builder.PacketBuilder.create_discovery_response",
|
||||
"openhop_core.protocol.packet_builder.PacketBuilder.create_discovery_response",
|
||||
return_value=object(),
|
||||
):
|
||||
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)
|
||||
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()
|
||||
|
||||
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._register_identity_everywhere = MagicMock(return_value=True)
|
||||
|
||||
with patch("pymc_core.LocalIdentity", _FakeLocalIdentity):
|
||||
with patch("openhop_core.LocalIdentity", _FakeLocalIdentity):
|
||||
await daemon._load_additional_identities()
|
||||
|
||||
# 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:
|
||||
"""Minimal pyMC_Repeater config sufficient to construct MeshCoreToMqttPusher."""
|
||||
"""Minimal openhop-repeater config sufficient to construct MeshCoreToMqttPusher."""
|
||||
return {
|
||||
"repeater": {"node_name": "test-node"},
|
||||
"radio": {
|
||||
|
||||
+12
-12
@@ -19,18 +19,18 @@ import time
|
||||
import unittest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from pymc_core.node.handlers.ack import AckHandler
|
||||
from pymc_core.node.handlers.advert import AdvertHandler
|
||||
from pymc_core.node.handlers.control import ControlHandler
|
||||
from pymc_core.node.handlers.group_text import GroupTextHandler
|
||||
from pymc_core.node.handlers.login_response import LoginResponseHandler
|
||||
from pymc_core.node.handlers.login_server import LoginServerHandler
|
||||
from pymc_core.node.handlers.path import PathHandler
|
||||
from pymc_core.node.handlers.protocol_request import ProtocolRequestHandler
|
||||
from pymc_core.node.handlers.protocol_response import ProtocolResponseHandler
|
||||
from pymc_core.node.handlers.text import TextMessageHandler
|
||||
from pymc_core.node.handlers.trace import TraceHandler
|
||||
from pymc_core.protocol.constants import ROUTE_TYPE_DIRECT
|
||||
from openhop_core.node.handlers.ack import AckHandler
|
||||
from openhop_core.node.handlers.advert import AdvertHandler
|
||||
from openhop_core.node.handlers.control import ControlHandler
|
||||
from openhop_core.node.handlers.group_text import GroupTextHandler
|
||||
from openhop_core.node.handlers.login_response import LoginResponseHandler
|
||||
from openhop_core.node.handlers.login_server import LoginServerHandler
|
||||
from openhop_core.node.handlers.path import PathHandler
|
||||
from openhop_core.node.handlers.protocol_request import ProtocolRequestHandler
|
||||
from openhop_core.node.handlers.protocol_response import ProtocolResponseHandler
|
||||
from openhop_core.node.handlers.text import TextMessageHandler
|
||||
from openhop_core.node.handlers.trace import TraceHandler
|
||||
from openhop_core.protocol.constants import ROUTE_TYPE_DIRECT
|
||||
|
||||
from repeater.packet_router import (
|
||||
PacketRouter,
|
||||
|
||||
@@ -8,9 +8,9 @@ returns 2 or 3. The dispatcher applies this in send_packet() before transmit.
|
||||
|
||||
import pytest
|
||||
|
||||
from pymc_core.node.dispatcher import Dispatcher
|
||||
from pymc_core.protocol import Packet
|
||||
from pymc_core.protocol.constants import (
|
||||
from openhop_core.node.dispatcher import Dispatcher
|
||||
from openhop_core.protocol import Packet
|
||||
from openhop_core.protocol.constants import (
|
||||
PAYLOAD_TYPE_ADVERT,
|
||||
PH_TYPE_SHIFT,
|
||||
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
|
||||
rather than mocking the protocol layer. Covers:
|
||||
@@ -16,9 +16,9 @@ import struct
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from pymc_core.node.handlers.trace import TraceHandler
|
||||
from pymc_core.protocol import Packet, PacketBuilder, PathUtils
|
||||
from pymc_core.protocol.constants import (
|
||||
from openhop_core.node.handlers.trace import TraceHandler
|
||||
from openhop_core.protocol import Packet, PacketBuilder, PathUtils
|
||||
from openhop_core.protocol.constants import (
|
||||
MAX_PATH_SIZE,
|
||||
PATH_HASH_COUNT_MASK,
|
||||
PATH_HASH_SIZE_SHIFT,
|
||||
@@ -730,7 +730,7 @@ class TestTraceHelperMultibyte:
|
||||
"""TraceHelper._should_forward_trace with 2-byte TRACE payload hashes."""
|
||||
|
||||
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
|
||||
|
||||
@@ -752,7 +752,7 @@ class TestTraceHelperMultibyte:
|
||||
assert th._should_forward_trace(pkt, trace_bytes, flags, hash_width)
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import yaml
|
||||
from pymc_core.protocol.constants import PAYLOAD_TYPE_GRP_TXT
|
||||
from pymc_core.protocol.identity import LocalIdentity
|
||||
from pymc_core.protocol.packet_builder import PacketBuilder
|
||||
from openhop_core.protocol.constants import PAYLOAD_TYPE_GRP_TXT
|
||||
from openhop_core.protocol.identity import LocalIdentity
|
||||
from openhop_core.protocol.packet_builder import PacketBuilder
|
||||
|
||||
from repeater.policy_engine import PolicyEngine
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""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
|
||||
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()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"pymc_core.hardware.sx1262_wrapper.SX1262Radio",
|
||||
"openhop_core.hardware.sx1262_wrapper.SX1262Radio",
|
||||
_DummySX1262Radio,
|
||||
)
|
||||
|
||||
@@ -81,7 +81,7 @@ def _pymc_radio_cfg():
|
||||
|
||||
|
||||
def test_get_radio_for_board_pymc_tcp(monkeypatch):
|
||||
pytest.importorskip("pymc_core.hardware.tcp_radio")
|
||||
pytest.importorskip("openhop_core.hardware.tcp_radio")
|
||||
captured = {}
|
||||
|
||||
class _DummyTCPLoRaRadio(_DummyRadio):
|
||||
@@ -89,7 +89,7 @@ def test_get_radio_for_board_pymc_tcp(monkeypatch):
|
||||
captured.update(kwargs)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"pymc_core.hardware.tcp_radio.TCPLoRaRadio",
|
||||
"openhop_core.hardware.tcp_radio.TCPLoRaRadio",
|
||||
_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):
|
||||
pytest.importorskip("pymc_core.hardware.tcp_radio")
|
||||
pytest.importorskip("openhop_core.hardware.tcp_radio")
|
||||
|
||||
monkeypatch.setattr(
|
||||
"pymc_core.hardware.tcp_radio.TCPLoRaRadio",
|
||||
"openhop_core.hardware.tcp_radio.TCPLoRaRadio",
|
||||
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):
|
||||
pytest.importorskip("pymc_core.hardware.usb_radio")
|
||||
pytest.importorskip("openhop_core.hardware.usb_radio")
|
||||
captured = {}
|
||||
|
||||
class _DummyUSBLoRaRadio(_DummyRadio):
|
||||
@@ -145,7 +145,7 @@ def test_get_radio_for_board_pymc_usb(monkeypatch):
|
||||
captured.update(kwargs)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"pymc_core.hardware.usb_radio.USBLoRaRadio",
|
||||
"openhop_core.hardware.usb_radio.USBLoRaRadio",
|
||||
_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):
|
||||
pytest.importorskip("pymc_core.hardware.usb_radio")
|
||||
pytest.importorskip("openhop_core.hardware.usb_radio")
|
||||
|
||||
monkeypatch.setattr(
|
||||
"pymc_core.hardware.usb_radio.USBLoRaRadio",
|
||||
"openhop_core.hardware.usb_radio.USBLoRaRadio",
|
||||
lambda **kwargs: _DummyRadio(),
|
||||
)
|
||||
|
||||
@@ -195,7 +195,7 @@ def test_get_radio_for_board_pymc_usb_requires_port(monkeypatch):
|
||||
|
||||
def _kiss_capture_radio_config(monkeypatch):
|
||||
"""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 = {}
|
||||
|
||||
class _DummyKissWrapper(_DummyRadio):
|
||||
@@ -203,7 +203,7 @@ def _kiss_capture_radio_config(monkeypatch):
|
||||
captured["kwargs"] = kwargs
|
||||
|
||||
monkeypatch.setattr(
|
||||
"pymc_core.hardware.kiss_modem_wrapper.KissModemWrapper",
|
||||
"openhop_core.hardware.kiss_modem_wrapper.KissModemWrapper",
|
||||
_DummyKissWrapper,
|
||||
)
|
||||
return captured
|
||||
|
||||
@@ -68,7 +68,7 @@ def test_generate_transport_key_uses_implicit_hashtag_region(tmp_path, monkeypat
|
||||
h = _make_handler(tmp_path)
|
||||
|
||||
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:
|
||||
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_protocol = types.ModuleType("pymc_core.protocol")
|
||||
fake_protocol = types.ModuleType("openhop_core.protocol")
|
||||
fake_protocol.transport_keys = fake_transport_keys
|
||||
|
||||
fake_core = types.ModuleType("pymc_core")
|
||||
fake_core = types.ModuleType("openhop_core")
|
||||
fake_core.protocol = fake_protocol
|
||||
|
||||
monkeypatch.setitem(sys.modules, "pymc_core", fake_core)
|
||||
monkeypatch.setitem(sys.modules, "pymc_core.protocol", fake_protocol)
|
||||
monkeypatch.setitem(sys.modules, "pymc_core.protocol.transport_keys", fake_transport_keys)
|
||||
monkeypatch.setitem(sys.modules, "openhop_core", fake_core)
|
||||
monkeypatch.setitem(sys.modules, "openhop_core.protocol", fake_protocol)
|
||||
monkeypatch.setitem(sys.modules, "openhop_core.protocol.transport_keys", fake_transport_keys)
|
||||
|
||||
generated = h.generate_transport_key("eu")
|
||||
generated_bytes = base64.b64decode(generated)
|
||||
|
||||
@@ -29,7 +29,7 @@ def _make_collector() -> StorageCollector:
|
||||
patch("repeater.data_acquisition.storage_collector.RRDToolHandler"),
|
||||
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
|
||||
# drive the loop deterministically.
|
||||
|
||||
@@ -33,7 +33,7 @@ def _fake_thread(*args, **kwargs):
|
||||
|
||||
def test_jwt_warning_fix_guard():
|
||||
# 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():
|
||||
|
||||
Reference in New Issue
Block a user