appimage: bump appimagetool to new version/repo
What changed, and why it matters
This commit updates Electrum's Linux AppImage packaging scripts. It swaps an old, discontinued AppImage build tool for a newer one, and builds the AppImage runtime from source instead of downloading a pre-built binary from the internet. The goal is to make builds more reproducible and fix a missing system-library problem on newer Linux distributions. There is no direct evidence in the commit of a security vulnerability being patched; it is primarily a build-hygiene and reliability change.
Review the new appimagetool 1.9.0 hash and the type2-runtime commit hash for correctness. Verify the reproducibility patch does not weaken runtime hardening (e.g., stripping debug info is acceptable, but ensure static linking and security flags remain). Confirm the Docker build environment is trusted and that SOURCE_DATE_EPOCH choice is intentional. No urgent security patch is indicated, but the change improves build reproducibility and supply-chain hygiene.
Security signals we found
Build tool migration from discontinued AppImageKit to actively maintained appimagetool
Eliminates download of unversioned 'latest' type2-runtime binary during AppImage creation
Adds source-build and hash-pinning for runtime to improve supply-chain integrity
Adds reproducibility patch with pinned Docker base image, pinned package versions, and deterministic build flags
No direct code vulnerability or exploit path is visible in the diff
Evidence from the diff
The patch modifies contrib/build-linux/appimage/ build scripts. It replaces AppImageKit’s obsolete appimagetool 13 with AppImage/appimagetool 1.9.0, and adds a build step that clones AppImage/type2-runtime at commit 5e7217b7, applies a reproducibility patch, and builds the runtime inside a Docker container. The new runtime is statically linked and bundled into the AppImage via –runtime-file. The reproducibility patch pins Alpine image hash, apk package versions, SOURCE_DATE_EPOCH, TZ/LC_ALL, disables build-id, and uses path remapping. The commit explicitly states this fixes missing libfuse2 on modern distributions and prevents appimagetool from downloading a random ‘latest’ runtime binary.
Changed components
contrib/build-linux/appimage/Dockerfilecontrib/build-linux/appimage/build.shcontrib/build-linux/appimage/make_appimage.shcontrib/build-linux/appimage/patches/type2-runtime-reproducible-build.patchInspect captured patch +190 / −10
diff --git a/contrib/build-linux/appimage/Dockerfile b/contrib/build-linux/appimage/Dockerfile
index cfedc17..2b5148c 100644
--- a/contrib/build-linux/appimage/Dockerfile
+++ b/contrib/build-linux/appimage/Dockerfile
@@ -67,6 +67,7 @@ RUN apt-get update -q && \
libv4l-dev \
libjpeg62-turbo-dev \
libx11-dev \
+ desktop-file-utils \
&& \
rm -rf /var/lib/apt/lists/* && \
apt-get autoremove -y && \
diff --git a/contrib/build-linux/appimage/build.sh b/contrib/build-linux/appimage/build.sh
index a6f5cab..d986bef 100755
--- a/contrib/build-linux/appimage/build.sh
+++ b/contrib/build-linux/appimage/build.sh
@@ -13,6 +13,10 @@ CONTRIB_APPIMAGE="$CONTRIB/build-linux/appimage"
DISTDIR="$PROJECT_ROOT/dist"
BUILD_UID=$(/usr/bin/stat -c %u "$PROJECT_ROOT")
+# when bumping the runtime commit also check if the `type2-runtime-reproducible-build.patch` still works
+TYPE2_RUNTIME_COMMIT="5e7217b7cfeecee1491c2d251e355c3cf8ba6e4d"
+TYPE2_RUNTIME_REPO="https://github.com/AppImage/type2-runtime.git"
+
. "$CONTRIB"/build_tools_util.sh
@@ -46,6 +50,32 @@ else
info "not doing fresh clone."
fi
+# build the type2-runtime binary, this build step uses a separate docker container
+# defined in the type2-runtime repo (patched with type2-runtime-reproducible-build.patch)
+TYPE2_RUNTIME_REPO_DIR="$PROJECT_ROOT_OR_FRESHCLONE_ROOT/contrib/build-linux/appimage/.cache/appimage/type2-runtime"
+(
+ if [ -f "$TYPE2_RUNTIME_REPO_DIR/runtime-x86_64" ]; then
+ info "type2-runtime already built, skipping"
+ exit 0
+ fi
+ clone_or_update_repo "$TYPE2_RUNTIME_REPO" "$TYPE2_RUNTIME_COMMIT" "$TYPE2_RUNTIME_REPO_DIR"
+
+ # Apply patch to make runtime build reproducible
+ info "Applying type2-runtime patch..."
+ cd "$TYPE2_RUNTIME_REPO_DIR"
+ git apply "$CONTRIB_APPIMAGE/patches/type2-runtime-reproducible-build.patch" || fail "Failed to apply runtime repo patch"
+
+ info "building type2-runtime in build container..."
+ cd "$TYPE2_RUNTIME_REPO_DIR/scripts/docker"
+ env ARCH=x86_64 ./build-with-docker.sh
+ mv "./runtime-x86_64" "$TYPE2_RUNTIME_REPO_DIR/"
+
+ # clean up the empty created 'out' dir to prevent permission issues
+ rm -rf "$TYPE2_RUNTIME_REPO_DIR/out"
+
+ info "runtime build successful: $(sha256sum "$TYPE2_RUNTIME_REPO_DIR/runtime-x86_64")"
+) || fail "Failed to build type2-runtime"
+
info "building binary..."
# check uid and maybe chown. see #8261
if [ ! -z "$ELECBUILD_COMMIT" ] ; then # fresh clone (reproducible build)
diff --git a/contrib/build-linux/appimage/make_appimage.sh b/contrib/build-linux/appimage/make_appimage.sh
index 43b8ba2..6370391 100755
--- a/contrib/build-linux/appimage/make_appimage.sh
+++ b/contrib/build-linux/appimage/make_appimage.sh
@@ -9,6 +9,7 @@ DISTDIR="$PROJECT_ROOT/dist"
BUILDDIR="$CONTRIB_APPIMAGE/build/appimage"
APPDIR="$BUILDDIR/electrum.AppDir"
CACHEDIR="$CONTRIB_APPIMAGE/.cache/appimage"
+TYPE2_RUNTIME_REPO_DIR="$CACHEDIR/type2-runtime"
export DLL_TARGET_DIR="$CACHEDIR/dlls"
PIP_CACHE_DIR="$CONTRIB_APPIMAGE/.cache/pip_cache"
@@ -37,11 +38,10 @@ info "downloading some dependencies."
download_if_not_exist "$CACHEDIR/functions.sh" "https://raw.githubusercontent.com/AppImage/pkg2appimage/$PKG2APPIMAGE_COMMIT/functions.sh"
verify_hash "$CACHEDIR/functions.sh" "8f67711a28635b07ce539a9b083b8c12d5488c00003d6d726c7b134e553220ed"
-download_if_not_exist "$CACHEDIR/appimagetool" "https://github.com/AppImage/AppImageKit/releases/download/13/obsolete-appimagetool-x86_64.AppImage"
-verify_hash "$CACHEDIR/appimagetool" "df3baf5ca5facbecfc2f3fa6713c29ab9cefa8fd8c1eac5d283b79cab33e4acb"
-# TODO migrate to https://github.com/AppImage/appimagetool/releases
-# note: we will also have to build AppImage/type2-runtime, as otherwise new appimagetool just downloads "latest" from the internet.
-# see https://github.com/AppImage/appimagetool/blob/7cfafc45e5a8e64ad0755870b1001e5d7ffb4e85/README.md#L61
+download_if_not_exist "$CACHEDIR/appimagetool" "https://github.com/AppImage/appimagetool/releases/download/1.9.0/appimagetool-x86_64.AppImage"
+verify_hash "$CACHEDIR/appimagetool" "46fdd785094c7f6e545b61afcfb0f3d98d8eab243f644b4b17698c01d06083d1"
+# note: desktop-file-utils in the docker image is needed to run desktop-file-validate for appimagetool <= 1.9.0, so it can be removed once
+# appimagetool tags a new release (see https://github.com/AppImage/appimagetool/pull/47)
download_if_not_exist "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" "https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tar.xz"
verify_hash "$CACHEDIR/Python-$PYTHON_VERSION.tar.xz" "c30bb24b7f1e9a19b11b55a546434f74e739bb4c271a3e3a80ff4380d49f7adb"
@@ -264,14 +264,14 @@ info "creating the AppImage."
"$CACHEDIR/appimagetool_copy" --appimage-extract
# We build a small wrapper for mksquashfs that removes the -mkfs-time option
# as it conflicts with SOURCE_DATE_EPOCH.
- mv "$BUILDDIR/squashfs-root/usr/lib/appimagekit/mksquashfs" "$BUILDDIR/squashfs-root/usr/lib/appimagekit/mksquashfs_orig"
- cat > "$BUILDDIR/squashfs-root/usr/lib/appimagekit/mksquashfs" << EOF
+ mv "$BUILDDIR/squashfs-root/usr/bin/mksquashfs" "$BUILDDIR/squashfs-root/usr/bin/mksquashfs_orig"
+ cat > "$BUILDDIR/squashfs-root/usr/bin/mksquashfs" << EOF
#!/bin/sh
args=\$(echo "\$@" | sed -e 's/-mkfs-time 0//')
-"$BUILDDIR/squashfs-root/usr/lib/appimagekit/mksquashfs_orig" \$args
+"$BUILDDIR/squashfs-root/usr/bin/mksquashfs_orig" \$args
EOF
- chmod +x "$BUILDDIR/squashfs-root/usr/lib/appimagekit/mksquashfs"
- env VERSION="$VERSION" ARCH=x86_64 ./squashfs-root/AppRun --no-appstream --verbose "$APPDIR" "$APPIMAGE"
+ chmod +x "$BUILDDIR/squashfs-root/usr/bin/mksquashfs"
+ env VERSION="$VERSION" ARCH=x86_64 ./squashfs-root/AppRun --runtime-file "$TYPE2_RUNTIME_REPO_DIR/runtime-x86_64" --no-appstream --verbose "$APPDIR" "$APPIMAGE"
)
diff --git a/contrib/build-linux/appimage/patches/type2-runtime-reproducible-build.patch b/contrib/build-linux/appimage/patches/type2-runtime-reproducible-build.patch
new file mode 100644
index 0000000..0895d2a
--- /dev/null
+++ b/contrib/build-linux/appimage/patches/type2-runtime-reproducible-build.patch
@@ -0,0 +1,149 @@
+From 0c54d91dd1d33235ae97566600e692edfb613642 Mon Sep 17 00:00:00 2001
+From: f321x <f@f321x.com>
+Date: Thu, 10 Jul 2025 17:45:20 +0200
+Subject: [PATCH] make docker build reproducible
+
+attempts to make the docker build more reproducible by:
+* pinning the docker image (alpine:3.21) to a hash
+* version pinning the apk packages in the dockerfile
+* setting TZ, LC_ALL and SOURCE_DATE_EPOCH in the container
+* only building single threaded (make -j1)
+* use a fixed build directory in `build-runtime.sh` instead of mktemp
+* prevent linker from adding build id (-Wl,--build-id=none)
+* replace absolute build paths in debug info with relative paths
+ (-fdebug-prefix-map=$(PWD)=.)
+* replace absolute paths in all compiler output with relative paths
+ (-ffile-prefix-map=$(PWD)=.)
+* stop adding gnu-debuglink to runtime binary
+---
+ scripts/build-runtime.sh | 18 +++++++++++----
+ scripts/common/install-dependencies.sh | 2 +-
+ scripts/docker/Dockerfile | 32 ++++++++++++++++++++++----
+ src/runtime/Makefile | 2 +-
+ 4 files changed, 42 insertions(+), 12 deletions(-)
+
+diff --git a/scripts/build-runtime.sh b/scripts/build-runtime.sh
+index 3ce3b91..e11f082 100755
+--- a/scripts/build-runtime.sh
++++ b/scripts/build-runtime.sh
+@@ -8,8 +8,10 @@ set -euo pipefail
+ out_dir="$(readlink -f "$(pwd)")"/out
+ mkdir -p "$out_dir"
+
+-# we create a temporary build directory
+-build_dir="$(mktemp -d -t type2-runtime-build-XXXXXX)"
++# we create a temporary build directory with a fixed name for reproducibility
++build_dir="$(readlink -f "$(pwd)")"/build-runtime-temp
++rm -rf "$build_dir"
++mkdir -p "$build_dir"
+
+ # since the plain ol' Makefile doesn't support out-of-source builds at all, we need to copy all the files
+ cp -R src "$build_dir"/
+@@ -17,13 +19,14 @@ cp -R src "$build_dir"/
+ pushd "$build_dir"
+
+ pushd src/runtime/
+-make -j"$(nproc)" runtime
++make -j1 runtime
+
+ file runtime
+
+ objcopy --only-keep-debug runtime runtime.debug
+
+-strip --strip-debug --strip-unneeded runtime
++# strip --strip-debug --strip-unneeded runtime
++strip --strip-all runtime
+
+ ls -lh runtime runtime.debug
+
+@@ -50,7 +53,7 @@ fi
+ mv runtime runtime-"$architecture"
+ mv runtime.debug runtime-"$architecture".debug
+
+-objcopy --add-gnu-debuglink runtime-"$architecture".debug runtime-"$architecture"
++# objcopy --add-gnu-debuglink runtime-"$architecture".debug runtime-"$architecture"
+
+ # "classic" magic bytes which cannot be embedded with compiler magic, always do AFTER strip
+ # needs to be done after calls to objcopy, strip etc.
+@@ -61,3 +64,8 @@ cp runtime-"$architecture" "$out_dir"/
+ cp runtime-"$architecture".debug "$out_dir"/
+
+ ls -al "$out_dir"
++
++# cleanup
++popd # return to build_dir
++popd # return to original working directory
++rm -rf "$build_dir"
+diff --git a/scripts/common/install-dependencies.sh b/scripts/common/install-dependencies.sh
+index 0e21cdb..5237079 100755
+--- a/scripts/common/install-dependencies.sh
++++ b/scripts/common/install-dependencies.sh
+@@ -39,7 +39,7 @@ tar xf 0.5.2.tar.gz
+ pushd squashfuse-*/
+ ./autogen.sh
+ ./configure LDFLAGS="-static"
+-make -j"$(nproc)"
++make -j1
+ make install
+ /usr/bin/install -c -m 644 ./*.h '/usr/local/include/squashfuse'
+ popd
+diff --git a/scripts/docker/Dockerfile b/scripts/docker/Dockerfile
+index 07b6533..fba9c6e 100644
+--- a/scripts/docker/Dockerfile
++++ b/scripts/docker/Dockerfile
+@@ -1,13 +1,35 @@
+-FROM alpine:3.21
++FROM alpine:3.21@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c
+
+ # includes dependencies from https://git.alpinelinux.org/aports/tree/main/fuse3/APKBUILD
+ RUN apk add --no-cache \
+- bash alpine-sdk util-linux strace file autoconf automake libtool xz \
+- eudev-dev gettext-dev linux-headers meson \
+- zstd-dev zstd-static zlib-dev zlib-static clang musl-dev mimalloc-dev
++ bash=5.2.37-r0 \
++ alpine-sdk=1.1-r0 \
++ util-linux=2.40.4-r1 \
++ strace=6.12-r0 \
++ file=5.46-r2 \
++ autoconf=2.72-r0 \
++ automake=1.17-r0 \
++ libtool=2.4.7-r3 \
++ xz=5.6.3-r1 \
++ eudev-dev=3.2.14-r5 \
++ gettext-dev=0.22.5-r0 \
++ linux-headers=6.6-r1 \
++ meson=1.6.1-r0 \
++ zstd-dev=1.5.6-r2 \
++ zstd-static=1.5.6-r2 \
++ zlib-dev=1.3.1-r2 \
++ zlib-static=1.3.1-r2 \
++ clang19=19.1.4-r0 \
++ musl-dev=1.2.5-r9 \
++ mimalloc2-dev=2.1.7-r0
+
+ COPY scripts/common/install-dependencies.sh /tmp/scripts/common/install-dependencies.sh
+ COPY patches/ /tmp/patches/
+
++# Set environment variables for reproducible build
++ENV SOURCE_DATE_EPOCH=1640995200
++ENV TZ=UTC
++ENV LC_ALL=C
++
+ WORKDIR /tmp
+-RUN bash scripts/common/install-dependencies.sh
++RUN bash scripts/common/install-dependencies.sh
+\ No newline at end of file
+diff --git a/src/runtime/Makefile b/src/runtime/Makefile
+index 9fd4165..3a3cbaa 100644
+--- a/src/runtime/Makefile
++++ b/src/runtime/Makefile
+@@ -1,6 +1,6 @@
+ GIT_COMMIT := $(shell cat version)
+ CC = clang
+-CFLAGS = -std=gnu99 -Os -D_FILE_OFFSET_BITS=64 -DGIT_COMMIT=\"$(GIT_COMMIT)\" -T data_sections.ld -ffunction-sections -fdata-sections -Wl,--gc-sections -static -Wall -Werror -static-pie
++CFLAGS = -std=gnu99 -Os -D_FILE_OFFSET_BITS=64 -DGIT_COMMIT=\"$(GIT_COMMIT)\" -T data_sections.ld -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--build-id=none -static -Wall -Werror -static-pie -fdebug-prefix-map=$(PWD)=. -ffile-prefix-map=$(PWD)=.
+ LIBS = -lsquashfuse -lsquashfuse_ll -lzstd -lz -lfuse3 -lmimalloc
+
+ all: runtime
+--
+2.50.0
Why this scored 29/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.