Refactor llama_server role for Windows support and EL-specific setup

- Added Windows-specific tasks for llama_server role, including installation of dependencies, setup of services using NSSM, and configuration of firewall rules.
- Introduced a new task file for Windows Exporter setup.
- Consolidated EL-specific setup tasks into a separate include file.
- Updated inventory to include additional host for llama server.
- Adjusted error handling in Prometheus service removal task to prevent failures.
This commit is contained in:
2026-06-01 14:59:30 +02:00
parent bb8b14dcca
commit 3775fa4b6e
12 changed files with 1306 additions and 355 deletions
@@ -0,0 +1,336 @@
---
- name: Validate llama_server_models schema
ansible.builtin.assert:
that:
- llama_server_models is iterable
fail_msg: "llama_server_models must be a list"
- name: Create group
become: true
ansible.builtin.group:
name: "{{ llama_server_group }}"
system: true
- name: Create user
become: true
ansible.builtin.user:
name: "{{ llama_server_user }}"
groups: "{{ podman_extra_groups | default([]) }}"
shell: /bin/bash
home: "{{ llama_server_home }}"
group: "{{ llama_server_group }}"
- name: Disable password login for user
ansible.builtin.command: passwd -d "{{ llama_server_user }}"
become: true
changed_when: false
- name: Create user home
become: true
ansible.builtin.file:
path: "{{ llama_server_home }}"
state: directory
owner: "{{ llama_server_user }}"
group: "{{ llama_server_group }}"
mode: "0750"
- name: Set fact podman_binary (Debian family)
ansible.builtin.set_fact:
podman_binary: /usr/bin/podman
when: ansible_os_family == 'Debian'
- name: Set fact podman_binary (Red Hat family)
ansible.builtin.set_fact:
podman_binary: /usr/sbin/podman
when: ansible_os_family == 'RedHat'
- name: Create required folders
become: true
ansible.builtin.file:
path: "{{ llama_server_home }}/{{ item }}"
state: directory
owner: "{{ llama_server_user }}"
group: "{{ llama_server_group }}"
mode: "0750"
loop:
- models
- .cache
- .cache/llama.cpp
- name: "Add SSH Authorized key"
become: true
ansible.posix.authorized_key:
user: "{{ llama_server_user }}"
key: "{{ llama_server_pubkey }}"
state: "{{ 'present' if llama_server_pubkey is defined and llama_server_pubkey != '' else 'absent' }}"
when: llama_server_pubkey is defined and llama_server_pubkey != ""
- name: SELinux tasks (RedHat only)
when: ansible_os_family == 'RedHat'
become: true
block:
- name: Persistently set SELinux context
community.general.sefcontext:
target: "{{ llama_server_home }}(/.*)?"
setype: user_home_dir_t
state: present
- name: Persistently set SELinux context (ssh authorized keys)
community.general.sefcontext:
target: "{{ llama_server_home }}/.ssh/authorized_keys"
setype: ssh_home_t
state: present
when: llama_server_pubkey is defined and llama_server_pubkey != ""
- name: Apply SELinux context
ansible.builtin.command: restorecon -Rv {{ llama_server_home }}
changed_when: false
- name: Enable sudo access to llama user (temporary)
become: true
ansible.builtin.lineinfile:
path: "/etc/sudoers.d/ansible_{{ llama_server_user }}_tmp"
create: true
mode: "0440"
line: "{{ ansible_facts['user_id'] }} ALL=({{ llama_server_user }}) NOPASSWD: ALL"
validate: "visudo -cf %s"
- name: Enable lingering for llama user
become: true
ansible.builtin.command: loginctl enable-linger {{ llama_server_user }}
register: linger_status
changed_when: >-
'created' in linger_status.stdout or
(linger_status.rc == 0 and not
('linger file already exists' in linger_status.stderr or
'linger file does not exist' in linger_status.stderr))
failed_when:
- linger_status.rc != 0 and not
('linger file already exists' in linger_status.stderr)
- name: Get current user's UID
ansible.builtin.command: id -u {{ llama_server_user }}
changed_when: false
register: uid
- name: Get current user's GID
ansible.builtin.command: id -g {{ llama_server_user }}
changed_when: false
register: gid
- name: Run tasks as podman user
become: true
become_user: "{{ llama_server_user }}"
block:
- name: Pull the requested images
containers.podman.podman_image:
name: "{{ llama_server_image }}"
tag: "{{ llama_server_tag }}"
state: present
- name: Create pods
containers.podman.podman_container:
name: "llama.cpp"
image: "{{ llama_server_image }}:{{ llama_server_tag }}"
state: started
device: "{{ llama_server_devices | default(omit) }}"
env: "{{ llama_server_env | default(omit) }}"
ports: "0.0.0.0:{{ llama_server_port }}:8080/tcp"
volumes:
- "{{ llama_server_home }}/models:/models:Z"
- "{{ llama_server_home }}/.cache:/app/.cache:Z"
command: >-
{{
[
'--metrics',
'--models-preset',
'/models/.router/models.ini',
'--models-max',
(llama_server_models_max | string)
]
+ (llama_server_argv_extra | default([]))
}}
cmd_args:
- "--userns=keep-id"
- "--security-opt=label=disable"
- name: Create systemd service file for pods
become: true
ansible.builtin.template:
src: podman.service.j2
dest: "/etc/systemd/system/podman-llama.cpp.service"
owner: root
group: root
mode: "0644"
- name: Reload systemd daemon
become: true
ansible.builtin.systemd:
daemon_reload: true
- name: Enable containers at boot
become: true
ansible.builtin.systemd:
name: "podman-llama.cpp"
enabled: true
state: "started"
- name: Install Python packages required for llama server model sync
become: true
ansible.builtin.package:
name: "{{ __llama_sync_system_packages__ }}"
state: present
vars:
__llama_sync_system_packages__: >-
{{
{
'Debian': ['python3-pip', 'python3-packaging', 'python3-venv'],
'RedHat': ['python3-pip', 'python3-packaging'],
'Archlinux': ['python-pip', 'python-packaging']
}[ansible_os_family]
}}
- name: Install Hugging Face hub client in llama server sync virtualenv
become: true
become_user: "{{ llama_server_user }}"
ansible.builtin.pip:
name:
- pip
- setuptools
- packaging
- huggingface_hub>=0.32.0
state: present
virtualenv: "{{ llama_server_home }}/.router/.venv"
virtualenv_command: "python3 -m venv"
- name: Create llama server router model directories
become: true
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
owner: "{{ llama_server_user }}"
group: "{{ llama_server_group }}"
mode: "{{ item.mode }}"
loop:
- { path: "{{ llama_server_home }}/models", mode: "0750" }
- { path: "{{ llama_server_home }}/models/managed", mode: "0750" }
- { path: "{{ llama_server_home }}/models/managed-links", mode: "0750" }
- { path: "{{ llama_server_home }}/models/.router", mode: "0750" }
- name: Copy llama server Hugging Face sync helper
become: true
ansible.builtin.copy:
src: scripts/llama_hf_sync.py
dest: "{{ llama_server_home }}/models/.router/llama_hf_sync.py"
owner: "{{ llama_server_user }}"
group: "{{ llama_server_group }}"
mode: "0750"
- name: Write desired llama server model manifest input
become: true
ansible.builtin.copy:
content: "{{ llama_server_models | to_nice_json }}"
dest: "{{ llama_server_home }}/models/.router/desired-models.json"
owner: "{{ llama_server_user }}"
group: "{{ llama_server_group }}"
mode: "0640"
- name: Write global llama server preset options
become: true
ansible.builtin.copy:
content: "{{ llama_server_preset_global | to_nice_json }}"
dest: "{{ llama_server_home }}/models/.router/preset-global.json"
owner: "{{ llama_server_user }}"
group: "{{ llama_server_group }}"
mode: "0640"
- name: Set arguments for llama server model sync
ansible.builtin.set_fact:
__llama_preset_file__:
- "{{ llama_server_home }}/.router/.venv/bin/python"
- "{{ llama_server_home }}/models/.router/llama_hf_sync.py"
- "--models-file"
- "{{ llama_server_home }}/models/.router/desired-models.json"
- "--managed-dir"
- "{{ llama_server_home }}/models/managed"
- "--links-dir"
- "{{ llama_server_home }}/models/managed-links"
- "--manifest-file"
- "{{ llama_server_home }}/models/.router/manifest.json"
- "--preset-file"
- "{{ llama_server_home }}/models/.router/models.ini"
- "--preset-global-file"
- "{{ llama_server_home }}/models/.router/preset-global.json"
- "--container-links-dir"
- "/models/managed-links"
- name: Run llama server model sync from Hugging Face
vars:
__llama_sync_argv__: >-
{{ __llama_preset_file__
+ (['--prune'] if llama_server_models_prune_enabled | default(true) else [])
+ (['--dry-run'] if llama_server_models_dry_run | default(false) else [])
}}
become: true
become_user: "{{ llama_server_user }}"
ansible.builtin.command:
argv: "{{ __llama_sync_argv__ }}"
environment:
HF_TOKEN: "{{ lookup('env', 'HF_TOKEN') | default('', true) }}"
register: __llama_sync_output__
changed_when: false
- name: Parse llama server sync output
ansible.builtin.set_fact:
__llama_sync_result__: "{{ __llama_sync_output__.stdout | from_json }}"
- name: Stat llama router preset file (when not in dry-run mode)
become: true
become_user: "{{ llama_server_user }}"
ansible.builtin.stat:
path: "{{ llama_server_home }}/models/.router/models.ini"
register: __llama_preset_stat__
when: not (llama_server_models_dry_run | default(false))
- name: Ensure router preset file exists when not in dry-run mode
ansible.builtin.assert:
that:
- __llama_preset_stat__.stat.exists
fail_msg: "llama router preset file was not created"
when: not (llama_server_models_dry_run | default(false))
- name: Query llama router model list
ansible.builtin.uri:
url: "http://127.0.0.1:{{ llama_server_port | default(11434) }}/models?reload=1"
method: GET
status_code: 200
register: __llama_router_models__
changed_when: false
when:
- llama_server_models_sync_enabled | default(true)
- not (llama_server_models_dry_run | default(false))
- name: Validate expected model aliases are available in router
ansible.builtin.assert:
that:
- item.name in (__llama_router_models__.json.data | map(attribute='id') | list)
fail_msg: "Model name '{{ item.name }}' not present in llama router /models output"
loop: "{{ __llama_sync_result__.models | default([]) }}"
loop_control:
label: "{{ item.name }}"
when:
- llama_server_models_sync_enabled | default(true)
- not (llama_server_models_dry_run | default(false))
- name: Include Llama Exporter setup
when: llama_server_exporter_enabled | default(true) | bool
ansible.builtin.include_tasks: llama_exporter.yml
tags:
- roles::llama_server::llama_exporter
- name: Remove temporary sudo access
become: true
ansible.builtin.file:
path: "/etc/sudoers.d/ansible_{{ llama_server_user }}_tmp"
state: absent
+12 -353
View File
@@ -1,358 +1,17 @@
---
- name: Validate llama_server_models schema
ansible.builtin.assert:
that:
- llama_server_models is iterable
fail_msg: "llama_server_models must be a list"
# --- EL-specific setup (podman + systemd) ---
- name: Create group
become: true
ansible.builtin.group:
name: "{{ llama_server_group }}"
system: true
- name: Create user
become: true
ansible.builtin.user:
name: "{{ llama_server_user }}"
groups: "{{ podman_extra_groups | default([]) }}"
shell: /bin/bash
home: "{{ llama_server_home }}"
group: "{{ llama_server_group }}"
- name: Disable password login for user
ansible.builtin.command: passwd -d "{{ llama_server_user }}"
become: true
changed_when: false
- name: Create user home
become: true
ansible.builtin.file:
path: "{{ llama_server_home }}"
state: directory
owner: "{{ llama_server_user }}"
group: "{{ llama_server_group }}"
mode: "0750"
- name: Set fact podman_binary (Debian family)
ansible.builtin.set_fact:
podman_binary: /usr/bin/podman
when: ansible_os_family == 'Debian'
- name: Set fact podman_binary (Red Hat family)
ansible.builtin.set_fact:
podman_binary: /usr/sbin/podman
- name: Run EL container setup
when: ansible_os_family == 'RedHat'
- name: Create required folders
become: true
ansible.builtin.file:
path: "{{ llama_server_home }}/{{ item }}"
state: directory
owner: "{{ llama_server_user }}"
group: "{{ llama_server_group }}"
mode: "0750"
loop:
- models
- .cache
- .cache/llama.cpp
- name: "Add SSH Authorized key"
become: true
ansible.posix.authorized_key:
user: "{{ llama_server_user }}"
key: "{{ llama_server_pubkey }}"
state: "{{ 'present' if llama_server_pubkey is defined and llama_server_pubkey != '' else 'absent' }}"
when: llama_server_pubkey is defined and llama_server_pubkey != ""
- name: SELinux tasks (RedHat only)
when: ansible_os_family == 'RedHat'
become: true
block:
- name: Persistently set SELinux context
community.general.sefcontext:
target: "{{ llama_server_home }}(/.*)?"
setype: user_home_dir_t
state: present
- name: Persistently set SELinux context (ssh authorized keys)
community.general.sefcontext:
target: "{{ llama_server_home }}/.ssh/authorized_keys"
setype: ssh_home_t
state: present
when: llama_server_pubkey is defined and llama_server_pubkey != ""
- name: Apply SELinux context
ansible.builtin.command: restorecon -Rv {{ llama_server_home }}
changed_when: false
- name: Enable sudo access to llama user (temporary)
become: true
ansible.builtin.lineinfile:
path: "/etc/sudoers.d/ansible_{{ llama_server_user }}_tmp"
create: true
mode: "0440"
line: "{{ ansible_facts['user_id'] }} ALL=({{ llama_server_user }}) NOPASSWD: ALL"
validate: "visudo -cf %s"
- name: Enable lingering for llama user
become: true
ansible.builtin.command: loginctl enable-linger {{ llama_server_user }}
register: linger_status
changed_when: >-
'created' in linger_status.stdout or
(linger_status.rc == 0 and not
('linger file already exists' in linger_status.stderr or
'linger file does not exist' in linger_status.stderr))
failed_when:
- linger_status.rc != 0 and not
('linger file already exists' in linger_status.stderr)
- name: Get current user's UID
ansible.builtin.command: id -u {{ llama_server_user }}
changed_when: false
register: uid
- name: Get current user's GID
ansible.builtin.command: id -g {{ llama_server_user }}
changed_when: false
register: gid
- name: Run tasks as podman user
become: true
become_user: "{{ llama_server_user }}"
block:
- name: Pull the requested images
containers.podman.podman_image:
name: "{{ llama_server_image }}"
tag: "{{ llama_server_tag }}"
state: present
- name: Create pods
containers.podman.podman_container:
name: "llama.cpp"
image: "{{ llama_server_image }}:{{ llama_server_tag }}"
state: started
device: "{{ llama_server_devices | default(omit) }}"
env: "{{ llama_server_env | default(omit) }}"
ports: "0.0.0.0:{{ llama_server_port }}:8080/tcp"
volumes:
- "{{ llama_server_home }}/models:/models:Z"
- "{{ llama_server_home }}/.cache:/app/.cache:Z"
command:
- "-t"
- "{{ llama_server_argv_threads | default(10) }}"
- "-np"
- "{{ llama_server_argv_parallel | default(1) }}"
- "-b"
- "{{ llama_server_argv_batch_size | default(1024) }}"
- "-ub"
- "{{ llama_server_argv_ubatch_size | default(512) }}"
- "-fa"
- "{{ llama_server_argv_flash_attn | default('on') }}"
- "-ctk"
- "{{ llama_server_argv_cache_type_k | default('q4_0') }}"
- "-ctv"
- "{{ llama_server_argv_cache_type_v | default('q8_0') }}"
- "-cram"
- "{{ llama_server_argv_cache_reuse | default(-1) }}"
- "-sm"
- "{{ llama_server_argv_split_mode | default('layer') }}"
- "-dev"
- "{{ llama_server_argv_devices | default('Vulkan1,Vulkan2') }}"
- "-ts"
- "{{ llama_server_argv_tensor_split | default('8,12') }}"
- "-fit"
- "{{ llama_server_argv_fit | default('off') }}"
- "-mg"
- "{{ llama_server_argv_main_gpu | default(1) }}"
- "--mmap"
- "--metrics"
- "--models-preset"
- "/models/.router/models.ini"
- "--models-max"
- "{{ llama_server_models_max }}"
cmd_args:
- "--userns=keep-id"
- "--security-opt=label=disable"
- name: Create systemd service file for pods
become: true
ansible.builtin.template:
src: podman.service.j2
dest: "/etc/systemd/system/podman-llama.cpp.service"
owner: root
group: root
mode: "0644"
- name: Reload systemd daemon
become: true
ansible.builtin.systemd:
daemon_reload: true
- name: Enable containers at boot
become: true
ansible.builtin.systemd:
name: "podman-llama.cpp"
enabled: true
state: "started"
- name: Install Python packages required for llama server model sync
become: true
ansible.builtin.package:
name: "{{ __llama_sync_system_packages__ }}"
state: present
vars:
__llama_sync_system_packages__: >-
{{
{
'Debian': ['python3-pip', 'python3-packaging', 'python3-venv'],
'RedHat': ['python3-pip', 'python3-packaging'],
'Archlinux': ['python-pip', 'python-packaging']
}[ansible_os_family]
}}
- name: Install Hugging Face hub client in llama server sync virtualenv
become: true
become_user: "{{ llama_server_user }}"
ansible.builtin.pip:
name:
- pip
- setuptools
- packaging
- huggingface_hub>=0.32.0
state: present
virtualenv: "{{ llama_server_home }}/.router/.venv"
virtualenv_command: "python3 -m venv"
- name: Create llama server router model directories
become: true
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
owner: "{{ llama_server_user }}"
group: "{{ llama_server_group }}"
mode: "{{ item.mode }}"
loop:
- { path: "{{ llama_server_home }}/models", mode: "0750" }
- { path: "{{ llama_server_home }}/models/managed", mode: "0750" }
- { path: "{{ llama_server_home }}/models/managed-links", mode: "0750" }
- { path: "{{ llama_server_home }}/models/.router", mode: "0750" }
- name: Copy llama server Hugging Face sync helper
become: true
ansible.builtin.copy:
src: scripts/llama_hf_sync.py
dest: "{{ llama_server_home }}/models/.router/llama_hf_sync.py"
owner: "{{ llama_server_user }}"
group: "{{ llama_server_group }}"
mode: "0750"
- name: Write desired llama server model manifest input
become: true
ansible.builtin.copy:
content: "{{ llama_server_models | to_nice_json }}"
dest: "{{ llama_server_home }}/models/.router/desired-models.json"
owner: "{{ llama_server_user }}"
group: "{{ llama_server_group }}"
mode: "0640"
- name: Write global llama server preset options
become: true
ansible.builtin.copy:
content: "{{ llama_server_preset_global | to_nice_json }}"
dest: "{{ llama_server_home }}/models/.router/preset-global.json"
owner: "{{ llama_server_user }}"
group: "{{ llama_server_group }}"
mode: "0640"
- name: Set arguments for llama server model sync
ansible.builtin.set_fact:
__llama_preset_file__:
- "{{ llama_server_home }}/.router/.venv/bin/python"
- "{{ llama_server_home }}/models/.router/llama_hf_sync.py"
- "--models-file"
- "{{ llama_server_home }}/models/.router/desired-models.json"
- "--managed-dir"
- "{{ llama_server_home }}/models/managed"
- "--links-dir"
- "{{ llama_server_home }}/models/managed-links"
- "--manifest-file"
- "{{ llama_server_home }}/models/.router/manifest.json"
- "--preset-file"
- "{{ llama_server_home }}/models/.router/models.ini"
- "--preset-global-file"
- "{{ llama_server_home }}/models/.router/preset-global.json"
- "--container-links-dir"
- "/models/managed-links"
- name: Run llama server model sync from Hugging Face
vars:
__llama_sync_argv__: >-
{{ __llama_preset_file__
+ (['--prune'] if llama_server_models_prune_enabled | default(true) else [])
+ (['--dry-run'] if llama_server_models_dry_run | default(false) else [])
}}
become: true
become_user: "{{ llama_server_user }}"
ansible.builtin.command:
argv: "{{ __llama_sync_argv__ }}"
environment:
HF_TOKEN: "{{ lookup('env', 'HF_TOKEN') | default('', true) }}"
register: __llama_sync_output__
changed_when: false
- name: Parse llama server sync output
ansible.builtin.set_fact:
__llama_sync_result__: "{{ __llama_sync_output__.stdout | from_json }}"
- name: Stat llama router preset file (when not in dry-run mode)
become: true
become_user: "{{ llama_server_user }}"
ansible.builtin.stat:
path: "{{ llama_server_home }}/models/.router/models.ini"
register: __llama_preset_stat__
when: not (llama_server_models_dry_run | default(false))
- name: Ensure router preset file exists when not in dry-run mode
ansible.builtin.assert:
that:
- __llama_preset_stat__.stat.exists
fail_msg: "llama router preset file was not created"
when: not (llama_server_models_dry_run | default(false))
- name: Query llama router model list
ansible.builtin.uri:
url: "http://127.0.0.1:{{ llama_server_port | default(8080) }}/models?reload=1"
method: GET
status_code: 200
register: __llama_router_models__
changed_when: false
when:
- llama_server_models_sync_enabled | default(true)
- not (llama_server_models_dry_run | default(false))
- name: Validate expected model aliases are available in router
ansible.builtin.assert:
that:
- item.name in (__llama_router_models__.json.data | map(attribute='id') | list)
fail_msg: "Model name '{{ item.name }}' not present in llama router /models output"
loop: "{{ __llama_sync_result__.models | default([]) }}"
loop_control:
label: "{{ item.name }}"
when:
- llama_server_models_sync_enabled | default(true)
- not (llama_server_models_dry_run | default(false))
- name: Include Llama Exporter setup
when: llama_server_exporter_enabled | default(true) | bool
ansible.builtin.include_tasks: llama_exporter.yml
ansible.builtin.include_tasks:
file: el/main.yml
tags:
- roles::llama_server::llama_exporter
- roles::llama_server::el
- name: Remove temporary sudo access
become: true
ansible.builtin.file:
path: "/etc/sudoers.d/ansible_{{ llama_server_user }}_tmp"
state: absent
# --- Windows-specific setup (NSSM) ---
- name: Run Windows setup
when: ansible_os_family == 'Windows'
ansible.builtin.include_tasks:
file: windows/main.yml
tags:
- roles::llama_server::windows
@@ -0,0 +1,163 @@
---
# Windows Exporter tasks for llama_server role
- name: Define Windows Exporter paths
ansible.builtin.set_fact:
__exporter_install_dir: "{{ llama_server_windows_install_dir }}\\exporter"
__exporter_script: "{{ llama_server_windows_install_dir }}\\exporter\\llama_exporter.py"
__exporter_venv_path: "{{ llama_server_windows_install_dir }}\\exporter\\.venv"
__exporter_venv_python: "{{ llama_server_windows_install_dir }}\\exporter\\.venv\\Scripts\\python.exe"
__exporter_env: "LLAMA_EXPORTER_PORT={{ llama_server_windows_exporter_port | default(9550) }}"
__exporter_interval: "{{ llama_server_windows_exporter_interval | default(15) }}"
__exporter_timeout: "{{ llama_server_windows_exporter_timeout | default(5) }}"
- name: Create exporter directories
ansible.windows.win_file:
path: "{{ item }}"
state: directory
loop:
- "{{ llama_server_windows_install_dir }}\\exporter"
- name: Copy Windows Exporter script
ansible.windows.win_copy:
src: scripts/llama_exporter.py
dest: "{{ __exporter_script }}"
- name: Install Python packages for Windows Exporter
ansible.windows.win_stat:
path: "{{ __exporter_venv_python }}"
register: __exporter_venv_python_check
- name: Create exporter virtualenv
ansible.windows.win_command:
argv:
- "{{ llama_server_windows_python_path }}"
- -m
- venv
- "{{ __exporter_venv_path }}"
changed_when: false
when: not __exporter_venv_python_check.stat.exists
- name: Install Python packages for Windows Exporter
ansible.windows.win_command:
argv:
- "{{ __exporter_venv_python }}"
- -m
- pip
- install
- --upgrade
- pip
- setuptools
changed_when: false
- name: Remove existing exporter service if present
ansible.windows.win_service:
name: llama-exporter
state: absent
register: __exporter_remove_service
failed_when: false
- name: Set exporter targets environment variable if provided
ansible.builtin.set_fact:
__exporter_targets_env: "LLAMA_TARGETS={{ llama_server_windows_exporter_targets | to_json }}"
when: llama_server_windows_exporter_targets | default([]) | length > 0
- name: Install Windows Exporter service via NSSM
ansible.windows.win_command:
argv:
- "{{ __nssm_exe }}"
- install
- llama-exporter
- "{{ __exporter_venv_python }}"
- "{{ __exporter_script }}"
args:
chdir: "{{ __exporter_install_dir }}"
register: __exporter_nssm_install
changed_when: __exporter_nssm_install.rc == 0
- name: Set exporter AppDirectory via NSSM
ansible.windows.win_command:
argv:
- "{{ __nssm_exe }}"
- set
- llama-exporter
- AppDirectory
- "{{ __exporter_install_dir }}"
- name: Configure exporter stdout log via NSSM
ansible.windows.win_command:
argv:
- "{{ __nssm_exe }}"
- set
- llama-exporter
- AppStdout
- "{{ llama_server_windows_install_dir }}\\exporter-service.out.log"
- name: Configure exporter stderr log via NSSM
ansible.windows.win_command:
argv:
- "{{ __nssm_exe }}"
- set
- llama-exporter
- AppStderr
- "{{ llama_server_windows_install_dir }}\\exporter-service.err.log"
- name: Set exporter environment variables
ansible.windows.win_command:
argv:
- "{{ __nssm_exe }}"
- set
- llama-exporter
- AppEnvironmentExtra
- "{{ (__exporter_env ~ (('\n' ~ __exporter_targets_env) if (__exporter_targets_env is defined) else '')) }}"
when:
- __exporter_env | default('') != ''
- name: Set exporter Start mode to AUTO via NSSM
ansible.windows.win_command:
argv:
- "{{ __nssm_exe }}"
- set
- llama-exporter
- Start
- SERVICE_AUTO_START
- name: Ensure Windows Exporter service is running
ansible.windows.win_service:
name: llama-exporter
start_mode: auto
state: restarted
when: not ansible_check_mode | default(false)
- name: Wait for exporter to listen on port
ansible.windows.win_wait_for:
port: "{{ llama_server_windows_exporter_port | default(9550) }}"
delay: 5
timeout: 30
when: not ansible_check_mode | default(false)
- name: Set firewall remote IPs when string provided
ansible.builtin.set_fact:
llama_server_windows_remoteip: "{{ llama_server_windows_sources }}"
when:
- llama_server_windows_sources is defined
- llama_server_windows_sources is string
- name: Set firewall remote IPs when list provided
ansible.builtin.set_fact:
llama_server_windows_remoteip: "{{ llama_server_windows_sources | join(',') }}"
when:
- llama_server_windows_sources is defined
- not (llama_server_windows_sources is string)
- name: Allow exporter port through Windows Firewall
community.windows.win_firewall_rule:
name: Llama Exporter "{{ llama_server_windows_exporter_port | default(9550) }}"
localport: "{{ llama_server_windows_exporter_port | default(9550) }}"
protocol: tcp
direction: in
action: allow
enabled: true
# Provide one or more IPs/CIDRs in llama_server_windows_sources (e.g. ['203.0.113.10'])
remoteip: "{{ llama_server_windows_remoteip | default(omit) }}"
state: present
@@ -0,0 +1,382 @@
---
# Windows-specific setup for llama_server role
- name: Define Windows paths and versions
ansible.builtin.set_fact:
__llama_user: "{{ llama_server_windows_user }}"
__nssm_exe: "{{ llama_server_windows_nssm_dir }}\\nssm.exe"
__llama_release_asset_name: "{{ 'llama-' ~ llama_server_windows_version ~ '-bin-win-' ~ llama_server_windows_backend ~ (llama_server_windows_backend == 'cuda') | ternary('-' ~ llama_server_cuda_version, '') ~ '-x64.zip' }}"
__llama_release_zip_url: "https://github.com/ggml-org/llama.cpp/releases/download/{{ llama_server_windows_version }}/{{ 'llama-' ~ llama_server_windows_version ~ '-bin-win-' ~ llama_server_windows_backend ~ (llama_server_windows_backend == 'cuda') | ternary('-' ~ llama_server_cuda_version, '') ~ '-x64.zip' }}"
__llama_release_zip_path: "{{ (ansible_env.TEMP | default('C:\\Windows\\Temp')) }}\\{{ 'llama-' ~ llama_server_windows_version ~ '-bin-win-' ~ llama_server_windows_backend ~ (llama_server_windows_backend == 'cuda') | ternary('-' ~ llama_server_cuda_version, '') ~ '-x64.zip' }}"
__llama_release_marker: "{{ llama_server_windows_install_dir }}\\.release-{{ 'llama-' ~ llama_server_windows_version ~ '-bin-win-' ~ llama_server_windows_backend ~ (llama_server_windows_backend == 'cuda') | ternary('-' ~ llama_server_cuda_version, '') ~ '-x64.zip' }}"
__nssm_zip_path: "{{ (ansible_env.TEMP | default('C:\\Windows\\Temp')) }}\\nssm.zip"
__llama_sync_script: "{{ llama_server_windows_working_dir }}\\.router\\llama_hf_sync_windows.py"
__llama_models_json: "{{ llama_server_windows_working_dir }}\\.router\\desired-models.json"
__llama_preset_global_json: "{{ llama_server_windows_working_dir }}\\.router\\preset-global.json"
__llama_managed_dir: "{{ llama_server_windows_working_dir }}\\models\\managed"
__llama_links_dir: "{{ llama_server_windows_working_dir }}\\models\\managed-links"
__llama_router_dir: "{{ llama_server_windows_working_dir }}\\.router"
__llama_manifest: "{{ llama_server_windows_working_dir }}\\.router\\manifest.json"
__llama_preset_file: "{{ llama_server_windows_working_dir }}\\.router\\models.ini"
__llama_venv_path: "{{ llama_server_windows_working_dir }}\\.router\\.venv"
__llama_venv_python: "{{ llama_server_windows_working_dir }}\\.router\\.venv\\Scripts\\python.exe"
- name: Validate llama_server_models schema
ansible.builtin.assert:
that:
- llama_server_models is iterable
fail_msg: "llama_server_models must be a list"
- name: Create llama server installation directories
ansible.windows.win_file:
path: "{{ item }}"
state: directory
loop:
- "{{ llama_server_windows_install_dir }}"
- "{{ llama_server_windows_working_dir }}"
- "{{ llama_server_windows_working_dir }}\\models\\managed"
- "{{ llama_server_windows_working_dir }}\\models\\managed-links"
- "{{ llama_server_windows_working_dir }}\\.router"
- name: Check if Python is available
ansible.windows.win_stat:
path: "{{ llama_server_windows_python_path }}"
register: __llama_python_check
- name: Download Python installer
ansible.windows.win_get_url:
url: "https://www.python.org/ftp/python/{{ llama_server_windows_python_version }}/python-{{ llama_server_windows_python_version }}-amd64.exe"
dest: "{{ (ansible_env.TEMP | default('C:\\Windows\\Temp')) }}\\python-{{ llama_server_windows_python_version }}-amd64.exe"
force: false
when: not __llama_python_check.stat.exists
- name: Install Python if not available
ansible.windows.win_package:
path: "{{ (ansible_env.TEMP | default('C:\\Windows\\Temp')) }}\\python-{{ llama_server_windows_python_version }}-amd64.exe"
arguments: /quiet InstallAllUsers=1 PrependPath=0 Include_test=0
state: present
when: not __llama_python_check.stat.exists
- name: Check Microsoft VC++ runtime availability
ansible.windows.win_stat:
path: "C:\\Windows\\System32\\vcruntime140.dll"
register: __llama_vcredist_check
- name: Download Microsoft VC++ redistributable
ansible.windows.win_get_url:
url: "{{ llama_server_windows_vcredist_url }}"
dest: "{{ (ansible_env.TEMP | default('C:\\Windows\\Temp')) }}\\vc_redist.x64.exe"
force: false
when: not __llama_vcredist_check.stat.exists
- name: Install Microsoft VC++ redistributable
ansible.windows.win_package:
path: "{{ (ansible_env.TEMP | default('C:\\Windows\\Temp')) }}\\vc_redist.x64.exe"
arguments: /install /quiet /norestart
state: present
when: not __llama_vcredist_check.stat.exists
- name: Download llama.cpp Windows binary
ansible.windows.win_get_url:
url: "{{ __llama_release_zip_url }}"
dest: "{{ __llama_release_zip_path }}"
force: false
- name: Check deployed llama.cpp release marker
ansible.windows.win_stat:
path: "{{ __llama_release_marker }}"
register: __llama_release_marker_stat
- name: Remove stale llama-server binary before release upgrade
ansible.windows.win_file:
path: "{{ llama_server_windows_install_dir }}\\{{ llama_server_windows_bin_name }}"
state: absent
when: not __llama_release_marker_stat.stat.exists
- name: Extract llama.cpp binary
community.windows.win_unzip:
src: "{{ __llama_release_zip_path }}"
dest: "{{ llama_server_windows_install_dir }}"
delete_archive: false
when: not __llama_release_marker_stat.stat.exists
- name: Mark deployed llama.cpp release
ansible.windows.win_copy:
content: "{{ __llama_release_asset_name }}"
dest: "{{ __llama_release_marker }}"
when: not __llama_release_marker_stat.stat.exists
- name: Verify llama-server binary is present
ansible.windows.win_stat:
path: "{{ llama_server_windows_install_dir }}\\{{ llama_server_windows_bin_name }}"
register: __llama_bin_stat
- name: Fail if llama-server binary is missing
ansible.builtin.fail:
msg: >-
llama-server binary not found at {{ llama_server_windows_install_dir }}\{{ llama_server_windows_bin_name }}.
Check download/unzip and llama_server_windows_backend settings.
when: not __llama_bin_stat.stat.exists
- name: Check if NSSM is installed
ansible.windows.win_stat:
path: "{{ llama_server_windows_nssm_dir }}\\nssm-{{ llama_server_windows_nssm_version }}\\win64\\nssm.exe"
register: __nssm_stat
- name: Download NSSM if not installed
ansible.windows.win_get_url:
url: "{{ llama_server_windows_nssm_url }}"
dest: "{{ __nssm_zip_path }}"
force: false
when: not __nssm_stat.stat.exists
- name: Extract NSSM if not installed
community.windows.win_unzip:
src: "{{ __nssm_zip_path }}"
dest: "{{ llama_server_windows_nssm_dir }}"
delete_archive: false
creates: "{{ llama_server_windows_nssm_dir }}\\nssm-{{ llama_server_windows_nssm_version }}\\win64\\nssm.exe"
when: not __nssm_stat.stat.exists
- name: Copy NSSM executable
ansible.windows.win_copy:
src: "{{ llama_server_windows_nssm_dir }}\\nssm-{{ llama_server_windows_nssm_version }}\\win64\\nssm.exe"
dest: "{{ __nssm_exe }}"
remote_src: true
- name: Copy Windows-compatible Hugging Face sync script
ansible.windows.win_copy:
src: scripts/llama_hf_sync_windows.py
dest: "{{ __llama_sync_script }}"
mode: "0750"
- name: Write desired llama server model manifest input
ansible.windows.win_copy:
content: "{{ llama_server_models | to_nice_json }}"
dest: "{{ __llama_models_json }}"
- name: Write global llama server preset options
ansible.windows.win_copy:
content: "{{ llama_server_preset_global | to_nice_json }}"
dest: "{{ __llama_preset_global_json }}"
- name: Check llama sync virtualenv
ansible.windows.win_stat:
path: "{{ __llama_venv_python }}"
register: __llama_venv_python_check
- name: Create llama sync virtualenv
ansible.windows.win_command:
argv:
- "{{ llama_server_windows_python_path }}"
- -m
- venv
- "{{ __llama_venv_path }}"
changed_when: false
when: not __llama_venv_python_check.stat.exists
- name: Upgrade pip tooling in llama sync virtualenv
ansible.windows.win_command:
argv:
- "{{ __llama_venv_python }}"
- -m
- pip
- install
- --upgrade
- pip
- setuptools
- packaging
changed_when: false
- name: Install Hugging Face dependency in llama sync virtualenv
ansible.windows.win_command:
argv:
- "{{ __llama_venv_python }}"
- -m
- pip
- install
- "huggingface_hub>=0.32.0"
changed_when: false
- name: Run llama server model sync from Hugging Face
ansible.windows.win_command:
cmd: >-
"{{ __llama_venv_python }}" "{{ __llama_sync_script }}"
--models-file "{{ __llama_models_json }}"
--managed-dir "{{ __llama_managed_dir }}"
--links-dir "{{ __llama_links_dir }}"
--manifest-file "{{ __llama_manifest }}"
--preset-file "{{ __llama_preset_file }}"
--preset-global-file "{{ __llama_preset_global_json }}"
--container-links-dir "{{ __llama_links_dir | replace('\\', '/') }}"
{% if llama_server_models_prune_enabled | default(true) %}--prune{% endif %}
{% if llama_server_models_dry_run | default(false) %}--dry-run{% endif %}
register: __llama_sync_output__
changed_when: false
environment:
HF_TOKEN: "{{ lookup('env', 'HF_TOKEN') | default('', true) }}"
- name: Parse llama server sync output
ansible.builtin.set_fact:
__llama_sync_result__: "{{ __llama_sync_output__.stdout | from_json }}"
- name: Stat llama router preset file (when not in dry-run mode)
ansible.windows.win_stat:
path: "{{ __llama_preset_file }}"
register: __llama_preset_stat__
when: not (llama_server_models_dry_run | default(false))
- name: Ensure router preset file exists when not in dry-run mode
ansible.builtin.assert:
that:
- __llama_preset_stat__.stat.exists
fail_msg: "llama router preset file was not created"
when: not (llama_server_models_dry_run | default(false))
- name: Remove existing llama-server service if present
ansible.windows.win_service:
name: llama-server
state: absent
failed_when: false
- name: Install llama-server service via NSSM
ansible.windows.win_command:
argv: >-
{{
[
__nssm_exe,
'install',
'llama-server',
(llama_server_windows_install_dir ~ '\\' ~ llama_server_windows_bin_name),
'--port',
(llama_server_port | default(11434) | string),
'--host',
(llama_server_host | default('127.0.0.1') | string),
'--metrics',
'--models-preset',
(__llama_preset_file | string),
'--models-max',
(llama_server_models_max | string)
]
+ (llama_server_argv_extra | default([]))
}}
args:
chdir: "{{ llama_server_windows_install_dir }}"
register: __llama_nssm_install
changed_when: __llama_nssm_install.rc == 0
- name: Set llama-server AppDirectory via NSSM
ansible.windows.win_command:
argv:
- "{{ __nssm_exe }}"
- set
- llama-server
- AppDirectory
- "{{ llama_server_windows_install_dir }}"
- name: Configure llama-server stdout log via NSSM
ansible.windows.win_command:
argv:
- "{{ __nssm_exe }}"
- set
- llama-server
- AppStdout
- "{{ llama_server_windows_install_dir }}\\llama-server.out.log"
- name: Configure llama-server stderr log via NSSM
ansible.windows.win_command:
argv:
- "{{ __nssm_exe }}"
- set
- llama-server
- AppStderr
- "{{ llama_server_windows_install_dir }}\\llama-server.err.log"
- name: Set llama-server Start mode to AUTO via NSSM
ansible.windows.win_command:
argv:
- "{{ __nssm_exe }}"
- set
- llama-server
- Start
- SERVICE_AUTO_START
- name: Ensure llama-server service is running
ansible.windows.win_service:
name: llama-server
start_mode: auto
state: restarted
when: not ansible_check_mode | default(false)
- name: Wait for llama-server to listen on port
ansible.windows.win_wait_for:
port: "{{ llama_server_port | default(11434) }}"
delay: 5
timeout: 60
when: not ansible_check_mode | default(false)
- name: Query llama router model list
ansible.windows.win_uri:
url: "http://127.0.0.1:{{ llama_server_port | default(11434) }}/models?reload=1"
method: GET
status_code: 200
return_content: true
register: __llama_router_models__
changed_when: false
when:
- llama_server_models_sync_enabled | default(true)
- not (llama_server_models_dry_run | default(false))
- name: Parse llama router model list payload
ansible.builtin.set_fact:
__llama_router_models_payload__: "{{ __llama_router_models__.content | from_json }}"
when:
- llama_server_models_sync_enabled | default(true)
- not (llama_server_models_dry_run | default(false))
- name: Validate expected model aliases are available in router
ansible.builtin.assert:
that:
- item.name in (__llama_router_models_payload__.data | map(attribute='id') | list)
fail_msg: "Model name '{{ item.name }}' not present in llama router /models output"
loop: "{{ __llama_sync_result__.models | default([]) }}"
loop_control:
label: "{{ item.name }}"
when:
- llama_server_models_sync_enabled | default(true)
- not (llama_server_models_dry_run | default(false))
- name: Set firewall remote IPs when string provided
ansible.builtin.set_fact:
llama_server_windows_remoteip: "{{ llama_server_windows_sources }}"
when:
- llama_server_windows_sources is defined
- llama_server_windows_sources is string
- name: Set firewall remote IPs when list provided
ansible.builtin.set_fact:
llama_server_windows_remoteip: "{{ llama_server_windows_sources | join(',') }}"
when:
- llama_server_windows_sources is defined
- not (llama_server_windows_sources is string)
- name: Allow incoming traffic to llama-server port through Windows Firewall
community.windows.win_firewall_rule:
name: Llama Server "{{ llama_server_port | default(11434) }}"
localport: "{{ llama_server_port | default(11434) }}"
protocol: tcp
direction: in
action: allow
enabled: true
# Provide one or more IPs/CIDRs in llama_server_windows_sources (e.g. ['203.0.113.10'])
remoteip: "{{ llama_server_windows_remoteip | default(omit) }}"
state: present
# --- Windows Exporter tasks ---
- name: Run Windows Exporter setup
when: llama_server_windows_exporter_enabled | default(true) | bool
ansible.builtin.include_tasks:
file: llama_exporter.yml
tags:
- roles::llama_server::llama_exporter