Skip to main content
Glama

That is the whole idea. Most homelab tools show you a graph of right now, and leave "does this matter?" to you. HomeButler remembers what your server looked like last time, decides what is worth saying, and says it โ€” six containers before and six after is not "no change" when one of them is a different container.

Reading a report

homebutler report
homebutler report --keep 7      # retain only the latest 7 snapshots
homebutler report --no-save     # preview without writing a snapshot

report gives you a concise butler-style summary of your homelab: current health, warnings, notable changes since the previous snapshot, and suggested next commands. On the first run, HomeButler creates a baseline under ~/.homebutler/reports/snapshots/; later runs compare against the latest snapshot. Old snapshots are pruned automatically (--keep 30 by default) so reports do not grow forever.

Section rules, labels, and severities are colour-coded in a terminal. Colour is dropped automatically when output is piped, redirected, or run from cron.

Reading a change

Every change line, like those in the card at the top, is three columns: what kind of change, what it happened to, and what exactly happened. The kind is one of eight words, and it is the same word in --json, so an agent branches on it without reading prose:

Kind

Means

You would see it after

gone

it was there last time and is not now

docker rm, a service stopping, a port closing

new

it was not there last time and is now

starting anything

replaced

same name, different thing underneath

docker compose up -d โ€” the container is recreated, so the name and the count are unchanged

image

same container, different image

pulling a new tag

state

same container, running where it was stopped, or the reverse; or a process that keeps restarting

a crash, a crash loop, or bringing something back up

port

same port, a different process answering on it

one service taking over another's port

disk

a mount moved by more than half a gigabyte

anything that writes

skipped

the comparison could not be made

Docker was down when either snapshot was taken

replaced is the one the rest of this exists for. A container recreated under the same name leaves every count identical, which is why a report that compares counts โ€” as this one did before 0.26.0 โ€” answers "no significant changes" while the thing you were running has been swapped out underneath you.

skipped is the second: homebutler says it could not compare rather than reporting nothing changed. An all-clear it cannot stand behind is worse than no answer.

The header names the snapshot being compared against, so "what changed" is never ambiguous about the window it covers.

๐Ÿ“– What earns a line, and what is deliberately suppressed โ†’

Related MCP server: Plutus MCP Server

See it run

Why homebutler?

HomeButler helps you answer the boring but painful questions every homelab eventually creates:

  • What is running on my server right now?

  • Which container owns this port?

  • Why did this service restart at 3 AM?

  • Is my backup actually restorable?

  • Can I install this self-hosted app without hand-writing another compose file?

  • Can I let an AI assistant inspect my server without handing it a full SSH shell?

No daemon required, no database. Run it from a terminal, cron, a web dashboard you start when you want one, or an AI agent.

The design goal is simple: give humans and agents a narrow, structured interface to the server. HomeButler returns readable summaries and JSON instead of asking you to trust a black-box shell session.

Self-hosting is not hard because one docker compose up is hard. It is hard because the maintenance never ends: ports collide, containers restart silently, backups look fine until restore day, and every server becomes a slightly different snowflake.

HomeButler is a small operations toolkit for that messy middle.

Alongside what you already run

Keep Uptime Kuma for is it up, Beszel or Netdata for the graph, Dozzle for the logs. HomeButler answers the question none of them ask: what is different from last time, and does it matter?

  • No agent on the machines it watches. The binary is put there once with homebutler deploy and runs only when asked, over SSH โ€” no daemon, no open port, nothing listening between runs.

  • The judgement is a written rule, not a model. What earns a line is in docs/report.md, and the same input gives the same report โ€” no AI required, no account, no paid tier.

  • It checks that a backup comes back. A backup you have never restored is a folder. backup drill boots the archive as a second copy of the app, on a network and port of its own, and requires it to answer an HTTP health check before calling the archive good โ€” then removes everything it made. A failed drill exits non-zero, so backup drill --all belongs in the same cron entry as backup. docs/backup.md has what it does and what it does not prove.

    ๐Ÿ” Integrity: โœ… tar valid (8 files)
    ๐Ÿš€ Boot: โœ… container started in 0s
    ๐ŸŒ Health: โœ… HTTP 200 on port 60405
    
    โœ… DRILL PASSED

Quick Start

# One-line install (auto-detects OS/arch)
curl -fsSL https://raw.githubusercontent.com/Higangssh/homebutler/main/install.sh | sh

# Or via Homebrew
brew install Higangssh/homebutler/homebutler

# Or as a container โ€” the hub that reaches your machines over SSH
docker run -d --name homebutler -p 8080:8080 -v ~/.config/homebutler:/config -v ~/.ssh:/root/.ssh:ro \
  -e HOMEBUTLER_TOKEN="$(openssl rand -hex 16)" ghcr.io/higangssh/homebutler
docker exec homebutler printenv HOMEBUTLER_TOKEN   # the token, to sign in to the dashboard

# Interactive setup โ€” add your servers in seconds
homebutler init

Or as a container, reaching your machines over SSH โ€” docs/docker.md: docker run -p 8080:8080 -e HOMEBUTLER_TOKEN=โ€ฆ -v ~/.config/homebutler:/config -v ~/.ssh:/root/.ssh:ro ghcr.io/higangssh/homebutler

Use it right away:

homebutler status                    # CPU, memory, disk, uptime
homebutler docker list               # running containers
homebutler inventory scan            # containers + ports + topology
homebutler report                    # butler-style health report + change summary
homebutler install uptime-kuma       # deploy a self-hosted app
homebutler backup drill uptime-kuma  # verify a backup actually restores
homebutler watch tui                 # terminal dashboard
homebutler serve                     # web dashboard at http://localhost:8080

Machine-readable output is available everywhere:

homebutler status --json
homebutler inventory scan --json
homebutler report --json

What else it does

  • Install apps โ€” deploy Uptime Kuma, Jellyfin, Pi-hole, Gitea, Portainer, and more with one command

  • Map your server โ€” see containers, exposed ports, system ports, and service topology

  • Run a doctor check โ€” diagnose resource pressure, stopped containers, public ports, backup hygiene, notifications, report baseline readiness, and configured Proxmox endpoint reachability

  • Catch crashes โ€” save logs before/after Docker, systemd, or PM2 restarts and detect flapping loops

  • Verify backups โ€” boot backups in isolated containers before you trust them

  • See a Proxmox cluster โ€” nodes, QEMU and LXC guests, storage, task status, and honest dashboard freshness, with power actions that name their target explicitly

  • Use it anywhere โ€” CLI, JSON, web dashboard, or MCP for AI agents without giving them SSH

