# ═══════════════════════════════════════════════════════════════════════════════
# K3S Ansible Stack — Makefile
# Всё запускается через Docker — Ansible устанавливать не нужно!
#
# Требования: docker, make
# Начало работы: make setup → отредактируй .env и inventory → make install
# ═══════════════════════════════════════════════════════════════════════════════

# ── Конфигурация ──────────────────────────────────────────────────────────────
IMAGE_NAME      := k3s-ansible
CONTAINER_NAME  := k3s-ansible-runner

# Загружаем .env если существует
-include .env
export

# Цвета терминала
CYAN   := \033[0;36m
GREEN  := \033[0;32m
YELLOW := \033[1;33m
RED    := \033[0;31m
BOLD   := \033[1m
NC     := \033[0m

# ── Базовая команда запуска контейнера ────────────────────────────────────────
# Molecule запускается тоже из контейнера — монтируем Docker socket для DinD
DOCKER_RUN_MOLECULE := docker run --rm -it \
	--name $(CONTAINER_NAME)-molecule \
	-v $(PWD):/ansible \
	-v /var/run/docker.sock:/var/run/docker.sock \
	-e ANSIBLE_FORCE_COLOR=1 \
	-e MOLECULE_NO_LOG=0 \
	$(IMAGE_NAME)

DOCKER_RUN := docker run --rm -it \
	--name $(CONTAINER_NAME) \
	--network host \
	-v $(PWD):/ansible \
	-v $(or $(SSH_KEY_PATH),$(HOME)/.ssh):/root/.ssh:ro \
	-e VAULT_PASSWORD="$(VAULT_PASSWORD)" \
	-e ANSIBLE_VERBOSITY="$(ANSIBLE_VERBOSITY)" \
	-e ANSIBLE_TAGS="$(ANSIBLE_TAGS)" \
	-e ANSIBLE_SKIP_TAGS="$(ANSIBLE_SKIP_TAGS)" \
	-e EXTRA_VARS="$(EXTRA_VARS)" \
	-e VERSION="$(VERSION)" \
	-e ANSIBLE_FORCE_COLOR=1 \
	$(IMAGE_NAME)

.PHONY: help setup build rebuild \
        bootstrap k8s-user mdadm k3s-certs chrony \
        install install-full install-k3s install-cni install-kubevip \
        addon-ingress-nginx addon-cert-manager addon-nfs-server addon-csi-nfs addon-nfs \
        addon-istio addon-prometheus-stack addon-metrics-server \
        addon-argocd addon-longhorn addon-kubernetes-dashboard \
        addon-postgresql addon-mysql addon-databasus \
        addon-minio addon-velero addon-crowdsec \
        addon-loki addon-promtail addon-tempo addon-pushgateway \
        addon-harbor addon-gitea addon-owncloud addon-nextcloud \
        addon-csi-s3 addon-csi-ceph addon-csi-glusterfs addon-vaultwarden \
        addon-smtp-relay addon-vault addon-external-secrets \
        addon-jenkins addon-netbird addon-mediaserver addon-hysteria2-server addon-splitgw addon-ingress-proxypass addon-ingress-add-domains addon-yandex-dns-controller addon-technitium-dns \
        add-node remove-node \
        add-etcd-node remove-etcd-node \
        etcd-backup etcd-restore etcd-list-snapshots \
        upgrade uninstall health verify ping \
        shell lint check \
        molecule-k3s molecule-prometheus molecule-istio molecule-all molecule-lint \
        vault-create vault-edit vault-view vault-encrypt-string \
        vault-bootstrap-create vault-bootstrap-edit \
        clean clean-all \
        _check_env _check_image

# ── DEFAULT ───────────────────────────────────────────────────────────────────
.DEFAULT_GOAL := help

help: ## Показать эту справку
	@echo ""
	@printf "$(CYAN)$(BOLD)K3S Ansible Stack — управление через Docker$(NC)\n"
	@printf "$(CYAN)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━$(NC)\n"
	@echo ""
	@printf "$(BOLD)Первый запуск:$(NC)\n"
	@echo "  1. make setup                            — создать .env из шаблона"
	@echo "  2. Отредактируй .env и inventory/hosts.ini"
	@echo "  3. make build                            — собрать Docker образ"
	@echo "  4. make vault-create                     — создать vault с K3S токеном"
	@echo "  5. make vault-bootstrap-create NODE=...  — создать vault с паролями нод"
	@echo "  6. make bootstrap                        — создать пользователя + задеплоить SSH ключ"
	@echo "  7. make ping                             — проверить SSH"
	@echo "  8. make install                          — развернуть полный стек"
	@echo ""
	@printf "$(BOLD)Все команды:$(NC)\n"
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
		| grep -v '^_' \
		| awk 'BEGIN {FS = ":.*?## "}; {printf "  $(CYAN)%-26s$(NC) %s\n", $$1, $$2}'
	@echo ""
	@printf "$(BOLD)Переменные (передаются в командной строке):$(NC)\n"
	@printf "  $(CYAN)VERSION$(NC)=v1.30.0+k3s1        версия K3S для upgrade\n"
	@printf "  $(CYAN)NODE$(NC)=master04               нода для add-node/remove-node\n"
	@printf "  $(CYAN)SNAPSHOT$(NC)=k3s-etcd-...db     снимок для etcd-restore\n"
	@printf "  $(CYAN)ETCD_COPY$(NC)=true               скопировать backup локально\n"
	@printf "  $(CYAN)ANSIBLE_VERBOSITY$(NC)=2          уровень debug вывода (0-4)\n"
	@printf "  $(CYAN)ANSIBLE_TAGS$(NC)=k3s,kube_vip   запустить только теги\n"
	@echo ""

# ═══════════════════════════════════════════════════════════════════════════════
# НАСТРОЙКА
# ═══════════════════════════════════════════════════════════════════════════════

setup: ## Первоначальная настройка: создать .env из шаблона
	@if [ -f .env ]; then \
		printf "$(YELLOW)⚠ .env уже существует, пропускаю$(NC)\n"; \
	else \
		cp .env.example .env; \
		printf "$(GREEN)✓ Создан .env$(NC)\n"; \
	fi
	@echo ""
	@printf "$(BOLD)Следующие шаги:$(NC)\n"
	@echo "  1. nano .env                       — задай VAULT_PASSWORD"
	@echo "  2. nano inventory/hosts.ini        — укажи IP серверов"
	@echo "  3. nano group_vars/all/main.yml    — задай kube_vip_address и интерфейс"
	@echo "  4. make build                      — собери Docker образ"
	@echo "  5. make vault-create               — создай vault"
	@echo "  6. make ping                       — проверь SSH"
	@echo "  7. make install                    — разверни стек"

# ═══════════════════════════════════════════════════════════════════════════════
# DOCKER ОБРАЗ
# ═══════════════════════════════════════════════════════════════════════════════

build: ## Собрать Docker образ ansible-runner
	@printf "$(CYAN)Сборка образа $(IMAGE_NAME)...$(NC)\n"
	docker build \
		--build-arg HELM_VERSION=3.14.4 \
		--build-arg KUBECTL_VERSION=v1.29.3 \
		-t $(IMAGE_NAME):latest \
		-f Dockerfile .
	@printf "$(GREEN)✓ Образ $(IMAGE_NAME):latest собран$(NC)\n"

rebuild: ## Пересобрать образ без кэша
	@printf "$(CYAN)Пересборка без кэша...$(NC)\n"
	docker build --no-cache \
		--build-arg HELM_VERSION=3.14.4 \
		--build-arg KUBECTL_VERSION=v1.29.3 \
		-t $(IMAGE_NAME):latest \
		-f Dockerfile .
	@printf "$(GREEN)✓ Готово$(NC)\n"

# ═══════════════════════════════════════════════════════════════════════════════
# ПРОВЕРКИ
# ═══════════════════════════════════════════════════════════════════════════════

ping: _check_env _check_image ## Проверить SSH доступность всех нод
	@printf "$(CYAN)Проверка SSH...$(NC)\n"
	$(DOCKER_RUN) ping

check: _check_env _check_image ## Dry-run: проверить плейбук без изменений
	@printf "$(CYAN)Dry-run...$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/site.yml --check --diff

lint: _check_image ## Проверить синтаксис всех плейбуков
	@printf "$(CYAN)Проверка синтаксиса...$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/site.yml --syntax-check
	@printf "$(GREEN)✓ Синтаксис корректен$(NC)\n"

# ═══════════════════════════════════════════════════════════════════════════════
# УСТАНОВКА
# ═══════════════════════════════════════════════════════════════════════════════

# ═══════════════════════════════════════════════════════════════════════════════
# BOOTSTRAP — первичная настройка нод (создание пользователя + SSH ключ)
# ═══════════════════════════════════════════════════════════════════════════════

bootstrap: _check_env _check_image ## Первый запуск: создать пользователя и задеплоить SSH ключ на все ноды
	@printf "$(CYAN)$(BOLD)Bootstrap нод (пользователь + SSH ключ)...$(NC)\n"
	@printf "$(YELLOW)Нужны: host_vars/<node>/vault.yml с bootstrap_user и bootstrap_password$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/bootstrap.yml \
		$(if $(NODE),-e "node_to_bootstrap=$(NODE)",)

vault-bootstrap-create: _check_image ## Создать vault с bootstrap credentials для ноды (NODE=master01)
	@if [ -z "$(NODE)" ]; then \
		printf "$(RED)✗ Укажи ноду: make vault-bootstrap-create NODE=master01$(NC)\n"; \
		exit 1; \
	fi
	@if [ ! -f "host_vars/$(NODE)/vault.yml.example" ]; then \
		printf "$(RED)✗ Не найден host_vars/$(NODE)/vault.yml.example$(NC)\n"; \
		exit 1; \
	fi
	@cp host_vars/$(NODE)/vault.yml.example host_vars/$(NODE)/vault.yml
	docker run --rm -it \
		-v $(PWD):/ansible \
		-e VAULT_PASSWORD="$(VAULT_PASSWORD)" \
		$(IMAGE_NAME) \
		ansible vault encrypt host_vars/$(NODE)/vault.yml
	@printf "$(GREEN)✓ Vault host_vars/$(NODE)/vault.yml создан$(NC)\n"

vault-bootstrap-edit: _check_image ## Редактировать bootstrap vault ноды (NODE=master01)
	@if [ -z "$(NODE)" ]; then \
		printf "$(RED)✗ Укажи ноду: make vault-bootstrap-edit NODE=master01$(NC)\n"; \
		exit 1; \
	fi
	docker run --rm -it \
		-v $(PWD):/ansible \
		-e VAULT_PASSWORD="$(VAULT_PASSWORD)" \
		$(IMAGE_NAME) \
		ansible vault edit host_vars/$(NODE)/vault.yml

k8s-user: _check_env _check_image ## Создать k8s пользователя + разложить SSH ключи на все ноды (cluster + lab_hosts)
	@printf "$(CYAN)$(BOLD)Настройка k8s пользователя и SSH ключей...$(NC)\n"
	@printf "$(YELLOW)Нужны: host_vars/<node>/vault.yml с bootstrap_user и bootstrap_password для lab_hosts$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/k8s-user.yml \
		$(if $(NODE),-e "node_to_limit=$(NODE)" --limit $(NODE),)

mdadm: _check_env _check_image ## Найти RAID массив и смонтировать в /storage (mdadm_enabled: true)
	@printf "$(CYAN)Настройка mdadm RAID...$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/mdadm.yml \
		$(if $(NODE),--limit $(NODE),)

chrony: _check_env _check_image ## Установить и настроить chrony (синхронизация времени + часовой пояс)
	@printf "$(CYAN)Настройка chrony (timezone: $(or $(TZ),из group_vars))...$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/site.yml --tags chrony \
		$(if $(TZ),-e chrony_timezone=$(TZ),) \
		$(if $(NODE),--limit $(NODE),)

k3s-certs: _check_env _check_image ## Установить systemd таймер автоматической ротации сертификатов K3S
	@printf "$(CYAN)Настройка автоматической ротации сертификатов K3S...$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/k3s-certs.yml \
		$(if $(NODE),--limit $(NODE),)

install: _check_env _check_image ## Развернуть core кластер (K3S + kube-vip + сертификаты)
	@printf "$(CYAN)$(BOLD)Разворачиваю K3S core кластер...$(NC)\n"
	$(DOCKER_RUN) install

install-full: _check_env _check_image ## Полный стек: core + аддоны по флагам из group_vars/all/addons.yml
	@printf "$(CYAN)$(BOLD)Полный стек: core + аддоны из addons.yml...$(NC)\n"
	$(DOCKER_RUN) install
	$(DOCKER_RUN) install-addons

install-k3s: _check_env _check_image ## Установить только K3S кластер
	@printf "$(CYAN)Устанавливаю K3S...$(NC)\n"
	$(DOCKER_RUN) install-k3s

install-cni: _check_env _check_image ## Установить CNI плагин (задай K3S_CNI=calico|cilium)
	@printf "$(CYAN)Устанавливаю CNI ($(or $(K3S_CNI),flannel))...$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/site.yml --tags cni $(if $(K3S_CNI),-e k3s_cni=$(K3S_CNI),)

install-kubevip: _check_env _check_image ## Установить только kube-vip
	@printf "$(CYAN)Устанавливаю kube-vip...$(NC)\n"
	$(DOCKER_RUN) install-kubevip

install-addons: _check_env _check_image ## Установить аддоны по флагам из group_vars/all/addons.yml
	@printf "$(CYAN)Устанавливаю аддоны по addons.yml...$(NC)\n"
	$(DOCKER_RUN) install-addons

install-etcd: _check_env _check_image ## Развернуть внешний etcd кластер (k3s_etcd_type: external)
	@printf "$(CYAN)Развёртываю внешний etcd кластер...$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/site.yml --tags etcd $(ARGS)

# ═══════════════════════════════════════════════════════════════════════════════
# АДДОНЫ — дополнительные компоненты кластера (addons/<name>/playbook.yml)
# Кастомизация: make addon-<name> ARGS="-e var=value"
# Добавить новый аддон: создай addons/<name>/playbook.yml + addons/<name>/role/
# ═══════════════════════════════════════════════════════════════════════════════

# ── Сетевые и хранилище ───────────────────────────────────────────────────────
addon-nfs-server: _check_env _check_image ## Установить NFS сервер (запускается на группе nfs_server)
	@printf "$(CYAN)Устанавливаю NFS сервер...$(NC)\n"
	$(DOCKER_RUN) addon nfs-server $(ARGS)

addon-csi-nfs: _check_env _check_image ## Установить CSI NFS Driver + StorageClass
	@printf "$(CYAN)Устанавливаю CSI NFS Driver...$(NC)\n"
	$(DOCKER_RUN) addon csi-nfs $(ARGS)

addon-nfs: _check_env _check_image ## Установить NFS сервер + CSI Driver (комплект)
	@printf "$(CYAN)$(BOLD)Устанавливаю NFS сервер + CSI Driver...$(NC)\n"
	$(DOCKER_RUN) addon nfs-server $(ARGS)
	$(DOCKER_RUN) addon csi-nfs $(ARGS)

addon-ingress-nginx: _check_env _check_image ## Установить ingress-nginx — Ingress controller
	@printf "$(CYAN)Устанавливаю ingress-nginx...$(NC)\n"
	$(DOCKER_RUN) addon ingress-nginx $(ARGS)

# ── TLS и безопасность ────────────────────────────────────────────────────────
addon-cert-manager: _check_env _check_image ## Установить cert-manager — TLS сертификаты (Let's Encrypt / self-signed)
	@printf "$(CYAN)Устанавливаю cert-manager...$(NC)\n"
	$(DOCKER_RUN) addon cert-manager $(ARGS)

# ── Мониторинг ────────────────────────────────────────────────────────────────
addon-prometheus-stack: _check_env _check_image ## Установить Prometheus + Grafana + Alertmanager
	@printf "$(CYAN)Устанавливаю kube-prometheus-stack...$(NC)\n"
	$(DOCKER_RUN) addon prometheus-stack $(ARGS)

addon-metrics-server: _check_env _check_image ## Установить metrics-server (kubectl top nodes/pods, HPA)
	@printf "$(CYAN)Устанавливаю metrics-server...$(NC)\n"
	$(DOCKER_RUN) addon metrics-server $(ARGS)

# ── Service Mesh ──────────────────────────────────────────────────────────────
addon-istio: _check_env _check_image ## Установить Istio + Kiali (ARGS="-e kiali_enabled=true" для Kiali)
	@printf "$(CYAN)Устанавливаю Istio...$(NC)\n"
	$(DOCKER_RUN) addon istio $(ARGS)

# ── GitOps и прочее ───────────────────────────────────────────────────────────
addon-argocd: _check_env _check_image ## Установить ArgoCD — GitOps CD (ARGS="-e argocd_ingress_enabled=true -e argocd_ingress_host=argocd.example.com")
	@printf "$(CYAN)Устанавливаю ArgoCD...$(NC)\n"
	$(DOCKER_RUN) addon argocd $(ARGS)

addon-longhorn: _check_env _check_image ## Установить Longhorn — distributed block storage (ARGS="-e longhorn_default_replica_count=1" для single-node)
	@printf "$(CYAN)Устанавливаю Longhorn...$(NC)\n"
	$(DOCKER_RUN) addon longhorn $(ARGS)

addon-kubernetes-dashboard: _check_env _check_image ## Установить Kubernetes Dashboard — веб UI кластера
	@printf "$(CYAN)Устанавливаю Kubernetes Dashboard...$(NC)\n"
	$(DOCKER_RUN) addon kubernetes-dashboard $(ARGS)

# ── Базы данных ───────────────────────────────────────────────────────────────
addon-postgresql: _check_env _check_image ## Установить PostgreSQL (Bitnami; ARGS="-e postgresql_storage_size=20Gi")
	@printf "$(CYAN)Устанавливаю PostgreSQL...$(NC)\n"
	$(DOCKER_RUN) addon postgresql $(ARGS)

addon-mysql: _check_env _check_image ## Установить MySQL (Bitnami; ARGS="-e mysql_storage_size=20Gi")
	@printf "$(CYAN)Устанавливаю MySQL...$(NC)\n"
	$(DOCKER_RUN) addon mysql $(ARGS)

addon-databasus: _check_env _check_image ## Установить Databasus — управление резервными копиями БД (ARGS="-e databasus_ingress_host=backup.example.com")
	@printf "$(CYAN)Устанавливаю Databasus...$(NC)\n"
	$(DOCKER_RUN) addon databasus $(ARGS)

# ── Объектное хранилище и backup ──────────────────────────────────────────────
addon-minio: _check_env _check_image ## Установить MinIO — S3 объектное хранилище (ARGS="-e minio_storage_size=20Gi")
	@printf "$(CYAN)Устанавливаю MinIO...$(NC)\n"
	$(DOCKER_RUN) addon minio $(ARGS)

addon-velero: _check_env _check_image ## Установить Velero — backup кластера + PVC через S3/MinIO
	@printf "$(CYAN)Устанавливаю Velero...$(NC)\n"
	$(DOCKER_RUN) addon velero $(ARGS)

# ── Безопасность ──────────────────────────────────────────────────────────────
addon-crowdsec: _check_env _check_image ## Установить CrowdSec — обнаружение вторжений (ARGS="-e crowdsec_nginx_bouncer_enabled=true")
	@printf "$(CYAN)Устанавливаю CrowdSec...$(NC)\n"
	$(DOCKER_RUN) addon crowdsec $(ARGS)

# ── Приложения ────────────────────────────────────────────────────────────────
addon-harbor: _check_env _check_image ## Установить Harbor — container registry (ARGS="-e harbor_ingress_host=harbor.example.com")
	@printf "$(CYAN)Устанавливаю Harbor...$(NC)\n"
	$(DOCKER_RUN) addon harbor $(ARGS)

addon-gitea: _check_env _check_image ## Установить Gitea — Git hosting (авто-обновление: gitea_version='')
	@printf "$(CYAN)Устанавливаю Gitea...$(NC)\n"
	$(DOCKER_RUN) addon gitea $(ARGS)

addon-owncloud: _check_env _check_image ## Установить ownCloud OCIS — файловое хранилище (авто-обновление: owncloud_version='')
	@printf "$(CYAN)Устанавливаю ownCloud OCIS...$(NC)\n"
	$(DOCKER_RUN) addon owncloud $(ARGS)

addon-nextcloud: _check_env _check_image ## Установить Nextcloud — файловое хранилище (авто-обновление: nextcloud_version='')
	@printf "$(CYAN)Устанавливаю Nextcloud...$(NC)\n"
	$(DOCKER_RUN) addon nextcloud $(ARGS)

# ── Observability (logging / tracing / metrics) ───────────────────────────────
addon-loki: _check_env _check_image ## Установить Loki — агрегация логов (ARGS="-e loki_storage_type=s3" для MinIO)
	@printf "$(CYAN)Устанавливаю Loki...$(NC)\n"
	$(DOCKER_RUN) addon loki $(ARGS)

addon-promtail: _check_env _check_image ## Установить Promtail — агент сбора логов → Loki
	@printf "$(CYAN)Устанавливаю Promtail...$(NC)\n"
	$(DOCKER_RUN) addon promtail $(ARGS)

addon-tempo: _check_env _check_image ## Установить Tempo — distributed tracing (OTLP/Jaeger/Zipkin)
	@printf "$(CYAN)Устанавливаю Tempo...$(NC)\n"
	$(DOCKER_RUN) addon tempo $(ARGS)

addon-pushgateway: _check_env _check_image ## Установить Prometheus Pushgateway — метрики batch-задач и скриптов
	@printf "$(CYAN)Устанавливаю Pushgateway...$(NC)\n"
	$(DOCKER_RUN) addon pushgateway $(ARGS)

addon-csi-s3: _check_env _check_image ## Установить CSI S3 Driver — монтирование S3/MinIO бакетов как PVC (авто-MinIO при addon_minio: true)
	@printf "$(CYAN)Устанавливаю CSI S3 Driver...$(NC)\n"
	$(DOCKER_RUN) addon csi-s3 $(ARGS)

addon-csi-ceph: _check_env _check_image ## Установить Rook-Ceph — distributed block (RWO) + filesystem (RWX) storage
	@printf "$(CYAN)Устанавливаю Rook-Ceph...$(NC)\n"
	$(DOCKER_RUN) addon csi-ceph $(ARGS)

addon-csi-glusterfs: _check_env _check_image ## Установить CSI GlusterFS Driver (требует внешний GlusterFS + Heketi, ARGS="-e csi_glusterfs_heketi_url=...")
	@printf "$(CYAN)Устанавливаю CSI GlusterFS Driver...$(NC)\n"
	$(DOCKER_RUN) addon csi-glusterfs $(ARGS)

addon-vaultwarden: _check_env _check_image ## Установить Vaultwarden — self-hosted Bitwarden (ARGS="-e vaultwarden_ingress_host=vault.example.com")
	@printf "$(CYAN)Устанавливаю Vaultwarden...$(NC)\n"
	$(DOCKER_RUN) addon vaultwarden $(ARGS)

addon-smtp-relay: _check_env _check_image ## Установить SMTP Relay — Postfix → Yandex (уведомления из подов без внешней авторизации)
	@printf "$(CYAN)Устанавливаю SMTP Relay...$(NC)\n"
	$(DOCKER_RUN) addon smtp-relay $(ARGS)

addon-vault: _check_env _check_image ## Установить HashiCorp Vault — менеджер секретов (ARGS="-e vault_mode=ha -e vault_auto_unseal_type=k8s")
	@printf "$(CYAN)Устанавливаю HashiCorp Vault...$(NC)\n"
	$(DOCKER_RUN) addon vault $(ARGS)

addon-external-secrets: _check_env _check_image ## Установить External Secrets Operator → Vault/AWS/GCP (ARGS="-e external_secrets_vault_role_id=...")
	@printf "$(CYAN)Устанавливаю External Secrets Operator...$(NC)\n"
	$(DOCKER_RUN) addon external-secrets $(ARGS)

addon-jenkins: _check_env _check_image ## Установить Jenkins CI/CD (ARGS="-e jenkins_ingress_host=jenkins.example.com")
	@printf "$(CYAN)Устанавливаю Jenkins...$(NC)\n"
	$(DOCKER_RUN) addon jenkins $(ARGS)

addon-netbird: _check_env _check_image ## Установить NetBird VPN — management+signal+coturn+kube-vip (ARGS="-e netbird_domain=netbird.example.com -e netbird_subnet_router_enabled=true")
	@printf "$(CYAN)Устанавливаю NetBird VPN...$(NC)\n"
	$(DOCKER_RUN) addon netbird $(ARGS)

addon-mediaserver: _check_env _check_image ## Установить MediaServer — Plex, Sonarr, Radarr, Lidarr, Bazarr, Prowlarr+Hysteria2, Overseerr, Transmission, Samba (ARGS="-e mediaserver_hysteria2_enabled=false" без прокси)
	@printf "$(CYAN)Устанавливаю MediaServer стек...$(NC)\n"
	$(DOCKER_RUN) addon mediaserver $(ARGS)

addon-hysteria2-server: _check_env _check_image ## Установить Hysteria2 VPN сервер на удалённый VPS из группы [hysteria2_server] (ARGS="-k" для SSH пароля, ARGS="-k -K" для SSH+sudo)
	@printf "$(CYAN)Устанавливаю Hysteria2 сервер на удалённый VPS...$(NC)\n"
	$(DOCKER_RUN) addon hysteria2-server $(ARGS)

addon-splitgw: _check_env _check_image ## Установить Split Gateway — sing-box+Hysteria2 TPROXY (группа [splitgw]; ARGS="-e splitgw_deploy_mode=k8s" для K8s DaemonSet)
	@printf "$(CYAN)Устанавливаю Split Gateway (sing-box + Hysteria2)...$(NC)\n"
	$(DOCKER_RUN) addon splitgw $(ARGS)

addon-ingress-proxypass: _check_env _check_image ## Проксировать внешние сервисы через ingress-nginx (ARGS="-e ingress_proxypass_vip=192.168.1.x")
	@printf "$(CYAN)Устанавливаю External Services Ingress Proxy...$(NC)\n"
	$(DOCKER_RUN) addon ingress-proxypass $(ARGS)

addon-ingress-add-domains: _check_env _check_image ## Добавить домены к существующим сервисам кластера (Ingress-only)
	@printf "$(CYAN)Устанавливаю ingress-add-domains...$(NC)\n"
	$(DOCKER_RUN) addon ingress-add-domains $(ARGS)

addon-yandex-dns-controller: _check_env _check_image ## Yandex 360 DNS controller (safe mode, ARGS="-e yandex_dns_controller_dry_run=true")
	@printf "$(CYAN)Устанавливаю Yandex 360 DNS Controller...$(NC)\n"
	$(DOCKER_RUN) addon yandex-dns-controller $(ARGS)

addon-technitium-dns: _check_env _check_image ## Technitium DNS HA — Primary+Secondary, kube-vip LB, zone sync
	@printf "$(CYAN)Устанавливаю Technitium DNS HA...$(NC)\n"
	$(DOCKER_RUN) addon technitium-dns $(ARGS)

# Generic цель — любой аддон из addons/<name>/playbook.yml
addon-%: _check_env _check_image
	@if [ ! -f "addons/$*/playbook.yml" ]; then \
		printf "$(RED)✗ Аддон '$*' не найден. Ожидается: addons/$*/playbook.yml$(NC)\n"; \
		printf "$(YELLOW)  Доступные аддоны:$(NC) "; \
		ls addons/ 2>/dev/null | tr '\n' ' '; echo ""; \
		exit 1; \
	fi
	@printf "$(CYAN)Устанавливаю аддон $*...$(NC)\n"
	$(DOCKER_RUN) addon $* $(ARGS)

# ═══════════════════════════════════════════════════════════════════════════════
# МАСШТАБИРОВАНИЕ КЛАСТЕРА
# ═══════════════════════════════════════════════════════════════════════════════

add-node: _check_env _check_image ## Добавить ноду: make add-node NODE=master04
	@if [ -z "$(NODE)" ]; then \
		printf "$(RED)✗ Укажи ноду: make add-node NODE=<nodename>$(NC)\n"; \
		exit 1; \
	fi
	@printf "$(CYAN)Добавляю ноду $(NODE) в кластер...$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/add-node.yml -e "node_to_add=$(NODE)"

remove-node: _check_env _check_image ## Удалить ноду: make remove-node NODE=worker04
	@if [ -z "$(NODE)" ]; then \
		printf "$(RED)✗ Укажи ноду: make remove-node NODE=<nodename>$(NC)\n"; \
		exit 1; \
	fi
	@printf "$(RED)$(BOLD)Удаление ноды $(NODE) из кластера...$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/remove-node.yml -e "node_to_remove=$(NODE)"

add-etcd-node: _check_env _check_image ## Добавить etcd ноду: make add-etcd-node NODE=etcd04
	@if [ -z "$(NODE)" ]; then \
		printf "$(RED)✗ Укажи ноду: make add-etcd-node NODE=<nodename>$(NC)\n"; \
		printf "$(YELLOW)  Нода должна быть в [etcd_nodes] в inventory$(NC)\n"; \
		exit 1; \
	fi
	@printf "$(CYAN)Добавляю etcd ноду $(NODE)...$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/add-etcd-node.yml -e "node_to_add=$(NODE)" $(ARGS)

remove-etcd-node: _check_env _check_image ## Удалить etcd ноду: make remove-etcd-node NODE=etcd04
	@if [ -z "$(NODE)" ]; then \
		printf "$(RED)✗ Укажи ноду: make remove-etcd-node NODE=<nodename>$(NC)\n"; \
		exit 1; \
	fi
	@printf "$(RED)$(BOLD)Удаление etcd ноды $(NODE) из кластера...$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/remove-etcd-node.yml -e "node_to_remove=$(NODE)" $(ARGS)

# ═══════════════════════════════════════════════════════════════════════════════
# РЕЗЕРВНОЕ КОПИРОВАНИЕ ETCD
# ═══════════════════════════════════════════════════════════════════════════════

etcd-backup: _check_env _check_image ## Создать снимок etcd (make etcd-backup [SNAPSHOT=name] [ETCD_COPY=true])
	@printf "$(CYAN)Создаю снимок etcd...$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/etcd-backup.yml \
		$(if $(SNAPSHOT),-e etcd_backup_name=$(SNAPSHOT),) \
		$(if $(ETCD_COPY),-e etcd_backup_copy_to_local=$(ETCD_COPY),)

etcd-restore: _check_env _check_image ## Восстановить etcd: make etcd-restore SNAPSHOT=k3s-etcd-20250101.db
	@if [ -z "$(SNAPSHOT)" ]; then \
		printf "$(RED)✗ Укажи снимок: make etcd-restore SNAPSHOT=<filename>$(NC)\n"; \
		printf "$(YELLOW)  Список снимков: make etcd-list-snapshots$(NC)\n"; \
		exit 1; \
	fi
	@printf "$(RED)$(BOLD)ВНИМАНИЕ: восстановление etcd перезапишет данные кластера!$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/etcd-restore.yml \
		-e "etcd_restore_snapshot=$(SNAPSHOT)" \
		$(if $(FORCE),-e etcd_restore_force=true,)

etcd-list-snapshots: _check_env _check_image ## Показать доступные снимки etcd
	@printf "$(CYAN)Список снимков etcd...$(NC)\n"
	$(DOCKER_RUN) ansible-playbook playbooks/etcd-restore.yml --tags list

# ═══════════════════════════════════════════════════════════════════════════════
# ОБНОВЛЕНИЕ
# ═══════════════════════════════════════════════════════════════════════════════

upgrade: _check_env _check_image ## Обновить K3S (make upgrade VERSION=v1.30.0+k3s1)
	@if [ -z "$(VERSION)" ]; then \
		printf "$(RED)✗ Укажи версию: make upgrade VERSION=v1.30.0+k3s1$(NC)\n"; \
		exit 1; \
	fi
	@printf "$(CYAN)Обновляю K3S до $(VERSION)...$(NC)\n"
	$(DOCKER_RUN) upgrade

# ═══════════════════════════════════════════════════════════════════════════════
# ДИАГНОСТИКА
# ═══════════════════════════════════════════════════════════════════════════════

health: _check_env _check_image ## Диагностика кластера (сервисы, поды, диск, память)
	@printf "$(CYAN)Диагностика...$(NC)\n"
	$(DOCKER_RUN) health

verify: _check_env _check_image ## Проверить весь стек (nodes/pods/svc/sc/ingress)
	@printf "$(CYAN)Проверка стека...$(NC)\n"
	$(DOCKER_RUN) verify

# ═══════════════════════════════════════════════════════════════════════════════
# УДАЛЕНИЕ
# ═══════════════════════════════════════════════════════════════════════════════

uninstall: _check_env _check_image ## ВНИМАНИЕ: удалить весь стек и данные
	@printf "$(RED)$(BOLD)ВНИМАНИЕ: Удаление всего стека — данные будут потеряны!$(NC)\n"
	@printf "$(YELLOW)Введи 'yes' для подтверждения: $(NC)"; \
	read CONFIRM && [ "$$CONFIRM" = "yes" ] || (printf "Отменено.\n" && exit 1)
	$(DOCKER_RUN) uninstall

# ═══════════════════════════════════════════════════════════════════════════════
# ANSIBLE VAULT
# ═══════════════════════════════════════════════════════════════════════════════

vault-create: _check_image ## Создать зашифрованный vault (group_vars/all/vault.yml)
	@printf "$(CYAN)Создание vault...$(NC)\n"
	docker run --rm -it \
		-v $(PWD):/ansible \
		$(IMAGE_NAME) \
		ansible vault create group_vars/all/vault.yml
	@printf "$(GREEN)✓ Vault создан$(NC)\n"

vault-edit: _check_image ## Редактировать vault
	docker run --rm -it \
		-v $(PWD):/ansible \
		-e VAULT_PASSWORD="$(VAULT_PASSWORD)" \
		$(IMAGE_NAME) \
		ansible vault edit group_vars/all/vault.yml

vault-view: _check_image ## Просмотреть vault (расшифрованный вывод)
	docker run --rm -it \
		-v $(PWD):/ansible \
		-e VAULT_PASSWORD="$(VAULT_PASSWORD)" \
		$(IMAGE_NAME) \
		ansible vault view group_vars/all/vault.yml

vault-encrypt-string: _check_image ## Зашифровать строку: make vault-encrypt-string STR=... NAME=...
	@if [ -z "$(STR)" ]; then \
		printf "$(RED)✗ Нужна строка: make vault-encrypt-string STR=мой-токен NAME=vault_k3s_token$(NC)\n"; \
		exit 1; \
	fi
	docker run --rm -it \
		-v $(PWD):/ansible \
		-e VAULT_PASSWORD="$(VAULT_PASSWORD)" \
		$(IMAGE_NAME) \
		ansible vault encrypt_string '$(STR)' --name '$(or $(NAME),encrypted_var)'

# ═══════════════════════════════════════════════════════════════════════════════
# MOLECULE — ТЕСТИРОВАНИЕ РОЛЕЙ
# Требования: pip install -r requirements-python.txt  (molecule, molecule-plugins[docker])
# ═══════════════════════════════════════════════════════════════════════════════

molecule-k3s: _check_image ## Тест роли k3s — 3 контейнера (Ubuntu+Debian), ~8-12 мин
	@printf "$(CYAN)Тестирую роль k3s (3 ноды: master01, worker01, rpi01)...$(NC)\n"
	$(DOCKER_RUN_MOLECULE) molecule k3s
	@printf "$(GREEN)✓ k3s role: OK$(NC)\n"

molecule-prometheus: _check_image ## Тест роли prometheus-stack (шаблоны + PVC), ~2-3 мин
	@printf "$(CYAN)Тестирую роль prometheus-stack...$(NC)\n"
	$(DOCKER_RUN_MOLECULE) molecule prometheus-stack
	@printf "$(GREEN)✓ prometheus-stack role: OK$(NC)\n"

molecule-istio: _check_image ## Тест роли istio + kiali (шаблоны), ~2-3 мин
	@printf "$(CYAN)Тестирую роль istio...$(NC)\n"
	$(DOCKER_RUN_MOLECULE) molecule istio
	@printf "$(GREEN)✓ istio role: OK$(NC)\n"

molecule-all: _check_image ## Запустить все Molecule тесты последовательно (~15-20 мин)
	@printf "$(CYAN)$(BOLD)Запуск всех Molecule тестов...$(NC)\n"
	$(MAKE) molecule-k3s
	$(MAKE) molecule-prometheus
	$(MAKE) molecule-istio
	@printf "$(GREEN)$(BOLD)✓ Все тесты прошли успешно$(NC)\n"

molecule-lint: _check_image ## Линтинг (yamllint + ansible-lint) в контейнере, ~30 сек
	@printf "$(CYAN)Запуск линтинга...$(NC)\n"
	$(DOCKER_RUN_MOLECULE) molecule-lint
	@printf "$(GREEN)✓ Линтинг прошёл$(NC)\n"

# ═══════════════════════════════════════════════════════════════════════════════
# ИНТЕРАКТИВНЫЙ SHELL
# ═══════════════════════════════════════════════════════════════════════════════

shell: _check_image ## Запустить интерактивный bash внутри контейнера
	@printf "$(CYAN)Запуск shell (exit для выхода)...$(NC)\n"
	docker run --rm -it \
		--name $(CONTAINER_NAME)-shell \
		--network host \
		-v $(PWD):/ansible \
		-v $(or $(SSH_KEY_PATH),$(HOME)/.ssh):/root/.ssh:ro \
		-e VAULT_PASSWORD="$(VAULT_PASSWORD)" \
		-e ANSIBLE_FORCE_COLOR=1 \
		$(IMAGE_NAME) shell

# ═══════════════════════════════════════════════════════════════════════════════
# ОЧИСТКА
# ═══════════════════════════════════════════════════════════════════════════════

clean: ## Удалить Docker образ
	@printf "$(YELLOW)Удаляю образ $(IMAGE_NAME)...$(NC)\n"
	docker rmi $(IMAGE_NAME):latest 2>/dev/null || printf "$(YELLOW)Образ не найден$(NC)\n"

clean-all: clean ## Удалить образ + kubeconfig + кэш Docker
	rm -f kubeconfig
	docker system prune -f
	@printf "$(GREEN)✓ Очистка завершена$(NC)\n"

# ═══════════════════════════════════════════════════════════════════════════════
# ВНУТРЕННИЕ
# ═══════════════════════════════════════════════════════════════════════════════

_check_env:
	@if [ ! -f .env ]; then \
		printf "$(RED)✗ Файл .env не найден! Запусти: make setup$(NC)\n"; \
		exit 1; \
	fi

_check_image:
	@if ! docker image inspect $(IMAGE_NAME):latest > /dev/null 2>&1; then \
		printf "$(YELLOW)⚠ Образ не найден, собираю...$(NC)\n"; \
		$(MAKE) build; \
	fi


