1824 lines
		
	
	
		
			96 KiB
		
	
	
	
		
			Makefile
		
	
	
	
	
	
			
		
		
	
	
			1824 lines
		
	
	
		
			96 KiB
		
	
	
	
		
			Makefile
		
	
	
	
	
	
| # =============================================================================
 | ||
| # DevOpsLab - Универсальная система тестирования Ansible ролей
 | ||
| # Автор: Сергей Антропов
 | ||
| # Сайт: https://devops.org.ru
 | ||
| # =============================================================================
 | ||
| 
 | ||
| SHELL := /bin/bash
 | ||
| 
 | ||
| # =============================================================================
 | ||
| # ПЕРЕМЕННЫЕ
 | ||
| # =============================================================================
 | ||
| # Цвета для вывода
 | ||
| RED := \033[0;31m
 | ||
| GREEN := \033[0;32m
 | ||
| YELLOW := \033[0;33m
 | ||
| BLUE := \033[0;34m
 | ||
| PURPLE := \033[0;35m
 | ||
| CYAN := \033[0;36m
 | ||
| WHITE := \033[0;37m
 | ||
| RESET := \033[0m
 | ||
| 
 | ||
| # Глобальные переменные
 | ||
| PROJECT_NAME ?= devops-lab
 | ||
| VERSION ?= 0.1.0
 | ||
| AUTHOR ?= "Сергей Антропов"
 | ||
| SITE ?= "https://devops.org.ru"
 | ||
| DOCKER_IMAGE ?= inecs/ansible-lab:ansible-controller-latest
 | ||
| DOCKER_K8S_IMAGE ?= inecs/ansible-lab:k8s-latest
 | ||
| DOCKER_DIND_IMAGE ?= docker:27-dind
 | ||
| CONTAINER_NAME ?= ansible-controller
 | ||
| EDITOR ?= nano
 | ||
| 
 | ||
| # Переменные для Docker Hub
 | ||
| DOCKER_REGISTRY ?= inecs/ansible-lab
 | ||
| DOCKER_VERSION ?= latest
 | ||
| DOCKER_IMAGES := ansible-controller alt9 alt10 astra-linux redos rhel centos7 centos8 centos9 alma rocky ubuntu20 ubuntu22 ubuntu24 debian9 debian10 debian11 debian12
 | ||
| 
 | ||
| # Multi-arch поддержка
 | ||
| DOCKER_PLATFORMS ?= linux/amd64,linux/arm64
 | ||
| DOCKER_BUILDX_BUILDER ?= multiarch-builder
 | ||
| 
 | ||
| # Базовые образы и их теги
 | ||
| BASE_IMAGES := altlinux/p9 astralinux/astra-1.7 redos/redos:9 registry.access.redhat.com/ubi8/ubi centos:7 quay.io/centos/centos:8 quay.io/centos/centos:stream9 almalinux:8 rockylinux:8 ubuntu:20.04 ubuntu:22.04 ubuntu:24.04 debian:9 debian:10 debian:11 debian:bookworm
 | ||
| 
 | ||
| .PHONY: role vault git docker presets controller k8s help update-playbooks generate-docs setup-cicd list create delete
 | ||
| 
 | ||
| # =============================================================================
 | ||
| # КОМАНДЫ ДЛЯ РАБОТЫ С РОЛЯМИ
 | ||
| # =============================================================================
 | ||
| role:
 | ||
| 	@case "$(word 2, $(MAKECMDGOALS))" in \
 | ||
| 		lint) \
 | ||
| 			$(MAKE) decrypt; \
 | ||
| 			ROLE_NAME="$(word 3, $(MAKECMDGOALS))"; \
 | ||
| 			if [ -z "$$ROLE_NAME" ]; then \
 | ||
| 				echo "🔍 Проверка синтаксиса всех ролей ..."; \
 | ||
| 				docker run --rm --name $(CONTAINER_NAME) -v "$(PWD):/workspace" -w /workspace -e ANSIBLE_FORCE_COLOR=1 $(DOCKER_IMAGE) bash -c "ansible-lint roles/ --config-file .ansible-lint || true"; \
 | ||
| 			else \
 | ||
| 				echo "🔍 Проверка синтаксиса роли: $$ROLE_NAME"; \
 | ||
| 				if [ -d "roles/$$ROLE_NAME" ]; then \
 | ||
| 					docker run --rm --name $(CONTAINER_NAME) -v "$(PWD):/workspace" -w /workspace -e ANSIBLE_FORCE_COLOR=1 $(DOCKER_IMAGE) bash -c "ansible-lint roles/$$ROLE_NAME/ --config-file .ansible-lint || true"; \
 | ||
| 				else \
 | ||
| 					echo "❌ Роль '$$ROLE_NAME' не найдена в roles/"; \
 | ||
| 					echo "📋 Доступные роли:"; \
 | ||
| 					ls -1 roles/ | grep -v "\.yml$$" | sed 's/^/  - /'; \
 | ||
| 					exit 1; \
 | ||
| 				fi; \
 | ||
| 			fi; \
 | ||
| 			$(MAKE) encrypt-all; \
 | ||
| 			echo ""; \
 | ||
| 			echo "✅ Lint завершен";; \
 | ||
| 		test) \
 | ||
| 			echo "🚀 Тестирование ролей ..."; \
 | ||
| 			PRESET="default"; \
 | ||
| 			ARGS="$(wordlist 3,10,$(MAKECMDGOALS))"; \
 | ||
| 			if [ -n "$$ARGS" ]; then \
 | ||
| 				PRESET="$$(echo $$ARGS | cut -d' ' -f1)"; \
 | ||
| 			fi; \
 | ||
| 			echo "📋 Используется пресет: $$PRESET"; \
 | ||
| 			if [ ! -f "molecule/presets/$$PRESET.yml" ]; then \
 | ||
| 				echo "❌ Ошибка: Пресет '$$PRESET' не найден!"; \
 | ||
| 				echo "💡 Доступные пресеты:"; \
 | ||
| 				ls -1 molecule/presets/*.yml 2>/dev/null | sed 's|molecule/presets/||g' | sed 's|\.yml||g' | sed 's/^/  - /' || echo "  ⚠️  Пресеты не найдены"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			echo ""; \
 | ||
| 			$(MAKE) decrypt; \
 | ||
| 			echo "🔧 Запуск ansible-controller контейнера..."; \
 | ||
| 			docker run --rm --name $(CONTAINER_NAME) -v "$(PWD):/workspace" -w /workspace \
 | ||
| 				-v /var/run/docker.sock:/var/run/docker.sock \
 | ||
| 				-u root \
 | ||
| 				-e ANSIBLE_FORCE_COLOR=1 \
 | ||
| 				-e MOLECULE_PRESET=$$PRESET \
 | ||
| 				-e MOLECULE_EPHEMERAL_DIRECTORY=/tmp/molecule_workspace \
 | ||
| 				-e MOLECULE_VAULT_ENABLED=$${MOLECULE_VAULT_ENABLED:-false} \
 | ||
| 				$(DOCKER_IMAGE) \
 | ||
| 				bash -c " \
 | ||
| 					echo '=== СОЗДАНИЕ ТЕСТОВЫХ КОНТЕЙНЕРОВ ==='; \
 | ||
| 					mkdir -p /tmp/molecule_workspace/inventory && \
 | ||
| 					cd molecule/default && \
 | ||
| 					ansible-playbook -i localhost, create.yml --connection=local -e molecule_ephemeral_directory=/tmp/molecule_workspace && \
 | ||
| 					echo ''; \
 | ||
| 					echo '=== НАСТРОЙКА VAULT И ПЕРЕМЕННЫХ ==='; \
 | ||
| 					ansible-playbook -i localhost, converge.yml --connection=local -e molecule_ephemeral_directory=/tmp/molecule_workspace && \
 | ||
| 					echo ''; \
 | ||
| 					echo '=== ПРОВЕРКА ПОДКЛЮЧЕНИЯ К КОНТЕЙНЕРАМ ==='; \
 | ||
| 					ansible all -i /tmp/molecule_workspace/inventory/hosts.ini -m ping && \
 | ||
| 					echo ''; \
 | ||
| 					echo '=== ЗАПУСК CONVERGE.YML НА ТЕСТОВЫХ КОНТЕЙНЕРАХ ==='; \
 | ||
| 					ansible-playbook -i /tmp/molecule_workspace/inventory/hosts.ini converge.yml && \
 | ||
| 					echo ''; \
 | ||
| 					echo '=== ЗАПУСК ROLES/DEPLOY.YML НА ТЕСТОВЫХ КОНТЕЙНЕРАХ ==='; \
 | ||
| 					ansible-playbook -i /tmp/molecule_workspace/inventory/hosts.ini ../../roles/deploy.yml && \
 | ||
| 					echo ''; \
 | ||
| 					echo '=== ОЧИСТКА РЕСУРСОВ ==='; \
 | ||
| 					ansible-playbook -i localhost, destroy.yml --connection=local -e molecule_ephemeral_directory=/tmp/molecule_workspace && \
 | ||
| 					echo '✅ Тестирование завершено'"; \
 | ||
| 			$(MAKE) encrypt-all;; \
 | ||
| 		deploy) \
 | ||
| 			echo "🚀 Развертывание ролей на реальные серверы..."; \
 | ||
| 			echo ""; \
 | ||
| 			if [ ! -f "inventory/hosts.ini" ]; then \
 | ||
| 				echo "❌ Ошибка: Файл inventory/hosts.ini не найден!"; \
 | ||
| 				echo "💡 Создайте файл inventory/hosts.ini с вашими серверами"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			$(MAKE) decrypt; \
 | ||
| 			echo "📋 Используется inventory: inventory/hosts.ini"; \
 | ||
| 			echo "📄 Содержимое inventory:"; \
 | ||
| 			docker run --rm -v "$(PWD):/workspace" -w /workspace $(DOCKER_IMAGE) cat inventory/hosts.ini || cat inventory/hosts.ini; \
 | ||
| 			echo ""; \
 | ||
| 			echo "🚀 Запуск развертывания (в контейнере)..."; \
 | ||
| 			docker run --rm --name $(CONTAINER_NAME) -v "$(PWD):/workspace" -w /workspace \
 | ||
| 				-v ~/.ssh:/root/.ssh:ro \
 | ||
| 				-e ANSIBLE_FORCE_COLOR=1 \
 | ||
| 				$(DOCKER_IMAGE) \
 | ||
| 				bash -c "ansible-playbook -i inventory/hosts.ini roles/deploy.yml --check"; \
 | ||
| 			echo ""; \
 | ||
| 			read -p "Продолжить развертывание? (y/N): " confirm; \
 | ||
| 			if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
 | ||
| 				docker run --rm --name $(CONTAINER_NAME) -v "$(PWD):/workspace" -w /workspace \
 | ||
| 					-v ~/.ssh:/root/.ssh:ro \
 | ||
| 					-e ANSIBLE_FORCE_COLOR=1 \
 | ||
| 					$(DOCKER_IMAGE) \
 | ||
| 					bash -c "ansible-playbook -i inventory/hosts.ini roles/deploy.yml"; \
 | ||
| 			else \
 | ||
| 				echo "❌ Развертывание отменено"; \
 | ||
| 			fi; \
 | ||
| 			$(MAKE) encrypt-all;; \
 | ||
| 		list) \
 | ||
| 			./scripts/role-manager.sh list;; \
 | ||
| 		create) \
 | ||
| 			./scripts/role-manager.sh create < /dev/tty;; \
 | ||
| 		delete) \
 | ||
| 			./scripts/role-manager.sh delete < /dev/tty;; \
 | ||
| 		*) \
 | ||
| 			echo "🎯 Доступные команды:"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🚀 make role test [preset]  - протестировать роли с preset'ом"; \
 | ||
| 			echo "     💡 Примеры:"; \
 | ||
| 			echo "       make role test                    # с default preset"; \
 | ||
| 			echo "       make role test minimal            # с minimal preset"; \
 | ||
| 			echo "       make role test all-images         # со всеми образами"; \
 | ||
| 			echo "       make role test etcd-patroni       # с etcd-patroni preset"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🚀 make role deploy         - развернуть роли на реальные серверы"; \
 | ||
| 			echo "     💡 Требует: inventory/hosts.ini"; \
 | ||
| 			echo "     💡 Примеры:"; \
 | ||
| 			echo "       make role deploy                    # развертывание всех ролей"; \
 | ||
| 			echo "       ansible-playbook -i inventory/hosts.ini roles/deploy.yml --tags web"; \
 | ||
| 			echo "       ansible-playbook -i inventory/hosts.ini roles/deploy.yml --limit webservers"; \
 | ||
| 			echo "       ansible-playbook -i inventory/hosts.ini roles/deploy.yml --check"; \
 | ||
| 			echo "     💡 Документация: docs/deploy-yml-customization.md"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔍 make role lint [role]     - проверить синтаксис ролей"; \
 | ||
| 			echo "     💡 Использует: ansible-lint"; \
 | ||
| 			echo "     💡 Без параметра: проверяет все роли"; \
 | ||
| 			echo "     💡 С параметром: проверяет конкретную роль"; \
 | ||
| 			echo "     💡 Валидация: показывает доступные роли при ошибке"; \
 | ||
| 			echo "     💡 Примеры:"; \
 | ||
| 			echo "       make role lint                    # проверить все роли"; \
 | ||
| 			echo "       make role lint devops             # проверить только devops"; \
 | ||
| 			echo "       make role lint ping               # проверить только ping"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  📋 make role list           - показать все роли"; \
 | ||
| 			echo "     💡 Показывает: список всех ролей в roles/"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  ➕ make role create         - создать новую роль"; \
 | ||
| 			echo "     💡 Интерактивно: запрашивает имя роли"; \
 | ||
| 			echo "     💡 Создает: структуру папок и файлов"; \
 | ||
| 			echo "     💡 Обновляет: roles/deploy.yml"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🗑️  make role delete         - удалить роль"; \
 | ||
| 			echo "     💡 Интерактивно: запрашивает имя роли"; \
 | ||
| 			echo "     💡 Удаляет: папку роли и файлы"; \
 | ||
| 			echo "     💡 Обновляет: roles/deploy.yml"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔧 ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ О ЛИНТИНГЕ:"; \
 | ||
| 			echo "     💡 Линтинг проверяет: синтаксис, стиль, лучшие практики"; \
 | ||
| 			echo "     💡 Профили: production, basic, min"; \
 | ||
| 			echo "     💡 Конфигурация: .ansible-lint"; \
 | ||
| 			echo "     💡 Ошибки: показываются с номерами строк"; \
 | ||
| 			echo "     💡 Валидация: автоматическая проверка существования роли";; \
 | ||
| 	esac
 | ||
| 
 | ||
| # =============================================================================
 | ||
| # КОМАНДЫ ДЛЯ РАБОТЫ С PRESET'АМИ
 | ||
| # =============================================================================
 | ||
| presets:
 | ||
| 	@case "$(word 2, $(MAKECMDGOALS))" in \
 | ||
| 		list) \
 | ||
| 			echo "📋 Доступные пресеты:"; \
 | ||
| 			echo ""; \
 | ||
| 			preset_count=0; \
 | ||
| 			for preset in molecule/presets/*.yml; do \
 | ||
| 				if [ -f "$$preset" ]; then \
 | ||
| 					preset_name=$$(basename "$$preset" .yml); \
 | ||
| 					preset_desc=$$(grep -E "^#description:" "$$preset" | head -1 | sed 's/^#description: *//' || echo "Описание отсутствует"); \
 | ||
| 					host_count=$$(grep -c "^- name:" "$$preset" 2>/dev/null || echo "?"); \
 | ||
| 					printf "  📄 %s - %s (%s хостов)\n" "$$preset_name" "$$preset_desc" "$$host_count"; \
 | ||
| 					preset_count=$$((preset_count + 1)); \
 | ||
| 				fi; \
 | ||
| 			done; \
 | ||
| 			if [ $$preset_count -eq 0 ]; then \
 | ||
| 				echo "  ⚠️  Пресеты не найдены"; \
 | ||
| 			fi;; \
 | ||
| 		info) \
 | ||
| 			if [ -z "$(PRESET)" ]; then \
 | ||
| 				echo "❌ Ошибка: Укажите PRESET=имя_пресета"; \
 | ||
| 				echo "💡 Пример: make presets info PRESET=etcd-patroni"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			if [ ! -f "molecule/presets/$(PRESET).yml" ]; then \
 | ||
| 				echo "❌ Ошибка: Пресет '$(PRESET)' не найден!"; \
 | ||
| 				echo "💡 Доступные пресеты:"; \
 | ||
| 				make presets list; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			echo "📋 Информация о пресете: $(PRESET)"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "📄 Описание:"; \
 | ||
| 			grep -E "^#description:" "molecule/presets/$(PRESET).yml" | head -1 | sed 's/^#description: *//' || echo "Описание отсутствует"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "🏠 Хосты:"; \
 | ||
| 			grep -E "^- name:" "molecule/presets/$(PRESET).yml" | sed 's/^- name: /  - /' || echo "Хосты не найдены"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "🌐 Сеть:"; \
 | ||
| 			grep -E "^docker_network:" "molecule/presets/$(PRESET).yml" | sed 's/^docker_network: /  - /' || echo "Сеть не указана"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "🐳 Образы:"; \
 | ||
| 			grep -E "^- " "molecule/presets/$(PRESET).yml" | grep -E "family:" | sed 's/.*family: /  - /' || echo "Образы не найдены";; \
 | ||
| 		*) \
 | ||
| 			echo "🎯 Доступные команды:"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  📋 make presets list        - показать список всех preset'ов"; \
 | ||
| 			echo "     💡 Показывает: название, описание, количество хостов"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  📄 make presets info        - подробная информация о preset'е"; \
 | ||
| 			echo "     💡 Показывает: описание, хосты, сеть, образы"; \
 | ||
| 			echo "     💡 Требует: PRESET=имя_пресета"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "💡 Примеры:"; \
 | ||
| 			echo "  make presets list                           # показать все preset'ы"; \
 | ||
| 			echo "  make presets info PRESET=etcd-patroni       # информация о etcd-patroni";; \
 | ||
| 	esac
 | ||
| 
 | ||
| # =============================================================================
 | ||
| # КОМАНДЫ ДЛЯ РАБОТЫ С VAULT
 | ||
| # =============================================================================
 | ||
| vault:
 | ||
| 	@case "$(word 2, $(MAKECMDGOALS))" in \
 | ||
| 		init) \
 | ||
| 			echo "🔐 Инициализация vault..."; \
 | ||
| 			if [ ! -f "vault/.vault" ]; then \
 | ||
| 				echo "📝 Создание файла vault/.vault..."; \
 | ||
| 				read -sp "Введите пароль для vault: " PASSWORD; \
 | ||
| 				echo ""; \
 | ||
| 				echo "$$PASSWORD" > vault/.vault; \
 | ||
| 				chmod 600 vault/.vault; \
 | ||
| 				echo "✅ Файл vault/.vault создан"; \
 | ||
| 			else \
 | ||
| 				echo "✅ Файл vault/.vault уже существует"; \
 | ||
| 			fi;; \
 | ||
| 		edit) \
 | ||
| 			echo "🔐 Редактирование vars/main.yml выбранной роли..."; \
 | ||
| 			echo "📋 Доступные роли:"; \
 | ||
| 			ls -1 roles | grep -v "\\.yml$$" | sed 's/^/  - /'; \
 | ||
| 			read -p "Введите имя роли: " ROLE; \
 | ||
| 			FILE="roles/$$ROLE/vars/main.yml"; \
 | ||
| 			if [ ! -f "$$FILE" ]; then echo "❌ Файл $$FILE не найден"; exit 1; fi; \
 | ||
| 			$(MAKE) decrypt-role ROLE=$$ROLE; \
 | ||
| 		docker run --rm -it -v "$(PWD):/workspace" -w /workspace \
 | ||
| 			-e EDITOR=$(EDITOR) \
 | ||
| 			$(DOCKER_IMAGE) \
 | ||
| 			bash -c 'exec ${EDITOR:-nano} "$1"' _ "$$FILE"; \
 | ||
| 			$(MAKE) encrypt-role ROLE=$$ROLE;; \
 | ||
| 		show) \
 | ||
| 			echo "🔐 Просмотр vars/main.yml выбранной роли..."; \
 | ||
| 			echo "📋 Доступные роли:"; \
 | ||
| 			ls -1 roles | grep -v "\\.yml$$" | sed 's/^/  - /'; \
 | ||
| 			read -p "Введите имя роли: " ROLE; \
 | ||
| 			FILE="roles/$$ROLE/vars/main.yml"; \
 | ||
| 			if [ ! -f "$$FILE" ]; then echo "❌ Файл $$FILE не найден"; exit 1; fi; \
 | ||
| 		docker run --rm -v "$(PWD):/workspace" -w /workspace \
 | ||
| 			$(DOCKER_IMAGE) bash -c 'f="$1"; \
 | ||
| 			if grep -q "ANSIBLE_VAULT" "$${f}" 2>/dev/null; then \
 | ||
| 				ansible-vault view --vault-password-file vault/.vault "$${f}"; \
 | ||
| 			else \
 | ||
| 				cat "$${f}"; \
 | ||
| 			fi' _ "$$FILE";; \
 | ||
| 		encrypt) \
 | ||
