No description
This repository has been archived on 2026-09-09. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Ivan Pavlina a70b77ac8a
Some checks failed
Validate with hassfest / validate (push) Has been cancelled
Resolve the host device with async_get_device_by_identifier
async_get_device is deprecated too, and its replacement needs the config
entry id, so the entry id is passed down to the S.M.A.R.T. entities. Falls
back to the old lookup on Home Assistant older than 2026.9.
2026-09-07 13:33:16 +02:00
.github/workflows Drop the HACS validation workflow 2026-09-07 12:32:28 +02:00
custom_components/beszel_api Resolve the host device with async_get_device_by_identifier 2026-09-07 13:33:16 +02:00
pictures Expose the Beszel hub data upstream omits 2026-09-07 12:32:13 +02:00
.gitignore Expose the Beszel hub data upstream omits 2026-09-07 12:32:13 +02:00
hacs.json Expose the Beszel hub data upstream omits 2026-09-07 12:32:13 +02:00
LICENSE Expose the Beszel hub data upstream omits 2026-09-07 12:32:13 +02:00
README.md Add per-disk SMART, per-interface network and capacity sensors 2026-09-07 13:25:03 +02:00

Beszel API (fork)

Fork of Ronjar/beszel-ha that surfaces the Beszel hub data the upstream integration leaves on the table. Same domain (beszel_api), so an existing config entry and its entity history carry over.

Not installable through HACS, which only accepts public GitHub repositories. Install by cloning into /config/custom_components/beszel_api and restarting Home Assistant.

What this fork adds

Collections upstream never reads:

  • Containers - container count and unhealthy-container count per system, plus a problem binary sensor. Container names and the health breakdown are attributes; there is deliberately no entity per container.
  • systemd services - service count and failed-service count per system, plus a problem binary sensor. Failed unit names land in the attributes.
  • ZFS pools - capacity used, free, used and size per pool, and a problem binary sensor that trips on a pool that is not ONLINE, on scrub errors, or on any non-zero per-vdev read/write/checksum counter. Scrub state and the full vdev breakdown are attributes.

Per-disk metrics from the smart_devices records, which upstream only exposed as attributes on one problem sensor:

  • Temperature, power-on hours and power cycles per disk. This is the only way to label a disk temperature: stats.t is keyed by hwmon label (drivetemp, drivetemp_3, drivetemp_40) and nothing in that payload maps a label to a device, so two drives at the same temperature are indistinguishable there. A smart_devices record carries /dev/sdc and the model.
  • A temp, hours or cycles of exactly 0 is reported as unknown rather than as a value. Every MD RAID pseudo-device and the eMMC on the reference hub reports all three as 0, which would otherwise put a permanent zero line into every chart and drag a min_max helper to the floor.
  • Each disk is its own device linked to its host with via_device, carrying the model, serial number and firmware in the device registry rather than in state attributes.

Per-interface network counters from stats.ni:

  • Receive and send rate, plus cumulative received and sent byte counters, per interface. The per-host aggregate is wrong on any bridged host: info.bb on the reference Proxmox reads roughly three times the physical NIC, because each veth pair counts the same traffic twice more.
  • Cumulative counters are TOTAL_INCREASING, ready for a utility_meter, and are created disabled. So are the rates on bridge and veth halves (fw*, veth*, tap*, vnet*, docker*, br-*).
  • Nothing is created on an agent that reports no ni at all - the RouterOS container namespaces /proc/net/dev - rather than a set of permanently unavailable entities.

Fields present in the stats record upstream already fetches but discards:

  • Load average as three entities (1m, 5m, 15m), with load-per-thread
  • CPU I/O wait and CPU steal time, both alertable in the hub itself
  • Buffer/cache memory, and ZFS ARC memory where the host reports it
  • Whole-host disk read/write throughput, cumulative bytes read/written, and disk I/O utilization with r_await / w_await
  • One entity per hardware temperature sensor and per fan, instead of a single temperature entity with everything crammed into its attributes
  • Root disk free space, and per-filesystem used, free and cumulative read/written bytes for every extra filesystem in stats.efs
  • GPU per-engine utilization and package power
  • Battery charge state, and the per-battery map on multi-battery hosts

Richer attributes on entities that already existed:

  • S.M.A.R.T.: raw values for the attributes that actually predict a failing disk (reallocated sectors, pending sectors, offline uncorrectable, UDMA CRC errors, wear indicators), instead of PASSED/FAILED alone. Linux MD RAID devices expose array state, RAID level and resync progress.

Units and naming:

  • Units come from the Home Assistant constants rather than string literals. Beszel divides by 1073741824 and 1048576, so what it labels GB and MB are GiB and MiB - the entities now say so.
  • Byte-rate entities report raw bytes/s with device_class: data_rate and let Home Assistant convert for display. Upstream divided bandwidth by 1024000 and called the result MB/s, which is neither 1e6 nor 1048576.
  • Percentage entities say what they measure: CPU Usage, RAM Usage, Disk Usage, Swap Usage. problem binary sensors end in Problem.
  • Uptime is a TIMESTAMP "Last Boot" entity instead of a TOTAL_INCREASING count of minutes, so Home Assistant renders the elapsed time itself and the recorder stores one state per boot rather than one per poll. The value is held steady unless it drifts more than 90 s, since info.u has one-second resolution.
  • S.M.A.R.T. entities are named after the device path. Upstream used the first word of the model, so eight WD drives on one host all became <host> WDC S.M.A.R.T..
  • The GPU entity is keyed on the hub's GPU index, not the card model, which was both inconsistent with every other entity and unstable across a swap.

