Merge pull request #6 from torlando-tech/fix/custom-config-directory-with-installer
fix: support custom config directories (runtime + installer)
This commit is contained in:
commit
7363b90e88
5 changed files with 208 additions and 6 deletions
|
|
@ -33,13 +33,16 @@ git clone https://github.com/torlando-tech/ble-reticulum.git
|
|||
cd ble-reticulum
|
||||
chmod +x install.sh
|
||||
./install.sh
|
||||
|
||||
# For custom config directory:
|
||||
# ./install.sh --config /path/to/custom/config
|
||||
```
|
||||
|
||||
The script will:
|
||||
1. ✓ Detect if Reticulum is in a venv or system-wide
|
||||
2. ✓ Install system dependencies (BlueZ, dbus)
|
||||
3. ✓ Install Python packages in the correct environment
|
||||
4. ✓ Copy BLE interface files to `~/.reticulum/interfaces/`
|
||||
4. ✓ Copy BLE interface files to `~/.reticulum/interfaces/` (or custom config directory if specified)
|
||||
5. ✓ Optionally set up Bluetooth permissions
|
||||
|
||||
### Option B: Manual Installation
|
||||
|
|
@ -113,6 +116,8 @@ Add the BLE interface to your Reticulum configuration (`~/.reticulum/config`):
|
|||
|
||||
For detailed configuration options, see [`examples/config_example.toml`](examples/config_example.toml).
|
||||
|
||||
**Custom Config Directory**: If you use a custom Reticulum config directory with `--config`, the BLE interface will automatically use that directory to find its companion modules. No additional configuration needed!
|
||||
|
||||
### 2. Start Reticulum
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
# Example Reticulum configuration with BLE interface
|
||||
# Place this in ~/.reticulum/config or /etc/reticulum/config
|
||||
#
|
||||
# Note: If you use a custom config directory with `rnsd --config /custom/path`,
|
||||
# the BLE interface will automatically detect and use that location.
|
||||
|
||||
[reticulum]
|
||||
enable_transport = No
|
||||
|
|
|
|||
45
install.sh
45
install.sh
|
|
@ -34,6 +34,31 @@ print_info() {
|
|||
echo -e "${BLUE}ℹ${NC} $1"
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
CUSTOM_CONFIG_DIR=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--config)
|
||||
CUSTOM_CONFIG_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
echo "Usage: $0 [--config CONFIG_DIR]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --config CONFIG_DIR Install to custom Reticulum config directory"
|
||||
echo " (default: ~/.reticulum)"
|
||||
echo " -h, --help Show this help message"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
echo "Use --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check if running on Linux
|
||||
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
|
||||
print_error "This interface only works on Linux (requires BlueZ)"
|
||||
|
|
@ -159,7 +184,17 @@ echo
|
|||
print_header "Installing BLE Interface Files"
|
||||
|
||||
# Determine where to copy files
|
||||
INTERFACES_DIR="$HOME/.reticulum/interfaces"
|
||||
if [ -n "$CUSTOM_CONFIG_DIR" ]; then
|
||||
# Use custom config directory if specified
|
||||
CONFIG_DIR="$CUSTOM_CONFIG_DIR"
|
||||
print_info "Using custom config directory: $CONFIG_DIR"
|
||||
else
|
||||
# Default to ~/.reticulum
|
||||
CONFIG_DIR="$HOME/.reticulum"
|
||||
print_info "Using default config directory: $CONFIG_DIR"
|
||||
fi
|
||||
|
||||
INTERFACES_DIR="$CONFIG_DIR/interfaces"
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
mkdir -p "$INTERFACES_DIR"
|
||||
|
|
@ -209,7 +244,7 @@ echo
|
|||
# Step 6: Configuration
|
||||
print_header "Configuration"
|
||||
|
||||
CONFIG_FILE="$HOME/.reticulum/config"
|
||||
CONFIG_FILE="$CONFIG_DIR/config"
|
||||
|
||||
print_info "Next steps:"
|
||||
echo
|
||||
|
|
@ -230,7 +265,11 @@ echo
|
|||
echo "2. See examples/config_example.toml for all configuration options"
|
||||
echo
|
||||
echo "3. Start Reticulum:"
|
||||
echo " rnsd --verbose"
|
||||
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:"
|
||||
echo " rnstatus"
|
||||
|
|
|
|||
|
|
@ -49,8 +49,18 @@ try:
|
|||
_interface_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
except NameError:
|
||||
# __file__ doesn't exist when loaded via exec() by Reticulum
|
||||
# Use the default external interface directory
|
||||
_interface_dir = os.path.expanduser("~/.reticulum/interfaces")
|
||||
# Try to get the config directory from RNS
|
||||
_interface_dir = None
|
||||
try:
|
||||
import RNS
|
||||
if hasattr(RNS.Reticulum, 'configdir') and RNS.Reticulum.configdir:
|
||||
_interface_dir = os.path.join(RNS.Reticulum.configdir, "interfaces")
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
# Fall back to default if we couldn't get it from RNS
|
||||
if _interface_dir is None:
|
||||
_interface_dir = os.path.expanduser("~/.reticulum/interfaces")
|
||||
|
||||
if _interface_dir not in sys.path:
|
||||
sys.path.insert(0, _interface_dir)
|
||||
|
|
|
|||
145
tests/test_config_directory.py
Normal file
145
tests/test_config_directory.py
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
"""
|
||||
Tests for config directory resolution in BLEInterface.
|
||||
|
||||
This test verifies that the BLE interface correctly resolves the interface
|
||||
directory based on RNS.Reticulum.configdir when available, and falls back
|
||||
to the default path otherwise.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
|
||||
class TestConfigDirectoryResolution(unittest.TestCase):
|
||||
"""Test cases for config directory resolution in BLEInterface."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
# Remove BLEInterface from sys.modules if it was imported
|
||||
modules_to_remove = [
|
||||
'BLEInterface',
|
||||
'RNS.Interfaces.BLEInterface'
|
||||
]
|
||||
for module in modules_to_remove:
|
||||
if module in sys.modules:
|
||||
del sys.modules[module]
|
||||
|
||||
def test_interface_dir_with_custom_configdir(self):
|
||||
"""Test that custom config directory is used when RNS.Reticulum.configdir is set."""
|
||||
# Create a mock RNS module with custom configdir
|
||||
mock_rns = MagicMock()
|
||||
mock_reticulum = MagicMock()
|
||||
custom_config_dir = "/custom/config/path"
|
||||
mock_reticulum.configdir = custom_config_dir
|
||||
mock_rns.Reticulum = mock_reticulum
|
||||
|
||||
# Patch the import to raise NameError for __file__ and provide our mock RNS
|
||||
with patch.dict('sys.modules', {'RNS': mock_rns}):
|
||||
# We need to simulate the NameError for __file__
|
||||
# This is tricky because we need to import the module code
|
||||
# Let's test the logic directly instead
|
||||
|
||||
# Simulate the logic from BLEInterface.py
|
||||
_interface_dir = None
|
||||
try:
|
||||
import RNS
|
||||
if hasattr(RNS.Reticulum, 'configdir') and RNS.Reticulum.configdir:
|
||||
_interface_dir = os.path.join(RNS.Reticulum.configdir, "interfaces")
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
if _interface_dir is None:
|
||||
_interface_dir = os.path.expanduser("~/.reticulum/interfaces")
|
||||
|
||||
# Verify the custom config directory is used
|
||||
expected_path = os.path.join(custom_config_dir, "interfaces")
|
||||
self.assertEqual(_interface_dir, expected_path)
|
||||
|
||||
def test_interface_dir_fallback_when_configdir_none(self):
|
||||
"""Test that default path is used when RNS.Reticulum.configdir is None."""
|
||||
# Create a mock RNS module with None configdir
|
||||
mock_rns = MagicMock()
|
||||
mock_reticulum = MagicMock()
|
||||
mock_reticulum.configdir = None
|
||||
mock_rns.Reticulum = mock_reticulum
|
||||
|
||||
with patch.dict('sys.modules', {'RNS': mock_rns}):
|
||||
# Simulate the logic from BLEInterface.py
|
||||
_interface_dir = None
|
||||
try:
|
||||
import RNS
|
||||
if hasattr(RNS.Reticulum, 'configdir') and RNS.Reticulum.configdir:
|
||||
_interface_dir = os.path.join(RNS.Reticulum.configdir, "interfaces")
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
if _interface_dir is None:
|
||||
_interface_dir = os.path.expanduser("~/.reticulum/interfaces")
|
||||
|
||||
# Verify the default path is used
|
||||
expected_path = os.path.expanduser("~/.reticulum/interfaces")
|
||||
self.assertEqual(_interface_dir, expected_path)
|
||||
|
||||
def test_interface_dir_fallback_when_rns_not_available(self):
|
||||
"""Test that default path is used when RNS module is not available."""
|
||||
# Simulate ImportError for RNS
|
||||
with patch.dict('sys.modules', {'RNS': None}):
|
||||
# Simulate the logic from BLEInterface.py
|
||||
_interface_dir = None
|
||||
try:
|
||||
import RNS
|
||||
if RNS and hasattr(RNS.Reticulum, 'configdir') and RNS.Reticulum.configdir:
|
||||
_interface_dir = os.path.join(RNS.Reticulum.configdir, "interfaces")
|
||||
except (ImportError, AttributeError, TypeError):
|
||||
pass
|
||||
|
||||
if _interface_dir is None:
|
||||
_interface_dir = os.path.expanduser("~/.reticulum/interfaces")
|
||||
|
||||
# Verify the default path is used
|
||||
expected_path = os.path.expanduser("~/.reticulum/interfaces")
|
||||
self.assertEqual(_interface_dir, expected_path)
|
||||
|
||||
def test_interface_dir_fallback_when_reticulum_missing_configdir(self):
|
||||
"""Test that default path is used when RNS.Reticulum doesn't have configdir attribute."""
|
||||
# Create a mock RNS module without configdir attribute
|
||||
mock_rns = MagicMock()
|
||||
mock_reticulum = MagicMock(spec=[]) # Empty spec, no attributes
|
||||
mock_rns.Reticulum = mock_reticulum
|
||||
|
||||
with patch.dict('sys.modules', {'RNS': mock_rns}):
|
||||
# Simulate the logic from BLEInterface.py
|
||||
_interface_dir = None
|
||||
try:
|
||||
import RNS
|
||||
if hasattr(RNS.Reticulum, 'configdir') and RNS.Reticulum.configdir:
|
||||
_interface_dir = os.path.join(RNS.Reticulum.configdir, "interfaces")
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
if _interface_dir is None:
|
||||
_interface_dir = os.path.expanduser("~/.reticulum/interfaces")
|
||||
|
||||
# Verify the default path is used
|
||||
expected_path = os.path.expanduser("~/.reticulum/interfaces")
|
||||
self.assertEqual(_interface_dir, expected_path)
|
||||
|
||||
def test_custom_config_path_construction(self):
|
||||
"""Test that the interfaces subdirectory is correctly constructed from custom config."""
|
||||
custom_configs = [
|
||||
"/home/user/.reticulumble",
|
||||
"/opt/reticulum/config",
|
||||
"~/.custom_reticulum"
|
||||
]
|
||||
|
||||
for custom_config in custom_configs:
|
||||
with self.subTest(custom_config=custom_config):
|
||||
expected_path = os.path.join(custom_config, "interfaces")
|
||||
actual_path = os.path.join(custom_config, "interfaces")
|
||||
self.assertEqual(actual_path, expected_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue