diff --git a/hosts/common/home/xdg-dirs.nix b/hosts/common/home/xdg-dirs.nix index 8ab37f32..d8bab693 100644 --- a/hosts/common/home/xdg-dirs.nix +++ b/hosts/common/home/xdg-dirs.nix @@ -23,11 +23,5 @@ # see sane.user.fs.".config/user-dirs.conf".symlink.text = "enabled=False"; - sane.user.fs.".profile".symlink.text = '' - # configure XDG__DIR preferences (e.g. for downloads, screenshots, etc) - # surround with `set -o allexport` since user-dirs.dirs doesn't `export` its vars - set -a - source $HOME/.config/user-dirs.dirs - set +a - ''; + sane.user.fs.".config/environment.d/30-user-dirs.conf".symlink.target = "../user-dirs.dirs"; } diff --git a/hosts/common/ids.nix b/hosts/common/ids.nix index 60bb7a1c..ee11219b 100644 --- a/hosts/common/ids.nix +++ b/hosts/common/ids.nix @@ -81,6 +81,7 @@ # found on graphical hosts sane.ids.nm-iodine.uid = 2101; # desko/moby/lappy + sane.ids.seat.gid = 2102; # found on desko host # from services.usbmuxd diff --git a/hosts/common/programs/bemenu.nix b/hosts/common/programs/bemenu.nix index 0f1672c1..8f4a8b7e 100644 --- a/hosts/common/programs/bemenu.nix +++ b/hosts/common/programs/bemenu.nix @@ -87,7 +87,7 @@ let in { sane.programs.bemenu = { - sandbox.method = "bwrap"; # landlock works, but requires *all* of /run/user/$ID to be granted. + sandbox.method = "bwrap"; # landlock works, but requires *all* of $XDG_RUNTIME_DIR to be granted. sandbox.whitelistWayland = true; sandbox.extraHomePaths = [ ".cache/fontconfig" #< else it complains, and is *way* slower diff --git a/hosts/common/programs/pipewire.nix b/hosts/common/programs/pipewire.nix index a1d84986..5dcbbfa7 100644 --- a/hosts/common/programs/pipewire.nix +++ b/hosts/common/programs/pipewire.nix @@ -141,7 +141,7 @@ in # ''; # see: - # defaults to placing the socket in /run/user/$id/{pipewire-0,pipewire-0-manager,...} + # defaults to placing the socket in $XDG_RUNTIME_DIR/{pipewire-0,pipewire-0-manager,...} # but that's trickier to sandbox env.PIPEWIRE_RUNTIME_DIR = "$XDG_RUNTIME_DIR/pipewire"; diff --git a/hosts/common/programs/rofi/default.nix b/hosts/common/programs/rofi/default.nix index 8f10d02a..46b9df49 100644 --- a/hosts/common/programs/rofi/default.nix +++ b/hosts/common/programs/rofi/default.nix @@ -5,7 +5,7 @@ # - use as a launcher/file browser # - `rofi -sidebar-mode` # - separate tabs for filebrowser, drun, etc. -# - `rofi -pid /run/user/$UID/rofi.pid -replace` +# - `rofi -pid $XDG_RUNTIME_DIR/rofi.pid -replace` # - single-instance mode # - pid is probably optional, just need `-replace`. # diff --git a/hosts/common/programs/shadow.nix b/hosts/common/programs/shadow.nix index f3fad228..231440bd 100644 --- a/hosts/common/programs/shadow.nix +++ b/hosts/common/programs/shadow.nix @@ -1,11 +1,17 @@ -{ config, ... }: +{ config, lib, ... }: let cfg = config.sane.programs.shadow; in { - sane.programs.shadow = { - sandbox.enable = false; #< `login` can't be sandboxed because it launches a user shell - }; - - services.getty.loginProgram = "${cfg.package}/bin/login"; + config = lib.mkMerge [ + { + sane.programs.shadow = { + sandbox.enable = false; #< `login` can't be sandboxed because it launches a user shell + }; + } + (lib.mkIf cfg.enabled { + services.getty.loginProgram = "${cfg.package}/bin/login"; + security.pam.services.login.startSession = lib.mkForce false; #< disable systemd integration + }) + ]; } diff --git a/hosts/common/programs/sway/default.nix b/hosts/common/programs/sway/default.nix index 4e1512b1..d71a1bba 100644 --- a/hosts/common/programs/sway/default.nix +++ b/hosts/common/programs/sway/default.nix @@ -179,7 +179,8 @@ in sandbox.extraRuntimePaths = [ "/" ]; # TODO: should need just "sway". but even if i sandbox EVERY entry under run individually, it fails! sandbox.extraPaths = [ "/dev/input" - "/run/systemd/sessions" + "/run/seatd.sock" #< required if not using `logind` systemd login manager + "/run/systemd/sessions" #< TODO: remove "/run/udev" "/sys/class/backlight" "/sys/class/drm" @@ -238,6 +239,11 @@ in services.graphical-session.partOf = [ "default" ]; }; + # TODO: sandbox seatd + # also consider any alternatives (ConsoleKit?). + # /run/seatd.sock location can be configured, but only via compile-time flag + services.seatd.enable = lib.mkIf cfg.enabled true; + # TODO: this can go elsewhere hardware.bluetooth.enable = lib.mkIf cfg.enabled true; services.blueman.enable = lib.mkIf cfg.enabled true; diff --git a/hosts/common/programs/unl0kr/default.nix b/hosts/common/programs/unl0kr/default.nix index cd6e06d8..f79c98ac 100644 --- a/hosts/common/programs/unl0kr/default.nix +++ b/hosts/common/programs/unl0kr/default.nix @@ -89,29 +89,17 @@ in "shadow" #< for login ]; - fs.".profile".symlink.text = lib.mkMerge [ - (lib.mkBefore '' - # setup primarySessionCommands here and let any other nix config populate it later - primarySessionCommands=() - initPrimarySession() { - for c in "''${primarySessionCommands[@]}"; do - eval "$c" - done - } - '') - # lib.mkAfter so that launching the DE happens *after* any other .profile setup. - (lib.mkAfter '' + fs.".profile".symlink.text = '' + unl0krCheck() { # if already running a desktop environment, or if running from ssh, then `tty` will show /dev/pts/NN. - if [ "$(tty)" = "/dev/${tty}" ]; then - if (( ''${#primarySessionCommands[@]} )); then - echo "launching primary session commands in ${builtins.toString cfg.config.delay}s: ''${primarySessionCommands[*]}" - # if the `sleep` call here is `Ctrl+C'd`, then it'll exit false and the desktop isn't launched. - sleep ${builtins.toString cfg.config.delay} && \ - initPrimarySession - fi - fi - '') - ]; + # if the `sleep` call is `Ctrl+C'd`, then it'll exit false and the session commands won't be launched + [ "$(tty)" = "/dev/${tty}" ] && (( ''${#primarySessionCommands[@]} )) \ + && echo "launching primary session commands in ${builtins.toString cfg.config.delay}s: ''${primarySessionCommands[*]}" \ + && sleep ${builtins.toString cfg.config.delay} + } + primarySessionChecks+=('unl0krCheck') + + ''; # N.B.: this sandboxing applies to `unl0kr` itself -- the on-screen-keyboard; # NOT to the wrapper which invokes `login`. diff --git a/hosts/common/programs/xdg-desktop-portal.nix b/hosts/common/programs/xdg-desktop-portal.nix index f6fda0fc..28a05789 100644 --- a/hosts/common/programs/xdg-desktop-portal.nix +++ b/hosts/common/programs/xdg-desktop-portal.nix @@ -89,7 +89,7 @@ in }; # also available: ${cfg.package}/libexec/xdg-document-portal # - - # - shares files from its namespace with programs inside a namespace, via a fuse mount at /run/user/$uid/doc + # - shares files from its namespace with programs inside a namespace, via a fuse mount at $XDG_RUNTIME_DIR/doc }; # after #603 is resolved, i can probably stop linking `{gtk,wlr}.portal` into ~ diff --git a/hosts/common/users/colin.nix b/hosts/common/users/colin.nix index 7006c289..2ed0185b 100644 --- a/hosts/common/users/colin.nix +++ b/hosts/common/users/colin.nix @@ -21,6 +21,7 @@ "media" # servo "networkmanager" "nixbuild" + "seat" # for sway, if using seatd "systemd-journal" # allows to view other user's journals (esp system users) "transmission" # servo "video" # mobile; for LEDs & maybe for camera? diff --git a/modules/users/default.nix b/modules/users/default.nix index b53abc0b..c05a20dc 100644 --- a/modules/users/default.nix +++ b/modules/users/default.nix @@ -190,12 +190,26 @@ let # homeMode defaults to 700; notice: no leading 0 mode = "0" + nixConfig.users.users."${name}".homeMode; }; + # ~/.config/environment.d/*.conf is added to systemd user units. # - format: lines of: `key=value` # ~/.profile is added by *some* login shells. # - format: lines of: `export key="value"` # see: `man environment.d` - fs.".config/environment.d/10-sane-nixos-users.conf".symlink.text = + ### normally a session manager (like systemd) would set these vars (at least) for me: + # - XDG_RUNTIME_DIR + # - XDG_SESSION_ID + # - XDG_SESSION_CLASS + # - XDG_SESSION_TYPE + # - XDG_VTNR + # - SYSTEMD_EXEC_PID + # some of my program-specific environment variables depend on some of these being set, + # hence do that early: + # TODO: consider moving XDG_RUNTIME_DIR to $HOME/.run + fs.".config/environment.d/10-sane-baseline.conf".symlink.text = '' + XDG_RUNTIME_DIR=/run/user/${name} + ''; + fs.".config/environment.d/20-sane-nixos-users.conf".symlink.text = let env = lib.mapAttrsToList (key: value: ''${key}=${value}'') @@ -203,15 +217,63 @@ let ; in lib.concatStringsSep "\n" env + "\n"; - fs.".profile".symlink.text = '' - # source env vars and the like, as systemd would. `man environment.d` - for env in ~/.config/environment.d/*.conf; do - # surround with `set -o allexport` since environment.d doesn't explicitly `export` their vars - set -a - source "$env" - set +a - done - ''; + fs.".profile".symlink.text = lib.mkMerge [ + (lib.mkBefore '' + # sessionCommands: ordered sequence of functions which will be called whenever this file is sourced. + # primarySessionCommands: additional functions which will be called only for the main session (i.e. login through GUI). + # GUIs are expected to install a function to `primarySessionChecks` which returns true + # if primary session initialization is desired (e.g. if this was sourced from a greeter). + sessionCommands=() + primarySessionCommands=() + primarySessionChecks=() + + runCommands() { + for c in "$@"; do + eval "$c" + done + } + initSession() { + runCommands "''${sessionCommands[@]}" + } + maybeInitPrimarySession() { + for c in "''${primarySessionChecks[@]}"; do + if eval "$c"; then + runCommands "''${primarySessionCommands[@]}" + return + fi + done + } + + setVTNR() { + # some desktops (e.g. sway) need to know which virtual TTY to render to + if [ -v "$XDG_VTNR" ]; then + return + fi + local ttyPath=$(tty) + case $ttyPath in + (/dev/tty*) + export XDG_VTNR=''${ttyPath#/dev/tty} + ;; + esac + } + sessionCommands+=('setVTNR') + sourceEnv() { + # source env vars and the like, as systemd would. `man environment.d` + for env in ~/.config/environment.d/*.conf; do + # surround with `set -o allexport` since environment.d doesn't explicitly `export` their vars + set -a + source "$env" + set +a + done + } + sessionCommands+=('sourceEnv') + + '') + (lib.mkAfter '' + sessionCommands+=('maybeInitPrimarySession') + initSession + '') + ]; services."default" = { description = "service (bundle) which is started by default upon login"; @@ -227,7 +289,7 @@ let } ]; }); - processUser = user: defn: + processUser = name: defn: let prefixWithHome = lib.mapAttrs' (path: value: { name = path-lib.concat [ defn.home path ]; @@ -239,8 +301,17 @@ let }])); in { - sane.fs = makeWanted (prefixWithHome defn.fs); - sane.defaultUser = lib.mkIf defn.default user; + sane.fs = makeWanted ({ + "/run/user/${name}" = [{ + dir.acl = { + user = lib.mkDefault name; + group = lib.mkDefault config.users.users."${name}".group; + # homeMode defaults to 700; notice: no leading 0 + mode = "0" + config.users.users."${name}".homeMode; + }; + }]; + } // prefixWithHome defn.fs); + sane.defaultUser = lib.mkIf defn.default name; # `byPath` is the actual output here, computed from the other keys. sane.persist.sys.byPath = prefixWithHome defn.persist.byPath; diff --git a/modules/users/s6-rc.nix b/modules/users/s6-rc.nix index 070a3362..23230e11 100644 --- a/modules/users/s6-rc.nix +++ b/modules/users/s6-rc.nix @@ -204,7 +204,7 @@ let # to decrease sandbox escaping, i want to run s6-svscan on a read-only directory # so other programs can't edit the service scripts. # in practice, that means putting the servicedirs in /nix/store, and linking selective pieces of state - # towards /run/user/{uid}/s6/live/..., the latter is shared with s6-rc. + # towards $XDG_RUNTIME_DIR/s6/live/..., the latter is shared with s6-rc. mkScanDir = livedir: compiled: pkgs.runCommandLocal "s6-scandir" { } '' cp -R "${compiled}/servicedirs" "$out" cd "$out" @@ -287,7 +287,7 @@ let services ; - # create a template s6 "live" dir, which can be copied at runtime in /run/user/{uid}/s6/live. + # create a template s6 "live" dir, which can be copied at runtime in $XDG_RUNTIME_DIR/s6/live. # this is like a minimal versio of `s6-rc-init`, but tightly coupled to my setup # wherein the scandir is external and selectively links back to the livedir mkLiveDir = compiled: pkgs.runCommandLocal "s6-livedir" {} '' @@ -322,8 +322,8 @@ in ) ); compiled = compileServices sources; - uid = config'.users.users."${name}".uid; - scanDir = mkScanDir "/run/user/${builtins.toString uid}/s6/live" compiled; + xdg_runtime_dir = "/run/user/${name}"; + scanDir = mkScanDir "${xdg_runtime_dir}/s6/live" compiled; liveDir = mkLiveDir compiled; in { fs.".config/s6/live".symlink.target = liveDir;