Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 49 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ After a clean bootstrap you have:

- Homebrew + the LLMKube-supporting package set (`kubectl`, `helm`,
`kind`, `jq`, `yq`, `gh`, `k9s`, `tmux`, `ripgrep`, `fzf`).
- Docker Desktop, started, with a `kind` cluster named
`llmkube-local` running the LLMKube operator from the published
Helm chart.
- `colima` started with a user-space Lima VM (4 CPU, 6 GiB, 60 GiB
disk by default; override via `--extra-vars`). Provides the Docker
socket `kind` uses. `docker` / `docker-compose` / `docker-buildx`
CLIs installed via Homebrew, no Docker Desktop required. To use
Docker Desktop instead, see the "Docker Desktop on Apple Silicon"
callout near the end of this README.
- A `kind` cluster named `llmkube-local` running the LLMKube
operator from the published Helm chart.
- A clone of `defilantech/LLMKube` at `~/src/LLMKube`, and the
`metal-agent` daemon installed + running under `launchd`.
- A starter model (`phi-4-mini-instruct`, Q4_K_M) deployed via
Expand Down Expand Up @@ -97,7 +102,7 @@ group_vars/all.yml # tunables (memory_fraction, src_dir, model_repo, etc.)
roles/
system/ # macOS sanity asserts, Xcode CLT
homebrew/ # brew + per-package installs
kubernetes/ # Docker Desktop, kind, kubectl, helm, k9s
kubernetes/ # colima, kind, kubectl, helm, k9s
llmkube_core/ # clone LLMKube, helm install, install metal-agent
model_starter/ # download phi-4-mini, apply Model + InferenceService
opencode/ # opencode binary + sanitized config template
Expand All @@ -119,11 +124,48 @@ Every PR runs three linters:
- `shellcheck` — `bootstrap.sh` and `teardown.sh`

These run on an Ubuntu runner in seconds. They do not exercise the
playbook end-to-end — macOS-bound bugs in homebrew / launchd / Docker
Desktop will only surface when a human runs the bootstrap on a real
Mac. A future change may add a nightly macOS runner that does the
playbook end-to-end — macOS-bound bugs in homebrew / launchd / colima
will only surface when a human runs the bootstrap on a real Mac. A
future change may add a nightly macOS runner that does the
full install; that's not in v0.1.

## Docker Desktop on Apple Silicon (opt-in only)

The default Docker runtime is **colima**. If you specifically want
Docker Desktop instead (for the GUI, for parity with an existing
team setup, or to test something Docker Desktop-only), opt in via
your `group_vars`:

```yaml
# group_vars/all.yml or a per-host override
homebrew_casks:
- docker # the cask is named `docker`; brew resolves to docker-desktop
```

Two caveats specific to Apple Silicon:

1. Docker Desktop's cask postinstall hook writes CLI plugin symlinks
into `/usr/local/cli-plugins`. The pre-task in
`roles/homebrew/tasks/main.yml` handles that automatically when
`homebrew_casks` contains `docker` / `docker-desktop`.
2. Docker Desktop also writes the `docker`, `docker-compose`, and
`docker-buildx` binaries into `/usr/local/bin`. On a fresh
Apple Silicon Mac that directory may not exist (Homebrew lives
at `/opt/homebrew/bin`), and `/usr/local/` is root-owned. If
bootstrap fails here, run once:

```sh
sudo mkdir -p /usr/local/bin
sudo chown -R "$(whoami)":staff /usr/local/bin
./bootstrap.sh
```

Subsequent runs no-op; Docker Desktop will not try to recreate
the directory once it has user-write perms.

If neither caveat sounds appealing, the default colima path is
strictly easier.

## Contributing

This repo is small but opinionated. Two rules:
Expand Down
21 changes: 19 additions & 2 deletions roles/homebrew/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ homebrew_cli_packages:
- k9s
- kustomize