| 			echo "🔐 Шифрование vars/main.yml выбранной роли..."; \
 | ||
| 			echo "📋 Доступные роли:"; \
 | ||
| 			ls -1 roles | grep -v "\\.yml$$" | sed 's/^/  - /'; \
 | ||
| 			read -p "Введите имя роли: " ROLE; \
 | ||
| 			$(MAKE) encrypt-role ROLE="$$ROLE";; \
 | ||
| 		decrypt) \
 | ||
| 			echo "🔓 Расшифровка vars/main.yml выбранной роли..."; \
 | ||
| 			echo "📋 Доступные роли:"; \
 | ||
| 			ls -1 roles | grep -v "\\.yml$$" | sed 's/^/  - /'; \
 | ||
| 			read -p "Введите имя роли: " ROLE; \
 | ||
| 			$(MAKE) decrypt-role ROLE="$$ROLE";; \
 | ||
| 		rekey) \
 | ||
| 			echo "🔑 Смена пароля для всех vars/main.yml..."; \
 | ||
| 			$(MAKE) rekey-all;; \
 | ||
| 		check) \
 | ||
| 			echo "🔍 Проверка vault файлов..."; \
 | ||
| 			if [ ! -d "vault" ]; then \
 | ||
| 				echo "❌ Директория vault не найдена"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			vault_files=$$(find vault -name "*.yml" -type f 2>/dev/null); \
 | ||
| 			if [ -z "$$vault_files" ]; then \
 | ||
| 				echo "⚠️  Vault файлы не найдены"; \
 | ||
| 				exit 0; \
 | ||
| 			fi; \
 | ||
| 			echo "📋 Найденные vault файлы:"; \
 | ||
| 			for file in $$vault_files; do \
 | ||
| 				echo "  📄 $$file"; \
 | ||
| 			done; \
 | ||
| 			echo ""; \
 | ||
| 			echo "🔍 Проверка структуры..."; \
 | ||
| 			for file in $$vault_files; do \
 | ||
| 				if grep -q "ANSIBLE_VAULT" "$$file"; then \
 | ||
| 					echo "  ✅ $$file - зашифрован"; \
 | ||
| 				else \
 | ||
| 					echo "  ⚠️  $$file - не зашифрован"; \
 | ||
| 				fi; \
 | ||
| 			done;; \
 | ||
| 		scan) \
 | ||
| 			echo "🔍 Поиск секретов в проекте..."; \
 | ||
| 			echo "📋 Поиск потенциальных секретов:"; \
 | ||
| 			find . -name "*.yml" -o -name "*.yaml" | grep -v ".git" | while read file; do \
 | ||
| 				if grep -qE "(password|secret|key|token|api_key)" "$$file" 2>/dev/null; then \
 | ||
| 					echo "  ⚠️  $$file - содержит потенциальные секреты"; \
 | ||
| 				fi; \
 | ||
| 			done; \
 | ||
| 			echo ""; \
 | ||
| 			echo "💡 Рекомендации:"; \
 | ||
| 			echo "  - Используйте ansible-vault для шифрования секретов"; \
 | ||
| 			echo "  - Не храните секреты в открытом виде"; \
 | ||
| 			echo "  - Регулярно проверяйте файлы на наличие секретов";; \
 | ||
| 		*) \
 | ||
| 			echo "🎯 Доступные команды:"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔑 make vault init       - инициализировать vault (создать vault/.vault)"; \
 | ||
| 			echo "     💡 Первая команда для настройки vault"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  ✏️  make vault edit       - редактировать существующие секреты"; \
 | ||
| 			echo "     💡 Открывает nano для изменения секретов"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  👁️  make vault show      - показать содержимое секретов"; \
 | ||
| 			echo "     💡 Расшифровывает и показывает содержимое"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔒 make vault encrypt   - зашифровать существующий файл"; \
 | ||
| 			echo "     💡 Шифрует незашифрованный файл"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔓 make vault decrypt   - расшифровать файл"; \
 | ||
| 			echo "     💡 Создает незашифрованную копию"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔑 make vault rekey     - сменить пароль шифрования"; \
 | ||
| 			echo "     💡 Изменяет пароль для всех ролей"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  ✅ make vault check     - проверить vault файлы"; \
 | ||
| 			echo "     💡 Проверяет структуру и статус файлов"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔍 make vault scan      - поиск потенциальных секретов"; \
 | ||
| 			echo "     💡 Сканирует проект на наличие незашифрованных секретов";; \
 | ||
| 	esac
 | ||
| 
 | ||
| # =============================================================================
 | ||
| # КОМАНДЫ ДЛЯ РАБОТЫ С GIT
 | ||
| # =============================================================================
 | ||
| git:
 | ||
| 	@case "$(word 2, $(MAKECMDGOALS))" in \
 | ||
| 		push) \
 | ||
| 			echo "📤 Отправка изменений в репозиторий..."; \
 | ||
| 			$(MAKE) encrypt-all; \
 | ||
| 			if [ -d "vault" ]; then \
 | ||
| 				VAULT_FILES=$$(find vault -maxdepth 1 -type f -name "*.yml" 2>/dev/null); \
 | ||
| 				if [ -n "$$VAULT_FILES" ]; then \
 | ||
| 					echo "🔐 Шифрование vault/*.yml..."; \
 | ||
| 					for f in $$VAULT_FILES; do \
 | ||
| 						if ! grep -q "ANSIBLE_VAULT" "$$f" 2>/dev/null; then \
 | ||
| 							docker run --rm -v "$(PWD):/workspace" -w /workspace $(DOCKER_IMAGE) \
 | ||
| 								ansible-vault encrypt --encrypt-vault-id default --vault-password-file vault/.vault "$$f" || true; \
 | ||
| 						fi; \
 | ||
| 					done; \
 | ||
| 				fi; \
 | ||
| 			fi; \
 | ||
| 			git add .; \
 | ||
| 			read -p "Введите сообщение коммита: " COMMIT_MSG; \
 | ||
| 			git commit -m "$$COMMIT_MSG"; \
 | ||
| 			git push origin main;; \
 | ||
| 		pull) \
 | ||
| 			echo "📥 Получение изменений из репозитория..."; \
 | ||
| 			git pull origin main;; \
 | ||
| 		new) \
 | ||
| 			echo "🌿 Создание новой ветки..."; \
 | ||
| 			read -p "Введите имя ветки: " BRANCH; \
 | ||
| 			git checkout -b "$$BRANCH"; \
 | ||
| 			echo "✅ Ветка '$$BRANCH' создана";; \
 | ||
| 		*) \
 | ||
| 			echo "🎯 Доступные команды:"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  📤 make git push  - отправить изменения в репозиторий"; \
 | ||
| 			echo "     💡 Выполняет: git add . && git commit && git push"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  📥 make git pull  - получить изменения из репозитория"; \
 | ||
| 			echo "     💡 Выполняет: git pull origin main"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🌿 make git new   - создать новую ветку"; \
 | ||
| 			echo "     💡 Интерактивно запрашивает имя ветки"; \
 | ||
| 			echo "     💡 Выполняет: git checkout -b имя_ветки";; \
 | ||
| 	esac
 | ||
| 
 | ||
| # =============================================================================
 | ||
| # КОМАНДЫ ДЛЯ РАБОТЫ С DOCKER
 | ||
| # =============================================================================
 | ||
| docker:
 | ||
| 	@case "$(word 2, $(MAKECMDGOALS))" in \
 | ||
| 		prepare) \
 | ||
| 			echo "🔧 Подготовка Docker образов для Docker Hub..."; \
 | ||
| 			echo "📋 Registry: $(DOCKER_REGISTRY)"; \
 | ||
| 			echo "📋 Version: $(DOCKER_VERSION)"; \
 | ||
| 			echo "📋 Images: $(DOCKER_IMAGES)"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "💡 Для работы с Docker Hub выполните:"; \
 | ||
| 			echo "  docker login - авторизация в Docker Hub"; \
 | ||
| 			echo "  make docker build - сборка образов"; \
 | ||
| 			echo "  make docker push - отправка в Docker Hub";; \
 | ||
| 		build) \
 | ||
| 			echo "🐳 Сборка Docker образов (multi-arch)..."; \
 | ||
| 			echo "📋 Платформы: $(DOCKER_PLATFORMS)"; \
 | ||
| 			echo "📋 Builder: $(DOCKER_BUILDX_BUILDER)"; \
 | ||
| 			echo "📋 Registry: $(DOCKER_REGISTRY)"; \
 | ||
| 			echo "📋 Version: $(DOCKER_VERSION)"; \
 | ||
| 			echo "📋 Images: $(DOCKER_IMAGES)"; \
 | ||
| 			echo "⚠️  ВНИМАНИЕ: RED OS и Astra Linux собираются только для AMD64"; \
 | ||
| 			echo ""; \
 | ||
| 			$(MAKE) docker setup-builder; \
 | ||
| 			for image in $(DOCKER_IMAGES); do \
 | ||
| 				echo "🔨 Сборка $(DOCKER_REGISTRY)/$$image:$(DOCKER_VERSION)"; \
 | ||
