#!/usr/bin/env bash
# install-human-slice — arm human.slice and the ancestor reservations it depends on.
#
# Idempotent. Every step is verified by reading the cgroup back; an unverifiable step
# is an error, never a silent pass.
#
#   1. link + reload the unit (deckctl units apply owns the symlink; this only checks)
#   2. reserve MemoryMin on every ancestor, so human.slice's own MemoryMin is not clamped to 0
#   3. start the slice so its cgroup exists before the first session needs it
#   4. read every value back and print it
set -uo pipefail

RESERVE="${HUMAN_SLICE_MEMORY_MIN:-8G}"
RESERVE_BYTES=$(( ${RESERVE%G} * 1024 * 1024 * 1024 ))
# The ancestors are shared: human.slice claims RESERVE and app.slice claims DESKTOP_RESERVE
# (monitor/systemd/user/app.slice.d/55-desktop-memory-min.conf). cgroup-v2 distributes a
# parent's memory.min across claiming children in proportion, so an ancestor reserve below
# the sum silently shrinks both.
DESKTOP_RESERVE="${DESKTOP_MEMORY_MIN:-4G}"
ANCESTOR_RESERVE="$(( ${RESERVE%G} + ${DESKTOP_RESERVE%G} ))G"
ANCESTOR_RESERVE_BYTES=$(( ${ANCESTOR_RESERVE%G} * 1024 * 1024 * 1024 ))
UNIT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
CG_ROOT=/sys/fs/cgroup
UID_NUM="$(id -u)"

# user@<uid>.service is a SYSTEM unit; its ancestors live in the system cgroup tree.
ANCESTORS=(user.slice "user-${UID_NUM}.slice" "user@${UID_NUM}.service")
HUMAN_CG="$CG_ROOT/user.slice/user-${UID_NUM}.slice/user@${UID_NUM}.service/human.slice"

fail() { printf 'install-human-slice: ERROR: %s\n' "$*" >&2; exit 1; }
step() { printf '\n== %s\n' "$*"; }

step "unit file"
if [[ ! -e "$UNIT_DIR/human.slice" ]]; then
  fail "human.slice not linked into $UNIT_DIR — run: $(dirname "$0")/../../../bin/deckctl units apply"
fi
printf 'human.slice -> %s\n' "$(readlink -f "$UNIT_DIR/human.slice")"

step "ancestor MemoryMin reservations (root)"
for unit in "${ANCESTORS[@]}"; do
  current="$(systemctl show "$unit" -p MemoryMin --value 2>/dev/null)"
  if [[ "$current" == "$ANCESTOR_RESERVE_BYTES" ]]; then
    printf '%-24s MemoryMin already %s\n' "$unit" "$ANCESTOR_RESERVE"
    continue
  fi
  deck-sudo systemctl set-property "$unit" "MemoryMin=$ANCESTOR_RESERVE" \
    || fail "set-property $unit MemoryMin=$ANCESTOR_RESERVE failed"
  printf '%-24s MemoryMin set to %s\n' "$unit" "$ANCESTOR_RESERVE"
done

step "reload + start"
systemctl --user daemon-reload || fail "user daemon-reload failed"
systemctl --user start human.slice || fail "cannot start human.slice"

step "read-back"
[[ -d "$HUMAN_CG" ]] || fail "human.slice cgroup absent at $HUMAN_CG"
for f in pids.max memory.max memory.high memory.min memory.low memory.swap.max cpu.weight; do
  [[ -e "$HUMAN_CG/$f" ]] && printf '%-40s %s\n' "human.slice/$f" "$(cat "$HUMAN_CG/$f")"
done
for unit in "${ANCESTORS[@]}"; do
  printf '%-40s %s\n' "$unit memory.min" "$(systemctl show "$unit" -p MemoryMin --value)"
done
printf '%-40s %s\n' "human.slice ManagedOOMPreference" \
  "$(systemctl --user show human.slice -p ManagedOOMPreference --value)"

# A reservation the kernel clamped to 0 reads back as the requested value on the leaf but
# protects nothing, so assert on the leaf AND on every ancestor.
[[ "$(cat "$HUMAN_CG/memory.min")" == "$RESERVE_BYTES" ]] \
  || fail "human.slice memory.min did not take"
[[ "$(cat "$HUMAN_CG/pids.max")" == max ]] || fail "human.slice pids.max is not unlimited"
[[ "$(cat "$HUMAN_CG/memory.max")" == max ]] || fail "human.slice memory.max is not unlimited"
[[ "$(cat "$HUMAN_CG/memory.swap.max")" == max ]] || fail "human.slice memory.swap.max is not unlimited"
for unit in "${ANCESTORS[@]}"; do
  [[ "$(systemctl show "$unit" -p MemoryMin --value)" == "$ANCESTOR_RESERVE_BYTES" ]] \
    || fail "$unit MemoryMin is not $ANCESTOR_RESERVE — human.slice and app.slice reservations would be clamped"
done
APP_CG="$CG_ROOT/user.slice/user-${UID_NUM}.slice/user@${UID_NUM}.service/app.slice/memory.min"
app_min="$(cat "$APP_CG" 2>/dev/null || echo 0)"
printf '%-40s %s\n' "app.slice memory.min" "$app_min"
[[ "$app_min" -gt 0 ]] \
  || fail "app.slice memory.min is 0 — the desktop is reclaimable; run: deckctl units apply"

printf '\ninstall-human-slice: armed\n'
