1453 Commits
Author SHA1 Message Date
yellowcooln 72266e24f5 fix(api): accept RRD query parameters during HTTP dispatch 2026-10-29 14:04:46 +00:00
Rightup 71495b6c3d test(policy_engine): add regression test for cache leakage across evaluations 2026-10-29 14:04:46 +00:00
Rightup a5cccc532c updated UI 2026-10-29 14:04:46 +00:00
Rightup 67df87c9b2 feat(api): enhance MQTT config handling for renamed brokers and password validation 2026-10-29 14:04:46 +00:00
Treehouse-00 49c9cda21d fix(api): keep MQTT broker passwords out of stats and the config export
/api/stats embeds config.mqtt_brokers as stored, so every broker
password went out on an endpoint every page polls, into browser memory,
devtools captures and anything holding a token. The default config
export, which redacts the login passwords, the JWT secret, the modem
token and the identity keys, carried them as well.

Send each broker password as the existing "*** REDACTED ***" sentinel
in both. The settings UIs post the brokers back as they received them,
so update_mqtt_config turns the sentinel into the stored password of
the broker with that name, and asks for the password again when no
single stored broker has the name; config_import keeps the stored
password in place of a redacted one, as it already does for the
security secrets and identity keys.
2026-10-29 14:04:46 +00:00
Rightup 6023e5d32c fix(tests): remove unnecessary blank line in test_trace_api.py 2026-10-29 14:04:46 +00:00
Rightup 65288f762e feat(trace): enhance trace logging and validation for path hops 2026-10-29 14:04:46 +00:00
vanous e1ca34e5af feat(api): add direct multi-hop TRACE endpoint 2026-10-29 14:04:46 +00:00
Rightup 85fc14a6b2 feat(theme): implement theme management 2026-10-16 16:41:15 +01:00
Lloyd fa2922511d Merge pull request #521 from flackrat/patch-1
Clear the policy decrypt cache at the start of evaluate()
2026-10-16 13:47:10 +01:00
Lloyd ad83191b54 Merge pull request #523 from yellowcooln/feat/victron-vedirect
feat(sensors): add read-only Victron VE.Direct telemetry
2026-10-16 12:46:16 +01:00
yellowcooln 127b0a4df1 fix(sensors): normalize Victron power precision and dependency setup 2026-10-12 15:02:09 -04:00
yellowcooln 1f2fb86c78 feat(sensors): calculate Victron load power from voltage and current 2026-10-12 14:33:23 -04:00
yellowcooln 8449a6372a fix(sensors): preserve V1 grouping with readable status labels 2026-10-12 14:13:15 -04:00
yellowcooln e94810cf10 fix(sensors): decode Victron values in the drop-in sensor 2026-10-12 13:40:04 -04:00
yellowcooln 3c9b5caf58 merge: sync Victron sensor branch with upstream dev 2026-10-12 12:30:03 -04:00
yellowcooln 98016dcc67 fix(sensors): render Victron statuses through metric descriptors 2026-10-12 12:30:03 -04:00
Lloyd a50e449bbd Merge pull request #522 from agessaman/fix/companion-early-and-multipart-acks
fix(router): confirm companion sends from early direct and multipart ACKs
2026-10-12 11:56:01 +01:00
yellowcooln 88e55c0e7e feat(sensors): label Victron charge states using official protocol 2026-10-11 22:33:15 -04:00
yellowcooln 17aa44bf17 feat(sensors): add read-only Victron VE.Direct telemetry 2026-10-11 22:29:25 -04:00
agessaman d8cc4609ca fix(router): confirm companion sends from early direct and multipart ACKs
A direct ACK that still had hops on its path was registered with the
dispatcher but never offered to the companion bridges. Firmware calls
onAckRecv on it before the next-hop check (Mesh.cpp), so a co-hosted
companion that sent the message is confirmed by the ACK it overhears on
the way back. Without it the companion waited for a 0-hop copy it may
never receive, and the send was reported as unconfirmed.