๐Ÿฉบ Doctor Check

homebutler doctor
homebutler doctor --strict          # non-zero exit if warnings/failures are found
homebutler doctor --json            # automation / MCP friendly

doctor is a read-only preflight for the problems homelab users usually discover too late: high disk or memory usage, stopped containers, public bind ports, stale or missing backups, missing notifications, whether report has a baseline for change detection, and whether each configured Proxmox endpoint is reachable with the token it has. Every finding names the next command to run, so --strict makes it usable from cron or CI โ€” including a Proxmox host that is unreachable or rebooting.

Every finding carries a category, and --json carries the same word, so a caller filters on it without reading the title:

Category

What it is about

system

CPU, memory, disk

docker

containers that are stopped or unhealthy

exposure

ports listening on every interface

backup

backups missing, stale, never drilled, or growing without a retention limit

report

whether there is a baseline to compare against

watch

targets listed with nothing polling them

notifications

channels configured, or never tested

proxmox

an endpoint that cannot be reached with the token it has

config

the config file itself โ€” permissions, keys homebutler does not know

incomplete

a collector did not answer, so this diagnosis is partial

overall

nothing specific: the all-clear

incomplete is the one worth handling. It means the answer you are reading is missing a section, which is different from that section being fine.

๐Ÿ—‚ Config Validation

homebutler config validate
homebutler config validate --strict   # exit non-zero on warnings too
homebutler config validate --json

config validate reads your config without starting anything and tells you which file was used, which of the four resolution rules picked it, and what homebutler actually made of each section. It exists because the two ways config goes wrong are both silent: a key homebutler does not recognise is dropped without a word, and a --config path that does not exist falls back to built-in defaults rather than failing.

Sections
   โœ“ servers     2 servers (homelab, nas)
   ยท notify      not set
   โœ“ alerts      cpu 95% ยท memory 85% ยท disk 90%

Findings
   โš ๏ธ Line 5: field notifiy not found in the homebutler config
      โ†’ Did you mean "notify"? Unrecognised keys are ignored silently.

Watch and alerts

๐Ÿ”„ Process Restart Watch

Your container crashed at 3 AM โ€” but why? homebutler watch catches it the moment it happens, saves the dying logs, figures out the cause, and tells you if it's happening over and over.

Supported backends: Docker (real-time event stream) ยท systemd (polling) ยท PM2 (polling)

Step 1: Add targets to watch

homebutler watch add nginx              # Interactive: choose Docker / systemd / PM2
homebutler watch add --kind docker nginx          # or specify directly
homebutler watch add --kind systemd nginx.service
homebutler watch add --kind pm2 my-api
homebutler watch add --kind systemd web --unit nginx.service   # listed as web, checked as nginx.service
homebutler watch list                   # See what you're watching

Step 2: Start monitoring

homebutler watch start                  # Foreground, Ctrl+C to stop
homebutler watch start --interval 10s   # Custom poll interval (default 30s)
homebutler watch install     # register it with systemd or launchd
homebutler watch installed   # is it registered?
homebutler watch uninstall

watch install hands the loop to whatever supervises the host โ€” a systemd user unit on Linux, a launchd agent on macOS โ€” so monitoring survives logout and reboot. Both are user-level and neither is a preference: on Linux the watch list lives in your home directory, so a root unit would find an empty list; on macOS Docker Desktop only runs inside a logged-in session, so a LaunchDaemon would poll a daemon that is not there. On Linux a user unit stops at logout unless lingering is on; watch install checks, and if it is off tells you to run loginctl enable-linger $USER (with sudo only if your system refuses it).

watch start is the monitoring process. It watches the containers and services on the watch list for restarts, checks CPU, memory and disk against your thresholds, and runs any remediation rules you have configured โ€” one process, one set of notification providers. alerts --watch still exists and does the threshold half on its own.

Every endpoint under proxmox: in your config is polled too: unreachable or ACL-filtered endpoints and any guest listed under that endpoint's guests: report one incident when the problem starts and one recovery incident when it clears. A guest not listed there is observational only โ€” watch start never alerts on it, deliberately stopped or not. See Proxmox setup โ†’ for the guests: field.

When a crash is detected, you'll see:

[03:14:22] INCIDENT: nginx (incident nginx-20260410-031422.581-7a2124)
  Crash: OOM โ€” process killed by SIGKILL (oom, confidence: high)
  โš  FLAPPING: acute (3 restarts in short window)

Step 3: Investigate

homebutler watch history                # List all incidents
homebutler watch show <incident-id>     # Full details

watch show output includes:

  • Pre-death logs โ€” what the process printed right before it died

  • Post-restart logs โ€” what happened after the restart

  • Crash analysis โ€” category (oom / panic / segfault / timeout / dependency / error), reason, confidence level, matched log patterns

  • Flapping status โ€” if the process is stuck in a crash loop

Crash Analysis

Every incident is automatically analyzed using exit codes and log patterns:

Signal

Exit Code

Meaning

SIGKILL

137

OOM Killer or forced kill

SIGSEGV

139

Segmentation fault (memory corruption)

SIGTERM

143

Graceful shutdown request

โ€”

1

Application error

โ€”

0

Clean exit (may be intentional restart)

Log patterns like panic:, Out of memory, Connection refused, FATAL, and timeout are matched automatically to help identify the root cause.

Flapping Detection

Detects when a process is stuck in a restart loop (e.g., crash โ†’ restart โ†’ crash again):

  • Acute โ€” 3+ restarts within 10 minutes (something is broken right now)

  • Chronic โ€” 5+ restarts within 24 hours (slow recurring issue)

Flapping incidents are tagged [FLAPPING] in history and highlighted in watch show.

Notifications (optional, off by default)

Notifications are disabled by default, which is useful for air-gapped or closed networks where everything runs locally.

A minimal example in ~/.config/homebutler/config.yaml:

