--- - 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/llamacpp - 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: "llamacpp" image: "{{ llama_server_image }}:{{ llama_server_tag }}" state: started device: "{{ llama_server_devices | default(omit) }}" env: "{{ llama_server_env | default(omit) }}" ports: "{{ llama_server_bind }}:{{ 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-llamacpp.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-llamacpp" 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/linux/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