| 				$(MAKE) docker-build-image IMAGE=$$image; \
 | ||
| 			done; \
 | ||
| 			echo "✅ Образы собраны";; \
 | ||
| 		rebuild) \
 | ||
| 			echo "🔄 Полная пересборка Docker образов (multi-arch)..."; \
 | ||
| 			echo "📋 Платформы: $(DOCKER_PLATFORMS)"; \
 | ||
| 			echo "📋 Builder: $(DOCKER_BUILDX_BUILDER)"; \
 | ||
| 			echo "📋 Registry: $(DOCKER_REGISTRY)"; \
 | ||
| 			echo "📋 Version: $(DOCKER_VERSION)"; \
 | ||
| 			echo "📋 Images: $(DOCKER_IMAGES)"; \
 | ||
| 			echo "⚠️  ВНИМАНИЕ: RED OS и Astra Linux собираются только для AMD64"; \
 | ||
| 			echo "🧹 Очистка кеша и старых образов..."; \
 | ||
| 			echo ""; \
 | ||
| 			$(MAKE) docker clean; \
 | ||
| 			$(MAKE) docker clean-builder; \
 | ||
| 			$(MAKE) docker setup-builder; \
 | ||
| 			for image in $(DOCKER_IMAGES); do \
 | ||
| 				echo "🔨 Пересборка $(DOCKER_REGISTRY):$$image-$(DOCKER_VERSION)"; \
 | ||
| 				$(MAKE) docker-build-image IMAGE=$$image; \
 | ||
| 			done; \
 | ||
| 			echo "✅ Образы пересобраны с нуля";; \
 | ||
| 		push) \
 | ||
| 			echo "📤 Отправка Docker образов в Docker Hub..."; \
 | ||
| 			for image in $(DOCKER_IMAGES); do \
 | ||
| 				echo "📤 Отправка $(DOCKER_REGISTRY):$$image-$(DOCKER_VERSION)"; \
 | ||
| 				docker push $(DOCKER_REGISTRY):$$image-$(DOCKER_VERSION); \
 | ||
| 			done; \
 | ||
| 			echo "✅ Образы отправлены в Docker Hub";; \
 | ||
| 		pull) \
 | ||
| 			echo "📥 Загрузка Docker образов из Docker Hub..."; \
 | ||
| 			echo "⚠️  ВНИМАНИЕ: astra-linux и redos собираются только для AMD64"; \
 | ||
| 			for image in $(DOCKER_IMAGES); do \
 | ||
| 				echo "📥 Загрузка $(DOCKER_REGISTRY):$$image-$(DOCKER_VERSION)"; \
 | ||
| 				if [ "$$image" = "astra-linux" ] || [ "$$image" = "redos" ]; then \
 | ||
| 					docker pull --platform linux/amd64 $(DOCKER_REGISTRY):$$image-$(DOCKER_VERSION) || echo "⚠️  Образ $$image не найден в Docker Hub"; \
 | ||
| 				else \
 | ||
| 					docker pull $(DOCKER_REGISTRY):$$image-$(DOCKER_VERSION) || echo "⚠️  Образ $$image не найден в Docker Hub"; \
 | ||
| 				fi; \
 | ||
| 			done; \
 | ||
| 			echo "✅ Загрузка завершена";; \
 | ||
| 		clean) \
 | ||
| 			echo "🧹 Очистка Docker образов и builds..."; \
 | ||
| 			for image in $(DOCKER_IMAGES); do \
 | ||
| 				echo "🗑️  Удаление $(DOCKER_REGISTRY):$$image-$(DOCKER_VERSION)"; \
 | ||
| 				docker rmi $(DOCKER_REGISTRY):$$image-$(DOCKER_VERSION) 2>/dev/null || true; \
 | ||
| 			done; \
 | ||
| 			echo "🗑️  Удаление кеша builds для наших образов..."; \
 | ||
| 			docker buildx prune --filter type=exec.cachemount --filter type=source.local --filter type=source.git.checkout --force 2>/dev/null || true; \
 | ||
| 			echo "✅ Образы и кеш builds очищены";; \
 | ||
| 		info) \
 | ||
| 			echo "📊 Информация об образах..."; \
 | ||
| 			for image in $(DOCKER_IMAGES); do \
 | ||
| 				if docker images | grep -q "$(DOCKER_REGISTRY):$$image"; then \
 | ||
| 					echo "📦 $(DOCKER_REGISTRY):$$image-$(DOCKER_VERSION)"; \
 | ||
| 					docker images | grep "$(DOCKER_REGISTRY):$$image" | head -1; \
 | ||
| 				fi; \
 | ||
| 			done;; \
 | ||
| 		update) \
 | ||
| 			echo "🔄 Обновление всех образов..."; \
 | ||
| 			$(MAKE) docker pull; \
 | ||
| 			$(MAKE) docker build; \
 | ||
| 			$(MAKE) docker push; \
 | ||
| 			echo "✅ Все образы обновлены";; \
 | ||
| 		purge) \
 | ||
| 			echo "🧹 Полная очистка Docker..."; \
 | ||
| 			echo "⚠️  ВНИМАНИЕ: Это удалит ВСЕ Docker данные!"; \
 | ||
| 			echo ""; \
 | ||
| 			read -p "Продолжить? (y/N): " confirm; \
 | ||
| 			if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
 | ||
| 				echo "🛑 Остановка всех контейнеров..."; \
 | ||
| 				docker stop $$(docker ps -aq) 2>/dev/null || true; \
 | ||
| 				echo "🗑️  Удаление всех контейнеров..."; \
 | ||
| 				docker rm $$(docker ps -aq) 2>/dev/null || true; \
 | ||
| 				echo "🗑️  Удаление всех образов..."; \
 | ||
| 				docker rmi $$(docker images -aq) 2>/dev/null || true; \
 | ||
| 				echo "🗑️  Удаление всех томов..."; \
 | ||
| 				docker volume rm $$(docker volume ls -q) 2>/dev/null || true; \
 | ||
| 				echo "🗑️  Удаление всех сетей..."; \
 | ||
| 				docker network rm $$(docker network ls -q) 2>/dev/null || true; \
 | ||
| 				echo "🧹 Очистка системы..."; \
 | ||
| 				docker system prune -af --volumes; \
 | ||
| 				echo "✅ Docker полностью очищен"; \
 | ||
| 			else \
 | ||
| 				echo "❌ Очистка отменена"; \
 | ||
| 			fi;; \
 | ||
| 		clean-builder) \
 | ||
| 			echo "🧹 Очистка multi-arch builder..."; \
 | ||
| 			$(MAKE) docker-reset-builder;; \
 | ||
| 		build-image) \
 | ||
| 			if [ -z "$(IMAGE)" ]; then \
 | ||
| 				echo "❌ Ошибка: Укажите IMAGE=имя_образа"; \
 | ||
| 				echo "💡 Пример: make docker build-image IMAGE=centos"; \
 | ||
| 				echo "💡 Доступные образы: $(DOCKER_IMAGES)"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			if [ ! -d "dockerfiles/$(IMAGE)" ]; then \
 | ||
| 				echo "❌ Ошибка: Директория dockerfiles/$(IMAGE) не найдена!"; \
 | ||
| 				echo "💡 Доступные образы:"; \
 | ||
| 				for img in $(DOCKER_IMAGES); do \
 | ||
| 					if [ -d "dockerfiles/$$img" ]; then \
 | ||
| 						echo "  - $$img"; \
 | ||
| 					fi; \
 | ||
| 				done; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			echo "🔨 Сборка отдельного образа: $(IMAGE)"; \
 | ||
| 			echo "📋 Платформы: $(DOCKER_PLATFORMS)"; \
 | ||
| 			echo "📋 Builder: $(DOCKER_BUILDX_BUILDER)"; \
 | ||
| 			echo "📋 Registry: $(DOCKER_REGISTRY)"; \
 | ||
| 			echo ""; \
 | ||
| 			$(MAKE) docker setup-builder; \
 | ||
| 			$(MAKE) docker-build-image IMAGE=$(IMAGE); \
 | ||
| 			echo "✅ Образ $(IMAGE) собран";; \
 | ||
| 		build-astra-arm64) \
 | ||
| 			echo "🔨 Сборка Astra Linux для ARM64 (совместимый образ)..."; \
 | ||
| 			echo "📋 Платформы: linux/amd64,linux/arm64"; \
 | ||
| 			echo "📋 Builder: $(DOCKER_BUILDX_BUILDER)"; \
 | ||
| 			echo "📋 Registry: $(DOCKER_REGISTRY)"; \
 | ||
| 			echo "⚠️  ВНИМАНИЕ: Используется совместимый образ на базе Debian"; \
 | ||
| 			echo ""; \
 | ||
| 			$(MAKE) docker setup-builder; \
 | ||
| 			cd dockerfiles/astra-linux && \
 | ||
| 			docker buildx build \
 | ||
| 				--platform linux/amd64,linux/arm64 \
 | ||
| 				--tag $(DOCKER_REGISTRY):astra-linux-arm64-latest \
 | ||
| 				--tag $(DOCKER_REGISTRY):astra-linux-latest \
 | ||
| 				--file Dockerfile.arm64 \
 | ||
| 				--push \
 | ||
| 				.; \
 | ||
| 			echo "✅ Astra Linux для ARM64 собран и отправлен";; \
 | ||
| 		build-redos-arm64) \
 | ||
| 			echo "🔨 Сборка RedOS для ARM64 (совместимый образ)..."; \
 | ||
| 			echo "📋 Платформы: linux/amd64,linux/arm64"; \
 | ||
| 			echo "📋 Builder: $(DOCKER_BUILDX_BUILDER)"; \
 | ||
| 			echo "📋 Registry: $(DOCKER_REGISTRY)"; \
 | ||
| 			echo "⚠️  ВНИМАНИЕ: Используется совместимый образ на базе CentOS Stream 9"; \
 | ||
| 			echo ""; \
 | ||
| 			$(MAKE) docker setup-builder; \
 | ||
| 			cd dockerfiles/redos && \
 | ||
| 			docker buildx build \
 | ||
| 				--platform linux/amd64,linux/arm64 \
 | ||
| 				--tag $(DOCKER_REGISTRY):redos-arm64-latest \
 | ||
| 				--tag $(DOCKER_REGISTRY):redos-latest \
 | ||
| 				--file Dockerfile.arm64 \
 | ||
| 				--push \
 | ||
| 				.; \
 | ||
| 			echo "✅ RedOS для ARM64 собран и отправлен";; \
 | ||
| 		setup-builder) \
 | ||
| 			echo "🔧 Настройка multi-arch builder в контейнере..."; \
 | ||
| 			if $(MAKE) docker-check-builder >/dev/null 2>&1; then \
 | ||
| 				echo "✅ Builder $(DOCKER_BUILDX_BUILDER) уже существует и готов"; \
 | ||
| 				docker buildx use $(DOCKER_BUILDX_BUILDER); \
 | ||
| 			else \
 | ||
| 				echo "📦 Создание builder $(DOCKER_BUILDX_BUILDER)..."; \
 | ||
| 				$(MAKE) docker-create-builder; \
 | ||
| 			fi; \
 | ||
| 			echo "🔍 Финальная проверка builder..."; \
 | ||
| 			$(MAKE) docker-check-builder;; \
 | ||
| 		diagnose) \
 | ||
| 			echo "🔍 Диагностика buildx проблем..."; \
 | ||
| 			$(MAKE) docker-diagnose-buildx;; \
 | ||
| 		reset-builder) \
 | ||
| 			echo "🔄 Сброс buildx builder..."; \
 | ||
| 			$(MAKE) docker-reset-builder;; \
 | ||
| 		*) \
 | ||
| 			echo "🎯 Доступные команды:"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔧 make docker prepare   - подготовка к работе с Docker Hub"; \
 | ||
| 			echo "     💡 Показывает: registry, version, список образов"; \
 | ||
| 			echo "     💡 Рекомендует: docker login перед работой"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🐳 make docker build     - собрать все Docker образы (multi-arch)"; \
 | ||
| 			echo "     💡 Собирает: ansible-controller, alt9, alt10, astra-linux, redos"; \
 | ||
| 			echo "     💡 Собирает: rhel, centos, alma, rocky, ubuntu, debian"; \
 | ||
| 			echo "     💡 Платформы: $(DOCKER_PLATFORMS)"; \
 | ||
| 			echo "     💡 Ограничения: RED OS и Astra Linux только AMD64"; \
 | ||
| 			echo "     💡 Тегирует: inecs/образ:<tag> (автоматически извлекает теги)"; \
 | ||
| 			echo "     💡 Отправляет: автоматически в Docker Hub"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔨 make docker build-image IMAGE=<имя> - собрать отдельный образ"; \
 | ||
| 			echo "     💡 Пример: make docker build-image IMAGE=centos"; \
 | ||
| 			echo "     💡 Собирает: только указанный образ (multi-arch)"; \
 | ||
| 			echo "     💡 Доступные образы: $(DOCKER_IMAGES)"; \
 | ||
| 			echo "     💡 Платформы: $(DOCKER_PLATFORMS)"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔄 make docker rebuild   - полная пересборка с очисткой кеша"; \
 | ||
| 			echo "     💡 Очищает: все локальные образы и кеш"; \
 | ||
| 			echo "     💡 Пересобирает: все образы с нуля"; \
 | ||
| 			echo "     💡 Полезно: при проблемах с кешем или зависимостями"; \
 | ||
| 			echo "     💡 Выполняет: clean + clean-builder + setup-builder + build"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  📤 make docker push      - отправить образы в Docker Hub"; \
 | ||
| 			echo "     💡 Требует: docker login"; \
 | ||
| 			echo "     💡 Отправляет: все образы в registry inecs"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  📥 make docker pull      - загрузить образы из Docker Hub"; \
 | ||
| 			echo "     💡 Загружает: все образы из registry inecs"; \
 | ||
| 			echo "     💡 Пропускает: отсутствующие образы"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🧹 make docker clean     - удалить локальные образы и кеш builds"; \
 | ||
| 			echo "     💡 Удаляет: все образы inecs/ansible-lab:*"; \
 | ||
| 			echo "     💡 Очищает: кеш builds (exec.cachemount, source.local, git.checkout)"; \
 | ||
| 			echo "     💡 Сохраняет: другие builds в системе"; \
 | ||
| 			echo "     💡 Безопасно: игнорирует ошибки"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🧹 make docker clean-builder - очистка multi-arch builder"; \
 | ||
| 			echo "     💡 Удаляет: builder контейнер и buildkit контейнеры"; \
 | ||
| 			echo "     💡 Полезно: при проблемах со сборкой";; \
 | ||
| 		setup-builder) \
 | ||
| 			echo "🔧 Настройка multi-arch builder в контейнере..."; \
 | ||
| 			if $(MAKE) docker-check-builder >/dev/null 2>&1; then \
 | ||
| 				echo "✅ Builder $(DOCKER_BUILDX_BUILDER) уже существует и готов"; \
 | ||
| 				docker buildx use $(DOCKER_BUILDX_BUILDER); \
 | ||
| 			else \
 | ||
