Files
nix-files/hosts/common/programs/assorted.nix
Colin 3c41a0bd29 blueberry: enable bluetooth.service
i'm still not able to connect to a bluetooth speaker yet...
2025-01-26 05:34:56 +00:00

1301 lines
46 KiB
Nix

{ config, lib, pkgs, ... }:
let
declPackageSet = pkgs: {
packageUnwrapped = null;
suggestedPrograms = pkgs;
};
in
{
sane.programs = {
# PACKAGE SETS
# packages which are unavoidably enabled system-wide by default nixos deployment
# the only real reason to make a proper package set out of these is for documentation
# and to allow them to be easily replaced by sandboxed versions.
nixosBuiltins = {
enableFor.system = lib.mkDefault true;
packageUnwrapped = null;
suggestedPrograms = [ "nixosBuiltinsNet" ]
++ lib.optionals config.networking.wireless.enable [ "nixosBuiltinsWireless" ];
};
nixosBuiltinsNet = declPackageSet [
# from nixos/modules/tasks/network-interfaces.nix
"host"
"iproute2"
"iputils"
"nettools"
];
nixosBuiltinsWireless = declPackageSet [
# from nixos/modules/tasks/network-interfaces.nix
# if config.networking.wireless.enable
"wirelesstools"
"iw"
];
sysadminUtils = declPackageSet [
"ausyscall"
"bandwhich" # network/bandwidth monitor
"bridge-utils" # for brctl; debug linux "bridge" inet devices
"btrfs-progs"
"cacert.unbundled" # some services require unbundled /etc/ssl/certs
"captree"
"cryptsetup"
"curl"
"ddrescue"
"dig"
"dmidecode" # to query low-level hardware details like RAM modules
"dtc" # device tree [de]compiler
"e2fsprogs" # resize2fs
"efibootmgr"
"erdtree" # like normal `tree` but colorful & prints sizes
"errno"
"ethtool"
"evtest"
"fatresize"
"fd"
"fftest" # for debugging moby haptics/vibrator, mostly
"file"
"forkstat" # monitor every spawned/forked process
"free"
# "fwupd"
"gawk"
"gdb" # to debug segfaults
"git"
"gptfdisk" # gdisk
"hdparm"
"hping"
"htop"
"iftop"
"inetutils" # for telnet
"iotop"
"iptables"
# "iw"
"jq"
"killall"
"less"
"lftp"
"libcap_ng" # for `netcap`, `pscap`, `captest`
"libgpiod" # `gpiodetect`, `gpioinfo`, `gpiomon`, ...
"lsof"
"man-db" # critical for `man -k` or `apropos` to work
"man-pages"
"man-pages-posix"
# "miniupnpc"
"mmcli"
"nano"
# "ncdu" # ncurses disk usage. doesn't cross compile (zig)
"neovim"
"netcat"
"nethogs"
"nix"
"nix-tree"
"nmap"
"nmcli"
"nmon"
"nvimpager"
"nvme-cli" # nvme
# "openssl"
"parted"
"pciutils"
"picocom" # serial TTY
"powertop"
"procs" # a better `ps`
"pstree"
"ripgrep"
"rsync"
# "s6-rc" # service manager
# "screen"
"smartmontools" # smartctl
# "socat"
"strace"
"subversion"
"tcpdump"
"tree"
"unixtools.ps"
"unixtools.sysctl"
"unixtools.xxd"
"usbutils" # lsusb
"util-linux" # lsblk, lscpu, etc
"valgrind"
"watch"
"wget"
# "wirelesstools" # iwlist
# "xq" # jq for XML
# "zfs" # doesn't cross-compile (requires samba)
];
sysadminExtraUtils = declPackageSet [
"sqlite" # to debug sqlite3 databases
];
# TODO: split these into smaller groups.
# - moby doesn't want a lot of these.
# - categories like
# - dev?
# - debugging?
consoleUtils = declPackageSet [
"alsa-utils" # for aplay, speaker-test
# "cdrtools"
# "clinfo"
# "dmidecode"
"dtrx" # `unar` alternative, "Do The Right eXtraction"
# "efivar"
"exiftool"
"eza" # a better 'ls'
# "flashrom"
"git" # needed as a user package, for config.
# "glib" # for `gsettings`
# "gnupg"
# "gocryptfs"
# "gopass"
# "gopass-jsonapi"
# "helix" # text editor
"htop" # needed as a user package, for ~/.config/htop
# "libsecret" # for managing user keyrings (secret-tool)
# "lm_sensors" # for sensors-detect
# "lshw"
# "memtester"
"mercurial" # hg
"mimeo" # like xdg-open
"neovim" # needed as a user package, for swap persistence
"nix" # needed as user package, for ~/.cache/nix persistence
# "nettools"
# "networkmanager"
# "nixos-generators"
# "node2nix"
# "oathToolkit" # for oathtool
"objdump"
# "ponymix"
"pulsemixer"
"python3-repl"
# "python3.pkgs.eyeD3" # music tagging
"ripgrep" # needed as a user package so that its user-level config file can be installed
# "rsyslog" # KEEP THIS HERE if you want persistent logging (TODO: port to systemd, store in /var/log/...)
"sane-deadlines"
"sane-scripts.bittorrent"
"sane-scripts.cli"
"sane-secrets-unlock"
"sane-sysload"
"sc-im"
"snapper"
"sops" # for manually viewing secrets; outside `sane-secrets` (TODO: improve sane-secrets!)
"speedtest-cli"
# "ssh-to-age"
"strings"
"sudo"
# "tageditor" # music tagging
# "unar"
# "unzip"
"wireguard-tools" # for `wg`
"xdg-utils" # for xdg-open
# "yarn"
"zsh"
];
pcConsoleUtils = declPackageSet [
"dasht" # docset documentation viewer
# "gh" # MS GitHub cli
"haredoc"
"nix-index"
"nixfmt-rfc-style" # run `nixpkgs path/to/package.nix` before submitting stuff upstream
"nixpkgs-hammering"
"nixpkgs-review"
"qmk-udev-rules"
"sane-scripts.dev"
"sequoia"
# "via"
"wally-cli"
# "zsa-udev-rules"
];
consoleMediaUtils = declPackageSet [
# "catt" # cast videos to chromecast
# "sblast" # cast audio to UPNP/DLNA devices (via pulseaudio sink)
"ffmpeg"
"go2tv" # cast videos to UPNP/DLNA device (i.e. tv).
"imagemagick"
"sane-cast" # cast videos to UPNP/DLNA, with compatibility
"sox"
"yt-dlp"
];
pcTuiApps = declPackageSet [
"aerc" # email client
# "msmtp" # sendmail
# "offlineimap" # email mailbox sync
# "sfeed" # RSS fetcher
"visidata" # TUI spreadsheet viewer/editor
"w3m" # web browser
];
devPkgs = declPackageSet [
"cargo"
"clang"
"lua"
"nodejs"
"patchelf"
"rustc"
# "tree-sitter"
];
gameApps = declPackageSet [
"animatch"
"gnome-2048"
"hitori" # like sudoku
];
pcGameApps = declPackageSet [
# "andyetitmoves" # TODO: fix build!
# "armagetronad" # tron/lightcycles; WAN and LAN multiplayer
"celeste64"
# "cutemaze" # meh: trivial maze game; qt6 and keyboard-only
# "cuyo" # trivial puyo-puyo clone
"endless-sky" # space merchantilism/exploration
# "factorio"
# "frozen-bubble" # WAN + LAN + 1P/2P bubble bobble
"hase" # WAN worms game
# "hedgewars" # WAN + LAN worms game (5~10 people online at any moment; <https://hedgewars.org>)
# "libremines" # meh: trivial minesweeper; qt6
# "mario0" # SMB + portal
# "mindustry"
# "minesweep-rs" # CLI minesweeper
# "nethack"
# "osu-lazer"
# "pinball" # 3d pinball; kb/mouse. old sourceforge project
# "powermanga" # STYLISH space invaders derivative (keyboard-only)
"shattered-pixel-dungeon" # doesn't cross compile
# "sm64ex-coop"
"sm64coopdx"
"space-cadet-pinball" # LMB/RMB controls (bindable though. volume buttons?)
"steam"
"superTux" # keyboard-only controls
"superTuxKart" # poor FPS on pinephone
"tumiki-fighters" # keyboard-only
"vvvvvv" # keyboard-only controls
# "wine"
];
guiApps = declPackageSet [
# package sets
"gameApps"
"guiBaseApps"
];
guiBaseApps = declPackageSet [
# "abaddon" # discord client
"alacritty" # terminal emulator
"calls" # gnome calls (dialer/handler)
"dbus"
# "dconf" # or use `gsettings`, with its keyfile backend
# "delfin" # Jellyfin client
"dialect" # language translation
"dino" # XMPP client
"dissent" # Discord client (formerly known as: gtkcord4)
# "emote"
"envelope" # GTK4 email client (alpha)
# "evince" # PDF viewer
# "flare-signal" # gtk4 signal client
"fractal" # matrix client
"g4music" # local music player
# "gnome.cheese"
# "gnome-feeds" # RSS reader (with claimed mobile support)
# "gnome.file-roller"
"geary" # adaptive e-mail client; uses webkitgtk 4.1
"gnome-calculator"
"gnome-calendar"
"gnome-clocks"
"gnome-contacts"
# "gnome-podcasts"
# "gnome.gnome-system-monitor"
# "gnome.gnome-terminal" # works on phosh
"gnome-frog" # OCR/QR decoder
"gnome-maps"
"gnome-screenshot" # libcamera-based screenshotter, for debugging; should be compatible with gc2145 camera on Pinephone
"gnome-weather"
"gpodder"
"gsettings"
"gst-device-monitor" # for debugging audio/video
"gst-launch" # for debugging audio/video
# "gthumb"
# "lemoa" # lemmy app
"libcamera" # for `cam` binary (useful for debugging cameras)
"libnotify" # for notify-send; debugging
# "lollypop"
"loupe" # image viewer
"mate.engrampa" # archive manager
"mepo" # maps viewer
# "mesa-demos" # for eglinfo, glxinfo & other testing tools
"mpv"
# "networkmanagerapplet" # for nm-connection-editor GUI. XXX(2024-09-03): broken, probably by NetworkManager sandboxing
# "ntfy-sh" # notification service
"newsflash" # RSS viewer
"papers" # PDF viewer
"pavucontrol"
"powersupply" # battery viewer
"pwvucontrol" # pipewire version of pavu
# "picard" # music tagging
"sane-color-picker"
# "seahorse" # keyring/secret manager
"signal-desktop"
"snapshot" # camera app
# "spot" # Gnome Spotify client
# "sublime-music"
# "tdesktop" # broken on phosh
# "tokodon"
"tuba" # mastodon/pleroma client (stores pw in keyring)
"v4l-utils" # for `media-ctl`; to debug cameras: <https://wiki.postmarketos.org/wiki/PINE64_PinePhone_(pine64-pinephone)#Cameras>
"video-trimmer"
"vulkan-tools" # vulkaninfo
# "whalebird" # pleroma client (Electron). input is broken on phosh.
"xdg-terminal-exec"
"youtube-tui"
# "zathura" # PDF/CBZ/ePUB viewer
];
handheldGuiApps = declPackageSet [
# "celluloid" # mpv frontend
# "chatty" # matrix/xmpp/irc client (2023/12/29: disabled because broken cross build)
# "cozy" # audiobook player
"epiphany" # gnome's web browser
"foliate" # e-book reader
# "iotas" # note taking app
"komikku"
# "koreader"
"lgtrombetta-compass"
# "millipixels" # camera app (libcamera, but does not support PPP as of 2024-11-29)
"megapixels" # camera app (does not support PPP as of 2024-11-29)
"megapixels-next" # camera app (which supports PPP, as of 2024-11-29)
"notejot" # note taking, e.g. shopping list
"planify" # todo-tracker/planner
"portfolio-filemanager"
# "tangram" # web browser
"wike" # Wikipedia Reader
"xarchiver" # archiver, backup option for when engrampa UI overflows screen and is unusale (xarchiver UI fails in different ways)
];
pcGuiApps = declPackageSet [
# package sets
"pcGameApps"
"pcTuiApps"
####
"audacity"
# "blanket" # ambient noise generator
"brave" # for the integrated wallet -- as a backup
# "cantata" # music player (mpd frontend)
# "chromium" # chromium takes hours to build. brave is chromium-based, distributed in binary form, so prefer it.
# "cups"
"discord" # x86-only
# "electrum"
"element-desktop"
"firefox"
"font-manager"
# "gajim" # XMPP client. cross build tries to import host gobject-introspection types (2023/09/01)
"gimp" # broken on phosh
"google-chrome" # for maximum compatibility
# "gnome.dconf-editor"
# "gnome.file-roller"
# "gnome-disk-utility"
"gparted"
"nautilus" # file browser
# "gnome.totem" # video player, supposedly supports UPnP
"handbrake"
"inkscape"
# "jellyfin-media-player"
"kdenlive"
# "keymapp"
# "kid3" # audio tagging
"krita"
"libreoffice" # TODO: replace with an office suite that uses saner packaging?
"losslesscut-bin" # x86-only
# "makemkv" # x86-only
# "monero-gui" # x86-only
"mumble"
# "nheko" # Matrix chat client
"nicotine-plus" # soulseek client
# "obsidian"
# "openscad" # 3d modeling
# "rhythmbox" # local music player
# "slic3r"
"soundconverter"
# "spotify" # x86-only
"tor-browser" # x86-only
# "vlc"
"wireshark" # could maybe ship the cli as sysadmin pkg
# "xterm" # requires Xwayland
# "zecwallet-lite" # x86-only
# "zulip"
];
# INDIVIDUAL PACKAGE DEFINITIONS
# alsa-utils amixer, aplay, speaker-test, ...
alsa-utils.sandbox.whitelistAudio = true; #< not strictly necessary?
alsa-utils.sandbox.extraPaths = [ "/dev/snd" ];
alsa-utils.sandbox.autodetectCliPaths = "existingFile"; # for `aplay ./file.wav`
backblaze-b2 = {};
bandwhich.sandbox.capabilities = [
# it recommends these caps
# - new_raw is absolutely required
# - dac_read_search + sys_ptrace are required to associate traffic with process names
# - net_admin is... seemingly not actually required for anything?
"dac_read_search"
# "net_admin"
"net_raw"
"sys_ptrace"
];
bandwhich.sandbox.keepPids = true; #< so it can determine process names
bandwhich.sandbox.tryKeepUsers = true;
bandwhich.sandbox.net = "all";
bash-language-server.sandbox.whitelistPwd = true;
blanket.buildCost = 1;
blanket.sandbox.whitelistAudio = true;
# blanket.sandbox.whitelistDbus.user = true; #< TODO: reduce # TODO: untested
blanket.sandbox.whitelistWayland = true;
bridge-utils.sandbox.net = "all";
"cacert.unbundled".sandbox.enable = false; #< data only
cargo.persist.byStore.plaintext = [ ".cargo" ];
# probably this sandboxing is too restrictive; i'm sandboxing it for rust-analyzer / neovim LSP
cargo.sandbox.whitelistPwd = true;
cargo.sandbox.net = "all";
cargo.sandbox.extraHomePaths = [ "dev" "ref" ];
clang = {};
clang-tools.sandbox.whitelistPwd = true;
clightning-sane.sandbox.extraPaths = [
"/var/lib/clightning/bitcoin/lightning-rpc"
];
# cryptsetup: typical use is `cryptsetup open /dev/loopxyz mappedName`, and creates `/dev/mapper/mappedName`
cryptsetup.sandbox.extraPaths = [
"/dev/mapper"
"/dev/random"
"/dev/urandom"
"/run" #< it needs the whole directory, at least if using landlock
# "/proc"
"/sys/dev/block"
"/sys/devices"
];
cryptsetup.sandbox.capabilities = [ "sys_admin" ];
cryptsetup.sandbox.autodetectCliPaths = "existing";
cryptsetup.sandbox.tryKeepUsers = true;
cryptsetup.sandbox.keepIpc = true;
ddrescue.sandbox.autodetectCliPaths = "existingOrParent";
ddrescue.sandbox.tryKeepUsers = true;
delfin.buildCost = 1;
delfin.sandbox.whitelistAudio = true;
delfin.sandbox.whitelistDbus.user = true; #< TODO: reduce # else `mpris` plugin crashes the player
delfin.sandbox.whitelistDri = true;
delfin.sandbox.whitelistWayland = true;
delfin.sandbox.net = "clearnet";
# auth token, preferences
delfin.persist.byStore.private = [ ".config/delfin" ];
dig.sandbox.net = "all";
dmidecode.sandbox.extraPaths = [ "/sys/firmware/dmi" ];
# dtc -o may write a file, so needs directory access
dtc.sandbox.autodetectCliPaths = "existingFileOrParent";
duplicity = {};
e2fsprogs.sandbox.autodetectCliPaths = "existing";
efibootmgr.sandbox.extraPaths = [
"/sys/firmware/efi"
];
electrum.buildCost = 1;
electrum.sandbox.net = "all"; # TODO: probably want to make this run behind a VPN, always
electrum.sandbox.whitelistWayland = true;
electrum.persist.byStore.ephemeral = [ ".electrum" ]; #< TODO: use XDG dirs!
endless-sky.buildCost = 1;
endless-sky.persist.byStore.plaintext = [ ".local/share/endless-sky" ];
endless-sky.sandbox.mesaCacheDir = ".cache/endless-sky/mesa";
endless-sky.sandbox.whitelistAudio = true;
endless-sky.sandbox.whitelistDri = true;
endless-sky.sandbox.whitelistWayland = true;
endless-sky.packageUnwrapped = pkgs.endless-sky.overrideAttrs (base: {
nativeBuildInputs = (base.nativeBuildInputs or []) ++ [
pkgs.makeWrapper
];
postInstall = (base.postInstall or "") + ''
wrapProgram $out/bin/endless-sky --set SDL_VIDEODRIVER wayland
'';
});
# `emote` will show a first-run dialog based on what's in this directory.
# mostly, it just keeps a LRU of previously-used emotes to optimize display order.
# TODO: package [smile](https://github.com/mijorus/smile) for probably a better mobile experience.
emote.persist.byStore.plaintext = [ ".local/share/Emote" ];
erdtree.sandbox.tryKeepUsers = true; #< to keep user/group info when running as root
erdtree.sandbox.autodetectCliPaths = "existingDir";
erdtree.sandbox.whitelistPwd = true;
ethtool.sandbox.capabilities = [ "net_admin" ];
ethtool.sandbox.net = "all";
ethtool.sandbox.tryKeepUsers = true;
evtest.sandbox.autodetectCliPaths = "existingFile"; # `evtest /dev/foo` to monitor events for a specific device
evtest.sandbox.extraPaths = [
"/dev/input"
];
# eza `ls` replacement
# bwrap causes `/proc` files to be listed differently (e.g. `eza /proc/sys/net/ipv6/conf/`)
# bwrap loses group info (so files owned by other users appear as owner "nobody")
eza.sandbox.tryKeepUsers = true; #< to keep user/group info when running as root
eza.sandbox.autodetectCliPaths = "existing";
eza.sandbox.whitelistPwd = true;
# eza.sandbox.extraHomePaths = [
# # so that e.g. `eza -l ~` can show which symlink exist
# # hol' up: this is almost like just un-sandboxing it
# ".persist/ephemeral"
# ".persist/plaintext"
# ];
fatresize.sandbox.autodetectCliPaths = "parent"; # /dev/sda1 -> needs /dev/sda
fatresize.sandbox.tryKeepUsers = true;
fd.sandbox.autodetectCliPaths = "existing";
fd.sandbox.whitelistPwd = true;
fd.sandbox.extraHomePaths = [
# let it follow symlinks to non-sensitive data
".persist/ephemeral"
".persist/plaintext"
];
ffmpeg.buildCost = 1;
ffmpeg.sandbox.autodetectCliPaths = "existingFileOrParent"; # it outputs uncreated files -> parent dir needs mounting
file.sandbox.autodetectCliPaths = "existing"; #< file OR directory, yes
findutils.sandbox.enable = false; #< `find -exec FOO`, needs to exec arbitrary commands
# findutils.sandbox.autodetectCliPaths = "existing";
# findutils.sandbox.whitelistPwd = true;
# findutils.sandbox.extraHomePaths = [
# # let it follow symlinks to non-sensitive data
# ".persist/ephemeral"
# ".persist/plaintext"
# ];
font-manager.buildCost = 1;
font-manager.sandbox.mesaCacheDir = ".cache/font-manager/mesa";
font-manager.sandbox.whitelistWayland = true;
font-manager.packageUnwrapped = pkgs.rmDbusServicesInPlace (pkgs.font-manager.override {
# build without the "Google Fonts" integration feature, to save closure / avoid webkitgtk_4_0
withWebkit = false;
});
forkstat.sandbox.keepPidsAndProc = true;
forkstat.sandbox.tryKeepUsers = true;
forkstat.sandbox.net = "all"; #< it errors without this, wish i knew why
fuzzel.sandbox.whitelistWayland = true;
fuzzel.persist.byStore.private = [
# this is a file of recent selections
{ path=".cache/fuzzel"; type="file"; }
];
gawk.sandbox.wrapperType = "inplace"; # /share/gawk libraries refer to /libexec
gawk.sandbox.autodetectCliPaths = "existingFile";
geoclue2-with-demo-agent = {};
# MS GitHub stores auth token in .config
# TODO: we can populate gh's stuff statically; it even lets us use the same oauth across machines
gh.persist.byStore.private = [ ".config/gh" ];
gimp.buildCost = 1;
gimp.sandbox.whitelistX = true;
gimp.sandbox.whitelistWayland = true;
gimp.sandbox.extraHomePaths = [
"Pictures/albums"
"Pictures/cat"
"Pictures/from"
"Pictures/Photos"
"Pictures/Screenshots"
"Pictures/servo-macros"
"dev"
"ref"
"tmp"
];
gimp.sandbox.autodetectCliPaths = true;
gimp.sandbox.extraPaths = [
"/tmp" # "Cannot open display:" if it can't mount /tmp 👀
];
gimp.suggestedPrograms = [
"xwayland" #< XXX(2024-11-10): version 3.0 should support wayland, but not 2.x
];
gitea = {};
gnome-calculator.buildCost = 1;
gnome-calculator.sandbox.mesaCacheDir = ".cache/gnome-calculator/mesa"; # TODO: is this the correct app-id?
gnome-calculator.sandbox.whitelistWayland = true;
gnome-calendar.buildCost = 2; # depends on webkitgtk_6_0 via evolution-data-server
gnome-calendar.sandbox.mesaCacheDir = ".cache/gnome-calendar/mesa"; # TODO: is this the correct app-id?
# gnome-calendar surely has data to persist, but i use it strictly to do date math, not track events.
gnome-calendar.sandbox.whitelistWayland = true;
gnome-calendar.sandbox.whitelistDbus.user = true; #< TODO: reduce
gnome-calendar.suggestedPrograms = [
"evolution-data-server" #< to access/persist calendar events
];
# gnome-disks
# XXX(2024-09-02): fails to show any disks even when run as `BUNPEN_DISABLE=1 sudo -E gnome-disks`.
gnome-disk-utility.buildCost = 1;
gnome-disk-utility.sandbox.whitelistDbus.system = true;
gnome-disk-utility.sandbox.whitelistWayland = true;
gnome-disk-utility.sandbox.extraHomePaths = [
"tmp"
"use/iso"
# TODO: probably need /dev and such
];
gnome-screenshot.sandbox.method = null;
google-chrome.sandbox.enable = false; # google-chrome is my "pleeeaaase work" fallback, so let it do anything.
# gparted: run with `sudo -E gparted` (-E to keep the wayland socket)
gparted.sandbox.tryKeepUsers = true;
gparted.sandbox.capabilities = [ "dac_override" "sys_admin" ];
gparted.sandbox.extraPaths = [
"/dev" #< necessary to see any devices
"/proc" #< silences segfaults when it invokes `pidof` on its children
"/sys" #< silences "partition has been written but unable to inform the kernel ..."
];
gparted.sandbox.extraRuntimePaths = [
"dconf" #< silences "unable to create file '/run/user/colin/dconf/user': Permission denied. dconf will not work properly."
];
gparted.sandbox.whitelistWayland = true;
hping.sandbox.net = "all";
hping.sandbox.capabilities = [ "net_raw" ];
hping.sandbox.autodetectCliPaths = "existingFile"; # for sending packet data from file
hping.sandbox.tryKeepUsers = true;
# seahorse: dump gnome-keyring secrets.
seahorse.buildCost = 1;
# N.B. it can lso manage ~/.ssh keys, but i explicitly don't add those to the sandbox for now.
seahorse.sandbox.whitelistDbus.user = true; #< TODO: reduce
seahorse.sandbox.whitelistWayland = true;
gnome-2048.buildCost = 1;
gnome-2048.sandbox.whitelistWayland = true;
gnome-2048.sandbox.mesaCacheDir = ".cache/gnome-2048/mesa";
gnome-2048.persist.byStore.plaintext = [ ".local/share/gnome-2048/scores" ];
gnugrep.sandbox.autodetectCliPaths = "existing";
gnugrep.sandbox.whitelistPwd = true;
gnugrep.sandbox.extraHomePaths = [
# let it follow symlinks to non-sensitive data
".persist/ephemeral"
".persist/plaintext"
];
gnused.sandbox.autodetectCliPaths = "existingFile";
gnused.sandbox.whitelistPwd = true; #< `-i` flag creates a temporary file in pwd (?) and then moves it.
gpsd = {};
gptfdisk.sandbox.extraPaths = [
"/dev"
];
gptfdisk.sandbox.autodetectCliPaths = "existing"; #< sometimes you'll use gdisk on a device file.
# N.B.: if the user doesn't specify an output path, `grim` will output to ~/Pictures (which isn't included in this sandbox)
grim.sandbox.autodetectCliPaths = "existingOrParent";
grim.sandbox.whitelistWayland = true;
hase.buildCost = 1;
hase.sandbox.net = "clearnet";
hase.sandbox.whitelistAudio = true;
hase.sandbox.whitelistDri = true;
hase.sandbox.whitelistWayland = true;
# hdparm: has to be run as sudo. e.g. `sudo hdparm -i /dev/sda`
hdparm.sandbox.autodetectCliPaths = "existingFile";
hdparm.sandbox.tryKeepUsers = true;
# hitori rules:
# - click to shade a tile
# 1. no number may appear unshaded more than once in the same row/column
# 2. no two shaded tiles can be direct N/S/E/W neighbors
# - win once (1) and (2) are satisfied
hitori.buildCost = 1;
hitori.sandbox.whitelistWayland = true;
host.sandbox.net = "all"; #< technically, only needs to contact localhost's DNS server
iftop.sandbox.net = "all";
iftop.sandbox.capabilities = [ "net_raw" ];
iftop.sandbox.tryKeepUsers = true;
# inetutils: ping, ifconfig, hostname, traceroute, whois, ....
# N.B.: inetutils' `ping` is shadowed by iputils' ping (by nixos, intentionally).
inetutils.sandbox.net = "all";
inetutils.sandbox.capabilities = [ "net_raw" ]; # for `sudo traceroute google.com`
inetutils.sandbox.tryKeepUsers = true;
iotop.sandbox.capabilities = [ "net_admin" ];
iotop.sandbox.keepPidsAndProc = true;
iotop.sandbox.tryKeepUsers = true;
# provides `ip`, `routel`, `bridge`, others.
# landlock works fine for most of these, but `ip netns exec` wants to attach to an existing namespace (which requires sudo)
# and that means we can't use ANY sandboxer for it.
iproute2.sandbox.method = null; #< TODO: sandbox
# iproute2.sandbox.net = "all";
# iproute2.sandbox.capabilities = [ "net_admin" ];
# iproute2.sandbox.extraPaths = [
# "/run/netns" # for `ip netns ...` to work, but maybe not needed anymore?
# "/sys/class/net" # for `ip netns ...` to work
# "/var/run/netns"
# ];
iptables.sandbox.method = null; # TODO: sandbox
# iptables.sandbox.net = "all";
# iptables.sandbox.capabilities = [ "net_admin" ];
# iputils provides `ping` (and arping, clockdiff, tracepath)
iputils.sandbox.net = "all";
iputils.sandbox.capabilities = [ "net_raw" ];
iputils.sandbox.tryKeepUsers = true; # for `sudo arping 10.78.79.1`
iw.sandbox.net = "all";
iw.sandbox.capabilities = [ "net_admin" ];
iw.sandbox.tryKeepUsers = true;
jq.sandbox.autodetectCliPaths = "existingFile";
killall.sandbox.keepPidsAndProc = true;
landlock-sandboxer.sandbox.enable = false; #< sandbox helper
libcap_ng.sandbox.enable = false; # TODO: `pscap` can sandbox with bwrap, `captest` and `netcap` with landlock
libgpiod.sandbox.extraPaths = [
"/dev" # really, /dev/gpiochip*
"/sys/bus/gpio"
"/sys/dev/char"
"/sys/devices"
];
libnotify.sandbox.whitelistDbus.user = true; #< TODO: reduce # notify-send
lightning-cli.packageUnwrapped = pkgs.linkBinIntoOwnPackage pkgs.clightning "lightning-cli";
lightning-cli.sandbox.extraHomePaths = [
".lightning/bitcoin/lightning-rpc"
];
# `lightning-cli` finds its RPC file via `~/.lightning/bitcoin/lightning-rpc`, to message the daemon
lightning-cli.fs.".lightning".symlink.target = "/var/lib/clightning";
losslesscut-bin.buildCost = 1;
losslesscut-bin.sandbox.extraHomePaths = [
"Music"
"Pictures/from" # videos from e.g. mobile phone
"Pictures/servo-macros"
"Videos/local"
"Videos/servo"
"tmp"
];
losslesscut-bin.sandbox.whitelistAudio = true;
losslesscut-bin.sandbox.whitelistDri = true;
losslesscut-bin.sandbox.whitelistWayland = true;
# losslesscut-bin.sandbox.whitelistX = true;
losslesscut-bin.sandbox.mesaCacheDir = ".cache/losslesscut/mesa"; # TODO: is this the correct app-id?
losslesscut-bin.packageUnwrapped = pkgs.losslesscut-bin.overrideAttrs (base: {
extraMakeWrapperArgs = (base.extraMakeWrapperArgs or []) ++ [
"--append-flags '--ozone-platform-hint=auto --ozone-platform=wayland --enable-features=WaylandWindowDecorations'"
];
});
# use: `lsof`; `sudo lsof -i 4`
lsof.sandbox.keepPidsAndProc = true;
lsof.sandbox.capabilities = [ "dac_override" "sys_ptrace" ];
# `lsof -i 4` demands we keep net, and also for some reason `/`.
# not just everything reachable from `/` (i.e. `/bin`, `/boot`, ...), but `/` itself.
# this is the case both for bunpen and for landlock.
lsof.sandbox.tryKeepUsers = true;
lsof.sandbox.net = "all";
lsof.sandbox.extraPaths = [ "/" ];
ltex-ls.sandbox.whitelistPwd = true;
lua = {};
lua-language-server.sandbox.whitelistPwd = true;
man-pages.sandbox.enable = false; #< data only
man-pages-posix.sandbox.enable = false; #< data only
marksman.sandbox.whitelistPwd = true;
mercurial.sandbox.net = "clearnet";
mercurial.sandbox.whitelistPwd = true;
mesa-demos.sandbox.whitelistDri = true;
mesa-demos.sandbox.whitelistWayland = true;
mesa-demos.sandbox.whitelistX = true;
meson = {};
millipixels.packageUnwrapped = pkgs.millipixels.override {
v4l-utils = config.sane.programs.v4l-utils.packageUnwrapped; # necessary for cross compilation
};
millipixels.sandbox.method = null; #< TODO: sandbox
# actual monero blockchain (not wallet/etc; safe to delete, just slow to regenerate)
monero-gui.buildCost = 1;
# XXX: is it really safe to persist this? it doesn't have info that could de-anonymize if captured?
monero-gui.persist.byStore.plaintext = [ ".bitmonero" ];
monero-gui.sandbox.net = "all";
monero-gui.sandbox.extraHomePaths = [
"records/finance/cryptocurrencies/monero"
];
nano.sandbox.autodetectCliPaths = "existingFileOrParent";
netcat.sandbox.net = "all";
nethogs.sandbox.capabilities = [ "net_admin" "net_raw" ];
nethogs.sandbox.tryKeepUsers = true;
nethogs.sandbox.net = "all";
# provides `arp`, `hostname`, `route`, `ifconfig`
nettools.sandbox.net = "all";
nettools.sandbox.capabilities = [ "net_admin" "net_raw" ];
networkmanagerapplet.sandbox.whitelistWayland = true;
networkmanagerapplet.sandbox.whitelistDbus.system = true;
nil.sandbox.whitelistPwd = true;
nil.sandbox.keepPids = true;
nixd.sandbox.whitelistPwd = true;
nix-tree.sandbox.extraPaths = [
"/nix/var"
];
nixfmt-rfc-style.sandbox.autodetectCliPaths = "existingDirOrParent"; #< it formats via rename
nixpkgs-hammering.sandbox.whitelistPwd = true;
nixpkgs-hammering.sandbox.extraPaths = [
"/nix/var" # to prevent complaints about it not finding build logs
];
nixpkgs-review.sandbox.net = "clearnet";
nixpkgs-review.sandbox.whitelistPwd = true;
nixpkgs-review.sandbox.extraHomePaths = [
".config/git" #< it needs to know commiter name/email, even if not posting
];
nixpkgs-review.sandbox.extraPaths = [
"/nix/var"
];
nixpkgs-review.persist.byStore.ephemeral = [
".cache/nixpkgs-review" #< help it not exhaust / tmpfs
];
nmap.sandbox.net = "all"; # clearnet and lan
nmon.sandbox.keepPidsAndProc = true;
nmon.sandbox.net = "all";
nodejs = {};
# `nvme list`
nvme-cli.sandbox.extraPaths = [
"/sys/devices"
"/sys/class/nvme"
"/sys/class/nvme-subsystem"
"/sys/class/nvme-generic"
"/dev"
];
# nvme-cli.sandbox.capabilities = [ "sys_rawio" ];
# contains only `oathtool`, which i only use for evaluating TOTP codes from CLI/stdin
oath-toolkit = {};
# settings (electron app)
obsidian.persist.byStore.plaintext = [ ".config/obsidian" ];
openscad-lsp.sandbox.whitelistPwd = true;
passt.sandbox.enable = false; #< sandbox helper (netns specifically)
parted.sandbox.extraPaths = [
"/dev"
];
parted.sandbox.autodetectCliPaths = "existing"; #< sometimes you'll use parted on a device file.
patchelf.sandbox.method = null; #< TODO: sandbox
pavucontrol.sandbox.whitelistAudio = true;
pavucontrol.sandbox.whitelistDri = true; #< to be a little more responsive
pavucontrol.sandbox.whitelistWayland = true;
pavucontrol.sandbox.mesaCacheDir = ".cache/pavucontrol/mesa";
pciutils.sandbox.extraPaths = [
"/sys/bus/pci"
"/sys/devices"
];
# e.g. `picocom -b 115200 /dev/ttyUSB0`
picocom.sandbox.autodetectCliPaths = "existingFile";
powersupply.sandbox.whitelistWayland = true;
powersupply.sandbox.extraPaths = [
"/sys/class/power_supply"
"/sys/devices"
];
powertop.sandbox.capabilities = [ "ipc_lock" "sys_admin" ];
powertop.sandbox.tryKeepUsers = true;
powertop.sandbox.extraPaths = [
"/proc"
"/sys/class"
"/sys/devices"
"/sys/kernel"
];
procs.sandbox.keepPidsAndProc = true;
procs.sandbox.tryKeepUsers = true; # if running as root, keep the user namespace so it can render usernames
procs.sandbox.capabilities = [ "setfcap" ]; #< this seems wrong, but when running as root without it, it fails
# procps: free, pgrep, pidof, pkill, ps, pwait, top, uptime, couple others
procps.sandbox.keepPidsAndProc = true;
pstree.sandbox.keepPidsAndProc = true;
pulseaudio.sandbox.method = null; #< TODO: sandbox
pulsemixer.sandbox.whitelistAudio = true;
pwvucontrol.buildCost = 1;
pwvucontrol.sandbox.whitelistAudio = true;
pwvucontrol.sandbox.whitelistDri = true; # else perf on moby is unusable
pwvucontrol.sandbox.whitelistWayland = true;
pwvucontrol.sandbox.mesaCacheDir = ".cache/pwvucontrol/mesa"; # TODO: is this the correct app-id?
pyright.sandbox.whitelistPwd = true;
python3-repl.packageUnwrapped = pkgs.python3.withPackages (ps: with ps; [
libgpiod
numpy
psutil
pykakasi
requests
scipy
unidecode
]);
python3-repl.sandbox.net = "clearnet";
python3-repl.sandbox.extraHomePaths = [
"/" #< this is 'safe' because with don't expose .persist/private, so no .ssh/id_ed25519
".persist/plaintext"
];
qemu.sandbox.enable = false; #< it's a launcher
qemu.buildCost = 2;
rsync.sandbox.net = "clearnet";
rsync.sandbox.autodetectCliPaths = "existingOrParent";
rsync.sandbox.tryKeepUsers = true; # if running as root, keep the user namespace so that `-a` can set the correct owners, etc
rust-analyzer.buildCost = 2;
rust-analyzer.sandbox.whitelistPwd = true;
rust-analyzer.suggestedPrograms = [
"cargo"
];
rustc = {};
rustup = {};
sane-cast.sandbox.net = "clearnet";
sane-cast.sandbox.autodetectCliPaths = "existingFile";
sane-cast.sandbox.whitelistAudio = true; #< for sblast audio casting
sane-cast.suggestedPrograms = [ "go2tv" "sblast" ];
sane-color-picker.sandbox.whitelistDbus.user = true; #< TODO: reduce #< required for eyedropper to work
sane-color-picker.sandbox.whitelistWayland = true;
sane-color-picker.sandbox.keepPidsAndProc = true; #< required by wl-clipboard
sane-color-picker.suggestedPrograms = [
"gnugrep"
"wl-clipboard"
# "zenity"
];
sane-color-picker.sandbox.mesaCacheDir = ".cache/sane-color-picker/mesa"; # TODO: is this the correct app-id?
sane-die-with-parent.sandbox.enable = false; #< it's a launcher; can't sandbox
sane-weather.sandbox.net = "clearnet";
sc-im.sandbox.autodetectCliPaths = "existingFile";
screen.sandbox.enable = false; #< tty; needs to run anything
sequoia.packageUnwrapped = pkgs.sequoia.overrideAttrs (_: {
# XXX(2024-07-30): sq_autocrypt_import test failure: "Warning: 9B7DD433F254904A is expired."
doCheck = false;
});
sequoia.buildCost = 1;
sequoia.sandbox.whitelistPwd = true;
sequoia.sandbox.autodetectCliPaths = "existingFileOrParent"; # supports `-o <file-to-create>`
shattered-pixel-dungeon.buildCost = 1;
shattered-pixel-dungeon.persist.byStore.plaintext = [ ".local/share/.shatteredpixel/shattered-pixel-dungeon" ];
shattered-pixel-dungeon.sandbox.whitelistAudio = true;
shattered-pixel-dungeon.sandbox.whitelistDri = true;
shattered-pixel-dungeon.sandbox.whitelistWayland = true;
shattered-pixel-dungeon.sandbox.mesaCacheDir = ".cache/.shatteredpixel/mesa";
# printer/filament settings
slic3r.buildCost = 1;
# slic3r.persist.byStore.plaintext = [
# ".Slic3r" #< printer/filament settings
# ];
slic3r.sandbox.autodetectCliPaths = "existingFileOrParent"; # slic3r <my-file>.stl -o <out>.gcode
slurp.sandbox.whitelistWayland = true;
snapper.sandbox.tryKeepUsers = true;
snapper.sandbox.whitelistDbus.system = true; #< all `snapper` does is speak to the daemon, via dbus
# snapshot camera, based on libcamera
# TODO: enable dma heaps for more efficient buffer sharing: <https://gitlab.com/postmarketOS/pmaports/-/issues/2789>
snapshot.sandbox.method = null; #< TODO: sandbox
sops.sandbox.extraHomePaths = [
".config/sops"
"nixos"
# TODO: sops should only need access to knowledge/secrets,
# except that i currently put its .sops.yaml config in the root of ~/knowledge
"knowledge"
];
sox.sandbox.autodetectCliPaths = "existingFileOrParent";
sox.sandbox.whitelistAudio = true;
space-cadet-pinball.buildCost = 1;
space-cadet-pinball.persist.byStore.plaintext = [ ".local/share/SpaceCadetPinball" ];
space-cadet-pinball.sandbox.mesaCacheDir = ".cache/SpaceCadetPinball/mesa"; # TODO: is this the correct app-id?
space-cadet-pinball.sandbox.whitelistAudio = true;
space-cadet-pinball.sandbox.whitelistDri = true;
space-cadet-pinball.sandbox.whitelistWayland = true;
speedtest-cli.sandbox.net = "all";
sqlite.sandbox.method = null; #< TODO: sandbox
# N.B. if you call sshfs-fuse from the CLI -- without `mount.fuse` -- disable sandboxing
sshfs-fuse.sandbox.net = "all";
sshfs-fuse.sandbox.autodetectCliPaths = "parent";
# sshfs-fuse.sandbox.extraPaths = [
# "/dev/fd" # fuse.mount3 -o drop_privileges passes us data over /dev/fd/3
# "/mnt" # XXX: not sure why i need all this, instead of just /mnt/desko, or /mnt/desko/home, etc
# ];
sshfs-fuse.sandbox.extraHomePaths = [
".ssh/id_ed25519" #< TODO: add -o foo,bar=path/to/thing style arguments to autodetection
];
sshfs-fuse.sandbox.keepPids = true; #< XXX: bwrap didn't need this, but bunpen does. why?
strace.sandbox.enable = false; #< needs to `exec` its args, and therefore support *anything*
subversion.sandbox.net = "clearnet";
subversion.sandbox.whitelistPwd = true;
sudo.sandbox.enable = false;
superTux.buildCost = 1;
superTux.sandbox.whitelistAudio = true;
superTux.sandbox.whitelistDri = true;
superTux.sandbox.whitelistWayland = true;
# superTux.sandbox.whitelistX = true;
superTux.sandbox.mesaCacheDir = ".cache/supertux2/mesa"; # TODO: is this the correct app-id?
superTux.persist.byStore.plaintext = [ ".local/share/supertux2" ];
superTux.packageUnwrapped = pkgs.superTux.overrideAttrs (base: {
nativeBuildInputs = (base.nativeBuildInputs or []) ++ [
pkgs.makeWrapper
];
postInstall = (base.postInstall or "") + ''
wrapProgram $out/bin/supertux2 --set SDL_VIDEODRIVER wayland
'';
});
swappy.sandbox.autodetectCliPaths = "existingFileOrParent";
swappy.sandbox.whitelistWayland = true;
systemctl.packageUnwrapped = pkgs.linkBinIntoOwnPackage pkgs.systemdMinimal "systemctl";
systemctl.sandbox.whitelistSystemctl = true;
# for `sudo systemctl edit --runtime FOO`
systemctl.sandbox.capabilities = [ "cap_dac_override" "cap_sys_admin" ];
systemctl.sandbox.keepPidsAndProc = true;
tcpdump.sandbox.net = "all";
tcpdump.sandbox.autodetectCliPaths = "existingFileOrParent";
tcpdump.sandbox.capabilities = [ "net_admin" "net_raw" ];
tcpdump.sandbox.tryKeepUsers = true;
tdesktop.persist.byStore.private = [ ".local/share/TelegramDesktop" ];
tokodon.buildCost = 1;
tokodon.persist.byStore.private = [ ".cache/KDE/tokodon" ];
tree.sandbox.autodetectCliPaths = "existing";
tree.sandbox.whitelistPwd = true;
tree.sandbox.tryKeepUsers = true;
tree.sandbox.capabilities = [ "dac_read_search" ];
typescript-language-server.buildCost = 2;
typescript-language-server.sandbox.whitelistPwd = true;
tumiki-fighters.buildCost = 1;
tumiki-fighters.sandbox.whitelistAudio = true;
tumiki-fighters.sandbox.whitelistDri = true; #< not strictly necessary, but triples CPU perf
tumiki-fighters.sandbox.whitelistWayland = true;
tumiki-fighters.sandbox.whitelistX = true;
tumiki-fighters.sandbox.mesaCacheDir = ".cache/tumiki-fighters/mesa"; # TODO: is this the correct app-id?
tumiki-fighters.suggestedPrograms = [
"xwayland" #< XXX(2024-11-10): does not start without X(wayland), not even with SDL_VIDEDRIVER=wayland
];
util-linux.sandbox.method = null; #< TODO: possible to sandbox if i specify a different profile for each of its ~50 binaries
"unixtools.ps".sandbox.keepPidsAndProc = true;
"unixtools.sysctl" = {}; #< XXX: probably not sandboxed correctly for sysctl writes; only for reads
unzip.sandbox.autodetectCliPaths = "existingOrParent";
unzip.sandbox.whitelistPwd = true;
# usbutils.sandbox.method = null; # fixes `usbhid-dump`. OTOH `lsusb`, `usb-devices` work under bunpen
usbutils.sandbox.extraPaths = [
"/sys/devices"
"/sys/bus/usb"
];
vala-language-server.sandbox.whitelistPwd = true;
vala-language-server.suggestedPrograms = [
# might someday support cmake, too: <https://github.com/vala-lang/vala-language-server/issues/73>
"meson"
];
valgrind.buildCost = 1;
valgrind.sandbox.enable = false; #< it's a launcher: can't sandbox
# `vulkaninfo`, `vkcube`
vulkan-tools.sandbox.whitelistDri = true;
vulkan-tools.sandbox.whitelistWayland = true;
vulkan-tools.sandbox.whitelistX = true;
vulkan-tools.sandbox.extraPaths = [
"/sys/dev/char"
"/sys/devices"
];
vvvvvv.buildCost = 1;
vvvvvv.sandbox.whitelistAudio = true;
vvvvvv.sandbox.whitelistDri = true; #< playable without, but burns noticably more CPU
vvvvvv.sandbox.whitelistWayland = true;
vvvvvv.sandbox.mesaCacheDir = ".cache/VVVVVV/mesa";
vvvvvv.persist.byStore.plaintext = [ ".local/share/VVVVVV" ];
w3m.sandbox.net = "all";
w3m.sandbox.extraHomePaths = [
# little-used feature, but you can save web pages :)
"tmp"
];
watch.sandbox.enable = false; #< it executes the command it's given
wdisplays.sandbox.mesaCacheDir = ".cache/wdisplays/mesa"; # TODO: is this the correct app-id?
wdisplays.sandbox.whitelistWayland = true;
wget.sandbox.net = "all";
wget.sandbox.whitelistPwd = true; # saves to pwd by default
whalebird.buildCost = 1;
whalebird.persist.byStore.private = [ ".config/Whalebird" ];
# `wg`, `wg-quick`
wireguard-tools.sandbox.net = "all";
wireguard-tools.sandbox.capabilities = [ "net_admin" ];
wireguard-tools.sandbox.tryKeepUsers = true;
# provides `iwconfig`, `iwlist`, `iwpriv`, ...
wirelesstools.sandbox.net = "all";
wirelesstools.sandbox.capabilities = [ "net_admin" ];
wirelesstools.sandbox.tryKeepUsers = true;
wl-clipboard.sandbox.whitelistWayland = true;
wl-clipboard.sandbox.keepPids = true; #< this is needed, but not sure why?
wtype = {};
wtype.sandbox.whitelistWayland = true;
xwayland.sandbox.wrapperType = "inplace"; #< consumers use it as a library (e.g. wlroots)
xwayland.sandbox.whitelistWayland = true; #< just assuming this is needed
xwayland.sandbox.whitelistX = true;
xwayland.sandbox.whitelistDri = true; #< would assume this gives better gfx perf
xwayland.sandbox.mesaCacheDir = ".cache/xwayland/mesa"; # TODO: is this the correct app-id?
xterm.sandbox.enable = false; # need to be able to do everything
yarn.persist.byStore.plaintext = [ ".cache/yarn" ];
};
sane.persist.sys.byStore.plaintext = lib.mkIf config.sane.programs.guiApps.enabled [
# "/var/lib/alsa" # preserve output levels, default devices
"/var/lib/systemd/backlight" # backlight brightness
];
hardware.graphics = lib.mkIf config.sane.programs.guiApps.enabled ({
enable = true;
} // (lib.optionalAttrs pkgs.stdenv.isx86_64 {
# for 32 bit applications
# upstream nixpkgs forbids setting enable32Bit unless specifically x86_64 (so aarch64 isn't allowed)
enable32Bit = lib.mkDefault true;
}));
system.activationScripts.notifyActive = lib.mkIf config.sane.programs.guiApps.enabled {
text = let
notify-active = pkgs.writeShellScriptBin "notify-active-inner" ''
export PATH="/etc/profiles/per-user/$USER/bin:$PATH:${pkgs.libnotify}/bin"
systemConfig="$1"
NIXOS_VERSION="$(cat $systemConfig/nixos-version)"
if [ -e "$HOME/.profile" ]; then
. "$HOME/.profile"
fi
dbus_file="$XDG_RUNTIME_DIR/dbus/bus"
if [ -z "$DBUS_SESSION_BUS_ADDRESS" ] && [ -e "$dbus_file" ]; then
export DBUS_SESSION_BUS_ADDRESS="unix:path=$dbus_file"
fi
if [ -n "$DBUS_SESSION_BUS_ADDRESS" ]; then
notify-send "nixos activated" "version: $NIXOS_VERSION"
fi
'';
in lib.concatStringsSep "\n" (lib.mapAttrsToList
(user: en: lib.optionalString en ''${lib.getExe pkgs.sudo} -u "${user}" "${lib.getExe notify-active}" "$systemConfig" > /dev/null'')
config.sane.programs.guiApps.enableFor.user
);
};
}