Added support for private apis
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
ARG MCP_VERSION=0.2.0
|
||||||
|
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
|
||||||
|
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/v${MCP_VERSION}/gitea-mcp_Linux_x86_64.tar.gz \
|
||||||
|
| tar -xz -C /usr/local/bin \
|
||||||
|
&& chmod +x /usr/local/bin/gitea-mcp
|
||||||
|
# End of super user custom actions
|
||||||
|
USER worker
|
||||||
|
RUN pip install mcpo uv
|
||||||
|
EXPOSE 8000
|
||||||
|
# Worker custom actions
|
||||||
|
ENV GITEA_HOST="https://gitea.com"
|
||||||
|
ENV GITEA_INSECURE="false"
|
||||||
|
ENV GITEA_ACCESS_TOKEN="<your personal access token>"
|
||||||
|
CMD ["uvx", "mcpo", "--host", "0.0.0.0", "--port", "8000", "--", "gitea-mcp"]
|
||||||
|
# End of worker custom actions
|
||||||
@@ -31,8 +31,7 @@ sshd_max_auth_tries: 10
|
|||||||
# Firewall ports
|
# Firewall ports
|
||||||
ufw_enable: true
|
ufw_enable: true
|
||||||
fw_allowed_ports:
|
fw_allowed_ports:
|
||||||
- { rule: "allow", port: "80", proto: "tcp" }
|
- { rule: "allow", port: "8000", proto: "tcp" }
|
||||||
- { rule: "allow", port: "443", proto: "tcp" }
|
|
||||||
- { rule: "allow", port: "22", proto: "tcp" }
|
- { rule: "allow", port: "22", proto: "tcp" }
|
||||||
|
|
||||||
ufw_outgoing_traffic:
|
ufw_outgoing_traffic:
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ ufw_outgoing_traffic:
|
|||||||
- 123 # NTP
|
- 123 # NTP
|
||||||
- 443 # HTTPS
|
- 443 # HTTPS
|
||||||
- 853 # DNS over TLS
|
- 853 # DNS over TLS
|
||||||
|
- 8000
|
||||||
|
|
||||||
ufw_default_forward_policy: "ACCEPT"
|
ufw_default_forward_policy: "ACCEPT"
|
||||||
|
|
||||||
@@ -70,31 +71,8 @@ unattended_reboot_time: "04:30"
|
|||||||
duo_users: ["*", "!provision"]
|
duo_users: ["*", "!provision"]
|
||||||
duo_api_hostname: api-7cda1654.duosecurity.com
|
duo_api_hostname: api-7cda1654.duosecurity.com
|
||||||
|
|
||||||
# ai tools
|
# open_web_ui settings
|
||||||
ai_home: "/mnt/data/ai"
|
open_webui_home: "/mnt/data/ai"
|
||||||
ai_network_name: "ai-network"
|
open_webui_network_name: "ai-network"
|
||||||
ai_user: "ai"
|
open_webui_user: "ai"
|
||||||
ai_group: "ai"
|
open_webui_group: "ai"
|
||||||
ai_workloads:
|
|
||||||
- name: ollama
|
|
||||||
build:
|
|
||||||
dockerfile: "{{ lookup('file', 'dockerfiles/Dockerfile.ollama') }}"
|
|
||||||
ports:
|
|
||||||
- 0.0.0.0:11434:11434/tcp
|
|
||||||
volumes:
|
|
||||||
- "{{ ai_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:
|
|
||||||
- "{{ ai_home }}/open-webui:/app/backend/data:Z"
|
|
||||||
device:
|
|
||||||
- "nvidia.com/gpu=all"
|
|
||||||
env:
|
|
||||||
OLLAMA_BASE_URL: "http://ollama:11434"
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,198 +0,0 @@
|
|||||||
- name: Setup AI tools with Podman (Ollama)
|
|
||||||
hosts: ai
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Install Podman and its dependencies
|
|
||||||
become: true
|
|
||||||
ansible.builtin.dnf:
|
|
||||||
name:
|
|
||||||
- podman
|
|
||||||
- nvidia-container-toolkit
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Create group and user for AI workloads
|
|
||||||
become: true
|
|
||||||
ansible.builtin.group:
|
|
||||||
name: "{{ ai_group }}"
|
|
||||||
system: true
|
|
||||||
|
|
||||||
- name: Create user and group for AI containers
|
|
||||||
become: true
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ ai_user }}"
|
|
||||||
groups: "{{ ai_group }}"
|
|
||||||
shell: /bin/bash
|
|
||||||
home: "{{ ai_home }}"
|
|
||||||
group: "{{ ai_group }}"
|
|
||||||
|
|
||||||
- name: Persistently set SELinux context for home directories on AI home
|
|
||||||
become: true
|
|
||||||
community.general.sefcontext:
|
|
||||||
target: "{{ ai_home }}(/.*)?"
|
|
||||||
setype: user_home_dir_t
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Apply SELinux context changes to AI home directories
|
|
||||||
become: true
|
|
||||||
ansible.builtin.command: restorecon -Rv {{ ai_home }}
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Disable password login for AI user
|
|
||||||
ansible.builtin.command: passwd -d "{{ ai_user }}"
|
|
||||||
become: true
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Create data folders for AI tools
|
|
||||||
become: true
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ ai_home }}"
|
|
||||||
state: directory
|
|
||||||
owner: "{{ ai_user }}"
|
|
||||||
group: "{{ ai_group }}"
|
|
||||||
mode: "0750"
|
|
||||||
|
|
||||||
- name: Create build folders for AI tools
|
|
||||||
become: true
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ ai_home }}/build"
|
|
||||||
state: directory
|
|
||||||
owner: "{{ ai_user }}"
|
|
||||||
group: "{{ ai_group }}"
|
|
||||||
mode: "0750"
|
|
||||||
|
|
||||||
- name: Create data folders for AI tools
|
|
||||||
become: true
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ ai_home }}/{{ item.name }}"
|
|
||||||
state: directory
|
|
||||||
owner: "{{ ai_user }}"
|
|
||||||
group: "{{ ai_group }}"
|
|
||||||
mode: "0750"
|
|
||||||
loop: "{{ ai_workloads }}"
|
|
||||||
|
|
||||||
- name: Create systemd service file for nvidia-cdi-generator
|
|
||||||
become: true
|
|
||||||
ansible.builtin.copy:
|
|
||||||
src: services/nvidia-cdi-generator.service
|
|
||||||
dest: /etc/systemd/system/nvidia-cdi-generator.service
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Reload systemd daemon
|
|
||||||
become: true
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
daemon_reload: true
|
|
||||||
name: nvidia-cdi-generator
|
|
||||||
|
|
||||||
- name: Enable nvidia-cdi-generator service
|
|
||||||
become: true
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: nvidia-cdi-generator
|
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
|
|
||||||
- name: Enable sudo access to ai user (temporary)
|
|
||||||
become: true
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /etc/sudoers.d/ansible_ai_tmp
|
|
||||||
create: true
|
|
||||||
mode: '0440'
|
|
||||||
line: "{{ ansible_user_id }} ALL=(ai) NOPASSWD: ALL"
|
|
||||||
validate: 'visudo -cf %s'
|
|
||||||
|
|
||||||
- name: Enable lingering for ai user
|
|
||||||
become: true
|
|
||||||
ansible.builtin.command: loginctl enable-linger {{ ai_user }}
|
|
||||||
register: linger_status
|
|
||||||
changed_when: "'created' in linger_status.stdout or (linger_status.rc == 0 and not ('linger file already exists' in linger_status.stderr or 'linger file does not exist' in linger_status.stderr))" # Heuristic for change
|
|
||||||
failed_when: linger_status.rc != 0 and not ('linger file already exists' in linger_status.stderr)
|
|
||||||
|
|
||||||
- name: Get current user's UID
|
|
||||||
ansible.builtin.command: id -u {{ ai_user }}
|
|
||||||
changed_when: false
|
|
||||||
register: uid
|
|
||||||
|
|
||||||
- name: Get current user's GID
|
|
||||||
ansible.builtin.command: id -g {{ ai_user }}
|
|
||||||
changed_when: false
|
|
||||||
register: gid
|
|
||||||
|
|
||||||
- name: SHowing UID and GID of ai user
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: "UID: {{ uid.stdout }}, GID: {{ gid.stdout }}"
|
|
||||||
|
|
||||||
- name: Run tasks as ai user
|
|
||||||
become: true
|
|
||||||
become_user: "{{ ai_user }}"
|
|
||||||
block:
|
|
||||||
- name: Create a custom Podman network
|
|
||||||
containers.podman.podman_network:
|
|
||||||
name: "{{ ai_network_name }}"
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Build containers images
|
|
||||||
containers.podman.podman_image:
|
|
||||||
name: "ai/{{ item.name }}"
|
|
||||||
path: "{{ ai_home }}/build"
|
|
||||||
build:
|
|
||||||
cache: false
|
|
||||||
force_rm: true
|
|
||||||
container_file: "{{ item.build.dockerfile }}"
|
|
||||||
extra_args: "--build-arg UID={{ uid.stdout }} --build-arg GID={{ gid.stdout }} --security-opt=label=disable {{ item.build.args | default('') }}"
|
|
||||||
tag: "latest"
|
|
||||||
state: present
|
|
||||||
loop: "{{ ai_workloads }}"
|
|
||||||
when: item.build is defined
|
|
||||||
|
|
||||||
- name: Pull the requested images
|
|
||||||
containers.podman.podman_image:
|
|
||||||
name: "{{ item.repo.image }}"
|
|
||||||
tag: "{{ item.repo.tag | default('latest') }}"
|
|
||||||
state: present
|
|
||||||
loop: "{{ ai_workloads }}"
|
|
||||||
when: item.repo is defined
|
|
||||||
|
|
||||||
- name: Create a container instance for each workload
|
|
||||||
containers.podman.podman_container:
|
|
||||||
name: "{{ item.name }}"
|
|
||||||
image: "{{ item.repo.image | default('ai/' + item.name) }}"
|
|
||||||
state: started
|
|
||||||
device: "{{ item.device | default(omit) }}"
|
|
||||||
env: "{{ item.env | default(omit) }}"
|
|
||||||
network: "{{ ai_network_name }}"
|
|
||||||
ports: "{{ item.ports | default(omit) }}"
|
|
||||||
volumes: "{{ item.volumes | default(omit) }}"
|
|
||||||
cmd_args:
|
|
||||||
- "--userns=keep-id"
|
|
||||||
- "--security-opt=label=disable"
|
|
||||||
loop: "{{ ai_workloads }}"
|
|
||||||
|
|
||||||
- name: Create systemd service file for containers
|
|
||||||
become: true
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: podman.service.j2
|
|
||||||
dest: "/etc/systemd/system/podman-{{ item.name }}.service"
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0644"
|
|
||||||
loop: "{{ ai_workloads }}"
|
|
||||||
|
|
||||||
- name: Reload systemd daemon
|
|
||||||
become: true
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
daemon_reload: true
|
|
||||||
|
|
||||||
- name: Enable containers at boot
|
|
||||||
become: true
|
|
||||||
ansible.builtin.systemd:
|
|
||||||
name: "podman-{{ item.name }}"
|
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
loop: "{{ ai_workloads }}"
|
|
||||||
|
|
||||||
- name: Remove temporary sudo access
|
|
||||||
become: true
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /etc/sudoers.d/ansible_ai_tmp
|
|
||||||
state: absent
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
- 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
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
- 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
|
||||||
@@ -10,3 +10,4 @@ MICROK8S_ENCRYPTION_SECRET=$MICROK8S_ENCRYPTION_SECRET
|
|||||||
KMS_ACCESS_KEY=$KMS_ACCESS_KEY
|
KMS_ACCESS_KEY=$KMS_ACCESS_KEY
|
||||||
KMS_SECRET_KEY=$KMS_SECRET_KEY
|
KMS_SECRET_KEY=$KMS_SECRET_KEY
|
||||||
KMS_KEY_ARN=$KMS_KEY_ARN
|
KMS_KEY_ARN=$KMS_KEY_ARN
|
||||||
|
GITEA_APP_TOKEN=$GITEA_APP_TOKEN
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
- name: Install nvidia-cdi-generator
|
||||||
|
become: true
|
||||||
|
ansible.builtin.package:
|
||||||
|
name:
|
||||||
|
- nvidia-container-toolkit
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Create systemd service file for nvidia-cdi-generator
|
||||||
|
become: true
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: services/nvidia-cdi-generator.service
|
||||||
|
dest: /etc/systemd/system/nvidia-cdi-generator.service
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Reload systemd daemon
|
||||||
|
become: true
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
daemon_reload: true
|
||||||
|
name: nvidia-cdi-generator
|
||||||
|
|
||||||
|
- name: Enable nvidia-cdi-generator service
|
||||||
|
become: true
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: nvidia-cdi-generator
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
- name: Check if required vars are set
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: "Required variables are not set for Podman setup. Please check your playbook variables.)"
|
||||||
|
when:
|
||||||
|
- podman_user is not defined
|
||||||
|
- podman_group is not defined
|
||||||
|
- podman_user_home is not defined
|
||||||
|
|
||||||
|
- name: Set fact podman_binary (Debian family)
|
||||||
|
when: ansible_os_family == 'Debian'
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
podman_binary: /usr/bin/podman
|
||||||
|
|
||||||
|
- name: Set fact podman_binary (Red Hat family)
|
||||||
|
when: ansible_os_family == 'RedHat'
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
podman_binary: /usr/sbin/podman
|
||||||
|
|
||||||
|
- name: Install Podman and its dependencies
|
||||||
|
become: true
|
||||||
|
ansible.builtin.package:
|
||||||
|
name:
|
||||||
|
- podman
|
||||||
|
- acl
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Create group and user for AI workloads
|
||||||
|
become: true
|
||||||
|
ansible.builtin.group:
|
||||||
|
name: "{{ podman_group }}"
|
||||||
|
system: true
|
||||||
|
|
||||||
|
- name: Create user and group for AI containers
|
||||||
|
become: true
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ podman_user }}"
|
||||||
|
groups: "{{ podman_group }}"
|
||||||
|
shell: /bin/bash
|
||||||
|
home: "{{ podman_user_home }}"
|
||||||
|
group: "{{ podman_group }}"
|
||||||
|
|
||||||
|
- name: Disable password login for AI user
|
||||||
|
ansible.builtin.command: passwd -d "{{ podman_user }}"
|
||||||
|
become: true
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: SELinux tasks (RedHat only)
|
||||||
|
when: ansible_os_family == 'RedHat'
|
||||||
|
become: true
|
||||||
|
block:
|
||||||
|
- name: Persistently set SELinux context for home directories on AI home
|
||||||
|
community.general.sefcontext:
|
||||||
|
target: "{{ podman_user_home }}(/.*)?"
|
||||||
|
setype: user_home_dir_t
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Apply SELinux context changes to AI home directories
|
||||||
|
ansible.builtin.command: restorecon -Rv {{ podman_user_home }}
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Create user home
|
||||||
|
become: true
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ podman_user_home }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ podman_user }}"
|
||||||
|
group: "{{ podman_group }}"
|
||||||
|
mode: "0750"
|
||||||
|
|
||||||
|
- name: Create build folders
|
||||||
|
become: true
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ podman_user_home }}/build"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ podman_user }}"
|
||||||
|
group: "{{ podman_group }}"
|
||||||
|
mode: "0750"
|
||||||
|
|
||||||
|
- name: Create folders
|
||||||
|
become: true
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ podman_user_home }}/{{ item.name }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ podman_user }}"
|
||||||
|
group: "{{ podman_group }}"
|
||||||
|
mode: "0750"
|
||||||
|
loop: "{{ pods }}"
|
||||||
|
|
||||||
|
- name: Enable sudo access to podman user (temporary)
|
||||||
|
become: true
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "/etc/sudoers.d/ansible_{{ podman_user }}_tmp"
|
||||||
|
create: true
|
||||||
|
mode: "0440"
|
||||||
|
line: "{{ ansible_user_id }} ALL=({{ podman_user }}) NOPASSWD: ALL"
|
||||||
|
validate: "visudo -cf %s"
|
||||||
|
|
||||||
|
- name: Enable lingering for podman user
|
||||||
|
become: true
|
||||||
|
ansible.builtin.command: loginctl enable-linger {{ podman_user }}
|
||||||
|
register: linger_status
|
||||||
|
changed_when: >-
|
||||||
|
"'created' in linger_status.stdout or
|
||||||
|
(linger_status.rc == 0 and not
|
||||||
|
('linger file already exists' in linger_status.stderr or
|
||||||
|
'linger file does not exist' in linger_status.stderr))"
|
||||||
|
failed_when:
|
||||||
|
- linger_status.rc != 0 and not
|
||||||
|
('linger file already exists' in linger_status.stderr)
|
||||||
|
|
||||||
|
- name: Get current user's UID
|
||||||
|
ansible.builtin.command: id -u {{ podman_user }}
|
||||||
|
changed_when: false
|
||||||
|
register: uid
|
||||||
|
|
||||||
|
- name: Get current user's GID
|
||||||
|
ansible.builtin.command: id -g {{ podman_user }}
|
||||||
|
changed_when: false
|
||||||
|
register: gid
|
||||||
|
|
||||||
|
- name: Ensure rootless Podman works with overlay driver on Debian-family systems
|
||||||
|
become: true
|
||||||
|
vars:
|
||||||
|
subuid_range: "100000:65536"
|
||||||
|
block:
|
||||||
|
- name: Install required packages for rootless Podman overlay support
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name:
|
||||||
|
- uidmap
|
||||||
|
- fuse-overlayfs
|
||||||
|
- libslirp0
|
||||||
|
- slirp4netns
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
|
||||||
|
- name: Ensure subuid entry exists for the user
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /etc/subuid
|
||||||
|
line: "{{ podman_user }}:{{ subuid_range }}"
|
||||||
|
create: true
|
||||||
|
state: present
|
||||||
|
regexp: "^{{ podman_user }}:"
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Ensure subgid entry exists for the user
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /etc/subgid
|
||||||
|
line: "{{ podman_user }}:{{ subuid_range }}"
|
||||||
|
create: true
|
||||||
|
state: present
|
||||||
|
regexp: "^{{ podman_user }}:"
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Ensure overlay module is loaded # noqa no-ignore-errors
|
||||||
|
community.general.modprobe:
|
||||||
|
name: overlay
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Ensure overlay module loads on boot
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /etc/modules
|
||||||
|
line: overlay
|
||||||
|
create: true
|
||||||
|
state: present
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Run tasks as podman user
|
||||||
|
become: true
|
||||||
|
become_user: "{{ podman_user }}"
|
||||||
|
block:
|
||||||
|
- name: Create a custom Podman network
|
||||||
|
when: podman_network_name is defined and podman_network_name != 'none'
|
||||||
|
containers.podman.podman_network:
|
||||||
|
name: "{{ podman_network_name }}"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Build containers images
|
||||||
|
containers.podman.podman_image:
|
||||||
|
name: "{{ podman_user }}/{{ item.name }}"
|
||||||
|
path: "{{ podman_user_home }}/build"
|
||||||
|
build:
|
||||||
|
cache: false
|
||||||
|
force_rm: true
|
||||||
|
container_file: "{{ item.build.dockerfile }}"
|
||||||
|
extra_args: "--build-arg UID={{ uid.stdout }} --build-arg GID={{ gid.stdout }} --security-opt=label=disable {{ item.build.args | default('') }}"
|
||||||
|
tag: "latest"
|
||||||
|
state: present
|
||||||
|
loop: "{{ pods }}"
|
||||||
|
when: item.build is defined
|
||||||
|
|
||||||
|
- name: Pull the requested images
|
||||||
|
containers.podman.podman_image:
|
||||||
|
name: "{{ item.repo.image }}"
|
||||||
|
tag: "{{ item.repo.tag | default('latest') }}"
|
||||||
|
state: present
|
||||||
|
loop: "{{ pods }}"
|
||||||
|
when: item.repo is defined
|
||||||
|
|
||||||
|
- name: Create pods
|
||||||
|
containers.podman.podman_container:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
image: "{{ item.repo.image | default(podman_user + '/' + item.name) }}"
|
||||||
|
state: started
|
||||||
|
device: "{{ item.device | default(omit) }}"
|
||||||
|
env: "{{ item.env | default(omit) }}"
|
||||||
|
network: "{{ podman_network_name | default(omit) }}"
|
||||||
|
ports: "{{ item.ports | default(omit) }}"
|
||||||
|
volumes: "{{ item.volumes | default(omit) }}"
|
||||||
|
cmd_args:
|
||||||
|
- "--userns=keep-id"
|
||||||
|
- "--security-opt=label=disable"
|
||||||
|
loop: "{{ pods }}"
|
||||||
|
|
||||||
|
- name: Create systemd service file for pods
|
||||||
|
become: true
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: podman.service.j2
|
||||||
|
dest: "/etc/systemd/system/podman-{{ item.name }}.service"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
loop: "{{ pods }}"
|
||||||
|
|
||||||
|
- name: Reload systemd daemon
|
||||||
|
become: true
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
daemon_reload: true
|
||||||
|
|
||||||
|
- name: Enable containers at boot
|
||||||
|
become: true
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "podman-{{ item.name }}"
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
loop: "{{ pods }}"
|
||||||
|
|
||||||
|
- name: Remove temporary sudo access
|
||||||
|
become: true
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "/etc/sudoers.d/ansible_{{ podman_user }}_tmp"
|
||||||
|
state: absent
|
||||||
@@ -4,11 +4,11 @@ After=local-fs.target network-online.target nvidia-cdi-generator.service
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
User={{ ai_user}}
|
User={{ podman_user }}
|
||||||
Group={{ ai_group }}
|
Group={{ podman_group }}
|
||||||
RemainAfterExit=true
|
RemainAfterExit=true
|
||||||
ExecStart=/usr/sbin/podman start {{ item.name }}
|
ExecStart={{ podman_binary }} start {{ item.name }}
|
||||||
ExecStop=/usr/sbin/podman stop {{ item.name }}
|
ExecStop={{ podman_binary }} stop {{ item.name }}
|
||||||
|
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
StandardError=journal
|
StandardError=journal
|
||||||
|
|||||||
+4
-1
@@ -68,5 +68,8 @@ dev-01.alexpires.dev
|
|||||||
vh-01.alexpires.dev
|
vh-01.alexpires.dev
|
||||||
gpu-01.alexpires.dev
|
gpu-01.alexpires.dev
|
||||||
|
|
||||||
[ai]
|
[openwebui]
|
||||||
gpu-01.alexpires.dev
|
gpu-01.alexpires.dev
|
||||||
|
|
||||||
|
[mcp]
|
||||||
|
dev-01.alexpires.dev
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ locals {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "ai"
|
name = "ai"
|
||||||
type = "CNAME"
|
type = "CNAME"
|
||||||
value = "${local.vps_1.name}.alexpires.me"
|
value = "${local.vps_1.name}.alexpires.me"
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -145,4 +145,5 @@ module "reverse_proxy" {
|
|||||||
|
|
||||||
services = local.reverse_proxy_services
|
services = local.reverse_proxy_services
|
||||||
wireguard_config = local.reverse_proxy_wireguard_config
|
wireguard_config = local.reverse_proxy_wireguard_config
|
||||||
|
auth_token = var.nginx_token_bearer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ locals {
|
|||||||
dns = "10.19.4.1"
|
dns = "10.19.4.1"
|
||||||
allowed_ips = [
|
allowed_ips = [
|
||||||
"10.19.4.106/32",
|
"10.19.4.106/32",
|
||||||
|
"10.19.4.136/32",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,11 +107,17 @@ locals {
|
|||||||
"Connection" = "upgrade"
|
"Connection" = "upgrade"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
private = true
|
||||||
|
path = "/tools/gitea"
|
||||||
|
upstream = "http://10.19.4.136:8000"
|
||||||
|
rewrite = "^/tools/gitea/(.*)$ /$1 break"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path = "/"
|
path = "/"
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -134,3 +134,9 @@ variable "wireguard_1_pre_shared_key" {
|
|||||||
type = string
|
type = string
|
||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "nginx_token_bearer" {
|
||||||
|
description = "Token bearer for nginx"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,3 +34,5 @@ TF_VAR_server_api_access_token=$SERVER_API_ACCESS_TOKEN
|
|||||||
TF_VAR_wireguard_1_private_key=$WIREGUARD_1_PRIVATE_KEY
|
TF_VAR_wireguard_1_private_key=$WIREGUARD_1_PRIVATE_KEY
|
||||||
TF_VAR_wireguard_1_public_key=$WIREGUARD_1_PUBLIC_KEY
|
TF_VAR_wireguard_1_public_key=$WIREGUARD_1_PUBLIC_KEY
|
||||||
TF_VAR_wireguard_1_pre_shared_key=$WIREGUARD_1_PRE_SHARED_KEY
|
TF_VAR_wireguard_1_pre_shared_key=$WIREGUARD_1_PRE_SHARED_KEY
|
||||||
|
TF_VAR_nginx_token_bearer=$NGINX_TOKEN_BEARER
|
||||||
|
|
||||||
|
|||||||
@@ -29,13 +29,20 @@ locals {
|
|||||||
upstream = v.upstream
|
upstream = v.upstream
|
||||||
tls = v.tls
|
tls = v.tls
|
||||||
fqdn = join(" ", v.fqdn)
|
fqdn = join(" ", v.fqdn)
|
||||||
|
auth = length([for l in v.locations : true if l.private]) > 0
|
||||||
locations = [for l in v.locations : {
|
locations = [for l in v.locations : {
|
||||||
path = l.path
|
path = l.path
|
||||||
headers = merge(l.headers, coalesce(v.default_headers, local.default_headers))
|
headers = merge(l.headers, coalesce(v.default_headers, local.default_headers))
|
||||||
|
private = l.private
|
||||||
|
upstream = l.upstream
|
||||||
|
rewrite = l.rewrite
|
||||||
}]
|
}]
|
||||||
custom_snippet = v.custom_snippet
|
custom_snippet = v.custom_snippet
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
services_config_checksum = sha256(jsonencode(local.services_config))
|
services_config_checksum = sha256(jsonencode(local.services_config))
|
||||||
|
|
||||||
|
auth_script = file("${path.module}/resources/auth/auth_server.py")
|
||||||
|
auth_script_checksum = sha256(local.auth_script)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,10 +102,21 @@ resource "kubernetes_config_map" "nginx_default_config" {
|
|||||||
data = local.services_config
|
data = local.services_config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_config_map" "auth_script" {
|
||||||
|
metadata {
|
||||||
|
name = "auth-script"
|
||||||
|
namespace = kubernetes_namespace.reverse_proxy.metadata[0].name
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"auth_server.py" = local.auth_script
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resource "kubernetes_ingress_v1" "reverse-proxy_ingress" {
|
resource "kubernetes_ingress_v1" "reverse-proxy_ingress" {
|
||||||
for_each = var.services
|
for_each = var.services
|
||||||
metadata {
|
metadata {
|
||||||
name = "reverse-proxy-ingress"
|
name = format("reverse-proxy-%s", replace(each.key, ".", "-"))
|
||||||
namespace = kubernetes_namespace.reverse_proxy.metadata[0].name
|
namespace = kubernetes_namespace.reverse_proxy.metadata[0].name
|
||||||
|
|
||||||
annotations = merge({
|
annotations = merge({
|
||||||
@@ -145,6 +156,19 @@ resource "kubernetes_ingress_v1" "reverse-proxy_ingress" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_secret" "auth_token" {
|
||||||
|
metadata {
|
||||||
|
name = "auth-token-secret"
|
||||||
|
namespace = kubernetes_namespace.reverse_proxy.metadata[0].name
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
token = var.auth_token
|
||||||
|
}
|
||||||
|
|
||||||
|
type = "Opaque"
|
||||||
|
}
|
||||||
|
|
||||||
resource "kubernetes_deployment" "reverse_proxy" {
|
resource "kubernetes_deployment" "reverse_proxy" {
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
@@ -175,9 +199,10 @@ resource "kubernetes_deployment" "reverse_proxy" {
|
|||||||
app = "reverse-proxy"
|
app = "reverse-proxy"
|
||||||
}
|
}
|
||||||
annotations = {
|
annotations = {
|
||||||
"checksum/nginx" = local.nginx_config_checksum
|
"checksum/nginx" = local.nginx_config_checksum
|
||||||
"checksum/services" = local.services_config_checksum
|
"checksum/services" = local.services_config_checksum
|
||||||
"checksum/wireguard" = local.wireguard_config_checksum
|
"checksum/wireguard" = local.wireguard_config_checksum
|
||||||
|
"checksum/auth_server" = local.auth_script_checksum
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
spec {
|
spec {
|
||||||
@@ -242,6 +267,34 @@ resource "kubernetes_deployment" "reverse_proxy" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
container {
|
||||||
|
name = "auth-sidecar"
|
||||||
|
image = "python:3.11-slim"
|
||||||
|
|
||||||
|
command = ["python", "/app/auth_server.py"]
|
||||||
|
|
||||||
|
env {
|
||||||
|
name = "AUTH_TOKEN"
|
||||||
|
value_from {
|
||||||
|
secret_key_ref {
|
||||||
|
name = "auth-token-secret"
|
||||||
|
key = "token"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
name = "auth-script"
|
||||||
|
mount_path = "/app"
|
||||||
|
read_only = true
|
||||||
|
}
|
||||||
|
|
||||||
|
port {
|
||||||
|
container_port = 5000
|
||||||
|
name = "auth"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
container {
|
container {
|
||||||
name = "reverse-proxy"
|
name = "reverse-proxy"
|
||||||
image = "nginx:${var.tag}"
|
image = "nginx:${var.tag}"
|
||||||
@@ -334,6 +387,13 @@ resource "kubernetes_deployment" "reverse_proxy" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
volume {
|
||||||
|
name = "auth-script"
|
||||||
|
config_map {
|
||||||
|
name = kubernetes_config_map.auth_script.metadata[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
volume {
|
volume {
|
||||||
name = "nginx"
|
name = "nginx"
|
||||||
config_map {
|
config_map {
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s [%(levelname)s] %(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
|
EXPECTED_TOKEN = os.getenv("AUTH_TOKEN", "")
|
||||||
|
|
||||||
|
class AuthHandler(BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
auth = self.headers.get('Authorization')
|
||||||
|
|
||||||
|
# Log request info
|
||||||
|
client_ip = self.client_address[0]
|
||||||
|
logging.info(f"Auth request from {client_ip}")
|
||||||
|
|
||||||
|
if auth == f"Bearer {EXPECTED_TOKEN}":
|
||||||
|
logging.info("Authentication successful")
|
||||||
|
self.send_response(200)
|
||||||
|
else:
|
||||||
|
logging.warning("Authentication failed")
|
||||||
|
self.send_response(401)
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
# Suppress default BaseHTTPRequestHandler logging
|
||||||
|
return
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if not EXPECTED_TOKEN:
|
||||||
|
logging.error("AUTH_TOKEN environment variable not set. Exiting.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
server = HTTPServer(('', 5000), AuthHandler)
|
||||||
|
logging.info("Auth server running on port 5000...")
|
||||||
|
server.serve_forever()
|
||||||
@@ -10,9 +10,24 @@ server {
|
|||||||
%{ if custom_snippet != "" ~}
|
%{ if custom_snippet != "" ~}
|
||||||
${custom_snippet}
|
${custom_snippet}
|
||||||
%{ endif ~}
|
%{ endif ~}
|
||||||
|
%{ if auth ~}
|
||||||
|
location = /auth {
|
||||||
|
internal;
|
||||||
|
proxy_pass http://localhost:5000/;
|
||||||
|
proxy_pass_request_body off;
|
||||||
|
proxy_set_header Content-Length "";
|
||||||
|
proxy_set_header Authorization $http_authorization;
|
||||||
|
}
|
||||||
|
%{ endif }
|
||||||
%{ for l in locations ~}
|
%{ for l in locations ~}
|
||||||
location ${l.path} {
|
location ${l.path} {
|
||||||
proxy_pass ${upstream};
|
%{ if l.private ~}
|
||||||
|
auth_request /auth;
|
||||||
|
%{ endif ~}
|
||||||
|
%{ if l.rewrite != "" ~}
|
||||||
|
rewrite ${l.rewrite};
|
||||||
|
%{ endif ~}
|
||||||
|
proxy_pass %{ if l.upstream != "" ~}${l.upstream}%{ else ~}${upstream}%{ endif ~};
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
%{ for k,v in l.headers ~}
|
%{ for k,v in l.headers ~}
|
||||||
proxy_set_header ${k} ${v};
|
proxy_set_header ${k} ${v};
|
||||||
|
|||||||
@@ -36,10 +36,19 @@ variable "services" {
|
|||||||
https_port = optional(number, 443)
|
https_port = optional(number, 443)
|
||||||
tls = optional(bool, false)
|
tls = optional(bool, false)
|
||||||
locations = optional(list(object({
|
locations = optional(list(object({
|
||||||
path = string,
|
path = string,
|
||||||
headers = optional(map(string), {}),
|
headers = optional(map(string), {}),
|
||||||
|
private = optional(bool, false),
|
||||||
|
upstream = optional(string, ""),
|
||||||
|
rewrite = optional(string, "")
|
||||||
})), [])
|
})), [])
|
||||||
custom_snippet = optional(string, "")
|
custom_snippet = optional(string, "")
|
||||||
config = optional(string, "")
|
config = optional(string, "")
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "auth_token" {
|
||||||
|
description = "The authentication token to use for the reverse proxy"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user