# -------- RailianceHosts Make Utilities --------
SHELL := /usr/bin/env bash
.DEFAULT_GOAL := help

# Set this to your Gitea host if you want 'remote-set' helper
GITEA ?= gitea.example.com
OWNER ?= coulomb
REPO  ?= railiance-hosts

# New-host defaults (can be overridden: make new-host NAME=... TYPE=...)
TYPE  ?= cpx11
REGION ?= nbg1
ROLE  ?= core
IMG   ?= ubuntu-24.04
USER  ?= admin

# Decrypt Hetzner token at runtime (requires SOPS_AGE_KEY or keys.txt locally)
HCLOUD_TOKEN := $(shell sops -d --extract '["hetzner"]["token"]' secrets/hetzner-token.yaml 2>/dev/null)

# ---- Help ----
help: ## Show this help
	@echo "RailianceHosts Commands"; \
	grep -E '^[a-zA-Z0-9_-]+:.*?## ' $(MAKEFILE_LIST) | sort | sed 's/:.*##/: /'

# ---- Git hooks ----
hooks: ## Configure git to use repo-local hooks (.githooks) and ensure executables
	@mkdir -p .githooks
	git config core.hooksPath .githooks
	@test -f .githooks/pre-commit || (echo "❌ Missing .githooks/pre-commit"; exit 1)
	chmod +x .githooks/pre-commit
	@echo "✔ hooks enabled and pre-commit is executable"

hooks-test: ## Test secrets hook blocks plaintext in secrets/
	@mkdir -p secrets && echo 'PLAINTEXT_TEST=true' > secrets/_hook_test.yaml
	@git add secrets/_hook_test.yaml || true
	@if git commit -m "TEST: should be blocked" 2>/dev/null; then \
	  echo "❌ Hook did NOT block plaintext (check .githooks/pre-commit)"; \
	  git reset --soft HEAD~1; \
	else \
	  echo "✔ Hook blocked plaintext as expected"; \
	fi
	@git restore --staged secrets/_hook_test.yaml || true
	@rm -f secrets/_hook_test.yaml

# ---- SOPS / Age helpers ----
sops-setup: ## Copy age key to SOPS default path (~/.config/sops/age/keys.txt)
	mkdir -p ~/.config/sops/age
	cp -n ~/.config/age/key.txt ~/.config/sops/age/keys.txt || true
	chmod 600 ~/.config/sops/age/keys.txt
	@echo "✔ SOPS key path set (~/.config/sops/age/keys.txt). Alternatively export SOPS_AGE_KEY."

sops-edit: ## Edit the global secrets with SOPS
	sops secrets/hetzner-token.yaml

sops-encrypt: ## Encrypt a file in place: make sops-encrypt FILE=secrets/foo.yaml
	@[ -n "$(FILE)" ] || (echo "Usage: make sops-encrypt FILE=secrets/xxx.yaml" && exit 1)
	sops --encrypt --in-place $(FILE)
	@echo "✔ Encrypted $(FILE)"

sops-decrypt: ## Print decrypted file to stdout (for inspection) FILE=secrets/foo.sops.yaml
	@[ -n "$(FILE)" ] || (echo "Usage: make sops-decrypt FILE=secrets/xxx.sops.yaml" && exit 1)
	sops -d $(FILE)

sops-rotate: ## Rotate recipients on a SOPS file (after updating .sops.yaml)
	@[ -n "$(FILE)" ] || (echo "Usage: make sops-rotate FILE=secrets/xxx.sops.yaml" && exit 1)
	sops --rotate --in-place $(FILE)

check-secrets: ## Fail if any file in secrets/ is not SOPS-encrypted
	@! (git ls-files secrets | xargs -r grep -L -E '(^sops:$$|\"sops\"[[:space:]]*:)' | tee /dev/stderr | read) \
	  || (echo "❌ Unencrypted secrets detected above. Encrypt with: sops --encrypt --in-place <file>"; exit 1)
	@echo "✔ All files in secrets/ appear SOPS-encrypted"

# ---- Terraform (Hetzner) ----
tf-fmt: ## Terraform fmt
	@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; export SOPS_AGE_KEY or set keys.txt & fill secrets.sops.yaml" && exit 1)
	@export HCLOUD_TOKEN=$(HCLOUD_TOKEN); @terraform -chdir=terraform/hetzner fmt -recursive || true

tf-init: ## Terraform init
	@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; export SOPS_AGE_KEY or set keys.txt & fill secrets.sops.yaml" && exit 1)
	@export HCLOUD_TOKEN=$(HCLOUD_TOKEN); terraform -chdir=terraform/hetzner init