notify:
  telegram:
    bot_token: "your-bot-token"
    chat_id: "your-chat-id"

watch:
  enabled: true
  notify_on: flapping
  cooldown: 5m
  flapping:
    short_window: 10m
    short_threshold: 3
    long_window: 24h
    long_threshold: 5
  retention:
    max_incidents: 200

alerts:
  cpu: 90
  memory: 85
  disk: 90
  rules:
    - name: cpu-spike
      metric: cpu
      threshold: 90
      action: notify

    - name: elsa-monitor-down
      metric: container
      kind: systemd          # docker (default) | systemd | pm2
      watch: [lh-elsa-monitor.service]
      action: restart

Restarting things that are not containers

action: restart restarts Docker containers unless the rule says otherwise. kind: systemd or kind: pm2 points it at a service or a PM2 app instead.

The kind is written on the rule rather than looked up from the watch list, so restarting a host service is something you asked for in the config. It also means every rule written before kind existed keeps meaning exactly what it meant.

Two things worth knowing before using it:

systemctl restart needs root or a polkit rule. Running homebutler unprivileged, a systemd restart will be refused, reported as failed, and warned about when alerts --watch starts rather than when the rule first fires.

A target that is flapping is not restarted. Restarting something already in a restart loop feeds the loop, and most systemd units carry Restart=always, so homebutler restarting them fights systemd's own backoff. The thresholds are the watch.flapping ones above, and the skip is reported rather than counted as either success or failure. This applies to Docker targets too.

Legacy ~/.homebutler/watch/config.json is still read as a fallback for watch-specific settings, and legacy alerts.yaml notify/webhook provider settings are still accepted for older setups.

  • watch.enabled: true โ€” allow watch notifications

  • watch.notify_on: flapping โ€” notify only when repeated restart loops are detected

  • watch.notify_on: incident โ€” notify on every incident

  • watch.notify_on: all โ€” notify on both incidents and flapping

  • watch.notify_on: off โ€” disable watch notifications without removing provider config

  • watch.cooldown: 5m โ€” suppress duplicate notifications for the same event fingerprint during the cooldown window

  • watch.flapping โ€” optional advanced tuning for restart-loop detection

  • watch.retention.max_incidents: 200 โ€” how many incidents to keep on disk, newest first. The directory grows fastest exactly when a service is restarting in a loop. Set -1 to keep everything.

    Each incident keeps up to 100 captured log lines per side, and at most 64 KB of them. Line counts alone do not bound a file: one stack trace or JSON document on a single line is arbitrarily long, and a container being OOM-killed is exactly the one likely to write one. A log that does not fit keeps its end โ€” the last thing a process said is what explains why it stopped โ€” and says how much was dropped.

These settings can also be written under a watch.notify: block, which is the canonical form:

watch:
  notify:
    enabled: true
    notify_on: flapping
    cooldown: 5m
  flapping:
    short_window: 10m

Both spellings are read, so either layout works. If a file contains both, the notify: block wins and homebutler config validate says so.

Manage targets

homebutler watch remove nginx           # Stop watching
homebutler watch check                  # One-shot check (no continuous monitoring)

Alert Thresholds (Advanced)

alerts still exists for CPU, memory, and disk threshold checks, but it is an advanced flow and not the recommended first step for new users.

homebutler alerts --watch                  # default: 30s interval
homebutler alerts --watch --interval 10s   # check every 10 seconds
homebutler alerts history                  # view alert history
homebutler notify test                     # test your notification channels

Default thresholds: CPU 90%, Memory 85%, Disk 90%. Start with watch, then add alerts only if you specifically want threshold-based checks.

Backup & Restore

One-command Docker backup โ€” volumes, compose files, and env variables.

homebutler backup                          # backup everything
homebutler backup --service jellyfin       # specific service
homebutler backup --to /mnt/nas/backups/   # custom destination
homebutler backup list                     # list backups
homebutler restore ./backup.tar.gz         # restore

โš ๏ธ Database services should be paused before backup for data consistency.

๐Ÿ“– Full backup documentation โ†’ โ€” how it works, archive structure, security notes.

๐Ÿ” Backup Drill

"Having a backup" and "being able to restore" are different things.

Backup Drill boots your backup in an isolated Docker environment and verifies the app actually responds โ€” like a fire drill for your data. A failed drill exits non-zero.

homebutler backup drill uptime-kuma        # verify one app
homebutler backup drill --all              # verify all apps
homebutler backup drill --json             # machine-readable output
homebutler backup drill --archive ./file   # use a specific backup

Full walkthrough, including what a passing drill does not prove: docs/backup.md.

What happens:

  1. Finds the latest backup archive

  2. Verifies archive integrity (tar validation)

  3. Creates an isolated Docker network + random port

  4. Boots the app from backup data

  5. Runs an HTTP health check

  6. Reports pass/fail and cleans up everything

๐Ÿ” Backup Drill โ€” uptime-kuma

  ๐Ÿ“ฆ Backup: ~/.homebutler/backups/backup_2026-04-04_1711.tar.gz
  ๐Ÿ“ Size: 18.6 MB
  ๐Ÿ” Integrity: โœ… tar valid (8 files)

  ๐Ÿš€ Boot: โœ… container started in 0s
  ๐ŸŒ Health: โœ… HTTP 200 on port 58574
  โฑ๏ธ  Total: 2s

  โœ… DRILL PASSED

Zero risk โ€” runs in a completely isolated environment. Your running services are never touched.

Supports health checks for: nginx-proxy-manager, vaultwarden, uptime-kuma, pi-hole, gitea, jellyfin, plex, portainer, homepage, adguard-home.

App Install

$ homebutler install list
๐Ÿ“ฆ Available apps (15):

  uptime-kuma          A self-hosted monitoring tool
  vaultwarden          Lightweight Bitwarden-compatible password manager
  jellyfin             Free software media system for streaming movies, TV, and music
  pi-hole              Network-wide ad blocking via DNS filtering
  โ€ฆ

homebutler install uptime-kuma โ€” Deploy self-hosted apps in seconds. Pre-checks Docker, ports, and duplicates. Generates docker-compose.yml automatically. See all available apps โ†’

