Compare commits

...

399 Commits
v0.1.0 ... main

Author SHA1 Message Date
Ben Allfree
b1c416d029 first cut (#651) 2026-03-28 17:09:12 +01:00
dependabot[bot]
8305ca588c build(deps): bump rustls-webpki from 0.103.8 to 0.103.10 in /matrix (#649)
Bumps [rustls-webpki](https://github.com/rustls/webpki) from 0.103.8 to 0.103.10.
- [Release notes](https://github.com/rustls/webpki/releases)
- [Commits](https://github.com/rustls/webpki/compare/v/0.103.8...v/0.103.10)

---
updated-dependencies:
- dependency-name: rustls-webpki
  dependency-version: 0.103.10
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-21 12:55:17 +01:00
dependabot[bot]
0cf56b6fba build(deps): bump quinn-proto from 0.11.13 to 0.11.14 in /matrix (#646)
Bumps [quinn-proto](https://github.com/quinn-rs/quinn) from 0.11.13 to 0.11.14.
- [Release notes](https://github.com/quinn-rs/quinn/releases)
- [Commits](https://github.com/quinn-rs/quinn/compare/quinn-proto-0.11.13...quinn-proto-0.11.14)

---
updated-dependencies:
- dependency-name: quinn-proto
  dependency-version: 0.11.14
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-11 14:56:43 +01:00
l5y
ecce7f3504 chore: bump version to 0.5.11 (#645)
* chore: bump version to 0.5.11

* data: run black
2026-03-01 21:59:04 +01:00
l5y
17fa183c4f web: limit horizontal size of dropdown (#644)
* web: limit horizontal size of dropdown

* address review comments
2026-03-01 21:49:06 +01:00
l5y
5b0a6f5f8b web: expose node stats in distinct api (#641)
* web: expose node stats in distinct api

* web: address review comments

* web: address review comments

* web: address review comments

* web: address review comments
2026-02-14 21:14:10 +01:00
l5y
2e8b5ad856 web: do not merge channels by name (#640) 2026-02-14 15:42:14 +01:00
l5y
e32b098be4 web: do not merge channels by ID in frontend (#637)
* web: do not merge channels by ID in frontend

* web: address review comments

* web: address review comments
2026-02-14 14:56:25 +01:00
l5y
b45629f13c web: do not touch neighbor last seen on neighbor info (#636)
* web: do not touch neighbor last seen on neighbor info

* web: address review comments
2026-02-14 14:43:46 +01:00
l5y
96421c346d ingestor: report self id per packet (#635)
* ingestor: report self id per packet

* ingestor: address review comments

* ingestor: address review comments

* ingestor: address review comments

* ingestor: address review comments
2026-02-14 14:29:05 +01:00
l5y
724b3e14e5 ci: fix docker compose and docs (#634)
* ci: fix docker compose and docs

* docker: address review comments
2026-02-14 13:25:43 +01:00
l5y
e8c83a2774 web: supress encrypted text messages in frontend (#633)
* web: supress encrypted text messages in frontend

* web: address review comments

* web: address review comments

* web: address review comments

* web: address review comments
2026-02-14 13:11:02 +01:00
l5y
5c5a9df5a6 federation: ensure requests timeout properly and can be terminated (#631)
* federation: ensure requests timeout properly and can be terminated

* web: address review comments

* web: address review comments

* web: address review comments

* web: address review comments
2026-02-14 12:29:01 +01:00
dependabot[bot]
7cb4bbe61b build(deps): bump bytes from 1.11.0 to 1.11.1 in /matrix (#627)
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.11.0 to 1.11.1.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.11.0...v1.11.1)

---
updated-dependencies:
- dependency-name: bytes
  dependency-version: 1.11.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 21:40:49 +01:00
l5y
fed8b9e124 matrix: config loading now merges optional TOML with CLI/env/secret inputs (#617)
* matrix: config loading now merges optional TOML with CLI/env/secret inputs

* matrix: fix tests

* matrix: address review comments

* matrix: fix tests

* matrix: cover missing unit test vectors
2026-01-10 23:39:53 +01:00
l5y
60e734086f matrix: logs only non-sensitive config fields (#616)
* matrix: logs only non-sensitive config fields

* matrix: run fmt
2026-01-10 21:06:51 +01:00
l5y
c3181e9bd5 web: decrypted takes precedence (#614)
* web: decrypted takes precedence

* web: run rufo

* web: fix tests

* web: fix tests

* web: cover missing unit test vectors

* web: fix tests
2026-01-10 13:13:55 +01:00
l5y
f4fa487b2d Add Apache headers to missing sources (#615) 2026-01-10 13:07:47 +01:00
l5y
e0237108c6 web: decrypt PSK-1 unencrypted messages on arrival (#611)
* web: decrypt PSK-1 unencrypted messages on arrival

* web: address review comments

* web: use proper psk to decrypt instead of alias

* cover missing unit test vectors

* tests: run black formatter

* web: fix tests

* web: refine decryption data processing logic

* web: address review comments

* web: cover missing unit test vectors

* web: cover missing unit test vectors

* web: cover missing unit test vectors

* web: cover missing unit test vectors
2026-01-10 12:33:59 +01:00
l5y
d7a636251d web: daemonize federation worker pool to avoid deadlocks on stuck announcments (#610)
* web: daemonize federation worker pool to avoid deadlocks on stuck announcments

* web: address review comments

* web: address review comments
2026-01-09 09:12:25 +01:00
l5y
108573b100 web: add announcement banner (#609)
* web: add announcement banner

* web: cover missing unit test vectors
2026-01-08 21:17:59 +01:00
l5y
36f55e6b79 l5y chore version 0510 (#608)
* chore: bump version to 0.5.10

* chore: bump version to 0.5.10

* chore: update changelog
2026-01-08 16:20:14 +01:00
l5y
b4dd72e7eb matrix: listen for synapse on port 41448 (#607)
* matrix: listen for synapse on port 41448

* matrix: address review comments

* matrix: address review comments

* matrix: cover missing unit test vectors

* matrix: cover missing unit test vectors
2026-01-08 15:51:31 +01:00
l5y
f5f2e977a1 web: collapse federation map ledgend (#604)
* web: collapse federation map ledgend

* web: cover missing unit test vectors
2026-01-06 17:31:20 +01:00
l5y
e9a0dc0d59 web: fix stale node queries (#603) 2026-01-06 16:13:04 +01:00
l5y
d75c395514 matrix: move short name to display name (#602)
* matrix: move short name to display name

* matrix: run fmt
2026-01-05 23:24:27 +01:00
l5y
b08f951780 ci: update ruby to 4 (#601)
* ci: update ruby to 4

* ci: update dispatch triggers
2026-01-05 23:23:56 +01:00
l5y
955431ac18 web: display traces of last 28 days if available (#599)
* web: display traces of last 28 days if available

* web: address review comments

* web: fix tests

* web: fix tests
2026-01-05 21:22:16 +01:00
l5y
7f40abf92a web: establish menu structure (#597)
* web: establish menu structure

* web: cover missing unit test vectors

* web: fix tests
2026-01-05 21:18:51 +01:00
l5y
c157fd481b matrix: fixed the text-message checkpoint regression (#595)
* matrix: fixed the text-message checkpoint regression

* matrix: improve formatting

* matrix: fix tests
2026-01-05 18:20:25 +01:00
l5y
a6fc7145bc matrix: cache seen messages by rx_time not id (#594)
* matrix: cache seen messages by rx_time not id

* matrix: fix review comments

* matrix: fix review comments

* matrix: cover missing unit test vectors

* matrix: fix tests
2026-01-05 17:34:54 +01:00
l5y
ca05cbb2c5 web: hide the default '0' tab when not active (#593) 2026-01-05 16:26:56 +01:00
l5y
5c79572c4d matrix: fix empty bridge state json (#592)
* matrix: fix empty bridge state json

* matrix: fix tests
2026-01-05 16:11:24 +01:00
l5y
6fd8e5ad12 web: allow certain charts to overflow upper bounds (#585)
* web: allow certain charts to overflow upper bounds

* web: cover missing unit test vectors
2025-12-31 15:15:18 +01:00
l5y
09fbc32e48 ingestor: support ROUTING_APP messages (#584)
* ingestor: support ROUTING_APP messages

* data: cover missing unit test vectors

* data: address review comments

* tests: fix
2025-12-31 13:13:34 +01:00
l5y
4591d5acd6 ci: run nix flake check on ci (#583)
* ci: run nix flake check on ci

* ci: fix tests
2025-12-31 12:58:37 +01:00
l5y
6c711f80b4 web: hide legend by default (#582)
* web: hide legend my default

* web: run rufo
2025-12-31 12:42:53 +01:00
Benjamin Grosse
e61e701240 nix flake (#577) 2025-12-31 12:00:11 +01:00
apo-mak
42f4e80a26 Support BLE UUID format for macOS Bluetooth devices (#575)
* Initial plan

* Add BLE UUID support for macOS devices

Co-authored-by: apo-mak <25563515+apo-mak@users.noreply.github.com>

* docs: Add UUID format example for macOS BLE connections

Co-authored-by: apo-mak <25563515+apo-mak@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: apo-mak <25563515+apo-mak@users.noreply.github.com>
2025-12-20 20:21:59 +01:00
l5y
4dc03f33ca web: add mesh.qrp.ro as seed node (#573) 2025-12-17 10:48:51 +01:00
l5y
5572c6cd12 web: ensure unknown nodes for messages and traces (#572) 2025-12-17 10:21:03 +01:00
l5y
4f7e66de82 chore: bump version to 0.5.9 (#569) 2025-12-16 21:14:10 +00:00
l5y
c1898037c0 web: add secondary seed node jmrp.io (#568) 2025-12-16 21:38:41 +01:00
l5y
efc5f64279 data: implement whitelist for ingestor (#567)
* data: implement whitelist for ingestor

* data: run black

* data: cover missing unit test vectors
2025-12-16 21:11:53 +01:00
l5y
636a203254 web: add ?since= parameter to all apis (#566) 2025-12-16 20:24:31 +01:00
l5y
2e78fa7a3a matrix: fix docker build 2025-12-16 19:26:31 +01:00
l5y
e74f985630 matrix: fix docker build (#564) 2025-12-16 18:52:07 +01:00
l5y
e4facd7f26 web: fix federation signature validation and create fallback (#563)
* web: fix federation signature validation and create fallback

* web: cover missing unit test vectors
2025-12-16 10:52:59 +01:00
l5y
f533362f8a chore: update readme (#561) 2025-12-16 08:54:31 +01:00
l5y
175a8f368f matrix: add docker file for bridge (#556)
* matrix: add docker file for bridge

* matrix: address review comments

* matrix: address review comments

* matrix: address review comments

* matrix: address review comments

* matrix: address review comments
2025-12-16 08:53:01 +01:00
l5y
872bcbd529 matrix: add health checks to startup (#555)
* matrix: add health checks to startup

* matrix: address review comments

* matrix: cover missing unit test vectors

* matrix: cover missing unit test vectors
2025-12-15 22:53:32 +01:00
l5y
8811f71e53 matrix: omit the api part in base url (#554)
* matrix: omit the api part in base url

* matrix: address review comments
2025-12-15 22:04:01 +01:00
l5y
fec649a159 app: add utility coverage tests for main.dart (#552)
* Add utility coverage tests for main.dart

* Add channel names to message sorting tests

* Fix MeshMessage sort test construction

* chore: run dart formatter
2025-12-15 11:03:51 +01:00
l5y
9e3f481401 Add unit tests for daemon helpers (#553) 2025-12-15 08:43:13 +01:00
l5y
1a497864a7 chore: bump version to 0.5.8 (#551)
* chore: bump version to 0.5.8

* chore: add missing license headers
2025-12-15 08:29:27 +01:00
l5y
06fb90513f data: track ingestors heartbeat (#549)
* data: track ingestors heartbeat

* data: address review comments

* cover missing unit test vectors

* cover missing unit test vectors
2025-12-14 18:42:17 +01:00
l5y
b5eecb1ec1 Harden instance selector navigation URLs (#550)
* Harden instance selector navigation URLs

* Cover malformed instance URL handling
2025-12-14 18:40:41 +01:00
l5y
0e211aebdd data: hide channels that have been flag for ignoring (#548)
* data: hide channels that have been flag for ignoring

* data: address review comments
2025-12-14 16:47:44 +01:00
l5y
96b62d7e14 web: fix limit when counting remote nodes (#547) 2025-12-14 15:05:19 +01:00
l5y
baf6ffff0b web: improve instances map and table view (#546)
* web: improve instances map and table view

* web: address review comments

* run rufo
2025-12-14 14:35:55 +01:00
l5y
135de0863c web: fix traces submission with optional fields on udp (#545) 2025-12-14 13:27:07 +01:00
l5y
074a61baac chore: bump version to 0.5.7 (#542)
* chore: bump version to 0.5.7

* Change version to 0.5.7 in AppFrameworkInfo.plist

Updated version numbers to 0.5.7.
2025-12-08 20:39:58 +01:00
l5y
209cc948bf Handle zero telemetry aggregates (#538)
* Handle zero telemetry aggregates

* Fix telemetry aggregation to drop zero readings
2025-12-08 20:31:32 +01:00
l5y
cc108f2f49 web: fix telemetry api to return current in amperes (#541)
* web: fix telemetry api to return current in amperes

* web: address review comments
2025-12-08 20:18:10 +01:00
l5y
844204f64d web: fix traces rendering (#535)
* web: fix traces rendering

* web: remove icon shortcuts

* web: further refine the trace routes
2025-12-08 19:48:33 +01:00
l5y
88f699f4ec Normalize numeric roles in node snapshots (#539) 2025-12-08 19:47:50 +01:00
l5y
d1b9196f47 Use INSTANCE_DOMAIN env for ingestor (#536)
* Use INSTANCE_DOMAIN env for ingestor

* Normalize instance domain handling
2025-12-07 11:05:13 +01:00
l5y
8181fc8e03 web: further refine the federation page (#534)
* web: further refine the federation page

* web: address review comments

* web: address review comments
2025-12-04 13:31:23 +01:00
apo-mak
5be2ac417a Add Federation Map (#532)
* Add Federation Map

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: l5y <220195275+l5yth@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-04 12:24:54 +01:00
apo-mak
6acb1c833c add contact link to the instance data (#533) 2025-12-04 12:17:26 +01:00
l5y
2bd69415c1 matrix: create potato-matrix-bridge (#528)
* matrix: create potato-matrix-bridge

* matrix: add unit tests

* matrix: address review comments

* ci: condition github actions to only run on paths affected...

* Add comprehensive unit tests for config, matrix, potatomesh, and main modules

* Revert "Add comprehensive unit tests for config, matrix, potatomesh, and main modules"

This reverts commit 212522b4a2.

* matrix: add unit tests

* matrix: add unit tests

* matrix: add unit tests
2025-11-29 08:52:20 +01:00
l5y
7160d72aae web: display sats in view (#523) 2025-11-26 22:01:53 +01:00
l5y
f4aee2aba4 web: display air quality in separate chart (#521) 2025-11-26 21:02:59 +01:00
l5y
a789a9552a Add macOS and Ubuntu builds to Flutter workflow (#519)
* Add macOS and Ubuntu builds to Flutter workflow

* Add no-codesign flag to iOS Flutter build
2025-11-26 20:53:47 +01:00
l5y
9fd1401737 web:add current to charts (#520)
* web:add current to charts

* cover missing unit test vectors
2025-11-26 20:45:04 +01:00
l5y
9ae837582b app: fix notification icon (#518)
* app: fix notification icon

* cover missing unit test vectors
2025-11-26 18:48:22 +01:00
l5y
db61a7d626 spec: update test fixtures (#517)
* spec: update test fixtures

* app: address review comments
2025-11-26 17:36:53 +01:00
l5y
f8d78b5489 app: generate proper icons (#516)
* app: generate proper icons

* app: address review comments
2025-11-25 23:01:07 +01:00
l5y
eaa691808a web: fix favicon (#515) 2025-11-25 22:26:30 +01:00
l5y
e733a0dd13 web: add ?since= parameter to api/messages (#512)
* web: add ?since= parameter to api/messages

* app: add ?since= parameter to api/messages
2025-11-25 22:11:20 +01:00
l5y
2ae1e34d63 app: implement notifications (#511)
* app: implement notifications

* app: request permission for notifications
2025-11-25 21:38:46 +01:00
l5y
e432c843c3 app: add theme selector (#507)
* app: add theme selector

* address review comments
2025-11-24 22:02:06 +01:00
l5y
6f2db06f25 app: further harden refresh logic and prefer local first (#506)
* app: further harden refresh logic and prefer local first

* address review comments

* cover missing unit test vectors
2025-11-24 15:48:50 +01:00
l5y
4946ea1751 ci: fix app artifacts for tags (#504)
* ci: fix app artifacts for tags

* ci: fix app artifacts for tags

* ci: fix app artifacts for tags
2025-11-23 23:23:19 +01:00
l5y
f8ed75a095 ci: build app artifacts for tags (#503)
* ci: build app artifacts for tags

* ci: build app artifacts for tags

* ci: build app artifacts for tags
2025-11-23 23:11:44 +01:00
l5y
2269d9b4ba app: add persistance (#501)
* app: add persistance

* app: address review comments

* app: do not fail on http error or timeout

* app: only load instances if none are cached
2025-11-23 21:17:56 +01:00
l5y
753c0f171f app: instance and chat mvp (#498)
* app: instance and chat mvp

* app: instance and chat mvp

* app: address review comments

* cover missing unit test vectors

* app: add backlink to github
2025-11-23 01:32:43 +01:00
l5y
8939911ce1 app: add instance selector to settings (#497)
* app: add instance selector to settings

* app: add instance selector to settings
2025-11-22 23:15:12 +01:00
l5y
356f60d02f app: add scaffholding gitignore (#496)
* app: add scaffholding gitignore

* update agents md

* add linter to mobile workflow

* run dart formatter
2025-11-22 21:44:35 +01:00
l5y
0067d7834f Handle reaction app packets without reply id (#495)
* Handle reaction app packets without reply id

* render emoji text rather then emoji boolean
2025-11-22 21:19:48 +01:00
l5y
d60d774e73 Render reaction multiplier counts (#494) 2025-11-22 18:51:19 +01:00
l5y
0bb237c4ab Add comprehensive tests for Flutter reader (#491)
* Add comprehensive tests for Flutter reader

* Fix Flutter UI text expectations

* Expose chat placeholders to widget finders

* Handle refresh errors in messages screen

* Reset message fetch when widget updates
2025-11-22 18:37:34 +01:00
l5y
54fa1759d1 Map numeric role ids to canonical Meshtastic roles (#489)
* Map numeric role ids to canonical Meshtastic roles

* Add coverage for role helper mappings
2025-11-21 19:22:35 +01:00
l5y
134cf92c6d update node detail hydration for traces (#490)
* update node detail hydration for traces

* cover missing unit test vectors
2025-11-21 19:20:46 +01:00
l5y
f93c14a9c5 Add mobile Flutter CI workflow (#488)
* Add mobile Flutter CI workflow

* Fix Flutter message sorting with null timestamps
2025-11-21 17:24:28 +01:00
l5y
189548277d Align OCI architecture labels in docker workflow (#487) 2025-11-21 17:16:23 +01:00
l5y
21cecc970f Add Meshtastic reader Flutter app (#483)
* Add Meshtastic reader Flutter app

* fix review comments
2025-11-21 17:11:18 +01:00
l5y
ca4a55312f Handle pre-release Docker tagging (#486) 2025-11-21 17:09:23 +01:00
l5y
c31bb1ac74 web: remove range from charts labels (#485) 2025-11-21 17:05:24 +01:00
l5y
ac36db19a7 Floor override frequencies to MHz integers (#476)
* Floor override frequencies to MHz integers

* Handle stub protobuf nodeinfo dictionaries

* Handle zero override frequency defaults

* Add defensive serialization coverage

* Stabilize nodeinfo user role normalization test
2025-11-20 21:00:47 +01:00
l5y
330a990bea Prevent message ids from being treated as node identifiers (#475)
* Prevent message ids from being treated as nodes (#)

* Cover node id candidate edge cases

* Revert "address missing id field ingestor bug (#469)"

This reverts commit 546e009867.
2025-11-20 20:50:22 +01:00
Alexkurd
5c2c2646cc Fix 1 after emojis in reply. (#464)
* Fix 1 after emojis in reply.

* Refactor message reply handling for emojis and text

* Fix reaction assertion in message-replies test

---------

Co-authored-by: Alexkurd <gitmac@acl.one>
2025-11-19 20:34:52 +01:00
l5y
eab6af414f add frequency and preset to node table (#472) 2025-11-19 17:39:15 +01:00
l5y
c55f3a19e9 subscribe to traceroute app pubsub topic (#471)
* subscribe to traceroute app pubsub topic

* cover missing unit test vectors
2025-11-19 17:03:46 +01:00
l5y
db670fbb7c aggregate telemetry over the last 7 days (#470)
* aggregate telemetry over the last 7 days

* cover missing unit test vectors
2025-11-19 11:31:42 +01:00
l5y
546e009867 address missing id field ingestor bug (#469)
* address missing id field ingestor bug

* cover missing unit test vectors

* cover missing unit test vectors
2025-11-19 08:22:24 +01:00
l5y
be46963744 merge secondary channels by name (#468)
* merge secondary channels by name

* cover missing unit test vectors
2025-11-18 18:33:02 +01:00
l5y
8f7adba65a rate limit host device telemetry (#467)
* rate limit host device telemetry

* Spec: add more unit tests
2025-11-18 18:04:40 +01:00
l5y
e8b38ed65a add traceroutes to frontend (#466)
* add traceroutes to frontend

* Spec: add more unit tests
2025-11-18 13:12:14 +01:00
l5y
700fcef33f feat: implement traceroute app packet handling across the stack (#463)
* feat: implement traceroute app packet handling across the stack

* run linter

* tests: fix

* Spec: add more unit tests
2025-11-18 11:23:46 +01:00
l5y
b23d864f1d Bump version and update changelog (#462)
* chore: bump version to 0.5.6 everywhere

* docs: update changelog

* chore: bump version to 0.5.6 everywhere
2025-11-16 17:38:41 +01:00
l5y
e1d43cec57 Added comprehensive helper unit tests (#457)
* Added comprehensive helper unit tests

* run black
2025-11-16 16:47:57 +01:00
l5y
cd7bced827 Added reaction-aware handling (#455) 2025-11-16 15:31:17 +01:00
l5y
b298f2f22c env: add map zoom (#454)
* chore: bump version to 0.5.5 everywhere

* add MAP_ZOOM varibale

* run black
2025-11-16 12:57:47 +01:00
l5y
9304a99745 charts: render aggregated telemetry charts for all nodes (#453) 2025-11-15 17:09:55 +01:00
l5y
4a03e17886 nodes: render charts detail pages as overlay (#452) 2025-11-15 12:13:06 +01:00
l5y
e502ddd436 fix telemetry parsing for charts (#451) 2025-11-14 21:18:37 +01:00
l5y
12f1801ed2 nodes: improve charts on detail pages (#450)
* nodes: add charts to detail pages

* nodes: improve charts on detail pages

* fix ignored packet debug loggin

* run rufo

* address review comments
2025-11-14 20:17:58 +01:00
l5y
a6a63bf12e nodes: add charts to detail pages (#449) 2025-11-14 16:24:09 +01:00
l5y
631455237f Aggregate frontend snapshots across views (#447) 2025-11-13 22:02:42 +01:00
Alexkurd
382e2609c9 Remove added 1 if reply with emoji (#443)
In reply message.text contains emoji, and message.emoji is 1.
2025-11-13 21:15:35 +01:00
l5y
05efbc5f20 Refine node detail view layout (#442)
* Refine node detail view layout

* Refine node detail controls and formatting

* Improve node detail neighbor roles and message metadata

* Fix node detail neighbor metadata hydration
2025-11-13 19:59:07 +01:00
l5y
9a45430321 Enable map centering from node table coordinates (#439)
* Enable map centering from node table coordinates

* Replace node coordinate buttons with links
2025-11-13 17:23:35 +01:00
l5y
cb843d5774 Add node detail route and page (#441) 2025-11-13 17:19:20 +01:00
l5y
c823347175 Ensure nodeinfo patch runs before importing interfaces (#440) 2025-11-13 17:16:59 +01:00
l5y
d87c0cc226 Filter zero-valued fields from API responses (#438)
* Filter zero-value fields from API responses

* Restore zero-valued API fields (#438)

* Clarify compact_api_row documentation
2025-11-13 17:10:46 +01:00
l5y
9c957a4a14 Add debug payload tracing and ignored packet logging (#437) 2025-11-13 17:06:35 +01:00
l5y
16442bab08 Tighten map auto-fit behaviour (#435) 2025-11-12 20:49:03 +01:00
l5y
e479983d38 Fetch encrypted chat log entries for log tab (#434)
* Fetch encrypted chat log entries for log tab

* Guard log-only chat log merge from plaintext
2025-11-12 14:13:46 +01:00
l5y
70fca17230 Add encrypted filter to messages API (#432) 2025-11-12 12:46:34 +01:00
l5y
2107d6790d Guard NodeInfo handler against missing IDs (#426) (#431) 2025-11-12 12:39:36 +01:00
l5y
8823b7cb48 Add standalone full-screen map, chat, and nodes views (#429)
* Add dedicated full-screen dashboard views

* Simplify full-screen routes layout

* Restore refresh controls on full-screen views

* Polish standalone view layout

* Streamline standalone layouts
2025-11-12 11:38:26 +01:00
l5y
e40c0d9078 Ensure chat history fetches full message limit (#428) 2025-11-11 22:33:30 +01:00
l5y
8b090cb238 Handle nodeinfo packets without identifiers (#426) (#427) 2025-11-11 20:45:32 +01:00
l5y
2bb8e3fd66 Chore: update license headers (#424) 2025-11-08 10:41:57 +01:00
l5y
deb7263c3e Chore: bump version to 0.5.5 (#423) 2025-11-08 09:15:52 +00:00
l5y
3daadc4f68 handle naming when primary channel has a name (#422) 2025-11-08 09:44:41 +01:00
l5y
6b72b1b3da handle edge case when primary channel has a name (#421) 2025-11-07 21:39:26 +01:00
l5y
52486d82ad Add preset mode to logs (#420) 2025-11-07 17:56:27 +01:00
l5y
487d618e00 Parallelize federation tasks with worker pool (#419)
* Parallelize federation work with worker pool

* Handle worker pool shutdown fallback during federation announcements
2025-11-07 17:24:37 +01:00
l5y
9239805129 allow filtering chat and logs by node name (#417) 2025-11-07 15:55:11 +01:00
l5y
554b2abd82 gem: add erb as dependency removed from std (#416)
* gem: add erb as dependency removed from std

* Relax erb dependency for Ruby 3.3 compatibility
2025-11-07 15:11:05 +01:00
l5y
8bb98f65d6 implement support for replies and reactions app (#411)
* implement support for replies and reactions app

* Allow numeric reaction port packets

* allow reaction packets through mai channel filter
2025-11-06 20:58:35 +01:00
l5y
71c0f8b21e ingestor: ignore direct messages on default channel (#414)
* ingestor: ignore direct messages on default channel

* tests: run black formatter
2025-11-06 20:14:32 +01:00
l5y
aa2bc68544 agents: add instructions (#410) 2025-11-03 22:23:20 +00:00
l5y
a8394effdc display encrypted messages in frontend log window (#409)
* display encrypted messages in frontend log window

* render recipient by known node name short id
2025-11-03 22:51:20 +01:00
l5y
e27d5ab53c Add chat log entries for telemetry, position, and neighbor events (#408)
* Add telemetry and neighbor chat log events

* Refine chat log highlights for telemetry and position updates

* Add emoji prefixes to chat log events

* Fix telemetry highlights and emoji styling

* Remove italic chat copy and drop zero-valued highlights

* address style and formatting issues
2025-11-03 12:33:02 +01:00
l5y
6af272c01f Handle missing instance domain outside production (#405) 2025-10-31 12:36:53 +01:00
l5y
03e2fe6a72 Add tabbed chat panel with channel grouping (#404)
* feat: add tabbed chat panel with channel grouping

* Handle ISO-only chat timestamps in dashboard renderer

* Remove redundant chat channel tag
2025-10-31 12:24:17 +01:00
l5y
87b4cd79e7 Normalize numeric client roles using Meshtastic CLI enums (#402)
* Normalize firmware client roles using CLI enums

* Prioritize CLI role lookup before protobuf fallbacks
2025-10-31 11:43:48 +01:00
l5y
d94d75e605 Ensure Docker images publish versioned tags (#403) 2025-10-31 11:43:30 +01:00
l5y
c965d05229 Document environment configuration variables (#400)
* Document environment configuration variables

* Escape sed replacements when updating .env values
2025-10-31 11:08:06 +01:00
l5y
ba80fac36c Document federation refresh cadence (#401) 2025-10-31 11:05:08 +01:00
l5y
3c2c7611ee docs: document prometheus metrics (#399) 2025-10-31 11:04:20 +01:00
Nic Jansma
49e0f39ca9 Config: Read PROM_REPORT_IDS from environment (#398) 2025-10-29 09:22:33 +01:00
KenADev
625df7982d feat: Mesh-Ingestor: Ability to provide already-existing interface instance (#395)
* feat: Mesh-Ingestor: Ability to provide already-existing interface instance

* Prevent Signal-Registration if not main thread (causes exception)

* fix redundant ternary operator

---------

Co-authored-by: Ken Ahr <ken.a.iphone@googlemail.com>
2025-10-26 20:47:23 +01:00
KenADev
8eeb13166b fix: Ingestor: Fix error for non-existing datetime.UTC reference (#396)
Co-authored-by: Ken Ahr <ken.a.iphone@googlemail.com>
2025-10-26 20:46:31 +01:00
l5y
80645990cb Chore: bump version to 0.5.4 (#388)
Co-authored-by: l5yth <d220195275+l5yth@users.noreply.github.com>
2025-10-19 10:36:09 +00:00
l5y
96a3bb86e9 Add telemetry formatting module and overlay metrics (#387) 2025-10-19 12:13:32 +02:00
l5y
6775de3cca Prune blank values from API responses (#386) 2025-10-18 20:16:14 +02:00
l5y
8143fbd8f7 Add full support to telemetry schema and API (#385)
* feat: auto-upgrade telemetry schema

* Ensure numeric metrics fallback to valid values

* Format data processing numeric metric lookup
2025-10-18 15:19:33 +02:00
l5y
cf3949ef95 Respect PORT environment override (#384) 2025-10-18 13:01:48 +02:00
l5y
32d9da2865 Add instance selector dropdown for federation deployments (#382)
* Add instance selector for federation regions

* Avoid HTML insertion when seeding instance selector
2025-10-18 10:53:26 +02:00
l5y
61e8c92f62 Harden federation announcements (#381) 2025-10-18 10:38:28 +02:00
l5y
d954df6294 Ensure private mode disables federation (#380) 2025-10-18 09:48:40 +02:00
l5y
30d535bd43 Ensure private mode disables chat messaging (#378) 2025-10-17 22:47:54 +02:00
l5y
d06aa42ab2 Respect FEDERATION flag for federation endpoints (#379) 2025-10-17 22:47:41 +02:00
l5y
108fc93ca1 Expose PRIVATE environment configuration (#377) 2025-10-17 22:43:42 +02:00
l5y
427479c1e6 Fix frontend coverage export for Codecov (#376)
* fix: export frontend coverage for codecov

* Merge V8 file coverages across workers
2025-10-17 22:43:23 +02:00
l5y
ee05f312e8 Restrict instance API to recent updates (#374) 2025-10-17 22:17:49 +02:00
l5y
c4193e38dc Document and expose federation configuration (#375) 2025-10-17 22:17:32 +02:00
l5y
cb9b081606 Chore: bump version to 0.5.3 (#372) 2025-10-17 19:47:18 +00:00
l5y
cc8fec6d05 Align theme and info controls (#371)
* Align theme and info controls

* design tweaks
2025-10-17 19:27:14 +00:00
l5y
01665b6e3a Fixes POST request 403 errors on instances behind Cloudflare proxy (#368)
* Add full headers to ingestor POST requests to avoid CF bans

* run black

* Guard Authorization header when token absent

---------

Co-authored-by: varna9000 <milen@aeroisk.com>
2025-10-16 22:29:04 +02:00
l5y
1898a99789 Delay initial federation announcements (#366) 2025-10-16 21:50:43 +02:00
l5y
3eefda9205 Ensure well-known document stays in sync (#365) 2025-10-16 21:43:11 +02:00
l5y
a6ba9a8227 Guard federation DNS resolution against restricted networks (#362)
* Guard federation DNS resolution against restricted networks

* Pin federation HTTP clients to vetted IPs
2025-10-16 21:15:34 +02:00
l5y
7055444c4b Add federation ingestion limits and tests (#364) 2025-10-16 21:15:18 +02:00
l5y
4bfc0e25cb Prefer reported primary channel names (#363) 2025-10-16 20:35:24 +02:00
l5y
81335cbf7b Decouple messages API from node joins (#360) 2025-10-16 13:19:29 +02:00
l5y
76b57c08c6 Fix ingestor reconnection detection (#361) 2025-10-16 13:06:32 +02:00
l5y
926b5591b0 Harden instance domain validation (#359) 2025-10-16 10:51:34 +02:00
l5y
957e597004 Ensure INSTANCE_DOMAIN propagates to containers (#358) 2025-10-15 23:22:46 +02:00
l5y
68cfbf139f chore: bump version to 0.5.2 (#356)
Co-authored-by: l5yth <d220195275+l5yth@users.noreply.github.com>
2025-10-15 23:16:30 +02:00
l5y
b2f4fcaaa5 Gracefully retry federation announcements over HTTP (#355) 2025-10-15 23:11:59 +02:00
l5y
dc2fa9d247 Recursively ingest federated instances (#353)
* Recursively ingest federated instances

* Keep absent is_private nil during signature verification
2025-10-15 21:35:37 +02:00
l5y
a32125996c Remove federation timeout environment overrides (#352) 2025-10-15 20:04:19 +02:00
l5y
506a1ab5f6 Close unrelated short info overlays when opening short info (#351)
* Close unrelated overlays when opening short info

* Ensure map overlays respect nested short overlay closing
2025-10-15 16:35:38 +00:00
l5y
db7b67d859 Improve federation instance error diagnostics (#350) 2025-10-15 18:35:22 +02:00
l5y
49f08a7f75 Harden federation domain validation and tests (#347)
* Harden federation domain validation and tests

* Preserve domain casing for signature verification

* Forward sanitize helper keyword argument

* Handle mixed-case domains during signature verification
2025-10-15 18:14:31 +02:00
l5y
b2d35d3edf Handle malformed instance records (#348) 2025-10-15 17:08:24 +02:00
l5y
a9d618cdbc Fix ingestor device mounting for non-serial connections (#346)
* Adjust ingestor device handling

* Restore serial device permissions for ingestor
2025-10-15 16:52:37 +02:00
l5y
6a65abd2e3 Persist instance config assets across Docker restarts (#345) 2025-10-15 16:14:59 +02:00
l5y
a3aef8cadd Add modem preset display to node overlay (#340)
* Add modem metadata line to node overlays

* Ensure modem metadata loads for all overlays
2025-10-14 20:59:47 +02:00
l5y
cff89a8c88 Display message frequency and channel in chat log (#339)
* Display message frequency and channel in chat log

* Ensure chat prefixes display consistent metadata brackets

* Ensure chat prefixes show non-breaking frequency placeholder

* Adjust chat channel tag placement
2025-10-14 20:56:42 +02:00
l5y
26c1366412 Bump fallback version to v0.5.1 (#338) 2025-10-14 16:51:04 +00:00
l5y
28f5b49f4d docs: update changelog for 0.5.0 (#337) 2025-10-14 16:48:36 +00:00
l5y
a46da284e5 Fix ingestor package layout in Docker image (#336) 2025-10-14 18:47:54 +02:00
l5y
22a31b6c80 Ensure node overlays appear above fullscreen map (#333)
* Increase overlay z-index to surface node info

* Ensure short info overlays attach to fullscreen host

* Ensure info overlay participates in fullscreen mode
2025-10-14 15:52:26 +02:00
l5y
b7ef0bbfcd Adjust node table columns responsively (#332) 2025-10-14 14:59:47 +02:00
l5y
03b5a10fe4 Add LoRa metadata fields to nodes and messages (#331)
* Add LoRa metadata fields to nodes and messages

* Filter numeric SQLite keys from message rows
2025-10-14 14:51:28 +02:00
l5y
e97498d09f Add channel metadata capture for message tagging (#329) 2025-10-13 23:10:01 +02:00
l5y
7db76ec2fc Capture radio metadata for ingestor payloads (#327)
* Capture radio metadata and tag ingestor payloads

* Log captured LoRa metadata when initializing radio config
2025-10-13 22:35:06 +02:00
l5y
63beb2ea6b Avoid mutating frozen node query results (#324) 2025-10-13 17:22:34 +02:00
l5y
ffad84f18a Ensure frontend reports git-aware version strings (#321)
* Ensure frontend reports git-aware version strings

* Keep footer fixed across viewport widths
2025-10-13 16:26:57 +02:00
l5y
2642ff7a95 Fix web Docker image to include application code (#322) 2025-10-13 16:25:44 +02:00
l5y
40b6eda096 Refine stacked short info overlays on the map (#319)
* Refine map overlays to use stacked short info panels

* Allow stacked overlays to pass neighbor clicks
2025-10-13 14:53:43 +02:00
l5y
dee6ad7e4a Refine environment configuration defaults (#318) 2025-10-13 14:06:14 +02:00
l5y
ea9c633eff Fix legacy configuration migration to XDG directories (#317)
* Handle legacy config migration for XDG assets

* Ensure legacy key migration precedes identity load

* Apply rufo formatting to identity module
2025-10-13 14:02:17 +02:00
l5y
9c73fceea7 Adopt XDG base directories for app data and config (#316)
* Support XDG base directories

* Keep Docker MESH_DB on persistent volume
2025-10-13 12:29:56 +02:00
l5y
5133e9d498 refactor: streamline ingestor environment variables (#314)
* refactor: streamline ingestor environment variables

* fix: set connection env var in docker test
2025-10-13 11:02:33 +02:00
l5y
b63e5328b1 Reduce auto-fit padding and increase default zoom (#315) 2025-10-13 10:57:54 +02:00
l5y
d66b09ddee Ensure APIs filter stale data and refresh node details from latest sources (#312)
* Ensure fresh API data and richer node refresh details

* Refresh map markers with latest node data
2025-10-13 10:54:47 +02:00
l5y
009965f2fb Handle offline tile layer creation failures (#307) 2025-10-13 09:27:03 +02:00
l5y
51e6479ab6 Handle offline tile rendering failures (#306) 2025-10-13 09:26:49 +02:00
l5y
874c8fd73c Fix map auto-fit handling and add controller (#311) 2025-10-13 09:26:35 +02:00
l5y
e4c48682b0 Fix map initialization bounds and add coverage (#305)
* Fix map initialization bounds and add coverage

* Handle antimeridian bounds when clustering map points

* Fix dateline-aware map bounds
2025-10-12 19:22:17 +02:00
l5y
00444f7611 test: expand config and sanitizer coverage (#303) 2025-10-12 14:41:20 +02:00
l5y
511e6d377c Add comprehensive theme and background front-end tests (#302) 2025-10-12 14:35:53 +02:00
l5y
e6974a683a Document sanitization and helper modules (#301) 2025-10-12 10:09:42 +02:00
l5y
c0d68b23d4 Add protobuf stubs for mesh tests (#300) 2025-10-12 10:09:13 +02:00
l5y
ee904633a8 Handle CRL lookup failures during federation TLS (#299) 2025-10-12 09:56:53 +02:00
l5y
4329605e6f Ensure JavaScript workflow runs tests with output (#298) 2025-10-12 09:46:42 +02:00
l5y
772c5888c3 Fix ingestor debug timestamps for structured logging (#296) 2025-10-12 09:40:57 +02:00
l5y
f04e917cd9 Add Apache license headers to missing sources (#297) 2025-10-12 09:38:04 +02:00
l5y
9e939194ba Update workflows for ingestor, sinatra, and frontend (#295) 2025-10-12 09:36:02 +02:00
l5y
e328a20929 Fix IPv6 instance domain canonicalization (#294) 2025-10-12 09:33:03 +02:00
l5y
aba94b197d Handle federation HTTPS CRL verification failures (#293) 2025-10-12 09:22:54 +02:00
l5y
80f2bbdb25 Adjust federation announcement cadence (#292) 2025-10-12 09:08:50 +02:00
l5y
522213c040 Restore modular app functionality (#291)
* Restore modular app functionality

* Fix federation thread settings and add coverage

* Use Sinatra set for federation threads

* Restore 41447 as default web port
2025-10-12 08:54:11 +02:00
l5y
58998ba274 Refactor config and metadata helpers into PotatoMesh modules (#290) 2025-10-11 23:19:25 +02:00
l5y
4ad718e164 Update default site configuration environment values (#288) 2025-10-11 21:20:36 +02:00
l5y
707786e222 Add test for draining queue with concurrent enqueue (#287) 2025-10-11 20:38:55 +02:00
l5y
868bf08fd1 Ensure config directories exist in web image (#286) 2025-10-11 20:38:47 +02:00
l5y
1316d4f2d1 Clarify network target parsing (#285) 2025-10-11 20:38:40 +02:00
l5y
9be390ee09 Ensure queue deactivates when empty (#284) 2025-10-11 20:38:27 +02:00
l5y
d9ed006b4c Clarify BLE connection phrasing (#283) 2025-10-11 20:31:12 +02:00
l5y
d09fc842b8 Configure web container for production (#282) 2025-10-11 19:39:22 +02:00
l5y
73bdd809bd Normalize INSTANCE_DOMAIN configuration to require hostnames (#280)
* Ensure INSTANCE_DOMAIN configuration uses hostname

* Define ip_from_domain before use
2025-10-11 19:39:05 +02:00
l5y
f1dba89d4b Run initial federation announcement asynchronously (#281) 2025-10-11 19:38:12 +02:00
l5y
131a63845c Add production build Dockerfile and compose contexts (#279) 2025-10-11 18:23:51 +02:00
l5y
2240be1f2d Improve instance domain detection logic (#278) 2025-10-11 18:22:50 +02:00
l5y
a048a83c6c Implement federation announcements and instances API (#277) 2025-10-11 18:01:08 +02:00
l5y
4ef1e29034 Fix federation signature handling and IP guard (#276)
* Fix federation signature handling and IP guard

* Avoid defaulting isPrivate before signature verification

* Normalize instance domain host handling for restricted IP check

* ignore web app credentials

---------

Co-authored-by: l5yth <d220195275+l5yth@users.noreply.github.com>
2025-10-11 17:39:52 +02:00
l5y
b21df3de5c Add persistent federation metadata endpoint (#274)
* Add federated metadata endpoint

* Fix configure-time database access

* Fix well-known refresh bypassed by static files

* run rufo

---------

Co-authored-by: l5yth <d220195275+l5yth@users.noreply.github.com>
2025-10-11 13:04:33 +00:00
l5y
678af5e55b Add configurable instance domain with reverse DNS fallback (#272)
* Add instance domain resolution with reverse DNS fallback

* run rufo

---------

Co-authored-by: l5yth <d220195275+l5yth@users.noreply.github.com>
2025-10-11 12:21:55 +00:00
l5y
c4fd59626f Add production configuration guidance (#273) 2025-10-11 14:01:53 +02:00
l5y
0a26e4252a Add targeted API endpoints and expose version metadata (#271)
* Add per-node API endpoints and version route

* Adjust version metadata and node lookup route
2025-10-11 12:36:28 +02:00
Nic Jansma
d19e032b40 Prometheus metrics updates on startup and for position/telemetry (#270)
* Prometheus metrics updates on startup and for position/telemetry

* Fix per rufo

* CoPilot feedback

* CR feedback
2025-10-11 09:24:12 +02:00
l5y
ab9ae796f3 Add hourly reconnect handling for inactive mesh interface (#267)
* Add hourly reconnect handling for inactive mesh interface

* Reset inactivity timestamp after reconnect
2025-10-07 16:41:16 +02:00
Nic Jansma
0f2f2f447c Dockerfile fixes (#268) 2025-10-07 16:34:45 +02:00
Nic Jansma
3a031694db Added prometheus /metrics endpoint (#262)
* Added prometheus /metrics endpoint

* Fixes per CoPilot suggestions

* More Copilot fixes

* Rufo formatted
2025-10-07 16:32:45 +02:00
l5y
3cfbffc155 Add fullscreen toggle to map view (#263)
* Add fullscreen mode controls for map

* Improve fullscreen map scaling and control icon

* Improve fullscreen map sizing and icon
2025-10-07 15:53:18 +02:00
l5y
4f5aec45b3 Relocate JavaScript coverage export under web (#266) 2025-10-07 15:49:40 +02:00
Nic Jansma
2acfca20d9 v4.0.0 version string in web UI (#265) 2025-10-07 13:34:23 +00:00
l5y
f2ed5f5c03 Add energy saving mode to ingestor (#256) 2025-10-07 15:28:41 +02:00
l5y
db04b85134 chore: restore apache headers (#260) 2025-10-07 10:28:24 +02:00
l5y
ba66ac5cea docs: add matrix to readme (#259) 2025-10-07 07:29:12 +00:00
l5y
a592b655c4 Force dark theme default based on sanitized cookie (#252)
* Ensure dark theme defaults cleanly on initial load

* Ensure form controls respond to theme
2025-10-07 09:00:55 +02:00
l5y
a5a2ae5edc Document mesh ingestor modules with PDoc-style docstrings (#255) 2025-10-07 08:59:38 +02:00
l5y
363b4c5525 Handle missing node IDs in Meshtastic nodeinfo packets (#251)
* Handle Meshtastic nodeinfo packets without IDs

* Guard BLE reader against missing payloads
2025-10-07 08:56:36 +02:00
l5y
16e1304ded Add comprehensive RDoc comments to Ruby helpers (#254) 2025-10-07 08:53:39 +02:00
l5y
b89347938a docs: expand jsdoc coverage (#253) 2025-10-07 08:53:26 +02:00
l5y
6969ae6c4a Fix mesh ingestor telemetry and neighbor handling (#249)
* Refine mesh ingestor modularization

* Handle script execution in mesh wrapper

* Ensure mesh shim finds package when run as script

* Ensure queue state resets after send errors
2025-10-07 08:40:28 +02:00
l5y
64f8862676 Refactor front-end assets into external modules (#245)
* Refactor front-end assets into external modules

* Restore chat flag inline script

* Declare legend toggle control variable

* Remove dynamic background generation

* Restore background script with theme-based color

* run rufo
2025-10-07 08:33:06 +02:00
l5y
6660986211 Add tests for helper utilities and asset routes (#243)
* test: expand coverage for helpers and assets

* Adjust failing helper and asset specs

* Adjust specs for fallback node lookup and missing logo
2025-10-07 07:07:23 +02:00
l5y
5dfcc1a5fe docs: add ingestor inline docstrings (#244) 2025-10-07 00:06:42 +02:00
l5y
2efd28766b Add comprehensive coverage tests for mesh ingestor (#241) 2025-10-07 00:04:33 +02:00
l5y
c9bba25e5a Add comprehensive inline documentation (#240) 2025-10-07 00:01:31 +02:00
l5y
41976a3b43 Update changelog (#238)
* Update changelog

* Update readme
2025-10-06 08:36:13 +02:00
l5y
5a47a8f8e4 Reformat neighbor overlay details (#237) 2025-10-06 08:08:24 +02:00
l5y
c13f3c913f Add neighbor lines toggle to map legend (#236) 2025-10-06 08:05:44 +02:00
l5y
2e9b54b6cf Hide Air Util Tx column on mobile (#235) 2025-10-06 08:04:07 +02:00
l5y
7e844be627 Add overlay for clickable neighbor links on map (#234)
* Add overlay for clickable neighbor links on map

* Fix neighbor overlays and include SNR details

* Prevent map neighbor overlay clicks from closing immediately
2025-10-06 07:41:11 +02:00
l5y
b37e55c29a Hide humidity and pressure on mobile (#232) 2025-10-06 06:34:48 +02:00
l5y
332ba044f2 Remove last position timestamp from map info overlay (#233) 2025-10-06 06:34:37 +02:00
l5y
09a2d849ec Improve live node positions and expose precision metadata (#231)
* Fetch latest node positions and precision metadata

* Stop showing position source and precision in UI

* Guard node positions against stale merges
2025-10-05 23:08:57 +02:00
l5y
a3fb9b0d5c Show neighbor short names in info overlays (#228)
* Show neighbor short names in info overlays

* Adjust neighbor info placement
2025-10-05 22:04:29 +02:00
l5y
192978acf9 Add telemetry environmental data to node UI (#227) 2025-10-05 21:49:28 +02:00
l5y
581aaea93b Reduce neighbor line opacity (#226) 2025-10-05 21:45:05 +02:00
l5y
299752a4f1 Visualize neighbor connections on map canvas (#224)
* Visualize neighbor connections on map

* Gracefully handle neighbor fetch failures
2025-10-05 21:27:41 +02:00
l5y
142c0aa539 Add clear control to filter input (#225) 2025-10-05 21:26:37 +02:00
l5y
78168ce3db Handle Bluetooth shutdown hangs gracefully (#221)
* Handle Bluetooth shutdown hangs gracefully

* Make interface close guard compatible with patched Event
2025-10-05 21:07:19 +02:00
l5y
332abbc183 Adjust mesh priorities and receive topics (#220) 2025-10-05 20:50:34 +02:00
l5y
c136c5cf26 Add BLE and fallback mesh interface handling (#219)
* Add BLE and fallback mesh interface support

* Handle SIGINT by propagating KeyboardInterrupt

* Guard optional BLE dependency

* run black
2025-10-05 20:48:23 +02:00
l5y
2a65e89eee Add neighbor info ingestion and API endpoints (#218)
* Add neighbor info ingestion and API support

* Fix neighbor spec and add fixture

* run black

* run rufo
2025-10-05 12:35:13 +02:00
l5y
d6f1e7bc80 Add debug logs for unknown node creation and last-heard updates (#214)
* Add debug logging for unknown nodes and last-heard updates

* Fix debug log syntax
2025-10-04 21:25:23 +02:00
l5y
5ac5f3ec3f Update node last seen when events are received (#212)
* Update node last seen timestamps from event receive times

* run rufo

* fix tests
2025-10-04 21:11:16 +02:00
l5y
bb4cbfa62c Improve debug logging for node and telemetry data (#213)
* Improve debug logging for node and telemetry data

* run black
2025-10-04 21:03:03 +02:00
l5y
f0d600e5d7 Improve stored message debug logging (#211) 2025-10-04 20:53:54 +02:00
l5y
e0f0a6390d Stop repeating ingestor node info snapshot and timestamp debug logs (#210)
* Adjust ingestor node snapshot cadence and debug logging

* Ensure node snapshot waits for data

* run black
2025-10-04 20:41:53 +02:00
l5y
d4a27dccf7 Add telemetry API and ingestion support (#205)
* Add telemetry ingestion and API support

* Flatten telemetry storage and API responses

* Fix telemetry insert placeholder count

* Adjust telemetry node updates

* run black

* run rufo
2025-10-04 18:28:18 +02:00
l5y
74c4596dc5 Add private mode to hide chat and message APIs (#204)
* Add private mode to hide chat and message APIs

* run rufo
2025-10-04 09:36:43 +02:00
l5y
1f2328613c Handle offline-ready map fallback (#202) 2025-10-03 11:24:18 +02:00
l5y
eeca67f6ea Add linux/armv7 images and configuration support (#201) 2025-10-03 11:11:14 +02:00
l5y
4ae8a1cfca Update Docker documentation (#200)
* Update Docker documentation

* docs: reference compose file
2025-10-03 11:03:25 +02:00
l5y
ff06129a6f Update node last seen when ingesting encrypted messages (#198)
* Update node last seen for encrypted messages

* run rufo
2025-10-03 10:59:12 +02:00
l5y
6d7aa4dd56 fix api in readme (#197) 2025-10-01 14:16:54 +00:00
l5y
4548f750d3 Add connection recovery for TCP interface (#186)
* Add connection recovery for TCP interface

* run black
2025-09-27 18:52:56 +02:00
l5y
31f02010d3 bump version to 0.3 (#191)
* bump version to 0.3

* update readme
2025-09-27 18:52:41 +02:00
l5y
ec1ea5cbba pgrade styles and fix interface issues (#190) 2025-09-27 18:46:56 +02:00
l5y
8500c59755 some updates in the front (#188)
* ok, i'm added correct image loader

* and some css

* make zebra in a table and add a background and some little changes in app

* for example you can check how it work on https://vrs.kdd2105.ru

* fix ai comments

---------

Co-authored-by: dkorotkih2014-hub <d.korotkih2014@gmail.com>
2025-09-27 18:18:02 +02:00
l5y
556dd6b51c Update last heard on node entry change (#185) 2025-09-26 20:43:53 +02:00
l5y
3863e2d63d Populate chat metadata for unknown nodes (#182)
* Populate chat metadata for unknown nodes

* run rufo

* fix comments

* run rufo
2025-09-26 16:45:42 +02:00
l5y
9e62621819 Update role colors to new palette (#183) 2025-09-26 16:08:14 +02:00
l5y
c8c7c8cc05 Add placeholder nodes for unknown senders (#181)
* Add placeholder nodes for unknown senders

* run rufo
2025-09-26 14:24:30 +02:00
l5y
5116313ab0 fix: update role colors and ordering for firmware 2.7.10 (#180) 2025-09-26 13:30:34 +02:00
l5y
66389dd27c Handle plain IP addresses in mesh TCP detection (#154)
* Fix TCP target detection for plain IPs

* run black
2025-09-26 13:25:42 +02:00
l5y
ee6501243f Handle encrypted messages (#173)
* Handle encrypted messages

* Remove redundant message node columns

* Preserve original numeric message senders

* Normalize message sender IDs in API responses

* Exclude encrypted messages from API responses

* run rufo
2025-09-24 07:34:28 +02:00
l5y
8dd912175d Add fallback display names for unnamed nodes (#171) 2025-09-23 19:06:28 +02:00
l5y
02f9fb45e2 Ensure routers render above other node types (#169) 2025-09-23 18:59:34 +02:00
l5y
4254dbda91 Reorder lint steps after tests in CI (#168) 2025-09-23 18:31:38 +02:00
l5y
a46bed1c33 Handle proto values in nodeinfo payloads (#167) 2025-09-23 18:31:22 +02:00
l5y
d711300442 Remove raw payload storage from database schema (#166) 2025-09-23 17:29:08 +02:00
l5y
98a8203591 Add POSITION_APP ingestion and API support (#160)
* Add POSITION_APP ingestion and API support

* Adjust mesh receive subscriptions and priorities

* run linters
2025-09-23 16:42:51 +02:00
l5y
084c5ae158 Add support for NODEINFO_APP packets (#159)
* Add support for NODEINFO_APP packets

* run black
2025-09-23 14:40:35 +02:00
l5y
17018aeb19 Derive SEO metadata from existing config (#153) 2025-09-23 08:20:42 +02:00
l5y
74b3da6f00 tests: create helper script to dump all mesh data from serial (#152)
* tests: create helper script to dump all mesh data from serial

* tests: use public callbacks for dump script
2025-09-23 08:09:31 +02:00
l5y
ab1217a8bf Limit chat log to recent entries (#151) 2025-09-22 18:54:09 +02:00
l5y
62de1480f7 Require time library before formatting ISO timestamps (#149)
* Require time library for ISO timestamp formatting

* Default to host networking in Compose
2025-09-22 09:21:04 +02:00
l5y
ab2e9b06e1 Define potatomesh network (#148) 2025-09-22 08:58:39 +02:00
l5y
e91ad24cf9 Fix sqlite3 native extension on Alpine (#146) 2025-09-22 08:12:48 +02:00
l5y
2e543b7cd4 Allow binding to all interfaces in app.sh (#147) 2025-09-22 08:11:36 +02:00
l5y
db4353ccdc Force building sqlite3 gem on Alpine (#145) 2025-09-22 08:10:00 +02:00
l5y
5a610cf08a Support mock serial interface in CI (#143) 2025-09-21 10:00:30 +02:00
l5y
71b854998c Fix Docker workflow to build linux images (#142) 2025-09-21 09:39:09 +02:00
l5y
0a70ae4b3e Add clickable role filters to the map legend (#140)
* Make map legend role entries filter nodes

* Adjust map legend spacing and toggle text
2025-09-21 09:33:48 +02:00
l5y
6e709b0b67 Rebuild chat log on each refresh (#139) 2025-09-21 09:19:07 +02:00
l5y
a4256cee83 fix: retain runtime libs for alpine production (#138) 2025-09-21 09:18:55 +02:00
l5y
89f0b1bcfe fix: support windows ingestor build (#136)
* fix: support windows ingestor build

* fix: restore alpine build deps for ingestor (#137)
2025-09-20 22:00:45 +02:00
l5y
e8af3b2397 fix: use supported ruby image (#135) 2025-09-20 19:10:36 +00:00
Taylor Rose
812d3c851f feat: Add comprehensive Docker support (#122)
* feat: Add comprehensive Docker support

- Add multi-container Docker setup with web app and data ingestor
- Create production-ready Dockerfiles with multi-stage builds
- Add Docker Compose configurations for dev, prod, and custom environments
- Implement CI/CD pipeline with GitHub Actions for automated builds
- Add comprehensive Docker documentation and setup guides
- Include security scanning and multi-platform builds
- Support for Meshtastic device integration via serial access
- Persistent data storage with named volumes
- Health checks and monitoring capabilities

Addresses GitHub issue #120: Dockerize the project for easier community adoption

Files added:
- web/Dockerfile: Ruby web application container
- data/Dockerfile: Python data ingestor container
- data/requirements.txt: Python dependencies
- docker-compose.yml: Base Docker Compose configuration
- docker-compose.dev.yml: Development environment overrides
- docker-compose.prod.yml: Production environment overrides
- .env.example: Environment configuration template
- .dockerignore: Docker build context optimization
- .github/workflows/docker.yml: CI/CD pipeline
- DOCKER.md: Comprehensive Docker documentation

This implementation transforms PotatoMesh from a complex manual setup
to a single-command deployment: docker-compose up -d

* feat: Add Docker support with multi-architecture builds

- Add web/Dockerfile with Ruby 3.4 Alpine base
- Add data/Dockerfile with Python 3.13 Alpine base
- Use Alpine's SQLite3 packages for cross-platform compatibility
- Support AMD64, ARM64, ARMv7, and Windows architectures
- Multi-stage builds for optimized production images
- Non-root user security and proper file permissions

* feat: Add Docker Compose configurations for different environments

- docker-compose.yml: Production setup with GHCR images
- docker-compose.dev.yml: Development setup with local builds
- docker-compose.raspberry-pi.yml: Pi-optimized with resource limits
- Support for all architectures (AMD64, ARM64, ARMv7)
- Proper volume mounts and network configuration
- Environment variable configuration for different deployments

* feat: Add GitHub Actions workflows for Docker CI/CD

- docker.yml: Multi-architecture build and push to GHCR
- test-raspberry-pi-hardware.yml: ARM64 testing with QEMU
- Support for manual workflow dispatch with version input
- Build and test all Docker variants (AMD64, ARM64, ARMv7, Windows)
- Automated publishing to GitHub Container Registry
- Comprehensive testing for Raspberry Pi deployments

* feat: Add Docker documentation and configuration tools

- docs/DOCKER.md: Comprehensive Docker setup and usage guide
- configure.sh: Interactive configuration script for deployment
- Platform-specific setup instructions (macOS, Linux, Windows)
- Raspberry Pi optimization guidelines
- Environment variable configuration
- Troubleshooting and best practices

* docs: Update README with comprehensive Docker support

- Add Docker Quick Start section with published images
- Add comprehensive table of all available GHCR images
- Include architecture-specific pull commands
- Update manual installation instructions
- Add platform-specific deployment examples
- Document all supported architectures and use cases

* chore: Update dependencies and project configuration

- Update data/requirements.txt for Python 3.13 compatibility
- Add v0.3.0 changelog entry documenting Docker support
- Update .gitignore for Docker-related files
- Prepare project for Docker deployment

* feat: Update web interface for Denver Mesh Network

- Update default configuration to center on Denver, Colorado
- Set SITE_NAME to 'Denver Mesh Network'
- Configure 915MHz frequency for US region
- Update map center coordinates (39.7392, -104.9903)
- Set appropriate node distance and Matrix room settings

* Update Docker configuration and documentation

- Remove Raspberry Pi specific Docker files and workflows
- Update Docker workflow configuration
- Consolidate Docker documentation
- Add AGENTS.md for opencode integration
- Update README with current project status

* cleanup: workflow/readme

* Update README.md

Co-authored-by: l5y <220195275+l5yth@users.noreply.github.com>

* Add .env.example and simplify documentation

- Add comprehensive .env.example with all environment variables
- Update web Dockerfile to use Berlin coordinates instead of Denver
- Simplify README Docker quick start with helpful comments
- Greatly simplify DOCKER.md with only essential information

* cleanup: readme

* Remove Stadia API key references

- Remove STADIA_API_KEY from docker-compose.yml environment variables
- Remove Stadia Maps configuration section from configure.sh
- Remove Stadia API key references from .env.example
- Simplify configuration to use basic OpenStreetMap tiles only

* quickfix

* cleanup: remove example usage from docker gh action output

---------

Co-authored-by: l5y <220195275+l5yth@users.noreply.github.com>
2025-09-20 21:04:19 +02:00
l5y
608d1e0396 bump version to 0.2.1 (#134) 2025-09-20 20:59:21 +02:00
l5y
63787454ca Fix dark mode tile styling on new map tiles (#132)
* Ensure dark mode styling applied to new map tiles

* Ensure dark mode filters apply to new map tiles

* Improve map tile filter handling
2025-09-20 18:13:18 +02:00
l5y
55c1384f80 Switch map tiles to OSM HOT and add theme filters (#130)
* Switch map tiles to OSM HOT and add theme filters

* Ensure OSM tiles are filtered for theme modes

* Ensure tile filters update when toggling dark mode

* run rufo
2025-09-19 23:02:55 +02:00
l5y
6750d7bc12 Add footer version display (#128)
* Add footer version display

* Ensure footer version text matches spec
2025-09-19 11:22:28 +02:00
l5y
d33fcaf5db Add responsive controls for map legend (#129) 2025-09-19 11:21:00 +02:00
l5y
7974fd9597 update changelog (#119) 2025-09-17 16:57:32 +02:00
l5y
dcb512636c update readme for 0.2 (#118)
* update readme for 0.2

* update readme for 0.2

* update readme for 0.2

* update readme for 0.2
2025-09-17 10:23:36 +02:00
l5y
7c6bf801e9 Add PotatoMesh logo to header and favicon (#117)
* Add PotatoMesh logo to header and favicon

* Ensure header logo remains visible

* update svg
2025-09-17 10:12:35 +02:00
l5y
71e9f89aae Harden API auth and request limits (#116)
* Harden API auth and request limits

* run rufo
2025-09-17 08:00:25 +02:00
l5y
0936c6087b Add sortable node table columns (#114) 2025-09-17 07:06:13 +02:00
l5y
95e3e8723a Add short name overlay for node details (#111)
* Add node details overlay for short names

* Simplify short info overlay layout
2025-09-16 23:22:41 +02:00
l5y
671a910936 Adjust python ingestor interval to 60 seconds (#112) 2025-09-16 21:07:53 +02:00
l5y
3b64e829a8 Hide location columns on medium screens (#109) 2025-09-16 19:43:31 +02:00
l5y
84ed739a61 Handle message updates based on sender info (#108)
* Handle message updates based on sender info

* run rufo
2025-09-16 19:41:56 +02:00
l5y
cffdb7dca6 Prioritize node posts in queued API updates (#107)
* Prioritize node posts in queued API updates

* run black
2025-09-16 19:30:38 +02:00
l5y
4182a9f83c Add auto-refresh toggle (#105) 2025-09-16 19:21:54 +02:00
l5y
9873f6105d Adjust Leaflet popup styling for dark mode (#104)
* Adjust Leaflet popup styling for dark mode

* some css fixing
2025-09-16 17:14:36 +00:00
l5y
8d3829cc4e feat: add site info overlay (#103) 2025-09-16 19:00:31 +02:00
l5y
e424485761 Add long name tooltip to short name badge (#102) 2025-09-16 18:58:29 +02:00
l5y
baf7f5d137 Ensure node numeric aliases are derived from canonical IDs (#101)
* Derive node numeric aliases when missing

* Preserve raw message senders when storing payloads

* Normalize packet message sender ids when available

* run rufo
2025-09-16 18:41:49 +02:00
l5y
3edf60c625 chore: clean up repository (#96)
* chore: clean up repository

* Fix message spec node lookup for numeric IDs (#98)

* Fix message spec node lookup for numeric IDs

* run rufo

* Fix message node fallback lookup (#99)
2025-09-16 15:25:12 +02:00
l5y
1beb343501 Handle SQLite busy errors when upserting nodes (#100) 2025-09-16 15:24:01 +02:00
l5y
0c0f877b13 Configure Sinatra logging level from DEBUG flag (#97)
* Configure Sinatra logging level

* Fix logger level helper invocation

* Fix Sinatra logger helper definition syntax
2025-09-16 14:46:50 +02:00
l5y
f7a1b5c5ad Add penetration tests for authentication and SQL injection (#95) 2025-09-16 13:13:57 +02:00
l5y
051d09dcaf Document Python and Ruby source modules (#94) 2025-09-16 13:13:12 +02:00
l5y
eb900aecb6 Add tests covering mesh helper edge cases (#93)
* test: expand coverage for mesh helpers

* run black
2025-09-16 12:48:01 +02:00
l5y
f16393eafd fix py code cov (#92) 2025-09-16 12:10:17 +02:00
l5y
49dcfebfb3 Add Codecov coverage and test analytics for Python CI (#91) 2025-09-16 12:04:46 +02:00
l5y
1c13b99f3b Skip null fields when choosing packet identifiers (#88) 2025-09-16 11:56:02 +02:00
l5y
54a1eb5b42 create python yml ga (#90)
* Create python.yml

* ci: add black

* run an actual formatter

* also add rufo

* fix pytest

* run black
2025-09-16 11:50:33 +02:00
l5y
2818c6d2b8 Add unit tests for mesh ingestor script (#89) 2025-09-16 11:44:28 +02:00
l5y
f4aa5d3873 Add coverage for debug logging on messages without sender (#86)
* Add debug logging spec for messages without sender

* Route debug logging through Kernel.warn

* Relax debug log matchers
2025-09-16 11:33:03 +02:00
l5y
542f4dd0e2 Handle concurrent node snapshot updates (#85) 2025-09-16 11:10:11 +02:00
l5y
4a72cdda75 Fix extraction of packet sender ids (#84) 2025-09-16 10:35:11 +02:00
l5y
4b9d581448 Add coverage for API authentication and payload edge cases (#83) 2025-09-16 10:18:10 +02:00
l5y
1d3b3f11e9 Add Codecov test analytics to Ruby workflow (#82) 2025-09-16 10:12:25 +02:00
l5y
e97824fd0b Configure SimpleCov for Codecov coverage (#81) 2025-09-16 09:58:44 +02:00
l5y
1cd9058685 update codecov job (#80)
* update codecov job

* add codecov condif
2025-09-16 09:55:53 +02:00
l5y
47e23ea14c fix readme badges (#79)
* fix readme badges

* fix readme badges
2025-09-16 09:46:44 +02:00
l5y
afd18794c7 Add Codecov upload step to Ruby workflow (#78) 2025-09-16 09:43:09 +02:00
l5y
203bd623bd Add Apache license headers to source files (#77)
* Add Apache license headers to source files

* fix formatting
2025-09-16 09:39:28 +02:00
l5y
2b6b44a31d Add integration specs for node and message APIs (#76) 2025-09-16 09:29:31 +02:00
l5y
0059a6aab3 docs: update for 0.2.0 release (#75)
* docs: update for 0.2.0 release

* docs: add scrot 0.2
2025-09-16 09:23:11 +02:00
l5y
fc30a080ff create ruby workflow (#74)
* create ruby workflow

* add step for dependencies

* bump ruby version

* Set up Ruby action in web directory
2025-09-16 08:52:33 +02:00
l5y
7399c02be9 Add RSpec tests for app boot and database setup (#73) 2025-09-16 08:25:13 +02:00
l5y
02e985d2a8 Align refresh controls with status text (#72)
* Align refresh controls with status text

* Improve mobile alignment for refresh controls
2025-09-16 08:21:15 +02:00
l5y
954352809f spec: update testdata 2025-09-16 08:11:11 +02:00
l5y
7eb36a5a3d remove duplication 2025-09-15 21:35:59 +02:00
l5y
0768b4d91a Improve mobile layout (#68)
* Improve mobile layout

* styling tweaks
2025-09-15 21:32:56 +02:00
l5y
be1306c9c0 Normalize message sender IDs using node numbers (#67) 2025-09-15 21:04:29 +02:00
l5y
7904717597 style: simplify node table (#65) 2025-09-15 18:16:36 +02:00
l5y
e2c19e1611 Add debug logging for missing from_id (#64) 2025-09-15 18:15:46 +02:00
l5y
b230e79ab0 Handle nested dataclasses in node snapshots (#63) 2025-09-15 14:59:23 +02:00
l5y
31727e35bb add placeholder for default frequency 2025-09-15 14:48:12 +02:00
l5y
22127bbfb4 ignore log files 2025-09-15 14:44:32 +02:00
l5y
413278544a Log node object on snapshot update failure (#62) 2025-09-15 14:34:56 +02:00
l5y
580a588df7 Run schema initialization only when database or tables are missing (#61) 2025-09-15 14:05:01 +02:00
l5y
b39b83fb51 Send mesh data to Potatomesh API (#60)
* feat: post mesh data to API

* Serialize node objects before posting

* don't put raw json in api/db
2025-09-15 14:00:48 +02:00
l5y
6d948603c9 Convert boolean flags to integers for SQLite (#59) 2025-09-15 13:37:30 +02:00
l5y
648bcc9b92 Use packet id as message primary key (#58)
* Use packet id as message primary key

* fix query

* fix query
2025-09-15 13:34:59 +02:00
l5y
4dc1227be7 Add POST /api/messages and enforce API token (#56) 2025-09-15 13:13:47 +02:00
l5y
3b097feaae Update README.md 2025-09-15 12:17:45 +02:00
l5y
da2e5fbde1 feat: parameterize community info (#55)
* feat: parameterize community info

* chore: restore test data and document env defaults

* also make default channel configurable
2025-09-15 12:15:51 +02:00
l5y
003db7c36a feat: add dark mode toggle (#54)
* feat: add dark mode toggle

* fix chat colors in dark mode
2025-09-15 11:53:49 +02:00
l5y
9aa640338d Update README.md 2025-09-15 11:44:44 +02:00
l5y
3c24b71f16 ignore copies 2025-09-15 11:42:27 +02:00
l5y
eee6738a9c add changelog 2025-09-15 08:49:12 +02:00
372 changed files with 156263 additions and 4546 deletions

20
.codecov.yml Normal file
View File

@@ -0,0 +1,20 @@
# Copyright © 2025-26 l5yth & contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
coverage:
status:
project:
default:
target: 99%
threshold: 1%

76
.dockerignore Normal file
View File

@@ -0,0 +1,76 @@
# Git
.git
.gitignore
# Documentation
README.md
CHANGELOG.md
*.md
# Docker files
docker-compose*.yml
.dockerignore
# Environment files
.env*
!.env.example
# Logs
*.log
logs/
# Runtime data
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# nyc test coverage
.nyc_output
# Dependency directories
node_modules/
vendor/
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Test files
tests/
spec/
test_*
*_test.py
*_spec.rb
# Development files
ai_docs/

84
.env.example Normal file
View File

@@ -0,0 +1,84 @@
# PotatoMesh Environment Configuration
# Copy this file to .env and customize for your setup
# =============================================================================
# REQUIRED SETTINGS
# =============================================================================
# Public domain name for this PotatoMesh instance (required for webapp)
# Provide a hostname (with optional port) that resolves to the web service.
# Example: mesh.example.org or mesh.example.org:41447
INSTANCE_DOMAIN="mesh.example.org"
# API authentication token (required for ingestor communication)
# Generate a secure token: openssl rand -hex 32
API_TOKEN="your-secure-api-token-here"
# Meshtastic connection target (required for ingestor)
# Common serial paths:
# - Linux: /dev/ttyACM0, /dev/ttyUSB0
# - macOS: /dev/cu.usbserial-*
# - Windows (WSL): /dev/ttyS*
# You may also provide an IP:PORT pair (e.g. 192.168.1.20:4403) or a
# Bluetooth address (e.g. ED:4D:9E:95:CF:60).
CONNECTION="/dev/ttyACM0"
# =============================================================================
# SITE CUSTOMIZATION
# =============================================================================
# Your mesh network name
SITE_NAME="My Meshtastic Network"
# Default Meshtastic channel
CHANNEL="#LongFast"
# Default frequency for your region
# Common frequencies: 868MHz (Europe), 915MHz (US), 433MHz (Worldwide)
FREQUENCY="915MHz"
# Map center coordinates (latitude, longitude)
# Berlin, Germany: 52.502889, 13.404194
# Denver, Colorado: 39.7392, -104.9903
# London, UK: 51.5074, -0.1278
MAP_CENTER="38.761944,-27.090833"
# Maximum distance to show nodes (kilometers)
MAX_DISTANCE=42
# =============================================================================
# OPTIONAL INTEGRATIONS
# =============================================================================
# Community chat link or Matrix room for your community (optional)
# Matrix aliases (e.g. #meshtastic-berlin:matrix.org) will be linked via matrix.to automatically.
CONTACT_LINK="#potatomesh:dod.ngo"
# Enable or disable PotatoMesh federation features (1=enabled, 0=disabled)
FEDERATION=1
# Hide public mesh messages from unauthenticated visitors (1=hidden, 0=public)
PRIVATE=0
# =============================================================================
# ADVANCED SETTINGS
# =============================================================================
# Debug mode (0=off, 1=on)
DEBUG=0
# Default map zoom override
# MAP_ZOOM=15
# Docker image architecture (linux-amd64, linux-arm64, linux-armv7)
POTATOMESH_IMAGE_ARCH="linux-amd64"
# Docker image tag (use "latest" for the newest release or pin to vX.Y)
POTATOMESH_IMAGE_TAG="latest"
# Docker Compose networking profile
# Leave unset for Linux hosts (default host networking).
# Set to "bridge" on Docker Desktop (macOS/Windows) if host networking
# is unavailable.
# COMPOSE_PROFILES="bridge"

View File

@@ -1,15 +1,24 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
# Copyright © 2025-26 l5yth & contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
version: 2
updates:
- package-ecosystem: "ruby" # See documentation for possible values
directory: "/web" # Location of package manifests
- package-ecosystem: "ruby"
directory: "/web"
schedule:
interval: "weekly"
- package-ecosystem: "python" # See documentation for possible values
directory: "/data" # Location of package manifests
- package-ecosystem: "python"
directory: "/"
schedule:
interval: "weekly"

21
.github/workflows/README.md vendored Normal file
View File

@@ -0,0 +1,21 @@
# GitHub Actions Workflows
## Workflows
- **`docker.yml`** - Build and push Docker images to GHCR
- **`codeql.yml`** - Security scanning
- **`python.yml`** - Python ingestor pipeline
- **`ruby.yml`** - Ruby Sinatra app testing
- **`javascript.yml`** - Frontend test suite
- **`mobile.yml`** - Flutter mobile tests with coverage reporting
- **`release.yml`** - Tag-triggered Flutter release builds for Android and iOS
## Usage
```bash
# Build locally
docker-compose build
# Deploy
docker-compose up -d
```

View File

@@ -1,14 +1,17 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
# Copyright © 2025-26 l5yth & contributors
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: "CodeQL Advanced"
on:
@@ -20,20 +23,10 @@ on:
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
@@ -43,58 +36,21 @@ jobs:
include:
- language: python
build-mode: none
- language: rust
build-mode: none
- language: ruby
build-mode: none
- language: javascript-typescript
build-mode: none
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
# or others). This is typically only required for manual builds.
# - name: Setup runtime (example)
# uses: actions/setup-example@v1
# Initializes the CodeQL tools for scanning.
uses: actions/checkout@v5
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"

259
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,259 @@
# Copyright © 2025-26 l5yth & contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Build and Push Docker Images
on:
push:
tags: [ 'v*' ]
workflow_dispatch:
inputs:
version:
description: 'Version to publish (e.g., 1.0.0)'
required: true
default: '1.0.0'
publish_all_variants:
description: 'Publish all Docker image variants (latest tag)'
type: boolean
default: false
env:
REGISTRY: ghcr.io
IMAGE_PREFIX: l5yth/potato-mesh
jobs:
build-and-push:
runs-on: ubuntu-latest
if: (startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push') || github.event_name == 'workflow_dispatch'
environment: production
permissions:
contents: read
packages: write
strategy:
matrix:
service: [web, ingestor, matrix-bridge]
architecture:
- { name: linux-amd64, platform: linux/amd64, label: "Linux x86_64", os: linux, architecture: amd64 }
- { name: linux-arm64, platform: linux/arm64, label: "Linux ARM64", os: linux, architecture: arm64 }
- { name: linux-armv7, platform: linux/arm/v7, label: "Linux ARMv7", os: linux, architecture: arm }
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU emulation
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag or input
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
RAW_VERSION="${{ github.event.inputs.version }}"
else
RAW_VERSION=${GITHUB_REF#refs/tags/}
fi
STRIPPED_VERSION=${RAW_VERSION#v}
echo "version=$STRIPPED_VERSION" >> $GITHUB_OUTPUT
echo "version_with_v=v$STRIPPED_VERSION" >> $GITHUB_OUTPUT
echo "raw_version=$RAW_VERSION" >> $GITHUB_OUTPUT
echo "Published version: $STRIPPED_VERSION"
- name: Determine tagging strategy
id: tagging
env:
EVENT_NAME: ${{ github.event_name }}
PUBLISH_ALL_VARIANTS: ${{ github.event.inputs.publish_all_variants || 'false' }}
run: |
VERSION="${{ steps.version.outputs.version }}"
if echo "$VERSION" | grep -E -- '-(rc|beta|alpha|dev)'; then
IS_PRERELEASE=true
else
IS_PRERELEASE=false
fi
if [ "$IS_PRERELEASE" = "false" ] && { [ "$EVENT_NAME" = "push" ] || [ "$PUBLISH_ALL_VARIANTS" = "true" ]; }; then
INCLUDE_LATEST=true
else
INCLUDE_LATEST=false
fi
echo "is_prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT
echo "include_latest=$INCLUDE_LATEST" >> $GITHUB_OUTPUT
- name: Build and push ${{ matrix.service }} for ${{ matrix.architecture.name }}
uses: docker/build-push-action@v5
with:
context: .
file: ${{ matrix.service == 'web' && './web/Dockerfile' || matrix.service == 'ingestor' && './data/Dockerfile' || './matrix/Dockerfile' }}
target: ${{ matrix.service == 'matrix-bridge' && 'runtime' || 'production' }}
platforms: ${{ matrix.architecture.platform }}
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.service }}-${{ matrix.architecture.name }}:${{ steps.version.outputs.version }}
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.service }}-${{ matrix.architecture.name }}:${{ steps.version.outputs.version_with_v }}
${{ steps.tagging.outputs.include_latest == 'true' && format('{0}/{1}-{2}-{3}:latest', env.REGISTRY, env.IMAGE_PREFIX, matrix.service, matrix.architecture.name) || '' }}
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.description=PotatoMesh ${{ matrix.service == 'web' && 'Web Application' || matrix.service == 'ingestor' && 'Python Ingestor' || 'Matrix Bridge' }} for ${{ matrix.architecture.label }}
org.opencontainers.image.licenses=Apache-2.0
org.opencontainers.image.version=${{ steps.version.outputs.version }}
org.opencontainers.image.created=${{ github.event.head_commit.timestamp }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.title=PotatoMesh ${{ matrix.service == 'web' && 'Web' || matrix.service == 'ingestor' && 'Ingestor' || 'Matrix Bridge' }} (${{ matrix.architecture.label }})
org.opencontainers.image.vendor=PotatoMesh
org.opencontainers.image.architecture=${{ matrix.architecture.architecture }}
org.opencontainers.image.os=${{ matrix.architecture.os }}
cache-from: type=gha,scope=${{ matrix.service }}-${{ matrix.architecture.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.service }}-${{ matrix.architecture.name }}
test-images:
runs-on: ubuntu-latest
needs: build-and-push
if: startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: version
run: |
RAW_VERSION=${GITHUB_REF#refs/tags/}
STRIPPED_VERSION=${RAW_VERSION#v}
echo "version=$STRIPPED_VERSION" >> $GITHUB_OUTPUT
echo "version_with_v=v$STRIPPED_VERSION" >> $GITHUB_OUTPUT
- name: Detect pre-release tag
id: tagging
run: |
VERSION="${{ steps.version.outputs.version }}"
if echo "$VERSION" | grep -E -- '-(rc|beta|alpha|dev)'; then
INCLUDE_LATEST=false
else
INCLUDE_LATEST=true
fi
echo "include_latest=$INCLUDE_LATEST" >> $GITHUB_OUTPUT
- name: Test web application (Linux AMD64)
run: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web-linux-amd64:${{ steps.version.outputs.version }}
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web-linux-amd64:${{ steps.version.outputs.version_with_v }}
docker run --rm -d --name web-test -p 41447:41447 \
-e API_TOKEN=test-token \
-e DEBUG=1 \
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web-linux-amd64:${{ steps.version.outputs.version }}
sleep 10
curl -f http://localhost:41447/ || exit 1
docker stop web-test
- name: Test ingestor (Linux AMD64)
run: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ingestor-linux-amd64:${{ steps.version.outputs.version }}
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ingestor-linux-amd64:${{ steps.version.outputs.version_with_v }}
docker run --rm --name ingestor-test \
-e POTATOMESH_INSTANCE=http://localhost:41447 \
-e API_TOKEN=test-token \
-e CONNECTION=mock \
-e DEBUG=1 \
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ingestor-linux-amd64:${{ steps.version.outputs.version }} &
sleep 5
docker stop ingestor-test || true
publish-summary:
runs-on: ubuntu-latest
needs: [build-and-push, test-images]
if: always() && startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push'
steps:
- name: Extract version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Determine tagging strategy
id: tagging
run: |
VERSION="${{ steps.version.outputs.version }}"
if echo "$VERSION" | grep -E -- '-(rc|beta|alpha|dev)'; then
INCLUDE_LATEST=false
else
INCLUDE_LATEST=true
fi
echo "include_latest=$INCLUDE_LATEST" >> $GITHUB_OUTPUT
- name: Publish release summary
run: |
echo "## 🚀 PotatoMesh Images Published to GHCR" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Published Images:**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Web images
echo "### 🌐 Web Application" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.tagging.outputs.include_latest }}" = "true" ]; then
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web-linux-amd64:latest\` - Linux x86_64" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web-linux-arm64:latest\` - Linux ARM64" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web-linux-armv7:latest\` - Linux ARMv7" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
# Ingestor images
echo "### 📡 Ingestor Service" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.tagging.outputs.include_latest }}" = "true" ]; then
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ingestor-linux-amd64:latest\` - Linux x86_64" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ingestor-linux-arm64:latest\` - Linux ARM64" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ingestor-linux-armv7:latest\` - Linux ARMv7" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
# Matrix bridge images
echo "### 🧩 Matrix Bridge" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.tagging.outputs.include_latest }}" = "true" ]; then
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-matrix-bridge-linux-amd64:latest\` - Linux x86_64" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-matrix-bridge-linux-arm64:latest\` - Linux ARM64" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-matrix-bridge-linux-armv7:latest\` - Linux ARMv7" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi

62
.github/workflows/javascript.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
# Copyright © 2025-26 l5yth & contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: JavaScript
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
paths:
- '.github/**'
- 'web/**'
- 'tests/**'
permissions:
contents: read
jobs:
frontend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- uses: actions/checkout@v5
- name: Set up Node.js 22
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install dependencies
run: npm ci
- name: Run JavaScript tests
run: npm test
- name: Upload coverage to Codecov
if: always()
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: web/reports/javascript-coverage.json
flags: frontend
name: frontend
fail_ci_if_error: false
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Upload test results to Codecov
uses: codecov/test-results-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: web/reports/javascript-junit.xml
flags: frontend

72
.github/workflows/mobile.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
# Copyright © 2025-26 l5yth & contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Mobile
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
paths:
- '.github/**'
- 'app/**'
- 'tests/**'
permissions:
contents: read
jobs:
flutter:
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: ./app
steps:
- uses: actions/checkout@v5
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- name: Install dependencies
run: flutter pub get
- name: Run Flutter tests with coverage
run: flutter test --coverage
- name: Check formatting
run: dart format --set-exit-if-changed .
- name: Analyze Dart code
run: flutter analyze
- name: Build Android debug APK
if: matrix.os == 'ubuntu-latest'
run: flutter build apk --debug
- name: Build iOS debug IPA
if: matrix.os == 'macos-latest'
run: flutter build ipa --debug --no-codesign
- name: Upload coverage to Codecov
if: always()
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/lcov.info
flags: flutter-mobile
name: flutter-mobile
fail_ci_if_error: false
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

35
.github/workflows/nix.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
# Copyright © 2025-26 l5yth & contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Nix
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
flake-check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v30
with:
extra_nix_config: |
experimental-features = nix-command flakes
- name: Run flake checks
run: nix flake check

66
.github/workflows/python.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
# Copyright © 2025-26 l5yth & contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Python
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
paths:
- '.github/**'
- 'data/**'
- 'tests/**'
permissions:
contents: read
jobs:
ingestor:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install black pytest pytest-cov meshtastic
- name: Test with pytest and coverage
run: |
mkdir -p reports
pytest --cov=data --cov-report=term --cov-report=xml:reports/python-coverage.xml --junitxml=reports/python-junit.xml
- name: Upload coverage to Codecov
if: always()
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: reports/python-coverage.xml
flags: python-ingestor
fail_ci_if_error: false
name: python-ingestor
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Upload test results to Codecov
uses: codecov/test-results-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: reports/python-junit.xml
flags: python-ingestor
- name: Lint with black
run: |
black --check ./

144
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,144 @@
# Copyright © 2025-26 l5yth & contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Mobile Release
on:
push:
tags:
- "*"
permissions:
contents: write
actions: write
jobs:
android-release:
name: Android Release
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./app
env:
TAG_NAME: ${{ github.ref_name }}
steps:
- uses: actions/checkout@v5
- name: Configure Android local.properties
run: |
cat > android/local.properties <<'EOF'
sdk.dir=${ANDROID_SDK_ROOT}
flutter.sdk=${FLUTTER_ROOT}
EOF
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
cache-key: ${{ runner.os }}-flutter-${{ hashFiles('app/pubspec.lock') }}
- name: Install dependencies
run: flutter pub get
- name: Build release APK
run: flutter build apk --release
- name: Prepare artifacts
run: |
set -euo pipefail
APK_DIR="build/app/outputs/flutter-apk"
mv "${APK_DIR}/app-release.apk" "${APK_DIR}/potatomesh-reader-android-${TAG_NAME}.apk"
(cd "${APK_DIR}" && sha256sum "potatomesh-reader-android-${TAG_NAME}.apk" > "potatomesh-reader-android-${TAG_NAME}.apk.sha256sum")
- name: Upload release artifacts
uses: actions/upload-artifact@v4
with:
name: potatomesh-reader-android-${{ env.TAG_NAME }}
path: |
app/build/app/outputs/flutter-apk/potatomesh-reader-android-${{ env.TAG_NAME }}.apk
app/build/app/outputs/flutter-apk/potatomesh-reader-android-${{ env.TAG_NAME }}.apk.sha256sum
ios-release:
name: iOS Release
runs-on: macos-latest
defaults:
run:
working-directory: ./app
env:
TAG_NAME: ${{ github.ref_name }}
steps:
- uses: actions/checkout@v5
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
cache-key: ${{ runner.os }}-flutter-${{ hashFiles('app/pubspec.lock') }}
- name: Install dependencies
run: flutter pub get
- name: Build release IPA (no codesign)
run: flutter build ipa --release --no-codesign
- name: Prepare artifacts
run: |
set -euo pipefail
IPA_DIR="build/ios/ipa"
IPA_FILE="$(ls "${IPA_DIR}"/*.ipa | head -n 1)"
mv "${IPA_FILE}" "${IPA_DIR}/potatomesh-reader-ios-${TAG_NAME}.ipa"
(cd "${IPA_DIR}" && shasum -a 256 "potatomesh-reader-ios-${TAG_NAME}.ipa" > "potatomesh-reader-ios-${TAG_NAME}.ipa.sha256sum")
- name: Upload release artifacts
uses: actions/upload-artifact@v4
with:
name: potatomesh-reader-ios-${{ env.TAG_NAME }}
path: |
app/build/ios/ipa/potatomesh-reader-ios-${{ env.TAG_NAME }}.ipa
app/build/ios/ipa/potatomesh-reader-ios-${{ env.TAG_NAME }}.ipa.sha256sum
publish-release:
name: Publish Release
runs-on: ubuntu-latest
needs:
- android-release
- ios-release
env:
TAG_NAME: ${{ github.ref_name }}
PUBLISH_RELEASE: "false"
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
steps:
- uses: actions/checkout@v5
- name: Check tag format
run: |
set -euo pipefail
if [[ "${TAG_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "PUBLISH_RELEASE=true" >> "$GITHUB_ENV"
else
echo "Non point release tag (${TAG_NAME}); skipping publish."
fi
- name: Download Android artifacts
if: env.PUBLISH_RELEASE == 'true'
uses: actions/download-artifact@v4
with:
name: potatomesh-reader-android-${{ env.TAG_NAME }}
path: artifacts/android
- name: Download iOS artifacts
if: env.PUBLISH_RELEASE == 'true'
uses: actions/download-artifact@v4
with:
name: potatomesh-reader-ios-${{ env.TAG_NAME }}
path: artifacts/ios
- name: Attach assets to release
if: env.PUBLISH_RELEASE == 'true'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
generate_release_notes: true
files: |
artifacts/android/potatomesh-reader-android-${{ env.TAG_NAME }}.apk
artifacts/android/potatomesh-reader-android-${{ env.TAG_NAME }}.apk.sha256sum
artifacts/ios/potatomesh-reader-ios-${{ env.TAG_NAME }}.ipa
artifacts/ios/potatomesh-reader-ios-${{ env.TAG_NAME }}.ipa.sha256sum

73
.github/workflows/ruby.yml vendored Normal file
View File

@@ -0,0 +1,73 @@
# Copyright © 2025-26 l5yth & contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Ruby
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
paths:
- '.github/**'
- 'web/**'
- 'tests/**'
permissions:
contents: read
jobs:
sinatra:
defaults:
run:
working-directory: ./web
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['3.4', '4.0']
steps:
- uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true
working-directory: ./web
- name: Set up dependencies
run: bundle install
- name: Run tests
run: |
mkdir -p tmp/test-results
bundle exec rspec \
--require rspec_junit_formatter \
--format progress \
--format RspecJunitFormatter \
--out tmp/test-results/rspec.xml
- name: Upload test results to Codecov
uses: codecov/test-results-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./web/tmp/test-results/rspec.xml
flags: sinatra-${{ matrix.ruby-version }}
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
flags: sinatra-${{ matrix.ruby-version }}
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Run rufo
run: bundle exec rufo --check .

78
.github/workflows/rust.yml vendored Normal file
View File

@@ -0,0 +1,78 @@
# Copyright © 2025-26 l5yth & contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Rust
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
paths:
- '.github/**'
- 'matrix/**'
- 'tests/**'
permissions:
contents: read
jobs:
matrix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache Cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
./matrix/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml', '**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Show rustc version
run: rustc --version
- name: Install llvm-tools-preview component
run: rustup component add llvm-tools-preview --toolchain stable
- name: Install cargo-llvm-cov
working-directory: ./matrix
run: cargo install cargo-llvm-cov --locked
- name: Check formatting
working-directory: ./matrix
run: cargo fmt --all -- --check
- name: Clippy lint
working-directory: ./matrix
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Build
working-directory: ./matrix
run: cargo build --all --all-features
- name: Test
working-directory: ./matrix
run: cargo test --all --all-features --verbose
- name: Run tests with coverage
working-directory: ./matrix
run: |
cargo llvm-cov --all-features --workspace --lcov --output-path coverage.lcov
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./matrix/coverage.lcov
flags: matrix-bridge
name: matrix-bridge
fail_ci_if_error: false

21
.gitignore vendored
View File

@@ -11,7 +11,7 @@
/tmp/
# Used by dotenv library to load environment variables.
# .env
.env
# Ignore Byebug command history file.
.byebug_history
@@ -57,3 +57,22 @@ Gemfile.lock
# Python cache directories
__pycache__/
.coverage
coverage/
coverage.xml
htmlcov/
reports/
# AI planning and documentation
ai_docs/
*.log
# Generated credentials for the instance
web/.config
# JavaScript dependencies
node_modules/
web/node_modules/
# Debug symbols
ignored.txt

48
AGENTS.md Normal file
View File

@@ -0,0 +1,48 @@
# Repository Guidelines
Keep code well structured, modular, and not monolithic. If modules get to big, consider submodules structure.
Make sure all tests pass for Python (`pytest`), Ruby (`rspec`), and JavaScript (`npm test`).
Make sure all code is properly inline documented (PDoc, RDoc, JSDoc, et.c). We do not want any undocumented code.
Make sure all code is 100% unit tested. We want all lines, units, and branches to be thouroughly covered by tests.
New source files should have Apache v2 license headers using the exact string `Copyright © 2025-26 l5yth & contributors`.
Run linters for Python (`black`) and Ruby (`rufo`) to ensure consistent code formatting.
## Project Structure & Module Organization
The repository splits runtime and ingestion logic. `web/` holds the Sinatra dashboard (Ruby code in `lib/potato_mesh`, views in `views/`, static bundles in `public/`).
`data/` hosts the Python Meshtastic ingestor plus migrations and CLI scripts. API fixtures and end-to-end harnesses live in `tests/`. Dockerfiles and compose files support containerized workflows.
`matrix/` contains the Rust Matrix bridge; build with `cargo build --release` or `docker build -f matrix/Dockerfile .`, and keep bridge config under `matrix/Config.toml` when running locally.
## Build, Test, and Development Commands
Run dependency installs inside `web/`: `bundle install` for gems and `npm ci` for JavaScript tooling. Start the app with `cd web && API_TOKEN=dev ./app.sh` for local work or `bundle exec rackup -p 41447` when integrating elsewhere.
Prep ingestion with `python -m venv .venv && pip install -r data/requirements.txt`; `./data/mesh.sh` streams from live radios. `docker-compose -f docker-compose.dev.yml up` brings up the full stack.
Container images publish via `.github/workflows/docker.yml` as `potato-mesh-{service}-linux-$arch` (`web`, `ingestor`, `matrix-bridge`), using the Dockerfiles in `web/`, `data/`, and `matrix/`.
## Coding Style & Naming Conventions
Use two-space indentation for Ruby and keep `# frozen_string_literal: true` at the top of new files. Keep Ruby classes/modules in `CamelCase`, filenames in `snake_case.rb`, and feature specs in `*_spec.rb`.
JavaScript follows ES modules under `public/assets/js`; co-locate components with `__tests__` folders and use kebab-case filenames. Format Ruby via `bundle exec rufo .` and Python via `black`. Skip committing generated coverage artifacts.
## Flutter Mobile App (`app/`)
The Flutter client lives in `app/`. Keep only the mobile targets (`android/`, `ios/`) under version control unless you explicitly support other platforms. Do not commit Flutter build outputs or editor cruft (`.dart_tool/`, `.flutter-plugins-dependencies`, `.idea/`, `.metadata`, `*.iml`, `.fvmrc` if unused).
Install dependencies with `cd app && flutter pub get`; format with `dart format .` and lint via `flutter analyze`. Run tests with `cd app && flutter test` and keep widget/unit coverage high—no new code without tests. Commit `pubspec.lock` and analysis options so toolchains stay consistent.
## Testing Guidelines
Ruby specs run with `cd web && bundle exec rspec`, producing SimpleCov output in `coverage/`. Front-end behaviour is verified through Nodes test runner: `cd web && npm test` writes V8 coverage and JUnit XML under `reports/`.
The ingestion layer is guarded by `pytest -q tests/test_mesh.py`; leave fixtures in `tests/` untouched so CI can replay them. New features should ship with matching specs and updated integration checks.
## Commit & Pull Request Guidelines
Commits should stay imperative and reference issues the way history does (`Add chat log entries... (#408)`). Squash noisy work-in-progress commits before pushing. Pull requests need a concise summary, screenshots or curl traces for UI/API tweaks, and links to tracked issues. Paste the command output for the test suites you ran and mention configuration toggles (`API_TOKEN`, `PRIVATE`) reviewers must set.
## Security & Configuration Tips
Never commit real API tokens or `.sqlite` dumps; use `.env.local` files ignored by Git. Confirm env defaults (`API_TOKEN`, `INSTANCE_DOMAIN`, `PRIVATE`) before deploying, and set `FEDERATION=0` when staging private nodes. Review `PROMETHEUS.md` when exposing metrics so scrape endpoints stay internal.

446
CHANGELOG.md Normal file
View File

@@ -0,0 +1,446 @@
# CHANGELOG
## v0.5.9
* Matrix: listen for synapse on port 41448 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/607>
* Web: collapse federation map ledgend by @l5yth in <https://github.com/l5yth/potato-mesh/pull/604>
* Web: fix stale node queries by @l5yth in <https://github.com/l5yth/potato-mesh/pull/603>
* Matrix: move short name to display name by @l5yth in <https://github.com/l5yth/potato-mesh/pull/602>
* Ci: update ruby to 4 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/601>
* Web: display traces of last 28 days if available by @l5yth in <https://github.com/l5yth/potato-mesh/pull/599>
* Web: establish menu structure by @l5yth in <https://github.com/l5yth/potato-mesh/pull/597>
* Matrix: fixed the text-message checkpoint regression by @l5yth in <https://github.com/l5yth/potato-mesh/pull/595>
* Matrix: cache seen messages by rx_time not id by @l5yth in <https://github.com/l5yth/potato-mesh/pull/594>
* Web: hide the default '0' tab when not active by @l5yth in <https://github.com/l5yth/potato-mesh/pull/593>
* Matrix: fix empty bridge state json by @l5yth in <https://github.com/l5yth/potato-mesh/pull/592>
* Web: allow certain charts to overflow upper bounds by @l5yth in <https://github.com/l5yth/potato-mesh/pull/585>
* Ingestor: support ROUTING_APP messages by @l5yth in <https://github.com/l5yth/potato-mesh/pull/584>
* Ci: run nix flake check on ci by @l5yth in <https://github.com/l5yth/potato-mesh/pull/583>
* Web: hide legend by default by @l5yth in <https://github.com/l5yth/potato-mesh/pull/582>
* Nix flake by @benjajaja in <https://github.com/l5yth/potato-mesh/pull/577>
* Support BLE UUID format for macOS Bluetooth devices by @apo-mak in <https://github.com/l5yth/potato-mesh/pull/575>
* Web: add mesh.qrp.ro as seed node by @l5yth in <https://github.com/l5yth/potato-mesh/pull/573>
* Web: ensure unknown nodes for messages and traces by @l5yth in <https://github.com/l5yth/potato-mesh/pull/572>
* Chore: bump version to 0.5.9 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/569>
## v0.5.8
* Web: add secondary seed node jmrp.io by @l5yth in <https://github.com/l5yth/potato-mesh/pull/568>
* Data: implement whitelist for ingestor by @l5yth in <https://github.com/l5yth/potato-mesh/pull/567>
* Web: add ?since= parameter to all apis by @l5yth in <https://github.com/l5yth/potato-mesh/pull/566>
* Matrix: fix docker build by @l5yth in <https://github.com/l5yth/potato-mesh/pull/565>
* Matrix: fix docker build by @l5yth in <https://github.com/l5yth/potato-mesh/pull/564>
* Web: fix federation signature validation and create fallback by @l5yth in <https://github.com/l5yth/potato-mesh/pull/563>
* Chore: update readme by @l5yth in <https://github.com/l5yth/potato-mesh/pull/561>
* Matrix: add docker file for bridge by @l5yth in <https://github.com/l5yth/potato-mesh/pull/556>
* Matrix: add health checks to startup by @l5yth in <https://github.com/l5yth/potato-mesh/pull/555>
* Matrix: omit the api part in base url by @l5yth in <https://github.com/l5yth/potato-mesh/pull/554>
* App: add utility coverage tests for main.dart by @l5yth in <https://github.com/l5yth/potato-mesh/pull/552>
* Data: add thorough daemon unit tests by @l5yth in <https://github.com/l5yth/potato-mesh/pull/553>
* Chore: bump version to 0.5.8 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/551>
## v0.5.7
* Data: track ingestors heartbeat by @l5yth in <https://github.com/l5yth/potato-mesh/pull/549>
* Harden instance selector navigation URLs by @l5yth in <https://github.com/l5yth/potato-mesh/pull/550>
* Data: hide channels that have been flag for ignoring by @l5yth in <https://github.com/l5yth/potato-mesh/pull/548>
* Web: fix limit when counting remote nodes by @l5yth in <https://github.com/l5yth/potato-mesh/pull/547>
* Web: improve instances map and table view by @l5yth in <https://github.com/l5yth/potato-mesh/pull/546>
* Web: fix traces submission with optional fields on udp by @l5yth in <https://github.com/l5yth/potato-mesh/pull/545>
* Chore: bump version to 0.5.7 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/542>
* Handle zero telemetry aggregates by @l5yth in <https://github.com/l5yth/potato-mesh/pull/538>
* Web: fix telemetry api to return current in amperes by @l5yth in <https://github.com/l5yth/potato-mesh/pull/541>
* Web: fix traces rendering by @l5yth in <https://github.com/l5yth/potato-mesh/pull/535>
* Normalize numeric node roles to canonical labels by @l5yth in <https://github.com/l5yth/potato-mesh/pull/539>
* Use INSTANCE_DOMAIN env for ingestor by @l5yth in <https://github.com/l5yth/potato-mesh/pull/536>
* Web: further refine the federation page by @l5yth in <https://github.com/l5yth/potato-mesh/pull/534>
* Add Federation Map by @apo-mak in <https://github.com/l5yth/potato-mesh/pull/532>
* Add contact link to the instance data by @apo-mak in <https://github.com/l5yth/potato-mesh/pull/533>
* Matrix: create potato-matrix-bridge by @l5yth in <https://github.com/l5yth/potato-mesh/pull/528>
## v0.5.6
* Web: display sats in view by @l5yth in <https://github.com/l5yth/potato-mesh/pull/523>
* Web: display air quality in separate chart by @l5yth in <https://github.com/l5yth/potato-mesh/pull/521>
* Ci: Add macOS and Ubuntu builds to Flutter workflow by @l5yth in <https://github.com/l5yth/potato-mesh/pull/519>
* Web: add current to charts by @l5yth in <https://github.com/l5yth/potato-mesh/pull/520>
* App: fix notification icon by @l5yth in <https://github.com/l5yth/potato-mesh/pull/518>
* Spec: update test fixtures by @l5yth in <https://github.com/l5yth/potato-mesh/pull/517>
* App: generate proper icons by @l5yth in <https://github.com/l5yth/potato-mesh/pull/516>
* Web: fix favicon by @l5yth in <https://github.com/l5yth/potato-mesh/pull/515>
* Web: add ?since= parameter to api/messages by @l5yth in <https://github.com/l5yth/potato-mesh/pull/512>
* App: implement notifications by @l5yth in <https://github.com/l5yth/potato-mesh/pull/511>
* App: add theme selector by @l5yth in <https://github.com/l5yth/potato-mesh/pull/507>
* App: further harden refresh logic and prefer local first by @l5yth in <https://github.com/l5yth/potato-mesh/pull/506>
* Ci: fix app artifacts for tags by @l5yth in <https://github.com/l5yth/potato-mesh/pull/504>
* Ci: build app artifacts for tags by @l5yth in <https://github.com/l5yth/potato-mesh/pull/503>
* App: add persistance by @l5yth in <https://github.com/l5yth/potato-mesh/pull/501>
* App: instance and chat mvp by @l5yth in <https://github.com/l5yth/potato-mesh/pull/498>
* App: add instance selector to settings by @l5yth in <https://github.com/l5yth/potato-mesh/pull/497>
* App: add scaffholding gitignore by @l5yth in <https://github.com/l5yth/potato-mesh/pull/496>
* Handle reaction app packets without reply id by @l5yth in <https://github.com/l5yth/potato-mesh/pull/495>
* Render reaction multiplier counts by @l5yth in <https://github.com/l5yth/potato-mesh/pull/494>
* Add comprehensive tests for Flutter reader by @l5yth in <https://github.com/l5yth/potato-mesh/pull/491>
* Map numeric role ids to canonical Meshtastic roles by @l5yth in <https://github.com/l5yth/potato-mesh/pull/489>
* Update node detail hydration for traces by @l5yth in <https://github.com/l5yth/potato-mesh/pull/490>
* Add mobile Flutter CI workflow by @l5yth in <https://github.com/l5yth/potato-mesh/pull/488>
* Align OCI labels in docker workflow by @l5yth in <https://github.com/l5yth/potato-mesh/pull/487>
* Add Meshtastic reader Flutter app by @l5yth in <https://github.com/l5yth/potato-mesh/pull/483>
* Handle pre-release Docker tagging by @l5yth in <https://github.com/l5yth/potato-mesh/pull/486>
* Web: remove range from charts labels by @l5yth in <https://github.com/l5yth/potato-mesh/pull/485>
* Floor override frequencies to MHz integers by @l5yth in <https://github.com/l5yth/potato-mesh/pull/476>
* Prevent message ids from being treated as node identifiers by @l5yth in <https://github.com/l5yth/potato-mesh/pull/475>
* Fix 1 after emojis in reply. by @Alexkurd in <https://github.com/l5yth/potato-mesh/pull/464>
* Add frequency and preset to node table by @l5yth in <https://github.com/l5yth/potato-mesh/pull/472>
* Subscribe to traceroute app pubsub topic by @l5yth in <https://github.com/l5yth/potato-mesh/pull/471>
* Aggregate telemetry over the last 7 days by @l5yth in <https://github.com/l5yth/potato-mesh/pull/470>
* Address missing id field ingestor bug by @l5yth in <https://github.com/l5yth/potato-mesh/pull/469>
* Merge secondary channels by name by @l5yth in <https://github.com/l5yth/potato-mesh/pull/468>
* Rate limit host device telemetry by @l5yth in <https://github.com/l5yth/potato-mesh/pull/467>
* Add traceroutes to frontend by @l5yth in <https://github.com/l5yth/potato-mesh/pull/466>
* Feat: implement traceroute app packet handling across the stack by @l5yth in <https://github.com/l5yth/potato-mesh/pull/463>
* Bump version and update changelog by @l5yth in <https://github.com/l5yth/potato-mesh/pull/462>
## v0.5.5
* Added comprehensive helper unit tests by @l5yth in <https://github.com/l5yth/potato-mesh/pull/457>
* Added reaction-aware handling by @l5yth in <https://github.com/l5yth/potato-mesh/pull/455>
* Env: add map zoom by @l5yth in <https://github.com/l5yth/potato-mesh/pull/454>
* Charts: render aggregated telemetry charts for all nodes by @l5yth in <https://github.com/l5yth/potato-mesh/pull/453>
* Nodes: render charts detail pages as overlay by @l5yth in <https://github.com/l5yth/potato-mesh/pull/452>
* Fix telemetry parsing for charts by @l5yth in <https://github.com/l5yth/potato-mesh/pull/451>
* Nodes: improve charts on detail pages by @l5yth in <https://github.com/l5yth/potato-mesh/pull/450>
* Nodes: add charts to detail pages by @l5yth in <https://github.com/l5yth/potato-mesh/pull/449>
* Aggregate frontend snapshots across views by @l5yth in <https://github.com/l5yth/potato-mesh/pull/447>
* Remove added 1 if reply with emoji by @Alexkurd in <https://github.com/l5yth/potato-mesh/pull/443>
* Refine node detail view layout by @l5yth in <https://github.com/l5yth/potato-mesh/pull/442>
* Enable map centering from node table coordinates by @l5yth in <https://github.com/l5yth/potato-mesh/pull/439>
* Add node detail route and page by @l5yth in <https://github.com/l5yth/potato-mesh/pull/441>
* Ensure Meshtastic nodeinfo patch runs before importing interfaces by @l5yth in <https://github.com/l5yth/potato-mesh/pull/440>
* Filter zero-valued fields from API responses by @l5yth in <https://github.com/l5yth/potato-mesh/pull/438>
* Add debug payload tracing and ignored packet logging by @l5yth in <https://github.com/l5yth/potato-mesh/pull/437>
* Tighten map auto-fit behaviour by @l5yth in <https://github.com/l5yth/potato-mesh/pull/435>
* Fetch encrypted chat log entries for log tab by @l5yth in <https://github.com/l5yth/potato-mesh/pull/434>
* Add encrypted filter to messages API by @l5yth in <https://github.com/l5yth/potato-mesh/pull/432>
* Guard NodeInfo handler against missing IDs by @l5yth in <https://github.com/l5yth/potato-mesh/pull/431>
* Add standalone full-screen map, chat, and nodes views by @l5yth in <https://github.com/l5yth/potato-mesh/pull/429>
* Ensure chat history fetches full message limit by @l5yth in <https://github.com/l5yth/potato-mesh/pull/428>
* Fix ingestion of nodeinfo packets missing ids (#426) by @l5yth in <https://github.com/l5yth/potato-mesh/pull/427>
* Chore: update license headers by @l5yth in <https://github.com/l5yth/potato-mesh/pull/424>
* Chore: bump version to 0.5.5 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/423>
## v0.5.4
* Handle naming when primary channel has a name by @l5yth in <https://github.com/l5yth/potato-mesh/pull/422>
* Handle edge case when primary channel has a name by @l5yth in <https://github.com/l5yth/potato-mesh/pull/421>
* Add preset mode to logs by @l5yth in <https://github.com/l5yth/potato-mesh/pull/420>
* Parallelize federation tasks with worker pool by @l5yth in <https://github.com/l5yth/potato-mesh/pull/419>
* Allow filtering chat and logs by node name by @l5yth in <https://github.com/l5yth/potato-mesh/pull/417>
* Gem: Add erb as dependency removed from std by @l5yth in <https://github.com/l5yth/potato-mesh/pull/416>
* Implement support for replies and reactions app by @l5yth in <https://github.com/l5yth/potato-mesh/pull/411>
* Ingestor: Ignore direct messages on default channel by @l5yth in <https://github.com/l5yth/potato-mesh/pull/414>
* Agents: Add instructions by @l5yth in <https://github.com/l5yth/potato-mesh/pull/410>
* Display encrypted messages in frontend log window by @l5yth in <https://github.com/l5yth/potato-mesh/pull/409>
* Add chat log entries for telemetry, position, and neighbor events by @l5yth in <https://github.com/l5yth/potato-mesh/pull/408>
* Handle missing instance domain outside production by @l5yth in <https://github.com/l5yth/potato-mesh/pull/405>
* Add tabbed chat panel with channel grouping by @l5yth in <https://github.com/l5yth/potato-mesh/pull/404>
* Normalize numeric client roles using Meshtastic CLI enums by @l5yth in <https://github.com/l5yth/potato-mesh/pull/402>
* Ensure Docker images publish versioned tags by @l5yth in <https://github.com/l5yth/potato-mesh/pull/403>
* Document environment configuration variables by @l5yth in <https://github.com/l5yth/potato-mesh/pull/400>
* Document federation refresh cadence by @l5yth in <https://github.com/l5yth/potato-mesh/pull/401>
* Add Prometheus monitoring documentation by @l5yth in <https://github.com/l5yth/potato-mesh/pull/399>
* Config: Read PROM_REPORT_IDS from environment by @nicjansma in <https://github.com/l5yth/potato-mesh/pull/398>
* Feat: Mesh-Ingestor: Ability to provide already-existing interface instance by @KenADev in <https://github.com/l5yth/potato-mesh/pull/395>
* Fix: Mesh-Ingestor: Fix error for non-existing datetime.UTC reference by @KenADev in <https://github.com/l5yth/potato-mesh/pull/396>
* Chore: bump version to 0.5.4 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/388>
## v0.5.3
* Add telemetry formatting utilities and extend node overlay by @l5yth in <https://github.com/l5yth/potato-mesh/pull/387>
* Prune blank values from API responses by @l5yth in <https://github.com/l5yth/potato-mesh/pull/386>
* Add full support to telemetry schema and API by @l5yth in <https://github.com/l5yth/potato-mesh/pull/385>
* Respect PORT environment override by @l5yth in <https://github.com/l5yth/potato-mesh/pull/384>
* Add instance selector dropdown for federation deployments by @l5yth in <https://github.com/l5yth/potato-mesh/pull/382>
* Harden federation announcements by @l5yth in <https://github.com/l5yth/potato-mesh/pull/381>
* Ensure private mode disables federation by @l5yth in <https://github.com/l5yth/potato-mesh/pull/380>
* Ensure private mode disables chat messaging by @l5yth in <https://github.com/l5yth/potato-mesh/pull/378>
* Disable federation features when FEDERATION=0 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/379>
* Expose PRIVATE environment configuration across tooling by @l5yth in <https://github.com/l5yth/potato-mesh/pull/377>
* Fix frontend coverage export for Codecov by @l5yth in <https://github.com/l5yth/potato-mesh/pull/376>
* Restrict /api/instances results to recent records by @l5yth in <https://github.com/l5yth/potato-mesh/pull/374>
* Expose FEDERATION environment option across tooling by @l5yth in <https://github.com/l5yth/potato-mesh/pull/375>
* Chore: bump version to 0.5.3 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/372>
## v0.5.2
* Align theme and info controls by @l5yth in <https://github.com/l5yth/potato-mesh/pull/371>
* Fixes POST request 403 errors on instances behind Cloudflare proxy by @varna9000 in <https://github.com/l5yth/potato-mesh/pull/368>
* Delay initial federation announcements by @l5yth in <https://github.com/l5yth/potato-mesh/pull/366>
* Ensure well-known document stays in sync on startup by @l5yth in <https://github.com/l5yth/potato-mesh/pull/365>
* Guard federation DNS resolution against restricted networks by @l5yth in <https://github.com/l5yth/potato-mesh/pull/362>
* Add federation ingestion limits and tests by @l5yth in <https://github.com/l5yth/potato-mesh/pull/364>
* Prefer reported primary channel names by @l5yth in <https://github.com/l5yth/potato-mesh/pull/363>
* Decouple message API node hydration by @l5yth in <https://github.com/l5yth/potato-mesh/pull/360>
* Fix ingestor reconnection detection by @l5yth in <https://github.com/l5yth/potato-mesh/pull/361>
* Harden instance domain validation by @l5yth in <https://github.com/l5yth/potato-mesh/pull/359>
* Ensure INSTANCE_DOMAIN propagates to containers by @l5yth in <https://github.com/l5yth/potato-mesh/pull/358>
* Chore: bump version to 0.5.2 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/356>
* Gracefully retry federation announcements over HTTP by @l5yth in <https://github.com/l5yth/potato-mesh/pull/355>
## v0.5.1
* Recursively ingest federated instances by @l5yth in <https://github.com/l5yth/potato-mesh/pull/353>
* Remove federation timeout environment overrides by @l5yth in <https://github.com/l5yth/potato-mesh/pull/352>
* Close unrelated short info overlays when opening short info by @l5yth in <https://github.com/l5yth/potato-mesh/pull/351>
* Improve federation instance error diagnostics by @l5yth in <https://github.com/l5yth/potato-mesh/pull/350>
* Harden federation domain validation and tests by @l5yth in <https://github.com/l5yth/potato-mesh/pull/347>
* Handle malformed instance records gracefully by @l5yth in <https://github.com/l5yth/potato-mesh/pull/348>
* Fix ingestor device mounting for non-serial connections by @l5yth in <https://github.com/l5yth/potato-mesh/pull/346>
* Ensure Docker deployments persist keyfile and well-known assets by @l5yth in <https://github.com/l5yth/potato-mesh/pull/345>
* Add modem preset display to node overlay by @l5yth in <https://github.com/l5yth/potato-mesh/pull/340>
* Display message frequency and channel in chat log by @l5yth in <https://github.com/l5yth/potato-mesh/pull/339>
* Bump fallback version string to v0.5.1 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/338>
* Docs: update changelog for 0.5.0 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/337>
* Fix ingestor docker import path by @l5yth in <https://github.com/l5yth/potato-mesh/pull/336>
## v0.5.0
* Ensure node overlays appear above fullscreen map by @l5yth in <https://github.com/l5yth/potato-mesh/pull/333>
* Adjust node table columns responsively by @l5yth in <https://github.com/l5yth/potato-mesh/pull/332>
* Add LoRa metadata fields to nodes and messages by @l5yth in <https://github.com/l5yth/potato-mesh/pull/331>
* Add channel metadata capture for message tagging by @l5yth in <https://github.com/l5yth/potato-mesh/pull/329>
* Capture radio metadata for ingestor payloads by @l5yth in <https://github.com/l5yth/potato-mesh/pull/327>
* Fix FrozenError when filtering node query results by @l5yth in <https://github.com/l5yth/potato-mesh/pull/324>
* Ensure frontend reports git-aware version strings by @l5yth in <https://github.com/l5yth/potato-mesh/pull/321>
* Ensure web Docker image ships application sources by @l5yth in <https://github.com/l5yth/potato-mesh/pull/322>
* Refine stacked short info overlays on the map by @l5yth in <https://github.com/l5yth/potato-mesh/pull/319>
* Refine environment configuration defaults by @l5yth in <https://github.com/l5yth/potato-mesh/pull/318>
* Fix legacy configuration migration to XDG directories by @l5yth in <https://github.com/l5yth/potato-mesh/pull/317>
* Adopt XDG base directories for app data and config by @l5yth in <https://github.com/l5yth/potato-mesh/pull/316>
* Refactor: streamline ingestor environment variables by @l5yth in <https://github.com/l5yth/potato-mesh/pull/314>
* Adjust map auto-fit padding and default zoom by @l5yth in <https://github.com/l5yth/potato-mesh/pull/315>
* Ensure APIs filter stale data and refresh node details from latest sources by @l5yth in <https://github.com/l5yth/potato-mesh/pull/312>
* Improve offline tile fallback initialization by @l5yth in <https://github.com/l5yth/potato-mesh/pull/307>
* Add fallback for offline tile rendering errors by @l5yth in <https://github.com/l5yth/potato-mesh/pull/306>
* Fix map auto-fit handling and add controller by @l5yth in <https://github.com/l5yth/potato-mesh/pull/311>
* Fix map initialization bounds and add coverage by @l5yth in <https://github.com/l5yth/potato-mesh/pull/305>
* Increase coverage for configuration and sanitizer helpers by @l5yth in <https://github.com/l5yth/potato-mesh/pull/303>
* Add comprehensive theme and background front-end tests by @l5yth in <https://github.com/l5yth/potato-mesh/pull/302>
* Document sanitization and helper modules by @l5yth in <https://github.com/l5yth/potato-mesh/pull/301>
* Add in-repo Meshtastic protobuf stubs for tests by @l5yth in <https://github.com/l5yth/potato-mesh/pull/300>
* Handle CRL lookup failures during federation TLS by @l5yth in <https://github.com/l5yth/potato-mesh/pull/299>
* Ensure JavaScript workflow runs frontend tests by @l5yth in <https://github.com/l5yth/potato-mesh/pull/298>
* Unify structured logging across application and ingestor by @l5yth in <https://github.com/l5yth/potato-mesh/pull/296>
* Add Apache license headers to missing sources by @l5yth in <https://github.com/l5yth/potato-mesh/pull/297>
* Update workflows for ingestor, sinatra, and frontend by @l5yth in <https://github.com/l5yth/potato-mesh/pull/295>
* Fix IPv6 instance domain canonicalization by @l5yth in <https://github.com/l5yth/potato-mesh/pull/294>
* Handle federation HTTPS CRL verification failures by @l5yth in <https://github.com/l5yth/potato-mesh/pull/293>
* Adjust federation announcement interval to eight hours by @l5yth in <https://github.com/l5yth/potato-mesh/pull/292>
* Restore modular app functionality by @l5yth in <https://github.com/l5yth/potato-mesh/pull/291>
* Refactor config and metadata helpers into PotatoMesh modules by @l5yth in <https://github.com/l5yth/potato-mesh/pull/290>
* Update default site configuration defaults by @l5yth in <https://github.com/l5yth/potato-mesh/pull/288>
* Add regression test for queue drain concurrency by @l5yth in <https://github.com/l5yth/potato-mesh/pull/287>
* Ensure Docker config directories are created for non-root user by @l5yth in <https://github.com/l5yth/potato-mesh/pull/286>
* Clarify numeric address requirement for network target parsing by @l5yth in <https://github.com/l5yth/potato-mesh/pull/285>
* Ensure mesh ingestor queue resets active flag when idle by @l5yth in <https://github.com/l5yth/potato-mesh/pull/284>
* Clarify BLE connection description in README by @l5yth in <https://github.com/l5yth/potato-mesh/pull/283>
* Configure web container for production mode by @l5yth in <https://github.com/l5yth/potato-mesh/pull/282>
* Normalize INSTANCE_DOMAIN configuration to require hostnames by @l5yth in <https://github.com/l5yth/potato-mesh/pull/280>
* Avoid blocking startup on federation announcements by @l5yth in <https://github.com/l5yth/potato-mesh/pull/281>
* Fix production Docker builds for web and ingestor images by @l5yth in <https://github.com/l5yth/potato-mesh/pull/279>
* Improve instance domain detection logic by @l5yth in <https://github.com/l5yth/potato-mesh/pull/278>
* Implement federation announcements and instances API by @l5yth in <https://github.com/l5yth/potato-mesh/pull/277>
* Fix federation signature handling and IP guard by @l5yth in <https://github.com/l5yth/potato-mesh/pull/276>
* Add persistent federation metadata endpoint by @l5yth in <https://github.com/l5yth/potato-mesh/pull/274>
* Add configurable instance domain with reverse DNS fallback by @l5yth in <https://github.com/l5yth/potato-mesh/pull/272>
* Document production deployment configuration by @l5yth in <https://github.com/l5yth/potato-mesh/pull/273>
* Add targeted API endpoints and expose version metadata by @l5yth in <https://github.com/l5yth/potato-mesh/pull/271>
* Prometheus metrics updates on startup and for position/telemetry by @nicjansma in <https://github.com/l5yth/potato-mesh/pull/270>
* Add hourly reconnect handling for inactive mesh interface by @l5yth in <https://github.com/l5yth/potato-mesh/pull/267>
* Dockerfile fixes by @nicjansma in <https://github.com/l5yth/potato-mesh/pull/268>
* Added prometheus /metrics endpoint by @nicjansma in <https://github.com/l5yth/potato-mesh/pull/262>
* Add fullscreen toggle to map view by @l5yth in <https://github.com/l5yth/potato-mesh/pull/263>
* Relocate JS coverage export script into web directory by @l5yth in <https://github.com/l5yth/potato-mesh/pull/266>
* V0.4.0 version string in web UI by @nicjansma in <https://github.com/l5yth/potato-mesh/pull/265>
* Add energy saving cycle to ingestor daemon by @l5yth in <https://github.com/l5yth/potato-mesh/pull/256>
* Chore: restore apache headers by @l5yth in <https://github.com/l5yth/potato-mesh/pull/260>
* Docs: add matrix to readme by @l5yth in <https://github.com/l5yth/potato-mesh/pull/259>
* Force dark theme default based on sanitized cookie by @l5yth in <https://github.com/l5yth/potato-mesh/pull/252>
* Document mesh ingestor modules with PDoc-style docstrings by @l5yth in <https://github.com/l5yth/potato-mesh/pull/255>
* Handle missing node IDs in Meshtastic nodeinfo packets by @l5yth in <https://github.com/l5yth/potato-mesh/pull/251>
* Document Ruby helper methods with RDoc comments by @l5yth in <https://github.com/l5yth/potato-mesh/pull/254>
* Add JSDoc documentation across client scripts by @l5yth in <https://github.com/l5yth/potato-mesh/pull/253>
* Fix mesh ingestor telemetry and neighbor handling by @l5yth in <https://github.com/l5yth/potato-mesh/pull/249>
* Refactor front-end assets into external modules by @l5yth in <https://github.com/l5yth/potato-mesh/pull/245>
* Add tests for helper utilities and asset routes by @l5yth in <https://github.com/l5yth/potato-mesh/pull/243>
* Docs: add ingestor inline docstrings by @l5yth in <https://github.com/l5yth/potato-mesh/pull/244>
* Add comprehensive coverage tests for mesh ingestor by @l5yth in <https://github.com/l5yth/potato-mesh/pull/241>
* Add inline documentation to config helpers and frontend scripts by @l5yth in <https://github.com/l5yth/potato-mesh/pull/240>
* Update changelog by @l5yth in <https://github.com/l5yth/potato-mesh/pull/238>
## v0.4.0
* Reformat neighbor overlay layout by @l5yth in <https://github.com/l5yth/potato-mesh/pull/237>
* Add legend toggle for neighbor lines by @l5yth in <https://github.com/l5yth/potato-mesh/pull/236>
* Hide Air Util Tx column on mobile by @l5yth in <https://github.com/l5yth/potato-mesh/pull/235>
* Add overlay for clickable neighbor links on map by @l5yth in <https://github.com/l5yth/potato-mesh/pull/234>
* Hide humidity and pressure columns on mobile by @l5yth in <https://github.com/l5yth/potato-mesh/pull/232>
* Remove last position timestamp from map info overlay by @l5yth in <https://github.com/l5yth/potato-mesh/pull/233>
* Improve live node positions and expose precision metadata by @l5yth in <https://github.com/l5yth/potato-mesh/pull/231>
* Show neighbor short names in info overlays by @l5yth in <https://github.com/l5yth/potato-mesh/pull/228>
* Add telemetry environment metrics to node UI by @l5yth in <https://github.com/l5yth/potato-mesh/pull/227>
* Reduce neighbor line opacity by @l5yth in <https://github.com/l5yth/potato-mesh/pull/226>
* Visualize neighbor connections on map canvas by @l5yth in <https://github.com/l5yth/potato-mesh/pull/224>
* Add clear control to filter input by @l5yth in <https://github.com/l5yth/potato-mesh/pull/225>
* Handle Bluetooth shutdown hangs gracefully by @l5yth in <https://github.com/l5yth/potato-mesh/pull/221>
* Adjust mesh priorities and receive topics by @l5yth in <https://github.com/l5yth/potato-mesh/pull/220>
* Add BLE and fallback mesh interface handling by @l5yth in <https://github.com/l5yth/potato-mesh/pull/219>
* Add neighbor info ingestion and API endpoints by @l5yth in <https://github.com/l5yth/potato-mesh/pull/218>
* Add debug logs for unknown node creation and last-heard updates by @l5yth in <https://github.com/l5yth/potato-mesh/pull/214>
* Update node last seen when events are received by @l5yth in <https://github.com/l5yth/potato-mesh/pull/212>
* Improve debug logging for node and telemetry data by @l5yth in <https://github.com/l5yth/potato-mesh/pull/213>
* Normalize stored message debug output by @l5yth in <https://github.com/l5yth/potato-mesh/pull/211>
* Stop repeating ingestor node info snapshot and timestamp debug logs by @l5yth in <https://github.com/l5yth/potato-mesh/pull/210>
* Add telemetry API and ingestion support by @l5yth in <https://github.com/l5yth/potato-mesh/pull/205>
* Add private mode to hide chat and message APIs by @l5yth in <https://github.com/l5yth/potato-mesh/pull/204>
* Handle offline-ready map fallback by @l5yth in <https://github.com/l5yth/potato-mesh/pull/202>
* Add linux/armv7 container builds and configuration options by @l5yth in <https://github.com/l5yth/potato-mesh/pull/201>
* Update Docker documentation by @l5yth in <https://github.com/l5yth/potato-mesh/pull/200>
* Update node last seen when ingesting encrypted messages by @l5yth in <https://github.com/l5yth/potato-mesh/pull/198>
* Fix api in readme by @l5yth in <https://github.com/l5yth/potato-mesh/pull/197>
## v0.3.0
* Add connection recovery for TCP interface by @l5yth in <https://github.com/l5yth/potato-mesh/pull/186>
* Bump version to 0.3 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/191>
* Pgrade styles and fix interface issues by @l5yth in <https://github.com/l5yth/potato-mesh/pull/190>
* Some updates in the front by @dkorotkih2014-hub in <https://github.com/l5yth/potato-mesh/pull/188>
* Update last heard on node entry change by @l5yth in <https://github.com/l5yth/potato-mesh/pull/185>
* Populate chat metadata for unknown nodes by @l5yth in <https://github.com/l5yth/potato-mesh/pull/182>
* Update role color theme to latest palette by @l5yth in <https://github.com/l5yth/potato-mesh/pull/183>
* Add placeholder nodes for unknown senders by @l5yth in <https://github.com/l5yth/potato-mesh/pull/181>
* Update role colors and ordering for firmware 2.7.10 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/180>
* Handle plain IP addresses in mesh TCP detection by @l5yth in <https://github.com/l5yth/potato-mesh/pull/154>
* Handle encrypted messages by @l5yth in <https://github.com/l5yth/potato-mesh/pull/173>
* Add fallback display names for unnamed nodes by @l5yth in <https://github.com/l5yth/potato-mesh/pull/171>
* Ensure routers render above other node types by @l5yth in <https://github.com/l5yth/potato-mesh/pull/169>
* Move lint checks after tests in CI by @l5yth in <https://github.com/l5yth/potato-mesh/pull/168>
* Handle proto values in nodeinfo payloads by @l5yth in <https://github.com/l5yth/potato-mesh/pull/167>
* Remove raw payload storage from database schema by @l5yth in <https://github.com/l5yth/potato-mesh/pull/166>
* Add POSITION_APP ingestion and API support by @l5yth in <https://github.com/l5yth/potato-mesh/pull/160>
* Add support for NODEINFO_APP packets by @l5yth in <https://github.com/l5yth/potato-mesh/pull/159>
* Derive SEO metadata from existing config values by @l5yth in <https://github.com/l5yth/potato-mesh/pull/153>
* Tests: create helper script to dump all mesh data from serial by @l5yth in <https://github.com/l5yth/potato-mesh/pull/152>
* Limit chat log to recent entries by @l5yth in <https://github.com/l5yth/potato-mesh/pull/151>
* Require time library before formatting ISO timestamps by @l5yth in <https://github.com/l5yth/potato-mesh/pull/149>
* Define docker compose network by @l5yth in <https://github.com/l5yth/potato-mesh/pull/148>
* Fix sqlite3 native extension on Alpine by @l5yth in <https://github.com/l5yth/potato-mesh/pull/146>
* Fix web app startup binding by @l5yth in <https://github.com/l5yth/potato-mesh/pull/147>
* Ensure sqlite3 builds from source on Alpine by @l5yth in <https://github.com/l5yth/potato-mesh/pull/145>
* Support mock serial interface in CI by @l5yth in <https://github.com/l5yth/potato-mesh/pull/143>
* Fix Docker workflow matrix for supported platforms by @l5yth in <https://github.com/l5yth/potato-mesh/pull/142>
* Add clickable role filters to the map legend by @l5yth in <https://github.com/l5yth/potato-mesh/pull/140>
* Rebuild chat log on each refresh by @l5yth in <https://github.com/l5yth/potato-mesh/pull/139>
* Fix: retain alpine runtime libs after removing build deps by @l5yth in <https://github.com/l5yth/potato-mesh/pull/138>
* Fix: support windows ingestor build by @l5yth in <https://github.com/l5yth/potato-mesh/pull/136>
* Fix: use supported ruby image by @l5yth in <https://github.com/l5yth/potato-mesh/pull/135>
* Feat: Add comprehensive Docker support by @trose in <https://github.com/l5yth/potato-mesh/pull/122>
* Chore: bump version to 0.2.1 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/134>
* Fix dark mode tile styling on new map tiles by @l5yth in <https://github.com/l5yth/potato-mesh/pull/132>
* Switch map tiles to OSM HOT and add theme filters by @l5yth in <https://github.com/l5yth/potato-mesh/pull/130>
* Add footer version display by @l5yth in <https://github.com/l5yth/potato-mesh/pull/128>
* Add responsive controls for map legend by @l5yth in <https://github.com/l5yth/potato-mesh/pull/129>
* Update changelog by @l5yth in <https://github.com/l5yth/potato-mesh/pull/119>
## v0.2.0
* Update readme for 0.2 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/118>
* Add PotatoMesh logo to header and favicon by @l5yth in <https://github.com/l5yth/potato-mesh/pull/117>
* Harden API auth and request limits by @l5yth in <https://github.com/l5yth/potato-mesh/pull/116>
* Add client-side sorting to node table by @l5yth in <https://github.com/l5yth/potato-mesh/pull/114>
* Add short name overlay for node details by @l5yth in <https://github.com/l5yth/potato-mesh/pull/111>
* Adjust python ingestor interval to 60 seconds by @l5yth in <https://github.com/l5yth/potato-mesh/pull/112>
* Hide location columns on medium screens by @l5yth in <https://github.com/l5yth/potato-mesh/pull/109>
* Handle message updates based on sender info by @l5yth in <https://github.com/l5yth/potato-mesh/pull/108>
* Prioritize node posts in queued API updates by @l5yth in <https://github.com/l5yth/potato-mesh/pull/107>
* Add auto-refresh toggle to UI by @l5yth in <https://github.com/l5yth/potato-mesh/pull/105>
* Adjust Leaflet popup styling for dark mode by @l5yth in <https://github.com/l5yth/potato-mesh/pull/104>
* Add site info overlay by @l5yth in <https://github.com/l5yth/potato-mesh/pull/103>
* Add long name tooltip to short name badge by @l5yth in <https://github.com/l5yth/potato-mesh/pull/102>
* Ensure node numeric aliases are derived from canonical IDs by @l5yth in <https://github.com/l5yth/potato-mesh/pull/101>
* Chore: clean up repository by @l5yth in <https://github.com/l5yth/potato-mesh/pull/96>
* Handle SQLite busy errors when upserting nodes by @l5yth in <https://github.com/l5yth/potato-mesh/pull/100>
* Configure Sinatra logging level from DEBUG flag by @l5yth in <https://github.com/l5yth/potato-mesh/pull/97>
* Add penetration tests for authentication and SQL injection by @l5yth in <https://github.com/l5yth/potato-mesh/pull/95>
* Document Python and Ruby source modules by @l5yth in <https://github.com/l5yth/potato-mesh/pull/94>
* Add tests covering mesh helper edge cases by @l5yth in <https://github.com/l5yth/potato-mesh/pull/93>
* Fix py code cov by @l5yth in <https://github.com/l5yth/potato-mesh/pull/92>
* Add Codecov reporting to Python CI by @l5yth in <https://github.com/l5yth/potato-mesh/pull/91>
* Skip null identifiers when selecting packet fields by @l5yth in <https://github.com/l5yth/potato-mesh/pull/88>
* Create python yml ga by @l5yth in <https://github.com/l5yth/potato-mesh/pull/90>
* Add unit tests for mesh ingestor script by @l5yth in <https://github.com/l5yth/potato-mesh/pull/89>
* Add coverage for debug logging on messages without sender by @l5yth in <https://github.com/l5yth/potato-mesh/pull/86>
* Handle concurrent node snapshot updates by @l5yth in <https://github.com/l5yth/potato-mesh/pull/85>
* Fix ingestion mapping for message sender IDs by @l5yth in <https://github.com/l5yth/potato-mesh/pull/84>
* Add coverage for API authentication and payload edge cases by @l5yth in <https://github.com/l5yth/potato-mesh/pull/83>
* Add JUnit test reporting to Ruby CI by @l5yth in <https://github.com/l5yth/potato-mesh/pull/82>
* Configure SimpleCov reporting for Codecov by @l5yth in <https://github.com/l5yth/potato-mesh/pull/81>
* Update codecov job by @l5yth in <https://github.com/l5yth/potato-mesh/pull/80>
* Fix readme badges by @l5yth in <https://github.com/l5yth/potato-mesh/pull/79>
* Add Codecov upload step to Ruby workflow by @l5yth in <https://github.com/l5yth/potato-mesh/pull/78>
* Add Apache license headers to source files by @l5yth in <https://github.com/l5yth/potato-mesh/pull/77>
* Add integration specs for node and message APIs by @l5yth in <https://github.com/l5yth/potato-mesh/pull/76>
* Docs: update for 0.2.0 release by @l5yth in <https://github.com/l5yth/potato-mesh/pull/75>
* Create ruby workflow by @l5yth in <https://github.com/l5yth/potato-mesh/pull/74>
* Add RSpec smoke tests for app boot and database init by @l5yth in <https://github.com/l5yth/potato-mesh/pull/73>
* Align refresh controls with status text by @l5yth in <https://github.com/l5yth/potato-mesh/pull/72>
* Improve mobile layout by @l5yth in <https://github.com/l5yth/potato-mesh/pull/68>
* Normalize message sender IDs using node numbers by @l5yth in <https://github.com/l5yth/potato-mesh/pull/67>
* Style: condense node table by @l5yth in <https://github.com/l5yth/potato-mesh/pull/65>
* Log debug details for messages without sender by @l5yth in <https://github.com/l5yth/potato-mesh/pull/64>
* Fix nested dataclass serialization for node snapshots by @l5yth in <https://github.com/l5yth/potato-mesh/pull/63>
* Log node object on snapshot update failure by @l5yth in <https://github.com/l5yth/potato-mesh/pull/62>
* Initialize database on startup by @l5yth in <https://github.com/l5yth/potato-mesh/pull/61>
* Send mesh data to Potatomesh API by @l5yth in <https://github.com/l5yth/potato-mesh/pull/60>
* Convert boolean flags for SQLite binding by @l5yth in <https://github.com/l5yth/potato-mesh/pull/59>
* Use packet id as message primary key by @l5yth in <https://github.com/l5yth/potato-mesh/pull/58>
* Add message ingestion API and stricter auth by @l5yth in <https://github.com/l5yth/potato-mesh/pull/56>
* Feat: parameterize community info by @l5yth in <https://github.com/l5yth/potato-mesh/pull/55>
* Feat: add dark mode toggle by @l5yth in <https://github.com/l5yth/potato-mesh/pull/54>
## v0.1.0
* Show daily node count in title and header by @l5yth in <https://github.com/l5yth/potato-mesh/pull/49>
* Add daily date separators to chat log by @l5yth in <https://github.com/l5yth/potato-mesh/pull/47>
* Feat: make frontend responsive for mobile by @l5yth in <https://github.com/l5yth/potato-mesh/pull/46>
* Harden mesh utilities by @l5yth in <https://github.com/l5yth/potato-mesh/pull/45>
* Filter out distant nodes from Berlin map view by @l5yth in <https://github.com/l5yth/potato-mesh/pull/43>
* Display filtered active node counts in #MediumFast subheading by @l5yth in <https://github.com/l5yth/potato-mesh/pull/44>
* Limit chat log and highlight short names by role by @l5yth in <https://github.com/l5yth/potato-mesh/pull/42>
* Fix string/integer comparison in node query by @l5yth in <https://github.com/l5yth/potato-mesh/pull/40>
* Escape chat message and node entries by @l5yth in <https://github.com/l5yth/potato-mesh/pull/39>
* Sort chat entries by timestamp by @l5yth in <https://github.com/l5yth/potato-mesh/pull/38>
* Feat: append messages to chat log by @l5yth in <https://github.com/l5yth/potato-mesh/pull/36>
* Normalize future timestamps for nodes by @l5yth in <https://github.com/l5yth/potato-mesh/pull/35>
* Optimize web frontend and Ruby app by @l5yth in <https://github.com/l5yth/potato-mesh/pull/32>
* Add messages API endpoint with node details by @l5yth in <https://github.com/l5yth/potato-mesh/pull/33>
* Clamp node timestamps and sync last_heard with position time by @l5yth in <https://github.com/l5yth/potato-mesh/pull/31>
* Refactor: replace deprecated utcfromtimestamp by @l5yth in <https://github.com/l5yth/potato-mesh/pull/30>
* Add optional debug logging for node and message operations by @l5yth in <https://github.com/l5yth/potato-mesh/pull/29>
* Data: enable serial collection of messages on channel 0 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/25>
* Add first_heard timestamp by @l5yth in <https://github.com/l5yth/potato-mesh/pull/23>
* Add persistent footer with contact information by @l5yth in <https://github.com/l5yth/potato-mesh/pull/22>
* Sort initial chat entries by last-heard by @l5yth in <https://github.com/l5yth/potato-mesh/pull/20>
* Display position time in relative 'time ago' format by @l5yth in <https://github.com/l5yth/potato-mesh/pull/19>
* Adjust marker size and map tile opacity by @l5yth in <https://github.com/l5yth/potato-mesh/pull/18>
* Add chat box for node notifications by @l5yth in <https://github.com/l5yth/potato-mesh/pull/17>
* Color markers by role with grayscale map by @l5yth in <https://github.com/l5yth/potato-mesh/pull/16>
* Default missing node role to client by @l5yth in <https://github.com/l5yth/potato-mesh/pull/15>
* Show live node count in nodes page titles by @l5yth in <https://github.com/l5yth/potato-mesh/pull/14>
* Filter stale nodes and add live search by @l5yth in <https://github.com/l5yth/potato-mesh/pull/13>
* Remove raw node JSON column by @l5yth in <https://github.com/l5yth/potato-mesh/pull/12>
* Add JSON ingest API for node updates by @l5yth in <https://github.com/l5yth/potato-mesh/pull/11>
* Ignore Python __pycache__ directories by @l5yth in <https://github.com/l5yth/potato-mesh/pull/10>
* Feat: load nodes from json for tests by @l5yth in <https://github.com/l5yth/potato-mesh/pull/8>
* Handle dataclass fields in node snapshots by @l5yth in <https://github.com/l5yth/potato-mesh/pull/6>
* Add index page and /nodes route for node map by @l5yth in <https://github.com/l5yth/potato-mesh/pull/4>

113
DOCKER.md Normal file
View File

@@ -0,0 +1,113 @@
# PotatoMesh Docker Guide
PotatoMesh publishes ready-to-run container images to the GitHub Packages container
registry (GHCR). You do not need to clone the repository to deploy them—Compose
will pull the latest release images for you.
## Prerequisites
- Docker Engine 24+ or Docker Desktop with the Compose plugin
- Access to `/dev/ttyACM*` (or equivalent) if you plan to attach a Meshtastic
device to the ingestor container
- An API token that authorises the ingestor to post to your PotatoMesh instance
## Images on GHCR
| Service | Image |
|----------|---------------------------------------------------------------------------------------------------------------|
| Web UI | `ghcr.io/l5yth/potato-mesh-web-linux-amd64:<tag>` (e.g. `latest`, `3.0`, `v3.0`, or `3.1.0-rc1`) |
| Ingestor | `ghcr.io/l5yth/potato-mesh-ingestor-linux-amd64:<tag>` (e.g. `latest`, `3.0`, `v3.0`, or `3.1.0-rc1`) |
Images are published for every tagged release. Stable builds receive both
semantic version tags (for example `3.0`) and a matching `v`-prefixed tag (for
example `v3.0`), plus a `latest` tag that tracks the newest stable release.
Pre-release tags (for example `-rc`, `-beta`, `-alpha`, or `-dev` suffixes) are
published only with their explicit version strings (`3.1.0-rc1` and `v3.1.0-rc1`
in this example) and do **not** advance `latest`. Pin the versioned tags when
you need a specific build.
## Configure environment
Create a `.env` file alongside your Compose file and populate the variables you
need. At a minimum you must set `API_TOKEN` so the ingestor can authenticate
against the web API.
```env
API_TOKEN=replace-with-a-strong-token
SITE_NAME=PotatoMesh Demo
CONNECTION=/dev/ttyACM0
INSTANCE_DOMAIN=mesh.example.org
```
Additional environment variables are optional:
| Variable | Default | Purpose |
| --- | --- | --- |
| `API_TOKEN` | _required_ | Shared secret used by the ingestor and API clients for authenticated `POST` requests. |
| `INSTANCE_DOMAIN` | _auto-detected_ | Public hostname (optionally with port) advertised by the web UI, metadata, and API responses. |
| `SITE_NAME` | `"PotatoMesh Demo"` | Title and branding surfaced in the web UI. |
| `CHANNEL` | `"#LongFast"` | Default LoRa channel label displayed on the dashboard. |
| `FREQUENCY` | `"915MHz"` | Default LoRa frequency description shown in the UI. |
| `CONTACT_LINK` | `"#potatomesh:dod.ngo"` | Chat link or Matrix room alias rendered in UI footers and overlays. |
| `MAP_CENTER` | `38.761944,-27.090833` | Latitude and longitude that centre the map view. |
| `MAP_ZOOM` | _unset_ | Fixed Leaflet zoom (disables the auto-fit checkbox when set). |
| `MAX_DISTANCE` | `42` | Maximum relationship distance (km) before edges are hidden. |
| `DEBUG` | `0` | Enables verbose logging across services when set to `1`. |
| `ALLOWED_CHANNELS` | _unset_ | Comma-separated channel names the ingestor accepts; other channels are skipped before hidden filters. |
| `HIDDEN_CHANNELS` | _unset_ | Comma-separated channel names the ingestor skips when forwarding packets. |
| `FEDERATION` | `1` | Controls whether the instance announces itself and crawls peers (`1`) or stays isolated (`0`). |
| `PRIVATE` | `0` | Restricts public visibility and disables chat/message endpoints when set to `1`. |
| `CONNECTION` | `/dev/ttyACM0` | Serial device, TCP endpoint, or Bluetooth target used by the ingestor to reach the radio. |
The ingestor posts to the URL configured via `INSTANCE_DOMAIN` (defaulting to
`http://web:41447` in the provided compose file) and still accepts
`POTATOMESH_INSTANCE` as a legacy alias when the primary variable is unset. Use
`CHANNEL_INDEX` to select a LoRa channel on serial or Bluetooth connections.
## Docker Compose file
Use the `docker-compose.yml` file provided in the repository (or download the
[raw file from GitHub](https://raw.githubusercontent.com/l5yth/potato-mesh/main/docker-compose.yml)).
It already references the published GHCR images, defines persistent volumes for
data, configuration, and logs, and includes optional bridge-profile services for
environments that require classic port mapping. Place this file in the same
directory as your `.env` file so Compose can pick up both.
The dedicated configuration volume binds to `/app/.config/potato-mesh` inside
the container. This path stores the instance private key and staged
`/.well-known/potato-mesh` documents. Because the volume persists independently
of container lifecycle events, generated credentials are not replaced on reboot
or re-deploy.
## Start the stack
From the directory containing the Compose file:
```bash
docker compose up -d
```
Docker automatically pulls the GHCR images when they are not present locally.
The dashboard becomes available at `http://127.0.0.1:41447`. Use the bridge
profile when you need to map the port explicitly:
```bash
COMPOSE_PROFILES=bridge docker compose up -d
```
## Updating
```bash
docker compose pull
docker compose up -d
```
## Troubleshooting
- **Serial device permissions (Linux/macOS):** grant access with `sudo chmod 666
/dev/ttyACM0` or add your user to the `dialout` group.
- **Port already in use:** identify the conflicting service with `sudo lsof -i
:41447`.
- **Viewing logs:** `docker compose logs -f` tails output from both services.
For general Docker support, consult the [Docker Compose documentation](https://docs.docker.com/compose/).

93
Dockerfile Normal file
View File

@@ -0,0 +1,93 @@
# Copyright © 2025-26 l5yth & contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# NOTE: This Dockerfile is kept for backward compatibility. The canonical build
# instructions live in `web/Dockerfile`; keep the two files in sync.
# Main application builder stage
FROM ruby:3.3-alpine AS builder
# Ensure native extensions are built against musl libc rather than
# using glibc precompiled binaries (which fail on Alpine).
ENV BUNDLE_FORCE_RUBY_PLATFORM=true
# Install build dependencies and SQLite3
RUN apk add --no-cache \
build-base \
sqlite-dev \
linux-headers \
pkgconfig
# Set working directory
WORKDIR /app
# Copy Gemfile and install dependencies
COPY web/Gemfile web/Gemfile.lock* ./
# Install gems with SQLite3 support
RUN bundle config set --local force_ruby_platform true && \
bundle config set --local without 'development test' && \
bundle install --jobs=4 --retry=3
# Production stage
FROM ruby:3.3-alpine AS production
# Install runtime dependencies
RUN apk add --no-cache \
sqlite \
tzdata \
curl
# Create non-root user
RUN addgroup -g 1000 -S potatomesh && \
adduser -u 1000 -S potatomesh -G potatomesh
# Set working directory
WORKDIR /app
# Copy installed gems from builder stage
COPY --from=builder /usr/local/bundle /usr/local/bundle
# Copy application code (exclude Dockerfile from web directory)
COPY --chown=potatomesh:potatomesh web/app.rb web/app.sh web/Gemfile web/Gemfile.lock* web/spec/ ./
COPY --chown=potatomesh:potatomesh web/public ./public
COPY --chown=potatomesh:potatomesh web/views/ ./views/
# Copy SQL schema files from data directory
COPY --chown=potatomesh:potatomesh data/*.sql /data/
# Create data directory for SQLite database
RUN mkdir -p /app/data /app/.local/share/potato-mesh && \
chown -R potatomesh:potatomesh /app/data /app/.local
# Switch to non-root user
USER potatomesh
# Expose port
EXPOSE 41447
# Default environment variables (can be overridden by host)
ENV APP_ENV=production \
RACK_ENV=production \
SITE_NAME="PotatoMesh Demo" \
INSTANCE_DOMAIN="potato.example.com" \
CHANNEL="#LongFast" \
FREQUENCY="915MHz" \
MAP_CENTER="38.761944,-27.090833" \
MAX_DISTANCE=42 \
CONTACT_LINK="#potatomesh:dod.ngo" \
DEBUG=0
# Start the application
CMD ["ruby", "app.rb", "-p", "41447", "-o", "0.0.0.0"]

View File

@@ -33,7 +33,7 @@
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
Object form, made available under the Licen2se, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright (C) 2025 l5yth
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

100
PROMETHEUS.md Normal file
View File

@@ -0,0 +1,100 @@
# Prometheus Monitoring for PotatoMesh
PotatoMesh exposes runtime telemetry through a dedicated Prometheus endpoint so you can
observe message flow, node health, and geospatial metadata alongside the rest of your
infrastructure. This guide explains how the exporter is wired into the web
application, which metrics are available, and how to integrate the endpoint with a
Prometheus server.
## Runtime integration
The Sinatra application automatically loads the `prometheus-client` gem and mounts the
collector and exporter middlewares during boot. No additional configuration is
required to enable the `/metrics` endpoint—running the web application is enough to
serve Prometheus data on the same port as the dashboard. The middleware pair both
collects default Rack statistics and publishes PotatoMesh-specific gauges and
counters that are updated whenever the ingestors process new node records.
A background refresh is triggered during start-up via
`update_all_prometheus_metrics_from_nodes`, which seeds the gauges based on the latest
state in the database. Subsequent POST requests to the ingest APIs update each metric
in near real time.
## Selecting which nodes are exported
To avoid creating high-cardinality time series, PotatoMesh does not export per-node
metrics unless you opt in by providing node identifiers. Control this behaviour with
the `PROM_REPORT_IDS` environment variable:
- Leave the variable unset or blank to only export aggregate gauges such as the total
node count.
- Set `PROM_REPORT_IDS=*` to export metrics for every node in the database.
- Provide a comma-separated list (for example `PROM_REPORT_IDS=ABCD1234,EFGH5678`) to
expose metrics for specific nodes.
The selection applies to both the initial refresh and the incremental updates handled
by the ingest pipeline.
## Available metrics
| Metric name | Type | Labels | Description |
| --- | --- | --- | --- |
| `meshtastic_messages_total` | Counter | _none_ | Increments each time the ingest pipeline accepts a new message payload. |
| `meshtastic_nodes` | Gauge | _none_ | Tracks the number of nodes currently stored in the database. |
| `meshtastic_node` | Gauge | `node`, `short_name`, `long_name`, `hw_model`, `role` | Reports a node as present (value `1`) along with identity metadata. |
| `meshtastic_node_battery_level` | Gauge | `node` | Most recent battery percentage reported by the node. |
| `meshtastic_node_voltage` | Gauge | `node` | Most recent battery voltage reading. |
| `meshtastic_node_uptime_seconds` | Gauge | `node` | Uptime reported by the device in seconds. |
| `meshtastic_node_channel_utilization` | Gauge | `node` | Latest channel utilisation ratio supplied by the node. |
| `meshtastic_node_transmit_air_utilization` | Gauge | `node` | Proportion of on-air time spent transmitting. |
| `meshtastic_node_latitude` | Gauge | `node` | Latitude component of the last known position. |
| `meshtastic_node_longitude` | Gauge | `node` | Longitude component of the last known position. |
| `meshtastic_node_altitude` | Gauge | `node` | Altitude (in metres) of the last known position. |
All per-node gauges are only emitted for identifiers included in `PROM_REPORT_IDS`.
Some values require telemetry packets to be present—for example, devices must provide
metrics or positional updates before the related gauges appear.
## Accessing the `/metrics` endpoint
Once the application is running, query the exporter directly:
```bash
curl http://localhost:41447/metrics
```
Use any HTTP client capable of plain-text requests. Prometheus scrapers should target
the same URL. The endpoint returns data in the standard exposition format produced by
`prometheus-client`.
## Prometheus scrape configuration
Add a job to your Prometheus server configuration that points to the PotatoMesh
instance. This example polls an instance running locally on the default port every 15
seconds:
```yaml
scrape_configs:
- job_name: potatomesh
scrape_interval: 15s
static_configs:
- targets:
- localhost:41447
```
If your deployment requires authentication or runs behind a reverse proxy, configure
Prometheus to match your network topology (for example by adding basic authentication
credentials, custom headers, or TLS settings).
## Troubleshooting
- **No per-node metrics appear.** Ensure that `PROM_REPORT_IDS` is set and that the
specified nodes exist in the database. Set the value to `*` if you want to export
every node during initial validation.
- **Metrics look stale after a restart.** Confirm that the ingestor is still posting
telemetry. The exporter only reflects data stored in the PotatoMesh database.
- **Scrapes time out.** Verify that the Prometheus server can reach the PotatoMesh
HTTP port and that no reverse proxy is blocking the `/metrics` path.
With the endpoint configured, you can build Grafana dashboards or alerting rules to
keep track of community mesh health in real time.

344
README.md
View File

@@ -1,70 +1,46 @@
# potato-mesh
# 🥔 PotatoMesh
a simple meshtastic node dashboard for your local community. here: berlin mediumfast.
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/l5yth/potato-mesh/ruby.yml?branch=main)](https://github.com/l5yth/potato-mesh/actions)
[![GitHub release](https://img.shields.io/github/v/release/l5yth/potato-mesh)](https://github.com/l5yth/potato-mesh/releases)
[![codecov](https://codecov.io/gh/l5yth/potato-mesh/branch/main/graph/badge.svg?token=FS7252JVZT)](https://codecov.io/gh/l5yth/potato-mesh)
[![Open-Source License](https://img.shields.io/github/license/l5yth/potato-mesh)](LICENSE)
[![Contributions Welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/l5yth/potato-mesh/issues)
[![Matrix Chat](https://img.shields.io/badge/matrix-%23potatomesh:dod.ngo-blue)](https://matrix.to/#/#potatomesh:dod.ngo)
![screenshot of the first version](./scrot-0.1.png)
A federated, Meshtastic-powered node dashboard for your local community.
_No MQTT clutter, just local LoRa aether._
## status
* Web dashboard with chat window and map view showing nodes, positions, neighbors,
trace routes, telemetry, and messages.
* API to POST (authenticated) and to GET nodes, messages, and telemetry.
* Shows new node notifications (first seen) and telemetry logs in chat.
* Allows searching and filtering for nodes in map and table view.
* Federated: _automatically_ froms a federation with other communities running
Potato Mesh!
* Supplemental Python ingestor to feed the POST APIs of the Web app with data remotely.
* Supports multiple ingestors per instance.
* Matrix bridge that posts Meshtastic messages to a defined matrix channel (no
radio required).
* Mobile app to _read_ messages on your local aether (no radio required).
_in active development._
Live demo for Berlin #MediumFast: [potatomesh.net](https://potatomesh.net)
what works:
![screenshot of the fourth version](./scrot-0.4.png)
* updating nodes from a locally connected meshtastic device (via serial)
* awaiting messages on default channel (0) from a local meshtastic device
* storing nodes and messages in a local database (sqlite3)
* displaying nodes ordered by last seen in a web app table view
* displaying nodes by geographic coordinates on a map layer, coloured by device role
* displaying new node notifications and chat messages in default channel in chat box
* displaying active node count and filtering nodes by name
* exposing nodes and messages to api endpoints
## Web App
what does not work _(yet):_
* posting nodes and messages to the api endpoints _(wip)_
## requirements
requires a meshtastic node connected (via serial) to gather mesh data and the meshtastic cli.
requires the meshtastic python api for the database.
Requires Ruby for the Sinatra web app and SQLite3 for the app's database.
```bash
python -m venv .venv
source .venv/bin/activate
pip install -U meshtastic
```
requires latest ruby and ruby gems for the sinatra web app.
```bash
gem install bundler
pacman -S ruby sqlite3
gem install sinatra sqlite3 rackup puma rspec rack-test rufo prometheus-client
cd ./web
bundle install
```
### database
### Run
uses python meshtastic library to ingest mesh data into an sqlite3 database locally
run `mesh.sh` in `data/` to keep updating node records and parsing new incoming messages.
```bash
MESH_SERIAL=/dev/ttyACM0 DEBUG=1 ./mesh.sh
[...]
[debug] upserted node !849b7154 shortName='7154'
[debug] upserted node !ba653ae8 shortName='3ae8'
[debug] upserted node !16ced364 shortName='Pat'
[debug] stored message from '!9ee71c38' to '^all' ch=0 text='Guten Morgen!'
```
enable debug output with `DEBUG=1`, specify the serial port with `MESH_SERIAL` (default `/dev/ttyACM0`).
### web app
uses a ruby sinatra webapp to display data from the sqlite database
run `app.sh` in `web/` to run the sinatra webserver and check
[127.0.0.1:41447](http://127.0.0.1:41447/) for the correct node map.
Check out the `app.sh` run script in `./web` directory.
```bash
API_TOKEN="1eb140fd-cab4-40be-b862-41c607762246" ./app.sh
@@ -76,17 +52,261 @@ Puma starting in single mode...
* Listening on http://127.0.0.1:41447
```
set `API_TOKEN` required for authorizations on the api post-endpoints (wip).
Check [127.0.0.1:41447](http://127.0.0.1:41447/) for the development preview
of the node map. Set `API_TOKEN` required for authorizations on the API's POST endpoints.
## api
### Production
the web app contains an api:
When promoting the app to production, run the server with the minimum required
configuration to ensure secure access and proper routing:
* GET `/api/nodes?limit=1000` - returns the latest 1000 nodes reported to the app
* GET `/api/messages?limit=1000` - returns the latest 1000 messages
```bash
RACK_ENV="production" \
APP_ENV="production" \
API_TOKEN="SuperSecureTokenReally" \
INSTANCE_DOMAIN="https://potatomesh.net" \
MAP_CENTER="53.55,13.42" \
exec ruby app.rb -p 41447 -o 0.0.0.0
```
the `POST` apis are _currently being worked on (tm)._
* `RACK_ENV` and `APP_ENV` must be set to `production` to enable optimized
settings suited for live deployments.
* Bind the server to a production port and all interfaces (`-p 41447 -o 0.0.0.0`)
so that clients can reach the dashboard over the network.
* Provide a strong `API_TOKEN` value to authorize POST requests against the API.
* Configure `INSTANCE_DOMAIN` with the public URL of your deployment so vanity
links and generated metadata resolve correctly.
* Don't forget to set a `MAP_CENTER` to point to your local region.
## license
The web app can be configured with environment variables (defaults shown):
apache v2.0
| Variable | Default | Purpose |
| --- | --- | --- |
| `API_TOKEN` | _required_ | Shared secret that authorizes ingestors and API clients making `POST` requests. |
| `INSTANCE_DOMAIN` | _auto-detected_ | Public hostname (optionally with port) used for metadata, federation, and generated API links. |
| `SITE_NAME` | `"PotatoMesh Demo"` | Title and header displayed in the UI. |
| `CHANNEL` | `"#LongFast"` | Default channel name displayed in the UI. |
| `FREQUENCY` | `"915MHz"` | Default frequency description displayed in the UI. |
| `CONTACT_LINK` | `"#potatomesh:dod.ngo"` | Chat link or Matrix alias rendered in the footer and overlays. |
| `ANNOUNCEMENT` | _unset_ | Optional announcement banner text rendered above the header on every page. |
| `MAP_CENTER` | `38.761944,-27.090833` | Latitude and longitude that centre the map on load. |
| `MAP_ZOOM` | _unset_ | Fixed Leaflet zoom applied on first load; disables auto-fit when provided. |
| `MAX_DISTANCE` | `42` | Maximum distance (km) before node relationships are hidden on the map. |
| `DEBUG` | `0` | Set to `1` for verbose logging in the web and ingestor services. |
| `ALLOWED_CHANNELS` | _unset_ | Comma-separated channel names the ingestor accepts; when set, all other channels are skipped before hidden filters. |
| `HIDDEN_CHANNELS` | _unset_ | Comma-separated channel names the ingestor will ignore when forwarding packets. |
| `FEDERATION` | `1` | Set to `1` to announce your instance and crawl peers, or `0` to disable federation. Private mode overrides this. |
| `PRIVATE` | `0` | Set to `1` to hide the chat UI, disable message APIs, and exclude hidden clients from public listings. |
The application derives SEO-friendly document titles, descriptions, and social
preview tags from these existing configuration values and reuses the bundled
logo for Open Graph and Twitter cards.
Example:
```bash
SITE_NAME="PotatoMesh Demo" MAP_CENTER=38.761944,-27.090833 MAP_ZOOM=11 MAX_DISTANCE=42 CONTACT_LINK="#potatomesh:dod.ngo" ./app.sh
```
### Configuration & Storage
PotatoMesh stores its runtime assets using the XDG base directory specification.
When XDG directories are not provided the application falls back
to the repository root.
The key is written to `$XDG_CONFIG_HOME/potato-mesh/keyfile` and the
well-known document is staged in
`$XDG_CONFIG_HOME/potato-mesh/well-known/potato-mesh`.
The database can be found in `$XDG_DATA_HOME/potato-mesh`.
### Federation
PotatoMesh instances can optionally federate by publishing signed metadata and
discovering peers. Federation is enabled by default and controlled with the
`FEDERATION` environment variable. Set `FEDERATION=1` (default) to announce your
instance, respond to remote crawlers, and crawl the wider network. Set
`FEDERATION=0` to keep your deployment isolated. Private mode still takes
precedence; when `PRIVATE=1`, federation features remain disabled regardless of
the `FEDERATION` value.
When federation is enabled, PotatoMesh automatically refreshes entries from
known peers every eight hours to keep the directory current. Instances that
stop responding are considered stale and are removed from the web frontend after
72 hours, ensuring visitors only see active deployments in the public
directory.
### API
The web app contains an API:
* GET `/api/nodes?limit=100` - returns the latest 100 nodes reported to the app
* GET `/api/positions?limit=100` - returns the latest 100 position data
* GET `/api/messages?limit=100&encrypted=false&since=0` - returns the latest 100 messages newer than the provided unix timestamp (defaults to `since=0` to return full history; disabled when `PRIVATE=1`)
* GET `/api/telemetry?limit=100` - returns the latest 100 telemetry data
* GET `/api/neighbors?limit=100` - returns the latest 100 neighbor tuples
* GET `/api/traces?limit=100` - returns the latest 100 trace-routes caught
* GET `/api/instances` - returns known potato-mesh instances in other locations
* GET `/api/ingestors` - returns active potato-mesh python ingestors that feed data
* GET `/metrics`- metrics for the prometheus endpoint
* GET `/version`- information about the potato-mesh instance
* POST `/api/nodes` - upserts nodes provided as JSON object mapping node ids to node data (requires `Authorization: Bearer <API_TOKEN>`)
* POST `/api/positions` - appends positions provided as a JSON object or array (requires `Authorization: Bearer <API_TOKEN>`)
* POST `/api/messages` - appends messages provided as a JSON object or array (requires `Authorization: Bearer <API_TOKEN>`; disabled when `PRIVATE=1`)
* POST `/api/telemetry` - appends telemetry provided as a JSON object or array (requires `Authorization: Bearer <API_TOKEN>`)
* POST `/api/neighbors` - appends neighbor tuples provided as a JSON object or array (requires `Authorization: Bearer <API_TOKEN>`)
* POST `/api/traces` - appends caught traces routes provided as a JSON object or array (requires `Authorization: Bearer <API_TOKEN>`)
The `API_TOKEN` environment variable must be set to a non-empty value and match the token supplied in the `Authorization` header for `POST` requests.
### Monitoring
PotatoMesh ships with a Prometheus exporter mounted at `/metrics`. Consult
[`PROMETHEUS.md`](./PROMETHEUS.md) for deployment guidance, metric details, and
scrape configuration examples.
## Python Ingestor
The web app is not meant to be run locally connected to a Meshtastic node but rather
on a remote host without access to a physical Meshtastic device. Therefore, it only
accepts data through the API POST endpoints. Benefit is, here _multiple nodes across the
community_ can feed the dashboard with data. The web app handles messages and nodes
by ID and there will be no duplication.
For convenience, the directory `./data` contains a Python ingestor. It connects to a
Meshtastic node via serial port or to a remote device that exposes the Meshtastic TCP
or Bluetooth (BLE) interfaces to gather nodes and messages seen by the node.
```bash
pacman -S python
cd ./data
python -m venv .venv
source .venv/bin/activate
pip install -U meshtastic
```
It uses the Meshtastic Python library to ingest mesh data and post nodes and messages
to the configured potato-mesh instance.
Check out `mesh.sh` ingestor script in the `./data` directory.
```bash
INSTANCE_DOMAIN=http://127.0.0.1:41447 API_TOKEN=1eb140fd-cab4-40be-b862-41c607762246 CONNECTION=/dev/ttyACM0 DEBUG=1 ./mesh.sh
[2025-02-20T12:34:56.789012Z] [potato-mesh] [info] channel=0 context=daemon.main port='41447' target='http://127.0.0.1' Mesh daemon starting
[...]
[2025-02-20T12:34:57.012345Z] [potato-mesh] [debug] context=handlers.upsert_node node_id=!849b7154 short_name='7154' long_name='7154' Queued node upsert payload
[2025-02-20T12:34:57.456789Z] [potato-mesh] [debug] context=handlers.upsert_node node_id=!ba653ae8 short_name='3ae8' long_name='3ae8' Queued node upsert payload
[2025-02-20T12:34:58.001122Z] [potato-mesh] [debug] context=handlers.store_packet_dict channel=0 from_id='!9ee71c38' payload='Guten Morgen!' to_id='^all' Queued message payload
```
Run the script with `INSTANCE_DOMAIN` and `API_TOKEN` to keep updating
node records and parsing new incoming messages. Enable debug output with `DEBUG=1`,
specify the connection target with `CONNECTION` (default `/dev/ttyACM0`) or set it to
an IP address (for example `192.168.1.20:4403`) to use the Meshtastic TCP
interface. `CONNECTION` also accepts Bluetooth device addresses in MAC format (e.g.,
`ED:4D:9E:95:CF:60`) or UUID format for macOS (e.g., `C0AEA92F-045E-9B82-C9A6-A1FD822B3A9E`)
and the script attempts a BLE connection if available. To keep
ingestion limited, set `ALLOWED_CHANNELS` to a comma-separated whitelist (for
example `ALLOWED_CHANNELS="Chat,Ops"`); packets on other channels are discarded.
Use `HIDDEN_CHANNELS` to block specific channels from the web UI even when they
appear in the allowlist.
## Nix
For the dev shell, run:
```bash
nix develop
```
The shell provides Ruby plus the Python ingestor dependencies (including `meshtastic`
and `protobuf`). To sanity-check that the ingestor starts, run `python -m data.mesh`
with the usual environment variables (`INSTANCE_DOMAIN`, `API_TOKEN`, `CONNECTION`).
To run the packaged apps directly:
```bash
nix run .#web
nix run .#ingestor
```
Minimal NixOS module snippet:
```nix
services.potato-mesh = {
enable = true;
apiTokenFile = config.sops.secrets.potato-mesh-api-token.path;
dataDir = "/var/lib/potato-mesh";
port = 41447;
instanceDomain = "https://mesh.me";
siteName = "Nix Mesh";
contactLink = "homeserver.mx";
mapCenter = "28.96,-13.56";
frequency = "868MHz";
ingestor = {
enable = true;
connection = "192.168.X.Y:4403";
};
};
```
## Docker
Docker images are published on GitHub Container Registry for each release.
Image names and tags follow the workflow format:
`${IMAGE_PREFIX}-${service}-${architecture}:${tag}` (see `.github/workflows/docker.yml`).
```bash
docker pull ghcr.io/l5yth/potato-mesh-web-linux-amd64:latest
docker pull ghcr.io/l5yth/potato-mesh-web-linux-arm64:latest
docker pull ghcr.io/l5yth/potato-mesh-web-linux-armv7:latest
docker pull ghcr.io/l5yth/potato-mesh-ingestor-linux-amd64:latest
docker pull ghcr.io/l5yth/potato-mesh-ingestor-linux-arm64:latest
docker pull ghcr.io/l5yth/potato-mesh-ingestor-linux-armv7:latest
docker pull ghcr.io/l5yth/potato-mesh-matrix-bridge-linux-amd64:latest
docker pull ghcr.io/l5yth/potato-mesh-matrix-bridge-linux-arm64:latest
docker pull ghcr.io/l5yth/potato-mesh-matrix-bridge-linux-armv7:latest
# version-pinned examples
docker pull ghcr.io/l5yth/potato-mesh-web-linux-amd64:v0.5.5
docker pull ghcr.io/l5yth/potato-mesh-ingestor-linux-amd64:v0.5.5
docker pull ghcr.io/l5yth/potato-mesh-matrix-bridge-linux-amd64:v0.5.5
```
Note: `latest` is only published for non-prerelease versions. Pre-release tags
such as `-rc`, `-beta`, `-alpha`, or `-dev` are version-tagged only.
When using Compose, set `POTATOMESH_IMAGE_ARCH` in `docker-compose.yml` (or via
environment) so service images resolve to the correct architecture variant and
you avoid manual tag mistakes.
Feel free to run the [configure.sh](./configure.sh) script to set up your
environment. See the [Docker guide](DOCKER.md) for more details and custom
deployment instructions.
## Matrix Bridge
A matrix bridge is currently being worked on. It requests messages from a configured
potato-mesh instance and forwards it to a specified matrix channel; see
[matrix/README.md](./matrix/README.md).
![matrix bridge](./scrot-0.6.png)
## Mobile App
A mobile _reader_ app is currently being worked on. Stay tuned for releases and updates.
## Demos
Post your nodes and screenshots here:
* <https://github.com/l5yth/potato-mesh/discussions/258>
## License
Apache v2.0, Contact <COM0@l5y.tech>
Join our community chat to discuss the dashboard or ask for technical support:
[#potatomesh:dod.ngo](https://matrix.to/#/#potatomesh:dod.ngo)

29
app/.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
# FVM Version Cache
.fvm/
# Scaffholding
.dart_tool/
.flutter-plugins-dependencies
.fvmrc
.idea/
.metadata
linux/
macos/
potato_mesh_reader.iml
pubspec.lock
web/
windows/
# Android/Gradle outputs
android/.gradle/
android/app/build/
# iOS/Xcode outputs
ios/Pods/
ios/.symlinks/
ios/Flutter/ephemeral/
ios/Flutter/App.framework
ios/Flutter/Flutter.framework
ios/Flutter/flutter_export_environment.sh
ios/Flutter/Generated.xcconfig
ios/Runner/GeneratedPluginRegistrant.*

12
app/README.md Normal file
View File

@@ -0,0 +1,12 @@
# Meshtastic Reader
Meshtastic Reader read-only PotatoMesh chat client for Android and iOS.
## Setup
```bash
cd app
flutter create .
flutter pub get
flutter run
```

28
app/analysis_options.yaml Normal file
View File

@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

11
app/android/.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
/.gradle
/captures/
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@@ -0,0 +1,64 @@
/*
* Copyright © 2025-26 l5yth & contributors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "net.potatomesh.reader"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "net.potatomesh.reader"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,49 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.potatomesh.reader">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:label="PotatoMesh Reader"
android:icon="@mipmap/ic_launcher">
<activity
android:name="net.potatomesh.reader.MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,18 @@
// Copyright © 2025-26 l5yth & contributors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package net.potatomesh.reader
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright © 2025-26 l5yth & contributors -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="256"
android:viewportHeight="256">
<path
android:fillColor="#000000"
android:pathData="M 126.00 231.38 C116.24,230.24 108.69,228.55 97.57,225.02 C89.71,222.53 81.61,220.73 74.92,220.01 C56.54,218.03 47.54,215.19 35.90,207.72 C25.90,201.31 16.09,188.73 11.28,176.17 C9.05,170.34 8.73,167.93 8.69,157.00 C8.66,146.56 9.01,143.52 10.83,138.58 C13.71,130.75 16.90,125.44 22.99,118.35 C25.75,115.13 30.06,108.55 32.57,103.72 C39.65,90.08 52.73,77.41 61.66,75.54 C63.80,75.09 65.00,74.16 65.38,72.61 C66.30,68.96 73.33,61.70 79.81,57.70 C83.11,55.66 86.29,54.00 86.90,54.00 C87.50,54.00 91.26,50.90 95.25,47.11 C107.07,35.89 118.95,31.10 131.78,32.42 C139.49,33.21 147.99,37.24 153.00,42.48 C154.93,44.50 160.84,48.38 166.15,51.12 C175.72,56.04 185.00,64.04 185.00,67.35 C185.00,68.84 187.05,70.00 189.69,70.00 C190.52,70.00 193.21,71.35 195.66,73.00 C198.10,74.65 200.30,76.00 200.55,76.00 C201.87,76.00 209.57,85.37 213.05,91.22 C215.39,95.15 221.15,102.01 226.90,107.72 C237.44,118.20 241.06,123.94 244.87,136.29 C252.34,160.53 244.67,187.19 225.29,204.27 C215.26,213.12 206.19,217.02 189.50,219.66 C184.00,220.54 174.32,222.95 168.00,225.04 C152.19,230.25 136.54,232.62 126.00,231.38 ZM 149.23 220.54 C152.93,219.75 160.90,217.52 166.93,215.57 C172.97,213.61 182.16,211.33 187.36,210.50 C192.56,209.66 198.70,208.31 200.99,207.50 C219.04,201.13 232.11,186.69 236.54,168.21 C239.68,155.13 237.23,137.60 230.79,127.15 C229.56,125.14 224.01,118.78 218.45,113.00 C212.70,107.01 206.60,99.49 204.27,95.50 C200.27,88.66 197.03,85.00 192.03,81.65 C188.54,79.32 187.14,79.59 186.36,82.75 C184.35,90.89 178.61,95.54 166.19,99.09 C162.70,100.09 157.74,102.15 155.17,103.68 C142.50,111.19 133.13,113.70 123.94,112.03 C120.95,111.49 114.12,108.78 108.77,106.02 C100.44,101.72 97.99,100.92 91.77,100.48 C81.44,99.74 78.08,99.08 74.05,97.01 C69.74,94.80 66.49,91.44 65.08,87.73 C64.04,84.97 64.02,84.96 60.27,86.48 C54.59,88.78 45.89,98.56 40.58,108.65 C38.01,113.52 33.29,120.79 30.09,124.80 C22.80,133.95 19.57,140.55 17.99,149.58 C14.84,167.46 24.86,188.52 41.92,199.87 C51.75,206.40 57.70,208.37 74.36,210.57 C80.89,211.43 90.12,213.30 94.86,214.73 C115.79,221.03 120.20,221.90 131.50,221.93 C137.55,221.95 145.53,221.32 149.23,220.54 ZM 136.76 102.49 C140.36,101.65 145.79,99.27 149.54,96.90 C153.18,94.60 159.03,92.02 162.79,91.05 C170.97,88.95 173.22,87.80 175.92,84.37 C178.38,81.24 178.67,75.20 176.55,71.10 C174.28,66.71 168.40,62.13 160.26,58.42 C155.62,56.31 150.53,52.93 146.85,49.52 C133.35,37.02 118.65,38.50 101.56,54.08 C97.39,57.87 91.17,62.38 87.74,64.10 C77.86,69.03 73.00,74.56 73.00,80.87 C73.00,88.43 77.15,90.86 91.91,91.95 C102.41,92.73 103.22,92.96 111.55,97.50 C124.46,104.54 126.44,104.93 136.76,102.49 ZM 88.35 189.48 C87.37,186.91 91.59,125.01 92.84,123.76 C94.47,122.13 105.29,121.55 107.91,122.95 C109.31,123.70 113.25,130.52 118.80,141.81 L 127.51 159.50 L 136.00 142.15 C140.68,132.61 145.09,124.17 145.82,123.40 C146.71,122.46 149.41,122.00 154.00,122.00 C161.67,122.00 163.99,123.34 164.02,127.82 C164.03,129.29 164.70,140.18 165.50,152.00 C167.35,179.25 167.41,189.65 165.75,190.21 C165.06,190.44 161.57,190.38 158.00,190.07 L 151.50 189.50 L 150.36 170.00 C149.73,159.27 149.14,150.27 149.06,150.00 C148.98,149.73 145.22,156.70 140.71,165.50 L 132.50 181.50 L 127.50 181.50 L 122.50 181.50 L 114.25 165.08 C107.87,152.38 105.97,149.32 105.88,151.58 C105.82,153.19 105.81,154.81 105.86,155.18 C105.91,155.55 105.48,163.42 104.90,172.67 C104.23,183.39 103.43,189.74 102.68,190.22 C102.03,190.63 98.67,190.98 95.22,190.98 C90.51,191.00 88.79,190.62 88.35,189.48 ZM 61.61 182.42 C60.64,179.90 60.89,178.25 62.48,176.66 C65.63,173.51 69.97,177.37 68.50,182.00 C67.71,184.48 62.52,184.80 61.61,182.42 ZM 216.53 167.38 C215.64,166.50 215.07,165.29 215.26,164.71 C215.85,162.94 218.62,161.64 220.36,162.31 C222.46,163.11 222.50,167.60 220.42,168.39 C218.30,169.21 218.37,169.22 216.53,167.38 ZM 36.04 153.55 C33.39,150.36 37.30,145.34 40.42,147.93 C42.30,149.49 42.47,152.13 40.80,153.80 C39.20,155.40 37.51,155.31 36.04,153.55 ZM 201.16 148.12 C199.41,147.01 200.04,142.99 202.19,141.64 C203.48,140.83 204.37,141.02 205.94,142.45 C208.41,144.68 208.48,145.38 206.43,147.43 C204.67,149.18 203.15,149.38 201.16,148.12 ZM 67.34 137.43 C66.47,135.18 68.24,132.50 70.61,132.50 C73.61,132.50 73.27,138.39 70.25,138.82 C68.83,139.02 67.75,138.50 67.34,137.43 ZM 56.54 116.66 C55.17,115.65 54.88,114.62 55.39,112.59 C55.94,110.38 56.56,109.93 58.68,110.19 C62.53,110.65 63.88,113.37 61.55,115.94 C59.36,118.37 58.96,118.42 56.54,116.66 ZM 186.67 113.33 C185.21,111.88 186.09,108.02 188.12,106.93 C189.87,106.00 190.60,106.15 192.23,107.78 C195.00,110.56 193.82,114.00 190.10,114.00 C188.58,114.00 187.03,113.70 186.67,113.33 Z" />
</vector>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright © 2025-26 l5yth & contributors -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="256"
android:viewportHeight="256">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M 126.00 231.38 C116.24,230.24 108.69,228.55 97.57,225.02 C89.71,222.53 81.61,220.73 74.92,220.01 C56.54,218.03 47.54,215.19 35.90,207.72 C25.90,201.31 16.09,188.73 11.28,176.17 C9.05,170.34 8.73,167.93 8.69,157.00 C8.66,146.56 9.01,143.52 10.83,138.58 C13.71,130.75 16.90,125.44 22.99,118.35 C25.75,115.13 30.06,108.55 32.57,103.72 C39.65,90.08 52.73,77.41 61.66,75.54 C63.80,75.09 65.00,74.16 65.38,72.61 C66.30,68.96 73.33,61.70 79.81,57.70 C83.11,55.66 86.29,54.00 86.90,54.00 C87.50,54.00 91.26,50.90 95.25,47.11 C107.07,35.89 118.95,31.10 131.78,32.42 C139.49,33.21 147.99,37.24 153.00,42.48 C154.93,44.50 160.84,48.38 166.15,51.12 C175.72,56.04 185.00,64.04 185.00,67.35 C185.00,68.84 187.05,70.00 189.69,70.00 C190.52,70.00 193.21,71.35 195.66,73.00 C198.10,74.65 200.30,76.00 200.55,76.00 C201.87,76.00 209.57,85.37 213.05,91.22 C215.39,95.15 221.15,102.01 226.90,107.72 C237.44,118.20 241.06,123.94 244.87,136.29 C252.34,160.53 244.67,187.19 225.29,204.27 C215.26,213.12 206.19,217.02 189.50,219.66 C184.00,220.54 174.32,222.95 168.00,225.04 C152.19,230.25 136.54,232.62 126.00,231.38 ZM 149.23 220.54 C152.93,219.75 160.90,217.52 166.93,215.57 C172.97,213.61 182.16,211.33 187.36,210.50 C192.56,209.66 198.70,208.31 200.99,207.50 C219.04,201.13 232.11,186.69 236.54,168.21 C239.68,155.13 237.23,137.60 230.79,127.15 C229.56,125.14 224.01,118.78 218.45,113.00 C212.70,107.01 206.60,99.49 204.27,95.50 C200.27,88.66 197.03,85.00 192.03,81.65 C188.54,79.32 187.14,79.59 186.36,82.75 C184.35,90.89 178.61,95.54 166.19,99.09 C162.70,100.09 157.74,102.15 155.17,103.68 C142.50,111.19 133.13,113.70 123.94,112.03 C120.95,111.49 114.12,108.78 108.77,106.02 C100.44,101.72 97.99,100.92 91.77,100.48 C81.44,99.74 78.08,99.08 74.05,97.01 C69.74,94.80 66.49,91.44 65.08,87.73 C64.04,84.97 64.02,84.96 60.27,86.48 C54.59,88.78 45.89,98.56 40.58,108.65 C38.01,113.52 33.29,120.79 30.09,124.80 C22.80,133.95 19.57,140.55 17.99,149.58 C14.84,167.46 24.86,188.52 41.92,199.87 C51.75,206.40 57.70,208.37 74.36,210.57 C80.89,211.43 90.12,213.30 94.86,214.73 C115.79,221.03 120.20,221.90 131.50,221.93 C137.55,221.95 145.53,221.32 149.23,220.54 ZM 136.76 102.49 C140.36,101.65 145.79,99.27 149.54,96.90 C153.18,94.60 159.03,92.02 162.79,91.05 C170.97,88.95 173.22,87.80 175.92,84.37 C178.38,81.24 178.67,75.20 176.55,71.10 C174.28,66.71 168.40,62.13 160.26,58.42 C155.62,56.31 150.53,52.93 146.85,49.52 C133.35,37.02 118.65,38.50 101.56,54.08 C97.39,57.87 91.17,62.38 87.74,64.10 C77.86,69.03 73.00,74.56 73.00,80.87 C73.00,88.43 77.15,90.86 91.91,91.95 C102.41,92.73 103.22,92.96 111.55,97.50 C124.46,104.54 126.44,104.93 136.76,102.49 ZM 88.35 189.48 C87.37,186.91 91.59,125.01 92.84,123.76 C94.47,122.13 105.29,121.55 107.91,122.95 C109.31,123.70 113.25,130.52 118.80,141.81 L 127.51 159.50 L 136.00 142.15 C140.68,132.61 145.09,124.17 145.82,123.40 C146.71,122.46 149.41,122.00 154.00,122.00 C161.67,122.00 163.99,123.34 164.02,127.82 C164.03,129.29 164.70,140.18 165.50,152.00 C167.35,179.25 167.41,189.65 165.75,190.21 C165.06,190.44 161.57,190.38 158.00,190.07 L 151.50 189.50 L 150.36 170.00 C149.73,159.27 149.14,150.27 149.06,150.00 C148.98,149.73 145.22,156.70 140.71,165.50 L 132.50 181.50 L 127.50 181.50 L 122.50 181.50 L 114.25 165.08 C107.87,152.38 105.97,149.32 105.88,151.58 C105.82,153.19 105.81,154.81 105.86,155.18 C105.91,155.55 105.48,163.42 104.90,172.67 C104.23,183.39 103.43,189.74 102.68,190.22 C102.03,190.63 98.67,190.98 95.22,190.98 C90.51,191.00 88.79,190.62 88.35,189.48 ZM 61.61 182.42 C60.64,179.90 60.89,178.25 62.48,176.66 C65.63,173.51 69.97,177.37 68.50,182.00 C67.71,184.48 62.52,184.80 61.61,182.42 ZM 216.53 167.38 C215.64,166.50 215.07,165.29 215.26,164.71 C215.85,162.94 218.62,161.64 220.36,162.31 C222.46,163.11 222.50,167.60 220.42,168.39 C218.30,169.21 218.37,169.22 216.53,167.38 ZM 36.04 153.55 C33.39,150.36 37.30,145.34 40.42,147.93 C42.30,149.49 42.47,152.13 40.80,153.80 C39.20,155.40 37.51,155.31 36.04,153.55 ZM 201.16 148.12 C199.41,147.01 200.04,142.99 202.19,141.64 C203.48,140.83 204.37,141.02 205.94,142.45 C208.41,144.68 208.48,145.38 206.43,147.43 C204.67,149.18 203.15,149.38 201.16,148.12 ZM 67.34 137.43 C66.47,135.18 68.24,132.50 70.61,132.50 C73.61,132.50 73.27,138.39 70.25,138.82 C68.83,139.02 67.75,138.50 67.34,137.43 ZM 56.54 116.66 C55.17,115.65 54.88,114.62 55.39,112.59 C55.94,110.38 56.56,109.93 58.68,110.19 C62.53,110.65 63.88,113.37 61.55,115.94 C59.36,118.37 58.96,118.42 56.54,116.66 ZM 186.67 113.33 C185.21,111.88 186.09,108.02 188.12,106.93 C189.87,106.00 190.60,106.15 192.23,107.78 C195.00,110.56 193.82,114.00 190.10,114.00 C188.58,114.00 187.03,113.70 186.67,113.33 Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#111417</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#111417</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#111417</color>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,39 @@
/*
* Copyright © 2025-26 l5yth & contributors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip

160
app/android/gradlew vendored Executable file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
app/android/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android" name="Android">
<configuration>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/gen" />
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/gen" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/app/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/app/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/app/src/main/assets" />
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/app/src/main/libs" />
<option name="PROGUARD_LOGS_FOLDER_RELATIVE_PATH" value="/app/src/main/proguard_logs" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/app/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/app/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
</content>
<orderEntry type="jdk" jdkName="Android API 24 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Flutter for Android" level="project" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>

View File

@@ -0,0 +1,41 @@
/*
* Copyright © 2025-26 l5yth & contributors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.11.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
}
include(":app")

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
app/assets/icon-splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
app/assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

5
app/assets/monochrom.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
<g>
<path d="M 126.00 231.38 C116.24,230.24 108.69,228.55 97.57,225.02 C89.71,222.53 81.61,220.73 74.92,220.01 C56.54,218.03 47.54,215.19 35.90,207.72 C25.90,201.31 16.09,188.73 11.28,176.17 C9.05,170.34 8.73,167.93 8.69,157.00 C8.66,146.56 9.01,143.52 10.83,138.58 C13.71,130.75 16.90,125.44 22.99,118.35 C25.75,115.13 30.06,108.55 32.57,103.72 C39.65,90.08 52.73,77.41 61.66,75.54 C63.80,75.09 65.00,74.16 65.38,72.61 C66.30,68.96 73.33,61.70 79.81,57.70 C83.11,55.66 86.29,54.00 86.90,54.00 C87.50,54.00 91.26,50.90 95.25,47.11 C107.07,35.89 118.95,31.10 131.78,32.42 C139.49,33.21 147.99,37.24 153.00,42.48 C154.93,44.50 160.84,48.38 166.15,51.12 C175.72,56.04 185.00,64.04 185.00,67.35 C185.00,68.84 187.05,70.00 189.69,70.00 C190.52,70.00 193.21,71.35 195.66,73.00 C198.10,74.65 200.30,76.00 200.55,76.00 C201.87,76.00 209.57,85.37 213.05,91.22 C215.39,95.15 221.15,102.01 226.90,107.72 C237.44,118.20 241.06,123.94 244.87,136.29 C252.34,160.53 244.67,187.19 225.29,204.27 C215.26,213.12 206.19,217.02 189.50,219.66 C184.00,220.54 174.32,222.95 168.00,225.04 C152.19,230.25 136.54,232.62 126.00,231.38 ZM 149.23 220.54 C152.93,219.75 160.90,217.52 166.93,215.57 C172.97,213.61 182.16,211.33 187.36,210.50 C192.56,209.66 198.70,208.31 200.99,207.50 C219.04,201.13 232.11,186.69 236.54,168.21 C239.68,155.13 237.23,137.60 230.79,127.15 C229.56,125.14 224.01,118.78 218.45,113.00 C212.70,107.01 206.60,99.49 204.27,95.50 C200.27,88.66 197.03,85.00 192.03,81.65 C188.54,79.32 187.14,79.59 186.36,82.75 C184.35,90.89 178.61,95.54 166.19,99.09 C162.70,100.09 157.74,102.15 155.17,103.68 C142.50,111.19 133.13,113.70 123.94,112.03 C120.95,111.49 114.12,108.78 108.77,106.02 C100.44,101.72 97.99,100.92 91.77,100.48 C81.44,99.74 78.08,99.08 74.05,97.01 C69.74,94.80 66.49,91.44 65.08,87.73 C64.04,84.97 64.02,84.96 60.27,86.48 C54.59,88.78 45.89,98.56 40.58,108.65 C38.01,113.52 33.29,120.79 30.09,124.80 C22.80,133.95 19.57,140.55 17.99,149.58 C14.84,167.46 24.86,188.52 41.92,199.87 C51.75,206.40 57.70,208.37 74.36,210.57 C80.89,211.43 90.12,213.30 94.86,214.73 C115.79,221.03 120.20,221.90 131.50,221.93 C137.55,221.95 145.53,221.32 149.23,220.54 ZM 136.76 102.49 C140.36,101.65 145.79,99.27 149.54,96.90 C153.18,94.60 159.03,92.02 162.79,91.05 C170.97,88.95 173.22,87.80 175.92,84.37 C178.38,81.24 178.67,75.20 176.55,71.10 C174.28,66.71 168.40,62.13 160.26,58.42 C155.62,56.31 150.53,52.93 146.85,49.52 C133.35,37.02 118.65,38.50 101.56,54.08 C97.39,57.87 91.17,62.38 87.74,64.10 C77.86,69.03 73.00,74.56 73.00,80.87 C73.00,88.43 77.15,90.86 91.91,91.95 C102.41,92.73 103.22,92.96 111.55,97.50 C124.46,104.54 126.44,104.93 136.76,102.49 ZM 88.35 189.48 C87.37,186.91 91.59,125.01 92.84,123.76 C94.47,122.13 105.29,121.55 107.91,122.95 C109.31,123.70 113.25,130.52 118.80,141.81 L 127.51 159.50 L 136.00 142.15 C140.68,132.61 145.09,124.17 145.82,123.40 C146.71,122.46 149.41,122.00 154.00,122.00 C161.67,122.00 163.99,123.34 164.02,127.82 C164.03,129.29 164.70,140.18 165.50,152.00 C167.35,179.25 167.41,189.65 165.75,190.21 C165.06,190.44 161.57,190.38 158.00,190.07 L 151.50 189.50 L 150.36 170.00 C149.73,159.27 149.14,150.27 149.06,150.00 C148.98,149.73 145.22,156.70 140.71,165.50 L 132.50 181.50 L 127.50 181.50 L 122.50 181.50 L 114.25 165.08 C107.87,152.38 105.97,149.32 105.88,151.58 C105.82,153.19 105.81,154.81 105.86,155.18 C105.91,155.55 105.48,163.42 104.90,172.67 C104.23,183.39 103.43,189.74 102.68,190.22 C102.03,190.63 98.67,190.98 95.22,190.98 C90.51,191.00 88.79,190.62 88.35,189.48 ZM 61.61 182.42 C60.64,179.90 60.89,178.25 62.48,176.66 C65.63,173.51 69.97,177.37 68.50,182.00 C67.71,184.48 62.52,184.80 61.61,182.42 ZM 216.53 167.38 C215.64,166.50 215.07,165.29 215.26,164.71 C215.85,162.94 218.62,161.64 220.36,162.31 C222.46,163.11 222.50,167.60 220.42,168.39 C218.30,169.21 218.37,169.22 216.53,167.38 ZM 36.04 153.55 C33.39,150.36 37.30,145.34 40.42,147.93 C42.30,149.49 42.47,152.13 40.80,153.80 C39.20,155.40 37.51,155.31 36.04,153.55 ZM 201.16 148.12 C199.41,147.01 200.04,142.99 202.19,141.64 C203.48,140.83 204.37,141.02 205.94,142.45 C208.41,144.68 208.48,145.38 206.43,147.43 C204.67,149.18 203.15,149.38 201.16,148.12 ZM 67.34 137.43 C66.47,135.18 68.24,132.50 70.61,132.50 C73.61,132.50 73.27,138.39 70.25,138.82 C68.83,139.02 67.75,138.50 67.34,137.43 ZM 56.54 116.66 C55.17,115.65 54.88,114.62 55.39,112.59 C55.94,110.38 56.56,109.93 58.68,110.19 C62.53,110.65 63.88,113.37 61.55,115.94 C59.36,118.37 58.96,118.42 56.54,116.66 ZM 186.67 113.33 C185.21,111.88 186.09,108.02 188.12,106.93 C189.87,106.00 190.60,106.15 192.23,107.78 C195.00,110.56 193.82,114.00 190.10,114.00 C188.58,114.00 187.03,113.70 186.67,113.33 Z" fill="rgba(0,0,0,1)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 65 KiB

27
app/debug.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Copyright © 2025-26 l5yth & contributors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
export GIT_TAG="$(git describe --tags --abbrev=0)"
export GIT_COMMITS="$(git rev-list --count ${GIT_TAG}..HEAD)"
export GIT_SHA="$(git rev-parse --short=9 HEAD)"
export GIT_DIRTY="$(git diff --quiet --ignore-submodules HEAD || echo true || echo false)"
flutter clean
flutter pub get
flutter run \
--dart-define=GIT_TAG="${GIT_TAG}" \
--dart-define=GIT_COMMITS="${GIT_COMMITS}" \
--dart-define=GIT_SHA="${GIT_SHA}" \
--dart-define=GIT_DIRTY="${GIT_DIRTY}" \
--device-id 38151FDJH00D4C

34
app/ios/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.5.11</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.5.11</string>
<key>MinimumOSVersion</key>
<string>14.0</string>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

56
app/ios/Podfile Normal file
View File

@@ -0,0 +1,56 @@
# Copyright © 2025-26 l5yth & contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
platform :ios, "14.0"
ENV["COCOAPODS_DISABLE_STATS"] = "true"
project "Runner", {
"Debug" => :debug,
"Profile" => :release,
"Release" => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join("..", "Flutter", "Generated.xcconfig"), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Run flutter pub get and try again."
end
require File.expand_path(File.join("packages", "flutter_tools", "bin", "podhelper"), flutter_root)
flutter_ios_podfile_setup
target "Runner" do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] = "14.0"
end
end
end

View File

@@ -0,0 +1,616 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.potatomesh.reader;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.potatomesh.reader.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.potatomesh.reader.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.potatomesh.reader.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.potatomesh.reader;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.potatomesh.reader;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,26 @@
// Copyright © 2025-26 l5yth & contributors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -0,0 +1 @@
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Some files were not shown because too many files have changed in this diff Show More