{ config, lib, ... }: let declPackageSet = pkgs: { packageUnwrapped = null; suggestedPrograms = pkgs; }; in { sane.programs = { "sane-scripts.backup" = declPackageSet [ "sane-scripts.backup-ls" "sane-scripts.backup-restore" ]; "sane-scripts.bittorrent" = declPackageSet [ "sane-scripts.bt-add" "sane-scripts.bt-rm" "sane-scripts.bt-search" "sane-scripts.bt-show" ]; "sane-scripts.dev" = declPackageSet [ "sane-scripts.clone" "sane-scripts.dev-cargo-loop" ]; "sane-scripts.cli" = declPackageSet [ "sane-scripts.deadlines" "sane-scripts.find-dotfiles" "sane-scripts.ip-check" "sane-scripts.private-change-passwd" "sane-scripts.private-do" "sane-scripts.private-init" "sane-scripts.private-lock" "sane-scripts.private-unlock" "sane-scripts.rcp" "sane-scripts.reboot" "sane-scripts.reclaim-boot-space" "sane-scripts.reclaim-disk-space" "sane-scripts.secrets-dump" "sane-scripts.secrets-unlock" "sane-scripts.secrets-update-keys" "sane-scripts.shutdown" "sane-scripts.sudo-redirect" "sane-scripts.tag-music" "sane-scripts.vpn" "sane-scripts.which" "sane-scripts.wipe" ]; "sane-scripts.sys-utils" = declPackageSet [ "sane-scripts.ip-port-forward" "sane-scripts.sync-music" ]; "sane-scripts.backup-ls" = {}; "sane-scripts.backup-restore" = {}; "sane-scripts.bt-add".sandbox = { method = "bwrap"; autodetectCliPaths = "existing"; #< for adding a .torrent from disk net = "clearnet"; # TODO: migrate `transmission_passwd` to `secrets` api extraPaths = [ "/run/secrets/transmission_passwd" ]; }; "sane-scripts.bt-rm".sandbox = { method = "bwrap"; net = "clearnet"; # TODO: migrate `transmission_passwd` to `secrets` api extraPaths = [ "/run/secrets/transmission_passwd" ]; }; "sane-scripts.bt-search".sandbox = { method = "bwrap"; net = "clearnet"; # TODO: migrate `jackett_apikey` to `secrets` api extraPaths = [ "/run/secrets/jackett_apikey" ]; }; "sane-scripts.bt-show".sandbox = { method = "bwrap"; net = "clearnet"; # TODO: migrate `transmission_passwd` to `secrets` api extraPaths = [ "/run/secrets/transmission_passwd" ]; }; # the idea of this script is to `cd` into a fresh clone... # but that's an ephemeral operation that would be lost when the sandbox closes. "sane-scripts.clone".sandbox.enable = false; "sane-scripts.deadlines".sandbox = { method = "bwrap"; extraHomePaths = [ "knowledge/planner/deadlines.tsv" ]; }; "sane-scripts.dev-cargo-loop".sandbox = { method = "bwrap"; net = "clearnet"; whitelistPwd = true; extraPaths = [ # a build script can do a lot... but a well-written one will be confined # to XDG dirs and the local dir, and maybe the internet for fetching dependencies. ".cache" ".config" ".local" ]; }; "sane-scripts.find-dotfiles".sandbox = { method = "bwrap"; extraHomePaths = [ "/" ".persist/ephemeral" ".persist/plaintext" ]; }; "sane-scripts.ip-check".sandbox = { method = "landlock"; net = "all"; }; "sane-scripts.ip-port-forward" = {}; "sane-scripts.private-change-passwd".sandbox = { method = "bwrap"; autodetectCliPaths = "existing"; #< for the new `private` location capabilities = [ "sys_admin" ]; # it needs to mount the new store extraHomePaths = [ ".persist/private" ]; }; "sane-scripts.private-do".sandbox = { # because `mount` is a cap_sys_admin syscall, there's no great way to mount stuff dynamically like this. # instead, we put ourselves in a mount namespace, do the mount, and drop into a shell or run a command. # this actually has an OK side effect, that the mount isn't shared, and so we avoid contention/interleaving that would cause the ending `umount` to fail. method = "bwrap"; # cap_sys_admin is needed to mount stuff. # ordinarily /run/wrappers/bin/mount would do that via setuid, but sandboxes have no_new_privs by default. capabilities = [ "sys_admin" ]; # `sane-private-do` acts as a launcher, so give it access to anything it could possibly need. # (crucially, that includes the backing store) net = "all"; extraPaths = [ "/" ]; }; "sane-scripts.private-init".sandbox = { method = "bwrap"; capabilities = [ "sys_admin" ]; # it needs to mount the new store extraHomePaths = [ ".persist/private" ]; }; "sane-scripts.private-lock".sandbox.enable = false; "sane-scripts.private-unlock".sandbox.enable = false; "sane-scripts.reclaim-boot-space".sandbox = { method = "bwrap"; extraPaths = [ "/boot" ]; }; # it's just a thin wrapper around rsync, which is already sandboxed "sane-scripts.rcp".sandbox.enable = false; # but make sure rsync is always on PATH, so that we actually do get sandboxing :) "sane-scripts.rcp".suggestedPrograms = [ "rsync" ]; "sane-scripts.reboot".sandbox = { method = "bwrap"; extraPaths = [ "/run/dbus" "/run/systemd" ]; }; "sane-scripts.reclaim-disk-space".sandbox = { method = "bwrap"; extraPaths = [ "/nix/var/nix" ]; }; "sane-scripts.secrets-unlock".sandbox = { method = "bwrap"; extraHomePaths = [ ".ssh/id_ed25519" ".ssh/id_ed25519.pub" ".config/sops" ]; }; # sane-secrets-dump is a thin wrapper around sops + some utilities. # really i should sandbox just the utilities "sane-scripts.secrets-dump".sandbox.enable = false; "sane-scripts.secrets-dump".suggestedPrograms = [ "gnugrep" "oath-toolkit" "sops" ]; # sane-secrets-update-keys is a thin wrapper around sops + some utilities. # really i should sandbox just the utilities "sane-scripts.secrets-update-keys".sandbox.enable = false; "sane-scripts.secrets-update-keys".suggestedPrograms = [ "findutils" "sops" ]; "sane-scripts.shutdown".sandbox = { method = "bwrap"; extraPaths = [ "/run/dbus" "/run/systemd" ]; }; "sane-scripts.stop-all-servo" = {}; # if `tee` isn't trustworthy we have bigger problems "sane-scripts.sudo-redirect".sandbox.enable = false; "sane-scripts.sync-music" = {}; "sane-scripts.sync-from-iphone" = {}; "sane-scripts.tag-music".sandbox = { method = "bwrap"; autodetectCliPaths = "existing"; }; "sane-scripts.vpn".fs = lib.foldl' (acc: vpn: let vpnCfg = config.sane.vpn."${vpn}"; in acc // { ".config/sane-vpn/vpns/${vpn}".symlink.text = '' id=${builtins.toString vpnCfg.id} fwmark=${builtins.toString vpnCfg.fwmark} priorityMain=${builtins.toString vpnCfg.priorityMain} priorityFwMark=${builtins.toString vpnCfg.priorityFwMark} bridgeDevice=${vpnCfg.bridgeDevice} dns=(${lib.concatStringsSep " " vpnCfg.dns}) ''; } // (lib.optionalAttrs vpnCfg.isDefault { ".config/sane-vpn/default".symlink.text = vpn; }) ) {} (builtins.attrNames config.sane.vpn); "sane-scripts.vpn".sandbox = { method = "landlock"; #< bwrap can't handle `ip link` stuff even with cap_net_admin net = "all"; capabilities = [ "net_admin" ]; extraHomePaths = [ ".config/sane-vpn" ]; }; "sane-scripts.which".sandbox = { method = "bwrap"; extraHomePaths = [ # for SXMO ".config/sxmo/hooks" ]; }; "sane-scripts.wipe".sandbox = { method = "bwrap"; whitelistDbus = [ "user" ]; #< for `secret-tool` whitelistS6 = true; #< for stopping services before wiping extraHomePaths = [ # could be more specific, but at a maintenance cost. # TODO: needs updating, now that persisted data lives behind symlinks! # both this list AND the script need patching for that. ".cache" ".config" ".local/share" ".librewolf" ".mozilla" ".persist/ephemeral/.cache" ".persist/ephemeral/.config" ".persist/ephemeral/.local/share" ".persist/ephemeral/.librewolf" ".persist/ephemeral/.mozilla" ".persist/plaintext/.cache" ".persist/plaintext/.config" ".persist/plaintext/.local/share" ".persist/plaintext/.librewolf" ".persist/plaintext/.mozilla" ".persist/private/.cache" ".persist/private/.config" ".persist/private/.local/share" ".persist/private/.librewolf" ".persist/private/.mozilla" ]; }; }; }