MULTIPART ACKs were likewise registered but not fanned out. The wrapper
is unwrapped as firmware does (route and path kept, 1-byte prefix
dropped) and handed to the bridges as a plain ACK, since bridges have no
MULTIPART handler.

Both paths go through _companion_bridges_for_packet, so policy drops and
origin exclusion still apply. Engine forwarding of the early ACK is
unchanged.
2026-10-10 19:23:07 -07:00
flackrat f2401f4436 Clear the policy decrypt cache at the start of evaluate()
Python reuses id() values once a packet is freed, so a cache that outlives one evaluate() call can give a new packet an older packet's sender and text. Fixes #520.
2026-10-10 22:51:09 +01:00
Lloyd 3c4bf3a958 Merge pull request #513 from agessaman/feat/guest-login-empty-guest-password
feat(acl): admit a blank password as guest when the repeater has no guest password
2026-10-06 12:14:18 +01:00
Lloyd 4519085b86 Merge pull request #512 from agessaman/fix/acl-target-multibyte-hash
fix(api): match ACL identity_hash on the full multi-byte hash
2026-10-06 12:14:03 +01:00
agessaman 20788733f5 fix(acl): authenticate by registered type and keep stored grants from blank logins
A room config without a "type" key authenticated as a repeater, so the
empty-guest-password rule admitted blank logins to a room with
allow_read_only off. The login callback now passes the registered type.

A blank-password newcomer on a full table could evict a stored read-only or
read-write grant and delete its row. It now evicts only entries that are not
stored, and is refused when none are left.
2026-10-02 16:07:16 -07:00
agessaman 737fda2628 feat(acl): admit a blank password as guest when the repeater has no guest password
Firmware simple_repeater compares a blank password from a sender not in the
ACL with guest_password, which defaults to empty, so anyone may log in as a
guest and fetch base telemetry. The repeater refused it unless
allow_read_only was on. Room servers are unchanged.
2026-10-02 15:48:13 -07:00
agessaman 5bbc01f1c9 fix(api): keep one-byte numeric ACL hashes and filter acl_clients by prefix
A bare number 0-255 named the first hash byte before multi-byte modes and
still does. acl_clients compared its identity_hash filter against the first
key byte only; it now matches the same prefix as remove.
2026-10-02 15:39:57 -07:00
agessaman 8c08bc91dc fix(api): match ACL targets on the full multi-byte identity hash
acl_clients lists identity_hash at the path_hash_mode width (2 or 3 bytes),
but _acl_targets compared the whole value against the key's first byte, so a
remove from the Sessions page selected no ACL in 2- and 3-byte modes and the
grant stayed. Treat the hash as a public-key prefix; a bare number is read at
the configured width.
2026-10-02 15:33:01 -07:00
Rightup 9c958e3111 feat: update ui 2026-10-02 15:09:44 +01:00
Rightup 05bea635fd Merge commit 'c6722f1' into dev 2026-10-02 11:06:08 +01:00
Rightup a285400227 fix: preserve room identity during hot reload 2026-10-02 11:05:04 +01:00
Treehouse-00 91ebdd0b36 docs(presets): correct the Waev preset's routing notes
The Waev preset's header described a router constant and a 1500 ms
broker-A-then-B failover that Waev's router no longer has, and called
mqtt-a.waev.app "force primary, no failover". Waev routes each
connection to one of two broker pools and fails over to the other; the
pool hostnames only choose which is tried first.

Describe that without naming Waev's internals. The override example in
config.yaml.example named a broker, waev-b, that the preset does not
have, so it did nothing; it now prefers pool A by overriding the host.
The two test docstrings that repeated the old claims follow.
2026-10-02 10:57:51 +01:00
agessaman 3f66829ec0 fix(cli): tolerate a core without __version__ or a changed CLI
mesh_cli is imported on every boot, so it reads openhop_core.__version__
with getattr rather than importing it, and /api/stats does the same instead
of failing the whole response. Registering the repeater version with the
companion CLI cannot stop a companion loading if the core's signature
changes.
2026-10-02 10:57:51 +01:00
agessaman dcd972932a test(companion): describe what the bridge ver test covers 2026-10-02 10:57:51 +01:00
agessaman 016ee64cbb fix(cli): report the installed repeater and core versions from ver
MeshCLI's `ver` read config["version"], which nothing sets, so every node
answered the fallback "openHop_repeater v13". Report the installed packages
instead: "openHop_repeater v<repeater> (core v<core>)".

