Skip to content

thinkaliker/LabAssistant

Repository files navigation

LabAssistant

A lightweight dashboard + docker orchestrator + host updater + more. AI assisted, human designed and reviewed.

Ansible too complicated? PyInfra also too complicated? Portainer too bloated? LabAssistant is a lightweight replacement for some of those: a single pane of glass dashboard with the ability to orchestrate docker compose files, restart containers, update images, and edit those compose files directly on the host. While we're at it, it also notifies you of host package updates and applies them. See DESIGN.md for the full design and API.md for the module contract and manager API.

Prerequisites

  • Go 1.26+ (only the manager build needs it; generated protobuf and dashboard assets are committed, so buf is not required to deploy)
  • tailscale (optional — otherwise route/open ports between your hosts and the manager)

Architecture

flowchart LR
    subgraph control["Control host"]
        dashboard["dashboard<br/>(web UI)"]
        subgraph manager["manager"]
            qm["quartermaster<br/>(SSH enroll / certs)"]
            aud["auditor<br/>(audit log)"]
            sched["scheduler<br/>(cron jobs)"]
            state[("state.json<br/>+ CA / certs")]
        end
    end

    subgraph host1["Host A"]
        assoc1["associate"]
        subgraph mods1["modules"]
            duo1["duo"]
            qup1["qup"]
            sys1["sys"]
        end
        assoc1 --> mods1
    end

    subgraph hostN["Host B …"]
        assocN["associate + modules"]
    end

    dashboard -->|REST + SSE| manager
    manager --- state
    qm -.->|SSH install / mTLS exchange| assoc1
    manager <-->|mTLS stream<br/>commands · status · logs · heartbeat| assoc1
    manager <-->|mTLS stream| assocN
Loading

Components

Component Role
associate Per-host agent and sole entrypoint. Holds a persistent mTLS stream to the manager (status, heartbeat, log streaming), serializes commands in a queue, and elevates via a helper for privileged actions.
manager The control host. Owns host/module state (state.json), the CA and issued certs, and the API the dashboard consumes. Derives liveness from the stream.
dashboard Slim web UI (frontend for the manager API): host/container health, manual actions, add/remove hosts, scheduled tasks, compose-file editing with validation + undo, log streaming, audit view, backup/restore, and login.

The manager has three internal packages:

  • quartermaster — negotiates SSH to install/upgrade associates, does the mTLS cert exchange, and watches cert expiry.
  • auditor — hash-chained, non-sensitive audit log (logins, host/module/cert changes, approvals) to file, SQLite, or external DB with configurable retention.
  • scheduler — cron jobs across one or many hosts with skip/catch-up/retry policy, confirmation for destructive tasks, and manager- or host-timezone scheduling.

Full detail for each lives in DESIGN.md.

Modules

Modules are the abilities an associate runs on a host (contract in API.md; compiled into the associate for v1).

Module Purpose
duo docker updater/orchestrator — image update checks (Watchtower-style), compose start/stop/restart, docker log streaming. On hosts without the docker CLI it stays empty; set LABASSISTANT_DEMO=1 on the associate to seed demo stacks for a docker-less walkthrough.
qup quick updater — dry-run + apply host package updates, distro-detected (Debian-based today).
sys system — host commands: logs, reboot (confirmed), interfaces, disk usage, uptime, service restarts.

More detail in DESIGN.md.

Quick deploy (dev VM)

To stand up a manager on a fresh Debian-based VM for development/testing, run the deploy script. It clones the repo, installs Go if needed, builds the manager, and installs the labassistant-manager systemd service (enabled on boot, but not started — you set the dashboard password first):

# on the VM
curl -fsSL https://raw.githubusercontent.com/thinkaliker/LabAssistant/main/scripts/deploy.sh -o deploy.sh
bash deploy.sh

Or from an existing checkout: ./scripts/deploy.sh. Options: --dir <checkout>, --branch <branch>, --home <data-home>, --no-service (skip the systemd unit). The service runs as the invoking user; if systemctl isn't present the deploy skips it and you run the manager by hand.

Then follow the printed next steps:

export LABASSISTANT_HOME="$HOME/.labassistant"
cd ~/LabAssistant
./scripts/manage.sh setpass   # set the dashboard login password
./scripts/manage.sh start     # start the service (already enabled on boot)
./scripts/manage.sh status    # check it; `logs -f` to follow

manage.sh wraps the whole lifecycle: start/stop/restart/status/logs, enable/disable, install-service/uninstall-service, build/update, and setpass. It calls sudo systemctl for you. setpass runs the manager binary against the service's home ($LABASSISTANT_HOME, else ~/.labassistant), so the password lands in the settings.json the running service reads — always prefer it over ./bin/manager setpass to avoid a home mismatch that leaves the dashboard in open mode (no login/logout). (Deployed with --no-service? Run ./bin/manager serve directly instead — dashboard on :8080, associate mTLS on :8443.)

The deploy script installs a starter config at $LABASSISTANT_HOME/config/config.toml (copied from config.sample.toml; with the default home that's ~/.labassistant/config/config.toml). To serve the dashboard on a different port — e.g. when 8080 is already in use — edit http_addr there before serve:

http_addr = ":9090"

Unset fields keep their defaults, and the associate mTLS port (grpc_addr, :8443) is independent, so enrolled hosts are unaffected. Deploying by hand instead of via the script? Just create that file yourself — a missing config means all defaults.

Rebuilding after code changes

The dashboard HTML/JS and generated protobuf are embedded into the manager binary, so any change to Go code or the dashboard requires a rebuild and restart. With the systemd service installed, use manage.sh:

cd ~/LabAssistant
./scripts/manage.sh build manager   # or `build all` for associate + helper too
./scripts/manage.sh restart         # restart under systemd
./scripts/manage.sh logs -f         # follow the journal

To pull, rebuild, and restart in one step: ./scripts/manage.sh update. Running by hand (--no-service) instead? go build -o bin/manager ./cmd/manager, then pkill -f 'bin/manager serve' and ./bin/manager serve.

Open the dashboard at http://<vm>:8080 (or whichever http_addr you set). To enroll a host without the SSH flow, mint a bundle with ./bin/manager enroll -name <host> (see BUILD.md).

Workflow

  1. Install and run the manager — it becomes your control host and root CA.
  2. Open the dashboard and add a host (SSH user; flag tailscale).
  3. The quartermaster SSHes in, installs the associate, and exchanges mTLS certs.
  4. The associate runs as a service, dials home over mTLS, and advertises its modules.
  5. Trigger actions from the dashboard → manager → associate → module.

Step-by-step setup is in DESIGN.md.

TODO

Known items not yet designed into the components above.

Associate / modules

  • Modules are compiled into the associate for v1; design toward external-binary modules later (the contract is already shaped for it).

Future work (post-v1)

  • Associate self-update: push a new associate/helper binary over the stream and swap+restart it (chunked transfer, atomic replace, restart). Deferred from v1.
  • ntfy integration
  • Webhooks for external notification.
  • Home Assistant integration.
  • Expand coverage over time: qup → more distros; duo → swarm/podman.
  • Multiple managers on different hosts can connect to an associate, but only one can perform operations at a time.
  • Dockerize LabAssistant.

About

all-in-one homelab management tool

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages