From 5504dd7b2e9c7807d1b4e3089651912afdd88c4e Mon Sep 17 00:00:00 2001 From: juvdiaz Date: Sat, 27 Jun 2026 23:55:21 -0600 Subject: [PATCH] Manage Ollama setup for homelab host --- README.md | 9 +++ README.md.tmpl | 9 +++ bootstrap/host/README.md | 12 ++++ bootstrap/host/group_vars/debian_pc.yml | 6 ++ bootstrap/host/playbook.yml | 84 +++++++++++++++++++++++++ homelab.yml | 2 + jeannie | 54 ++++++++++++++-- 7 files changed, 172 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6bd5345..96aabca 100644 --- a/README.md +++ b/README.md @@ -853,6 +853,15 @@ other nodes can reach `OLLAMA_HOST=http://192.168.100.73:11434`: ./jeannie website-ollama-listen ``` +The Debian host bootstrap also manages Ollama when `debian_pc_install_ollama` +is true: it installs the service, stores models under `/data/ollama/models`, +binds the API to the configured LAN listener, and pulls the lightweight +`ai_gateway.model`. On an existing host, apply just this setup with: + +```bash +./jeannie ollama-setup +``` + `jeannie` also has a backstage local AI helper for doctor commands. It is not a separate CLI action; when `ai_gateway.enabled` is true in `homelab.yml`, doctor commands try the configured Ollama model and silently diff --git a/README.md.tmpl b/README.md.tmpl index e9a574d..8eca15e 100644 --- a/README.md.tmpl +++ b/README.md.tmpl @@ -853,6 +853,15 @@ other nodes can reach `OLLAMA_HOST=http://192.168.100.73:11434`: ./{{ main_script }} website-ollama-listen ``` +The Debian host bootstrap also manages Ollama when `debian_pc_install_ollama` +is true: it installs the service, stores models under `/data/ollama/models`, +binds the API to the configured LAN listener, and pulls the lightweight +`ai_gateway.model`. On an existing host, apply just this setup with: + +```bash +./{{ main_script }} ollama-setup +``` + `{{ main_script }}` also has a backstage local AI helper for doctor commands. It is not a separate CLI action; when `ai_gateway.enabled` is true in `homelab.yml`, doctor commands try the configured Ollama model and silently diff --git a/bootstrap/host/README.md b/bootstrap/host/README.md index e749eba..f4bd99c 100644 --- a/bootstrap/host/README.md +++ b/bootstrap/host/README.md @@ -31,6 +31,8 @@ The playbook: - installs user-level pipx CLI tools such as `yt-dlp`, plus runtime helpers such as `ffmpeg` and `nodejs` - installs `age` and `sops` for encrypted repo-managed secrets +- installs and configures Ollama for homelab-local AI helpers when + `debian_pc_install_ollama` is true - installs Zoom from the upstream `.deb` when requested on amd64 - adds `jv` to expected local groups - enables core services such as Docker, containerd, kubelet, ssh, cron, and @@ -48,3 +50,13 @@ After the playbook finishes, initialize the Debian host's SOPS identity with: ./jeannie secrets-init ./jeannie secrets-check ``` + +Ollama is configured to listen on the LAN and keep models under +`/data/ollama/models`. The default lightweight model for `jeannie` diagnostics is +pulled during bootstrap. To apply only the Ollama setup on an existing Debian +host, run: + +```bash +./jeannie ollama-setup +./jeannie ai-check +``` diff --git a/bootstrap/host/group_vars/debian_pc.yml b/bootstrap/host/group_vars/debian_pc.yml index 4c1b3a8..48ee531 100644 --- a/bootstrap/host/group_vars/debian_pc.yml +++ b/bootstrap/host/group_vars/debian_pc.yml @@ -3,10 +3,16 @@ debian_pc_user: jv debian_pc_user_home: "/home/{{ debian_pc_user }}" debian_pc_kubernetes_minor_version: v1.36 debian_pc_enable_passwordless_sudo: true +debian_pc_install_ollama: true +debian_pc_ollama_install_url: https://ollama.com/install.sh +debian_pc_ollama_host: 0.0.0.0:11434 +debian_pc_ollama_models_dir: /data/ollama/models +debian_pc_ollama_default_model: qwen2.5:0.5b debian_pc_persistent_directories: - /data/openebs/local/registry - /data/docker + - /data/ollama/models - "/home/{{ debian_pc_user }}/git-server" - "/home/{{ debian_pc_user }}/backups/gitea" - "/home/{{ debian_pc_user }}/backups/gitea-restore-drills" diff --git a/bootstrap/host/playbook.yml b/bootstrap/host/playbook.yml index 5020916..5355688 100644 --- a/bootstrap/host/playbook.yml +++ b/bootstrap/host/playbook.yml @@ -229,6 +229,54 @@ mode: "{{ '0755' if item.startswith(debian_pc_user_home) else '0775' }}" loop: "{{ debian_pc_persistent_directories }}" + - name: Check whether Ollama is already installed + ansible.builtin.shell: + cmd: command -v ollama >/dev/null 2>&1 + register: debian_pc_ollama_installed + changed_when: false + failed_when: false + when: debian_pc_install_ollama | bool + + - name: Install Ollama from official Linux installer + ansible.builtin.shell: | + set -euo pipefail + tmp_installer="$(mktemp)" + trap 'rm -f "${tmp_installer}"' EXIT + curl -fsSL "{{ debian_pc_ollama_install_url }}" -o "${tmp_installer}" + sh "${tmp_installer}" + args: + executable: /bin/bash + when: + - debian_pc_install_ollama | bool + - debian_pc_ollama_installed.rc | default(1) != 0 + + - name: Ensure Ollama model storage exists on data disk + ansible.builtin.file: + path: "{{ debian_pc_ollama_models_dir }}" + state: directory + owner: ollama + group: ollama + mode: "0775" + when: debian_pc_install_ollama | bool + + - name: Ensure Ollama systemd override directory exists + ansible.builtin.file: + path: /etc/systemd/system/ollama.service.d + state: directory + mode: "0755" + when: debian_pc_install_ollama | bool + + - name: Configure Ollama for homelab LAN access and data storage + ansible.builtin.copy: + dest: /etc/systemd/system/ollama.service.d/homelab.conf + mode: "0644" + content: | + [Service] + Environment="OLLAMA_HOST={{ debian_pc_ollama_host }}" + Environment="OLLAMA_MODELS={{ debian_pc_ollama_models_dir }}" + register: debian_pc_ollama_override + when: debian_pc_install_ollama | bool + - name: Enable expected services when installed ansible.builtin.systemd: name: "{{ item }}" @@ -237,6 +285,42 @@ loop: "{{ debian_pc_enable_services }}" failed_when: false + - name: Enable and start Ollama + ansible.builtin.systemd: + name: ollama + enabled: true + state: "{{ 'restarted' if debian_pc_ollama_override.changed | default(false) else 'started' }}" + daemon_reload: true + when: debian_pc_install_ollama | bool + + - name: Wait for Ollama API + ansible.builtin.uri: + url: http://127.0.0.1:11434/api/tags + method: GET + status_code: 200 + register: debian_pc_ollama_api + retries: 12 + delay: 5 + until: debian_pc_ollama_api.status == 200 + when: debian_pc_install_ollama | bool + + - name: Pull default Ollama model for backstage homelab diagnostics + ansible.builtin.shell: | + set -euo pipefail + if ollama list | awk -v model="{{ debian_pc_ollama_default_model }}" 'NR > 1 && ($1 == model || $1 == model ":latest") { found = 1 } END { exit found ? 0 : 1 }'; then + echo present + exit 0 + fi + ollama pull "{{ debian_pc_ollama_default_model }}" + echo pulled + args: + executable: /bin/bash + register: debian_pc_ollama_pull_result + changed_when: "'pulled' in debian_pc_ollama_pull_result.stdout" + environment: + OLLAMA_HOST: http://127.0.0.1:11434 + when: debian_pc_install_ollama | bool + - name: Check whether config archive is present ansible.builtin.stat: path: "{{ debian_pc_config_archive }}" diff --git a/homelab.yml b/homelab.yml index 7ab93b2..912241c 100644 --- a/homelab.yml +++ b/homelab.yml @@ -86,6 +86,8 @@ services: ollama: host: debian url: http://192.168.100.73:11434 + bind_address: 0.0.0.0:11434 + models_dir: /data/ollama/models rpi_dns: host: rpi4 pihole_dns_port: 53 diff --git a/jeannie b/jeannie index 1992da6..93fb389 100755 --- a/jeannie +++ b/jeannie @@ -58,6 +58,8 @@ mapping = { "services.gitea.root_url": "LAB_GITEA_ROOT_URL", "services.gitea.ssh_remote": "LAB_GITOPS_REPO_URL", "services.local_registry.endpoint": "LAB_REGISTRY_ENDPOINT", + "services.ollama.bind_address": "LAB_OLLAMA_BIND_ADDRESS", + "services.ollama.models_dir": "LAB_OLLAMA_MODELS_DIR", "services.rpi_dns.pihole_web_port": "PIHOLE_WEB_PORT", "services.rpi_dns.uptime_kuma_port": "UPTIME_KUMA_PORT", "ai_gateway.provider": "LAB_AI_GATEWAY_PROVIDER", @@ -3389,9 +3391,10 @@ website_translation_model() { } website_ollama_listen() { - local bind_address="${WEBSITE_OLLAMA_BIND_ADDRESS:-0.0.0.0:11434}" + local bind_address="${WEBSITE_OLLAMA_BIND_ADDRESS:-${LAB_OLLAMA_BIND_ADDRESS:-0.0.0.0:11434}}" + local models_dir="${LAB_OLLAMA_MODELS_DIR:-/data/ollama/models}" local dropin_dir="/etc/systemd/system/ollama.service.d" - local dropin_file="${dropin_dir}/homelab-listen.conf" + local dropin_file="${dropin_dir}/homelab.conf" require_debian_server "website-ollama-listen" @@ -3402,7 +3405,11 @@ website_ollama_listen() { echo "Configuring ollama.service to listen on ${bind_address}..." sudo mkdir -p "${dropin_dir}" - printf '[Service]\nEnvironment="OLLAMA_HOST=%s"\n' "${bind_address}" | + sudo mkdir -p "${models_dir}" + if id ollama >/dev/null 2>&1; then + sudo chown ollama:ollama "${models_dir}" + fi + printf '[Service]\nEnvironment="OLLAMA_HOST=%s"\nEnvironment="OLLAMA_MODELS=%s"\n' "${bind_address}" "${models_dir}" | sudo tee "${dropin_file}" >/dev/null sudo systemctl daemon-reload sudo systemctl restart ollama.service @@ -3413,6 +3420,42 @@ website_ollama_listen() { echo "Ollama API is reachable on the Debian host." } +ollama_setup() { + local bind_address="${LAB_OLLAMA_BIND_ADDRESS:-0.0.0.0:11434}" + local models_dir="${LAB_OLLAMA_MODELS_DIR:-/data/ollama/models}" + local model="${LAB_AI_GATEWAY_MODEL:-qwen2.5:0.5b}" + local installer_url="${LAB_OLLAMA_INSTALL_URL:-https://ollama.com/install.sh}" + local tmp_installer + + require_debian_server "ollama-setup" + + if ! command -v curl >/dev/null 2>&1; then + echo "curl is required to install Ollama." >&2 + exit 1 + fi + + if ! command -v ollama >/dev/null 2>&1; then + echo "Installing Ollama from ${installer_url}..." + tmp_installer="$(mktemp)" + curl -fsSL "${installer_url}" -o "${tmp_installer}" + sudo sh "${tmp_installer}" + rm -f "${tmp_installer}" + else + echo "Ollama is already installed: $(command -v ollama)" + fi + + website_ollama_listen + + echo "Ensuring Ollama model ${model} is available..." + if ollama list | awk -v model="${model}" 'NR > 1 && ($1 == model || $1 == model ":latest") { found = 1 } END { exit found ? 0 : 1 }'; then + echo "Ollama model ${model} is already present." + else + OLLAMA_HOST=http://127.0.0.1:11434 ollama pull "${model}" + fi + + echo "Ollama homelab setup is ready." +} + apps() { local buildx_builder_ready=false local demos_image_built=false @@ -4728,6 +4771,9 @@ case "${1:-}" in website-ollama-listen) website_ollama_listen ;; + ollama-setup) + ollama_setup + ;; deploy-gitea) deploy_gitea ;; @@ -4789,7 +4835,7 @@ case "${1:-}" in nuke ;; *) - echo "Usage: $0 {up|rebuild-cluster|stop-cluster|start-cluster|status|apps|website-translation-model|website-ollama-listen|deploy-gitea|rpi-services|bootstrap-gitea-repo|backup-gitea|drill-gitea-restore|install-gitea-runner|move-prometheus-stack-workers|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|secrets-init|secrets-check|tailnet-policy-check|ai-index|ai-check|openwrt|nuke}" + echo "Usage: $0 {up|rebuild-cluster|stop-cluster|start-cluster|status|apps|website-translation-model|website-ollama-listen|ollama-setup|deploy-gitea|rpi-services|bootstrap-gitea-repo|backup-gitea|drill-gitea-restore|install-gitea-runner|move-prometheus-stack-workers|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|secrets-init|secrets-check|tailnet-policy-check|ai-index|ai-check|openwrt|nuke}" exit 1 ;; esac