129 lines
6.1 KiB
Makefile
129 lines
6.1 KiB
Makefile
# -------- RailianceHosts Make Utilities --------
|
||
SHELL := /usr/bin/env bash
|
||
.DEFAULT_GOAL := help
|
||
|
||
# Set this to your Gitea host if you want 'remote-set' helper
|
||
GITEA ?= gitea.example.com
|
||
OWNER ?= coulomb
|
||
REPO ?= railiance-hosts
|
||
|
||
# New-host defaults (can be overridden: make new-host NAME=... TYPE=...)
|
||
TYPE ?= cpx21
|
||
REGION ?= nbg1
|
||
ROLE ?= generic
|
||
IMG ?= ubuntu-24.04
|
||
USER ?= admin
|
||
|
||
# Decrypt Hetzner token at runtime (requires SOPS_AGE_KEY or keys.txt locally)
|
||
HCLOUD_TOKEN := $(shell sops -d --extract '["ops"]["hcloud_token"]' inventory/group_vars/secrets.sops.yaml 2>/dev/null)
|
||
|
||
# ---- Help ----
|
||
help: ## Show this help
|
||
@echo "RailianceHosts Commands"; \
|
||
grep -E '^[a-zA-Z0-9_-]+:.*?## ' $(MAKEFILE_LIST) | sort | sed 's/:.*##/: /'
|
||
|
||
# ---- Git hooks ----
|
||
hooks: ## Configure git to use repo-local hooks (.githooks) and ensure executables
|
||
@mkdir -p .githooks
|
||
git config core.hooksPath .githooks
|
||
@test -f .githooks/pre-commit || (echo "❌ Missing .githooks/pre-commit"; exit 1)
|
||
chmod +x .githooks/pre-commit
|
||
@echo "✔ hooks enabled and pre-commit is executable"
|
||
|
||
hooks-test: ## Test secrets hook blocks plaintext in secrets/
|
||
@mkdir -p secrets && echo 'PLAINTEXT_TEST=true' > secrets/_hook_test.yaml
|
||
@git add secrets/_hook_test.yaml || true
|
||
@if git commit -m "TEST: should be blocked" 2>/dev/null; then \
|
||
echo "❌ Hook did NOT block plaintext (check .githooks/pre-commit)"; \
|
||
git reset --soft HEAD~1; \
|
||
else \
|
||
echo "✔ Hook blocked plaintext as expected"; \
|
||
fi
|
||
@git restore --staged secrets/_hook_test.yaml || true
|
||
@rm -f secrets/_hook_test.yaml
|
||
|
||
# ---- SOPS / Age helpers ----
|
||
sops-setup: ## Copy age key to SOPS default path (~/.config/sops/age/keys.txt)
|
||
mkdir -p ~/.config/sops/age
|
||
cp -n ~/.config/age/key.txt ~/.config/sops/age/keys.txt || true
|
||
chmod 600 ~/.config/sops/age/keys.txt
|
||
@echo "✔ SOPS key path set (~/.config/sops/age/keys.txt). Alternatively export SOPS_AGE_KEY."
|
||
|
||
sops-edit: ## Edit the global secrets with SOPS
|
||
sops inventory/group_vars/secrets.sops.yaml
|
||
|
||
sops-encrypt: ## Encrypt a file in place: make sops-encrypt FILE=secrets/foo.yaml
|
||
@[ -n "$(FILE)" ] || (echo "Usage: make sops-encrypt FILE=secrets/xxx.yaml" && exit 1)
|
||
sops --encrypt --in-place $(FILE)
|
||
@echo "✔ Encrypted $(FILE)"
|
||
|
||
sops-decrypt: ## Print decrypted file to stdout (for inspection) FILE=secrets/foo.sops.yaml
|
||
@[ -n "$(FILE)" ] || (echo "Usage: make sops-decrypt FILE=secrets/xxx.sops.yaml" && exit 1)
|
||
sops -d $(FILE)
|
||
|
||
sops-rotate: ## Rotate recipients on a SOPS file (after updating .sops.yaml)
|
||
@[ -n "$(FILE)" ] || (echo "Usage: make sops-rotate FILE=secrets/xxx.sops.yaml" && exit 1)
|
||
sops --rotate --in-place $(FILE)
|
||
|
||
check-secrets: ## Fail if any file in secrets/ is not SOPS-encrypted
|
||
@! (git ls-files secrets | xargs -r grep -L -E '(^sops:$$|\"sops\"[[:space:]]*:)' | tee /dev/stderr | read) \
|
||
|| (echo "❌ Unencrypted secrets detected above. Encrypt with: sops --encrypt --in-place <file>"; exit 1)
|
||
@echo "✔ All files in secrets/ appear SOPS-encrypted"
|
||
|
||
# ---- Terraform (Hetzner) ----
|
||
tf-fmt: ## Terraform fmt
|
||
@terraform -chdir=terraform/hetzner fmt -recursive || true
|
||
|
||
tf-init: ## Terraform init
|
||
@terraform -chdir=terraform/hetzner init
|
||
|
||
tf-plan: tf-init ## Terraform plan (requires decrypted HCLOUD_TOKEN)
|
||
@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; export SOPS_AGE_KEY or set keys.txt & fill secrets.sops.yaml" && exit 1)
|
||
@export HCLOUD_TOKEN=$(HCLOUD_TOKEN); terraform -chdir=terraform/hetzner plan
|
||
|
||
tf-apply: tf-init ## Terraform apply (provision)
|
||
@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; export SOPS_AGE_KEY or set keys.txt & fill secrets.sops.yaml" && exit 1)
|
||
@export HCLOUD_TOKEN=$(HCLOUD_TOKEN); terraform -chdir=terraform/hetzner apply -auto-approve
|
||
|
||
tf-destroy: tf-init ## Terraform destroy (tear down)
|
||
@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; export SOPS_AGE_KEY or set keys.txt & fill secrets.sops.yaml" && exit 1)
|
||
@export HCLOUD_TOKEN=$(HCLOUD_TOKEN); terraform -chdir=terraform/hetzner destroy -auto-approve
|
||
|
||
# ---- Ansible ----
|
||
ansible-bootstrap: ## Run base bootstrap play (users, ssh, ufw, sops-agent)
|
||
cd ansible && ansible-playbook playbooks/bootstrap.yaml -u admin
|
||
|
||
converge: ansible-bootstrap ## Alias for current bootstrap converge
|
||
@true
|
||
|
||
# ---- Orchestration ----
|
||
apply: tf-fmt tf-apply ansible-bootstrap ## Provision via Terraform then converge via Ansible
|
||
|
||
# ---- Utilities ----
|
||
doctor: ## Check tools and basic repo setup
|
||
@bash -ceu ' \
|
||
ok(){ printf "✔ %s\n" "$$1"; }; fail(){ printf "❌ %s\n" "$$1"; exit 1; }; \
|
||
command -v git >/dev/null && ok "git: $$(git --version)" || fail "git missing"; \
|
||
command -v terraform >/dev/null && ok "terraform: $$(terraform version | head -1)"; \
|
||
command -v ansible >/dev/null && ok "ansible: $$(ansible --version | head -1)"; \
|
||
command -v sops >/dev/null && ok "sops: $$(sops --version)"; \
|
||
command -v age >/dev/null && ok "age: $$(age --version)"; \
|
||
test -f keys/admin_ssh.pub && ok "keys/admin_ssh.pub present" || echo "ℹ add your SSH pubkey to keys/admin_ssh.pub"; \
|
||
test -f inventory/group_vars/secrets.sops.yaml && ok "secrets.sops.yaml present" || echo "ℹ create inventory/group_vars/secrets.sops.yaml"; \
|
||
grep -q "age1" .sops.yaml && ok ".sops.yaml has an age recipient" || echo "ℹ add your age public key to .sops.yaml"; \
|
||
git config --get core.hooksPath >/dev/null && ok "git hooksPath: $$(git config --get core.hooksPath)" || echo "ℹ run: make hooks"; \
|
||
'
|
||
|
||
# ---- Inventory convenience ----
|
||
new-host: ## Add a new host quickly: make new-host NAME=web-01 TYPE=cpx21 REGION=nbg1 ROLE=web
|
||
@[ -n "$(NAME)" ] || (echo "Usage: make new-host NAME=... [TYPE=...] [REGION=...] [ROLE=...] [IMG=...] [USER=...]" && exit 1)
|
||
@python3 scripts/new_host.py --name "$(NAME)" --type "$(TYPE)" --region "$(REGION)" --role "$(ROLE)" --image "$(IMG)" --user "$(USER)"
|
||
@echo "✔ Added host $(NAME) to inventory/servers.yaml"
|
||
|
||
remote-set: ## Set origin to your Gitea repo (GITEA/OWNER/REPO vars)
|
||
git remote remove origin 2>/dev/null || true
|
||
git remote add origin https://$(GITEA)/$(OWNER)/$(REPO).git
|
||
git branch -M main
|
||
git push -u origin main
|
||
@echo "✔ Remote set to https://$(GITEA)/$(OWNER)/$(REPO).git"
|