Commit Graph
1273 Commits
Author SHA1 Message Date
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
Rightup bddb21a14a fix: correct project description in pyproject.toml 2026-09-03 08:51:14 +01:00
Rightup 77e938a167 Merge pull request #427 from yellowcooln/ci/fix-precommit-format 2026-09-03 08:50:08 +01:00
Rightup e476b6fb51 fix(api): improve send_advert function handling and response messages 2026-09-03 08:49:25 +01:00
Rightup af21189115 Merge pull request #431 from pfmos/fix/test-suite-self-kill 2026-09-03 08:44:53 +01:00
Perry MosbacherandClaude Opus 5 19ab3df5fc test: stop the suite killing itself part-way through
`RepeaterDaemon._shutdown` arms an exit watchdog -- a `threading.Timer` that
calls `os._exit(0)` after `SHUTDOWN_EXIT_GRACE_S` (5 s). That is right in
production: it guarantees SIGTERM is honoured.

`test_run_starts_http_and_handles_dispatcher_cancelled_gracefully` awaits
`daemon.run()`, whose `finally` reaches `_shutdown()`, so it armed a real
watchdog and returned. Five seconds later the timer fired and took the whole
pytest process down.

The failure mode is what makes this expensive: `os._exit(0)` exits with **status
zero**. pytest produces no summary line and a truncated progress bar, so the run
looks like it simply ended -- CI reads exit 0 as green on a run that never
finished, and any failure after that point is invisible. Locally the suite
appeared to hang at ~77% with no failures reported; it was actually being killed
there.

It also misattributes itself. The test blamed is whichever happened to be
running five seconds later, so it moves between runs
(`test_route_login_server_companion_only_skips_login_helper` and
`test_route_login_server_hash_collision_offers_to_both` on two runs here) and
never points at the cause. Finding it needed wrapping `_arm_exit_watchdog` to
log the arming test.

Two changes:

- Patch `_arm_exit_watchdog` for that test, matching the guard the shutdown
  tests in `test_main_py_coverage.py` already use.
- Add `tests/conftest.py` with an autouse fixture that fails any test leaving a
  live `shutdown-watchdog` timer, and cancels it. This blames the responsible
  test at the moment it leaks, rather than an unrelated one later. Verified it
  catches the original bug: reverting the one-line fix turns the silent
  process-kill into a teardown error naming the right test.

`test_exit_watchdog_is_a_daemon_timer_that_forces_exit` is unaffected -- it sets
a 0.05 s grace and asserts on a patched `os._exit` before returning, so it
leaves nothing behind.

With this the suite runs to completion: 3 failed, 1580 passed. Those 3 are
pre-existing on `dev` and unrelated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 23:14:35 -04:00
yellowcooln 1d5b8f8537 ci: expand pull request validation 2026-09-02 16:22:05 -04:00
yellowcooln f665237825 ci: format room server for pre-commit 2026-09-02 15:57:30 -04:00
Lloyd efc5616ec4 Merge pull request #421 from Wild-Nisse/fix/companion-battery-mv
fix(companion): report real battery voltage in core stats
2026-09-02 20:53:46 +01:00
yellowcooln 991cf8c47e fix(companion): resolve storage paths consistently 2026-09-02 12:05:57 -04:00
yellowcooln e2ea8b01ad fix(companion): reject non-finite battery values 2026-09-02 10:12:26 -04:00
Rightup ca3d4ecb8f fix(docs): update references from pyMC to openHop in README 2026-09-01 21:34:34 +01:00
Rightup 5c4615dd58 Merge branch 'pr-422' into dev 2026-09-01 21:00:17 +01:00
yellowcooln 50921e1400 refactor(sensors): remove legacy Station G3 fallback 2026-09-01 10:48:36 -04:00
yellowcooln 0d28072063 merge: sync modem power telemetry PR with dev 2026-08-30 20:56:48 -04:00
yellowcooln 40a4e4f245 fix(sensors): normalize modem power telemetry 2026-08-30 20:55:48 -04:00
Yellowcooln a0d75b6739 Merge pull request #419 from yellowcooln/feat/proxmox-debian13-installer
feat: modernize Proxmox LXC installer for Debian 13
2026-08-30 20:51:49 -04:00
skjegg a3f8ec18e7 fix(companion): report battery and storage to companion clients
The previous commit fixed battery_mv in the STATS_TYPE_CORE frame, but that is
not the path companion apps use to display battery. CMD_GET_BATT_AND_STORAGE
goes through `CompanionFrameServer._get_batt_and_storage()`, an openhop_core
extension hook documented as "Default: all zeros" that the repeater never
overrode — so apps showed 0% regardless of what any sensor reported.

Override it in the repeater's frame server: battery from the same sensor lookup
added for core stats, storage from the filesystem holding the configured
storage_dir. Both are wired in from main.py alongside stats_getter.

Found by deploying the previous fix and observing that Home Assistant showed the
modem battery correctly while the phone app still showed 0% — two different
paths, only one of which had been fixed.
2026-08-30 12:18:52 -04:00
CB 035ba2f672 bus current power sensor payload 2026-08-30 11:42:56 -04:00
skjegg 0a2eeea8ee fix(companion): report real battery voltage in core stats
`_get_companion_stats` hardcoded `battery_mv: 0`, so every MeshCore companion
client connected to a Python repeater showed no battery, even when a sensor
plug-in was reporting one. The protocol side is already complete: the frame
server packs `battery_mv` into the STATS_TYPE_CORE frame and clients request it
via CMD_GET_BATT_AND_STORAGE.

Surface the first sensor reading that publishes a usable battery voltage,
accepting either `battery_voltage_mv` or `battery_voltage_v`. This covers an
attached UPS HAT as well as the battery of an openHop modem the repeater is
driving, which is the case that prompted this: a solar-powered modem whose
battery was invisible to the companion app right up until it went flat.

Returns 0 when no sensor reports a battery, which is what clients already
expect from a mains-powered node, and the lookup never raises — stats must
not be able to take down the companion connection.
2026-08-30 08:54:36 -04:00
yellowcooln 9e79acc887 docs: update Proxmox LXC requirements 2026-08-29 23:46:49 -04:00
yellowcooln a6ab8bc906 feat: shallow clone Console distribution 2026-08-29 23:19:09 -04:00
yellowcooln def72a2b19 feat: fully update Debian before Repeater install 2026-08-29 23:04:59 -04:00
yellowcooln dc8a42a16a fix: set Proxmox container description correctly 2026-08-29 22:58:35 -04:00