Deploy self-hosted apps with a single command. Each app runs via docker compose with automatic pre-checks, health verification, and clean lifecycle management.

# List available apps
homebutler install list

# Install (default port)
homebutler install uptime-kuma

# Install with custom port
homebutler install uptime-kuma --port 8080

# Install jellyfin with media directory
homebutler install jellyfin --media /mnt/movies

# Check status
homebutler install status uptime-kuma

# Stop (data preserved)
homebutler install uninstall uptime-kuma

# Stop + delete everything
homebutler install purge uptime-kuma

How it works

~/.homebutler/apps/
  โ””โ”€โ”€ uptime-kuma/
       โ”œโ”€โ”€ docker-compose.yml   โ† auto-generated, editable
       โ””โ”€โ”€ data/                โ† persistent data (bind mount)
  • Pre-checks โ€” Verifies docker is installed/running, port is available, no duplicate containers

  • Compose-based โ€” Each app gets its own docker-compose.yml you can inspect and customize

  • Data safety โ€” uninstall stops containers but keeps your data; purge removes everything, including the Docker volumes the app used, and names them

  • Cross-platform โ€” Auto-detects docker socket (default, colima, podman)

Available apps

App

Default Port

Description

Notes

uptime-kuma

3001

Self-hosted monitoring tool

plex

32400

Plex Media Server

--media /path to mount media dir

vaultwarden

8080

Bitwarden-compatible password manager

filebrowser

8081

Web-based file manager

Its upstream project is archived and gets no further releases or security fixes

it-tools

8082

Developer utilities (JSON, Base64, Hash, etc.)

gitea

3002

Lightweight self-hosted Git service

jellyfin

8096

Media system (movies, TV, music)

--media /path to mount media dir

homepage

3010

Modern homelab dashboard

stirling-pdf

8083

All-in-one PDF tool (merge, split, convert, OCR)

speedtest-tracker

8084

Internet speed test with historical graphs

mealie

9925

Recipe manager and meal planner

pi-hole

8088

DNS ad blocking

โš ๏ธ Uses port 53 (DNS), NET_ADMIN capability

adguard-home

3000

DNS ad blocker and privacy

โš ๏ธ Uses port 53 (DNS)

portainer

9443

Docker management GUI

โš ๏ธ Mounts Docker socket (HTTPS)

nginx-proxy-manager

81

Reverse proxy with SSL and web UI

โš ๏ธ Uses ports 80/443

App-specific options

# Jellyfin: mount your media library
homebutler install jellyfin --media /mnt/movies

# Pi-hole / AdGuard: DNS ad blocking (port 53 required)
homebutler install pi-hole
# โš ๏ธ If port 53 is in use (Linux): sudo systemctl disable --now systemd-resolved

# Portainer: Docker GUI (mounts docker socket)
homebutler install portainer
# Access via HTTPS: https://localhost:9443

# Nginx Proxy Manager: reverse proxy
homebutler install nginx-proxy-manager
# Default login: admin@example.com / changeme (change immediately!)

# Any app: custom port
homebutler install <app> --port 9999

Safety checks

  • Port conflict detection โ€” Checks if the port is already in use before install

  • DNS mutual exclusion โ€” Warns if pi-hole and adguard-home are both installed

  • Docker socket warning โ€” Alerts when an app requires Docker socket access (portainer)

  • OS-specific guidance โ€” Linux gets systemd-resolved fix, macOS gets lsof command

  • Post-install tips โ€” DNS setup, HTTPS access, default credential warnings

Want more apps? Open an issue or see Contributing.

Dashboards

๐ŸŒ Web Dashboard

homebutler serve โ€” A web dashboard embedded in the single binary via go:embed. Servers, Docker containers, open ports, alerts and Wake-on-LAN from any browser. With --token it also edits the config: thresholds, notification channels, servers, Proxmox endpoints and wake devices.

The Report tab, where you actually read it

The screen somebody opens after a notification. What needs attention first, then what moved with the kind word each change carries, then the snapshot being compared against and how old it is. A comparison that could not be made says so rather than reading as an all-clear.

Loading it saves nothing โ€” a page that took a snapshot every time it was opened would prune the baseline you wanted to compare against. Saving one is a button.

  • Server Overview โ€” See all servers at a glance with color-coded status (green = online, red = offline)

  • System Metrics โ€” CPU, memory, disk usage with progress bars and color thresholds

  • Docker Containers โ€” Running/stopped status with friendly labels ("Running ยท 4d", "Stopped ยท 6h ago")

  • Top Processes โ€” Top processes sorted by CPU/memory with zombie detection

  • Resource Warnings โ€” Visual CPU, memory, and disk thresholds in the dashboard

  • Network Ports โ€” Open ports with process names and bind addresses

  • Wake-on-LAN โ€” One-click wake buttons for configured devices

  • Server Switching โ€” Dropdown to switch between local and remote servers

  • Zero dependencies โ€” No Node.js runtime needed. Frontend is compiled into the Go binary at build time

homebutler serve              # Start on port 8080
homebutler serve --port 3000  # Custom port
homebutler serve --demo       # Demo mode with realistic sample data
homebutler serve install      # Let the host keep it running

In Homepage

Already run Homepage? One customapi snippet in services.yaml puts a homebutler card on it: needs attention, changes since the last snapshot, containers running and stopped. The snippet โ†’

๐Ÿ–ฅ๏ธ TUI Dashboard

homebutler watch tui โ€” A terminal-based dashboard powered by Bubble Tea. Monitors all configured servers with real-time updates, color-coded resource bars, and Docker container status. No browser needed.

๐Ÿ—บ๏ธ Inventory & Topology

homebutler inventory scan
homebutler inventory show --filter exposed
homebutler inventory export --format mermaid
homebutler --json inventory scan

inventory scan gives you a quick map of what is running on a server: system health, Docker containers, app ports, and system ports. Docker-published ports are connected back to the container that owns them, so local forwarding details like Colima/Lima stay understandable.

๐Ÿ  Home Network
   Server  homelab (192.168.1.10)
   Summary โœ… 1 running ยท โšช 1 stopped ยท ๐ŸŒ 2 public ports ยท ๐Ÿ”’ 4 local ports

