2025-09-13 23:34:27 +02:00
# -------- RailianceHosts Make Utilities --------
2025-09-13 20:26:11 +02:00
SHELL := /usr/bin/env bash
2025-09-13 23:34:27 +02:00
.DEFAULT_GOAL := help
2025-09-13 20:26:11 +02:00
2025-09-13 23:34:27 +02:00
# Set this to your Gitea host if you want 'remote-set' helper
GITEA ?= gitea.example.com
OWNER ?= coulomb
REPO ?= railiance-hosts
# Decrypt Hetzner token at runtime (requires SOPS_AGE_KEY or keys.txt locally)
2025-09-13 20:26:11 +02:00
HCLOUD_TOKEN := $( shell sops -d --extract '["ops"]["hcloud_token"]' inventory/group_vars/secrets.sops.yaml 2>/dev/null)
2025-09-13 23:34:27 +02:00
# ---- 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)
git config core.hooksPath .githooks
@echo "✔ hooks enabled (core.hooksPath=.githooks)"
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) "
2025-09-13 20:26:11 +02:00
2025-09-13 23:34:27 +02:00
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)
2025-09-13 20:26:11 +02:00
2025-09-13 23:34:27 +02:00
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)
2025-09-13 20:26:11 +02:00
2025-09-13 23:34:27 +02:00
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"
2025-09-13 20:26:11 +02:00
2025-09-13 23:34:27 +02:00
# ---- Terraform (Hetzner) ----
tf-fmt : ## Terraform fmt
@terraform -chdir= terraform/hetzner fmt -recursive || true
2025-09-13 20:26:11 +02:00
2025-09-13 23:34:27 +02:00
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)
2025-09-13 20:26:11 +02:00
cd ansible && ansible-playbook playbooks/bootstrap.yaml -u admin
2025-09-13 23:34:27 +02:00
converge : ansible -bootstrap ## Alias for current bootstrap converge
@true
2025-09-13 20:26:11 +02:00
2025-09-13 23:34:27 +02:00
# ---- Orchestration ----
apply : tf -fmt tf -apply ansible -bootstrap ## Provision via Terraform then converge via Ansible
2025-09-13 20:26:11 +02:00
2025-09-13 23:34:27 +02:00
# ---- 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"; \ '
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=..." && exit 1)
@TYPE?= cpx21; REGION?= nbg1; ROLE?= generic; IMG?= ubuntu-24.04; USER?= admin; \ python3 - <<'PY' \import sys, yaml; \p="inventory/servers.yaml"; d=yaml.safe_load(open(p)) ; d['servers'].append({"name":"$(NAME)","labels":[],"role":"$(ROLE)","region":"$(REGION)","type":"$(TYP E) "," image":" $( IMG) "," ssh_user":" $( USER) "}); \yaml.safe_dump(d, open(p," w"), sort_keys=False); \print(" ✔ Added host $( NAME) to inventory/servers.yaml" )
P Y
2025-09-13 20:26:11 +02:00
2025-09-13 23:34:27 +02:00
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 "