# Ansible + Terraform homelab monorepo — Makefile
# All operations go through these targets. Never run ansible-playbook or terraform directly.

VENV        := .venv
PYTHON      := $(VENV)/bin/python
PIP         := $(VENV)/bin/pip
ANSIBLE     := $(VENV)/bin/ansible
PLAYBOOK_BIN := $(VENV)/bin/ansible-playbook
GALAXY      := $(VENV)/bin/ansible-galaxy
LINT        := $(VENV)/bin/ansible-lint
MOLECULE    := $(VENV)/bin/molecule
# Vault password is resolved via ansible.cfg (vault_password_file); no flag needed.
VAULT_ARGS  :=
# Default vault file for edit-vault / check-vault (override with VAULT=<path>).
VAULT       ?= inventories/production/group_vars/all/vault.yml
INVENTORY   := -i inventories/production/

TF              := terraform
TF_ENV          ?= staging
MOLECULE_IMAGE  := forgejo.nyumbani.baobab.band/sjat/molecule-debian13:latest
MOLECULE_DOCKERFILE := .docker/molecule-debian13/Dockerfile
# Custom Caddy + Gandi DNS-01 plugin (ADR-024). Build on ubongo, NOT askari/Hetzner
# (the Go module proxy 403s Hetzner IPs); push the pinned tag to the Forgejo registry.
CADDY_IMAGE     := forgejo.nyumbani.baobab.band/sjat/caddy-gandi:2.11.4
CADDY_DOCKERFILE := .docker/caddy-gandi/Dockerfile
# Forgejo container registry (same host/user as the image tags above). `make registry-login`
# logs the Docker daemon in using vault.forgejo.registry_token (2026-06-17 kaizen) so image
# pushes are agent-completable non-interactively.
REGISTRY_HOST   := forgejo.nyumbani.baobab.band
REGISTRY_USER   := sjat

# For TF_ENV=offsite, source the Hetzner token from the vault into the environment
# (rbw must be unlocked). Read in-memory; never written to a tfvars file (CLAUDE.md).
ifeq ($(TF_ENV),offsite)
TF_TOKEN_ENV := TF_VAR_hcloud_token="$$($(ANSIBLE)-vault view inventories/production/group_vars/all/vault.yml | $(PYTHON) -c 'import sys, yaml; print(yaml.safe_load(sys.stdin)["vault"]["hetzner"]["token"])')"
else
TF_TOKEN_ENV :=
endif

.DEFAULT_GOAL := help

.PHONY: help setup collections lint test test-all check deploy encrypt decrypt \
        edit-vault check-vault new-role \
        tf-init tf-plan tf-apply tf-output tf-inventory tf-inventory-offsite \
        molecule-image molecule-image-push caddy-image caddy-image-push registry-login

help:
	@echo ""
	@echo "Ansible homelab — available targets:"
	@echo ""
	@echo "  make setup                         Create venv and install Python deps"
	@echo "  make collections                   Install Ansible collections"
	@echo "  make lint                          Run yamllint + ansible-lint"
	@echo "  make test ROLE=<name>              Run Molecule tests for a role"
	@echo "  make test-all                      Run Molecule tests for all roles"
	@echo "  make check PLAYBOOK=<name> [LIMIT=<host>] [TAGS=<tags>]   Dry-run a playbook (check mode)"
	@echo "  make deploy PLAYBOOK=<name> [LIMIT=<host>] [TAGS=<tags>]  Run a playbook against production"
	@echo "  make edit-vault [VAULT=<path>]     Edit the vault in nvim (auto re-encrypts + checks)"
	@echo "  make check-vault [VAULT=<path>]    Validate vault structure (values masked)"
	@echo "  make encrypt FILE=<path>           Encrypt a vault file"
	@echo "  make decrypt FILE=<path>           Decrypt a vault file"
	@echo "  make new-role NAME=<name>          Scaffold a new role"
	@echo ""
	@echo "  make tf-init [TF_ENV=staging]      Initialise Terraform providers"
	@echo "  make tf-plan [TF_ENV=staging]      Show Terraform plan"
	@echo "  make tf-apply [TF_ENV=staging]     Apply Terraform changes"
	@echo "  make tf-output [TF_ENV=staging]    Print Terraform outputs as JSON"
	@echo "  make tf-inventory [TF_ENV=staging] Regenerate Ansible inventory from Terraform outputs"
	@echo "  make tf-inventory-offsite          Generate offsite_hosts inventory (askari) into inventories/production/"
	@echo ""
	@echo "  TF_ENV defaults to 'staging'. Use TF_ENV=production for production."
	@echo ""
	@echo "  make molecule-image           Build the Molecule test image locally"
	@echo "  make molecule-image-push      Push the test image to the Forgejo registry"
	@echo "  make caddy-image              Build the custom Caddy + Gandi DNS-01 image (run on ubongo)"
	@echo "  make caddy-image-push         Push the Caddy image to the Forgejo registry"
	@echo "  make registry-login           Log Docker into the Forgejo registry (vaulted token)"
	@echo ""

