feat: Add GitHub Container Registry publishing and pre-built images#9
Conversation
Add automated Docker image publishing to GitHub Container Registry (ghcr.io) to provide users with pre-built images for easier deployment. Changes: - Add GitHub Actions workflow for automated Docker image publishing - Builds and publishes on push to main and version tags - Tags images as latest, version tags (1.0.0, 1.0, 1), and branch names - Uses Docker buildx with layer caching for optimal build performance - Update docker-compose.yml to use pre-built image by default - Configured to pull ghcr.io/yeraze/meshmonitor:latest - Includes instructions for local builds as alternative - Enhance .dockerignore with additional exclusions - Add GitHub workflows, data directories, and log files - Update README.md with GHCR deployment instructions - Add Docker image badges at the top - Comprehensive deployment options (pre-built vs local build) - Document available image tags Benefits: - Faster deployment without local build step - Consistent images across all deployments - Version-tagged images for stability - Automatic publishing on every release 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
|
Claude finished @Yeraze's task —— View job Pull Request ReviewI've completed a comprehensive review of this PR for code quality, best practices, security concerns, and potential issues. Review Tasks:
✅ Overall Assessment: Excellent ImplementationThis PR demonstrates strong DevOps practices and follows security best practices. The implementation is well-structured and production-ready. 🔍 Detailed ReviewGitHub Actions Workflow (
|
- Mark messages played from the delivery-success callback, not at enqueue:
handleCommand returns { responses, playOnDelivery }; a dropped body DM now
leaves the message pending instead of losing it. (Yeraze#1)
- Wire purgeExpired into databaseMaintenanceService so expired rows are
reclaimed daily (purgeExpired returns a count). (Yeraze#2)
- Count the per-recipient cap across the recipient's identity forms via an
injected node resolver, so it can't be bypassed by addressing one node by
several name forms. (Yeraze#3)
- Mailbox bypasses the per-node cooldown (interactive flow). (Yeraze#4)
- inbox play <sender> filter matches !hex/node-num forms too. (Yeraze#5)
- Non-DM commands return [] (no unsolicited DM). (Yeraze#6)
- inbox delete returns the same response for not-yours vs non-existent ids
(no id enumeration). (Yeraze#7)
- Wrap the mailbox dispatch in try/catch like the script branch. (Yeraze#8)
- Remove the command-prefix tolerance: canonical msg/inbox only. (Yeraze#9)
- Use shared nodeIdHex (unsigned coerce) for the mailbox log target. (Yeraze#10)
Docs + tests updated to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…gaps (#3578) Addresses Claude Code Review findings on PR #3578: - Sanitize device-controlled message before logging/forwarding: strip control chars (log-injection defense) and bound length to 500 (#9, #10). - Widen protected-cap regex to {1,8} hex digits so a short node id still reconciles (#4). - Exclude clientNotification from the unknown-FromRadio debug JSON.stringify dump (#3). - Drop the redundant `&& this.sourceId` guard (always set) and comment why the toast still fires when the DB revert fails (#2, #5). - Frontend: name the level magic numbers and note they mirror the backend NOTIFICATION_LEVEL (#1). - Tests: sanitizer (control chars/whitespace/truncation/empty), short-hex-id parse, and the DB-revert-failure path (#6). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
…8 favorite/ignore cap (#3548) (#3578) * feat(notifications): surface device ClientNotifications + handle firmware 2.8 favorite/ignore cap (#3548) MeshMonitor decoded FromRadio.ClientNotification (mesh.proto field 16) and dropped it. Surface these device-originated warnings/errors as toasts, and handle the firmware 2.8 protected-node-cap refusal for Set Favorite / Ignore. Backend: - clientNotificationPolicy.ts (pure/testable): suppression patterns (recurring power-save "sleeping for N interval" INFO + key-verification variants + empty messages), the 2.8 protected-node-cap refusal parser, level->severity mapping, and a per-source ToastThrottle that dedupes identical messages within a window. - meshtasticProtobufService.ts: add the clientNotification dispatch branch (was falling through to the generic catch-all). - dataEventEmitter.ts: client-notification event type + emitClientNotification(). - meshtasticManager.ts: handleClientNotification() reverts the optimistic favorite/ignore flag and re-broadcasts the node when the device refuses at the protected-node cap, then applies the suppression/throttle policy and emits the toast event. Source-scoped throughout. Frontend: - DeviceNotificationToaster.tsx: listens for client-notification inside ToastProvider, maps level->severity, shows the toast. Wired into App.tsx. - WS forwarding + per-source room filtering needed no changes. Scope: the 2.8 NodeDB warm-tier restructure and the on-disk snr_q4 field do NOT affect the over-the-air wire MeshMonitor reads. OTA NodeInfo.snr stays a float in dB; no protobuf/decode change. A regression test guards this. The cap-refusal warning is only emitted by firmware for the locally-connected node (from == 0). Tests (20 new, full suite green): policy unit tests, manager handler tests (reconciliation/suppression/throttle, per-source), and protobuf dispatch + SNR float guard. Docs: FAQ (node count + blocked-node 2.8 behavior; device-notification toasts), IgnoredNodesSection inline help, CHANGELOG, and the support plan dev-note. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4 * address review: sanitize device notification text, robustness + test gaps (#3578) Addresses Claude Code Review findings on PR #3578: - Sanitize device-controlled message before logging/forwarding: strip control chars (log-injection defense) and bound length to 500 (#9, #10). - Widen protected-cap regex to {1,8} hex digits so a short node id still reconciles (#4). - Exclude clientNotification from the unknown-FromRadio debug JSON.stringify dump (#3). - Drop the redundant `&& this.sourceId` guard (always set) and comment why the toast still fires when the DB revert fails (#2, #5). - Frontend: name the level magic numbers and note they mirror the backend NOTIFICATION_LEVEL (#1). - Tests: sanitizer (control chars/whitespace/truncation/empty), short-hex-id parse, and the DB-revert-failure path (#6). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Mark messages played from the delivery-success callback, not at enqueue:
handleCommand returns { responses, playOnDelivery }; a dropped body DM now
leaves the message pending instead of losing it. (Yeraze#1)
- Wire purgeExpired into databaseMaintenanceService so expired rows are
reclaimed daily (purgeExpired returns a count). (Yeraze#2)
- Count the per-recipient cap across the recipient's identity forms via an
injected node resolver, so it can't be bypassed by addressing one node by
several name forms. (Yeraze#3)
- Mailbox bypasses the per-node cooldown (interactive flow). (Yeraze#4)
- inbox play <sender> filter matches !hex/node-num forms too. (Yeraze#5)
- Non-DM commands return [] (no unsolicited DM). (Yeraze#6)
- inbox delete returns the same response for not-yours vs non-existent ids
(no id enumeration). (Yeraze#7)
- Wrap the mailbox dispatch in try/catch like the script branch. (Yeraze#8)
- Remove the command-prefix tolerance: canonical msg/inbox only. (Yeraze#9)
- Use shared nodeIdHex (unsigned coerce) for the mailbox log target. (Yeraze#10)
Docs + tests updated to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ore) (#3538) * feat: add Dead Drop / Mailbox auto-responder (async message store) A per-source 'mesh voicemail': a node DMs the radio `msg <name> <text>` and the message is held until the named recipient retrieves it via `inbox` / `inbox play`. Implemented as a new auto-responder responseType ('mailbox'), reusing the existing DM-gating, per-node cooldown, param extraction, and per-source scoping. - DB: dead_drop_messages table (SQLite/PostgreSQL/MySQL) + migration 092 - Repository (Drizzle-only) + DatabaseService.deadDrop accessor - DeadDropService: command brain (store/inbox/play[/sender]/delete/clear, 180-byte cap, per-recipient & per-sender caps, 7-day expiry, batch play) - meshtasticManager: 'mailbox' responseType dispatch branch - UI: 'Mailbox' response type option + guidance in the auto-responder editor - Tests: 34 (migration registry, repository perSource, service) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(dead-drop): add Mailbox option to the Add-Trigger form select The Mailbox response type was only added to the per-trigger edit view (TriggerItem); the separate Add-Trigger form in AutoResponderSection had its own type <select> (Text/HTTP/Script) that was missed, so new mailbox triggers couldn't be created from the UI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(dead-drop): don't require response text to add a Mailbox trigger The Add button's disabled gate required a non-empty response field for all types; Mailbox has no response, so the button stayed greyed out. Exempt mailbox from the response-required check (matches validateResponse). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(dead-drop): accept mailbox responseType in settings save validation The settings-save validator required a non-empty response for every trigger and only allowed text/http/script responseTypes, so saving a Mailbox trigger failed with a generic 400. Exempt mailbox from the response-required check and add it to the responseType allowlist. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(dead-drop): settings-save accepts mailbox triggers without response text Regression coverage for the mailbox responseType in the autoResponderTriggers validator: a mailbox trigger with empty response now saves (200), while non-mailbox empty responses and unknown responseTypes still 400. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(dead-drop): tolerate optional command keyword prefix The mailbox service parsed hardcoded msg/inbox, but a trigger configured with prefixed keywords (e.g. betamsg/betainbox, to coexist with another responder already using msg/inbox) would fire the handler yet fall through to help. Strip an optional non-space prefix from the leading verb so prefixed keywords parse correctly; no-op for plain msg/inbox. Caught by live over-the-air testing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(dead-drop): add Mailbox feature docs + live testing brief - automation.md: Mailbox response type + 'Mailbox (Dead Drop)' section (commands, recipient matching, delivery format, limits, command-prefix coexistence, configuration). - dev-notes/DEAD_DROP_TESTING.md: architecture, automated coverage, and the over-the-air validation results (ALTO MF / ALTO LF / ZN Office). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(dead-drop): register dead_drop_messages in migrate-db table lists The cross-database migrate-db CLI tracks every schema table in TABLE_ORDER / SKIP_TABLES; migrationTables.test.ts fails if a new schema table isn't listed. Add dead_drop_messages to TABLE_ORDER and SOURCE_SCOPED_TABLES (it carries a sourceId, like auto_favorite_targets). Caught by the full Vitest suite. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(dead-drop): address PR review feedback - Mark messages played from the delivery-success callback, not at enqueue: handleCommand returns { responses, playOnDelivery }; a dropped body DM now leaves the message pending instead of losing it. (#1) - Wire purgeExpired into databaseMaintenanceService so expired rows are reclaimed daily (purgeExpired returns a count). (#2) - Count the per-recipient cap across the recipient's identity forms via an injected node resolver, so it can't be bypassed by addressing one node by several name forms. (#3) - Mailbox bypasses the per-node cooldown (interactive flow). (#4) - inbox play <sender> filter matches !hex/node-num forms too. (#5) - Non-DM commands return [] (no unsolicited DM). (#6) - inbox delete returns the same response for not-yours vs non-existent ids (no id enumeration). (#7) - Wrap the mailbox dispatch in try/catch like the script branch. (#8) - Remove the command-prefix tolerance: canonical msg/inbox only. (#9) - Use shared nodeIdHex (unsigned coerce) for the mailbox log target. (#10) Docs + tests updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(dead-drop): recommend enabling Verify response on the mailbox trigger Played-state is committed from the delivery-success callback; with Verify response off (maxAttempts=1) a single unacked send could mark a voicemail played on transmit. Document enabling Verify response so undelivered bodies resurface. (PR #3538 review follow-up) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: chrisn <chrisn@DebDev1.corp.tlclocal.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Randall Hand <randall.hand@gmail.com>
Summary
Add automated Docker image publishing to GitHub Container Registry (ghcr.io) to provide users with pre-built images for easier deployment.
Changes
✅ GitHub Actions Workflow (
.github/workflows/docker-publish.yml)✅ Docker Compose Updates (
docker-compose.yml)ghcr.io/yeraze/meshmonitor:latest✅ Enhanced .dockerignore
✅ README Documentation Updates
Benefits
🚀 Faster Deployment - No build step required, just pull and run
📦 Consistent Images - Same image across all deployments
🏷️ Version Tags - Pin to specific versions for stability
⚡ Automatic Publishing - New images published on every release
Test Plan
docker pull ghcr.io/yeraze/meshmonitor:latestNotes
After merge, the first Docker image will be automatically built and published to GHCR. The image will be publicly accessible at
ghcr.io/yeraze/meshmonitor:latest.Users can immediately start using:
🤖 Generated with Claude Code