--- # 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