feat(nixpak): sandbox Bambu Studio and OrcaSlicer (#495)

Assisted-by: DeepSeek Harness (deepseek-flash)
This commit is contained in:
ryan4yin | 二花 authored and GitHub committed 2026-11-01 11:43:35 +08:00
1 parent 1a939e2c9a
commit c3990cc4be
10 files changed
+255 -31

No files matched your search

+15 -13
View File
@@ -19,19 +19,21 @@ Deployed and interactively validated on the Linux desktop host (2026-10-31). Pol
`Media` means audio playback, microphone, Camera and ScreenCast portals. Writable paths are listed
explicitly; other files use the document portal. Every app also has private writable state.
| App | Network | Writable paths | Grants |
| ------------ | ------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| Firefox | Host loopback | `~/.mozilla`, personal files, Trash, Firefox artwork | Audio, browserpass, MPRIS, idle; camera/screen sharing intentionally unavailable |
| Chromium | Host loopback | `~/.config/chromium`, personal files | Media, browserpass, MPRIS, idle, v12 Secret |
| Chrome | Isolated | `~/.config/google-chrome`, Downloads | Media, browserpass, MPRIS, idle, v12 Secret |
| Brave Origin | Isolated | `~/.config/BraveSoftware/Brave-Origin`, Downloads | v12 Secret |
| Edge | Isolated | `~/.config/microsoft-edge`, Downloads | v12 Secret |
| QQ | Isolated | Personal files, private app state | Media, notifications, tray, MPRIS, idle, accessibility |
| Telegram | Isolated | Personal files, private app state | Media, notifications, tray, MPRIS, idle, accessibility, Ayatana indicators |
| Zoom | Isolated | `~/.zoom`, `zoomus.conf`, `zoom.conf`, Downloads, `Documents/Zoom`, private app state | Media, X11/XWayland, FHS, SNI tray, idle, `/dev/video0-1`, read-only `/sys/class/drm` |
| WeChat | Isolated | Documents, Downloads, app-scoped `wechat` data, private app state | Media, Wayland, X11 fallback, SNI tray, file chooser portal |
| Foliate | None | app-scoped state | File chooser, OpenURI, notifications, IME, accessibility |
| Krita | None | app-scoped state | File chooser, OpenURI, print, inhibit, notifications, IME, accessibility |
| App | Network | Writable paths | Grants |
| ------------ | ------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Firefox | Host loopback | `~/.mozilla`, personal files, Trash, Firefox artwork | Audio, browserpass, MPRIS, idle; camera/screen sharing intentionally unavailable |
| Chromium | Host loopback | `~/.config/chromium`, personal files | Media, browserpass, MPRIS, idle, v12 Secret |
| Chrome | Isolated | `~/.config/google-chrome`, Downloads | Media, browserpass, MPRIS, idle, v12 Secret |
| Brave Origin | Isolated | `~/.config/BraveSoftware/Brave-Origin`, Downloads | v12 Secret |
| Edge | Isolated | `~/.config/microsoft-edge`, Downloads | v12 Secret |
| QQ | Isolated | Personal files, private app state | Media, notifications, tray, MPRIS, idle, accessibility |
| Telegram | Isolated | Personal files, private app state | Media, notifications, tray, MPRIS, idle, accessibility, Ayatana indicators |
| Zoom | Isolated | `~/.zoom`, `zoomus.conf`, `zoom.conf`, Downloads, `Documents/Zoom`, private app state | Media, X11/XWayland, FHS, SNI tray, idle, `/dev/video0-1`, read-only `/sys/class/drm` |
| WeChat | Isolated | Documents, Downloads, app-scoped `wechat` data, private app state | Media, Wayland, X11 fallback, SNI tray, file chooser portal |
| Foliate | None | Documents/Downloads/Pictures read-only, app-scoped state | File chooser, OpenURI, notifications, IME, accessibility |
| Krita | None | Documents/Downloads/Pictures writable, app-scoped state | File chooser, OpenURI, print, inhibit, notifications, IME, accessibility |
| Bambu Studio | Isolated | Documents/Downloads/Pictures writable, app-scoped state | X11/XWayland, PulseAudio, cloud/LAN network, file chooser, notifications, IME, accessibility |
| OrcaSlicer | Isolated | Documents/Downloads/Pictures writable, app-scoped state | X11/XWayland, cloud/LAN network, file chooser, notifications, IME, accessibility |
All browsers get read-only NSS certificates, printing, native notifications and accessibility.
+70
View File
@@ -0,0 +1,70 @@
{
lib,
bambu-studio,
mkNixPak,
buildEnv,
makeDesktopItem,
runCommandLocal,
...
}:
let
appId = "com.bambulab.BambuStudio";
package = bambu-studio;
wrapped = mkNixPak {
config = {
imports = [ ./modules/slicer-policy.nix ];
app = {
inherit package;
binPath = "bin/bambu-studio";
};
flatpak = { inherit appId; };
# The Agora SDK used by the printer camera preview aborts when
# PulseAudio initialization fails.
slicer.enablePulse = true;
# Work around wxWidgets HiDPI text clipping under fractional scaling.
# https://github.com/bambulab/BambuStudio/issues/4959
bubblewrap.env.GDK_DPI_SCALE = "0.8";
};
};
exe = lib.getExe wrapped.config.script;
assets = runCommandLocal "bambu-studio-desktop-assets" { } ''
mkdir -p $out/share
ln -s ${package}/share/icons $out/share/icons
'';
in
buildEnv {
inherit (wrapped.config.script) name passthru;
meta = wrapped.config.script.meta // {
mainProgram = "bambu-studio";
};
paths = [
wrapped.config.script
assets
(makeDesktopItem {
name = "com.bambulab.BambuStudio";
desktopName = "BambuStudio";
genericName = "3D Printing Software";
comment = "PC software for BambuLab 3D printers";
exec = "${exe} %U";
icon = "BambuStudio";
terminal = false;
startupWMClass = "bambu-studio";
categories = [
"Graphics"
"3DGraphics"
"Engineering"
];
mimeTypes = [
"model/stl"
"model/3mf"
"application/vnd.ms-3mfdocument"
"application/prs.wavefront-obj"
"application/x-amf"
"x-scheme-handler/bambustudio"
"model/step"
];
extraConfig.X-Flatpak = appId;
})
];
}
+2
View File
@@ -29,6 +29,8 @@ in
microsoft-edge = wrapper super ./microsoft-edge.nix;
foliate = wrapper super ./foliate.nix;
krita = wrapper pkgs-stable ./krita.nix;
bambu-studio = wrapper pkgs-master ./bambu-studio.nix;
orca-slicer = wrapper pkgs-master ./orca-slicer.nix;
zoom = wrapper super ./zoom.nix;
wechat = wrapper pkgs-patched ./wechat.nix;
};
@@ -0,0 +1,50 @@
# Shared policy for the wxWidgets/GTK3 slicers. Both applications are X11-only
# and use pasta networking for cloud services and LAN printer discovery.
{
config,
lib,
sloth,
...
}:
let
cfg = config.slicer;
in
{
imports = [
./desktop-base.nix
./network.nix
];
options.slicer.enablePulse = lib.mkEnableOption "PulseAudio for Bambu printer video preview";
config = {
pasta.mode = "isolate";
bubblewrap = {
sockets = {
x11 = true;
pulse = lib.mkForce cfg.enablePulse;
};
env = {
DISPLAY = sloth.envOr "DISPLAY" "";
XAUTHORITY = sloth.envOr "XAUTHORITY" (sloth.concat' sloth.homeDir "/.Xauthority");
GDK_BACKEND = "x11";
};
# File-manager launches pass host paths into the sandbox. Slicers both read
# models and write sliced output, so give the common document directories
# read-write access; the file chooser still handles other locations.
bind.rw = [
sloth.xdgDocumentsDir
sloth.xdgDownloadDir
sloth.xdgPicturesDir
];
};
portalGroups = {
fileChooser = true;
openUri = true;
notifications = true;
inputMethod = true;
};
desktopIntegration = {
notifications = true;
accessibility = true;
};
};
}
+67
View File
@@ -0,0 +1,67 @@
{
lib,
orca-slicer,
mkNixPak,
buildEnv,
makeDesktopItem,
runCommandLocal,
...
}:
let
appId = "com.orcaslicer.OrcaSlicer";
package = orca-slicer;
wrapped = mkNixPak {
config = {
imports = [ ./modules/slicer-policy.nix ];
app = {
inherit package;
binPath = "bin/orca-slicer";
};
flatpak = { inherit appId; };
# WebKitGTK's DMA-BUF renderer produces a blank home page under
# XWayland on NVIDIA; force the software path like Bambu does.
bubblewrap.env.WEBKIT_DISABLE_DMABUF_RENDERER = "1";
};
};
exe = lib.getExe wrapped.config.script;
assets = runCommandLocal "orca-slicer-desktop-assets" { } ''
mkdir -p $out/share
ln -s ${package}/share/icons $out/share/icons
'';
in
buildEnv {
inherit (wrapped.config.script) name passthru;
meta = wrapped.config.script.meta // {
mainProgram = "orca-slicer";
};
paths = [
wrapped.config.script
assets
(makeDesktopItem {
name = "com.orcaslicer.OrcaSlicer";
desktopName = "OrcaSlicer";
genericName = "3D Printing Software";
comment = "G-code generator for 3D printers";
exec = "${exe} %U";
icon = "OrcaSlicer";
terminal = false;
startupWMClass = "orca-slicer";
categories = [
"Graphics"
"3DGraphics"
"Engineering"
];
mimeTypes = [
"model/stl"
"model/3mf"
"application/vnd.ms-3mfdocument"
"application/prs.wavefront-obj"
"application/x-amf"
"x-scheme-handler/orcaslicer"
"model/step"
];
extraConfig.X-Flatpak = appId;
})
];
}
+3 -3
View File
@@ -74,10 +74,9 @@ let
mediaCheck =
app: expected:
if expected then
app.bubblewrap.sockets.pulse && app.portalGroups.camera && app.portalGroups.screenCast
app.portalGroups.camera && app.portalGroups.screenCast
else
!app.bubblewrap.sockets.pulse
&& !app.bubblewrap.sockets.pipewire
!app.bubblewrap.sockets.pipewire
&& !app.portalGroups.camera
&& !app.portalGroups.screenCast
&& !(hasDbusFragment app "org.freedesktop.portal.Camera")
@@ -125,6 +124,7 @@ let
"${name}/network" = networkCheck app spec.network;
"${name}/x11" = app.bubblewrap.sockets.x11 == (spec.x11 or false);
"${name}/media" = mediaCheck app (spec.media or false);
"${name}/pulse" = app.bubblewrap.sockets.pulse == (spec.pulse or (spec.media or false));
"${name}/mpris" = app.desktopIntegration.mpris == (spec.mpris or false);
"${name}/notifications" = app.desktopIntegration.notifications == (spec.notifications or true);
"${name}/tray" = trayCheck app (spec.tray or false);
+22
View File
@@ -0,0 +1,22 @@
{
load,
mkAppPolicy,
...
}:
let
app = load "bambu-studio";
in
mkAppPolicy "bambu-studio" {
network = "pasta";
media = false;
pulse = true;
x11 = true;
notifications = true;
extraChecks = {
gdkX11 = app.bubblewrap.env.GDK_BACKEND == "x11";
fileChooser = app.portalGroups.fileChooser;
openUri = app.portalGroups.openUri;
inputMethod = app.portalGroups.inputMethod;
accessibility = app.desktopIntegration.accessibility;
};
}
+22
View File
@@ -0,0 +1,22 @@
{
load,
mkAppPolicy,
...
}:
let
app = load "orca-slicer";
in
mkAppPolicy "orca-slicer" {
network = "pasta";
media = false;
x11 = true;
notifications = true;
extraChecks = {
gdkX11 = app.bubblewrap.env.GDK_BACKEND == "x11";
webkitDmabuf = app.bubblewrap.env.WEBKIT_DISABLE_DMABUF_RENDERER == "1";
fileChooser = app.portalGroups.fileChooser;
openUri = app.portalGroups.openUri;
inputMethod = app.portalGroups.inputMethod;
accessibility = app.desktopIntegration.accessibility;
};
}
+2
View File
@@ -24,6 +24,8 @@ Nixpak wrappers are accepted in three layers:
| WeChat (native nixpak) | Pass | Interactive Wayland validation pending |
| Foliate | Pass | Interactive validation pending |
| Krita | Pass | Interactive validation pending |
| Bambu Studio | Pass | Interactive validation pending |
| OrcaSlicer | Pass | Interactive validation pending |
Intentional exceptions:
+2 -15
View File
@@ -1,29 +1,16 @@
{
pkgs,
pkgs-stable,
pkgs-master,
pkgs-blender,
...
}:
let
bambu-studio = pkgs.symlinkJoin {
name = "bambu-studio-${pkgs-master.bambu-studio.version}";
paths = [ pkgs-master.bambu-studio ];
nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = ''
# Work around wxWidgets HiDPI text clipping under fractional scaling.
# https://github.com/bambulab/BambuStudio/issues/4959
wrapProgram $out/bin/bambu-studio --set GDK_DPI_SCALE 0.8
'';
};
in
{
home.packages = with pkgs-stable; [
# creative
pkgs.nixpaks.krita # digital painting
musescore # music notation
pkgs-master.orca-slicer # 3d printer slicer app
bambu-studio # bambu 3d printer slicer app
pkgs.nixpaks.orca-slicer # 3d printer slicer app
pkgs.nixpaks.bambu-studio # bambu 3d printer slicer app
pkgs-blender.blender # 3d modeling
];