Remove AppImage logic and build system (#2446)

This commit is contained in:
Vítor Vasconcellos 2024-05-04 19:27:14 -03:00 committed by GitHub
parent 2d78edef4d
commit 44478207e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 10 additions and 464 deletions

View file

@ -7,7 +7,7 @@ type OS = 'darwin' | 'windows' | 'linux';
type Arch = 'x64' | 'arm64';
type TargetConfig = { bundle: string; ext: string };
type BuildTarget = {
updater: { bundle: string; bundleExt: string; archiveExt: string };
updater: false | { bundle: string; bundleExt: string; archiveExt: string };
standalone: Array<TargetConfig>;
};
@ -29,15 +29,8 @@ const OS_TARGETS = {
standalone: [{ ext: 'msi', bundle: 'msi' }]
},
linux: {
updater: {
bundle: 'appimage',
bundleExt: 'AppImage',
archiveExt: 'tar.gz'
},
standalone: [
{ ext: 'deb', bundle: 'deb' },
{ ext: 'AppImage', bundle: 'appimage' }
]
updater: false,
standalone: [{ ext: 'deb', bundle: 'deb' }]
}
} satisfies Record<OS, BuildTarget>;
@ -57,7 +50,9 @@ async function globFiles(pattern: string) {
return await globber.glob();
}
async function uploadUpdater({ bundle, bundleExt, archiveExt }: BuildTarget['updater']) {
async function uploadUpdater(updater: BuildTarget['updater']) {
if (!updater) return;
const { bundle, bundleExt, archiveExt } = updater;
const fullExt = `${bundleExt}.${archiveExt}`;
const files = await globFiles(`${BUNDLE_DIR}/${bundle}/*.${fullExt}*`);

View file

@ -112,16 +112,6 @@ jobs:
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: Build AppImage in Docker
if: ${{ runner.os == 'Linux' && ( matrix.settings.target == 'x86_64-unknown-linux-gnu' || matrix.settings.target == 'aarch64-unknown-linux-gnu' ) }}
run: |
set -euxo pipefail
docker run --rm -v $(pwd):/srv -e 'CI=true' -e 'TARGET=${{ matrix.settings.target }}' -w /srv debian:bookworm scripts/appimage/build_appimage.sh
cd 'target/${{ matrix.settings.target }}/release/bundle/appimage'
sudo chown "$(id -u):$(id -g)" -R .
tar -czf Updater.AppImage.tar.gz *.AppImage
pnpm tauri signer sign -k '${{ secrets.TAURI_PRIVATE_KEY }}' -p '${{ secrets.TAURI_KEY_PASSWORD }}' "$(pwd)/Updater.AppImage.tar.gz"
- name: Publish Artifacts
uses: ./.github/actions/publish-artifacts
with:

View file

@ -120,10 +120,6 @@ To run the mobile app:
- `xcrun simctl launch --console booted com.spacedrive.app` allows you to view the console output of the iOS app from `tracing`. However, the application must be built in `debug` mode for this.
- `pnpm mobile start` (runs the metro bundler only)
##### AppImage
Specific instructions on how to build an AppImage release are located [here](scripts/appimage/README.md)
### Pull Request
Once you have finished making your changes, create a pull request (PR) to submit them.

View file

@ -37,31 +37,6 @@ thread_local! {
// )).unwrap_or_default();
let ctx = AppLaunchContext::default();
if let Some(appdir) = std::env::var_os("APPDIR").map(PathBuf::from) {
// Remove AppImage paths from environment variables to avoid external applications attempting to use the AppImage's libraries
// https://github.com/AppImage/AppImageKit/blob/701b711f42250584b65a88f6427006b1d160164d/src/AppRun.c#L168-L194
ctx.unsetenv("PYTHONHOME");
ctx.unsetenv("GTK_DATA_PREFIX");
ctx.unsetenv("GTK_THEME");
ctx.unsetenv("GDK_BACKEND");
ctx.unsetenv("GTK_EXE_PREFIX");
ctx.unsetenv("GTK_IM_MODULE_FILE");
ctx.unsetenv("GDK_PIXBUF_MODULE_FILE");
remove_prefix_from_env_in_ctx(&ctx, "PATH", &appdir);
remove_prefix_from_env_in_ctx(&ctx, "LD_LIBRARY_PATH", &appdir);
remove_prefix_from_env_in_ctx(&ctx, "PYTHONPATH", &appdir);
remove_prefix_from_env_in_ctx(&ctx, "XDG_DATA_DIRS", &appdir);
remove_prefix_from_env_in_ctx(&ctx, "PERLLIB", &appdir);
remove_prefix_from_env_in_ctx(&ctx, "GSETTINGS_SCHEMA_DIR", &appdir);
remove_prefix_from_env_in_ctx(&ctx, "QT_PLUGIN_PATH", &appdir);
remove_prefix_from_env_in_ctx(&ctx, "GST_PLUGIN_SYSTEM_PATH", &appdir);
remove_prefix_from_env_in_ctx(&ctx, "GST_PLUGIN_SYSTEM_PATH_1_0", &appdir);
remove_prefix_from_env_in_ctx(&ctx, "GTK_PATH", &appdir);
remove_prefix_from_env_in_ctx(&ctx, "GIO_EXTRA_MODULES", &appdir);
}
ctx
}
}

View file

@ -2,7 +2,7 @@ use std::{
collections::HashSet,
env,
ffi::{CStr, OsStr, OsString},
io, mem,
mem,
os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
ptr,
@ -191,56 +191,6 @@ pub fn normalize_environment() {
],
)
.expect("PATH must be successfully normalized");
if let Ok(appdir) = get_appdir() {
println!("Running from APPIMAGE");
// Workaround for https://github.com/AppImageCrafters/appimage-builder/issues/175
env::set_current_dir(appdir.join({
let appimage_libc_version = version(
std::env::var("APPDIR_LIBC_VERSION")
.expect("AppImage Libc version must be set")
.as_str(),
);
let system_lic_version = version({
#[cfg(target_env = "gnu")]
{
use libc::gnu_get_libc_version;
let ptr = unsafe { gnu_get_libc_version() };
if ptr.is_null() {
panic!("Couldn't read glic version");
}
unsafe { CStr::from_ptr(ptr) }
.to_str()
.expect("Couldn't read glic version")
}
#[cfg(not(target_env = "gnu"))]
{
// Use the same version as gcompat
// https://git.adelielinux.org/adelie/gcompat/-/blob/current/libgcompat/version.c
std::env::var("GLIBC_FAKE_VERSION").unwrap_or_else(|_| "2.8".to_string())
}
});
if system_lic_version < appimage_libc_version {
"runtime/compat"
} else {
"runtime/default"
}
}))
.expect("Failed to set current directory to $APPDIR");
// Bubblewrap does not work from inside appimage
env::set_var("WEBKIT_FORCE_SANDBOX", "0");
env::set_var("WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS", "1");
// FIX-ME: This is required because appimage-builder generates a broken GstRegistry, which breaks video playback
env::remove_var("GST_REGISTRY");
env::remove_var("GST_REGISTRY_UPDATE");
}
}
pub(crate) fn remove_prefix_from_pathlist(
@ -271,21 +221,6 @@ pub fn is_snap() -> bool {
false
}
fn get_appdir() -> io::Result<PathBuf> {
if let Some(appdir) = std::env::var_os("APPDIR").map(PathBuf::from) {
if appdir.is_absolute() && appdir.is_dir() {
return Ok(appdir);
}
}
Err(io::Error::new(io::ErrorKind::NotFound, "AppDir not found"))
}
// Check if appimage by looking if APPDIR is set and is a valid directory
pub fn is_appimage() -> bool {
get_appdir().is_ok()
}
// Check if flatpak by looking if FLATPAK_ID is set and not empty and that the .flatpak-info file exists
pub fn is_flatpak() -> bool {
if let Some(flatpak_id) = std::env::var_os("FLATPAK_ID") {

View file

@ -4,4 +4,4 @@ mod app_info;
mod env;
pub use app_info::{list_apps_associated_with_ext, open_file_path, open_files_path_with};
pub use env::{get_current_user_home, is_appimage, is_flatpak, is_snap, normalize_environment};
pub use env::{get_current_user_home, is_flatpak, is_snap, normalize_environment};

View file

@ -373,21 +373,6 @@ pub async fn open_ephemeral_file_with(paths_and_urls: Vec<PathAndUrl>) -> Result
fn inner_reveal_paths(paths: impl Iterator<Item = PathBuf>) {
for path in paths {
#[cfg(target_os = "linux")]
if sd_desktop_linux::is_appimage() {
// This is a workaround for the app, when package inside an AppImage, crashing when using opener::reveal.
if let Err(e) = sd_desktop_linux::open_file_path(if path.is_file() {
path.parent().unwrap_or(&path)
} else {
&path
}) {
error!("Failed to open logs dir: {e:#?}");
}
} else if let Err(e) = opener::reveal(path) {
error!("Failed to open logs dir: {e:#?}");
}
#[cfg(not(target_os = "linux"))]
if let Err(e) = opener::reveal(path) {
error!("Failed to open logs dir: {e:#?}");
}

View file

@ -98,11 +98,8 @@ pub fn plugin<R: Runtime>() -> TauriPlugin<R> {
tauri::plugin::Builder::new("sd-updater")
.on_page_load(|window, _| {
#[cfg(target_os = "linux")]
let updater_available = {
let env = window.env();
let updater_available = false;
env.appimage.is_some()
};
#[cfg(not(target_os = "linux"))]
let updater_available = true;

View file

@ -1,34 +0,0 @@
import Image from 'next/image';
import { tw } from '@sd/ui';
const AppFrameOuter = tw.div`relative m-auto flex w-full max-w-7xl rounded-lg transition-opacity px-4`;
const AppFrameInner = tw.div`z-30 flex w-full rounded-lg border-t border-app-line/50 backdrop-blur`;
const AppImage = () => {
return (
<div className="w-screen">
<div className="relative mx-auto max-w-full sm:w-full sm:max-w-[1400px]">
<div className="bloom burst bloom-one" />
<div className="bloom burst bloom-three" />
<div className="bloom burst bloom-two" />
</div>
<div className="fade-in-app-embed relative z-30 mt-8 h-[255px] w-full px-1 text-center sm:mt-16 sm:h-[428px] md:h-[428px] lg:h-[628px]">
<AppFrameOuter>
<AppFrameInner>
<Image
className="rounded-lg"
alt="spacedrive"
src="/images/app/1.webp"
loading="eager"
width={1278}
height={626}
quality={100}
/>
</AppFrameInner>
</AppFrameOuter>
</div>
</div>
);
};
export default AppImage;

View file

@ -27,7 +27,7 @@ index: 11
- Available at `/api/releases/desktop/[version]/[target]/[arch]`
- Same version semantics as Desktop Update API
- Looks for assets starting with `Spacedrive-{Target}-{Arch}` to allow for extensions like `.dmg`, `.AppImage` and `.msi`
- Looks for assets starting with `Spacedrive-{Target}-{Arch}` to allow for extensions like `.dmg`, `.deb` and `.msi`
- Returns a redirect as it's intended to be invoked via `<a>` elements
## Publishing a Release

View file

@ -108,5 +108,3 @@ C:\Program Files\Spacedrive\Spacedrive.exe
```bash
/opt/Spacedrive/Spacedrive
```
- Alternatively you may launch the AppImage from a terminal to view the logs.

View file

@ -1,2 +0,0 @@
AppDir
appimage-build

View file

@ -1,166 +0,0 @@
# Reference:
# https://appimage-builder.readthedocs.io/en/latest/reference/version_1.html
version: 1
script: |
set -eu
# Clean up directories created by the recipe
rm "$TARGET_APPDIR" -rf || true
rm "$REPO_DIR" -rf || true
# Create a temporary Debian repository folder to put the generated Debian
# packages on, so we can install it like any other packages
mkdir -p "$REPO_DIR"
_deb="$(find "${TARGET_APPIMAGE_DIR}/../deb" -type f -name '*.deb' | sort -t '_' -k '2,2' -V | tail -n 1)"
cp -f "$_deb" "$REPO_DIR"
CWD="$PWD" && cd "$REPO_DIR" && apt-ftparchive packages . > Packages && cd "$CWD"
# The following two commands are a workaround for the local APT package not
# being saved to the archives directory, as expected by appimage-builder
mkdir -p appimage-build/apt/archives
cp -f "$_deb" appimage-build/apt/archives
AppDir:
path: !ENV '${TARGET_APPDIR}'
app_info:
id: com.spacedrive
name: Spacedrive
icon: spacedrive
version: !ENV '${VERSION}'
exec: usr/bin/spacedrive
exec_args: $@
apt:
arch: !ENV ${TARGET_APPIMAGE_APT_ARCH}
sources:
- sourceline: !ENV 'deb [arch=${TARGET_APPIMAGE_APT_ARCH}] http://deb.debian.org/debian bookworm main'
key_url: 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x1f89983e0081fde018f3cc9673a4f27b8dd47936'
- sourceline: !ENV 'deb [arch=${TARGET_APPIMAGE_APT_ARCH}] http://security.debian.org/debian-security bookworm-security main'
key_url: 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x1f89983e0081fde018f3cc9673a4f27b8dd47936'
- sourceline: !ENV 'deb [arch=${TARGET_APPIMAGE_APT_ARCH}] http://deb.debian.org/debian bookworm-updates main'
key_url: 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xac530d520f2f3269f5e98313a48449044aad5c5d'
- sourceline: !ENV 'deb [trusted=yes] file:${REPO_DIR}/ ./'
include:
- spacedrive
- gstreamer1.0-plugins-good
- gstreamer1.0-plugins-ugly
- gstreamer1.0-packagekit
# This package is necessary to avoid this: https://github.com/AppImageCrafters/appimage-builder/pull/191
- shared-mime-info
exclude:
- systemd
- systemd-sysv
- util-linux
- util-linux-extra
- xkb-data
- fdisk
- dmsetup
- mount
- dbus-daemon
- perl-base
- readline-common
- libpam-modules
- avahi-daemon
- adwaita-icon-theme
- humanity-icon-theme
- sensible-utils
- dconf-service
- ubuntu-mono
- fonts-*-core
- bubblewrap
- gstreamer1.0-plugins-bad
- gstreamer1.0-libav
files:
exclude:
# Development, localization, documentation and configuration files
- usr/include
- usr/share/bug
- usr/share/man
- usr/share/doc
- usr/share/doc-base
- usr/share/lintian
- usr/share/sounds
- usr/share/bash-completion
- usr/share/zsh
- usr/share/applications
- usr/share/pkgconfig
- usr/share/pam
- usr/share/pam-configs
- usr/share/polkit-1
- usr/share/util-linux
- usr/share/initramfs-tools
- usr/share/locale
- usr/share/systemd
- usr/lib/systemd
- usr/lib/*-linux-gnu/systemd
- usr/lib/udev
- usr/lib/*.d
- usr/lib/kernel
- usr/lib/tmpfiles.d
# using our own ALSA can cause issues, and the API is pretty stable anyway
- usr/lib/*/libasound.so.*
# produced by library compilation
- usr/lib/*.a
# produced by library compilation
- usr/lib/cmake
# produced by library compilation
- usr/lib/pkgconfig
- etc
- var
# systemd, pam, lsb, modprobe.d, udev and misc daemon files
- lib/systemd
- lib/*-linux-gnu/security
- lib/udev
- lib/lsb
- lib/modprobe.d
# Unneeded utility programs and libraries
- usr/sbin
# Remove any binary but spacedrive
- usr/bin/[!s]*
- usr/bin/s[!p]*
- usr/bin/sp[!a]*
- usr/bin/spa[!c]*
- usr/bin/spac[!e]*
- sbin
- bin
runtime:
path_mappings:
- !ENV '/usr/lib/${TARGET_APPIMAGE_ARCH}-linux-gnu/webkit2gtk-4.0/injected-bundle/libwebkit2gtkinjectedbundle.so:$APPDIR/usr/lib/${TARGET_APPIMAGE_ARCH}-linux-gnu/webkit2gtk-4.0/injected-bundle/libwebkit2gtkinjectedbundle.so'
# Patch webkit2gtk so it works under appimage
after_bundle: |
set -eux
# libwebkit2gtk Patch for WebKit binaries
mv "${TARGET_APPDIR}/usr/lib/${TARGET_APPIMAGE_ARCH}-linux-gnu/webkit2gtk-4.0/WebKitWebProcess" "${TARGET_APPDIR}/usr/bin/WebKitWebProcess"
mv "${TARGET_APPDIR}/usr/lib/${TARGET_APPIMAGE_ARCH}-linux-gnu/webkit2gtk-4.0/WebKitNetworkProcess" "${TARGET_APPDIR}/usr/bin/WebKitNetworkProcess"
if [ "$TARGET_APPIMAGE_ARCH" == 'aarch64' ]; then
find "${TARGET_APPDIR}/usr/lib/aarch64-linux-gnu" -type f -name 'libwebkit2gtk-4.0.so*' -exec sed -i -e "s|/usr/lib/aarch64-linux-gnu/webkit2gtk-4.0\([^/][^i][^n]\)|../../././././././././././././././usr/bin\1|g" '{}' \;
else
find "${TARGET_APPDIR}/usr/lib/x86_64-linux-gnu" -type f -name 'libwebkit2gtk-4.0.so*' -exec sed -i -e "s|/usr/lib/x86_64-linux-gnu/webkit2gtk-4.0\([^/][^i][^n]\)|../.././././././././././././././usr/bin/\1|g" '{}' \;
fi
# libwebkit2gtk Patch for gstreamer binaries
mv "${TARGET_APPDIR}/usr/libexec/pk-gstreamer-install" "${TARGET_APPDIR}/usr/bin/pk-gstreamer-install"
ln -sf pk-gstreamer-install "${TARGET_APPDIR}/usr/bin/gst-install-plugins-helper"
mv "${TARGET_APPDIR}/usr/lib/${TARGET_APPIMAGE_ARCH}-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper" "${TARGET_APPDIR}/usr/bin/gst-ptp-helper"
mv "${TARGET_APPDIR}/usr/lib/${TARGET_APPIMAGE_ARCH}-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner" "${TARGET_APPDIR}/usr/bin/gst-plugin-scanner"
find "${TARGET_APPDIR}/usr/lib/${TARGET_APPIMAGE_ARCH}-linux-gnu" -type f -name 'libwebkit2gtk-4.0.so*' -exec sed -i -e "s|/usr/libexec/gstreamer-1.0|../../././././././usr/bin/|g" '{}' \;
after_runtime: |
set -eux
find "$TARGET_APPDIR" -type d -empty -delete
# Tests don't work due to Tauri security checks we can't work around:
# https://github.com/tauri-apps/tauri/blob/35264b4c1801b381e0b867c1c35540f0fbb43365/core/tauri-utils/src/lib.rs#L202
AppImage:
arch: !ENV '${TARGET_APPIMAGE_ARCH}'
comp: xz
sign-key: None
update-information: None

View file

@ -1,31 +0,0 @@
# AppImage build script and files
This directory contains the script and recipe to build an AppImage from a Spacedrive `.deb` using [appimage-builder](https://appimage-builder.readthedocs.io/en/latest/index.html).
## Instructions (Requires a Linux environment)
- Install one of the following container runtimes:
- [Podman](https://podman.io/docs/installation#installing-on-linux)
- [Docker](https://docs.docker.com/engine/install/#supported-platforms)
- Set up your development environment following the steps in the [CONTRIBUTING](../../CONTRIBUTING.md) guide
- Build a production release of Spacedrive by invoking `pnpm tauri` in a terminal window inside the Spacedrive repository root
> After the build finishes you should end up with a `.deb` archive in `target/release/bundle/deb`
- Change your current work directory to `scripts/appimage`
- Execute the `build_appimage.sh` script inside a `debian:bookworm` container
- Podman: `podman run --rm -v "$(CDPATH='' cd ../.. && pwd -P):/srv" -w /srv debian:bookworm scripts/appimage/build_appimage.sh`
- You may have to run Podman with `podman run --privileged` if you get a permission denied error
- Docker: `docker run --rm -v "$(CDPATH='' cd ../.. && pwd -P):/srv" -w /srv debian:bookworm scripts/appimage/build_appimage.sh`
> If you are running a system with selinux enforcing you will need mount the `/srv` volume with the `Z` flag to avoid `Permission denied` errors. [more info](https://docs.podman.io/en/latest/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options)
> After the script finishes you should end up with an `.AppImage` executable in `target/release/bundle/appimage`

View file

@ -1,92 +0,0 @@
#!/usr/bin/env bash
# Creates an AppImage with the specified Rust package binary and its dependencies.
# AppImage is an universal Linux application distribution format, similar
# to macOS bundles, which packs an application and its dependencies to
# allow running it across a wide variety of distros with no system changes
# and little user hassle.
#
# Relevant documentation:
# https://docs.appimage.org/packaging-guide/index.html
# https://appimage-builder.readthedocs.io/en/latest/index.html
set -eEuo pipefail
if [ "${CI:-}" = "true" ]; then
set -x
fi
_root="$(CDPATH='' cd "$(dirname -- "$0")" && pwd -P)"
readonly _root
# The appimage-builder recipe to use to generate the AppImage.
readonly RECIPE="${_root}/AppImageBuilder.yml"
# The directory where the generated AppImage bundles will be stored.
readonly TARGET_APPIMAGE_DIR="${_root}/../../target/${TARGET:-.}/release/bundle/appimage"
export TARGET_APPIMAGE_DIR
alias wget='wget -nc -nv --show-progress -P "$APPIMAGE_WORKDIR"'
# Create a temporary working directory for this AppImage script
APPIMAGE_WORKDIR=$(mktemp -d -t spacedrive-appimagebuild.XXX)
readonly APPIMAGE_WORKDIR
trap '{ rm -rf "$APPIMAGE_WORKDIR" || true; } && { rm -rf appimage-build AppDir || true; }' EXIT INT TERM
# Install required system dependencies
echo 'Installind required system dependencies...'
apt-get update && apt-get install -yq \
git \
zsync \
dpkg-dev \
apt-utils \
libffi-dev \
squashfs-tools \
libglib2.0-bin \
gstreamer1.0-tools \
libgdk-pixbuf2.0-bin \
gtk-update-icon-cache
if [ "${CI:-}" = "true" ]; then
git config --global --add safe.directory '*'
fi
# gdk-pixbuf-query-loaders is not in PATH by default
ln -fs /usr/lib/x86_64-linux-gnu/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders /usr/local/bin/gdk-pixbuf-query-loaders
export TARGET_APPIMAGE_ARCH="${TARGET_APPIMAGE_ARCH:-$(uname -m)}"
if ! command -v appimage-builder >/dev/null 2>&1; then
apt-get install -yq python3 python3-dev python3-venv
# Set up a virtual environment so that we do not pollute the global Python
# packages list with the packages we need to install
echo 'Setting up temporary Python virtual environment...'
python3 -m venv "$APPIMAGE_WORKDIR/.venv"
. "$APPIMAGE_WORKDIR/.venv/bin/activate"
export MAKEFLAGS="-j$(nproc)"
pip3 install wheel setuptools --upgrade
echo 'Install appimage-build in temporary Python virtual environment...'
pip3 install git+https://github.com/AppImageCrafters/appimage-builder.git@61c8ddde9ef44b85d7444bbe79d80b44a6a5576d
fi
echo 'Running appimage-builder...'
export TARGET_APPIMAGE_APT_ARCH="${TARGET_APPIMAGE_APT_ARCH:-$(dpkg-architecture -q DEB_HOST_ARCH)}"
export TARGET_APPDIR="${APPIMAGE_WORKDIR}/AppDir"
export REPO_DIR="$APPIMAGE_WORKDIR/pkgs"
XDG_DATA_DIRS="$(pwd)/AppDir/usr/share:/usr/share:/usr/local/share:/var/lib/flatpak/exports/share"
export XDG_DATA_DIRS
VERSION="$(CDPATH='' cd "${_root}/../.." && git describe --tags --dirty=-custom --always)"
export VERSION
mkdir -p "$TARGET_APPIMAGE_DIR"
appimage-builder --recipe "$RECIPE" --skip-test
echo "> Moving generated AppImage to $TARGET_APPIMAGE_DIR"
mv -f ./*.AppImage* "$TARGET_APPIMAGE_DIR"