#!/bin/sh set -e # exit on failure IFS=$(printf '\n\t') # smarter ifs apprun() { export PATH="$APPDIR"/bin:"$PATH" export LD_LIBRARY_PATH="$APPDIR"/lib64:"$APPDIR"/lib:"$LD_LIBRARY_PATH" exec "$APPDIR/$(basename "$ARGV0")" "$@" } bundlehead() { MOUNTBIN=$(mktemp) # use provided offsets to supply dwarfs binary tail -n+"$OFF" "$0" | head -n"$LEN" | head -c-1 | zstd -cqd > "$MOUNTBIN" #shellcheck disable=SC2317 dwarfs() { chmod +x "$MOUNTBIN"; "$MOUNTBIN" "$@"; rm "$MOUNTBIN"; } } header() { APPDIR=$(mktemp -d) APPIMAGE="$(realpath "$0")" export APPDIR APPIMAGE ARGV0="$0" OWD="$PWD" dwarfs -o offset=auto -o tidy_strategy=swap -o workers="$(nproc)" "$0" "$APPDIR" trap 'fusermount -quz $APPDIR; rmdir $APPDIR' 0 1 2 3 6 14 15 EXIT "$APPDIR/AppRun" "$@" exit $? } outfunc() { echo '#!/bin/sh' echo 'set -e' # running outfunc with no input just prints these two lines sed -n "/^$1() {$/,/^}$/s/^ *//p" "$0" | tail -n+2 | head -n-1 } unappimage() { test "$(hexdump -n11 -e'"%x"' "$1")" = 464c457f1010224941 || return 1 echo "AppImage found. Extracting..." # appimage magic matched o=$(($(readelf -h "$1" | sed -e 's/[^0-9]//g;13p;18,19p;d' | sed -e 1a+ -e 2a*))) unsquashfs -o "$o" "$@" # calculate offset via ELF header } zzexe() { [ "$1" = "-p" ] && p="$2" && shift # save prefix if present shift; out=$(mktemp) # make tmp file to avoid io operations { outfunc echo "OFF=$(($( (outfunc; outfunc zzexe_header) | wc -l)+2))" outfunc zzexe_header | sed -e "3s/)$/${1##*[./]})/" -e "8s/^/$p /" zstdmt -cq19 "$@" } > "$out" mv "$out" "$1" chmod +x "$1" exit } zzexe_header() { dir="$(dirname "$0")" out=$(mktemp -t .zzXXXX.) tail -n+"$OFF" "$0" | zstd -cd > "$out" chmod +x "$out" [ ! -f "$dir/$(basename "$out")" ] && ln -s "$out" "$dir" trap 'rm -f "$out" "$dir/$(basename "$out")"' 0 1 2 3 6 14 15 EXIT "$dir/$(basename "$out")" "$@" exit $? } case "$1" in -a ) outfunc apprun > "$2" chmod +x "$2" exit ;; -b ) outfunc apprun | sed '5s#/#/bin/#' > "$2" chmod +x "$2" exit ;; --bundle ) # allow bundling dwarfs binary BUNDLE=$(realpath "$2") shift 2;; -d | --decompress ) d=dwarfs-root # just to keep line shorter, extract if file is dwarfs dwarfsck -d0 -i"$2" && mkdir $d && dwarfsextract -o $d -i "$2" && exit tmp=$(mktemp) # make tmp file to avoid io operations unappimage "$2" && exit # also extract appimages cuz we can tail -n+"$(sed -n '3s/^OFF=//p' "$2")" "$2" | zstd -cd > "$tmp" mv "$tmp" "$2" chmod +x "$2" exit ;; --fetch ) FETCH=1 shift ;; -p | -z | --prefix | --zzexe ) zzexe "$@" ;; --version | -v ) tput setaf 2; echo appdwarf 2023.02.04 tput setaf 6; echo Built by July 🏳️‍🌈; exit ;; -* | '' ) echo "Usage: appdwarf [option] [APP/FILE/FOLDER/URL] [compression options]" echo " -a [FILE] write example AppRun file" echo " -b [FILE] write bin subdir AppRun file" echo " -d [--decompress] [FILE] decompress a compressed program" echo echo " --bundle [FILE] bundle dwarfs with the appdwarf image" echo " --fetch only fetch a remote AppImage" echo echo " -p [--prefix] [PREFIX] zzexe a file with prefix" echo " -z [--zzexe] zzexe a file" echo echo " -h, --help Print this help text" echo " -v, --version Print the appdwarf version"; exit ;; esac if [ ! -d "$1" ]; then # directory doesn't exist, see if this is an appimage if [ ! -f "$1" ]; then # file doesn't exist, see if this is a url if ! echo "$1" | grep -q / ; then # AppImageHub echo "Checking AppImageHub for this program..." app=https://github.com/AppImage/appimage.github.io/raw/master/apps/$1.md shift set -- "$(curl -L "$app" | grep -o https.\*releases | sed 's|/releases$||')" "$@" elif ! echo "$1" | grep https ; then # GitHub in Author/Repo format app=$1 shift set -- "https://github.com/$app" "$@" fi if echo "$1" | grep -q 'https://github.com/[^/]*/[^/]*/*$'; then # GitHub url echo "Assuming this is a GitHub repo..." app="$(echo "${1%/}"/releases | sed 's|github.com|api.github.com/repos|')" api="$(curl "$app" | jq -r .[].assets[].browser_download_url | grep 'AppImage$')" link="$(echo "$api" | grep "$(uname -m)" || echo "$api" | grep -vEe '-(aarch|arm)(64|hf)\.AppImage')" shift set -- "$(echo "$link" | head -n1)" "$@" fi app=$(basename "$1") # actually try to get the appimage if aria2c -x16 -s16 "$1" -o "$app" || wget "$1" -O "$app"; then shift set -- "$app" "$@" test "$FETCH" && chmod +x "$1" && exit else tput setaf 1; echo "No valid remote or local input found. Exiting..." >&2 rm -f "$app"; exit 1 fi fi # this is a file, but it might be an existing dwarfs image if dwarfsck -d0 -i"$1"; then set -- "$@" --recompress=none elif unappimage "$1"; then app="$(basename "$1" .AppImage)" rm -rf "$1" "$app" mv squashfs-root "$app" shift set -- "$app" "$@" else tput setaf 4; echo "$1 is not an AppImage, it will be zzexe'd" zzexe -z "$@" fi fi head="$(mktemp)" if [ "$BUNDLE" ]; then { # behavior for bundling a dwarfs executable ZDATA="$(mktemp)" zstd -cq "$BUNDLE" > "$ZDATA" outfunc echo "OFF=$(($( (outfunc; outfunc bundlehead; outfunc header) | wc -l)+3))" echo "LEN=$(($(wc -l < "$ZDATA")+1))" outfunc bundlehead } > "$head" fi outfunc header >> "$head" test "$BUNDLE" && cat "$ZDATA" >> "$head" echo >> "$head" mkdwarfs -o "$(realpath "$1").sh" -B5 --header "$head" -i "$@" rm -rf "$head" "$1" chmod +x "$(realpath "$1").sh"