diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
| commit | 7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch) | |
| tree | 27fb39be241fdb5ac2734c574de678977d1856d0 /docker/AppRun.sh | |
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem,
Proton/umu-run integration, and Flatpak packaging.
Key features:
- FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak)
- Proton/GE-Proton/umu-run launcher with env var forwarding
- Flatpak support (sandbox-aware VFS, NXM handler, umu-run)
- Wine prefix management UI
- Case-insensitive path resolution for Linux filesystems
- QSettings-safe INI handling (avoids Bethesda INI corruption)
- Portable instance support with auto-generated launcher scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'docker/AppRun.sh')
| -rw-r--r-- | docker/AppRun.sh | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/docker/AppRun.sh b/docker/AppRun.sh new file mode 100644 index 0000000..f0ae807 --- /dev/null +++ b/docker/AppRun.sh @@ -0,0 +1,125 @@ +#!/bin/bash +HERE="$(dirname "$(readlink -f "$0")")" +APPIMAGE_DIR="$(dirname "$(readlink -f "${APPIMAGE:-$0}")")" + +cleanup_stale_fuse_mounts() { + local root="$1" + [ -n "${root}" ] || return 0 + [ -r /proc/mounts ] || return 0 + + local src mp fstype rest mount_path stat_out + while IFS=' ' read -r src mp fstype rest; do + # Decode escaped paths from /proc/mounts. + mount_path="${mp//\\040/ }" + mount_path="${mount_path//\\011/$'\t'}" + mount_path="${mount_path//\\012/$'\n'}" + + case "${mount_path}" in + "${root}"/*) ;; + *) continue ;; + esac + + case "${fstype}" in + fuse*|fuse.*) ;; + *) continue ;; + esac + + stat_out="$(LC_ALL=C stat "${mount_path}" 2>&1 || true)" + if printf "%s" "${stat_out}" | grep -Eq "Transport endpoint is not connected|Stale file handle|Input/output error"; then + echo "[Fluorine] Recovering stale FUSE mount: ${mount_path}" + fusermount3 -uz "${mount_path}" >/dev/null 2>&1 || \ + fusermount -uz "${mount_path}" >/dev/null 2>&1 || \ + umount -l "${mount_path}" >/dev/null 2>&1 || true + fi + done < /proc/mounts +} + +cleanup_stale_fuse_mounts "${APPIMAGE_DIR}" + +sync_dir_overwrite_files() { + local name="$1" + local src="${HERE}/usr/share/fluorine/${name}" + local dst="${APPIMAGE_DIR}/${name}" + + [ -d "${src}" ] || return 0 + + if [ ! -d "${dst}" ]; then + echo "[Fluorine] First run: extracting ${name}/ next to AppImage..." + else + echo "[Fluorine] Updating ${name}/ bundled files..." + fi + mkdir -p "${dst}" + # Merge bundled payload while skipping VCS metadata (.git, etc.). + ( + cd "${src}" + tar --exclude-vcs -cf - . + ) | ( + cd "${dst}" + tar -xf - + ) +} + +# ── Extract/sync writable dirs ── +sync_dir_overwrite_files "plugins" +sync_dir_overwrite_files "dlls" +sync_dir_overwrite_files "python" + +# Some existing Windows portable setups include plugins/plugin_python with +# .pyd/.dll payload. On Linux, the proxy will prefer this folder if present, +# which can hide the correct Linux mobase module. Overlay Linux runtime files +# into that folder to keep compatibility without deleting user content. +if [ -d "${APPIMAGE_DIR}/plugins/plugin_python" ]; then + if find "${APPIMAGE_DIR}/plugins/plugin_python" -type f \( -name '*.pyd' -o -name 'python*.dll' \) | grep -q .; then + echo "[Fluorine] Detected Windows plugin_python payload, overlaying Linux runtime files..." + mkdir -p "${APPIMAGE_DIR}/plugins/plugin_python/libs" "${APPIMAGE_DIR}/plugins/plugin_python/dlls" + [ -d "${APPIMAGE_DIR}/plugins/libs" ] && cp -a "${APPIMAGE_DIR}/plugins/libs/." "${APPIMAGE_DIR}/plugins/plugin_python/libs/" + [ -d "${APPIMAGE_DIR}/plugins/dlls" ] && cp -a "${APPIMAGE_DIR}/plugins/dlls/." "${APPIMAGE_DIR}/plugins/plugin_python/dlls/" + PYTHON_ROOT_CANDIDATE="${HERE}/usr/share/fluorine/python" + [ -d "${PYTHON_ROOT_CANDIDATE}/lib" ] || PYTHON_ROOT_CANDIDATE="${APPIMAGE_DIR}/python" + if [ -d "${PYTHON_ROOT_CANDIDATE}/lib" ]; then + PYVER_DIR="$(find "${PYTHON_ROOT_CANDIDATE}/lib" -mindepth 1 -maxdepth 1 -type d -name 'python3.*' | head -n 1)" + if [ -n "${PYVER_DIR}" ]; then + SITE_DIR="${PYVER_DIR}/site-packages" + [ -d "${SITE_DIR}/PyQt6" ] && cp -a "${SITE_DIR}/PyQt6" "${APPIMAGE_DIR}/plugins/plugin_python/libs/" + [ -d "${SITE_DIR}/PyQt6_sip" ] && cp -a "${SITE_DIR}/PyQt6_sip" "${APPIMAGE_DIR}/plugins/plugin_python/libs/" + [ -d "${SITE_DIR}/sip" ] && cp -a "${SITE_DIR}/sip" "${APPIMAGE_DIR}/plugins/plugin_python/libs/" + fi + fi + fi +fi + +# ── Environment ── +export PATH="${HERE}/usr/bin:${HERE}/usr/libexec:${PATH}" +export LD_LIBRARY_PATH="${HERE}/usr/lib:${HERE}/usr/libexec:${LD_LIBRARY_PATH:-}" + +# Qt plugins (read-only, inside AppImage) +export QT_PLUGIN_PATH="${HERE}/usr/plugins" +export QT_QPA_PLATFORM_PLUGIN_PATH="${HERE}/usr/plugins/platforms" +if [ -x "${HERE}/usr/libexec/QtWebEngineProcess" ]; then + export QTWEBENGINEPROCESS_PATH="${HERE}/usr/libexec/QtWebEngineProcess" +fi +if [ -d "${HERE}/usr/resources" ]; then + export QTWEBENGINE_RESOURCES_PATH="${HERE}/usr/resources" +fi +if [ -d "${HERE}/usr/translations/qtwebengine_locales" ]; then + export QTWEBENGINE_LOCALES_PATH="${HERE}/usr/translations/qtwebengine_locales" +fi + +# Tell the app to use the writable dirs next to the AppImage. +# MO2_BASE_DIR overrides qApp->applicationDirPath() for plugin/dll discovery. +export MO2_BASE_DIR="${APPIMAGE_DIR}" +export MO2_PLUGINS_DIR="${APPIMAGE_DIR}/plugins" +export MO2_DLLS_DIR="${APPIMAGE_DIR}/dlls" +MO2_PYTHON_BUNDLED="${HERE}/usr/share/fluorine/python" +if [ -d "${MO2_PYTHON_BUNDLED}/lib" ]; then + export MO2_PYTHON_DIR="${MO2_PYTHON_BUNDLED}" +else + export MO2_PYTHON_DIR="${APPIMAGE_DIR}/python" +fi +# Do not export PYTHONHOME/PYTHONPATH globally here. MO2 sets Python runtime +# internally for plugin_python, while child processes (umu/NaK/launchers) must +# use their own system Python environment. +unset PYTHONHOME PYTHONPATH PYTHONNOUSERSITE + +cd "${APPIMAGE_DIR}" +exec "${HERE}/usr/bin/ModOrganizer.bin" "$@" |
