Commit Graph

754 Commits

Author SHA1 Message Date
Louis King bb929dfe5f refactor(web): remove deprecated class names and hoist map marker colors
Zero-visual-delta cleanups:

- Drop input-bordered/select-bordered (removed in daisyUI v5; emitted
  no CSS) from all inputs and selects.
- Rename bare `shadow` to `shadow-sm` — in Tailwind v4 bare `shadow` is
  a deprecated alias with the identical value.
- Hoist the hardcoded map marker hex colors into CSS variables in the
  app.css palette block (same values; markers sit on map tiles and stay
  theme-independent by design).
- Convert the five text-base-content/* outliers to the repo's dominant
  opacity-* muted-text idiom (identical rendering on plain text).
- Remove a dead ternary in renderNodeDisplay and an unused iconLock
  import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:30:07 +01:00
Louis King e2c7a0f987 fix(web): repair unstyled channel modal labels and invisible prose link hover
The channels modal still used daisyUI v4's label-text class, which was
removed in v5, so its form labels rendered unstyled. Replace with the
Tailwind equivalent of what v4 produced (text-sm, muted).

.prose a:hover used --color-primary-content (the on-primary foreground),
making hovered markdown links nearly invisible against the page
background. Blend primary toward base-content instead so the hover
shade works in both themes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:28:06 +01:00
JingleManSweep 35037c64f2 Merge pull request #284 from ipnet-mesh/renovate/tailwindcss-monorepo
chore(deps): update tailwindcss monorepo to v4.3.2
2026-06-30 22:39:11 +01:00
JingleManSweep d90e6c1ff7 Merge branch 'main' into renovate/tailwindcss-monorepo 2026-06-30 22:36:57 +01:00
JingleManSweep 75264265f4 Merge pull request #283 from ipnet-mesh/renovate/daisyui-5.x-lockfile
chore(deps): update dependency daisyui to v5.6.6
2026-06-30 22:36:44 +01:00
renovate[bot] 4fa9b9b6ab chore(deps): update dependency daisyui to v5.6.6 2026-06-29 23:16:09 +00:00
renovate[bot] 775c875ef2 chore(deps): update tailwindcss monorepo to v4.3.2 2026-06-29 17:10:38 +00:00
JingleManSweep 369b3c6eb5 Merge pull request #282 from ipnet-mesh/fix/fastapi-0.137-route-introspection
fix(tests): introspect mounted paths via OpenAPI schema, unpin fastapi
2026-06-25 21:03:42 +01:00
Louis King b413904ace fix(tests): introspect mounted paths via OpenAPI schema, unpin fastapi
FastAPI 0.137.0 refactored include_router to keep included routers as
nested objects rather than flattening their routes into app.routes, so
test_app_factory's `{route.path for route in app.routes}` no longer found
the /metrics route (the endpoint still serves; only this introspection
broke). FastAPI now treats router.routes as an internal implementation
detail.

Switch the metrics route checks to the public OpenAPI schema
(app.openapi()["paths"]), which is stable across versions and resolves
router prefixes correctly, and drop the <0.137.0 pin that was blocking
the upgrade.

Verified: app factory tests pass on both 0.136.3 and 0.137.2; full
tests/test_api suite (462 tests) passes on 0.137.2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 21:00:35 +01:00
JingleManSweep 01077b3783 Merge pull request #281 from ipnet-mesh/feat/observer-ingestion-filters
feat(collector): observer allow/deny ingestion filter
2026-06-25 20:52:47 +01:00
Louis King 5fe8ce9156 feat(collector): add observer allow/deny ingestion filter
Restrict which remote observers may ingest events, keyed on the observer's
public key (the <public_key> segment of its LetsMesh upload topic). Anyone
with broker access can publish as an observer via JWT auth, so operators can
now gate ingestion.

- New ObserverFilter (case-insensitive prefix matching, allowlist overrides
  denylist, accept-all when both empty)
- New OBSERVER_ALLOWLIST / OBSERVER_DENYLIST collector settings, wired through
  the CLI, run_collector, create_subscriber, and Subscriber
- Filter applied at the top of _handle_mqtt_message: blocked observers' packets
  are dropped before any decode, raw-packet capture, or DB write; zero added
  work on the default accept-all path
- Tests: ObserverFilter unit tests, subscriber drop/allow integration tests,
  config parsing tests
- Docs: configuration.md, observer.md, upgrading.md (v0.16.0), .env.example,
  docker-compose.yml

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 20:25:29 +01:00
JingleManSweep 133e064a17 Merge pull request #279 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.17.11
2026-06-25 19:03:52 +01:00
renovate[bot] 9a8c21d3cf chore(deps): update anomalyco/opencode action to v1.17.11 2026-06-25 13:55:20 +00:00
JingleManSweep d86a00ea87 Merge pull request #278 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.17.10
v0.15.0
2026-06-24 22:49:34 +01:00
renovate[bot] 39900b15db chore(deps): update anomalyco/opencode action to v1.17.10 2026-06-24 21:44:53 +00:00
JingleManSweep 9df7bf2f92 Merge pull request #277 from ipnet-mesh/renovate/actions-cache-6.x
chore(deps): update actions/cache action to v6
2026-06-24 09:10:39 +01:00
renovate[bot] 316293f6a1 chore(deps): update actions/cache action to v6 2026-06-23 19:30:54 +00:00
JingleManSweep d1d621c35b Merge pull request #276 from ipnet-mesh/fix/message-row-height
fix(web): stop pre-wrap inflating message table row height
2026-06-23 09:55:17 +01:00
JingleManSweep c9d5b34587 Merge branch 'main' into fix/message-row-height 2026-06-23 09:50:53 +01:00
JingleManSweep ba185f0896 Merge pull request #274 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.17.9
2026-06-23 09:50:39 +01:00
Louis King c955ea7a64 fix(web): stop pre-wrap inflating message table row height
The spam-detection change wrapped the message table cell content in a
multi-line div/span so the spam badge could sit alongside the text, but left
white-space: pre-wrap on the td. The template literal's own indentation
(newlines + spaces around the div) then rendered as literal whitespace inside
the pre-wrap cell, padding out every row regardless of spam state.

Move pre-wrap onto the span that holds the message text so multi-line bodies
still wrap while the cell's layout whitespace collapses normally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 09:49:54 +01:00
renovate[bot] 643e9fa423 chore(deps): update anomalyco/opencode action to v1.17.9 2026-06-23 08:08:04 +00:00
JingleManSweep 45a76a9ad3 Merge pull request #275 from ipnet-mesh/feat/spam-detection
feat(spam): score messages at ingest and hide likely spam
2026-06-23 09:07:26 +01:00
Louis King 461dbc5008 feat(spam): ship spam detection enabled by default
Make FEATURE_SPAM_DETECTION on by default with opt-out, mirroring
FEATURE_PACKETS: flip the web feature flag's Python default to true and the
Compose substitutions (collector/api/web) to :-true, so the shipped stack
scores and hides likely-spam without configuration. Opt out with
FEATURE_SPAM_DETECTION=false.

Update .env.example, docs/configuration.md and the v0.15 upgrade notes to
describe the feature as enabled-by-default with opt-out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 09:00:16 +01:00
Louis King db952d632a fix(spam): align badge threshold with API hide-filter
The Messages page badged a row as spam at a hardcoded score >= 0.6 while the
API hides rows at SPAM_SCORE_THRESHOLD (now 0.65), so messages scored in
[0.6, 0.65) appeared flagged but were never hidden by the "show potential
spam" toggle.

Expose spam_score_threshold in the SPA config and use it for the badge so a
row is badged exactly when the API would hide it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 08:51:20 +01:00
Louis King caaecfb3c2 feat(spam): retune default scoring config and add v0.15 upgrade notes
Adjust the default spam-scoring knobs across Python settings, SpamConfig,
docker-compose, .env.example and docs to reduce false positives on chatty
legitimate users:

  SPAM_MIN_PATH_HOPS   5  -> 3
  SPAM_PATH_THRESHOLD  5  -> 6
  SPAM_NAME_THRESHOLD  5  -> 10
  SPAM_WEIGHT_PATH     0.7 -> 0.75
  SPAM_WEIGHT_NAME     0.3 -> 0.25
  SPAM_SCORE_THRESHOLD 0.6 -> 0.65

Also document the spam-detection feature and the pull_policy change in a
new v0.15.0 section of docs/upgrading.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 08:40:24 +01:00
JingleManSweep fa9e4c811a Merge branch 'main' into feat/spam-detection 2026-06-23 00:42:43 +01:00
Louis King 691a5f45c8 test(spam): fix mypy union-attr in sweep-error test
Guard the optional `_spam_rescore_thread` with an `is not None` assert before
calling `.is_alive()`, matching the sibling test. CI runs `pre-commit run
--all-files`, so mypy checks the test files too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:28:55 +01:00
Louis King 4d680b1de7 test(spam): cover scheduler, config cache, and scoring edge cases
Raise diff/patch coverage above the Codecov gate (was 79.8%, target 80.29%)
by exercising the previously-untested spam paths:

- subscriber: TestSpamRescoreScheduler covers the disabled early-return, the
  enabled thread spawning + one sweep + clean stop, and the swallowed-error
  branch of the background re-scoring loop.
- handler: a contact-message scoring test covers the contact log branch.
- spam: get/reset_spam_config caching, the zero-weight combine path, the
  default-`now` path, and the null-sender reset in rescore_recent.

Patch coverage for the change is now ~97%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:25:06 +01:00
Louis King c48db03afb feat(spam): score messages at ingest and hide likely spam
Add an optional, off-by-default spam-detection feature that scores each
message's spam likelihood at ingest, stores the score on the row, and lets
the display layer hide likely-spam by default behind a "show potential spam"
toggle. Nothing is ever dropped at ingest, so the threshold can be retuned
without reprocessing.

Scoring (collector/spam.py): windowed COUNT(*) over new
(path_prefix, received_at) and (sender_normalized, received_at) indexes —
joint path+sender signal plus a sender-name signal (trailing-digit suffix
stripped so bob1/bob2 collapse to bob). When the path is short/zero-hop or
absent, the name signal stands alone at full weight so local spam is still
flaggable. A background sweep re-scores recent rows with hindsight to catch
the leading edge of bursts. The collector logs each score (WARNING at/above
the threshold).

Display: the messages API gains include_spam and a master-switch-aware
hide-filter; the SPA shows the toggle + a badge only when the feature is on.

Config: FEATURE_SPAM_DETECTION is the single operator switch, bridged in
Compose to the backend SPAM_DETECTION_ENABLED for collector + api (mirrors
the FEATURE_PACKETS / RAW_PACKET_CAPTURE_ENABLED pattern). Both default off.

Works on SQLite and Postgres: DB-agnostic queries, an Alembic batch migration
for the three new columns + two indexes, and backend-aware collector test
fixtures (lifted db_backend/db_url into the shared conftest).

Also: move the meshcore-hub image pull_policy out of the base compose file.
It lived in docker-compose.yml as pull_policy: daily and made `make up` pull
the published image over a freshly built local one. Base is now policy-neutral
(default missing); dev sets pull_policy: build on the hub services so it only
ever uses local builds. Prod refreshes images via a manual `docker compose
... pull`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:11:39 +01:00
JingleManSweep 0e8950d4d1 Merge pull request #272 from ipnet-mesh/renovate/actions-checkout-7.x
chore(deps): update actions/checkout action to v7
2026-06-20 13:18:58 +01:00
renovate[bot] 0b534781da chore(deps): update actions/checkout action to v7 2026-06-18 21:40:43 +00:00
JingleManSweep 6bf3c1c62b Merge pull request #271 from ipnet-mesh/docs/reorganize-configuration-and-deployment
docs: centralise env vars and split deployment/observer/maintenance docs
v0.14.0
2026-06-18 12:36:04 +01:00
Louis King 973bf23fe8 docs: centralise env vars and split deployment/observer/maintenance docs
Move scattered configuration tables and operational sections out of the
README into dedicated reference documents:

- docs/configuration.md: single source of truth for all environment
  variables, grouped into 12 sections (Common, Database, Caching,
  Collector, Webhooks, Auth, Data Retention, API, Web Dashboard,
  Feature Flags, Traefik, Prometheus & Alertmanager)
- docs/deployment.md: production setup, reverse proxy, multi-instance,
  API scaling, Redis caching
- docs/observer.md: remote observers plus PACKETCAPTURE_* and
  SERIAL_PORT reference
- docs/maintenance.md: backup and restore

README is reduced from 712 to 385 lines; the ARM32/Raspberry Pi note
is dropped. database.md, auth.md, webhooks.md, and content.md have
their env-var tables removed and link back to configuration.md. Stale
cross-references in database.md, upgrading.md, and .env.example are
updated to point at the new locations.
2026-06-18 12:20:49 +01:00
JingleManSweep a96bb548f6 Merge pull request #269 from ipnet-mesh/renovate/anomalyco-opencode-1.x
chore(deps): update anomalyco/opencode action to v1.17.8
2026-06-18 11:00:21 +01:00
renovate[bot] 8cd70f87af chore(deps): update anomalyco/opencode action to v1.17.8 2026-06-18 03:02:00 +00:00
JingleManSweep 4e25b2fbf0 Merge pull request #267 from ipnet-mesh/docs/tidy-database-docs-and-sqlite-deprecation
docs: tidy database docs and deprecate SQLite
2026-06-17 22:00:40 +01:00
JingleManSweep e0b2a2cb89 Merge branch 'main' into docs/tidy-database-docs-and-sqlite-deprecation 2026-06-17 21:58:47 +01:00
JingleManSweep 2d45da2c1c Merge pull request #268 from ipnet-mesh/fix/ci-required-checks-on-prs
fix: ensure CI required checks run on all PRs
2026-06-17 21:57:20 +01:00
Louis King 6ede32b2d4 fix: ensure CI required checks run on all PRs
Remove paths-ignore from the pull_request trigger so the CI workflow
always runs and the required Lint/Test/Build Package checks report on
docs-only PRs (previously skipped entirely, blocking merges).

Drop the github.event_name == 'push' gate on the build job so the
required Build Package check also reports on pull requests.
2026-06-17 21:54:20 +01:00
Louis King 0bc66d9871 docs: drop pgloader explanation from upgrade guide 2026-06-17 15:20:11 +01:00
Louis King 30e8e88ee9 docs: add SQLite deprecation notice and consolidate DB docs
Replace the lingering v0.9 'Breaking Changes' alert with a concise v0.14
'DEPRECATION NOTICE' for SQLite (dual compatibility for ~3 months, then
PostgreSQL-only).

Move all database-specific instructions (SQLite + PostgreSQL) out of the
README into a new canonical docs/database.md covering:
- SQLite zero-config default (DATA_HOME / meshcore.db, WAL/single-host)
- PostgreSQL: DATABASE_* env vars, bundled Docker profile, production
  role/database provisioning (mirrors the ipnet-mesh/infrastructure init
  script), managed/external Postgres and DATABASE_URL
- Schema-per-instance (search_path) isolation for multiple instances on a
  shared cluster
- Pointer to the SQLite->PostgreSQL migration runbook in upgrading.md

Update the README Multi-Instance Deployments and Scaling the API sections
to link to docs/database.md, and add the new doc to the docs list and
project tree. Add a pointer in .env.example and a Postgres note in
AGENTS.md.

Consolidate docs/upgrading.md v0.14: move the env-var/schema/provisioning
reference to docs/database.md (single source of truth) and keep only the
upgrade-time migration runbook and dashboard chart fix.
2026-06-17 15:14:53 +01:00
JingleManSweep b38d0f20ff Merge pull request #266 from ipnet-mesh/chore/added-explicit-compose-pull-policies
Added explicit pull policies for core services
2026-06-16 22:58:33 +01:00
Louis King 3f739a783b Added explicit pull policies for core services 2026-06-16 22:55:19 +01:00
JingleManSweep 6ed2e6dc6a Merge pull request #265 from ipnet-mesh/fix/packets-feature-flag-check
fix: normalize packets feature flag check to !== false
2026-06-16 22:09:43 +01:00
Louis King 9071634606 fix: normalize packets feature flag check to !== false
The packets feature was the only flag gated with a strict `=== true` check
in the SPA JavaScript while every other flag uses `!== false`. Align packets
to the shared pattern across route registration, mobile nav, page titles, the
home nav card, and the messages/advertisements packet-detail link gates.
2026-06-16 22:02:59 +01:00
JingleManSweep f045de0dc3 Merge pull request #264 from ipnet-mesh/fix/postgres-charts-flatline
fix: normalize date-bucket keys for Postgres dashboard charts
2026-06-16 21:18:41 +01:00
Louis King cf5add9924 fix: normalize date-bucket keys for Postgres dashboard charts
Dashboard charts (activity, message-activity, node-count) rendered as
flat zeros on Postgres because func.date() returns a str on SQLite but
a datetime.date on Postgres — the dict lookup by string key always
missed. Fixed with a dialect-neutral _date_bucket_key() helper and
pinned the Postgres session timezone to UTC at the engine level.

Also adds dual-backend test infrastructure (TEST_DATABASE_BACKEND env
var), per-worker Postgres databases for pytest-xdist isolation, and
strengthened regression tests asserting non-zero date buckets.
2026-06-16 21:16:00 +01:00
JingleManSweep d4d55b16d9 Merge pull request #263 from ipnet-mesh/fix/node-list-nulls-last
fix: sink NULL last_seen nodes to bottom of node list
2026-06-16 19:25:19 +01:00
Louis King 8bf45362bb fix: sink NULL last_seen nodes to bottom of node list
After the Postgres migration, nodes with no last_seen timestamp floated
to the top of the default list because Postgres sorts NULLs first under
ORDER BY ... DESC, whereas SQLite (the previous backend) sorts them last.

Wrap the last_seen ORDER BY with SQLAlchemy nullslast() so NULL last_seen
nodes always sink to the end regardless of database backend or sort
direction. Adds three regression tests covering DESC, ASC, and all-NULL
cases.
2026-06-16 19:20:19 +01:00