๐Ÿ“ฆ Containers (2)
   โ”œโ”€ โšช vaultwarden ยท not started
   โ”‚  โ””โ”€ image vaultwarden/server:latest
   โ””โ”€ โœ… api-server ยท running
      โ”œโ”€ image my-api:latest
      โ””โ”€ exposes :8080 โ†’ 8080/tcp

๐ŸŒ App Ports (1)
   โ””โ”€ ๐ŸŒ :8080/tcp ยท api-server

To answer "what is reachable from outside my machine/network?" without reading the whole tree, filter the scan to exposed ports only:

homebutler inventory scan --filter exposed
๐Ÿ  Home Network
   Server  homelab

๐ŸŒ Exposed Ports
   โ”œโ”€ :8080/tcp ยท api-server
   โ””โ”€ :8443/tcp ยท dashboard

Only ports listening on all interfaces (0.0.0.0, ::, *) are shown. Anything bound to a specific address is hidden, including loopback and LAN addresses. Unsupported filter values return an error, as does combining --filter with --json; the default inventory scan output is unchanged.

Use Mermaid export when you want a diagram for GitHub, Obsidian, docs, or an AI assistant:

graph TD
  home["๐Ÿ  Home Network"] --> homelab["๐Ÿ–ฅ homelab<br/>192.168.1.10"]
  homelab --> c1["๐Ÿ“ฆ api-server<br/>running"]
  homelab --> p1["๐ŸŒ :8080/tcp<br/>api-server"]
  c1 -. exposes .-> p1

๐ŸงŠ Proxmox VE

homebutler proxmox status
homebutler proxmox guests --status running
homebutler proxmox guest shutdown --node pve1 --type lxc --vmid 105 --confirm
homebutler proxmox task UPID:pve1:... --node pve1

A Proxmox endpoint is its own kind of target, configured under proxmox: with an API token rather than SSH, so it does not join the --server or --all fan-out. TLS verification stays on: trust comes from a pinned SHA-256 fingerprint, then a CA file, and only then an explicit insecure fallback.

Reads are plain. Power actions are not: every one of them takes an explicit endpoint, node, guest type and VMID, and refuses to run without --confirm, which is checked before any credential is read. They also need their own action_token_id (plus action_token or action_token_file) configured on the endpoint โ€” the read token alone will not start, reboot, or shut down a guest; see Proxmox setup โ†’ for creating that second token. shutdown asks the guest to shut down cleanly โ€” it is not Proxmox's hard stop, which cuts power and can leave a filesystem behind it. A successful action reports the task it submitted, not that the guest finished; proxmox task answers that separately.

proxmox script prints the install command for a Community Script pinned to one commit, along with a warning that the script is not reviewed by homebutler and runs as root. It never fetches or runs it โ€” see #62 for why that line is where it is.

๐Ÿ“– Proxmox setup, tokens, and TLS โ†’

Installation

brew install Higangssh/homebutler/homebutler

Automatically installs to PATH. Works on macOS and Linux.

One-line Install

curl -fsSL https://raw.githubusercontent.com/Higangssh/homebutler/main/install.sh | sh

Auto-detects OS/architecture, downloads the latest release, and installs to PATH. macOS and Linux only; on Windows, use the zip below.

Windows

Download homebutler_<version>_windows_amd64.zip (or _windows_arm64.zip) from the latest release, extract homebutler.exe, and put it in a folder on your PATH. homebutler upgrade replaces it with the next release's. The npm package needs Windows 10 version 1803 or later, whose tar.exe it extracts the zip with.

On Windows, status and what is built on it read this machine, and ports and processes say they are not supported. Servers in your config are reached over SSH as from any other machine, with keys read from C:\Users\<you>\.ssh (configuration).

npm (MCP server)

npm install -g homebutler

Downloads the Go binary automatically. This package publishes one command, homebutler-mcp, and it starts the MCP server โ€” npx -y homebutler@latest launches that, not the CLI. For the command line, use one of the other installs above.

Claude Desktop extension (MCP server)

Download homebutler_<version>_darwin_arm64.mcpb (Apple silicon), homebutler_<version>_darwin_amd64.mcpb (Intel Mac), homebutler_<version>_windows_amd64.mcpb (Windows) or homebutler_<version>_windows_arm64.mcpb (Windows on Arm) from the latest release and open it, or drag it onto Claude Desktop's Settings โ†’ Extensions. The binary is inside, so nothing else needs installing. It starts read-only; see the MCP docs to allow more.

Go Install

go install github.com/Higangssh/homebutler@latest

Build from Source

git clone https://github.com/Higangssh/homebutler.git
cd homebutler
make build-all

make build-all compiles the web dashboard into the binary and needs Node installed. make build skips it โ€” the CLI is complete either way, and homebutler serve then says the dashboard is missing and names the two ways to get it.

Uninstall

rm $(which homebutler)           # Remove binary
rm -rf ~/.config/homebutler      # Remove config (optional)

Usage

homebutler <command> [flags]

Commands:
  status              System status (CPU, memory, disk, uptime)
  doctor              Diagnose health, exposure, backups, and readiness
  config validate     Check the config file and report what is ignored
  docker list         List running containers
  install <app>       Install a self-hosted app (docker compose)
  alerts              Show current alert status
  watch tui           TUI dashboard (monitors all configured servers)
  watch add/list/remove  Manage watched containers
  watch check/start   One-shot or continuous restart detection
  watch history/show  Browse restart history
  proxmox status      Proxmox VE cluster, nodes, guests, and storage
  serve               Web dashboard (browser-based, go:embed)

Flags:
  --json              JSON output (default: human-readable)
  --verbose, -v       Show detailed error information
  --server <name>     Run on a specific remote server
  --all               Run on all configured servers in parallel
  --port <number>     Port for serve command (default: 8080)
  --config <path>     Config file (auto-detected, see Configuration)

Run homebutler --help for all commands.

