Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/appimage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:

# Builds the AppImage builder image with GHA layer cache. First run on
# a clean cache is slow (~30+ min — boost + libssh + fmt + nlohmann +
# llvm-21 + cmake all installed/built). Subsequent runs reuse layers.
# llvm-22 + cmake all installed/built). Subsequent runs reuse layers.
- name: Build builder image
uses: docker/build-push-action@v6
with:
Expand Down
6 changes: 3 additions & 3 deletions PKGBUILD
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Maintainer: Tim Ebbeke <tim 06 tr (at) gmail dot com>

pkgname=nui-sftp
pkgver=1.3.1
pkgver=1.4.0
pkgrel=1
pkgdesc="NUI-based SFTP application"
arch=('x86_64')
Expand Down Expand Up @@ -39,9 +39,9 @@ source=(
"https://s3.g.s4.mega.io/jgemkib4a5fte35rktt5wxrwkw4ejk4ybemkf/nui-scp/icons.tar.gz"
)
sha256sums=(
'a46c1c3ad1551a20005b0a3119d8ff607af046bc0927d7a041a78b558a911417'
'71f7d4cec28c57790e0a65acd627bb1c1d65a61922d387d954b80d15ed83b9e1'
'cf0fc442069dfd28bd8b7769da6dfdca92111044ea9817197716ed9bfada3869'
'a8ac5edaac2a1b4ce21d48c90f0c3502572b5f3ec1ed225aa42f5c97c10d1caf'
'84777ed08bd9b689e78a2d855d714dee25620f6fbb97edffd8d45d12bede9294'
'30ffa48c3a509e878db31a1e5d80376242852e34d9c2aa3b44d2e3d1da2ce32e'
)

Expand Down
22 changes: 21 additions & 1 deletion appimage/AppRun
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,29 @@ HERE="$(dirname "$(readlink -f "${0}")")"
export APPDIR="${HERE}"
export PATH="${HERE}/usr/bin:${PATH}"
export LD_LIBRARY_PATH="${HERE}/usr/lib:${HERE}/usr/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH:-}"
# Prepends a bundled compat lib dir to LD_LIBRARY_PATH when the host's copy
# of the library exports an older highest symbol version than the bundled one.
# Using the bundled copy on a newer host would break host libs (e.g. GL
# drivers) that need the newer symbols.
useBundledIfNewer() {
local soname="$1" versionPrefix="$2" bundledDir="$3"
local hostLib bundledVersion hostVersion
hostLib=$(/sbin/ldconfig -p 2>/dev/null | grep -F "${soname} (libc6,x86-64)" | head -n1 | sed 's/.*=> //')
bundledVersion=$(grep -aoE "${versionPrefix}[0-9.]+" "${bundledDir}/${soname}" | sort -uV | tail -n1)
if [ -n "${hostLib}" ] && [ -f "${hostLib}" ]; then
hostVersion=$(grep -aoE "${versionPrefix}[0-9.]+" "${hostLib}" | sort -uV | tail -n1)
if [ "$(printf '%s\n%s\n' "${hostVersion}" "${bundledVersion}" | sort -V | tail -n1)" = "${hostVersion}" ]; then
return
fi
fi
export LD_LIBRARY_PATH="${bundledDir}:${LD_LIBRARY_PATH}"
}
useBundledIfNewer libstdc++.so.6 GLIBCXX_ "${HERE}/usr/optional/libstdc++"
useBundledIfNewer libgcc_s.so.1 GCC_ "${HERE}/usr/optional/libgcc_s"

export XDG_DATA_DIRS="${HERE}/usr/share:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"

# webkitgtk-6.0 has no env override for the helper exec path, intercept
# WebKitGTK release builds have no env override for the helper exec path, intercept
# posix_spawn/execve via LD_PRELOAD so spawning WebKitNetworkProcess et al.
# resolves to the bundled copy. The shim reads $APPDIR (set above).
export LD_PRELOAD="${HERE}/usr/lib/appimage-shims/webkit_path_shim.so${LD_PRELOAD:+:${LD_PRELOAD}}"
Expand Down
28 changes: 17 additions & 11 deletions appimage/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
FROM ubuntu:24.04
# Oldest still-supported Ubuntu LTS. The appimage.github.io catalog test runs
# the AppImage on it, so nothing bundled may need a newer glibc than 2.35.
FROM ubuntu:22.04

