Commit Graph
1246 Commits
Author SHA1 Message Date
agessaman 0d50f5feec fix(text): dispatch on the text type and guard replays
Two things firmware does that openHop did not.

Dispatch by type, not by text. simple_room_server::onPeerDataRecv routes
CLI_DATA/CLI_COMMAND to the admin CLI and turns PLAIN into a post;
simple_repeater has no chat function, so it runs every accepted type
through handleCommand with no text test at all. openHop asked
_is_cli_command(message_text) for both, and so got both wrong: a PLAIN
"set ..." ran as a command instead of being posted, a command whose text
was not in the prefix list was published to the room, and an admin's
mistyped repeater command vanished silently instead of being answered.

Guard replays. Both servers open the branch with
`sender_timestamp >= client->last_timestamp`, treat equality as a retry
they accept but do not re-run, and drop anything older. openHop checked
neither, so replaying an admin command with the same timestamp and
different attempt bits re-executed it -- packet dedup does not catch that
because the bytes differ. Note ACL._is_replay is not reusable here: its
strict <= rejects the equal case this branch accepts.

Both read from packet.decrypted, which openhop_core now publishes. A core
too old to report them falls back to the previous text-prefix behaviour
and logs a warning rather than dropping every message.
2026-09-07 22:27:50 +01:00
agessaman e6d1a43d86 fix(room): a guest may read the room, not post to it
simple_room_server::onPeerDataRecv takes the
`(client->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST` branch for a
PLAIN message and stores nothing. openHop called add_post for anyone who
authenticated, so knowing the guest password was enough to write to a room:
add_post caps length and rate-limits per client, but never looks at the role.

Also warns once per message when openhop_core does not publish txt_type,
so the open text-type gate is visible in the log rather than silent.
2026-09-07 22:27:50 +01:00
agessaman 369c6142b6 fix(text): gate the CLI on the text type, not the text
simple_repeater::onPeerDataRecv and simple_room_server::onPeerDataRecv
both open their TXT_MSG branch by rejecting any flags outside
{PLAIN, CLI_DATA, CLI_COMMAND}. openHop had no such gate: it decided what
a message was purely from whether the text looked like a command, so a
SIGNED_PLAIN -- a room post, whose 4-byte author prefix the core handler
has already stripped, leaving bare text -- ran as an admin CLI command
whenever it happened to start with a command prefix, and was stored as a
post on a room server that firmware would have dropped.

CLI_COMMAND (3) joined the accepted set when firmware split "a CLI
command" out of CLI_DATA (MeshCore 2c0ace25); CLI_DATA stays in it
because that is what every released client still sends.

Reads the type from packet.decrypted, which the core text handler now
publishes. A core too old to report it leaves the gate open rather than
dropping every message.
2026-09-07 22:27:50 +01:00
Rightup aeb749218f fix: dash packet order summary as aggregate details 2026-09-07 21:34:36 +01:00
Treehouse-00 b526343981 feat(api): summarise neighbor link history in buckets 2026-09-07 21:29:48 +01:00
Rightup 0a4c31c4fb fix(manager): Add SSE to UI 2026-09-07 21:29:48 +01:00
Treehouse-00 4d8f8d41bd feat(plugins): stream an install or update's progress over server-sent events 2026-09-07 21:29:48 +01:00
Treehouse-00 89d8a9813f perf(web): send the frontend compressed and cacheable 2026-09-07 21:29:48 +01:00
Rightup b9c73def4a feat: enhance plugin web path resolution and normalization in API endpoints and HTTP server 2026-09-07 21:29:48 +01:00
Adam Gessaman a2f883fd7d Merge pull request #452 from agessaman/ci/ruff-format-dev
ci: reformat two files ruff 0.15.14 would rewrite
2026-09-07 13:15:10 -07:00
agessaman 3efcd37f6f ci: reformat two files ruff 0.15.14 would rewrite
`pre-commit run --all-files` fails on `dev` at the `ruff-format` hook, so every
open pull request is red on a check that has nothing to do with its own diff.
Both files arrived with 72da7ee (pr-436) formatted by an earlier ruff.

Purely cosmetic, and exactly what `ruff format` produces:

- `sqlite_handler.py` — a generator expression and an f-string concatenation
  that both now fit inside the line limit unsplit.
- `test_companion_import_repeater_contacts.py` — the blank line ruff requires
  after a module docstring.
