mirror of
https://github.com/ryan4yin/nix-config.git
synced 2026-11-13 12:13:36 +01:00
feat(nixpak): unify desktop app sandboxes (#491)
* refactor(nixpak): unify desktop app sandboxes Move common GUI, portal, and network policy into reusable modules and migrate browsers plus Zoom/WeChat/QQ/Telegram to the same boundary. Assisted-by: DeepSeek Harness (deepseek-flash)
This commit is contained in:
1 parent
707da35996
commit
818965a11f
35 files changed
+1858
-565
No files matched your search
@@ -0,0 +1,16 @@
|
||||
{
|
||||
callPackage,
|
||||
mkNixPak,
|
||||
brave-origin,
|
||||
...
|
||||
}:
|
||||
(callPackage ./modules/chromium-browser.nix { inherit mkNixPak; }) {
|
||||
package = brave-origin;
|
||||
appId = "com.brave.Origin";
|
||||
profile = "BraveSoftware/Brave-Origin";
|
||||
command = "brave-origin";
|
||||
desktopName = "Brave Origin";
|
||||
desktopFile = "brave-origin";
|
||||
browserpass = false;
|
||||
secretPortal = true;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
callPackage,
|
||||
mkNixPak,
|
||||
chromium,
|
||||
...
|
||||
}:
|
||||
(callPackage ./modules/chromium-browser.nix { inherit mkNixPak; }) {
|
||||
package = chromium;
|
||||
appId = "org.chromium.Chromium";
|
||||
profile = "chromium";
|
||||
command = "chromium";
|
||||
desktopName = "Chromium";
|
||||
desktopFile = "chromium-browser";
|
||||
browserpass = true;
|
||||
media = true;
|
||||
hostLoopback = true;
|
||||
personalFiles = true;
|
||||
secretPortal = true;
|
||||
}
|
||||
@@ -21,6 +21,12 @@ in
|
||||
qq = wrapper pkgs-master ./qq.nix;
|
||||
telegram-desktop = wrapper super ./telegram-desktop.nix;
|
||||
firefox = wrapper super ./firefox.nix;
|
||||
google-chrome = wrapper super ./google-chrome.nix;
|
||||
chromium = wrapper super ./chromium.nix;
|
||||
brave-origin = wrapper super ./brave-origin.nix;
|
||||
microsoft-edge = wrapper super ./microsoft-edge.nix;
|
||||
zoom = wrapper super ./zoom.nix;
|
||||
wechat = wrapper super ./wechat.nix;
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
@@ -21,9 +21,6 @@ let
|
||||
sloth,
|
||||
...
|
||||
}:
|
||||
let
|
||||
passwordStore = sloth.concat' sloth.homeDir "/.local/share/password-store";
|
||||
in
|
||||
{
|
||||
app = {
|
||||
package = firefox;
|
||||
@@ -32,14 +29,19 @@ let
|
||||
flatpak.appId = appId;
|
||||
|
||||
imports = [
|
||||
./modules/gui-base.nix
|
||||
./modules/browser-permissions.nix
|
||||
./modules/network.nix
|
||||
./modules/custom-policy.nix
|
||||
];
|
||||
|
||||
browserPermissions = {
|
||||
media = true;
|
||||
personalFiles = true;
|
||||
browserpass = true;
|
||||
};
|
||||
|
||||
# Firefox keeps the host network: local development servers and the
|
||||
# mihomo controller live on 127.0.0.1, and pasta's isolated loopback
|
||||
# would hide them. The other sandboxed apps do not need host loopback.
|
||||
# would hide them. Chromium uses the same personal-browser policy.
|
||||
pasta.enable = lib.mkForce false;
|
||||
|
||||
bubblewrap = {
|
||||
@@ -55,16 +57,6 @@ let
|
||||
# path over MPRIS. Without this bind it lands under the sandboxed
|
||||
# XDG_DATA_HOME and Noctalia (outside) cannot read it.
|
||||
(sloth.mkdir (sloth.concat' sloth.homeDir "/.local/share/firefox-mpris"))
|
||||
|
||||
# Files deleted from Firefox go to the real trash instead of a
|
||||
# private one under the sandboxed XDG_DATA_HOME.
|
||||
(sloth.mkdir (sloth.concat' sloth.homeDir "/.local/share/Trash"))
|
||||
|
||||
sloth.xdgDocumentsDir
|
||||
sloth.xdgDownloadDir
|
||||
sloth.xdgMusicDir
|
||||
sloth.xdgVideosDir
|
||||
sloth.xdgPicturesDir
|
||||
];
|
||||
bind.ro = [
|
||||
"/sys/bus/pci"
|
||||
@@ -72,25 +64,7 @@ let
|
||||
"${config.app.package}/lib/firefox"
|
||||
"/app/etc/firefox"
|
||||
]
|
||||
|
||||
# ================ for browserpass extension ===============================
|
||||
"/etc/gnupg"
|
||||
(sloth.concat' sloth.homeDir "/.gnupg") # gpg's config
|
||||
passwordStore # my secrets
|
||||
(sloth.concat' sloth.runtimeDir "/gnupg") # for access gpg-agent socket
|
||||
|
||||
# Match the host's default-app associations and XDG user dirs.
|
||||
(sloth.concat' sloth.homeDir "/.config/mimeapps.list")
|
||||
(sloth.concat' sloth.homeDir "/.config/user-dirs.dirs")
|
||||
|
||||
# Shared NSS client-certificate store (imported outside Firefox).
|
||||
(sloth.concat' sloth.homeDir "/.pki/nssdb")
|
||||
|
||||
# dconf settings. Not verified as needed; candidate for removal.
|
||||
(sloth.concat' sloth.xdgConfigHome "/dconf")
|
||||
];
|
||||
# browserpass looks for the store here, not at pass's default path.
|
||||
env.PASSWORD_STORE_DIR = passwordStore;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
callPackage,
|
||||
mkNixPak,
|
||||
google-chrome,
|
||||
...
|
||||
}:
|
||||
(callPackage ./modules/chromium-browser.nix { inherit mkNixPak; }) {
|
||||
package = google-chrome;
|
||||
appId = "com.google.Chrome";
|
||||
profile = "google-chrome";
|
||||
command = "google-chrome";
|
||||
desktopName = "Google Chrome";
|
||||
desktopFile = "google-chrome";
|
||||
browserpass = true;
|
||||
media = true;
|
||||
secretPortal = true;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
callPackage,
|
||||
mkNixPak,
|
||||
microsoft-edge,
|
||||
...
|
||||
}:
|
||||
(callPackage ./modules/chromium-browser.nix { inherit mkNixPak; }) {
|
||||
package = microsoft-edge;
|
||||
appId = "com.microsoft.Edge";
|
||||
profile = "microsoft-edge";
|
||||
command = "microsoft-edge";
|
||||
desktopName = "Microsoft Edge";
|
||||
desktopFile = "microsoft-edge";
|
||||
browserpass = false;
|
||||
secretPortal = true;
|
||||
privateWindowFlag = "--inprivate";
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
# Shared browser capabilities. Network topology and application profiles are
|
||||
# selected by the app; personal files, browserpass, Secret portal and media are explicit opt-ins.
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
sloth,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.browserPermissions;
|
||||
passwordStore = sloth.concat' sloth.homeDir "/.local/share/password-store";
|
||||
in
|
||||
{
|
||||
imports = [ ./desktop-base.nix ];
|
||||
options.browserPermissions = {
|
||||
media = lib.mkEnableOption "audio playback/recording, camera and desktop screen sharing";
|
||||
personalFiles = lib.mkEnableOption "personal Documents, media directories and Trash";
|
||||
browserpass = lib.mkEnableOption "browserpass access to the password store and GPG agent";
|
||||
secretPortal = lib.mkEnableOption "the app-scoped Secret portal for v12 cookie encryption";
|
||||
};
|
||||
config = {
|
||||
portalGroups = {
|
||||
fileChooser = true;
|
||||
openUri = true;
|
||||
notifications = true;
|
||||
screenCast = cfg.media;
|
||||
camera = cfg.media;
|
||||
print = true;
|
||||
inhibit = cfg.media;
|
||||
inputMethod = true;
|
||||
secret = cfg.secretPortal;
|
||||
};
|
||||
desktopIntegration = {
|
||||
notifications = true;
|
||||
mpris = cfg.media;
|
||||
accessibility = true;
|
||||
};
|
||||
bubblewrap = {
|
||||
sockets.pulse = cfg.media;
|
||||
bind.rw = [
|
||||
sloth.xdgDownloadDir
|
||||
]
|
||||
++ lib.optionals cfg.personalFiles [
|
||||
sloth.xdgDocumentsDir
|
||||
sloth.xdgMusicDir
|
||||
sloth.xdgVideosDir
|
||||
sloth.xdgPicturesDir
|
||||
(sloth.mkdir (sloth.concat' sloth.homeDir "/.local/share/Trash"))
|
||||
];
|
||||
bind.ro = [
|
||||
(sloth.concat' sloth.homeDir "/.config/mimeapps.list")
|
||||
(sloth.concat' sloth.homeDir "/.config/user-dirs.dirs")
|
||||
(sloth.concat' sloth.homeDir "/.pki/nssdb")
|
||||
]
|
||||
++ lib.optionals cfg.browserpass [
|
||||
"/etc/gnupg"
|
||||
(sloth.concat' sloth.homeDir "/.gnupg")
|
||||
passwordStore
|
||||
(sloth.concat' sloth.runtimeDir "/gnupg")
|
||||
];
|
||||
env = lib.optionalAttrs cfg.browserpass { PASSWORD_STORE_DIR = passwordStore; };
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
# Chromium-family wrapper: nixpak adds filesystem, D-Bus and network boundaries;
|
||||
# the upstream Chromium renderer sandbox remains enabled.
|
||||
{
|
||||
lib,
|
||||
mkNixPak,
|
||||
buildEnv,
|
||||
makeDesktopItem,
|
||||
runCommandLocal,
|
||||
makeWrapper,
|
||||
...
|
||||
}:
|
||||
{
|
||||
package,
|
||||
appId,
|
||||
profile,
|
||||
command,
|
||||
desktopName,
|
||||
desktopFile ? command,
|
||||
browserpass ? false,
|
||||
media ? false,
|
||||
secretPortal ? false,
|
||||
hostLoopback ? false,
|
||||
personalFiles ? false,
|
||||
privateWindowFlag ? "--incognito",
|
||||
}:
|
||||
let
|
||||
# Chromium 156 disables PipeWire camera capture by default; media apps need
|
||||
# the portal FD path instead of raw video devices.
|
||||
# Keep the Wayland feature from nixpkgs: the last --enable-features wins.
|
||||
enabledFeatures = lib.concatStringsSep "," (
|
||||
[ "WaylandWindowDecorations" ]
|
||||
++ lib.optional media "WebRtcPipeWireCamera"
|
||||
++ lib.optional secretPortal "SecretPortalKeyProviderUseForEncryption"
|
||||
);
|
||||
usernsPackage =
|
||||
runCommandLocal "${package.name}-userns"
|
||||
{
|
||||
nativeBuildInputs = [ makeWrapper ];
|
||||
}
|
||||
''
|
||||
mkdir -p $out/bin
|
||||
makeWrapper ${package}/bin/${command} $out/bin/${command} \
|
||||
--add-flags "--enable-features=${enabledFeatures}"
|
||||
'';
|
||||
wrapped = mkNixPak {
|
||||
config =
|
||||
{ sloth, ... }:
|
||||
{
|
||||
imports = [
|
||||
./browser-permissions.nix
|
||||
./network.nix
|
||||
];
|
||||
app = {
|
||||
package = usernsPackage;
|
||||
binPath = "bin/${command}";
|
||||
};
|
||||
flatpak = { inherit appId; };
|
||||
# Firefox/Chromium need host loopback for local services; Chrome and the rest use pasta.
|
||||
pasta.enable = lib.mkForce (!hostLoopback);
|
||||
pasta.mode = "isolate";
|
||||
browserPermissions = {
|
||||
inherit
|
||||
browserpass
|
||||
personalFiles
|
||||
media
|
||||
secretPortal
|
||||
;
|
||||
};
|
||||
bubblewrap.bind.rw = [
|
||||
(sloth.mkdir (sloth.concat' sloth.homeDir "/.config/${profile}"))
|
||||
];
|
||||
};
|
||||
};
|
||||
exe = "${wrapped.config.script}/bin/${command}";
|
||||
# Copy desktop assets only; the full package would expose unwrapped binaries.
|
||||
assets = runCommandLocal "${command}-desktop-assets" { } ''
|
||||
mkdir -p $out/share
|
||||
for directory in icons pixmaps; do
|
||||
if [ -d ${package}/share/$directory ]; then
|
||||
ln -s ${package}/share/$directory $out/share/$directory
|
||||
fi
|
||||
done
|
||||
'';
|
||||
in
|
||||
buildEnv {
|
||||
inherit (wrapped.config.script) name meta passthru;
|
||||
paths = [
|
||||
wrapped.config.script
|
||||
assets
|
||||
(makeDesktopItem {
|
||||
name = appId;
|
||||
inherit desktopName;
|
||||
genericName = "Web Browser";
|
||||
exec = "${exe} %U";
|
||||
icon = desktopFile;
|
||||
terminal = false;
|
||||
startupNotify = true;
|
||||
categories = [
|
||||
"Network"
|
||||
"WebBrowser"
|
||||
];
|
||||
mimeTypes = [
|
||||
"text/html"
|
||||
"application/xhtml+xml"
|
||||
"x-scheme-handler/http"
|
||||
"x-scheme-handler/https"
|
||||
];
|
||||
actions = {
|
||||
new-window = {
|
||||
name = "New Window";
|
||||
exec = "${exe} --new-window %U";
|
||||
};
|
||||
new-private-window = {
|
||||
name = "New Private Window";
|
||||
exec = "${exe} ${privateWindowFlag} %U";
|
||||
};
|
||||
};
|
||||
extraConfig.X-Flatpak = appId;
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
# Custom, repo-local policy for the sandboxed desktop apps (Firefox, QQ,
|
||||
# Telegram). Unlike ./gui-base.nix, which forks nixpak/pkgs's gui-base and is
|
||||
# meant to track upstream, this file is hand-written for this repository: it
|
||||
# grants the D-Bus surface those apps need (portals, MPRIS, tray, input
|
||||
# methods, a11y), redirects the XDG dirs Flatpak-style, and adds the remaining
|
||||
# document/device mounts. Keep only app-specific mounts in the app files.
|
||||
#
|
||||
# Adapted from:
|
||||
# - https://github.com/mnixry/nixos-config/blob/74913c2b90d06e31170bbbaa0074f915721da224/desktop/packages/nixpaks-common.nix
|
||||
# - https://github.com/Kraftland/portable/blob/09c4a4227538a3f42de208a6ecbdc938ac9c00dd/portable.sh
|
||||
# - https://flatpak.github.io/xdg-desktop-portal/docs/api-reference.html
|
||||
{
|
||||
lib,
|
||||
sloth,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (config.flatpak) appId;
|
||||
in
|
||||
{
|
||||
config = {
|
||||
# list all dbus services:
|
||||
# ls -al /run/current-system/sw/share/dbus-1/services/
|
||||
# ls -al /etc/profiles/per-user/ryan/share/dbus-1/services/
|
||||
dbus = {
|
||||
# `--see`: The bus name can be enumerated by the application.
|
||||
# `--talk`: The application can send messages to, and receive replies and signals from, the bus name.
|
||||
# `--own`: The application can own the bus name
|
||||
policies = {
|
||||
"${appId}" = "own";
|
||||
"${appId}.*" = "own";
|
||||
"org.freedesktop.DBus" = "talk";
|
||||
"ca.desrt.dconf" = "talk";
|
||||
"org.freedesktop.appearance" = "talk";
|
||||
"org.freedesktop.appearance.*" = "talk";
|
||||
}
|
||||
// (builtins.listToAttrs (
|
||||
map (id: lib.nameValuePair "org.kde.StatusNotifierItem-${toString id}-1" "own") (
|
||||
lib.lists.range 2 29
|
||||
)
|
||||
))
|
||||
// {
|
||||
# --- MPRIS Media Control ---
|
||||
# Allows the app to register as a media player. These are derived from the appID.
|
||||
"org.mpris.MediaPlayer2.${appId}" = "own";
|
||||
"org.mpris.MediaPlayer2.${appId}.*" = "own";
|
||||
"org.mpris.MediaPlayer2.${lib.lists.last (lib.strings.splitString "." appId)}" = "own";
|
||||
"org.mpris.MediaPlayer2.${lib.lists.last (lib.strings.splitString "." appId)}.*" = "own";
|
||||
|
||||
# --- General Desktop Integration ---
|
||||
"com.canonical.AppMenu.Registrar" = "talk"; # For Ubuntu AppMenu
|
||||
"org.freedesktop.FileManager1" = "talk";
|
||||
"org.freedesktop.Notifications" = "talk";
|
||||
"org.kde.StatusNotifierWatcher" = "talk";
|
||||
"org.gnome.Shell.Screencast" = "talk";
|
||||
|
||||
# --- Accessibility (a11y) 无障碍服务 ---
|
||||
"org.a11y.Bus" = "see";
|
||||
|
||||
# --- Portal Access ---
|
||||
# "org.freedesktop.portal.*" = "talk";
|
||||
"org.freedesktop.portal.Documents" = "talk";
|
||||
"org.freedesktop.portal.FileTransfer" = "talk";
|
||||
"org.freedesktop.portal.FileTransfer.*" = "talk";
|
||||
"org.freedesktop.portal.Notification" = "talk";
|
||||
"org.freedesktop.portal.OpenURI" = "talk";
|
||||
"org.freedesktop.portal.OpenURI.OpenFile" = "talk";
|
||||
"org.freedesktop.portal.OpenURI.OpenURI" = "talk";
|
||||
"org.freedesktop.portal.Print" = "talk";
|
||||
"org.freedesktop.portal.Request" = "see";
|
||||
|
||||
# --- Input Method Portals ---
|
||||
"org.freedesktop.portal.Fcitx" = "talk";
|
||||
"org.freedesktop.portal.Fcitx.*" = "talk";
|
||||
"org.freedesktop.portal.IBus" = "talk";
|
||||
"org.freedesktop.portal.IBus.*" = "talk";
|
||||
};
|
||||
# '--call' rules permit specific method calls on D-Bus interfaces.
|
||||
rules.call = {
|
||||
# --- Accessibility (a11y) 无障碍服务 ---
|
||||
"org.a11y.Bus" = [
|
||||
"org.a11y.Bus.GetAddress@/org/a11y/bus"
|
||||
"org.freedesktop.DBus.Properties.Get@/org/a11y/bus"
|
||||
];
|
||||
|
||||
# --- General Portal Rules ---
|
||||
"org.freedesktop.FileManager1" = [ "*" ];
|
||||
"org.freedesktop.Notifications.*" = [ "*" ];
|
||||
"org.freedesktop.portal.Documents" = [ "*" ];
|
||||
"org.freedesktop.portal.FileTransfer" = [ "*" ];
|
||||
"org.freedesktop.portal.FileTransfer.*" = [ "*" ];
|
||||
"org.freedesktop.portal.Fcitx" = [ "*" ];
|
||||
"org.freedesktop.portal.Fcitx.*" = [ "*" ];
|
||||
"org.freedesktop.portal.IBus" = [ "*" ];
|
||||
"org.freedesktop.portal.IBus.*" = [ "*" ];
|
||||
"org.freedesktop.portal.Notification" = [ "*" ];
|
||||
"org.freedesktop.portal.OpenURI" = [ "*" ];
|
||||
"org.freedesktop.portal.OpenURI.OpenFile" = [ "*" ];
|
||||
"org.freedesktop.portal.OpenURI.OpenURI" = [ "*" ];
|
||||
"org.freedesktop.portal.Print" = [ "*" ];
|
||||
"org.freedesktop.portal.Request" = [ "*" ];
|
||||
|
||||
# --- Main Desktop Portal Interface ---
|
||||
# A comprehensive list of permissions for interacting with the desktop environment.
|
||||
"org.freedesktop.portal.Desktop" = [
|
||||
# Properties & Settings
|
||||
"org.freedesktop.DBus.Properties.GetAll"
|
||||
"org.freedesktop.DBus.Properties.Get@/org/freedesktop/portal/desktop"
|
||||
"org.freedesktop.portal.Session.Close"
|
||||
"org.freedesktop.portal.Settings.ReadAll"
|
||||
"org.freedesktop.portal.Settings.Read"
|
||||
"org.freedesktop.portal.Account.GetUserInformation"
|
||||
|
||||
# Network & Proxy
|
||||
"org.freedesktop.portal.NetworkMonitor"
|
||||
"org.freedesktop.portal.NetworkMonitor.*"
|
||||
"org.freedesktop.portal.ProxyResolver.Lookup"
|
||||
"org.freedesktop.portal.ProxyResolver.Lookup.*"
|
||||
|
||||
# Screenshot / Screen Capture & Sharing
|
||||
"org.freedesktop.portal.ScreenCast"
|
||||
"org.freedesktop.portal.ScreenCast.*"
|
||||
"org.freedesktop.portal.Screenshot"
|
||||
"org.freedesktop.portal.Screenshot.Screenshot"
|
||||
|
||||
# Device Access(Camera / USB)
|
||||
"org.freedesktop.portal.Camera"
|
||||
"org.freedesktop.portal.Camera.*"
|
||||
"org.freedesktop.portal.Usb"
|
||||
"org.freedesktop.portal.Usb.*"
|
||||
|
||||
# Remote Desktop
|
||||
"org.freedesktop.portal.RemoteDesktop"
|
||||
"org.freedesktop.portal.RemoteDesktop.*"
|
||||
|
||||
# File Operations
|
||||
"org.freedesktop.portal.Documents"
|
||||
"org.freedesktop.portal.Documents.*"
|
||||
"org.freedesktop.portal.FileChooser"
|
||||
"org.freedesktop.portal.FileChooser.*"
|
||||
"org.freedesktop.portal.FileTransfer"
|
||||
"org.freedesktop.portal.FileTransfer.*"
|
||||
|
||||
# Notifications & Printing
|
||||
"org.freedesktop.portal.Notification"
|
||||
"org.freedesktop.portal.Notification.*"
|
||||
"org.freedesktop.portal.Print"
|
||||
"org.freedesktop.portal.Print.*"
|
||||
|
||||
# Open/Launch Handlers
|
||||
"org.freedesktop.portal.OpenURI"
|
||||
"org.freedesktop.portal.OpenURI.*"
|
||||
"org.freedesktop.portal.Email.ComposeEmail"
|
||||
|
||||
# Input Methods
|
||||
"org.freedesktop.portal.Fcitx"
|
||||
"org.freedesktop.portal.Fcitx.*"
|
||||
"org.freedesktop.portal.IBus"
|
||||
"org.freedesktop.portal.IBus.*"
|
||||
|
||||
# Secrets (Keyring)
|
||||
"org.freedesktop.portal.Secret"
|
||||
"org.freedesktop.portal.Secret.RetrieveSecret"
|
||||
|
||||
# Get/Update GlobalShortcuts
|
||||
# "org.freedesktop.portal.GlobalShortcuts"
|
||||
# "org.freedesktop.portal.GlobalShortcuts.*"
|
||||
|
||||
# -- get the user's location
|
||||
# "org.freedesktop.portal.Location"
|
||||
# "org.freedesktop.portal.Location.*"
|
||||
|
||||
# -- inhibit the user session from ending, suspending, idling or getting switched away.
|
||||
"org.freedesktop.portal.Inhibit"
|
||||
"org.freedesktop.portal.Inhibit.*"
|
||||
|
||||
# Generic Request Fallback
|
||||
"org.freedesktop.portal.Request"
|
||||
];
|
||||
};
|
||||
|
||||
# 'broadcast' rules permit receiving signals from D-Bus names.
|
||||
rules.broadcast = {
|
||||
"org.freedesktop.portal.*" = [ "@/org/freedesktop/portal/*" ];
|
||||
};
|
||||
args = [
|
||||
"--filter"
|
||||
"--sloppy-names"
|
||||
];
|
||||
};
|
||||
|
||||
etc.sslCertificates.enable = true;
|
||||
bubblewrap = {
|
||||
network = lib.mkDefault true;
|
||||
|
||||
# Desktop-wide mounts (Wayland/at-spi/gvfsd, the app cache, fontconfig and
|
||||
# Mesa caches, GTK config, /dev/shm) are provided by ./gui-base.nix, so
|
||||
# each path is bound exactly once. This is what keeps a read-only bind
|
||||
# from shadowing a writable one. Only app-specific mounts belong in this
|
||||
# file.
|
||||
bind.rw = with sloth; [
|
||||
[
|
||||
(mkdir appDataDir)
|
||||
xdgDataHome
|
||||
]
|
||||
[
|
||||
(mkdir appConfigDir)
|
||||
xdgConfigHome
|
||||
]
|
||||
|
||||
# FileChooser may return a Document Portal path even for a directly mapped XDG directory;
|
||||
# keep the FUSE mount writable so SaveFile can create the selected file.
|
||||
(sloth.concat' sloth.runtimeDir "/doc")
|
||||
|
||||
(sloth.concat' sloth.runtimeDir "/dconf")
|
||||
|
||||
(sloth.concat' sloth.xdgCacheHome "/mesa_shader_cache_db")
|
||||
(sloth.concat' sloth.xdgCacheHome "/radv_builtin_shaders")
|
||||
];
|
||||
bind.ro = [
|
||||
(sloth.concat' sloth.xdgConfigHome "/kdeglobals")
|
||||
(sloth.concat' sloth.xdgConfigHome "/dconf")
|
||||
];
|
||||
bind.dev = map (id: "/dev/video${toString id}") (lib.lists.range 0 9);
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
# Shared desktop runtime; portal and native services are opt-in. No host GVfs,
|
||||
# shared caches, raw camera or PipeWire socket.
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
sloth,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
./portal-groups.nix
|
||||
./desktop-integration.nix
|
||||
];
|
||||
gpu = {
|
||||
enable = lib.mkDefault true;
|
||||
provider = "nixos";
|
||||
bundlePackage = pkgs.mesa.drivers;
|
||||
};
|
||||
fonts = {
|
||||
enable = true;
|
||||
# Preserve nixpak's default coverage and add Chinese/Japanese/Korean glyphs.
|
||||
fonts = with pkgs; [
|
||||
cantarell-fonts
|
||||
dejavu_fonts
|
||||
liberation_ttf
|
||||
gyre-fonts
|
||||
source-sans
|
||||
source-code-pro
|
||||
noto-fonts-color-emoji
|
||||
noto-fonts-cjk-sans
|
||||
];
|
||||
};
|
||||
locale.enable = true;
|
||||
dbus = {
|
||||
policies = {
|
||||
"${config.flatpak.appId}" = "own";
|
||||
"${config.flatpak.appId}.*" = "own";
|
||||
};
|
||||
rules.call."org.freedesktop.portal.Desktop" = [
|
||||
"org.freedesktop.DBus.Properties.Get@/org/freedesktop/portal/desktop"
|
||||
"org.freedesktop.DBus.Properties.GetAll@/org/freedesktop/portal/desktop"
|
||||
"org.freedesktop.portal.Settings.Read@/org/freedesktop/portal/desktop"
|
||||
"org.freedesktop.portal.Settings.ReadAll@/org/freedesktop/portal/desktop"
|
||||
"org.freedesktop.portal.Request.Close@/org/freedesktop/portal/desktop/request/*"
|
||||
"org.freedesktop.portal.Session.Close@/org/freedesktop/portal/desktop/session/*"
|
||||
];
|
||||
rules.broadcast."org.freedesktop.portal.Desktop" = [
|
||||
"org.freedesktop.portal.Settings.SettingChanged@/org/freedesktop/portal/desktop"
|
||||
"@/org/freedesktop/portal/desktop/*"
|
||||
];
|
||||
};
|
||||
bubblewrap = {
|
||||
network = lib.mkDefault false;
|
||||
newSession = true;
|
||||
dieWithParent = true;
|
||||
clearEnv = true;
|
||||
sockets = {
|
||||
wayland = true;
|
||||
x11 = lib.mkDefault false;
|
||||
pulse = lib.mkDefault true;
|
||||
pipewire = false;
|
||||
};
|
||||
bind.rw = with sloth; [
|
||||
[
|
||||
(mkdir appCacheDir)
|
||||
xdgCacheHome
|
||||
]
|
||||
[
|
||||
(mkdir appConfigDir)
|
||||
xdgConfigHome
|
||||
]
|
||||
[
|
||||
(mkdir appDataDir)
|
||||
xdgDataHome
|
||||
]
|
||||
[
|
||||
(mkdir (concat' appDir "/state"))
|
||||
xdgStateHome
|
||||
]
|
||||
(concat' runtimeDir "/doc")
|
||||
# Chrome/Firefox/Telegram/etc. use /tmp for single-instance IPC. Share
|
||||
# this directory across launches of the same app only; app IDs differ.
|
||||
[
|
||||
(mkdir (concat' runtimeDir "/nixpak-${config.flatpak.appId}-tmp"))
|
||||
"/tmp"
|
||||
]
|
||||
];
|
||||
bind.ro = [
|
||||
"/etc/localtime"
|
||||
"/etc/zoneinfo"
|
||||
(sloth.concat' sloth.xdgConfigHome "/gtk-2.0")
|
||||
(sloth.concat' sloth.xdgConfigHome "/gtk-3.0")
|
||||
(sloth.concat' sloth.xdgConfigHome "/gtk-4.0")
|
||||
(sloth.concat' sloth.xdgConfigHome "/kdeglobals")
|
||||
"/sys/devices/platform"
|
||||
]
|
||||
++ lib.optionals config.gpu.enable [
|
||||
"/etc/egl"
|
||||
"/etc/static/egl"
|
||||
];
|
||||
# nixpak's NixOS GPU provider maps DRM nodes, but NVIDIA also needs its
|
||||
# dedicated interfaces. Missing nodes are skipped by --dev-bind-try.
|
||||
bind.dev = lib.optionals config.gpu.enable [
|
||||
"/dev/nvidia0"
|
||||
"/dev/nvidiactl"
|
||||
"/dev/nvidia-modeset"
|
||||
"/dev/nvidia-uvm"
|
||||
];
|
||||
tmpfs = [
|
||||
"/dev/shm"
|
||||
];
|
||||
env = {
|
||||
HOME = sloth.homeDir;
|
||||
USER = sloth.env "USER";
|
||||
XDG_RUNTIME_DIR = sloth.runtimeDir;
|
||||
XDG_CACHE_HOME = sloth.xdgCacheHome;
|
||||
XDG_CONFIG_HOME = sloth.xdgConfigHome;
|
||||
XDG_DATA_HOME = sloth.xdgDataHome;
|
||||
XDG_STATE_HOME = sloth.xdgStateHome;
|
||||
XDG_SESSION_TYPE = "wayland";
|
||||
# Selects the Wayland path for portal clients.
|
||||
XDG_CURRENT_DESKTOP = sloth.envOr "XDG_CURRENT_DESKTOP" "niri";
|
||||
WAYLAND_DISPLAY = sloth.envOr "WAYLAND_DISPLAY" "wayland-0";
|
||||
NIXOS_OZONE_WL = "1";
|
||||
GTK_USE_PORTAL = "1";
|
||||
GTK_A11Y = sloth.envOr "GTK_A11Y" "0";
|
||||
XDG_DATA_DIRS = lib.makeSearchPath "share" [
|
||||
pkgs.papirus-icon-theme
|
||||
pkgs.bibata-cursors
|
||||
pkgs.shared-mime-info
|
||||
];
|
||||
XCURSOR_PATH = "${pkgs.bibata-cursors}/share/icons:${pkgs.bibata-cursors}/share/pixmaps";
|
||||
# Fontconfig configuration mounted by nixpak fonts.enable.
|
||||
FONTCONFIG_FILE = "/etc/fonts/fonts.conf";
|
||||
NIXOS_XDG_OPEN_USE_PORTAL = "1";
|
||||
}
|
||||
// lib.genAttrs [
|
||||
"LANG"
|
||||
"LANGUAGE"
|
||||
"LC_ALL"
|
||||
"LC_ADDRESS"
|
||||
"LC_COLLATE"
|
||||
"LC_CTYPE"
|
||||
"LC_IDENTIFICATION"
|
||||
"LC_MEASUREMENT"
|
||||
"LC_MESSAGES"
|
||||
"LC_MONETARY"
|
||||
"LC_NAME"
|
||||
"LC_NUMERIC"
|
||||
"LC_PAPER"
|
||||
"LC_TELEPHONE"
|
||||
"LC_TIME"
|
||||
"XCURSOR_THEME"
|
||||
"XCURSOR_SIZE"
|
||||
"NO_AT_BRIDGE"
|
||||
"QT_QPA_PLATFORMTHEME"
|
||||
"QT_WAYLAND_DISABLE_WINDOWDECORATION"
|
||||
] (name: sloth.envOr name "");
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
# Non-portal desktop services are separate opt-ins from portal capabilities.
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
sloth,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.desktopIntegration;
|
||||
appId = config.flatpak.appId;
|
||||
shortName = lib.last (lib.splitString "." appId);
|
||||
in
|
||||
{
|
||||
options.desktopIntegration = {
|
||||
notifications = lib.mkEnableOption "native desktop notifications";
|
||||
tray = lib.mkEnableOption "StatusNotifier tray integration";
|
||||
mpris = lib.mkEnableOption "MPRIS media controls for this application";
|
||||
accessibility = lib.mkEnableOption "the accessibility bus and AT-SPI socket";
|
||||
};
|
||||
config = lib.mkMerge [
|
||||
(lib.mkIf cfg.notifications {
|
||||
dbus.policies."org.freedesktop.Notifications" = "talk";
|
||||
})
|
||||
(lib.mkIf cfg.tray {
|
||||
dbus.policies = {
|
||||
# Qt probes NameHasOwner/GetNameOwner before registering its SNI.
|
||||
"org.freedesktop.DBus" = "talk";
|
||||
"org.kde.StatusNotifierWatcher" = "talk";
|
||||
}
|
||||
# SNI name is <pid>-<Qt tray instance ID>; WeChat 4.1.13 uses <pid>-2.
|
||||
// builtins.listToAttrs (
|
||||
lib.concatMap (
|
||||
id:
|
||||
map (
|
||||
instance: lib.nameValuePair "org.kde.StatusNotifierItem-${toString id}-${toString instance}" "own"
|
||||
) (lib.range 1 5)
|
||||
) (lib.range 2 5)
|
||||
);
|
||||
})
|
||||
(lib.mkIf cfg.mpris {
|
||||
# Chromium's MPRIS name is lowercase even though its app ID ends in
|
||||
# Chromium; retain the full ID and toolkit spelling as well.
|
||||
dbus.policies = builtins.listToAttrs (
|
||||
lib.concatMap
|
||||
(name: [
|
||||
(lib.nameValuePair "org.mpris.MediaPlayer2.${name}" "own")
|
||||
(lib.nameValuePair "org.mpris.MediaPlayer2.${name}.*" "own")
|
||||
])
|
||||
(
|
||||
lib.unique [
|
||||
appId
|
||||
shortName
|
||||
(lib.toLower shortName)
|
||||
]
|
||||
)
|
||||
);
|
||||
})
|
||||
(lib.mkIf cfg.accessibility {
|
||||
dbus.policies."org.a11y.Bus" = "see";
|
||||
dbus.rules.call."org.a11y.Bus" = [
|
||||
"org.a11y.Bus.GetAddress@/org/a11y/bus"
|
||||
"org.freedesktop.DBus.Properties.Get@/org/a11y/bus"
|
||||
];
|
||||
bubblewrap.bind.ro = [ (sloth.concat' sloth.runtimeDir "/at-spi/bus") ];
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
# Fork of nixpak/pkgs's gui-base.nix: the shared GUI runtime plumbing (GPU,
|
||||
# fonts, desktop caches, /dev/shm, and the audio/display sockets) that lets a
|
||||
# GUI app run under bubblewrap. Paths bound here must not be bound again in
|
||||
# ./custom-policy.nix or the app configs: nixpak emits read-only binds last, so
|
||||
# a duplicate read-only bind would silently shadow a writable one.
|
||||
#
|
||||
# https://github.com/nixpak/pkgs/blob/master/pkgs/modules/gui-base.nix
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
sloth,
|
||||
...
|
||||
}:
|
||||
let
|
||||
envSuffix = envKey: suffix: sloth.concat' (sloth.env envKey) suffix;
|
||||
# nixpak cannot pass a variable only when it is set, so an unset one arrives
|
||||
# empty; only list variables whose readers treat empty as unset.
|
||||
passHostEnv = names: lib.genAttrs names (name: sloth.envOr name "");
|
||||
# cursor & icon's theme should be the same as the host's one.
|
||||
cursorTheme = pkgs.bibata-cursors;
|
||||
iconTheme = pkgs.papirus-icon-theme;
|
||||
in
|
||||
{
|
||||
config = {
|
||||
dbus.policies = {
|
||||
"${config.flatpak.appId}" = "own";
|
||||
# we add other policies in ./custom-policy.nix
|
||||
};
|
||||
# https://github.com/nixpak/nixpak/blob/master/modules/gpu.nix
|
||||
# The NixOS provider exposes host drivers at /run/opengl-driver (read-only)
|
||||
# and all /dev/dri nodes (device access), including Intel, AMD and Asahi DRM devices.
|
||||
# Nixpak also maps /sys/dev/char and /sys/devices/pci0000:00 read-only for discovery.
|
||||
# These mappings grant access; they do not select the GPU used by an application.
|
||||
gpu = {
|
||||
enable = lib.mkDefault true;
|
||||
provider = "nixos";
|
||||
bundlePackage = pkgs.mesa.drivers;
|
||||
};
|
||||
# https://github.com/nixpak/nixpak/blob/master/modules/gui/fonts.nix
|
||||
# it works not well, bind system's /etc/fonts directly instead
|
||||
fonts.enable = false;
|
||||
# https://github.com/nixpak/nixpak/blob/master/modules/locale.nix
|
||||
locale.enable = true;
|
||||
bubblewrap = {
|
||||
# Nixpak uses --ro-bind-try / --dev-bind-try: absent host paths are skipped,
|
||||
# so shared mappings work on Intel/NVIDIA hosts and Apple Silicon alike.
|
||||
network = lib.mkDefault false;
|
||||
# setsid keeps the app off the launching terminal (TIOCSTI), and the
|
||||
# sandbox goes down with the launcher instead of outliving it.
|
||||
newSession = true;
|
||||
dieWithParent = true;
|
||||
|
||||
# nixpak binds sockets read-only, which is sufficient: connect() only needs
|
||||
# the socket inode's write bit, and the kernel's read-only-filesystem check
|
||||
# does not apply to sockets. Every app here is Wayland-only, so X11 stays
|
||||
# disabled (the nixpak default, spelled out for intent) and PipeWire is
|
||||
# shared for screensharing.
|
||||
sockets = {
|
||||
wayland = true;
|
||||
pipewire = true;
|
||||
pulse = true;
|
||||
x11 = false;
|
||||
};
|
||||
|
||||
bind.rw = [
|
||||
[
|
||||
(envSuffix "HOME" "/.var/app/${config.flatpak.appId}/cache")
|
||||
sloth.xdgCacheHome
|
||||
]
|
||||
(sloth.concat' sloth.xdgCacheHome "/fontconfig")
|
||||
(sloth.concat' sloth.xdgCacheHome "/mesa_shader_cache")
|
||||
|
||||
# gvfsd is a directory that holds FUSE mounts, not a socket, so it must
|
||||
# stay writable.
|
||||
(envSuffix "XDG_RUNTIME_DIR" "/gvfsd")
|
||||
];
|
||||
bind.ro = [
|
||||
# Sockets only need to be connectable, and connect() works on a read-only
|
||||
# bind (see the note on `sockets` above).
|
||||
(envSuffix "XDG_RUNTIME_DIR" "/at-spi/bus")
|
||||
|
||||
(sloth.concat' sloth.xdgConfigHome "/gtk-2.0")
|
||||
(sloth.concat' sloth.xdgConfigHome "/gtk-3.0")
|
||||
(sloth.concat' sloth.xdgConfigHome "/gtk-4.0")
|
||||
(sloth.concat' sloth.xdgConfigHome "/fontconfig")
|
||||
|
||||
"/etc/fonts" # for fontconfig
|
||||
"/etc/localtime" # this is a symlink to /etc/zoneinfo/xxx
|
||||
"/etc/zoneinfo"
|
||||
|
||||
# Host EGL configuration (read-only); needed by some driver setups.
|
||||
"/etc/egl"
|
||||
"/etc/static/egl"
|
||||
|
||||
# Asahi GPU discovery follows /sys/dev/char links into platform devices.
|
||||
# Expose their targets read-only; this includes non-GPU platform metadata too.
|
||||
"/sys/devices/platform"
|
||||
];
|
||||
bind.dev = [
|
||||
"/dev/shm" # Shared Memory
|
||||
|
||||
# Additional NVIDIA interfaces, retained for primary-GPU and offload use.
|
||||
# Unlike /dev/dri above, numbered NVIDIA nodes are not mapped as a directory.
|
||||
"/dev/nvidia0" # GPU index 0; additional GPUs need explicit mappings.
|
||||
"/dev/nvidiactl" # Driver control interface.
|
||||
"/dev/nvidia-modeset" # Display mode setting.
|
||||
"/dev/nvidia-uvm" # Unified virtual memory.
|
||||
];
|
||||
|
||||
tmpfs = [
|
||||
"/tmp"
|
||||
];
|
||||
|
||||
# Start from an empty environment so host secrets (tokens, credentials)
|
||||
# stay outside; everything the apps need is listed here.
|
||||
clearEnv = true;
|
||||
env = {
|
||||
HOME = sloth.homeDir;
|
||||
USER = sloth.env "USER";
|
||||
XDG_RUNTIME_DIR = sloth.runtimeDir;
|
||||
XDG_CACHE_HOME = sloth.xdgCacheHome;
|
||||
XDG_CONFIG_HOME = sloth.xdgConfigHome;
|
||||
XDG_DATA_HOME = sloth.xdgDataHome;
|
||||
XDG_STATE_HOME = sloth.xdgStateHome;
|
||||
# Same fallback as nixpak's Wayland socket bind, so they always agree.
|
||||
WAYLAND_DISPLAY = sloth.envOr "WAYLAND_DISPLAY" "wayland-0";
|
||||
XDG_SESSION_TYPE = "wayland";
|
||||
# GTK reads an empty GTK_A11Y as an unknown backend; "0" is its unset value.
|
||||
GTK_A11Y = sloth.envOr "GTK_A11Y" "0";
|
||||
|
||||
XDG_DATA_DIRS = lib.mkForce (
|
||||
lib.makeSearchPath "share" [
|
||||
iconTheme
|
||||
cursorTheme
|
||||
pkgs.shared-mime-info
|
||||
]
|
||||
);
|
||||
XCURSOR_PATH = lib.mkForce (
|
||||
lib.concatStringsSep ":" [
|
||||
"${cursorTheme}/share/icons"
|
||||
"${cursorTheme}/share/pixmaps"
|
||||
]
|
||||
);
|
||||
}
|
||||
// passHostEnv [
|
||||
"LANG"
|
||||
"LANGUAGE"
|
||||
"LC_ALL"
|
||||
"LC_ADDRESS"
|
||||
"LC_COLLATE"
|
||||
"LC_CTYPE"
|
||||
"LC_IDENTIFICATION"
|
||||
"LC_MEASUREMENT"
|
||||
"LC_MESSAGES"
|
||||
"LC_MONETARY"
|
||||
"LC_NAME"
|
||||
"LC_NUMERIC"
|
||||
"LC_PAPER"
|
||||
"LC_TELEPHONE"
|
||||
"LC_TIME"
|
||||
|
||||
"NO_AT_BRIDGE"
|
||||
"NIXOS_XDG_OPEN_USE_PORTAL"
|
||||
"NIXOS_OZONE_WL" # QQ's wrapper adds its Wayland flags only when set
|
||||
"XCURSOR_THEME"
|
||||
"XCURSOR_SIZE"
|
||||
"QT_QPA_PLATFORMTHEME"
|
||||
"QT_WAYLAND_DISABLE_WINDOWDECORATION"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
# xdg-dbus-proxy rules target interfaces on the Desktop bus name, not fictitious
|
||||
# per-interface bus names. Each capability is opt-in.
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
groups = {
|
||||
fileChooser = [
|
||||
"FileChooser.OpenFile"
|
||||
"FileChooser.SaveFile"
|
||||
"FileChooser.SaveFiles"
|
||||
];
|
||||
openUri = [
|
||||
"OpenURI.OpenURI"
|
||||
"OpenURI.OpenFile"
|
||||
"OpenURI.OpenDirectory"
|
||||
];
|
||||
notifications = [
|
||||
"Notification.AddNotification"
|
||||
"Notification.RemoveNotification"
|
||||
];
|
||||
screenCast = [
|
||||
"ScreenCast.CreateSession"
|
||||
"ScreenCast.SelectSources"
|
||||
"ScreenCast.Start"
|
||||
"ScreenCast.OpenPipeWireRemote"
|
||||
];
|
||||
camera = [
|
||||
"Camera.AccessCamera"
|
||||
"Camera.OpenPipeWireRemote"
|
||||
];
|
||||
print = [
|
||||
"Print.PreparePrint"
|
||||
"Print.Print"
|
||||
];
|
||||
secret = [ "Secret.RetrieveSecret" ];
|
||||
inhibit = [
|
||||
"Inhibit.Inhibit"
|
||||
"Inhibit.CreateMonitor"
|
||||
"Inhibit.QueryEndResponse"
|
||||
];
|
||||
};
|
||||
in
|
||||
{
|
||||
options.portalGroups =
|
||||
lib.mapAttrs (name: _: lib.mkEnableOption "the ${name} desktop portal methods") groups
|
||||
// {
|
||||
inputMethod = lib.mkEnableOption "Fcitx and IBus input method portals";
|
||||
};
|
||||
config = {
|
||||
dbus.rules.call."org.freedesktop.portal.Desktop" = lib.concatLists (
|
||||
lib.mapAttrsToList (
|
||||
name: methods:
|
||||
lib.optionals config.portalGroups.${name} (
|
||||
map (method: "org.freedesktop.portal.${method}@/org/freedesktop/portal/desktop") methods
|
||||
)
|
||||
) groups
|
||||
);
|
||||
dbus.policies = lib.mkIf config.portalGroups.inputMethod {
|
||||
"org.freedesktop.portal.Fcitx" = "talk";
|
||||
"org.freedesktop.portal.IBus" = "talk";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# The existing FHS layer owns WeChat's HOME remapping and xwechat_files link.
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
sloth,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
./desktop-base.nix
|
||||
./network.nix
|
||||
];
|
||||
pasta.mode = "isolate";
|
||||
desktopIntegration.tray = true;
|
||||
portalGroups = {
|
||||
fileChooser = true;
|
||||
openUri = true;
|
||||
notifications = true;
|
||||
screenCast = true;
|
||||
camera = true;
|
||||
inputMethod = true;
|
||||
};
|
||||
bubblewrap = {
|
||||
sockets.x11 = true;
|
||||
# libdbus aborts without /etc/machine-id; use a synthetic app-scoped value.
|
||||
bind.ro = [
|
||||
[
|
||||
(toString (
|
||||
pkgs.writeText "wechat-machine-id" "${
|
||||
builtins.substring 0 32 (builtins.hashString "sha256" config.flatpak.appId)
|
||||
}\n"
|
||||
))
|
||||
"/etc/machine-id"
|
||||
]
|
||||
];
|
||||
# The inner FHS /home reset replaces base XDG mounts with this backing tree.
|
||||
bind.rw = lib.mkForce [
|
||||
(sloth.mkdir (sloth.concat' sloth.xdgDocumentsDir "/WeChat_Data"))
|
||||
(sloth.concat' sloth.runtimeDir "/doc")
|
||||
[
|
||||
(sloth.mkdir (sloth.concat' sloth.runtimeDir "/nixpak-${config.flatpak.appId}-tmp"))
|
||||
"/tmp"
|
||||
]
|
||||
];
|
||||
env = {
|
||||
DISPLAY = sloth.envOr "DISPLAY" "";
|
||||
XAUTHORITY = sloth.envOr "XAUTHORITY" (sloth.concat' sloth.homeDir "/.Xauthority");
|
||||
XDG_DOCUMENTS_DIR = sloth.xdgDocumentsDir;
|
||||
# The existing pre-FHS script uses mkdir/ln before /usr/bin exists.
|
||||
PATH = lib.makeBinPath [ pkgs.coreutils ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# Zoom keeps the upstream FHS runtime inside the outer nixpak boundary.
|
||||
{ lib, sloth, ... }:
|
||||
{
|
||||
imports = [
|
||||
./desktop-base.nix
|
||||
./network.nix
|
||||
];
|
||||
pasta.mode = "isolate";
|
||||
desktopIntegration.tray = true;
|
||||
portalGroups = {
|
||||
fileChooser = true;
|
||||
openUri = true;
|
||||
notifications = true;
|
||||
screenCast = true;
|
||||
camera = true;
|
||||
inhibit = true;
|
||||
inputMethod = true;
|
||||
};
|
||||
bubblewrap = {
|
||||
# Screen sharing enumerates DRM connectors through sysfs. Missing this
|
||||
# directory makes Zoom abort with std::filesystem_error.
|
||||
bind.ro = [ "/sys/class/drm" ];
|
||||
# ZoomLauncher selects xcb; the X11 connection retains X11's display trust.
|
||||
sockets.x11 = true;
|
||||
env = {
|
||||
DISPLAY = sloth.envOr "DISPLAY" "";
|
||||
XAUTHORITY = sloth.envOr "XAUTHORITY" (sloth.concat' sloth.homeDir "/.Xauthority");
|
||||
};
|
||||
bind.dev = [
|
||||
# Zoom 7.2.1 still opens V4L2 device nodes directly for webcam capture.
|
||||
"/dev/video0"
|
||||
"/dev/video1"
|
||||
];
|
||||
bind.rw = [
|
||||
(sloth.mkdir (sloth.concat' sloth.homeDir "/.zoom"))
|
||||
(sloth.concat' sloth.xdgConfigHome "/zoomus.conf")
|
||||
(sloth.concat' sloth.xdgConfigHome "/zoom.conf")
|
||||
(sloth.mkdir (sloth.concat' sloth.xdgDocumentsDir "/Zoom"))
|
||||
sloth.xdgDownloadDir
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -26,11 +26,26 @@ let
|
||||
flatpak.appId = appId;
|
||||
|
||||
imports = [
|
||||
./modules/gui-base.nix
|
||||
./modules/desktop-base.nix
|
||||
./modules/network.nix
|
||||
./modules/custom-policy.nix
|
||||
];
|
||||
|
||||
portalGroups = {
|
||||
fileChooser = true;
|
||||
openUri = true;
|
||||
notifications = true;
|
||||
screenCast = true;
|
||||
camera = true;
|
||||
inhibit = true;
|
||||
inputMethod = true;
|
||||
};
|
||||
desktopIntegration = {
|
||||
notifications = true;
|
||||
tray = true;
|
||||
mpris = true;
|
||||
accessibility = true;
|
||||
};
|
||||
|
||||
bubblewrap = {
|
||||
# To trace all the home files QQ accesses, you can use the following nushell command:
|
||||
# just trace-access qq
|
||||
|
||||
@@ -13,9 +13,8 @@ let
|
||||
{ sloth, ... }:
|
||||
{
|
||||
imports = [
|
||||
./modules/gui-base.nix
|
||||
./modules/desktop-base.nix
|
||||
./modules/network.nix
|
||||
./modules/custom-policy.nix
|
||||
];
|
||||
app.package = telegram-desktop;
|
||||
flatpak = {
|
||||
@@ -30,6 +29,22 @@ let
|
||||
};
|
||||
};
|
||||
|
||||
portalGroups = {
|
||||
fileChooser = true;
|
||||
openUri = true;
|
||||
notifications = true;
|
||||
screenCast = true;
|
||||
camera = true;
|
||||
inhibit = true;
|
||||
inputMethod = true;
|
||||
};
|
||||
desktopIntegration = {
|
||||
notifications = true;
|
||||
tray = true;
|
||||
mpris = true;
|
||||
accessibility = true;
|
||||
};
|
||||
|
||||
bubblewrap = {
|
||||
# nixpak exposes /.flatpak-info, so Telegram uses FLATPAK_ID as its Wayland app ID.
|
||||
env.FLATPAK_ID = appId;
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
lib,
|
||||
bwraps,
|
||||
mkNixPak,
|
||||
buildEnv,
|
||||
makeDesktopItem,
|
||||
runCommandLocal,
|
||||
...
|
||||
}:
|
||||
let
|
||||
appId = "com.tencent.WeChat";
|
||||
# As with Zoom, preserve the outer metadata file across the inner FHS layer.
|
||||
# Keep the existing AppImage runtime and WeChat_Data layout unchanged.
|
||||
package = bwraps.wechat.overrideAttrs (old: {
|
||||
# Refuse unexpected layouts before the existing ln -snf can modify them.
|
||||
extraPreBwrapCmds = ''
|
||||
wechat_link="''${XDG_DOCUMENTS_DIR}/WeChat_Data/home/xwechat_files"
|
||||
wechat_files="''${XDG_DOCUMENTS_DIR}/WeChat_Data/xwechat_files"
|
||||
if [[ -e "$wechat_link" || -L "$wechat_link" ]]; then
|
||||
if [[ ! -L "$wechat_link" || "$(readlink "$wechat_link")" != "$wechat_files" ]]; then
|
||||
echo 'WeChat: unexpected xwechat_files layout; refusing to change existing data.' >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
''
|
||||
+ (old.extraPreBwrapCmds or "");
|
||||
extraBwrapArgs = (old.extraBwrapArgs or [ ]) ++ [
|
||||
"--ro-bind /.flatpak-info /.flatpak-info"
|
||||
# The inner /home reset must not hide an Xauthority file stored there.
|
||||
''--ro-bind-try "''${XAUTHORITY}" "''${XAUTHORITY}"''
|
||||
];
|
||||
});
|
||||
wrapped = mkNixPak {
|
||||
config = {
|
||||
imports = [ ./modules/wechat-policy.nix ];
|
||||
app = {
|
||||
inherit package;
|
||||
binPath = "bin/wechat";
|
||||
};
|
||||
flatpak = { inherit appId; };
|
||||
};
|
||||
};
|
||||
exe = lib.getExe wrapped.config.script;
|
||||
assets = runCommandLocal "wechat-desktop-assets" { } ''
|
||||
mkdir -p $out/share
|
||||
ln -s ${package}/share/pixmaps $out/share/pixmaps
|
||||
'';
|
||||
in
|
||||
buildEnv {
|
||||
inherit (wrapped.config.script) name passthru;
|
||||
meta = wrapped.config.script.meta // {
|
||||
mainProgram = "wechat";
|
||||
};
|
||||
paths = [
|
||||
wrapped.config.script
|
||||
assets
|
||||
(makeDesktopItem {
|
||||
name = "wechat";
|
||||
desktopName = "WeChat";
|
||||
exec = "${exe} %U";
|
||||
icon = "wechat";
|
||||
terminal = false;
|
||||
startupWMClass = "wechat";
|
||||
categories = [
|
||||
"Network"
|
||||
"InstantMessaging"
|
||||
];
|
||||
mimeTypes = [ "x-scheme-handler/weixin" ];
|
||||
extraConfig.X-Flatpak = appId;
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
lib,
|
||||
zoom-us,
|
||||
buildFHSEnv,
|
||||
mkNixPak,
|
||||
buildEnv,
|
||||
makeDesktopItem,
|
||||
runCommandLocal,
|
||||
...
|
||||
}:
|
||||
let
|
||||
appId = "us.zoom.Zoom";
|
||||
# Preserve the upstream FHS wrapper and /opt mount; its root mounts omit the
|
||||
# metadata file needed for portal identification inside the nested sandbox.
|
||||
package = zoom-us.override {
|
||||
xdgDesktopPortalSupport = true;
|
||||
buildFHSEnv =
|
||||
args:
|
||||
buildFHSEnv (
|
||||
args
|
||||
// {
|
||||
extraBwrapArgs = (args.extraBwrapArgs or [ ]) ++ [
|
||||
"--ro-bind /.flatpak-info /.flatpak-info"
|
||||
];
|
||||
}
|
||||
);
|
||||
};
|
||||
wrapped = mkNixPak {
|
||||
config = {
|
||||
imports = [ ./modules/zoom-policy.nix ];
|
||||
app = {
|
||||
inherit package;
|
||||
binPath = "bin/zoom";
|
||||
};
|
||||
flatpak = { inherit appId; };
|
||||
};
|
||||
};
|
||||
exe = lib.getExe wrapped.config.script;
|
||||
assets = runCommandLocal "zoom-desktop-assets" { } ''
|
||||
mkdir -p $out/share
|
||||
for directory in icons pixmaps; do
|
||||
if [ -d ${package}/share/$directory ]; then
|
||||
ln -s ${package}/share/$directory $out/share/$directory
|
||||
fi
|
||||
done
|
||||
'';
|
||||
in
|
||||
buildEnv {
|
||||
inherit (wrapped.config.script) name meta passthru;
|
||||
paths = [
|
||||
wrapped.config.script
|
||||
assets
|
||||
(makeDesktopItem {
|
||||
name = "Zoom";
|
||||
desktopName = "Zoom Workplace";
|
||||
comment = "Zoom Video Conference";
|
||||
exec = "${exe} %U";
|
||||
icon = "Zoom";
|
||||
terminal = false;
|
||||
startupWMClass = "zoom";
|
||||
categories = [ "Network" ];
|
||||
mimeTypes = [
|
||||
"x-scheme-handler/zoommtg"
|
||||
"x-scheme-handler/zoomus"
|
||||
"application/x-zoom"
|
||||
];
|
||||
extraConfig.X-Flatpak = appId;
|
||||
})
|
||||
];
|
||||
}
|
||||
Reference in new issue
Block a user