Skip to content

Version history

Monika edited this page Aug 21, 2026 · 3 revisions

Version history

What each release actually gave you, newest first. Downloads live on the releases page and, for everything current, the signed repository.

The whole project is seven weeks old at the time of writing — it started on 4 July 2026 — which is why the version numbers move quickly.

3.2.0 21 Aug 2026 one-shot timers, the panel signs its requests, OPNsense, containers rebuilt
3.1.0 18 Aug 2026 live web panel (SSE), programs and Nerd in the browser
3.0.5 16 Aug 2026 GET /api/system — the whole deployment, introspected
3.0.2 13 Aug 2026 the lockout postmortem: auth logging, 401 reason codes, fail2ban fixes
3.0.1 23 Jul 2026 first stable 3.0; FreeBSD and NetBSD packages
3.0.0 21–22 Jul 2026 Ed25519 request signing, SSE push, scan-to-add, capabilities, metrics
2.6.1 10 Jul 2026 SELinux label fix — writes silently denied on RHEL-family
2.6.0 10 Jul 2026 diag / approve / devices / revoke built into the binary
2.5.0 10 Jul 2026 no Python needed: self-contained native packages for 15+ distros
2.4.3 7 Jul 2026 config.json 640, so the admin CLIs work without sudo
2.4.2 7 Jul 2026 web panel refresh, six colour palettes, build-info footer
2.4.1 6 Jul 2026 web panel brought back to parity with the app
2.4.0 6 Jul 2026 batch state, /version, /health, delete-unit, brotli
2.3.0 5 Jul 2026 first-class Windows: installer, service, Caddy wizard
2.2.1 5 Jul 2026 non-systemd inits and musl hosts
2.2.0 5 Jul 2026 authenticated config API — rename and add units remotely
1.0.0 4 Jul 2026 the first working thing

3.2.0 — 21 August 2026

Five pieces of work:

  • One-shot timers. "Turn this off in 45 minutes", server-side, so it happens with the phone away or flat. Its own subsystem rather than a program kind, because a timer exists to happen once and then stop existing — Timers. The Android app drives it from an hourglass beside the power switch.

  • The web panel signs its requests with Ed25519 (auth v2), the last client that did not. The private key is non-extractable and lives in IndexedDB, so the page cannot read it back. A server set to AC_MIN_AUTH_VERSION=2 can now refuse unsigned clients without refusing its own panel.

  • OPNsense is a tier-1 port. The os-breeze-core plugin — a page under Services, service control through configd, an rc script — with the whole Python runtime vendored, because OPNsense is FreeBSD:14:amd64 with python311 and neither rust nor pip. Installing on OPNsense.

  • The container images were rebuilt from scratch, five of them, each named for what it is: Alpine Edge (musl, x86_64 and arm64, self-updating), UBI 9 (glibc) at two x86-64 psABI levels, and one with nginx bundled so HTTPS works on first start. That work also fixed a real bug: no image had a timezone database, so TZ was silently ignored and every schedule fired on UTC. Installing with containers.

  • The documentation moved here. The README was 2,000 words and docs/ another 25,000; this wiki replaced both with something organised.

3.1.0 — 18 August 2026

Live web UI, and programs in the browser.

The panel had been polling every five seconds. It now consumes the SSE stream the server has published since 3.0.0-pre2 — the same one the phone app uses — so state is live, and a backgrounded tab closes the stream entirely, which stops it causing any LAN traffic to the air conditioners. Polling stays as a documented fallback for an older server or a proxy that buffers.