ARG DEBIAN_FRONTEND=noninteractive
ARG LLVM_VERSION=21
ARG LLVM_VERSION=22
ARG BOOST_VERSION=1.90.0
ARG LIBSSH_TAG=libssh-0.11.4
ARG FMT_TAG=12.1.0
Expand All @@ -14,11 +16,13 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# Base toolchain + system deps that AREN'T built from source.
# Versions matching the flatpak manifest (boost/libssh/fmt/nlohmann-json/interval-tree)
# are built from source below. Other deps (webkitgtk-6.0, sdbus-c++, crypto++,
# curl, compression libs) come from Ubuntu packages.
# are built from source below. Other deps (webkit2gtk-4.1, sdbus-c++, crypto++,
# curl, compression libs) come from Ubuntu packages. Jammy has no webkitgtk-6.0,
# so the AppImage uses the GTK3 / webkit2gtk-4.1 code paths.
# libstdc++-15-dev is needed because libstdc++-14's std::ranges::to has a
# malformed forward_like that clang correctly rejects (gcc accepts it). The
# toolchain PPA provides gcc-15 / libstdc++-15 on Ubuntu 24.04.
# toolchain PPA provides gcc-15 / libstdc++-15 on Ubuntu 22.04. The resulting
# GLIBCXX requirement is covered by bundling libstdc++ (see AppRun).
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates gnupg software-properties-common \
&& add-apt-repository -y ppa:ubuntu-toolchain-r/test \
Expand All @@ -28,16 +32,16 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
dpkg-dev patchelf squashfs-tools \
libstdc++-15-dev \
desktop-file-utils appstream \
libwebkitgtk-6.0-dev \
libwebkit2gtk-4.1-dev \
libcurl4-openssl-dev \
libcrypto++-dev \
libsdbus-c++-dev \
libsdbus-c++-dev libsystemd-dev \
libopenmpi-dev \
zlib1g-dev libbz2-dev libzstd-dev liblzma-dev \
libgtk-4-dev \
libgtk-3-dev \
&& rm -rf /var/lib/apt/lists/*

# Clang from apt.llvm.org (matches flatpak's llvm21 SDK extension).
# Clang from apt.llvm.org (matches flatpak's llvm22 SDK extension).
RUN curl -fsSL https://apt.llvm.org/llvm.sh -o /tmp/llvm.sh \
&& chmod +x /tmp/llvm.sh \
&& /tmp/llvm.sh ${LLVM_VERSION} all \
Expand Down Expand Up @@ -99,7 +103,8 @@ RUN cd /tmp && git clone --depth 1 --branch "${INTERVAL_TREE_TAG}" https://githu
&& rm -rf /tmp/interval-tree

# linuxdeploy + gtk plugin + appimagetool. Extracted at build time so we can run
# without FUSE inside docker.
# without FUSE inside docker. Newer runtimes extract with 0700 directories, so
# open them up for the non-root user CI runs the container as.
RUN mkdir -p /opt/appimage-tools && cd /opt/appimage-tools \
&& wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage \
&& wget -q https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh \
Expand All @@ -114,7 +119,8 @@ RUN mkdir -p /opt/appimage-tools && cd /opt/appimage-tools \
&& ln -sf /opt/appimage-tools/appimagetool.AppDir/AppRun /usr/local/bin/appimagetool \
&& rm /opt/appimage-tools/linuxdeploy-x86_64.AppImage /opt/appimage-tools/appimagetool-x86_64.AppImage \
&& wget -q -O /opt/appimage-tools/runtime-x86_64 \
https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-x86_64
https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-x86_64 \
&& chmod -R a+rX /opt/appimage-tools

# linuxdeploy uses appstreamcli to validate; appstream package above provides it.
ENV APPIMAGE_EXTRACT_AND_RUN=1
Expand Down
48 changes: 37 additions & 11 deletions appimage/build_inner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ sed -i "s/Icon=${PKGNAME//-/_}/Icon=${APP_ID}/g" "${APPDIR}/usr/share/applicatio
install -Dm644 "${WORKSPACE}/${APP_ID}.metainfo.xml" \
"${APPDIR}/usr/share/metainfo/${APP_ID}.metainfo.xml"

# Bundle WebKit's helper processes + injected-bundle. On Ubuntu 24.04 these
# live under the libdir (/usr/lib/x86_64-linux-gnu/webkitgtk-6.0/) and
# Bundle WebKit's helper processes + injected-bundle. On Ubuntu these live
# under the libdir (/usr/lib/x86_64-linux-gnu/webkit2gtk-4.1/) and
# linuxdeploy-plugin-gtk doesn't copy them. The host layout is mirrored so
# the LD_PRELOAD shim (compiled below) can rewrite the hardcoded path that
# libwebkitgtk-6.0 uses to spawn WebKitNetworkProcess et al.
WEBKIT_EXEC_SRC="/usr/lib/x86_64-linux-gnu/webkitgtk-6.0"
WEBKIT_EXEC_DST="${APPDIR}/usr/lib/x86_64-linux-gnu/webkitgtk-6.0"
# libwebkit2gtk uses to spawn WebKitNetworkProcess et al.
WEBKIT_EXEC_SRC="/usr/lib/x86_64-linux-gnu/webkit2gtk-4.1"
WEBKIT_EXEC_DST="${APPDIR}${WEBKIT_EXEC_SRC}"
if [ -d "${WEBKIT_EXEC_SRC}" ]; then
mkdir -p "${WEBKIT_EXEC_DST}"
cp -a "${WEBKIT_EXEC_SRC}/." "${WEBKIT_EXEC_DST}/"
Expand All @@ -97,12 +97,12 @@ else
exit 1
fi

# webkitgtk-6.0 hardcodes /usr/lib/x86_64-linux-gnu/webkitgtk-6.0 for helper
# spawn (the WEBKIT_EXEC_PATH env override was removed). Compile a tiny
# LD_PRELOAD shim that intercepts posix_spawn/execve and rewrites that prefix
# to $APPDIR/usr/lib/x86_64-linux-gnu/webkitgtk-6.0.
# WebKitGTK release builds hardcode the helper path above for spawning and
# ignore WEBKIT_EXEC_PATH. Compile a tiny LD_PRELOAD shim that intercepts
# posix_spawn/execve/dlopen and rewrites that prefix into $APPDIR.
mkdir -p "${APPDIR}/usr/lib/appimage-shims"
clang -O2 -shared -fPIC -Wl,--no-as-needed \
-DWEBKIT_HOST_PREFIX="\"${WEBKIT_EXEC_SRC}\"" \
"${WORKSPACE}/appimage/webkit_path_shim.c" \
-o "${APPDIR}/usr/lib/appimage-shims/webkit_path_shim.so" \
-ldl
Expand All @@ -119,7 +119,7 @@ APPRUN_SRC="${WORKSPACE}/appimage/AppRun"
# produce the AppImage here; we run appimagetool ourselves below to control
# compression.
cd "${BUILD_DIR}"
DEPLOY_GTK_VERSION=4 \
DEPLOY_GTK_VERSION=3 \
linuxdeploy \
--appdir "${APPDIR}" \
--executable "${APPDIR}/opt/${PKGNAME}/bin/${PKGNAME}" \
Expand All @@ -128,13 +128,39 @@ linuxdeploy \
--custom-apprun "${APPRUN_SRC}" \
--plugin gtk

# libstdc++ / libgcc_s come from the gcc-15 toolchain PPA and are newer than
# what the target systems ship. linuxdeploy's excludelist skips them, so they
# are bundled outside usr/lib and AppRun only puts them on the library path
# when the host copy is older.
COMPAT_DIR="${APPDIR}/usr/optional"
mkdir -p "${COMPAT_DIR}/libstdc++" "${COMPAT_DIR}/libgcc_s"
cp -L /usr/lib/x86_64-linux-gnu/libstdc++.so.6 "${COMPAT_DIR}/libstdc++/"
cp -L /lib/x86_64-linux-gnu/libgcc_s.so.1 "${COMPAT_DIR}/libgcc_s/"

# Strip every ELF in the AppDir before compression. linuxdeploy doesn't strip
# helper processes or webkit's massive .so set; stripping shaves ~30-40 MB
# before squashfs compression even runs.
find "${APPDIR}" -type f \( -name '*.so' -o -name '*.so.*' \) -exec strip --strip-unneeded {} + 2>/dev/null || true
find "${APPDIR}/opt/${PKGNAME}/bin" "${APPDIR}/usr/lib/x86_64-linux-gnu/webkitgtk-6.0" \
find "${APPDIR}/opt/${PKGNAME}/bin" "${WEBKIT_EXEC_DST}" \
-type f -executable -exec strip --strip-unneeded {} + 2>/dev/null || true

# Fail early instead of letting the appimage.github.io catalog test find it:
# no bundled ELF may require a glibc newer than the build host's.
MAX_GLIBC=$(ldd --version | head -n1 | grep -oE '[0-9]+\.[0-9]+$')
GLIBC_VIOLATIONS=0
while IFS= read -r -d '' elf; do
file -b "${elf}" | grep -q '^ELF' || continue
required=$(objdump -T "${elf}" 2>/dev/null | grep -oE 'GLIBC_[0-9]+\.[0-9]+' | sed 's/GLIBC_//' | sort -uV | tail -n1)
[ -n "${required}" ] || continue
if [ "$(printf '%s\n%s\n' "${required}" "${MAX_GLIBC}" | sort -V | tail -n1)" != "${MAX_GLIBC}" ]; then
echo "ERROR: ${elf#"${APPDIR}"/} requires GLIBC_${required} (> ${MAX_GLIBC})" >&2
GLIBC_VIOLATIONS=$((GLIBC_VIOLATIONS + 1))
fi
done < <(find "${APPDIR}" -type f -print0)
if [ "${GLIBC_VIOLATIONS}" -ne 0 ]; then
exit 1
fi

# Produce the AppImage manually: mksquashfs concatenated with the type-2
# AppImage runtime. We bypass appimagetool because its bundled mksquashfs is
# limited; system squashfs-tools lets us tune block size and compression
Expand Down
25 changes: 15 additions & 10 deletions appimage/webkit_path_shim.c
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
/* webkit_path_shim — LD_PRELOAD intercept that rewrites webkitgtk-6.0's
* hardcoded path (/usr/lib/x86_64-linux-gnu/webkitgtk-6.0) to the bundled
* location inside the AppImage ($APPDIR/usr/lib/...).
/* webkit_path_shim — LD_PRELOAD intercept that rewrites WebKitGTK's
* hardcoded helper path (WEBKIT_HOST_PREFIX, e.g.
* /usr/lib/x86_64-linux-gnu/webkit2gtk-4.1) to the bundled location inside
* the AppImage ($APPDIR/usr/lib/...).
*
* webkitgtk-6.0 removed the WEBKIT_EXEC_PATH env override, so the only way
* WebKitGTK release builds ignore the WEBKIT_EXEC_PATH env override, so the only way
* to relocate its sibling files in an AppImage is to intercept the relevant
* libc calls:
* - posix_spawn / execve : GSubprocess uses these to launch helper processes
* (WebKitNetworkProcess, WebKitWebProcess, ...).
* - dlopen / dlmopen : the injected bundle .so is dlopen'd by absolute
* path from libwebkitgtk-6.0.
* path from libwebkit2gtk.
*/