2026-09-07 13:05:19 -07:00
Yellowcooln 726cbf99b9 Merge pull request #448 from openhop-dev/fix-post-upgrade
add post upgrade messege on how to fix plugin + QR code for PUB keys
2026-09-07 12:01:47 -04:00
Rightup 2eaf380d3d fix: add nosec comments for subprocess.run calls in service_utils.py 2026-09-07 16:51:27 +01:00
Rightup 0b98d2b680 add qr code to FE 2026-09-07 13:39:29 +01:00
Rightup 63ce5b4294 fix: include public key in API response for configured room servers 2026-09-07 13:36:04 +01:00
Rightup ab2e79d771 add further ins to update process. 2026-09-07 12:55:53 +01:00
Rightup cc7a1c426d fix: add notes on how to fix the issue. 2026-09-07 12:12:50 +01:00
Rightup b1c9b6f6a5 fix: enhance plugin manager service bootstrap to support sudo execution 2026-09-07 12:06:38 +01:00
Rightup 3afec59c1a feat: implement plugin manager service bootstrap and add tests 2026-09-07 10:31:05 +01:00
Rightup b65e54196b Merge branch 'pr-436' into dev 2026-09-06 20:35:09 +01:00
Rightup c9fddbfd4b fix: update file handling to use UTF-8 encoding for YAML operations 2026-09-06 20:32:43 +01:00
Lloyd f634c684ce Merge pull request #442 from openhop-dev/feat-plugin-manager
Feat plugin manager
2026-09-05 23:44:58 +01:00
Rightup 6a2315d5ce feat: add plugin update checking and display in Plugins view 2026-09-05 23:37:22 +01:00
Lloyd d2c42a8c6c Merge pull request #441 from yellowcooln/fix/docker-compat
fix(docker): preserve upgrade defaults and use catalogue plugins
2026-09-05 23:32:02 +01:00
yellowcooln f1fff383b4 fix(docker): preserve upgrade defaults and use catalogue plugins 2026-09-05 18:20:58 -04:00
Rightup 8182242f9f update home page plugin buttons 2026-09-05 22:07:12 +01:00
Rightup 3836cb8cf3 feat(catalogue): add homepage field to CataloguePlugin and validate URL format - tighten the fixes to be portable across macOS/Linux 2026-09-05 21:56:19 +01:00
Lloyd 632ec89ee7 Merge pull request #440 from yellowcooln/fix/plugin-security-hardening
fix(security): address authorization, file access and privileged execution vulnerabilities
2026-09-05 21:05:42 +01:00
yellowcooln 766c2c2605 docs(web): direct frontend edits to matching RepeaterUI branch 2026-09-05 15:39:17 -04:00
yellowcooln 4aa67a354f fix(plugins): recover interrupted Python environment rebuilds 2026-09-05 13:15:26 -04:00
yellowcooln 1cafdd0383 fix(auth): serialize setup and configuration import transactions 2026-09-05 13:15:26 -04:00
yellowcooln c294de2b12 fix(web): confine frontend assets by resolved path components 2026-09-05 13:11:11 -04:00
yellowcooln 06c41bccba fix(plugins): harden provisioning, runtime, IPC and native upgrades 2026-09-05 13:04:34 -04:00
Rightup c32fb83626 update ui for larger imgs 2026-09-05 00:13:04 +01:00
Rightup 6a393c5c61 feat(plugins): add runtime endpoint and functionality to retrieve plugin runtime data 2026-09-04 22:16:52 +01:00
Rightup 6f9ef1c394 fix(stats): handle missing plugin argument in StatsApp routing 2026-09-04 21:38:10 +01:00
Rightup 9ce23a77d5 feat(plugins): add plugin manager service installation and restart logic 2026-09-04 21:30:29 +01:00
Yellowcooln 65836c2e2f Merge pull request #438 from yellowcooln/fix/plugin-manager-docker-runtime
Harden plugin manager Docker and catalogue support
2026-09-04 13:37:18 -04:00
yellowcooln c6098f88e3 Download approved plugins from GitHub Releases 2026-09-04 13:08:09 -04:00
yellowcooln eb8f934124 Install approved plugin wheels directly from R2 2026-09-04 12:13:08 -04:00
yellowcooln 3e008cac26 fix(plugins): use hosted catalogue endpoint 2026-09-04 11:00:03 -04:00
yellowcooln 9150264f49 ci: satisfy plugin branch quality gates 2026-09-04 10:42:33 -04:00
yellowcooln c0aeb89399 fix(docker): supervise plugin manager lifecycle 2026-09-04 10:39:42 -04:00
Rightup 8054333e3f feat:add ui switcher for plugins 2026-09-04 11:12:15 +01:00
Rightup ae8569bbec feat: add web frontends API endpoint and corresponding tests 2026-09-04 11:09:48 +01:00
Rightup 472025da16 feat:add images to catalogue 2026-09-04 08:58:18 +01:00
Rightup 03efbf1a21 feat:plugin-ui 2026-09-03 22:21:07 +01:00
Rightup 26560fd50a Add comprehensive tests for plugin management and IPC functionality
- Introduced tests for the plugin catalogue installation and update flows in `test_plugin_catalogue_install.py`.
- Added unit tests for the GitHub Releases plugin client in `test_plugin_github_releases.py`.
- Implemented IPC tests for the plugin manager Unix socket protocol in `test_plugin_ipc.py`.
- Created lifecycle and installation tests for the plugin manager in `test_plugin_manager_lifecycle.py`.
- Added tests for plugin manifest parsing in `test_plugin_manifest.py`.
- Developed tests for plugin filesystem layout and state management in `test_plugin_storage.py`.
2026-09-03 22:16:38 +01:00
Perry MosbacherandClaude Opus 5 72da7eed7b fix(companion): make bulk contact import match stored advert types
"Import repeater contacts" wrote nothing whenever a contact-type filter was
supplied, and reported success having imported zero rows.