Three additions brought the panel closer to the app: programs (favourites, schedules and curves — view, apply, enable/disable, delete, and save a favourite from a unit's current state), Nerd (everything /api/system knows, rendered generically so new server facts appear without a UI change), and a per-browser beep toggle, off by default so a 2 a.m. schedule stays silent.

riscv64 deliberately stayed at 3.0.5: its bundle is a ~2-hour emulated build, and packages labelled 3.1.0 for riscv64 were built and discarded rather than shipped with a 3.0.5 binary inside.

3.0.5 — 16 August 2026

GET /api/system — everything this deployment knows about itself. One endpoint answering "what am I actually running?", behind the app's Nerd screen: host (distro, kernel, init system, CPU, byte order, libc, uptime), runtime (Python, and the version of every dependency), the deployment itself (build commit, install date, timezone, the paths and file modes of every store, the effective settings), the units, the enrolled devices, and this connection — the IP the server sees you as, whether that reads as private, and the proxy headers, which is the fastest way to diagnose a proxy that is not forwarding the real client address.

It needs the API key and a device credential, the same bar as controlling a unit, and it contains no secrets — a test greps the raw response to keep it that way.

Also: OpenBSD reports kern.boottime as a bare epoch where the other BSDs print a struct, found by running the probes on the actual VMs rather than assuming "the BSDs" agree.

3.0.2 — 13 August 2026

A reliability release written out of a real lockout, in which several users lost access for days and nothing in the log said why.

  • Authentication failures are now logged at all. Previously there was no auth logging — only uvicorn's bare 401 Unauthorized lines. Every failure now records a reason code, the client IP and an 8-character key-id hint, never a secret.
  • 401s say why, so a client can tell transient from fatal. A phone whose clock had drifted past the ±60 s signature window produced a 401 indistinguishable from a revoked credential — so the app concluded its credential was dead, deleted its Ed25519 private key, and with LAN-only enrolment stranded anyone away from home. Failures now carry a stable error code and a retryable flag, and a clock_skew rejection returns server_time so a client can measure its offset and re-sign instead of re-pairing.
  • The shipped fail2ban configs were banning legitimate users. The filter counted 400|401|403|404|405|422|429 — but 404 is a client feature-detecting endpoints, 422 is input validation, and 429 is our own rate limiter — at 5 hits per 10 minutes, escalating to a five-week ban, and the tripwire banned for a week on a single 403. Since clients behind NAT share one address, a ban took everyone on that connection offline at once.

riscv64 arrived here too, as the first architecture that compiles rather than unpacks: no PyPI wheels exist for it, so every dependency builds from source under emulation.

3.0.1 — 23 July 2026

The first stable of the 3.0 line, and BSD packages: FreeBSD and NetBSD install from a private virtualenv, since the Linux self-contained bundles cannot run on a BSD kernel. Verified end-to-end on real FreeBSD 15.1 and NetBSD 10.1 — installer, rc.d service and health check — which caught an rc.netbsd bug where the pidfile and log were written as the unprivileged user.

3.0.0 — 21–22 July 2026 (prereleases)

Two majors of work, shipped stable as 3.0.1.

  • Ed25519 request signing (auth v2). Every request is signed with a key generated on the device that never leaves it; the server stores only the public key, so a server compromise leaks nothing forgeable. Each signature binds method + path + timestamp + nonce + SHA3-512 of the body, so replay and tampering both fail. Legacy bearer tokens keep working and upgrade in place; AC_MIN_AUTH_VERSION=2 refuses them. See Signed auth v2 migration.
  • Live push over SSEGET /api/units/stream, polling the units centrally and only while a client is connected.
  • Scan-to-addGET /api/units/scan finds units by their open ports across the server's private subnet, so nobody has to type IP addresses.
  • Per-unit capabilities, so clients hide controls the hardware lacks; whoami; state history; and a Prometheus /metrics endpoint served from last-known state so scrapes cannot hammer the units.
  • Opt-in beep — silent by default, which is what makes scheduled changes bearable at night.

2.6.1 — 10 July 2026

A silent SELinux failure on RHEL, Fedora, AlmaLinux, Rocky and enforcing SUSE. The packaged binary lives in /usr/lib/breeze-core/, which SELinux labels lib_t — not a domain-transition entrypoint — so the service ran as init_t, whose writes to /etc/breeze-core are denied and dontaudit'd. The server started and read its config fine, but approving a pairing failed with a 500 and nothing in the audit log. Found live by breeze-core diag during a migration to the rpm.

2.6.0 — 10 July 2026

The diagnostic and approval tools moved into the binary: diag, approve, devices, revoke, with no zsh, curl or jq needed. diag is a faithful port of the zsh original and stays a pure HTTP client, so the API remains the only contract. See Command-line tools.

2.5.0 — 10 July 2026

Python stopped being a requirement. Every release began shipping self-contained builds — server, panel and a private runtime in one bundle — wrapped as native packages: deb, rpm, pacman, apk, tarballs for glibc and musl × amd64 and arm64, plus a Nix flake with a NixOS module and recipes for packagers. Each package is install-tested on 15 real distro userlands in CI before release.

2.4.x — 6–7 July 2026

  • 2.4.3config.json became mode 640. The CLIs told you to "run with sudo, or join the service group", but the file was mode 600, so joining the group did nothing — and sudo <alias> fails because sudo drops shell aliases. Now joining the group genuinely works, and the state directory stays 750.
  • 2.4.2 — a bigger, bolder panel, six Material-You-like palettes, and a footer showing the server's version and git commit.
  • 2.4.1 — the panel had fallen behind the app ("oops forgot about web ui"): manage units, °C/°F, one-request polling. Also closed two code-scanning alerts — setup_device.py no longer printed the API key or V3 secrets to the terminal.
  • 2.4.0GET /api/units/state (every unit in one call, fanned out server-side, with unreachable units marked online:false instead of 503-ing the batch), /api/version, /api/health, DELETE /api/units/{id}, and brotli compression with a gzip fallback.

2.3.0 — 5 July 2026

First-class Windows. A guided NSIS installer that builds a private venv, registers the BreezeCore service through NSSM as LOCAL SERVICE, locks the firewall to the LAN, strips ACL inheritance on %ProgramData%\breeze-core, and offers to pair your units on the finish page. The Caddy reverse proxy is a separate optional component, chosen only for public exposure.

2.2.1 — 5 July 2026

Beyond systemd and glibc. Install paths with copy-paste templates for OpenRC, runit, s6, supervisord and SysV, refined FreeBSD rc.d and macOS launchd, and a musl guide for Alpine, Void-musl and OpenWrt. The hardening runbook grew a section mapping every systemd sandbox directive to a non-systemd equivalent — most importantly a user-matched egress firewall that pins the service account's outbound traffic to the LAN.

2.2.0 — 5 July 2026

The authenticated config API: a sanitized GET /api/config that never returns the API key or V3 secrets, PATCH /api/units/{id} to rename, and POST /api/units to add a unit by its LAN address — so units could be managed from any paired client instead of only by editing a file on the server.

1.0.0 — 4 July 2026

The first working thing, in a single day: the FastAPI app with pairing-based auth, the vanilla-JS web panel, the zsh diagnostic and approval CLIs, a UBI 9 container image, a reverse-proxy wizard, and CI.


Gaps in the numbering, since they look like mistakes

  • No 2.0 or 2.1. 1.0.0 went straight to 2.2.0; neither was ever released.
  • 2.7.0 never shipped. Ed25519 request signing was developed under that number on the overhaul branch and released as part of 3.0.0 instead — it was too large a change to be a minor.
  • No 3.0.3 or 3.0.4. 3.0.2 was followed by 3.0.5; neither number was ever published.

Clone this wiki locally