diff --git a/infrastructure/playbooks/windows_ollama_models.yml b/infrastructure/playbooks/windows_ollama_models.yml new file mode 100644 index 0000000..669d12e --- /dev/null +++ b/infrastructure/playbooks/windows_ollama_models.yml @@ -0,0 +1,162 @@ +--- +# windows_ollama_models.yml — Manage local Ollama model variants on Windows GPU hosts. +# +# This is the small, Ansible-like control surface for the two Windows NVIDIA +# machines that run Ollama for Prole embedding workloads. +# +# Usage: +# cd infrastructure +# ansible-playbook playbooks/windows_ollama_models.yml --ask-pass +# +# Single host: +# ansible-playbook playbooks/windows_ollama_models.yml --limit morgoth.prole.org --ask-pass +# +# Dry-run intent only: +# ansible-playbook playbooks/windows_ollama_models.yml --check --diff --ask-pass +# +# Inventory target: +# [workstations_windows] +# morgoth.prole.org +# fairyland.prole.org +# +# What this deploys: +# ollama create mxbai-embed-large-2k FROM mxbai-embed-large PARAMETER num_ctx 2048 +# +# The desired Modelfile is written to C:\ProgramData\Prole\Ollama\Models and a +# SHA-256 marker is recorded after successful creation. Re-running the playbook +# is safe: it recreates the model only when the target model is missing or the +# managed Modelfile content changes. + +- name: Configure Prole Ollama models on Windows GPU hosts + hosts: workstations_windows + gather_facts: true + + vars: + prole_ollama_models_dir: 'C:\ProgramData\Prole\Ollama\Models' + prole_ollama_models: + - name: mxbai-embed-large-2k + base: mxbai-embed-large + parameters: + num_ctx: 2048 + + # Override per-host if WinRM cannot see ollama.exe in PATH. + # Common installs include: + # C:\Program Files\Ollama\ollama.exe + # C:\Users\\AppData\Local\Programs\Ollama\ollama.exe + ollama_windows_exe: '' + + tasks: + - name: Resolve ollama.exe path + ansible.windows.win_shell: | + $ErrorActionPreference = 'Stop' + $candidates = @() + + if ('{{ ollama_windows_exe }}') { + $candidates += '{{ ollama_windows_exe }}' + } + + $cmd = Get-Command ollama.exe -ErrorAction SilentlyContinue + if ($cmd) { + $candidates += $cmd.Source + } + + $candidates += @( + 'C:\Program Files\Ollama\ollama.exe', + 'C:\Users\{{ ansible_user }}\AppData\Local\Programs\Ollama\ollama.exe' + ) + + foreach ($candidate in $candidates | Select-Object -Unique) { + if ($candidate -and (Test-Path -LiteralPath $candidate)) { + Write-Output $candidate + exit 0 + } + } + + throw 'ollama.exe was not found. Set ollama_windows_exe for this host or install Ollama first.' + args: + executable: powershell.exe + register: ollama_exe_result + changed_when: false + + - name: Set resolved Ollama executable fact + ansible.builtin.set_fact: + ollama_resolved_exe: "{{ ollama_exe_result.stdout_lines[0] }}" + + - name: Ensure Prole Ollama model state directory exists + ansible.windows.win_file: + path: "{{ prole_ollama_models_dir }}" + state: directory + + - name: Show planned Ollama model configuration in check mode + ansible.builtin.debug: + msg: >- + Would ensure {{ item.name }} exists from {{ item.base }} with num_ctx={{ item.parameters.num_ctx }} + using {{ ollama_resolved_exe }}. + loop: "{{ prole_ollama_models }}" + when: ansible_check_mode + + - name: Create or update managed Ollama model variants + ansible.windows.win_shell: | + $ErrorActionPreference = 'Stop' + + $ollama = '{{ ollama_resolved_exe }}' + $modelName = '{{ item.name }}' + $baseModel = '{{ item.base }}' + $modelDir = '{{ prole_ollama_models_dir }}' + $modelfilePath = Join-Path $modelDir "$modelName.Modelfile" + $markerPath = Join-Path $modelDir "$modelName.Modelfile.sha256" + + $modelfile = @" + FROM {{ item.base }} + PARAMETER num_ctx {{ item.parameters.num_ctx }} + "@.TrimStart() + + Set-Content -LiteralPath $modelfilePath -Value $modelfile -Encoding ascii -NoNewline + $desiredHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $modelfilePath).Hash + $recordedHash = if (Test-Path -LiteralPath $markerPath) { + (Get-Content -LiteralPath $markerPath -Raw).Trim() + } else { + '' + } + + & $ollama show $modelName *> $null + $modelExists = ($LASTEXITCODE -eq 0) + + if ($modelExists -and ($recordedHash -eq $desiredHash)) { + Write-Output "unchanged: $modelName already matches managed Modelfile" + exit 0 + } + + & $ollama pull $baseModel + if ($LASTEXITCODE -ne 0) { + throw "ollama pull failed for $baseModel" + } + + & $ollama create $modelName -f $modelfilePath + if ($LASTEXITCODE -ne 0) { + throw "ollama create failed for $modelName" + } + + Set-Content -LiteralPath $markerPath -Value $desiredHash -Encoding ascii -NoNewline + Write-Output "changed: created or updated $modelName from $baseModel" + args: + executable: powershell.exe + loop: "{{ prole_ollama_models }}" + register: ollama_model_results + changed_when: "'changed:' in ollama_model_results.stdout" + when: not ansible_check_mode + + - name: Verify managed Ollama model variants + ansible.windows.win_shell: | + $ErrorActionPreference = 'Stop' + & '{{ ollama_resolved_exe }}' list + args: + executable: powershell.exe + register: ollama_list_result + changed_when: false + when: not ansible_check_mode + + - name: Show Ollama model list + ansible.builtin.debug: + var: ollama_list_result.stdout_lines + when: not ansible_check_mode