ansible/Makefile

367 lines
14 KiB
Makefile

.PHONY: help bootstrap lint test check apply dev local clean status tailscale tailscale-check tailscale-dev tailscale-status create-vault
.DEFAULT_GOAL := help
## Colors for output
BOLD := \033[1m
RED := \033[31m
GREEN := \033[32m
YELLOW := \033[33m
BLUE := \033[34m
RESET := \033[0m
## Auto-detect current host to exclude from remote operations
CURRENT_IP := $(shell hostname -I | awk '{print $$1}')
CURRENT_HOST := $(shell ansible-inventory --list | jq -r '._meta.hostvars | to_entries[] | select(.value.ansible_host == "$(CURRENT_IP)") | .key' 2>/dev/null | head -1)
EXCLUDE_CURRENT := $(if $(CURRENT_HOST),--limit '!$(CURRENT_HOST)',)
help: ## Show this help message
@echo "$(BOLD)Ansible Development Environment$(RESET)"
@echo ""
@echo "$(BOLD)Available commands:$(RESET)"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " $(BLUE)%-15s$(RESET) %s\n", $$1, $$2}'
@echo ""
@echo "$(BOLD)Examples:$(RESET)"
@echo " make bootstrap # Set up dependencies"
@echo " make check # Dry run all hosts"
@echo " make apply # Run on all dev hosts"
@echo " make dev HOST=dev01 # Run on specific host"
@echo " make local # Run local playbook"
@echo " make maintenance # Run maintenance on all hosts"
@echo " make maintenance GROUP=dev # Run maintenance on dev group"
@echo " make maintenance HOST=dev01 # Run maintenance on specific host"
@echo " make maintenance CHECK=true # Dry-run maintenance on all hosts"
@echo ""
bootstrap: ## Install required collections and dependencies
@echo "$(BOLD)Installing Dependencies$(RESET)"
@echo ""
@echo "$(YELLOW)Ansible Collections:$(RESET)"
@ansible-galaxy collection install -r collections/requirements.yml 2>&1 | grep -E "(Installing|Skipping|ERROR)" | while read line; do \
if echo "$$line" | grep -q "Installing"; then \
collection=$$(echo "$$line" | awk '{print $$2}' | sed 's/:.*//'); \
printf " $(GREEN)✓ %-30s$(RESET) Installed\n" "$$collection"; \
elif echo "$$line" | grep -q "Skipping"; then \
collection=$$(echo "$$line" | awk '{print $$2}' | sed 's/,.*//'); \
printf " $(BLUE)- %-30s$(RESET) Already installed\n" "$$collection"; \
elif echo "$$line" | grep -q "ERROR"; then \
printf " $(RED)✗ Error: $$line$(RESET)\n"; \
fi; \
done || ansible-galaxy collection install -r collections/requirements.yml
@echo ""
lint: ## Run ansible-lint on all playbooks and roles
@echo "$(YELLOW)Running ansible-lint...$(RESET)"
ansible-lint
@echo "$(GREEN)✓ Linting completed$(RESET)"
test-syntax: ## Run syntax check only
@echo "$(BOLD)Syntax Testing$(RESET)"
@echo ""
@echo "$(YELLOW)Playbook Syntax:$(RESET)"
@for playbook in dev-playbook.yml local-playbook.yml maintenance-playbook.yml tailscale-playbook.yml; do \
if [ -f "$$playbook" ]; then \
printf " %-25s " "$$playbook"; \
if ansible-playbook "$$playbook" --syntax-check >/dev/null 2>&1; then \
echo "$(GREEN)✓ OK$(RESET)"; \
else \
echo "$(RED)✗ FAIL$(RESET)"; \
fi; \
fi; \
done
@echo ""
test: ## Run all tests (lint + syntax check if available)
@if command -v ansible-lint >/dev/null 2>&1; then \
echo "$(YELLOW)Running ansible-lint...$(RESET)"; \
ansible-lint 2>/dev/null && echo "$(GREEN)✓ Linting completed$(RESET)" || echo "$(YELLOW)⚠ Linting had issues$(RESET)"; \
echo ""; \
fi
@$(MAKE) test-syntax
check: ## Dry-run the development playbook (--check mode)
@echo "$(YELLOW)Running dry-run on development hosts...$(RESET)"
ansible-playbook dev-playbook.yml --check --diff
check-local: ## Dry-run the local playbook
@echo "$(YELLOW)Running dry-run on localhost...$(RESET)"
ansible-playbook local-playbook.yml --check --diff -K
apply: ## Run the development playbook on all dev hosts
@echo "$(YELLOW)Applying development playbook...$(RESET)"
ansible-playbook dev-playbook.yml
local: ## Run the local playbook on localhost
@echo "$(YELLOW)Applying local playbook...$(RESET)"
ansible-playbook local-playbook.yml -K
# Host-specific targets
dev: ## Run on specific host (usage: make dev HOST=dev01)
ifndef HOST
@echo "$(RED)Error: HOST parameter required$(RESET)"
@echo "Usage: make dev HOST=dev01"
@exit 1
endif
@echo "$(YELLOW)Running on host: $(HOST)$(RESET)"
ansible-playbook dev-playbook.yml --limit $(HOST)
# Tag-based execution
security: ## Run only security-related roles
@echo "$(YELLOW)Running security roles...$(RESET)"
ansible-playbook dev-playbook.yml --tags security
# Unified maintenance target with intelligent parameter detection
maintenance: ## Run maintenance (usage: make maintenance [GROUP=dev] [HOST=dev01] [SERIAL=1] [CHECK=true])
@$(MAKE) _maintenance-run
_maintenance-run:
@# Determine target and build command
@TARGET="all"; \
ANSIBLE_CMD="ansible-playbook maintenance-playbook.yml"; \
DESCRIPTION="all hosts"; \
NEED_SUDO=""; \
\
if [ -n "$(HOST)" ]; then \
TARGET="host $(HOST)"; \
ANSIBLE_CMD="$$ANSIBLE_CMD --limit $(HOST)"; \
DESCRIPTION="host $(HOST)"; \
if [ "$(HOST)" = "localhost" ]; then \
NEED_SUDO="-K"; \
fi; \
elif [ -n "$(GROUP)" ]; then \
TARGET="$(GROUP) group"; \
ANSIBLE_CMD="$$ANSIBLE_CMD -e target_group=$(GROUP)"; \
DESCRIPTION="$(GROUP) group"; \
if [ "$(GROUP)" = "local" ]; then \
NEED_SUDO="-K"; \
fi; \
else \
NEED_SUDO="-K"; \
fi; \
\
if [ -n "$(SERIAL)" ]; then \
ANSIBLE_CMD="$$ANSIBLE_CMD -e maintenance_serial=$(SERIAL)"; \
DESCRIPTION="$$DESCRIPTION (serial=$(SERIAL))"; \
fi; \
\
if [ "$(CHECK)" = "true" ]; then \
ANSIBLE_CMD="$$ANSIBLE_CMD --check --diff"; \
echo "$(YELLOW)Dry-run maintenance on $$DESCRIPTION...$(RESET)"; \
else \
echo "$(YELLOW)Running maintenance on $$DESCRIPTION...$(RESET)"; \
fi; \
\
if [ -n "$(GROUP)" ] && [ "$(GROUP)" != "dev" ] && [ "$(GROUP)" != "local" ]; then \
echo "$(BLUE)Available groups: dev, gitea, portainer, homepage, ansible, local$(RESET)"; \
fi; \
\
$$ANSIBLE_CMD $$NEED_SUDO
# Legacy/convenience aliases
maintenance-dev: ## Run maintenance on dev group (legacy alias)
@$(MAKE) maintenance GROUP=dev
maintenance-all: ## Run maintenance on all hosts (legacy alias)
@$(MAKE) maintenance
maintenance-check: ## Dry-run maintenance (legacy alias, usage: make maintenance-check [GROUP=dev])
@$(MAKE) maintenance CHECK=true GROUP=$(GROUP)
docker: ## Install/configure Docker only
@echo "$(YELLOW)Running Docker setup...$(RESET)"
ansible-playbook dev-playbook.yml --tags docker
shell: ## Configure shell only
@echo "$(YELLOW)Running shell configuration...$(RESET)"
ansible-playbook dev-playbook.yml --tags shell
apps: ## Install applications only
@echo "$(YELLOW)Installing applications...$(RESET)"
ansible-playbook dev-playbook.yml --tags apps
# Connectivity targets
ping: ## Ping hosts with colored output (usage: make ping [GROUP=dev] [HOST=dev01])
ifdef HOST
@echo "$(YELLOW)Pinging host: $(HOST)$(RESET)"
@ansible $(HOST) -m ping --one-line | while read line; do \
if echo "$$line" | grep -q "SUCCESS"; then \
echo "$(GREEN)$$line$(RESET)"; \
elif echo "$$line" | grep -q "UNREACHABLE"; then \
echo "$(RED)$$line$(RESET)"; \
else \
echo "$(YELLOW)? $$line$(RESET)"; \
fi; \
done
else ifdef GROUP
@echo "$(YELLOW)Pinging $(GROUP) group...$(RESET)"
@ansible $(GROUP) -m ping --one-line | while read line; do \
if echo "$$line" | grep -q "SUCCESS"; then \
echo "$(GREEN)✓ $$line$(RESET)"; \
elif echo "$$line" | grep -q "UNREACHABLE"; then \
echo "$(RED)✗ $$line$(RESET)"; \
else \
echo "$(YELLOW)? $$line$(RESET)"; \
fi; \
done
else
@echo "$(YELLOW)Pinging all hosts...$(RESET)"
@if [ -n "$(CURRENT_HOST)" ]; then \
echo "$(BLUE)Auto-excluding current host: $(CURRENT_HOST) ($(CURRENT_IP))$(RESET)"; \
fi
@echo ""
@ansible all -m ping --one-line $(EXCLUDE_CURRENT) 2>/dev/null | grep -E "(SUCCESS|UNREACHABLE)" > /tmp/ping_results.tmp; \
success_count=$$(grep -c "SUCCESS" /tmp/ping_results.tmp 2>/dev/null || echo 0); \
fail_count=$$(grep -c "UNREACHABLE" /tmp/ping_results.tmp 2>/dev/null || echo 0); \
while read line; do \
host=$$(echo "$$line" | cut -d' ' -f1); \
if echo "$$line" | grep -q "SUCCESS"; then \
printf "$(GREEN) ✓ %-20s$(RESET) Connected\n" "$$host"; \
elif echo "$$line" | grep -q "UNREACHABLE"; then \
reason=$$(echo "$$line" | grep -o "Permission denied.*" | head -1 || echo "Connection failed"); \
printf "$(RED) ✗ %-20s$(RESET) $$reason\n" "$$host"; \
fi; \
done < /tmp/ping_results.tmp; \
rm -f /tmp/ping_results.tmp; \
echo ""; \
printf "$(BOLD)Summary:$(RESET) $(GREEN)$$success_count connected$(RESET), $(RED)$$fail_count failed$(RESET)\n"
endif
facts: ## Gather facts from all hosts
@echo "$(YELLOW)Gathering facts...$(RESET)"
ansible all -m setup --tree /tmp/facts $(EXCLUDE_CURRENT)
show-current: ## Show current host that will be auto-excluded
@echo "$(BOLD)Current Host Detection:$(RESET)"
@echo " Current IP: $(BLUE)$(CURRENT_IP)$(RESET)"
@echo " Current Host: $(BLUE)$(CURRENT_HOST)$(RESET)"
@echo " Exclude Flag: $(BLUE)$(EXCLUDE_CURRENT)$(RESET)"
clean: ## Clean up ansible artifacts
@echo "$(YELLOW)Cleaning up artifacts...$(RESET)"
rm -rf .ansible/facts/
find . -name "*.retry" -delete
@echo "$(GREEN)✓ Cleanup completed$(RESET)"
# Debug targets
debug: ## Run with debug output enabled
@echo "$(YELLOW)Running with debug output...$(RESET)"
ansible-playbook dev-playbook.yml -e "ansible_debug_output=true"
verbose: ## Run with verbose output
@echo "$(YELLOW)Running with verbose output...$(RESET)"
ansible-playbook dev-playbook.yml -vv
# Quick development workflow
quick: test check ## Quick test and check before applying
@echo "$(GREEN)✓ Ready to apply changes$(RESET)"
# Tailscale management
tailscale: ## Install Tailscale on all machines
@echo "$(YELLOW)Installing Tailscale on all machines...$(RESET)"
ansible-playbook -i hosts tailscale-playbook.yml
@echo "$(GREEN)✓ Tailscale installation complete$(RESET)"
tailscale-check: ## Check Tailscale installation (dry-run)
@echo "$(YELLOW)Checking Tailscale installation...$(RESET)"
ansible-playbook -i hosts tailscale-playbook.yml --check --diff
@echo "$(GREEN)✓ Tailscale check complete$(RESET)"
tailscale-dev: ## Install Tailscale on dev machines only
@echo "$(YELLOW)Installing Tailscale on dev machines...$(RESET)"
ansible-playbook -i hosts tailscale-playbook.yml --limit dev
@echo "$(GREEN)✓ Tailscale installation on dev machines complete$(RESET)"
tailscale-status: ## Check Tailscale status on all machines
@echo "$(BOLD)Tailscale Status$(RESET)"
@if [ -n "$(CURRENT_HOST)" ]; then \
echo "$(BLUE)Auto-excluding current host: $(CURRENT_HOST) ($(CURRENT_IP))$(RESET)"; \
fi
@echo ""
@ansible all -i hosts -m shell -a "tailscale status --json | jq -r '.Self.DNSName + \" (\" + .Self.TailscaleIPs[0] + \") - \" + .BackendState'" --become $(EXCLUDE_CURRENT) 2>/dev/null | while read line; do \
host=$$(echo "$$line" | cut -d' ' -f1); \
status=$$(echo "$$line" | grep -o "Running\|Stopped\|NeedsLogin" || echo "Unknown"); \
ip=$$(echo "$$line" | grep -o "100\.[0-9.]*" || echo "No IP"); \
if echo "$$line" | grep -q "SUCCESS"; then \
result=$$(echo "$$line" | cut -d'>' -f2 | tr -d ' "'); \
printf " $(GREEN)✓ %-20s$(RESET) %s\n" "$$host" "$$result"; \
elif echo "$$line" | grep -q "FAILED\|UNREACHABLE"; then \
printf " $(RED)✗ %-20s$(RESET) Not available\n" "$$host"; \
fi; \
done || ansible all -i hosts -m shell -a "tailscale status" --become $(EXCLUDE_CURRENT) 2>/dev/null | grep -E "(SUCCESS|FAILED)" | while read line; do \
host=$$(echo "$$line" | cut -d' ' -f1); \
if echo "$$line" | grep -q "SUCCESS"; then \
printf " $(GREEN)✓ %-20s$(RESET) Connected\n" "$$host"; \
else \
printf " $(RED)✗ %-20s$(RESET) Failed\n" "$$host"; \
fi; \
done
# Vault management
edit-vault: ## Edit encrypted host vars (usage: make edit-vault HOST=dev01)
ifndef HOST
@echo "$(RED)Error: HOST parameter required$(RESET)"
@echo "Usage: make edit-vault HOST=dev01"
@exit 1
endif
ansible-vault edit host_vars/$(HOST).yml
test-connectivity: ## Test network connectivity and SSH access to all hosts
@echo "$(BOLD)Connectivity Test$(RESET)"
@if [ -n "$(CURRENT_HOST)" ]; then \
echo "$(BLUE)Auto-excluding current host: $(CURRENT_HOST) ($(CURRENT_IP))$(RESET)"; \
fi
@echo ""
@echo "$(YELLOW)Network Connectivity:$(RESET)"
@ansible-inventory --list | jq -r '._meta.hostvars | to_entries[] | select(.value.ansible_host) | "\(.key) \(.value.ansible_host)"' 2>/dev/null | while read host ip; do \
if [ "$$host" != "$(CURRENT_HOST)" ]; then \
printf " %-20s " "$$host ($$ip)"; \
if ping -c 1 -W 2 $$ip >/dev/null 2>&1; then \
echo "$(GREEN)✓ Network OK$(RESET)"; \
else \
echo "$(RED)✗ Network FAIL$(RESET)"; \
fi; \
fi; \
done
@echo ""
@echo "$(YELLOW)SSH Connectivity:$(RESET)"
@ansible all -m ping --one-line $(EXCLUDE_CURRENT) 2>/dev/null | grep -E "(SUCCESS|UNREACHABLE)" | while read line; do \
host=$$(echo "$$line" | cut -d' ' -f1); \
if echo "$$line" | grep -q "SUCCESS"; then \
printf " $(GREEN)✓ %-20s$(RESET) SSH OK\n" "$$host"; \
elif echo "$$line" | grep -q "UNREACHABLE"; then \
printf " $(RED)✗ %-20s$(RESET) SSH FAIL\n" "$$host"; \
fi; \
done
@echo ""
@echo "$(YELLOW)SSH Keys:$(RESET)"
@if [ -f ~/.ssh/id_ed25519.pub ]; then \
echo " $(GREEN)✓ SSH key available$(RESET) (id_ed25519)"; \
elif [ -f ~/.ssh/id_rsa.pub ]; then \
echo " $(GREEN)✓ SSH key available$(RESET) (id_rsa)"; \
else \
echo " $(RED)✗ No SSH key found$(RESET)"; \
echo " $(YELLOW) Run: ssh-keygen -t ed25519$(RESET)"; \
fi
copy-ssh-key: ## Copy SSH key to specific host (usage: make copy-ssh-key HOST=giteaVM)
ifndef HOST
@echo "$(RED)Error: HOST parameter required$(RESET)"
@echo "Usage: make copy-ssh-key HOST=giteaVM"
@exit 1
endif
@echo "$(YELLOW)Copying SSH key to $(HOST)...$(RESET)"
@ip=$$(ansible-inventory --list | jq -r "._meta.hostvars.$(HOST).ansible_host // empty" 2>/dev/null); \
user=$$(ansible-inventory --list | jq -r "._meta.hostvars.$(HOST).ansible_user // empty" 2>/dev/null); \
if [ -n "$$ip" ] && [ "$$ip" != "null" ] && [ -n "$$user" ] && [ "$$user" != "null" ]; then \
echo "Target: $$user@$$ip"; \
ssh-copy-id $$user@$$ip; \
else \
echo "$(RED)Could not determine IP or user for $(HOST)$(RESET)"; \
echo "Check your inventory and host_vars"; \
fi
create-vault: ## Create encrypted vault file for secrets (passwords, auth keys, etc.)
@echo "$(YELLOW)Creating vault file for storing secrets...$(RESET)"
ansible-vault create group_vars/all/vault.yml
@echo "$(GREEN)✓ Vault file created. Add your secrets here (e.g. vault_tailscale_auth_key)$(RESET)"