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:
@@ -0,0 +1,9 @@
|
||||
[defaults]
|
||||
# ВАЖНО: collections_path в единственном числе!
|
||||
collections_path = ./.ansible/collections
|
||||
|
||||
# Отключаем проверку ключей (удобно для тестов)
|
||||
host_key_checking = False
|
||||
|
||||
# Не создавать файлы .retry
|
||||
retry_files_enabled = False
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
collections:
|
||||
- name: ansible.windows
|
||||
- name: microsoft.ad
|
||||
- name: community.windows
|
||||
- name: community.vmware
|
||||
- name: ansible.posix
|
||||
- name: community.general
|
||||
@@ -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
|
||||
@@ -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 }}"
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Test WinRM
|
||||
hosts: all
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Ping Windows host
|
||||
ansible.windows.win_ping:
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 }}, полученному из контейнера!"
|
||||
@@ -0,0 +1,6 @@
|
||||
- name: Test Connection to Servers
|
||||
hosts: all
|
||||
become: no
|
||||
tasks:
|
||||
- name: Ping my Servers
|
||||
ping:
|
||||
@@ -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
|
||||
@@ -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 }}"
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
collections:
|
||||
- name: ansible.windows
|
||||
- name: community.windows
|
||||
- name: microsoft.ad
|
||||
- name: community.general
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
provider "vsphere" {
|
||||
user = var.vsphere_user
|
||||
password = var.vsphere_password
|
||||
vsphere_server = var.vsphere_server
|
||||
allow_unverified_ssl = true
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
vsphere = {
|
||||
source = "vmware/vsphere"
|
||||
version = "~> 2.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
provider_installation {
|
||||
network_mirror {
|
||||
url = "https://terraform-mirror.yandexcloud.net/"
|
||||
include = ["registry.terraform.io/*/*"]
|
||||
}
|
||||
direct {
|
||||
exclude = ["registry.terraform.io/*/*"]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
provider "vsphere" {
|
||||
user = var.vsphere_user
|
||||
password = var.vsphere_password
|
||||
vsphere_server = var.vsphere_server
|
||||
allow_unverified_ssl = true
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
vsphere = {
|
||||
source = "vmware/vsphere"
|
||||
version = "~> 2.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user