| 				echo "📦 Создание builder $(DOCKER_BUILDX_BUILDER)..."; \
 | ||
| 				$(MAKE) docker-create-builder; \
 | ||
| 			fi; \
 | ||
| 			echo "🔍 Финальная проверка builder..."; \
 | ||
| 			$(MAKE) docker-check-builder;; \
 | ||
| 		diagnose) \
 | ||
| 			echo "🔍 Диагностика buildx проблем..."; \
 | ||
| 			$(MAKE) docker-diagnose-buildx;; \
 | ||
| 		reset-builder) \
 | ||
| 			echo "🔄 Сброс buildx builder..."; \
 | ||
| 			$(MAKE) docker-reset-builder;; \
 | ||
| 		*) \
 | ||
| 			echo "🎯 Доступные команды:"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔧 make docker prepare   - подготовка к работе с Docker Hub"; \
 | ||
| 			echo "     💡 Показывает: registry, version, список образов"; \
 | ||
| 			echo "     💡 Рекомендует: docker login перед работой"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🐳 make docker build     - собрать все Docker образы (multi-arch)"; \
 | ||
| 			echo "     💡 Собирает: ansible-controller, alt9, alt10, astra-linux, redos"; \
 | ||
| 			echo "     💡 Собирает: rhel, centos, alma, rocky, ubuntu, debian"; \
 | ||
| 			echo "     💡 Платформы: $(DOCKER_PLATFORMS)"; \
 | ||
| 			echo "     💡 Ограничения: RED OS и Astra Linux только AMD64"; \
 | ||
| 			echo "     💡 Тегирует: inecs/образ:<tag> (автоматически извлекает теги)"; \
 | ||
| 			echo "     💡 Отправляет: автоматически в Docker Hub"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔨 make docker build-image IMAGE=<имя> - собрать отдельный образ"; \
 | ||
| 			echo "     💡 Пример: make docker build-image IMAGE=centos"; \
 | ||
| 			echo "     💡 Собирает: только указанный образ (multi-arch)"; \
 | ||
| 			echo "     💡 Доступные образы: $(DOCKER_IMAGES)"; \
 | ||
| 			echo "     💡 Платформы: $(DOCKER_PLATFORMS)"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔄 make docker rebuild   - полная пересборка с очисткой кеша"; \
 | ||
| 			echo "     💡 Очищает: все локальные образы и кеш"; \
 | ||
| 			echo "     💡 Пересобирает: все образы с нуля"; \
 | ||
| 			echo "     💡 Полезно: при проблемах с кешем или зависимостями"; \
 | ||
| 			echo "     💡 Выполняет: clean + clean-builder + setup-builder + build"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  📤 make docker push      - отправить образы в Docker Hub"; \
 | ||
| 			echo "     💡 Требует: docker login"; \
 | ||
| 			echo "     💡 Отправляет: все образы в registry inecs"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  📥 make docker pull      - загрузить образы из Docker Hub"; \
 | ||
| 			echo "     💡 Загружает: все образы из registry inecs"; \
 | ||
| 			echo "     💡 Пропускает: отсутствующие образы"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🧹 make docker clean     - удалить локальные образы и кеш builds"; \
 | ||
| 			echo "     💡 Удаляет: все образы inecs/ansible-lab:*"; \
 | ||
| 			echo "     💡 Очищает: кеш builds (exec.cachemount, source.local, git.checkout)"; \
 | ||
| 			echo "     💡 Сохраняет: другие builds в системе"; \
 | ||
| 			echo "     💡 Безопасно: игнорирует ошибки"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🧹 make docker clean-builder - очистка multi-arch builder"; \
 | ||
| 			echo "     💡 Удаляет: builder контейнер и buildkit контейнеры"; \
 | ||
| 			echo "     💡 Полезно: при проблемах со сборкой"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔧 make docker setup-builder - настройка multi-arch builder"; \
 | ||
| 			echo "     💡 Создает: builder в контейнере (не в системе)"; \
 | ||
| 			echo "     💡 Поддерживает: amd64 и arm64 архитектуры"; \
 | ||
| 			echo "     💡 Безопасно: использует inspect вместо buildx ls"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔍 make docker diagnose - диагностика buildx проблем"; \
 | ||
| 			echo "     💡 Проверяет: версии, контексты, builder, registry"; \
 | ||
| 			echo "     💡 Показывает: рекомендации по устранению проблем"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔄 make docker reset-builder - сброс buildx builder"; \
 | ||
| 			echo "     💡 Удаляет: старый builder и buildkit контейнеры"; \
 | ||
| 			echo "     💡 Создает: новый builder с предварительной загрузкой образа"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  📊 make docker info      - информация о собранных образах"; \
 | ||
| 			echo "     💡 Показывает: размер, дата создания, теги"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔄 make docker update    - обновить все образы"; \
 | ||
| 			echo "     💡 Выполняет: pull + build + push"; \
 | ||
| 			echo "     💡 Полный цикл обновления"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  💥 make docker purge    - ПОЛНАЯ очистка Docker"; \
 | ||
| 			echo "     ⚠️  УДАЛЯЕТ: все контейнеры, образы, тома, сети"; \
 | ||
| 			echo "     ⚠️  ОСТАНОВИТ: все запущенные контейнеры"; \
 | ||
| 			echo "     ⚠️  ТРЕБУЕТ: подтверждение пользователя"; \
 | ||
| 			echo "";; \
 | ||
| 	esac
 | ||
| 
 | ||
| # =============================================================================
 | ||
| # ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ДЛЯ DOCKER
 | ||
| # =============================================================================
 | ||
| 
 | ||
| # Надежная проверка существования buildx builder без использования buildx ls
 | ||
| # Использует docker buildx inspect вместо buildx ls для избежания зависаний
 | ||
| .PHONY: docker-check-builder
 | ||
| docker-check-builder:
 | ||
| 	@echo "🔍 Проверка buildx builder $(DOCKER_BUILDX_BUILDER)..."
 | ||
| 	@if docker buildx inspect $(DOCKER_BUILDX_BUILDER) >/dev/null 2>&1; then \
 | ||
| 		echo "✅ Builder $(DOCKER_BUILDX_BUILDER) существует и готов"; \
 | ||
| 		exit 0; \
 | ||
| 	else \
 | ||
| 		echo "❌ Builder $(DOCKER_BUILDX_BUILDER) не найден"; \
 | ||
| 		exit 1; \
 | ||
| 	fi
 | ||
| 
 | ||
| # Безопасное создание buildx builder с предварительной очисткой
 | ||
| .PHONY: docker-create-builder
 | ||
| docker-create-builder:
 | ||
| 	@echo "🔧 Создание buildx builder $(DOCKER_BUILDX_BUILDER)..."
 | ||
| 	@echo "📦 Предварительная загрузка образа buildkit..."
 | ||
| 	@docker pull moby/buildkit:buildx-stable-1 || echo "⚠️  Не удалось загрузить moby/buildkit:buildx-stable-1, будет использован авто-пулл"
 | ||
| 	@echo "🗑️  Удаление существующего builder (если есть)..."
 | ||
| 	@docker buildx rm -f $(DOCKER_BUILDX_BUILDER) 2>/dev/null || true
 | ||
| 	@echo "📦 Создание нового builder..."
 | ||
| 	@docker buildx create \
 | ||
| 		--name $(DOCKER_BUILDX_BUILDER) \
 | ||
| 		--driver docker-container \
 | ||
| 		--driver-opt image=moby/buildkit:buildx-stable-1 \
 | ||
| 		--use || exit 1
 | ||
| 	@echo "⏳ Ожидание запуска buildkit контейнера..."
 | ||
| 	@sleep 3
 | ||
| 	@echo "🔍 Проверка готовности builder..."
 | ||
| 	@docker buildx inspect --builder $(DOCKER_BUILDX_BUILDER) --bootstrap >/dev/null || exit 1
 | ||
| 	@echo "✅ Builder $(DOCKER_BUILDX_BUILDER) создан и готов к работе"
 | ||
| 
 | ||
| # Диагностика проблем с buildx
 | ||
| .PHONY: docker-diagnose-buildx
 | ||
| docker-diagnose-buildx:
 | ||
| 	@echo "🔍 ДИАГНОСТИКА BUILDX ПРОБЛЕМ"
 | ||
| 	@echo "=========================================="
 | ||
| 	@echo ""
 | ||
| 	@echo "📊 1. Версии Docker и Buildx:"
 | ||
| 	@docker version --format "Docker: {{.Server.Version}}" 2>/dev/null || echo "❌ Docker недоступен"
 | ||
| 	@docker buildx version 2>/dev/null || echo "❌ Buildx недоступен"
 | ||
| 	@echo ""
 | ||
| 	@echo "📋 2. Docker контексты (поиск мертвых tcp://):"
 | ||
| 	@docker context ls 2>/dev/null || echo "❌ Не удалось получить список контекстов"
 | ||
| 	@echo ""
 | ||
| 	@echo "🔍 3. Проверка builder $(DOCKER_BUILDX_BUILDER):"
 | ||
| 	@if docker buildx inspect $(DOCKER_BUILDX_BUILDER) >/dev/null 2>&1; then \
 | ||
| 		echo "✅ Builder $(DOCKER_BUILDX_BUILDER) существует"; \
 | ||
| 		docker buildx inspect $(DOCKER_BUILDX_BUILDER) --bootstrap >/dev/null 2>&1 && echo "✅ Builder готов" || echo "❌ Builder не готов"; \
 | ||
| 	else \
 | ||
| 		echo "❌ Builder $(DOCKER_BUILDX_BUILDER) не найден"; \
 | ||
| 	fi
 | ||
| 	@echo ""
 | ||
| 	@echo "🐳 4. Buildkit контейнеры:"
 | ||
| 	@docker ps -a --filter "name=buildx_buildkit" --format "table {{.Names}}\t{{.Status}}\t{{.CreatedAt}}" 2>/dev/null || echo "❌ Не удалось получить список контейнеров"
 | ||
| 	@echo ""
 | ||
| 	@echo "🌐 5. Проверка доступа к registry:"
 | ||
| 	@echo "📥 Тест загрузки moby/buildkit:buildx-stable-1..."
 | ||
| 	@timeout 30 docker pull moby/buildkit:buildx-stable-1 >/dev/null 2>&1 && echo "✅ Доступ к registry OK" || echo "❌ Проблемы с доступом к registry"
 | ||
| 	@echo ""
 | ||
| 	@echo "🔧 6. Docker socket доступ:"
 | ||
| 	@if [ -S /var/run/docker.sock ]; then \
 | ||
| 		echo "✅ Docker socket доступен: /var/run/docker.sock"; \
 | ||
| 		ls -la /var/run/docker.sock; \
 | ||
| 	else \
 | ||
| 		echo "❌ Docker socket недоступен: /var/run/docker.sock"; \
 | ||
| 	fi
 | ||
| 	@echo ""
 | ||
| 	@echo "💡 РЕКОМЕНДАЦИИ:"
 | ||
| 	@echo "  - Если buildx ls зависает: удалите мертвые контексты (docker context rm <name>)"
 | ||
| 	@echo "  - Если pull зависает: настройте прокси или используйте mirror registry"
 | ||
| 	@echo "  - Если builder не создается: проверьте права доступа к Docker socket"
 | ||
| 	@echo "  - Для полной очистки: make docker clean-builder && make docker-create-builder"
 | ||
| 
 | ||
| # Быстрая очистка и пересоздание builder
 | ||
| .PHONY: docker-reset-builder
 | ||
| docker-reset-builder:
 | ||
| 	@echo "🔄 Сброс buildx builder..."
 | ||
| 	@echo "🗑️  Удаление builder $(DOCKER_BUILDX_BUILDER)..."
 | ||
| 	@docker buildx rm -f $(DOCKER_BUILDX_BUILDER) 2>/dev/null || true
 | ||
| 	@echo "🧹 Очистка buildkit контейнеров..."
 | ||
| 	@docker ps -a --filter "name=buildx_buildkit" --format "{{.Names}}" | xargs -r docker rm -f 2>/dev/null || true
 | ||
| 	@echo "📦 Создание нового builder..."
 | ||
| 	@$(MAKE) docker-create-builder
 | ||
| 	@echo "✅ Builder сброшен и готов к работе"
 | ||
| 
 | ||
| 
 | ||
| # Извлечение тега из базового образа
 | ||
| docker-get-base-tag:
 | ||
| 	@if [ -z "$(IMAGE)" ]; then \
 | ||
| 		echo "❌ Ошибка: IMAGE не указан"; \
 | ||
| 		exit 1; \
 | ||
| 	fi; \
 | ||
| 	case "$(IMAGE)" in \
 | ||
| 		alt9) \
 | ||
| 			BASE_IMAGE="alt:p9"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		alt10) \
 | ||
| 			BASE_IMAGE="alt:p10"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		astra-linux) \
 | ||
| 			BASE_IMAGE="registry.astralinux.ru/library/astra/ubi17:1.7.6.uu2"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		redos) \
 | ||
| 			BASE_IMAGE="registry.red-soft.ru/ubi7/ubi:latest"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG="latest";; \
 | ||
| 		astra-linux-arm64) \
 | ||
| 			BASE_IMAGE="debian:bookworm-slim"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG="latest";; \
 | ||
| 		redos-arm64) \
 | ||
| 			BASE_IMAGE="quay.io/centos/centos:stream9"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG="latest";; \
 | ||
| 		rhel) \
 | ||
| 			BASE_IMAGE="registry.access.redhat.com/ubi8/ubi"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		centos7) \
 | ||
| 			BASE_IMAGE="centos:7"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		centos8) \
 | ||
| 			BASE_IMAGE="quay.io/centos/centos:8"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		centos9) \
 | ||
| 			BASE_IMAGE="quay.io/centos/centos:stream9"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		alma) \
 | ||
| 			BASE_IMAGE="almalinux:8"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		rocky) \
 | ||
| 			BASE_IMAGE="rockylinux:8"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		ubuntu20) \
 | ||
| 			BASE_IMAGE="ubuntu:20.04"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		ubuntu22) \
 | ||
| 			BASE_IMAGE="ubuntu:22.04"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		ubuntu24) \
 | ||
| 			BASE_IMAGE="ubuntu:24.04"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		debian9) \
 | ||
| 			BASE_IMAGE="debian:9"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		debian10) \
 | ||
| 			BASE_IMAGE="debian:10"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		debian11) \
 | ||
| 			BASE_IMAGE="debian:11"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		debian12) \
 | ||
| 			BASE_IMAGE="debian:bookworm"; \
 | ||
| 			echo "📦 Загрузка базового образа $$BASE_IMAGE..." >&2; \
 | ||
| 			docker pull $$BASE_IMAGE >/dev/null 2>&1 || echo "⚠️  Не удалось загрузить $$BASE_IMAGE" >&2; \
 | ||
| 			TAG=$$(docker inspect --format='{{.RepoTags}}' $$BASE_IMAGE 2>/dev/null | tr -d '[]' | cut -d',' -f1 | cut -d':' -f2 | tr -d ' ' || echo "latest");; \
 | ||
| 		ansible-controller) \
 | ||
| 			TAG="latest";; \
 | ||
| 			k8s) \
 | ||
| 		TAG="latest";; \
 | ||
