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.
This commit is contained in:
agessaman
2026-01-07 20:06:43 -08:00
parent 070e02554e
commit 1acbee0b5b
7 changed files with 384 additions and 9 deletions

304
TESTING_GUIDE.md Normal file
View File

@@ -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 <pid>`
- 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 <<EOF
import paho.mqtt.client
import urllib3
import cryptography
import nacl
print("✓ All new dependencies importable")
EOF
```
### Translation Path Fix Verification
1. **For Nix build:**
```bash
nix build
test -d result/share/meshcore-bot/translations
ls result/share/meshcore-bot/translations/*.json
# Should show translation files
```
2. **For NixOS module:**
- The NixOS test should verify this automatically
- Check that `translation_path` in generated config points to correct location
### Code Quality Checks
1. **Syntax check:**
```bash
python3 -m py_compile meshcore_bot.py
python3 -m py_compile modules/web_viewer/app.py
```
2. **Import check:**
```bash
python3 -c "import meshcore_bot; import modules.web_viewer.app"
```
---
## Expected Test Results
### ✅ All Tests Should Pass
1. **Standard Python:**
- ✅ Dependencies install correctly
- ✅ Imports work
- ✅ Signal handler shuts down gracefully
- ✅ Web viewer accepts --config argument
- ✅ Entry points work
2. **Nix:**
- ✅ Flake evaluates
- ✅ Package builds
- ✅ Translations installed to share/
- ✅ All dependencies included
- ✅ Entry points work
- ✅ NixOS module tests pass (if run)
---
## Troubleshooting
### If signal handler test fails:
- Check that `asyncio.run()` is being used correctly
- Verify no blocking operations in signal handler
- Check for proper event loop handling
### If Nix build fails:
- **Experimental features error**: Enable with `--extra-experimental-features "nix-command flakes"` or configure in `~/.config/nix/nix.conf`:
```
experimental-features = nix-command flakes
```
- Run `nix --extra-experimental-features "nix-command flakes" flake update` to update lock file
- Check that `flake-parts` is in inputs
- Verify all dependencies are available in nixpkgs
### If translations not found:
- Check `nix/packages.nix` postInstall hook
- Verify source path is correct
- Check that translations directory exists in source
### If dependencies missing:
- Compare `requirements.txt` with `pyproject.toml`
- Check `nix/packages.nix` propagatedBuildInputs
- Verify package names match (e.g., `paho-mqtt` vs `paho.mqtt`)
---
## Quick Test Script
Save this as `quick_test.sh`:
```bash
#!/bin/bash
set -e
echo "=== Testing Standard Python Installation ==="
python3 -c "import paho.mqtt.client; import cryptography; import nacl; print('✓ Dependencies OK')"
python3 -c "from modules.core import MeshCoreBot; print('✓ Import OK')"
python3 meshcore_bot.py --help > /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`

View File

@@ -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";

View File

@@ -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())

View File

@@ -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()

View File

@@ -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 = {

View File

@@ -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

View File

@@ -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]