17 Commits

Author SHA1 Message Date
Yellowcooln 6de2b23266 Merge pull request #319 from yellowcooln/fix/ha-addon-dispatch-repo
fix: update HA add-on dispatch target
2026-06-26 09:39:08 -04:00
yellowcooln 864030283c fix: update HA add-on dispatch target 2026-06-26 09:35:19 -04:00
Yellowcooln 751c9e3807 Merge pull request #318 from yellowcooln/fix/openhop-docker-vars
Update Docker image branding
2026-06-26 09:23:21 -04:00
yellowcooln fbbc7e6844 fix: handle advert timeout cancellation 2026-06-26 09:18:41 -04:00
yellowcooln 4d095dad42 fix: update Docker image branding 2026-06-26 09:10:58 -04:00
Lloyd fcaad1dacf feat: add ensure_service_user_home function to manage service user home directory 2026-06-26 13:49:54 +01:00
Yellowcooln e2ae7e18a6 Merge pull request #317 from openhop-dev/revert-316-fix/updater-venv-pip-shim
Revert "fix: repair updater venv pip shim"
2026-06-25 22:13:57 -04:00
Yellowcooln 11f8ea2f43 Revert "fix: repair updater venv pip shim" 2026-06-25 22:13:42 -04:00
Yellowcooln d24a346cf7 Merge pull request #316 from yellowcooln/fix/updater-venv-pip-shim
fix: repair updater venv pip shim
2026-06-25 22:13:14 -04:00
yellowcooln af6bea5bbe fix: repair updater venv pip shim 2026-06-25 22:04:25 -04:00
Yellowcooln 9846666076 Merge pull request #315 from yellowcooln/fix/openhop-updater-repo
fix: update updater GitHub repository
2026-06-25 21:46:58 -04:00
yellowcooln 42340f683f fix: update updater GitHub repository 2026-06-25 21:32:52 -04:00
Yellowcooln 27e68167d5 Merge pull request #314 from openhop-dev/yellowcooln-patch-1
Update default Docker image repository
2026-06-25 20:07:28 -04:00
Yellowcooln f0408d263d Update default Docker image repository 2026-06-25 19:54:18 -04:00
Lloyd 135d6b1d8d Merge pull request #313 from openhop-dev/fix-venv-in-manage
Fix venv in manage
2026-06-25 23:36:48 +01:00
Lloyd 9748058010 fix: update virtual environment pip usage in manage.sh 2026-06-25 23:32:19 +01:00
Lloyd cc0dc2ffd6 fix: virtual environment creation logic in manage.sh 2026-06-25 23:13:28 +01:00
15 changed files with 128 additions and 65 deletions
+4 -4
View File
@@ -3,7 +3,7 @@
# Published image to run. Use a different repository or tag if you are testing # Published image to run. Use a different repository or tag if you are testing
# a fork or a specific release. # a fork or a specific release.
PYMC_REPEATER_IMAGE=pymcdev/openhop-repeater:main OPENHOP_REPEATER_IMAGE=openhop/openhop-repeater:main
# Storage defaults to Docker named volumes. This is the safest option for # Storage defaults to Docker named volumes. This is the safest option for
# Portainer and fresh installs because Docker preserves the image ownership. # Portainer and fresh installs because Docker preserves the image ownership.
@@ -12,8 +12,8 @@ PYMC_REPEATER_IMAGE=pymcdev/openhop-repeater:main
# sudo mkdir -p /opt/openhop-repeater/config /opt/openhop-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 # sudo chown -R 15888:15888 /opt/openhop-repeater/config /opt/openhop-repeater/data
# Then uncomment and adjust these paths: # Then uncomment and adjust these paths:
# PYMC_CONFIG_VOLUME=/opt/openhop-repeater/config # OPENHOP_CONFIG_VOLUME=/opt/openhop-repeater/config
# PYMC_DATA_VOLUME=/opt/openhop-repeater/data # OPENHOP_DATA_VOLUME=/opt/openhop-repeater/data
# Serial/SPI/GPIO access uses the host's numeric group IDs. Check your host with: # Serial/SPI/GPIO access uses the host's numeric group IDs. Check your host with:
# getent group dialout # getent group dialout
@@ -29,6 +29,6 @@ GPIO_GID=986
SPI_GID=989 SPI_GID=989
# Local build only. These are used by docker-compose.build.yml if you build the # Local build only. These are used by docker-compose.build.yml if you build the
# image yourself instead of pulling PYMC_REPEATER_IMAGE. # image yourself instead of pulling OPENHOP_REPEATER_IMAGE.
PUID=15888 PUID=15888
PGID=15888 PGID=15888
+3 -3
View File
@@ -10,7 +10,7 @@ on:
image_repository: image_repository:
description: "Docker image repository to publish to" description: "Docker image repository to publish to"
required: false required: false
default: "pymcdev/openhop-repeater" default: "openhop/openhop-repeater"
jobs: jobs:
docker: docker:
@@ -61,7 +61,7 @@ jobs:
elif [ "${{ github.repository }}" = "yellowcooln/openhop_repeater" ]; then elif [ "${{ github.repository }}" = "yellowcooln/openhop_repeater" ]; then
image_repository="yellowcooln/openhop-repeater" image_repository="yellowcooln/openhop-repeater"
else else
image_repository="pymcdev/openhop-repeater" image_repository="openhop/openhop-repeater"
fi fi
echo "Using image repository: ${image_repository}" echo "Using image repository: ${image_repository}"
@@ -107,5 +107,5 @@ jobs:
-X POST \ -X POST \
-H "Accept: application/vnd.github+json" \ -H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${DISPATCH_TOKEN}" \ -H "Authorization: Bearer ${DISPATCH_TOKEN}" \
https://api.github.com/repos/pyMC-dev/pyMC-HA-Add-on/dispatches \ https://api.github.com/repos/openhop-dev/openHop-HA-Add-on/dispatches \
-d "{\"event_type\":\"sync-upstream-channel\",\"client_payload\":{\"channel\":\"${CHANNEL}\",\"revision\":\"${REVISION}\"}}" -d "{\"event_type\":\"sync-upstream-channel\",\"client_payload\":{\"channel\":\"${CHANNEL}\",\"revision\":\"${REVISION}\"}}"
+11 -9
View File
@@ -2,6 +2,8 @@
Lightweight Python MeshCore repeater daemon built on `openhop_core`. Lightweight Python MeshCore repeater daemon built on `openhop_core`.
Formerly `pyMC_Repeater` built on `pyMC_core`.
openHop Repeater is designed to run continuously on low-power Linux hardware such openHop Repeater is designed to run continuously on low-power Linux hardware such
as Raspberry Pi-class devices, Proxmox LXC containers, and network-attached as Raspberry Pi-class devices, Proxmox LXC containers, and network-attached
radio modems. It forwards LoRa packets, exposes a web dashboard, and provides radio modems. It forwards LoRa packets, exposes a web dashboard, and provides
@@ -111,8 +113,8 @@ sudo apt install git -y
### Clone The Repository ### Clone The Repository
```bash ```bash
git clone https://github.com/openhop-dev/openhop-repeater.git git clone https://github.com/openhop-dev/openhop_repeater.git
cd openhop-repeater cd openhop_repeater
``` ```
### Quick Install ### Quick Install
@@ -258,7 +260,7 @@ The web interface can upgrade an installation or switch branches.
### CLI ### CLI
```bash ```bash
cd openhop-repeater cd openhop_repeater
sudo bash ./manage.sh upgrade sudo bash ./manage.sh upgrade
``` ```
@@ -295,7 +297,7 @@ Hardware, choose one:
Run this command on the Proxmox host, not inside a container: Run this command on the Proxmox host, not inside a container:
```bash ```bash
bash -c "$(curl -fsSL https://raw.githubusercontent.com/pyMC-dev/openhop-repeater/main/scripts/proxmox-install.sh)" bash -c "$(curl -fsSL https://raw.githubusercontent.com/openhop-dev/openhop_repeater/main/scripts/proxmox-install.sh)"
``` ```
Replace `main` in the URL with another branch name if needed. Replace `main` in the URL with another branch name if needed.
@@ -416,7 +418,7 @@ that source as a directory, which breaks startup.
### Setup ### Setup
1. Copy `.env.example` to `.env`. 1. Copy `.env.example` to `.env`.
2. Review `.env` and update `PYMC_REPEATER_IMAGE`, `DIALOUT_GID`, 2. Review `.env` and update `OPENHOP_REPEATER_IMAGE`, `DIALOUT_GID`,
`GPIO_GID`, or `SPI_GID` if needed. `GPIO_GID`, or `SPI_GID` if needed.
3. Configure `docker-compose.yml` for your hardware and device paths. 3. Configure `docker-compose.yml` for your hardware and device paths.
4. Uncomment the USB device mapping only if your host has that device path. 4. Uncomment the USB device mapping only if your host has that device path.
@@ -431,7 +433,7 @@ docker compose up -d
```yaml ```yaml
services: services:
openhop-repeater: openhop-repeater:
image: ${PYMC_REPEATER_IMAGE:-pymcdev/openhop-repeater:main} image: ${OPENHOP_REPEATER_IMAGE:-${PYMC_REPEATER_IMAGE:-openhop/openhop-repeater:main}}
container_name: openhop-repeater container_name: openhop-repeater
restart: unless-stopped restart: unless-stopped
ports: ports:
@@ -455,8 +457,8 @@ services:
- plugdev - plugdev
volumes: volumes:
- ${PYMC_CONFIG_VOLUME:-openhop-repeater-config}:/etc/openhop_repeater - ${OPENHOP_CONFIG_VOLUME:-${PYMC_CONFIG_VOLUME:-openhop-repeater-config}}:/etc/openhop_repeater
- ${PYMC_DATA_VOLUME:-openhop-repeater-data}:/var/lib/openhop_repeater - ${OPENHOP_DATA_VOLUME:-${PYMC_DATA_VOLUME:-openhop-repeater-data}}:/var/lib/openhop_repeater
volumes: volumes:
openhop-repeater-config: openhop-repeater-config:
@@ -518,7 +520,7 @@ Pre-commit hooks will automatically:
## Support ## Support
- [pyMC Core](https://github.com/openhop-dev/openhop-core) - [pyMC Core](https://github.com/openhop-dev/openhop_core)
- [MeshCore Discord](https://meshcore.gg) - [MeshCore Discord](https://meshcore.gg)
## Disclaimer ## Disclaimer
+3 -3
View File
@@ -22,7 +22,7 @@ R2_BASE_URL="https://wheel.pymc.dev/pymc_build_deps"
PIWHEELS_INDEX_URL="https://www.piwheels.org/simple" PIWHEELS_INDEX_URL="https://www.piwheels.org/simple"
R2_ENABLED=1 R2_ENABLED=1
YQ_VERSION="${YQ_VERSION:-v4.44.3}" YQ_VERSION="${YQ_VERSION:-v4.44.3}"
PYMC_CORE_REPO="${PYMC_CORE_REPO:-https://github.com/rightup/openhop-core.git}" PYMC_CORE_REPO="${PYMC_CORE_REPO:-https://github.com/openhop-dev/openhop_core.git}"
PYMC_CORE_REF="${PYMC_CORE_REF:-}" PYMC_CORE_REF="${PYMC_CORE_REF:-}"
PYMC_CORE_LOCAL_DIR="${PYMC_CORE_LOCAL_DIR:-}" PYMC_CORE_LOCAL_DIR="${PYMC_CORE_LOCAL_DIR:-}"
PYMC_SKIP_BUILDROOT_DEP_INSTALL="${PYMC_SKIP_BUILDROOT_DEP_INSTALL:-0}" PYMC_SKIP_BUILDROOT_DEP_INSTALL="${PYMC_SKIP_BUILDROOT_DEP_INSTALL:-0}"
@@ -1247,12 +1247,12 @@ prepare_git_version() {
git -C "$SCRIPT_DIR" fetch --tags 2>/dev/null || true git -C "$SCRIPT_DIR" fetch --tags 2>/dev/null || true
git_version=$(python3 -m setuptools_scm 2>/dev/null || echo "1.0.5") git_version=$(python3 -m setuptools_scm 2>/dev/null || echo "1.0.5")
export SETUPTOOLS_SCM_PRETEND_VERSION="$git_version" export SETUPTOOLS_SCM_PRETEND_VERSION="$git_version"
export SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYMC_REPEATER="$git_version" export SETUPTOOLS_SCM_PRETEND_VERSION_FOR_OPENHOP_REPEATER="$git_version"
info "Using version: $git_version" info "Using version: $git_version"
else else
git_version="1.0.5" git_version="1.0.5"
export SETUPTOOLS_SCM_PRETEND_VERSION="$git_version" export SETUPTOOLS_SCM_PRETEND_VERSION="$git_version"
export SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYMC_REPEATER="$git_version" export SETUPTOOLS_SCM_PRETEND_VERSION_FOR_OPENHOP_REPEATER="$git_version"
info "Using fallback version: $git_version" info "Using fallback version: $git_version"
fi fi
+1 -1
View File
@@ -15,7 +15,7 @@ Build-Depends: debhelper-compat (= 13),
python3-psutil, python3-psutil,
git git
Standards-Version: 4.6.2 Standards-Version: 4.6.2
Homepage: https://github.com/rightup/openhop-repeater Homepage: https://github.com/openhop-dev/openhop_repeater
X-Python3-Version: >= 3.9 X-Python3-Version: >= 3.9
Package: openhop-repeater Package: openhop-repeater
+3 -3
View File
@@ -1,6 +1,6 @@
services: services:
openhop-repeater: openhop-repeater:
image: ${PYMC_REPEATER_IMAGE:-pymcdev/openhop-repeater:main} image: ${OPENHOP_REPEATER_IMAGE:-${PYMC_REPEATER_IMAGE:-openhop/openhop-repeater:main}}
container_name: openhop-repeater container_name: openhop-repeater
restart: unless-stopped restart: unless-stopped
ports: ports:
@@ -21,8 +21,8 @@ services:
- "${SPI_GID:-989}" - "${SPI_GID:-989}"
- plugdev - plugdev
volumes: volumes:
- ${PYMC_CONFIG_VOLUME:-openhop-repeater-config}:/etc/openhop_repeater - ${OPENHOP_CONFIG_VOLUME:-${PYMC_CONFIG_VOLUME:-openhop-repeater-config}}:/etc/openhop_repeater
- ${PYMC_DATA_VOLUME:-openhop-repeater-data}:/var/lib/openhop_repeater - ${OPENHOP_DATA_VOLUME:-${PYMC_DATA_VOLUME:-openhop-repeater-data}}:/var/lib/openhop_repeater
volumes: volumes:
openhop-repeater-config: openhop-repeater-config:
+1 -1
View File
@@ -3,7 +3,7 @@ set -eu
INSTALL_DIR="${INSTALL_DIR:-/opt/openhop_repeater}" INSTALL_DIR="${INSTALL_DIR:-/opt/openhop_repeater}"
CONFIG_DIR="${CONFIG_DIR:-/etc/openhop_repeater}" CONFIG_DIR="${CONFIG_DIR:-/etc/openhop_repeater}"
CONFIG_PATH="${PYMC_REPEATER_CONFIG:-${CONFIG_DIR}/config.yaml}" CONFIG_PATH="${OPENHOP_REPEATER_CONFIG:-${PYMC_REPEATER_CONFIG:-${CONFIG_DIR}/config.yaml}}"
EXAMPLE_PATH="${CONFIG_DIR}/config.yaml.example" EXAMPLE_PATH="${CONFIG_DIR}/config.yaml.example"
BUNDLED_EXAMPLE_PATH="${INSTALL_DIR}/config.yaml.example" BUNDLED_EXAMPLE_PATH="${INSTALL_DIR}/config.yaml.example"
RUNTIME_USER="${USER:-repeater}" RUNTIME_USER="${USER:-repeater}"
+1 -1
View File
@@ -17,7 +17,7 @@ ENV INSTALL_DIR=/opt/openhop_repeater \
HOME_DIR=/home/${USER} \ HOME_DIR=/home/${USER} \
PATH=/home/${USER}/.local/bin:${PATH} \ PATH=/home/${USER}/.local/bin:${PATH} \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYMC_REPEATER=${PACKAGE_VERSION} \ SETUPTOOLS_SCM_PRETEND_VERSION_FOR_OPENHOP_REPEATER=${PACKAGE_VERSION} \
PUID=${PUID} \ PUID=${PUID} \
PGID=${PGID} \ PGID=${PGID} \
DIALOUT_GID=${DIALOUT_GID} \ DIALOUT_GID=${DIALOUT_GID} \
+4 -4
View File
@@ -3,7 +3,7 @@
# Published image to run. Use a different repository or tag if you are testing # Published image to run. Use a different repository or tag if you are testing
# a fork or a specific release. # a fork or a specific release.
PYMC_REPEATER_IMAGE=pymcdev/openhop-repeater:main OPENHOP_REPEATER_IMAGE=openhop/openhop-repeater:main
# Storage defaults to Docker named volumes. This is the safest option for # Storage defaults to Docker named volumes. This is the safest option for
# Portainer and fresh installs because Docker preserves the image ownership. # Portainer and fresh installs because Docker preserves the image ownership.
@@ -12,8 +12,8 @@ PYMC_REPEATER_IMAGE=pymcdev/openhop-repeater:main
# sudo mkdir -p /opt/openhop-repeater/config /opt/openhop-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 # sudo chown -R 15888:15888 /opt/openhop-repeater/config /opt/openhop-repeater/data
# Then uncomment and adjust these paths: # Then uncomment and adjust these paths:
# PYMC_CONFIG_VOLUME=/opt/openhop-repeater/config # OPENHOP_CONFIG_VOLUME=/opt/openhop-repeater/config
# PYMC_DATA_VOLUME=/opt/openhop-repeater/data # OPENHOP_DATA_VOLUME=/opt/openhop-repeater/data
# SPI/GPIO access uses the host's numeric group IDs. Check your host with: # SPI/GPIO access uses the host's numeric group IDs. Check your host with:
# getent group gpio # getent group gpio
@@ -26,6 +26,6 @@ GPIO_GID=986
SPI_GID=989 SPI_GID=989
# Local build only. These are used by docker-compose.build.yml if you build the # Local build only. These are used by docker-compose.build.yml if you build the
# image yourself instead of pulling PYMC_REPEATER_IMAGE. # image yourself instead of pulling OPENHOP_REPEATER_IMAGE.
PUID=15888 PUID=15888
PGID=15888 PGID=15888
+58 -18
View File
@@ -88,14 +88,51 @@ migrate_legacy_paths() {
migrate_one_path "$LEGACY_PYMC_INSTALL_DIR" "$INSTALL_DIR" "install" migrate_one_path "$LEGACY_PYMC_INSTALL_DIR" "$INSTALL_DIR" "install"
} }
ensure_service_user_home() {
local current_home
if ! id "$SERVICE_USER" &>/dev/null; then
return 0
fi
current_home="$(getent passwd "$SERVICE_USER" | cut -d: -f6)"
if [ -z "$current_home" ]; then
return 0
fi
if [ "$current_home" != "$DATA_DIR" ]; then
mkdir -p "$DATA_DIR" 2>/dev/null || true
usermod -d "$DATA_DIR" "$SERVICE_USER" 2>/dev/null || true
echo " ✓ Updated $SERVICE_USER home directory: $current_home -> $DATA_DIR"
fi
}
# Create (or re-create) the dedicated venv for openhop_repeater # Create (or re-create) the dedicated venv for openhop_repeater
ensure_venv() { ensure_venv() {
local recreate=0
if [ ! -x "$VENV_PYTHON" ]; then if [ ! -x "$VENV_PYTHON" ]; then
echo ">>> Creating virtual environment at $VENV_DIR ..." recreate=1
python3 -m venv --system-site-packages "$VENV_DIR" elif ! "$VENV_PYTHON" -c 'import sys; print(sys.executable)' >/dev/null 2>&1; then
# Upgrade pip inside the venv # Venv python exists but points to a missing interpreter (stale venv).
"$VENV_PIP" install --upgrade pip setuptools wheel >/dev/null 2>&1 || true recreate=1
elif ! "$VENV_PYTHON" -m pip --version >/dev/null 2>&1; then
# Pip script/shebang can break after Python upgrades; treat as stale.
recreate=1
fi fi
if [ "$recreate" -eq 1 ]; then
if [ -d "$VENV_DIR" ]; then
echo ">>> Rebuilding broken virtual environment at $VENV_DIR ..."
rm -rf "$VENV_DIR"
else
echo ">>> Creating virtual environment at $VENV_DIR ..."
fi
python3 -m venv --system-site-packages "$VENV_DIR"
fi
# Always use python -m pip so we don't rely on a potentially stale pip wrapper.
"$VENV_PYTHON" -m pip install --upgrade pip setuptools wheel >/dev/null 2>&1 || true
} }
# Migrate an existing system-pip install into the venv. # Migrate an existing system-pip install into the venv.
@@ -105,6 +142,7 @@ migrate_to_venv() {
# 1. Ensure the venv exists # 1. Ensure the venv exists
ensure_venv ensure_venv
ensure_service_user_home
# 2. Remove legacy PYTHONPATH from the service unit # 2. Remove legacy PYTHONPATH from the service unit
local svc_unit="/etc/systemd/system/openhop-repeater.service" local svc_unit="/etc/systemd/system/openhop-repeater.service"
@@ -424,6 +462,7 @@ install_repeater() {
if ! id "$SERVICE_USER" &>/dev/null; then if ! id "$SERVICE_USER" &>/dev/null; then
useradd --system --home "$DATA_DIR" --shell /sbin/nologin "$SERVICE_USER" useradd --system --home "$DATA_DIR" --shell /sbin/nologin "$SERVICE_USER"
fi fi
ensure_service_user_home
disable_legacy_services disable_legacy_services
@@ -567,7 +606,7 @@ fi
if [ ! -x "$VENV_PYTHON" ]; then if [ ! -x "$VENV_PYTHON" ]; then
echo "[pymc-do-upgrade] Creating venv at $VENV_DIR ..." echo "[pymc-do-upgrade] Creating venv at $VENV_DIR ..."
python3 -m venv --system-site-packages "$VENV_DIR" python3 -m venv --system-site-packages "$VENV_DIR"
"$VENV_PIP" install --upgrade pip setuptools wheel >/dev/null 2>&1 || true "$VENV_PYTHON" -m pip install --upgrade pip setuptools wheel >/dev/null 2>&1 || true
fi fi
# ---- Migration: clean up legacy service unit issues ---- # ---- Migration: clean up legacy service unit issues ----
SVC_UNIT=/etc/systemd/system/openhop-repeater.service SVC_UNIT=/etc/systemd/system/openhop-repeater.service
@@ -615,15 +654,15 @@ if [ -n "$ARCH_TAG" ]; then
PY_TAG=$("$VENV_PYTHON" -c 'import sys; v=f"cp{sys.version_info.major}{sys.version_info.minor}"; print(f"{v}-{v}")' 2>/dev/null || echo "cp311-cp311") PY_TAG=$("$VENV_PYTHON" -c 'import sys; v=f"cp{sys.version_info.major}{sys.version_info.minor}"; print(f"{v}-{v}")' 2>/dev/null || echo "cp311-cp311")
WHEEL_BASE="${R2_BASE_URL}/${ARCH_TAG}/${PLATFORM_TAG}/${PY_TAG}" WHEEL_BASE="${R2_BASE_URL}/${ARCH_TAG}/${PLATFORM_TAG}/${PY_TAG}"
echo "[pymc-do-upgrade] Trying dependencies from R2 wheels..." echo "[pymc-do-upgrade] Trying dependencies from R2 wheels..."
"$VENV_PIP" install --find-links "${WHEEL_BASE}/index.html" --no-cache-dir "pycryptodome>=3.23.0" "PyNaCl>=1.5.0" cffi "pyyaml>=6.0.0" 2>/dev/null || true "$VENV_PYTHON" -m pip install --find-links "${WHEEL_BASE}/index.html" --no-cache-dir "pycryptodome>=3.23.0" "PyNaCl>=1.5.0" cffi "pyyaml>=6.0.0" 2>/dev/null || true
fi fi
# ---- Install openhop_repeater from git ---- # ---- Install openhop_repeater from git ----
if "$VENV_PIP" install \ if "$VENV_PYTHON" -m pip install \
--upgrade \ --upgrade \
--no-cache-dir \ --no-cache-dir \
"openhop_repeater[hardware] @ git+https://github.com/rightup/openhop-repeater.git@${CHANNEL}"; then "openhop_repeater[hardware] @ git+https://github.com/openhop-dev/openhop_repeater.git@${CHANNEL}"; then
# Keep web/OTA updates aligned with manage.sh install/upgrade defaults. # Keep web/OTA updates aligned with manage.sh install/upgrade defaults.
RADIO_BASE_URL="https://raw.githubusercontent.com/rightup/openhop-repeater/${CHANNEL}" RADIO_BASE_URL="https://raw.githubusercontent.com/openhop-dev/openhop_repeater/${CHANNEL}"
RADIO_STORAGE_DIR="/var/lib/openhop_repeater" RADIO_STORAGE_DIR="/var/lib/openhop_repeater"
mkdir -p "$RADIO_STORAGE_DIR" mkdir -p "$RADIO_STORAGE_DIR"
wget -qO "$RADIO_STORAGE_DIR/radio-settings.json" "${RADIO_BASE_URL}/radio-settings.json" 2>/dev/null || true wget -qO "$RADIO_STORAGE_DIR/radio-settings.json" "${RADIO_BASE_URL}/radio-settings.json" 2>/dev/null || true
@@ -687,7 +726,7 @@ UPGRADEEOF
WHEEL_BASE="${R2_BASE_URL}/${ARCH_TAG}/${PLATFORM_TAG}/${PY_TAG}" WHEEL_BASE="${R2_BASE_URL}/${ARCH_TAG}/${PLATFORM_TAG}/${PY_TAG}"
echo " Checking for R2 wheels (${ARCH_TAG}/${PLATFORM_TAG}/${PY_TAG})..." echo " Checking for R2 wheels (${ARCH_TAG}/${PLATFORM_TAG}/${PY_TAG})..."
echo " Trying install from R2 pre-built wheels..." echo " Trying install from R2 pre-built 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 && R2_SUCCESS=1 || R2_SUCCESS=0 "$VENV_PYTHON" -m 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 && R2_SUCCESS=1 || R2_SUCCESS=0
if [ "$R2_SUCCESS" -eq 1 ]; then if [ "$R2_SUCCESS" -eq 1 ]; then
echo " ✓ R2 wheels installed" echo " ✓ R2 wheels installed"
else else
@@ -696,7 +735,7 @@ UPGRADEEOF
fi fi
fi fi
if "$VENV_PIP" install --upgrade --no-cache-dir .[hardware]; then if "$VENV_PYTHON" -m pip install --upgrade --no-cache-dir .[hardware]; then
echo "" echo ""
echo "✓ Python package installation completed successfully!" echo "✓ Python package installation completed successfully!"
@@ -868,6 +907,7 @@ upgrade_repeater() {
echo "[1.6/9] Ensuring required directories..." echo "[1.6/9] Ensuring required directories..."
mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" "$DATA_DIR" mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" "$DATA_DIR"
ensure_service_user_home
echo "[2/9] Backing up configuration..." echo "[2/9] Backing up configuration..."
if [ -d "$CONFIG_DIR" ]; then if [ -d "$CONFIG_DIR" ]; then
@@ -1002,7 +1042,7 @@ fi
if [ ! -x "$VENV_PYTHON" ]; then if [ ! -x "$VENV_PYTHON" ]; then
echo "[pymc-do-upgrade] Creating venv at $VENV_DIR ..." echo "[pymc-do-upgrade] Creating venv at $VENV_DIR ..."
python3 -m venv --system-site-packages "$VENV_DIR" python3 -m venv --system-site-packages "$VENV_DIR"
"$VENV_PIP" install --upgrade pip setuptools wheel >/dev/null 2>&1 || true "$VENV_PYTHON" -m pip install --upgrade pip setuptools wheel >/dev/null 2>&1 || true
fi fi
# ---- Migration: clean up legacy service unit issues ---- # ---- Migration: clean up legacy service unit issues ----
SVC_UNIT=/etc/systemd/system/openhop-repeater.service SVC_UNIT=/etc/systemd/system/openhop-repeater.service
@@ -1050,15 +1090,15 @@ python3 -m pip uninstall -y pymc_core 2>/dev/null || true
PY_TAG=$("$VENV_PYTHON" -c 'import sys; v=f"cp{sys.version_info.major}{sys.version_info.minor}"; print(f"{v}-{v}")' 2>/dev/null || echo "cp311-cp311") PY_TAG=$("$VENV_PYTHON" -c 'import sys; v=f"cp{sys.version_info.major}{sys.version_info.minor}"; print(f"{v}-{v}")' 2>/dev/null || echo "cp311-cp311")
WHEEL_BASE="${R2_BASE_URL}/${ARCH_TAG}/${PLATFORM_TAG}/${PY_TAG}" WHEEL_BASE="${R2_BASE_URL}/${ARCH_TAG}/${PLATFORM_TAG}/${PY_TAG}"
echo "[pymc-do-upgrade] Trying dependencies from R2 wheels..." echo "[pymc-do-upgrade] Trying dependencies from R2 wheels..."
"$VENV_PIP" install --find-links "${WHEEL_BASE}/index.html" --no-cache-dir "pycryptodome>=3.23.0" "PyNaCl>=1.5.0" cffi "pyyaml>=6.0.0" 2>/dev/null || true "$VENV_PYTHON" -m pip install --find-links "${WHEEL_BASE}/index.html" --no-cache-dir "pycryptodome>=3.23.0" "PyNaCl>=1.5.0" cffi "pyyaml>=6.0.0" 2>/dev/null || true
fi fi
# ---- Install openhop_repeater from git ---- # ---- Install openhop_repeater from git ----
if "$VENV_PIP" install \ if "$VENV_PYTHON" -m pip install \
--upgrade \ --upgrade \
--no-cache-dir \ --no-cache-dir \
"openhop_repeater[hardware] @ git+https://github.com/rightup/openhop-repeater.git@${CHANNEL}"; then "openhop_repeater[hardware] @ git+https://github.com/openhop-dev/openhop_repeater.git@${CHANNEL}"; then
# Keep web/OTA updates aligned with manage.sh install/upgrade defaults. # Keep web/OTA updates aligned with manage.sh install/upgrade defaults.
RADIO_BASE_URL="https://raw.githubusercontent.com/rightup/openhop-repeater/${CHANNEL}" RADIO_BASE_URL="https://raw.githubusercontent.com/openhop-dev/openhop_repeater/${CHANNEL}"
RADIO_STORAGE_DIR="/var/lib/openhop_repeater" RADIO_STORAGE_DIR="/var/lib/openhop_repeater"
mkdir -p "$RADIO_STORAGE_DIR" mkdir -p "$RADIO_STORAGE_DIR"
wget -qO "$RADIO_STORAGE_DIR/radio-settings.json" "${RADIO_BASE_URL}/radio-settings.json" 2>/dev/null || true wget -qO "$RADIO_STORAGE_DIR/radio-settings.json" "${RADIO_BASE_URL}/radio-settings.json" 2>/dev/null || true
@@ -1122,7 +1162,7 @@ UPGRADEEOF
WHEEL_BASE="${R2_BASE_URL}/${ARCH_TAG}/${PLATFORM_TAG}/${PY_TAG}" WHEEL_BASE="${R2_BASE_URL}/${ARCH_TAG}/${PLATFORM_TAG}/${PY_TAG}"
echo " Checking for R2 wheels (${ARCH_TAG}/${PLATFORM_TAG}/${PY_TAG})..." echo " Checking for R2 wheels (${ARCH_TAG}/${PLATFORM_TAG}/${PY_TAG})..."
echo " Trying install from R2 pre-built wheels..." echo " Trying install from R2 pre-built 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 && R2_SUCCESS=1 || R2_SUCCESS=0 "$VENV_PYTHON" -m 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 && R2_SUCCESS=1 || R2_SUCCESS=0
if [ "$R2_SUCCESS" -eq 1 ]; then if [ "$R2_SUCCESS" -eq 1 ]; then
echo " ✓ R2 wheels installed" echo " ✓ R2 wheels installed"
else else
@@ -1131,7 +1171,7 @@ UPGRADEEOF
fi fi
fi fi
if "$VENV_PIP" install --upgrade --no-cache-dir .[hardware]; then if "$VENV_PYTHON" -m pip install --upgrade --no-cache-dir .[hardware]; then
echo "" echo ""
echo "✓ Package and dependencies upgraded successfully!" echo "✓ Package and dependencies upgraded successfully!"
else else
+13 -6
View File
@@ -195,7 +195,10 @@ def get_node_info(config: Dict[str, Any]) -> Dict[str, Any]:
def load_config(config_path: Optional[str] = None) -> Dict[str, Any]: def load_config(config_path: Optional[str] = None) -> Dict[str, Any]:
if config_path is None: if config_path is None:
config_path = os.getenv("PYMC_REPEATER_CONFIG", "/etc/openhop_repeater/config.yaml") config_path = os.getenv(
"OPENHOP_REPEATER_CONFIG",
os.getenv("PYMC_REPEATER_CONFIG", "/etc/openhop_repeater/config.yaml"),
)
# Check if config file exists # Check if config file exists
if not Path(config_path).exists(): if not Path(config_path).exists():
@@ -301,10 +304,11 @@ def load_config(config_path: Optional[str] = None) -> Dict[str, Any]:
else: else:
config["repeater"]["identity_key"] = _load_or_create_identity_key() config["repeater"]["identity_key"] = _load_or_create_identity_key()
if os.getenv("PYMC_REPEATER_LOG_LEVEL"): env_log_level = os.getenv("OPENHOP_REPEATER_LOG_LEVEL", os.getenv("PYMC_REPEATER_LOG_LEVEL"))
if env_log_level:
if "logging" not in config: if "logging" not in config:
config["logging"] = {} config["logging"] = {}
config["logging"]["level"] = os.getenv("PYMC_REPEATER_LOG_LEVEL") config["logging"]["level"] = env_log_level
config = _load_policy_engine_config(config, config_path) config = _load_policy_engine_config(config, config_path)
@@ -323,7 +327,10 @@ def save_config(config_data: Dict[str, Any], config_path: Optional[str] = None)
True if successful, False otherwise True if successful, False otherwise
""" """
if config_path is None: if config_path is None:
config_path = os.getenv("PYMC_REPEATER_CONFIG", "/etc/openhop_repeater/config.yaml") config_path = os.getenv(
"OPENHOP_REPEATER_CONFIG",
os.getenv("PYMC_REPEATER_CONFIG", "/etc/openhop_repeater/config.yaml"),
)
try: try:
# Create backup of existing config # Create backup of existing config
@@ -617,7 +624,7 @@ def get_radio_for_board(board_config: dict):
except ImportError: except ImportError:
raise RuntimeError( raise RuntimeError(
"pymc_tcp radio requires openhop-core >= the release that includes " "pymc_tcp radio requires openhop-core >= the release that includes "
"PR pyMC-dev/openhop-core#68 (merged 2026-05-13). " "the openhop-core release with TCP modem support. "
"Reinstall the [hardware] extra to pick it up." "Reinstall the [hardware] extra to pick it up."
) from None ) from None
@@ -661,7 +668,7 @@ def get_radio_for_board(board_config: dict):
except ImportError: except ImportError:
raise RuntimeError( raise RuntimeError(
"pymc_usb radio requires openhop-core >= the release that includes " "pymc_usb radio requires openhop-core >= the release that includes "
"PR pyMC-dev/openhop-core#68 (merged 2026-05-13). " "the openhop-core release with TCP modem support. "
"Reinstall the [hardware] extra to pick it up." "Reinstall the [hardware] extra to pick it up."
) from None ) from None
+3 -6
View File
@@ -1580,6 +1580,7 @@ class APIEndpoints:
if cherrypy.request.method == "OPTIONS": if cherrypy.request.method == "OPTIONS":
return "" return ""
future = None
try: try:
self._require_post() self._require_post()
if not self.send_advert_func: if not self.send_advert_func:
@@ -1588,7 +1589,6 @@ class APIEndpoints:
return self._error("Event loop not available") return self._error("Event loop not available")
import asyncio import asyncio
future = None
future = asyncio.run_coroutine_threadsafe(self.send_advert_func(), self.event_loop) future = asyncio.run_coroutine_threadsafe(self.send_advert_func(), self.event_loop)
result = future.result(timeout=10) result = future.result(timeout=10)
return ( return (
@@ -1601,11 +1601,8 @@ class APIEndpoints:
"Error sending advert: timeout waiting for advert transmission to complete", "Error sending advert: timeout waiting for advert transmission to complete",
exc_info=True, exc_info=True,
) )
try: if future is not None:
if future is not None: future.cancel()
future.cancel()
except Exception:
pass
return self._error("Timed out waiting for advert transmission after 10 seconds") return self._error("Timed out waiting for advert transmission after 10 seconds")
except cherrypy.HTTPError: except cherrypy.HTTPError:
# Re-raise HTTP errors (like 405 Method Not Allowed) without logging # Re-raise HTTP errors (like 405 Method Not Allowed) without logging
+1 -1
View File
@@ -15,7 +15,7 @@ info:
version: 1.0.0 version: 1.0.0
contact: contact:
name: openHop Repeater name: openHop Repeater
url: https://github.com/rightup/openhop-repeater url: https://github.com/openhop-dev/openhop_repeater
servers: servers:
- url: /api - url: /api
+4 -4
View File
@@ -3,15 +3,15 @@
# Creates an LXC container with USB passthrough and installs openHop Repeater # Creates an LXC container with USB passthrough and installs openHop Repeater
# #
# Usage (run on the Proxmox host): # Usage (run on the Proxmox host):
# bash -c "$(curl -fsSL https://raw.githubusercontent.com/pyMC-dev/openhop-repeater/main/scripts/proxmox-install.sh)" # bash -c "$(curl -fsSL https://raw.githubusercontent.com/openhop-dev/openhop_repeater/main/scripts/proxmox-install.sh)"
# #
# License: MIT # License: MIT
# Source: https://github.com/openhop-dev/openhop-repeater # Source: https://github.com/openhop-dev/openhop_repeater
set -euo pipefail set -euo pipefail
# ── Defaults ─────────────────────────────────────────────────────────────── # ── Defaults ───────────────────────────────────────────────────────────────
REPO="https://github.com/openhop-dev/openhop-repeater.git" REPO="https://github.com/openhop-dev/openhop_repeater.git"
BRANCH="dev" BRANCH="dev"
CT_TEMPLATE="debian-12-standard" CT_TEMPLATE="debian-12-standard"
CT_RAM=1024 CT_RAM=1024
@@ -210,7 +210,7 @@ OS=\$(. /etc/os-release && echo \"\$NAME\")
VER=\$(. /etc/os-release && echo \"\$VERSION_ID\") VER=\$(. /etc/os-release && echo \"\$VERSION_ID\")
echo \"\" echo \"\"
echo \" openHop Repeater LXC Container\" echo \" openHop Repeater LXC Container\"
echo \" 🌐 GitHub: https://github.com/openhop-dev/openhop-repeater\" echo \" 🌐 GitHub: https://github.com/openhop-dev/openhop_repeater\"
echo \"\" echo \"\"
echo \" 🖥️ OS: \$OS - Version: \$VER\" echo \" 🖥️ OS: \$OS - Version: \$VER\"
echo \" 🏠 Hostname: \$HOSTNAME\" echo \" 🏠 Hostname: \$HOSTNAME\"
+18 -1
View File
@@ -1,6 +1,6 @@
from types import SimpleNamespace from types import SimpleNamespace
from repeater.config import BaselineCrcCounterRadio from repeater.config import BaselineCrcCounterRadio, load_config
def test_baseline_crc_counter_radio_reports_delta_from_initial_raw_count(): def test_baseline_crc_counter_radio_reports_delta_from_initial_raw_count():
@@ -26,3 +26,20 @@ def test_baseline_crc_counter_radio_handles_delayed_modem_counter():
raw.crc_error_count = 148 raw.crc_error_count = 148
assert radio.crc_error_count == 3 assert radio.crc_error_count == 3
def test_load_config_prefers_openhop_env_names(tmp_path, monkeypatch):
legacy_path = tmp_path / "legacy.yaml"
openhop_path = tmp_path / "openhop.yaml"
legacy_path.write_text("repeater:\n identity_key: legacy\n", encoding="utf-8")
openhop_path.write_text("repeater:\n identity_key: openhop\n", encoding="utf-8")
monkeypatch.setenv("PYMC_REPEATER_CONFIG", str(legacy_path))
monkeypatch.setenv("OPENHOP_REPEATER_CONFIG", str(openhop_path))
monkeypatch.setenv("PYMC_REPEATER_LOG_LEVEL", "WARNING")
monkeypatch.setenv("OPENHOP_REPEATER_LOG_LEVEL", "DEBUG")
config = load_config()
assert config["repeater"]["identity_key"] == "openhop"
assert config["logging"]["level"] == "DEBUG"