#!/bin/sh
# abyssinit local-top: open the abysscrypt-encrypted root before mount.
#
# Sources /etc/abyssinit.conf if present, accepts overrides from the
# kernel command line (abyssinit.<key>=<value>), otherwise prompts on
# /dev/console. Invokes abysscrypt with --no-mount so the dm-crypt stack
# is opened and the final mapper path is printed on stdout without
# touching any filesystem. The user is expected to set
# root=/dev/mapper/<prefix>_<levels> on the kernel command line (same
# convention as cryptsetup-initramfs).
PREREQ=""
prereqs() { echo "$PREREQ"; }
case "${1:-}" in
    prereqs) prereqs; exit 0 ;;
esac

. /scripts/functions

# --- 1. Parse kernel command line -------------------------------------
# All recognized keys are abyssinit.<name>=<value>, plus the bare
# escape-hatches abyssinit=no | abyssinit=off | noabyssinit. Unrecognized
# tokens are ignored so this script never panics on someone else's params.
CMD_CONTAINER=""
CMD_LEVELS=""
CMD_MULTIPASS=""
CMD_OFFSET=""
CMD_MAPPER_PREFIX=""
CMD_RETRIES=""
CMD_EXTRA=""
CMD_KEYFILE=""
CMD_CONF=""
CMD_TIMEOUT=""
CMD_ASK=0
CMD_DEBUG=0

for _arg in $(cat /proc/cmdline 2>/dev/null); do
    case "$_arg" in
        abyssinit=no|abyssinit=off|noabyssinit)
            log_warning_msg "abyssinit: disabled via kernel cmdline"
            exit 0
            ;;
        abyssinit.container=*)     CMD_CONTAINER="${_arg#*=}" ;;
        abyssinit.levels=*)        CMD_LEVELS="${_arg#*=}" ;;
        abyssinit.multipass=*)     CMD_MULTIPASS="${_arg#*=}" ;;
        abyssinit.offset=*)        CMD_OFFSET="${_arg#*=}" ;;
        abyssinit.mapper_prefix=*) CMD_MAPPER_PREFIX="${_arg#*=}" ;;
        abyssinit.retries=*)       CMD_RETRIES="${_arg#*=}" ;;
        abyssinit.extra=*)         CMD_EXTRA="${_arg#*=}" ;;
        abyssinit.keyfile=*)       CMD_KEYFILE="${_arg#*=}" ;;
        abyssinit.conf=*)          CMD_CONF="${_arg#*=}" ;;
        abyssinit.timeout=*)       CMD_TIMEOUT="${_arg#*=}" ;;
        abyssinit.ask)             CMD_ASK=1 ;;
        abyssinit.debug)           CMD_DEBUG=1 ;;
    esac
done

# --- 2. Defaults + config file ---------------------------------------
CONTAINER=""
LEVELS=3
MULTIPASS=""
OFFSET=0
EXTRA_ARGS=""
MAPPER_PREFIX="abyssinit"
RETRIES=3
KEYFILE=""
TIMEOUT=30

CONF="${CMD_CONF:-/etc/abyssinit.conf}"
if [ -f "$CONF" ]; then
    # shellcheck disable=SC1090
    . "$CONF"
fi

# --- 3. Cmdline overrides win ----------------------------------------
[ -n "$CMD_CONTAINER" ]     && CONTAINER="$CMD_CONTAINER"
[ -n "$CMD_LEVELS" ]        && LEVELS="$CMD_LEVELS"
[ -n "$CMD_MULTIPASS" ]     && MULTIPASS="$CMD_MULTIPASS"
[ -n "$CMD_OFFSET" ]        && OFFSET="$CMD_OFFSET"
[ -n "$CMD_MAPPER_PREFIX" ] && MAPPER_PREFIX="$CMD_MAPPER_PREFIX"
[ -n "$CMD_RETRIES" ]       && RETRIES="$CMD_RETRIES"
[ -n "$CMD_KEYFILE" ]       && KEYFILE="$CMD_KEYFILE"
[ -n "$CMD_TIMEOUT" ]       && TIMEOUT="$CMD_TIMEOUT"
# extra: comma-separated on cmdline (kernel doesn't preserve quoting),
# becomes space-separated for shell word-splitting below.
if [ -n "$CMD_EXTRA" ]; then
    EXTRA_ARGS=$(printf '%s' "$CMD_EXTRA" | tr ',' ' ')
fi

# --- 4. Prompt for whatever's still missing (or all of it on --ask) --
ai_prompt() {
    _msg="$1"; _default="${2:-}"
    if [ -n "$_default" ]; then
        printf '%s [%s]: ' "$_msg" "$_default" >/dev/console
    else
        printf '%s: ' "$_msg" >/dev/console
    fi
    read -r _reply </dev/console || _reply=""
    [ -z "$_reply" ] && _reply="$_default"
    printf '%s' "$_reply"
}

