--- # 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: false 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"