Files
infra/ansible/Makefile
T
DmitryandClaude Sonnet 5 e0c53a1b1b chore: relocate .env to the repository root and refresh Make targets
.env is now consumed by both Ansible and OpenTofu, so keep a single copy at
the repo root instead of ansible/.env:

- rename ansible/.env.example -> .env.example.
- Makefile: ENV_FILE ?= $(REPO_ROOT)/.env (absolute, works from any cwd);
  REQUIRE_ENV/LOAD_ENV updated; help text.
- bootstrap-pve-api-token.yml / bootstrap-monitoring-pve-token.yml write and
  read ../../.env; bootstrap now keeps backup: true (it rewrites the whole
  file, clobbering MONITORING_*/EMERGENCY_*/PROXMOX_ROOT_PASSWORD).
- roles/pve_lxc, roles/monitoring_server: fail_msg points at the repo-root .env.

The Makefile also picks up the new targets added by later commits
(tofu-*, validate, pbs-storage, ru-vps-base, zerotier-decommission); they are
grouped here so all recipes land together.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uoq5AVK8mkBgg83Mq6o5V
2026-09-03 07:04:12 +03:00

343 lines
18 KiB
Makefile
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HomeLab Ansible — единая точка входа для РУЧНОГО управления инфраструктурой.
#
# Запускать из каталога ansible/ (или через `make -C ansible <цель>`):
#
# make список целей
# make check базовая проверка связности
# make deploy-gitea playbooks/pve-gitea.yml с загруженным .env
# make dry-gitea то же самое в режиме --check --diff
# make update-gitea playbooks/gitea-update.yml
# make docs markdown-таблица хостов в stdout
#
# Дополнительные флаги ansible-playbook передаются через EXTRA:
#
# make check EXTRA="--limit pve_nodes -vv"
#
# Интерпретатор определяется автоматически: если рядом есть ./.venv — берётся он,
# иначе бинарь ищется в PATH (nix devshell, системный ansible). Любой путь можно
# переопределить переменной окружения, например:
#
# ANSIBLE_PLAYBOOK=$(which ansible-playbook) make check
SHELL := /bin/sh
.DEFAULT_GOAL := help
MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
ANSIBLE_DIR := $(patsubst %/,%,$(dir $(MAKEFILE_PATH)))
REPO_ROOT := $(abspath $(ANSIBLE_DIR)/..)
VENV_BIN := $(ANSIBLE_DIR)/.venv/bin
# Разрешение бинарей: сначала рабочий локальный venv, иначе PATH (nix devshell,
# системный ansible). VENV_OK проверяет, что интерпретатор venv реально запускается,
# — иначе сломанный .venv (например, после смены системного python) молча ломал бы
# все цели. Переменные окружения имеют приоритет благодаря `?=`.
VENV_OK := $(shell [ -x '$(VENV_BIN)/python3' ] && '$(VENV_BIN)/python3' -c '' >/dev/null 2>&1 && echo yes)
venv_bin = $(if $(VENV_OK),$(if $(wildcard $(VENV_BIN)/$(1)),$(VENV_BIN)/$(1),$(1)),$(1))
ANSIBLE_PLAYBOOK ?= $(call venv_bin,ansible-playbook)
ANSIBLE_INVENTORY_BIN ?= $(call venv_bin,ansible-inventory)
ANSIBLE_GALAXY ?= $(call venv_bin,ansible-galaxy)
ANSIBLE_LINT ?= $(call venv_bin,ansible-lint)
YAMLLINT ?= $(call venv_bin,yamllint)
# scripts/gen-inventory-docs.py обходится стандартной библиотекой, поэтому берём
# python3 из PATH, а не из .venv.
PYTHON ?= python3
# Секреты живут в КОРНЕ репозитория, а не в ansible/: их потребляет не только
# Ansible, но и OpenTofu (tofu/), а держать два файла или ходить в подкаталог
# из соседнего инструмента неудобно. Путь абсолютный, поэтому цели работают
# из любого cwd.
ENV_FILE ?= $(REPO_ROOT)/.env
# Дополнительные аргументы ansible-playbook для любой цели.
EXTRA ?=
# Спрашивать sudo-пароль там, где нужен become. `make openvpn ASK_BECOME=` отключает.
ASK_BECOME ?= -K
# Спрашивать пароль Ansible Vault. `make gyro ASK_VAULT=` отключает.
ASK_VAULT ?= --ask-vault-pass
# Строгая загрузка корневого .env: обязательна для Proxmox API и секретов.
# Каждая строка рецепта make — отдельный шелл, поэтому source и запуск идут одной строкой.
REQUIRE_ENV = if [ ! -f '$(ENV_FILE)' ]; then \
printf 'ОШИБКА: не найден %s\n' '$(ENV_FILE)' >&2; \
printf 'Создай его и заполни реальными значениями:\n' >&2; \
printf ' cp %s/.env.example %s\n' '$(REPO_ROOT)' '$(ENV_FILE)' >&2; \
printf 'Нужны PROXMOX_* (и MONITORING_* / EMERGENCY_* для профильных целей).\n' >&2; \
exit 1; \
fi; \
set -a; . '$(ENV_FILE)'; set +a
# Мягкая загрузка: .env подхватывается если есть, иначе просто предупреждение.
LOAD_ENV = if [ -f '$(ENV_FILE)' ]; then set -a; . '$(ENV_FILE)'; set +a; \
else printf 'ВНИМАНИЕ: %s не найден, продолжаю без него.\n' '$(ENV_FILE)' >&2; fi
# Защита от случайного запуска опасных плейбуков.
REQUIRE_CONFIRM = if [ "$(CONFIRM)" != "1" ]; then \
printf 'ОПАСНАЯ ЦЕЛЬ. Повтори явно: make $@ CONFIRM=1\n' >&2; \
exit 1; \
fi
# host_vars/gyro/vault.yml зашифрован Vault. Для чтения инвентаря он не нужен,
# поэтому vars-плагины отключаются, чтобы make не спрашивал пароль Vault.
INVENTORY_ENV = ANSIBLE_VARS_ENABLED= ANSIBLE_NOCOLOR=1
##@ Справка
.PHONY: help
help: ## Показать этот список целей
@awk 'BEGIN { FS = ":.*##"; \
print ""; \
print "HomeLab Ansible — ручное управление инфраструктурой"; \
print ""; \
print " Использование: make <цель> [EXTRA=\"--limit host -vv\"]"; \
print " Секреты берутся из .env в КОРНЕ репозитория"; \
} \
/^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5); next } \
/^[a-zA-Z0-9_%.-]+:.*##/ { printf " \033[36m%-24s\033[0m %s\n", $$1, $$2 } \
END { print "" }' $(MAKEFILE_PATH)
##@ Setup
.PHONY: setup
setup: ## Создать .venv, поставить requirements.txt и galaxy-коллекции (не нужно в nix)
$(PYTHON) -m venv $(ANSIBLE_DIR)/.venv
$(VENV_BIN)/pip install --upgrade pip
$(VENV_BIN)/pip install -r requirements.txt
$(VENV_BIN)/ansible-galaxy collection install -r requirements.yml -p collections
.PHONY: collections
collections: ## Доустановить только galaxy-коллекции из requirements.yml
$(ANSIBLE_GALAXY) collection install -r requirements.yml -p collections
.PHONY: env-check
env-check: ## Проверить наличие .env и заполненность ключевых переменных
@$(REQUIRE_ENV); \
rc=0; \
for v in PROXMOX_HOST PROXMOX_USER PROXMOX_TOKEN_ID PROXMOX_TOKEN_SECRET; do \
eval "val=\$$$$v"; \
if [ -z "$$val" ] || [ "$$val" = 'replace-me' ]; then \
printf 'не задано: %s\n' "$$v"; rc=1; \
else printf 'ok: %s\n' "$$v"; fi; \
done; \
exit $$rc
##@ Проверки и диагностика
.PHONY: check
check: ## Проверка связности и ожидаемых IP (playbooks/check.yml)
$(ANSIBLE_PLAYBOOK) playbooks/check.yml $(EXTRA)
.PHONY: status
status: ## Сводный статус инфраструктуры (playbooks/status.yml)
ANSIBLE_CALLBACK_RESULT_FORMAT=yaml $(ANSIBLE_PLAYBOOK) playbooks/status.yml $(EXTRA)
# Оба линтера ищут конфиг (.ansible-lint / .yamllint) в текущем каталоге, а не
# у родителей. Запуск из ansible/ конфиг не находил, из-за чего в проверку
# затягивался вендоренный collections/ и цель выдавала около тысячи чужих
# нарушений. Запускаем из корня — ровно как .gitea/workflows/lint.yml.
.PHONY: lint
lint: ## Прогнать ansible-lint, yamllint и syntax-check — то же, что делает CI
cd '$(REPO_ROOT)' && $(ANSIBLE_LINT)
cd '$(REPO_ROOT)' && $(YAMLLINT) .
@rc=0; for f in playbooks/*.yml; do \
if $(ANSIBLE_PLAYBOOK) --syntax-check "$$f" >/dev/null 2>&1; then \
printf 'ok %s\n' "$$f"; \
else \
rc=1; printf 'FAIL %s\n' "$$f"; $(ANSIBLE_PLAYBOOK) --syntax-check "$$f" 2>&1 | sed 's/^/ /'; \
fi; \
done; exit $$rc
.PHONY: docs
docs: ## Напечатать markdown-таблицу хостов и групп в stdout
@ANSIBLE_INVENTORY_BIN='$(ANSIBLE_INVENTORY_BIN)' $(PYTHON) scripts/gen-inventory-docs.py
.PHONY: inventory
inventory: ## Показать дерево инвентаря (ansible-inventory --graph)
@$(INVENTORY_ENV) $(ANSIBLE_INVENTORY_BIN) -i inventory/hosts.yml --graph
.PHONY: validate
validate: ## Сверить реестр homelab_services с фактическим состоянием Proxmox (gate)
$(ANSIBLE_PLAYBOOK) playbooks/validate.yml $(EXTRA)
.PHONY: openvpn-check
openvpn-check: ## Проверить OpenVPN-транспорт ru-vps <-> ovpn-mini
$(ANSIBLE_PLAYBOOK) playbooks/openvpn-check.yml $(EXTRA)
.PHONY: backup-audit
backup-audit: ## Аудит свежести бэкапов PBS и offsite restic
$(ANSIBLE_PLAYBOOK) playbooks/backup-audit.yml $(EXTRA)
dry-%: ## Прогон playbooks/pve-<имя>.yml в режиме --check --diff (пример: make dry-gitea)
@$(REQUIRE_ENV); $(ANSIBLE_PLAYBOOK) playbooks/pve-$*.yml --check --diff $(EXTRA)
##@ Деплой LXC/VM через Proxmox API (требует .env)
deploy-%: ## Применить playbooks/pve-<имя>.yml (пример: make deploy-gitea, deploy-adguard, deploy-monitoring)
@$(REQUIRE_ENV); $(ANSIBLE_PLAYBOOK) playbooks/pve-$*.yml $(EXTRA)
.PHONY: backup-jobs
backup-jobs: ## Настроить PBS backup jobs (playbooks/pve-backup-jobs.yml)
@$(REQUIRE_ENV); $(ANSIBLE_PLAYBOOK) playbooks/pve-backup-jobs.yml $(EXTRA)
.PHONY: pbs-storage
pbs-storage: ## Убрать storage-level prune policy с PBS storage (playbooks/pve-storage-pbs.yml)
$(ANSIBLE_PLAYBOOK) playbooks/pve-storage-pbs.yml $(EXTRA)
.PHONY: bootstrap-pve-token
bootstrap-pve-token: ## Выпустить Proxmox API-токен прямо с ноды (нужен sudo)
$(ANSIBLE_PLAYBOOK) playbooks/bootstrap-pve-api-token.yml $(ASK_BECOME) $(EXTRA)
.PHONY: bootstrap-monitoring-token
bootstrap-monitoring-token: ## Выпустить read-only PVE-токен для мониторинга
$(ANSIBLE_PLAYBOOK) playbooks/bootstrap-monitoring-pve-token.yml $(EXTRA)
.PHONY: bootstrap-ansible-user
bootstrap-ansible-user: ## Завести сервисный аккаунт ansible на shell-хостах (нужен sudo)
$(ANSIBLE_PLAYBOOK) playbooks/bootstrap-ansible-user.yml $(ASK_BECOME) $(EXTRA)
##@ Настройка сервисов
.PHONY: ru-vps-base
ru-vps-base: ## Базовое состояние ru-vps и стек Caddy (закрепляет образ, ставит homelab-caddy.service)
$(ANSIBLE_PLAYBOOK) playbooks/ru-vps-base.yml $(EXTRA)
.PHONY: dry-ru-vps-base
dry-ru-vps-base: ## Предпросмотр ru-vps-base.yml (--check --diff)
$(ANSIBLE_PLAYBOOK) playbooks/ru-vps-base.yml --check --diff $(EXTRA)
.PHONY: reverse-proxy
reverse-proxy: ## Единый reverse-proxy для gitea/vaultwarden/grimmory
@$(LOAD_ENV); $(ANSIBLE_PLAYBOOK) playbooks/reverse-proxy.yml $(EXTRA)
.PHONY: openvpn
openvpn: ## Поднять OpenVPN-транспорт ru-vps <-> ovpn-mini (нужен sudo)
$(ANSIBLE_PLAYBOOK) playbooks/openvpn-vps-mini.yml $(ASK_BECOME) $(EXTRA)
.PHONY: openvpn-laptop
openvpn-laptop: ## Настроить OpenVPN-профиль ноутбука
$(ANSIBLE_PLAYBOOK) playbooks/openvpn-laptop.yml $(EXTRA)
.PHONY: bash-config
bash-config: ## Раскатать единый bash-конфиг на shell_hosts
$(ANSIBLE_PLAYBOOK) playbooks/bash-config.yml $(EXTRA)
.PHONY: user-ssh-key
user-ssh-key: ## Разложить публичный SSH-ключ пользователя на все хосты
$(ANSIBLE_PLAYBOOK) playbooks/user-ssh-key.yml $(EXTRA)
.PHONY: emergency-access
emergency-access: ## Настроить emergency reverse-SSH и Telegram-бота (нужны EMERGENCY_* в .env)
@$(REQUIRE_ENV); $(ANSIBLE_PLAYBOOK) playbooks/emergency-access.yml $(EXTRA)
.PHONY: gyro
gyro: ## Настроить gyro-аллокатор в production CT 156 (Tofu-managed); CT 150 — остановленный rollback (спрашивает пароль Vault)
@$(REQUIRE_ENV); $(ANSIBLE_PLAYBOOK) playbooks/gyro.yml $(ASK_VAULT) $(EXTRA)
.PHONY: uptime-kuma
uptime-kuma: ## Развернуть/обновить Uptime Kuma на monitoring LXC
$(ANSIBLE_PLAYBOOK) playbooks/uptime-kuma.yml $(EXTRA)
.PHONY: offsite-restic
offsite-restic: ## Настроить offsite restic-бэкапы на Yandex.Disk
@$(LOAD_ENV); $(ANSIBLE_PLAYBOOK) playbooks/offsite-restic-yadisk.yml $(EXTRA)
play-%: ## Запустить произвольный playbooks/<имя>.yml с загруженным .env (пример: make play-check)
@$(LOAD_ENV); $(ANSIBLE_PLAYBOOK) playbooks/$*.yml $(EXTRA)
##@ Обновления сервисов (сначала свежий бэкап, потом апдейт, потом проверка)
update-%: ## Обновить сервис через playbooks/<имя>-update.yml (gitea, vaultwarden, adguard, mihomo, grimmory)
@$(LOAD_ENV); $(ANSIBLE_PLAYBOOK) playbooks/$*-update.yml $(EXTRA)
.PHONY: update-all
update-all: ## Последовательно обновить gitea, vaultwarden, adguard, mihomo, grimmory (требует CONFIRM=1)
@$(REQUIRE_CONFIRM)
$(MAKE) update-vaultwarden
$(MAKE) update-gitea
$(MAKE) update-adguard
$(MAKE) update-mihomo
$(MAKE) update-grimmory
##@ OpenTofu (пилот provisioning LXC)
TOFU_DIR := $(REPO_ROOT)/tofu
TOFU ?= tofu
# Tofu, в отличие от Ansible, ходит в Proxmox API напрямую по HTTPS и НЕ умеет
# ProxyJump из ssh_config. Вне локальной сети узлы недоступны (проверено:
# https://192.168.1.10:8006 отдаёт connection failure), поэтому на время команды
# поднимаем SSH-туннель через ru-vps и направляем провайдер в localhost.
# Изнутри LAN это тоже работает — просто лишний хоп, зато поведение одинаково.
TOFU_TUNNEL_PORT ?= 18006
TOFU_SSH_SOCKET := $(ANSIBLE_DIR)/.ansible/tofu-tunnel.sock
# insecure=true здесь не настройка вкуса: сертификат Proxmox выписан на узел,
# а обращаемся мы к 127.0.0.1, так что проверка имени не пройдёт в любом случае.
#
# Способ аутентификации выбирается автоматически: если в корневом .env задан
# PROXMOX_ROOT_PASSWORD — идём как root@pam (только так Proxmox разрешает
# features кроме nesting, device passthrough и bind mount каталога хоста),
# иначе токеном ansible@pve. Выбранный режим печатается в stderr, чтобы из
# вывода было видно, какими правами шёл apply.
TOFU_ENV = $(REQUIRE_ENV); \
mkdir -p '$(dir $(TOFU_SSH_SOCKET))'; \
ssh -F '$(ANSIBLE_DIR)/ssh_config' -M -S '$(TOFU_SSH_SOCKET)' -fN \
-L $(TOFU_TUNNEL_PORT):$$PROXMOX_HOST:$${PROXMOX_PORT:-8006} ru-vps; \
trap "ssh -S '$(TOFU_SSH_SOCKET)' -O exit ru-vps 2>/dev/null || true" EXIT; \
export TF_VAR_pve_endpoint="https://127.0.0.1:$(TOFU_TUNNEL_PORT)"; \
export TF_VAR_pve_insecure=true; \
if [ -n "$${PROXMOX_ROOT_PASSWORD:-}" ] && [ "$${PROXMOX_ROOT_PASSWORD}" != 'replace-me' ]; then \
export TF_VAR_pve_username="$${PROXMOX_ROOT_USER:-root@pam}"; \
export TF_VAR_pve_password="$$PROXMOX_ROOT_PASSWORD"; \
export TF_VAR_pve_api_token=""; \
printf 'Proxmox: аутентификация %s (привилегированный режим)\n' "$${PROXMOX_ROOT_USER:-root@pam}" >&2; \
else \
export TF_VAR_pve_api_token="$$PROXMOX_USER!$$PROXMOX_TOKEN_ID=$$PROXMOX_TOKEN_SECRET"; \
export TF_VAR_pve_username=""; \
export TF_VAR_pve_password=""; \
printf 'Proxmox: аутентификация токеном %s (features кроме nesting и dev-passthrough недоступны)\n' "$$PROXMOX_USER" >&2; \
fi
.PHONY: tofu-init
tofu-init: ## Скачать провайдер и инициализировать рабочий каталог tofu/
@cd '$(TOFU_DIR)' && $(TOFU) init -input=false
.PHONY: tofu-plan
tofu-plan: ## Показать план (read-only, ничего не меняет)
@$(TOFU_ENV); cd '$(TOFU_DIR)' && $(TOFU) plan -input=false
.PHONY: tofu-apply
tofu-apply: ## Применить план — СОЗДАЁТ гостей в Proxmox (требует CONFIRM=1)
@$(REQUIRE_CONFIRM)
@$(TOFU_ENV); cd '$(TOFU_DIR)' && $(TOFU) apply -input=false -auto-approve
.PHONY: tofu-destroy
tofu-destroy: ## УНИЧТОЖИТЬ всё, что создано в tofu/ (требует CONFIRM=1)
@$(REQUIRE_CONFIRM)
@printf 'Удаляет гостей Proxmox из состояния tofu вместе с их дисками.\n' >&2
@$(TOFU_ENV); cd '$(TOFU_DIR)' && $(TOFU) destroy -input=false -auto-approve
##@ Опасное (только осознанно, требует CONFIRM=1)
.PHONY: mihomo-harden
mihomo-harden: ## РОТИРУЕТ живые SOCKS-креды Mihomo на ru-vps и убирает публичный доступ
@$(REQUIRE_CONFIRM)
@printf 'Ротация кредов необратима для клиентов без отката бэкапа конфига.\n' >&2
$(ANSIBLE_PLAYBOOK) playbooks/ru-vps-mihomo-harden.yml -e ru_vps_mihomo_harden_confirm=true $(EXTRA)
.PHONY: zerotier-decommission
zerotier-decommission: ## Вывести ZeroTier с ru-vps и почистить его правила UFW (требует CONFIRM=1)
@$(REQUIRE_CONFIRM)
@printf 'Останавливает контейнер zerotier и удаляет правила UFW. Данные и файлы остаются.\n' >&2
$(ANSIBLE_PLAYBOOK) playbooks/ru-vps-zerotier-decommission.yml $(EXTRA)
.PHONY: dry-zerotier-decommission
dry-zerotier-decommission: ## Предпросмотр вывода ZeroTier (--check --diff)
$(ANSIBLE_PLAYBOOK) playbooks/ru-vps-zerotier-decommission.yml --check --diff $(EXTRA)
.PHONY: monitoring
monitoring: ## ЗАМОРОЖЕН: стек Prometheus. Запускать только при восстановлении мониторинга
@$(REQUIRE_CONFIRM)
@printf 'monitoring.yml заморожен, пока используется Uptime Kuma.\n' >&2
@$(REQUIRE_ENV); $(ANSIBLE_PLAYBOOK) playbooks/monitoring.yml $(EXTRA)