#!/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/newRadios" 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:-}: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 "═══════════════════════════════════════════════════════════════"