#!/usr/bin/env bash ALPINE="https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-minirootfs-3.20.3-x86_64.tar.gz" CHROOTDIR="./alpine_chroot" QEMU_STATIC_URL="https://github.com/multiarch/qemu-user-static/releases/download/v7.2.0-1/qemu-aarch64-static" log() { case "$2" in error) printf "\e[1m\e[31mERROR:\e[0m \e[1m%s\e[0m (host)\n" "$1">&2;; internal) printf "\e[1m\e[96m h>\e[0m \e[1m%s\e[0m\n" "$1">&2;; ierror) printf "\e[1m\e[31m h>\e[0m \e[1m%s\e[0m\n" "$1">&2;; *) printf "\e[1m\e[92mH>>\e[0m \e[1m%s\e[0m\n" "$1">&2;; esac } mkdir_if_not_exists() { [ -d "$1" ] && return 0 [ -f "$1" ] && rm -f "$1" mkdir "$1" } umount_if_mouted() { [ ! -d "$1" ] && return 0 if grep -qs "$(realpath "$1")" /proc/mounts; then umount "$1" return $? else return 0 fi } require () { which "$1" > /dev/null 2>&1 || { log "$1 not found" error exit 1 } } # shellcheck disable=SC2161 # shellcheck disable=SC2086 arguments() { while [[ $# -gt 0 ]]; do opt="$1" shift case "$(echo ${opt} | tr '[:upper:]' '[:lower:]')" in install) install_chroot break 2 ;; remove) remove_chroot break 2 ;; build) build "$1" break 2 ;; chroot) chroot_into break 2 ;; update) update break 2 ;; -r | -c | --cleanraw | --cleanup) run_makelni "$opt" break 2 ;; -h | -help | --help) usage break 2 ;; *) log "Unknown option: $opt" error break 2 ;; esac done } usage() { log "Usage commands:" cat <>\e[0m \e[1m%s\e[0m\n" "Shutdown signal received." _shutdown $sig } trap 'trap " " SIGINT SIGTERM SIGHUP; kill 0; wait; sigterm_handler' SIGINT SIGTERM SIGHUP # Chroot functions require "lsof" require "tar" require "gzip" require "wget" setup_inet() { [ ! -d "$CHROOTDIR" ] && { log "Rootdir [$CHROOTDIR] does not exists" ierror return 2 } echo "nameserver 1.1.1.1" > "$CHROOTDIR/etc/resolv.conf" echo "makelnichroot" > "$CHROOTDIR/etc/hostname" echo "127.0.0.1 localhost" > "$CHROOTDIR/etc/hosts" } prepare_chroot() { [ ! -d "$CHROOTDIR" ] && { log "Rootdir [$CHROOTDIR] does not exists" ierror return 2 } export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:\$PATH mount --bind "$CHROOTDIR" "$CHROOTDIR" mount -t proc proc "$CHROOTDIR/proc" mount -t sysfs sysfs "$CHROOTDIR/sys" mount -t devtmpfs devtmpfs "$CHROOTDIR/dev" mount -t devpts devpts "$CHROOTDIR/dev/pts" mount -t tmpfs devshm "$CHROOTDIR/dev/shm" if uname -m | grep -q aarch64 || [ -f "/proc/sys/fs/binfmt_misc/qemu-aarch64" ]; then log "Cancel qemu-aarch64-static register" else # shellcheck disable=SC2028 echo ':aarch64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:'"$(realpath $CHROOTDIR/bin/qemu-aarch64-static):PF" > /proc/sys/fs/binfmt_misc/register 2> /dev/null || true # shellcheck disable=SC2028 echo ':aarch64ld:M::\x7fELF\x02\x01\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\xb7\x00:\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:'"$(realpath $CHROOTDIR/bin/qemu-aarch64-static):PF" > /proc/sys/fs/binfmt_misc/register 2> /dev/null || true fi } detach_chroot() { [ ! -d "$CHROOTDIR" ] && { log "Rootdir [$CHROOTDIR] does not exists" ierror return 2 } umount_if_mouted "$CHROOTDIR/sys" umount_if_mouted "$CHROOTDIR/dev/pts" umount_if_mouted "$CHROOTDIR/dev/shm" umount_if_mouted "$CHROOTDIR/dev" umount_if_mouted "$CHROOTDIR/proc" if [ -f "/proc/sys/fs/binfmt_misc/aarch64" ]; then echo -1 > /proc/sys/fs/binfmt_misc/aarch64 2> /dev/null fi if [ -f "/proc/sys/fs/binfmt_misc/aarch64ld" ]; then echo -1 > /proc/sys/fs/binfmt_misc/aarch64ld 2> /dev/null fi umount_if_mouted "$CHROOTDIR" } install_chroot() { [ -d "$CHROOTDIR" ] && { log "Chroot already installed" error exit 1 } mkdir "$CHROOTDIR" [ ! -f "./alpine.tar.gz" ] && { log "Downloading alpine rootfs tarball" wget -q --show-progress "$ALPINE" -O "./alpine.tar.gz" || { ec=$? log "Failed to download rootfs tarball" error exit "$ec" } } log "Extracting alpine rootfs tarball" tar -xzf "./alpine.tar.gz" -C "$CHROOTDIR/" || { log "Failed to extract rootfs" error } setup_inet prepare_chroot || { log "Failed to setup chroot" error exit 1 } log "Updating apk repos" chroot "$CHROOTDIR" apk update --quiet || { log "Failed to update apk repos" error exit 1 } log "Installing pakages" chroot "$CHROOTDIR" apk add --quiet shadow fish bash git debootstrap pixz losetup rsync pv wget e2fsprogs e2fsprogs-extra libarchive-tools coreutils go libusb libusb-dev pkgconfig || { log "Failed to install packages" error exit 1 } chroot "$CHROOTDIR" chsh -s /usr/bin/fish root log "Cloning makelni repo" chroot "$CHROOTDIR" git clone --quiet "https://git.timoxa0.su/timoxa0/makelni.git" /makelni || { log "Failed to clone makelni repo" error exit 1 } log "Installing qemu-aarch64-static" { wget -q --show-progress -N "$QEMU_STATIC_URL" -O "$CHROOTDIR/bin/qemu-aarch64-static" && chmod 755 "$CHROOTDIR/bin/qemu-aarch64-static" } || { log "Failed to install qemu-aarch64-static" error exit 1 } log "Installing lon-tool" { chroot "$CHROOTDIR" git clone --quiet https://git.timoxa0.su/timoxa0/lon-tool.git /lon-tool-src && chroot "$CHROOTDIR" /bin/bash -c 'cd /lon-tool-src && rev=$(git describe --abbrev=4 --dirty --always --tags) && go get git.timoxa0.su/timoxa0/lon-tool/cmd && go build -ldflags "-X git.timoxa0.su/timoxa0/lon-tool/cmd.version=$rev" -o /bin/lon-tool main.go' } || { log "Failed to install lon-tool" error exit 1 } detach_chroot || { log "Failed to detach chroot" error exit 1 } log "Done!" exit 0 } remove_chroot() { [ ! -d "$CHROOTDIR" ] && { log "Chroot not installed" error exit 1 } detach_chroot || { log "Failed to umount chroot" error exit 1 } { rm -rf "${CHROOTDIR:?}/" && log "Done!" } || { log "Failed to remove chroot" error exit 1 } } chroot_into() { prepare_chroot || { log "Failed to setup chroot" error exit 1 } chroot "$CHROOTDIR" /usr/bin/fish detach_chroot || { log "Failed to detach chroot" error exit 1 } } build() { [ ! -d "$CHROOTDIR" ] && { log "Chroot not installed" error exit 1 } local lni="$1" [ ! -f "$lni" ] && { log "$lni no such file" error exit 2 } cp "$lni" "$CHROOTDIR/tmp/lnibuild" log "Entering build chroot" prepare_chroot || { log "Failed to setup chroot" error exit 1 } chroot "$CHROOTDIR" /bin/bash -c "cd /makelni && ./makelni /tmp/lnibuild" local exitcode=$? rm "$CHROOTDIR/tmp/lnibuild" log "Exiting build chroot" detach_chroot || { log "Failed to detach chroot" error exit 1 } [ "$exitcode" -eq "0" ] || { log "Failed to build image" error exit 1 } source <( grep name "$lni" ) mv "$CHROOTDIR/makelni/out/${name}.lni" "./${name}.lni" chown "$(stat -c '%U:%G' "$0")" "./${name}.lni" exit 0 } run_makelni() { [ ! -d "$CHROOTDIR" ] && { log "Chroot not installed" error exit 1 } log "Running makelni $1" prepare_chroot || { log "Failed to setup chroot" error exit 1 } chroot "$CHROOTDIR" /bin/bash -c "cd /makelni && ./makelni $1" local exitcode=$? detach_chroot || { log "Failed to detach chroot" error exit 1 } [ "$exitcode" -eq "0" ] || { log "Failed to build image" error } return "$exitcode" } update() { [ ! -d "$CHROOTDIR" ] && { log "Chroot not installed" error exit 1 } prepare_chroot || { log "Failed to setup chroot" error exit 1 } log "Updating makelni" chroot "$CHROOTDIR" /bin/bash -c "cd /makelni && git pull" local exitcode=$? detach_chroot || { log "Failed to detach chroot" error exit 1 } [ "$exitcode" -eq "0" ] || { log "Failed to update makelni" error } return "$exitcode" } if [ "$(id -u)" != "0" ]; then log "$0 must be run as root" exit 3 fi [[ "$(uname -m)" != "x86_64*" ]] || { printf "Unsupported CPU arch\n" exit 1 } arguments $*