#!/bin/sh
# svctl forecast  -- E10 (1.2.0)
#
# Sagt VORHER, welche Dienste ein "svctl update" anfassen wuerde.
#
# Warum das gebraucht wird: s6-rc-update startet bei STRUKTURELLEN Aenderungen
# (Abhaengigkeiten, Bundles, Typwechsel) die beruehrten Longruns neu -- und zwar
# auch solche, an denen niemand etwas geaendert hat, weil die Kaskade ueber die
# Abhaengigen laeuft. Gemessen am 24.09.2026: eine Abhaengigkeitsaenderung an
# keepalived riss cron mit (Laufzeit 3305 s -> 3 s). Auf rt-1 traegt ein Dienst in
# der Live-Datenbank 15 Abhaengigkeiten, von denen die Paketquelle nur eine
# mitbringt -- der Rest kommt aus der Policy. Wer die Policy anhebt, fasst damit
# fast alles an, und das ist der Grund fuer die Regel "eine s6-policy-Anhebung mit
# Strukturaenderung ist ein Fenster".
#
# Bis 1.1.3 liess sich dieses Fenster nur NACHTRAEGLICH aus /var/log/apt/term.log
# ablesen -- und dort stehen nur die GESCHEITERTEN Dienste, also die falsche Menge.
#
# Unterschied zu "svctl listforecast": das listet, welche Dienste nach dem
# naechsten "apply" OBEN waeren (Soll-Zustand). Hier geht es um die Uebergaenge,
# also welche Dienste angefasst, gestoppt oder neu gestartet WUERDEN.
#
# Es wird NICHTS geaendert: kompiliert wird in ein Wegwerfverzeichnis, und
# s6-rc-update laeuft mit -n (Trockenlauf). Die Live-Datenbank bleibt unberuehrt.
set -u

PROG="svctl-forecast"
if ! test -L /run/s6-rc/compiled ; then
  echo "$PROG: fatal: s6-rc is not running" 1>&2
  exit 2
fi

policy=$(readlink /usr/lib/s6-rc/policy/current 2>/dev/null || true)
case "${policy:-}" in
  exclusive|shared) ;;
  *)
    echo "$PROG: fatal: /usr/lib/s6-rc/policy/current should be a relative symlink to either shared or exclusive" 1>&2
    exit 100
    ;;
esac

tmpdb=$(mktemp -d /tmp/svctl-forecast.XXXXXX) || {
  echo "$PROG: fatal: kann kein Wegwerfverzeichnis anlegen" 1>&2
  exit 1
}
# s6-rc-compile will ein NICHT existierendes Ziel.
db="$tmpdb/db"
cleanup() { rm -rf "$tmpdb"; }
trap cleanup EXIT INT TERM

if ! s6-rc-compile -h sysfdh "$db" /usr/lib/s6-rc/policy/current /usr/lib/s6-rc/sources 2>"$tmpdb/err" ; then
  echo "$PROG: fatal: das Kompilat liesse sich nicht erzeugen -- ein 'svctl update' wuerde" 1>&2
  echo "$PROG:        hier ebenfalls scheitern. Meldung von s6-rc-compile:" 1>&2
  sed 's/^/'"$PROG"':        /' "$tmpdb/err" 1>&2
  exit 1
fi

# -v 2 ist noetig: mit der Vorgabe druckt s6-rc-update zwei s6-rc-Zeilen mit
# LEEREN Dienstlisten, und daran ist nicht zu erkennen, ob nichts zu tun ist oder
# ob nur nichts gesagt wird (gemessen am 24.09.2026 auf drei Knoten).
out=$(s6-rc-update -n -v 2 "$db" 2>&1) || {
  echo "$PROG: fatal: der Trockenlauf ist fehlgeschlagen:" 1>&2
  printf '%s\n' "$out" | sed 's/^/'"$PROG"':        /' 1>&2
  exit 1
}

# Die Dienstnamen aus den geplanten s6-rc-Aufrufen ziehen. Alles hinter "change"
# ist die Liste; Optionen und Pfade sind es nicht.
names=$(printf '%s\n' "$out" | sed -n 's/.*-- *change *//p' | tr ' ' '\n' | sed '/^$/d' | sort -u)

printf '%s\n' "$out" | sed 's/^/  /'
echo
if [ -z "$names" ]; then
  echo "$PROG: kein Dienst waere betroffen -- ein 'svctl update' ist hier kein Fenster."
  echo "$PROG: (Struktur und Definitionen entsprechen der laufenden Datenbank.)"
  exit 0
fi

_n=$(printf '%s\n' "$names" | wc -l | tr -d ' ')
echo "$PROG: ${_n} Dienst(e) waeren betroffen -- 'svctl update' fasst sie an:"
printf '%s\n' "$names" | while read -r _s ; do
  [ -n "$_s" ] || continue
  _d="/run/s6-rc/servicedirs/$_s"
  if [ -d "$_d" ]; then
    echo "  $_s  (laeuft jetzt: $(s6-svstat -o up,updownfor "$_d" 2>/dev/null || echo 'nicht messbar'))"
  else
    echo "  $_s  (oneshot oder nicht laufend)"
  fi
done
echo
echo "$PROG: Ein laufender Dienst in dieser Liste wird neu gestartet. Auf einem"
echo "$PROG: Hypervisor oder Datenbankknoten ist das ein Fenster; beim ersten Boot"
echo "$PROG: eines frischen Knotens ist es kostenlos, weil noch nichts laeuft."
exit 0
