mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-06-25 04:21:50 +02:00
@@ -194,6 +194,89 @@ The upgrade script will:
|
||||
- Restart the service automatically
|
||||
- Preserve your existing configuration
|
||||
|
||||
---
|
||||
|
||||
## Installing on Proxmox (LXC Container)
|
||||
|
||||
pyMC Repeater can run inside a Proxmox LXC container using a **CH341 USB-to-SPI adapter** for radio communication. This is ideal for headless, always-on deployments without dedicating a full Raspberry Pi.
|
||||
|
||||
### Requirements
|
||||
|
||||
- **Proxmox VE 7.x or 8.x** host
|
||||
- **CH341 USB-to-SPI adapter** (VID `1a86`, PID `5512`) connected to the Proxmox host
|
||||
- **SX1262-based LoRa module** (e.g. Ebyte E22-900M30S) wired to the CH341 adapter
|
||||
- Internet connectivity for the container
|
||||
|
||||
### One-Line Install
|
||||
|
||||
Run this on the **Proxmox host** (not inside a container):
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -fsSL https://raw.githubusercontent.com/rightup/pyMC_Repeater/main/scripts/proxmox-install.sh)"
|
||||
```
|
||||
|
||||
The installer will interactively prompt you for container settings (hostname, RAM, disk, bridge, etc.) and then:
|
||||
|
||||
1. Download a Debian 12 LXC template
|
||||
2. Create a **privileged** container with USB passthrough
|
||||
3. Install a host-side udev rule for the CH341 device
|
||||
4. Clone the repository and pre-seed the config with CH341 GPIO pin mappings
|
||||
5. Run `manage.sh install` inside the container
|
||||
6. Display the dashboard URL when finished
|
||||
|
||||
### Default Container Settings
|
||||
|
||||
| Setting | Default |
|
||||
|-----------|-----------------|
|
||||
| Hostname | `pymc-repeater` |
|
||||
| RAM | 1024 MB |
|
||||
| Disk | 4 GB |
|
||||
| CPU cores | 2 |
|
||||
| Bridge | `vmbr0` |
|
||||
| Storage | `local-lvm` |
|
||||
| Password | `pymc` |
|
||||
|
||||
### After Installation
|
||||
|
||||
```bash
|
||||
# Enter the container
|
||||
pct enter <CTID>
|
||||
|
||||
# View service logs
|
||||
journalctl -u pymc-repeater -f
|
||||
|
||||
# Access web dashboard
|
||||
http://<container-ip>:8000
|
||||
|
||||
# Manage the repeater
|
||||
cd /opt/pymc_repeater && bash manage.sh
|
||||
```
|
||||
|
||||
### CH341 GPIO Pin Mapping
|
||||
|
||||
The installer pre-configures the CH341 GPIO pins for an E22 module. These differ from the Raspberry Pi BCM pin numbers:
|
||||
|
||||
| Function | CH341 GPIO | Pi BCM (default) |
|
||||
|----------|-----------|-------------------|
|
||||
| CS | 0 | 21 |
|
||||
| RXEN | 1 | -1 |
|
||||
| Reset | 2 | 18 |
|
||||
| Busy | 4 | 20 |
|
||||
| IRQ | 6 | 16 |
|
||||
|
||||
The installer also enables `use_dio3_tcxo` and `use_dio2_rf` for E22 modules.
|
||||
|
||||
### Troubleshooting (Proxmox)
|
||||
|
||||
- **USB device not found**: Make sure the CH341 is plugged into the Proxmox host and shows up with `lsusb -d 1a86:5512`
|
||||
- **Permission denied on USB**: The installer creates a host udev rule (`/etc/udev/rules.d/99-ch341.rules`). Run `udevadm trigger` on the host if needed
|
||||
- **Container can't see USB**: Verify USB passthrough lines exist in `/etc/pve/lxc/<CTID>.conf`:
|
||||
```
|
||||
lxc.cgroup2.devices.allow: c 189:* rwm
|
||||
lxc.mount.entry: /dev/bus/usb dev/bus/usb none bind,optional,create=dir 0 0
|
||||
```
|
||||
- **NoBackendError (libusb)**: The installer installs `libusb-1.0-0` automatically. If you see this error, run `apt-get install libusb-1.0-0` inside the container
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -182,9 +182,16 @@ install_repeater() {
|
||||
# Welcome screen
|
||||
$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
|
||||
|
||||
# SPI Check - Universal approach that works on all boards
|
||||
# SPI Check - skip for CH341 USB-SPI adapter (handles SPI over USB)
|
||||
SPI_MISSING=0
|
||||
if ! ls /dev/spidev* >/dev/null 2>&1; then
|
||||
USES_CH341=0
|
||||
if [ -f "$CONFIG_DIR/config.yaml" ]; then
|
||||
if grep -q "radio_type:.*sx1262_ch341" "$CONFIG_DIR/config.yaml" 2>/dev/null; then
|
||||
USES_CH341=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$USES_CH341" -eq 0 ] && ! ls /dev/spidev* >/dev/null 2>&1; then
|
||||
# SPI devices not found, check if we're on a Raspberry Pi and can enable it
|
||||
CONFIG_FILE=""
|
||||
if [ -f "/boot/firmware/config.txt" ]; then
|
||||
@@ -226,26 +233,37 @@ install_repeater() {
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
# Installation progress
|
||||
(
|
||||
echo "0"; echo "# Creating service user..."
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " Installing pyMC 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"
|
||||
fi
|
||||
|
||||
echo "10"; echo "# Adding user to hardware groups..."
|
||||
usermod -a -G gpio,i2c,spi "$SERVICE_USER" 2>/dev/null || true
|
||||
usermod -a -G dialout "$SERVICE_USER" 2>/dev/null || true
|
||||
echo ">>> Adding user to hardware groups..."
|
||||
for grp in plugdev dialout gpio i2c spi; do
|
||||
getent group "$grp" >/dev/null 2>&1 && usermod -a -G "$grp" "$SERVICE_USER" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo "20"; echo "# Creating directories..."
|
||||
echo ">>> Creating directories..."
|
||||
mkdir -p "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater
|
||||
|
||||
echo "25"; echo "# Installing system dependencies..."
|
||||
echo ">>> Installing system dependencies..."
|
||||
apt-get update -qq
|
||||
apt-get install -y libffi-dev jq pip python3-rrdtool wget swig build-essential python3-dev
|
||||
pip install --break-system-packages setuptools_scm >/dev/null 2>&1 || true
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y libffi-dev libusb-1.0-0 sudo jq pip python3-rrdtool wget swig build-essential python3-dev
|
||||
# Install polkit (package name varies by distro version)
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y policykit-1 2>/dev/null \
|
||||
|| DEBIAN_FRONTEND=noninteractive apt-get install -y polkitd pkexec 2>/dev/null \
|
||||
|| echo " Warning: Could not install polkit (sudo fallback will be used)"
|
||||
pip install --break-system-packages setuptools_scm 2>&1 || true
|
||||
|
||||
# Install mikefarah yq v4 if not already installed
|
||||
if ! command -v yq &> /dev/null || [[ "$(yq --version 2>&1)" != *"mikefarah/yq"* ]]; then
|
||||
echo ">>> Installing yq..."
|
||||
YQ_VERSION="v4.40.5"
|
||||
YQ_BINARY="yq_linux_arm64"
|
||||
if [[ "$(uname -m)" == "x86_64" ]]; then
|
||||
@@ -253,32 +271,31 @@ install_repeater() {
|
||||
elif [[ "$(uname -m)" == "armv7"* ]]; then
|
||||
YQ_BINARY="yq_linux_arm"
|
||||
fi
|
||||
wget -qO /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY}" && chmod +x /usr/local/bin/yq
|
||||
wget -qO /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY}" 2>/dev/null && chmod +x /usr/local/bin/yq
|
||||
fi
|
||||
|
||||
echo "28"; echo "# Generating version file..."
|
||||
echo ">>> Generating version file..."
|
||||
cd "$SCRIPT_DIR"
|
||||
# Generate version file using setuptools_scm before copying
|
||||
if [ -d .git ]; then
|
||||
git fetch --tags 2>/dev/null || true
|
||||
git fetch --tags >/dev/null 2>&1 || true
|
||||
# Write the version file that will be copied
|
||||
GENERATED_VERSION=$(python3 -m setuptools_scm 2>&1 || echo "unknown (setuptools_scm not available)")
|
||||
python3 -c "from setuptools_scm import get_version; get_version(write_to='repeater/_version.py')" 2>&1 || echo " Warning: Could not generate _version.py file"
|
||||
echo " Generated version: $GENERATED_VERSION"
|
||||
python3 -m setuptools_scm >/dev/null 2>&1 || true
|
||||
python3 -c "from setuptools_scm import get_version; get_version(write_to='repeater/_version.py')" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# Clean up stale bytecode in source directory before copying
|
||||
find "$SCRIPT_DIR/repeater" -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
||||
find "$SCRIPT_DIR/repeater" -type f -name '*.pyc' -delete 2>/dev/null || true
|
||||
|
||||
echo "29"; echo "# Cleaning old installation files..."
|
||||
echo ">>> Cleaning old installation files..."
|
||||
# Remove old repeater directory to ensure clean install
|
||||
rm -rf "$INSTALL_DIR/repeater" 2>/dev/null || true
|
||||
# Clean up old Python bytecode
|
||||
find "$INSTALL_DIR" -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
||||
find "$INSTALL_DIR" -type f -name '*.pyc' -delete 2>/dev/null || true
|
||||
|
||||
echo "30"; echo "# Installing files..."
|
||||
echo ">>> Installing files..."
|
||||
cp -r "$SCRIPT_DIR/repeater" "$INSTALL_DIR/"
|
||||
cp "$SCRIPT_DIR/pyproject.toml" "$INSTALL_DIR/"
|
||||
cp "$SCRIPT_DIR/README.md" "$INSTALL_DIR/"
|
||||
@@ -287,17 +304,24 @@ install_repeater() {
|
||||
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
|
||||
|
||||
echo "45"; echo "# Installing configuration..."
|
||||
echo ">>> Installing configuration..."
|
||||
cp "$SCRIPT_DIR/config.yaml.example" "$CONFIG_DIR/config.yaml.example"
|
||||
if [ ! -f "$CONFIG_DIR/config.yaml" ]; then
|
||||
cp "$SCRIPT_DIR/config.yaml.example" "$CONFIG_DIR/config.yaml"
|
||||
fi
|
||||
|
||||
echo "55"; echo "# Installing systemd service..."
|
||||
echo ">>> Installing systemd service..."
|
||||
cp "$SCRIPT_DIR/pymc-repeater.service" /etc/systemd/system/
|
||||
systemctl daemon-reload
|
||||
|
||||
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
|
||||
udevadm control --reload-rules 2>/dev/null || true
|
||||
udevadm trigger 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "65"; echo "# Setting permissions..."
|
||||
echo ">>> Setting permissions..."
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater
|
||||
chmod 750 "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater
|
||||
# Ensure the service user can create subdirectories in their home directory
|
||||
@@ -307,6 +331,7 @@ install_repeater() {
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" /var/lib/pymc_repeater/.config
|
||||
|
||||
# Configure polkit for passwordless service restart
|
||||
echo ">>> Configuring polkit for service management..."
|
||||
mkdir -p /etc/polkit-1/rules.d
|
||||
cat > /etc/polkit-1/rules.d/10-pymc-repeater.rules <<'EOF'
|
||||
polkit.addRule(function(action, subject) {
|
||||
@@ -319,11 +344,19 @@ polkit.addRule(function(action, subject) {
|
||||
EOF
|
||||
chmod 0644 /etc/polkit-1/rules.d/10-pymc-repeater.rules
|
||||
|
||||
echo "75"; echo "# Starting service..."
|
||||
# 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
|
||||
EOF
|
||||
chmod 0440 /etc/sudoers.d/pymc-repeater
|
||||
|
||||
echo ">>> Enabling service..."
|
||||
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
|
||||
echo ">>> Installation files complete."
|
||||
|
||||
# Install Python package outside of progress gauge for better error handling
|
||||
clear
|
||||
@@ -354,7 +387,7 @@ EOF
|
||||
echo "Note: Using optimized binary wheels for faster installation"
|
||||
echo ""
|
||||
|
||||
if pip install --break-system-packages --no-cache-dir .; then
|
||||
if pip install --break-system-packages --no-build-isolation --ignore-installed --no-cache-dir .; then
|
||||
echo ""
|
||||
echo "✓ Python package installation completed successfully!"
|
||||
|
||||
@@ -394,6 +427,23 @@ EOF
|
||||
echo " • Set admin password"
|
||||
echo " 3. Log in to your configured repeater"
|
||||
echo ""
|
||||
# Container detection: warn about host-side udev rules
|
||||
if [ -f /run/host/container-manager ] || [ -n "${container:-}" ] || grep -qsai 'container=' /proc/1/environ 2>/dev/null || [ -f /.dockerenv ]; then
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " ⚠ CONTAINER ENVIRONMENT DETECTED"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " USB device udev rules do NOT work inside containers."
|
||||
echo " You MUST install the CH341 udev rule on the HOST machine:"
|
||||
echo ""
|
||||
echo " echo 'SUBSYSTEM==\"usb\", ATTR{idVendor}==\"1a86\", ATTR{idProduct}==\"5512\", MODE=\"0666\"' \\"
|
||||
echo " | sudo tee /etc/udev/rules.d/99-ch341.rules"
|
||||
echo " sudo udevadm control --reload-rules"
|
||||
echo " sudo udevadm trigger --subsystem-match=usb --action=change"
|
||||
echo ""
|
||||
echo " Then unplug and replug the CH341 USB adapter."
|
||||
echo ""
|
||||
fi
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
read -p "Press Enter to return to main menu..." || true
|
||||
@@ -497,7 +547,11 @@ upgrade_repeater() {
|
||||
echo "[3/9] Updating system dependencies..."
|
||||
apt-get update -qq
|
||||
|
||||
apt-get install -y libffi-dev jq pip python3-rrdtool wget swig build-essential python3-dev
|
||||
apt-get install -y libffi-dev libusb-1.0-0 sudo jq pip python3-rrdtool wget swig build-essential python3-dev
|
||||
# Install polkit (package name varies by distro version)
|
||||
apt-get install -y policykit-1 2>/dev/null \
|
||||
|| apt-get install -y polkitd pkexec 2>/dev/null \
|
||||
|| echo " Warning: Could not install polkit (sudo fallback will be used)"
|
||||
pip install --break-system-packages setuptools_scm >/dev/null 2>&1 || true
|
||||
|
||||
# Install mikefarah yq v4 if not already installed
|
||||
@@ -553,6 +607,22 @@ upgrade_repeater() {
|
||||
echo " ⚠ Configuration validation failed, keeping existing config"
|
||||
fi
|
||||
|
||||
echo "[5.5/9] Ensuring user groups and udev rules..."
|
||||
for grp in plugdev dialout gpio i2c spi; do
|
||||
getent group "$grp" >/dev/null 2>&1 && usermod -a -G "$grp" "$SERVICE_USER" 2>/dev/null || true
|
||||
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
|
||||
udevadm control --reload-rules 2>/dev/null || true
|
||||
udevadm trigger 2>/dev/null || true
|
||||
echo " ✓ CH341 udev rules updated"
|
||||
elif [ -f /etc/udev/rules.d/99-ch341.rules ]; then
|
||||
echo " ✓ CH341 udev rules already present"
|
||||
fi
|
||||
echo " ✓ User groups updated"
|
||||
|
||||
echo "[6/9] Fixing permissions..."
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater 2>/dev/null || true
|
||||
chmod 750 "$CONFIG_DIR" "$LOG_DIR" 2>/dev/null || true
|
||||
@@ -572,6 +642,13 @@ polkit.addRule(function(action, subject) {
|
||||
});
|
||||
EOF
|
||||
chmod 0644 /etc/polkit-1/rules.d/10-pymc-repeater.rules
|
||||
# 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
|
||||
EOF
|
||||
chmod 0440 /etc/sudoers.d/pymc-repeater
|
||||
echo " ✓ Permissions updated"
|
||||
|
||||
echo "[7/9] Reloading systemd..."
|
||||
@@ -607,7 +684,7 @@ EOF
|
||||
echo ""
|
||||
|
||||
# Upgrade packages (uses cache for unchanged dependencies - much faster)
|
||||
if python3 -m pip install --break-system-packages --upgrade --upgrade-strategy eager .; then
|
||||
if python3 -m pip install --break-system-packages --no-build-isolation --ignore-installed --upgrade --upgrade-strategy eager .; then
|
||||
echo ""
|
||||
echo "✓ Package and dependencies updated successfully!"
|
||||
else
|
||||
@@ -632,7 +709,12 @@ EOF
|
||||
|
||||
if is_running; then
|
||||
echo " ✓ Service is running"
|
||||
show_info "Upgrade Complete" "Upgrade completed successfully!\n\nVersion: $current_version → $new_version\n\n✓ Service is running\n✓ Configuration preserved"
|
||||
# Container detection: warn about host-side udev rules
|
||||
local container_note=""
|
||||
if [ -f /run/host/container-manager ] || [ -n "${container:-}" ] || grep -qsai 'container=' /proc/1/environ 2>/dev/null || [ -f /.dockerenv ]; then
|
||||
container_note="\n\n⚠ CONTAINER DETECTED:\nUSB udev rules must be set on the HOST, not here.\nSee documentation for CH341 host-side setup."
|
||||
fi
|
||||
show_info "Upgrade Complete" "Upgrade completed successfully!\n\nVersion: $current_version → $new_version\n\n✓ Service is running\n✓ Configuration preserved${container_note}"
|
||||
else
|
||||
echo " ✗ Service failed to start"
|
||||
show_error "Upgrade completed but service failed to start!\n\nVersion updated: $current_version → $new_version\n\nCheck logs from the main menu for details."
|
||||
@@ -689,33 +771,41 @@ uninstall_repeater() {
|
||||
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
|
||||
(
|
||||
echo "0"; echo "# Stopping and disabling service..."
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " Uninstalling pyMC Repeater"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
echo ">>> Stopping and disabling service..."
|
||||
systemctl stop "$SERVICE_NAME" 2>/dev/null || true
|
||||
systemctl disable "$SERVICE_NAME" 2>/dev/null || true
|
||||
|
||||
echo "20"; echo "# Backing up configuration..."
|
||||
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
|
||||
fi
|
||||
|
||||
echo "40"; echo "# Removing service files..."
|
||||
echo ">>> Removing service files..."
|
||||
rm -f /etc/systemd/system/pymc-repeater.service
|
||||
systemctl daemon-reload
|
||||
|
||||
echo "60"; echo "# Removing installation..."
|
||||
echo ">>> Removing installation..."
|
||||
rm -rf "$INSTALL_DIR"
|
||||
rm -rf "$CONFIG_DIR"
|
||||
rm -rf "$LOG_DIR"
|
||||
rm -rf /var/lib/pymc_repeater
|
||||
|
||||
echo "80"; echo "# Removing service user..."
|
||||
echo ">>> Removing service user..."
|
||||
if id "$SERVICE_USER" &>/dev/null; then
|
||||
userdel "$SERVICE_USER" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "100"; echo "# Uninstall complete!"
|
||||
) | $DIALOG --backtitle "pyMC Repeater Management" --title "Uninstalling" --gauge "Removing pyMC Repeater..." 8 70 0
|
||||
echo ">>> Removing polkit and sudoers rules..."
|
||||
rm -f /etc/polkit-1/rules.d/10-pymc-repeater.rules
|
||||
rm -f /etc/sudoers.d/pymc-repeater
|
||||
|
||||
echo ">>> Uninstall complete!"
|
||||
|
||||
show_info "Uninstall Complete" "\npyMC Repeater has been completely removed.\n\nConfiguration backup saved to /tmp/\n\nThank you for using pyMC Repeater!"
|
||||
fi
|
||||
@@ -849,7 +939,14 @@ validate_and_update_config() {
|
||||
# - Adds missing keys from the left operand (example config)
|
||||
local temp_merged="${config_file}.merged"
|
||||
|
||||
if "$YQ_CMD" eval-all '. as $item ireduce ({}; . * $item)' "$updated_example" "$config_file" > "$temp_merged" 2>/dev/null; then
|
||||
# Strip comments from user config before merge to prevent comment accumulation.
|
||||
# yq preserves comments from both files, so each upgrade cycle would duplicate
|
||||
# the header and inline comments. We keep only the example's comments.
|
||||
local stripped_user="${config_file}.stripped"
|
||||
"$YQ_CMD" eval '... comments=""' "$config_file" > "$stripped_user" 2>/dev/null || cp "$config_file" "$stripped_user"
|
||||
|
||||
if "$YQ_CMD" eval-all '. as $item ireduce ({}; . * $item)' "$updated_example" "$stripped_user" > "$temp_merged" 2>/dev/null; then
|
||||
rm -f "$stripped_user"
|
||||
# Verify the merged file is valid YAML
|
||||
if "$YQ_CMD" eval '.' "$temp_merged" > /dev/null 2>&1; then
|
||||
mv "$temp_merged" "$config_file"
|
||||
@@ -864,7 +961,7 @@ validate_and_update_config() {
|
||||
fi
|
||||
else
|
||||
echo " ✗ Config merge failed, keeping original"
|
||||
rm -f "$temp_merged"
|
||||
rm -f "$temp_merged" "$stripped_user"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=pymc-repeater
|
||||
|
||||
# Security (relaxed for proper operation)
|
||||
NoNewPrivileges=true
|
||||
# Security (relaxed for service self-restart via sudo)
|
||||
ReadWritePaths=/var/log/pymc_repeater /var/lib/pymc_repeater /etc/pymc_repeater
|
||||
SupplementaryGroups=plugdev dialout
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ keywords = ["mesh", "networking", "lora", "repeater", "daemon", "iot"]
|
||||
|
||||
|
||||
dependencies = [
|
||||
"pymc_core[hardware] @ git+https://github.com/rightup/pyMC_core.git@feat/newRadios",
|
||||
"pymc_core[hardware] @ git+https://github.com/rightup/pyMC_core.git@feat/E22p",
|
||||
"pyyaml>=6.0.0",
|
||||
"cherrypy>=18.0.0",
|
||||
"paho-mqtt>=1.6.0",
|
||||
|
||||
+20
-18
@@ -48,24 +48,8 @@
|
||||
"use_dio3_tcxo": true,
|
||||
"use_dio2_rf": true
|
||||
},
|
||||
"pimesh-1w-usa": {
|
||||
"name": "PiMesh-1W (USA)",
|
||||
"bus_id": 0,
|
||||
"cs_id": 0,
|
||||
"cs_pin": 21,
|
||||
"reset_pin": 18,
|
||||
"busy_pin": 20,
|
||||
"irq_pin": 16,
|
||||
"txen_pin": 13,
|
||||
"rxen_pin": 12,
|
||||
"txled_pin": -1,
|
||||
"rxled_pin": -1,
|
||||
"tx_power": 30,
|
||||
"use_dio3_tcxo": true,
|
||||
"preamble_length": 17
|
||||
},
|
||||
"pimesh-1w-uk": {
|
||||
"name": "PiMesh-1W (UK)",
|
||||
"pimesh-1w-v1": {
|
||||
"name": "PiMesh-1W (V1)",
|
||||
"bus_id": 0,
|
||||
"cs_id": 0,
|
||||
"cs_pin": 21,
|
||||
@@ -80,6 +64,24 @@
|
||||
"use_dio3_tcxo": true,
|
||||
"preamble_length": 17
|
||||
},
|
||||
"pimesh-1w-v2": {
|
||||
"name": "PiMesh-1W (V2)",
|
||||
"bus_id": 0,
|
||||
"cs_id": 0,
|
||||
"cs_pin": 8,
|
||||
"reset_pin": 18,
|
||||
"busy_pin": 5,
|
||||
"irq_pin": 6,
|
||||
"txen_pin": -1,
|
||||
"rxen_pin": -1,
|
||||
"txled_pin": -1,
|
||||
"rxled_pin": -1,
|
||||
"en_pin": 26,
|
||||
"tx_power": 22,
|
||||
"use_dio3_tcxo": true,
|
||||
"use_dio2_rf": true,
|
||||
"preamble_length": 17
|
||||
},
|
||||
"meshadv-mini": {
|
||||
"name": "MeshAdv Mini",
|
||||
"bus_id": 0,
|
||||
|
||||
@@ -247,6 +247,7 @@ def get_radio_for_board(board_config: dict):
|
||||
"rxen_pin": _parse_int(spi_config["rxen_pin"]),
|
||||
"txled_pin": _parse_int(spi_config.get("txled_pin", -1), default=-1),
|
||||
"rxled_pin": _parse_int(spi_config.get("rxled_pin", -1), default=-1),
|
||||
"en_pin": _parse_int(spi_config.get("en_pin", -1), default=-1),
|
||||
"use_dio3_tcxo": spi_config.get("use_dio3_tcxo", False),
|
||||
"dio3_tcxo_voltage": float(spi_config.get("dio3_tcxo_voltage", 1.8)),
|
||||
"use_dio2_rf": spi_config.get("use_dio2_rf", False),
|
||||
|
||||
@@ -491,10 +491,28 @@ class RepeaterDaemon:
|
||||
except Exception as e:
|
||||
logger.debug(f"CH341 reset skipped/failed: {e}")
|
||||
|
||||
@staticmethod
|
||||
def _detect_container() -> bool:
|
||||
"""Detect if running inside an LXC/Docker/systemd-nspawn container."""
|
||||
try:
|
||||
with open("/proc/1/environ", "rb") as f:
|
||||
if b"container=" in f.read():
|
||||
return True
|
||||
except (OSError, PermissionError):
|
||||
pass
|
||||
return os.path.exists("/run/host/container-manager")
|
||||
|
||||
async def run(self):
|
||||
|
||||
logger.info("Repeater daemon started")
|
||||
|
||||
# Warn if running inside a container (udev rules won't work here)
|
||||
if os.path.exists("/.dockerenv") or os.environ.get("container") or self._detect_container():
|
||||
logger.warning(
|
||||
"Container environment detected. "
|
||||
"USB device udev rules must be configured on the HOST, not inside this container."
|
||||
)
|
||||
|
||||
try:
|
||||
await self.initialize()
|
||||
|
||||
|
||||
@@ -13,12 +13,13 @@ def restart_service() -> Tuple[bool, str]:
|
||||
"""
|
||||
Restart the pymc-repeater service via systemctl.
|
||||
|
||||
Uses polkit for authentication (requires proper polkit rules configured).
|
||||
NoNewPrivileges systemd flag prevents sudo from working.
|
||||
Tries polkit-based restart first (plain systemctl), then falls back
|
||||
to sudo-based restart (requires sudoers.d rule installed by manage.sh).
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (success, message)
|
||||
"""
|
||||
# Try polkit-based restart first (works on bare metal / VMs with polkit running)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['systemctl', 'restart', 'pymc-repeater'],
|
||||
@@ -28,19 +29,49 @@ def restart_service() -> Tuple[bool, str]:
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info("Service restart command executed successfully")
|
||||
logger.info("Service restart via polkit succeeded")
|
||||
return True, "Service restart initiated"
|
||||
|
||||
stderr = result.stderr or ""
|
||||
if "Access denied" in stderr or "authorization" in stderr.lower():
|
||||
logger.info("Polkit denied restart, trying sudo fallback...")
|
||||
else:
|
||||
error_msg = result.stderr or "Unknown error"
|
||||
logger.error(f"Service restart failed: {error_msg}")
|
||||
return False, f"Restart failed: {error_msg}"
|
||||
# Some other error, still try sudo
|
||||
logger.warning(f"systemctl restart failed ({result.returncode}): {stderr.strip()}")
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
# Timeout likely means it's restarting - that's success
|
||||
logger.warning("Service restart command timed out (service may be restarting)")
|
||||
return True, "Service restart initiated (timeout - likely restarting)"
|
||||
except FileNotFoundError:
|
||||
logger.error("systemctl not found")
|
||||
return False, "systemctl not available"
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing restart command: {e}")
|
||||
logger.warning(f"Polkit restart attempt failed: {e}")
|
||||
|
||||
# Fallback: use sudo (requires /etc/sudoers.d/pymc-repeater rule)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['sudo', '--non-interactive', 'systemctl', 'restart', 'pymc-repeater'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info("Service restart via sudo succeeded")
|
||||
return True, "Service restart initiated"
|
||||
else:
|
||||
error_msg = result.stderr or "Unknown error"
|
||||
logger.error(f"Service restart via sudo failed: {error_msg}")
|
||||
return False, f"Restart failed: {error_msg}"
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("Sudo restart timed out (service likely restarting)")
|
||||
return True, "Service restart initiated (timeout - likely restarting)"
|
||||
except FileNotFoundError:
|
||||
logger.error("sudo not found - cannot restart service")
|
||||
return False, "Neither polkit nor sudo available for service restart"
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing sudo restart: {e}")
|
||||
return False, f"Restart command failed: {str(e)}"
|
||||
|
||||
@@ -491,8 +491,8 @@ class APIEndpoints:
|
||||
import time
|
||||
time.sleep(2) # Give time for response to be sent
|
||||
try:
|
||||
# Use systemctl without sudo - polkit rules allow the repeater user to restart the service
|
||||
subprocess.run(['systemctl', 'restart', 'pymc-repeater'], check=False)
|
||||
from repeater.service_utils import restart_service
|
||||
restart_service()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to restart service: {e}")
|
||||
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env bash
|
||||
# pyMC Repeater - Proxmox LXC Installer
|
||||
# Creates an LXC container with USB passthrough and installs pyMC Repeater
|
||||
#
|
||||
# Usage (run on the Proxmox host):
|
||||
# bash -c "$(curl -fsSL https://raw.githubusercontent.com/rightup/pyMC_Repeater/main/scripts/proxmox-install.sh)"
|
||||
#
|
||||
# License: MIT
|
||||
# Source: https://github.com/rightup/pyMC_Repeater
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Defaults ───────────────────────────────────────────────────────────────
|
||||
REPO="https://github.com/rightup/pyMC_Repeater.git"
|
||||
BRANCH="feat/E22p"
|
||||
CT_TEMPLATE="debian-12-standard"
|
||||
CT_RAM=1024
|
||||
CT_SWAP=512
|
||||
CT_DISK=4
|
||||
CT_CORES=2
|
||||
CT_HOSTNAME="pymc-repeater"
|
||||
CT_BRIDGE="vmbr0"
|
||||
CT_STORAGE="local-lvm"
|
||||
CT_TEMPLATE_STORAGE="local"
|
||||
CH341_VID="1a86"
|
||||
CH341_PID="5512"
|
||||
|
||||
# ── Colors ─────────────────────────────────────────────────────────────────
|
||||
RD="\033[01;31m" GN="\033[1;92m" YW="\033[33m" BL="\033[36m" BLD="\033[1m" CL="\033[m"
|
||||
|
||||
msg_info() { echo -e " ${BL}ℹ${CL} ${1}"; }
|
||||
msg_ok() { echo -e " ${GN}✓${CL} ${1}"; }
|
||||
msg_warn() { echo -e " ${YW}⚠${CL} ${1}"; }
|
||||
msg_error() { echo -e " ${RD}✗${CL} ${1}"; }
|
||||
|
||||
header() {
|
||||
clear
|
||||
echo -e "${BLD}"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " pyMC Repeater - Proxmox LXC Installer"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo -e "${CL}"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
local exit_code=$?
|
||||
if [ $exit_code -ne 0 ] && [ -n "${CTID:-}" ] && pct status "$CTID" &>/dev/null; then
|
||||
echo ""
|
||||
read -p " Delete the failed container ${CTID}? [y/N]: " -r
|
||||
if [[ "$REPLY" =~ ^[Yy]$ ]]; then
|
||||
pct stop "$CTID" 2>/dev/null || true
|
||||
pct destroy "$CTID" 2>/dev/null || true
|
||||
msg_ok "Container ${CTID} removed"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# ── Preflight checks ──────────────────────────────────────────────────────
|
||||
header
|
||||
|
||||
if ! command -v pct &>/dev/null; then
|
||||
msg_error "This script must be run on a Proxmox VE host."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
msg_error "Please run as root"
|
||||
exit 1
|
||||
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
|
||||
|
||||
# ── Interactive settings ──────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BLD}Container Settings${CL} (press Enter for defaults):"
|
||||
echo ""
|
||||
|
||||
read -p " Hostname [${CT_HOSTNAME}]: " -r input; CT_HOSTNAME="${input:-$CT_HOSTNAME}"
|
||||
read -p " RAM in MB [${CT_RAM}]: " -r input; CT_RAM="${input:-$CT_RAM}"
|
||||
read -p " Disk in GB [${CT_DISK}]: " -r input; CT_DISK="${input:-$CT_DISK}"
|
||||
read -p " CPU cores [${CT_CORES}]: " -r input; CT_CORES="${input:-$CT_CORES}"
|
||||
read -p " Bridge [${CT_BRIDGE}]: " -r input; CT_BRIDGE="${input:-$CT_BRIDGE}"
|
||||
|
||||
AVAILABLE_STORAGES=$(pvesm status -content rootdir 2>/dev/null | awk 'NR>1 {print $1}' || echo "local-lvm")
|
||||
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
|
||||
CT_PASSWORD="${CT_PASSWORD:-pymc}"
|
||||
|
||||
# ── Get next CTID ─────────────────────────────────────────────────────────
|
||||
CTID=$(pvesh get /cluster/nextid)
|
||||
|
||||
# ── 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 " Mode: privileged (required for USB passthrough)"
|
||||
echo ""
|
||||
read -p " Proceed? [Y/n]: " -r
|
||||
[[ "${REPLY:-Y}" =~ ^[Nn]$ ]] && { msg_warn "Aborted"; exit 0; }
|
||||
|
||||
# ── 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}')
|
||||
[ -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" || \
|
||||
pveam download "$CT_TEMPLATE_STORAGE" "$TEMPLATE_FILE"
|
||||
msg_ok "Template ready"
|
||||
|
||||
# ── Create container ──────────────────────────────────────────────────────
|
||||
msg_info "Creating LXC container ${CTID}..."
|
||||
pct create "$CTID" "${CT_TEMPLATE_STORAGE}:vztmpl/${TEMPLATE_FILE}" \
|
||||
--hostname "$CT_HOSTNAME" \
|
||||
--memory "$CT_RAM" \
|
||||
--swap "$CT_SWAP" \
|
||||
--cores "$CT_CORES" \
|
||||
--rootfs "${CT_STORAGE}:${CT_DISK}" \
|
||||
--net0 "name=eth0,bridge=${CT_BRIDGE},ip=dhcp" \
|
||||
--unprivileged 0 \
|
||||
--features nesting=1 \
|
||||
--onboot 1 \
|
||||
--start 0 \
|
||||
--password "$CT_PASSWORD" \
|
||||
--ostype debian
|
||||
msg_ok "Container created"
|
||||
|
||||
# ── USB passthrough ───────────────────────────────────────────────────────
|
||||
msg_info "Configuring USB passthrough..."
|
||||
cat >> "/etc/pve/lxc/${CTID}.conf" <<'EOF'
|
||||
|
||||
# CH341 USB passthrough for pyMC 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
|
||||
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"
|
||||
|
||||
# ── Start container & wait for network ────────────────────────────────────
|
||||
msg_info "Starting container..."
|
||||
pct start "$CTID"
|
||||
sleep 3
|
||||
for _ in $(seq 1 30); do
|
||||
pct exec "$CTID" -- ping -c1 -W1 8.8.8.8 &>/dev/null && break
|
||||
sleep 1
|
||||
done
|
||||
msg_ok "Container running with network"
|
||||
|
||||
# ── Bootstrap: install git, clone repo ────────────────────────────────────
|
||||
msg_info "Installing git inside container..."
|
||||
pct exec "$CTID" -- bash -c "
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Fix locale warnings
|
||||
apt-get update -qq
|
||||
apt-get install -y locales >/dev/null 2>&1
|
||||
sed -i 's/# en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen
|
||||
locale-gen >/dev/null 2>&1
|
||||
echo 'LANG=en_US.UTF-8' > /etc/default/locale
|
||||
|
||||
apt-get install -y git whiptail >/dev/null 2>&1
|
||||
|
||||
# Enable auto-login on console (no password prompt in Proxmox web console)
|
||||
mkdir -p /etc/systemd/system/container-getty@1.service.d
|
||||
cat > /etc/systemd/system/container-getty@1.service.d/override.conf <<'AUTOLOGIN'
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM
|
||||
AUTOLOGIN
|
||||
systemctl daemon-reload
|
||||
|
||||
# Login banner with system info
|
||||
cat > /etc/profile.d/pymc-motd.sh <<'MOTD'
|
||||
#!/bin/sh
|
||||
HOSTNAME=\$(hostname)
|
||||
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/rightup/pyMC_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 \"\"
|
||||
MOTD
|
||||
chmod +x /etc/profile.d/pymc-motd.sh
|
||||
"
|
||||
msg_ok "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_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
|
||||
# Set radio type to CH341
|
||||
sed -i 's/^radio_type: sx1262$/radio_type: sx1262_ch341/' /etc/pymc_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
|
||||
# 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
|
||||
fi
|
||||
"
|
||||
|
||||
# ── Run manage.sh install ─────────────────────────────────────────────────
|
||||
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"
|
||||
echo ""
|
||||
msg_ok "manage.sh install completed"
|
||||
|
||||
# ── Get container IP ──────────────────────────────────────────────────────
|
||||
sleep 2
|
||||
CT_IP=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}')
|
||||
|
||||
# ── Done ──────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BLD}"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " ✓ pyMC Repeater Installation Complete!"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo -e "${CL}"
|
||||
echo -e " Container: ${GN}${CTID}${CL} (${CT_HOSTNAME})"
|
||||
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 ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
Reference in New Issue
Block a user