#!/bin/bash
# felhom-internal-place — make the one-disk box's internal data place („Belső tárhely"), felhom.eu
# architecture/16-storage-physical-identity.md §5, §7 (sizes ruled by the operator 2026-10-16). Installed as
# /usr/local/sbin/felhom-internal-place (config bundle). Called by felhom-host-install.sh (a new box) and by the agent's
# signed `add_internal_place` job (an old box) — ONE implementation of the size rule and of the only mkfs it does.
#
#   felhom-internal-place plan   <planned GiB>   → prints the numbers, changes nothing
#   felhom-internal-place create <planned GiB>   → makes the place (or finds it), prints the result
#
# <planned GiB> is thin space still to be promised to the guest (rootfs + data volume) that does not exist yet:
# the installer passes 32 + the data volume; an old box passes 0 (its guest's volumes are already counted).
#
# The rule (16 §7, ruled): reserve = max(16 GiB, 10 % of the pool), at most 48 GiB; internal = pool − reserve −
# every thin volume already promised in the pool − <planned>, made only when ≥ 20 GiB. So nothing in the pool is ever
# promised beyond pool − reserve: a pool at 100 % stops every volume on the box.
#
# It trusts nothing it is told. Every rule is checked here, as root, and any doubt REFUSES (exit 1):
#   - the pool is the `lvmthin: local-lvm` section of /etc/pve/storage.cfg (its vgname/thinpool), nothing else;
#   - the only volume it creates, formats or reads is <vg>/felhom-belso;
#   - it formats ONLY a felhom-belso that carries no signature at all (blkid -p finds nothing) — the one it just made,
#     or one a crash left between lvcreate and mkfs; any other content REFUSES; it never resizes or removes anything.
# Output, one line: „created uuid=<fs-uuid> gib=<n>", „formatted uuid=… gib=…" (a crash left it empty), „existing uuid=<fs-uuid> gib=<n>", „skipped <why>" (exit 0),
# or „plan pool=… reserve=… promised=… planned=… internal=…". Pinned by configs/test_felhom_internal_place.py.
set -u
STORAGE_CFG=${FELHOM_STORAGE_CFG:-/etc/pve/storage.cfg}
LV_NAME=felhom-belso
LABEL=felhom-belso
MIN_GIB=20

die() { echo "felhom-internal-place: REFUSED: $*" >&2; exit 1; }

pool_of_local_lvm() { # prints "<vg> <thinpool>" of the lvmthin storage local-lvm, or nothing
	awk '
		/^[a-z]+:[ \t]/ { split($0, h, /:[ \t]+/); type = h[1]; id = h[2]; sub(/[ \t]+$/, "", id); in_s = (type == "lvmthin" && id == "local-lvm"); next }
		in_s && /^[ \t]+vgname[ \t]/ { vg = $2 }
		in_s && /^[ \t]+thinpool[ \t]/ { tp = $2 }
		END { if (vg != "" && tp != "") print vg, tp }
	' "$STORAGE_CFG"
}

gib_floor() { echo $(( $1 / 1073741824 )); }

