#!/bin/sh

# cryptsetup-generator - Parse /etc/crypttab and unlock encrypted devices
# This script is called on startup to unlock encrypted devices before the fstab get mounted

export PATH=/usr/bin:/usr/sbin:/bin:/sbin

set -e

CRYPTTAB="/etc/crypttab"
DM_PATH="/dev/mapper"
CRYPTO_TIMEOUT="${CRYPTO_TIMEOUT:-120}"

log_info() {
    echo "[INFO] $*" >&2
}

log_error() {
    echo "[ERROR] $*" >&2
}

# Parse a single crypttab line
parse_crypttab_line() {
    local line="$1"

    # Skip comments and empty lines
    [[ "$line" =~ ^[[:space:]]*# ]] && return 1
    [[ -z "$line" ]] && return 1

    # Split by whitespace, handling up to 4 fields
    local name device password options
    read -r name device password options <<<"$line"

    [[ -z "$name" ]] && return 1

    # Default values
    password="${password:--}"
    options="${options:=luks}"

    echo "$name|$device|$password|$options"
    return 0
}

# Resolve device path (handle UUID, PARTUUID, device paths)
resolve_device() {
    local device="$1"

    if [[ "$device" =~ ^UUID= ]] || [[ "$device" =~ ^PARTUUID= ]]; then
        # Use blkid to resolve
        local resolved=$(blkid -t "$device" -o device 2> /dev/null || true)
        if [[ -n "$resolved" ]]; then
            echo "$resolved"
            return 0
        fi
        log_error "Cannot resolve $device"
        return 1
    fi

    # Device path as-is
    if [[ -e "$device" ]]; then
        echo "$device"
        return 0
    fi

    # Try /dev prefix
    if [[ -e "/dev/$device" ]]; then
        echo "/dev/$device"
        return 0
    fi

    log_error "Device not found: $device"
    return 1
}

# Unlock a device using password/keyfile
unlock_device() {
    local name="$1"
    local device="$2"
    local password="$3"
    local options="$4"

    # Check if already unlocked
    if [[ -e "$DM_PATH/$name" ]]; then
        log_info "Device $name already unlocked"
        return 0
    fi

    local cryptsetup_opts=()

    # Parse options
    IFS=',' read -ra opts_array <<<"$options"
    for opt in "${opts_array[@]}"; do
        opt="${opt// /}"  # trim whitespace
        case "$opt" in
            discard)
                cryptsetup_opts+=(--allow-discards)
                ;;
            key-slot=*)
                cryptsetup_opts+=(--key-slot "${opt#key-slot=}")
                ;;
            tcrypt)
                cryptsetup_opts+=(--type tcrypt)
                ;;
            tcrypt-hidden)
                cryptsetup_opts+=(--type tcrypt --hidden)
                ;;
            timeout=*)
                local timeout="${opt#timeout=}"
                CRYPTO_TIMEOUT="$timeout"
                ;;
        esac
    done

    log_info "Unlocking device: $name from $device"

    # Determine password source
    if [[ "$password" == "-" ]]; then
        # Interactive password via systemd-ask-password or prompt
        if command -v systemd-ask-password &> /dev/null; then
            systemd-ask-password "Enter passphrase for $name:" | cryptsetup open "${cryptsetup_opts[@]}" "$device" "$name"
        else
            # Fallback to read from terminal
            read -rsp "Enter passphrase for $name: " pass
            echo
            echo "$pass" | cryptsetup open "${cryptsetup_opts[@]}" "$device" "$name"
        fi
    elif [[ "$password" == "none" ]] || [[ "$password" == "/dev/urandom" ]]; then
        # Random/urandom password (for temporary volumes)
        cryptsetup open "${cryptsetup_opts[@]}" --key-file="$password" "$device" "$name"
    elif [[ -f "$password" ]]; then
        # Keyfile
        cryptsetup open "${cryptsetup_opts[@]}" --key-file="$password" "$device" "$name"
    else
        log_error "Invalid password specification: $password"
        return 1
    fi

    log_info "Device $name unlocked successfully"
    return 0
}

# Main function
main() {
    if [[ ! -f "$CRYPTTAB" ]]; then
        log_info "No crypttab found, skipping device unlocking"
        return 0
    fi

    log_info "Processing crypttab"

    local count=0
    while IFS= read -r line; do
        # Parse the line
        parsed=$(parse_crypttab_line "$line") || continue

        IFS='|' read -r name device password options <<<"$parsed"

        # Resolve device path
        resolved_device=$(resolve_device "$device") || continue

        # Unlock the device (temporarily disable set -e to allow graceful error handling)
        set +e
        unlock_device "$name" "$resolved_device" "$password" "$options"
        result=$?
        set -e
        if [[ $result -eq 0 ]]; then
            count=$((count + 1))
        fi
    done < "$CRYPTTAB"

    log_info "Successfully unlocked $count devices"
    return 0
}

main "$@"
