Commit Graph
775 Commits
Author SHA1 Message Date
Rightup 6a2315d5ce feat: add plugin update checking and display in Plugins view 2026-09-05 23:37:22 +01: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
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 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 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
yellowcooln f665237825 ci: format room server for pre-commit 2026-09-02 15:57:30 -04: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
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
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 50921e1400 refactor(sensors): remove legacy Station G3 fallback 2026-09-01 10:48:36 -04:00
yellowcooln 40a4e4f245 fix(sensors): normalize modem power telemetry 2026-08-30 20:55:48 -04:00
CB 035ba2f672 bus current power sensor payload 2026-08-30 11:42:56 -04:00
Rightup e28be27a77 Merge branch 'pr-402' into dev 2026-08-27 20:53:21 +01:00
Rightup 8bc2cd68e9 fix: update map UI 2026-08-27 20:52:12 +01:00
Rightup 539f9d4454 fix:update ui-yellow 2026-08-24 14:27:37 +01:00
Rightup 24a45ba641 Merge branch 'pr-397' into dev 2026-08-24 14:21:50 +01:00
yellowcooln f280bbdc5c fix: secure modem migration and backup paths 2026-08-24 09:00:50 -04:00
Rightup 303e606bd4 fix:remove-ui-blocker 2026-08-24 13:55:57 +01:00
yellowcooln ada8ba98fb Merge current dev and preserve modem tokens 2026-08-24 08:50:44 -04:00
Rightup 047a68706d Merge branch 'pr-408' into dev 2026-08-24 13:48:47 +01:00
Rightup 7bd0c77c49 Merge branch 'pr-403' into dev 2026-08-24 13:28:04 +01:00
TeTeHackoandClaude Fable 5 8acba8394f fix(rooms): advertise settings.node_name from the CLI path too
The room CLI advert read only settings.room_name while the daemon
scheduler and the HTTP endpoint read settings.node_name -- so when the
two keys differed, a single `advert` issued over the mesh CLI renamed
the room on the mesh to the identity-name fallback. Both read sites now
resolve node_name -> room_name -> identity name.

Seen in production on 2026-08-19: a room configured with node_name only
renamed itself after one CLI advert and clients tracked it under the
identity name until the next scheduler advert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 00:05:30 +02:00
Thibault VINCENT 8008f09634 feat(ws): carry the sidebar vitals in the stats broadcast, decimate the 24h aggregate
The 5s WebSocket broadcast used to carry uptime_seconds alone — while
the dashboard sidebar starved: its duty-cycle gauge, noise floor and
mode never travel over WS, and the heartbeat marks stats fresh, which
suppresses the UI's HTTP fallback poll. The vitals now ride every
beat, all cheap in-memory scalars (~200 bytes): utilization_percent,
noise_floor_dbm (kept on record, no per-beat SQL), the repeater mode
(so a change made over RF or CLI reaches the web badge), and the
advert tier counters the sidebar's OK/Drop line shows, injected as a
callback from the daemon like the other collector hooks.

In exchange the heavy get_packet_stats(24h) SQL aggregate is decimated
to one beat in six (30s): nobody reads a 24h cumulative at 5s
resolution, and the UI already treats both payload fields as optional,
so older dashboards keep working. Net wire traffic goes down.

Signed-off-by: Thibault VINCENT <root@devcat.org>
2026-08-22 01:28:50 +02:00
Thibault VINCENT a725dfbe85 fix(api): persist the mode selected through set_mode
The mode selector applied its choice to the shared config dict — which
the engine reads live, so the switch felt effective — but nothing ever
wrote it to disk: a restart silently reverted the node to its previous
mode. The web UI is blameless; it calls /api/set_mode expecting it to
persist (SetCommand.ts even documents that expectation), and the RF
admin path already persists the same change through
_apply_config_update.

set_mode now saves through config_manager.update_and_save, the same
path the settings endpoints use, and reports persisted: true. If the
save fails the live switch stands — matching the old behaviour — but
the response is a loud error instead of a silent future loss.

Signed-off-by: Thibault VINCENT <root@devcat.org>
2026-08-21 22:16:22 +02:00
agessaman 7a2fb9b2df fix(acl): fail closed when openhop_core is too old for the role numbering
acl.py kept its own copy of the role constants, so an installation could
pair the new numbering with a core that still derives is_admin from the
0x02 bit. That combination announces a room server's READ_WRITE clients
(role 2) as admins on the login reply's is_admin byte — silently, and
exactly where this change is meant to make the two bytes agree. The
dependency is a mutable openhop_core@dev, so nothing prevented it.

Import the roles and acl_is_admin/acl_role from openhop_core instead of
duplicating them, and raise an actionable ImportError when they are
absent: refusing to start beats misannouncing a client's privileges. The
drift test no longer skips on an old core, so CI reports the mismatch
rather than hiding it.
2026-08-21 12:00:58 -07:00
agessaman 437a7ee332 fix(api): declare read_write in the ACL client permissions enum
The ACL client list now reports the real role, so a room server's
read-write clients emit "read_write" — a value the OpenAPI schema did
not allow, which generated clients and validators reject.

Add it to the enum, document all four roles, and add a test asserting
every role_name() we can emit is declared in the schema.
2026-08-21 11:57:24 -07:00
agessaman 3023eaca89 fix(acl): align ACL role numbering with upstream MeshCore
OpenHop numbered the ACL roles GUEST=0x01, ADMIN=0x02, READ_WRITE=0x01.
Firmware ClientACL.h uses GUEST=0, READ_ONLY=1, READ_WRITE=2, ADMIN=3 in
the low two bits, so our admin byte decoded as READ_WRITE in every stock
MeshCore client (KiekR showed admin sessions as guest and hid the admin
controls), and GUEST/READ_WRITE were indistinguishable from each other.

Adopt the firmware values and split the guest password by server type,
as firmware does: a repeater's guest password grants GUEST (fetch base
telemetry, no settings changes) and a room server's grants READ_WRITE
(post and read messages). Blank-password read-only access stays GUEST,
matching simple_room_server's allow_read_only branch.

Also fix the mesh-CLI admin gate in text.py, which shadowed a local
PERM_ACL_ADMIN = 0x02 and tested the bit rather than the role — under
the corrected numbering that would have let READ_WRITE clients run admin
commands. The web ACL list now reports the real role name instead of
collapsing every non-admin to "guest".

Requires openhop_core with acl_is_admin() in the login reply
(openhop_core fix/login-perms); the two must land together.

Fixes #388
2026-08-21 11:17:15 -07:00
yellowcooln 4ec79ed1dc fix: centralize config redaction marker 2026-08-20 13:37:45 -04:00
yellowcooln 301ba2cce8 fix: read canonical modem token status 2026-08-20 13:21:51 -04:00
yellowcooln 76e9a9c745 feat: migrate openHop modem sensor 2026-08-20 12:17:26 -04:00
yellowcooln 9511a8477f feat: migrate openHop modem transport names 2026-08-20 09:40:09 -04:00