# Docker via colima. colima boots a user-space Lima VM and exposes
# a Docker socket; the docker / docker-compose / docker-buildx CLIs
# talk to that socket. No /usr/local writes, no sudo dance, smaller
# resident footprint than Docker Desktop. To use Docker Desktop
# instead, add `docker` to `homebrew_casks` in your group_vars and
# be ready to run sudo for /usr/local/* directory creation -- see
# README "Docker Desktop on Apple Silicon" callout.
- colima
- docker
- docker-compose
- docker-buildx

# llama.cpp for Metal-accelerated inference (metal-agent shells to it)
- llama.cpp

Expand All @@ -26,5 +38,10 @@ homebrew_cli_packages:
- go
- make

homebrew_casks:
- docker # Docker Desktop -- container runtime kind depends on
# Default casks: empty. The previous default included Docker Desktop,
# but its postinstall hook sudo-writes to multiple /usr/local/*
# paths that do not exist on a fresh Apple Silicon Mac, and brew
# runs under `become: false` so the sudo prompts have no TTY. We
# switched to colima (above) as the default Docker runtime. Add
# casks here in your group_vars when you want GUI apps.
homebrew_casks: []
6 changes: 6 additions & 0 deletions roles/homebrew/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,9 @@
# Desktop manually.
accept_external_apps: true
become: false
# community.general.homebrew_cask errors with "You must select a
# cask to install" when handed an empty list. The default
# homebrew_casks is now [] (colima replaces docker-desktop), so
# skip the task entirely unless a user has opted into one or more
# casks via group_vars / --extra-vars.
when: homebrew_casks | length > 0
20 changes: 20 additions & 0 deletions roles/kubernetes/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
# kubernetes role defaults.
#
# The Docker runtime is colima (user-space Lima VM) rather than
# Docker Desktop. Reasons:
# - No /usr/local/* writes -> no sudo prompts during cask install
# - Matches the convention already in use on other LLMKube hosts
# - Smaller resident memory footprint, useful on Macs that also
# serve a 30B-class model locally
#
# Override any of the below in group_vars/all.yml or via
# --extra-vars when the defaults do not fit your hardware.

# colima VM sizing. Defaults are tuned for a single-node kind cluster
# running LLMKube core plus a couple of small workloads -- enough
# headroom that `kind create cluster` does not OOM, not so much that
# we steal RAM from mlx-server.
colima_cpu: 4
colima_memory_gib: 6
colima_disk_gib: 60
29 changes: 22 additions & 7 deletions roles/kubernetes/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
---
# kubernetes role -- bring up the local cluster the operator runs on.
#
# Docker Desktop is installed by the homebrew role; we just need to
# make sure it is running, then create the kind cluster + verify the
# operator can talk to it.
# Docker is provided by colima (user-space Lima VM), installed by the
# homebrew role. We make sure colima is running with enough resources
# for a kind cluster, then create the cluster + verify connectivity.
# Colima's daemon stays up across reboots once started; `colima start`
# is idempotent (no-ops when the VM is already up).

- name: Ensure Docker Desktop is running
ansible.builtin.command: open -ga Docker
- name: Check colima status
ansible.builtin.command: colima status
register: colima_status
changed_when: false
# `open -ga` is idempotent: silently no-ops when Docker is already up.
failed_when: false

- name: Start colima with the configured VM size
ansible.builtin.command: >-
colima start
--cpu {{ colima_cpu }}
--memory {{ colima_memory_gib }}
--disk {{ colima_disk_gib }}
# colima writes the 'is it running?' line to stderr, hence the
# stderr match below. rc != 0 covers the first-ever invocation
# where the VM has not been created yet.
when: colima_status.rc != 0 or 'Running' not in (colima_status.stderr | default(''))
changed_when: true

- name: Wait for Docker daemon to accept commands
- name: Wait for Docker daemon (via colima) to accept commands
ansible.builtin.command: docker info
register: docker_info
retries: 30
Expand Down
Loading