191 lines
8.4 KiB
Makefile
191 lines
8.4 KiB
Makefile
# 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
|
|
|
|
# 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
|
|
|
|
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> Dry-run a playbook (check mode)"
|
|
@echo " make deploy PLAYBOOK=<name> 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 ""
|
|
|
|
# ── 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) --check --diff playbooks/$(PLAYBOOK).yml
|
|
|
|
deploy:
|
|
ifndef PLAYBOOK
|
|
$(error PLAYBOOK is required: make deploy PLAYBOOK=<name>)
|
|
endif
|
|
$(PLAYBOOK_BIN) $(INVENTORY) $(VAULT_ARGS) 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)
|
|
|
|
# ── 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
|
|
echo "---" > 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"
|