# ── Environment setup ─────────────────────────────────────────────────────────

setup:
	python3 -m venv $(VENV)
	$(PIP) install --upgrade pip
	$(PIP) install -r requirements.txt
	@echo "Venv ready. Activate with: source $(VENV)/bin/activate"

collections:
	$(GALAXY) collection install -r requirements.yml --upgrade

# ── Linting ───────────────────────────────────────────────────────────────────

lint:
	$(VENV)/bin/yamllint .
	$(LINT)
	$(PYTHON) scripts/check-tags.py

# ── Testing ───────────────────────────────────────────────────────────────────

test:
ifndef ROLE
	$(error ROLE is required: make test ROLE=<rolename>)
endif
	cd roles/$(ROLE) && PATH="$(CURDIR)/$(VENV)/bin:$$PATH" molecule test

test-all:
	@for role in roles/*/; do \
	  echo "── Testing $$role ──"; \
	  cd $$role && PATH="$(CURDIR)/$(VENV)/bin:$$PATH" molecule test; cd ../..; \
	done

# ── Playbook execution ────────────────────────────────────────────────────────

check:
ifndef PLAYBOOK
	$(error PLAYBOOK is required: make check PLAYBOOK=<name>)
endif
	$(PLAYBOOK_BIN) $(INVENTORY) $(VAULT_ARGS) $(if $(LIMIT),--limit $(LIMIT)) $(if $(TAGS),--tags $(TAGS)) --check --diff playbooks/$(PLAYBOOK).yml

deploy:
ifndef PLAYBOOK
	$(error PLAYBOOK is required: make deploy PLAYBOOK=<name>)
endif
	$(PLAYBOOK_BIN) $(INVENTORY) $(VAULT_ARGS) $(if $(LIMIT),--limit $(LIMIT)) $(if $(TAGS),--tags $(TAGS)) playbooks/$(PLAYBOOK).yml

# ── Vault ─────────────────────────────────────────────────────────────────────

# Streamlined edit: ansible-vault edit decrypts to a temp file, opens nvim, and
# re-encrypts on :wq (abort with :cq) — no plaintext ever lands in the work tree.
# Then validate structure. Override the file with VAULT=<path>.
edit-vault:
	EDITOR=nvim $(ANSIBLE)-vault edit $(VAULT)
	@$(PYTHON) scripts/check-vault.py $(VAULT)

check-vault:
	@$(PYTHON) scripts/check-vault.py $(VAULT)

encrypt:
ifndef FILE
	$(error FILE is required: make encrypt FILE=<path>)
endif
	$(ANSIBLE)-vault encrypt $(FILE)

decrypt:
ifndef FILE
	$(error FILE is required: make decrypt FILE=<path>)
endif
	$(ANSIBLE)-vault decrypt $(FILE)

# ── Molecule test image ───────────────────────────────────────────────────────

molecule-image:
	docker build -t $(MOLECULE_IMAGE) -f $(MOLECULE_DOCKERFILE) .

molecule-image-push: molecule-image
	docker push $(MOLECULE_IMAGE)

# ── Custom Caddy image (Gandi DNS-01 plugin, ADR-024) ─────────────────────────
# DNS-01 (wildcard / mesh-LAN-only certs) needs the caddy-dns/gandi plugin compiled
# in via xcaddy. Build on ubongo — Google's Go module proxy 403s Hetzner IPs.

caddy-image:
	docker build -t $(CADDY_IMAGE) -f $(CADDY_DOCKERFILE) .docker/caddy-gandi

caddy-image-push: caddy-image
	docker push $(CADDY_IMAGE)

# Log the local Docker daemon into the Forgejo registry using the vaulted token, so the
# *-image-push targets above are agent-completable non-interactively (rbw must be unlocked).
registry-login:
	@ANSIBLE_VAULT="$(ANSIBLE)-vault" PYTHON="$(PYTHON)" VAULT="$(VAULT)" \
	  REGISTRY_HOST="$(REGISTRY_HOST)" REGISTRY_USER="$(REGISTRY_USER)" \
	  bash scripts/registry-login.sh

# ── Terraform ─────────────────────────────────────────────────────────────────

tf-init:
	$(TF_TOKEN_ENV) $(TF) -chdir=terraform/environments/$(TF_ENV) init

tf-plan:
	$(TF_TOKEN_ENV) $(TF) -chdir=terraform/environments/$(TF_ENV) plan

tf-apply:
	$(TF_TOKEN_ENV) $(TF) -chdir=terraform/environments/$(TF_ENV) apply

tf-output:
	$(TF_TOKEN_ENV) $(TF) -chdir=terraform/environments/$(TF_ENV) output -json

tf-inventory:
ifndef TF_ENV
	$(error TF_ENV is required: make tf-inventory TF_ENV=<staging|production>)
endif
	$(TF) -chdir=terraform/environments/$(TF_ENV) output -json \
	  | $(PYTHON) scripts/tf_to_inventory.py > inventories/$(TF_ENV)/hosts.yml
	@echo "Inventory written to inventories/$(TF_ENV)/hosts.yml"

tf-inventory-offsite:
	$(TF_TOKEN_ENV) $(TF) -chdir=terraform/environments/offsite output -json \
	  | $(PYTHON) scripts/tf_to_inventory.py > inventories/production/offsite.yml
	@echo "Offsite inventory written to inventories/production/offsite.yml"

# ── Role scaffolding ──────────────────────────────────────────────────────────

new-role:
ifndef NAME
	$(error NAME is required: make new-role NAME=<rolename>)
endif
	mkdir -p roles/$(NAME)/tasks roles/$(NAME)/handlers roles/$(NAME)/defaults \
	         roles/$(NAME)/templates roles/$(NAME)/files roles/$(NAME)/meta \
	         roles/$(NAME)/molecule/default
	echo "---" > roles/$(NAME)/tasks/main.yml
	echo "---" > roles/$(NAME)/handlers/main.yml
	printf '%s\n' '---' \
	  '# Role defaults use the <rolename>__var double-underscore namespace.' \
	  '#' \
	  '# Service roles (ADR-004) also declare access__*/backup__* data here. Those are' \
	  '# cross-role conventions (not rolename-prefixed), so EACH such line needs a trailing' \
	  '# noqa: var-naming[no-role-prefix]  (ansible-lint 24.x has no per-prefix allowlist).' \
	  '# Reference: roles/reverse_proxy/defaults/main.yml' \
	  > roles/$(NAME)/defaults/main.yml
	echo "---" > roles/$(NAME)/meta/main.yml
	printf '# %s\n\nRole description here.\n' "$(NAME)" > roles/$(NAME)/README.md
	cp .scaffold/molecule.yml roles/$(NAME)/molecule/default/molecule.yml
	sed 's/ROLE_NAME_PLACEHOLDER/$(NAME)/g' .scaffold/converge.yml > roles/$(NAME)/molecule/default/converge.yml
	cp .scaffold/verify.yml   roles/$(NAME)/molecule/default/verify.yml
	@echo "Role $(NAME) scaffolded at roles/$(NAME)/"
	@echo "Next: fill in meta/main.yml, defaults/main.yml, tasks/main.yml, README.md"
