.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 PLAYBOOK_PROXMOX_INFO := playbooks/app/proxmox_info.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)App Project Playbooks:$(RESET)" @for playbook in playbooks/app/site.yml playbooks/app/provision_vms.yml playbooks/app/configure_app.yml playbooks/app/ssh_client_config.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" >/dev/null 2>&1; then \ 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; \ else \ echo "$(YELLOW)⚠ Skipped (PyYAML not installed)$(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 only @echo "$(YELLOW)Running shell configuration...$(RESET)" $(ANSIBLE_PLAYBOOK) $(PLAYBOOK_DEV) --tags shell shell-all: ## Configure shell on all shell_hosts (usage: make shell-all) @echo "$(YELLOW)Running shell configuration on all shell 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)" proxmox-info: ## Show Proxmox VM/LXC info (usage: make proxmox-info [PROJECT=projectA] [ALL=true] [TYPE=lxc|qemu|all]) @echo "$(YELLOW)Querying Proxmox guest info...$(RESET)" @EXTRA=""; \ if [ -n "$(PROJECT)" ]; then EXTRA="$$EXTRA -e app_project=$(PROJECT)"; fi; \ if [ "$(ALL)" = "true" ]; then EXTRA="$$EXTRA -e proxmox_info_all=true"; fi; \ if [ -n "$(TYPE)" ]; then EXTRA="$$EXTRA -e proxmox_info_type=$(TYPE)"; fi; \ $(ANSIBLE_PLAYBOOK) $(PLAYBOOK_PROXMOX_INFO) $$EXTRA app-provision: ## Provision app project containers/VMs on Proxmox (usage: make app-provision PROJECT=projectA) ifndef PROJECT @echo "$(RED)Error: PROJECT parameter required$(RESET)" @echo "Usage: make app-provision PROJECT=projectA" @exit 1 endif @echo "$(YELLOW)Provisioning app project guests on Proxmox: $(PROJECT)$(RESET)" $(ANSIBLE_PLAYBOOK) playbooks/app/provision_vms.yml -e app_project=$(PROJECT) app-configure: ## Configure OS + app on project guests (usage: make app-configure PROJECT=projectA) ifndef PROJECT @echo "$(RED)Error: PROJECT parameter required$(RESET)" @echo "Usage: make app-configure PROJECT=projectA" @exit 1 endif @echo "$(YELLOW)Configuring app project guests: $(PROJECT)$(RESET)" $(ANSIBLE_PLAYBOOK) playbooks/app/configure_app.yml -e app_project=$(PROJECT) app: ## Provision + configure app project (usage: make app PROJECT=projectA) ifndef PROJECT @echo "$(RED)Error: PROJECT parameter required$(RESET)" @echo "Usage: make app PROJECT=projectA" @exit 1 endif @echo "$(YELLOW)Provisioning + configuring app project: $(PROJECT)$(RESET)" $(ANSIBLE_PLAYBOOK) playbooks/app/site.yml -e app_project=$(PROJECT) 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