#!/bin/bash
# Author: crims0n <https://minios.dev>

VERSION="1.0.1"
BEXT="sb"
COMP_TYPE="zstd"
export TEXTDOMAIN="minios-tools"

help() {
    echo "$(gettext "Usage"): $(basename $0) [OPTIONS]..."
    echo "$(gettext "Packages a module from changes made by an installation script")"
    echo ""
    echo "$(gettext "Options"):"
    echo "  -d, --directory DIR     $(gettext "Copy contents of DIR to the root of the module")"
    echo "  -n, --name NAME         $(gettext "Use NAME as the filename for the module")"
    echo "  -l, --level LEVEL       $(gettext "Use LEVEL as the filter level")"
    echo "  -c, --comp TYPE         $(gettext "Compression type (zstd, gzip, lzo, xz). Default: zstd")"
    echo "  -b, --bext EXT          $(gettext "Bundle extension. Default: sb")"
    echo "      --help              $(gettext "Display this help and exit")"
    echo "      --version           $(gettext "Display version information and exit")"
    echo ""
    echo "$(gettext "Examples"):"
    echo "  $(basename $0) --level 03 --comp gzip"
    echo "  $(basename $0) -n 06-chromium.$BEXT -l 3 -c xz"
    echo "  $(basename $0) -d /path/to/copy/contents -b mod"
    exit 0
}

brief_help() {
    echo "$(gettext "Usage"): $(basename $0) [OPTIONS]..."
    echo "$(gettext "Try") '$(basename $0) --help' $(gettext "for more information.")"
    exit 1
}

version() {
    echo "$(basename $0) $VERSION"
    exit 0
}

aufs_is_supported() {
    grep -q aufs /proc/filesystems
}

while [ $# -gt 0 ]; do
    case "$1" in
    -d | --directory)
        DIRECTORY="$2"
        shift 2
        ;;
    -n | --name)
        FILENAME="$2"
        shift 2
        ;;
    -l | --level)
        FILTER_LEVEL=$(printf "%02d" "$2")
        shift 2
        ;;
    -c | --comp)
        COMP_TYPE="$2"
        shift 2
        ;;
    -b | --bext)
        BEXT="$2"
        shift 2
        ;;
    --help)
        help
        ;;
    --version)
        version
        ;;
    *) # unknown option
        if [ "$1" == -* ]; then
            brief_help
        fi
        ;;
    esac
done

if [ "$(id -u)" != "0" ]; then
    echo "This script must be run as root."
    brief_help
fi

if [ "$DIRECTORY" ] && { [ ! -d "$DIRECTORY" ] || [ -d "$DIRECTORY" ] && [ ! -r "$DIRECTORY" ]; }; then
    echo "The specified directory must exist and be readable."
    brief_help
fi

if [ -d "/run/initramfs/memory/bundles" ]; then
    BUNDLES="/run/initramfs/memory/bundles"
    SYSTEMCHANGES="/run/initramfs/memory/changes"
elif [ -d "/memory/bundles" ]; then
    BUNDLES="/memory/bundles"
    SYSTEMCHANGES="/memory/changes"
fi

# Overlayfs requires one more subdir. Detect it this way
if [ -d "$SYSTEMCHANGES/changes" ] && [ -d "$SYSTEMCHANGES/workdir" ] && [ "$(ls -1 "$SYSTEMCHANGES" | egrep -v '^changes$' | egrep -v '^workdir')" = "" ]; then
   SYSTEMCHANGES="$SYSTEMCHANGES/changes"
fi

# We use the changes folder to store temporary files and perform module
# builds because it is on the physical file system, which will avoid RAM
# leaks when building a large module.
TMP=$(mktemp -d "$SYSTEMCHANGES"/build2sb.XXXXX)
CHANGES="$TMP/changes"
UNION="$TMP/union"

