From 8ff6d59ba6aebddb336b99ee7ef225c76ae9d5a3 Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Wed, 12 Aug 2026 19:24:02 +0000 Subject: [PATCH] Restore from Gitea ZIP snapshot (12.08.2026) after full instance reinstall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- ansible.cfg | 9 + ansible/active-win.yml | 225 +++++++++++++++++ ansible/expand_ubuntu_disk_workflow.yml | 199 +++++++++++++++ ansible/install-radmin.yml | 93 +++++++ ansible/install-soft-win-fs-alls.yml | 208 +++++++++++++++ ansible/promote_dc.yml | 65 +++++ ansible/requirements.yml | 8 + ansible/setup-windows-users.yml | 214 ++++++++++++++++ ansible/setup-windows.yml | 105 ++++++++ ansible/win_ping.yml | 7 + playbooks/change_dns.yml | 36 +++ playbooks/change_ssh_port.yml | 66 +++++ playbooks/change_ssh_port_win.yml | 40 +++ playbooks/check_ansible_config.yml | 16 ++ playbooks/inform_win.yml | 21 ++ playbooks/install_software_win.yml | 60 +++++ playbooks/inventory.yml | 142 +++++++++++ playbooks/parse_docker_tmp.yml | 56 +++++ playbooks/ping.yml | 6 + playbooks/scan_inventory.yml | 319 ++++++++++++++++++++++++ playbooks/win_dns_ps.yml | 15 ++ requirements.yml | 6 + terraform/resize-vm-disk/main.tf | 56 +++++ terraform/resize-vm-disk/provider.tf | 6 + terraform/resize-vm-disk/variables.tf | 30 +++ terraform/resize-vm-disk/versions.tf | 8 + terraform/vmware/.terraformrc | 9 + terraform/vmware/main.tf | 84 +++++++ terraform/vmware/provider.tf | 6 + terraform/vmware/variables.tf | 70 ++++++ terraform/vmware/versions.tf | 8 + test-playbooks/add_admins.yml | 17 ++ test-playbooks/add_user.yml | 16 ++ test-playbooks/change_user_password.yml | 13 + 34 files changed, 2239 insertions(+) create mode 100644 ansible.cfg create mode 100644 ansible/active-win.yml create mode 100644 ansible/expand_ubuntu_disk_workflow.yml create mode 100644 ansible/install-radmin.yml create mode 100644 ansible/install-soft-win-fs-alls.yml create mode 100644 ansible/promote_dc.yml create mode 100644 ansible/requirements.yml create mode 100644 ansible/setup-windows-users.yml create mode 100644 ansible/setup-windows.yml create mode 100644 ansible/win_ping.yml create mode 100644 playbooks/change_dns.yml create mode 100644 playbooks/change_ssh_port.yml create mode 100644 playbooks/change_ssh_port_win.yml create mode 100644 playbooks/check_ansible_config.yml create mode 100644 playbooks/inform_win.yml create mode 100644 playbooks/install_software_win.yml create mode 100644 playbooks/inventory.yml create mode 100644 playbooks/parse_docker_tmp.yml create mode 100644 playbooks/ping.yml create mode 100644 playbooks/scan_inventory.yml create mode 100644 playbooks/win_dns_ps.yml create mode 100644 requirements.yml create mode 100644 terraform/resize-vm-disk/main.tf create mode 100644 terraform/resize-vm-disk/provider.tf create mode 100644 terraform/resize-vm-disk/variables.tf create mode 100644 terraform/resize-vm-disk/versions.tf create mode 100644 terraform/vmware/.terraformrc create mode 100644 terraform/vmware/main.tf create mode 100644 terraform/vmware/provider.tf create mode 100644 terraform/vmware/variables.tf create mode 100644 terraform/vmware/versions.tf create mode 100644 test-playbooks/add_admins.yml create mode 100644 test-playbooks/add_user.yml create mode 100644 test-playbooks/change_user_password.yml diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..5092f0c --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,9 @@ +[defaults] +# ВАЖНО: collections_path в единственном числе! +collections_path = ./.ansible/collections + +# Отключаем проверку ключей (удобно для тестов) +host_key_checking = False + +# Не создавать файлы .retry +retry_files_enabled = False \ No newline at end of file diff --git a/ansible/active-win.yml b/ansible/active-win.yml new file mode 100644 index 0000000..0465d4b --- /dev/null +++ b/ansible/active-win.yml @@ -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() \ No newline at end of file diff --git a/ansible/expand_ubuntu_disk_workflow.yml b/ansible/expand_ubuntu_disk_workflow.yml new file mode 100644 index 0000000..95c8876 --- /dev/null +++ b/ansible/expand_ubuntu_disk_workflow.yml @@ -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 diff --git a/ansible/install-radmin.yml b/ansible/install-radmin.yml new file mode 100644 index 0000000..1f54905 --- /dev/null +++ b/ansible/install-radmin.yml @@ -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 \ No newline at end of file diff --git a/ansible/install-soft-win-fs-alls.yml b/ansible/install-soft-win-fs-alls.yml new file mode 100644 index 0000000..e7c2395 --- /dev/null +++ b/ansible/install-soft-win-fs-alls.yml @@ -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 \ No newline at end of file diff --git a/ansible/promote_dc.yml b/ansible/promote_dc.yml new file mode 100644 index 0000000..bb10edd --- /dev/null +++ b/ansible/promote_dc.yml @@ -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 diff --git a/ansible/requirements.yml b/ansible/requirements.yml new file mode 100644 index 0000000..12c700a --- /dev/null +++ b/ansible/requirements.yml @@ -0,0 +1,8 @@ +--- +collections: + - name: ansible.windows + - name: microsoft.ad + - name: community.windows + - name: community.vmware + - name: ansible.posix + - name: community.general diff --git a/ansible/setup-windows-users.yml b/ansible/setup-windows-users.yml new file mode 100644 index 0000000..25fa521 --- /dev/null +++ b/ansible/setup-windows-users.yml @@ -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 \ No newline at end of file diff --git a/ansible/setup-windows.yml b/ansible/setup-windows.yml new file mode 100644 index 0000000..63d22ae --- /dev/null +++ b/ansible/setup-windows.yml @@ -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 }}" \ No newline at end of file diff --git a/ansible/win_ping.yml b/ansible/win_ping.yml new file mode 100644 index 0000000..eabd9ac --- /dev/null +++ b/ansible/win_ping.yml @@ -0,0 +1,7 @@ +--- +- name: Test WinRM + hosts: all + gather_facts: no + tasks: + - name: Ping Windows host + ansible.windows.win_ping: diff --git a/playbooks/change_dns.yml b/playbooks/change_dns.yml new file mode 100644 index 0000000..8f3d492 --- /dev/null +++ b/playbooks/change_dns.yml @@ -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 \ No newline at end of file diff --git a/playbooks/change_ssh_port.yml b/playbooks/change_ssh_port.yml new file mode 100644 index 0000000..29e962b --- /dev/null +++ b/playbooks/change_ssh_port.yml @@ -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' \ No newline at end of file diff --git a/playbooks/change_ssh_port_win.yml b/playbooks/change_ssh_port_win.yml new file mode 100644 index 0000000..9b2d2ef --- /dev/null +++ b/playbooks/change_ssh_port_win.yml @@ -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 \ No newline at end of file diff --git a/playbooks/check_ansible_config.yml b/playbooks/check_ansible_config.yml new file mode 100644 index 0000000..5cfaf2b --- /dev/null +++ b/playbooks/check_ansible_config.yml @@ -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 \ No newline at end of file diff --git a/playbooks/inform_win.yml b/playbooks/inform_win.yml new file mode 100644 index 0000000..fb18a88 --- /dev/null +++ b/playbooks/inform_win.yml @@ -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 \ No newline at end of file diff --git a/playbooks/install_software_win.yml b/playbooks/install_software_win.yml new file mode 100644 index 0000000..8f47076 --- /dev/null +++ b/playbooks/install_software_win.yml @@ -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 \ No newline at end of file diff --git a/playbooks/inventory.yml b/playbooks/inventory.yml new file mode 100644 index 0000000..bfee436 --- /dev/null +++ b/playbooks/inventory.yml @@ -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 \ No newline at end of file diff --git a/playbooks/parse_docker_tmp.yml b/playbooks/parse_docker_tmp.yml new file mode 100644 index 0000000..02ab45d --- /dev/null +++ b/playbooks/parse_docker_tmp.yml @@ -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 }}, полученному из контейнера!" \ No newline at end of file diff --git a/playbooks/ping.yml b/playbooks/ping.yml new file mode 100644 index 0000000..ef76782 --- /dev/null +++ b/playbooks/ping.yml @@ -0,0 +1,6 @@ +- name: Test Connection to Servers + hosts: all + become: no + tasks: + - name: Ping my Servers + ping: \ No newline at end of file diff --git a/playbooks/scan_inventory.yml b/playbooks/scan_inventory.yml new file mode 100644 index 0000000..69482a1 --- /dev/null +++ b/playbooks/scan_inventory.yml @@ -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 \ No newline at end of file diff --git a/playbooks/win_dns_ps.yml b/playbooks/win_dns_ps.yml new file mode 100644 index 0000000..d182f4a --- /dev/null +++ b/playbooks/win_dns_ps.yml @@ -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 }}" \ No newline at end of file diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..cf20bbc --- /dev/null +++ b/requirements.yml @@ -0,0 +1,6 @@ +--- +collections: + - name: ansible.windows + - name: community.windows + - name: microsoft.ad + - name: community.general \ No newline at end of file diff --git a/terraform/resize-vm-disk/main.tf b/terraform/resize-vm-disk/main.tf new file mode 100644 index 0000000..f7eeafd --- /dev/null +++ b/terraform/resize-vm-disk/main.tf @@ -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 +} diff --git a/terraform/resize-vm-disk/provider.tf b/terraform/resize-vm-disk/provider.tf new file mode 100644 index 0000000..566afa3 --- /dev/null +++ b/terraform/resize-vm-disk/provider.tf @@ -0,0 +1,6 @@ +provider "vsphere" { + user = var.vsphere_user + password = var.vsphere_password + vsphere_server = var.vsphere_server + allow_unverified_ssl = true +} diff --git a/terraform/resize-vm-disk/variables.tf b/terraform/resize-vm-disk/variables.tf new file mode 100644 index 0000000..d044fa2 --- /dev/null +++ b/terraform/resize-vm-disk/variables.tf @@ -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" +} diff --git a/terraform/resize-vm-disk/versions.tf b/terraform/resize-vm-disk/versions.tf new file mode 100644 index 0000000..5ce4793 --- /dev/null +++ b/terraform/resize-vm-disk/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + vsphere = { + source = "vmware/vsphere" + version = "~> 2.15" + } + } +} diff --git a/terraform/vmware/.terraformrc b/terraform/vmware/.terraformrc new file mode 100644 index 0000000..9bc7728 --- /dev/null +++ b/terraform/vmware/.terraformrc @@ -0,0 +1,9 @@ +provider_installation { + network_mirror { + url = "https://terraform-mirror.yandexcloud.net/" + include = ["registry.terraform.io/*/*"] + } + direct { + exclude = ["registry.terraform.io/*/*"] + } +} \ No newline at end of file diff --git a/terraform/vmware/main.tf b/terraform/vmware/main.tf new file mode 100644 index 0000000..dfef290 --- /dev/null +++ b/terraform/vmware/main.tf @@ -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 + ] + } +} \ No newline at end of file diff --git a/terraform/vmware/provider.tf b/terraform/vmware/provider.tf new file mode 100644 index 0000000..566afa3 --- /dev/null +++ b/terraform/vmware/provider.tf @@ -0,0 +1,6 @@ +provider "vsphere" { + user = var.vsphere_user + password = var.vsphere_password + vsphere_server = var.vsphere_server + allow_unverified_ssl = true +} diff --git a/terraform/vmware/variables.tf b/terraform/vmware/variables.tf new file mode 100644 index 0000000..d3f72dd --- /dev/null +++ b/terraform/vmware/variables.tf @@ -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 +} diff --git a/terraform/vmware/versions.tf b/terraform/vmware/versions.tf new file mode 100644 index 0000000..022ccf0 --- /dev/null +++ b/terraform/vmware/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + vsphere = { + source = "vmware/vsphere" + version = "~> 2.15" + } + } +} \ No newline at end of file diff --git a/test-playbooks/add_admins.yml b/test-playbooks/add_admins.yml new file mode 100644 index 0000000..09fd865 --- /dev/null +++ b/test-playbooks/add_admins.yml @@ -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 diff --git a/test-playbooks/add_user.yml b/test-playbooks/add_user.yml new file mode 100644 index 0000000..593ff8b --- /dev/null +++ b/test-playbooks/add_user.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 diff --git a/test-playbooks/change_user_password.yml b/test-playbooks/change_user_password.yml new file mode 100644 index 0000000..64beb2f --- /dev/null +++ b/test-playbooks/change_user_password.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