Fixes:

  • system_stats is now filtered to type = '1m'. The hub writes 10m/20m/120m/ 480m aggregate rows into the same collection, so sorting by -created alone could return an averaged row instead of the freshest sample.
  • Collections whose updated field is a number (containers, systemd_services) are fetched as raw dicts. The pocketbase client's model decoder assumes a string there and raises.
  • BeszelBatterySensor.icon used and where it needed or, so a host reporting stats without a bat key unpacked None.
  • The status binary sensor now exposes the raw status as an attribute, so paused is distinguishable from down.
  • Entities are built from a coordinator listener rather than only during async_setup_entry, so a GPU that was not detected at boot, a newly added agent, a new pool, disk or interface gets an entity on the next poll instead of needing a reload. The builder is idempotent - it is re-run on every update and only unseen unique ids are added.

Everything is guarded on the data being present, so hosts that report no ZFS, no containers, no fans or no systemd data simply get fewer entities.

Still not exposed

Deliberately left out, in case it is wanted later: the alerts and alerts_history collections, per-core CPU usage (stats.cpus), per-container time series (container_stats), the remaining ~21 S.M.A.R.T. attributes as entities, the hub's peak/max fields, the /api/beszel/{smart,zfs}/refresh actions as buttons, and the container log and systemd unit detail endpoints.

Structural work this fork does not attempt, kept as notes: _attr_has_entity_name plus translation_key instead of hardcoded f"{system.name} ..." names, collapsing the one-class-per-metric layout into SensorEntityDescription tables, re-authenticating on a 401 instead of failing until Home Assistant restarts, collapsing the per-system stats queries into one (or moving to the PocketBase realtime endpoint and becoming local_push), and a hub device with the agents linked via_device.

Entity ids are a contract

Long-term statistics are keyed by entity_id, and history migrated from another integration by renaming is attached to these ids and nothing else. Changing the naming logic silently detaches that history. Treat every entity_id this integration has produced as frozen, and add entities alongside redundant ones rather than replacing them - the per-disk temperature sensors do not make the temperature_drivetemp* entities removable.


Upstream documentation

Installation

Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.

  1. Install the Beszel API addon via HACS from the link above or by searching for it inside of HACS
  2. Restart HomeAssistant
  3. Go to integrations, press Add integration and search for BeszelAPI
  4. In the Setup Dialog use the following values
    • URL: The root url / IP of your Beszel instance, like http://beszel.example.com or https://beszel.example.com
    • user: Either your default admin username / email or (recommended) create another user with the role user and assigning the agents you want to expose to it.
    • password: The password to the user
  5. The API will pull the data and reload every 2 minutes

Currently all machines are added, selection will be added later (you can change this yourself by creating a new user in Beszel's PocketBase and adding this user only to the machines you want to be monitored).

Usage

After installing the following entities will exposed as sensors (more to come):

  • Status (Connection)
  • Uptime (Minutes)
  • CPU (Percentage)
  • Disk usage (Percentage)
  • Temperature (°C)
  • Bandwidth (Mbit/s)
  • RAM (Percentage)
  • Battery (Percentage)

For example if your machine is named test, CPU will be available as sensor.test_cpu

Examples

Here is one of my machines with the entities the integration currently exports Screenshot from HomeAssistant settings page of my device and its entities

And here one card I created for myself using those sensors: Screenshot from HomeAssistant dashboard with a card showing CPU, RAM and Disk usage as bar charts

The YAML for this card layout:

type: custom:vertical-stack-in-card
cards:
  - type: horizontal-stack
    cards:
      - type: custom:mushroom-template-card
        primary: Evergreen
        icon: mdi:server
        secondary: ""
        icon_color: |-
          {% if states('binary_sensor.evergreen_status') | bool %}
            green
          {% else %}
            red
          {% endif %}
        fill_container: false
        multiline_secondary: false
        entity: binary_sensor.evergreen_status
      - type: custom:mushroom-template-card
        entity: sensor.evergreen_uptime
        icon: mdi:sort-clock-descending
        primary: "{{ (states('sensor.evergreen_uptime') | int / 1440) | int  }} Days"
        secondary: ""
        icon_color: blue
        card_mod:
          style: |
            ha-card {
              margin: 0 10px;
              align-items: end;
              box-shadow: none;
            }
  - type: custom:bar-card
    entities:
      - entity: sensor.evergreen_cpu
        name: CPU
        color: "#4caf50"
      - entity: sensor.evergreen_ram
        name: RAM
        color: "#2196f3"
      - entity: sensor.evergreen_disk
        name: Disk
        color: "#f44336"
    positions:
      indicator: "off"