tf-plan: tf-init ## Terraform plan (requires decrypted HCLOUD_TOKEN)
	@echo "🔍 Running terraform plan..."
	@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; export SOPS_AGE_KEY or set keys.txt & fill secrets.sops.yaml" && exit 1)
	@export HCLOUD_TOKEN=$(HCLOUD_TOKEN); terraform -chdir=terraform/hetzner plan -var="hcloud_token=$(HCLOUD_TOKEN)"

tf-apply: tf-init ## Terraform apply (provision)
	@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; export SOPS_AGE_KEY or set keys.txt & fill secrets.sops.yaml" && exit 1)
	@export HCLOUD_TOKEN=$(HCLOUD_TOKEN); terraform -chdir=terraform/hetzner apply -auto-approve -var="hcloud_token=$(HCLOUD_TOKEN)"

tf-destroy: tf-init ## Terraform destroy (tear down)
	@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; export SOPS_AGE_KEY or set keys.txt & fill secrets.sops.yaml" && exit 1)
	@export HCLOUD_TOKEN=$(HCLOUD_TOKEN); terraform -chdir=terraform/hetzner destroy -auto-approve -var="hcloud_token=$(HCLOUD_TOKEN)"

# --- Terraform provider/lockfile helpers ---
TF_DIR := terraform/hetzner
TF_TOKEN := $(HCLOUD_TOKEN)
LOCKFILE := $(TF_DIR)/.terraform.lock.hcl

tf-lock-commit: ## Commit the current provider lockfile
	@test -f $(LOCKFILE) || (echo "❌ $(LOCKFILE) not found. Run 'make tf-init' first."; exit 1)
	@git add $(LOCKFILE)
	@git commit -m "chore(terraform): lock providers" || echo "ℹ No lockfile changes to commit."

tf-providers-check: ## Check if newer provider versions are available (non-destructive)
	@echo "🔎 Checking for provider upgrades (lockfile readonly)…"
	@if terraform -chdir=$(TF_DIR) init -upgrade -lockfile=readonly >/dev/null 2>&1; then \
		echo "✔ Providers up to date (no upgrades available)."; \
	else \
		echo "↗ Provider upgrades likely available (readonly lockfile blocked changes)."; \
		echo "   Run: make tf-providers-upgrade"; \
	fi

tf-providers-upgrade: ## Upgrade providers (updates .terraform.lock.hcl)
	@echo "⬆️  Upgrading providers…"
	@terraform -chdir=$(TF_DIR) init -upgrade
	@echo "— Diff for $(LOCKFILE):"
	@git --no-pager diff -- $(LOCKFILE) || true
	@echo "💡 If changes look good: make tf-lock-commit"

tf-providers-upgrade-commit: tf-providers-upgrade tf-lock-commit ## Upgrade providers and commit the lockfile

tf-providers-plan: ## Plan after an upgrade (uses HCLOUD_TOKEN if set)
	@echo "🧪 Planning with upgraded providers…"
	@terraform -chdir=$(TF_DIR) plan $(if $(TF_TOKEN),-var="hcloud_token=$(TF_TOKEN)")


# ---- Ansible ----
ansible-bootstrap: ## Run base bootstrap play (users, ssh, ufw, sops-agent)
	cd ansible && ansible-playbook playbooks/bootstrap.yaml -u admin

# ---- Orchestration ----
apply: tf-fmt tf-apply ansible-bootstrap ## Provision via Terraform then converge via Ansible

# ---- Utilities ----
doctor: ## Check tools and basic repo setup
	@bash -ceu ' \
	  ok(){ printf "✔ %s\n" "$$1"; }; fail(){ printf "❌ %s\n" "$$1"; exit 1; }; \
	  command -v git >/dev/null && ok "git: $$(git --version)" || fail "git missing"; \
	  command -v ansible >/dev/null && ok "ansible: $$(ansible --version | head -1)"; \
	  command -v sops >/dev/null && ok "sops: $$(sops --version --check-for-updates)"; \
	  command -v age >/dev/null && ok "age: $$(age --version)"; \
	  command -v terraform >/dev/null && ok "terraform: $$(terraform -version | head -1)"; \
	  test -f keys/admin_ssh.pub && ok "keys/admin_ssh.pub present" || echo "ℹ add your SSH pubkey to keys/admin_ssh.pub"; \
	  test -f inventory/group_vars/secrets.sops.yaml && ok "secrets.sops.yaml present" || echo "ℹ create inventory/group_vars/secrets.sops.yaml"; \
	  grep -q "age1" .sops.yaml && ok ".sops.yaml has an age recipient" || echo "ℹ add your age public key to .sops.yaml"; \
	  git config --get core.hooksPath >/dev/null && ok "git hooksPath: $$(git config --get core.hooksPath)" || echo "ℹ run: make hooks"; \
	'