Commands:
  init                Interactive setup wizard
  config validate     Check the config file and report what is ignored
  status              System status (CPU, memory, disk, uptime)
  doctor              Diagnose health, exposure, backups, and readiness
  watch tui           TUI dashboard (monitors all configured servers)
  watch add <name>    Add container to restart watch list
  watch list          Show watched containers
  watch remove <name> Remove container from watch list
  watch check         One-shot restart check
  watch start         Continuous monitoring: restarts, thresholds, rules
  watch install       Register watch with systemd or launchd
  watch installed     Report whether it is registered
  watch uninstall     Remove the service unit
  watch history       List restart history (alias: incidents)
  watch show <id>     Show restart details with logs
  serve               Web dashboard (browser-based, go:embed)
  docker list         List running containers
  docker restart <n>  Restart a container
  docker stop <n>     Stop a container
  docker logs <n>     Show container logs
  docker top <n>      Show processes running inside a container
  docker inspect <n>  Show image, state, ports, mounts, networks, health
  report              What changed since the last snapshot
  inventory scan      Map containers, ports, and topology
  inventory show      Same as scan (--filter exposed narrows it)
  inventory export    Export the map (--format mermaid)
  proxmox status      Proxmox VE cluster, nodes, guests, storage
  proxmox guests      List QEMU and LXC guests
  proxmox node <n>    Node detail
  proxmox guest ...   start / shutdown / reboot (needs --confirm)
  proxmox task <upid> Task status for an action already submitted
  proxmox tasks       Recent tasks on a node
  proxmox script      Community Script install commands (prints, never runs)
  notify test         Send a test notification through configured providers
  wake <name>         Send Wake-on-LAN packet
  ports               List open ports with process info
  ps                  Show top processes (alias: processes)
  ps --sort mem       Sort by memory instead of CPU
  ps --limit 20       Show top 20 (default: 10, 0 = all)
  network scan        Discover devices on LAN
  alerts              Show current alert status
  alerts --watch      Thresholds only (watch start covers these too)
  trust <server>      Register SSH host key (TOFU)
  backup              Backup Docker volumes, compose files, and env
  backup list         List existing backups
  backup drill <app>  Verify backup restores correctly (isolated)
  backup drill --all  Verify all apps in backup
  restore <archive>   Restore from a backup archive
  upgrade             Upgrade local + all remote servers to latest
  deploy              Install homebutler on remote servers
  install <app>       Install a self-hosted app (docker compose)
  install list        List available apps
  install status <a>  Check installed app status
  install uninstall   Stop app (keep data)
  install purge       Stop app + delete all data
  mcp                 Start MCP server (JSON-RPC over stdio)
  version             Print version

Flags:
  --json              JSON output (default: human-readable)
  --verbose, -v       Show detailed error information
  --server <name>     Run on a specific remote server
  --all               Run on all configured servers in parallel
  --port <number>     Port for serve command (default: 8080)
  --demo              Run serve with realistic demo data
  --watch             Continuous monitoring mode (alerts command)
  --interval <dur>    Watch interval, e.g. 30s, 1m (default: 30s)
  --config <path>     Config file (auto-detected, see Configuration)
  --local             Upgrade only the local binary (skip remote servers)
  --local <path>      Use local binary for deploy (air-gapped)
  --service <name>    Target a specific Docker service (backup/restore)
  --allow-bind <path> Host path a restore may write a bind mount to (repeatable)
  --endpoint <name>   Proxmox endpoint from config (optional if only one)
  --confirm           Required for a Proxmox guest power action
  --to <path>         Custom backup destination directory
  --archive <path>    Specific backup archive for drill
  --all               Verify all supported apps (backup drill)

homebutler serve starts an embedded web dashboard โ€” no Node.js, no Docker, no extra dependencies.

homebutler serve                # http://localhost:8080
homebutler serve --port 3000    # custom port
homebutler serve --demo         # demo mode with sample data
homebutler serve install        # hand it to launchd or systemd, so it survives logout
homebutler serve uninstall      # and take it back

The installed unit records the address and never the token โ€” that comes from web.token in the config file, because --token is visible in ps to every user on the machine. In a container, set HOMEBUTLER_TOKEN instead; it is used when neither of the others is set, and never written to the config.

๐Ÿ“– Web dashboard details โ†’

Configuration

homebutler init    # interactive setup wizard

๐Ÿ“– What report compares โ†’ โ€” what earns a line, what is deliberately suppressed, and why.

๐Ÿ“– Configuration details โ†’ โ€” config file locations, watch/notify options, and advanced alert thresholds.

Multi-server

Manage multiple servers from a single machine over SSH.

homebutler status --server rpi     # query specific server
homebutler status --all            # query all in parallel
homebutler deploy --server rpi     # install on remote server
homebutler upgrade                 # upgrade all servers

๐Ÿ“– Multi-server setup โ†’ โ€” SSH auth, config examples, deploy & upgrade.

MCP Server

Built-in MCP server โ€” an agent gets the same report as typed JSON, kind, target and detail rather than a sentence to parse, and every tool is classed read, write or destructive.

Use natural language when you want automation. MCP clients can call homebutler tools to check server status, list Docker containers, inspect ports, or run operational workflows.

{
  "mcpServers": {
    "homebutler": {
      "command": "npx",
      "args": ["-y", "homebutler@latest"]
    }
  }
}

Works with Claude Desktop, ChatGPT, Cursor, Windsurf, and any MCP client.

To let an agent look without being able to change anything, add "--max-risk", "read" to args. Tools that change something are not offered and are refused if called by name, and report runs without saving a snapshot. Read mode stops changes, not reading: logs, container details and everything else a read returns are still visible. What read mode covers โ†’

๐Ÿ“– MCP server setup โ†’ โ€” supported clients, available tools, agent skills.

Architecture

The butler watches, decides what is worth saying, and says it โ€” to you or to an agent.

