Compare commits

..

5 Commits

Author SHA1 Message Date
084541da4c modules/programs: require manual definition; don't auto-populate attrset
this greatly decreases nix eval time
2024-02-28 13:32:52 +00:00
f7a82a845c sane.gui.phosh: remove 2024-02-28 13:32:52 +00:00
2bdef04552 nix-fast-build: fix to only populate it on supported platforms 2024-02-28 12:46:38 +00:00
2822a6f0dd import nix-fast-build
it's not really working on lappy (uses too much RAM), but maybe it'll help on desko
2024-02-28 12:37:50 +00:00
ab6e362f0c sane-wipe-browser: dont call pkill with sudo 2024-02-28 10:36:13 +00:00
246 changed files with 3813 additions and 24235 deletions

View File

@@ -15,9 +15,8 @@ the only hard dependency for my exported pkgs/modules should be [nixpkgs][nixpkg
building [hosts/](./hosts/) will require [sops][sops].
you might specifically be interested in these files (elaborated further in #key-points-of-interest):
- ~~[`sxmo-utils`](./pkgs/additional/sxmo-utils/default.nix)~~
- ~~[example SXMO deployment](./hosts/modules/gui/sxmo/default.nix)~~
- these files will remain until my config settles down, but i no longer use or maintain SXMO.
- [`sxmo-utils`](./pkgs/additional/sxmo-utils/default.nix)
- [example SXMO deployment](./hosts/modules/gui/sxmo/default.nix)
- [my implementation of impermanence](./modules/persist/default.nix)
- my way of deploying dotfiles/configuring programs per-user:
- [modules/fs/](./modules/fs/default.nix)

20
TODO.md
View File

@@ -1,14 +1,9 @@
## BUGS
- Signal restart loop drains battery
- decrease s6 restart time?
- mpv `player-mode=pseudo-gui` swallows all loggin
- ringer (i.e. dino incoming call) doesn't prevent moby from sleeping
- sway mouse/kb hotplug doesn't work
- `nix` operations from lappy hang when `desko` is unreachable
- could at least direct the cache to `http://desko-hn:5001`
## REFACTORING:
- REMOVE DEPRECATED `crypt` from sftpgo_auth_hook
- consolidate ~/dev and ~/ref
- ~/dev becomes a link to ~/ref/cat/mine
- fold hosts/common/home/ssh.nix -> hosts/common/users/colin.nix
@@ -16,15 +11,16 @@
### sops/secrets
- rework secrets to leverage `sane.fs`
- remove sops activation script as it's covered by my systemd sane.fs impl
- user secrets could just use `gocryptfs`, like with ~/private?
- can gocryptfs support nested filesystems, each with different perms (for desko, moby, etc)?
### roles
- allow any host to take the role of `uninsane.org`
- will make it easier to test new services?
### upstreaming
- split out a sxmo module usable by NUR consumers
- bump nodejs version in lemmy-ui
- add updateScripts to all my packages in nixpkgs
- fix lightdm-mobile-greeter for newer libhandy
- REVIEW/integrate jellyfin dataDir config: <https://github.com/NixOS/nixpkgs/pull/233617>
#### upstreaming to non-nixpkgs repos
@@ -51,8 +47,6 @@
- limit access to `~/knowledge/secrets` through an agent that requires GUI approval, so a firefox exploit can't steal all my logins
- port sane-sandboxed to a compiled language (hare?)
- it adds like 50-70ms launch time _on my laptop_. i'd hate to know how much that is on the pinephone.
- remove /run/wrappers from the sandbox path
- they're mostly useless when using no-new-privs, just an opportunity to forget to specify deps
- make dconf stuff less monolithic
- i.e. per-app dconf profiles for those which need it. possible static config.
- canaries for important services
@@ -60,11 +54,6 @@
- integrate `nix check` into Gitea actions?
### user experience
- xdg-desktop-portal shouldn't kill children on exit
- *maybe* a job for `setsid -f`?
- replace starship prompt with something more efficient
- watch `forkstat`: it does way too much
- cleanup waybar so that it's not invoking playerctl every 2 seconds
- install apps:
- display QR codes for WiFi endpoints: <https://linuxphoneapps.org/apps/noappid.wisperwind.wifi2qr/>
- shopping list (not in nixpkgs): <https://linuxphoneapps.org/apps/ro.hume.cosmin.shoppinglist/>
@@ -87,7 +76,6 @@
#### moby
- fix cpuidle (gets better power consumption): <https://xnux.eu/log/077.html>
- moby: tune keyboard layout
- SwayNC:
- don't show MPRIS if no players detected
- this is a problem of playerctld, i guess
@@ -109,7 +97,6 @@
- RSS: integrate a paywall bypass
- e.g. self-hosted [ladder](https://github.com/everywall/ladder) (like 12ft.io)
- neovim: set up language server (lsp; rnix-lsp; nvim-lspconfig)
- neovim: integrate LLMs
- Helix: make copy-to-system clipboard be the default
- firefox/librewolf: persist history
- just not cookies or tabs
@@ -127,6 +114,7 @@
### perf
- debug nixos-rebuild times
- i bet sane.programs adds a LOT of time, with how it automatically creates an attrs for EVERY package in nixpkgs.
- add `pkgs.impure-cached.<foo>` package set to build things with ccache enabled
- every package here can be auto-generated, and marked with some env var so that it doesn't pollute the pure package set
- would be super handy for package prototyping!

213
flake.lock generated
View File

@@ -1,76 +1,20 @@
{
"nodes": {
"flake-compat": {
"locked": {
"lastModified": 1688025799,
"narHash": "sha256-ktpB4dRtnksm9F5WawoIkEneh1nrEvuxb5lJFt1iOyw=",
"owner": "nix-community",
"repo": "flake-compat",
"rev": "8bf105319d44f6b9f0d764efa4fdef9f1cc9ba1c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"nixpkgs-wayland",
"nix-eval-jobs",
"nixpkgs"
]
},
"locked": {
"lastModified": 1701473968,
"narHash": "sha256-YcVE5emp1qQ8ieHUnxt1wCZCC3ZfAS+SRRWZ2TMda7E=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "34fed993f1674c8d06d58b37ce1e0fe5eebcb9f5",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"lib-aggregate": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1711886936,
"narHash": "sha256-D2WENp9GuaCostvNcQ7vElekk0V5cuMdnFZ7NfRhVrQ=",
"owner": "nix-community",
"repo": "lib-aggregate",
"rev": "9c06929b83e57c18d125f1105ba6a423f24083d2",
"lastModified": 1698882062,
"narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8c9fa2545007b49a5db5f650ae91f227672c3877",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "lib-aggregate",
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
@@ -91,56 +35,33 @@
"type": "github"
}
},
"nix-eval-jobs": {
"nix-fast-build": {
"inputs": {
"flake-parts": "flake-parts",
"nix-github-actions": "nix-github-actions",
"nixpkgs": "nixpkgs",
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1705242886,
"narHash": "sha256-TLj334vRwFtSym3m+NnKcNCnKKPNoTC/TDZL40vmOso=",
"owner": "nix-community",
"repo": "nix-eval-jobs",
"rev": "6b03a93296faf174b97546fd573c8b379f523a8d",
"lastModified": 1703607026,
"narHash": "sha256-Emh0BPoqlS4ntp2UJrwydXfIP4qIMF0VBB2FUE3/M/E=",
"owner": "Mic92",
"repo": "nix-fast-build",
"rev": "4376b8a33b217ee2f78ba3dcff01a3e464d13a46",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-eval-jobs",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"nixpkgs-wayland",
"nix-eval-jobs",
"nixpkgs"
]
},
"locked": {
"lastModified": 1701208414,
"narHash": "sha256-xrQ0FyhwTZK6BwKhahIkUVZhMNk21IEI1nUcWSONtpo=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "93e39cc1a087d65bcf7a132e75a650c44dd2b734",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"owner": "Mic92",
"repo": "nix-fast-build",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1703134684,
"narHash": "sha256-SQmng1EnBFLzS7WSRyPM9HgmZP2kLJcPAz+Ug/nug6o=",
"lastModified": 1698890957,
"narHash": "sha256-DJ+SppjpPBoJr0Aro9TAcP3sxApCSieY6BYBCoWGUX8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d6863cbcbbb80e71cecfc03356db1cda38919523",
"rev": "c082856b850ec60cda9f0a0db2bc7bd8900d708c",
"type": "github"
},
"original": {
@@ -152,26 +73,29 @@
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1711846064,
"narHash": "sha256-cqfX0QJNEnge3a77VnytM0Q6QZZ0DziFXt6tSCV8ZSc=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "90b1a963ff84dc532db92f678296ff2499a60a87",
"dir": "lib",
"lastModified": 1698611440,
"narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-next-unpatched": {
"locked": {
"lastModified": 1712383280,
"narHash": "sha256-YL8miM11o/jMqOwt5DsdyhPgh/JgCl1kOIzvX7ukniY=",
"lastModified": 1708992120,
"narHash": "sha256-t/8QV+lEroW5fK44w5oEUalIM0eYYVGs833AHDCIl4s=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "7c74352f2f7eca1925729f5c9c80cb89df8e74a2",
"rev": "6daf4de0662e1d895d220a4a4ddb356eb000abe9",
"type": "github"
},
"original": {
@@ -183,11 +107,11 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1711819797,
"narHash": "sha256-tNeB6emxj74Y6ctwmsjtMlzUMn458sBmwnD35U5KIM4=",
"lastModified": 1708819810,
"narHash": "sha256-1KosU+ZFXf31GPeCBNxobZWMgHsSOJcrSFA6F2jhzdE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2b4e3ca0091049c6fbb4908c66b05b77eaef9f0c",
"rev": "89a2a12e6c8c6a56c72eb3589982c8e2f89c70ea",
"type": "github"
},
"original": {
@@ -199,11 +123,11 @@
},
"nixpkgs-unpatched": {
"locked": {
"lastModified": 1712398506,
"narHash": "sha256-oopwPeBKBXQEw2BlyK2jEs2farZ5uMjAZU7H4FpGuGE=",
"lastModified": 1708995544,
"narHash": "sha256-YJgLopKOKVTggnKzjX4OiAS22hx/vNv397DcsAyTZgY=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "c58702222e0a29fd01cc42d70737d699995f6389",
"rev": "5bd8df40204f47a12263f3614c72cd5b6832a9a0",
"type": "github"
},
"original": {
@@ -213,35 +137,12 @@
"type": "github"
}
},
"nixpkgs-wayland": {
"inputs": {
"flake-compat": "flake-compat",
"lib-aggregate": "lib-aggregate",
"nix-eval-jobs": "nix-eval-jobs",
"nixpkgs": [
"nixpkgs-unpatched"
]
},
"locked": {
"lastModified": 1712237761,
"narHash": "sha256-NoMBBCADTms3yx5BL+sbc7vfDivNiYULO6t9GBAsPt0=",
"owner": "nix-community",
"repo": "nixpkgs-wayland",
"rev": "9b77653338f52da4b498abdf4835efb6ff6e453e",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs-wayland",
"type": "github"
}
},
"root": {
"inputs": {
"mobile-nixos": "mobile-nixos",
"nix-fast-build": "nix-fast-build",
"nixpkgs-next-unpatched": "nixpkgs-next-unpatched",
"nixpkgs-unpatched": "nixpkgs-unpatched",
"nixpkgs-wayland": "nixpkgs-wayland",
"sops-nix": "sops-nix",
"uninsane-dot-org": "uninsane-dot-org"
}
@@ -254,11 +155,11 @@
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1711855048,
"narHash": "sha256-HxegAPnQJSC4cbEbF4Iq3YTlFHZKLiNTk8147EbLdGg=",
"lastModified": 1708987867,
"narHash": "sha256-k2lDaDWNTU5sBVHanYzjDKVDmk29RHIgdbbXu5sdzBA=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "99b1e37f9fc0960d064a7862eb7adfb92e64fa10",
"rev": "a1c8de14f60924fafe13aea66b46157f0150f4cf",
"type": "github"
},
"original": {
@@ -267,35 +168,19 @@
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs-wayland",
"nix-eval-jobs",
"nix-fast-build",
"nixpkgs"
]
},
"locked": {
"lastModified": 1702979157,
"narHash": "sha256-RnFBbLbpqtn4AoJGXKevQMCGhra4h6G2MPcuTSZZQ+g=",
"lastModified": 1698438538,
"narHash": "sha256-AWxaKTDL3MtxaVTVU5lYBvSnlspOS0Fjt8GxBgnU0Do=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "2961375283668d867e64129c22af532de8e77734",
"rev": "5deb8dc125a9f83b65ca86cf0c8167c46593e0b1",
"type": "github"
},
"original": {
@@ -311,11 +196,11 @@
]
},
"locked": {
"lastModified": 1711371733,
"narHash": "sha256-+brjlMyLVnVADY31sN82Ap0IsPE2WZEwHUd94sY6BXI=",
"lastModified": 1707981105,
"narHash": "sha256-YCU1eNslBHabjP+OCY+BxPycEFO9SRUts10MrN9QORE=",
"ref": "refs/heads/master",
"rev": "b9502e6f190752d327f8cee7fa4b139094bd7c16",
"revCount": 237,
"rev": "bb10cd8853d05191e4d62947d93687c462e92c30",
"revCount": 235,
"type": "git",
"url": "https://git.uninsane.org/colin/uninsane"
},

View File

@@ -48,11 +48,6 @@
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=nixos-staging-next";
nixpkgs-next-unpatched.url = "github:nixos/nixpkgs?ref=staging-next";
nixpkgs-wayland = {
url = "github:nix-community/nixpkgs-wayland";
inputs.nixpkgs.follows = "nixpkgs-unpatched";
};
mobile-nixos = {
# <https://github.com/nixos/mobile-nixos>
# only used for building disk images, not relevant after deployment
@@ -62,6 +57,10 @@
url = "github:nixos/mobile-nixos?ref=d25d3b87e7f300d8066e31d792337d9cd7ecd23b";
flake = false;
};
nix-fast-build = {
# https://github.com/Mic92/nix-fast-build
url = "github:Mic92/nix-fast-build";
};
sops-nix = {
# <https://github.com/Mic92/sops-nix>
# used to distribute secrets to my hosts
@@ -81,8 +80,8 @@
self,
nixpkgs-unpatched,
nixpkgs-next-unpatched ? nixpkgs-unpatched,
nixpkgs-wayland,
mobile-nixos,
nix-fast-build,
sops-nix,
uninsane-dot-org,
...
@@ -103,7 +102,30 @@
patchNixpkgs = variant: nixpkgs: (import ./nixpatches/flake.nix).outputs {
inherit variant nixpkgs;
self = patchNixpkgs variant nixpkgs;
};
} // {
# sourceInfo includes fields (square brackets for the ones which are not always present):
# - [dirtyRev]
# - [dirtyShortRev]
# - lastModified
# - lastModifiedDate
# - narHash
# - outPath
# - [rev]
# - [revCount]
# - [shortRev]
# - submodules
#
# these values are used within nixpkgs:
# - to give a friendly name to the nixos system (`readlink /run/current-system` -> `...nixos-system-desko-24.05.20240227.dirty`)
# - to alias `import <nixpkgs>` so that nix uses the system's nixpkgs when called externally (supposedly).
#
# these values seem to exist both within the `sourceInfo` attrset and at the top-level.
# for a list of all implicit flake outputs (which is what these seem to be):
# $ nix-repl
# > lf .
# > <tab>
inherit (self) sourceInfo;
} // self.sourceInfo;
nixpkgs' = patchNixpkgs "master" nixpkgs-unpatched;
nixpkgsCompiledBy = system: nixpkgs'.legacyPackages."${system}";
@@ -190,18 +212,12 @@
let
mobile = (import "${mobile-nixos}/overlay/overlay.nix");
uninsane = uninsane-dot-org.overlays.default;
wayland = final: prev: {
# default is to dump the packages into `waylandPkgs` *and* the toplevel.
# but i just want the `waylandPkgs` set
inherit (nixpkgs-wayland.overlays.default final prev)
waylandPkgs
new-wayland-protocols #< 2024/03/10: nixpkgs-wayland assumes this will be in the toplevel
;
};
# TODO: why do i have to use `self.inputs.nix-fast-build` instead of just `nix-fast-build` here?
nix-fast-build = (_: prev: self.inputs.nix-fast-build.packages."${prev.stdenv.system}" or {});
in
(mobile final prev)
// (nix-fast-build final prev)
// (uninsane final prev)
// (wayland final prev)
;
};
@@ -257,8 +273,6 @@
pkgs = self.legacyPackages."x86_64-linux";
sanePkgs = import ./pkgs { inherit pkgs; };
deployScript = host: addr: action: pkgs.writeShellScript "deploy-${host}" ''
set -e
host="${host}"
addr="${addr}"
action="${if action != null then action else ""}"
@@ -272,8 +286,8 @@
fi
}
nix build ".#nixosConfigurations.$host.config.system.build.toplevel" --out-link "./build/result-$host" "$@"
storePath="$(readlink ./build/result-$host)"
nix build ".#nixosConfigurations.$host.config.system.build.toplevel" --out-link "./result-$host" "$@"
storePath="$(readlink ./result-$host)"
# mimic `nixos-rebuild --target-host`, in effect:
# - nix-copy-closure ...
@@ -376,8 +390,6 @@
- or `nix run '.#preDeploy'` to target all hosts
- `nix run '.#check'`
- make sure all systems build; NUR evaluates
- `nix run '.#bench'`
- benchmark the eval time of common targets this flake provides
specific build targets of interest:
- `nix build '.#imgs.rescue'`
@@ -521,7 +533,6 @@
--option allow-import-from-derivation true \
--drv-path --show-trace \
-I nixpkgs=${nixpkgs-unpatched} \
-I nixpkgs-overlays=${./.}/hosts/common/nix/overlay \
-I ../../ \
| tee # tee to prevent interactive mode
'');
@@ -533,7 +544,7 @@
checkHost = host: let
shellHost = pkgs.lib.replaceStrings [ "-" ] [ "_" ] host;
in ''
nix build -v '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./build/result-${host} -j2 "$@"
nix build -v '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${host} -j2 "$@"
RC_${shellHost}=$?
'';
in builtins.toString (pkgs.writeShellScript
@@ -582,31 +593,7 @@
check.rescue = {
type = "app";
program = builtins.toString (pkgs.writeShellScript "check-rescue" ''
nix build -v '.#imgs.rescue' --out-link ./build/result-rescue-img -j2
'');
};
bench = {
type = "app";
program = builtins.toString (pkgs.writeShellScript "bench" ''
doBench() {
attrPath="$1"
shift
echo -n "benchmarking eval of '$attrPath'... "
/run/current-system/sw/bin/time -f "%e sec" -o /dev/stdout \
nix eval --no-eval-cache --quiet --raw ".#$attrPath" --apply 'result: if result != null then "" else "unexpected null"' $@ 2> /dev/null
}
if [ -n "$1" ]; then
doBench "$@"
else
doBench hostConfigs
doBench hostConfigs.lappy
doBench hostConfigs.lappy.sane.programs
doBench hostConfigs.lappy.sane.users.colin
doBench hostConfigs.lappy.sane.fs
doBench hostConfigs.lappy.environment.systemPackages
fi
nix build -v '.#imgs.rescue' --out-link ./result-rescue-img -j2
'');
};
};

View File

@@ -2,6 +2,7 @@
{
imports = [
./fs.nix
./polyfill.nix
];
sane.roles.client = true;

View File

@@ -0,0 +1,41 @@
# doesn't actually *enable* anything,
# but sets up any modules such that if they *were* enabled, they'll act as expected.
{ pkgs, ... }:
{
sane.gui.sxmo = {
noidle = true; #< power button requires 1s hold, which makes it impractical to be dealing with.
settings = {
# XXX: make sure the user is part of the `input` group!
SXMO_LISGD_INPUT_DEVICE = "/dev/input/by-id/usb-Wacom_Co._Ltd._Pen_and_multitouch_sensor-event-if00";
# these identifiers are from `swaymsg -t get_inputs`
SXMO_VOLUME_BUTTON = "1:1:AT_Translated_Set_2_keyboard";
# SXMO_VOLUME_BUTTON = "none";
# N.B.: thinkpad's power button requires a full second press to do anything
SXMO_POWER_BUTTON = "0:1:Power_Button";
# SXMO_POWER_BUTTON = "none";
SXMO_DISABLE_LEDS = "1";
SXMO_UNLOCK_IDLE_TIME = "120"; # default
# sxmo tries to determine device type from /proc/device-tree/compatible,
# but that doesn't seem to exist on NixOS? (or maybe it just doesn't exist
# on non-aarch64 builds).
# the device type informs (at least):
# - SXMO_WIFI_MODULE
# - SXMO_RTW_SCAN_INTERVAL
# - SXMO_TOUCHSCREEN_ID
# - SXMO_MONITOR
# - SXMO_ALSA_CONTROL_NAME
# - SXMO_SWAY_SCALE
# see <repo:mil/sxmo-utils:scripts/deviceprofiles>
# SXMO_DEVICE_NAME = "pine64,pinephone-1.2";
# if sxmo doesn't know the device, it can't decide whether to use one_button or three_button mode
# and so it just wouldn't handle any button inputs (sxmo_hook_inputhandler.sh not on path)
SXMO_DEVICE_NAME = "three_button_touchscreen";
};
package = (pkgs.sxmo-utils.override { preferSystemd = true; }).overrideAttrs (base: {
postPatch = (base.postPatch or "") + ''
# after volume-button navigation mode, restore full keyboard functionality
cp ${./xkb_mobile_normal_buttons} ./configs/xkb/xkb_mobile_normal_buttons
'';
});
};
}

View File

@@ -1,22 +1,12 @@
# tow-boot: <https://tow-boot.org>
# docs (pinephone specific): <https://github.com/Tow-Boot/Tow-Boot/tree/development/boards/pine64-pinephoneA64>
# LED and button behavior is defined here: <https://github.com/Tow-Boot/Tow-Boot/blob/development/modules/tow-boot/phone-ux.nix>
# - hold VOLDOWN: enter recovery mode
# - LED will turn aqua instead of yellow
# - recovery mode would ordinarily allow a selection of entries, but for pinephone i guess it doesn't do anything?
# - hold VOLUP: force it to load the OS from eMMC?
# - LED will turn blue instead of yellow
# boot LEDs:
# - yellow = entered tow-boot
# - 10 red flashes => poweroff means tow-boot couldn't boot into the next stage (i.e. distroboot)
# - distroboot: <https://source.denx.de/u-boot/u-boot/-/blob/v2022.04/doc/develop/distro.rst>)
{ config, pkgs, ... }:
{
# we need space in the GPT header to place tow-boot.
# only actually need 1 MB, but better to over-allocate than under-allocate
sane.image.extraGPTPadding = 16 * 1024 * 1024;
sane.image.firstPartGap = 0;
sane.image.installBootloader = ''
dd if=${pkgs.tow-boot-pinephone}/Tow-Boot.noenv.bin of=$out/nixos.img bs=1024 seek=8 conv=notrunc
system.build.img = pkgs.runCommand "nixos_full-disk-image.img" {} ''
cp -v ${config.system.build.img-without-firmware}/nixos.img $out
chmod +w $out
dd if=${pkgs.tow-boot-pinephone}/Tow-Boot.noenv.bin of=$out bs=1024 seek=8 conv=notrunc
'';
}

View File

@@ -36,15 +36,10 @@
sops.secrets.colin-passwd.neededForUsers = true;
# sane.gui.sxmo.enable = true;
sane.programs.sway.enableFor.user.colin = true;
sane.programs.swaylock.enableFor.user.colin = false; #< not usable on touch
sane.programs.schlock.enableFor.user.colin = true;
sane.programs.swayidle.config.actions.screenoff.delay = 300;
sane.programs.swayidle.config.actions.screenoff.enable = true;
sane.programs.sane-input-handler.enableFor.user.colin = true;
sane.gui.sxmo.enable = true;
# sane.programs.consoleUtils.enableFor.user.colin = false;
# sane.programs.guiApps.enableFor.user.colin = false;
sane.programs.blueberry.enableFor.user.colin = false; # bluetooth manager: doesn't cross compile!
sane.programs.fcitx5.enableFor.user.colin = false; # does not cross compile
sane.programs.mercurial.enableFor.user.colin = false; # does not cross compile
sane.programs.nvme-cli.enableFor.system = false; # does not cross compile (libhugetlbfs)
@@ -57,6 +52,7 @@
# sane.programs.signal-desktop.config.autostart = true; # TODO: enable once electron stops derping.
# sane.programs."gnome.geary".config.autostart = true;
# sane.programs.calls.config.autostart = true;
sane.programs.mpv.config.vo = "wlshm"; #< see hosts/common/programs/mpv.nix for details
sane.programs.firefox.mime.priority = 300; # prefer other browsers when possible
# HACK/TODO: make `programs.P.env.VAR` behave according to `mime.priority`
@@ -128,12 +124,42 @@
# enable rotation sensor
hardware.sensor.iio.enable = true;
# TODO: move elsewhere...
systemd.services.ModemManager.serviceConfig = {
# N.B.: the extra "" in ExecStart serves to force upstream ExecStart to be ignored
ExecStart = [ "" "${pkgs.modemmanager}/bin/ModemManager --debug" ];
# --debug sets DEBUG level logging: so reset
ExecStartPost = [ "${pkgs.modemmanager}/bin/mmcli --set-logging=INFO" ];
# inject specialized alsa configs via the environment.
# specifically, this gets the pinephone headphones & internal earpiece working.
# see pkgs/patched/alsa-ucm-conf for more info.
environment.variables.ALSA_CONFIG_UCM2 = "/run/current-system/sw/share/alsa/ucm2";
environment.pathsToLink = [ "/share/alsa/ucm2" ];
environment.systemPackages = [
(pkgs.alsa-ucm-conf-sane.override {
# internal speaker has a tendency to break :(
preferEarpiece = true;
})
];
systemd = let
ucm-env = config.environment.variables.ALSA_CONFIG_UCM2;
in {
# cribbed from <repo:nixos/mobile-nixos:modules/quirks/audio.nix>
# pipewire
user.services.pipewire.environment.ALSA_CONFIG_UCM2 = ucm-env;
user.services.pipewire-pulse.environment.ALSA_CONFIG_UCM2 = ucm-env;
user.services.wireplumber.environment.ALSA_CONFIG_UCM2 = ucm-env;
services.pipewire.environment.ALSA_CONFIG_UCM2 = ucm-env;
services.pipewire-pulse.environment.ALSA_CONFIG_UCM2 = ucm-env;
services.wireplumber.environment.ALSA_CONFIG_UCM2 = ucm-env;
# pulseaudio
# user.services.pulseaudio.environment.ALSA_CONFIG_UCM2 = ucm-env;
# services.pulseaudio.environment.ALSA_CONFIG_UCM2 = ucm-env;
# TODO: move elsewhere...
services.ModemManager.serviceConfig = {
# N.B.: the extra "" in ExecStart serves to force upstream ExecStart to be ignored
ExecStart = [ "" "${pkgs.modemmanager}/bin/ModemManager --debug" ];
# --debug sets DEBUG level logging: so reset
ExecStartPost = [ "${pkgs.modemmanager}/bin/mmcli --set-logging=INFO" ];
};
};
services.udev.extraRules = let

View File

@@ -85,7 +85,6 @@ in
"lima.sched_timeout_ms=2000"
];
# services.xserver.displayManager.job.preStart = ensureHWReady;
# systemd.services.greetd.preStart = ensureHWReady;
systemd.services.unl0kr.preStart = ensureHWReady;
services.xserver.displayManager.job.preStart = ensureHWReady;
systemd.services.greetd.preStart = ensureHWReady;
}

View File

@@ -24,22 +24,73 @@
backlight = "backlight"; # /sys/class/backlight/*backlight*/brightness
};
sane.programs.alacritty.config.fontSize = 9;
sane.gui.sxmo = {
nogesture = true;
settings = {
### hardware: touch screen
SXMO_LISGD_INPUT_DEVICE = "/dev/input/by-path/platform-1c2ac00.i2c-event";
# vol and power are detected correctly by upstream
sane.programs.sway.config = {
font = "pango:monospace 10";
mod = "Mod1"; # prefer Alt
workspace_layout = "tabbed";
};
### preferences
DEFAULT_COUNTRY = "US";
sane.programs.waybar.config = {
fontSize = 14;
height = 26;
persistWorkspaces = [ "1" "2" "3" "4" "5" ];
modules.media = false;
modules.network = false;
modules.perf = false;
modules.windowTitle = false;
# TODO: show modem state
SXMO_AUTOROTATE = "1"; # enable auto-rotation at launch. has no meaning in stock/upstream sxmo-utils
# BEMENU lines (wayland DMENU):
# - camera is 9th entry
# - flashlight is 10th entry
# - config is 14th entry. inside that:
# - autorotate is 11th entry
# - system menu is 19th entry
# - close is 20th entry
# - power is 15th entry
# - close is 16th entry
SXMO_BEMENU_LANDSCAPE_LINES = "11"; # default 8
SXMO_BEMENU_PORTRAIT_LINES = "16"; # default 16
SXMO_LOCK_IDLE_TIME = "15"; # how long between screenoff -> lock -> back to screenoff (default: 8)
# gravity: how far to tilt the device before the screen rotates
# for a given setting, normal <-> invert requires more movement then left <-> right
# i.e. the settingd doesn't feel completely symmetric
# SXMO_ROTATION_GRAVITY default is 16374
# SXMO_ROTATION_GRAVITY = "12800"; # uncomfortably high
# SXMO_ROTATION_GRAVITY = "12500"; # kinda uncomfortable when walking
SXMO_ROTATION_GRAVITY = "12000";
SXMO_SCREENSHOT_DIR = "/home/colin/Pictures"; # default: "$HOME"
# sway/wayland scaling:
# - conflicting info out there on how scaling actually works
# at the least, for things where it matters (mpv), it seems like scale settings have 0 effect on perf
# ways to enforce scaling:
# - <https://wiki.archlinux.org/title/HiDPI>
# - `swaymsg -- output DSI-1 scale 2.0` (scales everything)
# - `dconf write /org/gnome/desktop/interface/text-scaling-factor 2.0` (scales ONLY TEXT)
# - `GDK_DPI_SCALE=2.0` (scales ONLY TEXT)
#
# application notes:
# - cozy: in landscape, playback position is not visible unless scale <= 1.7
# - if in a tab, then scale 1.6 is the max
# SXMO_SWAY_SCALE = "1.5"; # hard to press gPodder icons
SXMO_SWAY_SCALE = "1.6";
# SXMO_SWAY_SCALE = "1.8";
# SXMO_SWAY_SCALE = "2";
SXMO_WORKSPACE_WRAPPING = "5"; # how many workspaces. default: 4
# wvkbd layers:
# - full
# - landscape
# - special (e.g. coding symbols like ~)
# - emoji
# - nav
# - simple (like landscape, but no parens/tab/etc; even fewer chars)
# - simplegrid (simple, but grid layout)
# - dialer (digits)
# - cyrillic
# - arabic
# - persian
# - greek
# - georgian
WVKBD_LANDSCAPE_LAYERS = "landscape,special,emoji";
WVKBD_LAYERS = "full,special,emoji";
};
};
}