`adverts.contact_type` holds the *display* name written through
`handler_helpers.discovery.NODE_TYPE_NAMES` — "Chat Node", "Repeater",
"Room Server", "Sensor". The import API accepts MeshCore's names — "companion",
"repeater", "room_server", "sensor", validated in
`companion_endpoints.import_repeater_contacts`. `companion_import_repeater_contacts`
compared them directly:

    query += f" AND contact_type IN ({placeholders})"

so "repeater" never matched "Repeater", "room_server" never matched
"Room Server", and "companion" never matched "Chat Node" at all. On a live
repeater with 285 adverts, every one of the four permitted values selected 0
rows, as did all four together; only an unfiltered import worked.

A second defect had the same cause. The adv_type lookup normalised the stored
name (`lower()`, spaces to underscores) but mapped it with the API-name table,
where "chat_node" is absent — so chat contacts imported as adv_type 0 rather
than 1, even on an unfiltered import. That is silently wrong rather than empty,
and needs a manual `UPDATE companion_contacts SET adv_type=1 WHERE adv_type=0`
to repair.

Both sites derived the mapping independently, which is how they drifted. They
now share one `_ADVERT_TYPE_BY_STORED` table keyed on the normalised stored
form: the filter reverse-maps the requested API names onto the stored keys and
compares `LOWER(REPLACE(TRIM(contact_type), ' ', '_'))`, and the adv_type comes
from the same table. Filtering and limiting stay in SQL. An unrecognised type
still selects nothing rather than silently widening the query.

Tested against the real stored forms: 6 of the 8 new tests fail on current dev
(each of the four type filters, all four combined, and the adv_type mapping)
and all 8 pass with this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 11:06:44 -04:00
Perry MosbacherandClaude Opus 5 bd0c340856 fix(sqlite): stop creating the upstream index before its columns exist
`SQLiteHandler.__init__` runs `_init_database()` and then `_run_migrations()`.
`_init_database` created `idx_packets_upstream_time`, which spans
`upstream_hash` and `upstream_hash_size` -- columns that, on a database
predating migration 13, only appear once `add_upstream_hash_to_packets` has
added them in the *later* migration step.

Fresh installs were fine, because the `CREATE TABLE` carries both columns. Any
existing database raised `no such column: upstream_hash` part-way through
`_init_database`, which caught and logged it. Every statement after the failing
index was therefore skipped -- including the `room_messages` and
`room_client_sync` tables -- and the daemon started with a partially-created
schema while reporting itself healthy: service active, radio initialised,
companions serving, HTTP up, and no packets recorded.

The `add_upstream_hash_to_packets` migration already creates this index, right
after adding the columns it depends on, so the copy in `_init_database` was
redundant as well as too early. Remove it and leave the index to the migration
that owns those columns. All three paths still end up with it: a fresh database
gets the columns from `CREATE TABLE` and the index when the migration runs, an
un-migrated database gets both from the migration, and an already-migrated one
kept the index from its earlier run.

Observed on a Raspberry Pi 4 upgrading a ~13 MB production database from 1.1.1;
the repeater was off-air for four minutes before the cause was found. It
self-heals on a second start, because the migration still runs after the failed
init, which makes it easy to misread as harmless.

`tests/test_sqlite_upgrade_existing_db.py` builds a database with the
pre-migration-13 schema and asserts `_init_database` runs to completion, keyed
on tables created after the old failure point. Asserting only on the upstream
columns is not sufficient -- the migration adds those either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 09:05:50 +01:00