The virtual companion's CLI `ver` now names the repeater too: the bridge
registers its version with the core's companion CLI when that core has one.
2026-10-02 10:57:51 +01:00
agessaman d5e4bd754c fix(config): name the radios[] entry with a stray sync word
build_radio_stack pops _radio_id before building each radio, so the
warning never named the entry on a real node. A _radio_label survives the
pop, and the warning now points at radios[<id>].radio, saying the value is
inherited only when it is.

config.yaml.example documents sync_word.
2026-10-02 10:57:24 +01:00
agessaman 0ed70f4d1b test(config): cover an entry overriding the inherited sync word, and a null value 2026-10-02 10:57:24 +01:00
agessaman 688689fbbd feat(config): warn when a USB/TCP modem is not on MeshCore's sync word
A modem on another sync word connects, answers pings and transmits, but hears
no MeshCore traffic, so the failure is silent. Since per-radio sections
overlay the top-level ones, a radios[] modem entry inherits the top-level
radio.sync_word, which the SX1262 driver ignores; a stray value there (e.g.
13380 / 0x3444) left a real node's TCP modem deaf.

Log a warning naming the radio, the value and the inheritance, and keep
passing the configured value through so a deliberate private network is not
overridden. Only the USB and TCP modem drivers push the sync word to the
radio, so only they are checked.
2026-10-02 10:57:24 +01:00
agessaman ed89cb86a4 fix(storage): use fixed statements in delete_neighbors
One pair of statements for all neighbours and one for a prefix, bound as a
parameter, rather than a WHERE clause formatted into the SQL, which bandit
flags.
2026-10-02 10:57:24 +01:00
agessaman 9d32a99209 fix(cli): remove only neighbours, in one transaction
neighbor.remove now resolves its targets in SQL: zero-hop repeaters, the
nodes `neighbors` lists, all of them or those whose key starts with the
given prefix, with their cached scopes. A keyed remove no longer deletes a
companion's, room's or distant repeater's advert history that happens to
share the prefix, and a locked database leaves every neighbour in place
instead of some of them. The prefix is compared literally rather than
LIKE-matched.

The prefix delete's None branch, which cleared every advert, is gone along
with get_neighbors' raise_errors flag, which nothing uses now.
2026-10-02 10:57:24 +01:00
agessaman 6fdb12ef3d fix(cli): don't report OK when neighbor.remove all can't read neighbors
get_neighbors answers {} on a storage error, which `neighbor.remove all` took
for "no neighbours" and answered OK. Add an opt-in raise_errors to
get_neighbors (and the storage collector passthrough) and use it here, so a
failed read surfaces as an error.
2026-10-02 10:57:24 +01:00
agessaman 7c24a7f585 fix(cli): clear only the listed neighbors with neighbor.remove all
The storage call behind a clear-all deletes every advert, so
`neighbor.remove all` also wiped companions, room servers and multi-hop
nodes, with their first-seen and advert counts. Firmware clears only its
neighbour table. Remove just the zero-hop repeaters that `neighbors` lists,
each by its full key, and cover it against real SQLite storage.
2026-10-02 10:57:24 +01:00
agessaman faff312dac fix(cli): require neighbor.remove all to clear every neighbor
handle_command strips the line, so `neighbor.remove ` with nothing after it
never matched the "neighbor.remove " route and answered "Unknown command";
the empty and whitespace branches in _cmd_neighbor_remove were reachable only
from a unit test. Firmware (CommonCLI.cpp) clears every neighbour for an
empty key, which a stray trailing space can trigger.

- `neighbor.remove all` clears them all; a missing key answers
  "ERR: Missing pubkey. Do you mean `neighbor.remove all`?" and deletes
  nothing.
- An odd-length or over-32-byte key answers "ERR: bad pubkey", as firmware's
  fromHex does. Non-hex stays refused.
