# VPS ops hub — routine validate / health / gated Ansible wrappers.
# See runbooks/ansible-operations.md for playbook semantics.

SHELL := /usr/bin/env bash
.SHELLFLAGS := -eu -o pipefail -c

.DEFAULT_GOAL := help

REPO_ROOT := $(CURDIR)
ANSIBLE_DIR := $(REPO_ROOT)/ansible
export ANSIBLE_LOCAL_TEMP := $(REPO_ROOT)/.ansible/tmp
export ANSIBLE_HOME := $(REPO_ROOT)/.ansible

LIMIT ?=
EXTRA ?=
VERBOSE ?= 0
CONFIRM ?= 0
TARGETS ?=
TRAEFIK ?= 0

LIMIT_FLAG := $(if $(LIMIT),--limit $(LIMIT),)
VERBOSE_FLAG := $(if $(filter 1,$(VERBOSE)),-v,$(if $(filter 2,$(VERBOSE)),-vvv,))

.PHONY: help validate check deps galaxy syntax ansible-prep \
	ping inventory audit health health-mailcow health-matrix \
	maint-preview baseline compose-check \
	install-healthchecks install-matrix-healthchecks compose-deploy reconcile

help:
	@printf '%s\n' \
		'VPS ops hub — make targets (run from repo root)' \
		'' \
		'Variables: LIMIT=<group|host>  CONFIRM=1  TARGETS=<svc[,svc]>  TRAEFIK=1  VERBOSE=0|1|2  EXTRA=...' \
		'' \
		'Local / repo:' \
		'  validate, check     scripts/validate-repo.sh (pre-merge gate)' \
		'  deps, galaxy        ansible-galaxy collection install' \
		'  syntax              ansible-playbook --syntax-check all playbooks' \
		'' \
		'Read-only remote (ansible):' \
		'  ping                ansible managed -m ping' \
		'  inventory           ansible-inventory --graph' \
		'  audit               playbooks/audit.yml' \
		'  health [LIMIT=…]    playbooks/health-report.yml' \
		'  health-mailcow      health --limit mailcow' \
		'  health-matrix       health --limit matrix' \
		'  maint-preview       playbooks/maintenance-preview.yml' \
		'  baseline            playbooks/baseline.yml' \
		'  compose-check       compose-deploy --check --diff (requires LIMIT=)' \
		'' \
		'Mutating (require CONFIRM=1; host-scoped targets require LIMIT=):' \
		'  install-healthchecks        playbooks/healthchecks.yml' \
		'  install-matrix-healthchecks playbooks/matrix-healthchecks.yml' \
		'  compose-deploy              playbooks/compose-deploy.yml' \
		'  reconcile                   playbooks/compose-reconcile.yml (requires TARGETS=)' \
		'' \
		'Examples:' \
		'  make validate' \
		'  make health LIMIT=mailcow' \
		'  make compose-check LIMIT=vaultwarden' \
		'  make compose-deploy LIMIT=vaultwarden CONFIRM=1' \
		'  make reconcile LIMIT=powerdns TARGETS=auth CONFIRM=1' \
		'  make reconcile LIMIT=vaultwarden TARGETS=vaultwarden TRAEFIK=1 CONFIRM=1' \
		'' \
		'Advanced (not wrapped — use ansible-playbook directly):' \
		'  us4-firewalld, unifi-sso, k3s-server, matrix-stack, wireguard-harden,' \
		'  restic, rustdesk, email-alerts, mailcow update runbook'

validate check:
	@bash "$(REPO_ROOT)/scripts/validate-repo.sh"

deps galaxy: ansible-prep
	@command -v ansible-galaxy >/dev/null 2>&1 || { echo "ansible-galaxy not found; install Ansible first." >&2; exit 1; }
	@cd "$(ANSIBLE_DIR)" && ansible-galaxy collection install -r requirements.yml

