From 1acbee0b5bad2138cf106b79d8233d9de9573623 Mon Sep 17 00:00:00 2001 From: agessaman Date: Wed, 7 Jan 2026 20:06:43 -0800 Subject: [PATCH] feat: Enhance bot signal handling and update dependencies - Refactored bot's main execution flow to use asyncio.run() with improved signal handling for graceful shutdown on Unix systems. - Added new dependencies in pyproject.toml: urllib3, paho-mqtt, cryptography, and pynacl for enhanced functionality. - Updated Nix flake to include flake-parts for better modularization and added translation path for NixOS module compatibility. - Improved argument parsing in web viewer to maintain clarity and consistency. --- TESTING_GUIDE.md | 304 ++++++++++++++++++++++++++++++++++++++ flake.nix | 1 + meshcore_bot.py | 65 +++++++- modules/web_viewer/app.py | 5 +- nix/nixos-module.nix | 1 + nix/packages.nix | 13 ++ pyproject.toml | 4 + 7 files changed, 384 insertions(+), 9 deletions(-) create mode 100644 TESTING_GUIDE.md diff --git a/TESTING_GUIDE.md b/TESTING_GUIDE.md new file mode 100644 index 0000000..dd07d87 --- /dev/null +++ b/TESTING_GUIDE.md @@ -0,0 +1,304 @@ +# Testing Guide for PR #27 Integration + +This guide covers testing the integrated PR #27 changes, including both Nix and standard Python installations. + +## Quick Test Checklist + +### ✅ Standard Python Installation Tests + +#### 1. **Dependency Installation Test** +```bash +# Test that all dependencies can be installed +pip install -e . + +# Or test with requirements.txt (should match pyproject.toml) +pip install -r requirements.txt + +# Verify critical dependencies are available +python3 -c "import paho.mqtt.client; import cryptography; import nacl; print('✓ All dependencies available')" +``` + +#### 2. **Import Test** +```bash +# Test that the package can be imported +python3 -c "from modules.core import MeshCoreBot; print('✓ Import successful')" + +# Test web viewer import +python3 -c "from modules.web_viewer.app import main; print('✓ Web viewer import successful')" +``` + +#### 3. **Signal Handler Test** (Critical Fix) +```bash +# Start the bot +python3 meshcore_bot.py --config config.ini.example + +# In another terminal, test SIGTERM handling: +# Send SIGTERM and verify graceful shutdown +kill -TERM $(pgrep -f "meshcore_bot.py") + +# Or test SIGINT (Ctrl+C) - should show "Shutting down..." and exit cleanly +# Press Ctrl+C and verify it doesn't hang or crash +``` + +**Expected behavior:** +- Bot should print "Shutting down..." when signal received +- Bot should call `bot.stop()` and exit cleanly +- No hanging processes or error messages + +#### 4. **Web Viewer Config Argument Test** +```bash +# Test that --config argument works +python3 -m modules.web_viewer.app --config config.ini.example --help + +# Should show help including --config option +``` + +#### 5. **Package Installation Test** +```bash +# Install as package +pip install -e . + +# Test entry points +which meshcore-bot +which meshcore-viewer + +# Test they work +meshcore-bot --help +meshcore-viewer --help +``` + +--- + +### ✅ Nix Installation Tests (Linux) + +#### 1. **Flake Evaluation Test** +```bash +# Test that the flake evaluates correctly +# Note: If you get experimental feature errors, enable them: +nix --extra-experimental-features "nix-command flakes" flake check + +# Or configure permanently in ~/.config/nix/nix.conf: +# experimental-features = nix-command flakes + +# Should complete without errors +``` + +#### 2. **Package Build Test** +```bash +# Build the package (enable experimental features if needed) +nix --extra-experimental-features "nix-command flakes" build + +# Verify the package was built +ls -la result/ + +# Check that translations are installed +ls -la result/share/meshcore-bot/translations/ + +# Should show translation JSON files +``` + +#### 3. **Package Contents Verification** +```bash +# After building, check package contents +nix build +nix-store -q --tree result/ | grep -E "(meshcore-bot|translations)" + +# Verify entry points exist +result/bin/meshcore-bot --help +result/bin/meshcore-viewer --help +``` + +#### 4. **Dependency Verification** +```bash +# Check that all dependencies are included +nix build +nix-store -qR result/ | grep -E "(paho-mqtt|urllib3|cryptography|pynacl)" + +# Should show these packages in the dependency tree +``` + +#### 5. **NixOS Module Test** (Requires NixOS or VM) +```bash +# Run the NixOS tests (this creates VMs, takes time) +nix --extra-experimental-features "nix-command flakes" flake check --no-build + +# Or run specific test +nix --extra-experimental-features "nix-command flakes" build .#checks.x86_64-linux.nixos-module-basic + +# This will: +# - Create a NixOS VM +# - Install meshcore-bot service +# - Verify service starts +# - Check file permissions +# - Verify translations path +``` + +**Note:** NixOS tests require significant resources and time. They create full VMs. + +#### 6. **Development Shell Test** +```bash +# Enter development shell (enable experimental features if needed) +nix --extra-experimental-features "nix-command flakes" develop + +# Verify dependencies are available +python3 -c "import paho.mqtt.client; print('✓ paho-mqtt available')" +python3 -c "import meshcore; print('✓ meshcore available')" + +# Test imports +python3 -c "from modules.core import MeshCoreBot; print('✓ Import works')" +``` + +--- + +## Manual Verification Checklist + +### Signal Handler Fix Verification + +1. **Start bot normally:** + ```bash + python3 meshcore_bot.py --config config.ini.example + ``` + +2. **Test SIGTERM:** + - In another terminal: `kill -TERM ` + - Should see "Shutting down..." message + - Bot should exit cleanly (check with `ps aux | grep meshcore`) + +3. **Test SIGINT (Ctrl+C):** + - Press Ctrl+C + - Should see "Shutting down..." message + - Bot should exit cleanly + +4. **Verify no hanging:** + - After shutdown, check for zombie processes + - `ps aux | grep meshcore` should show nothing + +### Dependency Verification + +1. **Check pyproject.toml includes all dependencies:** + ```bash + grep -E "(paho-mqtt|urllib3|cryptography|pynacl)" pyproject.toml + ``` + +2. **Verify Nix packages include dependencies:** + ```bash + grep -E "(paho-mqtt|urllib3|pynacl)" nix/packages.nix + ``` + +3. **Test imports work:** + ```bash + python3 < /dev/null && echo "✓ meshcore-bot help works" +python3 -m modules.web_viewer.app --help > /dev/null && echo "✓ web viewer help works" + +echo "" +echo "=== Testing Nix (if available) ===" +if command -v nix &> /dev/null; then + nix flake check --no-build && echo "✓ Flake evaluates" + echo "Run 'nix build' to test package build" +else + echo "Nix not available, skipping Nix tests" +fi + +echo "" +echo "✓ All quick tests passed!" +``` + +Make it executable: `chmod +x quick_test.sh` diff --git a/flake.nix b/flake.nix index 7651472..6d98dc9 100644 --- a/flake.nix +++ b/flake.nix @@ -3,6 +3,7 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; + flake-parts.url = "github:hercules-ci/flake-parts"; treefmt-nix.url = "github:numtide/treefmt-nix"; meshcore-cli = { url = "github:meshcore-dev/meshcore-cli"; diff --git a/meshcore_bot.py b/meshcore_bot.py index e60cc31..7d8b83a 100644 --- a/meshcore_bot.py +++ b/meshcore_bot.py @@ -27,18 +27,67 @@ def main(): bot = MeshCoreBot(config_file=args.config) - def signal_handler(sig, frame): - print("\nShutting down...") - asyncio.create_task(bot.stop()) - sys.exit(0) - - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) + # Use asyncio.run() which handles KeyboardInterrupt properly + # For SIGTERM, we'll handle it in the async context + async def run_bot(): + """Run bot with proper signal handling""" + # Set up signal handlers for graceful shutdown (Unix only) + if sys.platform != 'win32': + loop = asyncio.get_running_loop() + shutdown_event = asyncio.Event() + + def signal_handler(): + """Signal handler for graceful shutdown""" + print("\nShutting down...") + shutdown_event.set() + + # Register signal handlers + for sig in (signal.SIGTERM, signal.SIGINT): + loop.add_signal_handler(sig, signal_handler) + + # Start bot + bot_task = asyncio.create_task(bot.start()) + + # Wait for shutdown or completion + done, pending = await asyncio.wait( + [bot_task, asyncio.create_task(shutdown_event.wait())], + return_when=asyncio.FIRST_COMPLETED + ) + + # Cancel pending + for task in pending: + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + + # If shutdown triggered, stop gracefully + if shutdown_event.is_set(): + bot_task.cancel() + try: + await bot_task + except asyncio.CancelledError: + pass + await bot.stop() + else: + # Bot completed normally + try: + await bot_task + finally: + await bot.stop() + else: + # Windows: just run and catch KeyboardInterrupt + try: + await bot.start() + finally: + await bot.stop() try: - asyncio.run(bot.start()) + asyncio.run(run_bot()) except KeyboardInterrupt: print("\nShutting down...") + asyncio.run(bot.stop()) except Exception as e: print(f"Error: {e}") asyncio.run(bot.stop()) diff --git a/modules/web_viewer/app.py b/modules/web_viewer/app.py index c71abbd..3894f57 100644 --- a/modules/web_viewer/app.py +++ b/modules/web_viewer/app.py @@ -3899,7 +3899,10 @@ def main(): parser.add_argument('--host', default='127.0.0.1', help='Host to bind to') parser.add_argument('--port', type=int, default=8080, help='Port to bind to') parser.add_argument('--debug', action='store_true', help='Enable debug mode') - parser.add_argument( "--config", default="config.ini", help="Path to configuration file (default: config.ini)", + parser.add_argument( + "--config", + default="config.ini", + help="Path to configuration file (default: config.ini)", ) args = parser.parse_args() diff --git a/nix/nixos-module.nix b/nix/nixos-module.nix index 302fa68..e162d85 100644 --- a/nix/nixos-module.nix +++ b/nix/nixos-module.nix @@ -73,6 +73,7 @@ db_path = "${cfg.dataDir}/meshcore-bot.db"; }; Localization = { + # Translation path: translations are installed to share directory by Nix package translation_path = "${cfg.package}/share/meshcore-bot/translations"; }; Logging = { diff --git a/nix/packages.nix b/nix/packages.nix index 5ed9664..3ff7d91 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -130,6 +130,16 @@ pyproject = true; nativeBuildInputs = with pkgs.python3Packages; [setuptools]; + + # Install translations to share directory for NixOS module compatibility + # Translations are in the source root, copy them to the standard location + postInstall = '' + mkdir -p $out/share/meshcore-bot + if [ -d "$src/translations" ]; then + cp -r "$src/translations" $out/share/meshcore-bot/ + fi + ''; + propagatedBuildInputs = (with pkgs.python3Packages; [ # Core Python dependencies from requirements.txt @@ -150,6 +160,9 @@ requests-cache schedule cryptography + paho-mqtt + urllib3 + pynacl ]) ++ [ # Custom packages from PyPI diff --git a/pyproject.toml b/pyproject.toml index ec34752..888466a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ "schedule>=1.2.0", "colorlog>=6.7.0", "requests>=2.31.0", + "urllib3>=2.0.0", "pyephem>=4.1.4", "geopy>=2.3.0", "maidenhead>=1.4.0", @@ -31,6 +32,9 @@ dependencies = [ "flask-socketio>=5.3.0", "meshcore-cli", "feedparser>=6.0.10", + "paho-mqtt>=1.6.0", + "cryptography>=41.0.0", + "pynacl>=1.5.0", ] [project.scripts]