trap 'if [ "$RESOLVCONFLNK" = "true" ]; then rm -f "$CHANGES/etc/resolv.conf" 2>/dev/null; else umount "$UNION/etc/resolv.conf" 2>/dev/null; fi; umount "$UNION/sys" 2>/dev/null; umount "$UNION/proc" 2>/dev/null; umount "$UNION/dev/pts" 2>/dev/null; umount "$UNION/dev" 2>/dev/null; rm "$CHANGES/install" 2>/dev/null; rmdir "$CHANGES/*" 2>/dev/null; umount "$UNION"; umount "$TMP" 2>/dev/null; rm -rf "$TMP"' EXIT

#mount -t tmpfs tmpfs "$TMP"
mkdir -p "$CHANGES"
mkdir -p "$UNION"

ROFLAG=""
if aufs_is_supported; then
    ROFLAG="=ro"
fi

if [ -n "$FILTER_LEVEL" ]; then
    for BUNDLE in $(ls -1dr "$BUNDLES"/*); do
        if [[ $(basename "$BUNDLE") =~ ^[0-9]+ ]]; then
            if [ ${BASH_REMATCH[0]} -gt $FILTER_LEVEL ]; then
                continue
            fi
        fi
        LOWERS+=$(printf '%s%s:' "$BUNDLE" "$ROFLAG")
    done
else
    for BUNDLE in $(ls -1dr "$BUNDLES"/*); do
        LOWERS+=$(printf '%s%s:' "$BUNDLE" "$ROFLAG")
    done
fi

LOWERS=${LOWERS/%:/}

if aufs_is_supported; then
    mount -t aufs -o br="$CHANGES"=rw:"$LOWERS" none "$UNION"
else
    mkdir $TMP/work
    mount -t overlay -o lowerdir="$LOWERS",upperdir="$CHANGES",workdir="$TMP"/work overlay "$UNION"
fi

for DIR in boot dev proc sys tmp media mnt run; do
    mkdir -p "$UNION/$DIR"
done

chmod ugo+rwx "$UNION/tmp"

for DIR in dev dev/pts proc sys; do
    mount --bind "/$DIR" "$UNION/$DIR"
done

if [ "$DIRECTORY" ]; then
    cd "$DIRECTORY" && cp --parents -afr * "$UNION/"
    cd -
fi

if [ -L "$UNION/etc/resolv.conf" ]; then
    rm "$UNION/etc/resolv.conf"
    echo "nameserver 8.8.8.8" >"$UNION/etc/resolv.conf"
    RESOLVCONFLNK="true"
else
    mount --bind "/etc/resolv.conf" "$UNION/etc/resolv.conf"
fi

echo "Entering chroot environment..."
echo "When finished, enter 'exit' in chroot to create the module."
if ! /usr/sbin/chroot "$UNION"; then
    FAILED="true"
else
    FAILED="false"
fi

if [ "$RESOLVCONFLNK" = "true" ]; then
    rm -f "$CHANGES/etc/resolv.conf"
else
    umount "$UNION/etc/resolv.conf"
fi
umount "$UNION/sys"
umount "$UNION/proc"
umount "$UNION/dev/pts"
umount "$UNION/dev"

if [ -f "$CHANGES/root/.bash_history" ]; then
    rm "$CHANGES/root/.bash_history"
fi
rmdir "$CHANGES/*" 2>/dev/null

if [ "$FAILED" != "true" ]; then
    if [ ! "$FILENAME" ]; then
        if [ -n "$FILTER_LEVEL" ]; then
            PREFIX="$(printf "%02d" $((10#$FILTER_LEVEL + 1)))-"
        else
            PREFIX=""
        fi
        FILENAME="$PREFIX$(date +%Y%m%d-%H%M).${BEXT}"
    fi

    echo "Saving changes made by installation script into a module $FILENAME..."
    savechanges "$FILENAME" "$CHANGES" -c "$COMP_TYPE"
fi
