github LanternOps/breeze v0.110.0

latest releases: v0.115.0, v0.114.0, v0.113.0...
16 days ago

Breeze RMM v0.110.0 — restart prompts end users can postpone, contracts billed by device set, QuickBooks payments, and a customer portal that shows customers what they are paying for.

This is another large one: ~270 commits, ~1,900 files and 54 migrations since v0.109.0. Six multi-wave programs land together (reboot deferral, device-set billing, QuickBooks Phase C/D, portal visibility, AI budget alerts, AI agent graduation and impact), plus a 52-row pre-release browser sweep that found and fixed seven defects before the tag, and a real-endpoint check of the restart prompt on Windows 11, Ubuntu and macOS. Read the upgrade notes before you pull: the migration set is large but every index on a big table is built CONCURRENTLY, and there is no breaking change.

Summary

  • End-user restart prompts with deferral — when a patch job needs a reboot, the signed-in user gets a native dialog (Windows, macOS, and now Linux via zenity) and can postpone it a policy-set number of times within a deadline. The device page shows the scheduled restart and the postponement count. Off by default (#4686, #4699, #4728, #4733, #4737).
  • Device-set billing — contract lines billed by device role or device group, with included quantities and overage, editable lines, per-device billing evidence on every generated invoice, an optional "Billed devices" PDF appendix, and quote lines priced by device set (#4585, #4694, #4745, #4808, #4731, #4854, #4838, #4901).
  • QuickBooks Online Phase C + D — push issued invoices to QuickBooks and pull payments back automatically via webhook plus a 15-minute reconcile sweep (#4492, #4531, #4800, #4795, #4827).
  • Customer portal visibility — Dashboard, Security, Backups, Devices (with CSV export), Tickets with SLA badges and support usage, and Reports, each behind a per-organization toggle that fails closed server-side (#4626, #4627, #4647, #4740, #4744, #4749, #4750, #4734, #4804, #4815, #4836, #4850).
  • Script cancellation foundation — the cancellation schema and the script_cancel agent command are registered, and every execution and automation-run status now has a badge (#4788, #4799). The Cancel button itself lands in a later release.
  • AI budget alerts and AI agent graduation — threshold ladder with email delivery and partner fan-out, credit balance on the usage page; graduation evidence ledger, partner-key ceiling, four-eyes promote, an AI Impact page with estimated time saved, and a platform-admin AI kill switch (#4487, #4496, #4517, #4529, #4490, #4533, #4565, #4486, #4504, #4704).
  • Scripts — selectable run context (System or logged-in user) everywhere, write device custom fields from script output, clone a script, and "Run again" from a past execution (#4906, #4692, #4722, #4781, #4897, #4895).
  • Partner trust probation (hosted only) — new self-serve partners on the hosted service start in probation until a settled card payment ages or an admin approves them. Inert on self-hosted installs (#4567, #4588, #4599, #4603, #4602, #4604).

Added

  • Patching — reboot deferral policy settings on the Patch tab (#4686); agent deferral state machine (#4699); Windows and macOS prompt via the desktop helper (#4728); Linux prompt drawn by the daemon as the signed-in user (#4733); scheduled-restart status on the heartbeat and console badges (#4737); unrated patches get an Unrated badge and an explicit opt-in before auto-approval, plus a dashboard callout (#4721, #4787, #4964, #4966).
  • Billing — per-device-role lines (#4585), per-device-group lines (#4694), in-place line editing with audit events (#4745), included quantity and overage (#4808), device coverage lookup with deep links from coverage warnings (#4731), device-set quote lines with server-derived quantities and acceptance hash v2 (#4854), billing evidence per invoice (#4838, #4901), change-currency dialog for draft quotes and invoices (#4802), Billing entry in the Settings sidebar (#4636).
  • Accounting — QuickBooks invoice push (#4492) and payment pull-back (#4531); reconcile skip reasons surfaced (#4795); voided or remotely deleted invoices refuse a push (#4800).
  • Portal — visibility read model and toggles (#4627, #4647), dashboard (#4740), security overview and per-device protection (#4744), backups (#4749), device enrichment columns and CSV (#4734), ticket SLA and support usage (#4750), report foundation and Reports tab with PDF/CSV (#4804, #4815), Guest Ledger design pass (#4850).
  • Devices — external device links (device_external_links) with serial/hostname indexes and a device resolver, groundwork for importing custom fields from another RMM (#3257 W06, #4973); custom-field values validated on both shipped write paths (#4917).
  • Contacts (#3258 follow-ups) — CRUD and CSV import with a real add_contact AI tool (#4530), Contacts tab and migration guide (#4546), inbound email requester → contact and portal ownership by contact (#4557), contact linking on Entra and Outlook add-in logins (#4594), invite warning when a contact could not be linked (#4590).
  • Scripts — run context selectable and visible everywhere (#4906); custom-field write-back from script output (#4692, #4722, #4781); clone/duplicate (#4897); Run again + post-run navigation (#4895); UDF help tooltip (#4703); cancellation schema and script_cancel command registration — no UI yet (#4799).
  • AI — budget alert thresholds, recipients and delivery worker (#4487, #4496, #4517), partner credit balance on the usage page (#4529); graduation evidence ledger and state machine with four-eyes promote (#4490, #4533), graduation panel with "Approve and always allow" (#4565), impact accounting and the AI Impact page (#4486, #4504), platform-admin kill switch UI (#4704), triage skip reasons and drifted-card deselect (#4691), ai.agent.circuit.opened event (#4697), rename AI Workspace chat tabs (#4702), feedbackBy on alert verdict badges (#4705).
  • Auth — org-level Enforce SSO surfaced on the login page (#4860); server-verified platform attestation for L4 authenticators, fail closed (#4724), challenge/verify mobile registration with ES256 (#4793), and an Apple App Attest verifier for iOS approver-key registration (#4968).
  • Mobile — native FCM push on Android with a server-side sender (#4723, #4780); New ticket screen (#4560); Reply/Internal composer toggle (#4536); iPhone-only shipping and App Review layout pass (#4508, #4534, #4541).
  • Partner APIPOST /partner-api/v1/enrollment-keys (#2826).
  • Self-hosting — internal-CA option for domains Let's Encrypt cannot reach, with an explained ERR_SSL_PROTOCOL_ERROR (#4479); CADDY_LOCAL_CERTS wiring backfilled into the release-pinned templates (#4564).
  • Ops — retention/prune for ticket_outbox, intent_outbox and metric_anomaly_incidents (#4400); audit-log retention settings route and UI plus a default policy for new organizations (#4825, #4900); bounded nightly audit-chain verification (#4548); worker /metrics in the Prometheus scrape config (#4689).

Improved

  • Performance — device activity feed uses an RLS-promotable two-arm predicate with partial indexes on audit_logs (#4835); metric rollups roll each source in one pass (#4419); the Activities-tab audit count is capped at 10,000 (#4842); act-verify reads no longer pin a pooled connection across device round-trips (#4849); software-inventory ingest bounds its lock wait (#4411).
  • Devices — Patches tab links to the device's patch policy (#4701); org name in the breadcrumb links to org settings (#4695); an archiving org stays visible for its whole drain window (#4869); the permanent-delete undo toast names the device and surfaces the agent-unreachable warning (#4394, #4938).
  • Remote tools — the Remote Tools tab persists in the URL hash and the terminal fills the viewport (#4641); Connect Desktop no longer revokes every live remote session (#4832); Caps Lock is synced instead of forwarded as a key (#4864); a cold Terminal mount survives a failed xterm stylesheet preload (#4893).
  • Agent — Homebrew upgrade batches run brew cleanup --prune=all (#4914); upload state advances only after confirmed delivery (#4859); secrets.yaml rename retries on a transient lock (#4840); the macOS updater and helper install stop churning the code-signing identity (#4862); helper session ids no longer embed host identity and value shapes are scrubbed from logs (#4876); Windows 10 / Server 2016 declared as the OS floor and the install one-liner forces TLS 1.2 (#4688, #4730).
  • Extensions — asset URLs are signed so dynamic import() can load them (#4870).
  • Alerts — suppressed and dismissed alerts skip baseline notification fan-out (#4684).
  • Quotes — quote emails send after the transaction commits with bounded mail transports (#4856); draft values reseed during render (#4819); the line grid no longer scrolls at 1280px (#4698); a failed quiet refetch no longer strands a new line (#4397).

Fixed

  • Tenancy / GDPR — device org-move now re-stamps or severs ticket_email_links, ticket_outbox, ticket_comments.agent_run_id, action_intents.scope_ticket_id, device_vulnerabilities.ticket_id and ai_agent_runs.ticket_id (#4736, #4786, #4821, #4831, #4833, #4642); org erasure detaches restore commands (#4898); device_group_memberships is pinned to its parents' org (#4857); composite tenant FKs on time_entries / ticket_parts (#4726); org merge honours the canonical ticket lock order (#4823, #4741).
  • RLS / scope — forced parent-join RLS for script_versions / script_to_tags (#4493); site scope enforced on maintenance window and DR plan mutations (#4848, #4855); policy compliance reads narrowed to the caller's site allowlist (#4899); partner-level config-policy assignments honoured in warranty and event-log retention (#4846); the partner-wide system-context escalations were removed (#4803); five earlier migrations that wrote rows without system scope are replayed correctly and a contract test now blocks new ones (#4926, #4866).
  • Auth — a rejected MFA proof or password is a 400 with a stable code, not a 401 that logs you out (#4651, #4739); the caller's session is re-issued when recovery codes rotate (#4646); current-password guesses are rate-limited on change-password and the portal (#4782, #4820); the SSO failure notice outlasts the refresh eviction (#4853).
  • Patching — stranded pending device_patches rows are flipped by version compare (#4843); three silent-failure paths in patch dispatch and desktop finalization (#4845); per-patch status recorded instead of the batch result (#4407).
  • Scripts — approved content and execution results are preserved (#4904); parameters are delivered on the run-as-user helper path (#4891); execution history no longer shows an epoch-0 start time or a dead duration column (#4938).
  • Activity logagent.command.* rows are no longer stamped success at dispatch, and the console renders a dispatched command as "Dispatched" rather than "Success" (#4405, #4938).
  • Billing — interactive contract-line device evidence is recorded (#4901); an error mapping row persists on a currency_mismatch push (#4827); the draft-invoice device appendix opens instead of bouncing to the Editor tab (#4938); WebP images are rejected at the upload boundary for quotes and catalog items (#4408, #4639).
  • Backup — device backup workloads are serialised per device (#4925); dispatch no longer duplicates child job rows on retry (#4852); an empty watch list is delivered instead of null (#4839).
  • Admin — the partner trust queue no longer 500s on an ambiguous column (#4930); the trust queue and AI kill switch pages require platform admin.
  • AI — the session allowlist is checked against the exposed MCP tool name (#4892); unknown onlyTools names error instead of being dropped (#4796); actively-processing sessions are never evicted (#4406, #4742); auto-demote always notifies (#4640).
  • Portal — raw-SQL timestamps that 500ed Devices and Backups (#4836); dev module graph served under /portal so islands hydrate (#4540).
  • Mobile — token refresh on every 401 (#4568, #4589), require-cycle fix (#4579), app-lock privacy cover cleared after sign-in (#4727), ApiError is a real Error subclass (#4779), approval takeover, chat hang, ticket keyboard and upload error surfacing from iOS testing (#4916).
  • Integrations — Umbrella reporting API receives epoch milliseconds (#4637); the M365 JWK secret file defaults to a placeholder instead of /dev/null (#4844).
  • Plus ~30 further fixes across CI, tests and internal tooling.

Security

  • Self-hosters are encouraged to upgrade. Hardening in this range: the RLS-bypassing partner read in the remote-access launcher is a real, audited escalation (#4847); UPDATE/DELETE/TRUNCATE are re-revoked on append-only tables missing from ensureAppRole and on peripheral_policy_delivery_events (#4789, #4826); DATABASE_URL no longer appears on pg_dump/pg_restore/psql argv (#4635); a stale tickets.assigned_team column is dropped (#4865); PAM rules pinned to an unreachable risk tier are flagged (#4877); helper session ids no longer leak host identity (#4876); password-guess rate limits on change-password and the portal (#4782, #4820).
  • No advisories are pending for this range.

Self-Hosting / Upgrade Notes

No breaking changes. No new required environment variables.

Upgrade command — bump BREEZE_VERSION in /opt/breeze/.env, then:

docker compose pull api web portal && docker compose up -d binaries-init api web portal

(portal is a separate container — include it. If you have opted into the worker split, add worker.)

Database — 54 migrations. Take a snapshot first. All are idempotent and auto-apply on API boot via autoMigrate (unless AUTO_MIGRATE=false). They add the reboot-deferral, device-set billing, billing-evidence, QuickBooks, portal-visibility, cancellation, graduation, trust-probation, contact and device-external-link columns and tables; 2026-10-10-100000-device-external-links.sql also adds two plain (non-concurrent) indexes on devices for serial number and hostname, which is a brief write block proportional to your device count. Every index on a table that can be large is built CONCURRENTLY through the no-transaction lane (audit_logs device-feed partial indexes, outbox retention indexes, invoice_lines / contract_billing_periods unique indexes), so writes are not blocked; an interrupted build leaves an INVALID index that the migration reports loudly on the next boot (recovery: DROP INDEX CONCURRENTLY <name> and restart). Backfills that touch existing rows run under breeze.scope=system and log their row counts as Postgres WARNINGs — two to read after the first boot:

  • cleaned N contract_lines rows whose site belonged to another org (2026-10-05-100100-contract-lines-device-roles.sql): if N > 0, re-scope those lines in the contract editor before the next billing run.
  • The site-name stamp on contract_lines (2026-10-08-101400-contract-lines-site-stamp.sql) records how many existing site-scoped lines were protected; a line whose site was deleted before this release stays ambiguous and keeps billing organization-wide until re-scoped (see behaviour changes).
  • 2026-10-09-000600-rls-scoped-replay-v0110.sql replays five earlier migrations whose row-writing statements ran without system scope; expect its rls-scoped replay warnings with real counts.

Privileged statements were audited against a non-superuser role: the range creates SECURITY DEFINER trigger functions (owned by the migrating role) and nothing else privileged — no CREATE ROLE, ALTER … OWNER TO, CREATE EXTENSION, or schema GRANTs.

New optional environment variables (missing = feature off or default; never fail boot; map any you set in the api compose environment: block, not just .env):

  • QBO_WEBHOOK_VERIFIER_TOKEN — Intuit app verifier token for POST /api/v1/webhooks/quickbooks. Without it the endpoint answers 503 and payment pull-back relies on the 15-minute accounting-reconcile sweep alone (the sweep is the guaranteed path either way).
  • FIREBASE_SERVICE_ACCOUNT — service-account JSON for native Android push; unset = no FCM sends.
  • BREEZE_AUTHENTICATOR_ATTESTATION_ENFORCED — require server-verified platform attestation before a mobile authenticator counts as L4. Defaults to on (an empty or unrecognised value keeps enforcement on; set false to relax it).
  • AUDIT_CHAIN_VERIFY_MODE (incremental, default) and AUDIT_CHAIN_VERIFY_RESCAN_SLICES — nightly audit-chain verification is now bounded: incremental from the last anchor plus a rolling 30-night re-scan, instead of a full-table walk. AUDIT_CHAIN_VERIFY_ENABLED=false now stops an in-flight sweep, not just the schedule.
  • TICKET_OUTBOX_RETENTION_DAYS, INTENT_OUTBOX_RETENTION_DAYS, METRIC_ANOMALY_INCIDENT_RETENTION_DAYS (default 14) with *_BATCH_SIZE / *_MAX_BATCHES knobs.
  • PORTAL_STATE_BACKEND=redis — portal rate-limit state; defaults to Redis in production already.
  • PARTNER_TRUST_MODE, IP_CLASSIFY_PROVIDER, IP_CLASSIFY_API_KEY, TRUST_ACTION_TOKEN_SECRET, PARTNER_MEETING_URL — hosted-only; PARTNER_TRUST_MODE resolves to off unless IS_HOSTED=true regardless of value, and with it off no trust code path touches the database, Redis or the network.

New workers and routes. One more BullMQ worker (accounting-reconcile, 15-minute sweep); abuse-signals-sweep moves from hourly to every 15 minutes; new routes include POST /api/v1/webhooks/quickbooks, GET /api/v1/devices/:id/billing (partner or system scope, needs both devices:read and contracts:read), POST /partner-api/v1/enrollment-keys, the audit-retention settings route, and the admin trust queue.

Behaviour changes

  • Reboot deferral ships off. Enable per policy on the Patch tab ("Let users postpone the restart"); the deadline is the guarantee, the postponement counter is a UX budget. Linux prompts need zenity on the endpoint; without it the user still gets a notify-send warning but cannot postpone.
  • Deleting a site used by a site-scoped contract line now fails invoice generation loudly with 409 SITE_DELETED instead of silently billing every device in the organization (#4693). Re-scope or remove the line, then generate again.
  • Contract line APIDELETE …/lines/:lineId returns 404 LINE_NOT_FOUND for a missing line (was a silent 200) and {"data":{"ok":true}} on success; unitPrice / manualQuantity / sortOrder bounds and catalogItemId validation now apply on create (400 instead of 500); adding a line with a site from another organization is 400 SITE_NOT_IN_ORG; generated invoice lines use deterministic (sortOrder, createdAt, id) ordering.
  • PATCH /quotes/:id/lines/:lineId returns 400 for an unrecognised key instead of accepting the request and changing nothing.
  • Audit retention now runs for every organization. Previously the job pruned per policy row and nothing ever created one, so it never deleted anything. New organizations get a default policy row via a trigger; existing organizations still have no row until an operator saves one under Organization settings → Audit retention (new audit:manage permission). Nothing is deleted until you do.
  • New partners get inbound email-to-ticket off on every creation path (#4638); existing partners unchanged.
  • Windows 10 / Server 2016 is the declared agent OS floor (#4688); the install one-liner forces TLS 1.2 (#4730).
  • Unrated patches are never coerced to Low and are excluded from auto-approval unless a ring opts in (#4721, #4787).
  • AI agents, act mode, budget alerts and the kill switch remain off by default; BREEZE_AI_AGENTS_ENABLED still gates the whole subsystem.
  • QuickBooks payment pull-back is on per connection (pull_payments, default on) once a connection exists; there is no connection until you complete the OAuth flow.

Full Changelog: v0.109.0...v0.110.0


What's Changed

New Contributors

Full Changelog: v0.109.0...v0.110.0

Don't miss a new breeze release

NewReleases is sending notifications on new releases.