397 lines
15 KiB
YAML
397 lines
15 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_0008" }
|
|
- { "model": "qwen2.5-coder:0.5b", "provider": "qwen_25_coder_0005" }
|
|
llm_small_cpu: 2
|
|
llm_small_ram: 4000
|
|
llm_small:
|
|
- { "model": "qwen3.5:2b", "provider": "qwen_35_0020" }
|
|
- { "model": "qwen2.5-coder:1.5b", "provider": "qwen_25_coder_0015" }
|
|
- { "model": "qwen2.5-coder:3b", "provider": "qwen_25_coder_0030" }
|
|
llm_medium_cpu: 4
|
|
llm_medium_ram: 8000
|
|
llm_medium:
|
|
- { "model": "qwen3.5:4b", "provider": "qwen_35_0040" }
|
|
- { "model": "qwen2.5-coder:7b", "provider": "qwen_25_coder_0070" }
|
|
llm_large_cpu: 8
|
|
llm_large_ram: 16000
|
|
llm_large:
|
|
- { "model": "qwen3.5:9b", "provider": "qwen_35_0090" }
|
|
- { "model": "qwen2.5-coder:14b", "provider": "qwen_25_coder_0140" }
|
|
llm_huge_cpu: 12
|
|
llm_huge_ram: 32000
|
|
llm_huge:
|
|
- { "model": "qwen3.5:27b", "provider": "qwen_35_0270" }
|
|
- { "model": "qwen3.5:35b", "provider": "qwen_35_0350" }
|
|
- { "model": "qwen3-coder:30b", "provider": "qwen_30_coder_0300" }
|
|
- { "model": "qwen2.5-coder:32b", "provider": "qwen_25_coder_0320" }
|
|
llm_giant_cpu: 18
|
|
llm_giant_ram: 64000
|
|
llm_giant:
|
|
- { "model": "qwen3.5:122b", "provider": "qwen_35_1220" }
|
|
llm_removals:
|
|
# QWEN3 is the most similar to Ask Brave.
|
|
- { "model": "qwen3:4b", "provider": "qwen_30_0040" }
|
|
- { "model": "qwen3-coder:420b", "provider": "qwen_30_coder_4200" }
|
|
|
|
##### 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
|
|
#- qwen2.5:72b
|
|
### TBD
|
|
#- deepseek-r1:1.5b
|
|
#- deepseek-r1:7b
|
|
#- mistal:7b
|
|
#- ministral:3b
|
|
#- ministral:8b
|
|
#- mistal:7b
|
|
#- glm4-flash
|
|
|
|
- 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 | 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
|
|
or 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
|
|
or 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
|
|
or ansible_memtotal_mb < llm_giant_ram
|
|
|
|
- name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Print
|
|
ansible.builtin.debug:
|
|
var: "{{ item }}"
|
|
loop:
|
|
- llm_installs
|
|
- llm_removals
|
|
|
|
- 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
|
|
state: present
|
|
create: false
|
|
backup: true
|
|
owner: "{{ user }}"
|
|
mode: "0644"
|
|
|
|
# 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"
|
|
insertafter: EOF
|
|
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 %}
|
|
state: present
|
|
create: false
|
|
backup: true
|
|
owner: "{{ user }}"
|
|
mode: "0644"
|
|
|
|
- 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"
|