Skip to content

Instantly share code, notes, and snippets.

@chalkers
Created March 25, 2026 06:11
Show Gist options
  • Select an option

  • Save chalkers/3509410db7d381ad1f96c542d65b5a60 to your computer and use it in GitHub Desktop.

Select an option

Save chalkers/3509410db7d381ad1f96c542d65b5a60 to your computer and use it in GitHub Desktop.
Install .deb of xTools Studio on Omarchy
#!/usr/bin/env bash
set -euo pipefail
DEFAULT_INSTALL_PATH="$HOME/Apps/xtools-studio"
CREATE_LAUNCHER=0
FORCE_INSTALL=0
INSTALL_PATH="$DEFAULT_INSTALL_PATH"
DEB_PATH=""
usage() {
cat <<'EOF'
Usage:
install-xtool-studio.sh [options] /path/to/xTool-Studio.deb
Options:
-i, --install-path PATH Install destination (default: ~/Apps/xtools-studio)
-l, --launcher-app Create a launcher script, desktop entry, and ~/.local/bin symlink
-f, --force Replace the install path if it already exists
-h, --help Show this help
Examples:
install-xtool-studio.sh ~/Downloads/xTool-Studio-x64-1.4.10.deb
install-xtool-studio.sh --install-path ~/Apps/xtool-studio --launcher-app ~/Downloads/xTool-Studio-x64-1.4.10.deb
EOF
}
require_cmd() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "Missing required command: $cmd" >&2
exit 1
fi
}
expand_path() {
local raw="$1"
if [[ "$raw" == "~" ]]; then
printf '%s\n' "$HOME"
elif [[ "$raw" == ~/* ]]; then
printf '%s\n' "$HOME/${raw#~/}"
else
printf '%s\n' "$raw"
fi
}
cleanup() {
if [[ -n "${WORK_DIR:-}" && -d "${WORK_DIR:-}" ]]; then
rm -rf "$WORK_DIR"
fi
}
while [[ $# -gt 0 ]]; do
case "$1" in
-i|--install-path)
INSTALL_PATH="${2:-}"
shift 2
;;
-l|--launcher-app)
CREATE_LAUNCHER=1
shift
;;
-f|--force)
FORCE_INSTALL=1
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
break
;;
-*)
echo "Unknown option: $1" >&2
usage >&2
exit 1
;;
*)
if [[ -n "$DEB_PATH" ]]; then
echo "Only one .deb path is supported." >&2
usage >&2
exit 1
fi
DEB_PATH="$1"
shift
;;
esac
done
if [[ $# -gt 0 ]]; then
echo "Unexpected arguments: $*" >&2
usage >&2
exit 1
fi
if [[ -z "$DEB_PATH" ]]; then
echo "Missing .deb path." >&2
usage >&2
exit 1
fi
require_cmd ar
require_cmd tar
require_cmd npx
require_cmd patchelf
require_cmd readelf
DEB_PATH="$(expand_path "$DEB_PATH")"
INSTALL_PATH="$(expand_path "$INSTALL_PATH")"
if [[ ! -f "$DEB_PATH" ]]; then
echo "File not found: $DEB_PATH" >&2
exit 1
fi
if [[ "${DEB_PATH##*.}" != "deb" ]]; then
echo "Expected a .deb file: $DEB_PATH" >&2
exit 1
fi
if [[ -e "$INSTALL_PATH" ]]; then
if [[ "$FORCE_INSTALL" -ne 1 ]]; then
echo "Install path already exists: $INSTALL_PATH" >&2
echo "Use --force to replace it." >&2
exit 1
fi
rm -rf "$INSTALL_PATH"
fi
WORK_DIR="$(mktemp -d)"
trap cleanup EXIT
DEB_EXTRACT_DIR="$WORK_DIR/deb"
ASAR_EXTRACT_DIR="$WORK_DIR/app"
mkdir -p "$DEB_EXTRACT_DIR" "$ASAR_EXTRACT_DIR" "$INSTALL_PATH"
echo "Extracting Debian package..."
(
cd "$DEB_EXTRACT_DIR"
ar x "$DEB_PATH"
)
DATA_ARCHIVE=""
for candidate in \
"$DEB_EXTRACT_DIR/data.tar.zst" \
"$DEB_EXTRACT_DIR/data.tar.xz" \
"$DEB_EXTRACT_DIR/data.tar.gz" \
"$DEB_EXTRACT_DIR/data.tar"; do
if [[ -f "$candidate" ]]; then
DATA_ARCHIVE="$candidate"
break
fi
done
if [[ -z "$DATA_ARCHIVE" ]]; then
echo "Could not find data.tar.* inside $DEB_PATH" >&2
exit 1
fi
case "$DATA_ARCHIVE" in
*.tar.zst) tar --zstd -xf "$DATA_ARCHIVE" -C "$INSTALL_PATH" ;;
*.tar.xz) tar -xJf "$DATA_ARCHIVE" -C "$INSTALL_PATH" ;;
*.tar.gz) tar -xzf "$DATA_ARCHIVE" -C "$INSTALL_PATH" ;;
*.tar) tar -xf "$DATA_ARCHIVE" -C "$INSTALL_PATH" ;;
esac
ASAR_PATH="$INSTALL_PATH/usr/lib/xtool-studio/resources/app.asar"
UNPACKED_NODE_PATH="$INSTALL_PATH/usr/lib/xtool-studio/resources/app.asar.unpacked/node_modules/@xtool/atomm_cpp_module/prebuild/electron/36.2/atomm_cpp_module.node"
EXTRACTED_NODE_PATH="$ASAR_EXTRACT_DIR/node_modules/@xtool/atomm_cpp_module/prebuild/electron/36.2/atomm_cpp_module.node"
if [[ ! -f "$ASAR_PATH" ]]; then
echo "Could not find app.asar at: $ASAR_PATH" >&2
exit 1
fi
if [[ ! -f "$UNPACKED_NODE_PATH" ]]; then
echo "Could not find native addon at: $UNPACKED_NODE_PATH" >&2
exit 1
fi
echo "Extracting app.asar..."
npx -y @electron/asar extract "$ASAR_PATH" "$ASAR_EXTRACT_DIR"
if [[ ! -f "$EXTRACTED_NODE_PATH" ]]; then
echo "Could not find embedded native addon in extracted app.asar." >&2
exit 1
fi
echo "Patching native addon exec-stack flags..."
patchelf --clear-execstack "$UNPACKED_NODE_PATH"
patchelf --clear-execstack "$EXTRACTED_NODE_PATH"
if [[ ! -f "$ASAR_PATH.bak-original" ]]; then
cp "$ASAR_PATH" "$ASAR_PATH.bak-original"
fi
echo "Repacking app.asar..."
npx -y @electron/asar pack "$ASAR_EXTRACT_DIR" "$ASAR_PATH"
echo "Verifying ELF flags..."
readelf -W -l "$UNPACKED_NODE_PATH" | grep 'GNU_STACK' || true
if [[ "$CREATE_LAUNCHER" -eq 1 ]]; then
LAUNCHER_SCRIPT="$INSTALL_PATH/xtool-studio"
LOCAL_BIN_DIR="$HOME/.local/bin"
DESKTOP_DIR="$HOME/.local/share/applications"
DESKTOP_FILE="$DESKTOP_DIR/xtool-studio-local.desktop"
ICON_PATH="$INSTALL_PATH/usr/share/pixmaps/xtool-studio.png"
BINARY_PATH="$INSTALL_PATH/usr/lib/xtool-studio/xTool Studio"
mkdir -p "$LOCAL_BIN_DIR" "$DESKTOP_DIR"
cat >"$LAUNCHER_SCRIPT" <<EOF
#!/usr/bin/env bash
set -euo pipefail
env ELECTRON_OZONE_PLATFORM_HINT=x11 "$BINARY_PATH" --gtk-version=3 --ozone-platform=x11 "\$@"
EOF
chmod +x "$LAUNCHER_SCRIPT"
ln -sfn "$LAUNCHER_SCRIPT" "$LOCAL_BIN_DIR/xtool-studio-local"
cat >"$DESKTOP_FILE" <<EOF
[Desktop Entry]
Type=Application
Version=1.0
Name=xTool Studio (Local)
Comment=xTool Studio patched for Linux loader compatibility
Exec=$LAUNCHER_SCRIPT
Icon=$ICON_PATH
Terminal=false
Categories=Graphics;
StartupNotify=true
EOF
echo "Launcher created:"
echo " Script: $LAUNCHER_SCRIPT"
echo " Symlink: $LOCAL_BIN_DIR/xtool-studio-local"
echo " Desktop entry: $DESKTOP_FILE"
fi
echo
echo "Install complete:"
echo " Deb: $DEB_PATH"
echo " Install path: $INSTALL_PATH"
echo " Binary: $INSTALL_PATH/usr/lib/xtool-studio/xTool Studio"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment