#!/bin/sh

set -eu
export LC_ALL=C

SELF=${0##*/}
CHROOT_CLEANUP_REQUIRED=0
CHROOT_CLEANUP_TARGET=

usage() {
    cat <<-EOF
	Utilisation :
	  sudo $SELF DISQUE_OU_PARTITION_RACINE [POINT_MONTAGE] [--boot PARTITION] [--efi PARTITION]
	  sudo $SELF --cleanup POINT_MONTAGE

	Exemples :
	  sudo $SELF /dev/sdb
	  sudo $SELF /dev/sdb2
	  sudo $SELF /dev/sdb2 /mnt/linux --efi /dev/sdb1
	  sudo $SELF /dev/mapper/rootfs /mnt/linux --boot /dev/sdb2 --efi /dev/sdb1
	  sudo $SELF --cleanup /mnt/linux

	Par défaut, le script monte le système Linux, ouvre un shell chroot, puis démonte
	tout automatiquement à la sortie du shell. --cleanup permet de nettoyer des
	montages restés en place après une interruption.

	Si le premier argument est un disque entier GPT ou MBR, le script détecte une
	partition racine ext2/ext3/ext4 et une éventuelle partition système EFI.
	--efi remplace la détection automatique ; --boot indique une partition /boot
	séparée, qui n'est pas détectable de manière fiable.

	Pendant le chroot, os-prober est désactivé afin que update-grub n'ajoute pas
	les systèmes de l'hôte. Cette configuration temporaire est retirée à la sortie.
	EOF
}

die() {
    printf 'Erreur : %s\n' "$*" >&2
    exit 1
}

require_root() {
    [ "$(id -u)" -eq 0 ] || die "ce script doit être lancé avec sudo ou en root"
}

canonical_mountpoint() {
    cmp_path=$1

    case "$cmp_path" in
        /*) ;;
        *) die "le point de montage doit etre un chemin absolu" ;;
    esac
    realpath -m -- "$cmp_path"
}

check_safe_mountpoint() {
    csm_target=$1

    case "$csm_target" in
        /|/boot|/dev|/etc|/home|/media|/proc|/run|/sys|/usr|/var)
            die "point de montage trop général ou dangereux : $csm_target"
            ;;
    esac
}

disk_partitions() {
    dp_disk=$1
    lsblk -lnpo NAME,TYPE "$dp_disk" | awk '$2 == "part" { print $1 }'
}

filesystem_type() {
    blkid -p -s TYPE -o value "$1" 2>/dev/null || :
}

mount_probe() {
    mp_device=$1
    mp_type=$2
    mp_target=$3

    case "$mp_type" in
        ext3|ext4) mp_options=ro,noload,nosuid,nodev,noexec ;;
        *)         mp_options=ro,nosuid,nodev,noexec ;;
    esac
    mount -t "$mp_type" -o "$mp_options" "$mp_device" "$mp_target"
}

detect_root_partition() {
    drp_disk=$1
    drp_target=$2
    drp_count=0
    drp_candidates=
    DETECTED_ROOT=

    while IFS= read -r drp_partition; do
        drp_type=$(filesystem_type "$drp_partition")
        case "$drp_type" in
            ext2|ext3|ext4) ;;
            *) continue ;;
        esac

        if findmnt -rn -S "$drp_partition" >/dev/null; then
            findmnt -rn -S "$drp_partition" >&2
            die "$drp_partition est déjà montée ; démonte-la avant la détection"
        fi

        mount_probe "$drp_partition" "$drp_type" "$drp_target" || \
            die "impossible de monter $drp_partition en lecture seule pour l'identifier"
        drp_is_root=0
        if [ -f "$drp_target/etc/passwd" ] && \
           { [ -e "$drp_target/bin/sh" ] || [ -L "$drp_target/bin/sh" ] || \
             [ -e "$drp_target/bin/bash" ] || [ -L "$drp_target/bin/bash" ]; } && \
           { [ -f "$drp_target/etc/os-release" ] || [ -f "$drp_target/usr/lib/os-release" ]; }; then
            drp_is_root=1
        fi
        umount -- "$drp_target"

        if [ "$drp_is_root" -eq 1 ]; then
            drp_count=$((drp_count + 1))
            DETECTED_ROOT=$drp_partition
            if [ -z "$drp_candidates" ]; then
                drp_candidates=$drp_partition
            else
                drp_candidates="$drp_candidates
$drp_partition"
            fi
        fi
    done <<-EOF
	$(disk_partitions "$drp_disk")
	EOF

    case "$drp_count" in
        1) ;;
        0) die "aucune partition racine Linux ext2/ext3/ext4 trouvée sur $drp_disk" ;;
        *)
            printf 'Partitions racines candidates sur %s :\n%s\n' \
                "$drp_disk" "$drp_candidates" >&2
            die "plusieurs racines détectées ; indique directement la partition voulue"
            ;;
    esac
}

detect_efi_partition() {
    dep_disk=$1
    dep_pttype=$2
    dep_count=0
    dep_candidates=
    DETECTED_EFI=

    while IFS= read -r dep_partition; do
        dep_parttype=$(lsblk -dnro PARTTYPE "$dep_partition" 2>/dev/null | tr '[:upper:]' '[:lower:]')
        dep_is_efi=0
        case "$dep_pttype:$dep_parttype" in
            gpt:c12a7328-f81f-11d2-ba4b-00a0c93ec93b|dos:0xef|dos:ef)
                dep_is_efi=1
                ;;
        esac
        [ "$dep_is_efi" -eq 1 ] || continue

        dep_type=$(filesystem_type "$dep_partition")
        case "$dep_type" in
            vfat|fat|fat16|fat32) ;;
            *) die "$dep_partition est une ESP mais ne contient pas un système FAT reconnu" ;;
        esac

        dep_count=$((dep_count + 1))
        DETECTED_EFI=$dep_partition
        if [ -z "$dep_candidates" ]; then
            dep_candidates=$dep_partition
        else
            dep_candidates="$dep_candidates
$dep_partition"
        fi
    done <<-EOF
	$(disk_partitions "$dep_disk")
	EOF

    case "$dep_count" in
        0|1) ;;
        *)
            printf 'Partitions EFI candidates sur %s :\n%s\n' \
                "$dep_disk" "$dep_candidates" >&2
            die "plusieurs ESP détectées ; utilise --efi pour choisir"
            ;;
    esac
}

mounted_at_or_below() {
    mab_target=$1
    if ! mab_mounts=$(findmnt -rn -o TARGET); then
        die "impossible de consulter les montages existants"
    fi
    printf '%s\n' "$mab_mounts" | awk -v target="$mab_target" '
        $0 == target || index($0, target "/") == 1
    '
}

unmount_tree() {
    umt_target=$1

    if ! findmnt -rn -M "$umt_target" >/dev/null; then
        printf 'Aucun système de fichiers monté sur %s.\n' "$umt_target"
        return 0
    fi

    printf 'Démontage de %s...\n' "$umt_target"
    umount -R -- "$umt_target"
}

os_prober_config_path() {
    printf '%s\n' "$1/etc/default/grub.d/99-chroot-disable-os-prober.cfg"
}

remove_temporary_os_prober_config() {
    rtopc_target=$1
    rtopc_file=$(os_prober_config_path "$rtopc_target")

    if [ -e "$rtopc_file" ]; then
        rm -f -- "$rtopc_file"
        printf 'Configuration temporaire os-prober retirée.\n'
    fi
}

install_temporary_os_prober_config() {
    itopc_target=$1
    itopc_directory=$itopc_target/etc/default/grub.d
    itopc_file=$(os_prober_config_path "$itopc_target")

    mkdir -p -- "$itopc_directory"
    printf '%s\n' 'GRUB_DISABLE_OS_PROBER=true' >"$itopc_file"
}

cleanup_on_exit() {
    cleanup_exit_status=$?
    cleanup_umount_status=0

    trap - 0 INT TERM
    if [ "$CHROOT_CLEANUP_REQUIRED" -eq 1 ]; then
        set +e
        remove_temporary_os_prober_config "$CHROOT_CLEANUP_TARGET"
        unmount_tree "$CHROOT_CLEANUP_TARGET"
        cleanup_umount_status=$?
        set -e
        if [ "$cleanup_umount_status" -ne 0 ]; then
            printf 'Le démontage a échoué. Après avoir fermé les processus actifs, lance :\n' >&2
            printf '  sudo "%s" --cleanup "%s"\n' "$0" "$CHROOT_CLEANUP_TARGET" >&2
            if [ "$cleanup_exit_status" -eq 0 ]; then
                cleanup_exit_status=$cleanup_umount_status
            fi
        fi
    fi

    exit "$cleanup_exit_status"
}

file_value() {
    fv_file=$1
    fv_key=$2

    awk -v key="$fv_key" '
        index($0, key "=") == 1 {
            value = substr($0, length(key) + 2)
            if (value ~ /^".*"$/ || value ~ /^\047.*\047$/) {
                value = substr(value, 2, length(value) - 2)
            }
            print value
            exit
        }
    ' "$fv_file"
}

display_system_version() {
    dsv_target=$1
    dsv_os_release=
    dsv_pretty_name=
    dsv_system_id=
    dsv_version_id=

    if [ -r "$dsv_target/etc/os-release" ]; then
        dsv_os_release=$dsv_target/etc/os-release
    elif [ -r "$dsv_target/usr/lib/os-release" ]; then
        dsv_os_release=$dsv_target/usr/lib/os-release
    fi

    if [ -n "$dsv_os_release" ]; then
        dsv_pretty_name=$(file_value "$dsv_os_release" PRETTY_NAME)
        dsv_system_id=$(file_value "$dsv_os_release" ID)
        dsv_version_id=$(file_value "$dsv_os_release" VERSION_ID)
    fi

    if [ -n "$dsv_pretty_name" ]; then
        printf 'Système détecté : %s\n' "$dsv_pretty_name"
    elif [ -n "$dsv_system_id" ] && [ -n "$dsv_version_id" ]; then
        printf 'Système détecté : %s %s\n' "$dsv_system_id" "$dsv_version_id"
    elif [ -n "$dsv_system_id" ]; then
        printf 'Système détecté : %s\n' "$dsv_system_id"
    else
        printf 'Système détecté : Linux (version non identifiée)\n'
    fi
}

enter_chroot() {
    ec_input_device=${1:-}
    shift || true

    [ -n "$ec_input_device" ] || die "disque ou partition racine manquant"
    ec_input_device=$(readlink -f -- "$ec_input_device")
    [ -b "$ec_input_device" ] || die "$ec_input_device n'est pas un périphérique bloc"

    ec_target=/mnt/linux
    if [ "$#" -gt 0 ]; then
        case "$1" in
            --*) ;;
            *)
                ec_target=$1
                shift
                ;;
        esac
    fi
    ec_target=$(canonical_mountpoint "$ec_target")
    check_safe_mountpoint "$ec_target"

    ec_boot_device=
    ec_efi_device=
    while [ "$#" -gt 0 ]; do
        case "$1" in
            --boot)
                [ "$#" -ge 2 ] || die "--boot attend une partition"
                ec_boot_device=$2
                shift 2
                ;;
            --efi)
                [ "$#" -ge 2 ] || die "--efi attend une partition"
                ec_efi_device=$2
                shift 2
                ;;
            -h|--help)
                usage
                exit 0
                ;;
            *)
                die "option inconnue : $1"
                ;;
        esac
    done

    if [ -n "$ec_boot_device" ] && [ ! -b "$ec_boot_device" ]; then
        die "$ec_boot_device n'est pas un périphérique bloc"
    fi
    if [ -n "$ec_efi_device" ] && [ ! -b "$ec_efi_device" ]; then
        die "$ec_efi_device n'est pas un périphérique bloc"
    fi

    ec_existing_mounts=$(mounted_at_or_below "$ec_target")
    if [ -n "$ec_existing_mounts" ]; then
        printf 'Montages déjà présents sous %s :\n%s\n' "$ec_target" "$ec_existing_mounts" >&2
        die "démonte-les ou choisis un autre point de montage"
    fi
    mkdir -p -- "$ec_target"

    ec_input_type=$(lsblk -dnro TYPE "$ec_input_device")
    if [ "$ec_input_type" = disk ]; then
        ec_pttype=$(lsblk -dnro PTTYPE "$ec_input_device")
        case "$ec_pttype" in
            gpt|dos) ;;
            *) die "$ec_input_device n'utilise pas une table GPT ou MBR reconnue" ;;
        esac

        detect_root_partition "$ec_input_device" "$ec_target"
        ec_root_device=$DETECTED_ROOT

        if [ -z "$ec_efi_device" ]; then
            detect_efi_partition "$ec_input_device" "$ec_pttype"
            ec_efi_device=$DETECTED_EFI
        fi

        printf 'Partition racine : %s\n' "$ec_root_device"
        if [ -n "$ec_efi_device" ]; then
            printf 'Partition EFI    : %s\n' "$ec_efi_device"
        else
            printf 'Partition EFI    : aucune\n'
        fi
    else
        ec_root_device=$ec_input_device
    fi

    if findmnt -rn -S "$ec_root_device" >/dev/null; then
        findmnt -rn -S "$ec_root_device" >&2
        die "$ec_root_device est déjà montée ; démonte-la d'abord"
    fi

    mount -- "$ec_root_device" "$ec_target"

    CHROOT_CLEANUP_TARGET=$ec_target
    CHROOT_CLEANUP_REQUIRED=1
    trap cleanup_on_exit 0
    trap 'exit 130' INT
    trap 'exit 143' TERM

    [ -f "$ec_target/etc/passwd" ] || die "$ec_root_device ne semble pas contenir une racine Linux (/etc/passwd absent)"

    ec_chroot_shell=
    if [ -x "$ec_target/bin/bash" ]; then
        ec_chroot_shell=/bin/bash
    elif [ -x "$ec_target/bin/sh" ]; then
        ec_chroot_shell=/bin/sh
    else
        die "$ec_root_device ne contient ni /bin/bash ni /bin/sh exécutable"
    fi

    display_system_version "$ec_target"

    if [ -n "$ec_boot_device" ]; then
        mkdir -p -- "$ec_target/boot"
        mount -- "$ec_boot_device" "$ec_target/boot"
    fi
    if [ -n "$ec_efi_device" ]; then
        mkdir -p -- "$ec_target/boot/efi"
        mount -- "$ec_efi_device" "$ec_target/boot/efi"
    fi

    install_temporary_os_prober_config "$ec_target"

    mkdir -p -- "$ec_target/dev" "$ec_target/proc" "$ec_target/sys" "$ec_target/run"
    mount --rbind /dev "$ec_target/dev"
    mount --make-rslave "$ec_target/dev"
    mount -t proc proc "$ec_target/proc"
    mount --rbind /sys "$ec_target/sys"
    mount --make-rslave "$ec_target/sys"
    mount --rbind /run "$ec_target/run"
    mount --make-rslave "$ec_target/run"

    printf '\nEntrée dans le système monté sur %s. Tape "exit" pour sortir.\n\n' "$ec_target"
    HOME=/root TERM=${TERM:-xterm} PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
        chroot --userspec=+0:+0 --groups='' "$ec_target" "$ec_chroot_shell" -l

    printf '\nSortie du chroot.\n'
}

cleanup_mounts() {
    cm_target=${1:-}

    [ -n "$cm_target" ] || die "point de montage manquant"
    cm_target=$(canonical_mountpoint "$cm_target")
    check_safe_mountpoint "$cm_target"
    remove_temporary_os_prober_config "$cm_target"
    unmount_tree "$cm_target"
}

case "${1:-}" in
    -h|--help|help)
        usage
        exit 0
        ;;
    '')
        usage >&2
        exit 2
        ;;
esac

require_root

case "$1" in
    -u|--cleanup)
        shift
        [ "$#" -eq 1 ] || die "--cleanup attend exactement un point de montage"
        cleanup_mounts "$1"
        ;;
    -*)
        die "option inconnue : $1"
        ;;
    *)
        if [ "${LINUX_CHROOT_PRIVATE_MOUNT_NS:-0}" -ne 1 ]; then
            LINUX_CHROOT_PRIVATE_MOUNT_NS=1
            export LINUX_CHROOT_PRIVATE_MOUNT_NS
            exec unshare --mount --propagation private -- "$0" "$@"
        fi
        enter_chroot "$@"
        ;;
esac