| 	k8s-portforward) \
 | ||
| 		TAG="latest";; \
 | ||
| 	*) \
 | ||
| 		echo "❌ Неизвестный образ: $(IMAGE)"; \
 | ||
| 		exit 1;; \
 | ||
| esac; \
 | ||
| echo "$$TAG"
 | ||
| 
 | ||
| # Сборка одного образа с multi-arch
 | ||
| docker-build-image:
 | ||
| 	@if [ -z "$(IMAGE)" ]; then \
 | ||
| 		echo "❌ Ошибка: IMAGE не указан"; \
 | ||
| 		exit 1; \
 | ||
| 	fi; \
 | ||
| 	TAG=$$($(MAKE) docker-get-base-tag IMAGE=$(IMAGE)); \
 | ||
| 	if [ "$(IMAGE)" = "redos" ] || [ "$(IMAGE)" = "astra-linux" ]; then \
 | ||
| 		PLATFORMS="linux/amd64"; \
 | ||
| 		echo ""; \
 | ||
| 		echo "=========================================="; \
 | ||
| 		echo "🔨 СБОРКА ОБРАЗА: $(DOCKER_REGISTRY):$(IMAGE)-$$TAG"; \
 | ||
| 		echo "📋 Платформы: $$PLATFORMS (только AMD64)"; \
 | ||
| 		echo "📋 Тег: $$TAG"; \
 | ||
| 		echo "📋 Registry: $(DOCKER_REGISTRY)"; \
 | ||
| 		echo "⚠️  ВНИМАНИЕ: Базовый образ поддерживает только AMD64"; \
 | ||
| 		echo "=========================================="; \
 | ||
| 	elif [ "$(IMAGE)" = "astra-linux-arm64" ] || [ "$(IMAGE)" = "redos-arm64" ]; then \
 | ||
| 		PLATFORMS="linux/amd64,linux/arm64"; \
 | ||
| 		echo ""; \
 | ||
| 		echo "=========================================="; \
 | ||
| 		echo "🔨 СБОРКА ОБРАЗА: $(DOCKER_REGISTRY):$(IMAGE)-$$TAG"; \
 | ||
| 		echo "📋 Платформы: $$PLATFORMS (AMD64 + ARM64)"; \
 | ||
| 		echo "📋 Тег: $$TAG"; \
 | ||
| 		echo "📋 Registry: $(DOCKER_REGISTRY)"; \
 | ||
| 		echo "⚠️  ВНИМАНИЕ: Совместимый образ с поддержкой ARM64"; \
 | ||
| 		echo "=========================================="; \
 | ||
| 	else \
 | ||
| 		PLATFORMS="$(DOCKER_PLATFORMS)"; \
 | ||
| 		echo ""; \
 | ||
| 		echo "=========================================="; \
 | ||
| 		echo "🔨 СБОРКА ОБРАЗА: $(DOCKER_REGISTRY):$(IMAGE)-$$TAG"; \
 | ||
| 		echo "📋 Платформы: $$PLATFORMS"; \
 | ||
| 		echo "📋 Тег: $$TAG"; \
 | ||
| 		echo "📋 Registry: $(DOCKER_REGISTRY)"; \
 | ||
| 		echo "=========================================="; \
 | ||
| 	fi; \
 | ||
| 	echo ""; \
 | ||
| 	if [ "$(IMAGE)" = "astra-linux-arm64" ]; then \
 | ||
| 		cd dockerfiles/astra-linux && \
 | ||
| 		docker buildx build \
 | ||
| 			--platform $$PLATFORMS \
 | ||
| 			--tag $(DOCKER_REGISTRY):$(IMAGE)-$$TAG \
 | ||
| 			--tag $(DOCKER_REGISTRY):$(IMAGE)-latest \
 | ||
| 			--file Dockerfile.arm64 \
 | ||
| 			--push \
 | ||
| 			.; \
 | ||
| 	elif [ "$(IMAGE)" = "redos-arm64" ]; then \
 | ||
| 		cd dockerfiles/redos && \
 | ||
| 		docker buildx build \
 | ||
| 			--platform $$PLATFORMS \
 | ||
| 			--tag $(DOCKER_REGISTRY):$(IMAGE)-$$TAG \
 | ||
| 			--tag $(DOCKER_REGISTRY):$(IMAGE)-latest \
 | ||
| 			--file Dockerfile.arm64 \
 | ||
| 			--push \
 | ||
| 			.; \
 | ||
| 	else \
 | ||
| 		cd dockerfiles/$(IMAGE) && \
 | ||
| 		docker buildx build \
 | ||
| 			--platform $$PLATFORMS \
 | ||
| 			--tag $(DOCKER_REGISTRY):$(IMAGE)-$$TAG \
 | ||
| 			--tag $(DOCKER_REGISTRY):$(IMAGE)-latest \
 | ||
| 			--push \
 | ||
| 			.; \
 | ||
| 	fi; \
 | ||
| 	echo ""; \
 | ||
| 	echo "✅ УСПЕШНО: $(DOCKER_REGISTRY):$(IMAGE)-$$TAG собран и отправлен"; \
 | ||
| 	echo "=========================================="
 | ||
| 
 | ||
| 
 | ||
| # =============================================================================
 | ||
| # КОМАНДЫ ДЛЯ РАБОТЫ С ANSIBLE-CONTROLLER
 | ||
| # =============================================================================
 | ||
| controller:
 | ||
| 	@case "$(word 2, $(MAKECMDGOALS))" in \
 | ||
| 		build) \
 | ||
| 			echo "🔨 Сборка ansible-controller (multi-arch)..."; \
 | ||
| 			echo "📋 Платформы: $(DOCKER_PLATFORMS)"; \
 | ||
| 			$(MAKE) docker setup-builder; \
 | ||
| 			cd dockerfiles/ansible-controller && \
 | ||
| 			docker buildx build \
 | ||
| 				--platform $(DOCKER_PLATFORMS) \
 | ||
| 				--tag $(DOCKER_REGISTRY):ansible-controller-$(DOCKER_VERSION) \
 | ||
| 				--tag $(DOCKER_REGISTRY):ansible-controller-latest \
 | ||
| 				--push \
 | ||
| 				.; \
 | ||
| 			echo "✅ ansible-controller собран и отправлен";; \
 | ||
| 		rebuild) \
 | ||
| 			echo "🔄 Пересборка ansible-controller с исправлениями..."; \
 | ||
| 			echo "📋 Платформы: $(DOCKER_PLATFORMS)"; \
 | ||
| 			echo "🔧 Исправления: добавлен passlib для хеширования паролей"; \
 | ||
| 			$(MAKE) docker setup-builder; \
 | ||
| 			cd dockerfiles/ansible-controller && \
 | ||
| 			docker buildx build \
 | ||
| 				--platform $(DOCKER_PLATFORMS) \
 | ||
| 				--tag $(DOCKER_REGISTRY):ansible-controller-$(DOCKER_VERSION) \
 | ||
| 				--tag $(DOCKER_REGISTRY):ansible-controller-latest \
 | ||
| 				--push \
 | ||
| 				--no-cache \
 | ||
| 				.; \
 | ||
| 			echo "✅ ansible-controller пересобран с исправлениями";; \
 | ||
| 		run) \
 | ||
| 			echo "🚀 Запуск ansible-controller..."; \
 | ||
| 			cd dockerfiles/ansible-controller && docker-compose up -d; \
 | ||
| 			echo "✅ ansible-controller запущен";; \
 | ||
| 		stop) \
 | ||
| 			echo "🛑 Остановка ansible-controller..."; \
 | ||
| 			cd dockerfiles/ansible-controller && docker-compose down; \
 | ||
| 			echo "✅ ansible-controller остановлен";; \
 | ||
| 		*) \
 | ||
| 			echo "🎯 Доступные команды:"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔨 make controller build  - собрать ansible-controller (multi-arch)"; \
 | ||
| 			echo "     💡 Собирает: inecs/ansible-lab:ansible-controller-latest"; \
 | ||
| 			echo "     💡 Платформы: $(DOCKER_PLATFORMS)"; \
 | ||
| 			echo "     💡 Использует: dockerfiles/ansible-controller/Dockerfile"; \
 | ||
| 			echo "     💡 Requirements: dockerfiles/ansible-controller/requirements.yml"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🔄 make controller rebuild - пересобрать ansible-controller с исправлениями"; \
 | ||
| 			echo "     💡 Пересобирает: с --no-cache для применения исправлений"; \
 | ||
| 			echo "     💡 Исправления: добавлен passlib для хеширования паролей"; \
 | ||
| 			echo "     💡 Платформы: $(DOCKER_PLATFORMS)"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🚀 make controller run     - запустить ansible-controller"; \
 | ||
| 			echo "     💡 Запускает: docker-compose up -d"; \
 | ||
| 			echo "     💡 Использует: dockerfiles/ansible-controller/docker-compose.yml"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  🛑 make controller stop    - остановить ansible-controller"; \
 | ||
| 			echo "     💡 Останавливает: docker-compose down"; \
 | ||
| 			echo "     💡 Удаляет: контейнеры и сети";; \
 | ||
| 	esac
 | ||
| 
 | ||
| # =============================================================================
 | ||
| # КОМАНДЫ ДЛЯ РАБОТЫ С KUBERNETES KIND
 | ||
| # =============================================================================
 | ||
| k8s:
 | ||
| 	@case "$(word 2, $(MAKECMDGOALS))" in \
 | ||
| 		create) \
 | ||
| 			echo "☸️  Создание Kind кластера..."; \
 | ||
| 			PRESET_ARG="$(word 3, $(MAKECMDGOALS))"; \
 | ||
| 			if [ -z "$$PRESET_ARG" ]; then \
 | ||
| 				PRESET=k8s-minimal; \
 | ||
| 				echo "📋 Используется preset по умолчанию: $$PRESET (минимальный без аддонов)"; \
 | ||
| 			else \
 | ||
| 				PRESET=$$PRESET_ARG; \
 | ||
| 				echo "📋 Используется preset: $$PRESET"; \
 | ||
| 			fi; \
 | ||
| 			if [ ! -f "molecule/presets/k8s/$$PRESET.yml" ]; then \
 | ||
| 				echo "❌ Ошибка: Пресет '$$PRESET' не найден!"; \
 | ||
| 				echo "💡 Доступные пресеты:"; \
 | ||
