Commit Graph
7 Commits
Author SHA1 Message Date
Claude 142897f84c Self-heal Beszel agent compose files on update
install_beszel() and install_beszel-agent()'s "update" branches only
did a pull+restart, never touching docker-compose.yml — so an
already-installed box would never pick up the systemd/dbus/sensor
mounts or apparmor:unconfined fixes without a manual edit or a
disruptive fresh reinstall.

Add _beszel_patch_agent_compose(), called from both update branches,
that idempotently patches an existing docker-compose.yml with
whichever of the two fixes it's still missing. Anchors on
`network_mode: host` and the docker.sock mount line, both unique to
the beszel-agent service and present in either compose shape
(combined hub+agent or agent-only), so one function covers both
install paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-11 17:14:12 +00:00
Claude e299b37b0c Add apparmor:unconfined to Beszel Docker agents - dbus mount alone isn't enough
The systemd/dbus mounts added last commit aren't sufficient by themselves
on an AppArmor-enabled host (Ubuntu/Debian by default): the dbus "Hello"
handshake fails with "An AppArmor policy prevents this sender from sending
this message to this recipient", since the container has no AppArmor label
the host's dbus-daemon profile recognizes. Only visible at LOG_LEVEL=debug
- silent otherwise, which is why the mounts alone looked like they should
have worked but didn't. Confirmed live against a real box hitting exactly
this error.

