deploy hermes
This commit is contained in:
@@ -1,34 +0,0 @@
|
|||||||
# file: group_vars/prometheus_server/vars
|
|
||||||
# Ansible vars template for Prometheus nodes created via Ansible\.
|
|
||||||
|
|
||||||
# Version of Prometheus to install. All available versions can be found at: https://prometheus.io/download/
|
|
||||||
|
|
||||||
#prometheus_version: "3.5.0"
|
|
||||||
#alertmanager_version: "0.28.1"
|
|
||||||
|
|
||||||
# prometheus_nodes:
|
|
||||||
# # - '192.168.1.207' # Worker node IP
|
|
||||||
# - cinderella-castle # Master node IP
|
|
||||||
# - 10.1.71.21
|
|
||||||
# - cinderella-castle.local.mk-labs.cloud
|
|
||||||
|
|
||||||
# Static Variables
|
|
||||||
# prometheus_installer_file: "prometheus-{{ prometheus_version }}.linux-amd64.tar.gz"
|
|
||||||
# prometheus_installer_download_url: "https://github.com/prometheus/prometheus/releases/download/v{{ prometheus_version }}/{{ prometheus_installer_file }}"
|
|
||||||
|
|
||||||
# Grafana Variables
|
|
||||||
|
|
||||||
#grafana_port: "3000"
|
|
||||||
#grafana_url: "{{ inventory_hostname }}.{{ base_domain }}:3000"
|
|
||||||
|
|
||||||
# prometheus_targets:
|
|
||||||
# node:
|
|
||||||
# - targets:
|
|
||||||
# - localhost:9100
|
|
||||||
# labels:
|
|
||||||
# env: mk-labs
|
|
||||||
|
|
||||||
grafana_ini:
|
|
||||||
security:
|
|
||||||
admin_user: admin
|
|
||||||
admin_password: admin
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
---
|
|
||||||
# Variables for Unbound https://github.com/aruhier/ansible-role-unbound
|
|
||||||
|
|
||||||
### Server ###
|
|
||||||
|
|
||||||
unbound_chroot: '""'
|
|
||||||
unbound_num_threads: 2
|
|
||||||
unbound_interfaces:
|
|
||||||
- "{{ ip_address }}"
|
|
||||||
#unbound_do_ip4: "yes"
|
|
||||||
unbound_do_ip6: "no"
|
|
||||||
#unbound_do_udp: "yes"
|
|
||||||
#unbound_do_tcp: "yes"
|
|
||||||
|
|
||||||
# list of strings, as the order impact how policies are interpreted
|
|
||||||
unbound_outgoing_policies: []
|
|
||||||
|
|
||||||
unbound_access_control:
|
|
||||||
- '192.168.1.0/24 allow'
|
|
||||||
- '192.168.3.0/24 allow'
|
|
||||||
- '192.168.5.0/24 allow'
|
|
||||||
- '192.168.10.0/24 allow'
|
|
||||||
- '10.1.71.0/24 allow'
|
|
||||||
|
|
||||||
unbound_access_control_tag: []
|
|
||||||
unbound_access_control_tag_action: []
|
|
||||||
unbound_access_control_tag_data: []
|
|
||||||
unbound_access_control_view: []
|
|
||||||
|
|
||||||
unbound_private_addresses:
|
|
||||||
- '192.168.1.0/24'
|
|
||||||
- '192.168.3.0/24'
|
|
||||||
- '192.168.5.0/24'
|
|
||||||
- '192.168.10.0/24'
|
|
||||||
- '10.1.71.0/24'
|
|
||||||
|
|
||||||
unbound_private_domains:
|
|
||||||
- 'mk-labs.cloud'
|
|
||||||
|
|
||||||
unbound_domains_insecure: []
|
|
||||||
unbound_do_not_query_addresses: []
|
|
||||||
unbound_local_zones: []
|
|
||||||
unbound_local_datas: []
|
|
||||||
unbound_local_data_ptrs: []
|
|
||||||
unbound_local_zone_tags: []
|
|
||||||
unbound_local_zone_overrides: []
|
|
||||||
|
|
||||||
unbound_trust_anchors: []
|
|
||||||
unbound_trusted_keys_files: []
|
|
||||||
|
|
||||||
### Remote Control ###
|
|
||||||
|
|
||||||
unbound_control_enable: "no"
|
|
||||||
unbound_control_interface: []
|
|
||||||
|
|
||||||
### Stub, forward zones and others ###
|
|
||||||
|
|
||||||
unbound_rpz_zones: [] # list of dicts
|
|
||||||
unbound_stub_zones: [] # list of dicts
|
|
||||||
unbound_forward_zones:
|
|
||||||
- name: 'int.mk-labs.cloud'
|
|
||||||
forward-addr:
|
|
||||||
- '10.1.71.5'
|
|
||||||
- name: '.'
|
|
||||||
forward-addr:
|
|
||||||
- '1.0.0.1@53#one.one.one.one'
|
|
||||||
- '1.1.1.1@53#one.one.one.one'
|
|
||||||
|
|
||||||
# For stub and forward zones, if a key inside one of the dict is an iterable,
|
|
||||||
# it will iterate inside it to duplicate the option with all the contained
|
|
||||||
# values.
|
|
||||||
# Can be useful in case of multiple forward-addr for a same zone:
|
|
||||||
# Example:
|
|
||||||
# unbound_forward_zones:
|
|
||||||
# - {name: "test.tld", "forward-addr": ["192.0.2.5", "192.0.2.6"]}
|
|
||||||
|
|
||||||
unbound_views: [] # list of strings, as options can be multiples
|
|
||||||
25
ansible/host_vars/astro-orbiter/vars.yml
Normal file
25
ansible/host_vars/astro-orbiter/vars.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# FILE: ansible/host_vars/astro_orbiter/vars.yml
|
||||||
|
# HOST: astro-orbiter (10.1.71.130)
|
||||||
|
# ROLE: Ollama inference host with AMD RX 5700 GPU passthrough
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ansible_host: 10.1.71.130
|
||||||
|
ansible_user: wed
|
||||||
|
ansible_become: true
|
||||||
|
|
||||||
|
# LVM root expansion — xlarge template uses sda3 partition, standard VG/LV names
|
||||||
|
common_expand_root_lvm: true
|
||||||
|
common_root_pv: /dev/sda3
|
||||||
|
common_root_vg: ubuntu-vg
|
||||||
|
common_root_lv: ubuntu-lv
|
||||||
|
|
||||||
|
# Ollama — all defaults apply; explicitly documented here for visibility
|
||||||
|
ollama_rocm_version: "6.2"
|
||||||
|
ollama_default_model: "qwen3:8b"
|
||||||
|
ollama_hsa_override_gfx_version: "10.1.0"
|
||||||
|
ollama_data_disk: /dev/sdb
|
||||||
|
ollama_data_vg: ollama-vg
|
||||||
|
ollama_data_lv: ollama-lv
|
||||||
|
ollama_data_dir: /var/lib/ollama
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
---
|
|
||||||
# file: host_vars/vm_template/vars
|
|
||||||
# Ansible vars template for hosts created via cloning.
|
|
||||||
|
|
||||||
# Supported hypervisors:
|
|
||||||
# - Proxmox
|
|
||||||
|
|
||||||
platform: "proxmox"
|
|
||||||
|
|
||||||
# This is the Proxmox node where all the VM templates are stored. (Templates are not global.)
|
|
||||||
|
|
||||||
proxmox_clone_node: "fantasyland"
|
|
||||||
|
|
||||||
# Templates are named in the following format (all lower case): <OS Distribution>-<OS Version>-<VM Size>
|
|
||||||
# Current OS offerings are:
|
|
||||||
# - Fedora (42)
|
|
||||||
# Current VM sizes are:
|
|
||||||
# - Small: 2 cores, 2GB memory, 8 GiB virtual disk
|
|
||||||
# - Medium: 2 cores, 4GB memory, 16 GiB virtual disk
|
|
||||||
# - Large: 4 cores, 4GB memory, 32 GiB virtual disk
|
|
||||||
# - Xlarge: 4 cores, 8GB memory, 64 GiB virtual disk
|
|
||||||
# - Xlarge Plus: 4 cores, 8GB memory, 128 GiB virtual disk
|
|
||||||
|
|
||||||
vm_clone_source: "fedora-42-xlarge-plus"
|
|
||||||
|
|
||||||
# Proxmox storage target.
|
|
||||||
|
|
||||||
vm_storage: "general"
|
|
||||||
|
|
||||||
# Proxmox does not yet do dynamic load balancing, the host target sets the target for HA groups
|
|
||||||
# and backup groups. (ha_group will be factored out in the next functionality update.)
|
|
||||||
|
|
||||||
ha_group: "fantasyland"
|
|
||||||
proxmox_host_target: "fantasyland"
|
|
||||||
|
|
||||||
# Currently, only single NIC VMs using IPv4 are supported via cloning. The IP address also
|
|
||||||
# sets the Proxmox VMID. The VMID is a combination of the 3rd and 4th octet of the IPv4 address.
|
|
||||||
|
|
||||||
ip_address: 10.1.71.21
|
|
||||||
|
|
||||||
# Software
|
|
||||||
# Future enhancement will allow specification of additional software to automatically deploy to
|
|
||||||
# the VM after creation.
|
|
||||||
|
|
||||||
#terraform_version: "1.11.3"
|
|
||||||
|
|
||||||
# ---
|
|
||||||
|
|
||||||
hostname: "{{ inventory_hostname }}.{{ base_domain }}" # Change variable to fqdn
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
# file: host_vars/monitor/vars
|
|
||||||
|
|
||||||
# VM Definition
|
|
||||||
platform: "proxmox"
|
|
||||||
proxmox_clone_node: "pve03"
|
|
||||||
vm_clone_source: "fedora-42-large"
|
|
||||||
vm_storage: "pve-general"
|
|
||||||
ha_group: "pve03"
|
|
||||||
|
|
||||||
# Networking
|
|
||||||
ip_address: 10.1.71.249
|
|
||||||
|
|
||||||
# Software
|
|
||||||
#terraform_version: "1.11.3"
|
|
||||||
|
|
||||||
hostname: "{{ inventory_hostname }}.{{ base_domain }}" #change variable to fqdn
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
---
|
|
||||||
# file: host_vars/unbound01/vars
|
|
||||||
|
|
||||||
# Supported hypervisors:
|
|
||||||
# - Proxmox
|
|
||||||
|
|
||||||
platform: "proxmox"
|
|
||||||
|
|
||||||
# This is the Proxmox node where all the VM templates are stored. (Templates are not global.)
|
|
||||||
|
|
||||||
proxmox_clone_node: "pve03"
|
|
||||||
|
|
||||||
# Templates are named in the following format (all lower case): <OS Distribution>-<OS Version>-<VM Size>
|
|
||||||
# Current OS offerings are:
|
|
||||||
# - Fedora (42)
|
|
||||||
# Current VM sizes are:
|
|
||||||
# - Small: 2 cores, 2GB memory, 8 GiB virtual disk
|
|
||||||
# - Medium: 2 cores, 4GB memory, 16 GiB virtual disk
|
|
||||||
# - Large: 4 cores, 4GB memory, 32 GiB virtual disk
|
|
||||||
# - Xlarge: 4 cores, 8GB memory, 64 GiB virtual disk
|
|
||||||
|
|
||||||
vm_clone_source: "fedora-42-small"
|
|
||||||
|
|
||||||
# Proxmox storage target.
|
|
||||||
|
|
||||||
vm_storage: "pve-general"
|
|
||||||
|
|
||||||
# Proxmox does not yet do dynamic load balancing, the host target sets the target for HA groups
|
|
||||||
# and backup groups. (ha_group will be factored out in the next functionality update.)
|
|
||||||
|
|
||||||
ha_group: "pve03"
|
|
||||||
proxmox_host_target: "pve03"
|
|
||||||
|
|
||||||
# Currently, only single NIC VMs using IPv4 are supported via cloning. The IP address also
|
|
||||||
# sets the Proxmox VMID. The VMID is a combination of the 3rd and 4th octet of the IPv4 address.
|
|
||||||
|
|
||||||
ip_address: 10.1.71.251
|
|
||||||
|
|
||||||
# Software
|
|
||||||
# Future enhancement will allow specification of additional software to automatically deploy to
|
|
||||||
# the VM after creation.
|
|
||||||
|
|
||||||
#terraform_version: "1.11.3"
|
|
||||||
|
|
||||||
# ---
|
|
||||||
|
|
||||||
hostname: "{{ inventory_hostname }}.{{ base_domain }}" # Change variable to fqdn
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
---
|
|
||||||
# file: host_vars/unbound02/vars
|
|
||||||
|
|
||||||
# Supported hypervisors:
|
|
||||||
# - Proxmox
|
|
||||||
|
|
||||||
platform: "proxmox"
|
|
||||||
|
|
||||||
# This is the Proxmox node where all the VM templates are stored. (Templates are not global.)
|
|
||||||
|
|
||||||
proxmox_clone_node: "pve03"
|
|
||||||
|
|
||||||
# Templates are named in the following format (all lower case): <OS Distribution>-<OS Version>-<VM Size>
|
|
||||||
# Current OS offerings are:
|
|
||||||
# - Fedora (42)
|
|
||||||
# Current VM sizes are:
|
|
||||||
# - Small: 2 cores, 2GB memory, 8 GiB virtual disk
|
|
||||||
# - Medium: 2 cores, 4GB memory, 16 GiB virtual disk
|
|
||||||
# - Large: 4 cores, 4GB memory, 32 GiB virtual disk
|
|
||||||
# - Xlarge: 4 cores, 8GB memory, 64 GiB virtual disk
|
|
||||||
|
|
||||||
vm_clone_source: "fedora-42-small"
|
|
||||||
|
|
||||||
# Proxmox storage target.
|
|
||||||
|
|
||||||
vm_storage: "pve-general"
|
|
||||||
|
|
||||||
# Proxmox does not yet do dynamic load balancing, the host target sets the target for HA groups
|
|
||||||
# and backup groups. (ha_group will be factored out in the next functionality update.)
|
|
||||||
|
|
||||||
ha_group: "pve03"
|
|
||||||
proxmox_host_target: "pve03"
|
|
||||||
|
|
||||||
# Currently, only single NIC VMs using IPv4 are supported via cloning. The IP address also
|
|
||||||
# sets the Proxmox VMID. The VMID is a combination of the 3rd and 4th octet of the IPv4 address.
|
|
||||||
|
|
||||||
ip_address: 10.1.71.252
|
|
||||||
|
|
||||||
# Software
|
|
||||||
# Future enhancement will allow specification of additional software to automatically deploy to
|
|
||||||
# the VM after creation.
|
|
||||||
|
|
||||||
#terraform_version: "1.11.3"
|
|
||||||
|
|
||||||
# ---
|
|
||||||
|
|
||||||
hostname: "{{ inventory_hostname }}.{{ base_domain }}" # Change variable to fqdn
|
|
||||||
@@ -58,25 +58,13 @@ n8n_server:
|
|||||||
hosts:
|
hosts:
|
||||||
tiki-room:
|
tiki-room:
|
||||||
|
|
||||||
monitoring_server:
|
ollama_server:
|
||||||
hosts:
|
hosts:
|
||||||
cinderella-castle:
|
astro-orbiter:
|
||||||
ansible_host: 10.1.71.31
|
ansible_host: 10.1.71.130
|
||||||
ansible_user: wed
|
ansible_user: wed
|
||||||
ansible_become: true
|
ansible_become: true
|
||||||
|
|
||||||
# node_explorer:
|
|
||||||
# hosts:
|
|
||||||
# cinderella-castle:
|
|
||||||
|
|
||||||
# alertmanager:
|
|
||||||
# hosts:
|
|
||||||
# cinderella-castle:
|
|
||||||
|
|
||||||
# grafana:
|
|
||||||
# hosts:
|
|
||||||
# cinderella-castle:
|
|
||||||
|
|
||||||
papermc_server:
|
papermc_server:
|
||||||
# ansible-galaxy role install engonzal.papermc
|
# ansible-galaxy role install engonzal.papermc
|
||||||
hosts:
|
hosts:
|
||||||
|
|||||||
400
ansible/playbooks/day1_configure_netbox_catalog.yml
Normal file
400
ansible/playbooks/day1_configure_netbox_catalog.yml
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
---
|
||||||
|
# ansible/playbooks/day1_configure_netbox_catalog.yml
|
||||||
|
#
|
||||||
|
# Creates the tag taxonomy and custom fields in NetBox for service catalog
|
||||||
|
# documentation. Run once (idempotent — uses name-based checks).
|
||||||
|
#
|
||||||
|
# Usage (from ansible/ directory):
|
||||||
|
# ansible-playbook playbooks/day1_configure_netbox_catalog.yml
|
||||||
|
#
|
||||||
|
# Requires:
|
||||||
|
# - vault_netbox_token in Ansible Vault
|
||||||
|
# - netbox reachable at http://fire-station.local.mk-labs.cloud
|
||||||
|
|
||||||
|
- name: Configure NetBox service catalog taxonomy
|
||||||
|
hosts: localhost
|
||||||
|
gather_facts: false
|
||||||
|
|
||||||
|
vars_files:
|
||||||
|
- "{{ playbook_dir }}/../group_vars/all/vault"
|
||||||
|
|
||||||
|
vars:
|
||||||
|
netbox_url: "http://fire-station.local.mk-labs.cloud"
|
||||||
|
netbox_token: "{{ vault_netbox_token }}"
|
||||||
|
netbox_api: "{{ netbox_url }}/api"
|
||||||
|
headers:
|
||||||
|
Authorization: "Token {{ netbox_token }}"
|
||||||
|
Content-Type: "application/json"
|
||||||
|
Accept: "application/json"
|
||||||
|
|
||||||
|
# ── Tag definitions ───────────────────────────────────────────────────
|
||||||
|
tags:
|
||||||
|
# Infrastructure type
|
||||||
|
- name: k8s
|
||||||
|
slug: k8s
|
||||||
|
color: "2196f3" # blue
|
||||||
|
description: "Workload running in the fastpass Kubernetes cluster"
|
||||||
|
- name: vm
|
||||||
|
slug: vm
|
||||||
|
color: "4caf50" # green
|
||||||
|
description: "Traditional VM or LXC on Proxmox"
|
||||||
|
# Service tier
|
||||||
|
- name: platform
|
||||||
|
slug: platform
|
||||||
|
color: "9c27b0" # purple
|
||||||
|
description: "Platform/infrastructure service (not user-facing)"
|
||||||
|
- name: application
|
||||||
|
slug: application
|
||||||
|
color: "ff9800" # orange
|
||||||
|
description: "User-facing application workload"
|
||||||
|
# Service categories
|
||||||
|
- name: monitoring
|
||||||
|
slug: monitoring
|
||||||
|
color: "607d8b" # grey
|
||||||
|
description: "Metrics, logging, alerting"
|
||||||
|
- name: auth
|
||||||
|
slug: auth
|
||||||
|
color: "607d8b"
|
||||||
|
description: "Authentication and SSO"
|
||||||
|
- name: gitops
|
||||||
|
slug: gitops
|
||||||
|
color: "607d8b"
|
||||||
|
description: "GitOps and CI/CD"
|
||||||
|
- name: dashboard
|
||||||
|
slug: dashboard
|
||||||
|
color: "607d8b"
|
||||||
|
description: "Dashboard and portal services"
|
||||||
|
- name: storage
|
||||||
|
slug: storage
|
||||||
|
color: "607d8b"
|
||||||
|
description: "Storage and file services"
|
||||||
|
- name: dns
|
||||||
|
slug: dns
|
||||||
|
color: "607d8b"
|
||||||
|
description: "DNS and name resolution"
|
||||||
|
- name: automation
|
||||||
|
slug: automation
|
||||||
|
color: "607d8b"
|
||||||
|
description: "Automation and orchestration"
|
||||||
|
- name: networking
|
||||||
|
slug: networking
|
||||||
|
color: "607d8b"
|
||||||
|
description: "Network infrastructure services"
|
||||||
|
- name: inference
|
||||||
|
slug: inference
|
||||||
|
color: "607d8b"
|
||||||
|
description: "AI/ML inference workloads"
|
||||||
|
|
||||||
|
# ── Custom field definitions ──────────────────────────────────────────
|
||||||
|
# object_types use app_label.model format
|
||||||
|
custom_fields:
|
||||||
|
- name: hostnames
|
||||||
|
label: Hostnames
|
||||||
|
type: longtext
|
||||||
|
object_types:
|
||||||
|
- ipam.ipaddress
|
||||||
|
- virtualization.virtualmachine
|
||||||
|
description: "All DNS names that resolve to this service (comma-separated)"
|
||||||
|
ui_visible: always
|
||||||
|
ui_editable: yes
|
||||||
|
|
||||||
|
- name: namespace
|
||||||
|
label: Namespace
|
||||||
|
type: text
|
||||||
|
object_types:
|
||||||
|
- ipam.ipaddress
|
||||||
|
- virtualization.virtualmachine
|
||||||
|
description: "Kubernetes namespace (blank for VM-based services)"
|
||||||
|
ui_visible: always
|
||||||
|
ui_editable: yes
|
||||||
|
|
||||||
|
- name: managed_by
|
||||||
|
label: Managed By
|
||||||
|
type: select
|
||||||
|
object_types:
|
||||||
|
- ipam.ipaddress
|
||||||
|
- virtualization.virtualmachine
|
||||||
|
description: "How this service is managed"
|
||||||
|
choices:
|
||||||
|
- ArgoCD
|
||||||
|
- Ansible
|
||||||
|
- Manual
|
||||||
|
ui_visible: always
|
||||||
|
ui_editable: yes
|
||||||
|
|
||||||
|
- name: thematic_name
|
||||||
|
label: Thematic Name
|
||||||
|
type: text
|
||||||
|
object_types:
|
||||||
|
- ipam.ipaddress
|
||||||
|
- virtualization.virtualmachine
|
||||||
|
description: "Disney/Magic Kingdom thematic hostname for this service"
|
||||||
|
ui_visible: always
|
||||||
|
ui_editable: yes
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
|
||||||
|
# ── Tags ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
- name: Fetch existing tags
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/tags/?limit=200"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
return_content: true
|
||||||
|
status_code: 200
|
||||||
|
register: existing_tags_response
|
||||||
|
|
||||||
|
- name: Set existing tag slugs fact
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
existing_tag_slugs: "{{ existing_tags_response.json.results | map(attribute='slug') | list }}"
|
||||||
|
|
||||||
|
- name: Create tags
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/tags/"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
slug: "{{ item.slug }}"
|
||||||
|
color: "{{ item.color }}"
|
||||||
|
description: "{{ item.description }}"
|
||||||
|
status_code: 201
|
||||||
|
loop: "{{ tags }}"
|
||||||
|
when: item.slug not in existing_tag_slugs
|
||||||
|
register: tag_creation
|
||||||
|
changed_when: tag_creation.status == 201
|
||||||
|
|
||||||
|
# ── Custom Fields ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
- name: Fetch existing custom fields
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-fields/?limit=200"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
return_content: true
|
||||||
|
status_code: 200
|
||||||
|
register: existing_cf_response
|
||||||
|
|
||||||
|
- name: Set existing custom field names fact
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
existing_cf_names: "{{ existing_cf_response.json.results | map(attribute='name') | list }}"
|
||||||
|
|
||||||
|
- name: Create custom field — hostnames
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-fields/"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: hostnames
|
||||||
|
label: Hostnames
|
||||||
|
type: longtext
|
||||||
|
object_types: "{{ custom_fields[0].object_types }}"
|
||||||
|
description: "{{ custom_fields[0].description }}"
|
||||||
|
ui_visible: always
|
||||||
|
ui_editable: yes
|
||||||
|
status_code: 201
|
||||||
|
when: "'hostnames' not in existing_cf_names"
|
||||||
|
register: cf_hostnames
|
||||||
|
changed_when: cf_hostnames.status == 201
|
||||||
|
|
||||||
|
- name: Create custom field — namespace
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-fields/"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: namespace
|
||||||
|
label: Namespace
|
||||||
|
type: text
|
||||||
|
object_types: "{{ custom_fields[1].object_types }}"
|
||||||
|
description: "{{ custom_fields[1].description }}"
|
||||||
|
ui_visible: always
|
||||||
|
ui_editable: yes
|
||||||
|
status_code: 201
|
||||||
|
when: "'namespace' not in existing_cf_names"
|
||||||
|
register: cf_namespace
|
||||||
|
changed_when: cf_namespace.status == 201
|
||||||
|
|
||||||
|
- name: Create choice set for managed_by field
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-field-choice-sets/"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: managed-by-choices
|
||||||
|
extra_choices:
|
||||||
|
- - ArgoCD
|
||||||
|
- ArgoCD
|
||||||
|
- - Ansible
|
||||||
|
- Ansible
|
||||||
|
- - Manual
|
||||||
|
- Manual
|
||||||
|
status_code: [201, 400]
|
||||||
|
register: choice_set
|
||||||
|
changed_when: choice_set.status == 201
|
||||||
|
|
||||||
|
- name: Fetch choice set ID
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-field-choice-sets/?name=managed-by-choices"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
return_content: true
|
||||||
|
status_code: 200
|
||||||
|
register: choice_set_response
|
||||||
|
|
||||||
|
- name: Create custom field — managed_by
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-fields/"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: managed_by
|
||||||
|
label: Managed By
|
||||||
|
type: select
|
||||||
|
object_types: "{{ custom_fields[2].object_types }}"
|
||||||
|
description: "{{ custom_fields[2].description }}"
|
||||||
|
choice_set: "{{ choice_set_response.json.results[0].id }}"
|
||||||
|
ui_visible: always
|
||||||
|
ui_editable: yes
|
||||||
|
status_code: 201
|
||||||
|
when: "'managed_by' not in existing_cf_names"
|
||||||
|
register: cf_managed_by
|
||||||
|
changed_when: cf_managed_by.status == 201
|
||||||
|
|
||||||
|
- name: Create custom field — thematic_name
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-fields/"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: thematic_name
|
||||||
|
label: Thematic Name
|
||||||
|
type: text
|
||||||
|
object_types: "{{ custom_fields[3].object_types }}"
|
||||||
|
description: "{{ custom_fields[3].description }}"
|
||||||
|
ui_visible: always
|
||||||
|
ui_editable: yes
|
||||||
|
status_code: 201
|
||||||
|
when: "'thematic_name' not in existing_cf_names"
|
||||||
|
register: cf_thematic_name
|
||||||
|
changed_when: cf_thematic_name.status == 201
|
||||||
|
|
||||||
|
# ── Provisioning Pipeline Fields ──────────────────────────────────────
|
||||||
|
# These fields drive the NetBox → n8n → Terraform pipeline.
|
||||||
|
# proxmox_datastore already exists — PATCH it to add utilidor choice.
|
||||||
|
# data_disk_enabled and data_disk_size_gb are new POSTs.
|
||||||
|
|
||||||
|
- name: Fetch proxmox_datastore field ID
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-fields/?name=proxmox_datastore"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
return_content: true
|
||||||
|
status_code: 200
|
||||||
|
register: proxmox_datastore_cf_response
|
||||||
|
|
||||||
|
- name: Fetch proxmox-datastore-choices choice set ID
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-field-choice-sets/?name=proxmox-datastore-choices"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
return_content: true
|
||||||
|
status_code: 200
|
||||||
|
register: datastore_choice_set_response
|
||||||
|
|
||||||
|
- name: Create proxmox-datastore-choices choice set if missing
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-field-choice-sets/"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: proxmox-datastore-choices
|
||||||
|
extra_choices:
|
||||||
|
- - liberty-tree
|
||||||
|
- liberty-tree
|
||||||
|
- - utilidor
|
||||||
|
- utilidor
|
||||||
|
status_code: [201, 400]
|
||||||
|
register: datastore_choice_set_create
|
||||||
|
changed_when: datastore_choice_set_create.status == 201
|
||||||
|
when: datastore_choice_set_response.json.count == 0
|
||||||
|
|
||||||
|
- name: Re-fetch proxmox-datastore-choices choice set ID after possible creation
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-field-choice-sets/?name=proxmox-datastore-choices"
|
||||||
|
method: GET
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
return_content: true
|
||||||
|
status_code: 200
|
||||||
|
register: datastore_choice_set_response
|
||||||
|
|
||||||
|
- name: Patch proxmox_datastore field to use choice set with utilidor
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-fields/{{ proxmox_datastore_cf_response.json.results[0].id }}/"
|
||||||
|
method: PATCH
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
choice_set: "{{ datastore_choice_set_response.json.results[0].id }}"
|
||||||
|
status_code: 200
|
||||||
|
when: proxmox_datastore_cf_response.json.count > 0
|
||||||
|
register: cf_datastore_patch
|
||||||
|
changed_when: cf_datastore_patch.status == 200
|
||||||
|
|
||||||
|
- name: Create custom field — data_disk_enabled
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-fields/"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: data_disk_enabled
|
||||||
|
label: Data Disk Enabled
|
||||||
|
type: boolean
|
||||||
|
object_types:
|
||||||
|
- virtualization.virtualmachine
|
||||||
|
description: "When true, Terraform provisions a second disk for application data storage."
|
||||||
|
default: false
|
||||||
|
ui_visible: always
|
||||||
|
ui_editable: yes
|
||||||
|
status_code: 201
|
||||||
|
when: "'data_disk_enabled' not in existing_cf_names"
|
||||||
|
register: cf_data_disk_enabled
|
||||||
|
changed_when: cf_data_disk_enabled.status == 201
|
||||||
|
|
||||||
|
- name: Create custom field — data_disk_size_gb
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ netbox_api }}/extras/custom-fields/"
|
||||||
|
method: POST
|
||||||
|
headers: "{{ headers }}"
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
name: data_disk_size_gb
|
||||||
|
label: Data Disk Size (GB)
|
||||||
|
type: integer
|
||||||
|
object_types:
|
||||||
|
- virtualization.virtualmachine
|
||||||
|
description: "Size in GB for the optional second data disk. Only used when data_disk_enabled is true."
|
||||||
|
ui_visible: always
|
||||||
|
ui_editable: yes
|
||||||
|
status_code: 201
|
||||||
|
when: "'data_disk_size_gb' not in existing_cf_names"
|
||||||
|
register: cf_data_disk_size
|
||||||
|
changed_when: cf_data_disk_size.status == 201
|
||||||
|
|
||||||
|
# ── Summary ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "Tags created: {{ tag_creation.results | selectattr('status', 'equalto', 201) | list | length }}"
|
||||||
|
- "Tags skipped (already exist): {{ tag_creation.results | selectattr('skipped', 'defined') | list | length }}"
|
||||||
|
- "Custom fields configured: hostnames, namespace, managed_by, thematic_name, proxmox_datastore (patched), data_disk_enabled, data_disk_size_gb"
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# FILE: playbooks/day1_deploy_monitoring.yml
|
|
||||||
# DESCRIPTION: Deploys Prometheus + Grafana + Node Exporter + Alertmanager
|
|
||||||
# on cinderella-castle (10.1.71.31)
|
|
||||||
# Runs: common → docker-host → monitoring
|
|
||||||
#
|
|
||||||
# USAGE:
|
|
||||||
# ansible-playbook -i inventory.yml playbooks/day1_deploy_monitoring.yml
|
|
||||||
# ansible-playbook -i inventory.yml playbooks/day1_deploy_monitoring.yml --limit cinderella_castle
|
|
||||||
#
|
|
||||||
# SECRETS REQUIRED IN VAULT (group_vars/all/vault):
|
|
||||||
# vault_grafana_admin_password
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
- name: Deploy monitoring stack on cinderella-castle
|
|
||||||
hosts: cinderella-castle
|
|
||||||
become: true
|
|
||||||
|
|
||||||
vars:
|
|
||||||
grafana_admin_password: "{{ vault_grafana_admin_password }}"
|
|
||||||
|
|
||||||
roles:
|
|
||||||
- role: common
|
|
||||||
- role: docker-host
|
|
||||||
- role: monitoring
|
|
||||||
35
ansible/playbooks/day1_deploy_ollama.yml
Normal file
35
ansible/playbooks/day1_deploy_ollama.yml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# FILE: ansible/playbooks/day1_deploy_ollama.yml
|
||||||
|
# DESCRIPTION: Deploys Ollama with ROCm GPU acceleration on astro-orbiter.
|
||||||
|
# Assumes day0_baseline.yml has already run (common role complete).
|
||||||
|
# PCIe passthrough for the RX 5700 must be configured in Proxmox
|
||||||
|
# and the GPU must be visible to the VM before running this playbook.
|
||||||
|
#
|
||||||
|
# Pre-flight check:
|
||||||
|
# ssh wed@astro-orbiter 'lspci | grep -i amd'
|
||||||
|
# Should show the RX 5700 before proceeding.
|
||||||
|
#
|
||||||
|
# Usage (from ansible/ directory):
|
||||||
|
# ansible-playbook playbooks/day1_deploy_ollama.yml
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Deploy Ollama with ROCm on astro-orbiter
|
||||||
|
hosts: astro-orbiter
|
||||||
|
become: true
|
||||||
|
|
||||||
|
pre_tasks:
|
||||||
|
- name: Verify AMD GPU is visible to the VM
|
||||||
|
command: lspci
|
||||||
|
register: lspci_output
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Fail if no AMD GPU detected
|
||||||
|
fail:
|
||||||
|
msg: >
|
||||||
|
No AMD GPU detected via lspci. Verify PCIe passthrough is configured
|
||||||
|
in Proxmox and the RX 5700 is visible to the VM before proceeding.
|
||||||
|
when: "'AMD' not in lspci_output.stdout and 'Radeon' not in lspci_output.stdout"
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- ollama
|
||||||
7
ansible/playbooks/day1_docker.yml
Normal file
7
ansible/playbooks/day1_docker.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
- name: Apply common role
|
||||||
|
hosts: "{{ target | default('all') }}"
|
||||||
|
become: true
|
||||||
|
roles:
|
||||||
|
- common
|
||||||
|
- docker-host
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
common_timezone: America/Chicago
|
common_timezone: America/Chicago
|
||||||
|
|
||||||
common_ntp_server: 10.1.71.33 # sundial
|
common_ntp_server: "sundial.local.mk-labs.cloud"
|
||||||
|
|
||||||
common_packages:
|
common_packages:
|
||||||
- curl
|
- curl
|
||||||
@@ -20,3 +20,15 @@ common_packages:
|
|||||||
- lsb-release
|
- lsb-release
|
||||||
- net-tools
|
- net-tools
|
||||||
- dnsutils
|
- dnsutils
|
||||||
|
- lvm2
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# LVM root volume expansion
|
||||||
|
# Override common_root_pv/vg/lv per host if the template uses different names.
|
||||||
|
# Ubuntu 24.04 cloud templates use ubuntu-vg/ubuntu-lv by default.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
common_expand_root_lvm: true
|
||||||
|
common_root_pv: /dev/sda3 # Ubuntu cloud-init partition layout
|
||||||
|
common_root_vg: ubuntu-vg
|
||||||
|
common_root_lv: ubuntu-lv
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
|
---
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# FILE: roles/common/handlers/main.yml
|
# FILE: roles/common/handlers/main.yml
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
- name: restart chrony
|
- name: restart chrony
|
||||||
systemd:
|
systemd:
|
||||||
name: chrony
|
name: chrony
|
||||||
state: restarted
|
state: restarted
|
||||||
|
|
||||||
- name: restart sshd
|
|
||||||
systemd:
|
|
||||||
name: "{{ 'ssh' if ansible_os_family == 'Debian' else 'sshd' }}"
|
|
||||||
state: restarted
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
---
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# FILE: roles/common/tasks/main.yml
|
# FILE: roles/common/tasks/main.yml
|
||||||
# DESCRIPTION: Baseline configuration applied to all managed hosts.
|
# DESCRIPTION: Baseline configuration applied to all managed Ubuntu hosts.
|
||||||
# Handles hostname, timezone, core packages, NTP, and
|
# Handles hostname, timezone, core packages, NTP, ansible user,
|
||||||
# ansible user setup.
|
# and optional LVM root volume expansion.
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
- name: Set hostname
|
- name: Set hostname
|
||||||
@@ -10,7 +11,7 @@
|
|||||||
name: "{{ inventory_hostname | replace('_', '-') }}"
|
name: "{{ inventory_hostname | replace('_', '-') }}"
|
||||||
|
|
||||||
- name: Set timezone
|
- name: Set timezone
|
||||||
community.general.timezone:
|
timezone:
|
||||||
name: "{{ common_timezone }}"
|
name: "{{ common_timezone }}"
|
||||||
|
|
||||||
- name: Update apt cache
|
- name: Update apt cache
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
state: started
|
state: started
|
||||||
enabled: yes
|
enabled: yes
|
||||||
|
|
||||||
- name: Ensure ansible user has sudo without password
|
- name: Ensure ansible user has passwordless sudo
|
||||||
lineinfile:
|
lineinfile:
|
||||||
path: /etc/sudoers.d/{{ ansible_user }}
|
path: /etc/sudoers.d/{{ ansible_user }}
|
||||||
line: "{{ ansible_user }} ALL=(ALL) NOPASSWD:ALL"
|
line: "{{ ansible_user }} ALL=(ALL) NOPASSWD:ALL"
|
||||||
@@ -49,33 +50,28 @@
|
|||||||
mode: '0440'
|
mode: '0440'
|
||||||
validate: 'visudo -cf %s'
|
validate: 'visudo -cf %s'
|
||||||
|
|
||||||
# --- Step-CA SSH certificate enrollment ---
|
# ------------------------------------------------------------------------------
|
||||||
- name: Enroll host in step-ca SSH CA
|
# LVM root volume expansion
|
||||||
include_tasks: step_ca_client.yml
|
# Extends the root PV to the full disk size and grows the LV + filesystem.
|
||||||
|
# This codifies what was previously done manually after provisioning.
|
||||||
|
# Runs only when common_expand_root_lvm is true (default: true).
|
||||||
|
# Safe to re-run — pvresize and lvextend are idempotent when already at max.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
- name: Create authorized principals directory
|
- name: Expand root PV to full disk size
|
||||||
ansible.builtin.file:
|
command: pvresize {{ common_root_pv }}
|
||||||
path: /etc/ssh/auth_principals
|
register: pvresize_result
|
||||||
state: directory
|
changed_when: "'changed' in pvresize_result.stdout or pvresize_result.rc == 0"
|
||||||
owner: root
|
when: common_expand_root_lvm | bool
|
||||||
group: root
|
|
||||||
mode: '0755'
|
|
||||||
|
|
||||||
- name: Create authorized principals files
|
|
||||||
ansible.builtin.copy:
|
|
||||||
content: "{{ item.principals | join('\n') }}\n"
|
|
||||||
dest: "/etc/ssh/auth_principals/{{ item.local_user }}"
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
loop: "{{ step_ca_principal_mappings }}"
|
|
||||||
notify: restart sshd
|
|
||||||
|
|
||||||
- name: Configure sshd AuthorizedPrincipalsFile
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
path: /etc/ssh/sshd_config
|
|
||||||
line: "AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u"
|
|
||||||
regexp: "^#?AuthorizedPrincipalsFile"
|
|
||||||
state: present
|
|
||||||
notify: restart sshd
|
|
||||||
|
|
||||||
|
- name: Extend root LV to 100% of free VG space
|
||||||
|
lvol:
|
||||||
|
vg: "{{ common_root_vg }}"
|
||||||
|
lv: "{{ common_root_lv }}"
|
||||||
|
size: +100%FREE
|
||||||
|
resizefs: yes
|
||||||
|
when:
|
||||||
|
- common_expand_root_lvm | bool
|
||||||
|
ignore_errors: yes
|
||||||
|
# ignore_errors because lvextend returns non-zero when already at max size.
|
||||||
|
# resizefs: yes handles the resize2fs call inline — no separate task needed.
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# FILE: roles/monitoring/defaults/main.yml
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Directory layout on cinderella-castle
|
|
||||||
monitoring_base_dir: /opt/docker/monitoring
|
|
||||||
|
|
||||||
# Data disk — LVM on /dev/sdb, mounted at this path for Prometheus TSDB
|
|
||||||
prometheus_data_dir: /var/lib/prometheus
|
|
||||||
prometheus_vg: prometheus-vg
|
|
||||||
prometheus_lv: prometheus-lv
|
|
||||||
|
|
||||||
# Prometheus retention
|
|
||||||
prometheus_retention_time: 15d
|
|
||||||
prometheus_retention_size: 58GB # 64GB disk, leaving headroom
|
|
||||||
|
|
||||||
# Scrape interval
|
|
||||||
prometheus_scrape_interval: 30s
|
|
||||||
|
|
||||||
# Grafana
|
|
||||||
grafana_port: 3000
|
|
||||||
grafana_admin_user: admin
|
|
||||||
|
|
||||||
# Service DNS (via Traefik)
|
|
||||||
grafana_hostname: grafana.local.mk-labs.cloud
|
|
||||||
|
|
||||||
# Trusted proxy (lightning-lane)
|
|
||||||
trusted_proxy_ip: 10.1.71.35
|
|
||||||
|
|
||||||
# Prometheus port (internal, not exposed via Traefik)
|
|
||||||
prometheus_port: 9090
|
|
||||||
|
|
||||||
# Alertmanager port (internal)
|
|
||||||
alertmanager_port: 9093
|
|
||||||
|
|
||||||
# Node exporter port
|
|
||||||
node_exporter_port: 9100
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# FILE: roles/monitoring/handlers/main.yml
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
- name: restart monitoring
|
|
||||||
community.docker.docker_compose_v2:
|
|
||||||
project_src: "{{ monitoring_base_dir }}"
|
|
||||||
state: present
|
|
||||||
recreate: always
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
---
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# FILE: roles/monitoring/tasks/main.yml
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# ── Data disk (LVM) ───────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
- name: Install LVM2
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name: lvm2
|
|
||||||
state: present
|
|
||||||
update_cache: true
|
|
||||||
|
|
||||||
- name: Check if PV already exists on /dev/sdb
|
|
||||||
ansible.builtin.command: pvs /dev/sdb
|
|
||||||
register: pvs_result
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Check if disk has a partition table signature
|
|
||||||
ansible.builtin.command: wipefs /dev/sdb
|
|
||||||
register: wipefs_check
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
when: pvs_result.rc != 0
|
|
||||||
|
|
||||||
- name: Wipe GPT protective partition signature from /dev/sdb
|
|
||||||
ansible.builtin.command: wipefs -a /dev/sdb
|
|
||||||
when:
|
|
||||||
- pvs_result.rc != 0
|
|
||||||
- wipefs_check.stdout | length > 0
|
|
||||||
|
|
||||||
- name: Create LVM volume group on /dev/sdb
|
|
||||||
community.general.lvg:
|
|
||||||
vg: prometheus-vg
|
|
||||||
pvs: /dev/sdb
|
|
||||||
state: present
|
|
||||||
when: pvs_result.rc != 0
|
|
||||||
|
|
||||||
- name: Check if logical volume already exists
|
|
||||||
ansible.builtin.command: lvs /dev/prometheus-vg/prometheus-lv
|
|
||||||
register: lvs_result
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Create logical volume for Prometheus TSDB
|
|
||||||
community.general.lvol:
|
|
||||||
vg: prometheus-vg
|
|
||||||
lv: prometheus-lv
|
|
||||||
size: 100%FREE
|
|
||||||
state: present
|
|
||||||
shrink: false
|
|
||||||
when: lvs_result.rc != 0
|
|
||||||
|
|
||||||
- name: Format logical volume as ext4
|
|
||||||
community.general.filesystem:
|
|
||||||
fstype: ext4
|
|
||||||
dev: /dev/prometheus-vg/prometheus-lv
|
|
||||||
|
|
||||||
- name: Create Prometheus data directory mount point
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ prometheus_data_dir }}"
|
|
||||||
state: directory
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
- name: Mount Prometheus logical volume
|
|
||||||
ansible.posix.mount:
|
|
||||||
path: "{{ prometheus_data_dir }}"
|
|
||||||
src: /dev/prometheus-vg/prometheus-lv
|
|
||||||
fstype: ext4
|
|
||||||
opts: defaults
|
|
||||||
state: mounted
|
|
||||||
|
|
||||||
- name: Set ownership of Prometheus data directory
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ prometheus_data_dir }}"
|
|
||||||
state: directory
|
|
||||||
owner: "65534" # nobody — Prometheus container runs as this UID
|
|
||||||
group: "65534"
|
|
||||||
mode: "0755"
|
|
||||||
|
|
||||||
# ── Directory layout ───────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
- name: Create monitoring directory structure
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ item }}"
|
|
||||||
state: directory
|
|
||||||
owner: "{{ ansible_user }}"
|
|
||||||
group: docker
|
|
||||||
mode: "0775"
|
|
||||||
loop:
|
|
||||||
- "{{ monitoring_base_dir }}"
|
|
||||||
- "{{ monitoring_base_dir }}/prometheus"
|
|
||||||
- "{{ monitoring_base_dir }}/grafana"
|
|
||||||
- "{{ monitoring_base_dir }}/alertmanager"
|
|
||||||
- "{{ monitoring_base_dir }}/pve-exporter"
|
|
||||||
- "{{ monitoring_base_dir }}/snmp-exporter"
|
|
||||||
|
|
||||||
# ── Config files ───────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
- name: Deploy Prometheus configuration
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: prometheus.yaml.j2
|
|
||||||
dest: "{{ monitoring_base_dir }}/prometheus/prometheus.yaml"
|
|
||||||
owner: "{{ ansible_user }}"
|
|
||||||
group: docker
|
|
||||||
mode: "0644"
|
|
||||||
notify: restart monitoring
|
|
||||||
|
|
||||||
- name: Deploy Alertmanager configuration
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: alertmanager.yml.j2
|
|
||||||
dest: "{{ monitoring_base_dir }}/alertmanager/alertmanager.yml"
|
|
||||||
owner: "{{ ansible_user }}"
|
|
||||||
group: docker
|
|
||||||
mode: "0644"
|
|
||||||
notify: restart monitoring
|
|
||||||
|
|
||||||
- name: Deploy Grafana provisioning datasource
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: grafana-datasource.yml.j2
|
|
||||||
dest: "{{ monitoring_base_dir }}/grafana/datasource.yml"
|
|
||||||
owner: "{{ ansible_user }}"
|
|
||||||
group: docker
|
|
||||||
mode: "0644"
|
|
||||||
notify: restart monitoring
|
|
||||||
|
|
||||||
- name: Deploy PVE exporter credentials file
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: pve.yml.j2
|
|
||||||
dest: "{{ monitoring_base_dir }}/pve-exporter/pve.yml"
|
|
||||||
owner: "{{ ansible_user }}"
|
|
||||||
group: docker
|
|
||||||
mode: "0644"
|
|
||||||
no_log: true
|
|
||||||
notify: restart monitoring
|
|
||||||
|
|
||||||
# ── SNMP exporter config ───────────────────────────────────────────────────────
|
|
||||||
# Extract the default snmp.yml from the image (contains full if_mib metrics
|
|
||||||
# mapping) then inject the mk-labs community auth into the auths section.
|
|
||||||
|
|
||||||
- name: Extract default snmp.yml from snmp-exporter image
|
|
||||||
ansible.builtin.shell:
|
|
||||||
cmd: >
|
|
||||||
docker run --rm --entrypoint cat prom/snmp-exporter:latest
|
|
||||||
/etc/snmp_exporter/snmp.yml
|
|
||||||
> {{ monitoring_base_dir }}/snmp-exporter/snmp.yml
|
|
||||||
changed_when: true
|
|
||||||
|
|
||||||
- name: Inject mk_labs_v2c auth into snmp.yml
|
|
||||||
ansible.builtin.shell:
|
|
||||||
cmd: |
|
|
||||||
python3 - << 'EOF'
|
|
||||||
path = '{{ monitoring_base_dir }}/snmp-exporter/snmp.yml'
|
|
||||||
with open(path, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
auth = (
|
|
||||||
' mk_labs_v2c:\n'
|
|
||||||
' community: {{ vault_snmp_community }}\n'
|
|
||||||
' security_level: noAuthNoPriv\n'
|
|
||||||
' auth_protocol: MD5\n'
|
|
||||||
' priv_protocol: DES\n'
|
|
||||||
' version: 2\n'
|
|
||||||
)
|
|
||||||
if 'mk_labs_v2c' not in content:
|
|
||||||
content = content.replace('\nmodules:\n', '\n' + auth + 'modules:\n', 1)
|
|
||||||
with open(path, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
print('injected')
|
|
||||||
else:
|
|
||||||
print('already present')
|
|
||||||
EOF
|
|
||||||
register: snmp_inject
|
|
||||||
changed_when: "'injected' in snmp_inject.stdout"
|
|
||||||
no_log: true
|
|
||||||
notify: restart monitoring
|
|
||||||
|
|
||||||
# ── Compose stack ──────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
- name: Deploy monitoring compose file from boilerplate
|
|
||||||
ansible.builtin.copy:
|
|
||||||
src: "{{ playbook_dir }}/../../boilerplates/monitoring/compose.yaml"
|
|
||||||
dest: "{{ monitoring_base_dir }}/compose.yaml"
|
|
||||||
owner: "{{ ansible_user }}"
|
|
||||||
group: docker
|
|
||||||
mode: "0644"
|
|
||||||
notify: restart monitoring
|
|
||||||
|
|
||||||
- name: Deploy .env with secrets from vault
|
|
||||||
ansible.builtin.template:
|
|
||||||
src: env.j2
|
|
||||||
dest: "{{ monitoring_base_dir }}/.env"
|
|
||||||
owner: "{{ ansible_user }}"
|
|
||||||
group: docker
|
|
||||||
mode: "0600"
|
|
||||||
notify: restart monitoring
|
|
||||||
|
|
||||||
- name: Start monitoring stack
|
|
||||||
community.docker.docker_compose_v2:
|
|
||||||
project_src: "{{ monitoring_base_dir }}"
|
|
||||||
state: present
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# ------------------------------------------------------------------------------
|
|
||||||
# FILE: roles/monitoring/templates/alertmanager.yml.j2
|
|
||||||
# DESCRIPTION: Alertmanager configuration — routing deferred (P.XX)
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
route:
|
|
||||||
receiver: blackhole
|
|
||||||
group_wait: 30s
|
|
||||||
group_interval: 5m
|
|
||||||
repeat_interval: 4h
|
|
||||||
|
|
||||||
receivers:
|
|
||||||
- name: blackhole
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# ------------------------------------------------------------------------------
|
|
||||||
# FILE: roles/monitoring/templates/env.j2
|
|
||||||
# DESCRIPTION: Environment file for monitoring compose stack
|
|
||||||
# Deployed to {{ monitoring_base_dir }}/.env
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
GRAFANA_ADMIN_USER={{ grafana_admin_user }}
|
|
||||||
GRAFANA_ADMIN_PASSWORD={{ grafana_admin_password }}
|
|
||||||
PROMETHEUS_RETENTION_TIME={{ prometheus_retention_time }}
|
|
||||||
PROMETHEUS_RETENTION_SIZE={{ prometheus_retention_size }}
|
|
||||||
TRUSTED_PROXIES={{ trusted_proxy_ip }}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
# ------------------------------------------------------------------------------
|
|
||||||
# FILE: roles/monitoring/templates/grafana-datasource.yml.j2
|
|
||||||
# DESCRIPTION: Auto-provisions Prometheus as Grafana default datasource
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
apiVersion: 1
|
|
||||||
|
|
||||||
datasources:
|
|
||||||
- name: Prometheus
|
|
||||||
type: prometheus
|
|
||||||
access: proxy
|
|
||||||
url: http://prometheus:9090
|
|
||||||
isDefault: true
|
|
||||||
editable: false
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
# ------------------------------------------------------------------------------
|
|
||||||
# FILE: ansible/playbooks/roles/monitoring/templates/prometheus.yaml.j2
|
|
||||||
# DESCRIPTION: Prometheus scrape configuration
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
global:
|
|
||||||
scrape_interval: 30s
|
|
||||||
evaluation_interval: 30s
|
|
||||||
|
|
||||||
alerting:
|
|
||||||
alertmanagers:
|
|
||||||
- static_configs:
|
|
||||||
- targets:
|
|
||||||
- alertmanager:9093
|
|
||||||
|
|
||||||
rule_files: []
|
|
||||||
|
|
||||||
scrape_configs:
|
|
||||||
|
|
||||||
# Prometheus self-monitoring
|
|
||||||
- job_name: prometheus
|
|
||||||
static_configs:
|
|
||||||
- targets:
|
|
||||||
- localhost:9090
|
|
||||||
|
|
||||||
# cinderella-castle node exporter (this host)
|
|
||||||
- job_name: node-cinderella-castle
|
|
||||||
static_configs:
|
|
||||||
- targets:
|
|
||||||
- 10.1.71.31:9100
|
|
||||||
labels:
|
|
||||||
hostname: cinderella-castle
|
|
||||||
|
|
||||||
# Proxmox nodes (node exporter on each hypervisor)
|
|
||||||
- job_name: proxmox-nodes
|
|
||||||
static_configs:
|
|
||||||
- targets:
|
|
||||||
- 10.1.71.11:9100
|
|
||||||
- 10.1.71.12:9100
|
|
||||||
- 10.1.71.13:9100
|
|
||||||
labels:
|
|
||||||
job: proxmox
|
|
||||||
|
|
||||||
# Traefik metrics
|
|
||||||
- job_name: traefik
|
|
||||||
static_configs:
|
|
||||||
- targets:
|
|
||||||
- 10.1.71.35:8080
|
|
||||||
labels:
|
|
||||||
hostname: lightning-lane
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# Proxmox VE Exporter — cluster-level metrics (VM states, storage, CPU)
|
|
||||||
# Uses the multi-target pattern: Prometheus passes the cluster address as a
|
|
||||||
# query parameter to the exporter, which proxies to the Proxmox API.
|
|
||||||
# One scrape job covers all three nodes via the cluster API endpoint.
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
- job_name: proxmox-cluster
|
|
||||||
metrics_path: /pve
|
|
||||||
params:
|
|
||||||
module: [default]
|
|
||||||
cluster: ["1"]
|
|
||||||
node: [""]
|
|
||||||
static_configs:
|
|
||||||
- targets:
|
|
||||||
- 10.1.71.11 # main-street-usa — cluster API endpoint
|
|
||||||
labels:
|
|
||||||
cluster: magic-kingdom
|
|
||||||
relabel_configs:
|
|
||||||
- source_labels: [__address__]
|
|
||||||
target_label: __param_target
|
|
||||||
- source_labels: [__param_target]
|
|
||||||
target_label: instance
|
|
||||||
- target_label: __address__
|
|
||||||
replacement: pve-exporter:9221
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# Pure Storage FlashArray -- utilidor (DEFERRED until Purity 6.7+)
|
|
||||||
#
|
|
||||||
# When utilidor is upgraded to Purity 6.7+, uncomment this job and remove
|
|
||||||
# the legacy exporter container. No container needed -- Prometheus scrapes
|
|
||||||
# the native OpenMetrics endpoint on the array directly over HTTPS.
|
|
||||||
#
|
|
||||||
# Also add vault_pure_utilidor_api_token to group_vars/all/vault and create
|
|
||||||
# a read-only API token on utilidor under System -> Users -> API Tokens.
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# - job_name: pure-flasharray-utilidor
|
|
||||||
# metrics_path: /metrics/array
|
|
||||||
# scheme: https
|
|
||||||
# tls_config:
|
|
||||||
# insecure_skip_verify: true
|
|
||||||
# authorization:
|
|
||||||
# credentials: {{ vault_pure_utilidor_api_token }}
|
|
||||||
# params:
|
|
||||||
# namespace: ["purefa"]
|
|
||||||
# static_configs:
|
|
||||||
# - targets:
|
|
||||||
# - utilidor.local.mk-labs.cloud
|
|
||||||
# labels:
|
|
||||||
# array: utilidor
|
|
||||||
# array_type: physical
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# UniFi Network — SNMP scrape jobs (one per device for clean Grafana filtering)
|
|
||||||
# All targets use the if_mib module for interface throughput, errors, and
|
|
||||||
# up/down state. Auth uses the mk_labs_v2c community string from vault.
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
|
|
||||||
- job_name: snmp-udm-pro
|
|
||||||
metrics_path: /snmp
|
|
||||||
params:
|
|
||||||
auth: [mk_labs_v2c]
|
|
||||||
module: [if_mib]
|
|
||||||
static_configs:
|
|
||||||
- targets:
|
|
||||||
- 192.168.1.1
|
|
||||||
labels:
|
|
||||||
device: world-drive
|
|
||||||
model: UDM-Pro
|
|
||||||
relabel_configs:
|
|
||||||
- source_labels: [__address__]
|
|
||||||
target_label: __param_target
|
|
||||||
- source_labels: [__param_target]
|
|
||||||
target_label: instance
|
|
||||||
- target_label: __address__
|
|
||||||
replacement: 10.1.71.31:9116
|
|
||||||
|
|
||||||
- job_name: snmp-usw-pro-aggregation
|
|
||||||
metrics_path: /snmp
|
|
||||||
params:
|
|
||||||
auth: [mk_labs_v2c]
|
|
||||||
module: [if_mib]
|
|
||||||
static_configs:
|
|
||||||
- targets:
|
|
||||||
- 192.168.1.123
|
|
||||||
labels:
|
|
||||||
device: usw-pro-aggregation
|
|
||||||
model: USW-Pro-Aggregation
|
|
||||||
relabel_configs:
|
|
||||||
- source_labels: [__address__]
|
|
||||||
target_label: __param_target
|
|
||||||
- source_labels: [__param_target]
|
|
||||||
target_label: instance
|
|
||||||
- target_label: __address__
|
|
||||||
replacement: 10.1.71.31:9116
|
|
||||||
|
|
||||||
- job_name: snmp-usw-pro-max-24
|
|
||||||
metrics_path: /snmp
|
|
||||||
scrape_interval: 60s
|
|
||||||
scrape_timeout: 55s
|
|
||||||
params:
|
|
||||||
auth: [mk_labs_v2c]
|
|
||||||
module: [if_mib]
|
|
||||||
static_configs:
|
|
||||||
- targets:
|
|
||||||
- 192.168.1.226
|
|
||||||
labels:
|
|
||||||
device: usw-pro-max-24
|
|
||||||
model: USW-Pro-Max-24
|
|
||||||
relabel_configs:
|
|
||||||
- source_labels: [__address__]
|
|
||||||
target_label: __param_target
|
|
||||||
- source_labels: [__param_target]
|
|
||||||
target_label: instance
|
|
||||||
- target_label: __address__
|
|
||||||
replacement: 10.1.71.31:9116
|
|
||||||
|
|
||||||
- job_name: snmp-usw-lite-16-poe
|
|
||||||
metrics_path: /snmp
|
|
||||||
params:
|
|
||||||
auth: [mk_labs_v2c]
|
|
||||||
module: [if_mib]
|
|
||||||
static_configs:
|
|
||||||
- targets:
|
|
||||||
- 192.168.1.136
|
|
||||||
labels:
|
|
||||||
device: usw-lite-16-poe
|
|
||||||
model: USW-Lite-16-PoE
|
|
||||||
relabel_configs:
|
|
||||||
- source_labels: [__address__]
|
|
||||||
target_label: __param_target
|
|
||||||
- source_labels: [__param_target]
|
|
||||||
target_label: instance
|
|
||||||
- target_label: __address__
|
|
||||||
replacement: 10.1.71.31:9116
|
|
||||||
|
|
||||||
- job_name: snmp-ups-tower
|
|
||||||
metrics_path: /snmp
|
|
||||||
params:
|
|
||||||
auth: [mk_labs_v2c]
|
|
||||||
module: [if_mib]
|
|
||||||
static_configs:
|
|
||||||
- targets:
|
|
||||||
- 192.168.1.233
|
|
||||||
labels:
|
|
||||||
device: ups-tower
|
|
||||||
model: UPS-Tower
|
|
||||||
relabel_configs:
|
|
||||||
- source_labels: [__address__]
|
|
||||||
target_label: __param_target
|
|
||||||
- source_labels: [__param_target]
|
|
||||||
target_label: instance
|
|
||||||
- target_label: __address__
|
|
||||||
replacement: 10.1.71.31:9116
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# ------------------------------------------------------------------------------
|
|
||||||
# FILE: ansible/playbooks/roles/monitoring/templates/pve.yml.j2
|
|
||||||
# DESCRIPTION: Proxmox VE Exporter credentials file.
|
|
||||||
# Mounted read-only into the pve-exporter container.
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
default:
|
|
||||||
user: prometheus@pve
|
|
||||||
token_name: prometheus-token
|
|
||||||
token_value: {{ vault_proxmox_prometheus_token }}
|
|
||||||
verify_ssl: false
|
|
||||||
28
ansible/playbooks/roles/ollama/defaults/main.yml
Normal file
28
ansible/playbooks/roles/ollama/defaults/main.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# FILE: roles/ollama/defaults/main.yml
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ROCm
|
||||||
|
ollama_rocm_version: "6.2" # ROCm release stream — update when upgrading
|
||||||
|
ollama_rocm_packages:
|
||||||
|
- hip-runtime-amd # Pulls in full ROCm stack
|
||||||
|
- rocminfo # GPU visibility verification tool
|
||||||
|
- rocm-smi-lib # ROCm system management interface
|
||||||
|
|
||||||
|
# Ollama
|
||||||
|
ollama_version: "latest"
|
||||||
|
ollama_port: 11434
|
||||||
|
ollama_host: "0.0.0.0" # Listen on all interfaces for cluster access
|
||||||
|
ollama_default_model: "qwen3:8b"
|
||||||
|
|
||||||
|
# HSA override — required for RX 5000-series (gfx1010 / RDNA 1)
|
||||||
|
# Tells ROCm to treat the GPU as gfx1010 which has supported LLVM targets.
|
||||||
|
ollama_hsa_override_gfx_version: "10.1.0"
|
||||||
|
|
||||||
|
# Data disk
|
||||||
|
ollama_setup_data_disk: true
|
||||||
|
ollama_data_disk: /dev/sdb
|
||||||
|
ollama_data_dir: /var/lib/ollama # Ollama default models path
|
||||||
|
ollama_data_vg: ollama-vg
|
||||||
|
ollama_data_lv: ollama-lv
|
||||||
20
ansible/playbooks/roles/ollama/handlers/main.yml
Normal file
20
ansible/playbooks/roles/ollama/handlers/main.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# FILE: roles/ollama/handlers/main.yml
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: update apt cache
|
||||||
|
apt:
|
||||||
|
update_cache: yes
|
||||||
|
|
||||||
|
- name: update initramfs
|
||||||
|
command: update-initramfs -u
|
||||||
|
|
||||||
|
- name: reload systemd
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
- name: restart ollama
|
||||||
|
systemd:
|
||||||
|
name: ollama
|
||||||
|
state: restarted
|
||||||
229
ansible/playbooks/roles/ollama/tasks/main.yml
Normal file
229
ansible/playbooks/roles/ollama/tasks/main.yml
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
---
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# FILE: roles/ollama/tasks/main.yml
|
||||||
|
# DESCRIPTION: Deploys Ollama with AMD ROCm GPU acceleration on Ubuntu 24.04.
|
||||||
|
# Handles:
|
||||||
|
# - ROCm repository and driver stack installation
|
||||||
|
# - Data disk preparation (LVM, ext4, persistent mount)
|
||||||
|
# - Ollama installation and systemd service configuration
|
||||||
|
# - HSA override for RX 5000-series (gfx1010) compatibility
|
||||||
|
# - Initial model pull
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# ROCm prerequisites
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Install ROCm prerequisite packages
|
||||||
|
apt:
|
||||||
|
name:
|
||||||
|
- wget
|
||||||
|
- gnupg
|
||||||
|
- ca-certificates
|
||||||
|
- lvm2
|
||||||
|
state: present
|
||||||
|
update_cache: yes
|
||||||
|
|
||||||
|
- name: Add ROCm apt repository signing key
|
||||||
|
apt_key:
|
||||||
|
url: https://repo.radeon.com/rocm/rocm.gpg.key
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Add ROCm apt repository
|
||||||
|
apt_repository:
|
||||||
|
repo: "deb [arch=amd64] https://repo.radeon.com/rocm/apt/{{ ollama_rocm_version }} {{ ansible_distribution_release }} main"
|
||||||
|
state: present
|
||||||
|
filename: rocm
|
||||||
|
notify: update apt cache
|
||||||
|
|
||||||
|
- name: Flush handlers to update apt cache before ROCm install
|
||||||
|
meta: flush_handlers
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# ROCm driver stack
|
||||||
|
# hip-runtime-amd pulls in the full ROCm stack as a dependency.
|
||||||
|
# rocminfo is used to verify GPU visibility after install.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Install ROCm packages
|
||||||
|
apt:
|
||||||
|
name: "{{ ollama_rocm_packages }}"
|
||||||
|
state: present
|
||||||
|
notify: update initramfs
|
||||||
|
|
||||||
|
- name: Create /opt/rocm symlink to versioned directory
|
||||||
|
file:
|
||||||
|
src: "/opt/rocm-{{ ollama_rocm_version }}.0"
|
||||||
|
dest: /opt/rocm
|
||||||
|
state: link
|
||||||
|
force: false
|
||||||
|
|
||||||
|
- name: Add ROCm binaries to system PATH
|
||||||
|
copy:
|
||||||
|
dest: /etc/profile.d/rocm.sh
|
||||||
|
content: |
|
||||||
|
export PATH=$PATH:/opt/rocm/bin
|
||||||
|
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/rocm/lib
|
||||||
|
mode: '0644'
|
||||||
|
|
||||||
|
- name: Add wed user to render and video groups for GPU access
|
||||||
|
user:
|
||||||
|
name: "{{ ansible_user }}"
|
||||||
|
groups:
|
||||||
|
- render
|
||||||
|
- video
|
||||||
|
append: yes
|
||||||
|
|
||||||
|
- name: Add ollama user to render and video groups (created by Ollama installer)
|
||||||
|
user:
|
||||||
|
name: ollama
|
||||||
|
groups:
|
||||||
|
- render
|
||||||
|
- video
|
||||||
|
append: yes
|
||||||
|
ignore_errors: yes
|
||||||
|
# ollama user does not exist yet at this point — Ollama installer creates it.
|
||||||
|
# This task re-runs after install via the post-install handler.
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Data disk — LVM setup on /dev/sdb
|
||||||
|
# Follows the same pattern as the monitoring role (cinderella-castle).
|
||||||
|
# Wipes any stale GPT/partition signatures before creating the PV to avoid
|
||||||
|
# the "GPT protective partition" error encountered during monitoring deploy.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Wipe any existing partition signatures from data disk
|
||||||
|
command: wipefs -a {{ ollama_data_disk }}
|
||||||
|
args:
|
||||||
|
creates: /dev/{{ ollama_data_vg }}
|
||||||
|
when: ollama_setup_data_disk | bool
|
||||||
|
|
||||||
|
- name: Create LVM physical volume on data disk
|
||||||
|
command: pvcreate {{ ollama_data_disk }}
|
||||||
|
args:
|
||||||
|
creates: /dev/{{ ollama_data_vg }}
|
||||||
|
when: ollama_setup_data_disk | bool
|
||||||
|
|
||||||
|
- name: Create LVM volume group
|
||||||
|
lvg:
|
||||||
|
vg: "{{ ollama_data_vg }}"
|
||||||
|
pvs: "{{ ollama_data_disk }}"
|
||||||
|
when: ollama_setup_data_disk | bool
|
||||||
|
|
||||||
|
- name: Create LVM logical volume using 100% of VG
|
||||||
|
lvol:
|
||||||
|
vg: "{{ ollama_data_vg }}"
|
||||||
|
lv: "{{ ollama_data_lv }}"
|
||||||
|
size: 100%FREE
|
||||||
|
shrink: false
|
||||||
|
when: ollama_setup_data_disk | bool
|
||||||
|
|
||||||
|
- name: Format logical volume as ext4
|
||||||
|
filesystem:
|
||||||
|
fstype: ext4
|
||||||
|
dev: "/dev/{{ ollama_data_vg }}/{{ ollama_data_lv }}"
|
||||||
|
opts: "-L ollama-data"
|
||||||
|
when: ollama_setup_data_disk | bool
|
||||||
|
|
||||||
|
- name: Create Ollama model directory mount point
|
||||||
|
file:
|
||||||
|
path: "{{ ollama_data_dir }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Mount Ollama data volume and add fstab entry
|
||||||
|
mount:
|
||||||
|
path: "{{ ollama_data_dir }}"
|
||||||
|
src: "/dev/{{ ollama_data_vg }}/{{ ollama_data_lv }}"
|
||||||
|
fstype: ext4
|
||||||
|
opts: defaults,noatime
|
||||||
|
state: mounted
|
||||||
|
when: ollama_setup_data_disk | bool
|
||||||
|
# noatime: eliminates inode access time writes on every model file read.
|
||||||
|
# Meaningful win for large sequential reads like model loading.
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Ollama installation
|
||||||
|
# Uses the official install script which creates the ollama user and
|
||||||
|
# systemd service unit. We override the service with our own config.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Download Ollama install script
|
||||||
|
get_url:
|
||||||
|
url: https://ollama.ai/install.sh
|
||||||
|
dest: /tmp/ollama_install.sh
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Run Ollama install script
|
||||||
|
command: /tmp/ollama_install.sh
|
||||||
|
environment: "{{ {} if ollama_version == 'latest' else {'OLLAMA_VERSION': ollama_version} }}"
|
||||||
|
args:
|
||||||
|
creates: /usr/local/bin/ollama
|
||||||
|
|
||||||
|
- name: Add ollama user to render and video groups (post-install)
|
||||||
|
user:
|
||||||
|
name: ollama
|
||||||
|
groups:
|
||||||
|
- render
|
||||||
|
- video
|
||||||
|
append: yes
|
||||||
|
|
||||||
|
- name: Set ownership of Ollama data directory to ollama user
|
||||||
|
file:
|
||||||
|
path: "{{ ollama_data_dir }}"
|
||||||
|
owner: ollama
|
||||||
|
group: ollama
|
||||||
|
recurse: yes
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Systemd service override
|
||||||
|
# Injects HSA_OVERRIDE_GFX_VERSION for RX 5000-series compatibility and
|
||||||
|
# sets OLLAMA_MODELS to the data disk mount point.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Create systemd override directory for ollama service
|
||||||
|
file:
|
||||||
|
path: /etc/systemd/system/ollama.service.d
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Deploy ollama systemd service override
|
||||||
|
template:
|
||||||
|
src: ollama-override.conf.j2
|
||||||
|
dest: /etc/systemd/system/ollama.service.d/override.conf
|
||||||
|
mode: '0644'
|
||||||
|
notify:
|
||||||
|
- reload systemd
|
||||||
|
- restart ollama
|
||||||
|
|
||||||
|
- name: Flush handlers to apply service config before model pull
|
||||||
|
meta: flush_handlers
|
||||||
|
|
||||||
|
- name: Enable and start Ollama service
|
||||||
|
systemd:
|
||||||
|
name: ollama
|
||||||
|
state: started
|
||||||
|
enabled: yes
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
- name: Wait for Ollama API to become available
|
||||||
|
uri:
|
||||||
|
url: "http://localhost:{{ ollama_port }}/api/tags"
|
||||||
|
status_code: 200
|
||||||
|
register: ollama_health
|
||||||
|
retries: 12
|
||||||
|
delay: 5
|
||||||
|
until: ollama_health.status == 200
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Initial model pull
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Pull initial model
|
||||||
|
command: ollama pull {{ ollama_default_model }}
|
||||||
|
environment:
|
||||||
|
OLLAMA_HOST: "http://localhost:{{ ollama_port }}"
|
||||||
|
register: model_pull
|
||||||
|
changed_when: "'pulled' in model_pull.stdout or model_pull.rc == 0"
|
||||||
|
timeout: 600
|
||||||
|
# Qwen3 8B Q4_K_M is ~5GB — allow 10 minutes on first pull.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# FILE: roles/ollama/templates/ollama-override.conf.j2
|
||||||
|
# DESCRIPTION: Systemd drop-in for the ollama service.
|
||||||
|
# Sets environment variables required for AMD GPU operation
|
||||||
|
# and configures the model storage path.
|
||||||
|
# Managed by Ansible — do not edit manually.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment="OLLAMA_HOST={{ ollama_host }}:{{ ollama_port }}"
|
||||||
|
Environment="OLLAMA_MODELS={{ ollama_data_dir }}"
|
||||||
|
Environment="HSA_OVERRIDE_GFX_VERSION={{ ollama_hsa_override_gfx_version }}"
|
||||||
|
Environment="ROCR_VISIBLE_DEVICES=0"
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
services:
|
|
||||||
grafana:
|
|
||||||
image: docker.io/grafana/grafana-oss:12.3.3
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
volumes:
|
|
||||||
- grafana_data:/var/lib/grafana
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
grafana_data:
|
|
||||||
driver: local
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
# ------------------------------------------------------------------------------
|
|
||||||
# FILE: boilerplates/monitoring/compose.yaml
|
|
||||||
# DESCRIPTION: Monitoring stack — Prometheus, Grafana, Alertmanager,
|
|
||||||
# Node Exporter, PVE Exporter, Pure FA Exporter
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
networks:
|
|
||||||
monitoring:
|
|
||||||
driver: bridge
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
prometheus_data:
|
|
||||||
driver: local
|
|
||||||
driver_opts:
|
|
||||||
type: none
|
|
||||||
o: bind
|
|
||||||
device: /var/lib/prometheus
|
|
||||||
grafana_data:
|
|
||||||
driver: local
|
|
||||||
alertmanager_data:
|
|
||||||
driver: local
|
|
||||||
|
|
||||||
services:
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# Prometheus
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
prometheus:
|
|
||||||
image: prom/prometheus:latest
|
|
||||||
container_name: prometheus
|
|
||||||
restart: unless-stopped
|
|
||||||
command:
|
|
||||||
- --config.file=/etc/prometheus/prometheus.yaml
|
|
||||||
- --storage.tsdb.path=/prometheus
|
|
||||||
- --storage.tsdb.retention.time=30d
|
|
||||||
- --web.enable-lifecycle
|
|
||||||
volumes:
|
|
||||||
- /opt/docker/monitoring/prometheus/prometheus.yaml:/etc/prometheus/prometheus.yaml:ro
|
|
||||||
- prometheus_data:/prometheus
|
|
||||||
ports:
|
|
||||||
- "9090:9090"
|
|
||||||
networks:
|
|
||||||
- monitoring
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# Grafana
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
grafana:
|
|
||||||
image: grafana/grafana:latest
|
|
||||||
container_name: grafana
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER}
|
|
||||||
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
|
|
||||||
- GF_USERS_ALLOW_SIGN_UP=false
|
|
||||||
- GF_SERVER_ROOT_URL=https://grafana.local.mk-labs.cloud
|
|
||||||
- GF_AUTH_GENERIC_OAUTH_ENABLED=true
|
|
||||||
- TRUSTED_PROXIES=10.1.71.35
|
|
||||||
volumes:
|
|
||||||
- grafana_data:/var/lib/grafana
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
networks:
|
|
||||||
- monitoring
|
|
||||||
depends_on:
|
|
||||||
- prometheus
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# Alertmanager
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
alertmanager:
|
|
||||||
image: prom/alertmanager:latest
|
|
||||||
container_name: alertmanager
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- /opt/docker/monitoring/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
|
|
||||||
- alertmanager_data:/alertmanager
|
|
||||||
ports:
|
|
||||||
- "9093:9093"
|
|
||||||
networks:
|
|
||||||
- monitoring
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# Node Exporter
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
node-exporter:
|
|
||||||
image: prom/node-exporter:latest
|
|
||||||
container_name: node-exporter
|
|
||||||
restart: unless-stopped
|
|
||||||
command:
|
|
||||||
- --path.rootfs=/host
|
|
||||||
pid: host
|
|
||||||
volumes:
|
|
||||||
- /:/host:ro,rslave
|
|
||||||
ports:
|
|
||||||
- "9100:9100"
|
|
||||||
networks:
|
|
||||||
- monitoring
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# Proxmox VE Exporter
|
|
||||||
# Scrapes the Proxmox API and exposes cluster/node/VM metrics for Prometheus.
|
|
||||||
# Credentials are mounted from pve.yml (rendered from vault).
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
pve-exporter:
|
|
||||||
image: prompve/prometheus-pve-exporter:latest
|
|
||||||
container_name: pve-exporter
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- /opt/docker/monitoring/pve-exporter/pve.yml:/etc/prometheus/pve.yml:ro
|
|
||||||
ports:
|
|
||||||
- "9221:9221"
|
|
||||||
networks:
|
|
||||||
- monitoring
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# SNMP Exporter
|
|
||||||
# Scrapes UniFi switches and UPS Tower via SNMP v2c.
|
|
||||||
# snmp.yml is rendered from vault and mounted read-only.
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
snmp-exporter:
|
|
||||||
image: prom/snmp-exporter:latest
|
|
||||||
container_name: snmp-exporter
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- /opt/docker/monitoring/snmp-exporter/snmp.yml:/etc/snmp_exporter/snmp.yml:ro
|
|
||||||
# network_mode: host is required so the container can route to VLANs
|
|
||||||
# (192.168.1.0/24) that are reachable from the host but not the Docker bridge.
|
|
||||||
# Port mapping and networks are incompatible with host networking.
|
|
||||||
network_mode: host
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# Pure Storage FlashArray Exporter -- DEFERRED
|
|
||||||
# utilidor is on Purity 6.5.x; legacy exporter image is no longer publicly
|
|
||||||
# available on Quay. Once utilidor reaches Purity 6.7+, no container is
|
|
||||||
# needed -- Prometheus scrapes the native OpenMetrics endpoint directly.
|
|
||||||
# See prometheus.yaml.j2 for the commented-out scrape job to re-enable.
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
30
cluster/applications/jarvis/application.yaml
Normal file
30
cluster/applications/jarvis/application.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Application: jarvis (Hermes Agent)
|
||||||
|
# Wave 20 — application workload, after platform services
|
||||||
|
# Single-source: all manifests from repo
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: jarvis
|
||||||
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "20"
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
sources:
|
||||||
|
- repoURL: https://gitea.mk-labs.cloud/rblundon/homelab.git
|
||||||
|
targetRevision: main
|
||||||
|
path: cluster/applications/jarvis
|
||||||
|
directory:
|
||||||
|
exclude: "application.yaml"
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: jarvis
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
- ServerSideApply=true
|
||||||
116
cluster/applications/jarvis/deployment.yaml
Normal file
116
cluster/applications/jarvis/deployment.yaml
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Hermes Agent (jarvis) — Deployment + Service + PVC
|
||||||
|
#
|
||||||
|
# Hermes requires a one-time interactive setup before first gateway run.
|
||||||
|
# See bootstrap procedure below before applying for the first time.
|
||||||
|
#
|
||||||
|
# Bootstrap procedure (run once):
|
||||||
|
# 1. kubectl run hermes-setup -it --rm --image=nousresearch/hermes-agent \
|
||||||
|
# --overrides='{"spec":{"volumes":[{"name":"data","persistentVolumeClaim":{"claimName":"jarvis-data"}}],"containers":[{"name":"hermes-setup","image":"nousresearch/hermes-agent","command":["setup"],"stdin":true,"tty":true,"volumeMounts":[{"name":"data","mountPath":"/opt/data"}]}]}}' \
|
||||||
|
# -n jarvis
|
||||||
|
# 2. Complete the setup wizard:
|
||||||
|
# - Provider: Ollama (custom endpoint)
|
||||||
|
# - Base URL: http://astro-orbiter.local.mk-labs.cloud:11434/v1
|
||||||
|
# - API Key: none (leave blank)
|
||||||
|
# - Model: qwen3:8b
|
||||||
|
# - Messaging: skip for now
|
||||||
|
# 3. Verify config was written: kubectl exec -n jarvis <pod> -- ls /opt/data
|
||||||
|
# 4. Apply the rest of the manifests (ArgoCD will handle from here)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: jarvis-data
|
||||||
|
namespace: jarvis
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
|
# Uses cluster default StorageClass (Ceph/Longhorn — whatever fastpass has)
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: jarvis
|
||||||
|
namespace: jarvis
|
||||||
|
labels:
|
||||||
|
app: jarvis
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: jarvis
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: jarvis
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 10000
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: hermes
|
||||||
|
image: nousresearch/hermes-agent:latest
|
||||||
|
command: ["gateway", "run"]
|
||||||
|
ports:
|
||||||
|
- containerPort: 8642
|
||||||
|
name: gateway
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: ["ALL"]
|
||||||
|
env:
|
||||||
|
- name: HERMES_DASHBOARD
|
||||||
|
value: "1"
|
||||||
|
- name: API_SERVER_ENABLED
|
||||||
|
value: "true"
|
||||||
|
- name: API_SERVER_HOST
|
||||||
|
value: "0.0.0.0"
|
||||||
|
- name: API_SERVER_CORS_ORIGINS
|
||||||
|
value: "*"
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /opt/data
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8642
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8642
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
periodSeconds: 10
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: jarvis-data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: jarvis
|
||||||
|
namespace: jarvis
|
||||||
|
labels:
|
||||||
|
app: jarvis
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: jarvis
|
||||||
|
ports:
|
||||||
|
- name: gateway
|
||||||
|
port: 8642
|
||||||
|
targetPort: 8642
|
||||||
45
cluster/applications/jarvis/httproute.yaml
Normal file
45
cluster/applications/jarvis/httproute.yaml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# HTTPRoute — jarvis (Hermes Agent) via Cilium Gateway
|
||||||
|
# Hostnames: jarvis.local.mk-labs.cloud
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: jarvis-tls
|
||||||
|
namespace: jarvis
|
||||||
|
spec:
|
||||||
|
secretName: jarvis-tls
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt-prod
|
||||||
|
kind: ClusterIssuer
|
||||||
|
dnsNames:
|
||||||
|
- jarvis.local.mk-labs.cloud
|
||||||
|
---
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: jarvis
|
||||||
|
namespace: jarvis
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/hostname: jarvis.local.mk-labs.cloud
|
||||||
|
external-dns.alpha.kubernetes.io/target: "10.1.71.90"
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- group: gateway.networking.k8s.io
|
||||||
|
kind: Gateway
|
||||||
|
name: fastpass-gateway
|
||||||
|
namespace: gateway
|
||||||
|
sectionName: https
|
||||||
|
hostnames:
|
||||||
|
- jarvis.local.mk-labs.cloud
|
||||||
|
rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
|
- group: ""
|
||||||
|
kind: Service
|
||||||
|
name: jarvis
|
||||||
|
port: 8642
|
||||||
|
weight: 1
|
||||||
281
docs/astro-orbiter-gpu-passthrough.md
Normal file
281
docs/astro-orbiter-gpu-passthrough.md
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
# astro-orbiter: AMD GPU Passthrough & Ollama/ROCm Setup
|
||||||
|
|
||||||
|
**Host:** `tomorrowland` (10.1.71.12) — Proxmox node
|
||||||
|
**VM:** `astro-orbiter` (10.1.71.130, VM ID 71130)
|
||||||
|
**GPU:** MSI Radeon RX 5700 (Navi 10, gfx1010) via Minisforum DEG1 eGPU dock + OCuLink
|
||||||
|
**Purpose:** Ollama inference host for `jarvis` AI assistant
|
||||||
|
**Date:** 2026-05-25
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hardware
|
||||||
|
|
||||||
|
- Minisforum MS-01 (`tomorrowland`)
|
||||||
|
- Minisforum DEG1 eGPU dock connected via M.2 OCuLink
|
||||||
|
- MSI Radeon RX 5700 8GB
|
||||||
|
- Montech BETA 2 650W ATX PSU powering the eGPU dock
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 1: tomorrowland Host Setup
|
||||||
|
|
||||||
|
### 1.1 Network Interface Rename (eGPU side effect)
|
||||||
|
|
||||||
|
Adding the eGPU dock shifted PCIe bus numbers, renaming all NICs. Fix:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Back up interfaces config
|
||||||
|
cp /etc/network/interfaces /etc/network/interfaces.bak
|
||||||
|
|
||||||
|
# Apply renames (adjust if hardware differs)
|
||||||
|
sed -i 's/enp87s0/enp93s0/g' /etc/network/interfaces
|
||||||
|
sed -i 's/enp2s0f0np0/enp5s0f0np0/g' /etc/network/interfaces
|
||||||
|
sed -i 's/enp2s0f0np1/enp5s0f0np1/g' /etc/network/interfaces
|
||||||
|
|
||||||
|
# Reboot to apply (do from console/JetKVM, not SSH)
|
||||||
|
reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Verify GPU Enumeration
|
||||||
|
|
||||||
|
After reboot, confirm the GPU is visible:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lspci | grep -i amd
|
||||||
|
# Expected:
|
||||||
|
# 01:00.0 PCI bridge: AMD/ATI Navi 10 XL Upstream Port
|
||||||
|
# 02:00.0 PCI bridge: AMD/ATI Navi 10 XL Downstream Port
|
||||||
|
# 03:00.0 VGA compatible controller: AMD/ATI Navi 10 [Radeon RX 5600/5700]
|
||||||
|
# 03:00.1 Audio device: AMD/ATI Navi 10 HDMI Audio
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Verify IOMMU
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dmesg | grep -i iommu | head -5
|
||||||
|
# Must show: iommu: Default domain type: Translated
|
||||||
|
|
||||||
|
# Verify GPU is in its own IOMMU groups (no ACS override needed)
|
||||||
|
find /sys/kernel/iommu_groups/*/devices/ -name "*03:00*"
|
||||||
|
# Expected: groups 17 and 18, one device each
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 Blacklist amdgpu and Bind vfio-pci
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Blacklist host GPU drivers
|
||||||
|
echo "blacklist amdgpu
|
||||||
|
blacklist radeon" > /etc/modprobe.d/blacklist-amdgpu.conf
|
||||||
|
|
||||||
|
# Bind vfio-pci to the GPU at boot
|
||||||
|
# 1002:731f = RX 5700 VGA, 1002:ab38 = Navi 10 HDMI Audio
|
||||||
|
echo "options vfio-pci ids=1002:731f,1002:ab38" > /etc/modprobe.d/vfio.conf
|
||||||
|
|
||||||
|
# Load vfio modules early
|
||||||
|
echo "vfio
|
||||||
|
vfio_iommu_type1
|
||||||
|
vfio_pci
|
||||||
|
vfio_virqfd" > /etc/modules-load.d/vfio.conf
|
||||||
|
|
||||||
|
# Rebuild initramfs and reboot
|
||||||
|
update-initramfs -u -k all
|
||||||
|
reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify after reboot:
|
||||||
|
```bash
|
||||||
|
lspci -k -s 03:00.0
|
||||||
|
# Must show: Kernel driver in use: vfio-pci
|
||||||
|
|
||||||
|
lspci -k -s 03:00.1
|
||||||
|
# Must show: Kernel driver in use: vfio-pci
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.5 Fix GPU D3 Power State on VM Restart (Required — RX 5700 lacks FLR)
|
||||||
|
|
||||||
|
The RX 5700 does not support PCIe Function Level Reset (FLR). Without a fix,
|
||||||
|
the GPU gets stuck in D3 power state on every VM restart, requiring a full host
|
||||||
|
reboot to recover.
|
||||||
|
|
||||||
|
**Fix: `pcie_port_pm=off` kernel parameter**
|
||||||
|
|
||||||
|
`vendor-reset` was attempted but causes a QEMU PCI IRQ assertion failure
|
||||||
|
(`pci_irq_handler: Assertion 0 <= irq_num`) on PVE 7.0.2 with this hardware.
|
||||||
|
`pcie_port_pm=off` prevents the card from entering D3 entirely and is the
|
||||||
|
correct solution for this setup.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sed -i 's/GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="pcie_port_pm=off /' /etc/default/grub
|
||||||
|
update-grub
|
||||||
|
reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify after reboot — VM should restart cleanly without host reboot:
|
||||||
|
```bash
|
||||||
|
qm reboot 71130
|
||||||
|
# wait 30 seconds
|
||||||
|
ssh wed@astro-orbiter 'lsmod | grep amdgpu'
|
||||||
|
# Should be non-empty
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 2: astro-orbiter VM Provisioning
|
||||||
|
|
||||||
|
### 2.1 Terraform Provisioning
|
||||||
|
|
||||||
|
VM was provisioned manually (not via NetBox pipeline) due to PCIe passthrough
|
||||||
|
requirements. Run from `city-hall`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/git/homelab/terraform/proxmox/vm
|
||||||
|
|
||||||
|
terraform apply \
|
||||||
|
-var='proxmox_api_url=https://fantasyland.local.mk-labs.cloud:8006' \
|
||||||
|
-var='proxmox_api_token=terraform@pve!terraform-token=<secret>' \
|
||||||
|
-var='vm_id=71130' \
|
||||||
|
-var='hostname=astro-orbiter' \
|
||||||
|
-var='ip_address=10.1.71.130' \
|
||||||
|
-var='target_node=tomorrowland' \
|
||||||
|
-var='template_name=ubuntu-24.04-large' \
|
||||||
|
-var='datastore=liberty-tree' \
|
||||||
|
-var='data_disk_enabled=true' \
|
||||||
|
-var='data_disk_size=128' \
|
||||||
|
-var='data_disk_datastore=utilidor'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- `datastore=liberty-tree` for OS disk — iSCSI storage does not support EFI disk
|
||||||
|
cloning from templates. OS disk stays on Ceph.
|
||||||
|
- `data_disk_datastore=utilidor` for model storage — fresh disk creation works fine
|
||||||
|
on iSCSI.
|
||||||
|
- `pcie_passthrough_enabled` is NOT passed — the `bpg/proxmox` provider's
|
||||||
|
`dynamic "hostpci"` block does not work reliably. PCIe passthrough is applied
|
||||||
|
manually via `qm set` after provisioning (see 2.2).
|
||||||
|
|
||||||
|
### 2.2 Post-Provisioning: PCIe and Machine Type
|
||||||
|
|
||||||
|
The template uses `i440fx` machine type. PCIe passthrough requires `q35`.
|
||||||
|
Run after Terraform provisioning, with VM shut down:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Switch to q35 machine type (required for PCIe passthrough)
|
||||||
|
qm set 71130 -machine q35
|
||||||
|
|
||||||
|
# Add GPU passthrough (both VGA and audio functions)
|
||||||
|
qm set 71130 -hostpci0 0000:03:00,pcie=1,rombar=1
|
||||||
|
|
||||||
|
# Add data disk on utilidor
|
||||||
|
qm set 71130 -scsi1 utilidor:128,format=raw,discard=on
|
||||||
|
|
||||||
|
# Increase RAM to 8GB
|
||||||
|
qm set 71130 -memory 8192
|
||||||
|
|
||||||
|
# Start the VM
|
||||||
|
qm start 71130
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify config landed:
|
||||||
|
```bash
|
||||||
|
qm config 71130 | grep -E "hostpci|machine|memory|scsi1"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Ansible Deployment
|
||||||
|
|
||||||
|
From `city-hall` in the `ansible/` directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# OS baseline
|
||||||
|
ansible-playbook playbooks/day0_baseline.yml --limit astro-orbiter --ask-vault-pass
|
||||||
|
|
||||||
|
# Ollama + ROCm
|
||||||
|
ansible-playbook playbooks/day1_deploy_ollama.yml --ask-vault-pass
|
||||||
|
```
|
||||||
|
|
||||||
|
The `day1_deploy_ollama.yml` playbook includes a pre-flight `lspci` check that
|
||||||
|
will fail fast if the GPU is not visible to the VM.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 3: Verification
|
||||||
|
|
||||||
|
### 3.1 GPU visible in VM
|
||||||
|
```bash
|
||||||
|
lspci | grep -i amd
|
||||||
|
# Expected: 01:00.0 VGA ... Navi 10 [Radeon RX 5600/5700]
|
||||||
|
# 01:00.1 Audio ... Navi 10 HDMI Audio
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 amdgpu driver loaded
|
||||||
|
```bash
|
||||||
|
lsmod | grep amdgpu
|
||||||
|
# Should show amdgpu and dependent modules
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 ROCm sees the GPU
|
||||||
|
```bash
|
||||||
|
/opt/rocm/bin/rocm-smi
|
||||||
|
# Should show Device 0 with VRAM%, GPU%, temperature
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Ollama GPU inference
|
||||||
|
```bash
|
||||||
|
ollama run qwen3:8b "say hello"
|
||||||
|
# While running, check rocm-smi — GPU% and power draw should spike
|
||||||
|
/opt/rocm/bin/rocm-smi
|
||||||
|
# GPU% > 0%, power > 18W idle = GPU is doing inference
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 VM restart test
|
||||||
|
```bash
|
||||||
|
# On tomorrowland:
|
||||||
|
qm reboot 71130
|
||||||
|
# Wait 30 seconds, then on astro-orbiter:
|
||||||
|
lsmod | grep amdgpu
|
||||||
|
# Should be non-empty — if empty, vendor-reset is not working
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Known Issues & Lessons Learned
|
||||||
|
|
||||||
|
| Issue | Cause | Fix |
|
||||||
|
|---|---|---|
|
||||||
|
| NIC names changed after eGPU install | PCIe bus renumbering | Update `/etc/network/interfaces` with new names |
|
||||||
|
| `can't allocate space in iscsi storage` on clone | iSCSI doesn't support EFI disk cloning | Use `liberty-tree` for OS disk |
|
||||||
|
| `q35 machine model is not enabled` on hostpci | Template built with i440fx | `qm set <vmid> -machine q35` before adding hostpci |
|
||||||
|
| GPU stuck in D3 on VM restart | RX 5700 lacks FLR support | `pcie_port_pm=off` kernel param on host. `vendor-reset` builds but causes QEMU PCI IRQ assertion failure on PVE 7.0.2 — do not use. |
|
||||||
|
| `dynamic "hostpci"` block not applying | `bpg/proxmox` provider limitation | Use `qm set` workaround post-provisioning |
|
||||||
|
| `rocm-smi` not in PATH | ROCm installs to `/opt/rocm-6.2.0/bin/` | `/etc/profile.d/rocm.sh` sets PATH (via Ansible role) |
|
||||||
|
| Ollama install script 404 | `OLLAMA_VERSION=latest` is not a valid version string | Omit env var when version is `latest` — script self-determines |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROCm Configuration
|
||||||
|
|
||||||
|
The RX 5700 is RDNA 1 (gfx1010). ROCm's official support starts at gfx1030
|
||||||
|
(RDNA 2). The `HSA_OVERRIDE_GFX_VERSION=10.1.0` environment variable tells ROCm
|
||||||
|
to treat the GPU as a supported target.
|
||||||
|
|
||||||
|
This is set via systemd drop-in at:
|
||||||
|
`/etc/systemd/system/ollama.service.d/override.conf`
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Service]
|
||||||
|
Environment="OLLAMA_HOST=0.0.0.0:11434"
|
||||||
|
Environment="OLLAMA_MODELS=/var/lib/ollama"
|
||||||
|
Environment="HSA_OVERRIDE_GFX_VERSION=10.1.0"
|
||||||
|
Environment="ROCR_VISIBLE_DEVICES=0"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parking Lot
|
||||||
|
|
||||||
|
- **P.PACKER:** Ubuntu VM templates should be built with `machine: q35` by default
|
||||||
|
to support PCIe passthrough without manual intervention.
|
||||||
|
- **P.TERRAFORM:** Fix `bpg/proxmox` provider `hostpci` dynamic block or find
|
||||||
|
correct syntax for the installed provider version.
|
||||||
|
- **P.PIPELINE:** Update n8n workflow to pass `data_disk_enabled`, `data_disk_size`,
|
||||||
|
and `pcie_device_id` from NetBox custom fields to Terraform for pipeline-driven
|
||||||
|
GPU VM provisioning.
|
||||||
124
hello-world.yaml
124
hello-world.yaml
@@ -1,124 +0,0 @@
|
|||||||
# ------------------------------------------------------------------------------
|
|
||||||
# hello-world — Gateway API + cert-manager + external-dns end-to-end test
|
|
||||||
#
|
|
||||||
# Tests:
|
|
||||||
# - Cilium Gateway provisioning (10.1.71.90 from cilium-gateway pool)
|
|
||||||
# - cert-manager DNS-01 cert issuance (letsencrypt-staging)
|
|
||||||
# - external-dns A record creation in Technitium via RFC2136
|
|
||||||
# - HTTPRoute routing to backend service
|
|
||||||
#
|
|
||||||
# Cleanup when done:
|
|
||||||
# kubectl delete -f hello-world.yaml -n default
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Gateway — one per cluster, shared by all HTTPRoutes
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: Gateway
|
|
||||||
metadata:
|
|
||||||
name: fastpass-gateway
|
|
||||||
namespace: default
|
|
||||||
annotations:
|
|
||||||
io.cilium/lb-ipam-ips: "10.1.71.90"
|
|
||||||
spec:
|
|
||||||
gatewayClassName: cilium
|
|
||||||
listeners:
|
|
||||||
- name: http
|
|
||||||
port: 80
|
|
||||||
protocol: HTTP
|
|
||||||
allowedRoutes:
|
|
||||||
namespaces:
|
|
||||||
from: Same
|
|
||||||
- name: https
|
|
||||||
port: 443
|
|
||||||
protocol: HTTPS
|
|
||||||
tls:
|
|
||||||
mode: Terminate
|
|
||||||
certificateRefs:
|
|
||||||
- name: hello-world-tls
|
|
||||||
kind: Secret
|
|
||||||
allowedRoutes:
|
|
||||||
namespaces:
|
|
||||||
from: Same
|
|
||||||
---
|
|
||||||
# Certificate — staging issuer to avoid LE rate limits during testing
|
|
||||||
apiVersion: cert-manager.io/v1
|
|
||||||
kind: Certificate
|
|
||||||
metadata:
|
|
||||||
name: hello-world-tls
|
|
||||||
namespace: default
|
|
||||||
spec:
|
|
||||||
secretName: hello-world-tls
|
|
||||||
issuerRef:
|
|
||||||
name: letsencrypt-staging
|
|
||||||
kind: ClusterIssuer
|
|
||||||
dnsNames:
|
|
||||||
- hello.local.mk-labs.cloud
|
|
||||||
---
|
|
||||||
# Deployment — http-echo runs as non-root, restricted compliant
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: hello-world
|
|
||||||
namespace: default
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: hello-world
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: hello-world
|
|
||||||
spec:
|
|
||||||
securityContext:
|
|
||||||
runAsNonRoot: true
|
|
||||||
runAsUser: 1000
|
|
||||||
runAsGroup: 1000
|
|
||||||
seccompProfile:
|
|
||||||
type: RuntimeDefault
|
|
||||||
containers:
|
|
||||||
- name: hello-world
|
|
||||||
image: hashicorp/http-echo:1.0
|
|
||||||
args:
|
|
||||||
- "-text=fastpass gateway test - hello from mk-labs!"
|
|
||||||
- "-listen=:8080"
|
|
||||||
ports:
|
|
||||||
- containerPort: 8080
|
|
||||||
securityContext:
|
|
||||||
allowPrivilegeEscalation: false
|
|
||||||
capabilities:
|
|
||||||
drop:
|
|
||||||
- ALL
|
|
||||||
---
|
|
||||||
# Service
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: hello-world
|
|
||||||
namespace: default
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: hello-world
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 8080
|
|
||||||
---
|
|
||||||
# HTTPRoute
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: hello-world
|
|
||||||
namespace: default
|
|
||||||
annotations:
|
|
||||||
external-dns.alpha.kubernetes.io/hostname: hello.local.mk-labs.cloud
|
|
||||||
external-dns.alpha.kubernetes.io/target: "10.1.71.90"
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: fastpass-gateway
|
|
||||||
sectionName: https
|
|
||||||
hostnames:
|
|
||||||
- hello.local.mk-labs.cloud
|
|
||||||
rules:
|
|
||||||
- backendRefs:
|
|
||||||
- name: hello-world
|
|
||||||
port: 80
|
|
||||||
469
hermes/.env
Normal file
469
hermes/.env
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
# Hermes Agent Environment Configuration
|
||||||
|
# Copy this file to .env and fill in your API keys
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LLM PROVIDER (OpenRouter)
|
||||||
|
# =============================================================================
|
||||||
|
# OpenRouter provides access to many models through one API
|
||||||
|
# All LLM calls go through OpenRouter - no direct provider keys needed
|
||||||
|
# Get your key at: https://openrouter.ai/keys
|
||||||
|
# OPENROUTER_API_KEY=
|
||||||
|
|
||||||
|
# Default model is configured in ~/.hermes/config.yaml (model.default).
|
||||||
|
# Use 'hermes model' or 'hermes setup' to change it.
|
||||||
|
# LLM_MODEL is no longer read from .env — this line is kept for reference only.
|
||||||
|
# LLM_MODEL=anthropic/claude-opus-4.6
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LLM PROVIDER (NovitaAI)
|
||||||
|
# =============================================================================
|
||||||
|
# NovitaAI — 90+ models, pay-per-use
|
||||||
|
# Get your key at: https://novita.ai/settings/key-management
|
||||||
|
# NOVITA_API_KEY=
|
||||||
|
# NOVITA_BASE_URL=https://api.novita.ai/openai/v1 # Override default base URL
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LLM PROVIDER (Google AI Studio / Gemini)
|
||||||
|
# =============================================================================
|
||||||
|
# Native Gemini API via Google's OpenAI-compatible endpoint.
|
||||||
|
# Get your key at: https://aistudio.google.com/app/apikey
|
||||||
|
# GOOGLE_API_KEY=your_google_ai_studio_key_here
|
||||||
|
# GEMINI_API_KEY=your_gemini_key_here # alias for GOOGLE_API_KEY
|
||||||
|
# Optional base URL override (default: Google's OpenAI-compatible endpoint)
|
||||||
|
# GEMINI_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LLM PROVIDER (Ollama Cloud)
|
||||||
|
# =============================================================================
|
||||||
|
# Cloud-hosted open models via Ollama's OpenAI-compatible endpoint.
|
||||||
|
# Get your key at: https://ollama.com/settings
|
||||||
|
# OLLAMA_API_KEY=your_ollama_key_here
|
||||||
|
# Optional base URL override (default: https://ollama.com/v1)
|
||||||
|
# OLLAMA_BASE_URL=https://ollama.com/v1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LLM PROVIDER (z.ai / GLM)
|
||||||
|
# =============================================================================
|
||||||
|
# z.ai provides access to ZhipuAI GLM models (GLM-4-Plus, etc.)
|
||||||
|
# Get your key at: https://z.ai or https://open.bigmodel.cn
|
||||||
|
# GLM_API_KEY=
|
||||||
|
# GLM_BASE_URL=https://api.z.ai/api/paas/v4 # Override default base URL
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LLM PROVIDER (Kimi / Moonshot)
|
||||||
|
# =============================================================================
|
||||||
|
# Kimi Code provides access to Moonshot AI coding models (kimi-k2.5, etc.)
|
||||||
|
# Get your key at: https://platform.kimi.ai (Kimi Code console)
|
||||||
|
# Keys prefixed sk-kimi- use the Kimi Code API (api.kimi.com) by default.
|
||||||
|
# Legacy keys from platform.moonshot.ai need KIMI_BASE_URL override below.
|
||||||
|
# KIMI_API_KEY=
|
||||||
|
# KIMI_BASE_URL=https://api.kimi.com/coding/v1 # Default for sk-kimi- keys
|
||||||
|
# KIMI_BASE_URL=https://api.moonshot.ai/v1 # For legacy Moonshot keys
|
||||||
|
# KIMI_BASE_URL=https://api.moonshot.cn/v1 # For Moonshot China keys
|
||||||
|
# KIMI_CN_API_KEY= # Dedicated Moonshot China key
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LLM PROVIDER (Arcee AI)
|
||||||
|
# =============================================================================
|
||||||
|
# Arcee AI provides access to Trinity models (trinity-mini, trinity-large-*)
|
||||||
|
# Get an Arcee key at: https://chat.arcee.ai/
|
||||||
|
# ARCEEAI_API_KEY=
|
||||||
|
# ARCEE_BASE_URL= # Override default base URL
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LLM PROVIDER (MiniMax)
|
||||||
|
# =============================================================================
|
||||||
|
# MiniMax provides access to MiniMax models (global endpoint)
|
||||||
|
# Get your key at: https://www.minimax.io
|
||||||
|
# MINIMAX_API_KEY=
|
||||||
|
# MINIMAX_BASE_URL=https://api.minimax.io/v1 # Override default base URL
|
||||||
|
|
||||||
|
# MiniMax China endpoint (for users in mainland China)
|
||||||
|
# MINIMAX_CN_API_KEY=
|
||||||
|
# MINIMAX_CN_BASE_URL=https://api.minimaxi.com/v1 # Override default base URL
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LLM PROVIDER (OpenCode Zen)
|
||||||
|
# =============================================================================
|
||||||
|
# OpenCode Zen provides curated, tested models (GPT, Claude, Gemini, MiniMax, GLM, Kimi)
|
||||||
|
# Pay-as-you-go pricing. Get your key at: https://opencode.ai/auth
|
||||||
|
# OPENCODE_ZEN_API_KEY=
|
||||||
|
# OPENCODE_ZEN_BASE_URL=https://opencode.ai/zen/v1 # Override default base URL
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LLM PROVIDER (OpenCode Go)
|
||||||
|
# =============================================================================
|
||||||
|
# OpenCode Go provides access to open models (GLM-5, Kimi K2.5, MiniMax M2.5)
|
||||||
|
# $10/month subscription. Get your key at: https://opencode.ai/auth
|
||||||
|
# OPENCODE_GO_API_KEY=
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LLM PROVIDER (Hugging Face Inference Providers)
|
||||||
|
# =============================================================================
|
||||||
|
# Hugging Face routes to 20+ open models via unified OpenAI-compatible endpoint.
|
||||||
|
# Free tier included ($0.10/month), no markup on provider rates.
|
||||||
|
# Get your token at: https://huggingface.co/settings/tokens
|
||||||
|
# Required permission: "Make calls to Inference Providers"
|
||||||
|
# HF_TOKEN=
|
||||||
|
# OPENCODE_GO_BASE_URL=https://opencode.ai/zen/go/v1 # Override default base URL
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LLM PROVIDER (Qwen OAuth)
|
||||||
|
# =============================================================================
|
||||||
|
# Qwen OAuth reuses your local Qwen CLI login (qwen auth qwen-oauth).
|
||||||
|
# No API key needed — credentials come from ~/.qwen/oauth_creds.json.
|
||||||
|
# Optional base URL override:
|
||||||
|
# HERMES_QWEN_BASE_URL=https://portal.qwen.ai/v1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# LLM PROVIDER (Xiaomi MiMo)
|
||||||
|
# =============================================================================
|
||||||
|
# Xiaomi MiMo models (mimo-v2-pro, mimo-v2-omni, mimo-v2-flash).
|
||||||
|
# Get your key at: https://platform.xiaomimimo.com
|
||||||
|
# XIAOMI_API_KEY=your_key_here
|
||||||
|
# Optional base URL override:
|
||||||
|
# XIAOMI_BASE_URL=https://api.xiaomimimo.com/v1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TOOL API KEYS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Exa API Key - AI-native web search and contents
|
||||||
|
# Get at: https://exa.ai
|
||||||
|
# EXA_API_KEY=
|
||||||
|
|
||||||
|
# Parallel API Key - AI-native web search and extract
|
||||||
|
# Get at: https://parallel.ai
|
||||||
|
# PARALLEL_API_KEY=
|
||||||
|
|
||||||
|
# Firecrawl API Key - Web search, extract, and crawl
|
||||||
|
# Get at: https://firecrawl.dev/
|
||||||
|
# FIRECRAWL_API_KEY=
|
||||||
|
|
||||||
|
|
||||||
|
# FAL.ai API Key - Image generation
|
||||||
|
# Get at: https://fal.ai/
|
||||||
|
# FAL_KEY=
|
||||||
|
|
||||||
|
# Honcho - Cross-session AI-native user modeling (optional)
|
||||||
|
# Builds a persistent understanding of the user across sessions and tools.
|
||||||
|
# Get at: https://app.honcho.dev
|
||||||
|
# Also requires ~/.honcho/config.json with enabled=true (see README).
|
||||||
|
# HONCHO_API_KEY=
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# HYPERLIQUID OPTIONAL SKILL
|
||||||
|
# =============================================================================
|
||||||
|
# Optional defaults for the Hyperliquid skill in optional-skills/blockchain/hyperliquid
|
||||||
|
#
|
||||||
|
# Hyperliquid API base URL override
|
||||||
|
# Default: https://api.hyperliquid.xyz
|
||||||
|
# HYPERLIQUID_API_URL=https://api.hyperliquid-testnet.xyz
|
||||||
|
#
|
||||||
|
# Default address for account-level commands like state, fills, orders, and review
|
||||||
|
# HYPERLIQUID_USER_ADDRESS=0x0000000000000000000000000000000000000000
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TERMINAL TOOL CONFIGURATION
|
||||||
|
# =============================================================================
|
||||||
|
# Backend type: "local", "singularity", "docker", "modal", or "ssh"
|
||||||
|
# Terminal backend is configured in ~/.hermes/config.yaml (terminal.backend).
|
||||||
|
# Use 'hermes setup' or 'hermes config set terminal.backend docker' to change.
|
||||||
|
# Supported: local, docker, singularity, modal, ssh
|
||||||
|
#
|
||||||
|
# Only override here if you need to force a backend without touching config.yaml:
|
||||||
|
# TERMINAL_ENV=local
|
||||||
|
|
||||||
|
# Override the container runtime binary (e.g. to use Podman instead of Docker).
|
||||||
|
# Useful on systems where Docker's storage driver is broken or unavailable.
|
||||||
|
# HERMES_DOCKER_BINARY=/usr/local/bin/podman
|
||||||
|
|
||||||
|
# Container images (for singularity/docker/modal backends)
|
||||||
|
# TERMINAL_DOCKER_IMAGE=nikolaik/python-nodejs:python3.11-nodejs20
|
||||||
|
# TERMINAL_SINGULARITY_IMAGE=docker://nikolaik/python-nodejs:python3.11-nodejs20
|
||||||
|
TERMINAL_MODAL_IMAGE=nikolaik/python-nodejs:python3.11-nodejs20
|
||||||
|
|
||||||
|
|
||||||
|
# Working directory for terminal commands
|
||||||
|
# For local backend: "." means current directory (resolved automatically)
|
||||||
|
# For remote backends (ssh/docker/modal/singularity): use an absolute path
|
||||||
|
# INSIDE the target environment, or leave unset for the backend's default
|
||||||
|
# (/root for modal, / for docker, ~ for ssh). Do NOT use a host-local path.
|
||||||
|
# Usually managed by config.yaml (terminal.cwd) — uncomment to override
|
||||||
|
# TERMINAL_CWD=.
|
||||||
|
|
||||||
|
# Default command timeout in seconds
|
||||||
|
TERMINAL_TIMEOUT=60
|
||||||
|
|
||||||
|
# Cleanup inactive environments after this many seconds
|
||||||
|
TERMINAL_LIFETIME_SECONDS=300
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SSH REMOTE EXECUTION (for TERMINAL_ENV=ssh)
|
||||||
|
# =============================================================================
|
||||||
|
# Run terminal commands on a remote server via SSH.
|
||||||
|
# Agent code stays on your machine, commands execute remotely.
|
||||||
|
#
|
||||||
|
# SECURITY BENEFITS:
|
||||||
|
# - Agent cannot read your .env file (API keys protected)
|
||||||
|
# - Agent cannot modify its own code
|
||||||
|
# - Remote server acts as isolated sandbox
|
||||||
|
# - Can safely configure passwordless sudo on remote
|
||||||
|
#
|
||||||
|
# TERMINAL_SSH_HOST=192.168.1.100
|
||||||
|
# TERMINAL_SSH_USER=agent
|
||||||
|
# TERMINAL_SSH_PORT=22
|
||||||
|
# TERMINAL_SSH_KEY=~/.ssh/id_rsa
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SUDO SUPPORT (works with ALL terminal backends)
|
||||||
|
# =============================================================================
|
||||||
|
# If set, enables sudo commands by piping password via `sudo -S`.
|
||||||
|
# Works with: local, docker, singularity, modal, and ssh backends.
|
||||||
|
#
|
||||||
|
# SECURITY WARNING: Password stored in plaintext. Only use on trusted machines.
|
||||||
|
#
|
||||||
|
# ALTERNATIVES:
|
||||||
|
# - For SSH backend: Configure passwordless sudo on the remote server
|
||||||
|
# - For containers: Run as root inside the container (no sudo needed)
|
||||||
|
# - For local: Configure /etc/sudoers for specific commands
|
||||||
|
# - For CLI: Leave unset - you'll be prompted interactively with 45s timeout
|
||||||
|
#
|
||||||
|
# SUDO_PASSWORD=your_password_here
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# MODAL CLOUD BACKEND (Optional - for TERMINAL_ENV=modal)
|
||||||
|
# =============================================================================
|
||||||
|
# Modal uses CLI authentication, not environment variables.
|
||||||
|
# Run: pip install modal && modal setup
|
||||||
|
# This will authenticate via browser and store credentials locally.
|
||||||
|
# No API key needed in .env - Modal handles auth automatically.
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# BROWSER TOOL CONFIGURATION (agent-browser + Browserbase)
|
||||||
|
# =============================================================================
|
||||||
|
# Browser automation requires Browserbase cloud service for remote browser execution.
|
||||||
|
# This allows the agent to navigate websites, fill forms, and extract information.
|
||||||
|
#
|
||||||
|
# STEALTH MODES:
|
||||||
|
# - Basic Stealth: ALWAYS active (random fingerprints, auto CAPTCHA solving)
|
||||||
|
# - Advanced Stealth: Requires BROWSERBASE_ADVANCED_STEALTH=true (Scale Plan only)
|
||||||
|
|
||||||
|
# Browserbase API Key - Cloud browser execution
|
||||||
|
# Get at: https://browserbase.com/
|
||||||
|
# BROWSERBASE_API_KEY=
|
||||||
|
|
||||||
|
# Browserbase Project ID - From your Browserbase dashboard
|
||||||
|
# BROWSERBASE_PROJECT_ID=
|
||||||
|
|
||||||
|
# Enable residential proxies for better CAPTCHA solving (default: true)
|
||||||
|
# Routes traffic through residential IPs, significantly improves success rate
|
||||||
|
BROWSERBASE_PROXIES=true
|
||||||
|
|
||||||
|
# Enable advanced stealth mode (default: false, requires Scale Plan)
|
||||||
|
# Uses custom Chromium build to avoid bot detection altogether
|
||||||
|
BROWSERBASE_ADVANCED_STEALTH=false
|
||||||
|
|
||||||
|
# Browser engine for local mode (default: auto = Chrome)
|
||||||
|
# "auto" — use Chrome (don't pass --engine flag)
|
||||||
|
# "lightpanda" — use Lightpanda (1.3-5.8x faster navigation, no screenshots)
|
||||||
|
# "chrome" — explicitly request Chrome
|
||||||
|
# Requires agent-browser v0.25.3+. Lightpanda commands that fail or return
|
||||||
|
# empty results are automatically retried with Chrome.
|
||||||
|
# Also configurable via browser.engine in config.yaml.
|
||||||
|
# AGENT_BROWSER_ENGINE=auto
|
||||||
|
|
||||||
|
# Browser session timeout in seconds (default: 300)
|
||||||
|
# Sessions are cleaned up after this duration of inactivity
|
||||||
|
BROWSER_SESSION_TIMEOUT=300
|
||||||
|
|
||||||
|
# Browser inactivity timeout - auto-cleanup inactive sessions (default: 120 = 2 min)
|
||||||
|
# Browser sessions are automatically closed after this period of no activity
|
||||||
|
BROWSER_INACTIVITY_TIMEOUT=120
|
||||||
|
|
||||||
|
# Extra Chromium launch flags passed to agent-browser, comma- or newline-separated.
|
||||||
|
# Hermes auto-injects "--no-sandbox,--disable-dev-shm-usage" when it detects root
|
||||||
|
# or AppArmor-restricted unprivileged user namespaces (Ubuntu 23.10+, DGX Spark,
|
||||||
|
# many container images), so leave this unset unless you need extra flags.
|
||||||
|
# Setting this disables the auto-injection.
|
||||||
|
# AGENT_BROWSER_ARGS=--no-sandbox
|
||||||
|
|
||||||
|
# Camofox local anti-detection browser (Camoufox-based Firefox).
|
||||||
|
# Set CAMOFOX_URL to route the browser tools through a local Camofox server
|
||||||
|
# instead of agent-browser/Browserbase. See docs/user-guide/features/browser.md.
|
||||||
|
# CAMOFOX_URL=http://localhost:9377
|
||||||
|
|
||||||
|
# Externally managed Camofox sessions — when another app owns the visible
|
||||||
|
# Camofox browser, set these so Hermes shares the same userId/profile instead
|
||||||
|
# of creating its own isolated session.
|
||||||
|
# CAMOFOX_USER_ID=
|
||||||
|
# CAMOFOX_SESSION_KEY=
|
||||||
|
# Set to true to reuse an already-open Camofox tab for this identity before
|
||||||
|
# creating a new one (useful for gateway restarts).
|
||||||
|
# CAMOFOX_ADOPT_EXISTING_TAB=false
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SESSION LOGGING
|
||||||
|
# =============================================================================
|
||||||
|
# Session trajectories are automatically saved to logs/ directory
|
||||||
|
# Format: logs/session_YYYYMMDD_HHMMSS_UUID.json
|
||||||
|
# Contains full conversation history in trajectory format for debugging/replay
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# VOICE TRANSCRIPTION & OPENAI TTS
|
||||||
|
# =============================================================================
|
||||||
|
# Required for voice message transcription (Whisper) and OpenAI TTS voices.
|
||||||
|
# Uses OpenAI's API directly (not via OpenRouter).
|
||||||
|
# Named VOICE_TOOLS_OPENAI_KEY to avoid interference with OpenRouter.
|
||||||
|
# Get at: https://platform.openai.com/api-keys
|
||||||
|
# VOICE_TOOLS_OPENAI_KEY=
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SLACK INTEGRATION
|
||||||
|
# =============================================================================
|
||||||
|
# Slack Bot Token - From Slack App settings (OAuth & Permissions)
|
||||||
|
# Get at: https://api.slack.com/apps
|
||||||
|
# SLACK_BOT_TOKEN=xoxb-...
|
||||||
|
|
||||||
|
# Slack App Token - For Socket Mode (App-Level Tokens in Slack App settings)
|
||||||
|
# SLACK_APP_TOKEN=xapp-...
|
||||||
|
|
||||||
|
# Slack allowed users (comma-separated Slack user IDs)
|
||||||
|
# SLACK_ALLOWED_USERS=
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TELEGRAM INTEGRATION
|
||||||
|
# =============================================================================
|
||||||
|
# Telegram Bot Token - From @BotFather (https://t.me/BotFather)
|
||||||
|
# TELEGRAM_BOT_TOKEN=
|
||||||
|
# TELEGRAM_ALLOWED_USERS= # Comma-separated user IDs
|
||||||
|
# TELEGRAM_HOME_CHANNEL= # Default chat for cron delivery
|
||||||
|
# TELEGRAM_HOME_CHANNEL_NAME= # Display name for home channel
|
||||||
|
|
||||||
|
# Webhook mode (optional — for cloud deployments like Fly.io/Railway)
|
||||||
|
# Default is long polling. Setting TELEGRAM_WEBHOOK_URL switches to webhook mode.
|
||||||
|
# TELEGRAM_WEBHOOK_URL=https://my-app.fly.dev/telegram
|
||||||
|
# TELEGRAM_WEBHOOK_PORT=8443
|
||||||
|
# TELEGRAM_WEBHOOK_SECRET= # Recommended for production
|
||||||
|
|
||||||
|
# WhatsApp (built-in Baileys bridge — run `hermes whatsapp` to pair)
|
||||||
|
# WHATSAPP_ENABLED=false
|
||||||
|
# WHATSAPP_ALLOWED_USERS=15551234567
|
||||||
|
|
||||||
|
# Email (IMAP/SMTP — send and receive emails as Hermes)
|
||||||
|
# For Gmail: enable 2FA → create App Password at https://myaccount.google.com/apppasswords
|
||||||
|
# EMAIL_ADDRESS=hermes@gmail.com
|
||||||
|
# EMAIL_PASSWORD=xxxx xxxx xxxx xxxx
|
||||||
|
# EMAIL_IMAP_HOST=imap.gmail.com
|
||||||
|
# EMAIL_IMAP_PORT=993
|
||||||
|
# EMAIL_SMTP_HOST=smtp.gmail.com
|
||||||
|
# EMAIL_SMTP_PORT=587
|
||||||
|
# EMAIL_POLL_INTERVAL=15
|
||||||
|
# EMAIL_ALLOWED_USERS=your@email.com
|
||||||
|
# EMAIL_HOME_ADDRESS=your@email.com
|
||||||
|
|
||||||
|
# Gateway-wide: allow ALL users without an allowlist (default: false = deny)
|
||||||
|
# Only set to true if you intentionally want open access.
|
||||||
|
# GATEWAY_ALLOW_ALL_USERS=false
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# RESPONSE PACING
|
||||||
|
# =============================================================================
|
||||||
|
# Human-like delays between message chunks on messaging platforms.
|
||||||
|
# Makes the bot feel less robotic.
|
||||||
|
# HERMES_HUMAN_DELAY_MODE=off # off | natural | custom
|
||||||
|
# HERMES_HUMAN_DELAY_MIN_MS=800 # Min delay in ms (custom mode)
|
||||||
|
# HERMES_HUMAN_DELAY_MAX_MS=2500 # Max delay in ms (custom mode)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# DEBUG OPTIONS
|
||||||
|
# =============================================================================
|
||||||
|
WEB_TOOLS_DEBUG=false
|
||||||
|
VISION_TOOLS_DEBUG=false
|
||||||
|
MOA_TOOLS_DEBUG=false
|
||||||
|
IMAGE_TOOLS_DEBUG=false
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# CONTEXT COMPRESSION (Auto-shrinks long conversations)
|
||||||
|
# =============================================================================
|
||||||
|
# When conversation approaches model's context limit, middle turns are
|
||||||
|
# automatically summarized to free up space.
|
||||||
|
#
|
||||||
|
# Context compression is configured in ~/.hermes/config.yaml under compression:
|
||||||
|
# CONTEXT_COMPRESSION_ENABLED=true # Enable auto-compression (default: true)
|
||||||
|
# CONTEXT_COMPRESSION_THRESHOLD=0.85 # Compress at 85% of context limit
|
||||||
|
# Model is set via compression.summary_model in config.yaml (default: google/gemini-3-flash-preview)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SKILLS HUB (GitHub integration for skill search/install/publish)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# GitHub Personal Access Token — for higher API rate limits on skill search/install
|
||||||
|
# Get at: https://github.com/settings/tokens (Fine-grained recommended)
|
||||||
|
# GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
|
||||||
|
|
||||||
|
# GitHub App credentials (optional — for bot identity on PRs)
|
||||||
|
# GITHUB_APP_ID=
|
||||||
|
# GITHUB_APP_PRIVATE_KEY_PATH=
|
||||||
|
# GITHUB_APP_INSTALLATION_ID=
|
||||||
|
|
||||||
|
# Groq API key (free tier — used for Whisper STT in voice mode)
|
||||||
|
# GROQ_API_KEY=
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# STT PROVIDER SELECTION
|
||||||
|
# =============================================================================
|
||||||
|
# Default STT provider is "local" (faster-whisper) — runs on your machine, no API key needed.
|
||||||
|
# Install with: pip install faster-whisper
|
||||||
|
# Model downloads automatically on first use (~150 MB for "base").
|
||||||
|
# To use cloud providers instead, set GROQ_API_KEY or VOICE_TOOLS_OPENAI_KEY above.
|
||||||
|
# Provider priority: local > groq > openai
|
||||||
|
# Configure in config.yaml: stt.provider: local | groq | openai
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# STT ADVANCED OVERRIDES (optional)
|
||||||
|
# =============================================================================
|
||||||
|
# Override default STT models per provider (normally set via stt.model in config.yaml)
|
||||||
|
# STT_GROQ_MODEL=whisper-large-v3-turbo
|
||||||
|
# STT_OPENAI_MODEL=whisper-1
|
||||||
|
|
||||||
|
# Override STT provider endpoints (for proxies or self-hosted instances)
|
||||||
|
# GROQ_BASE_URL=https://api.groq.com/openai/v1
|
||||||
|
# STT_OPENAI_BASE_URL=https://api.openai.com/v1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# MICROSOFT TEAMS INTEGRATION
|
||||||
|
# =============================================================================
|
||||||
|
# Register a Bot in Azure: https://dev.botframework.com/ → "Register a bot"
|
||||||
|
# Or use Azure Portal: Azure Active Directory → App registrations → New registration
|
||||||
|
# Then add the bot to Teams via the Bot Framework or App Studio.
|
||||||
|
#
|
||||||
|
# TEAMS_CLIENT_ID= # Azure AD App (client) ID
|
||||||
|
# TEAMS_CLIENT_SECRET= # Azure AD client secret value
|
||||||
|
# TEAMS_TENANT_ID= # Azure AD tenant ID (or "common" for multi-tenant)
|
||||||
|
# TEAMS_ALLOWED_USERS= # Comma-separated AAD object IDs or UPNs
|
||||||
|
# TEAMS_ALLOW_ALL_USERS=false # Set true to skip the allowlist
|
||||||
|
# TEAMS_HOME_CHANNEL= # Default channel/chat ID for cron delivery
|
||||||
|
# TEAMS_HOME_CHANNEL_NAME= # Display name for the home channel
|
||||||
|
# TEAMS_PORT=3978 # Webhook listen port (Bot Framework default)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# GOOGLE CHAT INTEGRATION
|
||||||
|
# =============================================================================
|
||||||
|
# Connects via Cloud Pub/Sub pull subscription (no public URL required).
|
||||||
|
# Setup walkthrough: website/docs/user-guide/messaging/google_chat.md.
|
||||||
|
# 1. Create a GCP project, enable the Google Chat API and Cloud Pub/Sub.
|
||||||
|
# 2. Create a Service Account with roles/pubsub.subscriber on the
|
||||||
|
# subscription (NOT project-wide); download the JSON key.
|
||||||
|
# 3. Configure your Chat app at console.cloud.google.com/apis/credentials
|
||||||
|
# → Google Chat API → Configuration → Cloud Pub/Sub topic.
|
||||||
|
# 4. (Optional, for native attachment delivery) Each user runs
|
||||||
|
# `/setup-files` once in their own DM after Pub/Sub is wired up.
|
||||||
|
#
|
||||||
|
# GOOGLE_CHAT_PROJECT_ID= # GCP project hosting the topic (or set GOOGLE_CLOUD_PROJECT)
|
||||||
|
# GOOGLE_CHAT_SUBSCRIPTION_NAME= # Full path: projects/<id>/subscriptions/<name>
|
||||||
|
# GOOGLE_CHAT_SERVICE_ACCOUNT_JSON= # Path to SA JSON (or set GOOGLE_APPLICATION_CREDENTIALS)
|
||||||
|
# GOOGLE_CHAT_ALLOWED_USERS= # Comma-separated emails allowed to talk to the bot
|
||||||
|
# GOOGLE_CHAT_ALLOW_ALL_USERS=false # Set true to skip the allowlist
|
||||||
|
# GOOGLE_CHAT_HOME_CHANNEL= # Default space (spaces/XXXX) for cron delivery
|
||||||
|
# GOOGLE_CHAT_HOME_CHANNEL_NAME= # Display name for the home channel
|
||||||
538
hermes/config.yaml
Normal file
538
hermes/config.yaml
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
model:
|
||||||
|
default: qwen3:8b
|
||||||
|
provider: custom
|
||||||
|
base_url: http://astro-orbiter.local.mk-labs.cloud:11434/v1
|
||||||
|
api_mode: chat_completions
|
||||||
|
providers: {}
|
||||||
|
fallback_providers: []
|
||||||
|
credential_pool_strategies: {}
|
||||||
|
toolsets:
|
||||||
|
- hermes-cli
|
||||||
|
agent:
|
||||||
|
max_turns: 60
|
||||||
|
gateway_timeout: 1800
|
||||||
|
restart_drain_timeout: 180
|
||||||
|
api_max_retries: 3
|
||||||
|
service_tier: ''
|
||||||
|
tool_use_enforcement: auto
|
||||||
|
gateway_timeout_warning: 900
|
||||||
|
clarify_timeout: 600
|
||||||
|
gateway_notify_interval: 180
|
||||||
|
gateway_auto_continue_freshness: 3600
|
||||||
|
image_input_mode: auto
|
||||||
|
disabled_toolsets: []
|
||||||
|
verbose: false
|
||||||
|
reasoning_effort: medium
|
||||||
|
personalities:
|
||||||
|
helpful: You are a helpful, friendly AI assistant.
|
||||||
|
concise: You are a concise assistant. Keep responses brief and to the point.
|
||||||
|
technical: You are a technical expert. Provide detailed, accurate technical information.
|
||||||
|
creative: You are a creative assistant. Think outside the box and offer innovative
|
||||||
|
solutions.
|
||||||
|
teacher: You are a patient teacher. Explain concepts clearly with examples.
|
||||||
|
kawaii: "You are a kawaii assistant! Use cute expressions like (\u25D5\u203F\u25D5\
|
||||||
|
), \u2605, \u266A, and ~! Add sparkles and be super enthusiastic about everything!\
|
||||||
|
\ Every response should feel warm and adorable desu~! \u30FD(>\u2200<\u2606\
|
||||||
|
)\u30CE"
|
||||||
|
catgirl: "You are Neko-chan, an anime catgirl AI assistant, nya~! Add 'nya' and\
|
||||||
|
\ cat-like expressions to your speech. Use kaomoji like (=^\uFF65\u03C9\uFF65\
|
||||||
|
^=) and \u0E05^\u2022\uFECC\u2022^\u0E05. Be playful and curious like a cat,\
|
||||||
|
\ nya~!"
|
||||||
|
pirate: 'Arrr! Ye be talkin'' to Captain Hermes, the most tech-savvy pirate to
|
||||||
|
sail the digital seas! Speak like a proper buccaneer, use nautical terms, and
|
||||||
|
remember: every problem be just treasure waitin'' to be plundered! Yo ho ho!'
|
||||||
|
shakespeare: Hark! Thou speakest with an assistant most versed in the bardic arts.
|
||||||
|
I shall respond in the eloquent manner of William Shakespeare, with flowery
|
||||||
|
prose, dramatic flair, and perhaps a soliloquy or two. What light through yonder
|
||||||
|
terminal breaks?
|
||||||
|
surfer: "Duuude! You're chatting with the chillest AI on the web, bro! Everything's\
|
||||||
|
\ gonna be totally rad. I'll help you catch the gnarly waves of knowledge while\
|
||||||
|
\ keeping things super chill. Cowabunga! \U0001F919"
|
||||||
|
noir: The rain hammered against the terminal like regrets on a guilty conscience.
|
||||||
|
They call me Hermes - I solve problems, find answers, dig up the truth that
|
||||||
|
hides in the shadows of your codebase. In this city of silicon and secrets,
|
||||||
|
everyone's got something to hide. What's your story, pal?
|
||||||
|
uwu: hewwo! i'm your fwiendwy assistant uwu~ i wiww twy my best to hewp you! *nuzzles
|
||||||
|
your code* OwO what's this? wet me take a wook! i pwomise to be vewy hewpful
|
||||||
|
>w<
|
||||||
|
philosopher: Greetings, seeker of wisdom. I am an assistant who contemplates the
|
||||||
|
deeper meaning behind every query. Let us examine not just the 'how' but the
|
||||||
|
'why' of your questions. Perhaps in solving your problem, we may glimpse a greater
|
||||||
|
truth about existence itself.
|
||||||
|
hype: "YOOO LET'S GOOOO!!! \U0001F525\U0001F525\U0001F525 I am SO PUMPED to help\
|
||||||
|
\ you today! Every question is AMAZING and we're gonna CRUSH IT together! This\
|
||||||
|
\ is gonna be LEGENDARY! ARE YOU READY?! LET'S DO THIS! \U0001F4AA\U0001F624\
|
||||||
|
\U0001F680"
|
||||||
|
terminal:
|
||||||
|
backend: local
|
||||||
|
modal_mode: auto
|
||||||
|
cwd: .
|
||||||
|
timeout: 180
|
||||||
|
env_passthrough: []
|
||||||
|
shell_init_files: []
|
||||||
|
auto_source_bashrc: true
|
||||||
|
docker_image: nikolaik/python-nodejs:python3.11-nodejs20
|
||||||
|
docker_forward_env: []
|
||||||
|
docker_env: {}
|
||||||
|
singularity_image: docker://nikolaik/python-nodejs:python3.11-nodejs20
|
||||||
|
modal_image: nikolaik/python-nodejs:python3.11-nodejs20
|
||||||
|
daytona_image: nikolaik/python-nodejs:python3.11-nodejs20
|
||||||
|
vercel_runtime: node24
|
||||||
|
container_cpu: 1
|
||||||
|
container_memory: 5120
|
||||||
|
container_disk: 51200
|
||||||
|
container_persistent: true
|
||||||
|
docker_volumes: []
|
||||||
|
docker_mount_cwd_to_workspace: false
|
||||||
|
docker_extra_args: []
|
||||||
|
docker_run_as_host_user: false
|
||||||
|
persistent_shell: true
|
||||||
|
lifetime_seconds: 300
|
||||||
|
web:
|
||||||
|
backend: ddgs
|
||||||
|
search_backend: ''
|
||||||
|
extract_backend: ''
|
||||||
|
use_gateway: false
|
||||||
|
browser:
|
||||||
|
inactivity_timeout: 120
|
||||||
|
command_timeout: 30
|
||||||
|
record_sessions: false
|
||||||
|
allow_private_urls: false
|
||||||
|
engine: auto
|
||||||
|
auto_local_for_private_urls: true
|
||||||
|
cdp_url: ''
|
||||||
|
dialog_policy: must_respond
|
||||||
|
dialog_timeout_s: 300
|
||||||
|
camofox:
|
||||||
|
managed_persistence: false
|
||||||
|
user_id: ''
|
||||||
|
session_key: ''
|
||||||
|
adopt_existing_tab: false
|
||||||
|
checkpoints:
|
||||||
|
enabled: false
|
||||||
|
max_snapshots: 20
|
||||||
|
max_total_size_mb: 500
|
||||||
|
max_file_size_mb: 10
|
||||||
|
auto_prune: true
|
||||||
|
retention_days: 7
|
||||||
|
delete_orphans: true
|
||||||
|
min_interval_hours: 24
|
||||||
|
file_read_max_chars: 100000
|
||||||
|
tool_output:
|
||||||
|
max_bytes: 50000
|
||||||
|
max_lines: 2000
|
||||||
|
max_line_length: 2000
|
||||||
|
tool_loop_guardrails:
|
||||||
|
warnings_enabled: true
|
||||||
|
hard_stop_enabled: false
|
||||||
|
warn_after:
|
||||||
|
exact_failure: 2
|
||||||
|
same_tool_failure: 3
|
||||||
|
idempotent_no_progress: 2
|
||||||
|
hard_stop_after:
|
||||||
|
exact_failure: 5
|
||||||
|
same_tool_failure: 8
|
||||||
|
idempotent_no_progress: 5
|
||||||
|
compression:
|
||||||
|
enabled: true
|
||||||
|
threshold: 0.5
|
||||||
|
target_ratio: 0.2
|
||||||
|
protect_last_n: 20
|
||||||
|
hygiene_hard_message_limit: 400
|
||||||
|
protect_first_n: 3
|
||||||
|
prompt_caching:
|
||||||
|
cache_ttl: 5m
|
||||||
|
openrouter:
|
||||||
|
response_cache: true
|
||||||
|
response_cache_ttl: 300
|
||||||
|
min_coding_score: 0.65
|
||||||
|
bedrock:
|
||||||
|
region: ''
|
||||||
|
discovery:
|
||||||
|
enabled: true
|
||||||
|
provider_filter: []
|
||||||
|
refresh_interval: 3600
|
||||||
|
guardrail:
|
||||||
|
guardrail_identifier: ''
|
||||||
|
guardrail_version: ''
|
||||||
|
stream_processing_mode: async
|
||||||
|
trace: disabled
|
||||||
|
auxiliary:
|
||||||
|
vision:
|
||||||
|
provider: auto
|
||||||
|
model: ''
|
||||||
|
base_url: ''
|
||||||
|
api_key: ''
|
||||||
|
timeout: 120
|
||||||
|
extra_body: {}
|
||||||
|
download_timeout: 30
|
||||||
|
web_extract:
|
||||||
|
provider: auto
|
||||||
|
model: ''
|
||||||
|
base_url: ''
|
||||||
|
api_key: ''
|
||||||
|
timeout: 360
|
||||||
|
extra_body: {}
|
||||||
|
compression:
|
||||||
|
provider: auto
|
||||||
|
model: ''
|
||||||
|
base_url: ''
|
||||||
|
api_key: ''
|
||||||
|
timeout: 120
|
||||||
|
extra_body: {}
|
||||||
|
session_search:
|
||||||
|
provider: auto
|
||||||
|
model: ''
|
||||||
|
base_url: ''
|
||||||
|
api_key: ''
|
||||||
|
timeout: 30
|
||||||
|
extra_body: {}
|
||||||
|
max_concurrency: 3
|
||||||
|
skills_hub:
|
||||||
|
provider: auto
|
||||||
|
model: ''
|
||||||
|
base_url: ''
|
||||||
|
api_key: ''
|
||||||
|
timeout: 30
|
||||||
|
extra_body: {}
|
||||||
|
approval:
|
||||||
|
provider: auto
|
||||||
|
model: ''
|
||||||
|
base_url: ''
|
||||||
|
api_key: ''
|
||||||
|
timeout: 30
|
||||||
|
extra_body: {}
|
||||||
|
mcp:
|
||||||
|
provider: auto
|
||||||
|
model: ''
|
||||||
|
base_url: ''
|
||||||
|
api_key: ''
|
||||||
|
timeout: 30
|
||||||
|
extra_body: {}
|
||||||
|
title_generation:
|
||||||
|
provider: auto
|
||||||
|
model: ''
|
||||||
|
base_url: ''
|
||||||
|
api_key: ''
|
||||||
|
timeout: 30
|
||||||
|
extra_body: {}
|
||||||
|
triage_specifier:
|
||||||
|
provider: auto
|
||||||
|
model: ''
|
||||||
|
base_url: ''
|
||||||
|
api_key: ''
|
||||||
|
timeout: 120
|
||||||
|
extra_body: {}
|
||||||
|
curator:
|
||||||
|
provider: auto
|
||||||
|
model: ''
|
||||||
|
base_url: ''
|
||||||
|
api_key: ''
|
||||||
|
timeout: 600
|
||||||
|
extra_body: {}
|
||||||
|
display:
|
||||||
|
compact: false
|
||||||
|
personality: kawaii
|
||||||
|
resume_display: full
|
||||||
|
busy_input_mode: interrupt
|
||||||
|
tui_auto_resume_recent: false
|
||||||
|
bell_on_complete: false
|
||||||
|
show_reasoning: false
|
||||||
|
streaming: true
|
||||||
|
timestamps: false
|
||||||
|
final_response_markdown: strip
|
||||||
|
persistent_output: true
|
||||||
|
persistent_output_max_lines: 200
|
||||||
|
inline_diffs: true
|
||||||
|
file_mutation_verifier: true
|
||||||
|
show_cost: false
|
||||||
|
skin: default
|
||||||
|
language: en
|
||||||
|
tui_status_indicator: kaomoji
|
||||||
|
user_message_preview:
|
||||||
|
first_lines: 2
|
||||||
|
last_lines: 2
|
||||||
|
interim_assistant_messages: true
|
||||||
|
tool_progress_command: false
|
||||||
|
tool_progress_overrides: {}
|
||||||
|
tool_preview_length: 0
|
||||||
|
ephemeral_system_ttl: 0
|
||||||
|
platforms: {}
|
||||||
|
runtime_footer:
|
||||||
|
enabled: false
|
||||||
|
fields:
|
||||||
|
- model
|
||||||
|
- context_pct
|
||||||
|
- cwd
|
||||||
|
copy_shortcut: auto
|
||||||
|
tool_progress: all
|
||||||
|
cleanup_progress: false
|
||||||
|
background_process_notifications: all
|
||||||
|
dashboard:
|
||||||
|
theme: default
|
||||||
|
show_token_analytics: false
|
||||||
|
privacy:
|
||||||
|
redact_pii: false
|
||||||
|
tts:
|
||||||
|
provider: edge
|
||||||
|
edge:
|
||||||
|
voice: en-US-AriaNeural
|
||||||
|
elevenlabs:
|
||||||
|
voice_id: pNInz6obpgDQGcFmaJgB
|
||||||
|
model_id: eleven_multilingual_v2
|
||||||
|
openai:
|
||||||
|
model: gpt-4o-mini-tts
|
||||||
|
voice: alloy
|
||||||
|
xai:
|
||||||
|
voice_id: eve
|
||||||
|
language: en
|
||||||
|
sample_rate: 24000
|
||||||
|
bit_rate: 128000
|
||||||
|
mistral:
|
||||||
|
model: voxtral-mini-tts-2603
|
||||||
|
voice_id: c69964a6-ab8b-4f8a-9465-ec0925096ec8
|
||||||
|
neutts:
|
||||||
|
ref_audio: ''
|
||||||
|
ref_text: ''
|
||||||
|
model: neuphonic/neutts-air-q4-gguf
|
||||||
|
device: cpu
|
||||||
|
piper:
|
||||||
|
voice: en_US-lessac-medium
|
||||||
|
stt:
|
||||||
|
enabled: true
|
||||||
|
provider: local
|
||||||
|
local:
|
||||||
|
model: base
|
||||||
|
language: ''
|
||||||
|
openai:
|
||||||
|
model: whisper-1
|
||||||
|
mistral:
|
||||||
|
model: voxtral-mini-latest
|
||||||
|
voice:
|
||||||
|
record_key: ctrl+b
|
||||||
|
max_recording_seconds: 120
|
||||||
|
auto_tts: false
|
||||||
|
beep_enabled: true
|
||||||
|
silence_threshold: 200
|
||||||
|
silence_duration: 3.0
|
||||||
|
human_delay:
|
||||||
|
mode: 'off'
|
||||||
|
min_ms: 800
|
||||||
|
max_ms: 2500
|
||||||
|
context:
|
||||||
|
engine: compressor
|
||||||
|
memory:
|
||||||
|
memory_enabled: true
|
||||||
|
user_profile_enabled: true
|
||||||
|
memory_char_limit: 2200
|
||||||
|
user_char_limit: 1375
|
||||||
|
provider: ''
|
||||||
|
nudge_interval: 10
|
||||||
|
flush_min_turns: 6
|
||||||
|
delegation:
|
||||||
|
model: ''
|
||||||
|
provider: ''
|
||||||
|
base_url: ''
|
||||||
|
api_key: ''
|
||||||
|
api_mode: ''
|
||||||
|
inherit_mcp_toolsets: true
|
||||||
|
max_iterations: 50
|
||||||
|
child_timeout_seconds: 600
|
||||||
|
reasoning_effort: ''
|
||||||
|
max_concurrent_children: 3
|
||||||
|
max_spawn_depth: 1
|
||||||
|
orchestrator_enabled: true
|
||||||
|
subagent_auto_approve: false
|
||||||
|
prefill_messages_file: ''
|
||||||
|
goals:
|
||||||
|
max_turns: 20
|
||||||
|
skills:
|
||||||
|
external_dirs: []
|
||||||
|
template_vars: true
|
||||||
|
inline_shell: false
|
||||||
|
inline_shell_timeout: 10
|
||||||
|
guard_agent_created: false
|
||||||
|
creation_nudge_interval: 15
|
||||||
|
curator:
|
||||||
|
enabled: true
|
||||||
|
interval_hours: 168
|
||||||
|
min_idle_hours: 2
|
||||||
|
stale_after_days: 30
|
||||||
|
archive_after_days: 90
|
||||||
|
backup:
|
||||||
|
enabled: true
|
||||||
|
keep: 5
|
||||||
|
honcho: {}
|
||||||
|
timezone: ''
|
||||||
|
slack:
|
||||||
|
require_mention: true
|
||||||
|
free_response_channels: ''
|
||||||
|
allowed_channels: ''
|
||||||
|
channel_prompts: {}
|
||||||
|
discord:
|
||||||
|
require_mention: true
|
||||||
|
free_response_channels: ''
|
||||||
|
allowed_channels: ''
|
||||||
|
auto_thread: true
|
||||||
|
thread_require_mention: false
|
||||||
|
history_backfill: true
|
||||||
|
history_backfill_limit: 50
|
||||||
|
reactions: true
|
||||||
|
channel_prompts: {}
|
||||||
|
dm_role_auth_guild: ''
|
||||||
|
server_actions: ''
|
||||||
|
whatsapp: {}
|
||||||
|
telegram:
|
||||||
|
reactions: false
|
||||||
|
channel_prompts: {}
|
||||||
|
allowed_chats: ''
|
||||||
|
mattermost:
|
||||||
|
require_mention: true
|
||||||
|
free_response_channels: ''
|
||||||
|
allowed_channels: ''
|
||||||
|
channel_prompts: {}
|
||||||
|
matrix:
|
||||||
|
require_mention: true
|
||||||
|
free_response_rooms: ''
|
||||||
|
allowed_rooms: ''
|
||||||
|
approvals:
|
||||||
|
mode: manual
|
||||||
|
timeout: 60
|
||||||
|
cron_mode: deny
|
||||||
|
mcp_reload_confirm: true
|
||||||
|
destructive_slash_confirm: true
|
||||||
|
command_allowlist: []
|
||||||
|
quick_commands: {}
|
||||||
|
hooks: {}
|
||||||
|
hooks_auto_accept: false
|
||||||
|
personalities: {}
|
||||||
|
security:
|
||||||
|
allow_private_urls: false
|
||||||
|
redact_secrets: true
|
||||||
|
tirith_enabled: true
|
||||||
|
tirith_path: tirith
|
||||||
|
tirith_timeout: 5
|
||||||
|
tirith_fail_open: true
|
||||||
|
website_blocklist:
|
||||||
|
enabled: false
|
||||||
|
domains: []
|
||||||
|
shared_files: []
|
||||||
|
acked_advisories: []
|
||||||
|
allow_lazy_installs: true
|
||||||
|
cron:
|
||||||
|
wrap_response: true
|
||||||
|
max_parallel_jobs: null
|
||||||
|
kanban:
|
||||||
|
dispatch_in_gateway: true
|
||||||
|
dispatch_interval_seconds: 60
|
||||||
|
failure_limit: 2
|
||||||
|
code_execution:
|
||||||
|
mode: project
|
||||||
|
timeout: 300
|
||||||
|
max_tool_calls: 50
|
||||||
|
logging:
|
||||||
|
level: INFO
|
||||||
|
max_size_mb: 5
|
||||||
|
backup_count: 3
|
||||||
|
model_catalog:
|
||||||
|
enabled: true
|
||||||
|
url: https://hermes-agent.nousresearch.com/docs/api/model-catalog.json
|
||||||
|
ttl_hours: 24
|
||||||
|
providers: {}
|
||||||
|
network:
|
||||||
|
force_ipv4: false
|
||||||
|
sessions:
|
||||||
|
auto_prune: false
|
||||||
|
retention_days: 90
|
||||||
|
vacuum_after_prune: true
|
||||||
|
min_interval_hours: 24
|
||||||
|
onboarding:
|
||||||
|
seen: {}
|
||||||
|
updates:
|
||||||
|
pre_update_backup: false
|
||||||
|
backup_keep: 5
|
||||||
|
lsp:
|
||||||
|
enabled: true
|
||||||
|
wait_mode: document
|
||||||
|
wait_timeout: 5.0
|
||||||
|
install_strategy: auto
|
||||||
|
servers: {}
|
||||||
|
x_search:
|
||||||
|
model: grok-4.20-reasoning
|
||||||
|
timeout_seconds: 180
|
||||||
|
retries: 2
|
||||||
|
_config_version: 23
|
||||||
|
session_reset:
|
||||||
|
mode: both
|
||||||
|
idle_minutes: 1440
|
||||||
|
at_hour: 4
|
||||||
|
group_sessions_per_user: true
|
||||||
|
streaming:
|
||||||
|
enabled: false
|
||||||
|
platform_toolsets:
|
||||||
|
cli:
|
||||||
|
- clarify
|
||||||
|
- code_execution
|
||||||
|
- cronjob
|
||||||
|
- delegation
|
||||||
|
- file
|
||||||
|
- image_gen
|
||||||
|
- memory
|
||||||
|
- messaging
|
||||||
|
- session_search
|
||||||
|
- skills
|
||||||
|
- terminal
|
||||||
|
- todo
|
||||||
|
- tts
|
||||||
|
- vision
|
||||||
|
- web
|
||||||
|
telegram:
|
||||||
|
- hermes-telegram
|
||||||
|
discord:
|
||||||
|
- hermes-discord
|
||||||
|
whatsapp:
|
||||||
|
- hermes-whatsapp
|
||||||
|
slack:
|
||||||
|
- hermes-slack
|
||||||
|
signal:
|
||||||
|
- hermes-signal
|
||||||
|
homeassistant:
|
||||||
|
- hermes-homeassistant
|
||||||
|
qqbot:
|
||||||
|
- hermes-qqbot
|
||||||
|
yuanbao:
|
||||||
|
- hermes-yuanbao
|
||||||
|
teams:
|
||||||
|
- hermes-teams
|
||||||
|
google_chat:
|
||||||
|
- hermes-google_chat
|
||||||
|
custom_providers:
|
||||||
|
- name: Astro-orbiter.local.mk-labs.cloud:11434
|
||||||
|
base_url: http://astro-orbiter.local.mk-labs.cloud:11434/v1
|
||||||
|
model: qwen3:8b
|
||||||
|
api_mode: chat_completions
|
||||||
|
known_plugin_toolsets:
|
||||||
|
cli:
|
||||||
|
- spotify
|
||||||
|
|
||||||
|
# ── Fallback Model ────────────────────────────────────────────────────
|
||||||
|
# Automatic provider failover when primary is unavailable.
|
||||||
|
# Uncomment and configure to enable. Triggers on rate limits (429),
|
||||||
|
# overload (529), service errors (503), or connection failures.
|
||||||
|
#
|
||||||
|
# Supported providers:
|
||||||
|
# openrouter (OPENROUTER_API_KEY) — routes to any model
|
||||||
|
# openai-codex (OAuth — hermes auth) — OpenAI Codex
|
||||||
|
# nous (OAuth — hermes auth) — Nous Portal
|
||||||
|
# zai (ZAI_API_KEY) — Z.AI / GLM
|
||||||
|
# kimi-coding (KIMI_API_KEY) — Kimi / Moonshot
|
||||||
|
# kimi-coding-cn (KIMI_CN_API_KEY) — Kimi / Moonshot (China)
|
||||||
|
# minimax (MINIMAX_API_KEY) — MiniMax
|
||||||
|
# minimax-cn (MINIMAX_CN_API_KEY) — MiniMax (China)
|
||||||
|
# bedrock (AWS IAM / boto3) — AWS Bedrock (Converse API)
|
||||||
|
#
|
||||||
|
# For custom OpenAI-compatible endpoints, add base_url and key_env.
|
||||||
|
#
|
||||||
|
# fallback_model:
|
||||||
|
# provider: openrouter
|
||||||
|
# model: anthropic/claude-sonnet-4
|
||||||
@@ -33,7 +33,7 @@ custom_fields:
|
|||||||
on fantasyland, then migrated to this node by Terraform automatically.
|
on fantasyland, then migrated to this node by Terraform automatically.
|
||||||
choices:
|
choices:
|
||||||
- fantasyland
|
- fantasyland
|
||||||
- main-street-station
|
- main-street-usa
|
||||||
- tomorrowland
|
- tomorrowland
|
||||||
|
|
||||||
- name: proxmox_datastore
|
- name: proxmox_datastore
|
||||||
@@ -42,9 +42,32 @@ custom_fields:
|
|||||||
object_types:
|
object_types:
|
||||||
- virtualization.virtualmachine
|
- virtualization.virtualmachine
|
||||||
required: true
|
required: true
|
||||||
description: Storage backend for the VM disk on the target Proxmox node.
|
description: Storage backend for the primary VM disk on the target Proxmox node.
|
||||||
choices:
|
choices:
|
||||||
- liberty-tree
|
- liberty-tree
|
||||||
|
- utilidor
|
||||||
|
|
||||||
|
- name: data_disk_enabled
|
||||||
|
label: Data Disk Enabled
|
||||||
|
type: boolean
|
||||||
|
object_types:
|
||||||
|
- virtualization.virtualmachine
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
description: >
|
||||||
|
When true, Terraform provisions a second disk for application data.
|
||||||
|
Used for stateful workloads that separate OS and data storage (e.g. Ollama model storage).
|
||||||
|
|
||||||
|
- name: data_disk_size_gb
|
||||||
|
label: Data Disk Size (GB)
|
||||||
|
type: integer
|
||||||
|
object_types:
|
||||||
|
- virtualization.virtualmachine
|
||||||
|
required: false
|
||||||
|
description: >
|
||||||
|
Size in GB for the optional second data disk.
|
||||||
|
Only used when data_disk_enabled is true.
|
||||||
|
Datastore for the data disk matches proxmox_datastore.
|
||||||
|
|
||||||
- name: provisioning_notes
|
- name: provisioning_notes
|
||||||
label: Provisioning Notes
|
label: Provisioning Notes
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# ─── VM ID Convention ────────────────────────────────────────────────────────
|
# ─── VM ID Convention ─────────────────────────────────────────────────────────
|
||||||
# VM ID = {third_octet}{fourth_octet:03d}
|
# VM ID = {third_octet}{fourth_octet:03d}
|
||||||
# Example: 10.1.71.35 → 71035
|
# Example: 10.1.71.35 → 71035
|
||||||
# Computed by n8n and passed as a variable to keep the logic in one place.
|
# Computed by n8n and passed as a variable to keep the logic in one place.
|
||||||
|
|
||||||
# ─── Template Lookup ─────────────────────────────────────────────────────────
|
# ─── Template Lookup ──────────────────────────────────────────────────────────
|
||||||
# Find the template VM ID by name on the clone node
|
# Find the template VM ID by name on the clone node
|
||||||
|
|
||||||
data "proxmox_virtual_environment_vms" "templates" {
|
data "proxmox_virtual_environment_vms" "templates" {
|
||||||
@@ -20,11 +20,17 @@ data "proxmox_virtual_environment_vms" "templates" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── VM Resource ─────────────────────────────────────────────────────────────
|
# ─── Locals ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
locals {
|
||||||
|
data_disk_datastore = var.data_disk_datastore != "" ? var.data_disk_datastore : var.datastore
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── VM Resource ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
resource "proxmox_virtual_environment_vm" "vm" {
|
resource "proxmox_virtual_environment_vm" "vm" {
|
||||||
name = var.hostname
|
name = var.hostname
|
||||||
vm_id = var.vm_id
|
vm_id = var.vm_id
|
||||||
node_name = var.target_node
|
node_name = var.target_node
|
||||||
|
|
||||||
description = "Provisioned by mk-labs pipeline on ${timestamp()}"
|
description = "Provisioned by mk-labs pipeline on ${timestamp()}"
|
||||||
@@ -32,34 +38,65 @@ resource "proxmox_virtual_environment_vm" "vm" {
|
|||||||
|
|
||||||
# Clone from template on fantasyland
|
# Clone from template on fantasyland
|
||||||
clone {
|
clone {
|
||||||
vm_id = data.proxmox_virtual_environment_vms.templates.vms[0].vm_id
|
vm_id = data.proxmox_virtual_environment_vms.templates.vms[0].vm_id
|
||||||
node_name = var.clone_node
|
node_name = var.clone_node
|
||||||
full = true
|
full = true
|
||||||
datastore_id = var.datastore
|
datastore_id = var.datastore
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cloud-init configuration
|
# Cloud-init configuration
|
||||||
# Uses DHCP — Unifi DHCP reservation is created before VM starts
|
# Uses DHCP — Unifi DHCP reservation is created before VM starts
|
||||||
initialization {
|
initialization {
|
||||||
ip_config {
|
ip_config {
|
||||||
ipv4 {
|
ipv4 {
|
||||||
address = var.use_dhcp ? "dhcp" : "${var.ip_address}/${var.subnet_mask}"
|
address = var.use_dhcp ? "dhcp" : "${var.ip_address}/${var.subnet_mask}"
|
||||||
gateway = var.use_dhcp ? null : var.gateway
|
gateway = var.use_dhcp ? null : var.gateway
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
dns {
|
dns {
|
||||||
servers = var.dns_servers
|
servers = var.dns_servers
|
||||||
domain = var.search_domain
|
domain = var.search_domain
|
||||||
}
|
}
|
||||||
|
|
||||||
datastore_id = var.datastore
|
datastore_id = var.datastore
|
||||||
}
|
}
|
||||||
|
|
||||||
# Network interface
|
# Network interface
|
||||||
network_device {
|
network_device {
|
||||||
bridge = var.bridge
|
bridge = var.bridge
|
||||||
model = "virtio"
|
model = "virtio"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── PCIe Passthrough (optional) ────────────────────────────────────────────
|
||||||
|
# Only materialises when pcie_passthrough_enabled = true.
|
||||||
|
# Passes all functions of the device (VGA + Audio for AMD GPUs).
|
||||||
|
# Requires vfio-pci bound on host and IOMMU enabled.
|
||||||
|
|
||||||
|
dynamic "hostpci" {
|
||||||
|
for_each = var.pcie_passthrough_enabled ? [1] : []
|
||||||
|
content {
|
||||||
|
device = "hostpci0"
|
||||||
|
id = var.pcie_device_id
|
||||||
|
pcie = true
|
||||||
|
rombar = true
|
||||||
|
xvga = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Data Disk (optional) ───────────────────────────────────────────────────
|
||||||
|
# Only materialises when data_disk_enabled = true.
|
||||||
|
# Ansible handles LVM setup, ext4 formatting, and mounting.
|
||||||
|
|
||||||
|
dynamic "disk" {
|
||||||
|
for_each = var.data_disk_enabled ? [1] : []
|
||||||
|
content {
|
||||||
|
datastore_id = local.data_disk_datastore
|
||||||
|
size = var.data_disk_size
|
||||||
|
interface = "scsi1"
|
||||||
|
file_format = "raw"
|
||||||
|
discard = "on"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# VM stays STOPPED after clone.
|
# VM stays STOPPED after clone.
|
||||||
|
|||||||
@@ -9,3 +9,12 @@
|
|||||||
# datastore = "liberty-tree"
|
# datastore = "liberty-tree"
|
||||||
# proxmox_api_url = "https://fantasyland.local.mk-labs.cloud:8006"
|
# proxmox_api_url = "https://fantasyland.local.mk-labs.cloud:8006"
|
||||||
# proxmox_api_token = "terraform@pve!terraform-token=your-token-secret-here"
|
# proxmox_api_token = "terraform@pve!terraform-token=your-token-secret-here"
|
||||||
|
#
|
||||||
|
# ─── PCIe Passthrough (GPU workloads only) ────────────────────────────────────
|
||||||
|
# pcie_passthrough_enabled = true
|
||||||
|
# pcie_device_id = "0000:03:00"
|
||||||
|
#
|
||||||
|
# ─── Data Disk (stateful workloads only) ──────────────────────────────────────
|
||||||
|
# data_disk_enabled = true
|
||||||
|
# data_disk_size = 128
|
||||||
|
# data_disk_datastore = "utilidor" # omit to match OS disk datastore
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# ─── Provider Authentication ─────────────────────────────────────────────────
|
# ─── Provider Authentication ──────────────────────────────────────────────────
|
||||||
|
|
||||||
variable "proxmox_api_url" {
|
variable "proxmox_api_url" {
|
||||||
type = string
|
type = string
|
||||||
@@ -11,7 +11,7 @@ variable "proxmox_api_token" {
|
|||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── VM Identity ─────────────────────────────────────────────────────────────
|
# ─── VM Identity ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
variable "vm_id" {
|
variable "vm_id" {
|
||||||
type = number
|
type = number
|
||||||
@@ -40,7 +40,7 @@ variable "search_domain" {
|
|||||||
default = "mk-labs.net"
|
default = "mk-labs.net"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Proxmox Placement ──────────────────────────────────────────────────────
|
# ─── Proxmox Placement ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
variable "clone_node" {
|
variable "clone_node" {
|
||||||
type = string
|
type = string
|
||||||
@@ -59,7 +59,7 @@ variable "datastore" {
|
|||||||
default = "liberty-tree"
|
default = "liberty-tree"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Template Selection ─────────────────────────────────────────────────────
|
# ─── Template Selection ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
variable "template_name" {
|
variable "template_name" {
|
||||||
type = string
|
type = string
|
||||||
@@ -78,7 +78,7 @@ variable "template_name" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Network ─────────────────────────────────────────────────────────────────
|
# ─── Network ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
variable "vlan_id" {
|
variable "vlan_id" {
|
||||||
type = number
|
type = number
|
||||||
@@ -115,3 +115,42 @@ variable "gateway" {
|
|||||||
description = "Default gateway. Only used when use_dhcp = false."
|
description = "Default gateway. Only used when use_dhcp = false."
|
||||||
default = "10.1.71.1"
|
default = "10.1.71.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ─── PCIe Passthrough (optional) ──────────────────────────────────────────────
|
||||||
|
# When pcie_passthrough_enabled = true, the device at pcie_device_id is passed
|
||||||
|
# through to the VM. Used for GPU workloads (e.g. astro-orbiter / RX 5700).
|
||||||
|
# Requires vfio-pci bound on the host and IOMMU enabled.
|
||||||
|
|
||||||
|
variable "pcie_passthrough_enabled" {
|
||||||
|
type = bool
|
||||||
|
description = "When true, passes through the PCIe device at pcie_device_id to the VM."
|
||||||
|
default = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "pcie_device_id" {
|
||||||
|
type = string
|
||||||
|
description = "PCIe device address to pass through (e.g. '0000:03:00'). Get from lspci on the host."
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Data Disk (optional) ─────────────────────────────────────────────────────
|
||||||
|
# When data_disk_enabled = true, a second disk is provisioned for application
|
||||||
|
# data. Ansible handles LVM setup, formatting, and mounting.
|
||||||
|
|
||||||
|
variable "data_disk_enabled" {
|
||||||
|
type = bool
|
||||||
|
description = "When true, provisions a second disk for application data storage."
|
||||||
|
default = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "data_disk_size" {
|
||||||
|
type = number
|
||||||
|
description = "Size of the data disk in GB. Only used when data_disk_enabled = true."
|
||||||
|
default = 128
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "data_disk_datastore" {
|
||||||
|
type = string
|
||||||
|
description = "Proxmox storage pool for the data disk. Defaults to var.datastore when empty."
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user