| 				ls -1 molecule/presets/k8s/*.yml 2>/dev/null | sed 's|molecule/presets/k8s/||g' | sed 's|\.yml||g' | sed 's/^/  - /' || echo "  - k8s-minimal"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			CONTAINER_NAME=k8s-controller; \
 | ||
| 			docker run -d --name $$CONTAINER_NAME --rm \
 | ||
| 				-v "$(PWD):/workspace" -w /workspace \
 | ||
| 				-v /var/run/docker.sock:/var/run/docker.sock \
 | ||
| 				-u root \
 | ||
| 				-e ANSIBLE_FORCE_COLOR=1 \
 | ||
| 				-e MOLECULE_PRESET=$$PRESET \
 | ||
| 				-e MOLECULE_EPHEMERAL_DIRECTORY=/tmp/molecule_workspace \
 | ||
| 				$(DOCKER_K8S_IMAGE) \
 | ||
| 				/bin/bash -c 'sleep infinity'; \
 | ||
| 			echo "🚀 Запуск создания кластера..."; \
 | ||
| 			docker exec $$CONTAINER_NAME bash -c "cd /workspace && python3 /workspace/scripts/create_k8s_cluster.py molecule/presets/k8s/$$PRESET.yml $$CONTAINER_NAME"; \
 | ||
| 			echo "✅ Kind кластер создан"; \
 | ||
| 			echo "💡 Для создания port-forward: make k8s portforward create"; \
 | ||
| 			echo "💡 Для подключения используйте: make k8s kubeconfig"; \
 | ||
| 			echo "💡 Для остановки используйте: make k8s stop";; \
 | ||
| 		destroy) \
 | ||
| 			echo "🗑️  Удаление Kind кластера и контейнеров..."; \
 | ||
| 			PRESET_ARG="$(word 3, $(MAKECMDGOALS))"; \
 | ||
| 			PRESET=$${PRESET_ARG:-k8s-minimal}; \
 | ||
| 			CONTAINER_NAME=k8s-controller; \
 | ||
| 			if docker ps | grep -q $$CONTAINER_NAME; then \
 | ||
| 				echo "🗑️  Удаление Kind кластеров..."; \
 | ||
| 				docker exec $$CONTAINER_NAME bash -c "kind delete clusters --all" 2>/dev/null || true; \
 | ||
| 			else \
 | ||
| 				echo "⚠️  Контейнер $$CONTAINER_NAME не запущен"; \
 | ||
| 			fi; \
 | ||
| 			docker rm -f $$CONTAINER_NAME 2>/dev/null || true; \
 | ||
| 			echo "🗑️  Удаление контейнеров из пресета..."; \
 | ||
| 			if [ -f "molecule/presets/k8s/$$PRESET.yml" ]; then \
 | ||
| 				if docker ps | grep -q $$CONTAINER_NAME; then \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "python3 /workspace/scripts/delete_hosts.py /workspace/molecule/presets/k8s/$$PRESET.yml" 2>/dev/null || true; \
 | ||
| 				else \
 | ||
| 					python3 scripts/delete_hosts.py molecule/presets/k8s/$$PRESET.yml 2>/dev/null || true; \
 | ||
| 				fi; \
 | ||
| 			fi; \
 | ||
| 			echo "✅ Удаление завершено";; \
 | ||
| 		stop) \
 | ||
| 			echo "🛑 Остановка Kind кластера..."; \
 | ||
| 			PRESET_ARG="$(word 3, $(MAKECMDGOALS))"; \
 | ||
| 			if [ -z "$$PRESET_ARG" ]; then \
 | ||
| 				echo "❌ Ошибка: Укажите пресет"; \
 | ||
| 				echo "💡 Пример: make k8s stop kubernetes"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			CONTAINER_NAME=k8s-controller; \
 | ||
| 			if docker ps | grep -q $$CONTAINER_NAME; then \
 | ||
| 				docker exec $$CONTAINER_NAME bash -c "kind get clusters | xargs -I {} kind stop cluster --name {}" 2>/dev/null || true; \
 | ||
| 				echo "✅ Kind кластер остановлен"; \
 | ||
| 			else \
 | ||
| 				echo "⚠️  Контейнер $$CONTAINER_NAME не запущен"; \
 | ||
| 			fi; \
 | ||
| 			echo "💡 Кластер остановлен, но не удален"; \
 | ||
| 			echo "💡 Для перезапуска: make k8s start $$PRESET_ARG"; \
 | ||
| 			echo "💡 Для полного удаления: make k8s destroy $$PRESET_ARG";; \
 | ||
| 		start) \
 | ||
| 			echo "🚀 Запуск Kind кластера..."; \
 | ||
| 			PRESET_ARG="$(word 3, $(MAKECMDGOALS))"; \
 | ||
| 			if [ -z "$$PRESET_ARG" ]; then \
 | ||
| 				echo "❌ Ошибка: Укажите пресет"; \
 | ||
| 				echo "💡 Пример: make k8s start kubernetes"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			CONTAINER_NAME=k8s-controller; \
 | ||
| 			if ! docker ps | grep -q $$CONTAINER_NAME; then \
 | ||
| 				echo "❌ Контейнер $$CONTAINER_NAME не запущен"; \
 | ||
| 				echo "💡 Запустите: make k8s create $$PRESET_ARG"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			docker exec $$CONTAINER_NAME bash -c "kind get clusters | xargs -I {} kind start cluster --name {}" 2>/dev/null || true; \
 | ||
| 			echo "✅ Kind кластер запущен";; \
 | ||
| 		status) \
 | ||
| 			echo "📊 Детальный отчет о состоянии кластера..."; \
 | ||
| 			PRESET_ARG="$(word 3, $(MAKECMDGOALS))"; \
 | ||
| 			if [ -z "$$PRESET_ARG" ]; then \
 | ||
| 				echo "❌ Ошибка: Укажите пресет"; \
 | ||
| 				echo "💡 Пример: make k8s status kubernetes"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			CONTAINER_NAME=k8s-controller; \
 | ||
| 			if docker ps | grep -q $$CONTAINER_NAME; then \
 | ||
| 				python3 scripts/k8s_status.py; \
 | ||
| 			else \
 | ||
| 				echo "⚠️  Контейнер $$CONTAINER_NAME не запущен"; \
 | ||
| 				echo "💡 Запустите: make k8s create $$PRESET_ARG"; \
 | ||
| 			fi;; \
 | ||
| 		config) \
 | ||
| 			echo "📋 Получение kubeconfig..."; \
 | ||
| 			PRESET_ARG="$(word 3, $(MAKECMDGOALS))"; \
 | ||
| 			if [ -z "$$PRESET_ARG" ]; then \
 | ||
| 				echo "❌ Ошибка: Укажите пресет"; \
 | ||
| 				echo "💡 Пример: make k8s config kubernetes"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			CONTAINER_NAME=k8s-controller; \
 | ||
| 			if ! docker ps | grep -q $$CONTAINER_NAME; then \
 | ||
| 				echo "❌ Контейнер $$CONTAINER_NAME не запущен"; \
 | ||
| 				echo "💡 Запустите: make k8s create $$PRESET_ARG"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			KUBECONFIG_FILE="$$(pwd)/kubeconfig"; \
 | ||
| 			docker exec $$CONTAINER_NAME bash -c "kind get kubeconfig" > $$KUBECONFIG_FILE 2>/dev/null || true; \
 | ||
| 			if [ -f $$KUBECONFIG_FILE ] && [ -s $$KUBECONFIG_FILE ]; then \
 | ||
| 				echo "✅ kubeconfig сохранен в: $$KUBECONFIG_FILE"; \
 | ||
| 				echo ""; \
 | ||
| 				echo "💡 Для использования:"; \
 | ||
| 				echo "   export KUBECONFIG=$$KUBECONFIG_FILE"; \
 | ||
| 				echo "   kubectl get nodes"; \
 | ||
| 				echo ""; \
 | ||
| 				echo "💡 Или для однократного использования:"; \
 | ||
| 				echo "   kubectl --kubeconfig=$$KUBECONFIG_FILE get nodes"; \
 | ||
| 			else \
 | ||
| 				echo "❌ Не удалось получить kubeconfig"; \
 | ||
| 				rm -f $$KUBECONFIG_FILE; \
 | ||
| 			fi;; \
 | ||
| 		nodes) \
 | ||
| 			echo "🖥️  Просмотр узлов кластера..."; \
 | ||
| 			PRESET_ARG="$(word 3, $(MAKECMDGOALS))"; \
 | ||
| 			if [ -z "$$PRESET_ARG" ]; then \
 | ||
| 				echo "❌ Ошибка: Укажите пресет"; \
 | ||
| 				echo "💡 Пример: make k8s nodes kubernetes"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			CONTAINER_NAME=k8s-controller; \
 | ||
| 			if ! docker ps | grep -q $$CONTAINER_NAME; then \
 | ||
| 				echo "❌ Контейнер $$CONTAINER_NAME не запущен"; \
 | ||
| 				echo "💡 Запустите: make k8s create $$PRESET_ARG"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			CLUSTER_NAME=$$(docker exec $$CONTAINER_NAME kind get clusters | head -1); \
 | ||
| 			docker exec $$CONTAINER_NAME bash -c "CLUSTER_NAME=$$CLUSTER_NAME; kubectl --server=https://\$${CLUSTER_NAME}-control-plane:6443 --insecure-skip-tls-verify get nodes";; \
 | ||
| 		shell) \
 | ||
| 			echo "🐚 Открытие shell в контейнере..."; \
 | ||
| 			PRESET_ARG="$(word 3, $(MAKECMDGOALS))"; \
 | ||
| 			if [ -z "$$PRESET_ARG" ]; then \
 | ||
| 				echo "❌ Ошибка: Укажите пресет"; \
 | ||
| 				echo "💡 Пример: make k8s shell kubernetes"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			CONTAINER_NAME=k8s-controller; \
 | ||
| 			if docker ps | grep -q $$CONTAINER_NAME; then \
 | ||
| 				docker exec -it $$CONTAINER_NAME bash; \
 | ||
| 			else \
 | ||
| 				echo "❌ Контейнер $$CONTAINER_NAME не запущен"; \
 | ||
| 				echo "💡 Запустите: make k8s create $$PRESET_ARG"; \
 | ||
| 			fi;; \
 | ||
| 		manifest) \
 | ||
| 			echo "📄 Работа с манифестами..."; \
 | ||
| 			MANIFEST_CMD="$(word 3, $(MAKECMDGOALS))"; \
 | ||
| 			PRESET_ARG="$(word 4, $(MAKECMDGOALS))"; \
 | ||
| 			MANIFEST_ARG="$(word 5, $(MAKECMDGOALS))"; \
 | ||
| 			if [ -z "$$MANIFEST_CMD" ] || [ -z "$$PRESET_ARG" ] || [ -z "$$MANIFEST_ARG" ]; then \
 | ||
| 				echo "❌ Ошибка: Укажите команду, пресет и путь к манифесту"; \
 | ||
| 				echo "💡 Пример: make k8s manifest apply kubernetes https://example.com/manifest.yaml"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			CONTAINER_NAME=k8s-controller; \
 | ||
| 			if ! docker ps | grep -q $$CONTAINER_NAME; then \
 | ||
| 				echo "❌ Контейнер $$CONTAINER_NAME не запущен"; \
 | ||
| 				echo "💡 Запустите: make k8s create $$PRESET_ARG"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			CLUSTER_NAME=$$(docker exec $$CONTAINER_NAME kind get clusters | head -1); \
 | ||
| 			case "$$MANIFEST_CMD" in \
 | ||
| 				apply) \
 | ||
| 					echo "📥 Применение манифеста: $$MANIFEST_ARG"; \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "CLUSTER_NAME=$$CLUSTER_NAME; kubectl --server=https://\$${CLUSTER_NAME}-control-plane:6443 --insecure-skip-tls-verify apply -f $$MANIFEST_ARG";; \
 | ||
| 				delete) \
 | ||
| 					echo "🗑️  Удаление ресурсов из манифеста: $$MANIFEST_ARG"; \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "CLUSTER_NAME=$$CLUSTER_NAME; kubectl --server=https://\$${CLUSTER_NAME}-control-plane:6443 --insecure-skip-tls-verify delete -f $$MANIFEST_ARG";; \
 | ||
| 				*) \
 | ||
| 					echo "❌ Неизвестная команда: $$MANIFEST_CMD"; \
 | ||
| 					echo "💡 Доступные команды: apply, delete"; \
 | ||
| 					exit 1;; \
 | ||
| 			esac;; \
 | ||
| 		helm) \
 | ||
| 			echo "📦 Работа с Helm..."; \
 | ||
| 			HELM_CMD="$(word 3, $(MAKECMDGOALS))"; \
 | ||
| 			PRESET_ARG="$(word 4, $(MAKECMDGOALS))"; \
 | ||
| 			RELEASE_ARG="$(word 5, $(MAKECMDGOALS))"; \
 | ||
| 			CHART_ARG="$(word 6, $(MAKECMDGOALS))"; \
 | ||
| 			if [ -z "$$HELM_CMD" ] || [ -z "$$PRESET_ARG" ]; then \
 | ||
| 				echo "❌ Ошибка: Укажите команду и пресет"; \
 | ||
| 				echo "💡 Пример: make k8s helm list kubernetes"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			CONTAINER_NAME=k8s-controller; \
 | ||
| 			if ! docker ps | grep -q $$CONTAINER_NAME; then \
 | ||
| 				echo "❌ Контейнер $$CONTAINER_NAME не запущен"; \
 | ||
| 				echo "💡 Запустите: make k8s create $$PRESET_ARG"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			CLUSTER_NAME=$$(docker exec $$CONTAINER_NAME kind get clusters | head -1); \
 | ||
| 			case "$$HELM_CMD" in \
 | ||
| 				apply) \
 | ||
| 					if [ -z "$$RELEASE_ARG" ] || [ -z "$$CHART_ARG" ]; then \
 | ||
| 						echo "❌ Ошибка: Укажите имя релиза и чарт"; \
 | ||
| 						echo "💡 Пример: make k8s helm apply kubernetes my-release stable/nginx-ingress"; \
 | ||
| 						exit 1; \
 | ||
| 					fi; \
 | ||
| 					echo "📦 Установка Helm чарта: $$CHART_ARG как $$RELEASE_ARG"; \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "CLUSTER_NAME=$$CLUSTER_NAME; helm upgrade --install $$RELEASE_ARG $$CHART_ARG --kube-apiserver=https://\$${CLUSTER_NAME}-control-plane:6443 --kube-token=dummy --kube-context=dummy 2>&1 | grep -v '^WARNING' || true";; \
 | ||
| 				delete) \
 | ||
| 					if [ -z "$$RELEASE_ARG" ]; then \
 | ||
| 						echo "❌ Ошибка: Укажите имя релиза"; \
 | ||
| 						echo "💡 Пример: make k8s helm delete kubernetes my-release"; \
 | ||
| 						exit 1; \
 | ||
| 					fi; \
 | ||
| 					echo "🗑️  Удаление Helm релиза: $$RELEASE_ARG"; \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "CLUSTER_NAME=$$CLUSTER_NAME; helm uninstall $$RELEASE_ARG --kube-apiserver=https://\$${CLUSTER_NAME}-control-plane:6443 --kube-token=dummy --kube-context=dummy 2>&1 | grep -v '^WARNING' || true";; \
 | ||
| 				update) \
 | ||
| 					if [ -z "$$RELEASE_ARG" ] || [ -z "$$CHART_ARG" ]; then \
 | ||
| 						echo "❌ Ошибка: Укажите имя релиза и чарт"; \
 | ||
| 						echo "💡 Пример: make k8s helm update kubernetes my-release stable/nginx-ingress"; \
 | ||
| 						exit 1; \
 | ||
| 					fi; \
 | ||
| 					echo "🔄 Обновление Helm релиза: $$RELEASE_ARG"; \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "CLUSTER_NAME=$$CLUSTER_NAME; helm upgrade $$RELEASE_ARG $$CHART_ARG --kube-apiserver=https://\$${CLUSTER_NAME}-control-plane:6443 --kube-token=dummy --kube-context=dummy 2>&1 | grep -v '^WARNING' || true";; \
 | ||
| 				rollback) \
 | ||
| 					if [ -z "$$RELEASE_ARG" ]; then \
 | ||
| 						echo "❌ Ошибка: Укажите имя релиза"; \
 | ||
| 						echo "💡 Пример: make k8s helm rollback kubernetes my-release"; \
 | ||
| 						exit 1; \
 | ||
| 					fi; \
 | ||
| 					echo "⏪ Откат Helm релиза: $$RELEASE_ARG"; \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "CLUSTER_NAME=$$CLUSTER_NAME; helm rollback $$RELEASE_ARG --kube-apiserver=https://\$${CLUSTER_NAME}-control-plane:6443 --kube-token=dummy --kube-context=dummy 2>&1 | grep -v '^WARNING' || true";; \
 | ||
| 				list) \
 | ||
| 					echo "📋 Список Helm релизов:"; \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "CLUSTER_NAME=$$CLUSTER_NAME; helm list --kube-apiserver=https://\$${CLUSTER_NAME}-control-plane:6443 --kube-token=dummy --kube-context=dummy --all-namespaces 2>&1 | grep -v '^WARNING' || true";; \
 | ||
| 				status) \
 | ||
| 					if [ -z "$$RELEASE_ARG" ]; then \
 | ||
| 						echo "❌ Ошибка: Укажите имя релиза"; \
 | ||
| 						echo "💡 Пример: make k8s helm status kubernetes my-release"; \
 | ||
| 						exit 1; \
 | ||
| 					fi; \
 | ||
| 					echo "📊 Статус Helm релиза: $$RELEASE_ARG"; \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "CLUSTER_NAME=$$CLUSTER_NAME; helm status $$RELEASE_ARG --kube-apiserver=https://\$${CLUSTER_NAME}-control-plane:6443 --kube-token=dummy --kube-context=dummy 2>&1 | grep -v '^WARNING' || true";; \
 | ||
| 				*) \
 | ||
| 					echo "❌ Неизвестная команда: $$HELM_CMD"; \
 | ||
| 					echo "💡 Доступные команды: apply, delete, update, rollback, list, status"; \
 | ||
| 					exit 1;; \
 | ||
| 			esac;; \
 | ||
| 		helmrepo) \
 | ||
| 			echo "🏪 Работа с Helm репозиториями..."; \
 | ||
| 			REPO_CMD="$(word 3, $(MAKECMDGOALS))"; \
 | ||
| 			PRESET_ARG="$(word 4, $(MAKECMDGOALS))"; \
 | ||
| 			NAME_ARG="$(word 5, $(MAKECMDGOALS))"; \
 | ||
| 			URL_ARG="$(word 6, $(MAKECMDGOALS))"; \
 | ||
| 			if [ -z "$$REPO_CMD" ] || [ -z "$$PRESET_ARG" ]; then \
 | ||
| 				echo "❌ Ошибка: Укажите команду и пресет"; \
 | ||
| 				echo "💡 Пример: make k8s helmrepo list kubernetes"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			CONTAINER_NAME=k8s-controller; \
 | ||
| 			if ! docker ps | grep -q $$CONTAINER_NAME; then \
 | ||
| 				echo "❌ Контейнер $$CONTAINER_NAME не запущен"; \
 | ||
| 				echo "💡 Запустите: make k8s create $$PRESET_ARG"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			case "$$REPO_CMD" in \
 | ||
| 				add) \
 | ||
| 					if [ -z "$$NAME_ARG" ] || [ -z "$$URL_ARG" ]; then \
 | ||
| 						echo "❌ Ошибка: Укажите имя и URL репозитория"; \
 | ||
| 						echo "💡 Пример: make k8s helmrepo add kubernetes stable https://charts.helm.sh/stable"; \
 | ||
| 						exit 1; \
 | ||
| 					fi; \
 | ||
| 					echo "➕ Добавление Helm репозитория: $$NAME_ARG"; \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "helm repo add $$NAME_ARG $$URL_ARG 2>&1 | grep -v '^WARNING' || true; helm repo update";; \
 | ||
| 				list) \
 | ||
| 					echo "📋 Список Helm репозиториев:"; \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "helm repo list 2>&1 | grep -v '^WARNING' || true";; \
 | ||
| 				delete) \
 | ||
| 					if [ -z "$$NAME_ARG" ]; then \
 | ||
| 						echo "❌ Ошибка: Укажите имя репозитория"; \
 | ||
| 						echo "💡 Пример: make k8s helmrepo delete kubernetes stable"; \
 | ||
| 						exit 1; \
 | ||
| 					fi; \
 | ||
| 					echo "🗑️  Удаление Helm репозитория: $$NAME_ARG"; \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "helm repo remove $$NAME_ARG 2>&1 | grep -v '^WARNING' || true";; \
 | ||
| 				update) \
 | ||
| 					echo "🔄 Обновление Helm репозиториев"; \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "helm repo update 2>&1 | grep -v '^WARNING' || true";; \
 | ||
| 				packages) \
 | ||
| 					if [ -z "$$NAME_ARG" ]; then \
 | ||
| 						echo "❌ Ошибка: Укажите имя репозитория"; \
 | ||
| 						echo "💡 Пример: make k8s helmrepo packages kubernetes stable"; \
 | ||
| 						exit 1; \
 | ||
| 					fi; \
 | ||
| 					echo "📦 Пакеты в репозитории: $$NAME_ARG"; \
 | ||
| 					docker exec $$CONTAINER_NAME bash -c "helm search repo $$NAME_ARG 2>&1 | grep -v '^WARNING' || true";; \
 | ||
| 				*) \
 | ||
| 					echo "❌ Неизвестная команда: $$REPO_CMD"; \
 | ||
| 					echo "💡 Доступные команды: add, list, delete, update, packages"; \
 | ||
| 					exit 1;; \
 | ||
| 			esac;; \
 | ||
| 		portforward) \
 | ||
| 			PORTFWD_CMD="$(word 3, $(MAKECMDGOALS))"; \
 | ||
| 			PORT_ARG="$(word 4, $(MAKECMDGOALS))"; \
 | ||
| 			if [ -z "$$PORTFWD_CMD" ]; then \
 | ||
| 				echo "❌ Ошибка: Укажите команду"; \
 | ||
| 				echo "💡 Пример: make k8s portforward create"; \
 | ||
| 				exit 1; \
 | ||
| 			fi; \
 | ||
| 			case "$$PORTFWD_CMD" in \
 | ||
| 				create) \
 | ||
| 					echo "🔌 Создание port-forward..."; \
 | ||
| 					python3 scripts/portforward.py create;; \
 | ||
| 				list) \
 | ||
| 					echo "📋 Список активных port-forward..."; \
 | ||
| 					python3 scripts/portforward.py list;; \
 | ||
| 				clear) \
 | ||
| 					echo "🗑️  Очистка всех port-forward..."; \
 | ||
| 					python3 scripts/portforward.py clear;; \
 | ||
| 				recreate) \
 | ||
| 					echo "🔄 Пересоздание port-forward..."; \
 | ||
| 					python3 scripts/portforward.py recreate;; \
 | ||
| 				delete) \
 | ||
| 					if [ -z "$$PORT_ARG" ]; then \
 | ||
| 						echo "❌ Ошибка: Укажите порт"; \
 | ||
| 						echo "💡 Пример: make k8s portforward delete 3000"; \
 | ||
| 						exit 1; \
 | ||
| 					fi; \
 | ||
| 					echo "🗑️  Удаление port-forward на порту $$PORT_ARG..."; \
 | ||
| 					python3 scripts/portforward.py delete $$PORT_ARG;; \
 | ||
| 				*) \
 | ||
| 					echo "❌ Неизвестная команда: $$PORTFWD_CMD"; \
 | ||
| 					echo "💡 Доступные команды: create, list, clear, recreate, delete"; \
 | ||
| 					exit 1;; \
 | ||
| 			esac;; \
 | ||
| 		*) \
 | ||
| 			echo "☸️  Доступные команды:"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  make k8s create [preset]       - создать Kind кластер"; \
 | ||
| 			echo "     💡 Без параметра: используется k8s-minimal (без аддонов)"; \
 | ||
| 			echo "     💡 С параметром: используется указанный пресет"; \
 | ||
| 			echo "     💡 Кластер НЕ удаляется автоматически"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  make k8s destroy [preset]      - удалить Kind кластер полностью"; \
 | ||
| 			echo "     💡 Удалит: кластер и контейнер ansible-controller"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  make k8s stop [cluster]        - остановить Kind кластер (без удаления)"; \
 | ||
| 			echo "     💡 Можно указать имя кластера или остановить все"; \
 | ||
| 			echo "     💡 Для перезапуска: make k8s start"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  make k8s start [cluster]       - запустить остановленный кластер"; \
 | ||
| 			echo "     💡 Можно указать имя кластера или запустить все"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  make k8s status [preset]      - детальный отчет о состоянии кластера"; \
 | ||
| 			echo "     💡 Показывает: узлы, pods, сервисы, Ingress, события, Helm релизы и т.д."; \
 | ||
| 			echo "     💡 Требует: пресет"; \
 | ||
| 			echo "     💡 Пример: make k8s status kubernetes"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  make k8s config [cluster]      - получить kubeconfig для подключения"; \
 | ||
| 			echo "     💡 Сохраняет: kubeconfig в корне проекта"; \
 | ||
| 			echo "     💡 Можно указать имя конкретного кластера"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  make k8s nodes [preset]        - показать узлы кластера"; \
 | ||
| 			echo "     💡 Требует: пресет"; \
 | ||
| 			echo "     💡 Пример: make k8s nodes kubernetes"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  make k8s shell [preset]        - открыть shell в контейнере"; \
 | ||
| 			echo "     💡 Для: ручного управления kubectl/kind"; \
 | ||
| 			echo "     💡 Пример: make k8s shell kubernetes"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  make k8s manifest [cmd] [preset] [url] - работа с манифестами"; \
 | ||
| 			echo "     💡 Команды: apply, delete"; \
 | ||
| 			echo "     💡 Пример: make k8s manifest apply kubernetes https://example.com/deploy.yaml"; \
 | ||
| 			echo ""; \
 | ||
| 			echo "  make k8s helm [cmd] [preset] [release] [chart] - работа с Helm"; \
 | ||
| 			echo "     💡 Команды: apply, delete, update, rollback, list, status"; \
 | ||
| 			echo "     💡 Пример: make k8s helm apply kubernetes nginx stable/nginx-ingress"; \
 | ||
| 			echo ""; \
 | ||
| 					echo "  make k8s helmrepo [cmd] [preset] [name] [url] - работа с Helm репозиториями"; \
 | ||
| 		echo "     💡 Команды: add, list, delete, update, packages"; \
 | ||
| 		echo "     💡 Пример: make k8s helmrepo add kubernetes stable https://charts.helm.sh/stable"; \
 | ||
| 		echo ""; \
 | ||
| 					echo "  make k8s portforward [cmd] - управление port-forward"; \
 | ||
| 			echo "     💡 Команды: create, list, clear, recreate, delete [port]"; \
 | ||
| 			echo "     💡 Пример: make k8s portforward create"; \
 | ||
| 		echo ""; \
 | ||
| 		echo "💡 Примеры:"; \
 | ||
| 			echo "   make k8s create                # создать минимальный кластер"; \
 | ||
| 			echo "   make k8s create kubernetes     # создать кластер с аддонами"; \
 | ||
| 			echo "   make k8s nodes kubernetes      # показать узлы кластера"; \
 | ||
| 			echo "   make k8s config kubernetes     # получить kubeconfig для кластера"; \
 | ||
| 			echo "   export KUBECONFIG=kubeconfig   # использовать конфиг"; \
 | ||
| 			echo "   kubectl get nodes              # проверить узлы"; \
 | ||
| 			echo "   make k8s manifest apply kubernetes https://example.com/manifest.yaml  # установить манифест"; \
 | ||
| 			echo "   make k8s stop kubernetes       # остановить кластер"; \
 | ||
| 			echo "   make k8s start kubernetes      # запустить кластер"; \
 | ||
| 			echo "   make k8s destroy kubernetes    # удалить кластер с пресетом kubernetes";; \
 | ||
| 	esac
 | ||
| 
 | ||
| # =============================================================================
 | ||
| # СПРАВКА
 | ||
| # =============================================================================
 | ||
| help:
 | ||
| 	@echo "=========================================="
 | ||
| 	@echo "DevOpsLab - Универсальная система"
 | ||
| 	@echo "тестирования Ansible ролей"
 | ||
| 	@echo "=========================================="
 | ||
| 	@echo ""
 | ||
| 	@echo "📁 Структура проекта:"
 | ||
| 	@echo "  scripts/          - Скрипты автоматизации"
 | ||
| 	@echo "  inventory/        - Инвентори файлы"
 | ||
| 	@echo "  molecule/default/ - Molecule конфигурация"
 | ||
| 	@echo "  roles/            - Ansible роли"
 | ||
| 	@echo "  vault/            - Зашифрованные секреты"
 | ||
| 	@echo "  dockerfiles/      - Docker образы для тестирования"
 | ||
| 	@echo ""
 | ||
| 	@echo "🚀 ОСНОВНЫЕ КОМАНДЫ:"
 | ||
| 	@echo "  make role lint [role]     - проверить синтаксис ролей (все или конкретную)"
 | ||
| 	@echo "    💡 Примеры: make role lint, make role lint devops"
 | ||
| 	@echo "  make role test [preset]  - протестировать роли с preset'ом"
 | ||
| 	@echo "  make role deploy         - развернуть роли на реальные серверы"
 | ||
| 	@echo "  make role list           - показать все роли"
 | ||
| 	@echo "  make role create         - создать новую роль (интерактивно)"
 | ||
| 	@echo "  make role delete         - удалить роль (интерактивно)"
 | ||
| 	@echo ""
 | ||
| 	@echo "📖 ДОКУМЕНТАЦИЯ:"
 | ||
| 	@echo "  docs/deploy-yml-customization.md - полное руководство по кастомизации"
 | ||
| 	@echo "  docs/linting-guide.md - руководство по линтингу ролей"
 | ||
| 	@echo ""
 | ||
| 	@echo "🔍 ЛИНТИНГ РОЛЕЙ:"
 | ||
| 	@echo "  make role lint [role]     - проверить синтаксис (все или конкретную роль)"
 | ||
| 	@echo "  💡 Профили: production, basic, min"
 | ||
| 	@echo "  💡 Конфигурация: .ansible-lint"
 | ||
| 	@echo "  💡 Валидация: автоматическая проверка существования роли"
 | ||
| 	@echo ""
 | ||
| 	@echo "📋 PRESET'Ы (тестовые окружения):"
 | ||
| 	@echo "  make presets list        - показать все доступные preset'ы"
 | ||
| 	@echo "  make presets info        - подробная информация о preset'е"
 | ||
| 	@echo ""
 | ||
| 	@echo "🐳 DOCKER ОБРАЗЫ (Multi-Arch):"
 | ||
| 	@echo "  make docker prepare       - подготовка к работе с Docker Hub"
 | ||
| 	@echo "  make docker build         - собрать все Docker образы (amd64 + arm64)"
 | ||
| 	@echo "  make docker build-image IMAGE=<имя> - собрать отдельный образ"
 | ||
| 	@echo "  make docker rebuild       - полная пересборка с очисткой кеша"
 | ||
| 	@echo "  make docker push          - отправить образы в Docker Hub"
 | ||
| 	@echo "  make docker pull          - загрузить образы из Docker Hub"
 | ||
| 	@echo "  make docker clean         - удалить локальные образы"
 | ||
| 	@echo "  make docker info          - информация о собранных образах"
 | ||
| 	@echo "  make docker update        - обновить все образы (pull + build + push)"
 | ||
| 	@echo "  make docker purge         - ПОЛНАЯ очистка Docker (ОСТОРОЖНО!)"
 | ||
| 	@echo ""
 | ||
| 	@echo "🔧 DOCKER BUILDER (Multi-Arch):"
 | ||
| 	@echo "  make docker setup-builder - настройка multi-arch builder в контейнере"
 | ||
| 	@echo "  make docker clean-builder - очистка multi-arch builder"
 | ||
| 	@echo "  make docker diagnose      - диагностика buildx проблем"
 | ||
| 	@echo "  make docker reset-builder - сброс buildx builder"
 | ||
| 	@echo "  💡 Поддерживает: amd64, arm64, riscv64, ppc64le, s390x, 386, arm/v7, arm/v6"
 | ||
| 	@echo ""
 | ||
| 	@echo "🔧 АВТОМАТИЗАЦИЯ:"
 | ||
| 	@echo "  make update-playbooks     - обновление playbook'ов при добавлении ролей"
 | ||
| 	@echo "  make generate-docs        - генерация документации для ролей"
 | ||
| 	@echo "  make setup-cicd          - настройка CI/CD для всех платформ"
 | ||
| 	@echo "  💡 Безопасно: использует inspect вместо buildx ls (избегает зависаний)"
 | ||
| 	@echo ""
 | ||
| 	@echo "🔐 VAULT (управление секретами):"
 | ||
| 	@echo "  make vault init          - инициализировать vault (создать vault/.vault)"
 | ||
| 	@echo "  make vault edit          - редактировать существующие секреты"
 | ||
| 	@echo "  make vault show          - показать содержимое секретов"
 | ||
| 	@echo "  make vault encrypt       - зашифровать файл"
 | ||
| 	@echo "  make vault decrypt       - расшифровать файл"
 | ||
| 	@echo "  make vault rekey         - сменить пароль шифрования для всех ролей"
 | ||
| 	@echo "  make vault check         - проверить vault файлы"
 | ||
| 	@echo "  make vault scan          - поиск потенциальных секретов"
 | ||
| 	@echo ""
 | ||
| 	@echo "🌿 GIT (управление версиями):"
 | ||
| 	@echo "  make git push            - отправить изменения в репозиторий"
 | ||
| 	@echo "  make git pull            - получить изменения из репозитория"
 | ||
| 	@echo "  make git new             - создать новую ветку"
 | ||
| 	@echo ""
 | ||
| 	@echo "🎮 CONTROLLER (ansible-controller Multi-Arch):"
 | ||
| 	@echo "  make controller build    - собрать ansible-controller (amd64 + arm64)"
 | ||
| 	@echo "  make controller run      - запустить ansible-controller"
 | ||
| 	@echo "  make controller stop     - остановить ansible-controller"
 | ||
| 	@echo ""
 | ||
| 	@echo "☸️  KUBERNETES (Kind кластеры):"
 | ||
| 	@echo "  make k8s create [preset]    - создать Kind кластер (по умолчанию: k8s-minimal)"
 | ||
| 	@echo "  make k8s destroy [preset]   - удалить Kind кластер"
 | ||
| 	@echo "  make k8s start [preset]     - запустить Kind кластер"
 | ||
| 	@echo "  make k8s stop [preset]      - остановить Kind кластер"
 | ||
| 	@echo "  make k8s status [preset]    - детальный отчет о состоянии кластера"
 | ||
| 	@echo "  make k8s nodes [preset]     - показать узлы кластера"
 | ||
| 	@echo "  make k8s config [preset]    - получить kubeconfig для подключения"
 | ||
| 	@echo "  make k8s manifest [cmd] [preset] [url] - работа с манифестами (apply, delete)"
 | ||
| 	@echo "  make k8s helm [cmd] [preset] [release] [chart] - работа с Helm"
 | ||
| 	@echo "  make k8s helmrepo [cmd] [preset] [name] [url] - управление Helm репозиториями"
 | ||
| 	@echo "  make k8s portforward [cmd] - управление port-forward (create, list, clear)"
 | ||
| 	@echo "  make k8s shell [preset]     - открыть shell в контейнере k8s"
 | ||
| 	@echo ""
 | ||
| 	@echo "💡 ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:"
 | ||
| 	@echo "  make presets list                    # показать все preset'ы"
 | ||
| 	@echo "  make role test minimal               # быстрый тест"
 | ||
| 	@echo "  make role test all-images            # тест всех образов"
 | ||
| 	@echo "  make docker setup-builder            # настройка multi-arch builder"
 | ||
| 	@echo "  make docker diagnose                 # диагностика buildx проблем"
 | ||
| 	@echo "  make docker reset-builder           # сброс buildx builder"
 | ||
| 	@echo "  make docker build                    # собрать все образы (amd64 + arm64)"
 | ||
| 	@echo "  make docker rebuild                  # полная пересборка с очисткой кеша"
 | ||
| 	@echo "  make controller build                # собрать ansible-controller (multi-arch)"
 | ||
| 	@echo "  make docker clean-builder            # очистка builder'а"
 | ||
| 	@echo "  make docker purge                    # полная очистка Docker"
 | ||
| 	@echo "  make vault init                      # инициализировать vault"
 | ||
| 	@echo "  make vault create                    # создать секреты"
 | ||
| 	@echo ""
 | ||
| 	@echo "📖 Подробная справка: make [команда]"
 | ||
| 	@echo "=========================================="
 | ||
| 
 | ||
| 
 | ||
| # =============================================================================
 | ||
| # АВТОМАТИЗАЦИЯ
 | ||
| # =============================================================================
 | ||
| 
 | ||
| update-playbooks:
 | ||
| 	@echo "🔄 Обновление playbook'ов..."
 | ||
| 	@chmod +x scripts/update-playbooks.sh
 | ||
| 	@./scripts/update-playbooks.sh
 | ||
| 
 | ||
| generate-docs:
 | ||
| 	@echo "📚 Генерация документации..."
 | ||
| 	@chmod +x scripts/generate-role-docs.sh
 | ||
| 	@./scripts/generate-role-docs.sh
 | ||
| 
 | ||
| setup-cicd:
 | ||
| 	@echo "🔧 Настройка CI/CD..."
 | ||
| 	@chmod +x scripts/setup-cicd.sh
 | ||
| 	@./scripts/setup-cicd.sh
 | ||
| 
 | ||
| # Вспомогательные функции: шифрование/расшифровка roles/*/vars/main.yml
 | ||
