# Homelab Blueprint Makefile .PHONY: help check-env status deploy-caddy deploy-ha deploy-arr deploy-immich deploy-dns deploy-postgres deploy-gramps deploy-beszel deploy-beszel-hub deploy-proxy deploy-youtarr deploy-bento-pdf deploy-it-tools deploy-paperless deploy-vaultwarden deploy-koffan deploy-forgejo # Default shell SHELL := /bin/bash # Configuration ENV_FILE ?= .env STACKS_DIR := docker-compose-files SUBSTITUTE_SCRIPT := ./substitute_env.sh help: @echo "Homelab Blueprint Management" @echo "" @echo "Usage: make " @echo "" @echo "Deploy Targets:" @echo " deploy-caddy - Deploy Caddy Reverse Proxy" @echo " deploy-ha - Deploy Home Assistant (uses setup script)" @echo " deploy-arr - Deploy Arr Stack (Media Management)" @echo " deploy-immich - Deploy Immich (Photos)" @echo " deploy-dns - Deploy Technitium DNS" @echo " deploy-postgres - Deploy PostgreSQL" @echo " deploy-gramps - Deploy Gramps Web" @echo " deploy-beszel - Deploy Beszel Agent" @echo " deploy-beszel-hub - Deploy Beszel Hub (dashboard)" @echo " deploy-proxy - Deploy Beszel Socket Proxy" @echo " deploy-youtarr - Deploy Youtarr" @echo " deploy-bento-pdf - Deploy BentoPDF" @echo " deploy-it-tools - Deploy IT-Tools" @echo " deploy-paperless - Deploy Paperless-ngx" @echo " deploy-vaultwarden - Deploy Vaultwarden" @echo " deploy-koffan - Deploy Koffan (grocery list)" @echo " deploy-forgejo - Deploy Forgejo (self-hosted Git)" @echo "" @echo "Utility Targets:" @echo " status - Show status of all deployed stacks" @echo " check-env - Validate .env file against templates" @echo " ip-check - Check IP leak protection for VPN containers" @echo " clean - Remove generated docker-compose.*.yml files" check-env: @target_dir=$$( [ -n "$(STACK)" ] && echo "$(STACKS_DIR)/$(STACK)" || echo "$(STACKS_DIR)/*" ); \ for dir in $$target_dir; do \ if [ -d "$$dir" ]; then \ stack=$$(basename $$dir); \ local_env="$$dir/.env"; \ placeholders=$$(find "$$dir" -type f \( -name '*template*' -o -name '*.yaml' -o -name '*.yml' \) -print0 | \ xargs -0 awk '!/^[[:space:]]*#/ { while (match($$0, /\{\{[A-Za-z_][A-Za-z0-9_]*\}\}/)) { print substr($$0, RSTART + 2, RLENGTH - 4); $$0 = substr($$0, RSTART + RLENGTH) } }' | \ sort -u || true); \ if [ -n "$$placeholders" ]; then \ echo "Validating environment for: $$stack"; \ ERR=0; \ for var in $$placeholders; do \ if ! grep -qE "^$${var}=" $(ENV_FILE) 2>/dev/null && ! grep -qE "^$${var}=" "$$local_env" 2>/dev/null; then \ echo " [MISSING] $$var"; \ ERR=1; \ fi; \ done; \ if [ $$ERR -eq 1 ]; then echo "Error: Missing variables for $$stack. Check your .env files."; exit 1; fi; \ fi; \ fi; \ done status: @echo "--- Stack Status ---" @for f in docker-compose.*.yml; do \ if [ -f "$$f" ]; then \ echo "Stack: $$f"; \ docker compose -f "$$f" ps; \ echo ""; \ fi \ done ip-check: @test -x ./ipfconfig || { echo "Error: ipfconfig script not found or not executable." >&2; exit 1; } @./ipfconfig clean: @echo "Removing generated compose files..." @rm -f docker-compose.*.yml # Individual Stack Deployments # # deploy_stack(stack-dir, compose-suffix) - shared recipe for the common case: # check-env -> substitute template -> docker compose up -d define deploy_stack @$(MAKE) check-env STACK=$(1) @echo "Deploying $(1)..." @$(SUBSTITUTE_SCRIPT) $(STACKS_DIR)/$(1)/template.yaml docker-compose.$(2).yml $(ENV_FILE) $(STACKS_DIR)/$(1)/.env @docker compose -f docker-compose.$(2).yml up -d endef # Caddy is special: also renders the Caddyfile and forces an image rebuild. deploy-caddy: @$(MAKE) check-env STACK=caddy @echo "Deploying Caddy..." @$(SUBSTITUTE_SCRIPT) $(STACKS_DIR)/caddy/Caddyfile_template $(STACKS_DIR)/caddy/Caddyfile $(ENV_FILE) $(STACKS_DIR)/caddy/.env @$(SUBSTITUTE_SCRIPT) $(STACKS_DIR)/caddy/template.yaml docker-compose.caddy.yml $(ENV_FILE) $(STACKS_DIR)/caddy/.env @chmod 644 $(STACKS_DIR)/caddy/Caddyfile @docker compose -f docker-compose.caddy.yml up -d --build # HA uses an external setup script instead of substitute_env.sh. deploy-ha: @test -x ./setup-homeassistant.sh || { echo "Error: setup-homeassistant.sh not found or not executable." >&2; exit 1; } @$(MAKE) check-env STACK=homeassistant @echo "Deploying Home Assistant..." @./setup-homeassistant.sh @docker compose -f docker-compose.homeassistant.yml up -d deploy-arr: $(call deploy_stack,arr-stack,arr-stack) deploy-immich: $(call deploy_stack,immich,immich) deploy-dns: $(call deploy_stack,technitium,technitium) deploy-postgres: $(call deploy_stack,postgres,postgres) deploy-gramps: @$(MAKE) check-env STACK=gramps-web @echo "Deploying gramps-web..." @mkdir -p gramps_config @$(SUBSTITUTE_SCRIPT) $(STACKS_DIR)/gramps-web/config/config_template.cfg gramps_config/config.cfg $(ENV_FILE) $(STACKS_DIR)/gramps-web/.env @$(SUBSTITUTE_SCRIPT) $(STACKS_DIR)/gramps-web/template.yaml docker-compose.gramps.yml $(ENV_FILE) $(STACKS_DIR)/gramps-web/.env @docker compose -f docker-compose.gramps.yml up -d deploy-beszel: $(call deploy_stack,beszel-agent,beszel) deploy-beszel-hub: $(call deploy_stack,beszel-hub,beszel-hub) deploy-bento-pdf: $(call deploy_stack,bento-pdf,bento-pdf) deploy-it-tools: $(call deploy_stack,it-tools,it-tools) deploy-paperless: $(call deploy_stack,paperless-ngx,paperless) deploy-vaultwarden: $(call deploy_stack,vaultwarden,vaultwarden) deploy-koffan: $(call deploy_stack,koffan,koffan) deploy-proxy: $(call deploy_stack,socket-proxy,proxy) deploy-forgejo: $(call deploy_stack,forgejo,forgejo) deploy-youtarr: $(call deploy_stack,youtarr,youtarr) reload-caddy: @docker compose -f docker-compose.caddy.yml ps --status running --services 2>/dev/null | grep -q '^caddy$$' \ || { echo "Error: Caddy container is not running. Run 'make deploy-caddy' first." >&2; exit 1; } @$(MAKE) check-env STACK=caddy @echo "Performing zero-downtime Caddy reload..." @$(SUBSTITUTE_SCRIPT) $(STACKS_DIR)/caddy/Caddyfile_template $(STACKS_DIR)/caddy/Caddyfile $(ENV_FILE) $(STACKS_DIR)/caddy/.env @chmod 644 $(STACKS_DIR)/caddy/Caddyfile @docker compose -f docker-compose.caddy.yml exec caddy caddy validate --config /etc/caddy/Caddyfile @docker compose -f docker-compose.caddy.yml exec caddy caddy reload --config /etc/caddy/Caddyfile