diff --git a/README.md b/README.md index a94ac7f..51ca23a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,15 @@ The script will: 2. ✓ Install system dependencies (BlueZ, dbus) 3. ✓ Install Python packages in the correct environment 4. ✓ Copy BLE interface files to `~/.reticulum/interfaces/` (or custom config directory if specified) -5. ✓ Optionally set up Bluetooth permissions +5. ✓ Enable BlueZ experimental mode (required for proper BLE connectivity) +6. ✓ Optionally set up Bluetooth permissions + +**BlueZ Experimental Mode**: The installer automatically enables BlueZ experimental mode, which is required for proper BLE connectivity. This allows the BLE interface to use LE-specific connection methods instead of defaulting to Classic Bluetooth (BR/EDR), preventing connection errors like "br-connection-profile-unavailable". + +To skip this configuration (not recommended): +```bash +./install.sh --skip-experimental +``` ### Option B: Manual Installation @@ -100,7 +108,35 @@ mkdir -p ~/.reticulum/interfaces cp src/RNS/Interfaces/BLE*.py ~/.reticulum/interfaces/ ``` -#### 4. Grant Bluetooth Permissions +#### 4. Enable BlueZ Experimental Mode (Required) + +BlueZ experimental mode is required for proper BLE connectivity. Without it, BlueZ may attempt Classic Bluetooth (BR/EDR) connections instead of BLE (LE) connections, causing connection failures. + +Enable experimental mode (BlueZ >= 5.49): +```bash +sudo systemctl edit bluetooth +``` + +Add these lines: +``` +[Service] +ExecStart= +ExecStart=/usr/lib/bluetooth/bluetoothd -E +``` + +Save and restart Bluetooth: +```bash +sudo systemctl daemon-reload +sudo systemctl restart bluetooth +``` + +Verify it's enabled: +```bash +ps aux | grep bluetoothd +# Should show: /usr/lib/bluetooth/bluetoothd -E +``` + +#### 5. Grant Bluetooth Permissions For non-root operation: ```bash @@ -199,9 +235,21 @@ python ble_minimal_test.py test - Set `enable_peripheral = no` to disable peripheral mode ### Permission denied errors -- Grant capabilities to Python (see Installation → Manual Installation → step 4) +- Grant capabilities to Python (see Installation → Manual Installation → step 5) - Or run with sudo: `sudo rnsd` (not recommended) +### BR/EDR connection errors (br-connection-profile-unavailable, ProfileUnavailable) +These errors occur when BlueZ attempts Classic Bluetooth (BR/EDR) connections instead of BLE (LE) connections. This is the most common BLE connection issue. + +**Symptoms:** +- Devices connect then immediately disconnect +- Errors: "br-connection-profile-unavailable", "ProfileUnavailable" +- "ConnectDevice() unavailable" in logs +- Devices get blacklisted after multiple failures + +**Solution:** +Enable BlueZ experimental mode (see Installation → Manual Installation → step 4). If you used the automated installer, re-run it without `--skip-experimental`. + ## Architecture The BLE interface consists of four main components: diff --git a/examples/config_example.toml b/examples/config_example.toml index f8482b9..823fd75 100644 --- a/examples/config_example.toml +++ b/examples/config_example.toml @@ -253,6 +253,19 @@ power_mode = balanced # - Restart Bluetooth service (Linux: sudo systemctl restart bluetooth) # - Check device_name is not too long (max ~20 characters) # +# 6. BR/EDR connection errors (br-connection-profile-unavailable, ProfileUnavailable): +# IMPORTANT: This is the most common BLE connection issue! +# - Symptoms: Devices connect then immediately disconnect, "ConnectDevice() unavailable" in logs +# - Cause: BlueZ attempting Classic Bluetooth (BR/EDR) instead of BLE (LE) connections +# - Solution: Enable BlueZ experimental mode (required for proper BLE connectivity) +# Linux: sudo systemctl edit bluetooth +# Add: [Service] +# ExecStart= +# ExecStart=/usr/lib/bluetooth/bluetoothd -E +# Then: sudo systemctl daemon-reload && sudo systemctl restart bluetooth +# - If you used install.sh, it should have enabled this automatically +# - Verify: ps aux | grep bluetoothd (should show -E flag) +# # For more troubleshooting, see README.md # ============================================================================ diff --git a/install.sh b/install.sh index 0db1189..f3a3520 100755 --- a/install.sh +++ b/install.sh @@ -51,19 +51,33 @@ pip_install() { # Parse command line arguments CUSTOM_CONFIG_DIR="" +SKIP_BLUEZ_EXPERIMENTAL=false +SKIP_BT_PERMISSIONS=false while [[ $# -gt 0 ]]; do case $1 in --config) CUSTOM_CONFIG_DIR="$2" shift 2 ;; + --skip-experimental) + SKIP_BLUEZ_EXPERIMENTAL=true + shift + ;; + --skip-bt-permissions) + SKIP_BT_PERMISSIONS=true + shift + ;; -h|--help) - echo "Usage: $0 [--config CONFIG_DIR]" + echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" - echo " --config CONFIG_DIR Install to custom Reticulum config directory" - echo " (default: ~/.reticulum)" - echo " -h, --help Show this help message" + echo " --config CONFIG_DIR Install to custom Reticulum config directory" + echo " (default: ~/.reticulum)" + echo " --skip-experimental Skip enabling BlueZ experimental mode" + echo " WARNING: May cause BLE connection failures" + echo " --skip-bt-permissions Skip granting Bluetooth permissions" + echo " (you will need to run rnsd with sudo)" + echo " -h, --help Show this help message" exit 0 ;; *) @@ -151,6 +165,11 @@ RNS_VENV="" RNS_PYTHON="" INSTALL_MODE="" +# Add user's local bin to PATH if it exists (common pip install location) +if [ -d "$HOME/.local/bin" ]; then + export PATH="$HOME/.local/bin:$PATH" +fi + # Check if rnsd is available if command -v rnsd &> /dev/null; then print_success "Found rnsd command" @@ -190,16 +209,50 @@ else # Install Reticulum using our pip helper function pip_install rns - # Verify installation + # Add user's local bin to PATH (pip may install there) + export PATH="$HOME/.local/bin:$PATH" + + # Verify installation (check multiple locations and Python import) if command -v rnsd &> /dev/null; then print_success "Reticulum installed successfully" INSTALL_MODE="system" RNS_PYTHON="python3" + elif [ -f "$HOME/.local/bin/rnsd" ]; then + print_success "Reticulum installed successfully (user installation)" + print_info "rnsd location: $HOME/.local/bin/rnsd" + INSTALL_MODE="system" + RNS_PYTHON="python3" + # Ensure it's executable + chmod +x "$HOME/.local/bin/rnsd" 2>/dev/null || true + + # Automatically add ~/.local/bin to PATH if not already there + if [ -f "$HOME/.bashrc" ]; then + if ! grep -q '.local/bin' "$HOME/.bashrc" 2>/dev/null; then + print_info "Adding ~/.local/bin to PATH in ~/.bashrc..." + echo '' >> "$HOME/.bashrc" + echo '# Added by Reticulum BLE installer' >> "$HOME/.bashrc" + echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc" + print_success "Added ~/.local/bin to PATH in ~/.bashrc" + print_warning "Reload your shell to use rnsd command:" + echo " source ~/.bashrc" + echo " # Or open a new terminal" + echo + else + print_info "~/.local/bin already in PATH configuration" + fi + fi + elif python3 -c "import RNS" 2>/dev/null; then + print_warning "Reticulum Python package installed, but rnsd command not found in PATH" + print_info "You may need to add ~/.local/bin to your PATH" + echo " Add to ~/.bashrc: export PATH=\"\$HOME/.local/bin:\$PATH\"" + INSTALL_MODE="system" + RNS_PYTHON="python3" else print_error "Reticulum installation failed" echo echo "Please try installing manually:" echo " pip install rns" + echo " # Then add to PATH: export PATH=\"\$HOME/.local/bin:\$PATH\"" echo "Or visit: https://reticulum.network" exit 1 fi @@ -213,20 +266,20 @@ print_header "Installing System Dependencies" if command -v apt-get &> /dev/null; then # Debian/Ubuntu/Raspberry Pi OS print_info "Detected Debian/Ubuntu-based system" - echo "Installing: python3-pip python3-gi python3-dbus python3-cairo bluez" + echo "Installing: python3-pip python3-gi python3-dbus python3-cairo bluez libcap2-bin" # Use sudo only if not running as root if [ "$EUID" -eq 0 ]; then apt-get update - apt-get install -y python3-pip python3-gi python3-dbus python3-cairo bluez + apt-get install -y python3-pip python3-gi python3-dbus python3-cairo bluez libcap2-bin else sudo apt-get update - sudo apt-get install -y python3-pip python3-gi python3-dbus python3-cairo bluez + sudo apt-get install -y python3-pip python3-gi python3-dbus python3-cairo bluez libcap2-bin fi print_success "System dependencies installed (using pre-compiled system packages)" elif command -v pacman &> /dev/null; then # Arch Linux print_info "Detected Arch Linux" - echo "Installing: base-devel gobject-introspection python-pip python-dbus python-cairo bluez bluez-utils" + echo "Installing: base-devel gobject-introspection python-pip python-dbus python-cairo bluez bluez-utils libcap" print_warning "Note: PyGObject will be compiled from pip due to version requirements (bluezero needs <3.52.0, Arch has 3.54.5)" # Use sudo only if not running as root if [ "$EUID" -eq 0 ]; then @@ -234,10 +287,10 @@ elif command -v pacman &> /dev/null; then pacman -Sy --noconfirm # Skip python-gobject to avoid version conflict - pip will compile PyGObject # gobject-introspection provides dev files needed for PyGObject compilation - pacman -S --needed --noconfirm base-devel gobject-introspection python-pip python-dbus python-cairo bluez bluez-utils + pacman -S --needed --noconfirm base-devel gobject-introspection python-pip python-dbus python-cairo bluez bluez-utils libcap else sudo pacman -Sy --noconfirm - sudo pacman -S --needed --noconfirm base-devel gobject-introspection python-pip python-dbus python-cairo bluez bluez-utils + sudo pacman -S --needed --noconfirm base-devel gobject-introspection python-pip python-dbus python-cairo bluez bluez-utils libcap fi print_success "System dependencies installed (PyGObject will be compiled from pip)" else @@ -329,27 +382,241 @@ echo # Step 5: Bluetooth permissions print_header "Bluetooth Permissions" -print_info "For BLE to work without root, Python needs network capabilities" -echo - -PYTHON_PATH=$(which python3) - -echo "The following command will grant capabilities to Python:" -echo " sudo setcap 'cap_net_raw,cap_net_admin+eip' $PYTHON_PATH" -echo - -read -p "Grant Bluetooth permissions now? (y/N) " -n 1 -r -echo -if [[ $REPLY =~ ^[Yy]$ ]]; then - sudo setcap 'cap_net_raw,cap_net_admin+eip' "$PYTHON_PATH" - print_success "Bluetooth permissions granted" +# Check if user wants to skip Bluetooth permissions +if [ "$SKIP_BT_PERMISSIONS" = true ]; then + print_warning "Skipping Bluetooth permissions (--skip-bt-permissions flag)" + print_info "You will need to run rnsd with sudo, or grant permissions manually:" + echo " sudo setcap 'cap_net_raw,cap_net_admin+eip' \$(readlink -f \$(which python3))" + echo else - print_warning "Skipped. You may need to run rnsd with sudo" - echo " To grant permissions later, run:" - echo " sudo setcap 'cap_net_raw,cap_net_admin+eip' \$(which python3)" + print_info "For BLE to work without root, Python needs network capabilities" + print_info "Automatically granting capabilities to Python..." + echo + + # Check if setcap is available + if ! command -v setcap &> /dev/null; then + print_error "setcap command not found" + print_info "Installing libcap2-bin package..." + + if command -v apt-get &> /dev/null; then + if [ "$EUID" -eq 0 ]; then + apt-get install -y libcap2-bin + else + sudo apt-get install -y libcap2-bin + fi + elif command -v pacman &> /dev/null; then + if [ "$EUID" -eq 0 ]; then + pacman -S --needed --noconfirm libcap + else + sudo pacman -S --needed --noconfirm libcap + fi + else + print_error "Could not install libcap2-bin/libcap automatically" + print_warning "Please install manually and re-run the installer" + echo + fi + + # Verify setcap is now available + if ! command -v setcap &> /dev/null; then + print_error "setcap still not available after installation attempt" + print_warning "You will need to run rnsd with sudo, or manually install libcap2-bin" + echo + else + print_success "setcap installed successfully" + echo + fi + fi + + if command -v setcap &> /dev/null; then + # Get python3 path + PYTHON_PATH=$(which python3) + print_info "Detected Python at: $PYTHON_PATH" + + # Check if it's a symlink and resolve it + if [ -L "$PYTHON_PATH" ]; then + print_warning "Python3 is a symlink (setcap requires the actual binary)" + PYTHON_REAL=$(readlink -f "$PYTHON_PATH") + print_info "Resolved to actual binary: $PYTHON_REAL" + + # Verify the resolved path exists and is a file + if [ ! -f "$PYTHON_REAL" ]; then + print_error "Could not resolve Python binary: $PYTHON_REAL" + print_warning "You may need to run rnsd with sudo" + echo + elif [ -L "$PYTHON_REAL" ]; then + print_error "Python path is still a symlink after resolution" + print_warning "Manual intervention required - you may need to run rnsd with sudo" + echo + else + PYTHON_PATH="$PYTHON_REAL" + fi + fi + + # Grant capabilities if we have a valid path + if [ -f "$PYTHON_PATH" ] && [ ! -L "$PYTHON_PATH" ]; then + print_info "Granting capabilities to: $PYTHON_PATH" + sudo setcap 'cap_net_raw,cap_net_admin+eip' "$PYTHON_PATH" + + if [ $? -eq 0 ]; then + print_success "Bluetooth permissions granted successfully" + # Verify capabilities were actually set + if command -v getcap &> /dev/null; then + CAPS=$(getcap "$PYTHON_PATH" 2>/dev/null) + if [ -n "$CAPS" ]; then + print_info "Verified: $CAPS" + fi + fi + else + print_error "Failed to grant Bluetooth permissions" + print_warning "You may need to run rnsd with sudo" + echo + print_info "To grant permissions manually, run:" + echo " sudo setcap 'cap_net_raw,cap_net_admin+eip' $PYTHON_PATH" + fi + else + print_error "Could not determine valid Python binary path" + print_warning "You may need to run rnsd with sudo" + fi + else + print_error "setcap command not available, cannot grant permissions" + print_warning "You will need to run rnsd with sudo" + fi + + echo fi -echo +# Step 5A: BlueZ Experimental Mode +print_header "BlueZ Experimental Mode" + +# Check if bluetoothctl is available +if ! command -v bluetoothctl &> /dev/null; then + print_warning "bluetoothctl not found - BlueZ may not be installed" + print_info "BLE interface requires BlueZ for Bluetooth functionality" + echo +elif ! command -v systemctl &> /dev/null; then + print_warning "systemctl not found - cannot configure BlueZ experimental mode" + print_info "This system may not use systemd, or this may be a container environment" + echo +else + # Detect BlueZ version + BLUEZ_VERSION=$(bluetoothctl --version 2>/dev/null | grep -oP '\d+\.\d+' | head -1) + + if [ -z "$BLUEZ_VERSION" ]; then + print_warning "Could not detect BlueZ version" + echo + else + print_info "Detected BlueZ version: $BLUEZ_VERSION" + + # Parse version to check if >= 5.49 + VERSION_MAJOR=$(echo "$BLUEZ_VERSION" | cut -d. -f1) + VERSION_MINOR=$(echo "$BLUEZ_VERSION" | cut -d. -f2) + + if [ "$VERSION_MAJOR" -lt 5 ] || ([ "$VERSION_MAJOR" -eq 5 ] && [ "$VERSION_MINOR" -lt 49 ]); then + print_warning "BlueZ version $BLUEZ_VERSION does not support experimental mode (requires >= 5.49)" + print_info "BLE interface will work with standard connection methods" + print_info "Consider upgrading BlueZ for full BLE compatibility" + echo + else + # Check if experimental mode is already enabled + if systemctl status bluetooth 2>/dev/null | grep -q -- "-E\|--experimental"; then + print_success "BlueZ experimental mode already enabled" + echo + elif [ "$SKIP_BLUEZ_EXPERIMENTAL" = true ]; then + # User explicitly skipped experimental mode - show strong warning + echo + print_error "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + print_error "WARNING: Skipping BlueZ experimental mode" + print_error "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo + echo -e "${RED}BLE connections may fail with errors like:${NC}" + echo " • br-connection-profile-unavailable" + echo " • ProfileUnavailable" + echo " • Immediate disconnections after pairing" + echo + echo -e "${RED}Your BLE interface may attempt Classic Bluetooth (BR/EDR)${NC}" + echo -e "${RED}connections instead of BLE (LE) connections.${NC}" + echo + echo -e "${YELLOW}This is NOT RECOMMENDED unless you have a specific reason.${NC}" + echo + echo "To enable experimental mode later:" + echo " 1. sudo systemctl edit bluetooth" + echo " 2. Add these lines:" + echo " [Service]" + echo " ExecStart=" + echo " ExecStart=/usr/lib/bluetooth/bluetoothd -E" + echo " 3. sudo systemctl daemon-reload" + echo " 4. sudo systemctl restart bluetooth" + echo + print_error "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo + else + # Enable experimental mode by default + print_info "Enabling BlueZ experimental mode (required for proper BLE connectivity)" + print_info "This enables LE-specific connection methods (ConnectDevice API)" + echo + + # Find bluetoothd path + BLUETOOTHD_PATH="" + for path in /usr/lib/bluetooth/bluetoothd /usr/libexec/bluetooth/bluetoothd; do + if [ -f "$path" ]; then + BLUETOOTHD_PATH="$path" + break + fi + done + + if [ -z "$BLUETOOTHD_PATH" ]; then + print_error "Could not find bluetoothd binary" + print_warning "Tried: /usr/lib/bluetooth/bluetoothd, /usr/libexec/bluetooth/bluetoothd" + echo + else + print_info "Using bluetoothd at: $BLUETOOTHD_PATH" + + # Create systemd override + print_info "Creating systemd override for bluetooth service..." + + # Use sudo only if not running as root + if [ "$EUID" -eq 0 ]; then + # Running as root - no sudo needed + mkdir -p /etc/systemd/system/bluetooth.service.d + cat > /etc/systemd/system/bluetooth.service.d/override.conf < /dev/null </dev/null; then + echo "3. Add ~/.local/bin to your PATH (for rnsd command):" + echo " echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.bashrc" + echo " source ~/.bashrc" + echo + STEP_NUM=4 + fi +fi + +echo "$STEP_NUM. Start Reticulum:" if [ -n "$CUSTOM_CONFIG_DIR" ]; then echo " rnsd --config $CONFIG_DIR --verbose" else echo " rnsd --verbose" fi echo -echo "4. Verify the interface is running:" +STEP_NUM=$((STEP_NUM + 1)) +echo "$STEP_NUM. Verify the interface is running:" echo " rnstatus" echo diff --git a/tests/test_installer.sh b/tests/test_installer.sh index 577e801..316c744 100755 --- a/tests/test_installer.sh +++ b/tests/test_installer.sh @@ -52,6 +52,7 @@ chmod +x install.sh mkdir -p /tmp/test-config # Run non-interactively (answer 'n' to bluetooth permissions prompt) +# Note: BlueZ experimental mode will be enabled by default (no prompt) ./install.sh --config /tmp/test-config < /dev/null && command -v bluetoothctl &> /dev/null; then + # systemctl is available - check if experimental mode was configured + if [ -f /etc/systemd/system/bluetooth.service.d/override.conf ]; then + echo " ✓ Systemd override file created" + if grep -q -- "-E" /etc/systemd/system/bluetooth.service.d/override.conf; then + echo " ✓ Experimental mode flag (-E) configured" + else + echo " ⚠ WARNING: Override file exists but -E flag not found" + fi + else + # No override file - may have been already enabled or not supported + if systemctl status bluetooth 2>/dev/null | grep -q -- "-E\|--experimental"; then + echo " ✓ Experimental mode already enabled (not via installer)" + else + echo " ⚠ WARNING: Experimental mode not configured" + fi + fi +else + # systemctl or bluetoothctl not available (container environment) + echo " ℹ Systemd/BlueZ not available (container environment - OK)" +fi + +echo "" + +# Test --skip-experimental flag +echo "Testing --skip-experimental flag..." +cd "$(dirname "$0")/.." +# Run with --skip-experimental to verify it doesn't fail +./install.sh --config /tmp/test-config-skip --skip-experimental > /tmp/skip-test.log 2>&1 <