| .PHONY: encrypt decrypt rekey-all
 | ||
| encrypt-all:
 | ||
| 	@echo "🔐 Шифрование всех roles/*/vars/main.yml (только незашифрованных) ..."
 | ||
| 	@docker run --rm -v "$(PWD):/workspace" -w /workspace $(DOCKER_IMAGE) bash -c '\
 | ||
|         VAULT=vault/.vault; \
 | ||
|         for f in roles/*/vars/main.yml; do [ -f "$$f" ] || continue; \
 | ||
|             if ! grep -q "ANSIBLE_VAULT" "$$f" 2>/dev/null; then \
 | ||
|                 ansible-vault encrypt --encrypt-vault-id default --vault-password-file "$$VAULT" "$$f" || true; \
 | ||
| 				printf "✅ Зашифровано: %s\n" "$$f"; \
 | ||
| 			else \
 | ||
| 				printf "ℹ️  Уже зашифровано: %s\n" "$$f"; \
 | ||
| 			fi; \
 | ||
| 		done'
 | ||
| 
 | ||
| decrypt-all:
 | ||
| 	@echo "🔓 Расшифровка всех roles/*/vars/main.yml (только зашифрованных) ..."
 | ||
| 	@docker run --rm -v "$(PWD):/workspace" -w /workspace $(DOCKER_IMAGE) bash -c '\
 | ||
