aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml18
-rwxr-xr-xdocker/build-inner.sh5
-rw-r--r--flake.nix55
-rw-r--r--src/src/organizercore.cpp42
-rw-r--r--src/src/vfs/mo2filesystem.cpp30
-rw-r--r--src/src/vfs/mo2filesystem.h1
6 files changed, 101 insertions, 50 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d3ca6be..0212a31 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,7 +15,7 @@ jobs:
build:
runs-on: ubuntu-latest
permissions:
- contents: read
+ contents: write
packages: write
steps:
@@ -75,23 +75,11 @@ jobs:
with:
name: Fluorine-Manager
path: artifact/fluorine-manager.tar.gz
+ compression-level: 0
if-no-files-found: warn
- release:
- if: startsWith(github.ref, 'refs/tags/v')
- needs: build
- runs-on: ubuntu-latest
- permissions:
- contents: write
-
- steps:
- - name: Download artifacts
- uses: actions/download-artifact@v4
- with:
- name: Fluorine-Manager
- path: artifact/
-
- name: Create GitHub Release
+ if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
with:
files: artifact/fluorine-manager.tar.gz
diff --git a/docker/build-inner.sh b/docker/build-inner.sh
index d035e34..e9add61 100755
--- a/docker/build-inner.sh
+++ b/docker/build-inner.sh
@@ -349,7 +349,10 @@ RUN="${BIN_DST}"
PYTHON_DIR="${RUN}/python"
export PATH="${RUN}:${PATH}"
-export LD_LIBRARY_PATH="${RUN}/lib:${PYTHON_DIR}/lib:${LD_LIBRARY_PATH:-}"
+# NOTE: Do NOT set LD_LIBRARY_PATH here. The binary uses DT_RPATH
+# ($ORIGIN/lib:$ORIGIN/python/lib) to find its libraries. Setting
+# LD_LIBRARY_PATH on a capability-bearing binary (cap_sys_admin for FUSE
+# passthrough) triggers AT_SECURE mode, which drops all file capabilities.
export MO2_BASE_DIR="${RUN}"
export MO2_PLUGINS_DIR="${RUN}/plugins"
export MO2_DLLS_DIR="${RUN}/dlls"
diff --git a/flake.nix b/flake.nix
index d982917..6e66a5e 100644
--- a/flake.nix
+++ b/flake.nix
@@ -142,7 +142,7 @@
# System libraries
sqlite
- tinyxml2
+ tinyxml-2
spdlog
fuse3
lz4
@@ -382,7 +382,8 @@ RUN="''${BIN_DST}"
PYTHON_DIR="''${RUN}/python"
export PATH="''${RUN}:''${PATH}"
-export LD_LIBRARY_PATH="''${RUN}/lib:''${PYTHON_DIR}/lib:''${LD_LIBRARY_PATH:-}"
+# NOTE: Do NOT set LD_LIBRARY_PATH — DT_RPATH handles library resolution.
+# Setting it on a capability-bearing binary drops file capabilities (AT_SECURE).
export MO2_BASE_DIR="''${RUN}"
export MO2_PLUGINS_DIR="''${RUN}/plugins"
export MO2_DLLS_DIR="''${RUN}/dlls"
@@ -443,19 +444,19 @@ LAUNCH
vulkan-loader
mesa
- xorg.libX11
- xorg.libxcb
- xorg.libXext
- xorg.libXrandr
- xorg.libXcursor
- xorg.libXi
- xorg.libXfixes
- xorg.libXrender
- xorg.libXcomposite
- xorg.libXdamage
- xorg.libXtst
- xorg.libXScrnSaver
- xorg.libXinerama
+ libx11
+ libxcb
+ libxext
+ libxrandr
+ libxcursor
+ libxi
+ libxfixes
+ libxrender
+ libxcomposite
+ libxdamage
+ libxtst
+ libxscrnsaver
+ libxinerama
libxkbcommon
wayland
@@ -490,18 +491,18 @@ LAUNCH
fontconfig
zlib
glib
- xorg.libX11
- xorg.libxcb
- xorg.libXext
- xorg.libXrandr
- xorg.libXcursor
- xorg.libXi
- xorg.libXfixes
- xorg.libXrender
- xorg.libXcomposite
- xorg.libXdamage
- xorg.libXtst
- xorg.libXinerama
+ libx11
+ libxcb
+ libxext
+ libxrandr
+ libxcursor
+ libxi
+ libxfixes
+ libxrender
+ libxcomposite
+ libxdamage
+ libxtst
+ libxinerama
wayland
libxkbcommon
gnutls
diff --git a/src/src/organizercore.cpp b/src/src/organizercore.cpp
index 3896039..b42c3ed 100644
--- a/src/src/organizercore.cpp
+++ b/src/src/organizercore.cpp
@@ -663,9 +663,49 @@ void OrganizerCore::prepareVFS()
}
// FUSE passthrough: read the per-instance setting and pass it to the VFS.
+ // The binary needs CAP_SYS_ADMIN for passthrough to work — it can be lost
+ // when the binary is copied, updated, or extracted to a new location.
{
- const bool passthrough =
+ bool passthrough =
QSettings().value("fluorine/fuse_passthrough", false).toBool();
+
+ if (passthrough) {
+ // Check if the binary actually has cap_sys_admin right now.
+ const QString binary = QCoreApplication::applicationFilePath();
+ QProcess getcap;
+ getcap.setProgram("getcap");
+ getcap.setArguments({binary});
+ getcap.start();
+ getcap.waitForFinished(5000);
+ const QString caps = QString::fromUtf8(getcap.readAllStandardOutput());
+ const bool hasCap = caps.contains("cap_sys_admin");
+
+ if (!hasCap) {
+ std::fprintf(stderr,
+ "[VFS] passthrough enabled but binary lacks cap_sys_admin "
+ "— requesting via pkexec...\n");
+ QProcess setcap;
+ setcap.setProgram("pkexec");
+ setcap.setArguments({"setcap", "cap_sys_admin+ep", binary});
+ setcap.start();
+ setcap.waitForFinished(60000);
+
+ if (setcap.exitCode() != 0) {
+ std::fprintf(stderr,
+ "[VFS] failed to set cap_sys_admin — disabling passthrough\n");
+ passthrough = false;
+ } else {
+ // The capability is on the file now, but the running process won't
+ // have it until next exec. Disable passthrough for this session —
+ // it will work automatically on next launch.
+ std::fprintf(stderr,
+ "[VFS] cap_sys_admin applied to binary — passthrough "
+ "will activate on next launch\n");
+ passthrough = false;
+ }
+ }
+ }
+
m_USVFS.setPassthroughEnabled(passthrough);
}
#endif
diff --git a/src/src/vfs/mo2filesystem.cpp b/src/src/vfs/mo2filesystem.cpp
index 42e90c0..6c8cb87 100644
--- a/src/src/vfs/mo2filesystem.cpp
+++ b/src/src/vfs/mo2filesystem.cpp
@@ -1056,12 +1056,25 @@ void mo2_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi)
ptFd = open(realPath.c_str(), O_RDONLY);
}
if (ptFd >= 0) {
- fi->backing_id = static_cast<uint32_t>(ptFd);
- // Store the fd so we can close it on release
- std::scoped_lock lock(ctx->open_files_mutex);
- auto it = ctx->open_files.find(fh);
- if (it != ctx->open_files.end()) {
- it->second.fd = ptFd;
+ int backingId = fuse_passthrough_open(req, ptFd);
+ if (backingId > 0) {
+ fi->backing_id = backingId;
+ // Store the fd so we can close it on release
+ std::scoped_lock lock(ctx->open_files_mutex);
+ auto it = ctx->open_files.find(fh);
+ if (it != ctx->open_files.end()) {
+ it->second.fd = ptFd;
+ it->second.backing_id = backingId;
+ }
+ } else {
+ // Passthrough registration failed (e.g. missing cap_sys_admin at
+ // runtime). Disable passthrough for the rest of this session to
+ // avoid spamming "Operation not permitted" on every open.
+ close(ptFd);
+ ctx->passthrough_active = false;
+ std::fprintf(stderr,
+ "[VFS] fuse_passthrough_open failed — disabling "
+ "passthrough for this session\n");
}
}
}
@@ -1667,6 +1680,11 @@ void mo2_release(fuse_req_t req, fuse_ino_t /*ino*/, struct fuse_file_info* fi)
std::scoped_lock lock(ctx->open_files_mutex);
auto it = ctx->open_files.find(fi->fh);
if (it != ctx->open_files.end()) {
+ if constexpr (FUSE_CAP_PASSTHROUGH != 0) {
+ if (it->second.backing_id > 0) {
+ fuse_passthrough_close(req, it->second.backing_id);
+ }
+ }
if (it->second.fd >= 0) {
close(it->second.fd);
}
diff --git a/src/src/vfs/mo2filesystem.h b/src/src/vfs/mo2filesystem.h
index 2883a18..9b27d76 100644
--- a/src/src/vfs/mo2filesystem.h
+++ b/src/src/vfs/mo2filesystem.h
@@ -39,6 +39,7 @@ struct Mo2FsContext
struct OpenFile
{
int fd = -1;
+ int backing_id = 0; // FUSE passthrough backing ID (0 = not using passthrough)
std::string real_path;
bool writable = false;
bool is_backing = false;