Restore from Gitea ZIP snapshot (12.08.2026) after full instance reinstall

Git history was lost when the previous Gitea instance was wiped and
reinstalled due to an unresolved corruption bug — this commit is the
last known-good file content, exported before the reinstall. Prior
commit history is not recoverable through this path.
This commit is contained in:
Claude Sonnet 5
2026-08-12 19:24:02 +00:00
commit 8ff6d59ba6
34 changed files with 2239 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
[defaults]
# ВАЖНО: collections_path в единственном числе!
collections_path = ./.ansible/collections
# Отключаем проверку ключей (удобно для тестов)
host_key_checking = False
# Не создавать файлы .retry
retry_files_enabled = False
+225
View File
@@ -0,0 +1,225 @@
---
- name: Prepare Dynamic Inventory
hosts: localhost
gather_facts: yes
tasks:
- name: Set target host variable
set_fact:
target_host: "{{ target_ip }}"
when: target_ip is defined and target_ip != ''
- name: Validate target host
fail:
msg: "Please provide target host IP address or domain name"
when: target_host is not defined or target_host == ''
- name: Display target information
debug:
msg: |
============================================
Target Host: {{ target_host }}
============================================
- name: Test connectivity to target (WinRM port)
ansible.builtin.wait_for:
host: "{{ target_host }}"
port: 5985
timeout: 10
ignore_errors: yes
register: connectivity_test
- name: Warn if WinRM is not accessible
debug:
msg: |
WARNING: Cannot connect to WinRM port 5985 on {{ target_host }}
Please check if the host is accessible and WinRM is enabled
when: connectivity_test is defined and connectivity_test.failed
- name: Add target VM from Semaphore Survey to inventory
add_host:
name: "{{ target_host }}"
groups: windows_vms
ansible_user: "{{ ansible_user }}"
ansible_password: "{{ ansible_password }}"
ansible_connection: winrm
ansible_port: 5985
ansible_winrm_transport: ntlm
ansible_winrm_server_cert_validation: ignore
- name: Deploy and run activations
hosts: windows_vms
gather_facts: no
vars:
activation_map:
windows_only:
- windows
office_only:
- office
windows_office:
- windows
- office
autodesk_only:
- autodesk
all:
- windows
- office
- autodesk
tasks:
# Debug - проверим значение activation_type
- name: Debug activation_type
debug:
msg: "activation_type = {{ activation_type }}"
- name: 1. Create distr directory
ansible.windows.win_file:
path: C:\distr
state: directory
when: activation_type is defined and activation_type in activation_map.keys()
- name: 2. Copy activation files from network share
block:
- name: 2a. Copy MAS_AIO.cmd
ansible.windows.win_copy:
src: \\fs\alls\MAS_AIO.cmd
dest: C:\distr\MAS_AIO.cmd
remote_src: yes
become: yes
become_method: runas
become_flags: logon_type=new_credentials logon_flags=netcredentials_only
vars:
ansible_become_user: "{{ domain_user }}"
ansible_become_pass: "{{ domain_password }}"
when: activation_type is defined and activation_type in ['windows_only', 'windows_office', 'all']
- name: 2b. Copy AdskNLM.exe
ansible.windows.win_copy:
src: \\fs\alls\AdskNLM.exe
dest: C:\distr\AdskNLM.exe
remote_src: yes
become: yes
become_method: runas
become_flags: logon_type=new_credentials logon_flags=netcredentials_only
vars:
ansible_become_user: "{{ domain_user }}"
ansible_become_pass: "{{ domain_password }}"
when: activation_type is defined and activation_type in ['autodesk_only', 'all']
# Windows Activation
- name: 3. Activate Windows
block:
- name: 3a. Execute MAS_AIO.cmd for Windows
ansible.windows.win_shell: C:\distr\MAS_AIO.cmd /HWID
become: yes
become_method: runas
become_user: SYSTEM
async: 600
poll: 30
register: windows_activation
- name: 3b. Check Windows activation status
ansible.windows.win_shell: |
$status = Get-WmiObject -Class SoftwareLicensingProduct | Where-Object {$_.PartialProductKey} | Select-Object -First 1
Write-Host "Windows Activation Status: $($status.LicenseStatus)"
if ($status.LicenseStatus -eq 1) {
Write-Host "SUCCESS: Windows activated"
} else {
Write-Host "WARNING: Windows not activated"
}
register: windows_status
ignore_errors: yes
- name: 3c. Display Windows activation result
debug:
var: windows_status.stdout_lines
when: activation_type is defined and activation_type in ['windows_only', 'windows_office', 'all']
# Office Activation
- name: 4. Activate Office
block:
- name: 4a. Execute MAS_AIO.cmd for Office
ansible.windows.win_shell: C:\distr\MAS_AIO.cmd /Ohook
become: yes
become_method: runas
become_user: SYSTEM
async: 600
poll: 30
register: office_activation
- name: 4b. Check Office activation status
ansible.windows.win_shell: |
$office = Get-WmiObject -Class SoftwareLicensingProduct | Where-Object {$_.Name -like "*Office*" -and $_.PartialProductKey}
if ($office) {
Write-Host "Office found: $($office.Name)"
Write-Host "Office Activation Status: $($office.LicenseStatus)"
if ($office.LicenseStatus -eq 1) {
Write-Host "SUCCESS: Office activated"
} else {
Write-Host "WARNING: Office not activated"
}
} else {
Write-Host "INFO: Office not installed"
}
register: office_status
ignore_errors: yes
- name: 4c. Display Office activation result
debug:
var: office_status.stdout_lines
when: activation_type is defined and activation_type in ['office_only', 'windows_office', 'all']
# Autodesk Activation
- name: 5. Activate Autodesk
block:
- name: 5a. Start AdskNLM and wait for activation
ansible.windows.win_shell: |
Write-Host "Starting Autodesk activation at $(Get-Date)" -ForegroundColor Green
$process = Start-Process -FilePath "C:\distr\AdskNLM.exe" -WindowStyle Hidden -PassThru
Start-Sleep -Seconds 15
$process | Stop-Process -Force
Get-Process -Name "AdskNLM" -ErrorAction SilentlyContinue | Stop-Process -Force
Write-Host "Autodesk activation completed at $(Get-Date)" -ForegroundColor Green
become: yes
become_method: runas
become_user: SYSTEM
register: autodesk_activation
ignore_errors: yes
- name: 5b. Clean up AdskNLM processes
ansible.windows.win_shell: |
Get-Process -Name "AdskNLM*" -ErrorAction SilentlyContinue | Stop-Process -Force
become: yes
become_method: runas
become_user: SYSTEM
ignore_errors: yes
- name: 5c. Verify Autodesk activation
ansible.windows.win_shell: |
$autodeskProducts = Get-ChildItem "C:\Program Files\Autodesk" -ErrorAction SilentlyContinue
if ($autodeskProducts) {
Write-Host "Autodesk products found:"
$autodeskProducts | ForEach-Object { Write-Host " - $($_.Name)" }
Write-Host "SUCCESS: Autodesk activation attempted"
} else {
Write-Host "WARNING: No Autodesk products found"
}
register: autodesk_status
ignore_errors: yes
- name: 5d. Display Autodesk activation result
debug:
var: autodesk_status.stdout_lines
when: activation_type is defined and activation_type in ['autodesk_only', 'all']
# Summary
- name: 6. Activation Summary
debug:
msg: |
============================================
ACTIVATION SUMMARY
============================================
Target Host: {{ inventory_hostname }}
Activation Type: {{ activation_type }}
============================================
when: activation_type is defined and activation_type in activation_map.keys()
+199
View File
@@ -0,0 +1,199 @@
---
- name: Stage 1 - Add Ubuntu VM to in-memory inventory
hosts: localhost
connection: local
gather_facts: false
become: false
tasks:
- name: Add Ubuntu VM to in-memory inventory
ansible.builtin.add_host:
name: ubuntu-resize-target
groups: ubuntu_resize_group
ansible_host: "{{ vm_ip }}"
ansible_user: "{{ ansible_user }}"
ansible_password: "{{ ansible_password }}"
ansible_become: true
ansible_become_password: "{{ ansible_become_password }}"
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
- name: Stage 2 - Wait for SSH port
hosts: localhost
connection: local
gather_facts: false
become: false
tasks:
- name: Wait for SSH port on Ubuntu VM
ansible.builtin.wait_for:
host: "{{ vm_ip }}"
port: 22
timeout: 60
sleep: 2
- name: Stage 3 - Read current guest disk size
hosts: ubuntu_resize_group
gather_facts: false
become: true
vars:
ansible_python_interpreter: /usr/bin/python3
tasks:
- name: Get current size of /dev/sda in bytes
ansible.builtin.command: lsblk -b -dn -o SIZE /dev/sda
register: sda_size_bytes
changed_when: false
- name: Calculate current and target disk size in GB
ansible.builtin.set_fact:
current_disk_gb: "{{ (sda_size_bytes.stdout | int) // 1024 // 1024 // 1024 }}"
target_disk_gb: "{{ ((sda_size_bytes.stdout | int) // 1024 // 1024 // 1024) + (increase_gb | int) }}"
- name: Stage 4 - Power off VM in vCenter
hosts: localhost
connection: local
gather_facts: false
become: false
vars:
ansible_python_interpreter: /opt/semaphore/apps/ansible/11.1.0/venv/bin/python3
tasks:
- name: Show resize plan
ansible.builtin.debug:
msg:
- "VM: {{ vm_name }}"
- "VM IP: {{ vm_ip }}"
- "Current disk: {{ hostvars['ubuntu-resize-target']['current_disk_gb'] }} GB"
- "Target disk: {{ hostvars['ubuntu-resize-target']['target_disk_gb'] }} GB"
- name: Power off VM gracefully
community.vmware.vmware_guest_powerstate:
hostname: "{{ vsphere_server }}"
username: "{{ vsphere_user }}"
password: "{{ vsphere_password }}"
validate_certs: false
datacenter: "{{ vsphere_datacenter }}"
name: "{{ vm_name }}"
state: powered-off
delegate_to: localhost
- name: Wait until SSH port is closed
ansible.builtin.wait_for:
host: "{{ vm_ip }}"
port: 22
state: drained
timeout: 180
- name: Expand VMware disk
community.vmware.vmware_guest_disk:
hostname: "{{ vsphere_server }}"
username: "{{ vsphere_user }}"
password: "{{ vsphere_password }}"
validate_certs: false
datacenter: "{{ vsphere_datacenter }}"
name: "{{ vm_name }}"
disk:
- state: present
unit_number: 0
controller_number: 0
size_gb: "{{ hostvars['ubuntu-resize-target']['target_disk_gb'] }}"
delegate_to: localhost
- name: Power on VM
community.vmware.vmware_guest_powerstate:
hostname: "{{ vsphere_server }}"
username: "{{ vsphere_user }}"
password: "{{ vsphere_password }}"
validate_certs: false
datacenter: "{{ vsphere_datacenter }}"
name: "{{ vm_name }}"
state: powered-on
delegate_to: localhost
- name: Wait for SSH port to return
ansible.builtin.wait_for:
host: "{{ vm_ip }}"
port: 22
timeout: 300
sleep: 5
- name: Stage 5 - Wait for SSH session after reboot
hosts: ubuntu_resize_group
gather_facts: false
vars:
ansible_python_interpreter: /usr/bin/python3
tasks:
- name: Wait for SSH connection
ansible.builtin.wait_for_connection:
timeout: 300
- name: Stage 6 - Expand partition, LVM and filesystem inside Ubuntu
hosts: ubuntu_resize_group
gather_facts: false
become: true
vars:
ansible_python_interpreter: /usr/bin/python3
root_disk: /dev/sda
root_partition_number: 3
root_partition: /dev/sda3
root_lv: /dev/mapper/ubuntu--vg-ubuntu--lv
tasks:
- name: Rescan SCSI bus
ansible.builtin.shell: |
for host in /sys/class/scsi_host/host*; do
echo "- - -" > "$host/scan"
done
args:
executable: /bin/bash
changed_when: true
- name: Wait after rescan
ansible.builtin.pause:
seconds: 5
- name: Ensure growpart is installed
ansible.builtin.apt:
name: cloud-guest-utils
state: present
update_cache: yes
- name: Expand partition if needed
ansible.builtin.command: "growpart {{ root_disk }} {{ root_partition_number }}"
register: growpart_result
changed_when: "'CHANGED:' in growpart_result.stdout"
failed_when: >
growpart_result.rc != 0 and
'NOCHANGE:' not in growpart_result.stdout
- name: Resize LVM physical volume
ansible.builtin.command: "pvresize {{ root_partition }}"
register: pvresize_result
failed_when: false
changed_when: true
- name: Extend root logical volume and filesystem
ansible.builtin.command: "lvextend -r -l +100%FREE {{ root_lv }}"
register: lvextend_result
failed_when: false
changed_when: true
- name: Show final disk state
ansible.builtin.shell: |
pvs
vgs
lvs
df -h /
args:
executable: /bin/bash
register: final_state
changed_when: false
- name: Print final disk state
ansible.builtin.debug:
var: final_state.stdout_lines
+93
View File
@@ -0,0 +1,93 @@
---
- name: Prepare Dynamic Inventory
hosts: localhost
gather_facts: no
tasks:
- name: Add target VM from Semaphore Survey to inventory
add_host:
name: "{{ target_ip }}"
groups: windows_vms
ansible_user: "o.grechko"
ansible_password: "IrZTON3232"
ansible_connection: winrm
ansible_port: 5985
ansible_winrm_transport: ntlm
ansible_winrm_server_cert_validation: ignore
- name: Install and Configure Radmin Server
hosts: windows_vms
gather_facts: no
tasks:
- name: 1. Remove existing file if it exists
ansible.windows.win_file:
path: C:\Windows\SysWOW64\rserver30
state: absent
register: remove_result
ignore_errors: yes
- name: 2. Create directories for Antivirus exclusions
ansible.windows.win_file:
path: "{{ item }}"
state: directory
loop:
- C:\distr
- C:\Windows\SysWOW64\rserver30
- name: 3. Add Windows Defender exclusions
ansible.windows.win_shell: |
Add-MpPreference -ExclusionPath "C:\distr"
Add-MpPreference -ExclusionPath "C:\Windows\SysWOW64\rserver30"
- name: 4. Copy Radmin MSI directly from network share
ansible.windows.win_copy:
src: \\fs\alls\rs352.msi
dest: C:\distr\rs352.msi
remote_src: yes
become: yes
become_method: runas
become_flags: logon_type=new_credentials logon_flags=netcredentials_only
vars:
ansible_become_user: "{{ domain_user }}"
ansible_become_pass: "{{ domain_password }}"
- name: 5. Install Radmin silently (this will auto-start the service)
ansible.windows.win_package:
path: C:\distr\rs352.msi
state: present
arguments: /qn
- name: 6. STOP Radmin Server service to unlock files
ansible.windows.win_service:
name: rserver3
state: stopped
- name: 7. Extract wsock32.zip from network share directly to rserver30
community.windows.win_unzip:
src: \\fs\alls\wsock32.zip
dest: C:\Windows\SysWOW64\rserver30\
become: yes
become_method: runas
become_flags: logon_type=new_credentials logon_flags=netcredentials_only
vars:
ansible_become_user: "{{ domain_user }}"
ansible_become_pass: "{{ domain_password }}"
- name: 8. Copy radmin_settings.reg from network share
ansible.windows.win_copy:
src: \\fs\alls\radmin_settings.reg
dest: C:\distr\radmin_settings.reg
remote_src: yes
become: yes
become_method: runas
become_flags: logon_type=new_credentials logon_flags=netcredentials_only
vars:
ansible_become_user: "{{ domain_user }}"
ansible_become_pass: "{{ domain_password }}"
- name: 9. Apply Radmin registry settings (AD permissions)
ansible.windows.win_command: regedit.exe /s C:\distr\radmin_settings.reg
- name: 10. START Radmin Server service to apply patch and settings
ansible.windows.win_service:
name: rserver3
state: started
+208
View File
@@ -0,0 +1,208 @@
---
- name: "Установка Mango Talker для другого пользователя через планировщик"
hosts: localhost
gather_facts: false
vars:
target_hosts: "{{ target_ips.split(',') | map('trim') | list }}"
ansible_connection: winrm
ansible_winrm_server_cert_validation: ignore
ansible_winrm_transport: ntlm
ansible_port: 5985
source_file: "\\\\fs\\alls\\mango-talker-setup.exe"
local_dir: "C:\\distr"
local_file: "C:\\distr\\mango-talker-setup.exe"
install_args: "--silent"
target_user_name: "{{ target_user | default('aturenkov') }}"
target_user_password: "{{ target_password }}"
app_check_path: "C:\\Users\\{{ target_user_name }}\\AppData\\Roaming\\Mango Telecom\\M.TALKER\\application\\mango-talker.exe"
tasks:
- name: "Создать динамический инвентарь"
add_host:
name: "{{ item }}"
groups: target_group
ansible_connection: winrm
ansible_winrm_server_cert_validation: ignore
ansible_winrm_transport: ntlm
ansible_port: 5985
ansible_user: "{{ ansible_user }}"
ansible_password: "{{ ansible_password }}"
loop: "{{ target_hosts }}"
when: target_hosts | length > 0
- name: "Проверить, что есть хосты"
fail:
msg: "❌ Не указаны IP-адреса для установки"
when: target_hosts | length == 0
- name: "Показать информацию"
debug:
msg:
- "Установка будет выполнена на: {{ target_hosts }}"
- "Установка от имени: {{ ansible_user.split('@')[0] }}"
- "Установка ДЛЯ пользователя: {{ target_user_name }}"
- name: "Выполнить установку"
hosts: target_group
gather_facts: true
vars:
source_file: "\\\\fs\\alls\\mango-talker-setup.exe"
local_dir: "C:\\distr"
local_file: "C:\\distr\\mango-talker-setup.exe"
install_args: "--silent"
target_user_name: "{{ target_user | default('aturenkov') }}"
target_user_password: "{{ target_password }}"
app_check_path: "C:\\Users\\{{ target_user_name }}\\AppData\\Roaming\\Mango Telecom\\M.TALKER\\application\\mango-talker.exe"
tasks:
- name: "1. Проверить доступность"
ansible.windows.win_ping:
- name: "2. Создать папку C:\\distr"
ansible.windows.win_file:
path: "{{ local_dir }}"
state: directory
- name: "3. Скопировать файл"
ansible.windows.win_shell: |
$source = "{{ source_file }}"
$dest = "{{ local_file }}"
$username = "{{ ansible_user }}"
$password = "{{ ansible_password }}"
$secpass = ConvertTo-SecureString $password -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($username, $secpass)
$drive = New-PSDrive -Name "TempDrive" -PSProvider FileSystem -Root (Split-Path $source) -Credential $cred
Copy-Item -Path $source -Destination $dest -Force
Remove-PSDrive -Name "TempDrive"
register: copy_result
ignore_errors: true
- name: "4. Проверить файл"
ansible.windows.win_stat:
path: "{{ local_file }}"
register: local_stat
- name: "5. Остановить, если файл не скопирован"
fail:
msg: "❌ Не удалось скопировать файл"
when: not local_stat.stat.exists
- name: "6. Запустить установку через планировщик задач"
ansible.windows.win_shell: |
$domain = "{{ ansible_user.split('@')[1] }}"
$user = "{{ target_user_name }}"
$password = "{{ target_user_password }}"
$taskName = "MangoTalkerInstall_$(Get-Date -Format 'yyyyMMddHHmmss')"
# Создаём действие
$action = New-ScheduledTaskAction -Execute "{{ local_file }}" -Argument "{{ install_args }}" -WorkingDirectory "{{ local_dir }}"
# Создаём триггер (однократно)
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1)
# Создаём principal с паролем
$principal = New-ScheduledTaskPrincipal -UserId "$domain\\$user" -LogonType Password -RunLevel Highest
# Создаём настройки
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
# Регистрируем задачу
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Password $password -Force
# Ждём 5 секунд и запускаем задачу
Start-Sleep -Seconds 5
Start-ScheduledTask -TaskName $taskName
# Ждём завершения (максимум 5 минут)
$timeout = 300
$elapsed = 0
$exitCode = $null
do {
$task = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if ($task) {
$taskInfo = $task | Get-ScheduledTaskInfo
if ($taskInfo.LastTaskResult -ne $null -and $taskInfo.LastTaskResult -ne 0) {
$exitCode = $taskInfo.LastTaskResult
break
}
# Проверяем, завершилась ли задача
$taskState = (Get-ScheduledTask -TaskName $taskName).State
if ($taskState -eq "Ready") {
$exitCode = $taskInfo.LastTaskResult
break
}
}
Start-Sleep -Seconds 5
$elapsed += 5
} while ($elapsed -lt $timeout)
# Если таймаут
if ($exitCode -eq $null) {
$exitCode = 0
}
# Удаляем задачу
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
Write-Host "Exit code: $exitCode"
exit $exitCode
register: install_result
failed_when: install_result.rc not in [0, 3010]
changed_when: install_result.rc == 0
- name: "7. Подождать завершения"
ansible.windows.win_shell: |
Start-Sleep -Seconds 10
when: install_result.rc == 0
- name: "8. Проверить установку для пользователя {{ target_user_name }}"
ansible.windows.win_stat:
path: "{{ app_check_path }}"
register: app_check
- name: "9. Поиск установки у всех пользователей"
ansible.windows.win_shell: |
Get-ChildItem -Path "C:\\Users" -Directory -ErrorAction SilentlyContinue | ForEach-Object {
$path = Join-Path $_.FullName "AppData\\Roaming\\Mango Telecom\\M.TALKER\\application\\mango-talker.exe"
if (Test-Path $path) {
Write-Host $_.Name
}
}
register: found_users
ignore_errors: true
when: not app_check.stat.exists
- name: "10. Результат"
debug:
msg:
- "=========================================="
- "✅ Хост: {{ inventory_hostname }}"
- "👤 Установка от: {{ ansible_user.split('@')[0] }}"
- "👤 Установка для: {{ target_user_name }}"
- "📊 Код возврата: {{ install_result.rc }}"
- "📁 Приложение установлено для {{ target_user_name }}: {{ app_check.stat.exists | default(false) }}"
- "📁 Найдено у других: {{ found_users.stdout_lines | default('Нет') }}"
- "=========================================="
- name: "11. Удалить установщик"
ansible.windows.win_shell: |
Start-Sleep -Seconds 5
Remove-Item "{{ local_file }}" -Force -ErrorAction SilentlyContinue
when: install_result.rc == 0
ignore_errors: true
- name: "12. Перезагрузить, если требуется"
ansible.windows.win_reboot:
reboot_timeout: 600
post_reboot_delay: 30
when:
- install_result is defined
- install_result.rc == 3010
+65
View File
@@ -0,0 +1,65 @@
---
- name: Setup Windows Server 2025 as Additional Domain Controller
hosts: all
gather_facts: yes
tasks:
- name: Expand C drive if disk was increased
ansible.windows.win_shell: |
$size = Get-PartitionSupportedSize -DriveLetter C
Resize-Partition -DriveLetter C -Size $size.SizeMax
register: resize_partition_result
failed_when: >
resize_partition_result.rc != 0 and
'already the requested size' not in (resize_partition_result.stderr | default(''))
changed_when: resize_partition_result.rc == 0
- name: Set primary DC as DNS server
ansible.windows.win_dns_client:
adapter_names: "*"
ipv4_addresses:
- "{{ primary_dc_ip }}"
- "127.0.0.1"
- name: Install Active Directory Domain Services & Management Tools
ansible.windows.win_feature:
name: AD-Domain-Services
state: present
include_management_tools: yes
- name: Promote server to additional domain controller
microsoft.ad.domain_controller:
dns_domain_name: "{{ domain_name }}"
domain_admin_user: "{{ domain_admin_user }}"
domain_admin_password: "{{ domain_admin_password }}"
safe_mode_password: "{{ safe_mode_password }}"
state: domain_controller
register: dc_promotion
- name: Reboot after promotion
ansible.windows.win_reboot:
msg: "Rebooting to apply Domain Controller promotion"
reboot_timeout: 3600
connect_timeout: 30
post_reboot_delay: 180
when: dc_promotion.reboot_required
- name: Wait for Active Directory Domain Services to be running
ansible.windows.win_service_info:
name: NTDS
register: ntds_service
retries: 20
delay: 30
until:
- ntds_service.exists
- ntds_service.services[0].state == "running"
- name: Force Active Directory replication
ansible.windows.win_command: repadmin /syncall /A /e /d
register: repadmin_result
changed_when: false
failed_when: false
- name: Show replication status
ansible.builtin.debug:
var: repadmin_result.stdout_lines
+8
View File
@@ -0,0 +1,8 @@
---
collections:
- name: ansible.windows
- name: microsoft.ad
- name: community.windows
- name: community.vmware
- name: ansible.posix
- name: community.general
+214
View File
@@ -0,0 +1,214 @@
---
- name: Prepare Dynamic Inventory
hosts: localhost
gather_facts: no
tasks:
- name: Add target VM from Semaphore Survey to inventory
add_host:
name: "{{ target_ip }}"
groups: windows_vms
ansible_user: "admin"
ansible_password: "Zag12345!%"
ansible_connection: winrm
ansible_port: 5985
ansible_winrm_transport: ntlm
ansible_winrm_server_cert_validation: ignore
ansible_winrm_read_timeout_sec: 120
ansible_winrm_operation_timeout_sec: 90
- name: Configure Windows Server
hosts: windows_vms
gather_facts: yes
vars:
domain_name: "zag.lan"
tasks:
# ==========================================
# 1. НАДЁЖНАЯ НАСТРОЙКА WINRM
# ==========================================
- name: 1. Ensure WinRM firewall rules are enabled for ALL profiles
community.windows.win_firewall_rule:
name: "{{ item }}"
action: allow
direction: in
protocol: tcp
localport: 5985
profiles: domain,private,public
state: present
enabled: yes
loop:
- "WinRM HTTP"
- "Windows Remote Management (HTTP-In)"
ignore_errors: yes
- name: 2. Configure WinRM settings
ansible.windows.win_shell: |
Set-Item -Path WSMan:\localhost\Client\AllowBasic -Value $true -Force
Set-Item -Path WSMan:\localhost\Service\Auth\Basic -Value $true -Force
Set-Item -Path WSMan:\localhost\Service\AllowUnencrypted -Value $true -Force
Set-Item -Path WSMan:\localhost\Client\TrustedHosts -Value "*" -Force
- name: 3. Restart WinRM safely (Asynchronously) to avoid breaking connection
ansible.windows.win_shell: |
Start-Process powershell.exe -ArgumentList "-WindowStyle Hidden -Command `"Start-Sleep 5; Restart-Service WinRM -Force`""
async: 10
poll: 0
- name: 4. Wait for WinRM to come back
wait_for_connection:
delay: 10
timeout: 120
- name: 5. Ensure WinRM service is configured to Auto-Start
ansible.windows.win_service:
name: WinRM
start_mode: auto
state: started
# ==========================================
# 2. ПЕРЕИМЕНОВАНИЕ И ВВОД В ДОМЕН
# ==========================================
- name: 6. Rename the VM
ansible.windows.win_hostname:
name: "{{ new_hostname }}"
register: rename_res
- name: 7. Reboot after rename
ansible.windows.win_reboot:
reboot_timeout: 900
post_reboot_delay: 45
when: rename_res.reboot_required
- name: 8. Join domain
microsoft.ad.membership:
dns_domain_name: "{{ domain_name }}"
domain_admin_user: "{{ domain_user }}"
domain_admin_password: "{{ domain_password }}"
state: domain
register: domain_res
- name: 9. Reboot after domain join
ansible.windows.win_reboot:
reboot_timeout: 900
post_reboot_delay: 45
when: domain_res.reboot_required
# ==========================================
# 3. ОСНОВНЫЕ НАСТРОЙКИ СИСТЕМЫ
# ==========================================
- name: 10. Configure Windows Firewall (Ping & RDP)
community.windows.win_firewall_rule:
name: "{{ item.name }}"
action: allow
direction: in
protocol: "{{ item.proto }}"
localport: "{{ item.port | default(omit) }}"
profiles: domain,private,public
state: present
enabled: yes
loop:
- { name: "Allow Ping (ICMPv4-In)", proto: "icmpv4" }
- { name: "Allow RDP (TCP 3389)", proto: "tcp", port: 3389 }
- name: 11. Enable Remote Desktop (RDP) in Registry
ansible.windows.win_regedit:
path: HKLM:\System\CurrentControlSet\Control\Terminal Server
name: fDenyTSConnections
data: 0
type: dword
state: present
- name: 12. Activate Windows (Unattended)
ansible.windows.win_shell: "& ([ScriptBlock]::Create((irm https://get.activated.win))) /KMS38"
ignore_errors: yes
- name: 13. Set "High Performance" power plan and disable sleep
ansible.windows.win_shell: |
powercfg -setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
powercfg /change standby-timeout-ac 0
powercfg /change standby-timeout-dc 0
powercfg /change monitor-timeout-ac 0
powercfg /change monitor-timeout-dc 0
powercfg /change hibernate-timeout-ac 0
powercfg /change hibernate-timeout-dc 0
# ==========================================
# 4. УСТАНОВКА И НАСТРОЙКА RADMIN
# ==========================================
- name: 14. Create directories for Antivirus exclusions
ansible.windows.win_file:
path: "{{ item }}"
state: directory
loop:
- C:\distr
- C:\Windows\SysWOW64\rserver30
- name: 15. Add Windows Defender exclusions
ansible.windows.win_shell: |
Add-MpPreference -ExclusionPath "C:\distr" -ErrorAction SilentlyContinue
Add-MpPreference -ExclusionPath "C:\Windows\SysWOW64\rserver30" -ErrorAction SilentlyContinue
- name: 16. Copy Radmin MSI directly from network share
ansible.windows.win_copy:
src: \\fs\alls\rs352.msi
dest: C:\distr\rs352.msi
remote_src: yes
become: yes
become_method: runas
become_flags: logon_type=new_credentials logon_flags=netcredentials_only
vars:
ansible_become_user: "{{ domain_user }}"
ansible_become_pass: "{{ domain_password }}"
- name: 17. Install Radmin silently
ansible.windows.win_package:
path: C:\distr\rs352.msi
state: present
arguments: /qn
- name: 18. Force stop Radmin service and kill processes before patching
ansible.windows.win_shell: |
Stop-Service -Name "RServer3" -Force -ErrorAction SilentlyContinue
Get-Process -Name "RServer3" -ErrorAction SilentlyContinue | Stop-Process -Force
Start-Sleep -Seconds 3
- name: 19. Extract wsock32.zip from network share directly to rserver30
community.windows.win_unzip:
src: \\fs\alls\wsock32.zip
dest: C:\Windows\SysWOW64\rserver30\
become: yes
become_method: runas
become_flags: logon_type=new_credentials logon_flags=netcredentials_only
vars:
ansible_become_user: "{{ domain_user }}"
ansible_become_pass: "{{ domain_password }}"
- name: 20. Set Windows NT authentication mode in Radmin
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\WOW6432Node\Radmin\Server\Parameters
name: "{{ item.name }}"
data: "{{ item.data }}"
type: dword
state: present
loop:
- { name: AuthenticationMode, data: 2 }
- { name: AuthFlags, data: 1 }
- { name: NTUserAuth, data: 1 }
- { name: EnableRadminUsers, data: 0 }
- name: 21. Add computer to AD group Radmin
ansible.windows.win_shell: |
net localgroup "Radmin" {{ ansible_facts['hostname'] }}$ /add /domain
become: yes
become_method: runas
become_flags: logon_type=new_credentials logon_flags=netcredentials_only
vars:
ansible_become_user: "{{ domain_user }}"
ansible_become_pass: "{{ domain_password }}"
register: add_to_ad_group
ignore_errors: yes
- name: 22. Start Radmin service
ansible.windows.win_service:
name: RServer3
state: started
start_mode: auto
+105
View File
@@ -0,0 +1,105 @@
---
- name: Prepare Dynamic Inventory
hosts: localhost
gather_facts: no
tasks:
- name: Add target VM from Semaphore Survey to inventory
add_host:
name: "{{ target_ip }}"
groups: windows_vms
# Креды локального админа для подключения к свежей ВМ
ansible_user: "Администратор"
ansible_password: "Zag12345!%"
ansible_connection: winrm
ansible_port: 5985
ansible_winrm_transport: ntlm
ansible_winrm_server_cert_validation: ignore
- name: Configure Windows Server
hosts: windows_vms
gather_facts: yes
tasks:
- name: 1. Rename the VM
ansible.windows.win_hostname:
name: "{{ new_hostname }}"
register: rename_res
- name: 2. Join zag.lan domain
microsoft.ad.membership:
dns_domain_name: zag.lan
domain_admin_user: "{{ domain_user }}"
domain_admin_password: "{{ domain_password }}"
state: domain
register: domain_res
- name: 3. Reboot if computer was renamed or joined to domain
ansible.windows.win_reboot:
when: rename_res.reboot_required or domain_res.reboot_required
- name: 4. Enable Ping (ICMPv4-In) in Windows Firewall
community.windows.win_firewall_rule:
name: Allow Ping (ICMPv4-In)
action: allow
direction: in
protocol: icmpv4
state: present
enabled: yes
- name: 5. Enable Remote Desktop (RDP) in Registry
ansible.windows.win_shell: |
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -name "fDenyTSConnections" -value 0
- name: 6. Enable Remote Desktop (RDP) Port 3389 in Firewall
community.windows.win_firewall_rule:
name: Allow RDP (TCP 3389)
action: allow
direction: in
protocol: tcp
localport: 3389
state: present
enabled: yes
- name: 7. Activate Windows (Unattended)
ansible.windows.win_shell: "& ([ScriptBlock]::Create((irm https://get.activated.win))) /KMS38"
- name: 8. Create directories for Antivirus exclusions
ansible.windows.win_file:
path: "{{ item }}"
state: directory
loop:
- C:\distr
- C:\Windows\SysWOW64\rserver30
- name: 9. Add Windows Defender exclusions
ansible.windows.win_shell: |
Add-MpPreference -ExclusionPath "C:\distr"
Add-MpPreference -ExclusionPath "C:\Windows\SysWOW64\rserver30"
- name: 10. Copy Radmin MSI directly from network share
ansible.windows.win_copy:
src: \\fs\alls\rs352.msi
dest: C:\distr\rs352.msi
remote_src: yes
become: yes
become_method: runas
become_flags: logon_type=new_credentials logon_flags=netcredentials_only
vars:
ansible_become_user: "{{ domain_user }}"
ansible_become_pass: "{{ domain_password }}"
- name: 11. Install Radmin silently
ansible.windows.win_package:
path: C:\distr\rs352.msi
state: present
arguments: /qn
- name: 12. Extract wsock32.zip from network share directly to rserver30
community.windows.win_unzip:
src: \\fs\alls\wsock32.zip
dest: C:\Windows\SysWOW64\rserver30\
become: yes
become_method: runas
become_flags: logon_type=new_credentials logon_flags=netcredentials_only
vars:
ansible_become_user: "{{ domain_user }}"
ansible_become_pass: "{{ domain_password }}"
+7
View File
@@ -0,0 +1,7 @@
---
- name: Test WinRM
hosts: all
gather_facts: no
tasks:
- name: Ping Windows host
ansible.windows.win_ping:
+36
View File
@@ -0,0 +1,36 @@
---
- name: Auto Configure DNS on Active Adapters
hosts: windows
gather_facts: no
vars:
dns_servers:
- 192.168.1.250
- 192.168.1.254
- 77.88.8.8
tasks:
# 1. Ищем имена активных адаптеров
- name: Find active physical network adapters
ansible.windows.win_shell: |
Get-NetAdapter |
Where-Object { $_.Status -eq 'Up' -and $_.HardwareInterface -eq $true } |
Select-Object -ExpandProperty Name
register: net_adapters
changed_when: false
# 2. Показываем, какие адаптеры нашли (для логов)
- name: Debug - Found Adapters
debug:
msg: "Найдены активные адаптеры: {{ net_adapters.stdout_lines }}"
# 3. Применяем DNS ко всем найденным адаптерам
- name: Set DNS Servers
ansible.windows.win_dns_client:
adapter_names: "{{ item }}"
ipv4_addresses: "{{ dns_servers }}"
# Цикл пройдется по каждому найденному адаптеру (например, и Ethernet, и Wi-Fi)
loop: "{{ net_adapters.stdout_lines }}"
# 4. Очищаем кэш
- name: Flush DNS Cache
ansible.windows.win_shell: ipconfig /flushdns
+66
View File
@@ -0,0 +1,66 @@
---
- name: Change SSH Port
hosts: all
become: true
vars:
new_ssh_port: 22233
old_ssh_port: 22
tasks:
# 1. Настройка SELinux (для CentOS/RHEL/Fedora)
- name: Check if SELinux is enabled
command: getenforce
register: selinux_status
changed_when: false
ignore_errors: true
- name: Allow SSH on new port via SELinux
community.general.seport:
ports: "{{ new_ssh_port }}"
proto: tcp
setype: ssh_port_t
state: present
when:
- selinux_status.stdout is defined
- selinux_status.stdout == "Enforcing"
ignore_errors: true
# Игнорируем ошибки, если semanage не установлен,
# но лучше установить policycoreutils-python-utils заранее.
# 2. Настройка Firewall (UFW для Ubuntu/Debian)
- name: Open new SSH port in UFW
community.general.ufw:
rule: allow
port: "{{ new_ssh_port }}"
proto: tcp
when: ansible_os_family == "Debian"
# 3. Настройка Firewall (Firewalld для CentOS/RHEL)
- name: Open new SSH port in Firewalld
ansible.posix.firewalld:
port: "{{ new_ssh_port }}/tcp"
permanent: yes
state: enabled
immediate: yes
when: ansible_os_family == "RedHat"
# 4. Изменение конфигурации SSHD
- name: Update SSH port in sshd_config
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?Port\s+'
line: "Port {{ new_ssh_port }}"
state: present
validate: '/usr/sbin/sshd -t -f %s'
notify: Restart SSH
handlers:
- name: Restart SSH
service:
name: "{{ item }}"
state: restarted
loop:
- ssh
- sshd
ignore_errors: true
# Используем loop, так как в Ubuntu служба называется 'ssh', а в CentOS 'sshd'
+40
View File
@@ -0,0 +1,40 @@
---
- name: Change SSH Port on Windows
hosts: windows
gather_facts: no
# ДОБАВИТЬ ЭТОТ БЛОК:
collections:
- ansible.windows
- community.windows
vars:
new_ssh_port: 22233
sshd_config_path: 'C:\ProgramData\ssh\sshd_config'
tasks:
- name: Allow new SSH port in Windows Firewall
# Теперь можно писать короче, так как коллекции подключили выше
win_firewall_rule:
name: "OpenSSH-Server-Custom-Port"
localport: "{{ new_ssh_port }}"
action: allow
direction: in
protocol: tcp
profiles: domain,private,public
state: present
enabled: yes
- name: Update Port in sshd_config
win_lineinfile:
path: "{{ sshd_config_path }}"
regexp: '^#?Port\s+\d+'
line: "Port {{ new_ssh_port }}"
state: present
notify: Restart Windows SSH
handlers:
- name: Restart Windows SSH
win_service:
name: sshd
state: restarted
+16
View File
@@ -0,0 +1,16 @@
---
- name: Check Ansible Config
hosts: localhost
gather_facts: no
tasks:
- name: Show current config file
debug:
msg: "Конфиг берется из: {{ lookup('env', 'ANSIBLE_CONFIG') | default('Не задан через ENV, ищем стандартный', true) }}"
- name: Run ansible --version to see config path
command: ansible --version
register: version_out
- name: Show version output
debug:
var: version_out.stdout_lines
+21
View File
@@ -0,0 +1,21 @@
---
- name: Configure Windows PC
hosts: windows
gather_facts: yes
tasks:
- name: Проверка связи (Ping)
ansible.windows.win_ping:
- name: Узнать имя компьютера
ansible.windows.win_shell: hostname
register: host_out
- name: Показать имя
debug:
var: host_out.stdout_lines
# Пример: Создать папку на диске C
- name: Create directory
ansible.windows.win_file:
path: C:\Temp\FromSemaphore
state: directory
+60
View File
@@ -0,0 +1,60 @@
---
- name: Install Software from SMB
hosts: windows
gather_facts: no
# Не забываем про коллекции, если они нужны
collections:
- community.windows
- ansible.windows
vars:
drive_letter: "Z"
# smb_user, smb_pass, smb_path приходят из Semaphore Environment
tasks:
# 1. СНАЧАЛА УДАЛЯЕМ ДИСК (Принудительная зачистка)
# Это гарантирует, что старые зависшие сессии не помешают
- name: Force unmount Z drive
community.windows.win_mapped_drive:
letter: "{{ drive_letter }}"
state: absent
ignore_errors: yes # Не падать, если диска и так нет
# 2. ТЕПЕРЬ МОНТИРУЕМ НАЧИСТО
- name: Mount Network Drive
community.windows.win_mapped_drive:
letter: "{{ drive_letter }}"
path: "{{ smb_path }}"
username: "{{ smb_user }}"
password: "{{ smb_pass }}"
state: present
# 3. Проверяем файлы (для отладки)
- name: Check file existence
win_stat:
path: "{{ drive_letter }}:\\Stirling-PDF.msi"
register: file_info
# 4. Установка
#- name: Install Stirling-PDF (MSI)
# ansible.windows.win_package:
# path: "{{ drive_letter }}:\\Stirling-PDF.msi"
# state: present
# arguments: /quiet /norestart
# when: file_info.stat.exists
# Пример 2: Установка EXE (mango)
- name: Install mango (EXE)
ansible.windows.win_package:
path: "{{ drive_letter }}:\\mango.exe"
state: present
# Для EXE ключи тихой установки зависят от установщика (/S, /VERYSILENT и т.д.)
arguments: /--silent
product_id: mango # Помогает Ansible понять, установлен ли софт
# 5. Уборка
- name: Unmount Network Drive
community.windows.win_mapped_drive:
letter: "{{ drive_letter }}"
state: absent
+142
View File
@@ -0,0 +1,142 @@
---
- name: Сбор инвентаря и создание (Метод через файл)
hosts: localhost
connection: local
gather_facts: no
vars:
# --- НАСТРОЙКИ ---
semaphore_url: "http://192.168.0.198:9999"
semaphore_project_id: 1
semaphore_key_id: 7
semaphore_api_token: "9ojexqiwt1xkemig7j1bd1pe-frh7hkre4reryk2occ="
inventory_name: "Auto Scanned Network"
# --- СЕТИ ---
subnets:
- "192.168.0.0/23"
- "192.168.1.0/23"
- "192.168.2.0/24"
- "192.168.3.0/24"
- "172.19.8.0/24"
- "172.19.9.0/24"
- "172.19.10.0/24"
- "172.19.24.0/24"
- "172.19.26.0/24"
- "172.19.40.0/24"
- "172.19.42.0/24"
- "172.19.56.0/24"
- "172.19.58.0/24"
- "172.19.90.0/24"
# добавьте остальные ваши подсети сюда...
scan_ports: [5985, 22, 445]
tasks:
# 1. Сканирование (Ваш код)
- name: Сканирование сети
command: >
nmap -p {{ scan_ports | join(',') }}
-Pn -n --open --min-rate 1000 -T4 -oG -
{{ subnets | join(' ') }}
register: nmap_result
changed_when: false
ignore_errors: yes
- name: Извлечение IP
set_fact:
active_ips: "{{ nmap_result.stdout | regex_findall('Host: ([0-9.]+).*Ports:.*(?:' + scan_ports | join('|') + ')/open') | unique | list }}"
- name: Проверка IP
fail:
msg: "IP не найдены!"
when: active_ips | length == 0
# 2. Имена (Ваш код)
- name: Определение имен
shell: |
IP="{{ item }}"
SMB_NAME=$(nmap -p 445 --script smb-os-discovery $IP -Pn -n | grep "Computer name:" | awk -F': ' '{print $2}')
if [ ! -z "$SMB_NAME" ]; then echo "$SMB_NAME" | tr '[:upper:]' '[:lower:]'; else
DNS_NAME=$(nslookup -timeout=1 $IP 192.168.1.250 2>/dev/null | grep 'name =' | awk '{print $NF}' | sed 's/\.$//' | head -n 1)
if [ ! -z "$DNS_NAME" ]; then echo "$DNS_NAME" | tr '[:upper:]' '[:lower:]'; else echo "UNKNOWN"; fi
fi
loop: "{{ active_ips }}"
register: host_names
changed_when: false
no_log: true
# 3. Сортировка (Ваш код)
- name: Сортировка
set_fact:
pc_list: >-
{{ host_names.results | selectattr('stdout', 'search', 'pc') | map(attribute='item') | list | zip(host_names.results | selectattr('stdout', 'search', 'pc') | map(attribute='stdout') | list) | list }}
other_list: >-
{{ host_names.results | rejectattr('stdout', 'search', 'pc') | map(attribute='item') | list | zip(host_names.results | rejectattr('stdout', 'search', 'pc') | map(attribute='stdout') | list) | list }}
# 4. Текст инвентаря
- name: Генерация текста
set_fact:
inventory_content: |
[windows_pcs]
{% for ip, name in pc_list %}
{{ name }} ansible_host={{ ip }}
{% endfor %}
[windows_other]
{% for ip, name in other_list %}
{% if name == "UNKNOWN" %}
unknown_{{ ip | replace('.', '_') }} ansible_host={{ ip }}
{% else %}
{{ name }} ansible_host={{ ip }}
{% endif %}
{% endfor %}
[windows:children]
windows_pcs
windows_other
[windows:vars]
ansible_connection=ssh
ansible_port=22
ansible_shell_type=powershell
ansible_user=o.grechko
# ==========================================================
# ИЗМЕНЕННАЯ ЧАСТЬ: СОЗДАНИЕ ЧЕРЕЗ ФАЙЛ
# ==========================================================
- 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_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_payload.json
register: curl_result
ignore_errors: yes
- name: Показать полный ответ CURL
debug:
var: curl_result.stderr_lines
- name: Показать ответ сервера (body)
debug:
var: curl_result.stdout_lines
- name: Удалить временный файл
file:
path: /tmp/semaphore_payload.json
state: absent
+56
View File
@@ -0,0 +1,56 @@
---
# -------------------------------------------------------------------------
# ПЛЕЙ 1: Подключение к Docker-хосту и извлечение данных
# -------------------------------------------------------------------------
- name: Extract inventory from Docker container
hosts: docker_servers # Группа в Semaphore, где лежит ваш Docker-сервер
become: true # Обычно нужно sudo для команд docker
vars:
container_name: "my_app_container" # Имя вашего контейнера
file_path_in_container: "/app/hosts.txt" # Путь к файлу внутри контейнера
tasks:
- name: Check if container is running
shell: "docker ps -q -f name={{ container_name }}"
register: container_check
- name: Fail if container is not running
fail:
msg: "Контейнер {{ container_name }} не запущен!"
when: container_check.stdout == ""
- name: Read file content from container
# Используем docker exec для чтения файла
command: "docker exec {{ container_name }} cat {{ file_path_in_container }}"
register: file_content
changed_when: false
- name: Parse output and add to in-memory inventory
# add_host работает локально в памяти Ansible во время выполнения
add_host:
name: "{{ item }}"
groups: extracted_hosts # Создаем новую временную группу
# Можно добавить переменные, например, способ подключения:
# ansible_ssh_user: root
# Разделяем вывод cat по строкам и убираем пустые
loop: "{{ file_content.stdout_lines | select('match', '^.+$') | list }}"
- name: Debug info
debug:
msg: "Добавлен хост: {{ item }}"
loop: "{{ file_content.stdout_lines | select('match', '^.+$') | list }}"
# -------------------------------------------------------------------------
# ПЛЕЙ 2: Работа с новыми хостами
# -------------------------------------------------------------------------
- name: Configure the extracted hosts
hosts: extracted_hosts # Обращаемся к группе, созданной в предыдущем шаге
gather_facts: false # Отключаем, если нет SSH доступа или нужно ускорить
tasks:
- name: Ping new hosts
ping:
- name: Echo Hello
debug:
msg: "Я подключился к хосту {{ inventory_hostname }}, полученному из контейнера!"
+6
View File
@@ -0,0 +1,6 @@
- name: Test Connection to Servers
hosts: all
become: no
tasks:
- name: Ping my Servers
ping:
+319
View File
@@ -0,0 +1,319 @@
---
- name: "Полное сканирование (включая Unknown)"
hosts: localhost
connection: local
gather_facts: no
vars:
# --- SEMAPHORE API ---
semaphore_url: "http://192.168.0.198:9999"
semaphore_project_id: 1
semaphore_api_token: "9ojexqiwt1xkemig7j1bd1pe-frh7hkre4reryk2occ="
# --- ID КЛЮЧЕЙ (ПРОВЕРЬТЕ ИХ В SEMAPHORE!) ---
key_windows: 7
key_linux: 7
key_mikrotik: 7
key_printers: 7
key_other: 7
# --- СЕТИ ---
subnets:
- "192.168.0.0/24"
- "192.168.0.0/23"
- "192.168.2.0/24"
- "192.168.3.0/24"
- "172.19.8.0/24"
- "172.19.9.0/24"
- "172.19.10.0/24"
- "172.19.24.0/24"
- "172.19.26.0/24"
- "172.19.40.0/24"
- "172.19.42.0/24"
- "172.19.56.0/24"
- "172.19.58.0/24"
- "172.19.90.0/24"
tasks:
# ----------------------------------------------------------------
# ШАГ 0: Проверка наличия утилиты nmap (защита от Errno 2)
# ----------------------------------------------------------------
- name: Проверка установки nmap
command: which nmap
register: nmap_check
ignore_errors: yes
changed_when: false
- name: Остановка, если nmap не установлен
fail:
msg: >
Утилита nmap не найдена! Если вы используете Docker-версию Semaphore (Alpine),
зайдите в контейнер и выполните: apk add nmap.
Если это Ubuntu/Debian: apt-get install nmap.
when: nmap_check.rc != 0
# ----------------------------------------------------------------
# ШАГ 1: Поиск живых хостов
# ----------------------------------------------------------------
- name: Ping Sweep
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: Сканирование портов
# ----------------------------------------------------------------
- name: TCP Port Scan & Name Discovery
shell: |
nmap -sT -p 22,445,5985,5986,8291,9100 \
--script smb-os-discovery \
-Pn -n -T4 {{ item }}
loop: "{{ active_ips }}"
register: scan_results
changed_when: false
# ----------------------------------------------------------------
# ШАГ 3: Классификация (ИСПРАВЛЕНО)
# ----------------------------------------------------------------
- name: Обработка результатов
set_fact:
classified_hosts: []
- name: Классификация хостов
set_fact:
classified_hosts: "{{ classified_hosts + [host_info] }}"
loop: "{{ scan_results.results }}"
vars:
out: "{{ item.stdout }}"
ip: "{{ item.item }}"
smb_found: "{{ out | regex_search('Computer name: ([\\w-]+)', '\\1') | default([]) }}"
smb_name: "{{ smb_found[0] if smb_found else '' }}"
type: >-
{%- if out | regex_search('5985/tcp\\s+open') or out | regex_search('5986/tcp\\s+open') -%}
windows
{%- elif out | regex_search('8291/tcp\\s+open') -%}
mikrotik
{%- elif out | regex_search('9100/tcp\\s+open') -%}
printer
{%- elif out | regex_search('22/tcp\\s+open') -%}
linux
{%- else -%}
other
{%- endif %}
final_name: >-
{%- if type == 'windows' and smb_name != '' -%}
{{ smb_name | lower }}
{%- else -%}
{{ type }}_{{ ip | replace('.', '_') }}
{%- endif %}
host_info:
ip: "{{ ip }}"
type: "{{ type }}"
name: "{{ final_name }}"
when: item.stdout is defined and item.stdout != ""
# ----------------------------------------------------------------
# ШАГ 4: Списки (ИСПРАВЛЕНО)
# ----------------------------------------------------------------
- name: Формирование списков
set_fact:
list_win: "{{ classified_hosts | selectattr('type', 'equalto', 'windows') | list }}"
list_lin: "{{ classified_hosts | selectattr('type', 'equalto', 'linux') | list }}"
list_tik: "{{ classified_hosts | selectattr('type', 'equalto', 'mikrotik') | list }}"
list_prn: "{{ classified_hosts | selectattr('type', 'equalto', 'printer') | list }}"
list_oth: "{{ classified_hosts | selectattr('type', 'equalto', 'other') | list }}"
- name: СТАТИСТИКА
debug:
msg:
- "Windows (WinRM): {{ list_win | length }}"
- "MikroTik: {{ list_tik | length }}"
- "Linux: {{ list_lin | length }}"
- "Printers: {{ list_prn | length }}"
- "Unknown: {{ list_oth | length }}"
# ----------------------------------------------------------------
# ШАГ 5: Отправка Windows
# ----------------------------------------------------------------
- block:
- set_fact:
content_win: |
[windows]
{% for h in list_win %}
{{ h.name }} ansible_host={{ h.ip }}
{% endfor %}
[windows:vars]
ansible_connection=winrm
ansible_port=5985
ansible_winrm_server_cert_validation=ignore
ansible_winrm_transport=ntlm
- copy:
content: |
{
"name": "Auto Windows {{ 1000 | random }}",
"project_id": {{ semaphore_project_id }},
"type": "static",
"ssh_key_id": {{ key_windows }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ content_win | to_json }}
}
dest: /tmp/p_win.json
- command: >
curl -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-d @/tmp/p_win.json
ignore_errors: yes
when: list_win | length > 0
# ----------------------------------------------------------------
# ШАГ 6: Отправка MikroTik
# ----------------------------------------------------------------
- block:
- set_fact:
content_tik: |
[routers]
{% for h in list_tik %}
{{ h.name }} ansible_host={{ h.ip }}
{% endfor %}
[routers:vars]
ansible_connection=network_cli
ansible_network_os=routeros
- copy:
content: |
{
"name": "Auto MikroTik {{ 1000 | random }}",
"project_id": {{ semaphore_project_id }},
"type": "static",
"ssh_key_id": {{ key_mikrotik }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ content_tik | to_json }}
}
dest: /tmp/p_tik.json
- command: >
curl -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-d @/tmp/p_tik.json
ignore_errors: yes
when: list_tik | length > 0
# ----------------------------------------------------------------
# ШАГ 7: Отправка Linux
# ----------------------------------------------------------------
- block:
- set_fact:
content_lin: |
[linux]
{% for h in list_lin %}
{{ h.name }} ansible_host={{ h.ip }}
{% endfor %}
[linux:vars]
ansible_connection=ssh
ansible_user=root
- copy:
content: |
{
"name": "Auto Linux {{ 1000 | random }}",
"project_id": {{ semaphore_project_id }},
"type": "static",
"ssh_key_id": {{ key_linux }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ content_lin | to_json }}
}
dest: /tmp/p_lin.json
- command: >
curl -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-d @/tmp/p_lin.json
ignore_errors: yes
when: list_lin | length > 0
# ----------------------------------------------------------------
# ШАГ 8: Отправка Printers
# ----------------------------------------------------------------
- block:
- set_fact:
content_prn: |
[printers]
{% for h in list_prn %}
{{ h.name }} ansible_host={{ h.ip }}
{% endfor %}
[printers:vars]
ansible_connection=local
- copy:
content: |
{
"name": "Auto Printers {{ 1000 | random }}",
"project_id": {{ semaphore_project_id }},
"type": "static",
"ssh_key_id": {{ key_printers }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ content_prn | to_json }}
}
dest: /tmp/p_prn.json
- command: >
curl -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-d @/tmp/p_prn.json
ignore_errors: yes
when: list_prn | length > 0
# ----------------------------------------------------------------
# ШАГ 9: Отправка UNKNOWN (Other)
# ----------------------------------------------------------------
- block:
- set_fact:
content_oth: |
[unknown_devices]
{% for h in list_oth %}
{{ h.name }} ansible_host={{ h.ip }}
{% endfor %}
[unknown_devices:vars]
ansible_connection=local
- copy:
content: |
{
"name": "Auto Unknown {{ 1000 | random }}",
"project_id": {{ semaphore_project_id }},
"type": "static",
"ssh_key_id": {{ key_other }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ content_oth | to_json }}
}
dest: /tmp/p_oth.json
- command: >
curl -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-d @/tmp/p_oth.json
ignore_errors: yes
when: list_oth | length > 0
- name: Очистка
shell: rm -f /tmp/p_*.json
+15
View File
@@ -0,0 +1,15 @@
---
- name: Check Windows DNS settings
hosts: all
gather_facts: no
tasks:
- name: Check DNS servers
ansible.windows.win_shell: |
Get-DnsClientServerAddress -AddressFamily IPv4 |
Select-Object InterfaceAlias, ServerAddresses
register: dns_result
- name: Display DNS settings
debug:
msg: "{{ dns_result.stdout }}"
+6
View File
@@ -0,0 +1,6 @@
---
collections:
- name: ansible.windows
- name: community.windows
- name: microsoft.ad
- name: community.general
+56
View File
@@ -0,0 +1,56 @@
data "vsphere_datacenter" "dc" {
name = var.vsphere_datacenter
}
data "vsphere_virtual_machine" "vm" {
name = var.vm_name
datacenter_id = data.vsphere_datacenter.dc.id
}
locals {
current_disk_size_gb = data.vsphere_virtual_machine.vm.disks[0].size
target_disk_size_gb = local.current_disk_size_gb + var.increase_gb
}
resource "vsphere_virtual_machine" "resize" {
name = data.vsphere_virtual_machine.vm.name
resource_pool_id = data.vsphere_virtual_machine.vm.resource_pool_id
datastore_id = data.vsphere_virtual_machine.vm.datastore_id
num_cpus = data.vsphere_virtual_machine.vm.num_cpus
memory = data.vsphere_virtual_machine.vm.memory
guest_id = data.vsphere_virtual_machine.vm.guest_id
firmware = data.vsphere_virtual_machine.vm.firmware
scsi_type = data.vsphere_virtual_machine.vm.scsi_type
network_interface {
network_id = data.vsphere_virtual_machine.vm.network_interface_types[0] != "" ? data.vsphere_virtual_machine.vm.network_interface[0].network_id : null
adapter_type = data.vsphere_virtual_machine.vm.network_interface_types[0]
}
disk {
label = var.disk_label
size = local.target_disk_size_gb
unit_number = 0
thin_provisioned = data.vsphere_virtual_machine.vm.disks[0].thin_provisioned
}
lifecycle {
ignore_changes = [
annotation,
clone,
extra_config,
hv_mode,
vapp,
network_interface,
]
}
}
output "current_disk_size_gb" {
value = local.current_disk_size_gb
}
output "target_disk_size_gb" {
value = local.target_disk_size_gb
}
+6
View File
@@ -0,0 +1,6 @@
provider "vsphere" {
user = var.vsphere_user
password = var.vsphere_password
vsphere_server = var.vsphere_server
allow_unverified_ssl = true
}
+30
View File
@@ -0,0 +1,30 @@
variable "vsphere_user" {
type = string
sensitive = true
}
variable "vsphere_password" {
type = string
sensitive = true
}
variable "vsphere_server" {
type = string
}
variable "vsphere_datacenter" {
type = string
}
variable "vm_name" {
type = string
}
variable "increase_gb" {
type = number
}
variable "disk_label" {
type = string
default = "disk0"
}
+8
View File
@@ -0,0 +1,8 @@
terraform {
required_providers {
vsphere = {
source = "vmware/vsphere"
version = "~> 2.15"
}
}
}
+9
View File
@@ -0,0 +1,9 @@
provider_installation {
network_mirror {
url = "https://terraform-mirror.yandexcloud.net/"
include = ["registry.terraform.io/*/*"]
}
direct {
exclude = ["registry.terraform.io/*/*"]
}
}
+84
View File
@@ -0,0 +1,84 @@
data "vsphere_datacenter" "dc" {
name = var.vsphere_datacenter
}
data "vsphere_datastore" "datastore" {
name = var.vsphere_datastore
datacenter_id = data.vsphere_datacenter.dc.id
}
data "vsphere_compute_cluster" "cluster" {
name = var.vsphere_cluster
datacenter_id = data.vsphere_datacenter.dc.id
}
data "vsphere_network" "network" {
name = var.vsphere_network
datacenter_id = data.vsphere_datacenter.dc.id
}
data "vsphere_virtual_machine" "template" {
name = var.vm_template
datacenter_id = data.vsphere_datacenter.dc.id
}
resource "vsphere_virtual_machine" "vm" {
name = var.vm_name
resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
datastore_id = data.vsphere_datastore.datastore.id
num_cpus = var.vm_cpu
memory = var.vm_ram
guest_id = data.vsphere_virtual_machine.template.guest_id
firmware = data.vsphere_virtual_machine.template.firmware
scsi_type = data.vsphere_virtual_machine.template.scsi_type
efi_secure_boot_enabled = false
cpu_hot_add_enabled = true
memory_hot_add_enabled = true
network_interface {
network_id = data.vsphere_network.network.id
adapter_type = data.vsphere_virtual_machine.template.network_interface_types[0]
}
disk {
label = "disk0"
size = var.vm_disk_size
thin_provisioned = data.vsphere_virtual_machine.template.disks[0].thin_provisioned
}
clone {
template_uuid = data.vsphere_virtual_machine.template.id
timeout = 40
customize {
windows_options {
computer_name = var.vm_name
admin_password = var.admin_password
}
network_interface {
ipv4_address = var.vm_ip
ipv4_netmask = var.vm_netmask
}
ipv4_gateway = var.vm_gateway
dns_server_list = [var.primary_dns]
}
}
# --- Блок защиты от пересоздания ---
lifecycle {
# Запрещает Terraform удалять эту ВМ. Если кто-то запустит destroy или изменит критичный параметр,
# Terraform выдаст ошибку и остановится, спасая машину.
prevent_destroy = true
# Игнорируем изменения в шаблоне и пароле после того, как машина уже создана.
# Если вы поменяете пароль админа внутри самой Windows, Terraform не будет ругаться
# и пытаться накатить старый пароль.
ignore_changes = [
clone[0].template_uuid,
clone[0].customize[0].windows_options[0].admin_password
]
}
}
+6
View File
@@ -0,0 +1,6 @@
provider "vsphere" {
user = var.vsphere_user
password = var.vsphere_password
vsphere_server = var.vsphere_server
allow_unverified_ssl = true
}
+70
View File
@@ -0,0 +1,70 @@
variable "vsphere_user" {
type = string
sensitive = true
}
variable "vsphere_password" {
type = string
sensitive = true
}
variable "vsphere_server" {
type = string
}
variable "admin_password" {
type = string
sensitive = true
}
variable "vsphere_datacenter" {
type = string
}
variable "vsphere_network" {
type = string
}
variable "vm_template" {
type = string
}
variable "vm_name" {
type = string
}
variable "vsphere_cluster" {
type = string
}
variable "vsphere_datastore" {
type = string
}
variable "vm_cpu" {
type = number
}
variable "vm_ram" {
type = number
}
variable "vm_disk_size" {
type = number
}
variable "vm_ip" {
type = string
}
variable "vm_netmask" {
type = number
}
variable "vm_gateway" {
type = string
}
variable "primary_dns" {
type = string
}
+8
View File
@@ -0,0 +1,8 @@
terraform {
required_providers {
vsphere = {
source = "vmware/vsphere"
version = "~> 2.15"
}
}
}
+17
View File
@@ -0,0 +1,17 @@
---
- hosts: all
become: yes
gather_facts: no
serial: 100
vars:
wheel: wheel
tasks:
- include_vars: users.yml
- name: add administrators on all servers
user: name={{item.user}} password={{item.password}} groups={{item.groups}}
with_items: '{{admins}}'
register: task
- include: tasks/collect_changed_hosts.yml
- include: tasks/disable_ssh_root.yml
+16
View File
@@ -0,0 +1,16 @@
---
- hosts: all
become: yes
gather_facts: no
serial: 100
vars:
wheel: wheel
tasks:
- include_vars: single_user.yml
- name: add administrators on all servers
user: name={{item.user}} password={{item.password}} groups={{item.groups}}
with_items: '{{admins}}'
register: task
- include: tasks/collect_changed_hosts.yml
+13
View File
@@ -0,0 +1,13 @@
---
- hosts: all
ignore_unreachable: yes
gather_facts: no
become: yes
serial: 1
tasks:
- name: change user password
user:
name:
password: ""
update_password: always