Some checks failed
CI / lint-and-test (pull_request) Failing after 58s
CI / ansible-validation (pull_request) Failing after 1m58s
CI / secret-scanning (pull_request) Successful in 58s
CI / dependency-scan (pull_request) Successful in 1m1s
CI / sast-scan (pull_request) Successful in 1m55s
CI / license-check (pull_request) Successful in 58s
CI / vault-check (pull_request) Failing after 1m55s
CI / playbook-test (pull_request) Successful in 1m57s
CI / container-scan (pull_request) Successful in 1m27s
CI / sonar-analysis (pull_request) Successful in 2m4s
CI / workflow-summary (pull_request) Successful in 55s
- Fix UFW firewall to allow outbound traffic (was blocking all outbound) - Add HOST parameter support to shell Makefile target - Fix all ansible-lint errors (trailing spaces, missing newlines, document starts) - Add changed_when: false to check commands - Fix variable naming (vault_devGPU -> vault_devgpu) - Update .ansible-lint config to exclude .gitea/ and allow strategy: free - Fix NodeSource repository GPG key handling in shell playbook - Add missing document starts to host_vars files - Clean up empty lines in datascience role files
597 lines
23 KiB
Makefile
597 lines
23 KiB
Makefile
.PHONY: help bootstrap lint test check dev datascience inventory inventory-all local clean status tailscale tailscale-check tailscale-dev tailscale-status create-vault create-vm monitoring
|
|
.DEFAULT_GOAL := help
|
|
|
|
## Colors for output
|
|
BOLD := \033[1m
|
|
RED := \033[31m
|
|
GREEN := \033[32m
|
|
YELLOW := \033[33m
|
|
BLUE := \033[34m
|
|
RESET := \033[0m
|
|
|
|
# Playbook paths
|
|
PLAYBOOK_SITE := playbooks/site.yml
|
|
PLAYBOOK_DEV := playbooks/development.yml
|
|
PLAYBOOK_LOCAL := playbooks/local.yml
|
|
PLAYBOOK_MAINTENANCE := playbooks/maintenance.yml
|
|
PLAYBOOK_TAILSCALE := playbooks/tailscale.yml
|
|
PLAYBOOK_PROXMOX := playbooks/infrastructure/proxmox-vm.yml
|
|
|
|
# Collection and requirement paths
|
|
COLLECTIONS_REQ := collections/requirements.yml
|
|
PYTHON_REQ := requirements.txt
|
|
|
|
# Inventory paths
|
|
INVENTORY := inventories/production
|
|
INVENTORY_HOSTS := $(INVENTORY)/hosts
|
|
|
|
# Common ansible-playbook command with options
|
|
ANSIBLE_PLAYBOOK := ansible-playbook -i $(INVENTORY)
|
|
ANSIBLE_ARGS := --vault-password-file ~/.ansible-vault-pass
|
|
# Note: sudo passwords are in vault files as ansible_become_password
|
|
|
|
## 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 " make maintenance VERBOSE=true # Run with verbose output"
|
|
@echo " make maintenance-verbose GROUP=dev # Verbose maintenance on dev group"
|
|
@echo ""
|
|
|
|
bootstrap: ## Install all project dependencies from requirements files
|
|
@echo "$(BOLD)Installing Project Dependencies$(RESET)"
|
|
@echo ""
|
|
@echo "$(YELLOW)Python Requirements ($(PYTHON_REQ)):$(RESET)"
|
|
@if [ -f "$(PYTHON_REQ)" ]; then \
|
|
if command -v pipx >/dev/null 2>&1; then \
|
|
printf " %-30s " "Installing with pipx"; \
|
|
if pipx install -r $(PYTHON_REQ) >/dev/null 2>&1; then \
|
|
echo "$(GREEN)✓ Installed$(RESET)"; \
|
|
else \
|
|
echo "$(YELLOW)⚠ Some packages may have failed$(RESET)"; \
|
|
fi; \
|
|
elif command -v pip3 >/dev/null 2>&1; then \
|
|
printf " %-30s " "Installing with pip3 --user"; \
|
|
if pip3 install --user -r $(PYTHON_REQ) >/dev/null 2>&1; then \
|
|
echo "$(GREEN)✓ Installed$(RESET)"; \
|
|
else \
|
|
printf " %-30s " "Trying with --break-system-packages"; \
|
|
if pip3 install --break-system-packages -r $(PYTHON_REQ) >/dev/null 2>&1; then \
|
|
echo "$(GREEN)✓ Installed$(RESET)"; \
|
|
else \
|
|
echo "$(RED)✗ Failed$(RESET)"; \
|
|
fi; \
|
|
fi; \
|
|
else \
|
|
printf " %-30s " "Python packages"; \
|
|
echo "$(YELLOW)⚠ Skipped (pip3/pipx not found)$(RESET)"; \
|
|
fi; \
|
|
else \
|
|
printf " %-30s " "$(PYTHON_REQ)"; \
|
|
echo "$(RED)✗ File not found$(RESET)"; \
|
|
fi
|
|
@echo ""
|
|
@echo "$(YELLOW)Node.js Dependencies (package.json):$(RESET)"
|
|
@if [ -f "package.json" ] && command -v npm >/dev/null 2>&1; then \
|
|
printf " %-30s " "Installing npm packages"; \
|
|
if npm install >/dev/null 2>&1; then \
|
|
echo "$(GREEN)✓ Installed$(RESET)"; \
|
|
else \
|
|
echo "$(RED)✗ Failed$(RESET)"; \
|
|
fi; \
|
|
else \
|
|
printf " %-30s " "npm packages"; \
|
|
echo "$(YELLOW)⚠ Skipped (package.json or npm not found)$(RESET)"; \
|
|
fi
|
|
@echo ""
|
|
@echo "$(YELLOW)Ansible Collections ($(COLLECTIONS_REQ)):$(RESET)"
|
|
@if [ -f "$(COLLECTIONS_REQ)" ]; then \
|
|
ansible-galaxy collection install -r $(COLLECTIONS_REQ) 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_REQ); \
|
|
else \
|
|
printf " %-30s " "$(COLLECTIONS_REQ)"; \
|
|
echo "$(RED)✗ File not found$(RESET)"; \
|
|
fi
|
|
@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 comprehensive syntax and validation checks
|
|
@echo "$(BOLD)Comprehensive Testing$(RESET)"
|
|
@echo ""
|
|
@echo "$(YELLOW)Playbook Syntax:$(RESET)"
|
|
@for playbook in $(PLAYBOOK_DEV) $(PLAYBOOK_LOCAL) $(PLAYBOOK_MAINTENANCE) $(PLAYBOOK_TAILSCALE); 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 ""
|
|
@echo "$(YELLOW)Infrastructure Playbooks:$(RESET)"
|
|
@for playbook in playbooks/infrastructure/*.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 ""
|
|
@echo "$(YELLOW)Role Test Playbooks:$(RESET)"
|
|
@for test_playbook in roles/*/tests/test.yml; do \
|
|
if [ -f "$$test_playbook" ]; then \
|
|
role_name=$$(echo "$$test_playbook" | cut -d'/' -f2); \
|
|
printf " %-25s " "roles/$$role_name/test"; \
|
|
if ansible-playbook "$$test_playbook" --syntax-check >/dev/null 2>&1; then \
|
|
echo "$(GREEN)✓ OK$(RESET)"; \
|
|
else \
|
|
echo "$(RED)✗ FAIL$(RESET)"; \
|
|
fi; \
|
|
fi; \
|
|
done
|
|
@echo ""
|
|
@echo "$(YELLOW)Markdown Validation:$(RESET)"
|
|
@if [ -f "package.json" ] && command -v npm >/dev/null 2>&1; then \
|
|
printf " %-25s " "Markdown syntax"; \
|
|
if npm run test:markdown >/dev/null 2>&1; then \
|
|
echo "$(GREEN)✓ OK$(RESET)"; \
|
|
else \
|
|
echo "$(YELLOW)⚠ Issues$(RESET)"; \
|
|
fi; \
|
|
else \
|
|
printf " %-25s " "Markdown syntax"; \
|
|
echo "$(YELLOW)⚠ npm/package.json not available$(RESET)"; \
|
|
fi
|
|
@echo ""
|
|
@echo "$(YELLOW)Documentation Links:$(RESET)"
|
|
@if [ -f "package.json" ] && command -v npm >/dev/null 2>&1; then \
|
|
printf " %-25s " "Link validation"; \
|
|
if npm run test:links >/dev/null 2>&1; then \
|
|
echo "$(GREEN)✓ OK$(RESET)"; \
|
|
else \
|
|
echo "$(YELLOW)⚠ Issues$(RESET)"; \
|
|
fi; \
|
|
else \
|
|
printf " %-25s " "Link validation"; \
|
|
echo "$(YELLOW)⚠ npm/package.json not available$(RESET)"; \
|
|
fi
|
|
@echo ""
|
|
@echo "$(YELLOW)Configuration Validation:$(RESET)"
|
|
@for yaml_file in inventories/production/group_vars/all/main.yml; do \
|
|
if [ -f "$$yaml_file" ]; then \
|
|
printf " %-25s " "$$yaml_file (YAML)"; \
|
|
if python3 -c "import yaml; yaml.safe_load(open('$$yaml_file'))" >/dev/null 2>&1; then \
|
|
echo "$(GREEN)✓ OK$(RESET)"; \
|
|
else \
|
|
echo "$(RED)✗ FAIL$(RESET)"; \
|
|
fi; \
|
|
fi; \
|
|
done
|
|
@printf " %-25s " "ansible.cfg (INI)"; \
|
|
if python3 -c "import configparser; c=configparser.ConfigParser(); c.read('ansible.cfg')" >/dev/null 2>&1; then \
|
|
echo "$(GREEN)✓ OK$(RESET)"; \
|
|
else \
|
|
echo "$(RED)✗ FAIL$(RESET)"; \
|
|
fi
|
|
@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: auto-fallback ## Dry-run the development playbook (--check mode)
|
|
@echo "$(YELLOW)Running dry-run on development hosts...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --check --diff
|
|
|
|
check-local: ## Dry-run the local playbook
|
|
@echo "$(YELLOW)Running dry-run on localhost...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_LOCAL) --check --diff -K
|
|
|
|
site: ## Run the complete site playbook
|
|
@echo "$(YELLOW)Running complete site deployment...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_SITE)
|
|
|
|
local: ## Run the local playbook on localhost
|
|
@echo "$(YELLOW)Applying local playbook...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_LOCAL) -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) $(PLAYBOOK_DEV) --limit $(HOST)
|
|
|
|
# Data science role
|
|
datascience: ## Install data science stack (usage: make datascience HOST=server01)
|
|
ifndef HOST
|
|
@echo "$(RED)Error: HOST parameter required$(RESET)"
|
|
@echo "Usage: make datascience HOST=server01"
|
|
@exit 1
|
|
endif
|
|
@echo "$(YELLOW)Installing data science stack on: $(HOST)$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --limit $(HOST) --tags datascience
|
|
|
|
# Inventory system
|
|
inventory: ## Show installed software on host (usage: make inventory HOST=dev01)
|
|
ifndef HOST
|
|
@echo "$(RED)Error: HOST parameter required$(RESET)"
|
|
@echo "Usage: make inventory HOST=dev01"
|
|
@exit 1
|
|
endif
|
|
@echo "$(BOLD)Software Inventory for: $(HOST)$(RESET)"
|
|
@echo ""
|
|
@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 \
|
|
scp -q scripts/inventory.sh $$user@$$ip:/tmp/inventory.sh 2>/dev/null && \
|
|
ssh $$user@$$ip 'bash /tmp/inventory.sh; rm /tmp/inventory.sh' 2>/dev/null; \
|
|
else \
|
|
echo "$(RED)Could not determine IP or user for $(HOST)$(RESET)"; \
|
|
fi
|
|
|
|
inventory-all: ## Show installed software on all hosts
|
|
@echo "$(BOLD)Software Inventory - All Hosts$(RESET)\n"
|
|
@for host in $$(ansible all --list-hosts 2>/dev/null | grep -v "hosts" | tr -d ' '); do \
|
|
echo "$(YELLOW)=== $$host ===$(RESET)"; \
|
|
ansible $$host -m script -a "scripts/inventory.sh" 2>/dev/null | sed -n '/CHANGED/,$$p' | tail -n +3 || echo "$(RED)Failed to connect$(RESET)"; \
|
|
echo ""; \
|
|
done
|
|
|
|
# Tag-based execution
|
|
security: ## Run only security-related roles
|
|
@echo "$(YELLOW)Running security roles...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --tags security
|
|
|
|
# Unified maintenance target with intelligent parameter detection
|
|
maintenance: ## Run maintenance (usage: make maintenance [GROUP=dev] [HOST=dev01] [SERIAL=1] [CHECK=true] [VERBOSE=true])
|
|
@$(MAKE) _maintenance-run
|
|
|
|
_maintenance-run:
|
|
@# Determine target and build command
|
|
@TARGET="all"; \
|
|
ANSIBLE_CMD="$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_MAINTENANCE)"; \
|
|
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 [ "$(VERBOSE)" = "true" ]; then \
|
|
ANSIBLE_CMD="$$ANSIBLE_CMD -v"; \
|
|
echo "$(BLUE)Running with verbose output...$(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)
|
|
|
|
maintenance-verbose: ## Run maintenance with verbose output (usage: make maintenance-verbose [GROUP=dev])
|
|
@$(MAKE) maintenance VERBOSE=true GROUP=$(GROUP)
|
|
|
|
docker: ## Install/configure Docker only
|
|
@echo "$(YELLOW)Running Docker setup...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --tags docker
|
|
|
|
shell: ## Configure shell (usage: make shell [HOST=dev02] [SUDO=true])
|
|
ifdef HOST
|
|
@echo "$(YELLOW)Running shell configuration on host: $(HOST)$(RESET)"
|
|
@if [ "$(SUDO)" = "true" ]; then \
|
|
$(ANSIBLE_PLAYBOOK) playbooks/shell.yml --limit $(HOST) $(ANSIBLE_ARGS) -K; \
|
|
else \
|
|
$(ANSIBLE_PLAYBOOK) playbooks/shell.yml --limit $(HOST) $(ANSIBLE_ARGS); \
|
|
fi
|
|
else
|
|
@echo "$(YELLOW)Running shell configuration on all dev hosts...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --tags shell
|
|
endif
|
|
|
|
shell-all: ## Configure shell on all hosts (usage: make shell-all)
|
|
@echo "$(YELLOW)Running shell configuration on all hosts...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) playbooks/shell.yml $(ANSIBLE_ARGS)
|
|
|
|
apps: ## Install applications only
|
|
@echo "$(YELLOW)Installing applications...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --tags apps
|
|
|
|
# Connectivity targets
|
|
ping: auto-fallback ## 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) $(PLAYBOOK_DEV) -e "ansible_debug_output=true"
|
|
|
|
verbose: ## Run with verbose output
|
|
@echo "$(YELLOW)Running with verbose output...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) -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) $(PLAYBOOK_TAILSCALE)
|
|
@echo "$(GREEN)✓ Tailscale installation complete$(RESET)"
|
|
|
|
tailscale-check: ## Check Tailscale installation (dry-run)
|
|
@echo "$(YELLOW)Checking Tailscale installation...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_TAILSCALE) --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) $(PLAYBOOK_TAILSCALE) --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 -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 -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
|
|
|
|
edit-group-vault: ## Edit encrypted group vars (usage: make edit-group-vault)
|
|
ansible-vault edit inventories/production/group_vars/all/vault.yml
|
|
|
|
|
|
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)"
|
|
|
|
create-vm: ## Create Ansible controller VM on Proxmox
|
|
@echo "$(YELLOW)Creating Ansible controller VM on Proxmox...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_PROXMOX) --ask-vault-pass
|
|
@echo "$(GREEN)✓ VM creation complete$(RESET)"
|
|
|
|
monitoring: ## Install monitoring tools on all machines
|
|
@echo "$(YELLOW)Installing monitoring tools...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --tags monitoring
|
|
@echo "$(GREEN)✓ Monitoring installation complete$(RESET)"
|
|
|
|
# Timeshift snapshot and rollback
|
|
timeshift-snapshot: ## Create Timeshift snapshot (usage: make timeshift-snapshot HOST=dev02)
|
|
ifndef HOST
|
|
@echo "$(RED)Error: HOST parameter required$(RESET)"
|
|
@echo "Usage: make timeshift-snapshot HOST=dev02"
|
|
@exit 1
|
|
endif
|
|
@echo "$(YELLOW)Creating Timeshift snapshot on $(HOST)...$(RESET)"
|
|
$(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --limit $(HOST) --tags timeshift,snapshot
|
|
@echo "$(GREEN)✓ Snapshot created$(RESET)"
|
|
|
|
timeshift-list: ## List Timeshift snapshots (usage: make timeshift-list HOST=dev02)
|
|
ifndef HOST
|
|
@echo "$(RED)Error: HOST parameter required$(RESET)"
|
|
@echo "Usage: make timeshift-list HOST=dev02"
|
|
@exit 1
|
|
endif
|
|
@echo "$(YELLOW)Listing Timeshift snapshots on $(HOST)...$(RESET)"
|
|
@$(ANSIBLE_PLAYBOOK) playbooks/timeshift.yml --limit $(HOST) -e "timeshift_action=list" $(ANSIBLE_ARGS)
|
|
|
|
timeshift-restore: ## Restore from Timeshift snapshot (usage: make timeshift-restore HOST=dev02 SNAPSHOT=2025-12-17_21-30-00)
|
|
ifndef HOST
|
|
@echo "$(RED)Error: HOST parameter required$(RESET)"
|
|
@echo "Usage: make timeshift-restore HOST=dev02 SNAPSHOT=2025-12-17_21-30-00"
|
|
@exit 1
|
|
endif
|
|
ifndef SNAPSHOT
|
|
@echo "$(RED)Error: SNAPSHOT parameter required$(RESET)"
|
|
@echo "Usage: make timeshift-restore HOST=dev02 SNAPSHOT=2025-12-17_21-30-00"
|
|
@echo "$(YELLOW)Available snapshots:$(RESET)"
|
|
@$(MAKE) timeshift-list HOST=$(HOST)
|
|
@exit 1
|
|
endif
|
|
@echo "$(RED)WARNING: This will restore the system to snapshot $(SNAPSHOT)$(RESET)"
|
|
@echo "$(YELLOW)This action cannot be undone. Continue? [y/N]$(RESET)"
|
|
@read -r confirm && [ "$$confirm" = "y" ] || exit 1
|
|
@echo "$(YELLOW)Restoring snapshot $(SNAPSHOT) on $(HOST)...$(RESET)"
|
|
@$(ANSIBLE_PLAYBOOK) playbooks/timeshift.yml --limit $(HOST) -e "timeshift_action=restore timeshift_snapshot=$(SNAPSHOT)" $(ANSIBLE_ARGS)
|
|
@echo "$(GREEN)✓ Snapshot restored$(RESET)"
|
|
|
|
test-connectivity: ## Test host connectivity with detailed diagnostics and recommendations
|
|
@echo "$(YELLOW)Testing host connectivity...$(RESET)"
|
|
@if [ -f "test_connectivity.py" ]; then \
|
|
python3 test_connectivity.py --hosts-file $(INVENTORY_HOSTS); \
|
|
else \
|
|
echo "$(RED)Error: test_connectivity.py not found$(RESET)"; \
|
|
exit 1; \
|
|
fi
|
|
|
|
auto-fallback: ## Automatically switch to fallback IPs when primary IPs fail
|
|
@echo "$(YELLOW)Auto-fallback: Testing and switching to working IPs...$(RESET)"
|
|
@if [ -f "auto-fallback.sh" ]; then \
|
|
chmod +x auto-fallback.sh && ./auto-fallback.sh; \
|
|
else \
|
|
echo "$(RED)Error: auto-fallback.sh not found$(RESET)"; \
|
|
exit 1; \
|
|
fi
|