- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks failed
Validate with hassfest / validate (push) Has been cancelled
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. |
||
| .github/workflows | ||
| custom_components/beszel_api | ||
| pictures | ||
| .gitignore | ||
| hacs.json | ||
| LICENSE | ||
| README.md | ||
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
problembinary 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
problembinary sensor. Failed unit names land in the attributes. - ZFS pools - capacity used, free, used and size per pool, and a
problembinary sensor that trips on a pool that is notONLINE, 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.tis 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. Asmart_devicesrecord carries/dev/sdcand the model. - A
temp,hoursorcyclesof 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 amin_maxhelper 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.bbon 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 autility_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
niat 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_rateand 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.problembinary sensors end inProblem. - Uptime is a
TIMESTAMP"Last Boot" entity instead of aTOTAL_INCREASINGcount 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, sinceinfo.uhas 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_statsis now filtered totype = '1m'. The hub writes 10m/20m/120m/ 480m aggregate rows into the same collection, so sorting by-createdalone could return an averaged row instead of the freshest sample.- Collections whose
updatedfield is a number (containers,systemd_services) are fetched as raw dicts. The pocketbase client's model decoder assumes a string there and raises. BeszelBatterySensor.iconusedandwhere it neededor, so a host reporting stats without abatkey unpackedNone.- The status binary sensor now exposes the raw
statusas an attribute, sopausedis distinguishable fromdown. - 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
- Install the Beszel API addon via HACS from the link above or by searching for it inside of HACS
- Restart HomeAssistant
- Go to integrations, press Add integration and search for BeszelAPI
- 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
- 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

And here one card I created for myself using those sensors:

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"