#define _GNU_SOURCE
Expand All @@ -20,10 +21,14 @@
#include <string.h>
#include <unistd.h>

static const char HOST_PFX[] = "/usr/lib/x86_64-linux-gnu/webkitgtk-6.0";
#ifndef WEBKIT_HOST_PREFIX
#define WEBKIT_HOST_PREFIX "/usr/lib/x86_64-linux-gnu/webkit2gtk-4.1"
#endif

static const char HOST_PFX[] = WEBKIT_HOST_PREFIX;

/**
* @brief Rewrite a host webkitgtk-6.0 helper path into the AppImage-relative path.
* @brief Rewrite a host WebKitGTK helper path into the AppImage-relative path.
*
* If @p path begins with HOST_PFX and is either exactly that directory or a child
* of it (i.e. the next character is '\0' or '/'), the AppImage prefix from
Expand Down Expand Up @@ -76,7 +81,7 @@ typedef int (*posix_spawn_fn)(pid_t *, const char *,
*
* @param pid Out-parameter for the spawned child's PID. Forwarded unchanged.
* @param path Absolute path to the executable. Rewritten via ::remap if it points
* at the host webkitgtk-6.0 helper directory.
* at the host WebKitGTK helper directory.
* @param fa File actions. Forwarded unchanged.
* @param attr Spawn attributes. Forwarded unchanged.
* @param argv Argument vector. Forwarded unchanged.
Expand Down Expand Up @@ -178,8 +183,8 @@ typedef void *(*dlopen_fn)(const char *, int);
/**
* @brief LD_PRELOAD interposer for @c dlopen that rewrites webkitgtk paths.
*
* libwebkitgtk-6.0 dlopens its injected bundle .so from the hardcoded
* /usr/lib/x86_64-linux-gnu/webkitgtk-6.0/injected-bundle/... path. This
* libwebkit2gtk dlopens its injected bundle .so from the hardcoded
* WEBKIT_HOST_PREFIX/injected-bundle/... path. This
* interposer reroutes that load to the bundled copy inside the AppImage.
*
* @param filename Library name or absolute path. Rewritten via ::remap on a
Expand Down
11 changes: 6 additions & 5 deletions org.nuicpp.nui_sftp.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
</screenshot>
</screenshots>
<releases>
<release version="1.4.0" date="2026-10-01">
<description>
<p>Bulk Uploads and Downloads now can have concurrency (moving multiple files at the same time).
The setting can be found in the SFTP settings and the default is 2.</p>
</description>
</release>
<release version="1.3.1" date="2026-06-03">
<description>
<p>What&apos;s Changed</p>
Expand Down Expand Up @@ -410,11 +416,6 @@ Also improved the buttons in the toolbar and gave the &quot;add server&quot; but
<p>The CMake version script no longer fails on -beta or +build suffixes.</p>
</description>
</release>
<release version="0.0.1-beta" date="2026-03-02">
<description>
<p>Initial feature onset.</p>
</description>
</release>
</releases>
<content_rating type="oars-1.1"></content_rating>
</component>
16 changes: 8 additions & 8 deletions org.nuicpp.nui_sftp.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
id: org.nuicpp.nui_sftp
runtime: org.gnome.Platform
runtime-version: '50'
runtime-version: '51'
sdk: org.gnome.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.llvm21
- org.freedesktop.Sdk.Extension.llvm22
- org.freedesktop.Sdk.Extension.node24
command: nui-sftp

Expand Down Expand Up @@ -70,8 +70,8 @@ modules:
- cp -r * /app/frontend
sources:
- type: archive
url: https://github.com/5cript/nui-sftp/releases/download/v1.3.1/nui-sftp-linux-frontend_1.3.1.tar.gz
sha256: a8ac5edaac2a1b4ce21d48c90f0c3502572b5f3ec1ed225aa42f5c97c10d1caf
url: https://github.com/5cript/nui-sftp/releases/download/v1.4.0/nui-sftp-linux-frontend_1.4.0.tar.gz
sha256: 84777ed08bd9b689e78a2d855d714dee25620f6fbb97edffd8d45d12bede9294

- name: nui-sftp
buildsystem: cmake-ninja
Expand All @@ -84,8 +84,8 @@ modules:
LD: lld
INSTALL_TARGET: /app
Boost_ROOT: /app
append-path: "/usr/lib/sdk/llvm21/bin:/usr/lib/sdk/node24/bin"
prepend-ld-library-path: "/usr/lib/sdk/llvm21/lib"
append-path: "/usr/lib/sdk/llvm22/bin:/usr/lib/sdk/node24/bin"
prepend-ld-library-path: "/usr/lib/sdk/llvm22/lib"
config-opts:
- -DCMAKE_BUILD_TYPE=Release
- -DCMAKE_C_COMPILER=clang
Expand Down Expand Up @@ -116,8 +116,8 @@ modules:
sources:
- type: git
url: https://github.com/5cript/nui-sftp
tag: v1.3.1
commit: 3f9419a53aa612dc97abef755ede0308fba5e64a
tag: v1.4.0
commit: 92996b0dfb5f4713e7d5c6ef7460044958f9c22e
- type: archive
url: https://s3.g.s4.mega.io/jgemkib4a5fte35rktt5wxrwkw4ejk4ybemkf/nui-scp/icons.tar.gz
sha256: 30ffa48c3a509e878db31a1e5d80376242852e34d9c2aa3b44d2e3d1da2ce32e
Expand Down
24 changes: 24 additions & 0 deletions scripts/build_appimage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash

set -e
set -u

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
DOCKER="${DOCKER:-docker}"

if ! "${DOCKER}" info > /dev/null 2>&1; then
echo "Docker daemon is not running, starting it..."
sudo systemctl start docker

for _ in $(seq 1 30); do
"${DOCKER}" info > /dev/null 2>&1 && break
sleep 1
done

if ! "${DOCKER}" info > /dev/null 2>&1; then
echo "Error: docker daemon did not become ready" >&2
exit 1
fi
fi

exec "${SCRIPT_DIR}/../appimage/build.sh" "$@"
2 changes: 1 addition & 1 deletion windows/nui-sftp.iss
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
; `prepare_release.mjs --version X.Y.Z` invocation. Do not edit by hand; bump
; via prepare_release.mjs so it stays in lockstep with PKGBUILD/flatpak/appimage.
#ifndef MyAppVersion
#define MyAppVersion "1.3.1"
#define MyAppVersion "1.4.0"
#endif
#ifndef SourceDir
#define SourceDir "..\..\nui-sftp\build\install"
Expand Down
Loading