- Tests go through handle_command, the real entry path.
2026-10-02 10:57:24 +01:00
agessaman 909fd19f73 fix(acl): build the ACL name lookup without SQL in f-strings
Bandit flags an f-string that holds SQL even when only placeholders are
interpolated. Start from a plain SELECT and append the IN (?, ...) clause,
as the other variable-length queries in this file do.
2026-10-02 10:57:24 +01:00
agessaman f38e3561e8 fix(api): refuse identity changes the next boot could not load
Names are unique across the repeater, rooms and companions at boot, so
create_identity and update_identity now refuse a name another identity
uses (including "repeater"), and creating a room with another identity's
key is refused as updating one already was.

A refused room update restores the live entry in place rather than
emptying and refilling it, since the identity manager holds that dict and a
concurrent config save could catch it with no name or key. A bad settings
value is refused too, undoing a rename made earlier in the same request.

An ACL request's identity_hash may be a JSON number, and 0 means hash byte
0 rather than every ACL.
2026-10-02 10:57:24 +01:00
agessaman 28e0f87a7e fix(acl): let an admin grant evict the least recently seen admin
A table full of admins refused every newcomer, and now that entries outlive
a restart that lockout was permanent; the upgrade placeholder made it likely
with max_clients: 1. The placeholder is now 32 (firmware MAX_CLIENTS), and
an admin password login or setperm to an admin role evicts the admin seen
least recently, by its stored last login after a restart. Blank-password and
guest newcomers are still refused, so they cannot strip admins one by one.

An eviction now waits for the newcomer: a refused login (a replay) puts the
evicted entry back, and its stored row is deleted only once the newcomer's
grant is stored, or once that grant's next write succeeds after a failure.
Evicted clients' replay watermarks are kept (capped, in RAM) so an evicted
admin's captured login cannot be replayed when it comes back.

unregister_identity also detaches an ACL listed only by name, after a hash
collision displaced it from the hash index.
2026-10-02 10:57:24 +01:00
agessaman 0a1d53f77f fix(acl): keep an evicted entry when the grant that evicted it fails
A setperm on a full ACL deleted the evicted client's stored entry before
trying to store the newcomer, so a failed write reported an error but still
revoked someone. The eviction is now deleted from the store only once the
grant is stored, and put back in the table if it isn't.

Deleting a room whose rename was not hot-reloaded looked up its live ACL by
the new name only, so the ACL listed under the old name stayed attached and
the next grant wrote the deleted room's rows back. Fall back to the store
key, as move_room_acl does.
2026-10-02 10:57:24 +01:00
agessaman 34c307ee33 feat(acl): keep an ACL entry's last login across restarts
Stored entries now record when they last logged in, so the web UI can
show whether a provisioned grant is still in use after a restart.
Firmware keeps this in RAM only; last_activity still starts at 0 on
load, so "not active since restart" keeps its meaning.

- acl_entries gains a nullable last_login column, added beside the
  table's CREATE so a failed earlier migration cannot lock stored
  admins out of a load that reads it.
- A login by a stored entry writes its time, best-effort, and not when
  the stored second already matches it.
- A guest promoted by setperm keeps its login time; a row taken over
  from another identity's label does not inherit that identity's.
- Key changes, renames and label adoption carry the time; a retried
  load keeps the later of the live and stored times and writes a login
  made while the store was unreadable.
2026-10-02 10:57:24 +01:00
agessaman 6a66096248 feat(acl): name ACL clients from adverts and companion contacts
/api/acl_clients adds client_name and client_type for each entry: the
name the node advertises, from an advert the repeater heard, or else a
companion's contact for it (a companion's own admin is often never heard
directly). Rebuilds the bundled UI (openHop_RepeaterUI feat/acl-management),
which shows the name in place of the one-byte address.
2026-10-02 10:57:24 +01:00
agessaman ec320a0f0e chore(web): rebuild bundled UI assets
Built from openHop_RepeaterUI feat/acl-management (90eebc9): the Sessions
page's Access List tab for managing ACL entries.
2026-10-02 10:57:24 +01:00