|         VAULT=vault/.vault; \
 | ||
|         for f in roles/*/vars/main.yml; do [ -f "$$f" ] || continue; \
 | ||
|             if grep -q "ANSIBLE_VAULT" "$$f" 2>/dev/null; then \
 | ||
|                 ansible-vault decrypt --vault-password-file "$$VAULT" "$$f" || true; \
 | ||
| 				printf "✅ Расшифровано: %s\n" "$$f"; \
 | ||
| 			else \
 | ||
| 				printf "ℹ️  Уже расшифровано: %s\n" "$$f"; \
 | ||
| 			fi; \
 | ||
| 		done'
 | ||
| 
 | ||
| rekey-all:
 | ||
| 	@echo "🔑 Смена пароля для всех roles/*/vars/main.yml ..."
 | ||
| 	@echo "🔍 Проверка статуса шифрования файлов..."
 | ||
| 	@UNENCRYPTED_FILES=$$(docker run --rm -v "$(PWD):/workspace" -w /workspace $(DOCKER_IMAGE) bash -c '\
 | ||
| 		for f in roles/*/vars/main.yml; do [ -f "$$f" ] || continue; \
 | ||
| 			if ! grep -q "ANSIBLE_VAULT" "$$f" 2>/dev/null; then \
 | ||
| 				echo "$$f"; \
 | ||
| 			fi; \
 | ||
| 		done'); \
 | ||
| 	if [ -n "$$UNENCRYPTED_FILES" ]; then \
 | ||
| 		echo "❌ Ошибка: Для смены пароля все файлы должны быть зашифрованы!"; \
 | ||
| 		echo "📋 Незашифрованные файлы:"; \
 | ||
| 		echo "$$UNENCRYPTED_FILES" | sed 's/^/  - /'; \
 | ||
| 		echo ""; \
 | ||
| 		echo "💡 Сначала зашифруйте все файлы командой: make encrypt-all"; \
 | ||
| 		exit 0; \
 | ||
| 	fi; \
 | ||
| 	echo "✅ Все файлы зашифрованы, можно менять пароль"; \
 | ||
| 	echo "📝 Введите новый пароль для vault:"; \
 | ||
| 	read -sp "Новый пароль: " NEW_PASSWORD; \
 | ||
| 	echo ""; \
 | ||
| 	echo "$$NEW_PASSWORD" > vault/.vault.new; \
 | ||
| 	chmod 600 vault/.vault.new; \
 | ||
| 	docker run --rm -it -v "$(PWD):/workspace" -w /workspace $(DOCKER_IMAGE) bash -c '\
 | ||
| 		VAULT=vault/.vault; \
 | ||
| 		NEW_VAULT=vault/.vault.new; \
 | ||
| 		for f in roles/*/vars/main.yml; do [ -f "$$f" ] || continue; \
 | ||
| 			printf "🔑 Смена пароля для: %s\n" "$$f"; \
 | ||
| 			ansible-vault rekey --vault-password-file "$$VAULT" --new-vault-password-file "$$NEW_VAULT" "$$f" || true; \
 | ||
| 			printf "✅ Пароль изменен: %s\n" "$$f"; \
 | ||
| 		done'; \
 | ||
| 	mv vault/.vault.new vault/.vault; \
 | ||
| 	echo "✅ Пароль vault обновлен для всех ролей"
 | ||
| 
 | ||
| # Вспомогательные функции: шифрование/расшифровка ТОЛЬКО одной роли
 | ||
| .PHONY: encrypt-role decrypt-role rekey-role
 | ||
| encrypt-role:
 | ||
| 	@ROLE_NAME="$(ROLE)"; \
 | ||
| 	if [ -z "$$ROLE_NAME" ]; then \
 | ||
| 		echo "📋 Доступные роли:"; \
 | ||
| 		ls -1 roles | grep -v "\.yml$$" | sed 's/^/  - /'; \
 | ||
| 		read -p "Введите имя роли: " ROLE_NAME; \
 | ||
| 	fi; \
 | ||
| 	if [ ! -d "roles/$$ROLE_NAME" ]; then \
 | ||
| 		echo "❌ Роль '$$ROLE_NAME' не найдена"; \
 | ||
| 		exit 0; \
 | ||
| 	fi; \
 | ||
| 	FILE="roles/$$ROLE_NAME/vars/main.yml"; \
 | ||
| 	if [ ! -f "$$FILE" ]; then \
 | ||
| 		echo "❌ Файл $$FILE не найден"; \
 | ||
| 		exit 0; \
 | ||
| 	fi; \
 | ||
| 	if grep -q "ANSIBLE_VAULT" "$$FILE" 2>/dev/null; then \
 | ||
| 		echo "ℹ️  Уже зашифровано: $$FILE"; \
 | ||
| 		exit 0; \
 | ||
| 	fi; \
 | ||
| 	docker run --rm -v "$(PWD):/workspace" -w /workspace $(DOCKER_IMAGE) bash -c '\
 | ||
| 		VAULT=vault/.vault; \
 | ||
| 		ansible-vault encrypt --encrypt-vault-id default --vault-password-file "$$VAULT" "$$1" || true' _ "$$FILE"; \
 | ||
| 	echo "✅ Зашифровано: $$FILE"
 | ||
| 
 | ||
| decrypt-role:
 | ||
| 	@ROLE_NAME="$(ROLE)"; \
 | ||
| 	if [ -z "$$ROLE_NAME" ]; then \
 | ||
| 		echo "📋 Доступные роли:"; \
 | ||
| 		ls -1 roles | grep -v "\.yml$$" | sed 's/^/  - /'; \
 | ||
| 		read -p "Введите имя роли: " ROLE_NAME; \
 | ||
| 	fi; \
 | ||
| 	if [ ! -d "roles/$$ROLE_NAME" ]; then \
 | ||
| 		echo "❌ Роль '$$ROLE_NAME' не найдена"; \
 | ||
| 		exit 0; \
 | ||
| 	fi; \
 | ||
| 	FILE="roles/$$ROLE_NAME/vars/main.yml"; \
 | ||
| 	if [ ! -f "$$FILE" ]; then \
 | ||
| 		echo "❌ Файл $$FILE не найден"; \
 | ||
| 		exit 0; \
 | ||
| 	fi; \
 | ||
| 	if ! grep -q "ANSIBLE_VAULT" "$$FILE" 2>/dev/null; then \
 | ||
| 		echo "ℹ️  Уже расшифровано: $$FILE"; \
 | ||
| 		exit 0; \
 | ||
| 	fi; \
 | ||
| 	docker run --rm -v "$(PWD):/workspace" -w /workspace $(DOCKER_IMAGE) bash -c '\
 | ||
| 		VAULT=vault/.vault; \
 | ||
| 		ansible-vault decrypt --vault-password-file "$$VAULT" "$$1" || true' _ "$$FILE"; \
 | ||
| 	echo "✅ Расшифровано: $$FILE"
 | ||
| 
 | ||
| rekey-role:
 | ||
| 	@ROLE_NAME="$(ROLE)"; \
 | ||
| 	echo "🔑 Смена пароля для vars/main.yml выбранной роли..."; \
 | ||
| 	echo "📋 Доступные роли:"; \
 | ||
| 	ls -1 roles | grep -v "\.yml$$" | sed 's/^/  - /'; \
 | ||
| 	if [ -z "$$ROLE_NAME" ]; then \
 | ||
| 		read -p "Введите имя роли: " ROLE_NAME; \
 | ||
| 	fi; \
 | ||
| 	if [ ! -d "roles/$$ROLE_NAME" ]; then \
 | ||
| 		echo "❌ Роль '$$ROLE_NAME' не найдена"; \
 | ||
| 		echo "📋 Доступные роли:"; \
 | ||
| 		ls -1 roles | grep -v "\\.yml$$" | sed 's/^/  - /'; \
 | ||
| 		echo "↩️  Возврат в интерактивный выбор роли для смены пароля..."; \
 | ||
| 		$(MAKE) vault rekey; \
 | ||
| 		exit 0; \
 | ||
| 	fi; \
 | ||
| 	FILE="roles/$$ROLE_NAME/vars/main.yml"; \
 | ||
| 	if [ ! -f "$$FILE" ]; then \
 | ||
| 		echo "❌ Файл $$FILE не найден"; \
 | ||
| 		exit 1; \
 | ||
| 	fi; \
 | ||
| 	if ! grep -q "ANSIBLE_VAULT" "$$FILE" 2>/dev/null; then \
 | ||
| 		echo "ℹ️  Файл $$FILE не зашифрован, сначала зашифруйте его"; \
 | ||
| 		exit 1; \
 | ||
| 	fi; \
 | ||
| 	docker run --rm -it -v "$(PWD):/workspace" -w /workspace \
 | ||
| 		-e EDITOR=$(EDITOR) \
 | ||
| 		$(DOCKER_IMAGE) \
 | ||
| 		ansible-vault rekey --vault-password-file vault/.vault "$$FILE"; \
 | ||
| 	echo "✅ Пароль изменен для: $$FILE"
 | ||
| 
 | ||
| # Очистка контейнеров Molecule
 | ||
| .PHONY: clean-containers
 | ||
| clean-containers:
 | ||
| 	@echo "🧹 Очистка контейнеров Molecule..."
 | ||
| 	@echo "📋 Поиск контейнеров проекта..."
 | ||
| 	@docker ps -a --filter "ancestor=inecs/ansible-lab" --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" || true
 | ||
| 	@echo ""
 | ||
| 	@echo "🗑️  Удаление контейнеров..."
 | ||
| 	@docker ps -a --filter "ancestor=inecs/ansible-lab" --format "{{.ID}}" | xargs -r docker rm -f 2>/dev/null || true
 | ||
| 	@docker ps -a --filter "network=labnet" --format "{{.ID}}" | xargs -r docker rm -f 2>/dev/null || true
 | ||
| 	@echo "🧹 Очистка сетей..."
 | ||
| 	@docker network rm labnet 2>/dev/null || true
 | ||
| 	@echo "✅ Очистка завершена"
 | ||
| 
 | ||
| # Пустые цели для совместимости
 | ||
| .PHONY: encrypt decrypt
 | ||
| encrypt:
 | ||
| 	@echo "ℹ️  Команда переименована. Используйте: make encrypt-all или make vault encrypt"
 | ||
| 	@true
 | ||
| decrypt:
 | ||
| 	@echo "ℹ️  Команда переименована. Используйте: make decrypt-all или make vault decrypt"
 | ||
| 	@true
 | ||
| 
 | ||
| view create edit show delete lint deploy new advanced list info test build push pull clean prepare update run stop purge clean-builder setup-builder diagnose reset-builder build-image:
 | ||
| 	@true
 | ||
| 
 | ||
| # Универсальный таргет для всех пресетов и других динамических целей
 | ||
| %:
 | ||
| 	@:
 |