#!/bin/bash
#
# tayra-setup - install Tayra from the live environment onto a disk.
#
# Tayra runs entirely from RAM when booted from the ISO. This script lays the
# live root filesystem down onto a target disk and installs GRUB so the machine
# boots Tayra on its own, on both legacy BIOS and UEFI firmware.
#
# The disk is partitioned MBR-style with a FAT EFI System Partition and an ext4
# root. GRUB's BIOS core is embedded in the post-MBR gap; the UEFI image is
# installed to the ESP in removable mode (EFI/BOOT/BOOTX64.EFI), so no firmware
# NVRAM entry is required. The installed system boots the on-disk kernel directly
# with root=PARTUUID=..., with no separate initramfs.
#
# Interactive by default. For unattended use set:
#   TAYRA_SETUP_DISK           target device, e.g. /dev/sda (skips the menu)
#   TAYRA_SETUP_ASSUME_YES=1   skip the destructive-wipe confirmation
#   TAYRA_SETUP_ROOT_PASSWORD  set the installed root password (default: none)
#   TAYRA_SETUP_ESP_SIZE_MB    ESP size in MiB (default 512)
#   TAYRA_SETUP_HOSTNAME       hostname for the installed system

set -Eeuo pipefail

TARGET_MNT=/mnt
ESP_SIZE_MB="${TAYRA_SETUP_ESP_SIZE_MB:-512}"

log()  { printf '==> %s\n' "$*"; }
warn() { printf '==> warning: %s\n' "$*" >&2; }
die()  { printf 'error: %s\n' "$*" >&2; exit 1; }

cleanup() {
    umount "${TARGET_MNT}/boot/efi" 2>/dev/null || true
    umount "${TARGET_MNT}" 2>/dev/null || true
}
trap cleanup EXIT

require_root() {
    [[ "$(id -u)" -eq 0 ]] || die "tayra-setup must run as root"
}

