Adds idempotent, data-driven GGUF staging for the two new router models on
astro-orbiter alongside the production Qwen3.6-35B-A3B-UD-Q4_K_S. Both files
were already staged live (byte-exact); this commit codifies them in Ansible so
future re-runs and any new model adds are version-controlled and audit-friendly.
Changes:
- roles/llm-inference-multimodel/tasks/stage_model.yml (NEW)
Idempotent per-model task: stat -> exact byte-size guard -> conditional
get_url -> ownership/mode ensure -> notify router restart handler only on
actual download. Loops from models.yml; nothing hardcoded.
- roles/llm-inference-multimodel/tasks/models.yml
Appends the stage_model.yml loop (tagged: models) after the existing Qwen3.6
download tasks. Data driven from host_vars/astro-orbiter/vars.yml.
- roles/llm-inference-multimodel/defaults/main.yml
Adds llm_staged_models: [] default (empty = safe no-op for hosts with no
staged model list defined).
- roles/llm-inference-multimodel/handlers/main.yml
Adds 'restart llama-server-router on new GGUF' handler. Only fires when
stage_model.yml performs an actual download or corrects ownership/mode.
Normal idempotent re-runs (files already correct) do NOT fire this handler.
- host_vars/astro-orbiter/vars.yml
Adds llm_staged_models list with the two new models:
* Phi-3.5-mini-instruct-Q8_0.gguf (4,061,222,688 bytes,
bartowski/Phi-3.5-mini-instruct-GGUF)
* Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf (4,920,739,232 bytes,
bartowski/Meta-Llama-3.1-8B-Instruct-GGUF)
- playbooks/day1_deploy_llm_inference_multimodel.yml
Updates header comment: removes stale 'Semaphore broken' note, documents
the correct execution channel (Semaphore template
llm_inference_multimodel_stage_models, --tags models).
- group_vars/all/semaphore.yml
Adds llm_inference_multimodel_stage_models template entry (config-as-code).
Template is scoped to --tags models explicitly. Phase 4 (verify) is
EXCLUDED: verify.yml starts llama-server-qwen on :8002, which would collide
with the production llama-server-router.service already running on :8002.
Semaphore template created via API: project 1 / template id 19.
Execution: triggered immediately after this commit via Semaphore REST API.
180 lines
7.1 KiB
YAML
180 lines
7.1 KiB
YAML
---
|
|
# ============================================================================
|
|
# Semaphore configuration-as-code
|
|
# ============================================================================
|
|
# Drives a freshly-deployed Semaphore instance into its desired state via
|
|
# the Semaphore REST API. Idempotent: every object is checked first; only
|
|
# missing ones are created. Existing objects are left alone.
|
|
#
|
|
# Loaded from group_vars/all/semaphore.yml so that the configuration is
|
|
# version-controlled in the homelab repo and survives a wipe-and-redeploy
|
|
# of the Semaphore VM.
|
|
# ============================================================================
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# API connection (defaults to the local Traefik-fronted service-name URL).
|
|
# Override semaphore_api_url to point at a specific instance if needed.
|
|
# ---------------------------------------------------------------------------
|
|
semaphore_api_url: "https://semaphore.local.mk-labs.cloud/api"
|
|
semaphore_api_validate_certs: true
|
|
semaphore_api_token: "{{ vault_semaphore_api_token }}"
|
|
|
|
# Feature flag — keeps day1_deploy_semaphore.yml deploy-only by default.
|
|
# Set true to also run the configuration pass.
|
|
semaphore_configure: false
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Declarative configuration of the Semaphore instance.
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
# Top-level shape:
|
|
#
|
|
# semaphore_config:
|
|
# project: single dict — the lab uses one project ("mk-labs")
|
|
# keys: list of credentials Semaphore stores
|
|
# repositories: git repos Semaphore can clone
|
|
# inventories: Ansible inventories from those repos
|
|
# environments: env-var bundles
|
|
# templates: task templates that tie everything together
|
|
#
|
|
# Each list element has a unique "name" used as the natural identity key.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
semaphore_config:
|
|
project:
|
|
name: mk-labs
|
|
alert: false
|
|
max_parallel_tasks: 0 # 0 = unlimited
|
|
|
|
keys:
|
|
# The ansible-vault password. login_password type with empty login
|
|
# — only the password field is consumed by Semaphore at runtime.
|
|
- name: ansible-vault-pass
|
|
type: login_password
|
|
login: ""
|
|
password: "{{ vault_ansible_vault_password }}"
|
|
|
|
# SSH key for the gitea deploy access (clone the homelab repo).
|
|
- name: gitea-deploy
|
|
type: ssh
|
|
ssh_login: git
|
|
ssh_private_key: "{{ vault_gitea_deploy_key }}"
|
|
|
|
# SSH key for the universal automation account 'wed' — pre-baked in
|
|
# every mk-labs VM template. This is the canonical user Semaphore
|
|
# uses to reach the fleet.
|
|
- name: wed-ssh
|
|
type: ssh
|
|
ssh_login: wed
|
|
ssh_private_key: "{{ vault_wed_ssh_private_key }}"
|
|
|
|
# SSH key Semaphore can use to reach the fleet as jarvis (admin
|
|
# account provisioned by linux-baseline). Retained for jobs that
|
|
# specifically need jarvis-level access; the default is wed-ssh.
|
|
- name: jarvis-ssh
|
|
type: ssh
|
|
ssh_login: jarvis
|
|
ssh_private_key: "{{ vault_jarvis_ssh_private_key }}"
|
|
|
|
repositories:
|
|
- name: homelab
|
|
git_url: "ssh://git@gitea.mk-labs.cloud:2221/rblundon/homelab.git"
|
|
git_branch: main
|
|
ssh_key: gitea-deploy
|
|
|
|
inventories:
|
|
- name: production
|
|
type: file
|
|
inventory_file: ansible/inventory.yml
|
|
repository: homelab
|
|
# wed is the universal automation account pre-baked in every VM
|
|
# template. Semaphore uses it for fleet-wide jobs.
|
|
ssh_key: wed-ssh
|
|
# become_key is Semaphore's sudo PASSWORD slot, not a second SSH
|
|
# key. wed has passwordless sudo on every host, so reference the
|
|
# built-in "None" key. (Semaphore rejects an SSH-type key here.)
|
|
become_key: None
|
|
|
|
environments:
|
|
- name: default
|
|
env:
|
|
ANSIBLE_HOST_KEY_CHECKING: "False"
|
|
ANSIBLE_FORCE_COLOR: "True"
|
|
# Semaphore runs ansible-playbook from the cloned REPO ROOT (not
|
|
# from the playbook's directory as I first assumed). Path is
|
|
# therefore relative to repo root, not playbook dir.
|
|
ANSIBLE_ROLES_PATH: "ansible/roles"
|
|
# Collections are installed by the semaphore role into a host-side
|
|
# directory bind-mounted into the container at this path.
|
|
ANSIBLE_COLLECTIONS_PATH: "/opt/ansible-collections"
|
|
|
|
templates:
|
|
- name: "day0_linux_baseline"
|
|
description: "Apply the mk-labs Linux baseline to one or more hosts."
|
|
app: ansible
|
|
playbook: ansible/playbooks/day0_linux_baseline.yml
|
|
inventory: production
|
|
repository: homelab
|
|
environment: default
|
|
vault_password: ansible-vault-pass
|
|
arguments: '["--diff"]'
|
|
survey_vars:
|
|
- name: target
|
|
title: "Target host or group"
|
|
description: "Inventory target (e.g. figment, semaphore_server, all)"
|
|
required: true
|
|
type: TextVar
|
|
default_value: "all"
|
|
|
|
- name: "day1_deploy_semaphore"
|
|
description: "Re-deploy Semaphore + PostgreSQL on figment."
|
|
app: ansible
|
|
playbook: ansible/playbooks/day1_deploy_semaphore.yml
|
|
inventory: production
|
|
repository: homelab
|
|
environment: default
|
|
vault_password: ansible-vault-pass
|
|
arguments: '["--diff"]'
|
|
|
|
- name: "day0_linux_baseline_check"
|
|
description: "Dry-run the baseline — shows diffs, applies nothing."
|
|
app: ansible
|
|
playbook: ansible/playbooks/day0_linux_baseline.yml
|
|
inventory: production
|
|
repository: homelab
|
|
environment: default
|
|
vault_password: ansible-vault-pass
|
|
arguments: '["--check","--diff"]'
|
|
survey_vars:
|
|
- name: target
|
|
title: "Target host or group"
|
|
description: "Inventory target (e.g. figment, semaphore_server, all)"
|
|
required: true
|
|
type: TextVar
|
|
default_value: "all"
|
|
|
|
- name: "llm_inference_multimodel_stage_models"
|
|
description: >-
|
|
Stage additional GGUF models into /opt/models on astro-orbiter via the
|
|
llm-inference-multimodel role (--tags models only). Idempotent: skips
|
|
files already present at the correct byte size. Notifies the
|
|
llama-server-router restart handler ONLY when a new GGUF is actually
|
|
downloaded. Does NOT touch Phase 4 (verify) or the legacy
|
|
llama-server-qwen service. Safe to run repeatedly.
|
|
app: ansible
|
|
playbook: ansible/playbooks/day1_deploy_llm_inference_multimodel.yml
|
|
inventory: production
|
|
repository: homelab
|
|
environment: default
|
|
vault_password: ansible-vault-pass
|
|
arguments: '["--tags","models","--diff"]'
|
|
# Scoped to --tags models:
|
|
# Phase 0 (discover) -- skipped (no tag)
|
|
# Phase 1 (models) -- RUN (idempotent GGUF staging via stage_model.yml)
|
|
# Phase 2 (systemd) -- skipped
|
|
# Phase 3 (firewall) -- skipped
|
|
# Phase 4 (verify) -- SKIPPED (collision risk: verify.yml would start
|
|
# llama-server-qwen on :8002, conflicting with the
|
|
# production llama-server-router.service. Excluded
|
|
# here deliberately. See t_730f9584.)
|