--- # 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 # # References # - https://docs.zeroclaw.com/master/en/reference/config.html # - https://docs.ollama.com # 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_concurrency: 1 # "{{ ansible_processor_vcpus / 4 | round(0, method='ceil') | int }}" llm_installs: [] llm_tier0_cpu: 1 llm_tier0_ram: 2000 llm_tier0: - { "model": "qwen3.5:0.8b", "provider": "qwen_35_0008" } - { "model": "qwen2.5-coder:0.5b", "provider": "qwen_25_coder_0005" } - { "model": "qwen2.5-coder:1.5b", "provider": "qwen_25_coder_0015" } llm_tier1_cpu: 2 llm_tier1_ram: 4000 llm_tier1: - { "model": "qwen3.5:2b", "provider": "qwen_35_0020" } - { "model": "qwen2.5-coder:3b", "provider": "qwen_25_coder_0030" } llm_tier2_cpu: 3 llm_tier2_ram: 6000 llm_tier2: - { "model": "qwen3.5:4b", "provider": "qwen_35_0040" } - { "model": "qwen2.5-coder:7b", "provider": "qwen_25_coder_0070" } llm_tier3_cpu: 4 llm_tier3_ram: 8000 llm_tier3: - { "model": "qwen3.5:9b", "provider": "qwen_35_0090" } - { "model": "qwen2.5-coder:14b", "provider": "qwen_25_coder_0140" } llm_tier4_cpu: 6 llm_tier4_ram: 12000 llm_tier4: [] llm_tier5_cpu: 10 llm_tier5_ram: 20000 llm_tier5: #- { "model": "devstral-small-2:24b", "provider": "devstral_20_0240_small" } #- { "model": "qwen3.8:27b", "provider": "qwen_38_0270" } llm_tier6_cpu: 12 llm_tier6_ram: 30000 llm_tier6: #- { "model": "qwen3.6:35b", "provider": "qwen_36_0350" } - { "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_tier7_cpu: 16 llm_tier7_ram: 64000 llm_tier7: [] llm_tier8_cpu: 32 llm_tier8_ram: 320000 llm_tier8: - { "model": "qwen3.5:122b", "provider": "qwen_35_1220" } llm_tier9_cpu: 64 llm_tier9_ram: 999000 llm_tier9: - { "model": "qwen3-coder:420b", "provider": "qwen_30_coder_4200" } llm_removals: # tier0 # # tier1 # - { "model": "qwen3:4b", "provider": "qwen_30_0040" } # tier2 # # tier3 # # tier4 # - { "model": "qwen3.6:27b", "provider": "qwen_36_0270" } - { "model": "qwen3.5:27b", "provider": "qwen_35_0270" } # tier5 # # tier9 # ##### TBD ##### ### 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 ### Others #- deepseek-r1:1.5b #- deepseek-r1:7b #- mistral:7b #- ministral-3:3b #- ministral-3:8b #- ministral-3:14b #- 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 | tier0 ansible.builtin.set_fact: llm_installs: "{{ llm_installs + llm_tier0 }}" when: ansible_processor_vcpus >= llm_tier0_cpu and ansible_memtotal_mb >= llm_tier0_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier0 | Uninstall ansible.builtin.set_fact: llm_removals: "{{ llm_removals + llm_tier0 }}" when: ansible_processor_vcpus < llm_tier0_cpu or ansible_memtotal_mb < llm_tier0_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier1 ansible.builtin.set_fact: llm_installs: "{{ llm_installs + llm_tier1 }}" when: ansible_processor_vcpus >= llm_tier1_cpu and ansible_memtotal_mb >= llm_tier1_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier1 | Uninstall ansible.builtin.set_fact: llm_removals: "{{ llm_removals + llm_tier1 }}" when: ansible_processor_vcpus < llm_tier1_cpu or ansible_memtotal_mb < llm_tier1_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier2 ansible.builtin.set_fact: llm_installs: "{{ llm_installs + llm_tier2 }}" when: ansible_processor_vcpus >= llm_tier2_cpu and ansible_memtotal_mb >= llm_tier2_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier2 | Uninstalls ansible.builtin.set_fact: llm_removals: "{{ llm_removals + llm_tier2 }}" when: ansible_processor_vcpus < llm_tier2_cpu or ansible_memtotal_mb < llm_tier2_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier3 ansible.builtin.set_fact: llm_installs: "{{ llm_installs + llm_tier3 }}" when: ansible_processor_vcpus >= llm_tier3_cpu and ansible_memtotal_mb >= llm_tier3_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier3 | Uninstalls ansible.builtin.set_fact: llm_removals: "{{ llm_removals + llm_tier3 }}" when: ansible_processor_vcpus < llm_tier3_cpu or ansible_memtotal_mb < llm_tier3_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier4 ansible.builtin.set_fact: llm_installs: "{{ llm_installs + llm_tier4 }}" when: ansible_processor_vcpus >= llm_tier4_cpu and ansible_memtotal_mb >= llm_tier4_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier4 | Uninstalls ansible.builtin.set_fact: llm_removals: "{{ llm_removals + llm_tier4 }}" when: ansible_processor_vcpus < llm_tier4_cpu or ansible_memtotal_mb < llm_tier4_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier5 ansible.builtin.set_fact: llm_installs: "{{ llm_installs + llm_tier5 }}" when: ansible_processor_vcpus >= llm_tier5_cpu and ansible_memtotal_mb >= llm_tier5_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier5 | Uninstalls ansible.builtin.set_fact: llm_removals: "{{ llm_removals + llm_tier5 }}" when: ansible_processor_vcpus < llm_tier5_cpu or ansible_memtotal_mb < llm_tier5_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier6 ansible.builtin.set_fact: llm_installs: "{{ llm_installs + llm_tier6 }}" when: ansible_processor_vcpus >= llm_tier6_cpu and ansible_memtotal_mb >= llm_tier6_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier6 | Uninstalls ansible.builtin.set_fact: llm_removals: "{{ llm_removals + llm_tier6 }}" when: ansible_processor_vcpus < llm_tier6_cpu or ansible_memtotal_mb < llm_tier6_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier7 ansible.builtin.set_fact: llm_installs: "{{ llm_installs + llm_tier7 }}" when: ansible_processor_vcpus >= llm_tier7_cpu and ansible_memtotal_mb >= llm_tier7_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier7 | Uninstalls ansible.builtin.set_fact: llm_removals: "{{ llm_removals + llm_tier7 }}" when: ansible_processor_vcpus < llm_tier7_cpu or ansible_memtotal_mb < llm_tier7_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier8 ansible.builtin.set_fact: llm_installs: "{{ llm_installs + llm_tier8 }}" when: ansible_processor_vcpus >= llm_tier8_cpu and ansible_memtotal_mb >= llm_tier8_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier8 | Uninstalls ansible.builtin.set_fact: llm_removals: "{{ llm_removals + llm_tier8 }}" when: ansible_processor_vcpus < llm_tier8_cpu or ansible_memtotal_mb < llm_tier8_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier9 ansible.builtin.set_fact: llm_installs: "{{ llm_installs + llm_tier9 }}" when: ansible_processor_vcpus >= llm_tier9_cpu and ansible_memtotal_mb >= llm_tier9_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | tier9 | Uninstalls ansible.builtin.set_fact: llm_removals: "{{ llm_removals + llm_tier9 }}" when: ansible_processor_vcpus < llm_tier9_cpu or ansible_memtotal_mb < llm_tier9_ram - name: Workstation | Linux | Software | ZeroClaw | Facts | Merge | Print ansible.builtin.debug: var: "{{ item }}" loop: - ansible_processor_vcpus - ansible_memtotal_mb - llm_concurrency - 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=20m # default 5m export OLLAMA_REQUEST_TIMEOUT=3600 # default 300 (unconfirmed, not in the list of --help vars) export OLLAMA_LOAD_TIMEOUT=30m # default is 5m export OLLAMA_MAX_LOADED_MODELS={{ llm_concurrency }} # default is 3 if using CPUs, or 3 * NumGPUs export OLLAMA_MAX_QUEUE=999 # default is 512 (unconfirmed) export OLLAMA_NUM_PARALLEL={{ llm_concurrency }} # default is 1 and applies per-model (unconfirmed) export OLLAMA_CONTEXT_LENGTH=999999 # default is 4096, https://docs.ollama.com/faq#how-can-i-specify-the-context-window-size 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 export HOME={{ user_user.home }} export SHELL={{ bash_exec.stdout }} exec chpst -u {{ user }} zeroclaw daemon -v --log-level info 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 | Config | zeroclaw | Root # ansible.builtin.blockinfile: # path: "{{ user_user.home }}/.zeroclaw/config.toml" # marker: "# {mark} ANSIBLE MANAGED BLOCK | zeroclaw | Defaults" # insertbefore: '(?m)^schema_version' # block: | # default_provider = "ollama" # default_model = "qwen3.5:4b" # provider_timeout_secs = 3600 # state: present # create: false # backup: true #- name: Workstation | Linux | Software | ZeroClaw | Config | zeroclaw | Channels | header # ansible.builtin.lineinfile: # path: "{{ user_user.home }}/.zeroclaw/config.toml" # insertbefore: '(?m)^[channels]' # block: | # message_timeout_secs = 3600 # state: present # create: false # backup: true #- name: Workstation | Linux | Software | ZeroClaw | Config | zeroclaw | Channels # ansible.builtin.blockinfile: # path: "{{ user_user.home }}/.zeroclaw/config.toml" # marker: "# {mark} ANSIBLE MANAGED BLOCK | zeroclaw | Channels" # insertbefore: '(?m)^[channels]' # block: | # message_timeout_secs = 3600 # state: present # create: false # backup: true - 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 # 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.{{ llm.provider }}] model = "{{ llm.model }}" timeout_secs = 3600 # 1 hour, default is 120 (2 minutes), issue happens between 5 and 6 minutes though. context_window = 999999999 {% endfor %} state: present create: false backup: true when: ansible_pkg_mgr == "pacman"