if [ -z "$CONTAINER" ] || [ "$CMD_ASK" = 1 ]; then
    {
        echo ""
        echo "================================================================"
        if [ "$CMD_ASK" = 1 ]; then
            echo " abyssinit: abyssinit.ask set - interactive override"
        else
            echo " abyssinit: no container configured - interactive setup"
        fi
        echo "================================================================"
    } >/dev/console
    CONTAINER=$(ai_prompt     "encrypted container (device path)"                 "$CONTAINER")
    LEVELS=$(ai_prompt        "encryption levels"                                 "$LEVELS")
    MULTIPASS=$(ai_prompt     "multipass groups (comma-separated, blank for none)" "$MULTIPASS")
    OFFSET=$(ai_prompt        "sector offset"                                     "$OFFSET")
    MAPPER_PREFIX=$(ai_prompt "dm mapper prefix"                                  "$MAPPER_PREFIX")
    RETRIES=$(ai_prompt       "passphrase retries (0..32)"                        "$RETRIES")
fi

# --- 5. Validate -----------------------------------------------------
[ -n "$CONTAINER" ] || panic "abyssinit: no container specified"
case "$LEVELS"  in (*[!0-9]*|"") panic "abyssinit: LEVELS must be a positive integer" ;; esac
case "$RETRIES" in (*[!0-9]*|"") panic "abyssinit: RETRIES must be an integer 0..32" ;; esac
case "$OFFSET"  in (*[!0-9]*|"") panic "abyssinit: OFFSET must be a non-negative integer" ;; esac
case "$TIMEOUT" in (*[!0-9]*|"") panic "abyssinit: TIMEOUT must be a non-negative integer" ;; esac
[ "$RETRIES" -le 32 ] || panic "abyssinit: RETRIES must be <= 32"

# --- 6. Debug echo ---------------------------------------------------
if [ "$CMD_DEBUG" = 1 ]; then
    {
        echo "[abyssinit] config source: $CONF $( [ -f "$CONF" ] && echo "(present)" || echo "(absent)" )"
        echo "[abyssinit] CONTAINER=$CONTAINER"
        echo "[abyssinit] LEVELS=$LEVELS MULTIPASS=$MULTIPASS OFFSET=$OFFSET"
        echo "[abyssinit] MAPPER_PREFIX=$MAPPER_PREFIX RETRIES=$RETRIES TIMEOUT=$TIMEOUT"
        echo "[abyssinit] KEYFILE=$KEYFILE"
        echo "[abyssinit] EXTRA_ARGS=$EXTRA_ARGS"
    } >/dev/console
fi

# --- 7. Wait for the container device --------------------------------
wait_for_udev 10 2>/dev/null || true
_tries=0
while [ ! -e "$CONTAINER" ] && [ "$_tries" -lt "$TIMEOUT" ]; do
    sleep 1
    _tries=$((_tries + 1))
done
[ -e "$CONTAINER" ] || panic "abyssinit: container $CONTAINER not present after ${TIMEOUT}s"

if [ -n "$KEYFILE" ] && [ ! -e "$KEYFILE" ]; then
    panic "abyssinit: keyfile $KEYFILE not present in initramfs"
fi

# --- 8. Build argv and invoke abysscrypt -----------------------------
# --no-mount opens the stack and prints the final mapper path on stdout.
# --console routes passphrase prompts to /dev/console. --retries lets
# abysscrypt re-prompt when the deepest mapper lacks a blkid signature
# (wrong cipher phrase).
set -- --no-mount --console --retries "$RETRIES" \
       --levels "$LEVELS" \
       --mapper-prefix "$MAPPER_PREFIX"
[ -n "$MULTIPASS" ]       && set -- "$@" --multipass "$MULTIPASS"
[ "${OFFSET:-0}" != "0" ] && set -- "$@" --offset    "$OFFSET"
[ -n "$KEYFILE" ]         && set -- "$@" --keyfile   "$KEYFILE"
# shellcheck disable=SC2086
if [ -n "$EXTRA_ARGS" ]; then
    set -- "$@" $EXTRA_ARGS
fi
set -- "$@" "$CONTAINER"

if [ "$CMD_DEBUG" = 1 ]; then
    echo "[abyssinit] exec: abysscrypt mount $*" >/dev/console
fi

echo "[abyssinit] opening encrypted root via abysscrypt..." >/dev/console

FINAL_DEV=$(/sbin/abysscrypt mount "$@" 2>/dev/console) \
    || panic "abyssinit: abysscrypt failed to open encrypted root"

# Trim any trailing whitespace abysscrypt may emit and validate.
FINAL_DEV=$(printf '%s' "$FINAL_DEV" | tr -d '\r\n' | awk '{print $NF}')
[ -n "$FINAL_DEV" ] \
    || panic "abyssinit: abysscrypt produced no mapper path"
[ -e "$FINAL_DEV" ] \
    || panic "abyssinit: mapper $FINAL_DEV reported but not present"

echo "[abyssinit] root device ready at $FINAL_DEV" >/dev/console

# If the bootloader did not pass root=, hint it via /conf/param.conf
# (sourced by /scripts/local after local-top scripts complete on some
# initramfs-tools versions). Setting root= on the kernel cmdline is still
# the canonical, version-portable approach.
if [ -z "${ROOT:-}" ] || [ "${ROOT:-}" = "$FINAL_DEV" ]; then
    echo "ROOT=$FINAL_DEV" >>/conf/param.conf 2>/dev/null || true
fi

exit 0