require_tools() {
    local tool missing=()
    for tool in sfdisk mkfs.ext4 mkfs.fat grub-install blockdev cp dd df hexdump \
        lsblk mknod mktemp mount umount sync seq awk; do
        command -v "${tool}" >/dev/null 2>&1 || missing+=("${tool}")
    done
    # openssl hashes the root password, so it is only needed for that path.
    if [[ -n "${TAYRA_SETUP_ROOT_PASSWORD:-}" ]]; then
        command -v openssl >/dev/null 2>&1 || missing+=(openssl)
    fi
    ((${#missing[@]} == 0)) || die "missing required tools: ${missing[*]}"
}

list_disks() {
    # BA6 lsblk has no -d; filter whole disks by TYPE.
    lsblk -n -o NAME,SIZE,TYPE 2>/dev/null | awk '$3 == "disk"'
}

is_whole_disk() {
    # BA6 lsblk takes no device operand, so match against the full listing.
    list_disks | awk -v name="${1#/dev/}" '$1 == name { found = 1 } END { exit !found }'
}

live_root_mib() {
    # Space the live root occupies, from df's "Used" column of 1K blocks. A
    # conservative fallback keeps the size check useful if df cannot report it.
    local used
    used="$(df -k / 2>/dev/null | awk 'NR == 2 { print $3 }')"
    [[ "${used}" =~ ^[0-9]+$ ]] || used=$(( 512 * 1024 ))
    printf '%s\n' "$(( used / 1024 ))"
}

choose_disk() {
    if [[ -n "${TAYRA_SETUP_DISK:-}" ]]; then
        DISK="${TAYRA_SETUP_DISK}"
        return
    fi
    printf 'Available disks:\n'
    list_disks | awk '{printf "  /dev/%s\t%s\n", $1, $2}'
    printf 'Target disk (e.g. /dev/sda): '
    read -r DISK
}

partition_suffix() {
    # nvme/mmc devices insert a "p" before the partition number.
    [[ "$1" =~ [0-9]$ ]] && printf 'p' || printf ''
}

confirm_wipe() {
    if [[ "${TAYRA_SETUP_ASSUME_YES:-}" == 1 ]]; then
        return
    fi
    printf '\nThis will ERASE ALL DATA on %s and install Tayra.\n' "${DISK}"
    printf 'Type "yes" to continue: '
    local answer
    read -r answer
    [[ "${answer}" == yes ]] || die "aborted at user request"
}

copy_system() {
    # BA6 tar has no --exclude, so copy each real top-level entry with cp -a and
    # skip the virtual/temporary filesystems and the target mount itself.
    local entry
    for entry in /*; do
        case "${entry}" in
            /proc|/sys|/dev|/run|/tmp|/mnt|/lost+found) continue ;;
        esac
        cp -a "${entry}" "${TARGET_MNT}/"
    done
}

reset_host_keys() {
    # A host key identifies one machine. The live environment generated its own
    # at boot and copy_system just copied it across, so drop it again and let
    # tayra-sshd-keygen create a fresh key on the installed system's first boot.
    rm -f "${TARGET_MNT}"/etc/ssh/ssh_host_*
}

create_mount_points() {
    # The directories copy_system skips still have to exist on the target: at
    # boot /etc/inittab mounts proc, sysfs, and devtmpfs over them, and mount
    # fails on a missing mount point.
    local directory
    for directory in proc sys dev run tmp mnt; do
        mkdir -p "${TARGET_MNT}/${directory}"
    done
    chmod 1777 "${TARGET_MNT}/tmp"
    # The kernel opens /dev/console for init before init has mounted devtmpfs,
    # so the on-disk /dev must carry the two nodes early boot depends on.
    [[ -e "${TARGET_MNT}/dev/console" ]] \
        || mknod -m 0600 "${TARGET_MNT}/dev/console" c 5 1
    [[ -e "${TARGET_MNT}/dev/null" ]] \
        || mknod -m 0666 "${TARGET_MNT}/dev/null" c 1 3
}

main() {
    require_root
    require_tools
    choose_disk

    [[ -b "${DISK}" ]] || die "not a block device: ${DISK}"
    is_whole_disk "${DISK}" || die "${DISK} is not a whole disk; pass the disk, not a partition"
    if grep -q "^${DISK}" /proc/mounts 2>/dev/null; then
        die "${DISK} has mounted partitions; unmount them first"
    fi
    [[ -f /boot/vmlinuz ]] || die "/boot/vmlinuz is missing from the live system"
    [[ "${ESP_SIZE_MB}" =~ ^[0-9]+$ ]] \
        || die "TAYRA_SETUP_ESP_SIZE_MB must be a whole number of MiB"
    # mkfs.fat -F32 needs 65525 clusters, which no smaller ESP can provide.
    [[ "${ESP_SIZE_MB}" -ge 34 ]] || die "the ESP must be at least 34 MiB"

    confirm_wipe

    local suffix esp root
    suffix="$(partition_suffix "${DISK}")"
    esp="${DISK}${suffix}1"
    root="${DISK}${suffix}2"

    local total_sectors esp_start esp_size root_start root_size total_mib
    total_sectors="$(blockdev --getsz "${DISK}")"
    esp_start=2048
    esp_size=$(( ESP_SIZE_MB * 2048 ))
    root_start=$(( esp_start + esp_size ))
    root_size=$(( total_sectors - root_start ))

    # The whole live root is copied to the target, so size the check against
    # what it actually occupies plus room for ext4 metadata, the journal, and
    # the reserved blocks; otherwise the copy fails part-way through a disk
    # that passed a fixed minimum.
    local live_mib root_mib needed_mib
    live_mib="$(live_root_mib)"
    needed_mib=$(( live_mib + live_mib / 2 + 64 ))
    root_mib=$(( root_size / 2048 ))
    [[ "${root_mib}" -ge "${needed_mib}" ]] \
        || die "disk too small; the root partition needs ${needed_mib} MiB for a ${live_mib} MiB system, but only ${root_mib} MiB is left after a ${ESP_SIZE_MB} MiB ESP"

    log "wiping existing signatures on ${DISK}"
    dd if=/dev/zero of="${DISK}" bs=1M count=1 status=none
    total_mib=$(( total_sectors / 2048 ))
    if [[ "${total_mib}" -gt 1 ]]; then
        dd if=/dev/zero of="${DISK}" bs=1M seek=$(( total_mib - 1 )) count=1 \
            status=none 2>/dev/null || true
    fi
    sync

    log "partitioning ${DISK} (ESP ${ESP_SIZE_MB} MiB + ext4 root)"
    printf 'start=%s, size=%s, type=ef\nstart=%s, size=%s, type=83\n' \
        "${esp_start}" "${esp_size}" "${root_start}" "${root_size}" \
        | sfdisk --force "${DISK}"

    log "writing a random MBR disk signature"
    local sig_file b0 b1 b2 b3 partuuid
    sig_file="$(mktemp)"
    dd if=/dev/urandom of="${sig_file}" bs=4 count=1 status=none
    dd if="${sig_file}" of="${DISK}" bs=1 seek=440 count=4 conv=notrunc status=none
    # Read the four signature bytes from hexdump -C's first line: the offset is
    # the first field, the bytes are the next four (parsed with the shell, since
    # BA6 awk rejects "$2 $3" field juxtaposition).
    read -r _ b0 b1 b2 b3 _ < <(hexdump -C "${sig_file}")
    rm -f "${sig_file}"
    # The kernel prints the MBR id little-endian: bytes reverse to the PARTUUID.
    partuuid="${b3}${b2}${b1}${b0}"
    local root_partuuid="${partuuid}-02"
    local esp_partuuid="${partuuid}-01"
    sync

    blockdev --rereadpt "${DISK}"
    for _ in $(seq 1 10); do
        [[ -b "${esp}" && -b "${root}" ]] && break
        sleep 1
    done
    [[ -b "${esp}" && -b "${root}" ]] \
        || die "partition device nodes did not appear (${esp}, ${root})"

    log "creating filesystems"
    mkfs.fat -F32 -n TAYRA_ESP "${esp}" >/dev/null
    # e2fsprogs 1.47 enables orphan_file and metadata_csum_seed by default, which
    # GRUB 2.12's ext2 driver cannot read (it would fail to load /boot/grub and
    # drop to a rescue prompt). Disable them so GRUB can boot the root directly.
    mkfs.ext4 -F -O '^orphan_file,^metadata_csum_seed' -L tayra-root "${root}" \
        >/dev/null

    log "copying the system to ${root} (this can take a while)"
    mkdir -p "${TARGET_MNT}"
    mount -t ext4 "${root}" "${TARGET_MNT}"
    copy_system
    reset_host_keys
    create_mount_points

    # Mount the ESP only after the copy so it does not shadow /mnt/boot.
    mkdir -p "${TARGET_MNT}/boot/efi"
    mount -t vfat "${esp}" "${TARGET_MNT}/boot/efi"

    log "writing /etc/fstab"
    cat > "${TARGET_MNT}/etc/fstab" <<EOF
# <file system>            <mount point>  <type>  <options>          <dump> <pass>
PARTUUID=${root_partuuid}  /              ext4    defaults            0      1
PARTUUID=${esp_partuuid}   /boot/efi      vfat    defaults,noauto     0      2
EOF

    if [[ -n "${TAYRA_SETUP_HOSTNAME:-}" ]]; then
        printf '%s\n' "${TAYRA_SETUP_HOSTNAME}" > "${TARGET_MNT}/etc/hostname"
    fi

    if [[ -n "${TAYRA_SETUP_ROOT_PASSWORD:-}" ]]; then
        log "setting the root password"
        local hash
        hash="$(printf '%s' "${TAYRA_SETUP_ROOT_PASSWORD}" \
            | openssl passwd -6 -stdin)"
        # The base system keeps password hashes in the root-only shadow database.
        sed -i "s|^root:[^:]*:|root:${hash}:|" "${TARGET_MNT}/etc/shadow"
    fi

    log "installing GRUB for BIOS (i386-pc)"
    grub-install --target=i386-pc --boot-directory="${TARGET_MNT}/boot" \
        --recheck "${DISK}"

    log "installing GRUB for UEFI (x86_64-efi, removable)"
    grub-install --target=x86_64-efi --efi-directory="${TARGET_MNT}/boot/efi" \
        --boot-directory="${TARGET_MNT}/boot" --removable --no-nvram --recheck

    log "writing the boot menu"
    cat > "${TARGET_MNT}/boot/grub/grub.cfg" <<EOF
set default=0
set timeout=5

serial --unit=0 --speed=115200
terminal_input serial console
terminal_output serial console

menuentry "Tayra Linux" {
    linux /boot/vmlinuz root=PARTUUID=${root_partuuid} rw console=tty0 console=ttyS0,115200 init=/init panic=-1
}

menuentry "Tayra Linux (VGA console)" {
    linux /boot/vmlinuz root=PARTUUID=${root_partuuid} rw console=ttyS0,115200 console=tty0 init=/init panic=-1
}
EOF

    sync
    umount "${TARGET_MNT}/boot/efi"
    umount "${TARGET_MNT}"
    sync
    trap - EXIT

    log "Tayra is installed on ${DISK}."
    log "Remove the installation media and reboot."
}

main "$@"
