Skip to content

Releases

1.8.0 — Server-side OTA + ESPHome 2026.10 fixes

Choose a tag to compare

@weirded weirded released this 04 Nov 19:03
9eea409

Thread and Matter devices can finally be flashed from any build worker, workers that can't reach a device hand the flash to the add-on instead of failing, and a round of fixes for the problems that showed up when ESPHome 2026.10 landed.

New

Flash Thread and Matter devices from any build worker. Thread devices sit on a mesh network that only the Home Assistant machine can reach, so a remote build worker could compile their firmware but every upload failed. Fleet now detects Thread devices automatically: any worker compiles the firmware and the add-on flashes it over the mesh itself. The Queue tab shows the extra flashing step. Thanks to @htilly.

Workers that can't reach a device no longer fail the job. If a build worker can compile but can't reach a device (a separate VLAN, a firewall, a cloud worker with no route home), it now checks before uploading, finishes the compile, and hands the firmware to the add-on to flash. Same mechanism as Thread devices. Set OTA_REACHABILITY_CHECK=0 on a worker to turn it off.

Watch an upgrade from start to finish without clicking around. Upgrading a single device now opens its build log straight away (with a "waiting for a worker" note while it's queued), and once the new firmware is flashed successfully it switches to that device's live logs so you can watch it boot, reconnecting for up to a minute and a half while the device restarts. Bulk upgrades still just take you to the Queue tab, and nothing pops up for failed builds or download-only compiles.

Get notified when a new ESPHome release comes out. The Home Assistant integration adds two entities to the Fleet hub device: Latest ESPHome version and ESPHome update available, which turns on while a stable release newer than the one Fleet compiles with is out (betas are ignored). The docs include a ready-to-copy automation that sends you a notification when it flips.

Workers stay in the list until you delete them. Previously a build worker vanished from the Workers tab the moment it shut down cleanly, and every worker disappeared whenever the add-on restarted — so a worker you'd tagged and configured could silently drop out of view, and a worker that came back after a wipe showed up as a second, duplicate row. Now the worker list is saved to disk and survives restarts, a stopped worker simply shows as offline with how long it's been gone, and a worker returning without its saved identity takes over its old row instead of creating a new one. The per-row Actions menu gains a Delete item, which is the only way a worker leaves the list; it's disabled with an explanation while the worker is online (a running worker would just re-register) and for the built-in worker. Remote workers pick up the matching client-side change through the usual automatic source update.

Pick an accent colour. Settings → Display has a new Accent colour row with eight presets. Indigo stays the default, and each colour is tuned so both coloured text and button labels stay readable in light and dark mode.

Config history diffs are easier to read. The history drawer's diff view now marks every change in the scrollbar and folds away unchanged sections, GitHub-style, so a one-line edit in a long config is easy to spot.

Kubernetes: sidecars, init containers and extra volumes. Both Helm charts now accept initContainers, extraContainers, extraVolumes and extraVolumeMounts, so you can, for example, run git-sync to pull your ESPHome YAML straight from a Git repository. The server chart's README has a worked example.

Fixes

"ESPHome install failed" right after an ESPHome update. When the ESPHome add-on updated, Fleet could start installing the new version twice at the same moment (once for the version change, once for the switch in the UI), and the two installs wiped each other out. Retrying could hit the same collision with the built-in worker. Installs of the same version now wait for each other, and the second one reuses the first's result.

ESP32 builds no longer fail the first time a new ESPHome version arrives. On a build worker running more than one build at a time, upgrading the fleet to ESPHome 2026.10.0 could fail a batch of ESP32 devices with "Failed to download from all mirrors" or "Install ESP-IDF … Python dependencies failure." The protection from 1.7.3, which lets only one build download a new ESP32 toolchain, was being switched off too early: an ESP8266 build, or a leftover toolchain from the previous ESPHome release, made the worker think the new toolchain was already in place. Workers now check for the exact toolchain version the ESPHome release needs, so the first ESP32 build downloads it alone and the rest wait for it. Re-queue any devices that failed this way and they'll build normally.

Devices built from online packages pick up upstream fixes again. If a device's YAML pulls packages or external components from GitHub (for example the Kauf plug configs), the add-on kept using the first copy it ever downloaded and never refreshed it. When a new ESPHome release needed an updated version of that package, those devices kept failing with an error from the old copy, even after the package author had published a fix. Packages are now refreshed on the same schedule ESPHome itself uses (daily by default, or never for sources you've pinned).

Entities going "unavailable" after every Home Assistant restart. Per-device and per-worker entities from the Fleet integration showed "This entity is no longer being provided" after each restart or integration reload. They now come back every time.

Changing a device's static IP in its YAML now updates the address Fleet shows and uses within about 30 seconds of saving. Previously the Devices tab kept the first address it ever saw, and mDNS could overwrite a pinned static IP.

"ESPHome install failed" after an add-on update that changed the Python version. The cached ESPHome install is now rebuilt automatically instead of staying broken.

Compiles started while ESPHome is still installing no longer fail with a YAML error. They wait and start once the install finishes.

Clearer install errors. When a worker's image is too old for the ESPHome version you picked, the error now says so instead of claiming the release doesn't exist. Very slow hosts can raise the install time limit with the ESPHOME_INSTALL_TIMEOUT worker setting.

Add-on fails to start because port 8765 is taken. The log now says plainly that another add-on (usually OpenThread Border Router) holds the port and how to fix it, instead of looking like a normal shutdown.

Large build workers no longer refuse every job as "disk full". A worker on a multi-terabyte drive above 95% use still has plenty of space; the disk-full check now also looks at actual free space. Thanks to @htilly.

Smaller worker fixes. ESP32 builds made with ESPHome's newer ESP-IDF toolchain now keep their OTA firmware file, a half-finished ESPHome install repairs itself, and a slow config validation no longer freezes the web UI. Thanks to @htilly.

