-
Notifications
You must be signed in to change notification settings - Fork 0
Installing from source
Shortcut: every release ships prebuilt, self-contained packages (.deb / .rpm / .pkg.tar.zst / .apk / opkg / tarball — no Python needed, hardened service included). If you just want it running, start at PACKAGES.md. This guide is the classic from-source install for people who want to own every layer.
This installs Breeze Core as a hardened systemd service on a Linux host on the same LAN as your Midea units. It's LAN-only by default; to reach it from outside your network, do this first, then follow REVERSE-PROXY.md.
The application is the same everywhere — uvicorn meow_ac.app:app with a few
env vars. Only how you supervise it and how you install the Python deps
change. Sections 1–4 (packages, user, venv, pairing) apply to every OS;
then take the branch that matches your system:
| Your system | Follow |
|---|---|
| Linux with systemd (most distros) | sections 1–7 straight through — pick your family in section 1: RHEL · Debian · SUSE · Arch & others |
| OpenRC / runit / s6 / supervisord / SysV | sections 1–4, then Running without systemd |
| musl libc (Alpine, Void-musl, …) | read Non-glibc systems before the pip install, then as above |
| FreeBSD / other BSD | FreeBSD and other BSDs (rc.d, pf, jails) |
| macOS | macOS (launchd) |
| NixOS | skip 1–5 entirely → NixOS (declarative) |
| Windows / WSL | WINDOWS.md / WSL |
The one thing only systemd gives you out of the box is the kernel-level sandbox (section 5) — HARDENING.md §7 shows how to recover the equivalents elsewhere.
All paths are conventions — change them freely, they're wired via env vars.
The one fixed technical name is the ASGI entry point meow_ac.app:app
(the Python package is meow_ac).
/opt/breeze-core/ the code + a Python virtualenv (venv/)
/etc/breeze-core/ config.json, devices.json, programs.json (owned by the service user)
/etc/systemd/system/breeze-core.service
service user: breeze (system account, no login, no home)
You need Python 3.11+, pip, the venv module, and git. Optional: curl, jq, zsh for the diagnostic/approval CLIs.
(RHEL, AlmaLinux, Rocky, CentOS Stream, Fedora, Oracle Linux)
sudo dnf install -y python3 python3-pip git
# optional CLIs:
sudo dnf install -y curl jq zsh- Check the version:
python3 --version. If your release ships older than 3.11 (e.g. RHEL 8 → 3.6), install a newer stream:sudo dnf install -y python3.11 python3.11-pipand usepython3.11in the commands below. -
Extra that helps: RHEL-family ships SELinux enforcing — a real plus. See SELinux below; you'll just need a
restoreconafter copying files.
(Debian, Ubuntu, Linux Mint, Pop!_OS, Raspberry Pi OS, Armbian)
sudo apt update
sudo apt install -y python3 python3-venv python3-pip git
# optional CLIs:
sudo apt install -y curl jq zsh-
python3-venvis a separate package on Debian/Ubuntu and is required — don't skip it. - Extra that helps: Debian/Ubuntu ship AppArmor. systemd sandboxing (in our unit) is the primary defense, but you can add an AppArmor profile — see AppArmor.
(openSUSE Leap, Tumbleweed, SUSE Linux Enterprise)
sudo zypper refresh
sudo zypper install -y python3 python3-pip git
# optional CLIs:
sudo zypper install -y curl jq zsh- Tumbleweed tracks recent Python; on Leap/SLES confirm
python3 --version≥ 3.11 (else install apython311package). - Extra that helps: SUSE ships firewalld and AppArmor — both covered below.
# Arch / Manjaro:
sudo pacman -S --needed python git
# Alpine (uses OpenRC, not systemd — see the note at the end):
sudo apk add python3 py3-pip git
# Void, Gentoo, etc.: install python (3.11+), pip, venv-capable python, git.Then continue with the common steps below. (NixOS users: skip sections 1–5 and use the declarative section instead.)
A dedicated, loginless system account limits blast radius.
# the nologin path differs by distro; this finds it:
NOLOGIN="$(command -v nologin || echo /usr/sbin/nologin)"
sudo useradd --system --no-create-home --shell "$NOLOGIN" breeze
sudo mkdir -p /opt/breeze-core /etc/breeze-core# put the tree in /opt/breeze-core (git clone, or copy/scp it there)
sudo git clone <repo-url> /opt/breeze-core
cd /opt/breeze-core
sudo python3 -m venv venv
sudo ./venv/bin/pip install --upgrade pip
sudo ./venv/bin/pip install -r requirements.txtAir-gapped / offline hosts:
pip download -r requirements.txton an internet-connected machine of the same OS/arch, copy the wheels over, andpip install --no-index --find-links ./wheels -r requirements.txt.Non-glibc (musl) hosts — Alpine, Void-musl, etc.: pip normally pulls prebuilt glibc (manylinux) wheels for the native deps (
pydantic-core,cryptography,aiohttp), which won't load on musl libc. Recent versions of those deps also publish musllinux wheels, so an up-to-date pip usually just works — but if you hit anImportError/Error loading shared libraryor pip starts compiling, you need build tools first. See Non-glibc (musl libc) systems below before running thepip install.
Run as a user that can send UDP broadcasts (root is simplest). This writes config.json and prints an API key once — save it.
sudo AC_CONFIG=/etc/breeze-core/config.json \
/opt/breeze-core/venv/bin/python /opt/breeze-core/setup_device.py- It broadcasts and should find every powered-on unit. Re-run later (or
--ip 192.168.1.73) to add ones that were off; it merges by device id and keeps the API key. - Now hand ownership of the runtime directory to the service user. The directory must be owned by
breeze, not justconfig.json— the service writesdevices.json/programs.jsonthere, and a root-owned directory makes those writes fail:
sudo chown -R breeze:breeze /etc/breeze-core /opt/breeze-core
sudo chmod 750 /etc/breeze-core
sudo chmod 640 /etc/breeze-core/config.json # group-readable (see below)Running the CLIs as yourself (no sudo). The diagnostic/approval tools (
tools/ac-*.zsh) readconfig.jsondirectly for the API key.config.jsonis mode 640, so add your admin user to the service group once, then log out and back in — after that the tools work withoutsudo:sudo usermod -aG breeze "$USER" # then re-login for it to take effectThe state dir is
750, so "group" is only the service account + admins you add. Don't run the tools viasudoif they're shell aliases —sudodrops your aliases and your group membership. See TROUBLESHOOTING.md.
Create /etc/systemd/system/breeze-core.service. Replace 192.168.1.10 with this host's LAN IP (bind the LAN interface, not 0.0.0.0). If you'll put it behind a reverse proxy, bind 127.0.0.1 instead and see REVERSE-PROXY.md.
[Unit]
Description=Breeze Core - self-hosted Midea AC control
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=breeze
Group=breeze
WorkingDirectory=/opt/breeze-core
Environment=AC_CONFIG=/etc/breeze-core/config.json
Environment=AC_DEVICES=/etc/breeze-core/devices.json
Environment=AC_PROGRAMS=/etc/breeze-core/programs.json
ExecStart=/opt/breeze-core/venv/bin/uvicorn meow_ac.app:app --host 192.168.1.10 --port 8420
Restart=on-failure
RestartSec=5
# --- sandboxing (works on any recent systemd) ---
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/etc/breeze-core
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
RestrictRealtime=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RemoveIPC=true
SystemCallFilter=@system-service
SystemCallArchitectures=native
CapabilityBoundingSet=
LockPersonality=true
# Egress lockdown: the service only needs to reach your AC units. Restrict
# outbound to loopback + your LAN so a compromise can't phone home. Adjust
# the CIDR to your network (drop these two lines if your systemd is old).
IPAddressAllow=127.0.0.0/8 ::1/128 192.168.0.0/16
IPAddressDeny=any
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now breeze-core
systemctl status breeze-core
# check the sandbox score (lower is better):
systemd-analyze security breeze-coreLogs: journalctl -u breeze-core -f. Verify: curl -s -o /dev/null -w '%{http_code}\n' -H 'X-API-Key: WRONG' http://192.168.1.10:8420/api/units should print 401.
Allow port 8420 only from your LAN. Pick your firewall:
# if your LAN interface is already in the 'internal'/'home' zone:
sudo firewall-cmd --permanent --zone=internal --add-port=8420/tcp
# otherwise, allow just your subnet:
sudo firewall-cmd --permanent --zone=internal --add-source=192.168.0.0/16
sudo firewall-cmd --reloadsudo ufw allow from 192.168.0.0/16 to any port 8420 proto tcp
sudo ufw reload# /etc/nftables.conf — inside your inet filter input chain:
tcp dport 8420 ip saddr 192.168.0.0/16 accept
tcp dport 8420 drop
sudo systemctl enable --now nftables && sudo nft -f /etc/nftables.confSELinux is a big win — keep it enforcing (getenforce). Two things to know:
- After copying files into
/opt/breeze-coreand/etc/breeze-core, restore their contexts:sudo restorecon -Rv /opt/breeze-core /etc/breeze-core
- The service runs as
unconfined_service_tby default, which is allowed to bind ports and reach the LAN — so no port labeling is normally needed. If you confine the service or bind an unusual port and hit denials, checksudo ausearch -m avc -ts recentand, if needed, label the port:sudo semanage port -a -t http_port_t -p tcp 8420 # requires policycoreutils-python-utils
systemd sandboxing (section 5) is the primary containment. For an extra layer, you can add an AppArmor profile for the uvicorn binary. Minimal starting point at /etc/apparmor.d/breeze-core:
#include <tunables/global>
profile breeze-core /opt/breeze-core/venv/bin/python3 flags=(complain) {
#include <abstractions/base>
#include <abstractions/python>
/opt/breeze-core/** r,
/etc/breeze-core/** rw,
network inet stream,
network inet6 stream,
}
sudo apparmor_parser -r /etc/apparmor.d/breeze-core # start in complain mode, then flip to enforce(Only pursue this if you want defense-in-depth beyond the systemd unit.)
- Keep the systemd sandbox directives from section 5 — they're distro-independent.
- Back up
/etc/breeze-core/(config + token hashes + programs) off-box. - Keep dependencies current:
sudo ./venv/bin/pip install -U -r requirements.txtthensystemctl restart breeze-core.
On NixOS you don't create users or drop files imperatively — you declare it. Two wrinkles: Breeze Core isn't in nixpkgs, and manylinux pip wheels (e.g. pydantic-core) won't run on NixOS without help. Pick one approach:
Approach A — nixpkgs Python env (recommended, pure). Build the Python deps from nixpkgs so native pieces are linked correctly, and only fetch msmart-ng (not in nixpkgs) via an overlay. Sketch for configuration.nix:
{ config, pkgs, lib, ... }:
let
# msmart-ng from PyPI (adjust version/hash):
msmart-ng = pkgs.python312Packages.buildPythonPackage rec {
pname = "msmart-ng"; version = "2026.7.0"; format = "pyproject";
src = pkgs.fetchPypi { inherit pname version; sha256 = lib.fakeSha256; };
nativeBuildInputs = [ pkgs.python312Packages.setuptools ];
propagatedBuildInputs = with pkgs.python312Packages; [ aiohttp cryptography ];
};
pyEnv = pkgs.python312.withPackages (ps: [ ps.fastapi ps.uvicorn ps.pydantic msmart-ng ]);
src = pkgs.fetchgit { url = "<repo-url>"; rev = "main"; sha256 = lib.fakeSha256; };
in {
users.users.breeze = { isSystemUser = true; group = "breeze"; };
users.groups.breeze = {};
systemd.tmpfiles.rules = [ "d /etc/breeze-core 0750 breeze breeze -" ];
systemd.services.breeze-core = {
description = "Breeze Core";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ]; wants = [ "network-online.target" ];
environment = {
AC_CONFIG = "/etc/breeze-core/config.json";
AC_DEVICES = "/etc/breeze-core/devices.json";
AC_PROGRAMS = "/etc/breeze-core/programs.json";
};
serviceConfig = {
User = "breeze"; Group = "breeze";
WorkingDirectory = "${src}";
ExecStart = "${pyEnv}/bin/uvicorn meow_ac.app:app --host 192.168.1.10 --port 8420";
Restart = "on-failure";
# same sandbox directives as the systemd unit above:
NoNewPrivileges = true; ProtectSystem = "strict"; ProtectHome = true;
ReadWritePaths = [ "/etc/breeze-core" ];
PrivateTmp = true; RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
SystemCallFilter = "@system-service"; CapabilityBoundingSet = "";
IPAddressAllow = "127.0.0.0/8 ::1/128 192.168.0.0/16"; IPAddressDeny = "any";
};
};
networking.firewall.extraInputRules = ''
ip saddr 192.168.0.0/16 tcp dport 8420 accept
'';
}Replace lib.fakeSha256 with real hashes (nix build will tell you the correct ones). Run setup_device.py once from nix shell nixpkgs#python312 -c ${pyEnv}/bin/python /path/setup_device.py to create /etc/breeze-core/config.json.
Approach B — nix-ld + venv (quick, impure). Enable programs.nix-ld.enable = true;, then create a normal venv under /etc/breeze-core/venv and pip install -r requirements.txt as in sections 3–4; nix-ld lets the manylinux wheels find a dynamic linker. Point the systemd ExecStart at that venv's uvicorn. Simpler to get going, but not fully declarative.
Extra that helps (NixOS): the whole thing is reproducible and the store is immutable — pin the repo rev and dep versions in your flake and your deployment is byte-for-byte rebuildable.
If your host doesn't run systemd (Alpine, Devuan, Gentoo, Artix, Void, an older SysV box, a container base image…), everything through section 4 still applies — install packages, create the breeze user, make the venv, pair your units. Only section 5 changes: instead of a .service unit you register the process with your init/supervisor.
Whatever you use, the invariants are the same:
-
Command:
/opt/breeze-core/venv/bin/uvicorn meow_ac.app:app --host <LAN-IP> --port 8420(bind127.0.0.1if a reverse proxy fronts it). -
User: run as the unprivileged
breezeaccount, never root. -
Environment: export
AC_CONFIG(and, if you split them,AC_DEVICES/AC_PROGRAMS), pointing at a directory thebreezeuser owns. -
Working directory: the repo root (
/opt/breeze-core) so the package imports andstatic/resolves. - Restart: let the supervisor respawn on exit.
⚠️ You do not get the section-5 kernel sandbox (egress lockdown,ProtectSystem, syscall filter). That protection is systemd-specific. Recover the important parts — outbound-egress lockdown, filesystem confinement, privilege drop — with your firewall and OS features as described in HARDENING.md §7 “Hardening without systemd”. For an internet-facing box, the strongest non-systemd containment is a container (see DOCKER.md,read_only: true) or a FreeBSD jail.
Ready-to-copy templates for each supervisor live in deploy/init/. Pick one:
Create the user, then install the service script (from deploy/init/openrc-breeze-core):
# Alpine uses adduser/addgroup; Gentoo/Artix have useradd like the common steps
addgroup -S breeze 2>/dev/null; adduser -S -D -H -G breeze -s /sbin/nologin breeze
install -m 0755 deploy/init/openrc-breeze-core /etc/init.d/breeze-core
rc-update add breeze-core default
rc-service breeze-core start
rc-service breeze-core statusThe script (an openrc-run service using supervise-daemon, which restarts crashes):
#!/sbin/openrc-run
name="breeze-core"
description="Breeze Core - self-hosted Midea AC control"
: ${BREEZE_HOME:=/opt/breeze-core}
: ${BREEZE_HOST:=192.168.1.10}
command="${BREEZE_HOME}/venv/bin/uvicorn"
command_args="meow_ac.app:app --host ${BREEZE_HOST} --port 8420"
command_user="breeze:breeze"
command_background=false
supervisor="supervise-daemon"
directory="${BREEZE_HOME}"
export AC_CONFIG=/etc/breeze-core/config.json
export AC_DEVICES=/etc/breeze-core/devices.json
export AC_PROGRAMS=/etc/breeze-core/programs.json
depend() { need net; after firewall; }mkdir -p /etc/sv/breeze-core
install -m 0755 deploy/init/runit-run /etc/sv/breeze-core/run
ln -s /etc/sv/breeze-core /var/service/ # or /etc/runit/runsvdir/default/
sv status breeze-core/etc/sv/breeze-core/run (runit expects the process to stay in the foreground — uvicorn already does; chpst drops privileges and sets env):
#!/bin/sh
export AC_CONFIG=/etc/breeze-core/config.json
export AC_DEVICES=/etc/breeze-core/devices.json
export AC_PROGRAMS=/etc/breeze-core/programs.json
cd /opt/breeze-core || exit 1
exec chpst -u breeze:breeze \
./venv/bin/uvicorn meow_ac.app:app --host 192.168.1.10 --port 8420 2>&1(Log with a log/run service piping to svlogd, or let your logger capture stdout.)
Same shape as runit — a run script that stays in the foreground. Use s6-setuidgid breeze in place of chpst -u:
#!/bin/sh
cd /opt/breeze-core || exit 1
export AC_CONFIG=/etc/breeze-core/config.json
exec s6-setuidgid breeze ./venv/bin/uvicorn meow_ac.app:app --host 192.168.1.10 --port 8420Add to /etc/supervisor/conf.d/breeze-core.conf (template: deploy/init/supervisor-breeze-core.conf):
[program:breeze-core]
command=/opt/breeze-core/venv/bin/uvicorn meow_ac.app:app --host 192.168.1.10 --port 8420
directory=/opt/breeze-core
user=breeze
environment=AC_CONFIG="/etc/breeze-core/config.json",AC_DEVICES="/etc/breeze-core/devices.json",AC_PROGRAMS="/etc/breeze-core/programs.json"
autostart=true
autorestart=true
stopsignal=INT
stdout_logfile=/var/log/breeze-core.log
redirect_stderr=truesupervisorctl reread && supervisorctl update && supervisorctl status breeze-coreOn a true SysV box, wrap the command with start-stop-daemon (Debian-family) or daemon/nohup + a PID file in /etc/init.d/breeze-core, running as --chuid breeze, then update-rc.d breeze-core defaults (or chkconfig --add). A ready script is at deploy/init/sysv-breeze-core. Prefer OpenRC/runit/supervisord if you have the choice — they handle respawn and logging for you.
Verify (any init): curl -s -o /dev/null -w '%{http_code}\n' -H 'X-API-Key: WRONG' http://192.168.1.10:8420/api/units → 401. Then open the firewall (§6) and — because you have no systemd sandbox — read HARDENING.md §7.
(Alpine Linux, Void-musl, OpenWrt, other musl distros, and musl-based containers.)
Breeze Core is pure Python, but three dependencies ship compiled extensions — pydantic-core (Rust), cryptography (Rust/OpenSSL), and aiohttp (C). pip installs these as prebuilt wheels, and the default wheels are manylinux (glibc-linked). On a musl system those either refuse to install or fail at import with Error loading shared library ... : No such file or directory.
There are two clean ways to handle it:
Modern releases of all three deps publish musllinux wheels, which pip ≥ 21.3 selects automatically on a musl host. This is the happy path and needs no compiler:
apk add python3 py3-pip git # Alpine
python3 -m venv venv
./venv/bin/pip install --upgrade pip # make sure pip is recent enough to see musllinux wheels
./venv/bin/pip install -r requirements.txt
# sanity check the native bits actually load:
./venv/bin/python -c "import pydantic_core, cryptography, aiohttp; print('musl wheels OK')"If that import line prints OK, you're done — continue with pairing (§4) and your init (above).
If pip can't find a musllinux wheel for your arch (older releases, uncommon architectures, or you pin an old version), pip will try to compile, which needs a toolchain — including Rust for pydantic-core and cryptography:
# Alpine build dependencies:
apk add build-base python3-dev libffi-dev openssl-dev cargo rust
# then the normal install; pip now compiles what it can't download:
./venv/bin/pip install -r requirements.txtBuilding cryptography/pydantic-core from source is slow (minutes) and pulls a lot of -dev packages. On a container image you'd do this in a builder stage and copy only the finished venv out — the same multi-stage pattern the shipped Dockerfile use — a throwaway builder stage that compiles the wheels, and a runtime stage that gets only the finished venv. There are five of them: Alpine Edge for musl (x86_64 and arm64), UBI 9 for glibc at two x86-64 levels, and one with nginx bundled.
Alpine also packages some of these: apk add py3-cryptography py3-aiohttp gives you musl-native builds from the distro, and you can create the venv with --system-site-packages so pip only needs to fetch the rest. Handy on tiny/slow devices where compiling Rust is painful.
Note: the diagnostic/approval CLIs need
zsh,curl,jq(apk add zsh curl jq) — same as any distro. And musl gives you no systemd either, so you'll use one of the inits above plus HARDENING.md §7.
sudo pkg install python311 py311-pip git # optional CLIs: curl jq zsh
sudo pw useradd breeze -d /nonexistent -s /usr/sbin/nologin -c "Breeze Core"
# then venv + pair + config as in sections 3–4, e.g. under
# /usr/local/breeze-core (code + venv)
# /usr/local/etc/breeze-core (config.json etc., chown breeze)Supervise it with rc.d — /usr/local/etc/rc.d/breeze_core (template: deploy/init/freebsd-rc-breeze_core):
#!/bin/sh
# PROVIDE: breeze_core
# REQUIRE: NETWORKING
. /etc/rc.subr
name=breeze_core; rcvar=breeze_core_enable
: ${breeze_core_enable:=NO}
: ${breeze_core_host:=192.168.1.10}
command=/usr/sbin/daemon
procname=/usr/local/breeze-core/venv/bin/python
command_args="-f -u breeze -o /var/log/breeze_core.log \
-P /var/run/breeze_core.pid \
/usr/local/breeze-core/venv/bin/uvicorn meow_ac.app:app --host ${breeze_core_host} --port 8420"
breeze_core_env="AC_CONFIG=/usr/local/etc/breeze-core/config.json AC_DEVICES=/usr/local/etc/breeze-core/devices.json AC_PROGRAMS=/usr/local/etc/breeze-core/programs.json"
load_rc_config $name; run_rc_command "$1"sudo sysrc breeze_core_enable=YES && sudo service breeze_core startFirewall with pf (pass in on $lan proto tcp to port 8420 and a matching egress block). Strongest isolation: run the whole thing in a thin jail — that gives you the filesystem/network confinement systemd provides on Linux. Other BSDs: NetBSD/OpenBSD use their own rc.d/rcctl; the command and env vars are identical.
Home/testing use (macOS has no systemd sandbox; keep it LAN-only):
brew install python git
# venv + pair + config as in sections 3–4; put state somewhere writable, e.g.
# AC_CONFIG="$HOME/Library/Application Support/breeze-core/config.json"Keep it running with launchd — ~/Library/LaunchAgents/com.breeze.core.plist (template: deploy/init/com.breeze.core.plist):
<plist version="1.0"><dict>
<key>Label</key><string>com.breeze.core</string>
<key>ProgramArguments</key><array>
<string>/opt/breeze-core/venv/bin/uvicorn</string><string>meow_ac.app:app</string>
<string>--host</string><string>192.168.1.10</string><string>--port</string><string>8420</string>
</array>
<key>WorkingDirectory</key><string>/opt/breeze-core</string>
<key>EnvironmentVariables</key><dict>
<key>AC_CONFIG</key><string>/opt/breeze-core/config.json</string>
</dict>
<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
</dict></plist>launchctl load ~/Library/LaunchAgents/com.breeze.core.plistFirewall with the built-in Application Firewall or pf. For an always-on internet-facing deployment, prefer a Linux/systemd box or a container.
Windows is a first-class target: a guided NSIS installer sets Breeze
Core up as a hardened Windows service (bundled NSSM, low-privilege
LOCAL SERVICE, LAN-locked firewall), with an optional guided Caddy reverse
proxy (auto-HTTPS) and a fail2ban-style IP banner. The full guide is
docs/WINDOWS.md — start there.
Quick testing run (no service), in PowerShell from the repo root:
$env:AC_CONFIG="C:\ProgramData\breeze-core\config.json"
.\venv\Scripts\python.exe setup_device.py # pair
.\venv\Scripts\uvicorn.exe meow_ac.app:app --host 192.168.1.10 --port 8420For anything persistent, use the installer or the scripts in
deploy/windows/ — see WINDOWS.md.
Follow the Debian/Ubuntu path inside WSL2, with two caveats:
-
Enable systemd so the section-5 service works — add to
/etc/wsl.conf, thenwsl --shutdownand reopen:[boot] systemd=true
-
Networking: WSL2 is NAT'd, so LAN units/clients can't reach it by default. Easiest fix (Windows 11): mirrored networking — add
networkingMode=mirroredunder[wsl2]in%UserProfile%\.wslconfig. Otherwise port-proxy from Windows:netsh interface portproxy add v4tov4 listenport=8420 connectaddress=<wsl-ip> connectport=8420.
Next: expose it safely from outside your LAN → REVERSE-PROXY.md, and review HARDENING.md — especially §7 Hardening without systemd if you're not on a systemd host.
Breeze Core · Breeze for Android · Packages · AGPL-3.0
Start here
Install it
Use it
Reference
Run it safely
Develop and port