Files
env-ansible/tasks/workstation/linux/software/zeroclaw.yml
T

377 lines
14 KiB
YAML

---
# 2026-10-05 Hyperling
# Install a local LLM for use offline.
# - https://zeroclaw.net/blog/install-zeroclaw
# - https://zeroclaw.net/blog/set-up-zeroclaw-bot-ollama-telegram
#
# Packages Details
# - https://archlinux.org/packages/extra/x86_64/zeroclaw/
# - https://archlinux.org/packages/extra/x86_64/ollama/
#
# Individual Guides
# - https://github.com/zeroclaw-labs/zeroclaw
# - https://wiki.archlinux.org/title/Ollama
#
# Model Explanations
# - https://webscraft.org/blog/ollama-na-8-gb-ram-yaki-modeli-pratsyuyut-u-2026?lang=en
#
# Models
# - https://ollama.com/search
# Currently only testing this on Arch based distros (pacman).
- name: Workstation | Linux | Software | ZeroClaw
block:
- name: Workstation | Linux | Software | ZeroClaw | Facts 01
ansible.builtin.set_fact:
ollama: ollama
zeroclaw: zeroclaw
zerocode: zerocode
llm_installs: []
llm_micro_cpu: 1
llm_micro_ram: 2000
llm_micro:
- { "model": "qwen3.5:0.8b", "provider": "qwen_35_008" }
- { "model": "qwen2.5-coder:0.5b", "provider": "qwen_25_coder_005" }
llm_small_cpu: 2
llm_small_ram: 4000
llm_small:
- { "model": "qwen3.5:2b", "provider": "qwen_35_020" }
- { "model": "qwen2.5-coder:1.5b", "provider": "qwen_25_coder_015" }
- { "model": "qwen2.5-coder:3b", "provider": "qwen_25_coder_030" }
llm_medium_cpu: 4
llm_medium_ram: 8000
llm_medium:
- { "model": "qwen3.5:4b", "provider": "qwen_35_040" }
- { "model": "qwen2.5-coder:7b", "provider": "qwen_25_coder_070" }
llm_large_cpu: 8
llm_large_ram: 16000
llm_large:
- { "model": "qwen3.5:9b", "provider": "qwen_35_090" }
- { "model": "qwen2.5-coder:14b", "provider": "qwen_25_coder_140" }
llm_huge_cpu: 12
llm_huge_ram: 32000
llm_huge:
- { "model": "qwen3.5:27b", "provider": "qwen_35_270" }
- { "model": "qwen3.5:35b", "provider": "qwen_35_350" }
- { "model": "qwen3-coder:30b", "provider": "qwen_30_coder_300" }
- { "model": "qwen2.5-coder:32b", "provider": "qwen_25_coder_320" }
llm_giant_cpu: 18
llm_giant_ram: 64000
llm_giant:
- { "model": "qwen3.5:27b", "provider": "qwen_35_270" }
- { "model": "qwen3.5:35b", "provider": "qwen_35_350" }
llm_removals:
# QWEN3 is the most similar to Ask Brave.
- { "model": "qwen3:4b", "provider": "qwen_30_040" }
##### Others #####
### 2.5, does not have "thinking" as an attribute, tempting!
#- qwen2.5:0.5b
#- qwen2.5:1.5b
#- qwen2.5:3b
#- qwen2.5:7b
#- qwen2.5:14b
#- qwen2.5:32b
### TBD
#- deepseek-r1:1.5b
#- deepseek-r1:7b
#- mistal:7b
#- ministral:3b
#- ministral:8b
#- mistal:7b
#- glm4-flash
- name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Micro
ansible.builtin.set_fact:
llm_installs: "{{ llm_installs + llm_micro }}"
when: ansible_processor_vcpus >= llm_micro_cpu
and ansible_memtotal_mb >= llm_micro_ram
- name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Micro | Uninstall
ansible.builtin.set_fact:
llm_installs: "{{ llm_removals + llm_micro }}"
when: ansible_processor_vcpus < llm_micro_cpu
or ansible_memtotal_mb < llm_micro_ram
- name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Small
ansible.builtin.set_fact:
llm_installs: "{{ llm_installs + llm_small }}"
when: ansible_processor_vcpus >= llm_small_cpu
and ansible_memtotal_mb >= llm_small_ram
- name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Small | Uninstall
ansible.builtin.set_fact:
llm_installs: "{{ llm_removals + llm_small }}"
when: ansible_processor_vcpus < llm_small_cpu
or ansible_memtotal_mb < llm_small_ram
- name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Medium
ansible.builtin.set_fact:
llm_installs: "{{ llm_installs + llm_medium }}"
when: ansible_processor_vcpus >= llm_medium_cpu
and ansible_memtotal_mb >= llm_medium_ram
- name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Medium | Uninstalls
ansible.builtin.set_fact:
llm_installs: "{{ llm_removals + llm_medium }}"
when: ansible_processor_vcpus < llm_medium_cpu
or ansible_memtotal_mb < llm_medium_ram
- name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Large
ansible.builtin.set_fact:
llm_installs: "{{ llm_installs + llm_large }}"
when: ansible_processor_vcpus >= llm_large_cpu
and ansible_memtotal_mb >= llm_large_ram
- name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Large | Uninstalls
ansible.builtin.set_fact:
llm_installs: "{{ llm_removals + llm_large }}"
when: ansible_processor_vcpus < llm_large_cpu
and ansible_memtotal_mb < llm_large_ram
- name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Huge
ansible.builtin.set_fact:
llm_installs: "{{ llm_installs + llm_huge }}"
when: ansible_processor_vcpus >= llm_huge_cpu
and ansible_memtotal_mb >= llm_huge_ram
- name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Huge | Uninstalls
ansible.builtin.set_fact:
llm_installs: "{{ llm_removals + llm_huge }}"
when: ansible_processor_vcpus < llm_huge_cpu
and ansible_memtotal_mb < llm_huge_ram
- name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Giant
ansible.builtin.set_fact:
llm_installs: "{{ llm_installs + llm_giant }}"
when: ansible_processor_vcpus >= llm_giant_cpu
and ansible_memtotal_mb >= llm_giant_ram
- name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Giant | Uninstalls
ansible.builtin.set_fact:
llm_installs: "{{ llm_removals + llm_giant }}"
when: ansible_processor_vcpus < llm_giant_cpu
and ansible_memtotal_mb < llm_giant_ram
- name: Workstation | Linux | Software | ZeroClaw | Facts 02
ansible.builtin.set_fact:
ollama_runit_dir: "{{ runit_script_dir }}/{{ ollama }}"
ollama_runit_log_dest: "{{ global_log }}/{{ ollama }}"
zeroclaw_runit_dir: "{{ runit_script_dir }}/{{ zeroclaw }}"
zeroclaw_runit_log_dest: "{{ global_log }}/{{ zeroclaw }}"
- name: Workstation | Linux | Software | ZeroClaw | Facts 03
ansible.builtin.set_fact:
ollama_runit_script: "{{ ollama_runit_dir }}/{{ runit_service_script }}"
ollama_runit_log_dir: "{{ ollama_runit_dir }}/{{ runit_log_dir }}"
ollama_runit_log_config: "{{ ollama_runit_log_dest }}/{{ global_log_config }}"
zeroclaw_runit_script: "{{ zeroclaw_runit_dir }}/{{ runit_service_script }}"
zeroclaw_runit_log_dir: "{{ zeroclaw_runit_dir }}/{{ runit_log_dir }}"
zeroclaw_runit_log_config: "{{ zeroclaw_runit_log_dest }}/{{ global_log_config }}"
- name: Workstation | Linux | Software | ZeroClaw | Facts 04
ansible.builtin.set_fact:
ollama_runit_log_script: "{{ ollama_runit_log_dir }}/{{ runit_log_script }}"
zeroclaw_runit_log_script: "{{ zeroclaw_runit_log_dir }}/{{ runit_log_script }}"
- name: Workstation | Linux | Software | ZeroClaw | Install
ansible.builtin.package:
name:
- "{{ zeroclaw }}"
- "{{ ollama }}"
- "{{ zerocode }}"
state: present
- name: Workstation | Linux | Software | ZeroClaw | runit
block:
- name: Workstation | Linux | Software | ZeroClaw | runit | ollama | Dir | Run
ansible.builtin.file:
name: "{{ ollama_runit_dir }}"
state: directory
owner: root
mode: "0755"
- name: Workstation | Linux | Software | ZeroClaw | runit | ollama | Dir | Log Run
ansible.builtin.file:
name: "{{ ollama_runit_log_dir }}"
state: directory
owner: root
mode: "0755"
- name: Workstation | Linux | Software | ZeroClaw | runit | ollama | Dir | Log Config
ansible.builtin.file:
name: "{{ ollama_runit_log_dest }}"
state: directory
owner: "{{ user }}"
mode: "0755"
- name: Workstation | Linux | Software | ZeroClaw | runit | ollama | Script | Run
ansible.builtin.copy:
content: |
#!/bin/sh -e
# Allow pulling LLMs for use with ZeroClaw.
exec 2>&1
export HOME={{ user_user.home }}
export OLLAMA_KEEP_ALIVE=0 # default 5m
export OLLAMA_REQUEST_TIMEOUT=600 # default 300
export OLLAMA_MAX_LOADED_MODELS=1 # default is 3 if using CPUs, or 3 * NumGPUs
exec chpst -u {{ user }} ollama serve
dest: "{{ ollama_runit_script }}"
mode: "0755"
- name: Workstation | Linux | Software | ZeroClaw | runit | ollama | Script | Log
ansible.builtin.copy:
content: |
#!/bin/sh -e
exec svlogd -tt {{ ollama_runit_log_dest }}
dest: "{{ ollama_runit_log_script }}"
mode: "0755"
- name: Workstation | Linux | Software | ZeroClaw | runit | ollama | Script | Log Config
ansible.builtin.copy:
content: |
s10000000 # rotate at 10 MB
n5 # keep 5 old files
dest: "{{ ollama_runit_log_config }}"
owner: "{{ user }}"
mode: "0755"
- name: Workstation | Linux | Software | ZeroClaw | runit | zeroclaw | Dir | Run
ansible.builtin.file:
name: "{{ zeroclaw_runit_dir }}"
state: directory
owner: root
mode: "0755"
- name: Workstation | Linux | Software | ZeroClaw | runit | zeroclaw | Dir | Log
ansible.builtin.file:
name: "{{ zeroclaw_runit_log_dir }}"
state: directory
owner: root
mode: "0755"
- name: Workstation | Linux | Software | ZeroClaw | runit | zeroclaw | Dir | Log Config
ansible.builtin.file:
name: "{{ zeroclaw_runit_log_dest }}"
state: directory
owner: "{{ user }}"
mode: "0755"
- name: Workstation | Linux | Software | ZeroClaw | runit | zeroclaw | Script | File
ansible.builtin.copy:
content: |
#!/bin/sh -e
# Allow using the ZeroClaw frontend.
exec 2>&1
exec chpst -u {{ user }} zeroclaw daemon
dest: "{{ zeroclaw_runit_script }}"
mode: "0755"
- name: Workstation | Linux | Software | ZeroClaw | runit | zeroclaw | Script | Log
ansible.builtin.copy:
content: |
#!/bin/sh -e
exec svlogd -tt {{ zeroclaw_runit_log_dest }}
dest: "{{ zeroclaw_runit_log_script }}"
mode: "0755"
- name: Workstation | Linux | Software | ZeroClaw | runit | zeroclaw | Script | Log Config
ansible.builtin.copy:
content: |
s1000000 # rotate at 1 MB
n5 # keep 5 old files
dest: "{{ zeroclaw_runit_log_config }}"
mode: "0755"
when: ansible_service_mgr == "runit"
- name: Workstation | Linux | Software | ZeroClaw | Enable
ansible.builtin.service:
name: "{{ item }}"
state: started
enabled: true
loop:
- "{{ ollama }}"
- "{{ zeroclaw }}"
- name: Workstation | Linux | Software | ZeroClaw | Verify
ansible.builtin.uri:
url: "{{ item }}"
status_code: 200
timeout: 5
register: health
until: health.status == 200
retries: 12
delay: 5
loop:
- http://127.0.0.1:11434/
- http://127.0.0.1:42617/health
- name: Workstation | Linux | Software | ZeroClaw | Models | zeroclaw | Defaults
ansible.builtin.blockinfile:
path: "{{ user_user.home }}/.zeroclaw/config.toml"
marker: "# {mark} ANSIBLE MANAGED BLOCK | zeroclaw | Defaults"
insertbefore: BOF
block: |
default_provider = "ollama"
default_model = "qwen3.5:4b"
provider_timeout_secs = 3600
# Cheat Sheet: https://docs.zeroclaw.com/master/en/providers/configuration.html
- name: Workstation | Linux | Software | ZeroClaw | Models | zeroclaw | Add Providers
ansible.builtin.blockinfile:
path: "{{ user_user.home }}/.zeroclaw/config.toml"
marker: "# {mark} ANSIBLE MANAGED BLOCK | zeroclaw | Providers"
block: |
{% for llm in llm_installs %}
[providers.models.ollama.ansible_{{ llm.provider }}]
model = "{{ llm.model }}"
timeout_secs = 3600 # 1 hour, default is 120 (2 minutes), issue happens between 5 and 6 minutes though.
{% endfor %}
- name: Workstation | Linux | Software | ZeroClaw | Models | ollama | Install
ansible.builtin.shell: |
set -o pipefail
item='{{ item.model }}'
if ollama list | grep -q $item; then
echo "OKAY" &&
return
else
ollama pull $item 1>/dev/null &&
echo "INSTALLED" &&
return
fi
echo "FAILED"
loop: "{{ llm_installs }}"
register: shell_check
changed_when: "'INSTALLED' in shell_check.stdout"
failed_when: "'FAILED' in shell_check.stdout"
become: true
become_user: "{{ user }}"
when: llm_installs | length > 0
- name: Workstation | Linux | Software | ZeroClaw | Models | ollama | Remove
ansible.builtin.shell: |
set -o pipefail
item='{{ item.model }}'
if ollama list | grep -q $item; then
ollama rm $item &&
echo "INSTALLED" &&
return
else
echo "OKAY" &&
return
fi
echo "FAILED"
loop: "{{ llm_removals }}"
register: shell_check
changed_when: "'INSTALLED' in shell_check.stdout"
failed_when: "'FAILED' in shell_check.stdout"
become: true
become_user: "{{ user }}"
when: llm_removals | length > 0
when: ansible_pkg_mgr == "pacman"