mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-10-31 04:01:59 +01:00
feat: modernize Proxmox LXC installer
This commit is contained in:
1 parent
e28be27a77
commit
0431dfe883
4 files changed
+227
-26
No files matched your search
@@ -275,8 +275,8 @@ The upgrade script will:
|
||||
## Proxmox LXC Installation
|
||||
|
||||
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.
|
||||
adapter or an openHop Modem over TCP or USB. This is useful for headless,
|
||||
always-on deployments without dedicating a full Raspberry Pi.
|
||||
|
||||
### Requirements
|
||||
|
||||
@@ -290,7 +290,7 @@ Hardware, choose one:
|
||||
- CH341 USB-to-SPI adapter with VID `1a86` and PID `5512`, connected to the
|
||||
Proxmox host and wired to an SX1262-based LoRa module such as an Ebyte
|
||||
E22-900M30S
|
||||
- TCP modem, such as MeshSmith EtherMesh
|
||||
- openHop Modem over TCP or USB, such as MeshSmith EtherMesh
|
||||
|
||||
### One-Line Install
|
||||
|
||||
@@ -303,14 +303,20 @@ bash -c "$(curl -fsSL https://raw.githubusercontent.com/openhop-dev/openhop_repe
|
||||
Replace `main` in the URL with another branch name if needed.
|
||||
|
||||
The installer will prompt for container settings (container ID, hostname, RAM,
|
||||
disk, bridge, etc.) and then:
|
||||
disk, bridge, etc.), whether to install the host-side CH341 udev rule, and
|
||||
whether to download the optional openHop Console WebUI. It will then:
|
||||
|
||||
1. Download a Debian 12 LXC template.
|
||||
1. Download a Debian 13 LXC template.
|
||||
2. Create a privileged container with USB passthrough.
|
||||
3. Install a host-side udev rule for the CH341 device.
|
||||
3. Install the host-side CH341 udev rule only when selected. This is not needed
|
||||
for openHop Modem TCP or USB connections and defaults to No.
|
||||
4. Clone the repository and pre-seed CH341 GPIO pin mappings.
|
||||
5. Run `manage.sh install` inside the container.
|
||||
6. Display the dashboard URL.
|
||||
5. Install an `update` command for Debian and openHop Repeater updates.
|
||||
6. Run `manage.sh install` inside the container.
|
||||
7. Optionally install openHop Console WebUI assets after Repeater installation.
|
||||
Console is not selected as the default frontend, allowing the Repeater setup
|
||||
wizard to be completed first.
|
||||
8. Display the dashboard URL.
|
||||
|
||||
### Default Container Settings
|
||||
|
||||
@@ -324,6 +330,8 @@ disk, bridge, etc.) and then:
|
||||
| Bridge | `vmbr0` |
|
||||
| Storage | `local-lvm` |
|
||||
| Password | `pymc` |
|
||||
| Host-side CH341 udev rule | No |
|
||||
| openHop Console WebUI | No |
|
||||
|
||||
### After Installation
|
||||
|
||||
@@ -339,6 +347,16 @@ cd /opt/openhop_repeater
|
||||
bash manage.sh
|
||||
```
|
||||
|
||||
Run `update` inside the LXC to update Debian packages, fast-forward the branch
|
||||
selected during installation, and run `manage.sh upgrade`:
|
||||
|
||||
```bash
|
||||
update
|
||||
```
|
||||
|
||||
If openHop Console WebUI was installed, complete the Repeater setup wizard
|
||||
before selecting `openHop Console` from Web Settings.
|
||||
|
||||
Open the dashboard at:
|
||||
|
||||
```text
|
||||
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# Update the Debian LXC and its openHop Repeater installation.
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
readonly REPO_DIR="/root/openhop-repeater"
|
||||
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
echo "This command must be run as root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "$REPO_DIR/.git" || ! -f "$REPO_DIR/manage.sh" ]]; then
|
||||
echo "openHop Repeater source checkout not found at $REPO_DIR." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BRANCH=$(git -C "$REPO_DIR" branch --show-current)
|
||||
if [[ -z "$BRANCH" ]]; then
|
||||
echo "The openHop Repeater checkout is in detached HEAD state; update aborted." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$(git -C "$REPO_DIR" status --porcelain)" ]]; then
|
||||
echo "The openHop Repeater checkout has local changes; update aborted." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Checking openHop Repeater branch: $BRANCH"
|
||||
git -C "$REPO_DIR" fetch origin --prune
|
||||
if ! git -C "$REPO_DIR" show-ref --verify --quiet "refs/remotes/origin/$BRANCH"; then
|
||||
echo "Remote branch origin/$BRANCH does not exist; update aborted." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! git -C "$REPO_DIR" merge-base --is-ancestor HEAD "origin/$BRANCH"; then
|
||||
echo "The local branch cannot be fast-forwarded to origin/$BRANCH; update aborted." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
echo "==> Updating Debian packages"
|
||||
apt-get update
|
||||
apt-get upgrade -y
|
||||
|
||||
echo "==> Updating openHop Repeater branch: $BRANCH"
|
||||
git -C "$REPO_DIR" pull --ff-only origin "$BRANCH"
|
||||
|
||||
echo "==> Running openHop Repeater upgrade"
|
||||
cd "$REPO_DIR"
|
||||
OPENHOP_UPGRADE_REF="$BRANCH" TERM="${TERM:-xterm}" bash manage.sh upgrade
|
||||
|
||||
# Refresh the installed helper for the next run without replacing this script
|
||||
# until all update steps have completed successfully.
|
||||
install -m 0755 "$REPO_DIR/scripts/openhop-update" /usr/local/bin/openhop-update.new
|
||||
mv /usr/local/bin/openhop-update.new /usr/local/bin/openhop-update
|
||||
ln -sfn /usr/local/bin/openhop-update /usr/local/bin/update
|
||||
|
||||
echo "==> Update complete"
|
||||
+68
-18
@@ -13,7 +13,7 @@ set -euo pipefail
|
||||
# ── Defaults ───────────────────────────────────────────────────────────────
|
||||
REPO="https://github.com/openhop-dev/openhop_repeater.git"
|
||||
BRANCH="dev"
|
||||
CT_TEMPLATE="debian-12-standard"
|
||||
CT_TEMPLATE="debian-13-standard"
|
||||
CT_RAM=1024
|
||||
CT_SWAP=512
|
||||
CT_DISK=4
|
||||
@@ -24,6 +24,9 @@ CT_STORAGE="local-lvm"
|
||||
CT_TEMPLATE_STORAGE="local"
|
||||
CH341_VID="1a86"
|
||||
CH341_PID="5512"
|
||||
CONSOLE_RELEASE_URL="https://github.com/Treehouse-00/pymc_console-dist/releases/latest/download/pymc-ui-latest.tar.gz"
|
||||
INSTALL_CH341_UDEV=false
|
||||
INSTALL_CONSOLE=false
|
||||
|
||||
# ── Colors ─────────────────────────────────────────────────────────────────
|
||||
RD="\033[01;31m" GN="\033[1;92m" YW="\033[33m" BL="\033[36m" BLD="\033[1m" CL="\033[m"
|
||||
@@ -71,14 +74,6 @@ fi
|
||||
|
||||
msg_ok "Running on Proxmox host as root"
|
||||
|
||||
# Check for CH341
|
||||
echo ""
|
||||
if lsusb -d "${CH341_VID}:${CH341_PID}" &>/dev/null; then
|
||||
msg_ok "CH341 USB device detected"
|
||||
else
|
||||
msg_warn "CH341 USB device not found — plug it in before starting the repeater"
|
||||
fi
|
||||
|
||||
# Default to the next available container ID, but allow the user to choose.
|
||||
DEFAULT_CTID=$(pvesh get /cluster/nextid)
|
||||
|
||||
@@ -110,14 +105,32 @@ AVAILABLE_STORAGES=$(pvesm status -content rootdir 2>/dev/null | awk 'NR>1 {prin
|
||||
echo " Available storages: ${AVAILABLE_STORAGES}"
|
||||
read -p " Storage [${CT_STORAGE}]: " -r input; CT_STORAGE="${input:-$CT_STORAGE}"
|
||||
read -p " Git branch [${BRANCH}]: " -r input; BRANCH="${input:-$BRANCH}"
|
||||
read -sp " Root password [pymc]: " CT_PASSWORD; echo
|
||||
read -p " Install host-side CH341 udev rule? [y/N]: " -r input
|
||||
[[ "${input:-n}" =~ ^[Yy]([Ee][Ss])?$ ]] && INSTALL_CH341_UDEV=true
|
||||
read -p " Install optional openHop Console WebUI? [y/N]: " -r input
|
||||
[[ "${input:-n}" =~ ^[Yy]([Ee][Ss])?$ ]] && INSTALL_CONSOLE=true
|
||||
read -rsp " Root password [pymc]: " CT_PASSWORD; echo
|
||||
CT_PASSWORD="${CT_PASSWORD:-pymc}"
|
||||
|
||||
CH341_UDEV_SUMMARY="No"
|
||||
CONSOLE_SUMMARY="No"
|
||||
if [[ "$INSTALL_CH341_UDEV" == "true" ]]; then
|
||||
CH341_UDEV_SUMMARY="Yes"
|
||||
if lsusb -d "${CH341_VID}:${CH341_PID}" &>/dev/null; then
|
||||
msg_ok "CH341 USB device detected"
|
||||
else
|
||||
msg_warn "CH341 USB device not found — plug it in before starting the repeater"
|
||||
fi
|
||||
fi
|
||||
[[ "$INSTALL_CONSOLE" == "true" ]] && CONSOLE_SUMMARY="Yes"
|
||||
|
||||
# ── Confirmation ──────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BLD}Summary:${CL}"
|
||||
echo " CTID: ${CTID} Host: ${CT_HOSTNAME} RAM: ${CT_RAM}MB Disk: ${CT_DISK}GB"
|
||||
echo " Cores: ${CT_CORES} Storage: ${CT_STORAGE} Bridge: ${CT_BRIDGE} Branch: ${BRANCH}"
|
||||
echo " CH341 host udev rule: ${CH341_UDEV_SUMMARY}"
|
||||
echo " openHop Console WebUI: ${CONSOLE_SUMMARY}"
|
||||
echo " Mode: privileged (required for USB passthrough)"
|
||||
echo ""
|
||||
read -p " Proceed? [Y/n]: " -r
|
||||
@@ -125,8 +138,11 @@ read -p " Proceed? [Y/n]: " -r
|
||||
|
||||
# ── Download template ─────────────────────────────────────────────────────
|
||||
echo ""
|
||||
msg_info "Downloading Debian 12 template..."
|
||||
TEMPLATE_FILE=$(pveam available -section system 2>/dev/null | grep "${CT_TEMPLATE}" | sort -t- -k4 -V | tail -1 | awk '{print $2}')
|
||||
msg_info "Downloading Debian 13 template..."
|
||||
TEMPLATE_FILE=$(pveam available -section system 2>/dev/null \
|
||||
| awk -v template="$CT_TEMPLATE" '$2 ~ template {print $2}' \
|
||||
| sort -V \
|
||||
| tail -1)
|
||||
[ -z "$TEMPLATE_FILE" ] && { msg_error "Template not found. Run: pveam update"; exit 1; }
|
||||
|
||||
pveam list "$CT_TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE_FILE" || \
|
||||
@@ -161,12 +177,16 @@ EOF
|
||||
msg_ok "USB passthrough configured"
|
||||
|
||||
# ── Host udev rule ────────────────────────────────────────────────────────
|
||||
msg_info "Installing CH341 udev rule on host..."
|
||||
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1a86", ATTR{idProduct}=="5512", MODE="0666"' \
|
||||
> /etc/udev/rules.d/99-ch341.rules
|
||||
udevadm control --reload-rules
|
||||
udevadm trigger --subsystem-match=usb --action=change
|
||||
msg_ok "Host udev rule installed"
|
||||
if [[ "$INSTALL_CH341_UDEV" == "true" ]]; then
|
||||
msg_info "Installing CH341 udev rule on host..."
|
||||
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1a86", ATTR{idProduct}=="5512", MODE="0666"' \
|
||||
> /etc/udev/rules.d/99-ch341.rules
|
||||
udevadm control --reload-rules
|
||||
udevadm trigger --subsystem-match=usb --action=change
|
||||
msg_ok "Host udev rule installed"
|
||||
else
|
||||
msg_info "Skipping host-side CH341 udev rule"
|
||||
fi
|
||||
|
||||
# ── Start container & wait for network ────────────────────────────────────
|
||||
msg_info "Starting container..."
|
||||
@@ -218,6 +238,7 @@ echo \" 💡 IP Address: \$IP\"
|
||||
echo \" 📡 Dashboard: http://\$IP:8000\"
|
||||
echo \"\"
|
||||
echo \" Management: cd /opt/openhop_repeater && bash manage.sh\"
|
||||
echo \" Update: update\"
|
||||
echo \"\"
|
||||
MOTD
|
||||
chmod +x /etc/profile.d/pymc-motd.sh
|
||||
@@ -228,6 +249,13 @@ msg_info "Cloning openhop-repeater (branch: ${BRANCH})..."
|
||||
pct exec "$CTID" -- bash -c "git clone --branch ${BRANCH} ${REPO} /root/openhop-repeater"
|
||||
msg_ok "Repository cloned"
|
||||
|
||||
msg_info "Installing LXC update command..."
|
||||
pct exec "$CTID" -- bash -c "
|
||||
install -m 0755 /root/openhop-repeater/scripts/openhop-update /usr/local/bin/openhop-update
|
||||
ln -sfn /usr/local/bin/openhop-update /usr/local/bin/update
|
||||
"
|
||||
msg_ok "Update command installed"
|
||||
|
||||
# Pre-seed config with CH341 radio type and correct GPIO pins
|
||||
pct exec "$CTID" -- bash -c "
|
||||
mkdir -p /etc/openhop_repeater
|
||||
@@ -255,6 +283,24 @@ lxc-attach -n "$CTID" -- bash -c "cd /root/openhop-repeater && TERM=xterm bash m
|
||||
echo ""
|
||||
msg_ok "manage.sh install completed"
|
||||
|
||||
# ── Optional openHop Console WebUI ─────────────────────────────────────────
|
||||
if [[ "$INSTALL_CONSOLE" == "true" ]]; then
|
||||
msg_info "Installing optional openHop Console WebUI..."
|
||||
pct exec "$CTID" -- bash -c "
|
||||
set -e
|
||||
console_archive=\$(mktemp)
|
||||
trap 'rm -f \"\$console_archive\"' EXIT
|
||||
curl -fL '${CONSOLE_RELEASE_URL}' -o \"\$console_archive\"
|
||||
rm -rf /opt/pymc_console/web/html
|
||||
install -d -m 0755 -o repeater -g repeater /opt/pymc_console/web/html
|
||||
tar -xzf \"\$console_archive\" -C /opt/pymc_console/web/html
|
||||
test -f /opt/pymc_console/web/html/index.html
|
||||
chown -R repeater:repeater /opt/pymc_console
|
||||
"
|
||||
msg_ok "Console is installed but not enabled"
|
||||
msg_info "Complete the Repeater setup wizard before selecting openHop Console in Web Settings"
|
||||
fi
|
||||
|
||||
# ── Get container IP ──────────────────────────────────────────────────────
|
||||
sleep 2
|
||||
CT_IP=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}')
|
||||
@@ -272,5 +318,9 @@ 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/openhop_repeater && bash manage.sh"
|
||||
echo " Update: pct enter ${CTID}, then run: update"
|
||||
if [[ "$INSTALL_CONSOLE" == "true" ]]; then
|
||||
echo " Console: installed but not enabled; select it in Web Settings after setup"
|
||||
fi
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
@@ -0,0 +1,74 @@
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
INSTALLER = ROOT / "scripts" / "proxmox-install.sh"
|
||||
UPDATER = ROOT / "scripts" / "openhop-update"
|
||||
README = ROOT / "README.md"
|
||||
|
||||
|
||||
def test_proxmox_installer_uses_debian_13() -> None:
|
||||
script = INSTALLER.read_text()
|
||||
|
||||
assert 'CT_TEMPLATE="debian-13-standard"' in script
|
||||
assert "Downloading Debian 13 template" in script
|
||||
assert "sort -V" in script
|
||||
assert "Debian 12" not in script
|
||||
|
||||
|
||||
def test_ch341_host_rule_is_opt_in() -> None:
|
||||
script = INSTALLER.read_text()
|
||||
|
||||
prompt = "Install host-side CH341 udev rule? [y/N]"
|
||||
rule = "/etc/udev/rules.d/99-ch341.rules"
|
||||
conditional = script.index('if [[ "$INSTALL_CH341_UDEV" == "true" ]]; then')
|
||||
|
||||
assert prompt in script
|
||||
assert script.index(rule) > conditional
|
||||
assert "CH341 host udev rule: ${CH341_UDEV_SUMMARY}" in script
|
||||
|
||||
|
||||
def test_console_is_opt_in_installed_after_repeater_and_not_enabled() -> None:
|
||||
script = INSTALLER.read_text()
|
||||
|
||||
assert "Install optional openHop Console WebUI? [y/N]" in script
|
||||
assert "openHop Console WebUI: ${CONSOLE_SUMMARY}" in script
|
||||
assert script.index("Installing optional openHop Console WebUI") > script.index(
|
||||
'msg_ok "manage.sh install completed"'
|
||||
)
|
||||
assert "pymc-ui-latest.tar.gz" in script
|
||||
assert "Console is installed but not enabled" in script
|
||||
assert "web.web_path" not in script
|
||||
|
||||
|
||||
def test_installer_adds_update_command() -> None:
|
||||
script = INSTALLER.read_text()
|
||||
|
||||
assert (
|
||||
"install -m 0755 /root/openhop-repeater/scripts/openhop-update "
|
||||
"/usr/local/bin/openhop-update"
|
||||
) in script
|
||||
assert "ln -sfn /usr/local/bin/openhop-update /usr/local/bin/update" in script
|
||||
assert "Update: update" in script
|
||||
|
||||
|
||||
def test_update_command_updates_apt_and_current_repeater_branch() -> None:
|
||||
updater = UPDATER.read_text()
|
||||
|
||||
assert "apt-get update" in updater
|
||||
assert "apt-get upgrade -y" in updater
|
||||
assert 'BRANCH=$(git -C "$REPO_DIR" branch --show-current)' in updater
|
||||
assert 'git -C "$REPO_DIR" status --porcelain' in updater
|
||||
assert 'merge-base --is-ancestor HEAD "origin/$BRANCH"' in updater
|
||||
assert 'git -C "$REPO_DIR" pull --ff-only origin "$BRANCH"' in updater
|
||||
assert 'OPENHOP_UPGRADE_REF="$BRANCH"' in updater
|
||||
assert "bash manage.sh upgrade" in updater
|
||||
assert 'install -m 0755 "$REPO_DIR/scripts/openhop-update"' in updater
|
||||
|
||||
|
||||
def test_readme_documents_new_installer_behavior() -> None:
|
||||
readme = README.read_text()
|
||||
|
||||
assert "Download a Debian 13 LXC template" in readme
|
||||
assert "host-side CH341 udev rule" in readme
|
||||
assert "openHop Console WebUI" in readme
|
||||
assert "Run `update` inside the LXC" in readme
|
||||
Reference in new issue
Block a user