security_opt: apparmor:unconfined is Beszel's own documented fix
(beszel.dev/guide/systemd#apparmor-error) for this exact error string.
Added to both Docker-based agent compose generators (install_beszel's
combined hub+agent, and install_beszel-agent's remote-only variant).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-11 17:04:15 +00:00
Claude 933b5b00f4 Mount systemd/dbus/sensors into Docker-based Beszel agents for Services/Temp
The hub's "Services" column is systemd unit monitoring (CPU/memory per
unit), and "Temp" is hardware sensor readings — neither is Docker container
stats, which is what the existing docker.sock mount actually provides. A
container is isolated from the host's systemd/dbus and most of /sys by
default, so a Docker-deployed agent silently showed both columns empty,
with nothing anywhere pointing at why. Confirmed live: a natively-installed
agent (no Docker, a plain systemd service) gets both for free just by
running as a normal host process, which is what surfaced the gap — a
Docker-deployed agent sitting right next to it on another box showed
nothing in either column.

Added read-only mounts for /var/run/systemd/private, dbus's
system_bus_socket, and /sys/class/hwmon + /sys/class/thermal to both
Docker-based agent compose generators (install_beszel's combined hub+agent,
and install_beszel-agent's remote-only variant). All four are best-effort:
if a path doesn't exist on a given host, Docker mounts an empty directory
rather than failing the container, so the worst case on an unusual host is
an empty column, not a regression or a crash risk.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-11 16:24:58 +00:00
Claude f7cc0e5bc4 Add beszel-agent: agent-only Beszel install for remote/homelab boxes
For monitoring a box that isn't the VPS (e.g. a homelab machine): only the
agent needs to run there, and it connects OUTBOUND to the hub over HTTPS
using the same key + universal token flow the hub-side installer already
uses — no VPN, no router port-forwarding, and no FQDN needed on that box,
since nothing on it ever needs to be reached FROM the hub.

New register_service beszel-agent in services/beszel.sh (a second
registration in the same file, precedented by base.sh's base+glow) reuses
_beszel_configure_agent's paste/parse UX for the key/token instead of
duplicating it — that function's signature changed from a bare hub port to
a full login-URL string so both the local-hub path and this new
agent-only path can share it.

Run on the remote box: sudo ./setup.sh beszel-agent

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-11 13:18:26 +00:00
Claude 7c85f326c8 Accept Beszel's own "copy for docker compose" snippet directly
Confirmed live: Beszel's Settings -> Tokens & Fingerprints page surfaces
a "copy for docker compose" shortcut as the prominent way to grab the
key/token — not a bare string — so the previous two-prompt flow (paste
plain key, paste plain token) didn't match what people actually have
in their clipboard. The user pasted that YAML snippet into .env by
hand afterward, using the container's raw KEY/TOKEN names and YAML
`NAME: 'value'` syntax instead of what the compose file's own
${AGENT_KEY:-}/${AGENT_TOKEN:-} substitution actually reads — the
agent then failed with "no key provided" since AGENT_KEY was never
actually set.

_beszel_extract_field pulls KEY/TOKEN out of whatever shape the paste
arrives in — YAML mapping (`KEY: 'value'`), compose list style
(`- KEY=value`), or plain `KEY=value` — regardless of quoting. The
agent prompt now accepts a multi-line paste (the whole snippet, or
just the two lines) instead of asking for two separately pre-extracted
values; if no labeled KEY/TOKEN line is found at all, it falls back to
treating the paste as a bare key and asks for the token separately, so
a Beszel version that really does just show plain strings still works.

Verified all three paths directly: the exact mixed KEY:/TOKEN= paste
the user had, the skip path (blank first line leaves .env untouched),
and the bare-value fallback with no labels at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-11 04:11:56 +00:00
Claude e10e1e90b4 Fix invalid docker-compose.yml when Beszel is installed with local Caddy
Confirmed live: "networks.beszel-agent additional properties ... not
allowed" — the root-level networks: block (_CADDY_NET_SECTION) was
placed between the two services instead of after both. Since it sits
at 0 indentation, YAML parsed the following beszel-agent: line as a
continuation of the networks: mapping instead of a new services: entry,
so the whole beszel-agent service definition got swallowed as if it
were a (invalid) child of networks.caddy_net. gatus.sh's identical
_CADDY_NET_BLOCK/_CADDY_NET_SECTION pattern never hit this because it
only ever has one service, so the same placement is always the last
content in the file there.

Moved _CADDY_NET_SECTION (the root-level networks: definition) to
after both services; _CADDY_NET_BLOCK (the per-service "join
caddy_net" snippet) stays right after the hub's own volumes, where it
correctly nests under the beszel: service only.

Verified by regenerating the compose file with local Caddy present and
parsing it with PyYAML: services.beszel and services.beszel-agent are
now proper siblings, beszel-agent keeps its image/environment/volumes
keys, beszel's own networks: is scoped to just that service, and the
root networks: definition is separate and correctly placed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-11 03:51:02 +00:00
Claude 7938399e99 Add Beszel for lightweight server + Docker monitoring
Answers "what's the best way to see CPU/RAM/disk usage on this box"
(IONOS's own dashboard doesn't expose it) and "does Gatus cover this" —
it doesn't, Gatus is a black-box HTTP check (is the site responding
from the outside), Beszel is white-box host/process monitoring (is the
box under memory/disk pressure, is a container actually running vs.
crash-looping). Complements Gatus rather than replacing it.

Mirrors the hub+agent same-system layout from beszel's own
supplemental/docker/same-system/docker-compose.yml (fetched from the
actual upstream repo, not reconstructed from memory) — hub is the web
dashboard, agent reads /var/run/docker.sock (read-only) to report every
currently-running container automatically, no per-service config
needed as containers get added or removed.

Genuinely a two-phase install: the hub's SSH keypair and universal
token only exist after logging into its web UI once, so this starts
the hub, walks through where to find both values, and finishes wiring
the agent once provided — skipping is fine, a rerun in "update" mode
detects the agent was never connected and offers to finish it.

Verified the generated docker-compose.yml/.env by running the actual
file-writing code path with docker/configure_caddy_for_service mocked
out — confirmed TOKEN/KEY are correctly left as literal
${AGENT_TOKEN:-}/${AGENT_KEY:-} for Docker Compose's own substitution
at "up" time, not prematurely expanded by the heredoc itself.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-11 03:43:58 +00:00