Hub
MeshSat Hub changelog: fleet management platform releases.
Generated from the commit history of meshsat-hub: features and fixes, newest first, one section per tag. Untagged work on the main branch is listed under Unreleased. The Hub is open: create an account and we review it before it goes live.
Latest tagged release: v1.7.0
Tagged 20 September 2026. This release included live dashboard event updates, Cloudloop receipt handling and broader browser checks. See the v1.7.0 release record for the complete changes.
Later work on main is listed separately under Unreleased. A tag is a software milestone,
not a claim of emergency reliability.
Unreleased (main, last change 2026-10-30)
Features
- sms: alert texts to Dutch numbers show the sender name MeshSat, while everything a kit or a person answers stays on the number (2e142717)
- warnings: the console and the satellite option say what a warning costs on a kit’s satellite leg, 5 SBD credits or 230 bytes of IMT airtime (8d569677)
- lxmf: every message row carries its attachment count, so the console lists voice notes and photos without a read per row and asks nothing of a message that has none (de7e41c4)
- warnings: an Update or Cancel that names no area goes to the kits the message it references went to, in the workspace whose match that was, at the first delivery, on the bench path and at every repeat (17204b2a)
- web: a workspace owner chooses on the Settings page whether a crew message to a kit off MQTT goes by satellite or SMS, or forces one bearer for a test, and the crew chat panel says which bearer each message went on (2e8ed12c)
- takchat: a dispatcher’s chat reaches a kit that is off MQTT as one frame over the workspace’s satellite account or as an SMS to the kit’s SIM, the crew’s answer and the kit’s word come back the same way, and the message records its bearer (f688c70b)
- web: an LXMF voice note plays in the console, Codec2 decoded by the vendored libcodec2 wasm on first play, Opus in the browser’s own player, a photo shown inline; the CSP admits same-origin WebAssembly (a4ae8d71)
- console: a Warnings page lists the public warnings relayed to the workspace’s kits, which kit got each one and when, and lets an owner send a test warning to the first area (e3501f67)
- warnings: a CAP polygon rides the digest ahead of the geocodes when it comes down to twelve points within 3 km, so a kit can place itself against the outline (c9a2236f)
- warnings: a warning in force is handed to each kit again every repeat_hours (default 6, 0 never), claimed once per kit and slot, so a kit that was off gets it at the next slot (f68a24fd)
- console: a device’s page sets its profile, ordinary or Community Post kit, and says what a post device does to the @ lane (2b8737ae)
- takchat: a kit’s word on what became of a dispatcher’s chat is recorded on the message, told to the Matrix room as a reaction and one notice, and shown under the message in the console (47ab803b)
- warnings: the Hub relays DWD and MeteoAlarm public warnings to the kits in their area as a digest signed by the directory key, once per kit across the replicas, with a per-workspace opt-in and an owner’s test injection (6418098f)
- personchat: a post device runs the “@” lane open and keys every conversation by the pickup code the kit appends, so two people at one Community Post never collide, and its replies carry “#<code>” in front (2d9f3834)
- lxmf: a tenant’s LXMF messages keep their voice note and photo in the object store, listed and served by the API, and a send carries both (dc2e6d07)
- netguard: a self-hosted Hub may reach the services its users configure on its own network, by the operator’s HUB_ALLOW_PRIVATE_TARGETS; the hosted Hub never sets it (7cd5ae07)
- matrix: a dispatcher in the tenant’s own Matrix room writes to one crew with @ and its callsign, and the crew’s answer comes back into the room (fefd40a5)
- rns: an app that joins a workspace’s Reticulum network hears the workspace’s address within a minute, without asking for a path (f036857a)
- rns: the Hub hands each kit its workspace’s Reticulum access code, so kits join the workspace’s Private network without anyone typing it (4a03e6db)
- rns: a node can mark an interface as a leaf that carries only its own traffic, which keeps a kit on a tenant’s Private network from bridging it to its mesh (dfbe4fbc)
- rns: each tenant chooses who reaches its Reticulum address: Private with its own network name and passphrase (the default), Public or Off (4ec8ca8a)
- tak: dispatcher-to-crew chat is on: a dispatcher on TAK or the Hub map can write to one crew (46b09c2b)
- tak: a dispatcher can write to one crew from TAK or the Hub map, and the crew’s answer comes back (9e759744)
- tak: a hosted TAK server hands the Hub a chat a TAK user addresses to one crew (f4389a4c)
- console: a Contacts page, and Settings chooses who devices may write to (fee8a67e)
- hub: a device writes to a person by SMS or email with “@”, and the reply comes back to the device, plain or end to end (755260b9)
- relay: satellite_relay reaches an SBD modem through Rock7 and carries an SBD message’s bytes on, one 270-byte frame at most, never cut (cca0a426)
- provision: the bundle names the bridge’s tenant, so a phone binds its directory to it, and the protocol says how a phone and a Bridge pin the directory key differently (1071d0ac)
- sms: the Hub reads app SMS wire v2 by the codec its frame names, keeps a v2 frame it cannot read as it came, and reads v1 by the old rule (dab5a12a)
- relay: the phones read their tenant’s signed directory and the key rotation chain from GET /api/relay/directory with their bridge credentials (7c3ef2e5)
- directory: bridges can fetch the Hub’s directory key to pin on their first connect (9745ebd1)
- directory: the signing key rotates through a signed chain every pinned bridge can follow (a5ccd39c)
- directory: the Hub pushes each tenant’s directory to its bridges within 10 s of a change and again at a bridge’s next connect (6590d70e)
- directory: the signed v2 envelope, every row with its origin and each policy with its seeded mark (4436dd40)
- directory: group and policy REST, TETRA and CELL addresses, MQTT bridge:/device: values, group kinds checked (3c7ca806)
- directory: contacts, groups and policies on Postgres, every row keyed by its tenant and every change versioned in its own transaction (952338ec)
- sos: an SOS carries its own id, so the Hub pages once whichever bearers carry it, a cancel stops it for good, and the sender gets an ack for each (553fac4d)
- hub: the Hub carries texts between bridges that ask for it, the route now on in production (ec16b29d)
- hub: the Hub carries a bridge’s text to another bridge of its tenant and tells the sender what is known, behind HUB_MSG_ROUTE_ENABLED (ba4c6a16)
- lxmf: the Reticulum page tells a tenant where a client connects, from HUB_RNS_PUBLIC_ENDPOINT (9cfccf8e)
- lxmf: every tenant address runs an LXMF propagation node that holds messages for offline clients, on RNS link requests added to the node (c9462e26)
- lxmf: the Reticulum page in the console (address with copy and QR, inbox, send, peers) and the public listener’s edge path (7e42c2f4)
- lxmf: every tenant gets an LXMF address on the Hub’s Reticulum node, with an inbox, a proven outbox and stamps, verified against Python LXMF 1.2.0 (a130872c)
- rns: one upstream-compatible Reticulum node on the leader, in front of the legacy layer, with persisted paths and a Python interop suite (f2aba581)
- rns: port the Bridge’s upstream-compatible Reticulum wire, node, msgpack and LXMF packages, golden vectors pinned to Python RNS 1.5.5 and LXMF 1.2.0 (0e34df39)
- federation: two MeshSat Hubs relay satellite messages to each other’s kits after exchanging certificates (41ac7e7b)
- admin: platform-read views of a tenant’s registered devices and bridges (99546546)
- auth: a read-only platform key for the directory reads (21f8b071)
- tak: the Hub delivers a tenant’s TAK traffic to its kits and apps, and shows a tenant’s clients to each other (ae0583c7)
- tak: the stream a TAK server sends is cut into events without trusting it (afb667bb)
- tak: kits and apps can export their TAK messages to the Hub, which forwards them to the tenant’s TAK servers (459bc078)
- tak: a TAK message from outside the Hub is checked and re-written before the Hub passes it on (32ffad3b)
- console: platform admin area with account requests, tenants, billing and consent-gated view-as (837cfcb1)
- relay: Reticulum packets between two RockBLOCK 9704s ride the satellite relay untouched, on the IMT topic they arrived on (c5b09b58)
- db: barman backups through the crypt gateway to Hetzner [IFRNLLEI01PRD-2850] (f361c169)
- web: test an escalation chain from the Alerts page (4cbbf4ac)
- web: the Hub becomes an operations console, quiet when all is well (f8f88563)
- settings: a tenant may set its send limit below the default too (3266bafd)
- satchat: a roaming satellite node and the kits’ meshes text each other (8fbb45de)
- settings: a tenant sets its own send limits (594f0441)
- routing: relay a satellite message to another modem byte for byte (797471b5)
- bridges: the command page names the bearer a command went out on (7306bc35)
Fixes
- lxmf: a message goes as one packet only up to LXMF 1.2’s 287 bytes of content, not 1.1’s 295, the same limit as the Bridge’s (cd5912cc)
- rns: a newer announce on a leaf displaces a stale path to the same destination elsewhere, never a neighbour’s path used within the reverse table’s lifetime, and SetLeaf changes the mark and the table under one lock (c1cc1195)
- tak: the hosted TAK server image passes its CVE gate again, dnspython and msgpack lifted past the advisories of pipeline 61943 (1ef06706)
- warnings: a digest goes only to a kit whose birth declares a mesh, APRS or the 2G cell, never to a phone or a bridge that has not sent a birth (0442bad1)
- warnings: MeteoAlarm is asked with Accept */*, the only value its feed does not refuse with 406 (1d58df0b)
- warnings: the closes are checked, the test key is parsed the Go 1.26 way, and the swagger spec carries the warnings routes (a8ae5d6a)
- takhosted: a crew’s sighting is recorded at the event’s own time, so a replayed old fix does not make a crew seen this minute (7ea3a87c)
- console: a page opened right after sign-in is the page that shows (571d993d)
- tak: hosted TAK clients get back in within seconds of a Hub rollout instead of after five minutes (0a8f874e)
- console: the offer of a new Reticulum passphrase after a kit is deleted is a visible button (c93fc2cf)
- bridge: a bridge id holds only letters, digits, - and _, a birth counts only for its own bridge, and deleting a kit offers a new Reticulum access code (32b171f6)
- nats: no bridge can publish the Hub’s answers to another bridge of its workspace, whatever that bridge is called (63fe4037)
- signups: a switched-off account’s right password gets the same answer as a wrong one at the MeshSat sign-in (3dc5c71f)
- rns: a path reloaded after a restart never names a connection of the new term (b43d13a5)
- rns: nothing from the public Reticulum port reaches the Hub’s internal kit layer (35e9c959)
- signups: a waiting applicant is told a person approves the account, at the end of sign-up and at sign-in (8b0c7db7)
- tak: hosted TAK servers move to the image that routes a dispatcher’s chat to one crew (da4f95c8)
- tak: the hosted TAK server image passes its CVE gate again (ab683719)
- tak: the hosted TAK server image builds again without the two feed packages deleted from PyPI (ad3ccbdb)
- cloudloop: a text to a 9704 goes as its own bytes, so MeshSat Android and iOS open it instead of showing cannot open (12acb1b5)
- compress: word 124 of the Meshtastic table is the Bridge’s “satellite” again, so the Hub and the kits read the same word (c1012241)
- sms: what a key opens outranks a clear app SMS wire v2 reading of the same bytes, so an old sealed SMS whose nonce starts like a v2 header reads as its words (82840301)
- sms: an SMS from a MeshSat app reads at the Hub as written, compressed or not, encrypted or not, Greek included (af087536)
- store: a SQLite Hub runs the WAL journal, busy timeout and synchronous mode its DSN names, with foreign keys off as on Postgres (ebe81fe5)
- satellite: a declaration this Hub confirmed earlier answers a failed lookup only when it is sealed, never plain, so a newer sealed stored meanwhile cannot be read as text (edb93e97)
- satellite: a birth is written whole in one ordered transaction, so a retry or a replica behind never undoes a newer declaration, and a held birth links its modem before it is let go (09217e7b)
- satellite: a declaration is saved with the birth that makes it and held until every write lands, so a failed write never leaves an older plain in charge (25979ad4)
- satellite: a kit’s sealing declaration is kept per modem, so a birth without the IMEI or the gateway, or a failed lookup, never turns its ciphertext into guessed text (4e5ac7d7)
- satellite: versioned plain SMAZ2 is read again, and a payload is taken for a kit’s ciphertext only when it is base64 of binary or its bridge declared the chain sealing (aadcff4d)
- satellite: a Bridge’s ciphertext without base64 stays opaque, an unframed 0x01 payload is Reticulum only when it is a signed announce, and nothing the Hub cannot read reaches a person as an empty message (b335ce93)
- directory: a key rotation makes its key current only from the key it started from, so a delayed caller never rolls the key back (d960f391)
- directory: two replicas rotating at once no longer fail on one tenant’s record (0c8e1912)
- directory: empty snapshots take the last rows back, key rotations reach every Bridge without being read as its rows, and a rotation’s pending key is stored by compare-and-set (8d84392c)
- directory: phones registered as bridges get no directory push and no longer hold their tenant on v1 (af1c7a9d)
- auth: a password reset no longer activates an account, so only approval does (0eb98cb7)
- sos: a cancel closes its run’s alert even before the alert exists, so a late start or a fallback pages nobody (1090a8ad)
- signups: a pending applicant who reset their password can be approved or rejected again (ae0a2c95)
- sos: a cancelled SOS stays cancelled through an escalation step, and its alert work survives a failed write or a restart (18efecca)
- sos: a Hub replica starts again with bridges connected, as the SOS answers no longer wait inside the MQTT handler (a32ca045)
- sos: a bridge that stays connected while the Hub restarts still learns the Hub’s SOS caps (11f85020)
- hub: a bridge that repeats its submission gets every event again, however soon it asks (26fd2a44)
- hub: the other replica’s copy of a bridge’s submission no longer sends the sender its status a second time (32449415)
- nats: a bridge or a phone can no longer publish its own msg/in or msg/status, which only the Hub may (c5b1b101)
- routing: a Bridge’s mesh text on any channel is routed and stored, and one SOS raises one alert whatever bearer carried it (9566a375)
- authentik: the sign-up notification rule fires once per enrolment, from one policy that cannot throw (9954d552)
- ots: lift urllib3 to 2.8.0 and virtualenv past 21.7.13, the floors trivy:ots asks for (649881fe)
- rns: the lint, gitleaks and interop-harness fixes pipeline 59198 asked for, plus the LXMF inbox store and provider row P3 builds on (67e699aa)
- auth: serve the link-preview image and home-screen icon to visitors who are not signed in (f49ab9de)
- k8s: the Hub can reach its own WireGuard manager again (84e6fbc0)
- docker: the Hub image takes Alpine’s published security fixes at build, so an OpenSSL fix no longer blocks every deploy (c7b725b5)
- tak: an unreachable TAK server no longer stalls the message bus, and positions are not forwarded twice after a change of leader (6aac8ab1)
- security: a tenant’s own TAK server may not be an internal address, checked on save and at connect (677b741e)
- k8s: restore drill passes through the backup gateway; the drill brings its own allow policy (10dde94f)
- basemap: the map and the audit archive go through the backup gateway, nl-s3 is retired (2f9c313b)
- sms: made-up numbers in the WhatsApp pool test, and the WhatsApp sender registered with the identifier gate (304e723a)
- whatsapp: the WhatsApp sender is its own number, per tenant on the Twilio account and for the platform from HUB_WHATSAPP_FROM_NUMBER (aad59771)
- audit: an operator action by a platform credential is attributed to the credential, not to “unknown” (39c7bda5)
- verify: the directory check no longer shadows the SQL row the teardown splits (cf404a22)
- verify: the journey probe grants support access with a session token, not a bare cookie jar (a5a4c329)
- auth: a customer who typed a country name instead of an ISO code can sign in; the billing country is normalised, never guessed (aade61bd)
- fleet: a setup QR replaces the kit’s login when it is scanned, not when it is shown (bdc45e85)
- web: the Reticulum topology page takes the console’s quiet colours (131e9d3c)
- web: the credit balance, the send-limit warning and the message export are back (907b3d29)
- audit: bridge changes and bridge credentials leave an audit record (ac2581bb)
- bridges: creating a bridge with an id another tenant holds no longer rewrites that tenant’s bridge (7e1f4824)
- provisioning: a provisioning bundle is kept per tenant and goes with its bridge (7bdcb5a8)
- provisioning: a claim is not held on another replica’s stale not-yet (7e8de9c0)
- logging: a claim nonce is no longer written to the request log (7d50aae6)
- fleet: a satellite frame from a kit that is live over MQTT no longer replaces its MQTT report (1483179e)
- provisioning: a provisioning QR is shown, and its bundle handed out, only once the broker accepts its credentials (2113ef89)
- satellite: announces are no longer flooded onto Iridium, and a Cloudloop refusal is not logged as sent (4ccc1a12)
- escalation: an SOS page names the kit that raised it, and only a failed delivery is a failure (c342d174)
- oob: a management command expires when the Hub stops waiting, and a late reply is reported as late (f16887f9)
- fleet: a command reply over satellite is not announced as a broken uplink frame (b5190917)
- fleet: a command job keeps the request’s context, minus its cancellation (4a397940)
- fleet: a kit’s fallback report reaches its bridge when the id arrives cut short (9c005e2c)
- fleet: a slow command to a bridge is not sent again by a retry, and no longer needs a request held open (1065d64e)
- fleet: the Log command names a unit, and a bad command says why (17e89df1)
- settings: a changed send limit applies on both replicas at once (9553b0b4)
- routing: renaming a route no longer blanks its recipients and senders (0ebae267)
- routing: a satellite chat reply is not also copied by the routes (99648e4e)
- mesh: only mesh traffic counts as a node heard on a kit’s mesh (f5f34e6f)
- satellite: every IMT message is its own message, not a repeat of the first (ca38e89d)
- k8s: the modem-to-thing map comes from the secret store, for both replicas (33186293)
- satellite: a whole message is no longer parked as half of a two-part one (4dcc76ac)
v1.7.0 (2026-09-20)
Features
- verify: the nightly browser check covers the deep map, not just sign-up (3b675577)
- cloudloop: the MO receipt, for the second tick on a tenant’s own account (22b2de00)
- web: the dashboard opens the live event socket it never used (ebd44b7c)
- edge: a statuscsp phase, so status.meshsat.net has a Content-Security-Policy (24a4b51a)
- verify: arm the nightly browser check (b73ca303)
- verify: a real browser checks the sign-up flow and the SPA every night (2bccc9d3)
- bridge: the Hub tells a bridge when it has an MO from one of its modems (2d370e72)
- monitoring: log-based security alerts for the Loki ruler (ef0b8053)
- monitoring: alert when a request budget refuses traffic (0c56f06c)
- api: request budgets on every authenticated route and the capability URLs; opaque 500s (2066c76c)
- ci: supply-chain gates: gitleaks, IaC scan, SBOM and cosign signing (859b38bc)
- auth: retire the static break-glass token; fix the nightly’s unlabelled publisher (03de90e2)
- verify: cut the nightly off the static token onto its own platform key (359e9118)
- auth: API keys can carry platform-admin, so the static token is replaceable (82718126)
- k8s: ResourceQuota, LimitRange and no SA tokens where none are used (c3f69cbd)
- k8s: default-deny ingress in both Hub namespaces (456dbd52)
- k8s: confine the last six workloads, every pod now policy-covered (4f1f5c61)
- k8s: confine keydb with a policy built from observed flows (f7fa8edc)
- k8s: enforce restricted on the DB namespace, minimise every capability set (19fd6a40)
- auth: make the break-glass platform-admin token accountable (304a1bec)
- tor: adopt a vanity .onion with a meshsat prefix (5aa3a5a5)
- monitoring: add the Hub’s first security alert rules (6bf60195)
- auth: count and log every refused request (93ff00e7)
- booth: know which meshes have someone listening, and say so (2254754b)
- booth: a spend ceiling, and no more silent unanswered relays (c77fc938)
- booth: run the stand on SMS as well as WhatsApp (1be6d6eb)
- booth: wire the stand flow end to end and enable it (ed4226dc)
- booth: route a mesh reply back to the visitor who started it (10dcf46c)
- booth: scripted visitor flow and the deterministic relay gate (4a0e63d6)
- whatsapp: register the inbound and status routes (4c727807)
- booth: persist the visitor session and the return-leg correlation (473140dc)
- whatsapp: make the SMS bearer channel-aware (ef868ea1)
- relay: publish the bridge CA at GET /api/relay/ca for the Bridge relay client (e1cda0ff)
- certauth: bridge certificates can serve a relay tunnel as the TLS server (9c01be48)
- relay: Hub side of the WebSocket relay, a rendezvous over the bus (c98988aa)
- store: slow-query log names the statement; pg_stat_statements on the cluster (b2507753)
- k8s: nightly in-cluster production verification (b128f138)
- cloudloop: a tenant can bring its own Cloudloop MQTT certificate (2571563d)
- k8s: Reloader annotations on hub, stunnel and tak-operator (63648816)
- k8s: onion heartbeat for the status page; admin-only OIDC client for it (ac26eb81)
- web: HUB status badge links to status.meshsat.net (20d43a35)
- health: report Stripe reachability as an informational dependency (19cc1474)
- hawkbit: move the OTA database from bundled H2 to CNPG Postgres (8a0daefc)
- api: tell the customer when a feature will not do anything (0909ae9d)
- tor: publish the Hub’s .onion address (e04ac2fa)
- apprise: deploy the notification relay that never existed (4cea1de7)
- tor: run the hidden service on Kubernetes at last (cbcb3170)
- hawkbit: per-tenant OTA, and guard every tenant-supplied URL (92f4c1ca)
- wireguard: per-tenant wg-easy, and deploy the platform’s own (fb2e049e)
- oob: per-tenant command policy, migration v24 (44f5d136)
- email: per-tenant PGP gateway, and contacts stop colliding (4af35ed2)
- notify: per-tenant Apprise and ntfy delivery backends (97b85524)
- aprsis: each tenant transmits under its own amateur callsign (851e89b9)
- geo: a fence stays quiet for a while after it alerts (2308f718)
- geo: geofences actually fire now (2b1c31b6)
- ratelimit: the send budget follows the plan (574c3a13)
- tenant: audit retention belongs to the tenant (385e3c66)
- tenant: the bridge offline timeout belongs to the tenant (9066c2e4)
- signups: tell someone when their account request is refused (f6a6a4b9)
- signups: email the operator when an account request is waiting (16ae0d4b)
- tak: switch the hosted TAK front on (eb416a1b)
- tak: switch the hosted TAK front on (d54ae4c7)
- tak: a TAK page for tenants, with one-time enrolment and a QR (b1dc58fb)
- tak: one-time enrolment packages for a tenant’s TAK users (73720748)
- tak: build ATAK, WinTAK and iTAK enrolment packages (5e1e1e9e)
- tak: a tenant can forward CoT to their own TAK server (5b3f04e9)
- tak: open the hosted TAK leg at the edge on 8089 (10dbfd23)
- tak: a tenant can turn TAK on and manage who sees the map (92506b51)
- tak: operator creates OpenTAKServer accounts on request (5ad9fea5)
- tak: destroy a closed tenant’s TAK server when its data is purged (74475647)
- tak: forward device positions to each tenant’s own TAK server (ae19ffa0)
- tak: wire the Hub to hosted per-tenant TAK (0b37066e)
- tak: per-tenant TAK schema and the TAK account ceiling (1794ef85)
- k8s: foundations for hosted per-tenant TAK (d396f06c)
- db: a stuck Backup no longer holds the slot until somebody notices [IFRNLLEI01PRD-2833] (6a26c02f)
- billing: the donation page is MeshSat’s, with Stripe’s form inside it (76588e35)
- auth: customers can change their password and turn on two-factor (18d18085)
- webhook: watch where satellite deliveries come from, never refuse one (7374b3b9)
- tenant: a customer can take their data and leave (f8652490)
- stripe: a moved Stripe field can no longer pass unnoticed (54c8d0eb)
- billing: the checkout page says whose it is (9a2b9208)
- billing: a renewal that fails is no longer invisible (53ed8a08)
- billing: a public donate link, and the last of Ko-fi (cbef220b)
- billing: donations, and they are outside the scope of BTW (cb54705c)
- billing: move Stripe to the dedicated MeshSat Hub account (00fef1d4)
- billing: Stripe is the payment provider; Ko-fi is decommissioned (35147455)
- billing: switch Stripe on (ade71293)
- billing: checkout starts in the Hub, and a cancellation ends a plan (7f44f4fc)
- stripe: the payment provider, inert until it is configured (b1c6eacb)
- mail: write money the way the customer’s document writes it (a4c73afd)
- mail: give Hub email the billing system’s brand shell (ff2be04c)
- invoiceninja: watch the sender setting, not just the tax one (cde5229c)
- refunds: money given back now leaves a credit note behind it (5e00f05c)
- vat: a receipt follows where the buyer is, and the threshold is measured (420d0d5b)
- enrollment: CAPTCHA is live, and the stage has to be interactive (ae76d826)
- admin: money that upgraded nobody is now visible and recoverable (82080cab)
- mail: the Hub can tell a customer what happened to their account (62751428)
- k8s: wire the Invoice Ninja token so receipts actually go out (621ea395)
- kofi: issue a VAT receipt for every paid subscription (381d8f90)
- login: the Hub sign-in gets the meshsat.net hero (5cdef982)
- auth: harden the enrollment flow now that it is public (09bd5565)
- auth: the enrollment page gets meshsat.net’s rolling hero (d15e0799)
- ux: carry the claim code to the upgrade, and link password recovery (e05dc469)
- plans: four subscription tiers metered by device count (d878807f)
- signups: the approval panel (df720a8c)
- signups: give the Hub its own identity at the provider (c5e3c212)
- signups: approve a beta request from the Hub (97ddb939)
- tenant: let a tenant take its data and have it destroyed (fbaf6849)
- auth: a password reset flow, and MeshSat mail that says MeshSat (cb2443b4)
- webhook: give every tenant its own inbound endpoint (160baa1c)
- map: basemap replicas copy the archive from each other (14305161)
- map: all of Europe at street-name detail, on its own disks (b9acc15f)
- k8s: a second Reticulum terminator, and the placement rules rewritten (9c7c4b81)
- k8s: the broker becomes a three node JetStream cluster on the control-plane tier (7f773c73)
- k8s: KeyDB replaces the single Redis, two masters on two machines (58f37ee3)
- k8s: the Hub can land on any worker, and gets there faster (d3404f2e)
- map: self-hosted MapLibre vector basemap, no third-party tile host (a7dc689c)
- k8s: two Hub replicas with rolling updates, a PDB and a per-pod leader identity (2a9cf159)
- edge: gated registration, file-backed IP allowlist and enrollment exemption (2d0d0bbe)
- routing: satellite route destination delivers a text to a bridge over its Iridium modem (0b4aeb67)
- oob: bridge commands over SMS, Iridium IMT and SBD as sealed OOB frames with replies correlated by counter (ea2f69df)
- fleet: uplink frames over SMS, SBD and IMT become fleet state and SOS alerts, last report bearer on the bridge (1cd44497)
- routing: optional sender list per route so kit A reaches kit B without an echo (3719f9a7)
- providers: Twilio, Rock7, RockBLOCK and Globalstar per tenant (8436af05)
- cloudloop: use the tenant’s own Cloudloop account for MT sends, thing lookup, credits and the MO webhook (c976c168)
- tenant: per-tenant provider accounts: encrypted Cloudloop, Twilio, Rock7, RockBLOCK and Globalstar credentials with a tenant API and Integrations page (02b4be22)
- audit: archive purged entries to S3 for every tenant (366db57f)
- nats: per-bridge NATS users and permissions rendered into a Secret (6dcc2a7f)
- mqtt: tenant-prefixed topic namespace with dual-shape subscriptions (6064909b)
- routing: evaluate routes and persist inbound traffic for the device’s tenant (2e2e24fc)
- k8s: run one Hub replica on notrf01 for the cutover (107beade)
- k8s: bootstrap owner attaches to the default tenant; operators count as verified (35685e3f)
- k8s: edge HAProxy patcher for the auth, cutover and rollback changes (2caa3688)
- cli:
--migrate-onlyand the MariaDB to Postgres rehearsal tooling (4efeda5a) - k8s: authentik bootstrap for the MeshSat brand, enrollment and approval (4e991435)
- k8s: kustomize tree for MeshSat Hub on notrf01cl01k8s (ee77d6e7)
- bridge: keep the bridge CA certificate in a Kubernetes Secret (94a33cde)
- api: tenant self-service and platform-admin tenant directory (4a220974)
- web: SSO-first UX pass, first-run onboarding, tenant panel, light-theme parity (bb3a12b7)
- web: MeshSat brand tokens, IBM Plex fonts, lockup and light theme (24168d85)
- web: sign in with MeshSat ID (OIDC) and auth callback view (acacc48e)
- auth: OIDC authorization-code login with JIT tenant provisioning (1e6cf492)
- store: tenants and tenant invites, default tenant seeded (79a88f3e)
- leader: run pollers, reapers, retention and evaluators on the leader only (3b222930)
- store: dispatch claims and CAS so N replicas send each message once (386f5ee6)
- config: HUB_DB_DRIVER selects sqlite/mariadb/postgres, configurable sqlite path (fe8063a8)
- store: Postgres port of users, tokens, API keys, device keys, routes, escalation, alerts, prefs (079b77f3)
- store: Postgres port of devices, messages, webhooks, positions, audit, device configs (0aec4754)
- store: Postgres implements store.Store, conformance suite runs on Postgres (1e9ea03b)
- store: shared helpers and conventions for the Postgres domain files (7e75c59b)
- store: Postgres port of bridges, bond groups, costs, groups, templates, alert rules, credentials (d639a0af)
- store: Postgres store skeleton, versioned migrations, conformance harness (2deeb3d9)
- auth: EC JWKS keys, discovery endpoints, slash-tolerant issuer check (0f812b28)
- health: readiness probe classes, drain on shutdown, startup marker (e615841d)
- api: provider-aware MT send routes with Cloudloop/IMT support (ba882db1)
- directory: Hub precedence-default policy seeding (5e909123)
- directory,api: vCard 4.0 + CSV import/export (Hub) (a204c7f3)
- directory: Hub directory foundation + signed push (6256490b)
- dtn: wire CustodyManager for hub-side custody acceptance (02d020ae)
- ui: add bond groups Vue UI, Fleet HeMB badges, and E2E tests (a4caf342)
- hemb: add bond group CRUD API + MQTT config push (3550997c)
- ui: complete API-to-UI coverage for keys, costs, alerts, credentials (72559eaa)
- ui: add alert rules Vue view and Playwright E2E tests (6a535ca7)
- ui: wire cost tracking view with route, nav, and E2E tests (865a5538)
- tak: store OTS markers as devices+positions in Hub DB (a3cb90f5)
- tak: add OTS REST API poller for inbound CoT relay (103471e4)
- tak: broadcast CoT XML to bridges via MQTT for map integration (250cfe6b)
- tak: redesign TAK Operations Center with tactical design system (78ca3bfe)
- map: interactive CoT type filter on Position Map legend (a7f84dc4)
- tak: Hub TAK Operations Center + enhanced KPIs + fleet badges (6fb87f93)
- tak: mission proxy endpoint GET /api/tak/missions (3203b8ce)
- tak: federation peers API + TAK KPI dashboard widget (bb8e5743)
- map: TAK/CoT-compliant SVG markers for fleet devices (91f14208)
- map: TAK/CoT-compliant markers: diamonds, squares, stale fade (37bc72a8)
- tak: Federation v2: Hub as bidirectional TAK federation bridge (14554a44)
- tak: Marti proxy for fleet DataPackage/Mission sync (994d4aaa)
- api: add TAK integration status to Integrations page (c9bfc626)
- keys: Hub channel key rotation + MQTT distribution to bridges (60b3dccf)
- hemb: Hub-side HeMB bearer integration: all 3 webhooks (2adad3c1)
- reticulum: add SMS interface for Reticulum packet relay (f0f590d9)
- sms: bring Twilio webhook to parity with Rock7/Cloudloop pipeline (62e30db3)
- hemb: port HeMB codec + bond group management + telemetry to Hub (72025bde)
- bridge: verify birth message ECDSA signatures to prevent MQTT spoofing (25bf29ce)
- nats: simple shared-password auth: no accounts, no cycles (1c23015e)
- settings-ui: Service Security section with auth status + password rotation (12f733dd)
- nats: NATS accounts: separate auth domains for Hub (password) and bridges (mTLS) (b39ae423)
- security: auto-generate service passwords on first boot (21abe1c0)
- fleet-ui: Provision QR button + modal on Fleet page (eb33601d)
- provision: single-use nonce + deduplicated generateBundle helper (5e13f570)
- fleet: one-step QR provisioning for bridges and Android apps (534e4059)
- protocol: port 4 protocol enhancements from bridge: FEC, Time Sync, DTN, RLNC (f61c519f)
- reticulum: Hub announces identity to TCP clients + broadcast on announce (85dfcd19)
- reticulum: broadcast announces to all interfaces (flood transport) (7c804f3d)
- reticulum: add TCP interface for external RNS node connectivity (8c9bde46)
- nats: mTLS WebSocket on port 9443 + Hub CA cert export (a2765323)
- nats: cross-site leaf nodes for bidirectional MQTT routing (86545bd9)
- nats: add MQTT-over-WebSocket via NATS native websocket (7416572d)
- deploy: GitOps for mqtt-ws sidecar + nginx WebSocket proxy (38673425)
- infra: Mosquitto WebSocket bridge sidecar for MQTT over port 443 (46206c92)
- settings: UI-configurable MQTT public URL for bridge onboarding (28d4ce36)
- fleet: full bridge management UI with add/edit/delete, credentials, commands, and onboarding (97e8fbde)
- observability: add comprehensive instrumentation across 12 issues (1b8a408b)
- dashboard: credential management page (92bf23e9)
- bridge: credential_push MQTT command for distribution (5ccc3362)
- credentials: centralized credential management for Hub (4aa07c3d)
- reticulum: wire Reticulum router to live MQTT bridge traffic (a939a724)
- dashboard: use unified topology API, add path discovery and hints stats (736fac78)
- hub: add configurable alerting rules engine with device_not_seen condition (12ffab9f)
- hub: add scheduled message delivery with background scheduler (fa6ce660)
- hub: add scheduled message delivery with background scheduler (ef2f4a1d)
- hub: add changelog generation script and CI job (acf8b900)
- hub: add device groups Vue page with CRUD and member management (959be3e3)
- hub: add message templates with variable substitution (23c35ab2)
- hub: add cost tracking ledger for satellite message sends (2470cdb2)
- hub: add server-side CSV export for messages, positions, and audit (3313f125)
- hub: add map breadcrumb tracks with time range selector (0b2e2388)
- hub: add Prometheus /metrics endpoint with HTTP and message metrics (76d41e39)
- hub: add /api/v1/ versioning prefix as alias for /api/ (72aa2479)
- cloudloop: ThingResolver: auto-learn IMEI-to-thingID from MO + API refresh (c716dbaa)
- constellation: upgrade IridiumBackend to official Cloudloop Data API (cbd678e7)
- cloudloop: Sender auto-selects SBD vs IMT API, supports imt_topic and ring_style (3df1f75f)
- cloudloop: upgrade MT client to official Data API: SendSBD, SendIMT, GetDeliveryStatus (049ebaa5)
- dashboard: Integrations page: inbound channel status and webhook URLs (329cf415)
- cloudloop: LingoMO webhook + MQTT subscriber for 9704 IMT (14a39636)
- dashboard: show bridges in Device Fleet widget with interface badges (3c389353)
- bridge: bridge-to-hub uplink protocol: registry, subscriber, CoT projection, fleet dashboard, commands, security (83ad4bbe)
- reticulum: Hub Super Transport Node: path discovery + routing hints (9693710f)
- help: polish Help & Documentation page with consistent styling (8e3256f7)
- ui: add Help/Documentation page (70362de1)
- ui: grouped dropdown nav menus (5 groups, 20 items) (31ca5f86)
- dashboard: add EmptyState to Devices view, rebuild dist (410f3001)
- dashboard: unify Hub UI with Bridge design patterns (f339bf32)
- ci: run OWASP scan automatically on every push to main (e512cfac)
- ci: auto-deploy with Galera health gates before and after (925c8e19)
- reticulum: add topology dashboard with relay stats and interface map (f7bbb986)
- dashboard: add toast notifications, empty states, mobile nav drawer, widget customization (2ec5aab6)
- reticulum: wire Iridium SBD as Reticulum transport interface (fa367832)
- geofences: add geofence API + map-based polygon drawing UI (d1fb059f)
- dashboard: add WebSocket real-time events, sparklines, theme toggle, search, CSV export (9f595203)
- dashboard: add Device Detail, Email Gateway, Backup views + rewrite Settings + new nav (f69d1f7d)
- dashboard: redesign operations dashboard with professional layout and full data surface (92e97352)
- routing: wire all 5 missing destination handlers: TAK, APRS, webhook, notification, MQTT (ca5c18d8)
- globalstar: add Globalstar satellite constellation as Reticulum interface (1e26f646)
- sms: add MQTT inbound subscriber for Android SMS relay (849bf661)
- dashboard: add Reticulum topology view with identity, routes, and stats (c056e6df)
- reticulum: add Astrocast and Globalstar satellite interfaces (eec3368f)
- reticulum: add packet relay with hop counting, loop prevention, and rate limiting (7028eb72)
- reticulum: add transport interfaces: Iridium, MQTT, Tor, WireGuard (9c24faad)
- reticulum: add global routing table with cost-aware path selection (d46deded)
- reticulum: add Hub identity generation, persistence, and API endpoint (5564c224)
- reticulum: add RNS wire-compatible packet format library (3f22847e)
- sms: decrypt inbound SMS using global or per-sender key (6d2cde70)
- crypto: add key import endpoint + hydrate global SMS key on startup (050df7fd)
- sms: Twilio SMS send from dashboard with compression + encryption (a1443a91)
- mt: Rock7 MT send with SMAZ2 compression + AES-256-GCM encryption (d3fdee5c)
- rock7: add Rock7 RockBLOCK MT sender: live tested OK,5037977 (bb2ae597)
- security: TLS certificate pinning + encryption architecture doc (a6fe6f27)
- astrocast: wire MSVQ-SC decoder for Android-compressed messages (467bf8ad)
- demo: MSVQ-SC decoder + email test endpoint + demo setup script (cc4e9717)
- scripts: add demo setup script for Hub provisioning (15e2ff33)
- compat: add protocol version byte support (aeeb4fbc)
- compat,security: Astrocast 1-byte fragment + MQTT mutual TLS (78b4a427)
- compat: add bridge GPS codec (0x50/0x44) + canned codebook (0xCA) (7f6ebae3)
- ipougrs: add experimental IP-over-satellite tunnel adapter (32e711df)
- codec: add pluggable sensor payload decoder framework (aa729f9e)
- sim: add MT delivery simulation, movement patterns, Astrocast base64 fix (7b3b7007)
- sim: add MeshSat device simulator and dev environment (ff87e818)
- routing: add route test endpoint and enhanced routing UI (9ec12d23)
- ui: add routing rule management view (a06bf640)
- routing: add SMS and Email as routing destinations (594de150)
- routing: add configurable message routing engine with default routes (b3b2439c)
- wg,tor: WireGuard auto-provisioning + Tor .onion API (546af07e)
- email: add PGP email gateway with key management API (75d3f902)
- sms: add SMS gateway and escalation notifier (06624235)
- astrocast: add MO webhook receiver for Astrocast satellite messages (313fa827)
- swagger: generate OpenAPI spec with swaggo and serve Swagger UI (2d837bb2)
- deadman: wire dead man’s switch to device heartbeats (8271adf3)
- crypto: wire E2E encryption + key management API (b1943c87)
- safety: wire fragment reassembly and SOS detection (67a7a377)
- integration: wire reassembler in testStack and add fragment reassembly test (101042e1)
- api: add strict JSON request validation, update roadmap and README (421342ef)
- ops: add Ansible playbooks, runbook, and updated README (44720544)
- cluster: add Galera cluster health monitoring + remediation UI (a26857cb)
- health: add Galera write-readiness probe for HAProxy health checks (cf1f7ff7)
- store: replace PostgreSQL with MariaDB Galera store layer (9bafd7cf)
- login: email/password login UI with token refresh + API token fallback (a6410faa)
- auth: add built-in user management with Argon2id + JWT sessions (a315327f)
- auth: add built-in user management with local accounts and JWT sessions (9eff8954)
- dashboard: add views for all v0.3-v1.0 features + Playwright e2e tests (ea00b1da)
- mptcp: add bandwidth monitoring, failover logic, and endpoint management (f64bf72e)
- hawkbit: add Eclipse hawkBit OTA integration + REST API (eec89e8d)
- mptcp: add MPTCP concentrator monitor + API endpoints (2f361e75)
- astrocast: add Astrocast Astronode S REST API client + constellation backend (bea2963c)
- geofence: add polygon geofencing engine + position MQTT subscriber (45783c22)
- position: extend position model + Douglas-Peucker simplification (2c7b977c)
- crypto: add AES-256-GCM encryption and per-device key management (eded0563)
- dedup: wire message deduplication into MO ingestion pipeline (5b619797)
- fragment: add SBD fragmentation and reassembly (8cde1a51)
- api: add notification preferences CRUD endpoints (93c5dc36)
- deadman: add per-device dead man’s switch with snooze (8cceed88)
- ntfy: add ntfy push notification client + multi-notifier fanout (1d4e4efb)
- apprise: add Apprise notification client for escalation alerts (a0926aa4)
- escalation: add SOS escalation chain engine with alert state machine (b509add0)
- security: add OWASP compliance testing for public-facing Hub URLs (5575c2e8)
- api: add security headers middleware (HSTS, CSP, X-Frame-Options) (77a2644e)
- security: add SAST/SCA pipeline stages and security-first documentation (59771830)
- k8s: add Kubernetes manifests and Helm chart (31057bf7)
- e2e: add post-deploy smoke tests and verify CI stage (76742eca)
- leader: implement Kubernetes Lease API leader election (391b15f3)
- cluster: add cluster-mode docker-compose with PG, Redis, NATS (b7e115c2)
- health: add active dependency probes to readyz endpoint (a544e9ad)
- web: auth UI with API key management and role-based nav (af82fca2)
- web: enhance device, message, and credit views with Tailwind (ff2169fd)
- ratelimit: per-device monthly budget caps alongside daily limits (28254585)
- audit: wire message_sent events and add audit dashboard (80f112ad)
- audit: tamper-evident hash-chain audit log with verification API (7cec84b3)
- device: per-device config versioning with history API (af8ef157)
- web: add Tailwind CSS, auth store, login view, router auth guard (40cbcfb6)
- auth: device API keys with RBAC enforcement (9ab7cc14)
- auth: tenant isolation layer with per-tenant data scoping (5a207f7d)
- auth: JWKS-based JWT signature verification for OIDC mode (fe0797bd)
- auth: OAuth2/OIDC + token auth middleware (3f33bcbb)
- Vue.js dashboard with position map, device registry, messages (db571856)
- wireguard: wg-easy API client + peer management REST endpoints (e21bc691)
- constellation: unified satellite send interface with routing strategies (3895244b)
- credits: Iridium credit balance polling + REST endpoint (567099ce)
- tri-mode wiring: all subscribers use bus.MessageBus, main.go mode switch (d45c444d)
- log mode on startup (fe860b4f)
- tri-mode foundation: MessageBus, Leader, PostgreSQL store, mode config (54a3aa4a)
- ratelimit,dedup: Limiter interface + Redis impl + idempotent dedup (eb2bd86d)
- store: SQLite storage layer: Store interface + device/message/webhook/position/audit CRUD (de8d8fb7)
- backup: full state export/import with diff preview (ea054da4)
- webhook: outbound webhook API with HMAC signing and retry (e6395b95)
- ratelimit: per-device token bucket rate limiting for MT sends (cd5e3d27)
- aprsis: Hub APRS-IS IGate: satellite positions on aprs.fi (ddfa2f22)
- tak: Hub TAK gateway: MQTT subscriber → CoT → OpenTAKServer (58852c6a)
- test: add integration test suite with embedded MQTT broker (792120e7)
- sbd: add RockBLOCK webhook handler and Cloudloop MT sender (143b0bd6)
- add MQTT client, topic namespace, and SMAZ2 compression (f9172d2f)
- infra: add Docker Compose stack with MQTT and Tor (b1dd28e0)
- initial repository setup (0d807efe)
Fixes
- web: 404 unknown paths and serve a real robots.txt (6068a6cd)
- web: house style in the Hub’s own copy (3c282a90)
- auth: pin the MeshSat brand to dark, so the CAPTCHA is not a white slab (f2d5e329)
- k8s: stop the relay’s own probes knocking on NATS and stunnel (7b0a99bf)
- k8s: the onion-heartbeat policy is a real rule now, not an empty list (619f3baf)
- rockblock: verify a Ground Control signature on every path, not just the platform one (ea93bc17)
- verify: name the registry pull secret on the browser job (7770bca0)
- verify: the build’s own import check was the thing that failed (6155f7e7)
- verify: install python3-venv before creating the venv (f7c0eaaf)
- verify: build the browser image, because upstream’s ships no playwright (7d36d6ef)
- auth: the owner can provision a bridge by QR again (609aebab)
- k8s: let ingress-nginx reach the basemap, so the map loads and shows streets again (6fc48b41)
- auth: the sign-up page no longer flashes authentik’s stock background (258a4931)
- nats: routes speak TLS from the internal CA, second attempt after the offline reproduction (ed17964c)
- monitoring: NATS auth alert counts both sides of a refused connection (58467654)
- nats: routes authenticate, with the credential in the route URLs (cb1092f0)
- nats: take route authorization out until it is reproduced offline (de56d3ad)
- nats: routes speak TLS with certificates from the cluster’s internal CA (22b98590)
- verify: the onion probe pod satisfies the restricted profile (3e1d87c8)
- ci: the IaC gate reads key names; the NATS route credential is an env reference (55a576a3)
- ci: the ZAP baseline scans the Hub authenticated, as a read-only probe tenant (4527db06)
- nats: routes authenticate; members roll fifteen seconds apart (c5afee56)
- api: bounded list limits, CSV injection neutralised, cookie Secure from config, audit on the credential surface (9ca59f86)
- verify: the nightly’s throwaway publisher pod satisfies the restricted profile (0c378eff)
- k8s: nats, keydb and the edge relay run as declared non-root users (fffb7431)
- audit: hash chain binds tenant, id and timestamp; appends serialised per tenant (66f72c36)
- k8s: the Hub could not complete a rollout: maxSurge 1 -> 0 (e762cd41)
- k8s: default-deny must be a k8s NetworkPolicy: two Cilium attempts did nothing (c65048da)
- k8s: the default-deny was a no-op: enableDefaultDeny is what turns it on (8cffbe0f)
- reticulum: bound the HDLC reassembly buffer (2e5160ed)
- monitoring: key the auth alerts on rejected credentials, not missing ones (05646eec)
- k8s: confine the Hub’s Postgres cluster (1d40cccc)
- k8s: confine the NATS listeners, including the cluster route port (6245411e)
- k8s: confine the Hub’s onion and Reticulum listeners (4989c433)
- k8s: bring stunnel and basemap up to the restricted profile (17cdfce3)
- k8s: drop capabilities and seal the root filesystem on the Hub (ad24b100)
- auth: require a role on every state-changing API route (7d28d5d9)
- sms: ask for a delivery receipt on every outbound message (27bacd79)
- booth: keep every message inside one SMS segment (a194296e)
- booth: deliver a mesh reply once, not once per replica (738535d5)
- booth: subscribe to a real wildcard, not a device named “+” (790f18a4)
- booth: one conversation per kit, and a token that survives the kit (772bd83d)
- tor: confine the onion pod and gate credential minting (b33085c9)
- onion: terminate MQTT with client-certificate verification (ed68f4aa)
- onion: serve Tor on its own listener and key it separately (a20e4d0e)
- sms: require a configured credential on the inbound webhook (c5e13fce)
- nats: a bridge credential can no longer read the Hub’s internal bus or another tenant (e4ec355c)
- sms: the Android inbound subscriber ignores the Hub’s own sms/inbound topic (26b299ca)
- api: a bridge command outlives the server’s 15 s write timeout (2ab93605)
- oob: correlate command replies across replicas and assemble multi-segment replies (b9025363)
- tor: the onion heartbeat is a warm Tor client, not a cold bootstrap every ten minutes (f41e73e1)
- verify: notify suite may read hub-config, and keeps the log tail its last check needs (688e6d2d)
- http: WebSocket upgrades survive the metrics and logging wrappers (4fd25bde)
- verify: notify suite reads the ConfigMap, not a startup line that scrolls away (3ab5a2a4)
- verify: the journey probe drops the exec power it never used (90ee36be)
- signups: a verification probe is approved without mailing anyone (6367ca4c)
- verify: restore run.sh, which the previous fix truncated to nothing (d2857df7)
- verify: the all-green run crashed before reporting (e9900216)
- verify: replica suite survives a rollout that swaps a pod mid-run (1dd6d5f2)
- verify: journey suite reads both replicas; MQTT broker field gets the public-address guard (a5f72530)
- verify: suites that assumed the runner or the Ko-fi era (e4e40d55)
- k8s: verify job pulls with the registry secret; image pinned by digest; Secret canary (14bafb54)
- k8s: verify suites ConfigMap without a name hash (36d9fa74)
- replicas: one send per MT and SMS request, one HeMB publish, one inbound SMS row (309bdb18)
- store: scoping ratchet parses files without the deprecated ParseDir (a5c83172)
- store: tenant-scoping ratchet; UpdateMessageStatus ignored its tenant (1b1fc1a9)
- k8s: onion heartbeat: DataDirectory tor owns (6d2ff619)
- k8s: onion heartbeat: tor needs a regular torrc, written to /tmp (04cf6a9f)
- k8s: onion heartbeat: tor must not read the image torrc on a read-only root (60f602f2)
- hawkbit: activate the postgres profile the way the image can actually hear (88a13ba2)
- scripts: the digest gate must also see no OLD pod left (b758eb63)
- store: a comment inside an applied migration is a text change (31b44479)
- hawkbit: OTA was non-functional since deploy; H2 2.x removed IDENTITY() (94f0175e)
- scripts: no grep -m1 under pipefail in the digest check (1b1b4215)
- devices: tell the second tenant whose IMEI it is, and keep the global key (dc695a38)
- bus: a broker blip at startup no longer silences a replica for good (d9fa3f45)
- integrations: retry the replica subscription instead of giving up once (853486cc)
- integrations: a saved provider account reaches both replicas at once (7ea3706b)
- api: rewrite the customer-facing capability messages (4bbda43d)
- tor: drop fsGroup, which made the onion key too readable to start (efcf0b93)
- tor,wireguard: the onion key survives losing the volume; tighten wg-easy (c05afb18)
- k8s: cap apprise’s worker pool; memory was never the problem (f1097a39)
- k8s: apprise needs more than 256Mi (8620450e)
- k8s: apprise creates its own user, so it cannot run with no capabilities (16f43fb4)
- k8s: stop overriding the tor image’s command (4159451a)
- wireguard: log in on demand, not once at startup (5169a0de)
- k8s: wg-easy needs root and NET_RAW, not the kernel modules (2f43d50e)
- k8s: hawkBit’s user property is a map keyed by username (d74dbdad)
- k8s: wg-easy and hawkBit name a storage class that exists (d4007e0b)
- email: persist PGP contacts and share them across replicas (a53346d6)
- config: always seal OOB frames, classify every setting’s owner (f75af5b0)
- aprsis: both replicas would transmit the same packet (1dfd4cc1)
- position: every position was stored twice (2613d96b)
- webhook: a customer’s endpoint was being POSTed to twice (54b18413)
- geo: one crossing, one page, on two replicas (666ff720)
- web: pick a geofence’s escalation chain, don’t type it (b41233a5)
- webhook: a created webhook could never be deleted, and had no timeout (1f957a1b)
- geo: a device could cross another tenant’s fence (66b7cb55)
- ratelimit: one tenant could lift another’s send budget (ba593ef9)
- webhook: every tenant’s events went to every tenant’s webhook (89e30369)
- deadman: the dead man’s switch was every tenant writing yours (7570e90c)
- security: gate the remaining platform-only routes (b7a2af97)
- security: stop the backup export leaking live credentials (ec50e232)
- escalation: let a tenant configure an SOS chain, and make it hold (d441c039)
- ots: own the httpx timeout, and test the icon patch (1cde8f9e)
- web: stop asking for credits once the answer is 404 (29c6b8f2)
- takfront: audit the one refusal that names a real client (4a59408e)
- tenancy: evict a purged tenant from every cache, on every replica (12747102)
- security: one-time provisioning material must not outlive its claim (a7c0e879)
- security: seal the Hub’s own long-lived keys at rest (d1b9525a)
- takfront: tell plaintext on the TAK port apart from a refused client (76a8d8f1)
- ots: guard close_connection, which is the call site that mattered (f33136aa)
- tak: the Hub asks for the identity name the CRD admits (9060252f)
- ots: stop the probe traceback flood, and time out every request (c4a5a82e)
- tak: give each instance an OTS account for the identity the front uses (41fbefa2)
- tak: quiet the probe WARN, and recover from a refused identity (fe4f505c)
- web: point Help at the docs, and offer the iTAK package (f4329c02)
- tak: give each Hub replica its own upstream identity (54081342)
- tak: switch the hosted TAK front back off until the identity race is fixed (8c7007e2)
- tak: let the Hub ask for its own upstream identity (8d3c4cc2)
- tak: reload the front’s server certificate when it is renewed (ebd7a21d)
- tak: put the front’s chain in the enrolment truststore, not the tenant CA (4889bc13)
- tak: point the operator at the icon-fix OTS image (a514bd8a)
- ots: load marker icons from the image instead of a blocked network fetch (0a239474)
- tak: point the operator at the CVE-clean OTS image (85dd05f1)
- tak: a TAK server that refuses the Hub no longer loses positions in silence (abced58a)
- ots: apply Debian security updates to the hosted TAK image (1a5c7caf)
- ci: the image pin comment names the build it pins (e3e10ebd)
- tak: change the administrator password from OpenTAKServer’s default (6b608ee0)
- ots: drop pip from the runtime image, it vendors a vulnerable msgpack and setuptools (cb5fa9c9)
- ots: patch the vulnerable dependencies OpenTAKServer pins, and gate branch builds on CVEs (b3980961)
- tak: only the platform’s own traffic reaches the operator’s TAK server and APRS-IS (d7cc3898)
- tak: federation refuses to start without its own CA, and is off until it has one (df767ed7)
- scripts: no early-exiting pipe reader survives pipefail (fd36a139)
- stripe: raising the pinned API version no longer breaks donations (1c57dad7)
- ci: the pin guard blocked every deploy with SIGPIPE (08a803f6)
- billing: the donation page stops arguing against its own ask (4dbed55e)
- metrics: the money alert should not wait for money to go missing (86a09fc9)
- scratchpad: a probe must not manufacture the alerts it helps prove (8d014fcb)
- stripe: only real money counts as an unattributed payment (b9725ff7)
- stripe: Subscribe stopped working for a day after the first attempt (11356f29)
- refunds: refunding a donation took a month off the customer’s subscription (678f1bdb)
- stripe: a refunded subscription issued no credit note (d6ad2fbc)
- invoiceninja: a tenant paying from a second address got no document at all (b349af90)
- stripe: a plan expired on a date the Hub invented, not the one Stripe bills on (50f9c01f)
- stripe: the first subscription payment took the money and issued no document (0871dda9)
- billing: a donation returned the giver to a sign-in wall and a subscription receipt (b4e582eb)
- mail: the plan-changed email described a cancellation that does not happen (f2780886)
- api: unattributed payments have been invisible since the Stripe migration (202e0b1c)
- billing: the Subscribe button 403’d for every real customer (0a99153b)
- routing: seeded fan-out routes listen to satellite channels only, SOS over SMS escalates, duplicate persist is not a warning (b54578e2)
- billing: the half of the Stripe wiring that never reached the commit (15ac6650)
- mqtt: a phone number as device id no longer puts an MQTT wildcard in a publish topic (9adf8b94)
- sms: a plain-text inbound SMS reaches the routing engine, so the kit to Hub to kit relay fires (bb91bd4b)
- refunds: the two drainers share a receipt row, and could both lose (d49c5037)
- refunds: record the refund before taking the paid period back (35158c34)
- invoiceninja: the inclusive-taxes guard was checking a URL that does not exist (aca87e36)
- vat: a donation is not a subscription and not the free plan (6f857291)
- billing: the tenant owner keeps owner, and a receipt is claimed before it is issued (9fb0ce33)
- enrollment: the flow title wrapped to an orphan and broke the card’s axis (280dfcf3)
- enrollment: the bootstrap never checked the template it repoints to (505e83ff)
- k8s: the grace period was shorter than the shutdown it has to allow (a2bb76ac)
- oidc: never discard the tenant this request just won (90468e33)
- billing: an operator-set plan could lapse, and a handover could double-issue (006d9d9e)
- oidc: two sign-ins for one new account left an orphaned tenant (b2c55941)
- tenant: a claim code shown to a customer was not always the stored one (c9624dc4)
- mail: state the exact moment a plan ends, not “tomorrow” (b0fac0d3)
- auth: an API key resolved no tenant, so every key answered 403 (e1379df0)
- enrollment: the first mail a customer gets was branded authentik (bf053231)
- enrollment: stop the bootstrap silently deleting the CAPTCHA (1ec47962)
- signups: approving a request could activate any account on the shared IdP (2546cc91)
- kofi: close four ways a subscription could be granted wrongly (24a90d70)
- auth: the enrollment hero was only half painting (fbfbfc2b)
- security: close what the IP allowlist was hiding, part 2 (73f719c4)
- security: close what the IP allowlist was hiding, part 1 (e80e072b)
- kofi: a retried delivery must not buy a second month (382a5b79)
- kofi: remember the payer, or every renewal loses the claim code (2e3f9e68)
- plans: a new tenant starts on free, not on the unlimited beta plan (98855aed)
- bridge: a birth cannot choose its own tenant (d3a1f4b6)
- auth: stop sending MeshSat alerts from another brand’s address (64a389ea)
- rockblock: actually verify the signature Ground Control sends (d28bc978)
- tenant: apply a suspension on every replica at once (a45bf7cb)
- signups: decode a real authentik user, not an imagined one (34e2961d)
- tenant: keep secrets out of the export (bc6537b1)
- webhook: stop the IP allowlist refusing a tenant’s own webhook (42a6f11a)
- k8s: give the platform tenant a RockBLOCK webhook secret (8689ba33)
- map: make the map labels readable on the brand background (5b9b080c)
- map: give the basemap server somewhere to write (e3aadba8)
- map: let the basemap build collect garbage instead of being killed (ebeae9ec)
- map: the deep basemap covers Greece too, where half the fleet is (2263d6c9)
- map: put the streets back, with a second deeper basemap archive (66e27783)
- cloudloop: subscribe to the satellite feed on the leader only (7d01130e)
- k8s: mount the Cloudloop MQTT client certificate, the Iridium feed was dead (25de8dde)
- edge: let provider callbacks through the beta gate (6a4fb575)
- k8s: roll the Hub one pod at a time, two eligible workers cannot host a surge pod (56ca4ec6)
- bus: MQTT client id per pod so two replicas do not replace each other’s broker session (4856ee4b)
- escalation: notify with the alert’s tenant in the context so SMS use the tenant’s Twilio account (1c8ac06e)
- ui: OTA and Email pages say the feature is not enabled on this Hub instead of an empty list (37d1e098)
- oob: bounded conversions for the stored peer id, role and replay window (gosec G115) (1246f5bc)
- ui: allow the OpenStreetMap apex host in the CSP, refresh the session once per burst, phone layouts for Messages and the Tenant panel (a38f2f61)
- webhook: store inbound messages in the device’s tenant, gate the email webhook and the wildcard Cloudloop allowlist (dac8940e)
- auth: refreshed sessions keep the platform-admin flag, IdP name updates the user row (40f6fa7c)
- auth: the Hub owner is the dedicated MeshSat identity admin@meshsat.net, not an omoikane account (ffe54fa3)
- cloudloop: key MT cost rows and mt/status by device IMEI, not the Cloudloop thing ID (c5b3a93a)
- ui: ship the rebuilt SPA bundle, keep the authentik card below the language switcher on phones (9375a4e8)
- ui: geofence map on OpenStreetMap tiles too, Credentials buttons on the brand tokens (34f4334d)
- ui: browser audit round 1 after the cutover (7638c02e)
- authentik: dark card container, hide the injected omoikane sky canvas (7a38c47d)
- nats: explicit permissions for the shared user so a reload does not crash nats-server (c3d051cb)
- ci: keep the deploy stage for bump_k8s_pin; lint fixes from the post-cutover MRs (083d7215)
- audit: verify the retained chain segment after retention purged its head (d4f39d80)
- store: audit chain verification read zero entries on mariadb and postgres (76471d9e)
- authentik: give the signup notification rule a destination (6fd1a9bf)
- authentik: report the enrollment email-verified marker in the email scope (5a35821c)
- k8s: NATS password variable, OAuth2 grant types, tolerant approval mail (d8a7734c)
- bus: redact broker credentials in logs and stop overriding the MQTT URL on k8s (752f48fc)
- deps: bump golang.org/x/crypto to v0.55.0 and x/net to v0.57.0 (bf7128c1)
- authentik: validate the Matrix ID from either prompt_data key shape (b69e59f2)
- k8s: authentik 2026.8 ClientType import, wrapper grep exit codes, phase 3 notes (d99dd635)
- k8s: edge auth backend health checks a static asset, not /-/health/live/ (4a65a9b0)
- k8s: relay health checks via the NATS monitor port (acf40bf7)
- security: annotate operator-path file access for gosec v2.29.0 G703 (394b4d50)
- security: inline path cleaning so gosec v2.29.0 sees the sanitizer (bf81240e)
- security: make the gosec HIGH gate real and resolve its 84 findings (c3c4910e)
- message: stable inbound message IDs, duplicate insert is a no-op (cc8173ad)
- auth: on-curve check via crypto/ecdh, lint cleanups (93d098cf)
- leader: compile the Lease elector unconditionally, never leader on error (29bfa759)
- deps: bump x/text to v0.39.0 and x/net to v0.55.0 (GO-2026-5970, GO-2026-5026) (a9d6511a)
- dbwrap: bounded driver-agnostic DB retry, fix int64 backoff overflow (dfa75004)
- cloudloop: resolver learns IMT things from live API shape + env seed (9f2ffbff)
- bus: fan out same-filter MQTT subscriptions: stop paho router clobbering (0094ecf9)
- security: drop deprecated middleware.RealIP: restore direct-peer RemoteAddr (5ec7d85e)
- deps: bump chi to v5.3.0: RealIP IP-spoofing CVEs (GO-2026-5774/5775) (ab85e561)
- tak,aprsis: stop bufio.Scanner busy-loop on idle TCP read timeout (176d4bff)
- ci: bump package job to docker:28 for runner daemon API 1.44 (ae5b6640)
- deps: bump cloudflare/circl to v1.6.3 (GO-2026-4550) (137be4c1)
- email: migrate openpgp to maintained ProtonMail fork (GO-2026-5932) (7fcf2f03)
- tak,aprsis: remove read deadlines that poison bufio.Scanner into a CPU spin (1d2f811e)
- cloudloop: tolerate subscriberCertus string-or-object (IFRNLLEI01PRD-906) (ded4cda9)
- main: drop directory group/policy route registrations (a0cf7e2a)
- directory: explicit errcheck on deferred rows.Close (7fb2b532)
- ui: nav dropdown menus hidden behind Leaflet map z-index (4f4c5a0d)
- galera: align garbd EVS timeouts with MariaDB data nodes (f40e0d43)
- galera: align garbd EVS timeouts with MariaDB data nodes (989f9f1f)
- galera: increase EVS timeouts for WAN SST, prevent seqno -1 auto-bootstrap (8845faff)
- galera: prevent garbd solo PRIMARY proto 127/127 poisoning (ef8fd9d4)
- galera: auto-bootstrap broken: parse node address from args, add retry path (13baf974)
- galera: WSREP 1047 retry, smart auto-bootstrap, UUID cleanup (7183bc33)
- ui: use bridge_id in BondGroupsView selector and E2E tests (3b35b631)
- db: add bond_groups table to MariaDB migrations (89c1fa02)
- e2e: use ESM imports and handle MQTT timeout in credential rotation test (d192612c)
- security: harden v1.2 channel APIs and mark v1.2 complete (c8fd8cdf)
- owasp: use exec-based ZAP setup with correct uid 1000 ownership (4aebf9ab)
- owasp: add required rule name column to ZAP baseline config (43688dcf)
- owasp: set world-readable permissions on ZAP config for non-root user (50b06af3)
- owasp: copy wrk directory structure into ZAP container (53110169)
- owasp: use docker create+cp instead of volume mounts for ZAP (d47b0587)
- owasp: resolve REPORT_DIR to absolute path for Docker bind mount (596a7e38)
- owasp: fix ZAP config mount and path traversal false positives (c158075c)
- tak: add errcheck nolint for OTS poller resp.Body.Close (c701ffd3)
- tak: start TAK client unconditionally, not in leader election (09901b02)
- tak: Hub IS a TAK gateway: show as always active, not disabled (bd59946f)
- galera: include garbd arbitrator in cluster address (802f120d)
- galera: hardcode cluster address in compose template (cfdcc539)
- galera: watchdog must use
--force-recreatefor Docker Compose v5 (4a15c3aa) - tak: remove unused wg field from federationPeer (9d8bba8f)
- tak: nolint directive for federation wg field (9b4ea2d5)
- tak: restore wg field for federation goroutine coordination (d11c5968)
- tak: lint: unused field, unchecked Close() returns (6668b36d)
- routing: skip matchFilter for SMS/email recipients + API key auth (001f9dfa)
- cloudloop: LingoIMT.MessageID as json.Number (was string) (a57b4871)
- sms: Hub SMS encrypt uses channel key (sms:*) first (dcd75e75)
- api: check json.Encode error (errcheck lint) (8a60b49d)
- api: commit device key distribute + rotate methods (a8a0673f)
- rockblock: accept unsigned webhooks when secret not configured (cd09593b)
- cloudloop: apply De Morgan’s law to satisfy staticcheck QF1001 (bf80be62)
- cloudloop: support wildcard (*) in webhook IP allowlist (cc4c548b)
- sms: remove undefined retSMSIface reference (6d30be07)
- hemb: backport generation cleanup from bridge E2E fixes (27e54a45)
- lint: check errcheck on rows.Close and AddSymbol returns (6591fae3)
- lint: check json.Unmarshal return in subscriber tests (f41da053)
- lint: check json.Unmarshal return in birthverify tests (41beeb1b)
- provision-qr: add auth refresh to fetchBlob + better error messages (d6ed65a3)
- auth: exempt QR provision claim from auth middleware (bf71f16b)
- csp: add blob: to img-src for QR provisioning + Playwright tests (18ce4000)
- lint: remove unused os import (e7aa0ea6)
- lint: use t.Setenv in security headers test (d3cae21e)
- security: address all audit findings: MPTCP injection, webhook auth, CORS, rate limiting (e18103f0)
- mqtt: parse credentials from broker URL for NATS auth (26e9cded)
- security: harden NATS auth, stunnel mTLS, Redis password, Galera UFW (3ac5f1f8)
- provision: two-step QR: short URL in QR, full bundle via claim endpoint (93f515e2)
- lint: check w.Write error return in QR provisioning handler (3f7a3c99)
- lint: use time.Until per staticcheck S1024 (a3dd1ec6)
- lint: resolve errcheck, staticcheck, unused lint findings (216cb2fd)
- deploy: HAProxy SNI routing for Reticulum TCP + stunnel Docker fix (d4aaf125)
- reticulum: RNS-compatible random_hash + wire compat test + TCP debug logging (c0255c61)
- bridge: retry on Galera deadlock during bridge birth registration (ae61eeca)
- store: include credential columns in GetBridge and ListBridges (05f204ee)
- bridge: health messages re-set online status after reaper timeout (7413cf88)
- galera: eliminate all bare gcomm:// writes to .env (87f7475c)
- nats: prevent leaf node loop by restricting remotes to spoke nodes (67e55000)
- fleet: clear retained MQTT messages on bridge delete (ae4a36c5)
- test: add GetSystemConfig to mock store for credential test (d0c5c6c4)
- docker: bundle MSVQ-SC codebook assets in container image (37c9aae0)
- infra: add hub service to Galera compose + fleet Playwright tests (0787a054)
- lint: handle errcheck warnings in credentials, mariadb, sqlite stores (d9196474)
- galera: add garbd Dockerfile and fix
--optionsyntax (053c691d) - galera: enforce cluster_size=3 with garbd arbitrator (8fe35ebb)
- galera: add garbd arbitrator, entrypoint wrapper, and pc.recovery for self-healing (e95c634b)
- bridge: ignore stale retained birth messages on Hub restart (47400da5)
- bridge: add reaper to mark stale bridges offline (ff5a348a)
- hub: escalation engine now respects tenant isolation in alert processing (f80003ee)
- hub: WebSocket auth: require token, fix CheckOrigin to same-origin (be9b7836)
- cloudloop: add debug fields to no-IMEI warning for test messages (096f95d8)
- dashboard: Hub status shows ‘OK’ in uppercase (57da4dfd)
- store: MariaDB JSON columns reject empty strings, use ‘{}’ fallback (5cb64aa3)
- store: MariaDB NOT NULL columns missing DEFAULT values (b2e6d604)
- bridge: staticcheck SA9004 lint error in satdecoder const group (ebd8d09b)
- ui: map gap, delete confirmations, message chart, constellations (c5edc2f3)
- ui: add fullscreen background logo matching Bridge (cce267a4)
- ui: single-row header, compact status bar, watermark empty states (3b3acd19)
- ui: unify Hub design tokens with Bridge codebase (7dd99836)
- owasp: remove python3 dependency, accept SAMEORIGIN from nginx (85768013)
- ci: use docker exec for healthz checks (port not exposed to host) (eb500197)
- ci: add DEPLOY_SSH_KEY, use IPs for DMZ hosts, fix pre-deploy SSH (a3088514)
- dashboard: unify all 19 views with consistent design language and TAK color scheme (641b8bc5)
- deploy: fix SSH user in health check, use container exec for Ansible healthcheck (13042b73)
- galera: pass wsrep settings via command-line args, fix file permission issue (3c23df73)
- sms: persist inbound SMS to messages table + show in dashboard (30df4963)
- auth: exempt all /api/webhook/* paths from auth (Twilio/Astrocast/email can’t send bearer tokens) (e7fa8cab)
- sms: use base64 encoding for encrypted SMS (matches Android AesGcmCrypto) (d623d0b4)
- sms: auto-generate encryption key on first use, force compress when checked (1284a5aa)
- core: persist MO messages directly in webhook handler (bypass MQTT loop-back) (96f425af)
- ci: replace AWX deploy with direct SSH: AWX was restarting MariaDB (4183fb17)
- auth: add OIDC cert pinning support + gitignore sim binary (2cf15cc1)
- core: add message persistence subscriber: MO messages now saved to DB (afc4d499)
- ci: swagger job validates generation succeeds, skip diff check (non-deterministic across Go versions) (497698fe)
- ci: compare only swagger.json not yaml (yaml formatting varies by Go version) (59a44e5a)
- ci: add go mod download before swag init (4ad73ffb)
- ci: pin swag version to v1.16.4 for reproducible swagger generation (b41d0e40)
- ci: install git in swagger CI job, regenerate spec (dfca15fb)
- codec: fix GPSDecoder magic byte (0x47→0xA5), add bridge format unit tests (61263eb3)
- security: use constant-time comparison for webhook JWT verification (e42c8bd4)
- test: use larger fragment test message to match MinFragmentPayload=100 (71bafacc)
- ui: standardize all timestamps to UTC 24h format (e9703de9)
- deploy: use
--no-depsto prevent MariaDB restart on Hub deploy (3804f253) - cluster: make /api/cluster/node auth-exempt for peer health queries (eca7dd58)
- mariadb: handle NULL datetime columns with sql.NullTime + add garbd Dockerfile (e13f5553)
- mqtt: retry initial MQTT connection with backoff (73ab36b2)
- mqtt: remove NATS auth username, use anonymous MQTT connections (36f713b7)
- mqtt: add NATS MQTT adapter auth username for cluster mode (893c8725)
- auth: add legacy token fallback in local auth mode (c6042924)
- dashboard: fix healthz path, CSP for map tiles, improve settings page (01d1220b)
- store: add NotificationPref type/methods missing from prior commits (46212c58)
- main: bounded worker for API key last_used + remove dead config field (2e186802)
- bus: replay MQTT subscriptions on broker reconnect (e8bcd7d0)
- health: use live probe for MQTT instead of one-shot check (f8dbeb9a)
- bus: set MQTT 3.1.1 protocol version for NATS compatibility (20318820)
- web: validate login token against authenticated endpoint (3d189406)
- lint: handle errcheck on fmt.Fprintf in auth error writer (aa1543f5)
- lint: replace nil context with context.Background in auth test (84be36e2)
- dedup: use SET NX PX instead of deprecated SetNX (0790d8f5)
- last errcheck in cloudloop client (23ae6a15)
- resolve 21 golangci-lint v2 issues across 7 files (bda3f320)
- ci: install golangci-lint on go1.25 image (df5ab61d)
- ci: merge duplicate variables blocks (d954b4bb)
- aprsis: handle SetReadDeadline error return values (8dff59c6)
- resolve golangci-lint errcheck and unused warnings (1cade3a8)
Performance
- store: migrate the sqlite conformance template once and copy it per sub-test (ecb2e93a)
- bridge: only the Lease holder writes a health report to the store (9ed866a5)
- bridge: a health report is one UPDATE, not four queued on the same row (5fd27a99)
- ci: test databases skip fsync; the fixed cost was never Argon2 (79dbcf5b)