Remote build workers must rebuild their Docker image after this update. A dependency refresh in the worker image moved the minimum image version, so a worker still on the old image shows as needing an upgrade in the Workers list and won't take jobs until it's rebuilt; the built-in worker updates automatically with the add-on.

Under the hood. A broad dependency refresh across the server, build worker, web UI, and CI workflows — including security fixes in aiohttp and the web UI's HTML sanitiser, plus newer pydantic, cryptography, zeroconf, react, react-dom, react-i18next, and eslint — with the Python lockfiles regenerated to match.

1.7.3 — ESPHome 2026.7 toolchain fallout + dependency sweep

Choose a tag to compare

@weirded weirded released this 28 Jul 20:56
b9bcb80

A patch fast-follow to 1.7.2, closing the last two gaps left by ESPHome 2026.7's new ESP32 build toolchain.

Build workers no longer break permanently when a new ESPHome version arrives. A worker that runs more than one build at a time could corrupt its own ESP32 build tools the first time it compiled with a newly-released ESPHome version — two builds starting at the same moment would both try to download and unpack the same toolchain into the same place, leaving it half-installed. From then on every compile on that worker failed within a second, and because the damage looked like a completed install to ESPHome, it never repaired itself. Reinstalling the worker actually made things worse: a fresh worker starts with an empty toolchain cache, so the next batch of builds simply re-triggered the same collision. Workers running a single build at a time were never affected.

Two changes fix it. Workers now let just one build do the initial toolchain download after an ESPHome version change, with the others waiting a few minutes for it to finish — after that, parallel builds resume as normal, so ongoing speed is unchanged. And a worker that already has a damaged toolchain (including one damaged before this update) now detects it, clears it, and rebuilds automatically on the next compile instead of failing forever. If you have a worker stuck failing every job with an ESP-IDF or toolchain error, it will heal itself the next time it picks up work.

Firmware downloads work again for ESP32 devices on the newest ESPHome releases. Starting with ESPHome 2026.7, ESP32 builds use a new toolchain that writes the finished firmware to a different folder than before. Compiling itself kept working, but the add-on still looked in the old location when saving a copy — so asking for a factory image from the Queue tab failed with "no firmware binary was found," even though the build had just succeeded moments earlier. Regular over-the-air upgrades were never affected. The add-on now checks both locations, so downloads work for ESP32 devices on the new toolchain as well as ESP8266 and LibreTiny devices, which still use the older one. (Community contribution.)

Under the hood. Fourteen dependency updates across the server, build worker, and web UI — including newer aiohttp, aioesphomeapi, and idna — plus fixes to two build-and-release checks that had stopped doing their job: an unpinned code-linter that started failing on unmodified code the day a new version of it was published, and a test-deployment script that was writing to a directory Home Assistant had stopped reading, so its results described an install that never happened. Neither affected the shipped add-on.

1.7.2 — ESPHome 2026.7 support + polish

Choose a tag to compare

@weirded weirded released this 20 Jul 15:25

Newer ESPHome releases now compile. ESPHome 2026.7.0 requires Python 3.12 or newer, but the add-on and its build workers still shipped on Python 3.11 — so every compile pinned to 2026.7.0 failed at the install step with "No matching distribution found," before any firmware was built. Even once that was cleared, ESP32 builds hit a second wall: 2026.7 replaced its build toolchain with a native ESP-IDF installer that needs a system library the image didn't carry, so ESP32 firmware still wouldn't compile. Both are fixed — the add-on now runs on Python 3.13 with the required libraries, and current and upcoming ESPHome versions install and compile normally across ESP32, ESP8266, RP2040, and LibreTiny targets. Remote build workers must rebuild their Docker image to pick up the new runtime — a worker still on the old image shows as needing an upgrade in the Workers list and won't take jobs until it's rebuilt; the built-in worker updates automatically with the add-on.

Settings → Display → Font size. New three-way picker (Small / Normal / Large) that scales the whole UI proportionally — tables, buttons, dialogs, and modals shrink or grow together rather than just body copy. Useful if you run Home Assistant at a non-100 % browser zoom and find Fleet's secondary text too small. Default stays at Normal so existing installs render byte-identical to 1.7.1.

Per-worker "Working" sensor. Every build worker now exposes a binary_sensor.fleet_<worker>_working entity in Home Assistant alongside the existing _online sensor. The entity flips on while the worker has a compile in flight, so you can write automations like "stop the VSCode add-on while any worker is busy" — exposed as the building block rather than hard-coding any specific stop/start action. Generalises beyond VSCode to Frigate, NodeRED, AdGuard, or a dashboard warning card; mirrors HA's BinarySensorDeviceClass.RUNNING for the right icon and label.

Version dropdown — "Installable only" filter. Both the header version dropdown and the Upgrade modal's version picker now offer a third filter alongside Show betas: Installable only (default ON) hides ESPHome versions older than 2023.7.0 — the floor below which pip install esphome==X is likely to fail on the current Python runtime. Untick to see the full PyPI catalogue. Pairs with 1.7.1's compile-with-old-versions fix.