View File

@@ -6,7 +6,7 @@
boot.loader.efi.canTouchEfiVariables = false;
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
sane.persist.enable = false; # what we mean here is that the image is immutable; `/` is still tmpfs.
sane.persist.enable = false;
sane.nixcache.enable = false; # don't want to be calling out to dead machines that we're *trying* to rescue
# auto-login at shell

View File

@@ -8,7 +8,6 @@
# - 1. identify disk IDs: `ls -l /dev/disk/by-id`
# - 2. pool these disks: `zpool create -f -m legacy pool raidz ata-ST4000VN008-2DR166_WDH0VB45 ata-ST4000VN008-2DR166_WDH17616 ata-ST4000VN008-2DR166_WDH0VC8Q ata-ST4000VN008-2DR166_WDH17680`
# - legacy documented: <https://superuser.com/questions/790036/what-is-a-zfs-legacy-mount-point>
# - 3. enable acl support: `zfs set acltype=posixacl pool`
#
# import pools: `zpool import pool`
# show zfs datasets: `zfs list` (will be empty if haven't imported)
@@ -26,7 +25,6 @@
# scrub all zfs pools weekly:
services.zfs.autoScrub.enable = true;
boot.extraModprobeConfig = ''
### zfs_arc_max tunable:
# ZFS likes to use half the ram for its own cache and let the kernel push everything else to swap.
# so, reduce its cache size
# see: <https://askubuntu.com/a/1290387>
@@ -35,13 +33,7 @@
# for all tunables, see: `man 4 zfs`
# to update these parameters without rebooting:
# - `echo '4294967296' | sane-sudo-redirect /sys/module/zfs/parameters/zfs_arc_max`
### zfs_bclone_enabled tunable
# this allows `cp --reflink=always FOO BAR` to work. i.e. shallow copies.
# it's unstable as of 2.2.3. led to *actual* corruption in 2.2.1, but hopefully better by now.
# - <https://github.com/openzfs/zfs/issues/405>
# note that `du -h` won't *always* show the reduced size for reflink'd files (?).
# `zpool get all | grep clone` seems to be the way to *actually* see how much data is being deduped
options zfs zfs_arc_max=4294967296 zfs_bclone_enabled=1
options zfs zfs_arc_max=4294967296
'';
# to be able to mount the pool like this, make sure to tell zfs to NOT manage it itself.
# otherwise local-fs.target will FAIL and you will be dropped into a rescue shell.
@@ -51,7 +43,6 @@
fileSystems."/mnt/pool" = {
device = "pool";
fsType = "zfs";
options = [ "acl" ]; #< not sure if this `acl` flag is actually necessary. it mounts without it.
};
# services.zfs.zed = ... # TODO: zfs can send me emails when disks fail
sane.programs.sysadminUtils.suggestedPrograms = [ "zfs" ];
@@ -91,45 +82,61 @@
};
sane.fs."/mnt/usb-hdd".mount = {};
# FIRST TIME SETUP FOR MEDIA DIRECTORY:
# - set the group stick bit: `sudo find /var/media -type d -exec chmod g+s {} +`
# - this ensures new files/dirs inherit the group of their parent dir (instead of the user who creates them)
# - ensure everything under /var/media is mounted with `-o acl`, to support acls
# - ensure all files are rwx by group: `setfacl --recursive --modify d:g::rwx /var/media`
# - alternatively, `d:g:media:rwx` to grant `media` group even when file has a different owner, but that's a bit complex
sane.persist.sys.byStore.ext = [{
path = "/var/media";
user = "colin";
group = "media";
mode = "0775";
}];
sane.fs."/var/media/archive".dir = {};
sane.persist.sys.byStore.plaintext = [
# TODO: this is overly broad; only need media and share directories to be persisted
{ user = "colin"; group = "users"; path = "/var/lib/uninsane"; method = "bind"; }
];
# force some problematic directories to always get correct permissions:
sane.fs."/var/lib/uninsane/media".dir.acl = {
user = "colin"; group = "media"; mode = "0775";
};
sane.fs."/var/lib/uninsane/media/archive".dir = {};
# this is file.text instead of symlink.text so that it may be read over a remote mount (where consumers might not have any /nix/store/.../README.md path)
sane.fs."/var/media/archive/README.md".file.text = ''
sane.fs."/var/lib/uninsane/media/archive/README.md".file.text = ''
this directory is for media i wish to remove from my library,
but keep for a short time in case i reverse my decision.
treat it like a system trash can.
'';
sane.fs."/var/media/Books".dir = {};
sane.fs."/var/media/Books/Audiobooks".dir = {};
sane.fs."/var/media/Books/Books".dir = {};
sane.fs."/var/media/Books/Visual".dir = {};
sane.fs."/var/media/collections".dir = {};
# sane.fs."/var/media/datasets".dir = {};
sane.fs."/var/media/freeleech".dir = {};
sane.fs."/var/media/Music".dir = {};
sane.fs."/var/media/Pictures".dir = {};
sane.fs."/var/media/Videos".dir = {};
sane.fs."/var/media/Videos/Film".dir = {};
sane.fs."/var/media/Videos/Shows".dir = {};
sane.fs."/var/media/Videos/Talks".dir = {};
sane.fs."/var/lib/uninsane/media/Books".dir = {};
sane.fs."/var/lib/uninsane/media/Books/Audiobooks".dir = {};
sane.fs."/var/lib/uninsane/media/Books/Books".dir = {};
sane.fs."/var/lib/uninsane/media/Books/Visual".dir = {};
sane.fs."/var/lib/uninsane/media/collections".dir = {};
sane.fs."/var/lib/uninsane/media/datasets".dir = {};
sane.fs."/var/lib/uninsane/media/freeleech".dir = {};
sane.fs."/var/lib/uninsane/media/Music".dir = {};
sane.fs."/var/lib/uninsane/media/Pictures".dir = {};
sane.fs."/var/lib/uninsane/media/Videos".dir = {};
sane.fs."/var/lib/uninsane/media/Videos/Film".dir = {};
sane.fs."/var/lib/uninsane/media/Videos/Shows".dir = {};
sane.fs."/var/lib/uninsane/media/Videos/Talks".dir = {};
# this is file.text instead of symlink.text so that it may be read over a remote mount (where consumers might not have any /nix/store/.../README.md path)
sane.fs."/var/lib/uninsane/datasets/README.md".file.text = ''
this directory may seem redundant with ../media/datasets. it isn't.
this directory exists on SSD, allowing for speedy access to specific datasets when necessary.
the contents should be a subset of what's in ../media/datasets.
'';
# make sure large media is stored to the HDD
sane.persist.sys.byStore.ext = [
{
user = "colin";
group = "users";
mode = "0777";
path = "/var/lib/uninsane/media/Videos";
}
{
user = "colin";
group = "users";
mode = "0777";
path = "/var/lib/uninsane/media/freeleech";
}
{
user = "colin";
group = "users";
mode = "0777";
path = "/var/lib/uninsane/media/datasets";
}
];
# btrfs doesn't easily support swapfiles
# swapDevices = [

View File

@@ -87,7 +87,7 @@ in
}
];
preSetup = ''
${ip} netns add ovpns || (test -e /run/netns/ovpns && echo "ovpns already exists")
${ip} netns add ovpns || echo "ovpns already exists"
'';
postShutdown = ''
${in-ns} ip link del ovpns-veth-b || echo "couldn't delete ovpns-veth-b"

View File

@@ -9,7 +9,7 @@
fileSystems."/var/export/media" = {
# everything in here could be considered publicly readable (based on the viewer's legal jurisdiction)
device = "/var/media";
device = "/var/lib/uninsane/media";
options = [ "rbind" ];
};
# fileSystems."/var/export/playground" = {

View File

@@ -26,7 +26,7 @@
description = "NFS server portmapper";
};
sane.ports.ports."2049" = {
protocol = [ "tcp" "udp" ];
protocol = [ "tcp" ];
visibleTo.lan = true;
description = "NFS server";
};
@@ -51,23 +51,6 @@
services.nfs.server.mountdPort = 4002;
services.nfs.server.statdPort = 4000;
services.nfs.extraConfig = ''
[nfsd]
# XXX: NFS over UDP REQUIRES SPECIAL CONFIG TO AVOID DATA LOSS.
# see `man 5 nfs`: "Using NFS over UDP on high-speed links".
# it's actually just a general property of UDP over IPv4 (IPv6 fixes it).
# both the client and the server should configure a shorter-than-default IPv4 fragment reassembly window to mitigate.
# OTOH, tunneling NFS over Wireguard also bypasses this weakness, because a mis-assembled packet would not have a valid signature.
udp=y
[exports]
# all export paths are relative to rootdir.
# for NFSv4, the export with fsid=0 behaves as `/` publicly,
# but NFSv3 implements no such feature.
# using `rootdir` instead of relying on `fsid=0` allows consistent export paths regardless of NFS proto version
rootdir=/var/export
'';
# format:
# fspoint visibility(options)
# options:
@@ -102,20 +85,13 @@
in "${export} 10.78.79.0/22(${lib.concatStringsSep "," lanOpts}) 10.0.10.0/24(${lib.concatStringsSep "," vpnOpts})";
in lib.concatStringsSep "\n" [
(fmtExport {
export = "/";
export = "/var/export";
baseOpts = [ "crossmnt" "fsid=root" ];
extraLanOpts = [ "ro" ];
extraVpnOpts = [ "rw" "no_root_squash" ];
})
(fmtExport {
# provide /media as an explicit export. NFSv4 can transparently mount a subdir of an export, but NFSv3 can only mount paths which are exports.
export = "/media";
baseOpts = [ "crossmnt" ]; # TODO: is crossmnt needed here?
extraLanOpts = [ "ro" ];
extraVpnOpts = [ "rw" "no_root_squash" ];
})
(fmtExport {
export = "/playground";
export = "/var/export/playground";
baseOpts = [
"mountpoint"
"all_squash"

View File

@@ -6,25 +6,107 @@
# - nixos example: <repo:nixos/nixpkgs:nixos/tests/sftpgo.nix>
#
# sftpgo is a FTP server that also supports WebDAV, SFTP, and web clients.
#
# TODO: change umask so sftpgo-created files default to 644.
# - it does indeed appear that the 600 is not something sftpgo is explicitly doing.
{ config, lib, pkgs, sane-lib, ... }:
let
sftpgo_external_auth_hook = pkgs.static-nix-shell.mkPython3Bin {
# user permissions:
# - see <repo:drakkan/sftpgo:internal/dataprovider/user.go>
# - "*" = grant all permissions
# - read-only perms:
# - "list" = list files and directories
# - "download"
# - rw perms:
# - "upload"
# - "overwrite" = allow uploads to replace existing files
# - "delete" = delete files and directories
# - "delete_files"
# - "delete_dirs"
# - "rename" = rename files and directories
# - "rename_files"
# - "rename_dirs"
# - "create_dirs"
# - "create_symlinks"
# - "chmod"
# - "chown"
# - "chtimes" = change atime/mtime (access and modification times)
#
# home_dir:
# - it seems (empirically) that a user can't cd above their home directory.
# though i don't have a reference for that in the docs.
authResponseSuccess = {
status = 1;
username = "anonymous";
expiration_date = 0;
home_dir = "/var/export";
# uid/gid 0 means to inherit sftpgo uid.
# - i.e. users can't read files which Linux user `sftpgo` can't read
# - uploaded files belong to Linux user `sftpgo`
# other uid/gid values aren't possible for localfs backend, unless i let sftpgo use `sudo`.
uid = 0;
gid = 0;
# uid = 65534;
# gid = 65534;
max_sessions = 0;
# quota_*: 0 means to not use SFTP's quota system
quota_size = 0;
quota_files = 0;
permissions = {
"/" = [ "list" "download" ];
"/playground" = [
# read-only:
"list"
"download"
# write:
"upload"
"overwrite"
"delete"
"rename"
"create_dirs"
"create_symlinks"
# intentionally omitted:
# "chmod"
# "chown"
# "chtimes"
];
};
upload_bandwidth = 0;
download_bandwidth = 0;
filters = {
allowed_ip = [];
denied_ip = [];
};
public_keys = [];
# other fields:
# ? groups
# ? virtual_folders
};
authResponseFail = {
username = "";
};
authSuccessJson = pkgs.writeText "sftp-auth-success.json" (builtins.toJSON authResponseSuccess);
authFailJson = pkgs.writeText "sftp-auth-fail.json" (builtins.toJSON authResponseFail);
unwrappedAuthProgram = pkgs.static-nix-shell.mkBash {
pname = "sftpgo_external_auth_hook";
srcRoot = ./.;
pkgs = [ "coreutils" ];
};
authProgram = pkgs.writeShellScript "sftpgo-auth-hook" ''
${unwrappedAuthProgram}/bin/sftpgo_external_auth_hook ${authFailJson} ${authSuccessJson}
'';
in
{
# Client initiates a FTP "control connection" on port 21.
# - this handles the client -> server commands, and the server -> client status, but not the actual data
# - file data, directory listings, etc need to be transferred on an ephemeral "data port".
# - 50000-50100 is a common port range for this.
# 50000 is used by soulseek.
sane.ports.ports = {
"21" = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-FTP server";
};
} // (sane-lib.mapToAttrs
@@ -33,11 +115,10 @@ in
value = {
protocol = [ "tcp" ];
visibleTo.lan = true;
visibleTo.wan = true;
description = "colin-FTP server data port range";
};
})
(lib.range 50050 50100)
(lib.range 50000 50100)
);
services.sftpgo = {
@@ -53,7 +134,7 @@ in
debug = true;
}
{
# binding this means any LAN client can connect (also WAN traffic forwarded from the gateway)
# binding this means any LAN client can connect
address = "10.78.79.51";
port = 21;
debug = true;
@@ -64,25 +145,22 @@ in
disable_active_mode = true;
hash_support = true;
passive_port_range = {
start = 50050;
start = 50000;
end = 50100;
};
banner = ''
Welcome, friends, to Colin's FTP server! Also available via NFS on the same host, but LAN-only.
Read-only access (LAN-restricted):
Welcome, friends, to Colin's read-only FTP server! Also available via NFS on the same host.
Username: "anonymous"
Password: "anonymous"
CONFIGURE YOUR CLIENT FOR "PASSIVE" mode, e.g. `ftp --passive uninsane.org`.
Please let me know if anything's broken or not as it should be. Otherwise, browse and transfer freely :)
CONFIGURE YOUR CLIENT FOR "PASSIVE" mode, e.g. `ftp --passive uninsane.org`
Please let me know if anything's broken or not as it should be. Otherwise, browse and DL freely :)
'';
};
data_provider = {
driver = "memory";
external_auth_hook = "${sftpgo_external_auth_hook}/bin/sftpgo_external_auth_hook";
external_auth_hook = "${authProgram}";
# track_quota:
# - 0: disable quota tracking
# - 1: quota is updated on every upload/delete, even if user has no quota restriction
@@ -92,20 +170,17 @@ in
};
};
users.users.sftpgo.extraGroups = [
"export"
"media"
];
users.users.sftpgo.extraGroups = [ "export" ];
systemd.services.sftpgo = {
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
ReadWritePaths = [ "/var/export" ];
ReadOnlyPaths = [ "/var/export" ];
ReadWritePaths = [ "/var/export/playground" ];
Restart = "always";
RestartSec = "20s";
UMask = lib.mkForce "0002";
};
};
}

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])"
# vim: set filetype=python :
#!nix-shell -i bash -p coreutils
# vim: set filetype=bash :
#
# available environment variables:
# - SFTPGO_AUTHD_USERNAME
@@ -12,146 +12,12 @@
# - SFTPGO_AUTHD_KEYBOARD_INTERACTIVE
# - SFTPGO_AUTHD_TLS_CERT
#
# user permissions:
# - see <repo:drakkan/sftpgo:internal/dataprovider/user.go>
# - "*" = grant all permissions
# - read-only perms:
# - "list" = list files and directories
# - "download"
# - rw perms:
# - "upload"
# - "overwrite" = allow uploads to replace existing files
# - "delete" = delete files and directories
# - "delete_files"
# - "delete_dirs"
# - "rename" = rename files and directories
# - "rename_files"
# - "rename_dirs"
# - "create_dirs"
# - "create_symlinks"
# - "chmod"
# - "chown"
# - "chtimes" = change atime/mtime (access and modification times)
#
# home_dir:
# - it seems (empirically) that a user can't cd above their home directory.
# though i don't have a reference for that in the docs.
# call with <script_name> /path/to/fail/response.json /path/to/success/response.json
import crypt
import json
import os
from hmac import compare_digest
authFail = dict(username="")
PERM_RO = [ "list", "download" ]
PERM_RW = [
# read-only:
"list",
"download",
# write:
"upload",
"overwrite",
"delete",
"rename",
"create_dirs",
"create_symlinks",
# intentionally omitted:
# "chmod",
# "chown",
# "chtimes",
]
TRUSTED_CREDS = [
# /etc/shadow style creds.
# mkpasswd -m sha-512
# $<method>$<salt>$<hash>
"$6$Zq3c2u4ghUH4S6EP$pOuRt13sEKfX31OqPbbd1LuhS21C9MICMc94iRdTAgdAcJ9h95gQH/6Jf6Ie4Obb0oxQtojRJ1Pd/9QHOlFMW." #< m. rocket boy
]
def mkAuthOk(username: str, permissions: dict[str, list[str]]) -> dict:
return dict(
status = 1,
username = username,
expiration_date = 0,
home_dir = "/var/export",
# uid/gid 0 means to inherit sftpgo uid.
# - i.e. users can't read files which Linux user `sftpgo` can't read
# - uploaded files belong to Linux user `sftpgo`
# other uid/gid values aren't possible for localfs backend, unless i let sftpgo use `sudo`.
uid = 0,
gid = 0,
# uid = 65534,
# gid = 65534,
max_sessions = 0,
# quota_*: 0 means to not use SFTP's quota system
quota_size = 0,
quota_files = 0,
permissions = permissions,
upload_bandwidth = 0,
download_bandwidth = 0,
filters = dict(
allowed_ip = [],
denied_ip = [],
),
public_keys = [],
# other fields:
# ? groups
# ? virtual_folders
)
def isLan(ip: str) -> bool:
return ip.startswith("10.78.76.") \
or ip.startswith("10.78.77.") \
or ip.startswith("10.78.78.") \
or ip.startswith("10.78.79.")
def isWireguard(ip: str) -> bool:
return ip.startswith("10.0.10.")
def isTrustedCred(password: str) -> bool:
for cred in TRUSTED_CREDS:
_, method, salt, hash_ = cred.split("$")
# assert method == "6", f"unrecognized crypt entry: {cred}"
if crypt.crypt(password, f"${method}${salt}") == cred:
return True
return False
def getAuthResponse(ip: str, username: str, password: str) -> dict:
"""
return a sftpgo auth response either denying the user or approving them
with a set of permissions.
"""
if isTrustedCred(password) and username != "colin":
# allow r/w access from those with a special token
return mkAuthOk(username, permissions = {
"/": PERM_RW,
"/playground": PERM_RW,
})
if isWireguard(ip):
# allow any user from wireguard
return mkAuthOk(username, permissions = {
"/": PERM_RW,
"/playground": PERM_RW,
})
if isLan(ip):
if username == "anonymous":
# allow anonymous users on the LAN
return mkAuthOk("anonymous", permissions = {
"/": PERM_RO,
"/playground": PERM_RW,
})
return authFail
def main():
ip = os.environ.get("SFTPGO_AUTHD_IP", "")
username = os.environ.get("SFTPGO_AUTHD_USERNAME", "")
password = os.environ.get("SFTPGO_AUTHD_PASSWORD", "")
resp = getAuthResponse(ip, username, password)
print(json.dumps(resp))
if __name__ == "__main__":
main()
if [ "$SFTPGO_AUTHD_USERNAME" = "anonymous" ]; then
cat "$2"
else
cat "$1"
fi

View File