[[ $# -eq 2 ]] || die "usage: felhom-internal-place plan|create <planned GiB>"
MODE=$1; PLANNED=$2
[[ "$MODE" == plan || "$MODE" == create ]] || die "mode must be plan or create"
[[ "$PLANNED" =~ ^(0|[1-9][0-9]{0,5})$ ]] || die "planned GiB must be a whole number"

read -r VG POOL < <(pool_of_local_lvm) || true
[[ -n "${VG:-}" && -n "${POOL:-}" ]] || die "no lvmthin storage local-lvm in $STORAGE_CFG"
[[ "$VG" =~ ^[A-Za-z0-9_.+-]+$ && "$POOL" =~ ^[A-Za-z0-9_.+-]+$ ]] || die "unexpected vg/pool name"

pool_b=$(lvs --noheadings --nosuffix --units b -o lv_size "$VG/$POOL" 2>/dev/null | tr -d ' ')
[[ "$pool_b" =~ ^[0-9]+$ ]] || die "cannot read the size of $VG/$POOL"
pool_gib=$(gib_floor "$pool_b")

# Every thin volume promised in the pool (its virtual size) — except the place itself and vzdump's short-lived
# snapshots (snap_*_vzdump), which exist only during a whole-box copy and are what the reserve is for.
# FAIL CLOSED: a failed or empty listing must never read as „nothing promised" (that would size the place too big).
listing=$(lvs --noheadings --nosuffix --units b -o lv_name,pool_lv,lv_size "$VG" 2>/dev/null) || die "cannot list the volumes of $VG"
pool_seen=false; promised_b=0; have_lv=false
while read -r name pl size; do
	[[ -n "${name:-}" ]] || continue
	[[ "$name" == "$POOL" ]] && pool_seen=true
	[[ "$pl" == "$POOL" ]] || continue
	if [[ "$name" == "$LV_NAME" ]]; then have_lv=true; continue; fi
	[[ "$name" =~ ^snap_.*_vzdump$ ]] && continue
	[[ "$size" =~ ^[0-9]+$ ]] || die "cannot read the size of $VG/$name"
	promised_b=$(( promised_b + size ))
done <<< "$listing"
$pool_seen || die "the listing of $VG does not contain the pool $POOL — refusing to guess what is promised"
promised_gib=$(( (promised_b + 1073741823) / 1073741824 ))   # rounded UP: never promise a partial GiB twice

reserve=$(( pool_gib / 10 )); (( reserve < 16 )) && reserve=16; (( reserve > 48 )) && reserve=48
internal=$(( pool_gib - reserve - promised_gib - PLANNED ))

if [[ "$MODE" == plan ]]; then
	echo "plan pool=$pool_gib reserve=$reserve promised=$promised_gib planned=$PLANNED internal=$internal exists=$have_lv"
	exit 0
fi

DEV=/dev/$VG/$LV_NAME
is_block() { [[ "$(stat -L -c %F "$1" 2>/dev/null)" == "block special file" ]]; }
fs_uuid() { blkid -p -s UUID -o value "$DEV" 2>/dev/null; }
fs_type() { blkid -p -s TYPE -o value "$DEV" 2>/dev/null; }
fs_label() { blkid -p -s LABEL -o value "$DEV" 2>/dev/null; }
# has_signature: FAIL CLOSED. blkid -p exits 0 when it found something and 2 when it found NOTHING; any other result (an
# error, an ambivalent probe, blkid missing) must never read as „empty" — that is the only gate in front of mkfs.
has_signature() {
	local rc=0
	blkid -p -o value "$DEV" >/dev/null 2>&1 || rc=$?
	case $rc in
		0) return 0 ;;
		2) return 1 ;;
		*) die "cannot probe $DEV for a signature (blkid exit $rc) — refusing" ;;
	esac
}
lv_gib() { gib_floor "$(lvs --noheadings --nosuffix --units b -o lv_size "$VG/$LV_NAME" 2>/dev/null | tr -d ' ')"; }

if $have_lv; then
	is_block "$DEV" || die "$DEV is not an active block device"
	t=$(fs_type)
	if [[ -n "$t" ]]; then
		[[ "$t" == ext4 && "$(fs_label)" == "$LABEL" ]] || die "$DEV holds something that is not Felhom's ($t) — left alone"
		echo "existing uuid=$(fs_uuid) gib=$(lv_gib)"; exit 0
	fi
	# A felhom-belso with NO signature: a crash between lvcreate and mkfs. Nothing can be on it — format it.
	has_signature && die "$DEV carries a signature blkid cannot name — left alone"
else
	(( internal >= MIN_GIB )) || { echo "skipped only $internal GiB free for it (pool $pool_gib, reserve $reserve, promised $promised_gib, planned $PLANNED; needs $MIN_GIB)"; exit 0; }
	lvcreate -q -y -V "${internal}G" -T "$VG/$POOL" -n "$LV_NAME" >/dev/null || die "lvcreate failed"
	is_block "$DEV" || die "$DEV did not appear after lvcreate"
	has_signature && die "the new $DEV already carries a signature — left alone"
	made=created
fi
mkfs.ext4 -q -L "$LABEL" "$DEV" >/dev/null || die "mkfs.ext4 failed on $DEV"
u=$(fs_uuid)
[[ "$u" =~ ^[0-9a-fA-F-]{36}$ ]] || die "no filesystem UUID after mkfs on $DEV"
echo "${made:-formatted} uuid=$u gib=$(lv_gib)"
