--- - name: Умное сканирование сети и классификация устройств hosts: localhost connection: local gather_facts: no become: yes # !!! ВАЖНО: Для определения OS (nmap -O) нужны права root vars: # --- НАСТРОЙКИ SEMAPHORE --- semaphore_url: "http://192.168.0.198:9999" semaphore_project_id: 1 semaphore_key_id: 7 semaphore_api_token: "9ojexqiwt1xkemig7j1bd1pe-frh7hkre4reryk2occ=" inventory_name: "Smart Network Scan" # --- СЕТИ --- subnets: - "192.168.0.0/24" # добавьте другие подсети # Порты для проверки (добавил 8291 для MikroTik, 80/443 для веб-морд, 631 для принтеров) scan_ports: [22, 445, 5985, 80, 443, 8291, 631] tasks: # ---------------------------------------------------------------- # ШАГ 1: Быстрый поиск живых хостов (Ping Sweep) # Это нужно, чтобы не сканировать пустоту тяжелым сканом # ---------------------------------------------------------------- - name: Быстрый поиск активных IP command: > nmap -sn -n --min-rate 1000 -T4 -oG - {{ subnets | join(' ') }} register: ping_scan changed_when: false - name: Формирование списка живых IP set_fact: active_ips: "{{ ping_scan.stdout | regex_findall('Host: ([0-9.]+)') | unique | list }}" - name: Проверка fail: msg: "Живых хостов не найдено." when: active_ips | length == 0 # ---------------------------------------------------------------- # ШАГ 2: Глубокий анализ каждого IP (ОС, Версии, Имена) # Используем -O (OS), -sV (Versions) и скрипты # ---------------------------------------------------------------- - name: Глубокое сканирование найденных хостов shell: | # Запускаем Nmap с определением ОС и скриптами SMB/Banner # -O: Определение ОС (требует sudo) # --osscan-guess: Пытаться угадать, если не уверен # --script: Скрипты для точного имени Windows и SSH баннеров nmap -O -sV -p {{ scan_ports | join(',') }} --script=smb-os-discovery,banner -Pn -n -T4 {{ item }} loop: "{{ active_ips }}" register: deep_scan_results changed_when: false # ---------------------------------------------------------------- # ШАГ 3: Анализ и классификация (Магия Ansible + Jinja2) # ---------------------------------------------------------------- - name: Классификация хостов set_fact: classified_hosts: [] - name: Разбор результатов сканирования set_fact: classified_hosts: "{{ classified_hosts + [ host_data ] }}" vars: output: "{{ item.stdout }}" ip: "{{ item.item }}" # --- ЛОГИКА ОПРЕДЕЛЕНИЯ ИМЕНИ --- # Пытаемся найти имя через SMB, если нет - DNS, если нет - IP smb_name: "{{ output | regex_search('Computer name: ([\\w-]+)', '\\1') | first | default('') }}" dns_name_cmd: "nslookup {{ ip }} 192.168.1.250 | grep 'name =' | awk '{print $NF}' | sed 's/\\.$//'" # (В реальном плейбуке nslookup внутри loop медленно, тут упрощено) final_name: >- {% if smb_name != '' %}{{ smb_name | lower }} {% else %}host_{{ ip | replace('.', '_') }}{% endif %} # --- ЛОГИКА ОПРЕДЕЛЕНИЯ ТИПА --- host_type: >- {% if 'Windows' in output or 'Microsoft Windows' in output or '445/tcp open' in output %}windows {% elif 'MikroTik' in output or 'RouterOS' in output or '8291/tcp open' in output %}mikrotik {% elif 'Linux' in output or 'Ubuntu' in output or 'Debian' in output %}linux {% elif 'HP' in output or 'Printer' in output or '631/tcp open' in output %}printer {% else %}other{% endif %} # --- СОБИРАЕМ ОБЪЕКТ --- host_data: ip: "{{ ip }}" name: "{{ final_name }}" type: "{{ host_type }}" raw_os: "{{ output | regex_search('OS details: ([^\\n]+)', '\\1') | first | default('Unknown') }}" loop: "{{ deep_scan_results.results }}" no_log: true # ---------------------------------------------------------------- # ШАГ 4: Генерация инвентаря по группам # ---------------------------------------------------------------- - name: Генерация текста инвентаря set_fact: inventory_content: | # === Windows Systems === [windows] {% for host in classified_hosts if host.type == 'windows' %} {{ host.name }} ansible_host={{ host.ip }} # Detected: {{ host.raw_os }} {% endfor %} [windows:vars] ansible_connection=ssh ansible_shell_type=powershell ansible_user=o.grechko ansible_port=22 # === Linux Systems === [linux] {% for host in classified_hosts if host.type == 'linux' %} {{ host.name }} ansible_host={{ host.ip }} # Detected: {{ host.raw_os }} {% endfor %} [linux:vars] ansible_connection=ssh ansible_user=root # === MikroTik Routers === [routers_mikrotik] {% for host in classified_hosts if host.type == 'mikrotik' %} {{ host.name }} ansible_host={{ host.ip }} {% endfor %} [routers_mikrotik:vars] ansible_connection=network_cli ansible_network_os=routeros ansible_user=admin # === Printers & Others === [others] {% for host in classified_hosts if host.type == 'printer' or host.type == 'other' %} {{ host.name }} ansible_host={{ host.ip }} # Type: {{ host.type }}, OS: {{ host.raw_os }} {% endfor %} # ---------------------------------------------------------------- # ШАГ 5: Отправка в Semaphore (Ваш рабочий метод через файл) # ---------------------------------------------------------------- - name: Сохранение JSON copy: content: | { "name": "{{ inventory_name }} {{ 1000 | random }}", "project_id": {{ semaphore_project_id | int }}, "type": "static", "ssh_key_id": {{ semaphore_key_id | int }}, "become_key_id": null, "repository_id": null, "inventory": {{ inventory_content | to_json }} } dest: /tmp/semaphore_smart_payload.json - name: Отправка через CURL command: > curl -v -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory" -H "Authorization: Bearer {{ semaphore_api_token }}" -H "Content-Type: application/json" -H "Accept: application/json" -d @/tmp/semaphore_smart_payload.json register: curl_result ignore_errors: yes - name: Очистка file: path=/tmp/semaphore_smart_payload.json state=absent - name: Результат debug: msg: "Проверьте инвентарь! Создан ID, если статус 200/201"