297 lines
9.1 KiB
YAML
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
|