102 lines
3.6 KiB
Bash
102 lines
3.6 KiB
Bash
#!/usr/bin/env bash
|
|
# The estate: what it is, what would change, and — behind a gate — changing it.
|
|
#
|
|
# Usage:
|
|
# ./estate.sh # show
|
|
# ./estate.sh list # every estate/*.json
|
|
# ./estate.sh plan # tofu plan, read-only
|
|
# ./estate.sh apply --yes # refuses without --yes
|
|
# ./estate.sh destroy --yes
|
|
#
|
|
# Why the default is read-only: ../README.md
|
|
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")"
|
|
|
|
source ./lib/config.sh
|
|
source ./lib/estate.sh
|
|
load_config
|
|
|
|
show() {
|
|
echo "estate: $ESTATE ($ESTATE_FILE)"
|
|
echo "target: $TARGET (cloud=$CLOUD region=$REGION)"
|
|
echo "domain: $DOMAIN"
|
|
echo "host: $HOST (sudo: $HOST_ADMIN)"
|
|
echo "workspace: $TOFU_WORKSPACE"
|
|
echo
|
|
local status; status=$(estate_get "_meta.status")
|
|
[ -n "$status" ] && echo " !! $status" && echo
|
|
|
|
echo "services in scope for '$TARGET':"
|
|
local name host up kind raw
|
|
while IFS=$'\x1f' read -r name host up kind raw placement peer port lhost; do
|
|
[ -z "$name" ] && continue
|
|
printf ' %-12s %-14s %-24s %s%s\n' \
|
|
"$name" "${host:--}" "${up:--}" "$kind" \
|
|
"$([ -n "$raw" ] && echo ' [hand-written]')"
|
|
done < <(estate_services "$TARGET")
|
|
|
|
echo
|
|
echo "cert SANs (derived, not listed):"
|
|
estate_sans | sed 's/^/ /'
|
|
}
|
|
|
|
list() {
|
|
local f n
|
|
printf '%-12s %-16s %s\n' ESTATE DOMAIN STATUS
|
|
for f in ../estate/*.json; do
|
|
[ -f "$f" ] || continue
|
|
n=$(basename "$f" .json)
|
|
printf '%-12s %-16s %s%s\n' "$n" \
|
|
"$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("domain",""))' "$f")" \
|
|
"$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("_meta",{}).get("status",""))' "$f")" \
|
|
"$([ "$n" = "$ESTATE" ] && echo ' <- this one')"
|
|
done
|
|
}
|
|
|
|
# Read-only. Meaningful only once state is imported: against empty state,
|
|
# plan reports "create N resources", which is not drift.
|
|
plan() {
|
|
echo "== $TOFU_BIN plan =="
|
|
if ! command -v "$TOFU_BIN" >/dev/null; then
|
|
echo " $TOFU_BIN not installed — skipped." >&2
|
|
echo " OpenTofu is the MPL-2.0 fork; 'terraform' works identically." >&2
|
|
else
|
|
echo " would run: $TOFU_BIN plan -var-file=<(estate)"
|
|
fi
|
|
echo
|
|
echo "NOTE: not wired up yet, and that is the point. tofu plan against empty"
|
|
echo " state reports \"create N resources\" — which is not drift, it is an"
|
|
echo " empty state. It becomes the check that proves the description"
|
|
echo " matches reality only once state is IMPORTED from the inventory."
|
|
echo " ppl/infra/ describes an aspiration: it was never applied."
|
|
}
|
|
|
|
# The gate. Two things have to be true: --yes present, AND the plan shown first.
|
|
require_yes() {
|
|
local verb="$1"; shift
|
|
local yes=""
|
|
for a in "$@"; do [ "$a" = "--yes" ] && yes=1; done
|
|
if [ -z "$yes" ]; then
|
|
echo "refusing to $verb without --yes." >&2
|
|
echo >&2
|
|
echo " $verb changes a live, billable estate and can take DNS with it." >&2
|
|
echo " Read the plan first: make estate plan" >&2
|
|
echo " Then: ./ctrl/estate.sh $verb --yes" >&2
|
|
exit 1
|
|
fi
|
|
echo "refusing to $verb: not implemented, and deliberately so." >&2
|
|
echo " The executor is not wired up, and nothing is imported yet, so" >&2
|
|
echo " there is nothing truthful to apply." >&2
|
|
exit 1
|
|
}
|
|
|
|
case "${1:-show}" in
|
|
show) show ;;
|
|
list) list ;;
|
|
plan) plan ;;
|
|
apply) shift; require_yes apply "$@" ;;
|
|
destroy) shift; require_yes destroy "$@" ;;
|
|
*) echo "usage: $0 [show|list|plan|apply --yes|destroy --yes]" >&2; exit 1 ;;
|
|
esac
|