Bug fixes.

  • Compiles no longer get stuck failing after the built-in worker frees disk space. When the add-on's built-in worker trimmed its ESPHome version cache to stay within its disk budget, it could delete the very ESPHome version the server needs to prepare each build — after which every compile failed with a "Bundle creation failed" error and the queue stalled until the add-on was restarted. The server now reserves its active ESPHome version so the worker's cache cleanup leaves it alone, and transparently reinstalls it if it ever goes missing, so the build queue keeps running on its own.
  • wifi.use_address set to a non-.local hostname is now honoured everywhere. For a device reached by a corporate-DNS FQDN (e.g. a VPN'd device where mDNS doesn't reach Home Assistant), the address was respected by the Devices tab and the OTA network-diagnostics dump, but Live Logs and the OTA upload itself silently fell back to <name>.local and failed to connect. All four paths now resolve the address the same way.
  • Standalone Docker workers no longer flap Offline during large ESPHome installs. On the standalone (non-add-on) deployment with slower storage, the built-in worker could stall its own heartbeat while measuring disk usage after a big ESPHome install — long enough for the server to mark it offline and abandon the in-flight compile, then recover, in a loop. Disk measurement now runs on a background sampler so heartbeats keep flowing.

For integrators.

  • KEDA-compatible queue-depth metric. New GET /api/v1/metrics/queue returns {pending, working, active, online_workers, max_parallel_capacity, schema_version} for external autoscalers (KEDA's metrics-api scaler, k8s HPA, Sablier, the in-tree Proxmox scaler). Bearer-auth via the existing /api/v1/* middleware — same worker token, read-only metric. Lets you spin workers up only when there's pending compile work and shut them down when idle.

  • Helm chart for Kubernetes build workers. A new chart under deploy/kubernetes/ deploys build workers on Kubernetes and wires them to KEDA autoscaling off the queue-depth metric above — workers scale up when compile work is pending and down to zero when idle. Ships with a values schema, unit tests, and a README/design doc. (Community contribution.)

Under the hood. The server and build-worker images move to Python 3.13. Groundwork for UI translations landed (an i18next foundation and a Settings language selector); actual translated locales will follow in a later release.

1.7.1 — Fleet for ESPHome rebrand + community bug fixes

Choose a tag to compare

@weirded weirded released this 10 May 01:50
515a548

A brand-refresh release.

Renamed to "Fleet for ESPHome" (the add-on shipped as ESPHome Fleet in 1.5.0–1.7.0; before that ESPHome Distributed Build Server). The add-on store entry, sidebar tile, browser tab title, in-app wordmark, HA-integration display name, and the device-registry rows under Settings → Devices all flip to the new name on next deploy. The add-on slug, the integration domain, every esphome_fleet.* HA service, the GitHub repo, and the worker's Docker image names stay on their existing identifiers — nothing to migrate.

YAML editor — Save vs. Save & Close. The editor footer now offers two save buttons: Save writes the file to disk and stays open without creating a git commit (use it as a working checkpoint while you keep editing); Save & Close writes the file, commits, and closes the modal. With auto-commit on the close path commits silently with a default message; with auto-commit off it prompts for a message. The old "Save & Commit" button is gone — Save & Close covers both flows. The browser's "Save As HTML" no longer hijacks Ctrl+S (Cmd+S on Mac) inside the editor either; Ctrl+S maps to the same plain Save. The footer also has an explicit Close button (or just press Esc).

Scheduler — remote workers no longer starve behind a fast local one. With the local worker on 1 slot and remote workers idle, the scheduler used to defer every remote claim back to the local worker (because the local's "active" count momentarily dipped to 0 between jobs). The queue now claims onto remote workers as soon as it exceeds what the higher-priority pool can drain alone, so a 6-job queue across 1 local + 3 remote workers actually parallelises.

Worker registration — fresh workers no longer get stuck in 500 loops. A freshly registered worker that hadn't reported a perf_score yet could trip a server-side TypeError on every job poll, returning HTTP 500 forever; the worker would never claim a job, never benchmark, and never recover. The scheduler is now defensive on missing/None perf data and falls through to "no job for you right now" on any unexpected eligibility-check error so a single bad worker can't lock itself out of the fleet.

Worker firmware archive — no more spurious "Failed" jobs after a successful flash. A slow worker uploading both factory + ota variants of the compiled binary could race the server's job-timeout checker mid-upload; the server then refused both uploads with HTTP 409 and the UI flagged the job as failed even though the device was already flashed. The server now tolerates a brief grace window after a job finishes so the still-assigned worker's late variant uploads land cleanly.

Slow networks — pip install for ESPHome venvs is more patient. The pip install esphome==X.Y.Z step that runs on first use of a new ESPHome version was timing out for some HAOS users on slower or remote networks. Bumped the timeout, added a single retry, and a failure now names the network/PyPI cause so it's distinguishable from a real compile error.

Install docs — HA Container / HA Core users now have a clearer path. The README and add-on docs both spell out that the Add-on Store install path is HAOS / HA Supervised only, and point HA Container / HA Core users straight at the standalone Docker section.

Older ESPHome versions are installable again. The version dropdown showed every release back to 2024 but rejected any pin below 2026.4 — pre-1.7.1 the server's bundle path required esphome.bundle (which only landed in 2026.4), so anyone pinning an older release got "Fleet for ESPHome 1.6.2+ requires 2026.4.0 or newer" and a stuck install. 1.7.1 keeps the modern, scoped bundle for ESPHome 2026.4+ and falls back to a full-config-dir tar for older versions, so pinning 2026.3.x to dodge a 2026.4 YAML-parser regression (or to keep an older toolchain) just works. The legacy bundle ships the full config directory to the worker — same shape ESPHome's own dashboard uses — so users sharing workers with untrusted parties should still pin to 2026.4+; single-user fleets are unaffected.

Quieter device logs — Fleet no longer reconnects every 60 seconds. The add-on used to open a fresh native-API connection to every known ESPHome device once a minute just to read the running firmware version. On a device with on_connect: automations or a chatty publish-on-connect path, those automations re-fired on every Fleet poll; on devices whose ESPHome firmware allowed only one API client, the constant churn could compete with Home Assistant's persistent connection and pressure the device's reboot_timeout. Fleet now leans on mDNS announcements (which already carry the running version) for steady-state liveness and only opens an API connection on first sight of a new device or right after a Fleet-driven OTA. Power users diagnosing a flaky device can re-enable the old behaviour by flipping device_native_api_poll in Settings.

Bug fixes (carried from earlier in the cycle).

  • #231 — The "Authentication Expired" repair flow, the initial config-flow copy, and the underlying error message used to point users to Settings → Add-ons → ESPHome Fleet → Configuration (the Supervisor add-on Configuration tab). The Server token actually lives in the add-on's own UI at Settings → Authentication → Server token. The four affected strings now match the visible UI labels.

1.7.0 — fleet tags, routing rules, bounded worker disk

Choose a tag to compare

@weirded weirded released this 03 May 03:50
2f3690d

A major release built around fleet tags + rule-based job routing, with bounded worker disk, device-management polish, and a unified Upgrade modal on top.

Tags and routing rules. Devices and workers can now carry user-managed tags. Click any tag chip in the Devices or Workers tab to edit; autocomplete pulls from the fleet's existing tag pool. A new tag-filter pill bar above each table narrows the visible set as you click successive tags.

  • Routing rules… in the Workers toolbar opens a builder with all_of / any_of / none_of clauses against device and worker tags. A rule constrains which workers may claim matching jobs, with a live preview of how many devices and online workers it covers.
  • A new BLOCKED queue state surfaces when a rule eliminates every eligible worker; click the badge to jump straight into the offending rule.
  • Workers can be tagged at startup; the Connect Worker dialog has a Tags field that bakes the value into the generated docker snippet.
  • esphome_fleet.compile and esphome_fleet.validate HA services accept a tags list (with optional any_of / all_of / none_of matcher) so an automation can target a tag-defined subset of the fleet without naming each device.

Unified Upgrade modal. Per-row Upgrade, the bulk-upgrade items, and per-row Rerun all open the same modal — pick action, worker, and ESPHome version up-front, then confirm.

  • Three actions: Upgrade Now, Download Now, or Schedule for later.
  • Three worker modes: Any worker, a Specific worker, or a Tag expression that constrains a one-off compile (e.g. "any worker tagged windows") without authoring a full routing rule.
  • When your selection conflicts with an active routing rule, the modal lists the offending rules and offers an Upgrade & override path.

Bounded worker disk. Build workers used to grow their cache without an upper bound. 1.7.0 replaces the loose MAX_ESPHOME_VERSIONS=3 count cap with a single byte budget across the whole tree (default 10 GiB), configurable per-worker or fleet-wide in Settings → Disk management. The Workers tab shows Quota: 2.1 / 10 GiB per worker and a worker that fills its disk pauses itself with a disk full badge instead of repeatedly failing jobs with No space left on device.

Device-management polish.

  • Archived devices live in the same table at 50 % opacity below active ones — toggle Show archived devices in the column picker. The separate Archived page is gone, and Archive moves into the row menu (no confirm modal, just a toast with the restore path).
  • Ping device… fires 10 ICMP packets at the device's OTA address and reports reachability + RTT. Works on every install path, including HAOS where unprivileged ICMP is disabled.
  • Install to address… sends the OTA bundle to a hand-typed address — useful when mDNS resolves the wrong IP after a router reboot or a device shows up on a recovery network.
  • View rendered config opens a read-only, syntax-highlighted view of the fully-resolved YAML (!secret substituted, packages: flattened, external_components: realized). Header carries a "contains plaintext secrets — copy with care" notice.

Workers self-heal corrupted toolchains. When a build fails because the cached PlatformIO toolchain is in a bad state, the worker now wipes the affected directories and retries the same job in-process. The first hit pays a 5–10 min re-download tax; previously an operator had to ssh in and clean up by hand.

Other fleet-management wins.

  • New Upgrade Changed bulk action targets every device whose YAML has drifted since its last successful compile (distinct from Upgrade Outdated, which targets firmware-version mismatch).
  • New Commit all uncommitted entry commits every dirty YAML in one shot, with an optional shared commit message and a live count.
  • Bulk Archive Selected / Unarchive Selected in the Devices action menu, plus per-row Rerun that opens the Upgrade modal pre-seeded with the original job's parameters.
  • New firmware_retention_days Settings field (default 2) evicts old compile binaries; combined with excluding firmware/ from add-on backups, a typical partial-addon snapshot drops from ~237 MB to ~2 MB.
  • The Queue's worker-selection cell now stacks what the user asked for above why this worker won (Pinned / Only eligible / Least busy / Fastest / First to poll), so the routing story is readable from one cell.
  • New optional Devices-tab columns: Platform (chip family + PlatformIO board) and BLE proxy (off / passive / active). Both default off.
  • The address-source label under each IP carries a plain-language tooltip ("Detected via ARP scan…", "From wifi.use_address in the device YAML…").

Bug fixes.

  • The Last compiled column is now populated for every device running ESPHome firmware, even when the server has no compile history — falls back to the device-reported build time (marked ~).
  • Devices that compose their esphome: block via packages: / <<: !include keep their friendly-name and area when archived (used to render the bare filename).
  • Archived devices show Archived in the Status column instead of "Checking…" forever, and tag editing is disabled on them on every surface.
  • The Reconfigure form's submit button reads Save changes instead of HA's stock "Submit".
  • Tag chip palette redesigned for distinctness — a row of 4 tags now reads as 4 visibly different colors instead of "two reds, two greens".
  • Cleaning a worker's cache no longer wipes ESPHome venvs or PlatformIO toolchains; it clears only the volatile build outputs, so the next compile doesn't pay the toolchain re-download tax.
  • Concurrent compiles no longer race on a shared git checkout — a batch sharing the same external_components repo used to fail with multiple phantom errors.

Under the hood. Hardened the file-mutation paths so every UI edit leaves your config's git history clean, and CI now builds against multiple ESPHome versions to catch upstream breakage earlier.

1.6.2 — install paths users can trust

Choose a tag to compare

@weirded weirded released this 25 Apr 03:13
2c278bb

A hardening release on top of 1.6.1.

Corrections to 1.6.1. A handful of things 1.6.1 claimed or shipped weren't quite right; 1.6.2 is where they get honest.

  • AppArmor confinement is real now. 1.6.1 added an AppArmor profile and lit up the Supervisor security-star badge, but the profile was permissive enough that it didn't meaningfully confine the running container. 1.6.2's profile keeps the badge and adds a handful of explicit deny rules that actually close concrete paths (/etc/shadow*, /run/secrets/**, /proc/<pid>/mem, kernel sysctl writes, cross-container ptrace).
  • Integration quality-scale claim corrected. 1.6.1's manifest.json declared silver, but the audit file (quality_scale.yaml) hadn't been reconciled against the Silver rules. 1.6.2 honestly retreats to bronze — which is hassfest-validated and accurately reflects what's in the repo today. The gold tier-flip moves to 1.6.3 where every rule gets walked to done/exempt with a reason.
  • Install paths that were documented to work but didn't. Fresh HAOS installs could get stuck on "Installing ESPHome…" forever, standalone Docker installs required an HA Bearer token the user had no way to obtain, and OTA failed outright on any device with a non-.local mDNS domain. Each of those is fixed in this release — see Bug fixes below for the specifics.
  • Remote workers stop shipping the whole config directory. 1.6.1's worker bundle tar+gzipped every file under /config/esphome/, including .git/ (with push credentials baked into remote URLs), every unrelated device's YAML, and in-place PlatformIO caches. The bundle now contains only what the target being compiled actually needs. Upgrading in place is enough — no action required.

Bug fixes.

  • Add-on install no longer fails with Image docker.io/library/docker:<version>-cli does not exist when Docker Hub is rate-limiting or briefly unreachable. The add-on now ships as a prebuilt multi-architecture image on GitHub Container Registry, so fresh installs pull in seconds instead of building locally through the Docker-in-Docker builder image that was the source of the failure.
  • Fresh installs without the Home Assistant ESPHome builder add-on no longer get stuck on "Installing ESPHome…" forever. The first boot now auto-bootstraps the latest stable ESPHome from PyPI when no other version is pinned; if the ESPHome builder add-on is installed later, its version takes over on the next refresh. The Version picker in the header is also a working recovery path now — picking a version there actually triggers the install instead of just recording the selection.
  • Direct-port access to the web UI works out of the box on standalone Docker Compose installs. 1.6.1 required a Home Assistant Bearer token on direct port 8765 regardless of install path, which left standalone users at a bare-JSON 401 with no way forward. Fresh standalone installs now default "Require Home Assistant auth on direct port" to off; turn it on in Settings → Authentication if the port is reachable from an untrusted network. Home Assistant add-on installs (where the tunnel is always available) continue to default the flag to on, so direct port 8765 still returns 401 there. Home Assistant tunnel access is unaffected on either install path.
  • When direct-port auth is on and a browser lands on :8765 without a token, you now see a styled Authentication required page that explains both recovery paths (provide a token, or disable the flag via the Home Assistant tunnel), instead of a raw JSON error body.
  • OTA targets honour wifi.domain / ethernet.domain / openthread.domain again. If your devices live on a non-.local mDNS domain, the worker now uses the correct <name><domain> address ESPHome itself would use — previously the compile succeeded but the OTA step hung at "Error resolving IP address" because the server was handing the worker <name>.local regardless of the domain: field.
  • Queue page: the Clear dropdown has a new Clear Selected option, so you can check a few rows and remove just those. The existing "Clear Succeeded", "Clear All Finished", and "Clear Entire Queue" entries are unchanged.
  • Startup logs no longer flood with a WARNING line every scan cycle when /config/esphome/ doesn't exist. A single informational line now explains what the state means (install the Home Assistant ESPHome builder add-on, or create the directory yourself).
  • Add Device now works on a truly-empty first install, even when /config/esphome/ doesn't exist yet (i.e. you installed Fleet without ever installing the Home Assistant ESPHome builder add-on). The directory is created the moment you create your first device instead of failing with a No such file or directory error.
  • The Connect Worker modal's Bash and PowerShell snippets now include --network host, matching the Docker Compose snippet that already had network_mode: host. Without it, a worker started from the copy-pasted command landed on Docker's default bridge network and could not reach ESP devices on the host's LAN, so every OTA failed.
  • The Devices tab's "config changed" badge no longer lights up on devices you haven't edited. It now tracks the same git status the rest of the UI uses, so a device whose YAML is unchanged locally stays unflagged even if the file's timestamp got nudged (an editor autosave, a git checkout, or a failed OTA that left the firmware's own "compiled at" time behind). The Upgrade button's "config has changed" highlight also keys off this same signal now, so the two can't disagree.
  • Fixes a hang where the add-on pegged at 100 % CPU and the UI stopped responding on fleets with large /config/esphome/ trees (many top-level YAMLs plus a common/ package tree and/or external-components directory). Root cause was the per-job config bundling synchronously tar+gzipping the whole directory on the event loop for every claimed job — now bundles are built off-thread and scoped to just the files the target being compiled references (see Remote workers stop shipping the whole config directory above).
  • Reconfigure and Reauth flows no longer crash with TypeError: object dict can't be used in 'await' expression on Home Assistant 2024.11 and newer — the handler was incorrectly awaiting a synchronous helper. Was a shipped regression in 1.6.1; every Reconfigure click logged the traceback.

Smaller changes.

  • New Request diagnostics action. Click it in the Workers tab's per-worker Actions menu (online workers only) to pull a live Python thread dump from any remote worker, or in Settings → Advanced → Diagnostics to capture one from the add-on's own server process. The dump downloads as a timestamped .txt file you can attach to a bug report. Useful when a compile hangs or a worker pegs at 100 % CPU — the dump shows exactly which line each thread is sitting on. Works on every install variant out of the box — Home Assistant add-on, HAOS, Supervised, and standalone Docker — with no special container capabilities or shell access required on your part.

Under the hood.

  • Integration config-flow hardening: the Reconfigure and Reauth flows have gained end-to-end tests against a real Home Assistant fixture. The same test pass is what caught the Reconfigure TypeError in 1.6.1 covered under Bug fixes above.

1.6.1 — bug fixes, Supervisor hardening, integration polish

Choose a tag to compare

@weirded weirded released this 21 Apr 15:05
f4951a2

A bug-fix + polish release on top of 1.6.

Bug fixes.

  • Static-IP devices OTA correctly again — workers were shipping the .local mDNS hostname to esphome upload when the YAML declared a static IP.
  • Live logs and OTA to encrypted devices work on fresh first-boot — the scanner used to race the ESPHome install and miss the api.encryption.key.
  • Turning config versioning on from the Settings drawer now initialises the git repo immediately (no add-on restart needed). If .git/ goes missing on a later boot (restored backup, container rebuild), it's recreated automatically.
  • The noisy aioesphomeapi.connection: disconnect request failed traceback that fired after every OTA is gone — the expected disconnect-during-reboot case now logs at DEBUG instead of ERROR.
  • OTA failure diagnostics now report real ping RTT / packet-loss instead of Ping: [Errno 2] No such file or directory: 'ping'.
  • When config versioning is off, every UI surface that led to an empty History drawer (hamburger Config history…, editor History button, Diff since compile, commit-hash columns) is now hidden or disabled with a tooltip.
  • Devices tab IP column is visible again for users upgrading from before it existed.

Small additions.

  • Every successful compile's firmware is archived on the server, not just "Download Now" jobs. A Download button shows up on every row in Queue, per-device Compile history, and fleet-wide Queue history.
  • Optional MAC column on the Devices tab (toggle via the column picker). The IP column gets an ARP-table fallback when mDNS doesn't know the address.
  • New Worker selection column in Queue + Compile history explaining why a given worker picked up a compile (Pinned to worker, Only worker online, Least busy, Fastest available, First to poll).
  • The integration now supports HA Download diagnostics (redacted), shows up on the System Health panel, and the Configure button lets you edit the add-on URL + token in place. Deleted devices are removed from HA's device registry automatically.

Housekeeping.

  • AppArmor profile added; the add-on runs under confinement now and the Supervisor security-star card reflects it. stage: experimental dropped.
  • All open Dependabot alerts resolved (dompurify + hono).
  • Integration now ships a hassfest-validated quality-scale declaration (stays at bronze) and runs hassfest in CI.
  • Add-on icon swapped for the one the official ESPHome add-on ships, so the store card renders at the same visual size as ESPHome Device Builder.
  • YAML config comment marker renamed from # distributed-esphome: to # esphome-fleet: (both are still read; the new one is written on save).

Note for standalone worker users. The worker Docker image bumped from version 6 to 7 (adds iputils-ping). Old-image workers are flagged in the Workers tab — run docker pull ghcr.io/weirded/esphome-dist-client:latest && docker restart <name> to refresh.

1.6.0 — config versioning + job history + in-app Settings

Choose a tag to compare

@weirded weirded released this 20 Apr 03:34
1862b38

An in-app Settings drawer. Fleet now has a proper Settings surface — click the gear icon in the header. Every knob that used to live on the Supervisor Configuration tab (server token, timeouts, worker-offline threshold, require-HA-auth) has moved here, and several new fields sit alongside them (history retention, firmware cache size, job-log retention, time-of-day format, config-versioning toggle + author). Edits apply immediately without restarting the add-on. The drawer is split into Basic and Advanced tabs — Basic holds what you'll touch most (versioning, authentication, display), Advanced holds the plumbing knobs (retention, cache, timeouts, polling). Values from your previous Supervisor configuration are imported on first boot so nothing you set before is lost.

Per-file config history with diff + rollback. Every save to /config/esphome/ becomes a local git commit. Click Config history… in any device's hamburger menu to browse that file's full history — Monaco side-by-side diff viewer on the right, commit list on the left — and roll back to any earlier version in one click. The Queue tab grows a Diff since compile button that opens the history panel pre-set to "what's changed since this compile started." Works both for users without git experience (fresh installs get a Fleet-owned repo) and users who already run git in /config/esphome/ (your existing repo is left completely untouched — no .gitignore mutations, no identity override, no auto-commits unless you opt in). Commit messages are human-readable ("Automatically saved after editing in UI", "Pinned ESPHome version", etc.) so git log reads like an activity feed.

Archive is tracked in git. Deleted devices land in .archive/ as before, but the move now uses git mv so blame + log --follow thread cleanly through the delete → restore cycle. Permanent-delete runs through git rm + commit, so even that's recoverable by a git operator.

Compile history that sticks around. The Queue tab only ever shows the latest compile per device (by design — one row per device, not an ever-growing list). 1.6 adds a persistent history that survives queue coalescing and explicit Clear, reachable three ways:

  • History button on the Queue toolbar — fleet-wide modal with filters for device, state, and time window, plus a universal search.
  • Compile history… in each Device row's hamburger — scoped to that one device, with a stats strip (total / ok / failed / avg) at the top.
  • An optional Last compiled column on the Devices tab (off by default, enable via the column picker).

Every row expands to show the last bit of the build log inline.

Local worker starts active. Fresh installs boot the built-in local-worker with 1 slot instead of 0 (paused). The first compile just works; raise the slot count in Workers if you want parallelism, set to 0 to pause.

Smaller fleet-management wins.

  • Devices tab: + New Device and Archive… buttons collapsed into one Add device ▾ dropdown — New device, Duplicate existing (clone any target), or Restore from archive.
  • Devices tab: hamburger items that would no-op on a given device (Restart with no restart_button in YAML, Copy API Key with no encryption) are disabled with a tooltip explaining what YAML to add to enable them.
  • Queue + History "Triggered" column is now consistent — same icons, same labels across both surfaces, with compiles initiated via the direct API rendering distinctly from HA-action triggers.
  • Queue History is sortable on every column (Device, State, ESPHome, Duration, Started, Finished, Trigger, Worker) with infinite-scroll pagination.
  • secrets.yaml no longer trips the Validate button — the esphome config schema doesn't apply to a !secret dictionary, so Fleet now mirrors the ESPHome Dashboard and skips validation on that file.

UX polish pass.

  • The History drawer's Monaco diff editor follows the app's light/dark theme.
  • Drawers (History, Compile history) darken the underlying tab so still-updating rows don't distract.
  • Restore button on the HEAD row is disabled with "Already at this version" when the working tree is clean.
  • Every greyed-out dropdown item (Retry All Failed, Clear Succeeded, bulk actions, etc.) explains why it's disabled in a hover tooltip.
  • Every numeric Settings field shows its (default N, min M, max K) bounds inline.
  • Authentication field's Show / Copy icon buttons are properly labeled for screen readers.

Bug fixes users will notice.

  • Scheduled compiles cancelled before a worker picks them up now render Scheduled (once) — cancelled before start in history instead of a row full of — dashes.
  • Restore-confirmation dialog no longer hard-codes "No new commit will be created" — it now reads the live auto-commit setting and tells the truth.
  • Manual-commit message field's placeholder is the curated default ("Manually committed from UI") instead of the old raw save: foo.yaml (manual) form.
  • Queue tab's Commit column is a clickable short-hash button that opens the History panel preset to "what's changed since this compile."
  • Queue / History / Schedules tabs show absolute timestamps stacked below relative ones so you don't have to hover to see when something happened.
  • Config-history diff editor is the full visible drawer height instead of a cramped 330 px panel.
  • Toggling auto-commit on with uncommitted changes offers to commit them first instead of silently leaving them dirty.

Under the hood. Settings writes are atomic and validated per-field; downstream consumers re-read them on every tick so a drawer edit takes effect within one cycle with no restart. The auto-commit write path is debounced + async so the editor's Save never blocks on git. The job-history DAO uses SQLite WAL mode for read-while-write concurrency; writes are sub-millisecond.

v1.5.0 — ESPHome Fleet

Choose a tag to compare

@weirded weirded released this 17 Apr 16:05

Rebrand: now called ESPHome Fleet. Same add-on, same Docker images, no migration needed — just a new name that better describes what the tool does. The HA sidebar entry, add-on store name, browser tab title, and add-on logs all read "ESPHome Fleet" instead of "ESPHome Distributed Build Server". The GitHub repo, Docker image names, and add-on slug are unchanged so existing installs upgrade in place with no action required.

Native Home Assistant integration. ESPHome Fleet is now a first-class HA citizen. The add-on drops a custom integration into your HA config on first boot and advertises itself via mDNS; HA pops a one-click "ESPHome Fleet discovered" notification. Once you confirm, every managed device, every build worker, and the add-on itself become real HA devices with entities you can dashboard on:

  • Update entities per device — use HA's built-in Update card to upgrade firmware, with the current/latest version both visible.
  • Sensors for queue depth, worker online state, per-worker active-job count, fleet-wide online/outdated/total-slot counts, and per-device running version.
  • Buttons to kick off compiles directly from an automation.
  • Numbers for each worker's max-parallel-jobs slot.
  • esphome_fleet.compile / .cancel / .validate actions — invoke from the HA action editor with a real device picker (choose the device, not a filename) and an optional worker pin. Supports bulk targets, "all", and "outdated".
  • esphome_fleet_compile_complete events on the HA bus for automation triggers — fires once per terminal state with target, state, duration, worker, and schedule context.
  • Devices managed by this add-on auto-merge with the same device from the stock ESPHome integration, so the Update card sits next to your existing ESPHome sensors and switches.

Downloadable firmware binaries. Pick "Download Now" in the Upgrade dialog to get a compile without OTA — the Queue tab then offers a Download menu with every variant the compile produced. ESP32 gets both Factory (full flash image for first-time USB/serial flash) and OTA (smaller OTA-safe image); ESP8266 gets OTA. Each is available raw or gzip-compressed for smaller wire transfer (~30–40% smaller on typical ESP firmware).

Mandatory authentication on the direct-port API. The /ui/api/* endpoints now require an Authorization: Bearer <token> on port 8765. Two token shapes are accepted: the add-on's shared worker token (used automatically by the HA integration's coordinator, so no user action needed) and a Home Assistant long-lived access token (for scripts and curl — gives real per-user audit attribution). Ingress access from the HA sidebar is unaffected. The add-on's Bearer token flows through Supervisor discovery into the integration transparently — just accept the discovery notification and it works.

ESPHome unbundled from the server image. The add-on no longer ships with a particular ESPHome version baked in — it installs whatever your HA ESPHome add-on reports at first boot. This means your builds always match the version HA expects. Heads up: first boot takes 1–3 minutes while ESPHome installs into /data/esphome-versions/. The UI shows an "Installing ESPHome…" banner during that window and features that depend on the binary (validate, autocomplete, compiles) stay disabled until ready. Subsequent restarts are instant because the install is cached.

Supply-chain signals you can verify. Every GHCR image carries a cosign signature (keyless, GitHub OIDC) and a CycloneDX-format SBOM attestation. See DOCS.md for the cosign verify + cosign verify-attestation commands. Every GitHub Actions step is SHA-pinned, and CI fails on any unpinned uses: line. pip-audit and npm audit gate every push.

UI quality sprint.

  • Accessibility — every icon-only button now has both an aria-label (for screen readers) and a title (for hover), enforced by a new invariant. Hamburger/sort-header clicks are real <button>s now; row-menu keyboard nav comes from Radix.
  • Lucide icons everywhere — emoji glyphs (🕐 📅 📌 👁 🔒 ☀ ☾) and HTML entities (⋮ ⚙ ↻ ↓ ↗ ▲ ▼) replaced with consistent Lucide icons across the app.
  • One Action selector in the Upgrade dialog — Upgrade Now / Download Now / Schedule Upgrade, replacing the former nested Now/Scheduled + Compile+OTA/Compile+Download toggles.
  • Queue dropdown stability — the Download and Retry dropdown menus no longer slam shut on each 1 Hz SWR poll (same class of bug as the devices-tab hamburger #2 fix). Applied to every row-cell DropdownMenu in the app.
  • Table polish — column headers now consistently sentence-case across all four tabs; state badges (Compiling, Failed, Timed Out, Cancelled, etc.) are title-case everywhere, same badge component used on both the Queue and Workers tabs. The Worker cell renders the slot on a muted second line (local-worker / slot 2) instead of gluing it to the hostname.
  • Queue tab triggered-by column — shows "Manual", "Recurring · Daily 03:00", "Once @ 2026-04-17 08:30" with full cron/tz in the tooltip.
  • Connect Worker modal — new Docker Compose format tab alongside Bash and PowerShell, with the shared token baked into the snippet. Default container name is now esphome-fleet-worker (rebranded). The old docker-compose.worker.yml in the repo root is retired — the modal generates it live.
  • Download buttons disambiguated — per-row "Download .bin" vs log-file "Download log" are no longer ambiguously named.
  • Schedules tab empty-state copy fixed to point at the Upgrade dialog's Scheduled option (the previous text referenced a hamburger menu item that had been removed).
  • Error Boundary around the root <App /> so a transient render error shows a recoverable dialog instead of a blank page.
  • QS.27 polish — persisted sort state per tab, URL ?tab= deep-linking, and a small pile of micro-cleanups.

Under the hood.

  • Frontend API calls all funnel through a typed api/client.ts layer with named response interfaces; no more fetch() in components (enforced invariant). Silent return [] on !r.ok fallbacks are now throws that SWR's error path surfaces.
  • DevicesTab.tsx split from 1,173 lines + 24 hooks into six focused files. Monaco glue split into yamlValidation.ts / completionProvider.ts / monacoSetup.ts.
  • HA integration tests (37 new): config flow, device-info builders, coordinator events, installer atomicity, services schema + lifecycle.
  • Protocol (protocol.py) pydantic contracts enforced byte-identical between server and client; every /api/v1/* handler parses through a typed model.
  • integration_installer now writes atomically (os.replace over a staging dir) so a crash mid-install can't strand a missing custom integration.
  • 16 enforced invariants checked mechanically on every push (up from 10): fetch() placement, shadcn wrappers, any ban in new code, <td> flex ban, typed e2e fixtures, YAML safe_load vs regex, subprocess logging, worker image version bumps, hash-pinned lockfiles, macOS-package lock guard, protocol parity, --ignore-vuln applicability, typed request bodies, icon-button labels, e2e waits, API-layer error propagation.

1.4.0 — Fleet management & automation

Choose a tag to compare

@weirded weirded released this 14 Apr 15:03
18b815d

The fleet management release. Schedule upgrades, pin device versions, and create new devices — all from the UI.

New features

  • Scheduled upgrades — set any device to upgrade on a recurring schedule (daily, weekly, monthly, custom cron) or at a one-time future date. Times are entered in your local timezone. A new Schedules tab lists every scheduled device with its next/last run, status, and recent run history.
  • Per-device version pinning — pin individual devices to a specific ESPHome version. Bulk upgrades and scheduled runs respect the pin. The upgrade modal warns you when a one-off upgrade differs from a device's pin.
  • Create devices from the UI — "+ New Device" makes a new YAML from a stub; "Duplicate…" clones an existing device (preserving !include, !secret, and substitutions). Both open the editor on the new file. Cancelling without saving cleans up after itself.
  • Unified Upgrade dialog — one dialog handles "upgrade now" and "schedule for later" with a mode toggle.
  • Searchable ESPHome version picker (#44) — both the header version dropdown and the Upgrade dialog now have a search box and list every historic ESPHome release from PyPI (no more 50-version cap). Beta versions are hidden by default with a "Show betas" toggle.
  • Home Assistant device deep-links — the "Yes" indicator in the HA column is now a clickable link to the device's page in Home Assistant.
  • Cancel and clear queue jobs — cancelled jobs are now distinct from failed (gray "Cancelled" badge) and can be retried. New "Clear Entire Queue" option cancels everything in flight and clears all terminal jobs in one click.

Improvements

  • The editor and log viewers now scale to fill the available screen space.
  • Schedule fields and last-run timestamps in the Devices and Schedules tabs are clickable shortcuts that open the upgrade dialog.
  • Concurrent compiles on the same worker no longer share build directories — full parallelism, with cache reuse across worker slots.
  • Home Assistant backup sizes shrunk significantly — the local worker's ESPHome cache (1-2 GB after a fleet upgrade) is no longer included in backups.

Bug fixes

  • Web server links now appear for devices that enable web_server: with default settings.
  • Devices that have been offline for more than 4 hours are now cleaned up automatically, so the Devices tab doesn't fill up with ghost entries from retired or unplugged hardware.