This commit is contained in:
admin_rb
2026-09-15 21:33:10 +02:00
parent 7c24c7ba7f
commit d095887d2e
118 changed files with 1655 additions and 21 deletions
+2
View File
@@ -0,0 +1,2 @@
**/bak.yml
**/vault.yml
+76
View File
@@ -0,0 +1,76 @@
# AIM --- Ansible Inventory Manager
AIM is an operator-focused Python application for managing Ansible
customer inventories, access configuration, Vaults and curated playbook
execution.
## Source layout
``` text
/etc/ansible/scripts/
├── README.md
├── install.md
├── CHANGELOG.md
├── docs/
├── pyproject.toml
└── src/
└── aim/
```
AIM uses `/etc/ansible/inventories/<customer>/hosts.yml` as the
inventory source of truth.
## Supported platform groups
- `linux`
- `windows`
- `sophosxgs`
- `pfsense`
A host can belong to multiple groups/subgroups. Platform groups remain
the top-level groups because they define Ansible connection semantics.
## Quick start
See [install.md](install.md) for installation, virtual-environment
setup, authorization-group configuration and recovery of Git-ignored
runtime data.
Start AIM with:
``` bash
aim
```
Useful UI troubleshooting modes:
``` bash
aim --no-clear
aim --plain
aim --live-output
```
## Documentation
- [Installation](install.md)
- [Operations](docs/OPERATIONS.md)
- [Inventory](docs/INVENTORY.md)
- [Windows / WinRM / AD](docs/WINDOWS.md)
- [Recovery](docs/RECOVERY.md)
- [Security and sensitive data](docs/SECURITY.md)
- [Development](docs/DEVELOPMENT.md)
- [Changelog](CHANGELOG.md)
## Important operational rules
AIM does not use `.hosts.tsv` as inventory state. `hosts.yml` is
authoritative.
Routine host changes use local YAML validation and do not unnecessarily
prompt for the Vault password. Explicit inventory validation may invoke
`ansible-inventory` and request the customer Vault password when a Vault
exists.
Existing customer-specific values and arbitrary valid YAML structures
should be preserved unless an operator explicitly requests an operation
that changes them.
+12
View File
@@ -0,0 +1,12 @@
root_dir: /etc/ansible
service_user: svc_ansible
required_group: root
platform_groups:
- linux
- windows
- sophosxgs
- pfsense
ui:
clear_screen: true
ascii: false
output: compact
+5
View File
@@ -0,0 +1,5 @@
domain_suffix: desq-gaming.lan
network_address: 192.168.20.0
netmask: 255.255.255.0
ad_dns_domain: ''
ad_netbios_domain: ''
@@ -0,0 +1 @@
# group_vars/all/main.yml for desq_gaming
@@ -0,0 +1,8 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCzzsHfog
Wv9erYAv5gNSMrAAAAGAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIFEjnIIVsyXXaoqd
VGOGwSPXthf8vzZ7L//SZP3OuJjxAAAAoPoC23Ps5HmvCpJrlOsubdcAjq0Ol65xhOb8VQ
OolhyCPFPj/eH1z21w/PvXhL1S8tDsBut27qW8+5dSwT2gqjtt/x2SiOQYMHt6EjGGAISu
NNy9L+vRcOFIB4Lo1IE/BMeZRUpgW2GXRFcQH9KgZXh/IWMDqcS5q8GbIT69OUxVUeBg3x
Wa5f3MyCUMEmIMkBcdbJrebWXLjDvVYDI3myE=
-----END OPENSSH PRIVATE KEY-----
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFEjnIIVsyXXaoqdVGOGwSPXthf8vzZ7L//SZP3OuJjx svc_ansible@desq_gaming
@@ -0,0 +1,7 @@
# Linux / SSH variables
ansible_connection: ssh
ansible_user: svc_ansible
ansible_private_key_file:
/etc/ansible/inventories/desq_gaming/group_vars/linux/.ssh/svc_ansible
ansible_ssh_pass: '{{ ansible_vault_linux_ssh_pass }}'
ansible_become_method: sudo
@@ -0,0 +1,2 @@
# pfSense-specific variables
{}
@@ -0,0 +1,7 @@
# SophosXGS-specific variables
#ansible_password: "{{ ansible_password }}" # Passwort wird aus --ask-pass übernommen
ansible_user: admin
ansible_connection: ansible.netcommon.httpapi
ansible_httpapi_validate_certs: false
ansible_httpapi_port: 4444
ansible_network_os: sophos.sophos_firewall.sfos
@@ -0,0 +1,7 @@
# Windows / WinRM variables
ansible_connection: winrm
ansible_port: 5986
ansible_winrm_transport: ntlm
ansible_winrm_server_cert_validation: ignore
ansible_user: svc_ansible
ansible_password: '{{ vault_windows_ansible_password }}'
@@ -0,0 +1,4 @@
all:
children:
desq_gaming:
children: {}
+99
View File
@@ -0,0 +1,99 @@
all:
children:
desq_gaming:
children:
linux:
children:
networking:
hosts:
dewenpm01.desq-gaming.lan:
ansible_host: 192.168.20.3
dewedns02.desq-gaming.lan:
ansible_host: 192.168.20.2
dewesrv-unifi02.desq-gaming.lan:
ansible_host: 192.168.99.5
backup:
hosts:
dewepbs01.desq-gaming.lan:
ansible_host: 192.168.99.32
proxmox:
hosts:
dewepbs01.desq-gaming.lan:
ansible_host: 192.168.99.32
dewepve01.desq-gaming.lan:
ansible_host: 192.168.99.31
hosting:
hosts:
dewepve01.desq-gaming.lan:
ansible_host: 192.168.99.31
management:
hosts:
dewesrv-ansible01.desq-gaming.lan:
ansible_host: 192.168.20.46
dewesrv-patch01.desq-gaming.lan:
ansible_host: 192.168.20.45
applications:
hosts:
dewesrv-budget02.desq-gaming.lan:
ansible_host: 192.168.20.44
dewesrv-cache02.desq-gaming.lan:
ansible_host: 192.168.20.24
dewesrv-cloud01.desq-gaming.lan:
ansible_host: 192.168.20.14
dewesrv-crafty02.desq-gaming.lan:
ansible_host: 192.168.20.19
dewesrv-db01.desq-gaming.lan:
ansible_host: 192.168.20.21
dewesrv-db02.desq-gaming.lan:
ansible_host: 192.168.20.23
dewesrv-docker02.desq-gaming.lan:
ansible_host: 192.168.20.30
dewesrv-git01.desq-gaming.lan:
ansible_host: 192.168.20.38
dewesrv-grafana01.desq-gaming.lan:
ansible_host: 192.168.20.22
dewesrv-ha01.desq-gaming.lan:
ansible_host: 192.168.30.10
dewesrv-homarr01.desq-gaming.lan:
ansible_host: 192.168.20.35
dewesrv-mail01.desq-gaming.lan:
ansible_host: 192.168.20.40
dewesrv-nodejs01.desq-gaming.lan:
ansible_host: 192.168.20.20
dewesrv-omv01.desq-gaming.lan:
ansible_host: 192.168.20.15
dewesrv-overseerr01.desq-gaming.lan:
ansible_host: 192.168.20.18
dewesrv-plex02.desq-gaming.lan:
ansible_host: 192.168.20.39
dewesrv-puppet01.desq-gaming.lan:
ansible_host: 192.168.20.25
dewesrv-recipe01.desq-gaming.lan:
ansible_host: 192.168.20.37
dewesrv-rust02.desq-gaming.lan:
ansible_host: 192.168.20.27
dewesrv-speed01.desq-gaming.lan:
ansible_host: 192.168.20.16
dewesrv-steam01.desq-gaming.lan:
ansible_host: 192.168.20.12
dewesrv-support01.desq-gaming.lan:
ansible_host: 192.168.20.28
dewesrv-tautulli01.desq-gaming.lan:
ansible_host: 192.168.20.17
dewesrv-tv01.desq-gaming.lan:
ansible_host: 192.168.20.43
dewesrv-uptime01.desq-gaming.lan:
ansible_host: 192.168.20.11
dewesrv-vault01.desq-gaming.lan:
ansible_host: 192.168.20.34
dewesrv-wallos01.desq-gaming.lan:
ansible_host: 192.168.20.29
dewesrv-wazuh01.desq-gaming.lan:
ansible_host: 192.168.20.13
dewesrv-wiki01.desq-gaming.lan:
ansible_host: 192.168.20.36
+64
View File
@@ -0,0 +1,64 @@
---
- name: "Checkmk | Cleanup agent"
hosts: all
gather_facts: true
tasks:
- name: "Windows | Remove installed Checkmk agent"
when: ansible_facts['os_family'] == 'Windows'
ansible.windows.win_powershell:
script: |
$Ansible.Changed = $false
$uninstallRoots = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
)
$products = foreach ($root in $uninstallRoots) {
if (Test-Path -LiteralPath $root) {
Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue |
ForEach-Object {
$product = Get-ItemProperty -LiteralPath $_.PSPath -ErrorAction SilentlyContinue
if ($product.DisplayName -like 'Check MK Agent*' -or
$product.DisplayName -like 'Checkmk Agent*') {
[PSCustomObject]@{
ProductCode = $_.PSChildName
DisplayName = $product.DisplayName
}
}
}
}
}
foreach ($product in $products) {
if ($product.ProductCode -match '^\{[0-9A-Fa-f-]+\}$') {
$process = Start-Process -FilePath 'msiexec.exe' `
-ArgumentList "/x $($product.ProductCode) /qn /norestart" `
-Wait -PassThru
if ($process.ExitCode -notin @(0, 1605, 1614, 3010)) {
throw "Failed to uninstall $($product.DisplayName). MSI exit code: $($process.ExitCode)"
}
if ($process.ExitCode -in @(0, 3010)) {
$Ansible.Changed = $true
}
}
}
$Ansible.Result = @{
removed_products = @($products.DisplayName)
}
- name: "Debian | Remove Checkmk agent"
when: ansible_facts['os_family'] == 'Debian'
ansible.builtin.apt:
name: check-mk-agent
state: absent
purge: true
- name: "RedHat | Remove Checkmk agent"
when: ansible_facts['os_family'] == 'RedHat'
ansible.builtin.dnf:
name: check-mk-agent
state: absent
+10
View File
@@ -0,0 +1,10 @@
---
- name: "Checkmk | Deploy agent, scripts and configuration"
hosts: all
gather_facts: true
roles:
- checkmk_agent
- server_role_selection
- checkmk_scripts
- checkmk_agent_config
+8
View File
@@ -0,0 +1,8 @@
---
- name: "Checkmk | Update agent configuration"
hosts: all
gather_facts: true
roles:
- server_role_selection
- checkmk_agent_config
+8
View File
@@ -0,0 +1,8 @@
---
- name: "Checkmk | Update monitoring scripts"
hosts: all
gather_facts: true
roles:
- server_role_selection
- checkmk_scripts
+38
View File
@@ -0,0 +1,38 @@
---
- name: "Debug | Disk usage"
hosts: all
gather_facts: true
tasks:
- name: "Linux | Collect filesystem usage"
when: ansible_facts['os_family'] != 'Windows'
ansible.builtin.command:
cmd: df -hP -x tmpfs -x devtmpfs
register: disk_usage_linux
changed_when: false
- name: "Linux | Show filesystem usage"
when: ansible_facts['os_family'] != 'Windows'
ansible.builtin.debug:
var: disk_usage_linux.stdout_lines
- name: "Windows | Collect filesystem drive usage"
when: ansible_facts['os_family'] == 'Windows'
ansible.windows.win_powershell:
script: |
Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" |
Select-Object DeviceID,
@{Name='SizeGB';Expression={[math]::Round($_.Size / 1GB, 2)}},
@{Name='FreeGB';Expression={[math]::Round($_.FreeSpace / 1GB, 2)}},
@{Name='UsedPercent';Expression={
if ($_.Size -gt 0) {
[math]::Round((($_.Size - $_.FreeSpace) / $_.Size) * 100, 1)
} else { 0 }
}}
register: disk_usage_windows
changed_when: false
- name: "Windows | Show filesystem drive usage"
when: ansible_facts['os_family'] == 'Windows'
ansible.builtin.debug:
var: disk_usage_windows.output
+13
View File
@@ -0,0 +1,13 @@
---
- name: "Debug | Ping hosts"
hosts: all
gather_facts: false
tasks:
- name: "Windows | WinRM ping"
when: ansible_connection | default('') == 'winrm'
ansible.windows.win_ping:
- name: "Linux | Ansible ping"
when: ansible_connection | default('ssh') != 'winrm'
ansible.builtin.ping:
+87
View File
@@ -0,0 +1,87 @@
---
- name: "Debug | Server Role Selection"
hosts: all
gather_facts: true
roles:
- server_role_selection
tasks:
- name: "Debug | Display detected server roles"
ansible.builtin.debug:
msg:
host: "{{ inventory_hostname }}"
os_family: "{{ ansible_facts['os_family'] | default('unknown') }}"
windows_roles:
domain_controller: "{{ is_dc | default(false) | bool }}"
dhcp_server: "{{ is_dhcp_server | default(false) | bool }}"
hyperv_host: "{{ is_hyperv_host | default(false) | bool }}"
veeam:
vbr: "{{ has_veeam_vbr | default(false) | bool }}"
vbo: "{{ has_veeam_vbo | default(false) | bool }}"
enterprise_manager: "{{ has_veeam_em | default(false) | bool }}"
linux_roles:
unifi_controller: "{{ is_unifi_controller | default(false) | bool }}"
optional_features:
linux_certificate_check: "{{ want_linux_check_certificate | default(false) | bool }}"
windows_citrix: "{{ want_windows_citrix | default(false) | bool }}"
windows_surebackup: "{{ want_windows_surebackup | default(false) | bool }}"
windows_backup: "{{ want_windows_backup | default(false) | bool }}"
- name: "Debug | Display Checkmk script deployment decisions"
ansible.builtin.debug:
msg:
linux:
unifi_controller:
deploy: "{{ is_unifi_controller | default(false) | bool }}"
reason: "UniFi Controller detected"
scripts:
- "check_unifi-controller.sh"
- "unifi.cfg"
certificate_directory:
deploy: "{{ want_linux_check_certificate | default(false) | bool }}"
reason: "Certificate directory monitoring explicitly enabled"
scripts:
- "check_certificate_directory.sh"
windows:
check_ping:
deploy: "{{ is_dc | default(false) | bool }}"
reason: "Domain Controller"
scripts:
- "check-ping.ps1"
veeam_config_backup:
deploy: "{{ has_veeam_vbr | default(false) | bool }}"
reason: "Veeam Backup & Replication detected"
scripts:
- "veeam_config_backup_status.ps1"
veeam_o365:
deploy: "{{ has_veeam_vbo | default(false) | bool }}"
reason: "Veeam Backup for Microsoft 365 detected"
scripts:
- "veeam_o365_status.ps1"
citrix_sessions:
deploy: "{{ want_windows_citrix | default(false) | bool }}"
reason: "Citrix monitoring explicitly enabled"
scripts:
- "citrix_sessions_customized.ps1"
veeam_surebackup:
deploy: "{{ want_windows_surebackup | default(false) | bool }}"
reason: "Veeam SureBackup monitoring explicitly enabled"
scripts:
- "veeam_surebackup_status.ps1"
windows_backup:
deploy: "{{ want_windows_backup | default(false) | bool }}"
reason: "Windows Backup monitoring explicitly enabled"
scripts:
- "windows-backup.ps1"
@@ -0,0 +1,74 @@
---
- name: "Maintenance | Backup system logs"
hosts: all
gather_facts: true
vars:
windows_event_log_backup_dir: 'C:\\Windows\\Temp\\EventLogBackup'
windows_event_logs_to_backup:
- Application
- System
- Security
linux_log_backup_dir: /var/backups/system-logs
linux_journal_since: "-24h"
tasks:
- name: "Windows | Ensure event log backup directory exists"
when: ansible_facts['os_family'] == 'Windows'
ansible.windows.win_file:
path: "{{ windows_event_log_backup_dir }}"
state: directory
- name: "Windows | Export event logs"
when: ansible_facts['os_family'] == 'Windows'
ansible.windows.win_command: >-
wevtutil epl {{ item }} "{{ windows_event_log_backup_dir }}\\{{ item }}.evtx" /ow:true
loop: "{{ windows_event_logs_to_backup }}"
changed_when: true
- name: "Linux | Ensure system log backup directory exists"
when: ansible_facts['os_family'] != 'Windows'
become: true
ansible.builtin.file:
path: "{{ linux_log_backup_dir }}"
state: directory
owner: root
group: root
mode: "0750"
- name: "Linux | Check whether systemd journal is available"
when: ansible_facts['os_family'] != 'Windows'
ansible.builtin.command:
cmd: journalctl --version
register: journalctl_available
changed_when: false
failed_when: false
- name: "Linux | Export systemd journal"
when:
- ansible_facts['os_family'] != 'Windows'
- journalctl_available.rc == 0
become: true
ansible.builtin.shell: >-
journalctl --since {{ linux_journal_since | quote }} --no-pager
> {{ (linux_log_backup_dir ~ '/journal.log') | quote }}
args:
executable: /bin/sh
changed_when: true
- name: "Linux | Backup traditional system logs"
when:
- ansible_facts['os_family'] != 'Windows'
- journalctl_available.rc != 0
become: true
ansible.builtin.shell: |
set -e
for file in /var/log/syslog /var/log/messages /var/log/auth.log /var/log/secure; do
if [ -f "$file" ]; then
cp -p "$file" "{{ linux_log_backup_dir }}/$(basename "$file")"
fi
done
args:
executable: /bin/sh
changed_when: true
+35
View File
@@ -0,0 +1,35 @@
---
- name: "Maintenance | Patch operating system"
hosts: all
gather_facts: true
tasks:
- name: "Debian | Update package cache and upgrade packages"
when: ansible_facts['os_family'] == 'Debian'
ansible.builtin.apt:
update_cache: true
upgrade: dist
- name: "RedHat | Upgrade installed packages"
when: ansible_facts['os_family'] == 'RedHat'
ansible.builtin.dnf:
name: '*'
state: latest
- name: "Windows | Install available updates"
when: ansible_facts['os_family'] == 'Windows'
ansible.windows.win_updates:
category_names:
- CriticalUpdates
- SecurityUpdates
- UpdateRollups
- Updates
reboot: false
register: windows_updates
- name: "Windows | Report reboot requirement"
when:
- ansible_facts['os_family'] == 'Windows'
- windows_updates.reboot_required | default(false)
ansible.builtin.debug:
msg: "Windows updates were installed and a reboot is required. No reboot was performed."
+15
View File
@@ -0,0 +1,15 @@
---
- name: "Maintenance | Reboot systems"
hosts: all
gather_facts: true
tasks:
- name: "Windows | Reboot system"
when: ansible_facts['os_family'] == 'Windows'
ansible.windows.win_reboot:
reboot_timeout: 1800
- name: "Linux | Reboot system"
when: ansible_facts['os_family'] != 'Windows'
ansible.builtin.reboot:
reboot_timeout: 1800
@@ -0,0 +1,82 @@
---
- name: "Maintenance | Start stopped automatic services"
hosts: all
gather_facts: true
tasks:
- name: "Windows | Start stopped automatic services"
when: ansible_facts['os_family'] == 'Windows'
ansible.windows.win_powershell:
script: |
$Ansible.Changed = $false
$started = @()
Get-CimInstance Win32_Service |
Where-Object {
$_.StartMode -eq 'Auto' -and
$_.State -ne 'Running'
} |
ForEach-Object {
try {
Start-Service -Name $_.Name -ErrorAction Stop
$started += $_.Name
$Ansible.Changed = $true
}
catch {
Write-Warning "Could not start service $($_.Name): $($_.Exception.Message)"
}
}
$Ansible.Result = @{
started_services = $started
}
register: started_services_windows
- name: "Windows | Show started services"
when: ansible_facts['os_family'] == 'Windows'
ansible.builtin.debug:
var: started_services_windows.result.started_services
- name: "Linux | Collect service facts"
when: ansible_facts['os_family'] != 'Windows'
ansible.builtin.service_facts:
- name: "Linux | Start stopped enabled systemd services"
when:
- ansible_facts['os_family'] != 'Windows'
- ansible_facts['service_mgr'] == 'systemd'
- item.value.status | default('') == 'enabled'
- item.value.state | default('') != 'running'
become: true
ansible.builtin.systemd:
name: "{{ item.key }}"
state: started
loop: "{{ ansible_facts.services | dict2items }}"
loop_control:
label: "{{ item.key }}"
register: started_services_linux
failed_when: false
- name: "Linux | Show services that were started"
when:
- ansible_facts['os_family'] != 'Windows'
- ansible_facts['service_mgr'] == 'systemd'
ansible.builtin.debug:
msg: >-
{{
started_services_linux.results
| default([])
| selectattr('changed', 'defined')
| selectattr('changed')
| map(attribute='item.key')
| list
}}
- name: "Linux | Report unsupported service manager"
when:
- ansible_facts['os_family'] != 'Windows'
- ansible_facts['service_mgr'] != 'systemd'
ansible.builtin.debug:
msg: >-
Automatic stopped-service recovery currently supports systemd hosts only.
Detected service manager: {{ ansible_facts['service_mgr'] }}
+9
View File
@@ -0,0 +1,9 @@
---
checkmk_linux_tmp_path: "/tmp"
checkmk_windows_tmp_path: "C:\\Windows\\Temp"
checkmk_deb_filename: "check-mk-agent.deb"
checkmk_rpm_filename: "check-mk-agent.rpm"
checkmk_msi_filename: "check_mk_agent.msi"
checkmk_linux_socket_name: "check-mk-agent.socket"
checkmk_linux_service_name: "check-mk-agent"
checkmk_windows_service_name: "CheckMkService"
+5
View File
@@ -0,0 +1,5 @@
Place the Checkmk agent packages here:
- `check-mk-agent.deb`
- `check-mk-agent.rpm`
- `check_mk_agent.msi`
Binary file not shown.
Binary file not shown.
Binary file not shown.
+55
View File
@@ -0,0 +1,55 @@
---
- name: "Enable & start Checkmk socket (if present)"
listen: "checkmk | linux | agent-ensure-running"
when: ansible_facts['os_family'] != "Windows"
ansible.builtin.systemd:
name: "{{ checkmk_linux_socket_name }}"
enabled: true
state: started
daemon_reload: true
failed_when: false
- name: "Enable & start Checkmk service (fallback/if present)"
listen: "checkmk | linux | agent-ensure-running"
when: ansible_facts['os_family'] != "Windows"
ansible.builtin.systemd:
name: "{{ checkmk_linux_service_name }}"
enabled: true
state: started
daemon_reload: true
failed_when: false
- name: "Windows | Detect Checkmk service name"
listen: "checkmk | windows | agent-ensure-running"
when: ansible_facts['os_family'] == "Windows"
ansible.windows.win_powershell:
script: |
$candidates = @('CheckMkService','Check_MK_Agent')
foreach ($n in $candidates) {
$svc = Get-Service -Name $n -ErrorAction SilentlyContinue
if ($svc) { $svc.Name; break }
}
register: cmk_detect
failed_when: false
- name: "Windows | Set detected service name"
listen: "checkmk | windows | agent-ensure-running"
when: ansible_facts['os_family'] == "Windows"
ansible.builtin.set_fact:
cmk_service_name: >-
{{
(cmk_detect.output[0] | default('') | trim)
if (cmk_detect.output | default([]) | length > 0)
else (checkmk_windows_service_name | default('CheckMkService'))
}}
- name: "Windows | Ensure Checkmk agent service running"
listen: "checkmk | windows | agent-ensure-running"
when:
- ansible_facts['os_family'] == "Windows"
- (cmk_service_name | default('')) | length > 0
ansible.windows.win_service:
name: "{{ cmk_service_name }}"
start_mode: auto
state: started
failed_when: false
+6
View File
@@ -0,0 +1,6 @@
---
galaxy_info:
role_name: checkmk_agent
description: Install Checkmk agent from local packages
min_ansible_version: "2.18"
dependencies: []
+11
View File
@@ -0,0 +1,11 @@
---
- name: "Debian | Copy Checkmk agent package"
ansible.builtin.copy:
src: "{{ checkmk_deb_filename }}"
dest: "{{ checkmk_linux_tmp_path }}/{{ checkmk_deb_filename }}"
mode: "0644"
- name: "Debian | Install Checkmk agent"
ansible.builtin.apt:
deb: "{{ checkmk_linux_tmp_path }}/{{ checkmk_deb_filename }}"
notify: "checkmk | linux | agent-ensure-running"
+12
View File
@@ -0,0 +1,12 @@
---
- name: Include Debian installation
ansible.builtin.include_tasks: debian.yml
when: ansible_facts['os_family'] == 'Debian'
- name: Include RedHat installation
ansible.builtin.include_tasks: redhat.yml
when: ansible_facts['os_family'] == 'RedHat'
- name: Include Windows installation
ansible.builtin.include_tasks: windows.yml
when: ansible_facts['os_family'] == 'Windows'
+12
View File
@@ -0,0 +1,12 @@
---
- name: "RedHat | Copy Checkmk agent package"
ansible.builtin.copy:
src: "{{ checkmk_rpm_filename }}"
dest: "{{ checkmk_linux_tmp_path }}/{{ checkmk_rpm_filename }}"
mode: "0644"
- name: "RedHat | Install Checkmk agent"
ansible.builtin.package:
name: "{{ checkmk_linux_tmp_path }}/{{ checkmk_rpm_filename }}"
state: present
notify: "checkmk | linux | agent-ensure-running"
+11
View File
@@ -0,0 +1,11 @@
---
- name: "Windows | Copy Checkmk agent MSI"
ansible.windows.win_copy:
src: "{{ checkmk_msi_filename }}"
dest: "{{ checkmk_windows_tmp_path }}\\{{ checkmk_msi_filename }}"
- name: "Windows | Install Checkmk agent from local MSI"
ansible.windows.win_package:
path: "{{ checkmk_windows_tmp_path }}\\{{ checkmk_msi_filename }}"
state: present
notify: "checkmk | windows | agent-ensure-running"
@@ -0,0 +1,9 @@
---
checkmk_windows_user_cfg: "C:\\ProgramData\\checkmk\\agent\\check_mk.user.yml"
checkmk_windows_updates_timeout: 3600
checkmk_windows_updates_cache: 43200
checkmk_mk_inventory_timeout: 120
checkmk_plugins_default_timeout: 120
checkmk_plugins_default_cache: 600
checkmk_extra_plugin_patterns: []
checkmk_extra_local_patterns: []
+6
View File
@@ -0,0 +1,6 @@
---
galaxy_info:
role_name: checkmk_agent_config
description: Render Windows Checkmk agent configuration from detected server roles
min_ansible_version: "2.18"
dependencies: []
+20
View File
@@ -0,0 +1,20 @@
---
- name: "Debug detected role flags"
when: ansible_facts['os_family'] == "Windows"
ansible.builtin.debug:
msg:
dc: "{{ is_dc | default(false) }}"
dhcp: "{{ is_dhcp_server | default(false) }}"
vbr: "{{ has_veeam_vbr | default(false) }}"
vbo: "{{ has_veeam_vbo | default(false) }}"
em: "{{ has_veeam_em | default(false) }}"
hv: "{{ is_hyperv_host | default(false) }}"
timeout_updates: "{{ checkmk_windows_updates_timeout }}"
- name: "Windows | Render check_mk.user.yml"
when: ansible_facts['os_family'] == "Windows"
ansible.windows.win_template:
src: "windows_check_mk.user.yml.j2"
dest: "{{ checkmk_windows_user_cfg }}"
backup: true
notify: "checkmk | windows | agent-ensure-running"
@@ -0,0 +1,130 @@
# Managed by Ansible (checkmk_agent_config)
# Windows Checkmk Agent user configuration built from detected roles.
# $CUSTOM_PLUGINS_PATH$ -> ProgramData\checkmk\agent\plugins
# $BUILTIN_PLUGINS_PATH$ -> Program Files (x86)\checkmk\service\plugins
# $CUSTOM_AGENT_PATH$ -> ProgramData\checkmk\agent
# $CUSTOM_LOCAL_PATH$ -> ProgramData\checkmk\agent\local
global:
_only_from:
_realtime:
enabled: yes
timeout: 90
port: 6559
encrypted: no
passphrase: this is my password
run:
- mem
- df
- winperf_processor
winperf:
counters:
- MSExchangeTransport Queues: msx_queues
_logfiles:
enabled: no
fileinfo:
path: []
logwatch:
logfile: []
plugins:
execution:
# --- Built-in defaults ---
- pattern: '$BUILTIN_PLUGINS_PATH$\windows_updates.vbs'
run: yes
async: yes
timeout: {{ checkmk_windows_updates_timeout }}
cache_age: {{ checkmk_windows_updates_cache }}
retry_count: 0
- pattern: '$BUILTIN_PLUGINS_PATH$\mk_inventory.vbs'
run: yes
async: yes
timeout: {{ checkmk_mk_inventory_timeout }}
cache_age: 3600
- pattern: '$BUILTIN_PLUGINS_PATH$\mssql.vbs'
run: yes
timeout: {{ checkmk_plugins_default_timeout }}
{% if has_veeam_vbr | default(false) %}
# --- Veeam Backup & Replication ---
- pattern: '$BUILTIN_PLUGINS_PATH$\veeam_backup_status.ps1'
run: yes
async: yes
timeout: {{ checkmk_plugins_default_timeout }}
cache_age: {{ checkmk_plugins_default_cache }}
{% endif %}
{% if is_dc | default(false) %}
# --- Domain Controller ---
- pattern: '$BUILTIN_PLUGINS_PATH$\ad_replication.bat'
run: yes
timeout: {{ checkmk_plugins_default_timeout }}
{% endif %}
{% if is_dhcp_server | default(false) %}
# --- DHCP Server ---
- pattern: '$BUILTIN_PLUGINS_PATH$\win_dhcp_pools.bat'
run: yes
timeout: {{ checkmk_plugins_default_timeout }}
{% endif %}
# --- Generic patterns / precedence ---
- pattern: '$CUSTOM_PLUGINS_PATH$\*.ps1'
run: yes
async: yes
timeout: {{ checkmk_plugins_default_timeout }}
cache_age: {{ checkmk_plugins_default_cache }}
- pattern: '$CUSTOM_PLUGINS_PATH$\*.*'
run: yes
timeout: {{ checkmk_plugins_default_timeout }}
- pattern: '$BUILTIN_PLUGINS_PATH$\*.*'
run: no
timeout: {{ checkmk_plugins_default_timeout }}
- pattern: '*'
run: no
{% for p in (checkmk_extra_plugin_patterns | default([])) %}
- pattern: '{{ p.pattern }}'
{% if p.run is defined %}
run: {{ p.run | bool }}
{% endif %}
{% if p.async is defined %}
async: {{ p.async | bool }}
{% endif %}
{% if p.timeout is defined %}
timeout: {{ p.timeout }}
{% endif %}
{% if p.cache_age is defined %}
cache_age: {{ p.cache_age }}
{% endif %}
{% endfor %}
local:
_execution:
- pattern: '*.*'
run: yes
{% for l in (checkmk_extra_local_patterns | default([])) %}
- pattern: '{{ l.pattern }}'
{% if l.run is defined %}
run: {{ l.run | bool }}
{% endif %}
{% if l.async is defined %}
async: {{ l.async | bool }}
{% endif %}
{% if l.timeout is defined %}
timeout: {{ l.timeout }}
{% endif %}
{% endfor %}
mrpe:
config: []
+12
View File
@@ -0,0 +1,12 @@
---
checkmk_linux_local_dir: "/usr/lib/check_mk_agent/local"
checkmk_linux_config_dir: "/etc/check_mk"
checkmk_windows_local_dir: "C:\\ProgramData\\checkmk\\agent\\local"
checkmk_linux_scripts_dir: "Linux/local"
checkmk_windows_scripts_dir: "Windows/local"
# Optional checks, disabled until explicitly requested
want_linux_check_certificate: false
want_windows_citrix: false
want_windows_surebackup: false
want_windows_backup: false
+16
View File
@@ -0,0 +1,16 @@
Place the actual monitoring scripts in these directories.
Linux/local/
- check_certificate_directory.sh
- check_unifi-controller.sh
- unifi.cfg
Windows/local/
- check-ping.ps1
- citrix_sessions_customized.ps1
- veeam_config_backup_status.ps1
- veeam_o365_status.ps1
- veeam_surebackup_status.ps1
- windows-backup.ps1
The ZIP intentionally does not invent script contents that were not provided.
+6
View File
@@ -0,0 +1,6 @@
---
galaxy_info:
role_name: checkmk_scripts
description: Deploy role-specific Checkmk local monitoring scripts
min_ansible_version: "2.18"
dependencies: []
+36
View File
@@ -0,0 +1,36 @@
---
- name: "Linux | Ensure local dir exists"
ansible.builtin.file:
path: "{{ checkmk_linux_local_dir }}"
state: directory
mode: "0755"
- name: "Linux | Ensure config dir exists"
ansible.builtin.file:
path: "{{ checkmk_linux_config_dir }}"
state: directory
mode: "0755"
- name: "Linux | Deploy UniFi local check"
when: is_unifi_controller | default(false) | bool
ansible.builtin.copy:
src: "{{ checkmk_linux_scripts_dir }}/check_unifi-controller.sh"
dest: "{{ checkmk_linux_local_dir }}/check_unifi-controller.sh"
mode: "0755"
notify: "checkmk | linux | agent-ensure-running"
- name: "Linux | Deploy unifi.cfg"
when: is_unifi_controller | default(false) | bool
ansible.builtin.copy:
src: "{{ checkmk_linux_scripts_dir }}/unifi.cfg"
dest: "{{ checkmk_linux_config_dir }}/unifi.cfg"
mode: "0644"
notify: "checkmk | linux | agent-ensure-running"
- name: "Linux | Deploy certificate directory check"
when: want_linux_check_certificate | default(false) | bool
ansible.builtin.copy:
src: "{{ checkmk_linux_scripts_dir }}/check_certificate_directory.sh"
dest: "{{ checkmk_linux_local_dir }}/check_certificate_directory.sh"
mode: "0755"
notify: "checkmk | linux | agent-ensure-running"
+8
View File
@@ -0,0 +1,8 @@
---
- name: Include Linux monitoring scripts
ansible.builtin.include_tasks: linux.yml
when: ansible_facts['os_family'] != 'Windows'
- name: Include Windows monitoring scripts
ansible.builtin.include_tasks: windows.yml
when: ansible_facts['os_family'] == 'Windows'
+47
View File
@@ -0,0 +1,47 @@
---
- name: "Windows | Ensure local dir exists"
ansible.windows.win_file:
path: "{{ checkmk_windows_local_dir }}"
state: directory
- name: "Windows | Deploy check-ping.ps1 (DC only)"
when: is_dc | default(false) | bool
ansible.windows.win_copy:
src: "{{ checkmk_windows_scripts_dir }}/check-ping.ps1"
dest: "{{ checkmk_windows_local_dir }}\\check-ping.ps1"
notify: "checkmk | windows | agent-ensure-running"
- name: "Windows | Deploy Veeam configuration backup status check (VBR only)"
when: has_veeam_vbr | default(false) | bool
ansible.windows.win_copy:
src: "{{ checkmk_windows_scripts_dir }}/veeam_config_backup_status.ps1"
dest: "{{ checkmk_windows_local_dir }}\\veeam_config_backup_status.ps1"
notify: "checkmk | windows | agent-ensure-running"
- name: "Windows | Deploy veeam_o365_status.ps1 (VBO only)"
when: has_veeam_vbo | default(false) | bool
ansible.windows.win_copy:
src: "{{ checkmk_windows_scripts_dir }}/veeam_o365_status.ps1"
dest: "{{ checkmk_windows_local_dir }}\\veeam_o365_status.ps1"
notify: "checkmk | windows | agent-ensure-running"
- name: "Windows | Deploy Citrix sessions check"
when: want_windows_citrix | default(false) | bool
ansible.windows.win_copy:
src: "{{ checkmk_windows_scripts_dir }}/citrix_sessions_customized.ps1"
dest: "{{ checkmk_windows_local_dir }}\\citrix_sessions_customized.ps1"
notify: "checkmk | windows | agent-ensure-running"
- name: "Windows | Deploy Veeam SureBackup check"
when: want_windows_surebackup | default(false) | bool
ansible.windows.win_copy:
src: "{{ checkmk_windows_scripts_dir }}/veeam_surebackup_status.ps1"
dest: "{{ checkmk_windows_local_dir }}\\veeam_surebackup_status.ps1"
notify: "checkmk | windows | agent-ensure-running"
- name: "Windows | Deploy Windows Backup check"
when: want_windows_backup | default(false) | bool
ansible.windows.win_copy:
src: "{{ checkmk_windows_scripts_dir }}/windows-backup.ps1"
dest: "{{ checkmk_windows_local_dir }}\\windows-backup.ps1"
notify: "checkmk | windows | agent-ensure-running"
@@ -0,0 +1,12 @@
---
ad_ds_feature_name: "AD-Domain-Services"
dhcp_windows_service_name: "DHCPServer"
veeam_services:
vbr: "VeeamBackupSvc"
vbo: "Veeam.Archiver.Service"
em: "VeeamEnterpriseManagerSvc"
unifi_linux_services:
- unifi
- unifi.service
unifi_linux_packages:
- unifi
@@ -0,0 +1,6 @@
---
galaxy_info:
role_name: server_role_selection
description: Detect server roles used for Checkmk deployment decisions
min_ansible_version: "2.18"
dependencies: []
+111
View File
@@ -0,0 +1,111 @@
---
# ==========================
# WINDOWS DETECTION
# ==========================
- name: "Windows | Detect AD DS feature (DC)"
when: ansible_facts['os_family'] == "Windows"
ansible.windows.win_feature_info:
name: "{{ ad_ds_feature_name }}"
register: _win_dc_feature
failed_when: false
- name: "Windows | Fallback: check NTDS service (DC)"
when: ansible_facts['os_family'] == "Windows"
ansible.windows.win_service_info:
name: "NTDS"
register: _win_ntds_svc
failed_when: false
- name: "Windows | Check DHCP service"
when: ansible_facts['os_family'] == "Windows"
ansible.windows.win_service_info:
name: "{{ dhcp_windows_service_name }}"
register: _win_dhcp_svc
failed_when: false
- name: "Windows | Check Veeam VBR service"
when: ansible_facts['os_family'] == "Windows"
ansible.windows.win_service_info:
name: "{{ veeam_services.vbr }}"
register: _veeam_vbr
failed_when: false
- name: "Windows | Check Veeam VBO service"
when: ansible_facts['os_family'] == "Windows"
ansible.windows.win_service_info:
name: "{{ veeam_services.vbo }}"
register: _veeam_vbo
failed_when: false
- name: "Windows | Check Veeam Enterprise Manager service"
when: ansible_facts['os_family'] == "Windows"
ansible.windows.win_service_info:
name: "{{ veeam_services.em }}"
register: _veeam_em
failed_when: false
- name: "Windows | Check Hyper-V service"
when: ansible_facts['os_family'] == "Windows"
ansible.windows.win_service_info:
name: "vmms"
register: _win_hyperv_svc
failed_when: false
- name: "Windows | Set detection booleans"
when: ansible_facts['os_family'] == "Windows"
ansible.builtin.set_fact:
is_dc: >-
{{
(((_win_dc_feature.features | default([])) | selectattr('installed') | list | length) > 0)
or (_win_ntds_svc.exists | default(false))
}}
is_dhcp_server: "{{ _win_dhcp_svc.exists | default(false) }}"
has_veeam_vbr: "{{ _veeam_vbr.exists | default(false) }}"
has_veeam_vbo: "{{ _veeam_vbo.exists | default(false) }}"
has_veeam_em: "{{ _veeam_em.exists | default(false) }}"
is_hyperv_host: "{{ _win_hyperv_svc.exists | default(false) }}"
- name: "Windows | Debug summary"
when: ansible_facts['os_family'] == "Windows"
ansible.builtin.debug:
msg:
is_dc: "{{ is_dc }}"
is_dhcp_server: "{{ is_dhcp_server }}"
has_veeam_vbr: "{{ has_veeam_vbr }}"
has_veeam_vbo: "{{ has_veeam_vbo }}"
has_veeam_em: "{{ has_veeam_em }}"
is_hyperv_host: "{{ is_hyperv_host }}"
# ==========================
# LINUX DETECTION
# ==========================
- name: "Linux | Collect service facts"
when: ansible_facts['os_family'] != "Windows"
ansible.builtin.service_facts:
- name: "Linux | Collect package facts"
when: ansible_facts['os_family'] != "Windows"
ansible.builtin.package_facts:
manager: auto
- name: "Linux | Set UniFi flag"
when: ansible_facts['os_family'] != "Windows"
vars:
svcs: "{{ ansible_facts.services | default({}) }}"
pkgs: "{{ ansible_facts.packages | default({}) | list }}"
ansible.builtin.set_fact:
is_unifi_controller: >-
{{
(unifi_linux_services | select('in', svcs.keys()) | list | length > 0)
or (pkgs | intersect(unifi_linux_packages) | length > 0)
}}
- name: "Normalize detection booleans"
ansible.builtin.set_fact:
is_dc: "{{ is_dc | default(false) }}"
is_dhcp_server: "{{ is_dhcp_server | default(false) }}"
has_veeam_vbr: "{{ has_veeam_vbr | default(false) }}"
has_veeam_vbo: "{{ has_veeam_vbo | default(false) }}"
has_veeam_em: "{{ has_veeam_em | default(false) }}"
is_hyperv_host: "{{ is_hyperv_host | default(false) }}"
is_unifi_controller: "{{ is_unifi_controller | default(false) }}"
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "aim-inventory-manager" name = "aim-inventory-manager"
version = "2.1.2" version = "2.2.2"
description = "AIM - Ansible Inventory Manager" description = "AIM - Ansible Inventory Manager"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [
+1 -1
View File
@@ -1 +1 @@
__version__ = "2.1.2" __version__ = "2.2.2"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+27 -2
View File
@@ -25,6 +25,11 @@ class Config:
ui_ascii: bool = False ui_ascii: bool = False
ui_output: str = "compact" ui_output: str = "compact"
checkmk_agent_base_url: str = ""
checkmk_agent_version: str = ""
checkmk_agent_role_files_dir: Path = Path("/etc/ansible/roles/checkmk_agent/files")
checkmk_monitoring_scripts_dir: Path = Path("/etc/checkmk_monitoring_scripts")
@property @property
def inventory_dir(self) -> Path: def inventory_dir(self) -> Path:
return self.root_dir / "inventories" return self.root_dir / "inventories"
@@ -64,8 +69,19 @@ class Config:
for setting in ("clear_screen", "ascii"): for setting in ("clear_screen", "ascii"):
if setting in ui and not isinstance(ui[setting], bool): if setting in ui and not isinstance(ui[setting], bool):
raise ValueError(f"ui.{setting} must be true or false") raise ValueError(f"ui.{setting} must be true or false")
return cls(root, service_user, required_group, platform_groups, maintenance = data.get("maintenance", {}) or {}
ui.get("clear_screen", True), ui.get("ascii", False), output) if not isinstance(maintenance, dict):
raise ValueError("maintenance must be a mapping")
return cls(
root_dir=root, service_user=service_user, required_group=required_group,
platform_groups=platform_groups,
ui_clear_screen=ui.get("clear_screen", True),
ui_ascii=ui.get("ascii", False), ui_output=output,
checkmk_agent_base_url=str(maintenance.get("checkmk_agent_base_url", "")).strip(),
checkmk_agent_version=str(maintenance.get("checkmk_agent_version", "")).strip(),
checkmk_agent_role_files_dir=Path(str(maintenance.get("checkmk_agent_role_files_dir", "/etc/ansible/roles/checkmk_agent/files"))),
checkmk_monitoring_scripts_dir=Path(str(maintenance.get("checkmk_monitoring_scripts_dir", "/etc/checkmk_monitoring_scripts"))),
)
except Exception as exc: except Exception as exc:
raise ConfigurationError(f"Could not load {path}: {exc}") from exc raise ConfigurationError(f"Could not load {path}: {exc}") from exc
@@ -86,6 +102,15 @@ class Config:
if not isinstance(ui, dict): if not isinstance(ui, dict):
raise ConfigurationError("Existing ui configuration is not a mapping") raise ConfigurationError("Existing ui configuration is not a mapping")
ui.update(clear_screen=self.ui_clear_screen, ascii=self.ui_ascii, output=self.ui_output) ui.update(clear_screen=self.ui_clear_screen, ascii=self.ui_ascii, output=self.ui_output)
maintenance = data.setdefault("maintenance", {})
if not isinstance(maintenance, dict):
raise ConfigurationError("Existing maintenance configuration is not a mapping")
maintenance.update(
checkmk_agent_base_url=self.checkmk_agent_base_url,
checkmk_agent_version=self.checkmk_agent_version,
checkmk_agent_role_files_dir=str(self.checkmk_agent_role_files_dir),
checkmk_monitoring_scripts_dir=str(self.checkmk_monitoring_scripts_dir),
)
fd, name = tempfile.mkstemp(prefix=".aim-config-", dir=self.root_dir) fd, name = tempfile.mkstemp(prefix=".aim-config-", dir=self.root_dir)
tmp = Path(name) tmp = Path(name)
try: try:
+1
View File
@@ -0,0 +1 @@
"""Controller-local maintenance helpers."""
+116
View File
@@ -0,0 +1,116 @@
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import os
import re
from pathlib import Path
import shutil
import tempfile
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
from aim.exceptions import AIMError
@dataclass(frozen=True)
class PackageResult:
label: str
filename: str
source_url: str
size: int
old_sha256: str | None
new_sha256: str
class CheckmkAgentUpdater:
"""Download and atomically stage Checkmk agent packages on the controller."""
def __init__(self, target_dir: Path):
self.target_dir = target_dir
@staticmethod
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open('rb') as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b''):
digest.update(chunk)
return digest.hexdigest()
@staticmethod
def _specs(base_url: str, version: str) -> list[tuple[str, str, str]]:
base = base_url.rstrip('/')
return [
('Windows MSI', f'{base}/windows/check_mk_agent.msi', 'check_mk_agent.msi'),
('Debian / Ubuntu DEB', f'{base}/check-mk-agent_{version}_all.deb', 'check-mk-agent.deb'),
('RHEL / Alma / Rocky RPM', f'{base}/check-mk-agent-{version}.noarch.rpm', 'check-mk-agent.rpm'),
]
def update(self, base_url: str, version: str) -> list[PackageResult]:
base_url = base_url.strip().rstrip('/')
version = version.strip()
if not base_url:
raise AIMError('Checkmk agent base URL is required.')
if not version:
raise AIMError('Checkmk agent version is required.')
if not re.fullmatch(r'\d+\.\d+\.\d+p\d+-\d+', version):
raise AIMError(
'Checkmk agent version must use the package format <major>.<minor>.<patch>p<patchlevel>-<revision> '
'(example: 2.4.0p21-1).'
)
if not base_url.startswith(('https://', 'http://')):
raise AIMError('Checkmk agent base URL must start with https:// or http://.')
if not base_url.endswith('/check_mk/agents'):
raise AIMError('Checkmk agent base URL must end with /check_mk/agents.')
specs = self._specs(base_url, version)
downloaded: list[tuple[str, str, str, Path, int, str]] = []
with tempfile.TemporaryDirectory(prefix='aim-checkmk-agent-') as tmp_name:
tmp = Path(tmp_name)
for label, url, filename in specs:
destination = tmp / filename
try:
request = Request(url, headers={'User-Agent': 'bitformer-AIM/CheckmkAgentUpdater'})
with urlopen(request, timeout=60) as response, destination.open('wb') as output:
shutil.copyfileobj(response, output)
except (HTTPError, URLError, TimeoutError, OSError) as exc:
raise AIMError(f'Could not download {label} from {url}: {exc}') from exc
size = destination.stat().st_size
if size <= 0:
raise AIMError(f'Downloaded {label} is empty: {url}')
downloaded.append((label, url, filename, destination, size, self._sha256(destination)))
try:
self.target_dir.mkdir(parents=True, exist_ok=True)
except OSError as exc:
raise AIMError(f'Could not create Checkmk role files directory {self.target_dir}: {exc}') from exc
staged: list[tuple[str, str, str, Path, Path, int, str, str | None]] = []
try:
# Stage every file in the destination filesystem before replacing anything.
for label, url, filename, source, size, new_hash in downloaded:
target = self.target_dir / filename
old_hash = self._sha256(target) if target.exists() else None
fd, stage_name = tempfile.mkstemp(prefix=f'.{filename}.aim-', dir=self.target_dir)
stage = Path(stage_name)
try:
with os.fdopen(fd, 'wb') as output, source.open('rb') as input_stream:
shutil.copyfileobj(input_stream, output)
output.flush()
os.fsync(output.fileno())
stage.chmod(0o644)
except Exception:
stage.unlink(missing_ok=True)
raise
staged.append((label, url, filename, stage, target, size, new_hash, old_hash))
results: list[PackageResult] = []
for label, url, filename, stage, target, size, new_hash, old_hash in staged:
os.replace(stage, target)
results.append(PackageResult(label, filename, url, size, old_hash, new_hash))
return results
except OSError as exc:
raise AIMError(f'Could not stage Checkmk agent packages in {self.target_dir}: {exc}') from exc
finally:
for _label, _url, _filename, stage, _target, _size, _new_hash, _old_hash in staged:
stage.unlink(missing_ok=True)
+75
View File
@@ -0,0 +1,75 @@
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import subprocess
from aim.exceptions import AIMError
@dataclass(frozen=True)
class RepositoryState:
path: Path
branch: str
upstream: str
head: str
tracked_dirty: bool
untracked_count: int
@dataclass(frozen=True)
class SyncResult:
before: str
after: str
upstream: str
changed: bool
stat: str
class ShadowRepository:
"""Manage a checkout that intentionally mirrors its configured upstream."""
def __init__(self, path: Path):
self.path = path
def _git(self, *args: str, check: bool = True) -> str:
try:
proc = subprocess.run(
['git', '-C', str(self.path), *args],
text=True, capture_output=True, check=False,
)
except OSError as exc:
raise AIMError(f'Could not execute git: {exc}') from exc
output = (proc.stdout or '').strip()
error = (proc.stderr or '').strip()
if check and proc.returncode != 0:
detail = error or output or f'exit code {proc.returncode}'
raise AIMError(f'git {" ".join(args)} failed: {detail}')
return output
def inspect(self) -> RepositoryState:
if not self.path.is_dir():
raise AIMError(f'Monitoring scripts directory does not exist: {self.path}')
if self._git('rev-parse', '--is-inside-work-tree') != 'true':
raise AIMError(f'Not a Git working tree: {self.path}')
branch = self._git('branch', '--show-current') or '(detached HEAD)'
if branch == '(detached HEAD)':
raise AIMError('The monitoring scripts shadow repository is in detached HEAD state.')
upstream = self._git('rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}')
head = self._git('rev-parse', 'HEAD')
tracked = self._git('status', '--porcelain', '--untracked-files=no')
untracked = self._git('ls-files', '--others', '--exclude-standard')
return RepositoryState(self.path, branch, upstream, head, bool(tracked.strip()),
len([line for line in untracked.splitlines() if line.strip()]))
def sync(self) -> SyncResult:
before = self._git('rev-parse', 'HEAD')
upstream = self._git('rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}')
self._git('fetch', '--prune')
remote_head = self._git('rev-parse', '@{u}')
stat = '' if before == remote_head else self._git('diff', '--stat', f'{before}..{remote_head}', check=False)
self._git('reset', '--hard', '@{u}')
after = self._git('rev-parse', 'HEAD')
if after != remote_head:
raise AIMError(f'Repository reset did not reach upstream {upstream}. Expected {remote_head}, got {after}.')
return SyncResult(before, after, upstream, before != after, stat)
+106 -14
View File
@@ -21,22 +21,114 @@ class PlaybookSpec:
PLAYBOOKS: tuple[PlaybookSpec, ...] = ( PLAYBOOKS: tuple[PlaybookSpec, ...] = (
PlaybookSpec("checkmk_cleanup", "Cleanup CheckMK", "checkmk_cleanup.yml", "CheckMK", ("linux", "windows"), ("checkmk_cleanup_enabled=true",)), # CheckMK
PlaybookSpec("checkmk_install_agent", "Install CheckMK Agent", "checkmk_install_agent.yml", "CheckMK", ("linux", "windows")), PlaybookSpec(
PlaybookSpec("checkmk_update_config", "Update CheckMK Config", "checkmk_update_config.yml", "CheckMK", ("linux", "windows")), "checkmk_cleanup",
PlaybookSpec("debug_ping", "Ping", "debug_ping.yml", "Debug", ("linux", "windows")), "Cleanup CheckMK",
PlaybookSpec("debug_server_role_selection", "Server Role Selection", "debug_server_role_selection.yml", "Debug", ("linux", "windows")), "checkmk_cleanup.yml",
PlaybookSpec("debug_disk_usage", "Disk Usage", "debug_disk_usage.yml", "Debug", ("windows",)), "CheckMK",
PlaybookSpec("patch_os", "Patch OS", "patch_os.yml", "Maintenance", ("linux", "windows")), ("linux", "windows"),
PlaybookSpec("reboot_system", "Reboot System", "reboot_system.yml", "Maintenance", ("linux", "windows")), ("checkmk_cleanup_enabled=true",),
PlaybookSpec("backup_eventlog", "Backup Event Log", "backup_eventlog.yml", "Maintenance", ("windows",)), ),
PlaybookSpec("start_stopped_services", "Start Stopped Services", "start_stopped_services.yml", "Maintenance", ("windows",)), PlaybookSpec(
PlaybookSpec("configure_sophos_initial", "Initial Bitformer Config", "configure_sophos_initial_bitformer_config.yml", "Sophos XGS", ("sophosxgs",), ask_pass=True, require_vault=True), "checkmk_deploy",
PlaybookSpec("configure_sophosxgs", "Configure Sophos XGS", "configure_sophosxgs.yml", "Sophos XGS", ("sophosxgs",), customer_specific=True, ask_pass=True, require_vault=True), "Deploy CheckMK",
"checkmk_deploy.yml",
"CheckMK",
("linux", "windows"),
),
PlaybookSpec(
"checkmk_update_config",
"Update CheckMK Config",
"checkmk_update_config.yml",
"CheckMK",
("windows",),
),
PlaybookSpec(
"checkmk_update_scripts",
"Update CheckMK Scripts",
"checkmk_update_scripts.yml",
"CheckMK",
("linux", "windows"),
),
# Debug
PlaybookSpec(
"debug_ping",
"Ping",
"debug_ping.yml",
"Debug",
("linux", "windows"),
),
PlaybookSpec(
"debug_server_role_selection",
"Server Role Selection",
"debug_server_role_selection.yml",
"Debug",
("linux", "windows"),
),
PlaybookSpec(
"debug_disk_usage",
"Disk Usage",
"debug_disk_usage.yml",
"Debug",
("linux", "windows"),
),
# Maintenance
PlaybookSpec(
"maintenance_patch_os",
"Patch OS",
"maintenance_patch_os.yml",
"Maintenance",
("linux", "windows"),
),
PlaybookSpec(
"maintenance_reboot",
"Reboot System",
"maintenance_reboot.yml",
"Maintenance",
("linux", "windows"),
),
PlaybookSpec(
"maintenance_backup_event_log",
"Backup System Logs",
"maintenance_backup_event_log.yml",
"Maintenance",
("linux", "windows"),
),
PlaybookSpec(
"maintenance_start_stopped_services",
"Start Stopped Services",
"maintenance_start_stopped_services.yml",
"Maintenance",
("linux", "windows"),
),
# Sophos XGS
# PlaybookSpec(
# "configure_sophos_initial",
# "Initial Bitformer Config",
# "configure_sophos_initial_bitformer_config.yml",
# "Sophos XGS",
# ("sophosxgs",),
# ask_pass=True,
# require_vault=True,
# ),
# PlaybookSpec(
# "configure_sophosxgs",
# "Configure Sophos XGS",
# "configure_sophosxgs.yml",
# "Sophos XGS",
# ("sophosxgs",),
# customer_specific=True,
# ask_pass=True,
# require_vault=True,
# ),
) )
CATEGORY_ORDER = ("CheckMK", "Debug", "Maintenance", "Sophos XGS") # CATEGORY_ORDER = ("CheckMK", "Debug", "Maintenance", "Sophos XGS")
CATEGORY_ORDER = ("CheckMK", "Debug", "Maintenance")
class PlaybookManager: class PlaybookManager:
def __init__(self, customers, config): def __init__(self, customers, config):
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More