syntax: ansible-prep
	@if ! command -v ansible-playbook >/dev/null 2>&1; then \
		echo "ansible-playbook not found; syntax check skipped." >&2; \
		exit 0; \
	fi
	@fail=0; \
	for p in "$(ANSIBLE_DIR)"/playbooks/*.yml; do \
		if ! (cd "$(ANSIBLE_DIR)" && ansible-playbook --syntax-check "playbooks/$$(basename "$$p")" >/dev/null 2>&1); then \
			echo "syntax-check failed: $$p" >&2; \
			fail=1; \
		fi; \
	done; \
	exit $$fail

ansible-prep:
	@mkdir -p "$(ANSIBLE_HOME)/tmp" "$(ANSIBLE_HOME)/ssh-control"

define require_ansible
	@command -v ansible-playbook >/dev/null 2>&1 || { echo "ansible-playbook not found; install Ansible first." >&2; exit 1; }
endef

define require_limit
	@if [ -z "$(LIMIT)" ]; then \
		echo "LIMIT is required (e.g. LIMIT=mailcow, LIMIT=vaultwarden, LIMIT=powerdns)." >&2; \
		exit 1; \
	fi
endef

define require_confirm
	@if [ "$(CONFIRM)" != "1" ]; then \
		echo "Mutating operation blocked. Re-run with CONFIRM=1" >&2; \
		exit 1; \
	fi
endef

define require_targets
	@if [ -z "$(TARGETS)" ]; then \
		echo "TARGETS is required (comma-separated service names, e.g. TARGETS=auth or TARGETS=vaultwarden)." >&2; \
		exit 1; \
	fi
endef

ping: ansible-prep
	$(require_ansible)
	@cd "$(ANSIBLE_DIR)" && ansible managed -m ping $(LIMIT_FLAG) $(VERBOSE_FLAG) $(EXTRA)

inventory: ansible-prep
	$(require_ansible)
	@cd "$(ANSIBLE_DIR)" && ansible-inventory --graph $(EXTRA)

audit: ansible-prep
	$(require_ansible)
	@cd "$(ANSIBLE_DIR)" && ansible-playbook playbooks/audit.yml $(LIMIT_FLAG) $(VERBOSE_FLAG) $(EXTRA)

health: ansible-prep
	$(require_ansible)
	@cd "$(ANSIBLE_DIR)" && ansible-playbook playbooks/health-report.yml $(LIMIT_FLAG) $(VERBOSE_FLAG) $(EXTRA)

health-mailcow: ansible-prep
	$(require_ansible)
	@cd "$(ANSIBLE_DIR)" && ansible-playbook playbooks/health-report.yml --limit mailcow $(VERBOSE_FLAG) $(EXTRA)

health-matrix: ansible-prep
	$(require_ansible)
	@cd "$(ANSIBLE_DIR)" && ansible-playbook playbooks/health-report.yml --limit matrix $(VERBOSE_FLAG) $(EXTRA)

maint-preview: ansible-prep
	$(require_ansible)
	@cd "$(ANSIBLE_DIR)" && ansible-playbook playbooks/maintenance-preview.yml $(LIMIT_FLAG) $(VERBOSE_FLAG) $(EXTRA)

baseline: ansible-prep
	$(require_ansible)
	@cd "$(ANSIBLE_DIR)" && ansible-playbook playbooks/baseline.yml $(LIMIT_FLAG) $(VERBOSE_FLAG) $(EXTRA)

compose-check: ansible-prep
	$(require_ansible)
	$(require_limit)
	@cd "$(ANSIBLE_DIR)" && ansible-playbook playbooks/compose-deploy.yml --check --diff --limit $(LIMIT) $(VERBOSE_FLAG) $(EXTRA)

install-healthchecks: ansible-prep
	$(require_ansible)
	$(require_confirm)
	@cd "$(ANSIBLE_DIR)" && ansible-playbook playbooks/healthchecks.yml $(LIMIT_FLAG) $(VERBOSE_FLAG) $(EXTRA)

install-matrix-healthchecks: ansible-prep
	$(require_ansible)
	$(require_confirm)
	@cd "$(ANSIBLE_DIR)" && ansible-playbook playbooks/matrix-healthchecks.yml $(LIMIT_FLAG) $(VERBOSE_FLAG) $(EXTRA)

compose-deploy: ansible-prep
	$(require_ansible)
	$(require_limit)
	$(require_confirm)
	@cd "$(ANSIBLE_DIR)" && ansible-playbook playbooks/compose-deploy.yml --limit $(LIMIT) \
		-e '{"compose_deploy_confirm": true}' $(VERBOSE_FLAG) $(EXTRA)

reconcile: ansible-prep
	$(require_ansible)
	$(require_limit)
	$(require_targets)
	$(require_confirm)
	@json=$$(python3 -c 'import json,sys; t=[x.strip() for x in sys.argv[1].split(",") if x.strip()]; \
		(not t) and sys.exit("TARGETS must contain at least one non-empty service name"); \
		d={"service_reconcile_confirm": True, "service_reconcile_targets": t}; \
		(sys.argv[2]=="1") and d.update({"service_reconcile_restart_traefik": True}); \
		print(json.dumps(d))' "$(TARGETS)" "$(TRAEFIK)"); \
	cd "$(ANSIBLE_DIR)" && ansible-playbook playbooks/compose-reconcile.yml --limit $(LIMIT) \
		-e "$$json" $(VERBOSE_FLAG) $(EXTRA)