# ---- Inventory convenience ----
new-host: ## Add a new host quickly: make new-host NAME=core1 TYPE=cpx11 REGION=nbg1 ROLE=core
	@[ -n "$(NAME)" ] || (echo "Usage: make new-host NAME=... [TYPE=...] [REGION=...] [ROLE=...] [IMG=...] [USER=...]" && exit 1)
	@python3 scripts/new_host.py --name "$(NAME)" --type "$(TYPE)" --region "$(REGION)" --role "$(ROLE)" --image "$(IMG)" --user "$(USER)"
	@echo "✔ Added host $(NAME) to inventory/servers.yaml"

remote-set: ## Set origin to your Gitea repo (GITEA/OWNER/REPO vars)
	git remote remove origin 2>/dev/null || true
	git remote add origin https://$(GITEA)/$(OWNER)/$(REPO).git
	git branch -M main
	git push -u origin main
	@echo "✔ Remote set to https://$(GITEA)/$(OWNER)/$(REPO).git"


# ==== Convergence (Ansible) ====
ANS_DIR := ansible
INV_SCRIPT := $(ANS_DIR)/inventory_from_yaml.py
PLAY := $(ANS_DIR)/playbooks/bootstrap.yaml
SSH_USER ?= admin

# Load your SOPS key for decryption when running playbooks (optional if you use keys.txt)
export SOPS_AGE_KEY := $(shell cat ~/.config/sops/age/keys.txt 2>/dev/null)

ansible-help: ## Show common Ansible commands
	@echo "Convergence targets:"
	@echo "  make ansible-inventory        # show resolved inventory"
	@echo "  make ansible-ping             # ping all hosts"
	@echo "  make converge                 # run baseline convergence on all hosts"
	@echo "  make converge-host HOST=web-01# run on a single host"
	@echo "  make converge-tags TAGS=base  # run only tagged tasks"
	@echo "  make converge-check           # dry-run (check mode)"
	@echo "  make converge-diff            # show config diffs"

ansible-inventory: ## Print the dynamic inventory Ansible will use
	cd $(ANS_DIR) && ansible-inventory --list | head -200

ansible-ping: ## Quick connectivity check (SSH + Python availability)
	cd $(ANS_DIR) && ansible all -u $(SSH_USER) -m ping

status: ## Show live security state of all hosts (UFW, fail2ban, SSH hardening)
	@echo "=== Connectivity ==="
	cd $(ANS_DIR) && ansible all -u $(SSH_USER) -m ping
	@echo "=== UFW ==="
	cd $(ANS_DIR) && ansible all -u $(SSH_USER) -m shell -a "ufw status" --become
	@echo "=== fail2ban ==="
	cd $(ANS_DIR) && ansible all -u $(SSH_USER) -m shell -a "systemctl is-active fail2ban"
	@echo "=== SSH hardening ==="
	cd $(ANS_DIR) && ansible all -u $(SSH_USER) -m shell -a "grep -iE '^(PermitRootLogin|PasswordAuthentication)' /etc/ssh/sshd_config" --become
	@echo ""
	@echo "--- Hint: run 'make verify' for a structured pass/fail report ---"

verify: ## Run Goss test suite against all hosts, commit TAP reports — exits non-zero on failure
	@echo "Running Goss baseline assertions..."
	@cd $(ANS_DIR) && ansible-playbook playbooks/verify.yaml -u $(SSH_USER) || \
	  (echo "One or more assertions FAILED — see reports/ for TAP output." && exit 1)
	@echo "All assertions passed."
	@git add reports/ && \
	  git diff --cached --quiet && echo "No new reports to commit." || \
	  git commit -m "chore: Goss verification reports $$(date -u +%Y-%m-%dT%H%M%SZ)"

converge: ## Converge all hosts to the baseline (idempotent)
	cd $(ANS_DIR) && ansible-playbook $(PLAY) -u $(SSH_USER)

converge-host: ## Converge a single host: make converge-host HOST=core-01
	@test -n "$(HOST)" || (echo "Usage: make converge-host HOST=<name>"; exit 1)
	cd $(ANS_DIR) && ansible-playbook $(PLAY) -u $(SSH_USER) -l $(HOST)

converge-tags: ## Run only certain tags: make converge-tags TAGS="base,ufw"
	@test -n "$(TAGS)" || (echo "Usage: make converge-tags TAGS=tag1,tag2"; exit 1)
	cd $(ANS_DIR) && ansible-playbook $(PLAY) -u $(SSH_USER) --tags "$(TAGS)"

converge-check: ## Dry-run (no changes), great for previews
	cd $(ANS_DIR) && ansible-playbook $(PLAY) -u $(SSH_USER) --check

converge-diff: ## Show file/templating diffs while applying changes
	cd $(ANS_DIR) && ansible-playbook $(PLAY) -u $(SSH_USER) --diff

