playbooks/roles/vhosts/litellm/tasks/main.yml

297 lines
9.1 KiB
YAML

---
# Provision the litellm database and user BEFORE litellm starts.
# Only runs when litellm_database_host is set.
- name: Install LiteLLM prerequisites (Linux)
ansible.builtin.package:
name:
- python3
- python3-pip
- python3-venv
- python3-psycopg2
state: present
when: ansible_os_family != 'Darwin'
- name: Install LiteLLM prerequisites (macOS)
# Use brew from PATH (Apple Silicon prefix first) instead of the
# community.general.homebrew module, which can select a stale Intel Homebrew
# at /usr/local that crashes on newer macOS versions. See postgres macos.yml.
ansible.builtin.command: brew install python@3.13
environment:
PATH: "/opt/homebrew/bin:/usr/local/bin:{{ ansible_env.PATH }}"
HOMEBREW_NO_AUTO_UPDATE: "1"
register: litellm_brew_python
changed_when: >-
'already installed' not in (litellm_brew_python.stderr | default(''))
and 'already installed' not in (litellm_brew_python.stdout | default(''))
failed_when: litellm_brew_python.rc != 0
when: ansible_os_family == 'Darwin'
- name: Materialize persisted LiteLLM secrets
ansible.builtin.assert:
that:
- litellm_salt_key | length > 0
- litellm_database_password | length > 0
fail_msg: "LiteLLM persisted secrets must not be empty."
no_log: true
- name: Inspect persisted LiteLLM secrets
ansible.builtin.stat:
path: "{{ item }}"
loop:
- "{{ litellm_salt_key_file }}"
- "{{ litellm_database_password_file }}"
register: litellm_secret_files
no_log: true
- name: Protect persisted LiteLLM secrets
ansible.builtin.file:
path: "{{ item.item }}"
owner: root
group: root
mode: "0600"
state: file
loop: "{{ litellm_secret_files.results }}"
when: item.stat.exists
no_log: true
- name: Ensure litellm service group exists
ansible.builtin.group:
name: "{{ litellm_service_group }}"
state: present
when:
- litellm_service_group != 'root'
- ansible_os_family != 'Darwin'
- name: Ensure litellm service user exists
ansible.builtin.user:
name: "{{ litellm_service_user }}"
group: "{{ litellm_service_group }}"
shell: /bin/bash
create_home: true
state: present
when:
- litellm_service_user != 'root'
- ansible_os_family != 'Darwin'
- name: Ensure litellm config directory exists
ansible.builtin.file:
path: "{{ litellm_config_dir }}"
state: directory
owner: "{{ litellm_service_user if ansible_os_family == 'Darwin' else 'root' }}"
group: "{{ litellm_service_group if ansible_os_family == 'Darwin' else 'root' }}"
mode: "0755"
- name: Provision LiteLLM Database
ansible.builtin.import_tasks: provision-database.yml
when: litellm_database_host | trim | length > 0
- name: Create litellm config template
ansible.builtin.template:
src: config.yaml.j2
dest: "{{ litellm_config_file }}"
owner: "{{ litellm_service_user }}"
group: "{{ litellm_service_group }}"
mode: "0644"
notify: Restart litellm
- name: Create litellm environment file template
ansible.builtin.template:
src: litellm.env.j2
dest: "{{ litellm_env_file }}"
owner: "{{ litellm_service_user if ansible_os_family == 'Darwin' else 'root' }}"
group: "{{ litellm_service_group if ansible_os_family == 'Darwin' else 'root' }}"
mode: "0600"
notify: Restart litellm
- name: Ensure LiteLLM and DB dependencies are installed
ansible.builtin.pip:
name:
- "{{ litellm_package_spec }}"
- "prisma"
- "psycopg2-binary"
extra_args: --break-system-packages
executable: "{{ litellm_pip_executable if litellm_pip_executable | length > 0 else omit }}"
state: present
become: true
become_user: "{{ litellm_service_user }}"
- name: Resolve LiteLLM Python site-packages path
ansible.builtin.shell: |
{{ litellm_python_executable | quote }} - <<'PY'
import glob
import os
paths = glob.glob(os.path.expanduser("~/.local/lib/python*/site-packages/litellm/proxy"))
if not paths:
raise SystemExit("litellm proxy package path not found")
print(sorted(paths)[-1])
PY
register: litellm_proxy_package_path
changed_when: false
become: true
become_user: "{{ litellm_service_user }}"
- name: Set LiteLLM Python paths
ansible.builtin.set_fact:
litellm_proxy_dir: "{{ litellm_proxy_package_path.stdout | trim }}"
litellm_python_site_packages: "{{ (litellm_proxy_package_path.stdout | trim) | dirname | dirname }}"
- name: Generate Prisma Python Client
ansible.builtin.shell: |
export PATH={{ litellm_service_home }}/.local/bin:$PATH
prisma generate
args:
chdir: "{{ litellm_proxy_dir }}"
become: true
become_user: "{{ litellm_service_user }}"
changed_when: false
- name: Create systemd service template
ansible.builtin.template:
src: litellm-proxy.service.j2
dest: "{{ litellm_systemd_unit_path }}"
owner: root
group: root
mode: "0644"
notify: Restart litellm
when: ansible_os_family != 'Darwin'
- name: Reload systemd after unit changes
ansible.builtin.systemd:
daemon_reload: true
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Ensure Caddy fragment directory exists
ansible.builtin.file:
path: "{{ litellm_caddy_conf_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Generate bcrypt hash for LiteLLM UI basic auth
ansible.builtin.command: caddy hash-password --plaintext "{{ litellm_master_key }}"
register: caddy_hash_result
changed_when: false
no_log: true
when: litellm_enable_basic_auth
- name: Set litellm_basic_auth_password_hash fact
ansible.builtin.set_fact:
litellm_basic_auth_password_hash: "{{ caddy_hash_result.stdout }}"
no_log: true
when: litellm_enable_basic_auth
- name: Ensure Caddy imports managed fragments
ansible.builtin.lineinfile:
path: "{{ litellm_caddyfile_path }}"
regexp: "^import {{ litellm_caddy_conf_dir }}/\\*\\.caddy"
line: "import {{ litellm_caddy_conf_dir }}/*.caddy"
insertafter: EOF
create: true
owner: root
group: root
mode: "0644"
state: present
notify: Reload caddy
when: litellm_caddy_config_enabled | bool
- name: Create LiteLLM API Caddy fragment
ansible.builtin.template:
src: api.svc.plus.caddy.j2
dest: "{{ litellm_api_caddy_fragment_path }}"
owner: root
group: root
mode: "0644"
notify: Reload caddy
when: litellm_caddy_config_enabled | bool
- name: Create LiteLLM UI Caddy fragment
ansible.builtin.template:
src: litellm.svc.plus.caddy.j2
dest: "{{ litellm_ui_caddy_fragment_path }}"
owner: root
group: root
mode: "0644"
notify: Reload caddy
when: litellm_caddy_config_enabled | bool
- name: Remove LiteLLM public Caddy fragments when public access is disabled
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- "{{ litellm_api_caddy_fragment_path }}"
- "{{ litellm_ui_caddy_fragment_path }}"
notify: Reload caddy
when: not (litellm_caddy_config_enabled | bool)
- name: Ensure litellm service is enabled and running
ansible.builtin.systemd:
name: "{{ litellm_service_name }}"
enabled: true
state: started
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Import macOS specific service tasks
ansible.builtin.import_tasks: macos.yml
when: ansible_os_family == 'Darwin'
- name: Ensure Caddy is enabled and running
ansible.builtin.systemd:
name: caddy
enabled: true
state: started
when:
- not ansible_check_mode
- litellm_caddy_config_enabled | bool
- ansible_os_family != 'Darwin'
- name: Apply litellm and Caddy changes before validation
ansible.builtin.meta: flush_handlers
- name: Validate LiteLLM service
ansible.builtin.uri:
url: "http://{{ litellm_listen_host }}:{{ litellm_listen_port }}/health"
method: GET
status_code: [200, 401]
register: litellm_health_check
failed_when: false
changed_when: false
when: not ansible_check_mode
- name: Display LiteLLM health status
ansible.builtin.debug:
msg: "LiteLLM health check: {{ litellm_health_check.status | default('unreachable') }}"
- name: Create litellm environment file example
ansible.builtin.template:
src: litellm.env.example.j2
dest: "{{ litellm_config_dir }}/litellm.env.example"
owner: "{{ litellm_service_user }}"
group: "{{ litellm_service_group }}"
mode: "0600"
- name: Register mainstream models to LiteLLM
ansible.builtin.script: files/register_mainstream_models.sh
environment:
LITELLM_URL: "http://{{ litellm_listen_host }}:{{ litellm_listen_port }}"
LITELLM_TOKEN: "{{ litellm_master_key }}"
DEEPSEEK_API_KEY: "{{ litellm_deepseek_api_key }}"
OPENAI_API_KEY: "{{ litellm_openai_api_key }}"
NVIDIA_API_KEY: "{{ litellm_nvidia_api_key }}"
GEMINI_API_KEY: "{{ litellm_gemini_api_key }}"
ANTHROPIC_API_KEY: "{{ litellm_anthropic_api_key }}"
OLLAMA_API_KEY: "{{ litellm_ollama_api_key }}"
when: >
not ansible_check_mode and
((litellm_deepseek_api_key | length > 0) or
(litellm_openai_api_key | length > 0) or
(litellm_nvidia_api_key | length > 0) or
(litellm_gemini_api_key | length > 0) or
(litellm_anthropic_api_key | length > 0) or
(litellm_ollama_api_key | length > 0))
changed_when: false