Something changes โ†’ report names it and ranks it โ†’ a notification, the dashboard, or an MCP call carries the same answer.

        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
        โ”‚   CLI   โ”‚  โ”‚   MCP   โ”‚  โ”‚   Web   โ”‚
        โ”‚ stdout  โ”‚  โ”‚  stdio  โ”‚  โ”‚  :8080  โ”‚
        โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜
             โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                          โ–ผ
                   internal/*
         system ยท docker ยท ports ยท network
         wake ยท alerts ยท remote (SSH)

Three interfaces, one core:

Interface

Transport

Use case

CLI

Shell stdout/stderr

Terminal, scripts, AI agents via exec

MCP

JSON-RPC over stdio

Claude Desktop, ChatGPT, Cursor, any MCP client

Web

HTTP (go:embed)

Browser dashboard, on-demand with homebutler serve

All three call the same internal/ packages โ€” no code duplication.

An agent reads the same report a person does, and gets the parts it has to branch on rather than a screen it has to interpret:

report = json.loads(subprocess.run(
    ["homebutler", "report", "--json"], capture_output=True, text=True).stdout)

for change in report["notable_changes"]:
    if change["kind"] == "replaced":         # the container came back as something else
        redeploy(change["target"])
    else:
        log(change["text"])                  # "port: :8080/tcp โ€” nginx โ†’ caddy"

Every line carries text as well, so a caller that only wants to print it does not have to put the sentence back together. A run that found nothing returns "notable_changes": [], so the loop above does nothing on a quiet day.

Nothing above this is homebutler's business: an MCP client, a chat bot, a cron line or a person at a terminal all reach the same answer.

No ports opened by default. CLI and MCP use stdin/stdout only. The web dashboard is opt-in (homebutler serve, binds 127.0.0.1).

Contributing

Contributions welcome! Please open an issue first to discuss what you'd like to change. CONTRIBUTING.md covers what homebutler accepts and what a new target has to prove.

Security

Found a vulnerability? Report it privately through the Security tab rather than a public issue. SECURITY.md covers what is in scope and what to expect.

License

MIT

Available Tools

44 tools
alertsA
Read-only

Check resource alerts for CPU, memory, and disk usage against configured thresholds. It reads and compares; nothing is sent anywhere and nothing is recorded, which is what a running watcher does instead

Inputs: server.

alerts_historyA
Read-only

Show recorded alert and remediation history. Entries are only written while a watcher is running, so an empty list means nothing was recording rather than nothing went wrong

Inputs: server.

backup_createA
Destructive

Create a Docker compose backup archive for all services or one service. Volumes are read while the containers run, so a database mid-write can land inconsistent; an archive is not evidence it restores, which is what backup_drill answers

Inputs: to, server, exclude, service.

backup_drillA
Destructive

Verify a backup by booting an app in an isolated Docker environment and checking that it responds. A second copy runs beside the live one on a network and port of its own, and everything it made is removed either way. A pass means the archive is not corrupt and the app starts on it, not that every row is there

Inputs: all, app, server, archive.

backup_listA
Read-only

List existing backup archives in the configured backup directory. It reads names, sizes and dates โ€” that an archive is here says nothing about whether it restores, which is what backup_drill answers

Inputs: server.

backup_restoreA
Destructive

Restore Docker volumes from a backup archive, overwriting the data the app is running on. Destructive: confirm intent before calling. Bind mounts declared by the archive are always refused here, because an agent has no way to name a host path it may write to

Inputs: archive (required), server, service.

config_validateA
Read-only

Check the config file this server is running on: which file was used, which rule selected it, what was read from each section, and anything wrong or silently ignored

Inputs: strict.

docker_inspectA
Read-only

Summarize a Docker container's image, state, restart policy, ports, mounts, networks, and health. Environment variable values are never included

Inputs: name (required), server.

docker_listA
Read-only

List Docker containers with their status, image, and ports. Stopped containers are included, so a name appearing here is not a name that is running โ€” read state

Inputs: server.

docker_logsA
Read-only

Get logs from a Docker container: the last lines only, 50 by default, and it returns rather than following the stream

Inputs: name (required), lines, server.

docker_restartA

Restart a Docker container by name. It goes down and comes back, and the result says the restart command succeeded, not that the app inside is serving again. Read the logs first if you do not know why it needs restarting

Inputs: name (required), server.

docker_statsB
Read-only

Get resource usage statistics (CPU, memory, network, block I/O) for all running Docker containers

Inputs: server.

docker_stopA
Destructive

Stop a Docker container by name. Nothing here starts it again: there is no start tool, so the operator brings it back themselves

Inputs: name (required), server.

docker_topA
Read-only

List the processes running inside a Docker container, read from the host. Nothing runs in the container: no exec, no TTY. Each process's command is the kernel's name for it (ps comm, at most 15 characters), never read from its command line, because a token passed on a command line would otherwise be in the result

Inputs: name (required), server.

doctorA
Read-only

Run a read-only diagnosis for resource pressure, stopped containers, public ports, backup hygiene, notifications, and report baseline readiness

Inputs: server, backup_max_age_hours.

install_appA
Destructive

Install a self-hosted app via docker compose. Pre-checks docker, ports, and duplicates automatically, and a refusal comes back as a result with the reasons rather than as an error. warning is present when an earlier install of the app left state in Docker volumes this install could not carry over

Inputs: app (required), port.

install_listA
Read-only

List available self-hosted apps that can be installed. The catalogue is compiled into the binary, so this reaches no network and answers the same on any machine

install_purgeA
Destructive

Stop an installed app and delete all data including containers, config, and volumes. Nothing here restores it and no backup is taken first: take one before calling if the data matters. A volume that still exists afterwards (external, or used by another container) is listed in kept_volumes, and one Docker could not be asked about in unchecked_volumes

Inputs: app (required).

install_statusA
Read-only

Check the status of an installed app, as its containers report it. An app homebutler did not install is not known here

Inputs: app (required).

install_uninstallA
Destructive

Stop an installed app and remove its containers. The app directory and its volumes stay on disk; install_purge is the one that deletes them. warning is present when part of the app's state is in Docker volumes outside the app directory, left there by an earlier install

Inputs: app (required).

inventory_exportA
Read-only

Export server inventory/topology as a Mermaid diagram locally, or JSON locally/remotely. Inside a container a local export is refused, because a container cannot see the machine it is on

Inputs: format, server.

inventory_scanB
Read-only

Collect server inventory/topology including system status, Docker containers, app ports, and system ports

Inputs: server.

network_scanA
Read-only

Scan the local network to discover devices (IP, MAC, hostname). It probes every address on the subnet and takes up to 30 seconds, so it is an answer to a question somebody asked rather than a way to begin. Inside a container it refuses, because the subnet it would scan is the container's own

notify_testA

Send one test notification through every configured channel and report which ones arrived. A real message goes out to each, so anyone reading those channels sees it

Inputs: server.

open_portsA
Read-only

List open network ports with associated process information. The process behind a port is not always readable without privilege, and missing_process says so rather than leaving the field quietly empty

Inputs: server.

processesA
Read-only

List the top processes by CPU or memory, with a total count and any zombies broken out separately

Inputs: limit, server, sort_by.

proxmox_guest_rebootA

Reboot one explicitly targeted Proxmox guest after confirmation and return the accepted task UPID

Inputs: node (required), type (required), vmid (required), confirm (required), +1 more.

proxmox_guestsA
Read-only

List Proxmox QEMU and LXC guests, optionally filtered by node, status, or type

Inputs: node, type, status, endpoint.

proxmox_guest_shutdownA
Destructive

Gracefully shut down one explicitly targeted Proxmox guest after confirmation and return the accepted task UPID

Inputs: node (required), type (required), vmid (required), confirm (required), +1 more.

proxmox_guest_startA

Start one explicitly targeted Proxmox guest after confirmation and return the accepted task UPID

Inputs: node (required), type (required), vmid (required), confirm (required), +1 more.

proxmox_nodeC
Read-only

Get detailed Proxmox node status

Inputs: node (required), endpoint.

proxmox_script_commandA
Read-only

Render the pinned install command for one Proxmox VE Community Script. Never fetches or runs it; the caller reviews and runs it themselves on the Proxmox host

Inputs: slug (required).

proxmox_script_listA
Read-only

List the curated Proxmox VE Community Scripts catalog (community-scripts/ProxmoxVE)

proxmox_statusA
Read-only

Get Proxmox VE version, cluster status, and resources

Inputs: endpoint.

proxmox_tasksA
Read-only

Get the 50 most recent Proxmox tasks for a node

Inputs: node (required), endpoint.

proxmox_task_statusA
Read-only

Inspect one asynchronous Proxmox task by node and opaque UPID

Inputs: node (required), upid (required), endpoint (required).

reportA
Destructive

Generate a butler-style health report with snapshot comparison, warnings, notable changes, and suggested actions. It saves a snapshot unless no_save is set, which moves the window every later comparison is measured from โ€” so a loop that calls this leaves nothing to compare against

Inputs: keep, server, no_save.

system_statusA
Read-only

Get system status including CPU, memory, disk usage, and uptime, for the machine this binary runs on. Inside a container it refuses rather than describe the container as the host; name a server to read a machine over SSH

Inputs: server.

wakeA

Send a Wake-on-LAN magic packet to wake a machine. The packet is fire-and-forget: a successful result means it was sent, not that anything woke up

Inputs: target (required), broadcast.

watch_addA

Add a Docker container, systemd unit, or PM2 app to the watch list. This writes the list and nothing begins watching it โ€” a supervisor has to be installed separately, which only the operator can do. Adding a target twice is reported as added=false rather than an error

Inputs: container (required), kind, unit, server.

watch_checkA
Destructive

Run a one-shot restart check on watched targets and report restarts detected since the last check. Only docker targets can be inspected this way; systemd and pm2 targets are reported as skipped rather than assumed healthy

Inputs: server.

watch_historyA
Read-only

List recorded restart incidents, newest first. Captured logs are excluded unless include_logs is set, because every incident carries a hundred lines of output twice over

Inputs: limit, server, container, include_logs.

watch_listA
Read-only

List the targets being watched, with their kind and what the last check recorded

Inputs: server.

watch_removeA
Destructive

Remove a target from the watch list, leaving its recorded incidents in place. Only the list changes: whatever was supervising it keeps running until the operator stops it

Inputs: container (required), server.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelogโ€ฆ

    TDQS

    B3.4/5.0

    Scored across 44 tools

    Disambiguation4/5

    Tools are organized into clear families (docker_, install_, backup_, watch_, proxmox_, inventory_) with distinct resource+action targets, and the descriptions go out of their way to explain boundaries such as install_uninstall vs install_purge and backup_list vs backup_drill. The main soft spots are the diagnostics cluster โ€” alerts, alerts_history, doctor, report, and to a lesser degree system_status โ€” which all answer overlapping "is my server healthy" questions and require reading descriptions carefully to choose. Docker_stop vs install_uninstall also overlap conceptually, though the descriptions disambiguate them.

    Naming Consistency4/5

    Nearly all tools follow a namespace_action pattern (docker_list, backup_create, watch_add, proxmox_guest_start, install_status), giving a predictable, scannable surface. The deviations are a handful of bare noun names (alerts, doctor, report, processes, wake, system_status, open_ports, proxmox_status, proxmox_node) that break the verb_noun expectation but stay readable and non-colliding. No camelCase/snake_case mixing.

    Tool Count3/5

    44 tools is heavy for a single server and pushes past what an agent can hold in working context comfortably, even though the families are well organized and most tools earn a distinct place. There is real consolidation potential in the diagnostics area (alerts, alerts_history, doctor, report) and in the Proxmox read tools (proxmox_status, proxmox_node, proxmox_tasks, proxmox_task_status, proxmox_guests). The breadth is defensible given the domain spans Docker, Proxmox, backups, app installs, and monitoring, so it is not arbitrary bloat.

    Completeness4/5

    Coverage is unusually thorough: full lifecycle for app installs (install/status/uninstall/purge), backups (create/list/restore/drill), watch targets (add/remove/list/check/history), Docker inspection and control, and Proxmox guest and node operations. The one acknowledged gap is the absence of a docker_start to pair with docker_stop, which forces operators to bring containers back manually; other omissions (no docker exec, no config write, no supervisor install) appear deliberate and are documented as such. Minor gaps only, most with stated rationale.

    Maintenance

    ActivityActive
    ResponsivenessUsually handled

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      MCP servers for managing homelab infrastructure. Monitor Docker/Podman containers, Ollama AI models, Pi-hole DNS, Unifi networks, and Ansible inventory.
      44
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      MCP server for homelab diagnostics + auto-update pipeline, managing Docker hosts via SSH with read-only diagnostics, image-drift visibility, and automated update execution with rollback.
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      A single MCP server that gives an AI assistant comprehensive access to manage a homelab, including SSH, Docker, Proxmox, Synology, Cloudflare, and more, with 85 tools and a centralized configuration.
      MIT