- Add allow_private param to validate_external_url (alias for
allow_localhost) to unblock web viewer SSRF guard using allow_private=
- Block non-globally-routable IPs (RFC 6598 100.64.0.0/10 CGN) on
Python 3.10 which does not classify them as private or reserved
- Remove tests for greeter DB tables and admin_config template that
depend on features not present on this branch
- USE-05: Add /api-explorer page listing all ~65 API endpoints in 9
categories (System, Contacts, Mesh, Channels, Feeds, Radio, Admin,
Maintenance, Config, Greeter) with method badges, descriptions, and
curl example modal. Filter bar and collapse per section. Nav item
added to base.html.
- USE-06: Three targeted error-message improvements:
1. 500 handler now returns user-friendly HTML page (error.html) for
browser requests and sanitized JSON for API/JSON requests instead
of a bare string.
2. Feed processed-items query failures promoted from logger.debug to
logger.warning so operators see them in normal log output.
3. Global JS fetch interceptor in base.html redirects to /login?next=
on any 401 response, handling session expiry mid-page.
- Fix pre-existing test bug: test_reload_endpoint_success mock return
value did not match actual code message from reload_config.
Updated the BotDataViewer class to utilize a context manager for database connections, enhancing resource management. Additionally, refactored test files to implement a centralized approach for managing SQLite connections, ensuring proper cleanup after tests. This change improves code maintainability and reliability across the application.
Introduced a new configuration option `cmd_reference_url` in the Cmd_Command section, allowing users to override the default `cmd` output with a link to a full documentation page. Updated the CmdCommand class to utilize this new setting and modified related documentation and tests to ensure proper functionality.
- Make MeshCoreBot.stop idempotent; remove duplicate stop from start().
- Avoid web viewer restart during shutdown; gate main-loop restarts.
- Fix packet capture MQTT callbacks to log each broker’s host.
- Only shutdown APScheduler when running; silence double-shutdown noise.
- Add regression tests in tests/test_shutdown_reliability.py.
Make outbound send suppression consistent by honoring both radio-offline and zombie states across command-manager and scheduler paths. Preserve strict SSRF defaults while adding explicit private-feed URL opt-ins, persist allow_local_smtp from notifications config writes, reconcile zombie alert setting precedence, and replace deprecated UTC timestamp calls with timezone-aware UTC usage.
Added new configuration options for radio health monitoring and alerting in config.ini.example, including radio_offline_threshold, radio_offline_alert_enabled, and radio_offline_alert_email. Introduced an [Admin] section with settings for a local admin API, including enabled, port, and token fields. Updated related files to reflect these changes, improving the organization and functionality of the configuration structure.
Changed the default value of max_results in config.ini.example from 0 (no limit) to 3, ensuring a more manageable output size for aircraft listings. Updated the AirplanesCommand class to reflect this new default and added logic to handle message size constraints when formatting the aircraft list. This refactor improves the user experience by preventing excessive data in responses while maintaining functionality.
Removed the fortune command and its associated files, transitioning to existing new random lines command that retrieves fortunes from a different file structure. Updated configuration settings in config.ini.example to reflect this change, including the new file path for fortunes. Adjusted the .gitignore to accommodate the new directory structure for random lines. This refactor enhances the organization of fortune-related content and improves maintainability.
Moved radio-related configuration options from the [Bot] section to a new [Connection] section in config.ini.example for better organization. Updated the core logic to reference the new configuration paths, ensuring that radio health monitoring and alert settings are correctly handled. This change enhances clarity and maintainability of the configuration structure.
Updated the path validation logic in security_utils.py and web_viewer/app.py to include additional dangerous prefixes, specifically targeting private directories. This change aims to strengthen security by preventing access to sensitive system paths. Additionally, modified the test for posting feeds to mock the external URL validation, ensuring consistent behavior during tests.
Prevent multi-message aircraft list replies by capping formatted output to one frame, preserving anti-flood behavior on mesh channels.
Made-with: Cursor
PR #152 introduced trailing whitespace in several command files, set
message.content_lower at runtime without declaring it on the MeshMessage
dataclass, and left unused/unsorted imports in test and service files.
All cause ruff/mypy CI failures on branches that rebase onto dev after
that merge.
Linting fixes (ruff --fix):
- modules/command_manager.py: restore PUBLIC_CHANNEL_KEY_HEX re-export
with noqa guard (core.py imports it from here; auto-fix silently dropped it)
- modules/commands/multitest_command.py: strip W291/W293 trailing whitespace
- modules/commands/path_command.py: strip W293 trailing whitespace
- modules/commands/prefix_command.py: strip W291 trailing whitespace
- modules/commands/roll_command.py: strip W293 trailing whitespace
- modules/commands/sports_command.py: strip W293 trailing whitespace
- modules/core.py: strip W293 blank-line whitespace
- modules/service_plugins/packet_capture_service.py: sort imports (I001)
- modules/version_info.py: remove unused typing.Any import (F401)
- tests/integration/test_flood_scope_reply.py: remove unused call import
- tests/unit/test_log_data_scope_fields.py: remove unused asyncio import
- tests/unit/test_public_channel_guard.py: sort imports, remove unused
patch and validate_config imports
Dataclass fix (mypy attr-defined):
- modules/models.py: declare content_lower field on MeshMessage so mypy
resolves the attribute set by base_command.cleanup_message_for_matching
Must be merged before or alongside PRs #155–#158 to clear CI on those
branches.
Remove the "(x more)" truncation from _format_aircraft_list — all
matching aircraft are now returned. The hard max_results=10 cap is
replaced with max_results=0 (no limit) as the default; operators can
still set max_results in config.ini to impose a ceiling, and users can
pass limit=N per-query. List results always route through
_send_split_response for chunked multi-message delivery.
Adds a new !fortune command that picks a random fortune from a
configured BSD-format fortune file (entries separated by a line
containing only %). The parser handles: % as separator or terminator,
multi-line fortunes, trailing whitespace, and % embedded mid-line.
Path validation via validate_safe_path() blocks path traversal before
any file open. Command is auto-discovered by the plugin loader.
Config section [Fortune_Command] added to config.ini.example with
enabled and file keys. Default file: data/fortune/fortunes.txt.
22 tests cover: parsing edge cases, security path rejection,
can_execute enable/disable, execute happy path, empty file fallback,
and plugin metadata.
Fortune file: BSD V8-fortunes (fortunes-freebsd-classic)
Source: https://github.com/HubTou/fortunes-freebsd-classic
1,903 fortunes drawn from the classic BSD UNIX fortune database.
Content note -- data/fortune/fortunes.txt (1,903 fortunes):
No racist, lewd, or pornographic content found. Six lines flagged
during review as mildly coarse; retained but noted here for reference
in case a future maintainer wishes to remove them:
L1763: Some days chicken salad -- some days chicken shit.
L2289: We retard what we cannot repel, we palliate what we cannot
cure. -Johnson
L3009: In New York they signal the wrong way just to fuck you up.
-David Yost
L3161: Sure [Somoza]'s a son-of-a-bitch, but he's OUR
son-of-a-bitch. -F.D. Roosevelt
L3305: When better machines are built, jks will break them, and td
will bitch about it.
L3589: It's the thought, if any, that counts! -Dick Grantges
("Dick" is a proper name, not a slur)
- core.py: add _BotAdminServer daemon thread (Flask, 127.0.0.1 only,
bearer token auth); POST /api/admin/reload calls reload_config() and
returns JSON {success, message}; GET /api/admin/health; started from
start() when [Admin] enabled = true and token is set
- scripts/reload_config.sh: curl wrapper for the reload API; reads
port/token from config.ini [Admin] section; exits 1 on rejection
- tests/test_core.py: TestBotAdminServer — 7 tests covering server
creation, missing token guard, reload success/failure/auth, health
Clean up residual cherry-pick conflict markers and keep SMTP guidance in config templates brief while preserving full behavior in code and tests.
Made-with: Cursor
Two incompatibilities surfaced after rebasing onto upstream/dev in code
introduced by #147:
- tests/integration/test_flood_scope_reply.py: add bot.is_radio_zombie = False
mock; the zombie-detection guard added to send_channel_message in #147
causes the call to short-circuit without it, failing the scope assertion;
also removes unused `call` import
- modules/web_viewer/app.py: replace ~50-line inline _get_version_info()
with 5-line delegate to resolve_runtime_version(); removes now-unnecessary
import subprocess; the resolve_runtime_version import is now actually used
Do not merge until #147 is merged.
Two small incompatibilities surfaced after rebasing dev-kg7qin-changes
onto upstream/dev in code introduced by #138:
- message_handler.py: replace two remaining Optional[str] annotations
with str | None; the Optional import was removed during typing
modernization but two annotations in this module were missed
- command_manager.py: restore # noqa: F401 guard on PUBLIC_CHANNEL_KEY_HEX
re-export so ruff auto-fix does not silently remove it (imported by core.py)
Do not merge until #138 is merged.
Add SSRF host validation to maintenance.py send_nightly_email and
scheduler.py send_zombie_alert_email using validate_external_url().
New allow_local_smtp config key permits private-IP SMTP for local
relay setups.
Add sanitize_name() to security_utils and apply it to all log calls
in message_handler, repeater_manager, path_command, solarforecast_command,
command_manager, and discord_bridge_service to prevent log injection.
Move nightly email logic from duplicate scheduler._send_nightly_email()
into the canonical maintenance.py implementation, removing the duplicate.
Update tests to call maintenance.send_nightly_email() directly.
Add validate_external_url allow_private parameter with support for
loopback, RFC1918, CGN, and link-local address ranges.
- base.html: persistent danger banner appears on every web page when
is_radio_zombie is true; shows datetime zombie was detected; includes
"Restart Bot Processing" button (POST /api/admin/zombie-recover) that
clears _radio_zombie_detected and _radio_fail_count on the live bot
object and removes the persisted DB flag; banner turns green on success
- config.html: new "Zombie Radio Alert" card with enable/disable toggle
and alert-email field; "Save" writes to bot_metadata (immediate,
survives restarts); "Save to config.ini" also persists values to
config.ini and keeps the in-memory config in sync; card shows
current config.ini values as baseline defaults
- app.py: inject_template_vars context processor now provides
radio_zombie and radio_zombie_since to all templates; added
GET/POST /api/config/zombie-alert endpoints (GET returns both
bot_metadata and config.ini values; POST supports write_to_config
flag); added POST /api/admin/zombie-recover endpoint; stored
config_path on self for write-back use
- scheduler.py: send_zombie_alert_email now prefers bot_metadata
(zombie.alert_enabled, zombie.alert_email) over config.ini so web
UI changes take effect without a restart; uses isinstance(..., str)
guard so mock/None values safely fall through to config.ini defaults
When the radio firmware is unresponsive but not yet flagged as zombie,
send_advert() would hang indefinitely on the main event loop, blocking
all packet processing (no data in/out) for 60+ seconds until the
scheduler thread's future.result() timed out — but that only timed
out the waiting thread, not the coroutine itself.
Fix: wrap every send_advert() call that runs on the event loop with
asyncio.wait_for(timeout=30.0). On timeout in the interval-advert
path, _radio_fail_count is incremented so repeated timeouts feed into
the existing zombie-detection threshold.
- Updated `app.py` to ensure the project root is correctly added to `sys.path` when the script is executed directly, allowing for proper module imports.
- Removed redundant code that previously handled project root path insertion, streamlining the import process.
- Updated `config.ini.example` to introduce the `respond_to_mentions` setting, allowing configuration of how the bot responds to mentions in channel messages.
- Refactored `MessageHandler` to implement logic for handling mentions based on the new configuration, including stripping mentions when appropriate.
- Added `cleanup_message_for_matching` method in `BaseCommand` to streamline message processing and mention validation.
- Enhanced various command classes to utilize the new cleanup method for consistent mention handling.
- Introduced tests to validate the behavior of the new mention handling logic across different configurations.
Match firmware TC_FLOOD scope overhead in get_max_message_length via
MeshMessage.effective_outgoing_flood_scope; keep CommandManager and
BaseCommand in sync with tests.
Made-with: Cursor
- Updated `config.ini.example` to clarify the usage of `outgoing_flood_scope_override` and `flood_scopes`, providing examples for better understanding.
- Modified `configuration.md` to reflect changes in flood scope handling, emphasizing the distinction between `outgoing_flood_scope_override` and `flood_scopes`.
- Refactored `CommandManager` to utilize the new `outgoing_flood_scope_override` for sending messages, ensuring consistent scope handling.
- Enhanced `MessageHandler` to prioritize library-provided scope fields for improved accuracy in flood scope matching.
- Added tests to validate the handling of flood scope fields from library payloads, ensuring robustness in message processing.
- Updated `config.ini.example` to clarify password requirements for web viewer authentication, emphasizing the need for a password when binding to non-loopback interfaces.
- Introduced a new function `normalized_web_viewer_password` in `integration.py` to standardize password retrieval and validation, handling various empty and null placeholder cases.
- Enhanced error logging in `core.py` to use `logger.error` for web viewer integration failures, improving visibility of issues.
- Modified `app.py` to utilize the new password normalization function, ensuring consistent password handling across the application.
- Added tests in `test_web_viewer_integration.py` to validate password normalization and error logging behavior when the web viewer is configured without a password.
- Updated `config.ini.example` to include a warning about running the bot on the Public channel and added an override key for intentional usage.
- Enhanced `config_validation.py` to implement a public channel guard that prevents the bot from starting if the Public channel is included in monitored channels without the override.
- Refactored `CommandManager` and `Core` to check for the Public channel key during channel loading and connection setup, ensuring compliance with the new guard.
- Improved documentation in `configuration.md` and `config-validation.md` to clarify the implications of using the Public channel and the necessary configuration changes.
- Updated `config.ini.example` to clarify flood scope configuration, introducing the auto-hashtag format for region names and adding support for multi-scope replies.
- Refactored `ChannelManager` to improve handling of empty channels, adjusting timeout logic and increasing request delay to prevent overwhelming devices.
- Enhanced `CommandManager` to load flood scope keys for HMAC matching and normalize scope names for consistency.
- Implemented scope matching in `MessageHandler` to ensure replies respect configured flood scopes, improving message routing accuracy.
- Updated `MeshMessage` model to include a `reply_scope` attribute for tracking matched flood scopes.
- Increased the per-user rate limit from 5 to 30 seconds across multiple configuration files to reduce response frequency.
- Added the version command to the configuration examples and updated help text to include the new command.
- Refactored version information retrieval in the bot and web viewer to utilize a shared runtime resolver for consistency.
- Improved documentation in README.md to reflect changes in commands and configuration options.
- Refactored the logic for displaying multibyte path statistics in the doughnut chart, improving clarity and responsiveness.
- Introduced a function to disable animations for smoother updates when data changes.
- Updated chart options to dynamically reflect multibyte and other path data, enhancing user experience and visual representation.
These changes improve the accuracy and usability of the multibyte path statistics in the dashboard.
- Improved error handling for `out_path_hash_mode` and `out_path_len` to ensure proper type conversion and validation.
- Added logic to derive `out_path_len` from `out_path` when it is missing, enhancing robustness against incomplete data.
- Updated tests to verify correct behavior when `out_path_hash_mode` is provided as a string and `out_path_len` is absent.
- Introduced a new `CondensePathsMode` type to support multiple condensing styles: "off", "flat", and "nested".
- Updated `_condense_path_lines` to handle different condensing modes, improving path representation.
- Enhanced test coverage for path condensing scenarios, ensuring accurate output for various path configurations.
These changes improve the flexibility and usability of path handling in the MultitestCommand, allowing for better visualization of path structures.