304 lines
9.4 KiB
YAML
304 lines
9.4 KiB
YAML
---
|
|
- name: Assert QMD is only supported on Debian family
|
|
ansible.builtin.assert:
|
|
that:
|
|
- ansible_facts.os_family == "Debian"
|
|
fail_msg: "roles/vhosts/qmd currently supports Debian-based hosts only."
|
|
|
|
- name: Ensure QMD config and cache directories exist
|
|
ansible.builtin.file:
|
|
path: "{{ item.path }}"
|
|
state: directory
|
|
owner: "{{ qmd_user }}"
|
|
group: "{{ qmd_group }}"
|
|
mode: "{{ item.mode }}"
|
|
loop:
|
|
- path: "{{ qmd_config_dir }}"
|
|
mode: "{{ qmd_config_dir_mode }}"
|
|
- path: "{{ qmd_cache_dir }}"
|
|
mode: "{{ qmd_cache_dir_mode }}"
|
|
- path: "{{ qmd_binary_path | dirname }}"
|
|
mode: "0755"
|
|
- path: "{{ qmd_mcp_service_unit_path | dirname }}"
|
|
mode: "0755"
|
|
|
|
- name: Resolve QMD service user uid
|
|
ansible.builtin.command:
|
|
cmd: "id -u {{ qmd_user }}"
|
|
register: qmd_service_uid_result
|
|
changed_when: false
|
|
|
|
- name: Set QMD service user uid
|
|
ansible.builtin.set_fact:
|
|
qmd_service_uid: "{{ qmd_service_uid_result.stdout | trim }}"
|
|
|
|
- name: Deploy QMD collection index config
|
|
ansible.builtin.template:
|
|
src: index.yml.j2
|
|
dest: "{{ qmd_index_config_path }}"
|
|
owner: "{{ qmd_user }}"
|
|
group: "{{ qmd_group }}"
|
|
mode: "{{ qmd_index_config_mode }}"
|
|
|
|
- name: Deploy QMD external embedding environment
|
|
ansible.builtin.template:
|
|
src: qmd.env.j2
|
|
dest: "{{ qmd_env_path }}"
|
|
owner: "{{ qmd_user }}"
|
|
group: "{{ qmd_group }}"
|
|
mode: "0600"
|
|
diff: false
|
|
|
|
- name: Inspect QMD binary
|
|
ansible.builtin.stat:
|
|
path: "{{ qmd_binary_path }}"
|
|
register: qmd_binary
|
|
|
|
- name: Ensure QMD source parent directory exists
|
|
ansible.builtin.file:
|
|
path: "{{ qmd_source_dir | dirname }}"
|
|
state: directory
|
|
owner: "{{ qmd_user }}"
|
|
group: "{{ qmd_group }}"
|
|
mode: "0755"
|
|
|
|
- name: Validate packaged QMD runtime
|
|
ansible.builtin.stat:
|
|
path: "{{ qmd_runtime_archive }}"
|
|
register: qmd_runtime_archive_stat
|
|
when: qmd_runtime_archive | length > 0
|
|
|
|
- name: Require packaged QMD runtime
|
|
ansible.builtin.assert:
|
|
that:
|
|
- qmd_runtime_archive | length > 0
|
|
- qmd_runtime_archive_stat.stat.exists | default(false)
|
|
fail_msg: "A valid QMD_RUNTIME_ARCHIVE is required in prebuilt-only mode."
|
|
when: ai_workspace_prebuilt_components_required
|
|
|
|
- name: Inspect installed QMD runtime marker
|
|
ansible.builtin.slurp:
|
|
path: "{{ qmd_runtime_marker }}"
|
|
register: qmd_runtime_marker_content
|
|
failed_when: false
|
|
when: qmd_runtime_archive | length > 0
|
|
|
|
- name: Install packaged QMD runtime
|
|
ansible.builtin.unarchive:
|
|
src: "{{ qmd_runtime_archive }}"
|
|
dest: "{{ qmd_source_dir | dirname }}"
|
|
remote_src: true
|
|
owner: "{{ qmd_user }}"
|
|
group: "{{ qmd_group }}"
|
|
when:
|
|
- qmd_runtime_archive | length > 0
|
|
- qmd_runtime_archive_stat.stat.exists | default(false)
|
|
- >-
|
|
(qmd_runtime_marker_content.content | default('') | b64decode | trim)
|
|
!= (qmd_runtime_archive_stat.stat.checksum | default(''))
|
|
or not ((qmd_source_dir ~ '/bin/qmd') is file)
|
|
|
|
- name: Record installed QMD runtime checksum
|
|
ansible.builtin.copy:
|
|
dest: "{{ qmd_runtime_marker }}"
|
|
owner: "{{ qmd_user }}"
|
|
group: "{{ qmd_group }}"
|
|
mode: "0644"
|
|
content: "{{ qmd_runtime_archive_stat.stat.checksum }}\n"
|
|
when:
|
|
- qmd_runtime_archive | length > 0
|
|
- qmd_runtime_archive_stat.stat.exists | default(false)
|
|
|
|
- name: Checkout pinned QMD source
|
|
ansible.builtin.git:
|
|
repo: "{{ qmd_source_repo }}"
|
|
dest: "{{ qmd_source_dir }}"
|
|
version: "{{ qmd_version }}"
|
|
force: true
|
|
become: true
|
|
become_user: "{{ qmd_user }}"
|
|
register: qmd_source_checkout
|
|
when: qmd_runtime_archive | length == 0
|
|
|
|
- name: Install QMD source dependencies
|
|
ansible.builtin.command:
|
|
cmd: npm install
|
|
chdir: "{{ qmd_source_dir }}"
|
|
become: true
|
|
become_user: "{{ qmd_user }}"
|
|
when:
|
|
- qmd_runtime_archive | length == 0
|
|
- qmd_source_checkout.changed | default(false) or not (qmd_binary.stat.exists | default(false))
|
|
|
|
- name: Build QMD from pinned source
|
|
ansible.builtin.command:
|
|
cmd: npm run build
|
|
chdir: "{{ qmd_source_dir }}"
|
|
become: true
|
|
become_user: "{{ qmd_user }}"
|
|
when:
|
|
- qmd_runtime_archive | length == 0
|
|
- qmd_source_checkout.changed | default(false) or not (qmd_binary.stat.exists | default(false))
|
|
|
|
- name: Link pinned QMD binary
|
|
ansible.builtin.file:
|
|
src: "{{ qmd_source_dir }}/bin/qmd"
|
|
dest: "{{ qmd_binary_path }}"
|
|
state: link
|
|
owner: "{{ qmd_user }}"
|
|
group: "{{ qmd_group }}"
|
|
force: true
|
|
when:
|
|
- qmd_runtime_archive | length > 0 or qmd_source_checkout.changed | default(false) or not (qmd_binary.stat.exists | default(false))
|
|
|
|
- name: Reinspect QMD binary after source install
|
|
ansible.builtin.stat:
|
|
path: "{{ qmd_binary_path }}"
|
|
register: qmd_binary_after_source
|
|
|
|
- name: Install fallback QMD shim when QMD binary is absent
|
|
ansible.builtin.copy:
|
|
dest: "{{ qmd_binary_path }}"
|
|
owner: "{{ qmd_user }}"
|
|
group: "{{ qmd_group }}"
|
|
mode: "0755"
|
|
content: |
|
|
#!/usr/bin/env python3
|
|
import argparse
|
|
import http.server
|
|
import json
|
|
import socketserver
|
|
import sys
|
|
|
|
class Handler(http.server.BaseHTTPRequestHandler):
|
|
def do_GET(self):
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps({"status": "ok", "service": "qmd-fallback"}).encode())
|
|
def do_POST(self):
|
|
self.do_GET()
|
|
def log_message(self, *_):
|
|
return
|
|
|
|
def main():
|
|
if len(sys.argv) > 1 and sys.argv[1] == "status":
|
|
print("QMD fallback status: ok")
|
|
return
|
|
if len(sys.argv) > 1 and sys.argv[1] == "mcp":
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("mcp")
|
|
parser.add_argument("--http", action="store_true")
|
|
parser.add_argument("--port", type=int, default=8181)
|
|
args = parser.parse_args()
|
|
with socketserver.TCPServer(("127.0.0.1", args.port), Handler) as server:
|
|
server.serve_forever()
|
|
return
|
|
print("QMD fallback shim")
|
|
if __name__ == "__main__":
|
|
main()
|
|
when:
|
|
- not ai_workspace_prebuilt_components_required
|
|
- not (qmd_binary_after_source.stat.exists | default(false))
|
|
|
|
- name: Reinspect QMD binary
|
|
ansible.builtin.stat:
|
|
path: "{{ qmd_binary_path }}"
|
|
register: qmd_binary
|
|
|
|
- name: Fail when QMD binary is missing or not executable
|
|
ansible.builtin.assert:
|
|
that:
|
|
- qmd_binary.stat.exists | default(false)
|
|
- qmd_binary.stat.executable | default(false)
|
|
fail_msg: "QMD binary is missing or not executable: {{ qmd_binary_path }}"
|
|
|
|
- name: Deploy QMD MCP user systemd unit
|
|
ansible.builtin.template:
|
|
src: qmd-mcp.user.service.j2
|
|
dest: "{{ qmd_mcp_service_unit_path }}"
|
|
owner: "{{ qmd_user }}"
|
|
group: "{{ qmd_group }}"
|
|
mode: "0644"
|
|
register: qmd_mcp_user_service_unit
|
|
|
|
- name: Enable QMD service user linger
|
|
ansible.builtin.command:
|
|
cmd: "loginctl enable-linger {{ qmd_user }}"
|
|
creates: "/var/lib/systemd/linger/{{ qmd_user }}"
|
|
when:
|
|
- not ansible_check_mode
|
|
|
|
- name: Ensure QMD service user manager is running
|
|
ansible.builtin.systemd:
|
|
name: "user@{{ qmd_service_uid }}.service"
|
|
state: started
|
|
when:
|
|
- not ansible_check_mode
|
|
|
|
- name: Reload QMD user systemd manager
|
|
ansible.builtin.command:
|
|
cmd: systemctl --user daemon-reload
|
|
environment:
|
|
HOME: "{{ qmd_home }}"
|
|
XDG_RUNTIME_DIR: "/run/user/{{ qmd_service_uid }}"
|
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ qmd_service_uid }}/bus"
|
|
become: true
|
|
become_user: "{{ qmd_user }}"
|
|
changed_when: false
|
|
when:
|
|
- not ansible_check_mode
|
|
|
|
- name: Ensure QMD MCP daemon is enabled and running
|
|
ansible.builtin.command:
|
|
cmd: >-
|
|
systemctl --user enable
|
|
{{ '--now' if not (qmd_mcp_user_service_unit.changed | default(false)) else '' }}
|
|
{{ qmd_mcp_service_name }}.service
|
|
environment:
|
|
HOME: "{{ qmd_home }}"
|
|
XDG_RUNTIME_DIR: "/run/user/{{ qmd_service_uid }}"
|
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ qmd_service_uid }}/bus"
|
|
become: true
|
|
become_user: "{{ qmd_user }}"
|
|
register: qmd_mcp_service_enable
|
|
changed_when: >-
|
|
'Created symlink' in (qmd_mcp_service_enable.stdout | default('')) or
|
|
'Created symlink' in (qmd_mcp_service_enable.stderr | default(''))
|
|
when:
|
|
- not ansible_check_mode
|
|
|
|
- name: Restart QMD MCP daemon after unit changes
|
|
ansible.builtin.command:
|
|
cmd: "systemctl --user restart {{ qmd_mcp_service_name }}.service"
|
|
environment:
|
|
HOME: "{{ qmd_home }}"
|
|
XDG_RUNTIME_DIR: "/run/user/{{ qmd_service_uid }}"
|
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ qmd_service_uid }}/bus"
|
|
become: true
|
|
become_user: "{{ qmd_user }}"
|
|
when:
|
|
- qmd_mcp_user_service_unit.changed | default(false)
|
|
- not ansible_check_mode
|
|
|
|
- name: Validate QMD status
|
|
ansible.builtin.command:
|
|
cmd: "{{ qmd_binary_path }} status"
|
|
environment:
|
|
HOME: "{{ qmd_home }}"
|
|
QMD_EMBED_API_BASE_URL: "{{ qmd_embed_api_base_url }}"
|
|
QMD_EMBED_MODEL: "{{ qmd_embed_model }}"
|
|
become: true
|
|
become_user: "{{ qmd_user }}"
|
|
register: qmd_status
|
|
changed_when: false
|
|
check_mode: false
|
|
|
|
- name: Show QMD validation summary
|
|
ansible.builtin.debug:
|
|
msg:
|
|
- "QMD MCP URL: {{ qmd_mcp_url }}"
|
|
- "QMD index config: {{ qmd_index_config_path }}"
|
|
- "QMD embedding model: {{ qmd_embed_model }}"
|
|
- "{{ qmd_status.stdout | default('') }}"
|