2026-05-30 14:10:01 +02:00
|
|
|
# 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 := $(VENV)/bin/ansible-playbook
|
|
|
|
|
GALAXY := $(VENV)/bin/ansible-galaxy
|
|
|
|
|
LINT := $(VENV)/bin/ansible-lint
|
|
|
|
|
MOLECULE := $(VENV)/bin/molecule
|
2026-05-30 18:16:35 +02:00
|
|
|
# Vault password is resolved via ansible.cfg (vault_password_file); no flag needed.
|
|
|
|
|
VAULT_ARGS :=
|
2026-05-30 14:10:01 +02:00
|
|
|
INVENTORY := -i inventories/production/hosts.yml
|
|
|
|
|
|
|
|
|
|
TF := terraform
|
|
|
|
|
TF_ENV ?= staging
|
2026-05-30 18:16:35 +02:00
|
|
|
MOLECULE_IMAGE := forgejo.nyumbani.baobab.band/<owner>/<repo>/molecule-debian13:latest
|
2026-05-30 14:10:01 +02:00
|
|
|
MOLECULE_DOCKERFILE := .docker/molecule-debian13/Dockerfile
|
|
|
|
|
|
|
|
|
|
.DEFAULT_GOAL := help
|
|
|
|
|
|
|
|
|
|
.PHONY: help setup collections lint test test-all check deploy encrypt decrypt new-role \
|
|
|
|
|
tf-init tf-plan tf-apply tf-output tf-inventory \
|
|
|
|
|
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 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 ""
|
|
|
|
|
@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)
|
|
|
|
|
|
|
|
|
|
# ── Testing ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
test:
|
|
|
|
|
ifndef ROLE
|
|
|
|
|
$(error ROLE is required: make test ROLE=<rolename>)
|
|
|
|
|
endif
|
|
|
|
|
cd roles/$(ROLE) && ../../$(MOLECULE) test
|
|
|
|
|
|
|
|
|
|
test-all:
|
|
|
|
|
@for role in roles/*/; do \
|
|
|
|
|
echo "── Testing $$role ──"; \
|
|
|
|
|
cd $$role && ../../$(MOLECULE) test; cd ../..; \
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
# ── Playbook execution ────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
check:
|
|
|
|
|
ifndef PLAYBOOK
|
|
|
|
|
$(error PLAYBOOK is required: make check PLAYBOOK=<name>)
|
|
|
|
|
endif
|
|
|
|
|
$(PLAYBOOK) $(INVENTORY) $(VAULT_ARGS) --check --diff playbooks/$(PLAYBOOK).yml
|
|
|
|
|
|
|
|
|
|
deploy:
|
|
|
|
|
ifndef PLAYBOOK
|
|
|
|
|
$(error PLAYBOOK is required: make deploy PLAYBOOK=<name>)
|
|
|
|
|
endif
|
|
|
|
|
$(PLAYBOOK) $(INVENTORY) $(VAULT_ARGS) playbooks/$(PLAYBOOK).yml
|
|
|
|
|
|
|
|
|
|
# ── Vault ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
encrypt:
|
|
|
|
|
ifndef FILE
|
|
|
|
|
$(error FILE is required: make encrypt FILE=<path>)
|
|
|
|
|
endif
|
2026-05-30 18:16:35 +02:00
|
|
|
$(ANSIBLE)-vault encrypt $(FILE)
|
2026-05-30 14:10:01 +02:00
|
|
|
|
|
|
|
|
decrypt:
|
|
|
|
|
ifndef FILE
|
|
|
|
|
$(error FILE is required: make decrypt FILE=<path>)
|
|
|
|
|
endif
|
2026-05-30 18:16:35 +02:00
|
|
|
$(ANSIBLE)-vault decrypt $(FILE)
|
2026-05-30 14:10:01 +02:00
|
|
|
|
|
|
|
|
# ── 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) -chdir=terraform/environments/$(TF_ENV) init
|
|
|
|
|
|
|
|
|
|
tf-plan:
|
|
|
|
|
$(TF) -chdir=terraform/environments/$(TF_ENV) plan
|
|
|
|
|
|
|
|
|
|
tf-apply:
|
|
|
|
|
$(TF) -chdir=terraform/environments/$(TF_ENV) apply
|
|
|
|
|
|
|
|
|
|
tf-output:
|
|
|
|
|
$(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"
|
|
|
|
|
|
|
|
|
|
# ── Role scaffolding ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
new-role:
|
|
|
|
|
ifndef NAME
|
|
|
|
|
$(error NAME is required: make new-role NAME=<rolename>)
|
|
|
|
|
endif
|
|
|
|
|
mkdir -p roles/$(NAME)/{tasks,handlers,defaults,templates,files,meta,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
|
|
|
|
|
echo "# $(NAME)\n\nRole description here." > roles/$(NAME)/README.md
|
|
|
|
|
cp .scaffold/molecule.yml roles/$(NAME)/molecule/default/molecule.yml
|
|
|
|
|
cp .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"
|