aspic / breeze-core
Breeze Core
Self-hosted, LAN-first control for Midea air conditioners: a REST API, a web panel, device-pairing authentication, server-side schedules and temperature curves. No cloud account, no vendor app, nothing phoning home.
sudo apt install breeze-core # Debian family sudo dnf install breeze-core # Fedora / RHEL family sudo zypper install breeze-core # openSUSE sudo pacman -S breeze-core # Arch family sudo apk add breeze-core # Alpine sudo xbps-install -S breeze-core # Void sudo emerge -av app-misc/breeze-core-bin # Gentoo (the -bin ebuild) apk add breeze-core # OpenWrt 25.12+ opkg install breeze-core # OpenWrt 24.10 and older pkg install breeze-core # Termux (Android) pkgin -y install breeze-core # NetBSD pkg install breeze-core # FreeBSD
OpenBSD and OPNsense install one named file rather than a package by name — the OpenBSD and OPNsense sections on the index have the command. Gentoo needs its overlay added rather than the package repository, since ebuilds are not packages.
Building it yourself
The source, through the same repository, built into the same package with your distribution's own Rustdnf download --source breeze-core && rpmbuild --rebuild breeze-core-*.src.rpm # Fedora / RHEL apt source breeze-core && cd breeze-core-*/ && dpkg-buildpackage -b # Debian / Ubuntu, with deb-src # Arch: the newest tarball in /arch/sources/, then tar -xf breeze-core-*.src.tar.gz && cd breeze-core && makepkg -si
An SRPM, a Debian source package and a makepkg
tarball (/arch/sources/, with a
.sig beside each), each signed with the repository key and building offline from
vendored crates. What comes out has the same name, files and
breeze account as the package above, so either replaces the
other; only the binary differs, linked against your glibc instead of
static. Needs Rust 1.88 or newer, which Debian 13 does not have.
The
wiki has the build dependencies and every step.
After installing
Three steps, and the first one is the only one that needs your air conditioners switched on# 1. find the units on your network and write the config sudo breeze-core pair # 2. bind somewhere useful: your LAN address for direct use, or leave it on # 127.0.0.1 with a reverse proxy in front sudoedit /etc/breeze-core/breeze-core.env # 3. start it sudo systemctl enable --now breeze-core # and, if you want it reachable by name over HTTPS: nginx, Apache or # Caddy, set up step by step, with --undo to take it all back out sudo breeze-core proxy
Step 2's file moves with the platform's prefix:
/usr/local/etc/breeze-core on FreeBSD and OPNsense,
/usr/pkg/etc/breeze-core on NetBSD,
%ProgramData%\breeze-core on Windows — or set
AC_CONFIG_DIR once and stop thinking about it. Step 3 is
whatever your init system is:
systemd
Debian · Ubuntu · Fedora · RHEL · SUSE · Arch · Gentoo (systemd profile)sudo systemctl enable --now breeze-core
sudo systemctl status breeze-core
journalctl -u breeze-core -f # follow the log
OpenRC
Alpine · Artix (openrc) · Gentoo · Devuansudo rc-update add breeze-core default sudo rc-service breeze-core start sudo rc-service breeze-core status
On Gentoo the settings live in
/etc/conf.d/breeze-core, which is where OpenRC expects them —
not in breeze-core.env.
runit
Void · Artix (runit)# The Void package already ships /etc/sv/breeze-core, disabled.
# Enabling it is the symlink and nothing else:
sudo ln -s /etc/sv/breeze-core /var/service/
sudo sv status breeze-core
Installed by hand instead? The templates are
deploy/init/runit-run and its companion
runit-log-run, which gives you a rotated log under
/var/log/breeze-core rather than runit's catch-all.
s6 / s6-rc
Artix (s6) · Obarunsudo mkdir -p /etc/s6/sv/breeze-core sudo install -m 0755 deploy/init/s6-run /etc/s6/sv/breeze-core/run sudo s6-rc-bundle-update add default breeze-core sudo s6-rc -u change breeze-core
SysV init
Devuan (sysvinit) · Slackware · older LTS releasessudo install -m 0755 deploy/init/sysv-breeze-core /etc/init.d/breeze-core
sudo update-rc.d breeze-core defaults # RHEL-ish: chkconfig --add
sudo service breeze-core start
procd
OpenWrt/etc/init.d/breeze-core enable
/etc/init.d/breeze-core start
logread -e breeze-core # follow the log
termux-services
Termux (Android)sv-enable breeze-core # now, and with every Termux session
sv status breeze-core
tail -f $PREFIX/var/log/sv/breeze-core/current
The package ships the service switched off, as Void's does.
Android still stops a background Termux unless it holds a wake lock —
termux-wake-lock — and the Termux:Boot add-on is what starts
it again after a reboot.
rc.d — FreeBSD and OPNsense
FreeBSD 14 · 15sudo sysrc breeze_core_enable=YES sudo service breeze_core start sudo service breeze_core status
Underscore, not hyphen: FreeBSD's rc system needs a name that is a valid shell variable. On OPNsense the GUI does this for you — Services > Breeze Core.
rc.d — NetBSD
echo 'breeze_core=YES' | sudo tee -a /etc/rc.conf sudo service breeze_core start
rcctl — OpenBSD
doas rcctl enable breeze_core doas rcctl start breeze_core doas rcctl check breeze_core
OpenBSD has no service(8) at all;
rcctl is the whole interface.
launchd
macOS — a LaunchDaemon, not an Agentsudo cp deploy/init/com.breeze.core.plist /Library/LaunchDaemons/ sudo chown root:wheel /Library/LaunchDaemons/com.breeze.core.plist sudo launchctl load /Library/LaunchDaemons/com.breeze.core.plist
A daemon rather than an agent, because an agent only runs
while its user is logged in — the wrong lifetime for something a phone
talks to. The plist creates no account; the header comments carry the
three dscl lines.
supervisord
Anywhere, and the usual answer inside a container that runs more than one thingsudo cp deploy/init/supervisor-breeze-core.conf \ /etc/supervisor/conf.d/breeze-core.conf sudo supervisorctl reread && sudo supervisorctl update
Windows service
Registered by the installer, supervised by NSSMGet-Service BreezeCore Restart-Service BreezeCore Get-Content "$env:ProgramData\breeze-core\logs\service.log" -Tail 40 -Wait
Containers
docker compose up -d docker logs -f breeze-core
Writing your own? Three things matter. Pass
serve explicitly — a bare breeze-core prints its
usage and exits 0, which as a start command looks like success and leaves
you with no server. Run it in the foreground and let your supervisor own
it; there is no --daemonize. And stop it with a plain
TERM: every store is written to a temporary file and renamed
into place, so no signal can catch one half-written.
Then open http://<that address>:8420. The panel asks for
the API key once per device — it is in
/etc/breeze-core/config.json, which pair wrote —
and then asks an admin on the local network to approve the pairing:
sudo breeze-core approve --config /etc/breeze-core/config.json <CODE> sudo breeze-core devices # what is enrolled sudo breeze-core diag # is anything wrong
pair takes a token and key if you already have them;
if you don't, sudo breeze-core fetch asks for that account
(MSmartHome, or Meiju in China) once, checks each token on the unit, and
forgets the password. It also explains what to do if your units came with
NetHome Plus, which no longer hands tokens out at all. Keep a copy of
config.json somewhere else: those credentials may not
be issued again.From the command line
The whole server is one binary, and it is also the clientbreeze-core control 'kitchen' cool 25.5 both auto turbo 15m breeze-core control 'living room' heat 22 horizontal low eco breeze-core control 'back room' none none none none none 30m
Name, mode, temperature, flap, fan, extra, timer — in that
order, with none valid anywhere and names matched case
insensitively (or by any unambiguous prefix). It enrols itself the first
time you use it, like any other client.
On Windows
A guided installer, a service, and an optional reverse proxy — no interpreter to install firstBreeze-Core-Setup-4.3.2.exe
(1.2 MB · sha256
· metalink)
lays down one executable, registers a hardened service under
NSSM as LOCAL SERVICE, locks down
%ProgramData%\breeze-core, and opens one firewall rule
restricted to your local subnet.
A first install needs the internet. Setup downloads two small
helpers, each checked against a checksum pinned in the installer:
NSSM, which runs the server as a service, and the updater.
NSSM is fetched rather than carried because antivirus products flag
installers that embed it. An upgrade reuses the NSSM already installed,
so it needs no internet. On a machine that never has any, put
nssm-2.24.zip next to the installer.
Once installed, it checks aspic for a newer release when an administrator
signs in, and asks before downloading anything; you can turn that
off.
Public HTTPS is a separate, optional component — a guided Caddy wizard that fetches Caddy, writes a hardened config with automatic certificates and a LAN-only admin gate, and can install a fail2ban-style watcher that bans abusive addresses through Windows Firewall. Your LAN is never banned.
Setup asks Simple or Advanced. Simple serves
your local network on port 8420; Advanced lets you choose the folder, the
port, whether the network or only this PC can reach it, the firewall rule
and the update check. Then: Pair AC units
from the Start menu (or breeze-core pair), and
Start-Service BreezeCore. To change the address or port
later, run the installer again and choose to review the settings.
Uninstalling always keeps %ProgramData%\breeze-core, with
your units' tokens in it; remove that folder by hand for a full
wipe.
On a Mac, or with Homebrew on Linux
One formula, prebuilt for Apple silicon, Intel Macs, and x86-64 and ARM64 Linuxbrew tap aspic/breeze https://aspic.salataputarica.hr.eu.org/homebrew/breeze.git brew trust aspic/breeze # once: Homebrew asks this of every tap outside its own brew install breeze-core # 1. find the units on your network and write the config breeze-core pair # 2. bind somewhere useful: this machine's LAN address, for phones "${EDITOR:-vi}" "$(brew --prefix)/etc/breeze-core/breeze-core.env" # 3. start it, now and at every login brew services start breeze-core
The settings file is the same breeze-core.env the Linux
packages use, kept in $(brew --prefix)/etc/breeze-core
with the rest of the store, and brew upgrade never overwrites
it. brew services runs Breeze Core as you, while you are
logged in: a launchd agent on macOS, a systemd user unit on Linux. On a
Linux server, the distribution packages above are the better fit, because
they run it as its own account from boot.
The Mac binaries need macOS 13 or newer. They are ad-hoc
signed, not notarised, which Homebrew does not mind: it downloads with
curl, so nothing is quarantined. Uninstalling keeps the
store, as every package here does.
What you get
| one binary | ~2.5 MB, no interpreter, no runtime, nothing installed alongside it. The web panel is compiled into it |
| the API | 30 endpoints, key + per-device-token auth, an SSE stream for live state, Prometheus metrics |
| schedules | favourites, weekly schedules and interpolated temperature curves, run server-side — nothing has to stay open |
| an Android app | Breeze, which talks to the same API |
| runs on | 22 targets. Linux on x86-64, 32-bit x86, arm64, armv7, armv6 (every Raspberry Pi), riscv64, POWER in both endiannesses, IBM Z, LoongArch, MIPS in both endiannesses and Cavium Octeon; OpenWrt routers and OPNsense firewalls; FreeBSD, NetBSD and OpenBSD; Windows; macOS on Apple silicon and Intel; Android phones under Termux. Each is run before it ships. And, untested and for fun, sixteen oddities: illumos, Solaris, DragonFly, Haiku, SPARC, the BSDs on ARM, Windows on ARM and a watch. |
| package managers | apt · dnf ·
zypper · pacman · apk ·
xbps (Void, glibc and musl) · emerge
(a Gentoo overlay) · opkg and OpenWrt's apk · pkg ·
pkgin · pkg_add · Termux's apt,
plus a Windows installer, an
OPNsense plugin and container images |
| init systems | systemd, OpenRC, runit, s6, SysV, procd, the
three BSD rc flavours, launchd, supervisord, termux-services
and a Windows service — templates for every one of them |
Migrating from 3.x
One script: it works out your package manager, backs everything up, swaps bolero for aspic and upgrades the packageSo read it. It is mostly comments, and the parts that delete things are marked. The checksum tells you the download was not corrupted; it cannot tell you the file is safe, because it comes from the same place. Reading is the only real check you have.
curl -fsSL https://aspic.salataputarica.hr.eu.org/migrate.sh -o migrate.sh less migrate.sh # the important step sudo sh migrate.sh # prints the plan, changes nothing sudo sh migrate.sh --yes # does it
Or, once you have read it:
curl -fsSL https://aspic.salataputarica.hr.eu.org/migrate.sh | sudo sh -s -- --yes
Run without --yes it prints what it found and what it would
change, and stops. That is the default on purpose.
| it detects | your OS, architecture and package manager — apt,
dnf, zypper, pacman, apk, opkg, pkgin, pkg (FreeBSD) or
pkg_add (OpenBSD) |
| it backs up | /etc/breeze-core and your
repository files and keys, into a timestamped root-only directory, with a
generated ROLLBACK.sh holding the exact commands for
your machine — not "restore from your backup" |
| it removes | the bolero repository and its signing key |
| it adds | the aspic repository and its key |
| it upgrades | breeze-core 3.x to 4.x. The package
name is unchanged, so this is an upgrade in place: your configuration,
device tokens, programs and timers are untouched and the service stays
enabled |
| it leaves alone | a service that was already stopped, and anything it cannot do safely — a source install is refused rather than half-converted |
Verified by installing the real Python 3.2.0 from bolero and
migrating it: Debian 12, AlmaLinux 9, Arch and Alpine 3.20 in clean
containers, and NetBSD 11 and FreeBSD 15.1 on real machines — on FreeBSD
that means 3.2.0 with its python312 dependency replaced by a
2.4 MB executable with none, config and device tokens untouched.
pkg_add owns none
of it and cannot replace it — and removing a service and a virtualenv is
not something this script will do behind your back. It stops and prints
the three commands to do it yourself, keeping your configuration; run it
again afterwards and it installs the native package. Both halves of that
were walked on OpenBSD 7.9.Coming from 3.x
The Python implementation, still published on bolero4.3.2 is the same project reimplemented — same REST API, same
config.json, devices.json,
programs.json and timers.json, same
AC_* environment variables, same service name, same web panel
and the same Android app. It is meant to be installed over a 3.x
deployment rather than migrated to, and the existing paths it does not need
any more are kept as symlinks so nothing that names them breaks.
Everything published for 3.x stays exactly where it is on bolero and keeps installing; nothing is being switched off underneath anyone.
/etc/breeze-core before any
upgrade. Not because this one is risky — an upgrade keeps the directory
— but because a paired V3 unit's credentials cannot be obtained again, and
that is true whatever you are upgrading.