@@ -10,21 +10,16 @@ let
uiPort = 1234; # default ui port is 1234
backendPort = 8536; # default backend port is 8536
#^ i guess the "backend" port is used for federation?
pict-rs = pkgs.pict-rs;
# pict-rs = pkgs.pict-rs.overrideAttrs (upstream: {
# # as of v0.4.2, all non-GIF video is forcibly transcoded.
# # that breaks lemmy, because of the request latency.
# # and it eats up hella CPU.
# # pict-rs is iffy around video altogether: mp4 seems the best supported.
# # XXX: this patch no longer applies after 0.5.10 -> 0.5.11 update.
# # git log is hard to parse, but *suggests* that video is natively supported
# # better than in the 0.4.2 days, e.g. 5fd59fc5b42d31559120dc28bfef4e5002fb509e
# # "Change commandline flag to allow disabling video, since it is enabled by default"
# postPatch = (upstream.postPatch or "") + ''
# substituteInPlace src/validate.rs \
# --replace 'if transcode_options.needs_reencode() {' 'if false {'
# '';
# });
pict-rs = pkgs.pict-rs.overrideAttrs (upstream: {
# as of v 0.4.2, all non-GIF video is forcibly transcoded.
# that breaks lemmy, because of the request latency.
# and it eats up hella CPU.
# pict-rs is iffy around video altogether: mp4 seems the best supported.
postPatch = (upstream.postPatch or "") + ''
substituteInPlace src/validate.rs \
--replace 'if transcode_options.needs_reencode() {' 'if false {'
'';
});
in {
services.lemmy = {
enable = true;

View File

@@ -1,6 +1,5 @@
{ lib, ... }:
lib.mkIf false #< i don't actively use navidrome
{
sane.persist.sys.byStore.plaintext = [
{ user = "navidrome"; group = "navidrome"; path = "/var/lib/navidrome"; method = "bind"; }
@@ -10,7 +9,7 @@ lib.mkIf false #< i don't actively use navidrome
# docs: https://www.navidrome.org/docs/usage/configuration-options/
Address = "127.0.0.1";
Port = 4533;
MusicFolder = "/var/media/Music";
MusicFolder = "/var/lib/uninsane/media/Music";
CovertArtPriority = "*.jpg, *.JPG, *.png, *.PNG, embedded";
AutoImportPlaylists = false;
ScanSchedule = "@every 1h";

View File

@@ -55,8 +55,8 @@ in
# web blog/personal site
# alternative way to link stuff into the share:
# sane.fs."/var/www/sites/uninsane.org/share/Ubunchu".mount.bind = "/var/media/Books/Visual/HiroshiSeo/Ubunchu";
# sane.fs."/var/media/Books/Visual/HiroshiSeo/Ubunchu".dir = {};
# sane.fs."/var/lib/uninsane/share/Ubunchu".mount.bind = "/var/lib/uninsane/media/Books/Visual/HiroshiSeo/Ubunchu";
# sane.fs."/var/lib/uninsane/media/Books/Visual/HiroshiSeo/Ubunchu".dir = {};
services.nginx.virtualHosts."uninsane.org" = publog {
# a lot of places hardcode https://uninsane.org,
# and then when we mix http + non-https, we get CORS violations

View File

@@ -3,14 +3,10 @@
#
# config precedence (higher precedence overrules lower precedence):
# - Default Values < Environment Variables < YAML Configuraiton File < Command Line Arguments
#
# debugging:
# - soulseek is just *flaky*. if you see e.g. DNS errors, even though you can't replicate them via `dig` or `getent ahostsv4`, just give it 10 minutes to work out:
# - "Soulseek.AddressException: Failed to resolve address 'vps.slsknet.org': Resource temporarily unavailable"
{ config, lib, ... }:
{
sane.persist.sys.byStore.plaintext = [
{ user = "slskd"; group = "media"; path = "/var/lib/slskd"; method = "bind"; }
{ user = "slskd"; group = "slskd"; path = "/var/lib/slskd"; method = "bind"; }
];
sops.secrets."slskd_env" = {
owner = config.users.users.slskd.name;
@@ -19,7 +15,7 @@
users.users.slskd.extraGroups = [ "media" ];
sane.ports.ports."50300" = {
sane.ports.ports."50000" = {
protocol = [ "tcp" ];
# not visible to WAN: i run this in a separate netns
visibleTo.ovpn = true;
@@ -32,14 +28,12 @@
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://10.0.1.6:5030";
proxyPass = "http://10.0.1.6:5001";
proxyWebsockets = true;
};
};
services.slskd.enable = true;
services.slskd.domain = null; # i'll manage nginx for it
services.slskd.group = "media";
# env file, for auth (SLSKD_SLSK_PASSWORD, SLSKD_SLSK_USERNAME)
services.slskd.environmentFile = config.sops.secrets.slskd_env.path;
services.slskd.settings = {
@@ -50,13 +44,13 @@
# [Alias]/path/on/disk
# NOTE: Music library is quick to scan; videos take a solid 10min to scan.
# TODO: re-enable the other libraries
# "[Audioooks]/var/media/Books/Audiobooks"
# "[Books]/var/media/Books/Books"
# "[Manga]/var/media/Books/Visual"
# "[games]/var/media/games"
"[Music]/var/media/Music"
# "[Film]/var/media/Videos/Film"
# "[Shows]/var/media/Videos/Shows"
# "[Audioooks]/var/lib/uninsane/media/Books/Audiobooks"
# "[Books]/var/lib/uninsane/media/Books/Books"
# "[Manga]/var/lib/uninsane/media/Books/Visual"
# "[games]/var/lib/uninsane/media/games"
"[Music]/var/lib/uninsane/media/Music"
# "[Film]/var/lib/uninsane/media/Videos/Film"
# "[Shows]/var/lib/uninsane/media/Videos/Shows"
];
# directories.downloads = "..." # TODO
# directories.incomplete = "..." # TODO
@@ -74,6 +68,7 @@
NetworkNamespacePath = "/run/netns/ovpns";
Restart = lib.mkForce "always"; # exits "success" when it fails to connect to soulseek server
RestartSec = "60s";
Group = "media";
};
};
}

View File

@@ -22,41 +22,6 @@ let
--replace-fail 'set(TR_USER_AGENT_PREFIX "''${TR_SEMVER}")' 'set(TR_USER_AGENT_PREFIX "3.00")'
'';
});
download-dir = "/var/media/torrents";
torrent-done = pkgs.writeShellApplication {
name = "torrent-done";
runtimeInputs = with pkgs; [
rsync
util-linux
];
text = ''
destructive() {
if [ -n "''${TR_DRY_RUN-}" ]; then
echo "$*"
else
"$@"
fi
}
if [[ "$TR_TORRENT_DIR" =~ ^.*freeleech.*$ ]]; then
# freeleech torrents have no place in my permanent library
echo "freeleech: nothing to do"
exit 0
fi
if ! [[ "$TR_TORRENT_DIR" =~ ^${download-dir}/.*$ ]]; then
echo "unexpected torrent dir, aborting: $TR_TORRENT_DIR"
exit 0
fi
REL_DIR="''${TR_TORRENT_DIR#${download-dir}/}"
MEDIA_DIR="/var/media/$REL_DIR"
destructive mkdir -p "$(dirname "$MEDIA_DIR")"
destructive rsync -arv "$TR_TORRENT_DIR/" "$MEDIA_DIR/"
# dedupe the whole media library.
# yeah, a bit excessive: move this to a cron job if that's problematic.
destructive hardlink /var/media --reflink=always --ignore-time --verbose
'';
};
in
{
sane.persist.sys.byStore.plaintext = [
@@ -107,23 +72,11 @@ in
# see: https://git.zknt.org/mirror/transmission/commit/cfce6e2e3a9b9d31a9dafedd0bdc8bf2cdb6e876?lang=bg-BG
anti-brute-force-enabled = false;
inherit download-dir;
incomplete-dir = "${download-dir}/incomplete";
download-dir = "/var/lib/uninsane/media";
incomplete-dir = "/var/lib/uninsane/media/incomplete";
# transmission regularly fails to move stuff from the incomplete dir to the main one, so disable:
# TODO: uncomment this line!
incomplete-dir-enabled = false;
# env vars available in script:
# - TR_APP_VERSION - Transmission's short version string, e.g. `4.0.0`
# - TR_TIME_LOCALTIME
# - TR_TORRENT_BYTES_DOWNLOADED - Number of bytes that were downloaded for this torrent
# - TR_TORRENT_DIR - Location of the downloaded data
# - TR_TORRENT_HASH - The torrent's info hash
# - TR_TORRENT_ID
# - TR_TORRENT_LABELS - A comma-delimited list of the torrent's labels
# - TR_TORRENT_NAME - Name of torrent (not filename)
# - TR_TORRENT_TRACKERS - A comma-delimited list of the torrent's trackers' announce URLs
script-torrent-done-enabled = true;
script-torrent-done-filename = "${torrent-done}/bin/torrent-done";
};
systemd.services.transmission.after = [ "wireguard-wg-ovpns.service" ];
@@ -133,7 +86,6 @@ in
NetworkNamespacePath = "/run/netns/ovpns";
Restart = "on-failure";
RestartSec = "30s";
BindPaths = [ "/var/media" ]; #< so it can move completed torrents into the media library
};
# service to automatically backup torrents i add to transmission

View File

@@ -43,6 +43,17 @@
fi
'';
};
system.activationScripts.notifyActive = {
text = ''
# send a notification to any sway users logged in, that the system has been activated/upgraded.
# this probably doesn't work if more than one sway session exists on the system.
_notifyActiveSwaySock="$(echo /run/user/*/sway-ipc*.sock)"
if [ -e "$_notifyActiveSwaySock" ]; then
SWAYSOCK="$_notifyActiveSwaySock" ${config.sane.programs.sway.packageUnwrapped}/bin/swaymsg -- exec \
"${pkgs.libnotify}/bin/notify-send 'nixos activated' 'version: $(cat $systemConfig/nixos-version)'"
fi
'';
};
# link debug symbols into /run/current-system/sw/lib/debug
# hopefully picked up by gdb automatically?

View File

@@ -81,7 +81,6 @@ let
(fromDb "feeds.simplecast.com/xKJ93w_w" // uncat) # Atlas Obscura
(fromDb "feeds.transistor.fm/acquired" // tech)
(fromDb "fulltimenix.com" // tech)
(fromDb "hackerpublicradio.org" // tech)
(fromDb "lexfridman.com/podcast" // rat)
(fromDb "mapspodcast.libsyn.com" // uncat) # Multidisciplinary Association for Psychedelic Studies
(fromDb "omegataupodcast.net" // tech) # 3/4 German; 1/4 eps are English
@@ -104,7 +103,6 @@ let
(fromDb "techwontsave.us" // pol) # rec by Cory Doctorow
(fromDb "wakingup.libsyn.com" // pol) # Sam Harris
(fromDb "werenotwrong.fireside.fm" // pol)
(mkPod "https://sfconservancy.org/casts/the-corresponding-source/feeds/ogg/" // tech)
# (fromDb "feeds.libsyn.com/421877" // rat) # Less Wrong Curated
# (fromDb "feeds.megaphone.fm/hubermanlab" // uncat) # Daniel Huberman on sleep
@@ -123,7 +121,6 @@ let
texts = [
(fromDb "acoup.blog/feed") # history, states. author: <https://historians.social/@bretdevereaux/following>
(fromDb "amosbbatto.wordpress.com" // tech)
(fromDb "anish.lakhwara.com" // tech)
(fromDb "applieddivinitystudies.com" // rat)
(fromDb "artemis.sh" // tech)
(fromDb "ascii.textfiles.com" // tech) # Jason Scott
@@ -144,7 +141,6 @@ let
(fromDb "edwardsnowden.substack.com" // pol // text)
(fromDb "fasterthanli.me" // tech)
(fromDb "gwern.net" // rat)
(fromDb "hardcoresoftware.learningbyshipping.com" // tech) # Steven Sinofsky
(fromDb "harihareswara.net" // tech // pol) # rec by Cory Doctorow
(fromDb "ianthehenry.com" // tech)
(fromDb "idiomdrottning.org" // uncat)
@@ -161,12 +157,10 @@ let
(fromDb "mg.lol" // tech)
(fromDb "mindingourway.com" // rat)
(fromDb "morningbrew.com/feed" // pol)
(fromDb "nixpkgs.news" // tech)
(fromDb "overcomingbias.com" // rat) # Robin Hanson
(fromDb "palladiummag.com" // uncat)
(fromDb "philosopher.coach" // rat) # Peter Saint-Andre -- side project of stpeter.im
(fromDb "pomeroyb.com" // tech)
(fromDb "postmarketos.org/blog" // tech)
(fromDb "preposterousuniverse.com" // rat) # Sean Carroll
(fromDb "profectusmag.com" // uncat)
(fromDb "project-insanity.org" // tech) # shared blog by a few NixOS devs, notably onny
@@ -178,11 +172,9 @@ let
(fromDb "sagacioussuricata.com" // tech) # ian (Sanctuary)
(fromDb "semiaccurate.com" // tech)
(fromDb "sideways-view.com" // rat) # Paul Christiano
(fromDb "slatecave.net" // tech)
(fromDb "slimemoldtimemold.com" // rat)
(fromDb "spectrum.ieee.org" // tech)
(fromDb "stpeter.im/atom.xml" // pol)
(fromDb "thediff.co" // pol) # Byrne Hobart
# (fromDb "theregister.com" // tech)
(fromDb "thisweek.gnome.org" // tech)
(fromDb "tuxphones.com" // tech)
@@ -190,23 +182,23 @@ let
(fromDb "unintendedconsequenc.es" // rat)
# (fromDb "vitalik.ca" // tech) # moved to vitalik.eth.limo
(fromDb "vitalik.eth.limo" // tech) # Vitalik Buterin
# (fromDb "webcurious.co.uk" // uncat) # link aggregator; defunct?
(fromDb "willow.phantoma.online") # wizard@xyzzy.link
(fromDb "webcurious.co.uk" // uncat)
(fromDb "xn--gckvb8fzb.com" // tech)
(mkSubstack "astralcodexten" // rat // daily) # Scott Alexander
(mkSubstack "byrnehobart" // pol // infrequent)
# (mkSubstack "doomberg" // tech // weekly) # articles are all pay-walled
(mkSubstack "eliqian" // rat // weekly)
(mkSubstack "oversharing" // pol // daily)
(mkSubstack "samkriss" // humor // infrequent)
(mkText "http://benjaminrosshoffman.com/feed" // pol // weekly)
(mkText "http://boginjr.com/feed" // tech // infrequent)
(mkText "https://anish.lakhwara.com/home.html" // tech // weekly)
(mkText "https://forum.merveilles.town/rss.xml" // pol // infrequent) #quality RSS list here: <https://forum.merveilles.town/thread/57/share-your-rss-feeds%21-6/>
# (mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
(mkText "https://jvns.ca/atom.xml" // tech // weekly) # Julia Evans
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
(mkText "https://nixos.org/blog/announcements-rss.xml" // tech // infrequent) # more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html>
(mkText "https://nixos.org/blog/stories-rss.xml" // tech // weekly)
(mkText "https://solar.lowtechmagazine.com/posts/index.xml" // tech // weekly)
# (mkText "https://til.simonwillison.net/tils/feed.atom" // tech // weekly)
# (mkText "https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" // pol // weekly) # Matt Levine (preview/paywalled)
(mkText "https://www.stratechery.com/rss" // pol // weekly) # Ben Thompson
@@ -219,7 +211,6 @@ let
(fromDb "youtube.com/@Exurb1a")
(fromDb "youtube.com/@hbomberguy")
(fromDb "youtube.com/@JackStauber")
(fromDb "youtube.com/@NativLang")
(fromDb "youtube.com/@PolyMatter")
# (fromDb "youtube.com/@rossmanngroup" // pol // tech) # Louis Rossmann
(fromDb "youtube.com/@TechnologyConnections" // tech)
@@ -232,7 +223,6 @@ let
images = [
(fromDb "catandgirl.com" // img // humor)
(fromDb "davidrevoy.com" // img // art)
(fromDb "miniature-calendar.com" // img // art // daily)
(fromDb "pbfcomics.com" // img // humor)
(fromDb "poorlydrawnlines.com/feed" // img // humor)

View File

@@ -2,7 +2,7 @@
# - x-systemd options: <https://www.freedesktop.org/software/systemd/man/systemd.mount.html>
# - fuse options: `man mount.fuse`
{ config, lib, pkgs, sane-lib, utils, ... }:
{ lib, pkgs, sane-lib, ... }:
let
fsOpts = rec {
@@ -23,15 +23,15 @@ let
# N.B.: `remote-fs.target` is a dependency of multi-user.target, itself of graphical.target.
# hence, omitting `noauto` can slow down boots.
noauto = [ "noauto" ];
# lazyMount: defer mounting until first access from userspace.
# see: `man systemd.automount`, `man automount`, `man autofs`
# lazyMount: defer mounting until first access from userspace
lazyMount = noauto ++ automount;
wg = [
"x-systemd.requires=wireguard-wg-home.service"
"x-systemd.after=wireguard-wg-home.service"
];
fuse = [
ssh = common ++ [
"identityfile=/home/colin/.ssh/id_ed25519"
"allow_other" # allow users other than the one who mounts it to access it. needed, if systemd is the one mounting this fs (as root)
# allow_root: allow root to access files on this fs (if mounted by non-root, else it can always access them).
# N.B.: if both allow_root and allow_other are specified, then only allow_root takes effect.
@@ -44,18 +44,7 @@ let
# with default_permissions, sshfs doesn't tunnel file ops from users until checking that said user could perform said op on an equivalent local fs.
"default_permissions"
];
fuseColin = fuse ++ [
"uid=1000"
"gid=100"
];
ssh = common ++ fuse ++ [
"identityfile=/home/colin/.ssh/id_ed25519"
# i *think* idmap=user means that `colin` on `localhost` and `colin` on the remote are actually treated as the same user, even if their uid/gid differs?
# i.e., local colin's id is translated to/from remote colin's id on every operation?
"idmap=user"
];
sshColin = ssh ++ fuseColin ++ [
sshColin = ssh ++ [
# follow_symlinks: remote files which are symlinks are presented to the local system as ordinary files (as the target of the symlink).
# if the symlink target does not exist, the presentation is unspecified.
# symlinks which point outside the mount ARE followed. so this is more capable than `transform_symlinks`
@@ -63,6 +52,9 @@ let
# symlinks on the remote fs which are absolute paths are presented to the local system as relative symlinks pointing to the expected data on the remote fs.
# only symlinks which would point inside the mountpoint are translated.
"transform_symlinks"
"idmap=user"
"uid=1000"
"gid=100"
];
# sshRoot = ssh ++ [
# # we don't transform_symlinks because that breaks the validity of remote /nix stores
@@ -75,43 +67,21 @@ let
# actimeo=n = how long (in seconds) to cache file/dir attributes (default: 3-60s)
# bg = retry failed mounts in the background
# retry=n = for how many minutes `mount` will retry NFS mount operation
# intr = allow Ctrl+C to abort I/O (it will error with `EINTR`)
# soft = on "major timeout", report I/O error to userspace
# softreval = on "major timeout", service the request using known-stale cache results instead of erroring -- if such cache data exists
# retrans=n = how many times to retry a NFS request before giving userspace a "server not responding" error (default: 3)
# timeo=n = number of *deciseconds* to wait for a response before retrying it (default: 600)
# note: client uses a linear backup, so the second request will have double this timeout, then triple, etc.
# proto=udp = encapsulate protocol ops inside UDP packets instead of a TCP session.
# requires `nfsvers=3` and a kernel compiled with `NFS_DISABLE_UDP_SUPPORT=n`.
# UDP might be preferable to TCP because the latter is liable to hang for ~100s (kernel TCP timeout) after a link drop.
# however, even UDP has issues with `umount` hanging.
#
# N.B.: don't change these without first testing the behavior of sandboxed apps on a flaky network.
nfs = common ++ [
# "actimeo=5"
# "bg"
"retrans=1"
# "actimeo=10"
"bg"
"retrans=4"
"retry=0"
# "intr"
"soft"
"softreval"
"timeo=30"
"timeo=15"
"nofail" # don't fail remote-fs.target when this mount fails (not an option for sshfs else would be common)
# "proto=udp" # default kernel config doesn't support NFS over UDP: <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1964093> (see comment 11).
# "nfsvers=3" # NFSv4+ doesn't support UDP at *all*. it's ok to omit nfsvers -- server + client will negotiate v3 based on udp requirement. but omitting causes confusing mount errors when the server is *offline*, because the client defaults to v4 and thinks the udp option is a config error.
# "x-systemd.idle-timeout=10" # auto-unmount after this much inactivity
];
# manually perform a ftp mount via e.g.
# curlftpfs -o ftpfs_debug=2,user=anonymous:anonymous,connect_timeout=10 -f -s ftp://servo-hn /mnt/my-ftp
ftp = common ++ fuseColin ++ [
# "ftpfs_debug=2"
"user=colin:ipauth"
"connect_timeout=10"
];
};
remoteHome = host: {
sane.programs.sshfs-fuse.enableFor.system = true;
fileSystems."/mnt/${host}/home" = {
device = "colin@${host}:/home/colin";
fsType = "fuse.sshfs";
@@ -124,54 +94,6 @@ let
dir.acl.mode = "0700";
};
};
remoteServo = subdir: {
sane.programs.curlftpfs.enableFor.system = true;
sane.fs."/mnt/servo/${subdir}" = sane-lib.fs.wanted {
dir.acl.user = "colin";
dir.acl.group = "users";
dir.acl.mode = "0750";
};
fileSystems."/mnt/servo/${subdir}" = {
device = "ftp://servo-hn:/${subdir}";
noCheck = true;
fsType = "fuse.curlftpfs";
options = fsOpts.ftp ++ fsOpts.noauto ++ fsOpts.wg;
# fsType = "nfs";
# options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg;
};
systemd.services."automount-servo-${utils.escapeSystemdPath subdir}" = let
fs = config.fileSystems."/mnt/servo/${subdir}";
in {
# this is a *flaky* network mount, especially on moby.
# if done as a normal autofs mount, access will eternally block when network is dropped.
# notably, this would block *any* sandboxed app which allows media access, whether they actually try to use that media or not.
# a practical solution is this: mount as a service -- instead of autofs -- and unmount on timeout error, in a restart loop.
# until the ftp handshake succeeds, nothing is actually mounted to the vfs, so this doesn't slow down any I/O when network is down.
description = "automount /mnt/servo/${subdir} in a fault-tolerant and non-blocking manner";
after = [ "network-online.target" ];
requires = [ "network-online.target" ];
wantedBy = [ "default.target" ];
serviceConfig.Type = "simple";
serviceConfig.ExecStart = lib.escapeShellArgs [
"/usr/bin/env"
"PATH=/run/current-system/sw/bin"
"mount.${fs.fsType}"
"-f" # foreground (i.e. don't daemonize)
"-s" # single-threaded (TODO: it's probably ok to disable this?)
"-o"
(lib.concatStringsSep "," (lib.filter (o: !lib.hasPrefix "x-systemd." o) fs.options))
fs.device
"/mnt/servo/${subdir}"
];
# not sure if this configures a linear, or exponential backoff.
# but the first restart will be after `RestartSec`, and the n'th restart (n = RestartSteps) will be RestartMaxDelaySec after the n-1'th exit.
serviceConfig.Restart = "always";
serviceConfig.RestartSec = "10s";
serviceConfig.RestartMaxDelaySec = "120s";
serviceConfig.RestartSteps = "5";
};
};
in
lib.mkMerge [
{
@@ -206,6 +128,35 @@ lib.mkMerge [
# but it decreases working memory under the heaviest of loads by however much space the compressed memory occupies (e.g. 50% if 2:1; 25% if 4:1)
zramSwap.memoryPercent = 100;
# fileSystems."/mnt/servo-nfs" = {
# device = "servo-hn:/";
# noCheck = true;
# fsType = "nfs";
# options = fsOpts.nfs ++ fsOpts.automount ++ fsOpts.wg;
# };
fileSystems."/mnt/servo/media" = {
device = "servo-hn:/media";
noCheck = true;
fsType = "nfs";
options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg;
};
sane.fs."/mnt/servo/media" = sane-lib.fs.wanted {
dir.acl.user = "colin";
dir.acl.group = "users";
dir.acl.mode = "0750";
};
fileSystems."/mnt/servo/playground" = {
device = "servo-hn:/playground";
noCheck = true;
fsType = "nfs";
options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg;
};
sane.fs."/mnt/servo/playground" = sane-lib.fs.wanted {
dir.acl.user = "colin";
dir.acl.group = "users";
dir.acl.mode = "0750";
};
# environment.pathsToLink = [
# # needed to achieve superuser access for user-mounted filesystems (see sshRoot above)
# # we can only link whole directories here, even though we're only interested in pkgs.openssh
@@ -213,23 +164,13 @@ lib.mkMerge [
# ];
programs.fuse.userAllowOther = true; #< necessary for `allow_other` or `allow_root` options.
environment.systemPackages = [
pkgs.sshfs-fuse
];
}
(remoteHome "desko")
(remoteHome "lappy")
(remoteHome "moby")
# this granularity of servo media mounts is necessary to support sandboxing:
# for flaky mounts, we can only bind the mountpoint itself into the sandbox,
# so it's either this or unconditionally bind all of media/.
(remoteServo "media/archive")
(remoteServo "media/Books")
(remoteServo "media/collections")
# (remoteServo "media/datasets")
(remoteServo "media/freeleech")
(remoteServo "media/games")
(remoteServo "media/Music")
(remoteServo "media/Pictures/macros")
(remoteServo "media/Videos")
(remoteServo "playground")
]

View File

@@ -35,16 +35,6 @@
# servo needs zfs though, which doesn't support every kernel.
boot.kernelPackages = lib.mkDefault pkgs.zfs.latestCompatibleLinuxPackages;
# TODO: remove after linux 6.9. see: <https://github.com/axboe/liburing/issues/1113>
# - <https://github.com/neovim/neovim/issues/28149>
# - <https://git.kernel.dk/cgit/linux/commit/?h=io_uring-6.9&id=e5444baa42e545bb929ba56c497e7f3c73634099>
# when removing, try starting and suspending (ctrl+z) two instances of neovim simultaneously.
# if the system doesn't freeze, then this is safe to remove.
# added 2024-04-04
sane.user.fs.".profile".symlink.text = lib.mkBefore ''
export UV_USE_IO_URING=0
'';
# hack in the `boot.shell_on_fail` arg since that doesn't always seem to work.
boot.initrd.preFailCommands = "allowShell=1";
@@ -89,6 +79,11 @@
HandleLidSwitch=lock
'';
# some packages build only if binfmt *isn't* present
nix.settings.system-features = lib.mkIf (config.boot.binfmt.emulatedSystems == []) [
"no-binfmt"
];
# services.snapper.configs = {
# root = {
# subvolume = "/";

View File

@@ -1,4 +1,4 @@
{ config, lib, ... }:
{ config, ... }:
{
sane.user.persist.byStore.plaintext = [
"archive"
@@ -29,17 +29,14 @@
];
# convenience
sane.user.fs = let
persistEnabled = config.sane.persist.enable;
in {
".persist/private" = lib.mkIf persistEnabled { symlink.target = config.sane.persist.stores.private.origin; };
".persist/plaintext" = lib.mkIf persistEnabled { symlink.target = config.sane.persist.stores.plaintext.origin; };
".persist/ephemeral" = lib.mkIf persistEnabled { symlink.target = config.sane.persist.stores.cryptClearOnBoot.origin; };
sane.user.fs.".persist/private".symlink.target = config.sane.persist.stores.private.origin;
sane.user.fs.".persist/plaintext".symlink.target = config.sane.persist.stores.plaintext.origin;
sane.user.fs.".persist/ephemeral".symlink.target = config.sane.persist.stores.cryptClearOnBoot.origin;
"nixos".symlink.target = "dev/nixos";
sane.user.fs."nixos".symlink.target = "dev/nixos";
"Books/servo".symlink.target = "/mnt/servo/media/Books";
"Videos/servo".symlink.target = "/mnt/servo/media/Videos";
"Pictures/servo-macros".symlink.target = "/mnt/servo/media/Pictures/macros";
};
sane.user.fs."Books/servo".symlink.target = "/mnt/servo/media/Books";
sane.user.fs."Videos/servo".symlink.target = "/mnt/servo/media/Videos";
# sane.user.fs."Music/servo".symlink.target = "/mnt/servo/media/Music";
sane.user.fs."Pictures/servo-macros".symlink.target = "/mnt/servo/media/Pictures/macros";
}

View File

@@ -10,7 +10,6 @@
XDG_MUSIC_DIR="$HOME/Music"
XDG_PICTURES_DIR="$HOME/Pictures"
XDG_PUBLICSHARE_DIR="$HOME/.xdg/Public"
XDG_SCREENSHOTS_DIR="$HOME/Pictures/Screenshots"
XDG_TEMPLATES_DIR="$HOME/.xdg/Templates"
XDG_VIDEOS_DIR="$HOME/Videos"
'';
@@ -18,12 +17,4 @@
# prevent `xdg-user-dirs-update` from overriding/updating our config
# see <https://manpages.ubuntu.com/manpages/bionic/man5/user-dirs.conf.5.html>
sane.user.fs.".config/user-dirs.conf".symlink.text = "enabled=False";
sane.user.fs.".profile".symlink.text = ''
# configure XDG_<type>_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
'';
}

View File

@@ -53,9 +53,8 @@
# allow `nix-shell` (and probably nix-index?) to locate our patched and custom packages.
# this is actually a no-op, and the real action happens in assigning `nix.settings.nix-path`.
nix.nixPath = (lib.optionals config.sane.enableSlowPrograms [
nix.nixPath = [
"nixpkgs=${pkgs.path}"
]) ++ [
# note the import starts at repo root: this allows `./overlay/default.nix` to access the stuff at the root
# "nixpkgs-overlays=${../../..}/hosts/common/nix-path/overlay"
# as long as my system itself doesn't rely on NIXPKGS at runtime, we can point the overlays to git
@@ -64,13 +63,7 @@
];
# ensure new deployments have a source of this repo with which they can bootstrap.
# this however changes on every commit and can be slow to copy for e.g. `moby`.
environment.etc."nixos" = lib.mkIf config.sane.enableSlowPrograms {
source = ../../..;
};
environment.etc."nix/registry.json" = lib.mkIf (!config.sane.enableSlowPrograms) {
enable = false;
};
environment.etc."nixos".source = ../../..;
systemd.services.nix-daemon.serviceConfig = {
# the nix-daemon manages nix builders

View File

@@ -13,7 +13,7 @@
"/run/current-system/sw"
];
# NIXPKGS_CONFIG defaults to "/etc/nix/nixpkgs-config.nix" in <nixos/modules/programs/environment.nix>.
# NIXPKGS_CONFIG defaults to "/etc/nix/nixpkgs-config.nix", for idfk why.
# that's never existed on my system and everything does fine without it set empty (no nixpkgs API to forcibly *unset* it).
environment.variables.NIXPKGS_CONFIG = lib.mkForce "";
# XDG_CONFIG_DIRS defaults to "/etc/xdg", which doesn't exist.
@@ -42,31 +42,4 @@
# so as to inform when trying to run a non-nixos binary?
# IMO that's confusing: i thought /lib/ld-linux.so was some file actually required by nix.
environment.stub-ld.enable = false;
# `less.enable` sets LESSKEYIN_SYSTEM, LESSOPEN, LESSCLOSE env vars, which does confusing "lesspipe" things, so disable that.
# it's enabled by default from `<nixos/modules/programs/environment.nix>`, who also sets `PAGER="less"` and `EDITOR="nano"` (keep).
programs.less.enable = lib.mkForce false;
environment.variables.PAGER = lib.mkOverride 900 ""; # mkDefault sets 1000. non-override is 100. 900 will beat the nixpkgs `mkDefault` but not anyone else.
environment.variables.EDITOR = lib.mkOverride 900 "";
# several packages (dconf, modemmanager, networkmanager, gvfs, polkit, udisks, bluez/blueman, feedbackd, etc)
# will add themselves to the dbus search path.
# i prefer dbus to only search XDG paths (/share/dbus-1) for service files, as that's more introspectable.
# see: <repo:nixos/nixpkgs:nixos/modules/services/system/dbus.nix>
# TODO: sandbox dbus? i pretty explicitly don't want to use it as a launcher.
services.dbus.packages = lib.mkForce [
"/run/current-system/sw"
# config.system.path
# pkgs.dbus
# pkgs.polkit.out
# pkgs.modemmanager
# pkgs.networkmanager
# pkgs.udisks
# pkgs.wpa_supplicant
];
# systemd by default forces shitty defaults for e.g. /tmp/.X11-unix.
# nixos propagates those in: <nixos/modules/system/boot/systemd/tmpfiles.nix>
# by overwriting this with an empty file, we can effectively remove it.
environment.etc."tmpfiles.d/x11.conf".text = "# (removed by Colin)";
}

View File

@@ -87,8 +87,13 @@ in
services.abaddon = {
description = "unofficial Discord chat client";
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
command = "abaddon";
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/abaddon";
Type = "simple";
Restart = "always";
RestartSec = "20s";
};
};
};
}

View File

@@ -4,7 +4,7 @@
{
sane.programs.aerc = {
sandbox.method = "bwrap";
sandbox.wrapperType = "inplace"; #< /share/aerc/aerc.conf refers to other /share files by absolute path
sandbox.wrapperType = "inplace";
sandbox.net = "clearnet";
secrets.".config/aerc/accounts.conf" = ../../../secrets/common/aerc_accounts.conf.bin;
mime.associations."x-scheme-handler/mailto" = "aerc.desktop";

View File

@@ -3,28 +3,14 @@
# - `man 5 alacritty`
# - defaults: <https://github.com/alacritty/alacritty/releases> -> alacritty.yml
# - irc: #alacritty on libera.chat
{ config, lib, ... }:
let
cfg = config.sane.programs.alacritty;
in
{ lib, ... }:
{
sane.programs.alacritty = {
configOption = with lib; mkOption {
default = {};
type = types.submodule {
options.fontSize = mkOption {
type = types.int;
default = 14;
};
};
};
sandbox.enable = false;
env.TERMINAL = lib.mkDefault "alacritty";
fs.".config/alacritty/alacritty.toml".symlink.text = ''
[font]
size = ${builtins.toString cfg.config.fontSize}
size = 14
[[keyboard.bindings]]
mods = "Control"
@@ -50,21 +36,6 @@ in
mods = "Control|Shift"
key = "PageDown"
action = "ScrollPageDown"
# disable OS shortcuts which leak through...
# see sway config or sane-input-handler for more info on why these leak through
[[keyboard.bindings]]
key = "AudioVolumeUp"
action = "None"
[[keyboard.bindings]]
key = "AudioVolumeDown"
action = "None"
[[keyboard.bindings]]
key = "Power"
action = "None"
[[keyboard.bindings]]
key = "PowerOff"
action = "None"
'';
};
}

View File

@@ -1,65 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.alsa-ucm-conf;
in
{
sane.programs.alsa-ucm-conf = {
configOption = with lib; mkOption {
default = {};
type = types.submodule {
options.preferEarpiece = mkOption {
type = types.bool;
default = true;
};
};
};
# upstream alsa ships with PinePhone audio configs, but they don't actually produce sound.
# see: <https://github.com/alsa-project/alsa-ucm-conf/pull/134>
# these audio files come from some revision of:
# - <https://gitlab.manjaro.org/manjaro-arm/packages/community/phosh/alsa-ucm-pinephone>
#
# alternative to patching is to plumb `ALSA_CONFIG_UCM2 = "${./ucm2}"` environment variable into the relevant places
# e.g. `systemd.services.pulseaudio.environment`.
# that leaves more opportunity for gaps (i.e. missing a service),
# on the other hand this method causes about 500 packages to be rebuilt (including qt5 and webkitgtk).
#
# note that with these files, the following audio device support:
# - headphones work.
# - "internal earpiece" works.
# - "internal speaker" doesn't work (but that's probably because i broke the ribbon cable)
# - "analog output" doesn't work.
packageUnwrapped = pkgs.alsa-ucm-conf.overrideAttrs (upstream: {
postPatch = (upstream.postPatch or "") + ''
cp ${./ucm2/PinePhone}/* ucm2/Allwinner/A64/PinePhone/
# fix the self-contained ucm files i source from to have correct path within the alsa-ucm-conf source tree
substituteInPlace ucm2/Allwinner/A64/PinePhone/PinePhone.conf \
--replace-fail 'HiFi.conf' '/Allwinner/A64/PinePhone/HiFi.conf'
substituteInPlace ucm2/Allwinner/A64/PinePhone/PinePhone.conf \
--replace-fail 'VoiceCall.conf' '/Allwinner/A64/PinePhone/VoiceCall.conf'
'' + lib.optionalString cfg.config.preferEarpiece ''
# decrease the priority of the internal speaker so that sounds are routed
# to the earpiece by default.
# this is just personal preference.
substituteInPlace ucm2/Allwinner/A64/PinePhone/{HiFi.conf,VoiceCall.conf} \
--replace-fail 'PlaybackPriority 300' 'PlaybackPriority 100'
'';
});
sandbox.enable = false; #< only provides #out/share/alsa
# alsa-lib package only looks in its $out/share/alsa to find runtime config data, by default.
# but ALSA_CONFIG_UCM2 is an env var that can override that.
# this is particularly needed by wireplumber;
# also *maybe* pipewire and pipewire-pulse.
# taken from <repo:nixos/mobile-nixos:modules/quirks/audio.nix>
env.ALSA_CONFIG_UCM2 = "/run/current-system/sw/share/alsa/ucm2";
enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true;
};
environment.pathsToLink = lib.mkIf cfg.enabled [
"/share/alsa/ucm2"
];
}

View File

@@ -31,6 +31,7 @@
};
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.whitelistWayland = true;
persist.byStore.plaintext = [

View File

@@ -62,7 +62,6 @@ in
# "iw"
"jq"
"killall"
"less"
# "libcap_ng" # for `netcap`
"lsof"
# "miniupnpc"
@@ -79,7 +78,6 @@ in
"powertop"
"pstree"
"ripgrep"
"s6-rc" # service manager
"screen"
"smartmontools" # smartctl
# "socat"
@@ -123,7 +121,6 @@ in
# "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"
@@ -168,7 +165,6 @@ in
];
consoleMediaUtils = declPackageSet [
"blast-ugjka" # cast audio to UPNP/DLNA devices (via pulseaudio sink)
# "catt" # cast videos to chromecast
"ffmpeg"
"go2tv" # cast videos to UPNP/DLNA device (i.e. tv).
@@ -207,17 +203,19 @@ in
# INDIVIDUAL PACKAGE DEFINITIONS
alsaUtils.sandbox.method = "landlock";
alsaUtils.sandbox.wrapperType = "wrappedDerivation";
alsaUtils.sandbox.whitelistAudio = true; #< not strictly necessary?
backblaze-b2 = {};
blanket.sandbox.method = "bwrap";
blanket.sandbox.wrapperType = "wrappedDerivation";
blanket.sandbox.whitelistAudio = true;
# blanket.sandbox.whitelistDbus = [ "user" ]; # TODO: untested
blanket.sandbox.whitelistWayland = true;
blueberry.sandbox.method = "bwrap";
blueberry.sandbox.wrapperType = "inplace"; #< various /lib scripts refer to the bins by full path
blueberry.sandbox.wrapperType = "inplace"; # /etc/xdg/autostart hardcodes paths
blueberry.sandbox.whitelistWayland = true;
blueberry.sandbox.extraPaths = [
"/dev/rfkill"
@@ -227,9 +225,11 @@ in
];
bridge-utils.sandbox.method = "bwrap"; #< bwrap, landlock: both work
bridge-utils.sandbox.wrapperType = "wrappedDerivation";
bridge-utils.sandbox.net = "all";
brightnessctl.sandbox.method = "landlock"; # also bwrap, but landlock is more responsive
brightnessctl.sandbox.wrapperType = "wrappedDerivation";
brightnessctl.sandbox.extraPaths = [
"/sys/class/backlight"
"/sys/class/leds"
@@ -238,6 +238,7 @@ in
brightnessctl.sandbox.whitelistDbus = [ "system" ];
btrfs-progs.sandbox.method = "bwrap"; #< bwrap, landlock: both work
btrfs-progs.sandbox.wrapperType = "wrappedDerivation";
btrfs-progs.sandbox.autodetectCliPaths = "existing"; # e.g. `btrfs filesystem df /my/fs`
"cacert.unbundled".sandbox.enable = false;
@@ -248,6 +249,7 @@ in
# cryptsetup: typical use is `cryptsetup open /dev/loopxyz mappedName`, and creates `/dev/mapper/mappedName`
cryptsetup.sandbox.method = "landlock";
cryptsetup.sandbox.wrapperType = "wrappedDerivation";
cryptsetup.sandbox.extraPaths = [
"/dev/mapper"
"/dev/random"
@@ -261,10 +263,12 @@ in
cryptsetup.sandbox.autodetectCliPaths = "existing";
ddrescue.sandbox.method = "landlock"; # TODO:sandbox: untested
ddrescue.sandbox.wrapperType = "wrappedDerivation";
ddrescue.sandbox.autodetectCliPaths = "existingOrParent";
# auth token, preferences
delfin.sandbox.method = "bwrap";
delfin.sandbox.wrapperType = "wrappedDerivation";
delfin.sandbox.whitelistAudio = true;
delfin.sandbox.whitelistDbus = [ "user" ]; # else `mpris` plugin crashes the player
delfin.sandbox.whitelistDri = true;
@@ -273,41 +277,34 @@ in
delfin.persist.byStore.private = [ ".config/delfin" ];
dig.sandbox.method = "bwrap";
dig.sandbox.wrapperType = "wrappedDerivation";
dig.sandbox.net = "all";
# creds, but also 200 MB of node modules, etc
discord.persist.byStore.private = [ ".config/discord" ];
discord.suggestedPrograms = [ "xwayland" ];
discord.sandbox.method = "bwrap";
discord.sandbox.wrapperType = "inplace"; #< /opt-style packaging
discord.sandbox.whitelistAudio = true;
discord.sandbox.whitelistDbus = [ "user" ]; # needed for xdg-open
discord.sandbox.whitelistWayland = true;
discord.sandbox.whitelistX = true;
discord.sandbox.net = "clearnet";
discord.sandbox.extraHomePaths = [
# still needs these paths despite it using the portal's file-chooser :?
"Pictures/cat"
"Pictures/Screenshots"
"Pictures/servo-macros"
"Videos/local"
"Videos/servo"
"tmp"
];
discord.persist.byStore.private = [ ".config/discord" ];
dtc.sandbox.method = "bwrap";
dtc.sandbox.autodetectCliPaths = true; # TODO:sandbox: untested
dtrx.sandbox.method = "bwrap";
dtrx.sandbox.wrapperType = "wrappedDerivation";
dtrx.sandbox.whitelistPwd = true;
dtrx.sandbox.autodetectCliPaths = "existing"; #< for the archive
duplicity = {};
e2fsprogs.sandbox.method = "landlock";
e2fsprogs.sandbox.wrapperType = "wrappedDerivation";
e2fsprogs.sandbox.autodetectCliPaths = "existing";
efibootmgr.sandbox.method = "landlock";
efibootmgr.sandbox.wrapperType = "wrappedDerivation";
efibootmgr.sandbox.extraPaths = [
"/sys/firmware/efi"
];
@@ -315,12 +312,14 @@ in
eg25-control = {};
electrum.sandbox.method = "bwrap"; # TODO:sandbox: untested
electrum.sandbox.wrapperType = "wrappedDerivation";
electrum.sandbox.net = "all"; # TODO: probably want to make this run behind a VPN, always
electrum.sandbox.whitelistWayland = true;
electrum.persist.byStore.cryptClearOnBoot = [ ".electrum" ]; #< TODO: use XDG dirs!
endless-sky.persist.byStore.plaintext = [ ".local/share/endless-sky" ];
endless-sky.sandbox.method = "bwrap";
endless-sky.sandbox.wrapperType = "wrappedDerivation";
endless-sky.sandbox.whitelistAudio = true;
endless-sky.sandbox.whitelistDri = true;
endless-sky.sandbox.whitelistWayland = true;
@@ -331,12 +330,14 @@ in
emote.persist.byStore.plaintext = [ ".local/share/Emote" ];
ethtool.sandbox.method = "landlock";
ethtool.sandbox.wrapperType = "wrappedDerivation";
ethtool.sandbox.capabilities = [ "net_admin" ];
# eza `ls` replacement
# landlock is OK, only `whitelistPwd` doesn't make the intermediate symlinks traversable, so it breaks on e.g. ~/Videos/servo/Shows/foo
# eza.sandbox.method = "landlock";
eza.sandbox.method = "bwrap";
eza.sandbox.wrapperType = "wrappedDerivation"; # slow to build
eza.sandbox.autodetectCliPaths = true;
eza.sandbox.whitelistPwd = true;
eza.sandbox.extraHomePaths = [
@@ -346,9 +347,11 @@ in
];
fatresize.sandbox.method = "landlock";
fatresize.sandbox.wrapperType = "wrappedDerivation";
fatresize.sandbox.autodetectCliPaths = "parent"; # /dev/sda1 -> needs /dev/sda
fd.sandbox.method = "landlock";
fd.sandbox.wrapperType = "wrappedDerivation"; # slow to build
fd.sandbox.autodetectCliPaths = true;
fd.sandbox.whitelistPwd = true;
fd.sandbox.extraHomePaths = [
@@ -358,12 +361,15 @@ in
];
ffmpeg.sandbox.method = "bwrap";
ffmpeg.sandbox.wrapperType = "wrappedDerivation"; # slow to build
ffmpeg.sandbox.autodetectCliPaths = "existingFileOrParent"; # it outputs uncreated files -> parent dir needs mounting
file.sandbox.method = "bwrap";
file.sandbox.wrapperType = "wrappedDerivation";
file.sandbox.autodetectCliPaths = true;
findutils.sandbox.method = "bwrap";
findutils.sandbox.wrapperType = "wrappedDerivation";
findutils.sandbox.autodetectCliPaths = true;
findutils.sandbox.whitelistPwd = true;
findutils.sandbox.extraHomePaths = [
@@ -375,12 +381,14 @@ in
fluffychat-moby.persist.byStore.plaintext = [ ".local/share/chat.fluffy.fluffychat" ];
font-manager.sandbox.method = "bwrap";
font-manager.packageUnwrapped = pkgs.rmDbusServicesInPlace (pkgs.font-manager.override {
font-manager.sandbox.wrapperType = "inplace"; # .desktop and dbus .service file refer to /libexec
font-manager.packageUnwrapped = pkgs.font-manager.override {
# build without the "Google Fonts" integration feature, to save closure / avoid webkitgtk_4_0
withWebkit = false;
});
};
forkstat.sandbox.method = "landlock"; #< doesn't seem to support bwrap
forkstat.sandbox.wrapperType = "wrappedDerivation";
forkstat.sandbox.extraConfig = [
"--sane-sandbox-keep-namespace" "pid"
];
@@ -388,7 +396,12 @@ in
"/proc"
];
# fuzzel: TODO: re-enable sandbox. i use fuzzel both as an entry system (snippets) AND an app-launcher.
# as an app-launcher, it cannot be sandboxed without over-restricting the app it launches.
# should probably make it not be an app-launcher
fuzzel.sandbox.enable = false;
fuzzel.sandbox.method = "bwrap"; #< landlock nearly works, but unable to open ~/.cache
fuzzel.sandbox.wrapperType = "wrappedDerivation";
fuzzel.sandbox.whitelistWayland = true;
fuzzel.persist.byStore.private = [
# this is a file of recent selections
@@ -396,11 +409,12 @@ in
];
gawk.sandbox.method = "bwrap"; # TODO:sandbox: untested
gawk.sandbox.wrapperType = "inplace"; # /share/gawk libraries refer to /libexec
gawk.sandbox.wrapperType = "inplace"; # share/gawk libraries refer to /libexec
gawk.sandbox.autodetectCliPaths = true;
gdb.sandbox.enable = false; # gdb doesn't sandbox well. i don't know how you could.
# gdb.sandbox.method = "landlock"; # permission denied when trying to attach, even as root
gdb.sandbox.wrapperType = "wrappedDerivation";
gdb.sandbox.autodetectCliPaths = true;
geoclue2-with-demo-agent = {};
@@ -410,7 +424,7 @@ in
gh.persist.byStore.private = [ ".config/gh" ];
gimp.sandbox.method = "bwrap";
gimp.sandbox.whitelistX = true;
gimp.sandbox.wrapperType = "wrappedDerivation";
gimp.sandbox.whitelistWayland = true;
gimp.sandbox.extraHomePaths = [
"Pictures/albums"
@@ -429,32 +443,39 @@ in
];
"gnome.gnome-calculator".sandbox.method = "bwrap";
"gnome.gnome-calculator".sandbox.wrapperType = "inplace"; # /libexec/gnome-calculator-search-provider
"gnome.gnome-calculator".sandbox.whitelistWayland = true;
# gnome-calendar surely has data to persist, but i use it strictly to do date math, not track events.
"gnome.gnome-calendar".sandbox.method = "bwrap";
"gnome.gnome-calendar".sandbox.wrapperType = "wrappedDerivation";
"gnome.gnome-calendar".sandbox.whitelistWayland = true;
"gnome.gnome-clocks".sandbox.method = "bwrap";
"gnome.gnome-clocks".sandbox.wrapperType = "wrappedDerivation";
"gnome.gnome-clocks".sandbox.whitelistWayland = true;
"gnome.gnome-clocks".suggestedPrograms = [ "dconf" ];
# gnome-disks
"gnome.gnome-disk-utility".sandbox.method = "bwrap";
"gnome.gnome-disk-utility".sandbox.wrapperType = "inplace"; # /etc/xdg/autostart
"gnome.gnome-disk-utility".sandbox.whitelistDbus = [ "system" ];
"gnome.gnome-disk-utility".sandbox.whitelistWayland = true;
# seahorse: dump gnome-keyring secrets.
# N.B.: it can also manage ~/.ssh keys, but i explicitly don't add those to the sandbox for now.
"gnome.seahorse".sandbox.method = "bwrap";
"gnome.seahorse".sandbox.wrapperType = "wrappedDerivation";
"gnome.seahorse".sandbox.whitelistDbus = [ "user" ];
"gnome.seahorse".sandbox.whitelistWayland = true;
gnome-2048.sandbox.method = "bwrap";
gnome-2048.sandbox.wrapperType = "wrappedDerivation";
gnome-2048.sandbox.whitelistWayland = true;
gnome-2048.persist.byStore.plaintext = [ ".local/share/gnome-2048/scores" ];
gnome-frog.sandbox.method = "bwrap";
gnome-frog.sandbox.wrapperType = "wrappedDerivation";
gnome-frog.sandbox.whitelistWayland = true;
gnome-frog.sandbox.whitelistDbus = [ "user" ];
gnome-frog.sandbox.extraPaths = [
@@ -481,9 +502,11 @@ in
# 2. no two shaded tiles can be direct N/S/E/W neighbors
# - win once (1) and (2) are satisfied
"gnome.hitori".sandbox.method = "bwrap";
"gnome.hitori".sandbox.wrapperType = "wrappedDerivation";
"gnome.hitori".sandbox.whitelistWayland = true;
gnugrep.sandbox.method = "bwrap";
gnugrep.sandbox.wrapperType = "wrappedDerivation";
gnugrep.sandbox.autodetectCliPaths = true;
gnugrep.sandbox.whitelistPwd = true;
gnugrep.sandbox.extraHomePaths = [
@@ -492,24 +515,20 @@ in
".persist/plaintext"
];
# sed: there is an edgecase of `--file=<foo>`, wherein `foo` won't be whitelisted.
gnused.sandbox.method = "bwrap";
gnused.sandbox.autodetectCliPaths = "existingFile";
gnused.sandbox.whitelistPwd = true; #< `-i` flag creates a temporary file in pwd (?) and then moves it.
gnused = {};
gpsd = {};
gptfdisk.sandbox.method = "landlock";
gptfdisk.sandbox.wrapperType = "wrappedDerivation";
gptfdisk.sandbox.extraPaths = [
"/dev"
];
gptfdisk.sandbox.autodetectCliPaths = "existing"; #< sometimes you'll use gdisk on a device file.
grim.sandbox.method = "bwrap";
grim.sandbox.autodetectCliPaths = "existingOrParent";
grim.sandbox.whitelistWayland = true;
grim = {};
hase.sandbox.method = "bwrap";
hase.sandbox.wrapperType = "wrappedDerivation";
hase.sandbox.net = "clearnet";
hase.sandbox.whitelistAudio = true;
hase.sandbox.whitelistDri = true;
@@ -517,19 +536,35 @@ in
# hdparm: has to be run as sudo. e.g. `sudo hdparm -i /dev/sda`
hdparm.sandbox.method = "bwrap";
hdparm.sandbox.wrapperType = "wrappedDerivation";
hdparm.sandbox.autodetectCliPaths = true;
host.sandbox.method = "landlock";
host.sandbox.wrapperType = "wrappedDerivation";
host.sandbox.net = "all"; #< technically, only needs to contact localhost's DNS server
htop.sandbox.method = "landlock";
htop.sandbox.wrapperType = "wrappedDerivation";
htop.sandbox.extraPaths = [
"/proc"
"/sys/devices"
];
htop.persist.byStore.plaintext = [
# consider setting `show_program_path=0` and either `hide_userland_threads=1` or `show_thread_names=1`
".config/htop"
];
iftop.sandbox.method = "landlock";
iftop.sandbox.wrapperType = "wrappedDerivation";
iftop.sandbox.capabilities = [ "net_raw" ];
# inetutils: ping, ifconfig, hostname, traceroute, whois, ....
# N.B.: inetutils' `ping` is shadowed by iputils' ping (by nixos, intentionally).
inetutils.sandbox.method = "landlock"; # want to keep the same netns, at least.
inetutils.sandbox.wrapperType = "wrappedDerivation";
inkscape.sandbox.method = "bwrap";
inkscape.sandbox.wrapperType = "wrappedDerivation";
inkscape.sandbox.whitelistWayland = true;
inkscape.sandbox.extraHomePaths = [
"Pictures/albums"
@@ -545,6 +580,7 @@ in
inkscape.sandbox.autodetectCliPaths = true;
iotop.sandbox.method = "landlock";
iotop.sandbox.wrapperType = "wrappedDerivation";
iotop.sandbox.extraPaths = [
"/proc"
];
@@ -552,35 +588,38 @@ in
# provides `ip`, `routel`, others
iproute2.sandbox.method = "landlock";
iproute2.sandbox.wrapperType = "wrappedDerivation";
iproute2.sandbox.net = "all";
iproute2.sandbox.capabilities = [ "net_admin" ];
iproute2.sandbox.extraPaths = [
"/run/netns" # for `ip netns ...` to work
"/var/run/netns"
];
iptables.sandbox.method = "landlock";
iptables.sandbox.wrapperType = "wrappedDerivation";
iptables.sandbox.net = "all";
iptables.sandbox.capabilities = [ "net_admin" ];
# iputils provides `ping` (and arping, clockdiff, tracepath)
iputils.sandbox.method = "landlock";
iputils.sandbox.wrapperType = "wrappedDerivation";
iputils.sandbox.net = "all";
iputils.sandbox.capabilities = [ "net_raw" ];
iw.sandbox.method = "landlock";
iw.sandbox.wrapperType = "wrappedDerivation";
iw.sandbox.net = "all";
iw.sandbox.capabilities = [ "net_admin" ];
jq.sandbox.method = "bwrap";
jq.sandbox.wrapperType = "wrappedDerivation";
jq.sandbox.autodetectCliPaths = "existingFile";
killall.sandbox.method = "landlock";
killall.sandbox.wrapperType = "wrappedDerivation";
killall.sandbox.extraPaths = [
"/proc"
];
krita.sandbox.method = "bwrap";
krita.sandbox.wrapperType = "wrappedDerivation";
krita.sandbox.whitelistWayland = true;
krita.sandbox.autodetectCliPaths = "existing";
krita.sandbox.extraHomePaths = [
@@ -598,9 +637,11 @@ in
libcap_ng.sandbox.enable = false; # there's something about /proc/$pid/fd which breaks `readlink`/stat with every sandbox technique (except capsh-only)
libnotify.sandbox.method = "bwrap";
libnotify.sandbox.wrapperType = "wrappedDerivation";
libnotify.sandbox.whitelistDbus = [ "user" ]; # notify-send
losslesscut-bin.sandbox.method = "bwrap";
losslesscut-bin.sandbox.wrapperType = "wrappedDerivation";
losslesscut-bin.sandbox.extraHomePaths = [
"Music"
"Pictures/from" # videos from e.g. mobile phone
@@ -615,10 +656,25 @@ in
losslesscut-bin.sandbox.whitelistX = true;
lsof.sandbox.method = "capshonly"; # lsof doesn't sandbox under bwrap or even landlock w/ full access to /
lsof.sandbox.wrapperType = "wrappedDerivation";
lua = {};
"mate.engrampa".sandbox.method = "bwrap"; # TODO:sandbox: untested
"mate.engrampa".sandbox.wrapperType = "inplace";
"mate.engrampa".sandbox.whitelistWayland = true;
"mate.engrampa".sandbox.autodetectCliPaths = "existingOrParent";
"mate.engrampa".sandbox.extraHomePaths = [
"archive"
"Books/local"
"Books/servo"
"records"
"ref"
"tmp"
];
mercurial.sandbox.method = "bwrap"; # TODO:sandbox: untested
mercurial.sandbox.wrapperType = "wrappedDerivation";
mercurial.sandbox.net = "clearnet";
mercurial.sandbox.whitelistPwd = true;
@@ -626,6 +682,7 @@ in
# 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.method = "bwrap";
monero-gui.sandbox.wrapperType = "wrappedDerivation";
monero-gui.sandbox.net = "all";
monero-gui.sandbox.extraHomePaths = [
"records/finance/cryptocurrencies/monero"
@@ -634,16 +691,20 @@ in
mumble.persist.byStore.private = [ ".local/share/Mumble" ];
nano.sandbox.method = "bwrap";
nano.sandbox.wrapperType = "wrappedDerivation";
nano.sandbox.autodetectCliPaths = "existingFileOrParent";
netcat.sandbox.method = "landlock";
netcat.sandbox.wrapperType = "wrappedDerivation";
netcat.sandbox.net = "all";
nethogs.sandbox.method = "capshonly"; # *partially* works under landlock w/ full access to /
nethogs.sandbox.wrapperType = "wrappedDerivation";
nethogs.sandbox.capabilities = [ "net_admin" "net_raw" ];
# provides `arp`, `hostname`, `route`, `ifconfig`
nettools.sandbox.method = "landlock";
nettools.sandbox.wrapperType = "wrappedDerivation";
nettools.sandbox.net = "all";
nettools.sandbox.capabilities = [ "net_admin" "net_raw" ];
nettools.sandbox.extraPaths = [
@@ -651,6 +712,7 @@ in
];
networkmanagerapplet.sandbox.method = "bwrap";
networkmanagerapplet.sandbox.wrapperType = "wrappedDerivation";
networkmanagerapplet.sandbox.whitelistWayland = true;
networkmanagerapplet.sandbox.whitelistDbus = [ "system" ];
@@ -663,9 +725,11 @@ in
];
nmap.sandbox.method = "bwrap";
nmap.sandbox.wrapperType = "wrappedDerivation";
nmap.sandbox.net = "all"; # clearnet and lan
nmon.sandbox.method = "landlock";
nmon.sandbox.wrapperType = "wrappedDerivation";
nmon.sandbox.extraPaths = [
"/proc"
];
@@ -674,6 +738,7 @@ in
# `nvme list` only shows results when run as root.
nvme-cli.sandbox.method = "landlock";
nvme-cli.sandbox.wrapperType = "wrappedDerivation";
nvme-cli.sandbox.extraPaths = [
"/sys/devices"
"/sys/class/nvme"
@@ -685,11 +750,13 @@ in
# contains only `oathtool`, which i only use for evaluating TOTP codes from CLI/stdin
oath-toolkit.sandbox.method = "bwrap";
oath-toolkit.sandbox.wrapperType = "wrappedDerivation";
# settings (electron app)
obsidian.persist.byStore.plaintext = [ ".config/obsidian" ];
parted.sandbox.method = "landlock";
parted.sandbox.wrapperType = "wrappedDerivation";
parted.sandbox.extraPaths = [
"/dev"
];
@@ -698,10 +765,12 @@ in
patchelf = {};
pavucontrol.sandbox.method = "bwrap";
pavucontrol.sandbox.wrapperType = "wrappedDerivation";
pavucontrol.sandbox.whitelistAudio = true;
pavucontrol.sandbox.whitelistWayland = true;
pciutils.sandbox.method = "landlock";
pciutils.sandbox.wrapperType = "wrappedDerivation";
pciutils.sandbox.extraPaths = [
"/sys/bus/pci"
"/sys/devices"
@@ -710,6 +779,7 @@ in
"perlPackages.FileMimeInfo".sandbox.enable = false; #< TODO: sandbox `mimetype` but not `mimeopen`.
powertop.sandbox.method = "landlock";
powertop.sandbox.wrapperType = "wrappedDerivation";
powertop.sandbox.capabilities = [ "ipc_lock" "sys_admin" ];
powertop.sandbox.extraPaths = [
"/proc"
@@ -718,23 +788,18 @@ in
"/sys/kernel"
];
# procps: free, pgrep, pidof, pkill, ps, pwait, top, uptime, couple others
procps.sandbox.method = "bwrap";
procps.sandbox.extraConfig = [
"--sane-sandbox-keep-namespace" "pid"
];
pstree.sandbox.method = "landlock";
pstree.sandbox.wrapperType = "wrappedDerivation";
pstree.sandbox.extraPaths = [
"/proc"
];
pulseaudio = {};
pulsemixer.sandbox.method = "landlock";
pulsemixer.sandbox.wrapperType = "wrappedDerivation";
pulsemixer.sandbox.whitelistAudio = true;
pwvucontrol.sandbox.method = "bwrap";
pwvucontrol.sandbox.wrapperType = "wrappedDerivation";
pwvucontrol.sandbox.whitelistAudio = true;
pwvucontrol.sandbox.whitelistWayland = true;
@@ -742,6 +807,7 @@ in
requests
]);
python3-repl.sandbox.method = "bwrap";
python3-repl.sandbox.wrapperType = "wrappedDerivation";
python3-repl.sandbox.net = "clearnet";
python3-repl.sandbox.extraHomePaths = [
"/"
@@ -752,24 +818,22 @@ in
qemu.slowToBuild = true;
rsync.sandbox.method = "bwrap";
rsync.sandbox.wrapperType = "wrappedDerivation";
rsync.sandbox.net = "clearnet";
rsync.sandbox.autodetectCliPaths = "existingOrParent";
rustc = {};
sane-open-desktop.sandbox.enable = false; #< trivial script, and all our deps are sandboxed
sane-open-desktop.suggestedPrograms = [
"gdbus"
];
screen.sandbox.enable = false; #< tty; needs to run anything
sequoia.sandbox.method = "bwrap"; # TODO:sandbox: untested
sequoia.sandbox.wrapperType = "wrappedDerivation"; # slow to build
sequoia.sandbox.whitelistPwd = true;
sequoia.sandbox.autodetectCliPaths = true;
shattered-pixel-dungeon.persist.byStore.plaintext = [ ".local/share/.shatteredpixel/shattered-pixel-dungeon" ];
shattered-pixel-dungeon.sandbox.method = "bwrap";
shattered-pixel-dungeon.sandbox.wrapperType = "wrappedDerivation";
shattered-pixel-dungeon.sandbox.whitelistAudio = true;
shattered-pixel-dungeon.sandbox.whitelistDri = true;
shattered-pixel-dungeon.sandbox.whitelistWayland = true;
@@ -777,8 +841,7 @@ in
# printer/filament settings
slic3r.persist.byStore.plaintext = [ ".Slic3r" ];
slurp.sandbox.method = "bwrap";
slurp.sandbox.whitelistWayland = true;
slurp = {};
# use like `sudo smartctl /dev/sda -a`
smartmontools.sandbox.method = "landlock";
@@ -787,6 +850,7 @@ in
smartmontools.sandbox.capabilities = [ "sys_rawio" ];
sops.sandbox.method = "bwrap"; # TODO:sandbox: untested
sops.sandbox.wrapperType = "wrappedDerivation";
sops.sandbox.extraHomePaths = [
".config/sops"
"dev/nixos"
@@ -796,6 +860,7 @@ in
];
soundconverter.sandbox.method = "bwrap";
soundconverter.sandbox.wrapperType = "wrappedDerivation";
soundconverter.sandbox.whitelistWayland = true;
soundconverter.sandbox.extraHomePaths = [
"Music"
@@ -809,25 +874,27 @@ in
soundconverter.sandbox.autodetectCliPaths = "existingOrParent";
sox.sandbox.method = "bwrap";
sox.sandbox.wrapperType = "wrappedDerivation";
sox.sandbox.autodetectCliPaths = "existingFileOrParent";
sox.sandbox.whitelistAudio = true;
space-cadet-pinball.persist.byStore.plaintext = [ ".local/share/SpaceCadetPinball" ];
space-cadet-pinball.sandbox.method = "bwrap";
space-cadet-pinball.sandbox.wrapperType = "wrappedDerivation";
space-cadet-pinball.sandbox.whitelistAudio = true;
space-cadet-pinball.sandbox.whitelistDri = true;
space-cadet-pinball.sandbox.whitelistWayland = true;
speedtest-cli.sandbox.method = "bwrap";
speedtest-cli.sandbox.wrapperType = "wrappedDerivation";
speedtest-cli.sandbox.net = "all";
sqlite = {};
sshfs-fuse = {}; # used by fs.nix
strace.sandbox.enable = false; #< needs to `exec` its args, and therefore support *anything*
subversion.sandbox.method = "bwrap";
subversion.sandbox.wrapperType = "wrappedDerivation";
subversion.sandbox.net = "clearnet";
subversion.sandbox.whitelistPwd = true;
sudo.sandbox.enable = false;
@@ -839,11 +906,8 @@ in
superTux.sandbox.whitelistWayland = true;
superTux.persist.byStore.plaintext = [ ".local/share/supertux2" ];
swappy.sandbox.method = "bwrap";
swappy.sandbox.autodetectCliPaths = "existingFileOrParent";
swappy.sandbox.whitelistWayland = true;
tcpdump.sandbox.method = "landlock";
tcpdump.sandbox.wrapperType = "wrappedDerivation";
tcpdump.sandbox.net = "all";
tcpdump.sandbox.autodetectCliPaths = "existingFileOrParent";
tcpdump.sandbox.capabilities = [ "net_admin" "net_raw" ];
@@ -853,10 +917,12 @@ in
tokodon.persist.byStore.private = [ ".cache/KDE/tokodon" ];
tree.sandbox.method = "landlock";
tree.sandbox.wrapperType = "wrappedDerivation";
tree.sandbox.autodetectCliPaths = true;
tree.sandbox.whitelistPwd = true;
tumiki-fighters.sandbox.method = "bwrap";
tumiki-fighters.sandbox.wrapperType = "wrappedDerivation";
tumiki-fighters.sandbox.whitelistAudio = true;
tumiki-fighters.sandbox.whitelistDri = true; #< not strictly necessary, but triples CPU perf
tumiki-fighters.sandbox.whitelistWayland = true;
@@ -865,28 +931,34 @@ in
util-linux.sandbox.enable = false; #< TODO: possible to sandbox if i specific a different profile for each of its ~50 binaries
unzip.sandbox.method = "bwrap";
unzip.sandbox.wrapperType = "wrappedDerivation";
unzip.sandbox.autodetectCliPaths = "existingOrParent";
unzip.sandbox.whitelistPwd = true;
usbutils.sandbox.method = "bwrap"; # breaks `usbhid-dump`, but `lsusb`, `usb-devices` work
usbutils.sandbox.wrapperType = "wrappedDerivation";
usbutils.sandbox.extraPaths = [
"/sys/devices"
"/sys/bus/usb"
];
visidata.sandbox.method = "bwrap"; # TODO:sandbox: untested
visidata.sandbox.wrapperType = "wrappedDerivation";
visidata.sandbox.autodetectCliPaths = true;
# `vulkaninfo`, `vkcube`
vulkan-tools.sandbox.method = "landlock";
vulkan-tools.sandbox.wrapperType = "wrappedDerivation";
vvvvvv.sandbox.method = "bwrap";
vvvvvv.sandbox.wrapperType = "wrappedDerivation";
vvvvvv.sandbox.whitelistAudio = true;
vvvvvv.sandbox.whitelistDri = true; #< playable without, but burns noticably more CPU
vvvvvv.sandbox.whitelistWayland = true;
vvvvvv.persist.byStore.plaintext = [ ".local/share/VVVVVV" ];
w3m.sandbox.method = "bwrap";
w3m.sandbox.wrapperType = "wrappedDerivation";
w3m.sandbox.net = "all";
w3m.sandbox.extraHomePaths = [
# little-used feature, but you can save web pages :)
@@ -894,9 +966,11 @@ in
];
wdisplays.sandbox.method = "bwrap";
wdisplays.sandbox.wrapperType = "wrappedDerivation";
wdisplays.sandbox.whitelistWayland = true;
wget.sandbox.method = "bwrap";
wget.sandbox.wrapperType = "wrappedDerivation";
wget.sandbox.net = "all";
wget.sandbox.whitelistPwd = true; # saves to pwd by default
@@ -904,13 +978,16 @@ in
# `wg`, `wg-quick`
wireguard-tools.sandbox.method = "landlock";
wireguard-tools.sandbox.wrapperType = "wrappedDerivation";
wireguard-tools.sandbox.capabilities = [ "net_admin" ];
# provides `iwconfig`, `iwlist`, `iwpriv`, ...
wirelesstools.sandbox.method = "landlock";
wirelesstools.sandbox.wrapperType = "wrappedDerivation";
wirelesstools.sandbox.capabilities = [ "net_admin" ];
wl-clipboard.sandbox.method = "bwrap";
wl-clipboard.sandbox.wrapperType = "wrappedDerivation";
wl-clipboard.sandbox.whitelistWayland = true;
wtype = {};
@@ -918,14 +995,16 @@ in
xwayland.sandbox.method = "bwrap";
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.net = "clearnet"; #< just assuming this is needed (X11 traffic)
xwayland.sandbox.whitelistDri = true; #< would assume this gives better gfx perf
xdg-terminal-exec.sandbox.enable = false; # xdg-terminal-exec is a launcher for $TERM
xterm.sandbox.enable = false; # need to be able to do everything
yarn.persist.byStore.plaintext = [ ".cache/yarn" ];
yt-dlp.sandbox.method = "bwrap"; # TODO:sandbox: untested
yt-dlp.sandbox.wrapperType = "wrappedDerivation";
yt-dlp.sandbox.net = "all";
yt-dlp.sandbox.whitelistPwd = true; # saves to pwd by default

View File

@@ -1,8 +1,3 @@
# tips/tricks
# - audio recording
# - default recording input will be silent, on lappy.
# - Audio Setup -> Rescan Audio Devices ...
# - Audio Setup -> Recording device -> sysdefault
{ pkgs, ... }:
{
sane.programs.audacity = {
@@ -15,6 +10,7 @@
};
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.whitelistAudio = true;
sandbox.whitelistWayland = true;
sandbox.autodetectCliPaths = true;
@@ -25,9 +21,6 @@
# audacity needs the entire config dir mounted if running in a sandbox
".config/audacity"
];
sandbox.extraPaths = [
"/dev/snd" # for recording audio inputs to work
];
# disable first-run splash screen
fs.".config/audacity/audacity.cfg".file.text = ''

View File

@@ -88,6 +88,7 @@ in
{
sane.programs.bemenu = {
sandbox.method = "bwrap"; # landlock works, but requires *all* of /run/user/$ID to be granted.
sandbox.wrapperType = "wrappedDerivation";
sandbox.whitelistWayland = true;
sandbox.extraHomePaths = [
".cache/fontconfig" #< else it complains, and is *way* slower

View File

@@ -1,216 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p blast-ugjka
# vim: set filetype=python :
import ctypes
import logging
import os
import signal
import socket
import subprocess
from enum import Enum
logger = logging.getLogger(__name__)
# map from known devices -> required flags
DEVICE_MAP = {
"Theater TV": [],
"[LG] webOS TV OLED55C9PUA": [ "-usewav" ],
}
def set_pdeathsig(sig=signal.SIGTERM):
"""
helper function to ensure once parent process exits, its children processes will automatically die.
see: <https://stackoverflow.com/a/43152455>
see: <https://www.man7.org/linux/man-pages/man2/prctl.2.html>
"""
libc = ctypes.CDLL("libc.so.6")
return libc.prctl(1, sig)
MY_PID = None
def reap_children(sig=None, frame=None):
global MY_PID
# reset SIGTERM handler to avoid recursing
signal.signal(signal.SIGTERM, signal.Handlers.SIG_DFL)
logger.info("killing all children (of pid %d)", MY_PID)
os.killpg(MY_PID, signal.SIGTERM)
def reap_on_exit():
"""
catch when the parent exits, and map that to SIGTERM for this process.
when this process receives SIGTERM, also terminate all descendent processes.
this is done because:
1. mpv invokes this, but (potentially) via the sandbox wrapper.
2. when mpv exits, it `SIGKILL`s that sandbox wrapper.
3. bwrap does not pass SIGKILL or SIGTERM to its child.
4. hence, we neither receive that signal NOR can we pass it on simply by killing our immediate children
(since any bwrap'd children wouldn't pass that signal on...)
really, the proper fix would be on mpv's side:
- mpv should create a new process group when it launches a command, and kill that process group on exit.
or fix this in the sandbox wrapper:
- why *doesn't* bwrap forward the signals?
- there's --die-with-parent, but i can't apply that *system wide* and expect reasonably behavior
<https://github.com/containers/bubblewrap/issues/529>
"""
global MY_PID
MY_PID = os.getpid()
# create a new process group, pgid = gid
os.setpgid(MY_PID, MY_PID)
set_pdeathsig(signal.SIGTERM)
signal.signal(signal.SIGTERM, reap_children)
def get_ranked_ip_addrs():
"""
return the IP addresses most likely to be LAN addresses
based on: <https://stackoverflow.com/a/1267524>
"""
_name, _aliases, static_addrs = socket.gethostbyname_ex(socket.gethostname())
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("1", 53))
con_addr, _port = s.getsockname()
return sorted(set(static_addrs + [ con_addr ]), key=lambda a: (a.startswith("127"), a))
class ParserState(Enum):
Break = "break"
Receiver = "receiver"
Ips = "ip"
class Status(Enum):
Continue = "continue"
Error = "error"
RedoWithFlags = "redo_with_flags"
Launched = "launched"
class BlastDriver:
parsing: ParserState | None = None
last_write: str | None = None
def __init__(self, blast_flags: list[str] = []):
self.ranked_ips = get_ranked_ip_addrs()
self.blast = subprocess.Popen(
["blast", "-source", "blast.monitor"] + blast_flags,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
# this pdeathsig isn't necessary; seems it might result in leaked pulse outputs
# preexec_fn=set_pdeathsig
)
self.blast_flags = list(blast_flags)
self.receiver_names = []
self.ips = []
def writeline(self, line: str) -> None:
logger.debug("[send] %s", line)
self.blast.stdin.write(f"{line}\n".encode())
self.blast.stdin.flush()
self.last_write = line
def readline(self) -> str:
line = self.blast.stdout.readline().decode('utf-8').strip()
line = line.replace('\x1b[1A\x1b[K', '') #< escape codes
logger.debug("[recv] %r", line)
return line
def set_state(self, state: ParserState):
logger.debug("[pars] %s", state)
self.parsing = state
def feedline(self, line: str) -> (Status, str|None):
"""
apply a line from blast's stdout to modify parser state.
returns a status code (e.g. Status.Continue), and optionally a reply to send back to blast.
"""
if line == "Loading...":
return Status.Continue, None
elif line == "----------":
self.set_state(ParserState.Break)
return Status.Continue, None
elif line == "DLNA receivers":
self.set_state(ParserState.Receiver)
return Status.Continue, None
elif line == "Your LAN ip addresses":
self.set_state(ParserState.Ips)
return Status.Continue, None
elif line == "Select the DLNA device:":
assert len(self.receiver_names) == 1, self.receiver_names
name = self.receiver_names[0]
if name in DEVICE_MAP and DEVICE_MAP[name] != self.blast_flags:
return Status.RedoWithFlags, None
return Status.Continue, "0"
elif line == "Select the lan IP address for the stream:":
for r in self.ranked_ips:
if r in self.ips:
return Status.Launched, str(self.ips.index(r))
# fallback: just guess the best IP
return Status.Launched, "0"
elif self.parsing == ParserState.Receiver:
id_, name = line.split(": ")
assert id_ == str(len(self.receiver_names)), (id_, self.receiver_names)
self.receiver_names.append(name)
return Status.Continue, None
elif self.parsing == ParserState.Ips:
id_, ip = line.split(": ")
assert id_ == str(len(self.ips)), (id_, self.ips)
self.ips.append(ip)
return Status.Continue, None
elif line == f"[{self.last_write}]":
# it's echoing to us what we wrote
return Status.Continue, None
# elif line == "":
# return Status.Continue, None
else:
logger.info("unrecognized output (state=%s): %r", self.parsing, line)
return Status.Error, None
def step(self) -> Status:
"""
advance the interaction between us and blast.
reads a line from blast, modifies internal state, maybe sends a reply.
could block indefinitely.
"""
line = self.readline()
status, reply = self.feedline(line)
if reply is not None:
self.writeline(reply)
return status
def try_blast(*args, **kwargs) -> BlastDriver | None:
blast = BlastDriver(*args, **kwargs)
status = Status.Continue
while status == Status.Continue:
status = blast.step()
if status == Status.RedoWithFlags:
dev = blast.receiver_names[0]
blast_flags = DEVICE_MAP[dev]
logger.info("re-exec blast for %s with flags: %r", dev, blast_flags)
blast.blast.terminate()
return try_blast(blast_flags=blast_flags)
elif status == Status.Error:
logger.info("blast error => terminating")
blast.blast.terminate()
else:
# successfully launched
return blast
def main():
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
reap_on_exit()
blast = try_blast()
if blast is not None:
logger.info("waiting until blast exits")
blast.blast.wait()
reap_children()
if __name__ == "__main__":
main()

View File

@@ -1,51 +0,0 @@
# blast: tunnel audio from a pulseaudio sink to a UPnP/DLNA device (like a TV).
# - expect 7s of latency
# - can cast the default sink, or create a new one "blast.monitor"
# and either assign that to default or assign apps to it.
# compatibility:
# - there is no single invocation which will be compatible with all known devices.
# - sony tv:
# - `blast` (default): WORKS
# - `-usewav`: FAILS
# - LG TV:
# - `-usewav`: WORKS!
# - `-useaac`: FAILS
# - `-useflac`: FAILS
# - `-uselpcm`: FAILS
# - `-uselpcmle`: FAILS
# - `-format aac`: FAILS
# - `-bitrate 128`: FAILS
# - `-nochunked`: FAILS
# - `-format "ogg" -mime 'audio/x-opus+ogg'`: FAILS
# - `-mime audio/ac3 -format ac3`: FAILS
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.blast-ugjka;
in
{
sane.programs.blast-ugjka = {
sandbox.method = "bwrap";
sandbox.whitelistAudio = true;
sandbox.net = "clearnet";
};
sane.programs.blast-to-default = {
# helper to deal with blast's interactive CLI
packageUnwrapped = pkgs.static-nix-shell.mkPython3Bin {
pname = "blast-to-default";
pkgs = [ "blast-ugjka" ];
srcRoot = ./.;
};
sandbox.method = "bwrap";
sandbox.whitelistAudio = true;
sandbox.net = "clearnet";
sandbox.extraConfig = [
# else it fails to reap its children (or, maybe, it fails to hook its parent's death signal?)
# might be possible to remove this, but kinda hard to see a clean way.
"--sane-sandbox-keep-namespace" "pid"
];
suggestedPrograms = [ "blast-ugjka" ];
};
networking.firewall.allowedTCPPorts = lib.mkIf cfg.enabled [ 9000 ];
}

View File

@@ -111,16 +111,20 @@ in
};
};
sandbox.method = "bwrap";
sandbox.extraRuntimePaths = [
"/" #< just needs "bonsai", but needs to create it first...
];
services.bonsaid = {
description = "bonsai: programmable input dispatcher";
partOf = [ "graphical-session" ];
command = "bonsaid -t ${cfg.config.configFile}";
cleanupCommand = "rm -f $XDG_RUNTIME_DIR/bonsai";
after = [ "graphical-session.target" ];
wantedBy = [ "graphical-session.target" ];
script = ''
${pkgs.coreutils}/bin/rm -f $XDG_RUNTIME_DIR/bonsai
exec ${cfg.package}/bin/bonsaid -t ${cfg.config.configFile}
'';
serviceConfig = {
Type = "simple";
Restart = "always";
RestartSec = "5s";
};
};
};
}

View File

@@ -21,12 +21,12 @@
# note that invoking bwrap with capabilities in the 'init' namespace does NOT grant the sandboxed process
# capabilities in the 'init' namespace. it's a limitation of namespaces that namespaced processes can
# never receive capabilities in their parent namespace.
substituteInPlace bubblewrap.c --replace-fail \
substituteInPlace bubblewrap.c --replace \
'die ("Unexpected capabilities but not setuid, old file caps config?");' \
'// die ("Unexpected capabilities but not setuid, old file caps config?");'
# enable debug printing
# substituteInPlace utils.h --replace-fail \
# substituteInPlace utils.h --replace \
# '#define __debug__(x)' \
# '#define __debug__(x) printf x'
'';

View File

@@ -44,9 +44,15 @@ in
services.gnome-calls = {
# TODO: prevent gnome-calls from daemonizing when started manually
description = "gnome-calls daemon to monitor incoming SIP calls";
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
# add --verbose for more debugging
command = "env G_MESSAGES_DEBUG=all gnome-calls --daemon";
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
serviceConfig = {
# add --verbose for more debugging
ExecStart = "${cfg.package}/bin/gnome-calls --daemon";
Type = "simple";
Restart = "always";
RestartSec = "10s";
};
environment.G_MESSAGES_DEBUG = "all";
};
};
programs.calls = lib.mkIf cfg.enabled {

View File

@@ -1,17 +0,0 @@
{ ... }:
{
sane.programs.celeste64 = {
sandbox.method = "bwrap";
sandbox.whitelistAudio = true;
sandbox.whitelistDri = true;
sandbox.whitelistWayland = true;
sandbox.extraPaths = [
"/dev/input" #< for controllers
];
persist.byStore.plaintext = [
# save data, controls map
".local/share/Celeste64"
];
};
}

View File

@@ -1,4 +1,4 @@
{ pkgs, ... }:
{ config, pkgs, ... }:
{
sane.programs.conky = {
# TODO: non-sandboxed `conky` still ships via `sxmo-utils`, but unused
@@ -14,7 +14,6 @@
fs.".config/conky/conky.conf".symlink.target =
let
# TODO: make this just another `suggestedPrograms`!
battery_estimate = pkgs.static-nix-shell.mkBash {
pname = "battery_estimate";
srcRoot = ./.;
@@ -27,8 +26,14 @@
services.conky = {
description = "conky dynamic desktop background";
partOf = [ "graphical-session" ];
command = "conky";
after = [ "graphical-session.target" ];
# partOf = [ "graphical-session.target" ]; # propagate stop/restart signal from graphical-session to this unit
wantedBy = [ "graphical-session.target" ];
serviceConfig.ExecStart = "${config.sane.programs.conky.package}/bin/conky";
serviceConfig.Type = "simple";
serviceConfig.Restart = "on-failure";
serviceConfig.RestartSec = "10s";
};
};
}

View File

@@ -1,19 +1,9 @@
{ pkgs, ... }:
{ ... }:
{
sane.programs.cozy = {
packageUnwrapped = pkgs.cozy.overrideAttrs (upstream: {
postPatch = (upstream.postPatch or "") + ''
# disable all reporting.
# this can be done via the settings, but that's troublesome and easy to forget.
# specifically, i don't want moby to be making these network requests several times per hour
# while it might be roaming or trying to put the RF to sleep.
substituteInPlace cozy/application_settings.py \
--replace-fail 'self._settings.get_int("report-level")' '0'
'';
});
sandbox.method = "bwrap"; # landlock gives: _multiprocessing.SemLock: Permission Denied
sandbox.wrapperType = "wrappedDerivation";
sandbox.whitelistAudio = true;
sandbox.whitelistDbus = [ "user" ]; # mpris
sandbox.whitelistWayland = true;

View File

@@ -1,25 +0,0 @@
{ pkgs, ... }:
{
sane.programs.curlftpfs = {
packageUnwrapped = pkgs.curlftpfs.overrideAttrs (upstream: {
# my fork includes:
# - per-operation timeouts (CURLOPT_TIMEOUT; would use CURLOPT_LOW_SPEED_TIME/CURLOPT_LOW_SPEED_LIMIT but they don't apply)
# - exit on timeout (so that one knows to abort the mount, instead of waiting indefinitely)
# - support for "meta" keys found in /etc/fstab
src = pkgs.fetchFromGitea {
domain = "git.uninsane.org";
owner = "colin";
repo = "curlftpfs";
rev = "0890d32e709b5a01153f00d29ed4c00299744f5d";
hash = "sha256-M28PzHqEAkezQdtPeL16z56prwl3BfMZqry0dlpXJls=";
};
# `mount` clears PATH before calling the mount helper (see util-linux/lib/env.c),
# so the traditional /etc/fstab approach of fstype=fuse and device = curlftpfs#URI doesn't work.
# instead, install a `mount.curlftpfs` mount helper. this is what programs like `gocryptfs` do.
postInstall = (upstream.postInstall or "") + ''
ln -s curlftpfs $out/bin/mount.fuse.curlftpfs
ln -s curlftpfs $out/bin/mount.curlftpfs
'';
});
};
}

View File

@@ -9,40 +9,17 @@ let
in
{
sane.programs.dconf = {
configOption = with lib; mkOption {
type = types.submodule {
options = {
site = mkOption {
type = types.listOf types.package;
default = [];
description = ''
extra packages to link into /etc/dconf
'';
};
};
};
default = {};
};
packageUnwrapped = pkgs.rmDbusServicesInPlace pkgs.dconf;
sandbox.method = "bwrap";
sandbox.wrapperType = "inplace"; #< dbus/systemd services live in `.out` but point to `.lib` data.
sandbox.whitelistDbus = [ "user" ];
sandbox.wrapperType = "wrappedDerivation";
persist.byStore.private = [
".config/dconf"
];
};
services.dconf = {
description = "dconf configuration database/server";
partOf = [ "graphical-session" ];
command = "${lib.getLib cfg.package}/libexec/dconf-service";
};
# supposedly necessary for packages which haven't been wrapped (i.e. wrapGtkApp?),
# but in practice seems unnecessary.
# env.GIO_EXTRA_MODULES = "${pkgs.dconf.lib}/lib/gio/modules";
config.site = [
programs.dconf = lib.mkIf cfg.enabled {
# note that `programs.dconf` doesn't allow specifying the dconf package.
enable = true;
packages = [
(pkgs.writeTextFile {
name = "dconf-user-profile";
destination = "/etc/dconf/profile/user";
@@ -53,18 +30,4 @@ in
})
];
};
# TODO: get dconf to read these from ~/.config/dconf ?
environment.etc.dconf = lib.mkIf cfg.enabled {
source = pkgs.symlinkJoin {
name = "dconf-system-config";
paths = map (x: "${x}/etc/dconf") cfg.config.site;
nativeBuildInputs = [ (lib.getBin pkgs.dconf) ];
postBuild = ''
if test -d $out/db; then
dconf update $out/db
fi
'';
};
};
}

View File

@@ -1,139 +0,0 @@
/* Notification center */
.blurredBG, #main_window, .blurredBG.low, .blurredBG.normal {
background: rgba(255, 255, 255, 1.0);
}
.noti-center.time {
font-size: 32px;
}
/* Notifications */
.notification.content {
margin-left: 15px;
margin-right: 15px;
}
.title {
font-weight: bold;
font-size: 16px;
}
.appname {
font-size: 12px;
}
.time {
font-size: 12px;
}
.blurredBG.notification {
background: rgba(255, 255, 255, 0.4);
}
.blurredBG.notification.critical {
background: rgba(255, 0, 0, 0.5);
}
.notificationInCenter.critical {
background: rgba(155, 0, 20, 0.5);
}
/* Labels */
label {
color: #322;
}
label.notification {
color: #322;
}
label.critical {
color: #000;
}
.notificationInCenter label.critical {
color: #000;
}
/* Buttons */
button {
background: transparent;
color: #322;
border-radius: 3px;
border-width: 0px;
background-position: 0px 0px;
text-shadow: none;
}
button:hover {
border-radius: 3px;
background: rgba(0, 20, 20, 0.2);
border-width: 0px;
border-top: transparent;
border-color: #f00;
color: #fee;
}
/* Custom Buttons */
.userbutton {
background: rgba(20,0,0, 0.15);
}
.userbuttonlabel {
color: #222;
font-size: 12px;
}
.userbutton:hover {
background: rgba(20, 0, 0, 0.2);
}
.userbuttonlabel:hover {
color: #111;
}
button.buttonState1 {
background: rgba(20,0,0,0.5);
}
.userbuttonlabel.buttonState1 {
color: #fff;
}
button.buttonState1:hover {
background: rgba(20,0,0, 0.4);
}
.userbuttonlabel.buttonState1:hover {
color: #111;
}
button.buttonState2 {
background: rgba(255,255,255,0.3);
}
.userbuttonlabel.buttonState2 {
color: #111;
}
button.buttonState2:hover {
background: rgba(20,0,0, 0.3);
}
.userbuttonlabel.buttonState2:hover {
color: #000;
}
/* Images */
image.deadd-noti-center.notification.image {
margin-left: 20px;
}

View File

@@ -1,263 +0,0 @@
### Margins for notification-center/notifications
margin-top: 0
margin-right: 0
### Margins for notification-center
margin-bottom: 0
### Width of the notification center/notifications in pixels.
width: 360
### Command to run at startup. This can be used to setup
### button states.
# startup-command: deadd-notification-center-startup
### Monitor on which the notification center/notifications will be
### printed. If "follow-mouse" is set true, this does nothing.
monitor: 0
### If true, the notification center/notifications will open on the
### screen, on which the mouse is. Overrides the "monitor" setting.
follow-mouse: false
notification-center:
### Margin at the top/right/bottom of the notification center in
### pixels. This can be used to avoid overlap between the notification
### center and bars such as polybar or i3blocks.
margin-top: 40
# margin-right: 0
# margin-bottom: 0
### Width of the notification center in pixels.
# width: 500
### Monitor on which the notification center will be printed. If
### "follow-mouse" is set true, this does nothing.
# monitor: 0
### If true, the notification center will open on the screen, on which
### the mouse is. Overrides the "monitor" setting.
# follow-mouse: false
### Notification center closes when the mouse leaves it
hide-on-mouse-leave: true
### If newFirst is set to true, newest notifications appear on the top
### of the notification center. Else, notifications stack, from top to
### bottom.
new-first: true
### If true, the transient field in notifications will be ignored,
### thus the notification will be persisted in the notification
### center anyways
ignore-transient: false
### Custom buttons in notification center
buttons:
### Numbers of buttons that can be drawn on a row of the notification
### center.
# buttons-per-row: 5
### Height of buttons in the notification center (in pixels).
# buttons-height: 60
### Horizontal and vertical margin between each button in the
### notification center (in pixels).
# buttons-margin: 2
### Button actions and labels. For each button you must specify a
### label and a command.
actions:
# - label: VPN
# command: "sudo vpnToggle"
# - label: Bluetooth
# command: bluetoothToggle
# - label: Wifi
# command: wifiToggle
# - label: Screensaver
# command: screensaverToggle
# - label: Keyboard
# command: keyboardToggle
notification:
### If true, markup (<u>, <i>, <b>, <a>) will be displayed properly
use-markup: true
### If true, html entities (&#38; for &, &#37; for %, etc) will be
### parsed properly. This is useful for chromium-based apps, which
### tend to send these in notifications.
parse-html-entities: true
dbus:
### If noti-closed messages are enabled, the sending application
### will know that a notification was closed/timed out. This can
### be an issue for certain applications, that overwrite
### notifications on status updates (e.g. Spotify on each
### song). When one of these applications thinks, the notification
### has been closed/timed out, they will not overwrite existing
### notifications but send new ones. This can lead to redundant
### notifications in the notification center, as the close-message
### is send regardless of the notification being persisted.
send-noti-closed: false
app-icon:
### If set to true: If no icon is passed by the app_icon parameter
### and no application "desktop-entry"-hint is present, deadd will
### try to guess the icon from the application name (if present).
guess-icon-from-name: true
### The display size of the application icons in the notification
### pop-ups and in the notification center
icon-size: 20
image:
### The maximal display size of images that are part of
### notifications for notification pop-ups and in the notification
### center
size: 100
### The margin around the top, bottom, left, and right of
### notification images.
margin-top: 15
margin-bottom: 15
margin-left: 15
margin-right: 0
### Apply modifications to certain notifications:
### Each modification rule needs a "match" and either a "modify" or
### a "script" entry.
modifications:
### Match:
### Matches the notifications against these rules. If all of the
### values (of one modification rule) match, the "modify"/"script"
### part is applied.
# - match:
### Possible match criteria:
# title: "Notification title"
# body: "Notification body"
# time: "12:44"
# app-name: "App name"
# urgency: "low" # "low", "normal" or "critical"
# modify:
### Possible modifications
# title: "abc"
# body: "abc"
# app-name: "abc"
# app-icon: "file:///abc.png"
### The timeout has three special values:
### timeout: 0 -> don't time out at all
### timeout: -1 -> use default timeout
### timeout: 1 -> don't show as pop-up
### timeout: >1 -> milliseconds until timeout
# timeout: 1
# margin-right: 10
# margin-top: 10
# image: "file:///abc.png"
# image-size: 10
# transient: true
# send-noti-closed: false
### Remove action buttons from notifications
# remove-actions: true
### Set the action-icons hint to true, action labels will then
### be intergreted as GTK icon names
# action-icons: true
### List of actions, where the even elements (0, 2, ...) are the
### action name and the odd elements are the label
# actions:
# - previous
# - media-skip-backward
# - play
# - media-playback-start
# - next
# - media-skip-forward
### Action commands, where the keys (e.g. "play") is the action
### name and the value is a program call that should be executed
### on action. Prevents sending of the action to the application.
# action-commands:
# play: playerctl play-pause
# previous: playerctl previous
# next: playerctl next
### Add a class-name to the notification container, that can be
### used for specific styling of notifications using the
### deadd.css file
# class-name: "abc"
# - match:
# app-name: "Chromium"
### Instead of modifying a notification directly, a script can be
### run, which will receive the notification as JSON on STDIN. It
### is expected to return JSON/YAML configuration that defines the
### modifications that should be applied. Minimum complete return
### value must be '{"modify": {}, "match": {}}'. Always leave the "match"
### object empty (technical reasons, i.e. I am lazy).
# script: "linux-notification-center-parse-chromium"
- match:
app-name: "Spotify"
modify:
image-size: 80
timeout: 1
send-noti-closed: true
class-name: "Spotify"
action-icons: true
actions:
- previous
- media-skip-backward
- play
- media-playback-start
- next
- media-skip-forward
action-commands:
play: playerctl play-pause
previous: playerctl previous
next: playerctl next
# - match:
# title: Bildschirmhelligkeit
# modify:
# image-size: 60
popup:
### Default timeout used for notifications in milli-seconds. This can
### be overwritten with the "-t" option (or "--expire-time") of the
### notify-send command.
default-timeout: 10000
### Margin above/right/between notifications (in pixels). This can
### be used to avoid overlap between notifications and a bar such as
### polybar or i3blocks.
margin-top: 50
margin-right: 50
margin-between: 20
### Defines after how many lines of text the body will be truncated.
### Use 0 if you want to disable truncation.
max-lines-in-body: 3
### Determines whether the GTK widget that displays the notification body
### in the notification popup will be hidden when empty. This is especially
### useful for transient notifications that display a progress bar.
# hide-body-if-empty: false
### Monitor on which the notifications will be
### printed. If "follow-mouse" is set true, this does nothing.
# monitor: 0
### If true, the notifications will open on the
### screen, on which the mouse is. Overrides the "monitor" setting.
# follow-mouse: false
click-behavior:
### The mouse button for dismissing a popup. Must be either "mouse1",
### "mouse2", "mouse3", "mouse4", or "mouse5"
dismiss: mouse1
### The mouse button for opening a popup with the default action.
### Must be either "mouse1", "mouse2", "mouse3", "mouse4", or "mouse5"
default-action: mouse3

View File

@@ -1,17 +0,0 @@
# docs are via README only:
# - <https://github.com/phuhl/linux_notification_center>
# reload config:
# - `notify-send a --hint=boolean:deadd-notification-center:true --hint=string:type:reloadStyle`
# toggle visibility:
# - `kill -s USR1 $(pidof deadd-notification-center)`
# clear notifications:
# - `notify-send a --hint=boolean:deadd-notification-center:true --hint=string:type:clearInCenter`
# set state of user button 0 to "highlighted" (true)
# - `notify-send a --hint=boolean:deadd-notification-center:true --hint=int:id:0 --hint=boolean:state:true --hint=type:string:buttons`
{ ... }:
{
sane.programs.deadd-notification-center = {
fs.".config/deadd/deadd.css".symlink.target = ./deadd.css;
fs.".config/deadd/deadd.yml".symlink.target = ./deadd.yml;
};
}

View File

@@ -5,33 +5,25 @@
./abaddon.nix
./aerc.nix
./alacritty.nix
./alsa-ucm-conf
./animatch.nix
./assorted.nix
./audacity.nix
./bemenu.nix
./blast-ugjka
./bonsai.nix
./brave.nix
./bubblewrap.nix
./calls.nix
./cantata.nix
./catt.nix
./celeste64.nix
./chatty.nix
./conky
./cozy.nix
./curlftpfs.nix
./dconf.nix
./deadd-notification-center
./dialect.nix
./dino.nix
./dissent.nix
./element-desktop.nix
./engrampa.nix
./epiphany.nix
./evince.nix
./fcitx5.nix
./feedbackd.nix
./firefox.nix
./firejail.nix
@@ -53,15 +45,14 @@
./gpodder.nix
./grimshot.nix
./gthumb.nix
./gtkcord4.nix
./handbrake.nix
./helix.nix
./htop
./imagemagick.nix
./jellyfin-media-player.nix
./kdenlive.nix
./komikku.nix
./koreader
./less.nix
./libreoffice.nix
./lemoa.nix
./loupe.nix
@@ -70,7 +61,7 @@
./mepo.nix
./mimeo
./mopidy.nix
./mpv
./mpv.nix
./msmtp.nix
./nautilus.nix
./neovim.nix
@@ -90,11 +81,7 @@
./rhythmbox.nix
./ripgrep.nix
./rofi
./s6-rc.nix
./sane-input-handler
./sane-screenshot.nix
./sane-scripts.nix
./schlock.nix
./sfeed.nix
./signal-desktop.nix
./splatmoji.nix
@@ -107,10 +94,8 @@
./supertuxkart.nix
./sway
./sway-autoscaler
./swayidle.nix
./swaylock.nix
./swaynotificationcenter
./sysvol.nix
./swaynotificationcenter.nix
./tangram.nix
./tor-browser.nix
./tuba.nix
@@ -122,19 +107,20 @@
./wine.nix
./wireplumber.nix
./wireshark.nix
./wvkbd.nix
./wob
./xarchiver.nix
./xdg-desktop-portal.nix
./xdg-desktop-portal-gtk.nix
./xdg-desktop-portal-wlr.nix
./xdg-terminal-exec.nix
./xdg-utils.nix
./zathura.nix
./zeal.nix
./zecwallet-lite.nix
./zsh
];
# XXX: this might not be necessary. try removing this and cacert.unbundled (servo)?
environment.etc."ssl/certs".source = "${pkgs.cacert.unbundled}/etc/ssl/certs/*";
config = {
# XXX: this might not be necessary. try removing this and cacert.unbundled (servo)?
environment.etc."ssl/certs".source = "${pkgs.cacert.unbundled}/etc/ssl/certs/*";
};
}

View File

@@ -5,9 +5,6 @@
sandbox.wrapperType = "inplace"; # share/search_providers/ calls back into the binary, weird wrap semantics
sandbox.whitelistWayland = true;
sandbox.net = "clearnet";
sandbox.extraHomePaths = [
".config/dconf" # won't start without it
];
suggestedPrograms = [ "dconf" ]; #< to persist settings
packageUnwrapped = pkgs.dialect.overrideAttrs (upstream: {

View File

@@ -14,11 +14,6 @@
# but at present it has no "start in tray" type of option: it must render a window.
#
# outstanding bugs:
# - NAT holepunching burns CPU/NET when multiple interfaces are up
# - fix by just `ip link set ovpnd-xyz down`
# - setting `wg-home` down *seems* to be not necessary
# - characterized by UPnP/SOAP error 500/718 in wireshark
# - seems it asks router A to open a port mapping for an IP address which belongs to a different subnet...
# - mic is sometimes disabled at call start despite presenting as enabled
# - fix is to toggle it off -> on in the Dino UI
# - default mic gain is WAY TOO MUCH (heavily distorted)
@@ -34,7 +29,7 @@
# - possibly Dino should be updated internally: `info.rate / 100` -> `info.rate / 50`.
# - i think that affects the batching for echo cancellation, adaptive gain control, etc.
#
{ config, lib, pkgs, ... }:
{ config, lib, ... }:
let
cfg = config.sane.programs.dino;
in
@@ -50,25 +45,8 @@ in
};
};
packageUnwrapped = pkgs.dino.overrideAttrs (upstream: {
# i'm updating experimentally to see if it improves call performance.
# i don't *think* this is actually necessary; i don't notice any difference.
version = "0.4.3-unstable-2024-04-01";
src = lib.warnIf (lib.versionOlder "0.4.3" upstream.version) "dino update: safe to remove sane patches" pkgs.fetchFromGitHub {
owner = "dino";
repo = "dino";
rev = "d9fa4daa6a7d16f5f0e2183a77ee2d07849dd9f3";
hash = "sha256-vJBIMsMLlK8Aw19fD2aFNtegXkjOqEgb3m1hi3fE5DE=";
};
checkPhase = ''
runHook preCheck
./xmpp-vala-test
# ./signal-protocol-vala-test # doesn't exist anymore
runHook postCheck
'';
});
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.net = "clearnet";
sandbox.whitelistAudio = true;
sandbox.whitelistDbus = [ "user" ]; # notifications
@@ -91,22 +69,26 @@ in
services.dino = {
description = "dino XMPP client";
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
after = [ "graphical-session.target" ];
# partOf = [ "graphical-session.target" ];
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/dino";
Type = "simple";
Restart = "always";
RestartSec = "20s";
};
# audio buffering; see: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#pipewire-buffering-explained>
# dino defaults to 10ms mic buffer, which causes underruns, which Dino handles *very* poorly
# as in, the other end of the call will just not receive sound from us for a couple seconds.
# pipewire uses power-of-two buffering for the mic itself. that would put us at 21.33 ms, but this env var supports only whole numbers (21ms ends up not power-of-two).
# also, Dino's likely still doing things in 10ms batches internally anyway.
#
# further: decrease the "niceness" of dino, so that it can take precedence over anything else.
# ideally this would target just the audio processing, rather than the whole program.
# pipewire is the equivalent of `nice -n -21`, so probably don't want to go any more extreme than that.
# nice -n -15 chosen arbitrarily; not optimized
#
environment.PULSE_LATENCY_MSEC = "20";
# note that debug logging during calls produces so much journal spam that it pegs the CPU and causes dropped audio
# env G_MESSAGES_DEBUG = "all";
command = "env PULSE_LATENCY_MSEC=20 nice -n -15 dino";
# environment.G_MESSAGES_DEBUG = "all";
};
};
}

View File

@@ -4,28 +4,20 @@
# - <https://github.com/vector-im/element-desktop/issues/1029#issuecomment-1632688224>
# - `rm -rf ~/.config/Element/GPUCache`
# - <https://github.com/NixOS/nixpkgs/issues/244486>
{ lib, pkgs, ... }:
{ pkgs, ... }:
{
sane.programs.element-desktop = {
packageUnwrapped = (pkgs.element-desktop.override {
# use pre-built electron because otherwise it takes 4 hrs to build from source.
electron = pkgs.electron_28-bin;
}).overrideAttrs (upstream: {
# fix to use wayland instead of Xwayland:
# - replace `NIXOS_OZONE_WL` non-empty check with `WAYLAND_DISPLAY`
# - use `wayland` instead of `auto` because --ozone-platform-hint=auto still prefers X over wayland when both are available
# alternatively, set env var: `ELECTRON_OZONE_PLATFORM_HINT=wayland` and ignore all of this
installPhase = lib.replaceStrings
[ "NIXOS_OZONE_WL" "--ozone-platform-hint=auto" ]
[ "WAYLAND_DISPLAY" "--ozone-platform-hint=wayland" ]
upstream.installPhase
;
});
packageUnwrapped = pkgs.element-desktop.override {
# use pre-build electron because otherwise it takes 4 hrs to build from source.
electron = pkgs.electron-bin;
};
suggestedPrograms = [
"gnome-keyring"
"xwayland"
];
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.net = "clearnet";
sandbox.whitelistAudio = true;
sandbox.whitelistDbus = [ "user" ]; # notifications

View File

@@ -1,17 +0,0 @@
{ pkgs, ... }:
{
sane.programs."mate.engrampa" = {
packageUnwrapped = pkgs.rmDbusServices pkgs.mate.engrampa;
sandbox.method = "bwrap"; # TODO:sandbox: untested
sandbox.whitelistWayland = true;
sandbox.autodetectCliPaths = "existingOrParent";
sandbox.extraHomePaths = [
"archive"
"Books/local"
"Books/servo"
"records"
"ref"
"tmp"
];
};
}

View File

@@ -12,13 +12,11 @@
sandbox.wrapperType = "inplace"; # /share/epiphany/default-bookmarks.rdf refers back to /share; dbus files to /libexec
sandbox.net = "clearnet";
sandbox.whitelistAudio = true;
sandbox.whitelistDbus = [ "user" ]; #< silently fails to start without it.
# default sandboxing breaks rendering in weird ways. sites are super zoomed in / not scaled.
# enabling DRI/DRM (as below) seems to fix that.
sandbox.whitelistDri = true;
sandbox.whitelistWayland = true;
sandbox.extraHomePaths = [
".config/dconf" # else will always prompt "make default browser?"
".config/epiphany" #< else it gets angry at launch
"tmp"
];

View File

@@ -1,113 +0,0 @@
# fcitx5 is an "input method", to e.g. allow typing CJK on qwerty.
# but i also misuse it to allow typing emoji on qwerty:
# - press `Super+backtick`
# - type something like "effort"
# - it should be underlined, at the least
# - if well supported (e.g. Firefox; also gtk4, alacritty on sway 1.10+), a drop-down fuzzy matcher will appear
# - press space
# - "effort" should be replaced by `(ง •̀_•́)ง`
#
## debugging
# - `fcitx5-diagnose`
#
## config/docs:
# - `fcitx5-configtool`, then check ~/.config/fcitx5 files
# - <https://fcitx-im.org/wiki/Fcitx_5>
# - <https://wiki.archlinux.org/title/Fcitx5>
# - theming: <https://wiki.archlinux.org/title/Fcitx5#Themes_and_appearance>
# - <https://en.wikipedia.org/wiki/Fcitx>
# - wayland specifics: <https://fcitx-im.org/wiki/Using_Fcitx_5_on_Wayland>
# - quickphrase (emoji): <https://fcitx-im.org/wiki/QuickPhrase>
# - override phrases via `~/.config/fcitx/data/QuickPhrase.mb`
# - customize bindings via `fcitx5-configtool` > addons > QuickPhrase
# - theming:
# - nixpkgs has a few themes: `fcitx5-{material-color,nord,rose-pine}`
# - NUR has a few themes
# - <https://github.com/catppuccin/fcitx5>
{ lib, pkgs, ... }:
{
sane.programs.fcitx5 = {
packageUnwrapped = pkgs.fcitx5-with-addons.override {
addons = with pkgs; [
# fcitx5-mozc # japanese input: <https://github.com/fcitx/mozc>
fcitx5-gtk # <https://github.com/fcitx/fcitx5-gtk>
];
};
sandbox.method = "bwrap";
sandbox.whitelistDbus = [ "user" ];
sandbox.whitelistWayland = true; # for `fcitx5-configtool, if nothing else`
sandbox.extraHomePaths = [
# ".config/fcitx"
".config/fcitx5"
".local/share/fcitx5"
];
fs.".config/fcitx5/conf/quickphrase.conf".symlink.text = ''
# Choose key modifier
Choose Modifier=None
# Enable Spell check
Spell=True
FallbackSpellLanguage=en
[TriggerKey]
# defaults: Super+grave, Super+semicolon
# gtk apps use ctrl+period, so super+period is a nice complement
0=Super+grave
1=Super+semicolon
2=Super+period
'';
fs.".config/fcitx5/conf/classicui.conf".symlink.text = ''
Theme=sane
Font="Sans 20"
Vertical Candidate List=True
'';
fs.".local/share/fcitx5/themes/sane/theme.conf".symlink.text = ''
# i omit several keys, especially the ones which don't seem to do much.
# for a theme which uses many more options, see:
# - <https://github.com/catppuccin/fcitx5/blob/main/src/catppuccin-mocha/theme.conf>
[Metadata]
Name=sane
ScaleWithDPI=True
[InputPanel]
NormalColor=#d8d8d8
HighlightCandidateColor=#FFFFFF
HighlightColor=#FFFFFF
HighlightBackgroundColor=#1f5e54
[InputPanel/Background]
Color=#1f5e54
[InputPanel/Highlight]
Color=#418379
[InputPanel/Highlight/Margin]
Left=20
Right=20
Top=7
Bottom=7
[InputPanel/TextMargin]
Left=20
Right=20
Top=6
Bottom=6
'';
services.fcitx5 = {
description = "fcitx5: input method (IME) for emoji/internationalization";
partOf = [ "graphical-session" ];
command = "fcitx5";
};
env.XMODIFIERS = "@im=fcitx";
# setting IM_MODULE is generally not required on wayland, but can be used to override the toolkit's own dialogs with our own.
# env.GTK_IM_MODULE = "fcitx";
# enable if you want them:
# env.QT_IM_MODULE = "fcitx";
# env.QT_PLUGIN_PATH = [ "${cfg.package}/${pkgs.qt6.qtbase.qtPluginPrefix}" ];
# env.SDL_IM_MODULE = "fcitx";
# env.GLFW_IM_MODULE = "ibus"; # for KiTTY, as per <https://wiki.archlinux.org/title/Fcitx5#Integration>
};
}

View File

@@ -25,6 +25,7 @@ in
};
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.whitelistDbus = [ "user" ];
sandbox.whitelistAudio = true;
@@ -96,16 +97,18 @@ in
services.feedbackd = {
description = "feedbackd audio/vibration/led controller";
depends = [ "sound" ];
partOf = [ "default" ];
command = lib.concatStringsSep " " ([
"env"
"G_MESSAGES_DEBUG=all"
] ++ lib.optionals cfg.config.proxied [
"FEEDBACK_THEME=$HOME/.config/feedbackd/themes/proxied.json"
] ++ [
"${cfg.package}/libexec/feedbackd"
]);
wantedBy = [ "default.target" ]; #< should technically be `sound.target`, but that doesn't seem to get auto-started?
serviceConfig = {
ExecStart = "${cfg.package}/libexec/feedbackd";
Type = "simple";
Restart = "on-failure";
RestartSec = "10s";
};
environment = {
G_MESSAGES_DEBUG = "all";
} // (lib.optionalAttrs cfg.config.proxied {
FEEDBACK_THEME = "/home/colin/.config/feedbackd/themes/proxied.json";
});
};
};

View File

@@ -234,7 +234,7 @@ in
sane.programs.firefox = {
inherit packageUnwrapped;
sandbox.method = "bwrap"; # landlock works, but requires all of /proc to be linked
sandbox.wrapperType = "inplace"; # trivial package; cheap enough to wrap inplace
sandbox.wrapperType = "inplace"; # probably wrappedDerivation could work too.
sandbox.net = "all";
sandbox.whitelistAudio = true;
sandbox.whitelistDbus = [ "user" ]; # mpris
@@ -322,8 +322,6 @@ in
defaultPref("widget.use-xdg-desktop-portal.mime-handler", 1);
defaultPref("widget.use-xdg-desktop-portal.open-uri", 1);
defaultPref("browser.toolbars.bookmarks.visibility", "never");
// auto-open mpv:// URIs without prompting.
// can do this with other protocols too (e.g. matrix?). see about:config for common handlers.
defaultPref("network.protocol-handler.external.mpv", true);

View File

@@ -1,10 +1,6 @@
# to preview fonts:
# - `font-manager` (gui)
# - useful to determine official name; codepoint support
# docs:
# - <https://slatecave.net/notebook/fontconfig/>
# debugging:
# - `fc-conflist` -> show all config files loaded
{ config, lib, pkgs, ... }:
let
# nerdfonts takes popular open fonts and patches them to support a wider range of glyphs, notably emoji.
@@ -34,12 +30,13 @@ in
{
sane.programs.fontconfig = {
sandbox.method = "bwrap"; # TODO:sandbox: untested
sandbox.wrapperType = "wrappedDerivation";
sandbox.autodetectCliPaths = "existingOrParent"; #< this might be overkill; or, how many programs reference fontconfig internally?
# persist.byStore.plaintext = [
# # < 10 MiB. however, nixos generates its own fontconfig cache at build time now.
# ".cache/fontconfig"
# ];
persist.byStore.plaintext = [
# < 10 MiB
".cache/fontconfig"
];
};
fonts = lib.mkIf config.sane.programs.fontconfig.enabled {
@@ -47,8 +44,8 @@ in
fontconfig.defaultFonts = {
emoji = [
"Noto Color Emoji"
# "Font Awesome 6 Free"
# "Font Awesome 6 Brands"
"Font Awesome 6 Free"
"Font Awesome 6 Brands"
];
monospace = [
"Hack Nerd Font Propo"
@@ -70,7 +67,7 @@ in
# TODO: reduce this font set.
# - probably need only one of dejavu/freefont/liberation
dejavu_fonts # 10 MiB; DejaVu {Sans,Serif,Sans Mono,Math TeX Gyre}; also available as a NerdFonts (Sans Mono only)
# font-awesome # 2 MiB; Font Awesome 6 {Free,Brands}
font-awesome # 2 MiB; Font Awesome 6 {Free,Brands}
freefont_ttf # 11 MiB; Free{Mono,Sans,Serif}
gyre-fonts # 4 MiB; Tex Gyre *; ttf substitutes for standard PostScript fonts
# hack-font # 1 MiB; Hack; also available as a NerdFonts

View File

@@ -28,6 +28,7 @@ in
# packageUnwrapped = pkgs.fractal-next;
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.net = "clearnet";
sandbox.whitelistAudio = true;
sandbox.whitelistDbus = [ "user" ]; # notifications
@@ -59,19 +60,26 @@ in
persist.byStore.private = [
# XXX by default fractal stores its state in ~/.local/share/<build-profile>/<UUID>.
# ".local/share/hack" # for debug-like builds
# ".local/share/stable" # for normal releases
".local/share/fractal" # for version 5+
".local/share/hack" # for debug-like builds
".local/share/stable" # for normal releases
".local/share/fractal" # for version 5+, i think?
];
suggestedPrograms = [ "gnome-keyring" ];
services.fractal = {
description = "fractal Matrix client";
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
after = [ "graphical-session.target" ];
# partOf = [ "graphical-session.target" ];
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
# env "G_MESSAGES_DEBUG=all"
command = "fractal";
serviceConfig = {
ExecStart = "${cfg.package}/bin/fractal";
Type = "simple";
Restart = "always";
RestartSec = "20s";
};
# environment.G_MESSAGES_DEBUG = "all";
};
};
}

View File

@@ -3,6 +3,7 @@
{
sane.programs.frozen-bubble = {
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.net = "clearnet"; # net play
sandbox.whitelistAudio = true;
sandbox.whitelistWayland = true;

View File

@@ -9,6 +9,7 @@
{
sane.programs.g4music = {
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.whitelistAudio = true;
sandbox.whitelistDbus = [ "user" ]; # mpris
sandbox.whitelistWayland = true;

View File

@@ -4,6 +4,7 @@
packageUnwrapped = pkgs.linkIntoOwnPackage pkgs.glib "bin/gdbus";
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.whitelistDbus = [ "user" ]; #< XXX: maybe future users will also want system access
};
}

View File

@@ -20,6 +20,7 @@ in
};
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.net = "clearnet";
sandbox.whitelistDbus = [ "user" ]; # notifications
sandbox.whitelistWayland = true;
@@ -87,8 +88,16 @@ in
services.geary = {
description = "geary email client";
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
command = "geary";
after = [ "graphical-session.target" ];
# partOf = [ "graphical-session.target" ];
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/geary";
Type = "simple";
Restart = "always";
RestartSec = "20s";
};
};
};

View File

@@ -19,6 +19,7 @@ in
'';
});
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.net = "clearnet";
sandbox.whitelistPwd = true;
sandbox.autodetectCliPaths = true; # necessary for git-upload-pack

View File

@@ -1,12 +1,15 @@
{ lib, pkgs, ... }:
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.gnome-keyring;
in
{
sane.programs.gnome-keyring = {
packageUnwrapped = pkgs.rmDbusServices pkgs.gnome.gnome-keyring;
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.whitelistDbus = [ "user" ];
sandbox.extraRuntimePaths = [
"keyring" #< only needs keyring/control, but has to *create* that.
# "keyring/control"
"keyring/control"
];
sandbox.capabilities = [
# ipc_lock: used to `mlock` the secrets so they don't get swapped out.
@@ -25,10 +28,10 @@
fs.".local/share/keyrings/default" = {
file.text = "Default_keyring.keyring"; #< no trailing newline
# wantedBy = [ config.sane.fs."${config.sane.persist.stores.private.origin}".unit ];
# wantedBeforeBy = [ #< don't create this as part of `multi-user.target`
# "gnome-keyring.service" # TODO: sane.programs should declare this dependency for us
# ];
wantedBy = [ config.sane.fs."${config.sane.persist.stores.private.origin}".unit ];
wantedBeforeBy = [ #< don't create this as part of `multi-user.target`
"gnome-keyring.service" # TODO: sane.programs should declare this dependency for us
];
};
# N.B.: certain keyring names have special significance
# `login.keyring` is forcibly encrypted to the user's password, so that pam gnome-keyring can unlock it on login.
@@ -40,21 +43,22 @@
lock-on-idle=false
lock-after=false
'';
# wantedBy = [ config.sane.fs."${config.sane.persist.stores.private.origin}".unit ];
# wantedBeforeBy = [ #< don't create this as part of `multi-user.target`
# "gnome-keyring.service"
# ];
wantedBy = [ config.sane.fs."${config.sane.persist.stores.private.origin}".unit ];
wantedBeforeBy = [ #< don't create this as part of `multi-user.target`
"gnome-keyring.service"
];
};
services.gnome-keyring = {
description = "gnome-keyring-daemon: secret provider";
partOf = [ "graphical-session" ];
command = let
gkr-start = pkgs.writeShellScriptBin "gnome-keyring-daemon-start" ''
mkdir -m 0700 -p $XDG_RUNTIME_DIR/keyring
exec gnome-keyring-daemon --start --foreground --components=secrets
'';
in "${gkr-start}/bin/gnome-keyring-daemon-start";
after = [ "graphical-session.target" ];
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/gnome-keyring-daemon --start --foreground --components=secrets";
Type = "simple";
Restart = "always";
RestartSec = "20s";
};
};
};
}

View File

@@ -1,8 +1,8 @@
{ pkgs, ... }:
{ ... }:
{
sane.programs."gnome.gnome-maps" = {
packageUnwrapped = pkgs.rmDbusServices pkgs.gnome.gnome-maps;
sandbox.method = "bwrap";
sandbox.wrapperType = "inplace"; #< dbus files
sandbox.whitelistDri = true; # for perf
sandbox.whitelistDbus = [
"system" # system is required for non-portal location services

View File

@@ -4,7 +4,7 @@
{
sane.programs."gnome.gnome-weather" = {
sandbox.method = "bwrap";
sandbox.wrapperType = "inplace"; #< /share/org.gnome.Weather/org.gnome.Weather file refers to bins by full path
sandbox.wrapperType = "inplace";
sandbox.whitelistWayland = true;
sandbox.net = "clearnet";
suggestedPrograms = [ "dconf" ]; #< stores city/location settings

View File

@@ -34,6 +34,7 @@ in
{
sane.programs.go2tv = {
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.net = "clearnet";
sandbox.autodetectCliPaths = true;
# for GUI invocation, allow the common media directories

View File

@@ -23,6 +23,7 @@ in {
});
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.whitelistDbus = [ "user" ]; # it won't launch without it, dunno exactly why.
sandbox.whitelistWayland = true;
sandbox.net = "clearnet";

View File

@@ -15,6 +15,7 @@
"wl-clipboard"
];
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.whitelistWayland = true;
sandbox.whitelistDbus = [ "user" ];
sandbox.autodetectCliPaths = "existingFileOrParent";

View File

@@ -3,10 +3,10 @@
# - notification sounds can be handled by swaync
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.dissent;
cfg = config.sane.programs.gtkcord4;
in
{
sane.programs.dissent = {
sane.programs.gtkcord4 = {
configOption = with lib; mkOption {
default = {};
type = types.submodule {
@@ -17,21 +17,22 @@ in
};
};
packageUnwrapped = pkgs.dissent.overrideAttrs (upstream: {
packageUnwrapped = pkgs.gtkcord4.overrideAttrs (upstream: {
postConfigure = (upstream.postConfigure or "") + ''
# dissent uses go-keyring to interface with the org.freedesktop.secrets provider (i.e. gnome-keyring).
# gtkcord4 uses go-keyring to interface with the org.freedesktop.secrets provider (i.e. gnome-keyring).
# go-keyring hardcodes `login.keyring` as the keyring to store secrets in, instead of reading `~/.local/share/keyring/default`.
# `login.keyring` seems to be a special keyring preconfigured (by gnome-keyring) to encrypt everything to the user's password.
# that's redundant with my fs-level encryption and makes the keyring less inspectable,
# so patch dissent to use Default_keyring instead.
# so patch gtkcord4 to use Default_keyring instead.
# see:
# - <https://github.com/diamondburned/dissent/issues/139>
# - <https://github.com/diamondburned/gtkcord4/issues/139>
# - <https://github.com/zalando/go-keyring/issues/46>
substituteInPlace vendor/github.com/zalando/go-keyring/secret_service/secret_service.go \
--replace-fail '"login"' '"Default_keyring"'
--replace '"login"' '"Default_keyring"'
'';
});
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.net = "clearnet";
sandbox.whitelistAudio = true;
sandbox.whitelistDbus = [ "user" ]; # notifications
@@ -51,14 +52,22 @@ in
];
persist.byStore.private = [
".cache/dissent"
".config/dissent" # empty?
".cache/gtkcord4"
".config/gtkcord4" # empty?
];
services.dissent = {
description = "dissent Discord client";
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
command = "dissent";
services.gtkcord4 = {
description = "gtkcord4 Discord client";
after = [ "graphical-session.target" ];
# partOf = [ "graphical-session.target" ];
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/gtkcord4";
Type = "simple";
Restart = "always";
RestartSec = "20s";
};
};
};
}

View File

@@ -2,6 +2,7 @@
{
sane.programs.handbrake = {
sandbox.method = "landlock"; #< also supports bwrap, but landlock ensures we don't write to non-mounted tmpfs dir
sandbox.wrapperType = "wrappedDerivation";
sandbox.whitelistDbus = [ "user" ]; # notifications
sandbox.whitelistWayland = true;
sandbox.extraHomePaths = [
@@ -15,7 +16,7 @@
# disable expensive sambda dependency; i don't use it.
packageUnwrapped = pkgs.handbrake.override {
ffmpeg-full = pkgs.ffmpeg-full.override {
ffmpeg_5-full = pkgs.ffmpeg_5-full.override {
withSamba = false;
};
};

View File

@@ -1,11 +0,0 @@
{ ... }:
{
sane.programs.htop = {
sandbox.method = "landlock";
sandbox.extraPaths = [
"/proc"
"/sys/devices"
];
fs.".config/htop/htoprc".symlink.target = ./htoprc;
};
}

View File

@@ -1,63 +0,0 @@
# Beware! This file is rewritten by htop when settings are changed in the interface.
# The parser is also very primitive, and not human-friendly.
htop_version=3.3.0
config_reader_min_version=3
fields=0 48 6 18 39 130 2 46 47 49 1
hide_kernel_threads=1
hide_userland_threads=0
hide_running_in_container=0
shadow_other_users=0
show_thread_names=0
show_program_path=0
highlight_base_name=0
highlight_deleted_exe=1
shadow_distribution_path_prefix=0
highlight_megabytes=1
highlight_threads=1
highlight_changes=0
highlight_changes_delay_secs=5
find_comm_in_cmdline=1
strip_exe_from_cmdline=1
show_merged_command=0
header_margin=1
screen_tabs=1
detailed_cpu_time=0
cpu_count_from_one=0
show_cpu_usage=1
show_cpu_frequency=0
show_cpu_temperature=0
degree_fahrenheit=0
update_process_names=0
account_guest_in_cpu_meter=0
color_scheme=0
enable_mouse=1
delay=15
hide_function_bar=0
header_layout=two_67_33
column_meters_0=AllCPUs Memory Swap Zram
column_meter_modes_0=1 1 1 1
column_meters_1=Systemd Uptime Tasks LoadAverage NetworkIO DiskIO
column_meter_modes_1=2 2 2 2 2 2
tree_view=0
sort_key=46
tree_sort_key=0
sort_direction=-1
tree_sort_direction=1
tree_view_always_by_pid=0
all_branches_collapsed=0
screen:Main=PID USER TTY NICE M_RESIDENT M_PRIV STATE PERCENT_CPU PERCENT_MEM TIME Command
.sort_key=PERCENT_CPU
.tree_sort_key=PID
.tree_view_always_by_pid=0
.tree_view=0
.sort_direction=-1
.tree_sort_direction=1
.all_branches_collapsed=0
screen:I/O=PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command
.sort_key=IO_RATE
.tree_sort_key=PID
.tree_view_always_by_pid=0
.tree_view=0
.sort_direction=-1
.tree_sort_direction=1
.all_branches_collapsed=0

View File

@@ -2,6 +2,7 @@
{
sane.programs.kdenlive = {
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.extraHomePaths = [
"Music"
"Pictures/from" # e.g. Videos taken from my phone

View File

@@ -11,6 +11,7 @@
});
sandbox.method = "bwrap"; # TODO:sandbox untested
sandbox.wrapperType = "wrappedDerivation";
sandbox.net = "clearnet";
sandbox.whitelistDbus = [ "user" ]; # needs to connect to dconf via dbus
sandbox.whitelistDri = true; #< required

View File

@@ -46,6 +46,7 @@ in {
sane.programs.koreader = {
packageUnwrapped = pkgs.koreader-from-src;
sandbox.method = "bwrap"; # sandboxes fine under landlock too, except for FTP
sandbox.wrapperType = "wrappedDerivation";
sandbox.net = "clearnet";
sandbox.whitelistDri = true; # reduces startup time and subjective page flip time
sandbox.whitelistWayland = true;

View File

@@ -2,6 +2,7 @@
{
sane.programs.lemoa = {
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.net = "clearnet";
sandbox.whitelistDbus = [ "user" ]; # for clicking links
sandbox.whitelistDri = true;

View File

@@ -1,8 +0,0 @@
{ ... }:
{
sane.programs.less = {
sandbox.method = "bwrap";
sandbox.autodetectCliPaths = "existingFile";
env.PAGER = "less";
};
}

View File

@@ -12,6 +12,7 @@
}));
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.whitelistWayland = true;
sandbox.autodetectCliPaths = "parent";
sandbox.extraHomePaths = [

View File

@@ -53,8 +53,13 @@
# on environment.packages, but then logs are blackholed.
services.mako = {
description = "mako desktop notification daemon";
partOf = [ "graphical-session" ];
command = "${config.sane.programs.mako.package}/bin/mako";
wantedBy = [ "graphical-session.target" ];
serviceConfig.ExecStart = "${config.sane.programs.mako.package}/bin/mako";
serviceConfig.Type = "simple";
# mako will predictably fail if launched before the wayland server is fully initialized
serviceConfig.Restart = "on-failure";
serviceConfig.RestartSec = "10s";
};
};
}

View File

@@ -10,13 +10,19 @@
# bwrap (loupe image viewer) doesn't like to run inside landlock
# "bwrap: failed to make / slave: Operation not permitted"
sandbox.method = "bwrap"; # supports landlock or bwrap
sandbox.wrapperType = "wrappedDerivation";
sandbox.whitelistDri = true;
sandbox.whitelistWayland = true;
sandbox.whitelistDbus = [ "user" ]; #< so that it can in theory open the image viewer using fdo portal... but it doesn't :|
sandbox.extraHomePaths = [
".config/dconf" #< else it segfaults during post-process
# ".config/megapixels"
# ".config/xcb"
# ".xcb"
".local/share/applications" #< needed for viewing photos, until i can sort out the portal stuff
# ".local/share/icons"
# ".icons" #< actually needed!
# ".themes"
# ".nix-profile"
".cache/mesa_shader_cache" # loads way faster
"tmp"
"Pictures" #< TODO: make this Pictures/Photos and save photos there

View File

@@ -5,6 +5,7 @@
{
sane.programs.mepo = {
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.net = "all"; # for tiles *and* for localhost comm to gpsd
sandbox.whitelistDri = true;
sandbox.whitelistWayland = true;

View File

@@ -1,8 +1,6 @@
# mimeo is an exec dispatcher like xdg-open, but which allows mapping different URL regexes to different handlers.
# mimeo is an exec dispatcher like xdg-open, but why allows mapping different URL regexes to different handlers.
# my setup sets mimeo as the default http/https handler,
# and from there it dispatches specialized rules, falling back to the original http/https handler if no URL specialization exists
#
# alternative to mimeo is jaro: <https://github.com/isamert/jaro>
{ config, lib, pkgs, ... }:
let
mimeo-open-desktop = pkgs.static-nix-shell.mkPython3Bin {

View File

@@ -0,0 +1,195 @@
# mpv docs:
# - <https://mpv.io/manual/master>
# - <https://github.com/mpv-player/mpv/wiki>
# curated mpv mods/scripts/users:
# - <https://github.com/stax76/awesome-mpv>
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.mpv;
in
{
sane.programs.mpv = {
configOption = with lib; mkOption {
default = {};
type = types.submodule {
options.vo = mkOption {
type = types.nullOr types.str;
default = null;
description = "--vo=FOO flag to pass to mpv";
};
};
};
packageUnwrapped = (pkgs.wrapMpv pkgs.mpv-unwrapped {
scripts = with pkgs.mpvScripts; [
mpris
uosc
# pkgs.mpv-uosc-latest
];
extraMakeWrapperArgs = lib.optionals (cfg.config.vo != null) [
# 2023/08/29: fixes an error where mpv on moby launches with the message
# "DRM_IOCTL_MODE_CREATE_DUMB failed: Cannot allocate memory"
# audio still works, and controls, screenshotting, etc -- just not the actual rendering
#
# this is likely a regression for mpv 0.36.0.
# the actual error message *appears* to come from the mesa library, but it's tough to trace.
#
# TODO(2023/12/03): remove once mesa 23.3.1 lands: <https://github.com/NixOS/nixpkgs/pull/265740>
#
# backend compatibility (2023/10/22):
# run with `--vo=help` to see a list of all output options.
# non-exhaustive (W=works, F=fails, A=audio-only, U=audio+ui only (no video))
# ? null Null video output
# A (default)
# A dmabuf-wayland Wayland dmabuf video output
# A libmpv render API for libmpv (mpv plays the audio, but doesn't even render a window)
# A vdpau VDPAU with X11
# F drm Direct Rendering Manager (software scaling)
# F gpu-next Video output based on libplacebo
# F vaapi VA API with X11
# F x11 X11 (software scaling)
# F xv X11/Xv
# U gpu Shader-based GPU Renderer
# W caca libcaca (terminal rendering)
# W sdl SDL 2.0 Renderer
# W wlshm Wayland SHM video output (software scaling)
"--add-flags" "--vo=${cfg.config.vo}"
];
}).overrideAttrs (base: {
buildCommand = base.buildCommand + ''
# runHook postFixup to allow sandbox wrappers to wrap the binaries
runHook postFixup
'';
});
sandbox.method = "bwrap";
sandbox.autodetectCliPaths = true;
sandbox.net = "all";
sandbox.whitelistAudio = true;
sandbox.whitelistDbus = [ "user" ]; #< mpris
sandbox.whitelistDri = true; #< mpv has excellent fallbacks to non-DRI, but DRI offers a good 30%-50% reduced CPU
sandbox.whitelistWayland = true;
sandbox.extraHomePaths = [
".config/mpv" #< else mpris plugin crashes on launch
# it's common for album (or audiobook, podcast) images/lyrics/metadata to live adjacent to the primary file.
# CLI detection is too poor to pick those up, so expose the common media dirs to the sandbox to make that *mostly* work.
"Books/local"
"Books/servo"
"Music"
"Videos/local"
"Videos/servo"
];
persist.byStore.plaintext = [
# for `watch_later`
".local/state/mpv"
];
fs.".config/mpv/input.conf".symlink.text = let
execInTerm = "${pkgs.xdg-terminal-exec}/bin/xdg-terminal-exec";
in ''
# docs:
# - <https://mpv.io/manual/master/#list-of-input-commands>
# - script-binding: <https://mpv.io/manual/master/#command-interface-script-binding>
# - properties: <https://mpv.io/manual/master/#property-list>
# let volume/power keys be interpreted by the system.
# this is important for sxmo.
# mpv defaults is POWER = close, VOLUME_{UP,DOWN} = adjust application-level volume
POWER ignore
VOLUME_UP ignore
VOLUME_DOWN ignore
# uosc menu
# text after the shebang is parsed by uosc to construct the menu and names
menu script-binding uosc/menu
s script-binding uosc/subtitles #! Subtitles
a script-binding uosc/audio #! Audio tracks
q script-binding uosc/stream-quality #! Stream quality
p script-binding uosc/items #! Playlist
c script-binding uosc/chapters #! Chapters
> script-binding uosc/next #! Navigation > Next
< script-binding uosc/prev #! Navigation > Prev
o script-binding uosc/open-file #! Navigation > Open file
# set video-aspect-override "-1" #! Utils > Aspect ratio > Default
# set video-aspect-override "16:9" #! Utils > Aspect ratio > 16:9
# set video-aspect-override "4:3" #! Utils > Aspect ratio > 4:3
# set video-aspect-override "2.35:1" #! Utils > Aspect ratio > 2.35:1
# script-binding uosc/audio-device #! Utils > Audio devices
# script-binding uosc/editions #! Utils > Editions
ctrl+s async screenshot #! Utils > Screenshot
alt+i script-binding uosc/keybinds #! Utils > Key bindings
O script-binding uosc/show-in-directory #! Utils > Show in directory
# script-binding uosc/open-config-directory #! Utils > Open config directory
# set pause yes; run ${execInTerm} go2tv -v "''${stream-open-filename}" #! Cast
# set pause yes; run ${execInTerm} go2tv -u "''${stream-open-filename}" #! Cast (...) > Stream
# set pause yes; run go2tv #! Cast (...) > GUI
# TODO: unify "Cast" and "Cast (stream)" options above.
'';
fs.".config/mpv/mpv.conf".symlink.text = ''
save-position-on-quit=yes
keep-open=yes
# force GUI, even for tracks w/o album art
# see: <https://www.reddit.com/r/mpv/comments/rvrrpt/oscosdgui_and_arch_linux/>
player-operation-mode=pseudo-gui
# use uosc instead (for On Screen Controls)
osc=no
# uosc provides its own seeking/volume indicators, so you also don't need this
osd-bar=no
# uosc will draw its own window controls if you disable window border
border=no
'';
fs.".config/mpv/script-opts/osc.conf".symlink.text = ''
# make the on-screen controls *always* visible
# unfortunately, this applies to full-screen as well
# - docs: <https://mpv.io/manual/master/#on-screen-controller-visibility>
# if uosc is installed, this file is unused
visibility=always
'';
fs.".config/mpv/script-opts/uosc.conf".symlink.text = let
play_pause_btn = "cycle:play_arrow:pause:no=pause/yes=play_arrow";
rev_btn = "command:replay_10:seek -10";
fwd_btn = "command:forward_30:seek 30";
in ''
# docs:
# - <https://github.com/tomasklaen/uosc>
# - <https://github.com/tomasklaen/uosc/blob/main/src/uosc.conf>
# - <https://superuser.com/questions/1775550/add-new-buttons-to-mpv-uosc-ui>
timeline_style=bar
timeline_persistency=paused,audio
controls_persistency=paused,audio
volume_persistency=audio
volume_opacity=0.75
# speed_persistency=paused,audio
# vvv want a close button?
top_bar=always
top_bar_persistency=paused
controls=menu,<video>subtitles,<has_many_audio>audio,<has_many_video>video,<has_many_edition>editions,<stream>stream-quality,space,${rev_btn},${play_pause_btn},${fwd_btn},space,speed:1.0,gap,<video>fullscreen
text_border=6.0
font_bold=yes
color=foreground=ff8080,background_text=ff8080
ui_scale=1.0
'';
# mime.priority = 200; # default = 100; 200 means to yield to other apps
mime.priority = 50; # default = 100; 50 in order to take precedence over vlc.
mime.associations."audio/flac" = "mpv.desktop";
mime.associations."audio/mpeg" = "mpv.desktop";
mime.associations."audio/x-opus+ogg" = "mpv.desktop";
mime.associations."audio/x-vorbis+ogg" = "mpv.desktop";
mime.associations."video/mp4" = "mpv.desktop";
mime.associations."video/quicktime" = "mpv.desktop";
mime.associations."video/webm" = "mpv.desktop";
mime.associations."video/x-flv" = "mpv.desktop";
mime.associations."video/x-matroska" = "mpv.desktop";
mime.urlAssociations."^https?://(www.)?youtube.com/watch\?.*v=" = "mpv.desktop";
mime.urlAssociations."^https?://(www.)?youtube.com/v/" = "mpv.desktop";
mime.urlAssociations."^https?://(www.)?youtu.be/.+" = "mpv.desktop";
};
}

View File

@@ -1,3 +0,0 @@
# font size used by mpv's console (`); default 16
# font_size=28
scale=2

View File

@@ -1,219 +0,0 @@
# curated mpv mods/scripts/users:
# - <https://github.com/stax76/awesome-mpv>
# mpv docs:
# - <https://mpv.io/manual/master>
# - <https://github.com/mpv-player/mpv/wiki>
# extensions i use:
# - <https://github.com/jonniek/mpv-playlistmanager>
# other extensions that could be useful:
# - list: <https://github.com/stax76/awesome-mpv>
# - list: <https://nudin.github.io/mpv-script-directory/>
# - browse DLNA shares: <https://github.com/chachmu/mpvDLNA>
# - act as a DLNA renderer (sink): <https://github.com/xfangfang/Macast>
# - update watch_later periodically -- not just on exit: <https://gist.github.com/CyberShadow/2f71a97fb85ed42146f6d9f522bc34ef>
# - <https://github.com/AN3223/dotfiles/blob/master/.config/mpv/scripts/auto-save-state.lua>
# - touch shortcuts (double-tap L/R portions of window to seek, etc): <https://github.com/christoph-heinrich/mpv-touch-gestures>
# - <https://github.com/omeryagmurlu/mpv-gestures>
# - jellyfin client: <https://github.com/EmperorPenguin18/mpv-jellyfin>
# - DLNA client (player only: no casting): <https://github.com/chachmu/mpvDLNA>
# - search videos on Youtube: <https://github.com/rozari0/mpv-youtube-search>
# - <https://github.com/CogentRedTester/mpv-scripts/blob/master/youtube-search.lua>
# - sponsorblock: <https://codeberg.org/jouni/mpv_sponsorblock_minimal>
# - screenshot-to-clipboard: <https://github.com/zc62/mpv-scripts/blob/master/screenshot-to-clipboard.js>
# - mpv-as-image-viewer: <https://github.com/guidocella/mpv-image-config>
# debugging:
# - enter console by pressing backtick.
# > `set volume 50` -> sets application volume to 50%
# > `set ao-volume 50` -> sets system-wide volume to 50%
# > `show-text "vol: ${volume}"` -> get the volume
# - show script output by running mpv with `--msg-level=all=trace`
# - and then just `print(...)` from lua & it'll show in terminal
# - requires that mpv.conf NOT include player-operation-mode=pseudo-gui
# - invoke mpv with `--no-config` to have it not read ~/.config/mpv/*
# - press `i` to show decoder info
#
# usage tips:
# - `<` or `>` to navigate prev/next-file-in-folder (uosc)
# - shift+enter to view the playlist, then arrow-keys to navigate (mpv-playlistmanager)
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.mpv;
uosc = pkgs.mpvScripts.uosc.overrideAttrs (upstream: {
version = "5.2.0-unstable-2024-03-13";
src = lib.warnIf (lib.versionOlder "5.2.0" upstream.version) "uosc outdated; remove patch?" pkgs.fetchFromGitHub {
owner = "tomasklaen";
repo = "uosc";
rev = "6fa34c31d0a5290dee83282205768d15111df7d8";
hash = "sha256-qxyNZHmH33bKRp4heFSC+RtvSApIfbVFt4otfS351nE=";
};
# src = pkgs.fetchFromGitea {
# domain = "git.uninsane.org";
# owner = "colin";
# repo = "uosc";
# rev = "dev-sane-5.2.0";
# hash = "sha256-lpqk4nnCxDZr/Y7/seM4VyR30fVrDAT4VP7C8n88lvA=";
# };
postPatch = (upstream.postPatch or "") + ''
### patch so touch controls work well with sway 1.9+
### in particular, "mouse.hover" is *always* false for touch events (i guess this is a bug in mpv?)
### and a touch release event is always followed by a mouse move to the cursor (that's a sway thing) which doesn't make sense.
# 1. always listen for mbtn_left events, even before a hover event would activate a zone:
substituteInPlace src/uosc/lib/cursor.lua \
--replace-fail \
"if binding and cursor:collides_with(zone.hitbox)" \
"if binding"
# 2. uosc already simulates mouse movements on touch down, but because of the hover handling, they get misunderstood as mouse leaves.
# so, bypass the cursor:leave() check.
substituteInPlace src/uosc/lib/cursor.lua \
--replace-fail \
"handle_mouse_pos(nil, mp.get_property_native('mouse-pos'))" \
"local mpos = mp.get_property_native('mouse-pos')
cursor:move(mpos.x, mpos.y)
cursor.hover_raw = mpos.hover"
# 3. explicitly fire a cursor:leave on touch release, so that all zones are deactivated (and control visibility goes back to default state)
substituteInPlace src/uosc/lib/cursor.lua \
--replace-fail \
"cursor:create_handler('primary_up')" \
"function(...)
cursor:trigger('primary_up', ...)
if not cursor.hover_raw then
cursor:leave()
end
end"
# 4. sometimes we get a touch movement shortly AFTER touch is released:
# detect that and ignore it
substituteInPlace src/uosc/lib/cursor.lua \
--replace-fail \
"cursor:move(mouse.x, mouse.y)" \
"local last_down = cursor.last_event['primary_down'] or { time = 0 }
local last_up = cursor.last_event['primary_up'] or { time = 0 }
if cursor.hover_raw or last_down.time >= last_up.time then cursor:move(mouse.x, mouse.y) end"
### patch so that uosc volume control is routed to sane-sysvol.
### this is particularly nice for moby, because it avoids the awkwardness that system volume
### is hard to adjust while screen is on.
### previously i used ao-volume instead of sane-sysvol: but that forced `ao=alsa`
### and came with heavy perf penalties (especially when adjusting the volume)
substituteInPlace src/uosc/main.lua \
--replace-fail \
"mp.observe_property('volume'" \
"mp.observe_property('user-data/sane-sysvol/volume'"
substituteInPlace src/uosc/elements/Volume.lua \
--replace-fail \
"mp.commandv('set', 'volume'" \
"mp.set_property_native('user-data/sane-sysvol/volume'" \
--replace-fail \
"mp.set_property_native('volume'" \
"mp.set_property_native('user-data/sane-sysvol/volume'"
'';
});
mpv-unwrapped = pkgs.mpv-unwrapped.overrideAttrs (upstream: {
version = "0.37.0-unstable-2024-03-31";
src = lib.warnIf (lib.versionOlder "0.37.0" upstream.version) "mpv outdated; remove patch?" pkgs.fetchFromGitHub {
owner = "mpv-player";
repo = "mpv";
rev = "4ce4bf1795e6dfd6f1ddf07fb348ce5d191ab1dc";
hash = "sha256-nOGuHq7SWDAygROV7qHtezDv1AsMpseImI8TVd3F+Oc=";
};
patches = [];
});
in
{
sane.programs.mpv = {
packageUnwrapped = pkgs.wrapMpv (mpv-unwrapped.override { lua = pkgs.luajit; }) {
scripts = [
pkgs.mpvScripts.mpris
pkgs.mpvScripts.mpv-playlistmanager
uosc
# pkgs.mpv-uosc-latest
];
# extraMakeWrapperArgs = lib.optionals (cfg.config.vo != null) [
# # 2023/08/29: fixes an error where mpv on moby launches with the message
# # "DRM_IOCTL_MODE_CREATE_DUMB failed: Cannot allocate memory"
# # audio still works, and controls, screenshotting, etc -- just not the actual rendering
# #
# # this is likely a regression for mpv 0.36.0.
# # the actual error message *appears* to come from the mesa library, but it's tough to trace.
# #
# # 2024/03/02: no longer necessary, with mesa 23.3.1: <https://github.com/NixOS/nixpkgs/pull/265740>
# #
# # backend compatibility (2023/10/22):
# # run with `--vo=help` to see a list of all output options.
# # non-exhaustive (W=works, F=fails, A=audio-only, U=audio+ui only (no video))
# # ? null Null video output
# # A (default)
# # A dmabuf-wayland Wayland dmabuf video output
# # A libmpv render API for libmpv (mpv plays the audio, but doesn't even render a window)
# # A vdpau VDPAU with X11
# # F drm Direct Rendering Manager (software scaling)
# # F gpu-next Video output based on libplacebo
# # F vaapi VA API with X11
# # F x11 X11 (software scaling)
# # F xv X11/Xv
# # U gpu Shader-based GPU Renderer
# # W caca libcaca (terminal rendering)
# # W sdl SDL 2.0 Renderer
# # W wlshm Wayland SHM video output (software scaling)
# "--add-flags" "--vo=${cfg.config.vo}"
# ];
};
suggestedPrograms = [
"blast-to-default"
"go2tv"
"xdg-terminal-exec"
];
sandbox.method = "bwrap";
sandbox.autodetectCliPaths = true;
sandbox.net = "all";
sandbox.whitelistAudio = true;
sandbox.whitelistDbus = [ "user" ]; #< mpris
sandbox.whitelistDri = true; #< mpv has excellent fallbacks to non-DRI, but DRI offers a good 30%-50% reduced CPU
sandbox.whitelistWayland = true;
sandbox.extraHomePaths = [
".config/mpv" #< else mpris plugin crashes on launch
".local/share/applications" #< for xdg-terminal-exec (go2tv)
# it's common for album (or audiobook, podcast) images/lyrics/metadata to live adjacent to the primary file.
# CLI detection is too poor to pick those up, so expose the common media dirs to the sandbox to make that *mostly* work.
"Books/local"
"Books/servo"
"Music"
"Videos/gPodder"
"Videos/local"
"Videos/servo"
];
persist.byStore.plaintext = [
# for `watch_later`
".local/state/mpv"
];
fs.".config/mpv/scripts/sane-cast/main.lua".symlink.target = ./sane-cast-main.lua;
fs.".config/mpv/scripts/sane-sysvol/main.lua".symlink.target = ./sane-sysvol/main.lua;
fs.".config/mpv/scripts/sane-sysvol/non_blocking_popen.lua".symlink.target = ./sane-sysvol/non_blocking_popen.lua;
fs.".config/mpv/input.conf".symlink.target = ./input.conf;
fs.".config/mpv/mpv.conf".symlink.target = ./mpv.conf;
fs.".config/mpv/script-opts/osc.conf".symlink.target = ./osc.conf;
fs.".config/mpv/script-opts/console.conf".symlink.target = ./console.conf;
fs.".config/mpv/script-opts/uosc.conf".symlink.target = ./uosc.conf;
fs.".config/mpv/script-opts/playlistmanager.conf".symlink.target = ./playlistmanager.conf;
# mime.priority = 200; # default = 100; 200 means to yield to other apps
mime.priority = 50; # default = 100; 50 in order to take precedence over vlc.
mime.associations."audio/flac" = "mpv.desktop";
mime.associations."audio/mpeg" = "mpv.desktop";
mime.associations."audio/x-opus+ogg" = "mpv.desktop";
mime.associations."audio/x-vorbis+ogg" = "mpv.desktop";
mime.associations."video/mp4" = "mpv.desktop";
mime.associations."video/quicktime" = "mpv.desktop";
mime.associations."video/webm" = "mpv.desktop";
mime.associations."video/x-flv" = "mpv.desktop";
mime.associations."video/x-matroska" = "mpv.desktop";
mime.urlAssociations."^https?://(www.)?youtube.com/watch\?.*v=" = "mpv.desktop";
mime.urlAssociations."^https?://(www.)?youtube.com/v/" = "mpv.desktop";
mime.urlAssociations."^https?://(www.)?youtu.be/.+" = "mpv.desktop";
};
}

View File

@@ -1,37 +0,0 @@
# docs:
# - <https://mpv.io/manual/master/#list-of-input-commands>
# - script-binding: <https://mpv.io/manual/master/#command-interface-script-binding>
# - properties: <https://mpv.io/manual/master/#property-list>
# let volume/power keys be interpreted by the system.
# this is important for sxmo.
# mpv defaults is POWER = close, VOLUME_{UP,DOWN} = adjust application-level volume
POWER ignore
VOLUME_UP ignore
VOLUME_DOWN ignore
# uosc menu
# text after the shebang is parsed by uosc to construct the menu and names
menu script-binding uosc/menu
s script-binding uosc/subtitles #! Subtitles
a script-binding uosc/audio #! Audio tracks
q script-binding uosc/stream-quality #! Stream quality
p script-binding uosc/items #! Playlist
c script-binding uosc/chapters #! Chapters
> script-binding uosc/next #! Navigation > Next
< script-binding uosc/prev #! Navigation > Prev
o script-binding uosc/open-file #! Navigation > Open file
# set video-aspect-override "-1" #! Utils > Aspect ratio > Default
# set video-aspect-override "16:9" #! Utils > Aspect ratio > 16:9
# set video-aspect-override "4:3" #! Utils > Aspect ratio > 4:3
# set video-aspect-override "2.35:1" #! Utils > Aspect ratio > 2.35:1
# script-binding uosc/audio-device #! Utils > Audio devices
# script-binding uosc/editions #! Utils > Editions
ctrl+s async screenshot #! Utils > Screenshot
alt+i script-binding uosc/keybinds #! Utils > Key bindings
O script-binding uosc/show-in-directory #! Utils > Show in directory
# script-binding uosc/open-config-directory #! Utils > Open config directory
ctrl+r script-binding sane-cast/blast #! Audiocast
ctrl+t script-binding sane-cast/go2tv-video #! Cast
# script-binding sane-cast/go2tv-stream #! Cast (...) > Stream
# script-binding sane-cast/go2tv-gui #! Cast (...) > GUI

View File

@@ -1,26 +0,0 @@
# write ~/.local/state/mpv/watch_later on exit, to allow resume
save-position-on-quit=yes
# identify resumed files by filename only, since i use so many symlinks and doubt mpv does well with that.
ignore-path-in-watch-later-config
# keep-open: don't exit on completion of last file in playlist
keep-open=yes
# seeking once at the end of the file causes auto-resume
keep-open-pause=no
# force GUI, even for tracks w/o album art
# see: <https://www.reddit.com/r/mpv/comments/rvrrpt/oscosdgui_and_arch_linux/>
player-operation-mode=pseudo-gui
# use uosc instead (for On Screen Controls)
osc=no
# uosc provides its own seeking/volume indicators, so you also don't need this
osd-bar=no
# uosc will draw its own window controls if you disable window border
border=no
# ao=alsa so that uosc can work with ao-volume (see my uosc patch)
ao=alsa
# with `ao-volume`, the max actually is 100.
# to go higher you'll have to use the system's native controls.
volume-max=100

View File

@@ -1,5 +0,0 @@
# make the on-screen controls *always* visible
# unfortunately, this applies to full-screen as well
# - docs: <https://mpv.io/manual/master/#on-screen-controller-visibility>
# if uosc is installed, this file is unused
visibility=always

View File

@@ -1,4 +0,0 @@
# script docs: <https://github.com/jonniek/mpv-playlistmanager>
# auto-populate playlist with other files in the same directory, on launch.
loadfiles_on_start=yes

View File

@@ -1,34 +0,0 @@
function subprocess(in_terminal, args)
if in_terminal then
args = { "xdg-terminal-exec", table.unpack(args) }
end
mp.command_native({
name = "subprocess",
args = args,
detach = false,
capture_stdout = false,
capture_stderr = false,
-- capture_size=0,
passthrough_stdin = false,
playback_only = false,
})
end
function invoke_go2tv(in_terminal, args)
mp.commandv("set", "pause", "yes")
subprocess(in_terminal, { "go2tv", table.unpack(args) })
end
function invoke_go2tv_on_open_file(mode)
local path = mp.get_property("stream-open-filename");
return invoke_go2tv(true, { mode, path })
end
mp.add_key_binding(nil, "blast", function() subprocess(false, { "blast-to-default" }) end)
mp.add_key_binding(nil, 'go2tv-gui', function() invoke_go2tv(false, {}) end)
mp.add_key_binding(nil, 'go2tv-video', function() invoke_go2tv_on_open_file("-v") end)
mp.add_key_binding(nil, 'go2tv-stream', function() invoke_go2tv_on_open_file("-s") end)
-- uncomment for debugging:
-- if mpv fails to eval this script (e.g. syntax error), then it will fail to quit on launch
-- mp.command('quit')

View File

@@ -1,240 +0,0 @@
msg = require('mp.msg')
msg.trace('sane-sysvol: load: begin')
non_blocking_popen = require("non_blocking_popen")
RD_SIZE = 4096
function startswith(superstring, substring)
return superstring:sub(1, substring:len()) == substring
end
function strip_prefix(superstring, substring)
return superstring:sub(substring:len())
end
function ltrim(s)
-- remove all leading whitespace from `s`
local i = 1
while s:sub(i, i) == " " or s:sub(i, i) == "\t" do
i = i + 1
end
return s:sub(i)
end
function subprocess(args)
mp.command_native({
name = "subprocess",
args = args,
-- these arguments below probably don't matter: copied from sane-cast
detach = false,
capture_stdout = false,
capture_stderr = false,
passthrough_stdin = false,
playback_only = false,
})
end
function sysvol_new()
return {
-- sysvol is pipewire-native volume
-- it's the cube of the equivalent 0-100% value represented inside mpv
sysvol = nil,
change_sysvol = function(self, mpv_vol)
-- called when mpv wants to set the system-wide volume
if mpv_vol == nil then
return
end
local old_mpv_vol = nil
if self.sysvol ~= nil then
old_mpv_vol = 100 * self.sysvol^(1/3)
end
if old_mpv_vol ~= nil and math.floor(old_mpv_vol) == math.floor(mpv_vol) then
return
end
local volstr = tostring(mpv_vol) .. "%"
msg.debug("setting system-wide volume:", volstr)
self.sysvol = (0.01*mpv_vol)^3
subprocess({
"wpctl",
"set-volume",
"@DEFAULT_AUDIO_SINK@",
volstr
})
end,
on_sysvol_change = function(self, sysvol)
if sysvol == nil then
return
end
-- called when the pipewire system volume is changed (either by us, or an external application)
local new_mpv_vol = 100 * sysvol^(1/3)
local old_mpv_vol = nil
if self.sysvol ~= nil then
old_mpv_vol = 100 * self.sysvol^(1/3)
end
if old_mpv_vol ~= nil and math.abs(new_mpv_vol - old_mpv_vol) < 1.0 then
msg.debug("NOT announcing volume change to mpv (because it was what triggered the change):", old_mpv_vol, new_mpv_vol)
return
end
self.sysvol = sysvol
msg.debug("announcing volume change to mpv:", old_mpv_vol, new_mpv_vol)
mp.set_property_native("user-data/sane-sysvol/volume", new_mpv_vol)
end
}
end
function pwmon_parser_new()
return {
-- volume: pipewire-native volume. usually 0.0 - 1.0, but can go higher (e.g. 3.25)
-- `wpctl get-volume` and this volume are related, in that the volume reported by
-- wpctl is the cube-root of this one.
volume = {}, -- object-id (number) -> volume (number)
mute = {}, -- object-id (number) -> mute (bool)
last_audio_device_id = nil, -- TODO: might not actually be necessary
-- parser state:
in_changed = false,
changed_id = nil,
in_device = false,
in_direction = false,
in_output = false,
in_vol = false,
in_mute = false,
feed_line = function(self, line)
line = ltrim(line)
if startswith(line, "changed:") then
self.in_changed = true
self.changed_id = nil
self.in_device = false
self.in_direction = false
self.in_output = false
self.in_vol = false
self.in_mute = false
self.in_properties = false
elseif startswith(line, "added:") or startswith(line, "removed:") then
self.in_changed = false
self.changed_id = nil
self.in_device = false
self.in_direction = false
self.in_output = false
self.in_vol = false
self.in_mute = false
self.in_properties = false
elseif startswith(line, "id: ") and self.in_changed then
if self.changed_id == nil then
self.changed_id = tonumber(strip_prefix(line, "id: "))
msg.debug("changed_id:", self.changed_id)
end
elseif startswith(line, "type: ") and self.in_changed then
self.in_device = startswith(line, "type: PipeWire:Interface:Device")
msg.trace("parsed type:", line, self.in_device)
elseif startswith(line, "Prop: ") and self.in_changed and self.in_device then
self.in_direction = startswith(line, "Prop: key Spa:Pod:Object:Param:Route:direction")
if self.in_direction then
self.in_output = false
end
-- which of the *Volumes params we read is unclear.
-- alternative to this is to just detect the change, and then cal wpctl get-volume @DEFAULT_AUDIO_SINK@
self.in_vol = startswith(line, "Prop: key Spa:Pod:Object:Param:Props:channelVolumes")
self.in_mute = startswith(line, "Prop: key Spa:Pod:Object:Param:Props:softMute")
msg.trace("parsed `Prop:`", line, self.in_vol)
elseif line:find("Spa:Enum:Direction:Output", 1, true) and self.in_direction then
self.in_output = true
elseif startswith(line, "Float ") and self.in_changed and self.in_device and self.in_output and self.in_vol then
value = tonumber(strip_prefix(line, "Float "))
self:feed_volume(value)
elseif startswith(line, "Bool ") and self.in_changed and self.in_device and self.in_output and self.in_mute then
value = tonumber(strip_prefix(line, "Bool ")) == "true"
self:feed_mute(value)
elseif startswith(line, "properties:") and self.in_changed and self.in_device then
self.in_properties = true
elseif line == 'media.class = "Audio/Device"' and self.in_changed and self.in_device and self.in_properties then
self.last_audio_device_id = self.changed_id
msg.debug("last_audio_device_id:", self.changed_id)
end
end,
feed_volume = function(self, vol)
msg.debug("volume:", self.changed_id, vol)
self.volume[self.changed_id] = vol
end,
feed_mute = function(self, mute)
msg.debug("mute:", self.changed_id, mute)
self.mute[self.changed_id] = mute
end,
get_effective_volume = function(self, id)
if id == nil then
id = self.last_audio_device_id
end
if self.mute[id] then
return 0
else
return self.volume[id]
end
end
}
end
function pwmon_new()
return {
-- non_blocking_popen handle for the pw-mon process
-- which can be periodically read and parsed to detect volume changes
handle = non_blocking_popen.non_blocking_popen("pw-mon", RD_SIZE),
stdout_unparsed = "",
pwmon_parser = pwmon_parser_new(),
service = function(self)
-- do a single non-blocking read, and parse the result
-- in the *rare* case in which more than RD_SIZE data is ready, we service that remaining data on the next call
local buf, res = self.handle:read(RD_SIZE)
if res == "closed" then
msg.debug("pw-mon unexpectedly closed!")
end
if buf ~= nil then
self.stdout_unparsed = self.stdout_unparsed .. buf
self:consume_stdout()
end
end,
consume_stdout = function(self)
local idx_newline, next_newline = 0, 0
while next_newline ~= nil do
next_newline = self.stdout_unparsed:find("\n", idx_newline + 1, true)
if next_newline ~= nil then
self:ingest_line(self.stdout_unparsed:sub(idx_newline + 1, next_newline - 1))
idx_newline = next_newline
end
end
self.stdout_unparsed = self.stdout_unparsed:sub(idx_newline + 1)
end,
ingest_line = function(self, line)
msg.trace("pw-mon:", line)
local old_vol = self.pwmon_parser:get_effective_volume()
self.pwmon_parser:feed_line(line)
local new_vol = self.pwmon_parser:get_effective_volume()
if new_vol ~= old_vol then
msg.debug("pipewire volume change:", old_vol, new_vol)
mp.set_property_native("user-data/sane-sysvol/pw-mon-volume", new_vol)
end
end
}
end
mp.set_property_native("user-data/sane-sysvol/volume", 0)
local sysvol = sysvol_new()
mp.observe_property("user-data/sane-sysvol/volume", "native", function(_, val)
sysvol:change_sysvol(val)
end)
mp.observe_property("user-data/sane-sysvol/pw-mon-volume", "native", function(_, val)
sysvol:on_sysvol_change(val)
end)
local pwmon = pwmon_new()
mp.register_event('tick', function() pwmon:service() end)
msg.trace("sane-sysvol: load: complete")

View File

@@ -1,80 +0,0 @@
-- source: <https://gist.github.com/max1220/c19ccd4d90ed32d41b879eba727cbcbd>
-- requires: luajit
--
-- Implements a basic binding for popen that allows non-blocking reads
-- returned "file" table only supports :read(with an optional size argument, no mode etc.) and :close
local function non_blocking_popen(cmd, read_buffer_size)
local ffi = require("ffi")
-- C functions that we need
ffi.cdef([[
void* popen(const char* cmd, const char* mode);
int pclose(void* stream);
int fileno(void* stream);
int fcntl(int fd, int cmd, int arg);
int *__errno_location ();
ssize_t read(int fd, void* buf, size_t count);
]])
-- you can compile a simple C programm to find these values(Or look in the headers)
local F_SETFL = 4
local O_NONBLOCK = 2048
local EAGAIN = 11
-- this "array" holds the errno variable
local _errno = ffi.C.__errno_location()
-- the buffer for reading from the process
local read_buffer_size = tonumber(read_buffer_size) or 2048
local read_buffer = ffi.new('uint8_t[?]',read_buffer_size)
-- get a FILE* for our command
local file = assert(ffi.C.popen(cmd, "r"))
-- turn the FILE* to a fd(int) for fcntl
local fd = ffi.C.fileno(file)
-- set non-blocking mode for read
assert(ffi.C.fcntl(fd, F_SETFL, O_NONBLOCK)==0, "fcntl failed")
-- close the process, prevent reading, allow garbage colletion
function file_close(self)
ffi.C.pclose(file)
self.read_buffer = nil
read_buffer = nil
self.read = function() return nil, "closed"end
end
-- read up to size bytes from the process. Returns data(string) and number of bytes read if successfull,
-- nil, "EAGAIN" if there is no data aviable, and
-- nil, "closed" if the process has ended
local read = ffi.C.read
function file_read(self, size)
local _size = math.min(read_buffer_size, size)
while true do
local nbytes = read(fd,read_buffer,_size)
if nbytes > 0 then
local data = ffi.string(read_buffer, nbytes)
return data, nbytes
elseif (nbytes == -1) and (_errno[0] == EAGAIN) then
return nil, "EAGAIN"
else
file_close(self)
return nil, "closed"
end
end
end
return {
_fd = fd,
_file = file,
_read_buffer = read_buffer,
_read_buffer_size = read_buffer_size,
read = file_read,
close = file_close
}
end
return {
non_blocking_popen = non_blocking_popen
}

View File

@@ -1,32 +0,0 @@
# docs:
# - <https://github.com/tomasklaen/uosc>
# - <https://github.com/tomasklaen/uosc/blob/main/src/uosc.conf>
# - <https://superuser.com/questions/1775550/add-new-buttons-to-mpv-uosc-ui>
timeline_style=bar
timeline_line_width=4
timeline_size=36
timeline_persistency=paused,audio
controls_persistency=paused,audio
volume_persistency=audio
# speed_persistency=paused,audio
# vvv want a close button?
top_bar=always
top_bar_persistency=paused,audio
controls=menu,<video>subtitles,<has_many_audio>audio,<has_many_video>video,<has_many_edition>editions,<stream>stream-quality,space,command:replay_10:seek -10,cycle:play_arrow:pause:no=pause/yes=play_arrow,command:forward_30:seek 30,space,speed:1.0,gap,<video>fullscreen
# text_border: shadow to place around icons/text which is rendered over the video
text_border=5.0
# border_radius: rounding of volume slider, etc.
border_radius=5.0
font_scale=1.5
font_bold=yes
# refine=text_width: slightly better text rendering
refine=text_width
color=foreground=ff8080,background_text=ff8080
# N.B.: if `opacity=` is set non-empty, then ALL items must be specified (else they get 0 opacity).
# opacity values *must* be a multiple of 0.1
opacity=timeline=0.8,position=1,chapters=0.8,slider=0.8,slider_gauge=0.8,controls=0,speed=0.8,menu=1,submenu=0.4,border=1,title=0.8,tooltip=1,thumbnail=1,curtain=0.8,idle_indicator=0.8,audio_indicator=0.5,buffering_indicator=0.3,playlist_position=0.8
stream_quality_options=1440,1080,720,480,360,240,144

View File

@@ -1,16 +1,16 @@
{ pkgs, ... }:
{
sane.programs."gnome.nautilus" = {
# some of its dbus services don't even refer to real paths
packageUnwrapped = pkgs.rmDbusServicesInPlace (pkgs.gnome.nautilus.overrideAttrs (orig: {
packageUnwrapped = pkgs.gnome.nautilus.overrideAttrs (orig: {
# enable the "Audio and Video Properties" pane. see: <https://nixos.wiki/wiki/Nautilus>
buildInputs = orig.buildInputs ++ (with pkgs.gst_all_1; [
gst-plugins-good
gst-plugins-bad
]);
}));
});
sandbox.method = "bwrap";
sandbox.wrapperType = "inplace";
sandbox.whitelistDbus = [ "user" ]; # for portals launching apps
sandbox.whitelistWayland = true;
sandbox.extraHomePaths = [

View File

@@ -88,6 +88,7 @@ in
{
sane.programs.neovim = {
sandbox.method = "bwrap";
sandbox.wrapperType = "wrappedDerivation";
sandbox.autodetectCliPaths = "existingOrParent";
sandbox.whitelistWayland = true; # for system clipboard integration
# sandbox.whitelistPwd = true;

Some files were not shown because too many files have changed in this diff Show More