diff --git a/ansible/host_vars/dev-01.alexpires.dev.yml b/ansible/host_vars/dev-01.alexpires.dev.yml index 6b97bb0..c3530f1 100644 --- a/ansible/host_vars/dev-01.alexpires.dev.yml +++ b/ansible/host_vars/dev-01.alexpires.dev.yml @@ -31,8 +31,9 @@ sshd_max_auth_tries: 10 # Firewall ports ufw_enable: true fw_allowed_ports: - - { rule: "allow", port: "8000", proto: "tcp" } - { rule: "allow", port: "22", proto: "tcp" } + - { rule: "allow", port: "8000", proto: "tcp" } + - { rule: "allow", port: "8080", proto: "tcp" } ufw_outgoing_traffic: - 22 # SSH @@ -41,6 +42,7 @@ ufw_outgoing_traffic: - 123 # NTP - 443 # HTTPS - 853 # DNS over TLS + - 11434 # Ollama ufw_default_forward_policy: "ACCEPT" @@ -73,3 +75,7 @@ unattended_reboot_time: "04:30" # Duo Security duo_users: ["*", "!provision"] duo_api_hostname: api-7cda1654.duosecurity.com + +# podman services settings +open_webui_home: "/srv/open-webui" +mcp_home: "/srv/mcp" diff --git a/ansible/host_vars/gpu-01.alexpires.dev.yml b/ansible/host_vars/gpu-01.alexpires.dev.yml index 48ed6a4..4f3ef18 100644 --- a/ansible/host_vars/gpu-01.alexpires.dev.yml +++ b/ansible/host_vars/gpu-01.alexpires.dev.yml @@ -35,7 +35,6 @@ packages_blocklist: [] ufw_enable: true fw_allowed_ports: - { rule: "allow", port: "22", proto: "tcp" } - - { rule: "allow", port: "8080", proto: "tcp" } - { rule: "allow", port: "11434", proto: "tcp" } ufw_outgoing_traffic: @@ -45,7 +44,6 @@ ufw_outgoing_traffic: - 123 # NTP - 443 # HTTPS - 853 # DNS over TLS - - 8000 ufw_default_forward_policy: "ACCEPT" @@ -71,8 +69,6 @@ unattended_reboot_time: "04:30" duo_users: ["*", "!provision"] duo_api_hostname: api-7cda1654.duosecurity.com -# open_web_ui settings -open_webui_home: "/mnt/data/ai" -open_webui_network_name: "ai-network" -open_webui_user: "ai" -open_webui_group: "ai" +# podman services settings +ollama_home: "/mnt/data/ollama" +kokora_home: "/mnt/data/kokoro" diff --git a/ansible/playbook_mcp_servers.yml b/ansible/playbook_mcp_servers.yml deleted file mode 100644 index abe58c6..0000000 --- a/ansible/playbook_mcp_servers.yml +++ /dev/null @@ -1,20 +0,0 @@ -- name: Setup MCP servers - hosts: mcp - vars: - podman_user: "{{ mcp_user | default('mcp') }}" - podman_group: "{{ mcp_group | default('mcp') }}" - podman_user_home: "{{ mcp_home | default('/home/mcp') }}" - pods: - - name: gitea-mcp - build: - dockerfile: "{{ lookup('file', 'dockerfiles/Dockerfile.mcp.gitea') }}" - ports: - - 0.0.0.0:8000:8000/tcp - env: - GITEA_HOST: "https://code.alexpires.me" - GITEA_ACCESS_TOKEN: "{{ lookup('env', 'GITEA_APP_TOKEN') }}" - - tasks: - - name: Setup Pods - ansible.builtin.include_tasks: - file: tasks/podman.yml diff --git a/ansible/playbook_open_webui.yml b/ansible/playbook_open_webui.yml deleted file mode 100644 index 9fdfa2a..0000000 --- a/ansible/playbook_open_webui.yml +++ /dev/null @@ -1,38 +0,0 @@ -- name: Setup open-webui - hosts: openwebui - vars: - podman_user: "{{ open_webui_user | default('openwebui') }}" - podman_group: "{{ open_webui_group | default('openwebui') }}" - podman_network_name: "{{ open_webui_network_name | default('openwebui-network') }}" - podman_user_home: "{{ open_webui_home | default('/home/open-webui') }}" - pods: - - name: ollama - build: - dockerfile: "{{ lookup('file', 'dockerfiles/Dockerfile.ollama') }}" - ports: - - 0.0.0.0:11434:11434/tcp - volumes: - - "{{ podman_user_home }}/ollama:/home/worker/.ollama:Z" - device: - - "nvidia.com/gpu=all" - - name: open-webui - repo: - image: ghcr.io/open-webui/open-webui - tag: main - ports: - - 0.0.0.0:8080:8080/tcp - volumes: - - "{{ podman_user_home }}/open-webui:/app/backend/data:Z" - device: - - "nvidia.com/gpu=all" - env: - OLLAMA_BASE_URL: "http://ollama:11434" - - tasks: - - name: Setup nvidia-cdi-generator - ansible.builtin.include_tasks: - file: tasks/nvidia-cdi-generator.yml - - - name: Setup Pods - ansible.builtin.include_tasks: - file: tasks/podman.yml diff --git a/ansible/playbook_podman_mcp_servers.yml b/ansible/playbook_podman_mcp_servers.yml new file mode 100644 index 0000000..98762a1 --- /dev/null +++ b/ansible/playbook_podman_mcp_servers.yml @@ -0,0 +1,33 @@ +- name: Setup MCP servers + hosts: mcp + vars: + podman_user: "{{ mcp_user | default('mcp') }}" + podman_group: "{{ mcp_group | default('mcp') }}" + podman_user_home: "{{ mcp_home | default('/home/mcp') }}" + mcp_servers: + - name: gitea-mcp + enabled: false + build: + super_user_custom_actions: | + RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + tar \ + && rm -rf /var/lib/apt/lists/* + RUN curl -L https://gitea.com/gitea/gitea-mcp/releases/download/v0.2.0/gitea-mcp_Linux_x86_64.tar.gz \ + | tar -xz -C /usr/local/bin \ + && chmod +x /usr/local/bin/gitea-mcp + worker_custom_actions: | + ENV GITEA_HOST="https://gitea.com" + ENV GITEA_INSECURE="false" + ENV GITEA_ACCESS_TOKEN="" + mcp_server_commands: ["gitea-mcp"] + port: 8000 + env: + GITEA_HOST: "https://code.alexpires.me" + GITEA_ACCESS_TOKEN: "{{ lookup('env', 'GITEA_APP_TOKEN') }}" + + tasks: + - name: Setup Pods + ansible.builtin.include_tasks: + file: tasks/mcp-proxy.yml + loop: "{{ mcp_servers }}" diff --git a/ansible/playbook_podman_ollama.yml b/ansible/playbook_podman_ollama.yml new file mode 100644 index 0000000..e13c913 --- /dev/null +++ b/ansible/playbook_podman_ollama.yml @@ -0,0 +1,27 @@ +- name: Setup ollama + hosts: ollama + vars: + podman_user: "{{ ollama_user | default('ollama') }}" + podman_group: "{{ ollama_group | default('ollama') }}" + podman_user_home: "{{ ollama_home | default('/home/ollama') }}" + podman_user_folders: + - data + pods: + - name: ollama + build: + dockerfile: "{{ lookup('file', 'dockerfiles/Dockerfile.ollama') }}" + ports: + - "0.0.0.0:{{ ollama_port | default(11434) }}:11434/tcp" + volumes: + - "{{ ollama_home }}/data:/home/worker/.ollama:Z" + device: + - "nvidia.com/gpu=all" + + tasks: + - name: Setup nvidia-cdi-generator + ansible.builtin.include_tasks: + file: tasks/nvidia-cdi-generator.yml + + - name: Setup Pods + ansible.builtin.include_tasks: + file: tasks/podman.yml diff --git a/ansible/playbook_podman_open_webui.yml b/ansible/playbook_podman_open_webui.yml new file mode 100644 index 0000000..71cb2a6 --- /dev/null +++ b/ansible/playbook_podman_open_webui.yml @@ -0,0 +1,22 @@ +- name: Setup open-webui + hosts: openwebui + vars: + podman_user: "{{ open_webui_user | default('openwebui') }}" + podman_group: "{{ open_webui_group | default('openwebui') }}" + podman_user_home: "{{ open_webui_home | default('/home/open-webui') }}" + podman_user_folders: + - data + pods: + - name: open-webui + repo: + image: ghcr.io/open-webui/open-webui + tag: main + ports: + - "0.0.0.0:{{ open_webui_port | default(8080) }}:8080/tcp" + volumes: + - "{{ open_webui_home }}/data:/app/backend/data:Z" + + tasks: + - name: Setup Pods + ansible.builtin.include_tasks: + file: tasks/podman.yml diff --git a/ansible/tasks/mcp-proxy.yml b/ansible/tasks/mcp-proxy.yml new file mode 100644 index 0000000..bad9a6c --- /dev/null +++ b/ansible/tasks/mcp-proxy.yml @@ -0,0 +1,15 @@ +- name: Setup pods fact + ansible.builtin.set_fact: + pods: + - name: "{{ item.name }}" + build: + dockerfile: "{{ lookup('template', 'Dockerfile.mcp.j2') }}" + ports: + - 0.0.0.0:{{ item.port | default(8000) }}:8000/tcp + env: "{{ item.env | default({}) }}" + volumes: "{{ item.volumes | default([]) }}" + +- name: Setup Pods + ansible.builtin.include_tasks: + file: podman.yml + when: item.enabled | default(true) diff --git a/ansible/tasks/podman.yml b/ansible/tasks/podman.yml index da55f78..8e611e6 100644 --- a/ansible/tasks/podman.yml +++ b/ansible/tasks/podman.yml @@ -24,13 +24,13 @@ - acl state: present -- name: Create group and user for AI workloads +- name: Create group and user for workloads become: true ansible.builtin.group: name: "{{ podman_group }}" system: true -- name: Create user and group for AI containers +- name: Create user and group for containers become: true ansible.builtin.user: name: "{{ podman_user }}" @@ -39,7 +39,7 @@ home: "{{ podman_user_home }}" group: "{{ podman_group }}" -- name: Disable password login for AI user +- name: Disable password login for user ansible.builtin.command: passwd -d "{{ podman_user }}" become: true changed_when: false @@ -79,12 +79,12 @@ - name: Create folders become: true ansible.builtin.file: - path: "{{ podman_user_home }}/{{ item.name }}" + path: "{{ podman_user_home }}/{{ item }}" state: directory owner: "{{ podman_user }}" group: "{{ podman_group }}" mode: "0750" - loop: "{{ pods }}" + loop: "{{ podman_user_folders }}" - name: Enable sudo access to podman user (temporary) become: true @@ -119,6 +119,7 @@ register: gid - name: Ensure rootless Podman works with overlay driver on Debian-family systems + when: ansible_os_family == 'Debian' become: true vars: subuid_range: "100000:65536" diff --git a/ansible/templates/Dockerfile.mcp.j2 b/ansible/templates/Dockerfile.mcp.j2 new file mode 100644 index 0000000..44775c8 --- /dev/null +++ b/ansible/templates/Dockerfile.mcp.j2 @@ -0,0 +1,17 @@ +FROM python:3.11-slim +ARG UID=1000 +ARG GID=1000 +RUN groupadd --system --gid ${GID} worker && \ + adduser --system --gid ${GID} --uid ${UID} --home /home/worker worker +ENV PATH=/home/worker/.local/bin:$PATH +WORKDIR /home/worker +# Super user custom actions +{{ item.build.super_user_custom_actions }} +# End of super user custom actions +USER worker +RUN pip install mcpo uv +EXPOSE 8000 +# Worker custom actions +{{ item.build.worker_custom_actions }} +# End of worker custom actions +CMD ["uvx", "mcpo", "--host", "0.0.0.0", "--port", "8000", "--"{% for cmd in item.build.mcp_server_commands %}, "{{ cmd }}"{% endfor %}] \ No newline at end of file diff --git a/inventory/hosts b/inventory/hosts index ed99a85..6ff3ea5 100644 --- a/inventory/hosts +++ b/inventory/hosts @@ -68,8 +68,14 @@ dev-01.alexpires.dev vh-01.alexpires.dev gpu-01.alexpires.dev -[openwebui] +[ollama] gpu-01.alexpires.dev +[kokoro] +gpu-01.alexpires.dev + +[openwebui] +dev-01.alexpires.dev + [mcp] dev-01.alexpires.dev diff --git a/terraform/k8sapps/config.tf b/terraform/k8sapps/config.tf index 2f5fcb0..f2fd799 100644 --- a/terraform/k8sapps/config.tf +++ b/terraform/k8sapps/config.tf @@ -98,7 +98,7 @@ locals { https_port = 8443 tls = true fqdn = ["ai.alexpires.me"] - upstream = "http://10.19.4.106:8080" + upstream = "http://10.19.4.136:8080" locations = [ { path = "/ws/socket.io/" @@ -113,6 +113,12 @@ locals { upstream = "http://10.19.4.136:8000" rewrite = "^/tools/gitea/(.*)$ /$1 break" }, + { + private = true + path = "/providers/ollama" + upstream = "http://10.19.4.106:11434" + rewrite = "^/providers/ollama/(.*)$ /$1 break" + }, { path = "/" },