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

Git history was lost when the previous Gitea instance was wiped and
reinstalled due to an unresolved corruption bug — this commit is the
last known-good file content, exported before the reinstall. Prior
commit history is not recoverable through this path.
This commit is contained in:
Claude Sonnet 5
2026-08-12 19:24:02 +00:00
commit 8ff6d59ba6
34 changed files with 2239 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
---
- name: Auto Configure DNS on Active Adapters
hosts: windows
gather_facts: no
vars:
dns_servers:
- 192.168.1.250
- 192.168.1.254
- 77.88.8.8
tasks:
# 1. Ищем имена активных адаптеров
- name: Find active physical network adapters
ansible.windows.win_shell: |
Get-NetAdapter |
Where-Object { $_.Status -eq 'Up' -and $_.HardwareInterface -eq $true } |
Select-Object -ExpandProperty Name
register: net_adapters
changed_when: false
# 2. Показываем, какие адаптеры нашли (для логов)
- name: Debug - Found Adapters
debug:
msg: "Найдены активные адаптеры: {{ net_adapters.stdout_lines }}"
# 3. Применяем DNS ко всем найденным адаптерам
- name: Set DNS Servers
ansible.windows.win_dns_client:
adapter_names: "{{ item }}"
ipv4_addresses: "{{ dns_servers }}"
# Цикл пройдется по каждому найденному адаптеру (например, и Ethernet, и Wi-Fi)
loop: "{{ net_adapters.stdout_lines }}"
# 4. Очищаем кэш
- name: Flush DNS Cache
ansible.windows.win_shell: ipconfig /flushdns
+66
View File
@@ -0,0 +1,66 @@
---
- name: Change SSH Port
hosts: all
become: true
vars:
new_ssh_port: 22233
old_ssh_port: 22
tasks:
# 1. Настройка SELinux (для CentOS/RHEL/Fedora)
- name: Check if SELinux is enabled
command: getenforce
register: selinux_status
changed_when: false
ignore_errors: true
- name: Allow SSH on new port via SELinux
community.general.seport:
ports: "{{ new_ssh_port }}"
proto: tcp
setype: ssh_port_t
state: present
when:
- selinux_status.stdout is defined
- selinux_status.stdout == "Enforcing"
ignore_errors: true
# Игнорируем ошибки, если semanage не установлен,
# но лучше установить policycoreutils-python-utils заранее.
# 2. Настройка Firewall (UFW для Ubuntu/Debian)
- name: Open new SSH port in UFW
community.general.ufw:
rule: allow
port: "{{ new_ssh_port }}"
proto: tcp
when: ansible_os_family == "Debian"
# 3. Настройка Firewall (Firewalld для CentOS/RHEL)
- name: Open new SSH port in Firewalld
ansible.posix.firewalld:
port: "{{ new_ssh_port }}/tcp"
permanent: yes
state: enabled
immediate: yes
when: ansible_os_family == "RedHat"
# 4. Изменение конфигурации SSHD
- name: Update SSH port in sshd_config
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?Port\s+'
line: "Port {{ new_ssh_port }}"
state: present
validate: '/usr/sbin/sshd -t -f %s'
notify: Restart SSH
handlers:
- name: Restart SSH
service:
name: "{{ item }}"
state: restarted
loop:
- ssh
- sshd
ignore_errors: true
# Используем loop, так как в Ubuntu служба называется 'ssh', а в CentOS 'sshd'
+40
View File
@@ -0,0 +1,40 @@
---
- name: Change SSH Port on Windows
hosts: windows
gather_facts: no
# ДОБАВИТЬ ЭТОТ БЛОК:
collections:
- ansible.windows
- community.windows
vars:
new_ssh_port: 22233
sshd_config_path: 'C:\ProgramData\ssh\sshd_config'
tasks:
- name: Allow new SSH port in Windows Firewall
# Теперь можно писать короче, так как коллекции подключили выше
win_firewall_rule:
name: "OpenSSH-Server-Custom-Port"
localport: "{{ new_ssh_port }}"
action: allow
direction: in
protocol: tcp
profiles: domain,private,public
state: present
enabled: yes
- name: Update Port in sshd_config
win_lineinfile:
path: "{{ sshd_config_path }}"
regexp: '^#?Port\s+\d+'
line: "Port {{ new_ssh_port }}"
state: present
notify: Restart Windows SSH
handlers:
- name: Restart Windows SSH
win_service:
name: sshd
state: restarted
+16
View File
@@ -0,0 +1,16 @@
---
- name: Check Ansible Config
hosts: localhost
gather_facts: no
tasks:
- name: Show current config file
debug:
msg: "Конфиг берется из: {{ lookup('env', 'ANSIBLE_CONFIG') | default('Не задан через ENV, ищем стандартный', true) }}"
- name: Run ansible --version to see config path
command: ansible --version
register: version_out
- name: Show version output
debug:
var: version_out.stdout_lines
+21
View File
@@ -0,0 +1,21 @@
---
- name: Configure Windows PC
hosts: windows
gather_facts: yes
tasks:
- name: Проверка связи (Ping)
ansible.windows.win_ping:
- name: Узнать имя компьютера
ansible.windows.win_shell: hostname
register: host_out
- name: Показать имя
debug:
var: host_out.stdout_lines
# Пример: Создать папку на диске C
- name: Create directory
ansible.windows.win_file:
path: C:\Temp\FromSemaphore
state: directory
+60
View File
@@ -0,0 +1,60 @@
---
- name: Install Software from SMB
hosts: windows
gather_facts: no
# Не забываем про коллекции, если они нужны
collections:
- community.windows
- ansible.windows
vars:
drive_letter: "Z"
# smb_user, smb_pass, smb_path приходят из Semaphore Environment
tasks:
# 1. СНАЧАЛА УДАЛЯЕМ ДИСК (Принудительная зачистка)
# Это гарантирует, что старые зависшие сессии не помешают
- name: Force unmount Z drive
community.windows.win_mapped_drive:
letter: "{{ drive_letter }}"
state: absent
ignore_errors: yes # Не падать, если диска и так нет
# 2. ТЕПЕРЬ МОНТИРУЕМ НАЧИСТО
- name: Mount Network Drive
community.windows.win_mapped_drive:
letter: "{{ drive_letter }}"
path: "{{ smb_path }}"
username: "{{ smb_user }}"
password: "{{ smb_pass }}"
state: present
# 3. Проверяем файлы (для отладки)
- name: Check file existence
win_stat:
path: "{{ drive_letter }}:\\Stirling-PDF.msi"
register: file_info
# 4. Установка
#- name: Install Stirling-PDF (MSI)
# ansible.windows.win_package:
# path: "{{ drive_letter }}:\\Stirling-PDF.msi"
# state: present
# arguments: /quiet /norestart
# when: file_info.stat.exists
# Пример 2: Установка EXE (mango)
- name: Install mango (EXE)
ansible.windows.win_package:
path: "{{ drive_letter }}:\\mango.exe"
state: present
# Для EXE ключи тихой установки зависят от установщика (/S, /VERYSILENT и т.д.)
arguments: /--silent
product_id: mango # Помогает Ansible понять, установлен ли софт
# 5. Уборка
- name: Unmount Network Drive
community.windows.win_mapped_drive:
letter: "{{ drive_letter }}"
state: absent
+142
View File
@@ -0,0 +1,142 @@
---
- name: Сбор инвентаря и создание (Метод через файл)
hosts: localhost
connection: local
gather_facts: no
vars:
# --- НАСТРОЙКИ ---
semaphore_url: "http://192.168.0.198:9999"
semaphore_project_id: 1
semaphore_key_id: 7
semaphore_api_token: "9ojexqiwt1xkemig7j1bd1pe-frh7hkre4reryk2occ="
inventory_name: "Auto Scanned Network"
# --- СЕТИ ---
subnets:
- "192.168.0.0/23"
- "192.168.1.0/23"
- "192.168.2.0/24"
- "192.168.3.0/24"
- "172.19.8.0/24"
- "172.19.9.0/24"
- "172.19.10.0/24"
- "172.19.24.0/24"
- "172.19.26.0/24"
- "172.19.40.0/24"
- "172.19.42.0/24"
- "172.19.56.0/24"
- "172.19.58.0/24"
- "172.19.90.0/24"
# добавьте остальные ваши подсети сюда...
scan_ports: [5985, 22, 445]
tasks:
# 1. Сканирование (Ваш код)
- name: Сканирование сети
command: >
nmap -p {{ scan_ports | join(',') }}
-Pn -n --open --min-rate 1000 -T4 -oG -
{{ subnets | join(' ') }}
register: nmap_result
changed_when: false
ignore_errors: yes
- name: Извлечение IP
set_fact:
active_ips: "{{ nmap_result.stdout | regex_findall('Host: ([0-9.]+).*Ports:.*(?:' + scan_ports | join('|') + ')/open') | unique | list }}"
- name: Проверка IP
fail:
msg: "IP не найдены!"
when: active_ips | length == 0
# 2. Имена (Ваш код)
- name: Определение имен
shell: |
IP="{{ item }}"
SMB_NAME=$(nmap -p 445 --script smb-os-discovery $IP -Pn -n | grep "Computer name:" | awk -F': ' '{print $2}')
if [ ! -z "$SMB_NAME" ]; then echo "$SMB_NAME" | tr '[:upper:]' '[:lower:]'; else
DNS_NAME=$(nslookup -timeout=1 $IP 192.168.1.250 2>/dev/null | grep 'name =' | awk '{print $NF}' | sed 's/\.$//' | head -n 1)
if [ ! -z "$DNS_NAME" ]; then echo "$DNS_NAME" | tr '[:upper:]' '[:lower:]'; else echo "UNKNOWN"; fi
fi
loop: "{{ active_ips }}"
register: host_names
changed_when: false
no_log: true
# 3. Сортировка (Ваш код)
- name: Сортировка
set_fact:
pc_list: >-
{{ host_names.results | selectattr('stdout', 'search', 'pc') | map(attribute='item') | list | zip(host_names.results | selectattr('stdout', 'search', 'pc') | map(attribute='stdout') | list) | list }}
other_list: >-
{{ host_names.results | rejectattr('stdout', 'search', 'pc') | map(attribute='item') | list | zip(host_names.results | rejectattr('stdout', 'search', 'pc') | map(attribute='stdout') | list) | list }}
# 4. Текст инвентаря
- name: Генерация текста
set_fact:
inventory_content: |
[windows_pcs]
{% for ip, name in pc_list %}
{{ name }} ansible_host={{ ip }}
{% endfor %}
[windows_other]
{% for ip, name in other_list %}
{% if name == "UNKNOWN" %}
unknown_{{ ip | replace('.', '_') }} ansible_host={{ ip }}
{% else %}
{{ name }} ansible_host={{ ip }}
{% endif %}
{% endfor %}
[windows:children]
windows_pcs
windows_other
[windows:vars]
ansible_connection=ssh
ansible_port=22
ansible_shell_type=powershell
ansible_user=o.grechko
# ==========================================================
# ИЗМЕНЕННАЯ ЧАСТЬ: СОЗДАНИЕ ЧЕРЕЗ ФАЙЛ
# ==========================================================
- name: Сохранение JSON-пейлоада в файл (для надежности)
copy:
content: |
{
"name": "{{ inventory_name }} {{ 1000 | random }}",
"project_id": {{ semaphore_project_id | int }},
"type": "static",
"ssh_key_id": {{ semaphore_key_id | int }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ inventory_content | to_json }}
}
dest: /tmp/semaphore_payload.json
- name: Отправка через CURL (чтение из файла)
command: >
curl -v -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-H "Accept: application/json"
-d @/tmp/semaphore_payload.json
register: curl_result
ignore_errors: yes
- name: Показать полный ответ CURL
debug:
var: curl_result.stderr_lines
- name: Показать ответ сервера (body)
debug:
var: curl_result.stdout_lines
- name: Удалить временный файл
file:
path: /tmp/semaphore_payload.json
state: absent
+56
View File
@@ -0,0 +1,56 @@
---
# -------------------------------------------------------------------------
# ПЛЕЙ 1: Подключение к Docker-хосту и извлечение данных
# -------------------------------------------------------------------------
- name: Extract inventory from Docker container
hosts: docker_servers # Группа в Semaphore, где лежит ваш Docker-сервер
become: true # Обычно нужно sudo для команд docker
vars:
container_name: "my_app_container" # Имя вашего контейнера
file_path_in_container: "/app/hosts.txt" # Путь к файлу внутри контейнера
tasks:
- name: Check if container is running
shell: "docker ps -q -f name={{ container_name }}"
register: container_check
- name: Fail if container is not running
fail:
msg: "Контейнер {{ container_name }} не запущен!"
when: container_check.stdout == ""
- name: Read file content from container
# Используем docker exec для чтения файла
command: "docker exec {{ container_name }} cat {{ file_path_in_container }}"
register: file_content
changed_when: false
- name: Parse output and add to in-memory inventory
# add_host работает локально в памяти Ansible во время выполнения
add_host:
name: "{{ item }}"
groups: extracted_hosts # Создаем новую временную группу
# Можно добавить переменные, например, способ подключения:
# ansible_ssh_user: root
# Разделяем вывод cat по строкам и убираем пустые
loop: "{{ file_content.stdout_lines | select('match', '^.+$') | list }}"
- name: Debug info
debug:
msg: "Добавлен хост: {{ item }}"
loop: "{{ file_content.stdout_lines | select('match', '^.+$') | list }}"
# -------------------------------------------------------------------------
# ПЛЕЙ 2: Работа с новыми хостами
# -------------------------------------------------------------------------
- name: Configure the extracted hosts
hosts: extracted_hosts # Обращаемся к группе, созданной в предыдущем шаге
gather_facts: false # Отключаем, если нет SSH доступа или нужно ускорить
tasks:
- name: Ping new hosts
ping:
- name: Echo Hello
debug:
msg: "Я подключился к хосту {{ inventory_hostname }}, полученному из контейнера!"
+6
View File
@@ -0,0 +1,6 @@
- name: Test Connection to Servers
hosts: all
become: no
tasks:
- name: Ping my Servers
ping:
+319
View File
@@ -0,0 +1,319 @@
---
- name: "Полное сканирование (включая Unknown)"
hosts: localhost
connection: local
gather_facts: no
vars:
# --- SEMAPHORE API ---
semaphore_url: "http://192.168.0.198:9999"
semaphore_project_id: 1
semaphore_api_token: "9ojexqiwt1xkemig7j1bd1pe-frh7hkre4reryk2occ="
# --- ID КЛЮЧЕЙ (ПРОВЕРЬТЕ ИХ В SEMAPHORE!) ---
key_windows: 7
key_linux: 7
key_mikrotik: 7
key_printers: 7
key_other: 7
# --- СЕТИ ---
subnets:
- "192.168.0.0/24"
- "192.168.0.0/23"
- "192.168.2.0/24"
- "192.168.3.0/24"
- "172.19.8.0/24"
- "172.19.9.0/24"
- "172.19.10.0/24"
- "172.19.24.0/24"
- "172.19.26.0/24"
- "172.19.40.0/24"
- "172.19.42.0/24"
- "172.19.56.0/24"
- "172.19.58.0/24"
- "172.19.90.0/24"
tasks:
# ----------------------------------------------------------------
# ШАГ 0: Проверка наличия утилиты nmap (защита от Errno 2)
# ----------------------------------------------------------------
- name: Проверка установки nmap
command: which nmap
register: nmap_check
ignore_errors: yes
changed_when: false
- name: Остановка, если nmap не установлен
fail:
msg: >
Утилита nmap не найдена! Если вы используете Docker-версию Semaphore (Alpine),
зайдите в контейнер и выполните: apk add nmap.
Если это Ubuntu/Debian: apt-get install nmap.
when: nmap_check.rc != 0
# ----------------------------------------------------------------
# ШАГ 1: Поиск живых хостов
# ----------------------------------------------------------------
- name: Ping Sweep
command: "nmap -sn -n --min-rate 1000 -T4 -oG - {{ subnets | join(' ') }}"
register: ping_scan
changed_when: false
- name: Формирование списка активных IP
set_fact:
active_ips: "{{ ping_scan.stdout | regex_findall('Host: ([0-9.]+)') | unique | list }}"
- name: Проверка наличия хостов
fail:
msg: "Сеть пуста или ни один хост не ответил на пинг."
when: active_ips | length == 0
# ----------------------------------------------------------------
# ШАГ 2: Сканирование портов
# ----------------------------------------------------------------
- name: TCP Port Scan & Name Discovery
shell: |
nmap -sT -p 22,445,5985,5986,8291,9100 \
--script smb-os-discovery \
-Pn -n -T4 {{ item }}
loop: "{{ active_ips }}"
register: scan_results
changed_when: false
# ----------------------------------------------------------------
# ШАГ 3: Классификация (ИСПРАВЛЕНО)
# ----------------------------------------------------------------
- name: Обработка результатов
set_fact:
classified_hosts: []
- name: Классификация хостов
set_fact:
classified_hosts: "{{ classified_hosts + [host_info] }}"
loop: "{{ scan_results.results }}"
vars:
out: "{{ item.stdout }}"
ip: "{{ item.item }}"
smb_found: "{{ out | regex_search('Computer name: ([\\w-]+)', '\\1') | default([]) }}"
smb_name: "{{ smb_found[0] if smb_found else '' }}"
type: >-
{%- if out | regex_search('5985/tcp\\s+open') or out | regex_search('5986/tcp\\s+open') -%}
windows
{%- elif out | regex_search('8291/tcp\\s+open') -%}
mikrotik
{%- elif out | regex_search('9100/tcp\\s+open') -%}
printer
{%- elif out | regex_search('22/tcp\\s+open') -%}
linux
{%- else -%}
other
{%- endif %}
final_name: >-
{%- if type == 'windows' and smb_name != '' -%}
{{ smb_name | lower }}
{%- else -%}
{{ type }}_{{ ip | replace('.', '_') }}
{%- endif %}
host_info:
ip: "{{ ip }}"
type: "{{ type }}"
name: "{{ final_name }}"
when: item.stdout is defined and item.stdout != ""
# ----------------------------------------------------------------
# ШАГ 4: Списки (ИСПРАВЛЕНО)
# ----------------------------------------------------------------
- name: Формирование списков
set_fact:
list_win: "{{ classified_hosts | selectattr('type', 'equalto', 'windows') | list }}"
list_lin: "{{ classified_hosts | selectattr('type', 'equalto', 'linux') | list }}"
list_tik: "{{ classified_hosts | selectattr('type', 'equalto', 'mikrotik') | list }}"
list_prn: "{{ classified_hosts | selectattr('type', 'equalto', 'printer') | list }}"
list_oth: "{{ classified_hosts | selectattr('type', 'equalto', 'other') | list }}"
- name: СТАТИСТИКА
debug:
msg:
- "Windows (WinRM): {{ list_win | length }}"
- "MikroTik: {{ list_tik | length }}"
- "Linux: {{ list_lin | length }}"
- "Printers: {{ list_prn | length }}"
- "Unknown: {{ list_oth | length }}"
# ----------------------------------------------------------------
# ШАГ 5: Отправка Windows
# ----------------------------------------------------------------
- block:
- set_fact:
content_win: |
[windows]
{% for h in list_win %}
{{ h.name }} ansible_host={{ h.ip }}
{% endfor %}
[windows:vars]
ansible_connection=winrm
ansible_port=5985
ansible_winrm_server_cert_validation=ignore
ansible_winrm_transport=ntlm
- copy:
content: |
{
"name": "Auto Windows {{ 1000 | random }}",
"project_id": {{ semaphore_project_id }},
"type": "static",
"ssh_key_id": {{ key_windows }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ content_win | to_json }}
}
dest: /tmp/p_win.json
- command: >
curl -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-d @/tmp/p_win.json
ignore_errors: yes
when: list_win | length > 0
# ----------------------------------------------------------------
# ШАГ 6: Отправка MikroTik
# ----------------------------------------------------------------
- block:
- set_fact:
content_tik: |
[routers]
{% for h in list_tik %}
{{ h.name }} ansible_host={{ h.ip }}
{% endfor %}
[routers:vars]
ansible_connection=network_cli
ansible_network_os=routeros
- copy:
content: |
{
"name": "Auto MikroTik {{ 1000 | random }}",
"project_id": {{ semaphore_project_id }},
"type": "static",
"ssh_key_id": {{ key_mikrotik }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ content_tik | to_json }}
}
dest: /tmp/p_tik.json
- command: >
curl -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-d @/tmp/p_tik.json
ignore_errors: yes
when: list_tik | length > 0
# ----------------------------------------------------------------
# ШАГ 7: Отправка Linux
# ----------------------------------------------------------------
- block:
- set_fact:
content_lin: |
[linux]
{% for h in list_lin %}
{{ h.name }} ansible_host={{ h.ip }}
{% endfor %}
[linux:vars]
ansible_connection=ssh
ansible_user=root
- copy:
content: |
{
"name": "Auto Linux {{ 1000 | random }}",
"project_id": {{ semaphore_project_id }},
"type": "static",
"ssh_key_id": {{ key_linux }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ content_lin | to_json }}
}
dest: /tmp/p_lin.json
- command: >
curl -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-d @/tmp/p_lin.json
ignore_errors: yes
when: list_lin | length > 0
# ----------------------------------------------------------------
# ШАГ 8: Отправка Printers
# ----------------------------------------------------------------
- block:
- set_fact:
content_prn: |
[printers]
{% for h in list_prn %}
{{ h.name }} ansible_host={{ h.ip }}
{% endfor %}
[printers:vars]
ansible_connection=local
- copy:
content: |
{
"name": "Auto Printers {{ 1000 | random }}",
"project_id": {{ semaphore_project_id }},
"type": "static",
"ssh_key_id": {{ key_printers }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ content_prn | to_json }}
}
dest: /tmp/p_prn.json
- command: >
curl -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-d @/tmp/p_prn.json
ignore_errors: yes
when: list_prn | length > 0
# ----------------------------------------------------------------
# ШАГ 9: Отправка UNKNOWN (Other)
# ----------------------------------------------------------------
- block:
- set_fact:
content_oth: |
[unknown_devices]
{% for h in list_oth %}
{{ h.name }} ansible_host={{ h.ip }}
{% endfor %}
[unknown_devices:vars]
ansible_connection=local
- copy:
content: |
{
"name": "Auto Unknown {{ 1000 | random }}",
"project_id": {{ semaphore_project_id }},
"type": "static",
"ssh_key_id": {{ key_other }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ content_oth | to_json }}
}
dest: /tmp/p_oth.json
- command: >
curl -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-d @/tmp/p_oth.json
ignore_errors: yes
when: list_oth | length > 0
- name: Очистка
shell: rm -f /tmp/p_*.json
+15
View File
@@ -0,0 +1,15 @@
---
- name: Check Windows DNS settings
hosts: all
gather_facts: no
tasks:
- name: Check DNS servers
ansible.windows.win_shell: |
Get-DnsClientServerAddress -AddressFamily IPv4 |
Select-Object InterfaceAlias, ServerAddresses
register: dns_result
- name: Display DNS settings
debug:
msg: "{{ dns_result.stdout }}"