Compare commits
5 Commits
master
...
wip-mpv-sy
Author | SHA1 | Date |
---|---|---|
Colin | 72c7287445 | |
Colin | b715fd346f | |
Colin | 1b9b0ac0f6 | |
Colin | 79c4e2c405 | |
Colin | 17a3f90825 |
|
@ -22,7 +22,7 @@ you might specifically be interested in these files (elaborated further in #key-
|
|||
- my way of deploying dotfiles/configuring programs per-user:
|
||||
- [modules/fs/](./modules/fs/default.nix)
|
||||
- [modules/programs/](./modules/programs/default.nix)
|
||||
- [modules/users/](./modules/users/default.nix)
|
||||
- [modules/users.nix](./modules/users.nix)
|
||||
|
||||
[nixpkgs]: https://github.com/NixOS/nixpkgs
|
||||
[sops]: https://github.com/Mic92/sops-nix
|
||||
|
@ -109,10 +109,9 @@ i.e. you might find value in using these in your own config:
|
|||
- `sane.programs.firefox.sandbox.whitelistWayland = true; # allow it to render a wayland window`
|
||||
- `sane.programs.firefox.sandbox.extraHomePaths = [ "Downloads" ]; # allow it read/write access to ~/Downloads`
|
||||
- integrated with `fs` and `persist` modules so that programs' config files and persisted data stores are linked into the sandbox w/o any extra involvement.
|
||||
- `modules/users/`
|
||||
- `modules/users.nix`
|
||||
- convenience layer atop the above modules so that you can just write
|
||||
`fs.".config/git"` instead of `fs."/home/colin/.config/git"`
|
||||
- per-user services managed by [s6-rc](https://www.skarnet.org/software/s6-rc/)
|
||||
|
||||
some things in here could easily find broader use. if you would find benefit in
|
||||
them being factored out of my config, message me and we could work to make that happen.
|
||||
|
|
22
TODO.md
22
TODO.md
|
@ -1,18 +1,11 @@
|
|||
## BUGS
|
||||
- moby: megapixels doesn't load in sandbox
|
||||
- when moby wlan is explicitly set down (via ip link set wlan0 down), /var/lib/trust-dns/dhcp-configs doesn't get reset
|
||||
- trust-dns: can't recursively resolve api.mangadex.org
|
||||
- and *sometimes* apple.com fails
|
||||
- sandbox: `ip netns exec ovpns bash`: doesn't work
|
||||
- sandbox: link cache means that if i update ~/.config/... files inline, sandboxed programs still see the old version
|
||||
- mpv: no way to exit fullscreen video on moby
|
||||
- uosc hides controls on FS, and touch doesn't support unhiding
|
||||
- Signal restart loop drains battery
|
||||
- decrease s6 restart time?
|
||||
- `ssh` access doesn't grant same linux capabilities as login
|
||||
- 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
|
||||
- sysvol (volume overlay): when casting with `blast`, sysvol doesn't react to volume changes
|
||||
- `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
|
||||
|
@ -32,15 +25,13 @@
|
|||
|
||||
### upstreaming
|
||||
- add updateScripts to all my packages in nixpkgs
|
||||
- REVIEW/integrate jellyfin dataDir config: <https://github.com/NixOS/nixpkgs/pull/233617>
|
||||
|
||||
#### upstreaming to non-nixpkgs repos
|
||||
- gtk: build schemas even on cross compilation: <https://github.com/NixOS/nixpkgs/pull/247844>
|
||||
|
||||
|
||||
## IMPROVEMENTS:
|
||||
- systemd/journalctl: use a less shit pager
|
||||
- there's an env var for it: SYSTEMD_PAGER? and a flag for journalctl
|
||||
|
||||
### security/resilience
|
||||
- validate duplicity backups!
|
||||
- encrypt more ~ dirs (~/archives, ~/records, ..?)
|
||||
|
@ -58,7 +49,7 @@
|
|||
- <https://github.com/flatpak/xdg-dbus-proxy>
|
||||
- remove `.ssh` access from Firefox!
|
||||
- limit access to `~/knowledge/secrets` through an agent that requires GUI approval, so a firefox exploit can't steal all my logins
|
||||
- port sanebox to a compiled language (hare?)
|
||||
- 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
|
||||
|
@ -69,7 +60,6 @@
|
|||
- integrate `nix check` into Gitea actions?
|
||||
|
||||
### user experience
|
||||
- rofi: sort items case-insensitively
|
||||
- xdg-desktop-portal shouldn't kill children on exit
|
||||
- *maybe* a job for `setsid -f`?
|
||||
- replace starship prompt with something more efficient
|
||||
|
@ -94,8 +84,6 @@
|
|||
- numberlink (generic name for Flow Free). not packaged in Nix
|
||||
- Neverball (https://neverball.org/screenshots.php). nix: as `neverball`
|
||||
- blurble (https://linuxphoneapps.org/games/app.drey.blurble/). nix: not as of 2024-02-05
|
||||
- Trivia Quiz (https://linuxphoneapps.org/games/io.github.nokse22.trivia-quiz/)
|
||||
- sane-sync-music: remove empty dirs
|
||||
|
||||
#### moby
|
||||
- fix cpuidle (gets better power consumption): <https://xnux.eu/log/077.html>
|
||||
|
|
80
flake.lock
80
flake.lock
|
@ -24,11 +24,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712014858,
|
||||
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
||||
"lastModified": 1701473968,
|
||||
"narHash": "sha256-YcVE5emp1qQ8ieHUnxt1wCZCC3ZfAS+SRRWZ2TMda7E=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
||||
"rev": "34fed993f1674c8d06d58b37ce1e0fe5eebcb9f5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -61,11 +61,11 @@
|
|||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1715515815,
|
||||
"narHash": "sha256-yaLScMHNFCH6SbB0HSA/8DWDgK0PyOhCXoFTdHlWkhk=",
|
||||
"lastModified": 1711886936,
|
||||
"narHash": "sha256-D2WENp9GuaCostvNcQ7vElekk0V5cuMdnFZ7NfRhVrQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "lib-aggregate",
|
||||
"rev": "09883ca828e8cfaacdb09e29190a7b84ad1d9925",
|
||||
"rev": "9c06929b83e57c18d125f1105ba6a423f24083d2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -99,11 +99,11 @@
|
|||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1715804156,
|
||||
"narHash": "sha256-GtIHP86Cz1kD9xZO/cKbNQACHKdoT9WFbLJAq6W2EDY=",
|
||||
"lastModified": 1705242886,
|
||||
"narHash": "sha256-TLj334vRwFtSym3m+NnKcNCnKKPNoTC/TDZL40vmOso=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-eval-jobs",
|
||||
"rev": "bb95091f6c6f38f6cfc215a1797a2dd466312c8b",
|
||||
"rev": "6b03a93296faf174b97546fd573c8b379f523a8d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -121,11 +121,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1703863825,
|
||||
"narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=",
|
||||
"lastModified": 1701208414,
|
||||
"narHash": "sha256-xrQ0FyhwTZK6BwKhahIkUVZhMNk21IEI1nUcWSONtpo=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "5163432afc817cf8bd1f031418d1869e4c9d5547",
|
||||
"rev": "93e39cc1a087d65bcf7a132e75a650c44dd2b734",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -136,11 +136,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1715037484,
|
||||
"narHash": "sha256-OUt8xQFmBU96Hmm4T9tOWTu4oCswCzoVl+pxSq/kiFc=",
|
||||
"lastModified": 1703134684,
|
||||
"narHash": "sha256-SQmng1EnBFLzS7WSRyPM9HgmZP2kLJcPAz+Ug/nug6o=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ad7efee13e0d216bf29992311536fce1d3eefbef",
|
||||
"rev": "d6863cbcbbb80e71cecfc03356db1cda38919523",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -152,11 +152,11 @@
|
|||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1715474941,
|
||||
"narHash": "sha256-CNCqCGOHdxuiVnVkhTpp2WcqSSmSfeQjubhDOcgwGjU=",
|
||||
"lastModified": 1711846064,
|
||||
"narHash": "sha256-cqfX0QJNEnge3a77VnytM0Q6QZZ0DziFXt6tSCV8ZSc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "58e03b95f65dfdca21979a081aa62db0eed6b1d8",
|
||||
"rev": "90b1a963ff84dc532db92f678296ff2499a60a87",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -167,11 +167,11 @@
|
|||
},
|
||||
"nixpkgs-next-unpatched": {
|
||||
"locked": {
|
||||
"lastModified": 1715839255,
|
||||
"narHash": "sha256-IKUEASEZKDqOC/q6RP54O3Dz3C2+BBi+VtnIbhBpBbw=",
|
||||
"lastModified": 1712383280,
|
||||
"narHash": "sha256-YL8miM11o/jMqOwt5DsdyhPgh/JgCl1kOIzvX7ukniY=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1887e39d7e68bb191eb804c0f976ad25b3980595",
|
||||
"rev": "7c74352f2f7eca1925729f5c9c80cb89df8e74a2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -183,11 +183,11 @@
|
|||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1715458492,
|
||||
"narHash": "sha256-q0OFeZqKQaik2U8wwGDsELEkgoZMK7gvfF6tTXkpsqE=",
|
||||
"lastModified": 1711819797,
|
||||
"narHash": "sha256-tNeB6emxj74Y6ctwmsjtMlzUMn458sBmwnD35U5KIM4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8e47858badee5594292921c2668c11004c3b0142",
|
||||
"rev": "2b4e3ca0091049c6fbb4908c66b05b77eaef9f0c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -199,11 +199,11 @@
|
|||
},
|
||||
"nixpkgs-unpatched": {
|
||||
"locked": {
|
||||
"lastModified": 1715851096,
|
||||
"narHash": "sha256-ed72tDlrU4/PBWPYoxPk+HFazU3Yny0stTjlGZ7YeMA=",
|
||||
"lastModified": 1712398506,
|
||||
"narHash": "sha256-oopwPeBKBXQEw2BlyK2jEs2farZ5uMjAZU7H4FpGuGE=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "977a49df312d89b7dfbb3579bf13b7dfe23e7878",
|
||||
"rev": "c58702222e0a29fd01cc42d70737d699995f6389",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -223,11 +223,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1715843614,
|
||||
"narHash": "sha256-qveerNXc6yF2digoKDR9Hj/o0n8Y3bW/yET6sRochv0=",
|
||||
"lastModified": 1712237761,
|
||||
"narHash": "sha256-NoMBBCADTms3yx5BL+sbc7vfDivNiYULO6t9GBAsPt0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs-wayland",
|
||||
"rev": "5e2c5345f3204c867c9d4183cbb68069d0f7a951",
|
||||
"rev": "9b77653338f52da4b498abdf4835efb6ff6e453e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -254,11 +254,11 @@
|
|||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1715482972,
|
||||
"narHash": "sha256-y1uMzXNlrVOWYj1YNcsGYLm4TOC2aJrwoUY1NjQs9fM=",
|
||||
"lastModified": 1711855048,
|
||||
"narHash": "sha256-HxegAPnQJSC4cbEbF4Iq3YTlFHZKLiNTk8147EbLdGg=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "b6cb5de2ce57acb10ecdaaf9bbd62a5ff24fa02e",
|
||||
"rev": "99b1e37f9fc0960d064a7862eb7adfb92e64fa10",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -291,11 +291,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1711963903,
|
||||
"narHash": "sha256-N3QDhoaX+paWXHbEXZapqd1r95mdshxToGowtjtYkGI=",
|
||||
"lastModified": 1702979157,
|
||||
"narHash": "sha256-RnFBbLbpqtn4AoJGXKevQMCGhra4h6G2MPcuTSZZQ+g=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "49dc4a92b02b8e68798abd99184f228243b6e3ac",
|
||||
"rev": "2961375283668d867e64129c22af532de8e77734",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -311,11 +311,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1713198740,
|
||||
"narHash": "sha256-8SUaqMJdAkMOI9zhvlToL7eCr5Sl+2o2pDQ7nq+HoJU=",
|
||||
"lastModified": 1711371733,
|
||||
"narHash": "sha256-+brjlMyLVnVADY31sN82Ap0IsPE2WZEwHUd94sY6BXI=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "af8420d1c256d990b5e24de14ad8592a5d85bf77",
|
||||
"revCount": 239,
|
||||
"rev": "b9502e6f190752d327f8cee7fa4b139094bd7c16",
|
||||
"revCount": 237,
|
||||
"type": "git",
|
||||
"url": "https://git.uninsane.org/colin/uninsane"
|
||||
},
|
||||
|
|
36
flake.nix
36
flake.nix
|
@ -108,22 +108,20 @@
|
|||
nixpkgs' = patchNixpkgs "master" nixpkgs-unpatched;
|
||||
nixpkgsCompiledBy = system: nixpkgs'.legacyPackages."${system}";
|
||||
|
||||
evalHost = { name, local, target, variant ? null, nixpkgs ? nixpkgs' }: nixpkgs.lib.nixosSystem {
|
||||
evalHost = { name, local, target, light ? false, nixpkgs ? nixpkgs' }: nixpkgs.lib.nixosSystem {
|
||||
system = target;
|
||||
modules = [
|
||||
{
|
||||
nixpkgs.buildPlatform.system = local;
|
||||
# nixpkgs.config.replaceStdenv = { pkgs }: pkgs.ccacheStdenv;
|
||||
}
|
||||
(optionalAttrs (local != target) {
|
||||
# XXX(2023/12/11): cache.nixos.org uses `system = ...` instead of `hostPlatform.system`, and that choice impacts the closure of every package.
|
||||
# so avoid specifying hostPlatform.system on non-cross builds, so i can use upstream caches.
|
||||
nixpkgs.hostPlatform.system = target;
|
||||
})
|
||||
(optionalAttrs (variant == "light") {
|
||||
sane.maxBuildCost = 2;
|
||||
})
|
||||
(optionalAttrs (variant == "min") {
|
||||
sane.maxBuildCost = 0;
|
||||
(optionalAttrs light {
|
||||
sane.enableSlowPrograms = false;
|
||||
})
|
||||
(import ./hosts/instantiate.nix { hostName = name; })
|
||||
self.nixosModules.default
|
||||
|
@ -141,13 +139,11 @@
|
|||
hosts = {
|
||||
servo = { name = "servo"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
desko = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
desko-light = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; variant = "light"; };
|
||||
desko-light = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; light = true; };
|
||||
lappy = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
lappy-light = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; variant = "light"; };
|
||||
lappy-min = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; variant = "min"; };
|
||||
lappy-light = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; light = true; };
|
||||
moby = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; };
|
||||
moby-light = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; variant = "light"; };
|
||||
moby-min = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; variant = "min"; };
|
||||
moby-light = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; light = true; };
|
||||
rescue = { name = "rescue"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
};
|
||||
hostsNext = mapAttrs' (h: v: {
|
||||
|
@ -289,7 +285,7 @@
|
|||
# - sandbox friendliness (especially: `git` doesn't have to be run as root)
|
||||
|
||||
if [ -n "$addr" ]; then
|
||||
sudo nix store sign -r -k /run/secrets/nix_signing_key "$storePath"
|
||||
sudo nix store sign -r -k /run/secrets/nix_serve_privkey "$storePath"
|
||||
# add more `-v` for more verbosity (up to 5).
|
||||
# builders-use-substitutes false: optimizes so that the remote machine doesn't try to get paths from its substituters.
|
||||
# we already have all paths here, and the remote substitution is slow to check and SERIOUSLY flaky on moby in particular.
|
||||
|
@ -412,17 +408,14 @@
|
|||
desko-light = deployApp "desko-light" "desko" "switch";
|
||||
lappy = deployApp "lappy" "lappy" "switch";
|
||||
lappy-light = deployApp "lappy-light" "lappy" "switch";
|
||||
lappy-min = deployApp "lappy-min" "lappy" "switch";
|
||||
moby = deployApp "moby" "moby" "switch";
|
||||
moby-light = deployApp "moby-light" "moby" "switch";
|
||||
moby-min = deployApp "moby-min" "moby" "switch";
|
||||
moby-test = deployApp "moby" "moby" "test";
|
||||
servo = deployApp "servo" "servo" "switch";
|
||||
|
||||
# like `nixos-rebuild --flake . switch`
|
||||
self = deployApp "$(hostname)" "" "switch";
|
||||
self-light = deployApp "$(hostname)-light" "" "switch";
|
||||
self-min = deployApp "$(hostname)-min" "" "switch";
|
||||
self = deployApp "$(hostname)" "" "switch";
|
||||
self-light = deployApp "$(hostname)-light" "" "switch";
|
||||
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "deploy-all" ''
|
||||
|
@ -438,16 +431,12 @@
|
|||
desko-light = deployApp "desko-light" "desko" null;
|
||||
lappy = deployApp "lappy" "lappy" null;
|
||||
lappy-light = deployApp "lappy-light" "lappy" null;
|
||||
lappy-min = deployApp "lappy-min" "lappy" null;
|
||||
moby = deployApp "moby" "moby" null;
|
||||
moby-light = deployApp "moby-light" "moby" null;
|
||||
moby-min = deployApp "moby-min" "moby" null;
|
||||
servo = deployApp "servo" "servo" null;
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "predeploy-all" ''
|
||||
# copy the -min/-light variants first; this might be run while waiting on a full build. or the full build failed.
|
||||
nix run '.#preDeploy.moby-min' -- "$@"
|
||||
nix run '.#preDeploy.lappy-min' -- "$@"
|
||||
# copy the -light variants first; this might be run while waiting on a full build. or the full build failed.
|
||||
nix run '.#preDeploy.moby-light' -- "$@"
|
||||
nix run '.#preDeploy.lappy-light' -- "$@"
|
||||
nix run '.#preDeploy.desko-light' -- "$@"
|
||||
|
@ -552,9 +541,6 @@
|
|||
''
|
||||
# build minimally-usable hosts first, then their full image.
|
||||
# this gives me a minimal image i can deploy or copy over, early.
|
||||
${checkHost "lappy-min"}
|
||||
${checkHost "moby-min"}
|
||||
|
||||
${checkHost "desko-light"}
|
||||
${checkHost "moby-light"}
|
||||
${checkHost "lappy-light"}
|
||||
|
|
|
@ -4,16 +4,18 @@
|
|||
./fs.nix
|
||||
];
|
||||
|
||||
sane.services.trust-dns.asSystemResolver = false; # TEMPORARY: TODO: re-enable trust-dns
|
||||
# sane.programs.devPkgs.enableFor.user.colin = true;
|
||||
# sane.guest.enable = true;
|
||||
|
||||
# don't enable wifi by default: it messes with connectivity.
|
||||
# systemd.services.iwd.enable = false;
|
||||
# systemd.services.wpa_supplicant.enable = false;
|
||||
# services.distccd.enable = true;
|
||||
# sane.programs.distcc.enableFor.user.guest = true;
|
||||
|
||||
# TODO: remove emulation, but need to fix nixos-rebuild to moby for that.
|
||||
# sane.roles.build-machine.emulation = true;
|
||||
|
||||
sops.secrets.colin-passwd.neededForUsers = true;
|
||||
|
||||
sane.ports.openFirewall = true; # for e.g. nix-serve
|
||||
|
||||
sane.roles.build-machine.enable = true;
|
||||
sane.roles.client = true;
|
||||
sane.roles.dev-machine = true;
|
||||
|
@ -21,14 +23,17 @@
|
|||
sane.services.wg-home.enable = true;
|
||||
sane.services.wg-home.ip = config.sane.hosts.by-name."desko".wg-home.ip;
|
||||
sane.services.duplicity.enable = true;
|
||||
sane.services.nixserve.secretKeyFile = config.sops.secrets.nix_serve_privkey.path;
|
||||
|
||||
sane.nixcache.substituters.desko = false;
|
||||
sane.nixcache.remote-builders.desko = false;
|
||||
|
||||
sane.programs.cups.enableFor.user.colin = true;
|
||||
sane.programs.sway.enableFor.user.colin = true;
|
||||
sane.programs.iphoneUtils.enableFor.user.colin = true;
|
||||
sane.programs.steam.enableFor.user.colin = true;
|
||||
|
||||
# sane.programs.devPkgs.enableFor.user.colin = true;
|
||||
|
||||
sane.programs."gnome.geary".config.autostart = true;
|
||||
sane.programs.signal-desktop.config.autostart = true;
|
||||
|
||||
|
@ -38,6 +43,10 @@
|
|||
# needed to use libimobiledevice/ifuse, for iphone sync
|
||||
services.usbmuxd.enable = true;
|
||||
|
||||
# don't enable wifi by default: it messes with connectivity.
|
||||
systemd.services.iwd.enable = false;
|
||||
systemd.services.wpa_supplicant.enable = false;
|
||||
|
||||
# default config: https://man.archlinux.org/man/snapper-configs.5
|
||||
# defaults to something like:
|
||||
# - hourly snapshots
|
||||
|
|
|
@ -14,12 +14,10 @@
|
|||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||
|
||||
sane.programs.cups.enableFor.user.colin = true;
|
||||
sane.programs.stepmania.enableFor.user.colin = true;
|
||||
sane.programs.sway.enableFor.user.colin = true;
|
||||
|
||||
sane.programs."gnome.geary".config.autostart = true;
|
||||
sane.programs.signal-desktop.config.autostart = true;
|
||||
sane.programs.stepmania.enableFor.user.colin = true;
|
||||
|
||||
sops.secrets.colin-passwd.neededForUsers = true;
|
||||
|
||||
|
|
|
@ -21,10 +21,14 @@
|
|||
|
||||
sane.roles.client = true;
|
||||
sane.roles.handheld = true;
|
||||
sane.programs.zsh.config.showDeadlines = false; # unlikely to act on them when in shell
|
||||
sane.zsh.showDeadlines = false; # unlikely to act on them when in shell
|
||||
sane.services.wg-home.enable = true;
|
||||
sane.services.wg-home.ip = config.sane.hosts.by-name."moby".wg-home.ip;
|
||||
|
||||
# for some reason desko -> moby deploys are super flaky when desko is also a nixcache (not true of desko -> lappy deploys, though!)
|
||||
# > unable to download 'http://desko:5001/<hash>.narinfo': Server returned nothing (no headers, no data) (52)
|
||||
sane.nixcache.substituters.desko = false;
|
||||
|
||||
# XXX colin: phosh doesn't work well with passwordless login,
|
||||
# so set this more reliable default password should anything go wrong
|
||||
users.users.colin.initialPassword = "147147";
|
||||
|
@ -58,8 +62,18 @@
|
|||
# HACK/TODO: make `programs.P.env.VAR` behave according to `mime.priority`
|
||||
sane.programs.firefox.env = lib.mkForce {};
|
||||
sane.programs.epiphany.env.BROWSER = "epiphany";
|
||||
sane.programs.pipewire.config = {
|
||||
# tune so Dino doesn't drop audio
|
||||
|
||||
# note the .conf.d approach: using ~/.config/pipewire/pipewire.conf directly breaks all audio,
|
||||
# presumably because that deletes the defaults entirely whereas the .conf.d approach selectively overrides defaults
|
||||
sane.user.fs.".config/pipewire/pipewire.conf.d/10-fix-dino-mic-cutout.conf".symlink.text = ''
|
||||
# config docs: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-PipeWire#properties>
|
||||
# useful to run `pw-top` to see that these settings are actually having effect,
|
||||
# and `pw-metadata` to see if any settings conflict (e.g. max-quantum < min-quantum)
|
||||
#
|
||||
# restart pipewire after editing these files:
|
||||
# - `systemctl --user restart pipewire`
|
||||
# - pipewire users will likely stop outputting audio until they are also restarted
|
||||
#
|
||||
# there's seemingly two buffers for the mic (see: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#pipewire-buffering-explained>)
|
||||
# 1. Pipewire buffering out of the driver and into its own member.
|
||||
# 2. Pipewire buffering into Dino.
|
||||
|
@ -70,9 +84,11 @@
|
|||
# `pw-metadata -n settings 0 clock.force-quantum 1024` reduces to about 1 error per second.
|
||||
# `pw-metadata -n settings 0 clock.force-quantum 2048` reduces to 1 error every < 10s.
|
||||
# pipewire default config includes `clock.power-of-two-quantum = true`
|
||||
min-quantum = 2048;
|
||||
max-quantum = 8192;
|
||||
};
|
||||
context.properties = {
|
||||
default.clock.min-quantum = 2048
|
||||
default.clock.max-quantum = 8192
|
||||
}
|
||||
'';
|
||||
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
# /boot space is at a premium. default was 20.
|
||||
|
@ -112,6 +128,14 @@
|
|||
# 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" ];
|
||||
};
|
||||
|
||||
services.udev.extraRules = let
|
||||
chmod = "${pkgs.coreutils}/bin/chmod";
|
||||
chown = "${pkgs.coreutils}/bin/chown";
|
||||
|
|
|
@ -64,5 +64,6 @@
|
|||
"dialout" # TODO: figure out if dialout is required. that's for /dev/ttyUSB1, but geoclue probably doesn't read that?
|
||||
];
|
||||
|
||||
sane.services.eg25-control.enable = true;
|
||||
sane.programs.where-am-i.enableFor.user.colin = true;
|
||||
}
|
||||
|
|
|
@ -15,19 +15,20 @@
|
|||
};
|
||||
|
||||
sane.roles.build-machine.enable = true;
|
||||
sane.programs.zsh.config.showDeadlines = false; # ~/knowledge doesn't always exist
|
||||
sane.zsh.showDeadlines = false; # ~/knowledge doesn't always exist
|
||||
sane.programs.consoleUtils.suggestedPrograms = [
|
||||
"consoleMediaUtils" # notably, for go2tv / casting
|
||||
"pcConsoleUtils"
|
||||
"sane-scripts.stop-all-servo"
|
||||
];
|
||||
sane.services.dyn-dns.enable = true;
|
||||
sane.services.trust-dns.asSystemResolver = false; # TODO: enable once it's all working well
|
||||
sane.services.wg-home.enable = true;
|
||||
sane.services.wg-home.visibleToWan = true;
|
||||
sane.services.wg-home.forwardToWan = true;
|
||||
sane.services.wg-home.routeThroughServo = false;
|
||||
sane.services.wg-home.ip = config.sane.hosts.by-name."servo".wg-home.ip;
|
||||
sane.nixcache.substituters.servo = false;
|
||||
sane.nixcache.substituters.desko = false;
|
||||
sane.nixcache.remote-builders.desko = false;
|
||||
sane.nixcache.remote-builders.servo = false;
|
||||
# sane.services.duplicity.enable = true; # TODO: re-enable after HW upgrade
|
||||
|
|
|
@ -24,7 +24,7 @@ lib.mkIf false
|
|||
# services.calibre-web.options.calibreLibrary = svc-dir;
|
||||
|
||||
services.nginx.virtualHosts."calibre.uninsane.org" = {
|
||||
forceSSL = true;
|
||||
addSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://${ip}:${builtins.toString port}";
|
||||
|
|
|
@ -24,64 +24,50 @@
|
|||
# that is NOT the case when the STUN server and client A are on the same LAN
|
||||
# even if client A contacts the STUN server via its WAN address with port reflection enabled.
|
||||
# hence, there's no obvious way to put the STUN server on the same LAN as either client and expect the rest to work.
|
||||
# - there an old version which *half worked*, which is:
|
||||
# - run the turn server in the root namespace.
|
||||
# - bind the turn server to the veth connecting it to the VPN namespace (so it sends outgoing traffic to the right place).
|
||||
# - NAT the turn port range from VPN into root namespace (so it receives incomming traffic).
|
||||
# - this approach would fail the prosody conversations.im check, but i didn't notice *obvious* call routing errors.
|
||||
#
|
||||
# debugging:
|
||||
# - log messages like 'usage: realm=<turn.uninsane.org>, username=<1715915193>, rp=14, rb=1516, sp=8, sb=684'
|
||||
# - rp = received packets
|
||||
# - rb = received bytes
|
||||
# - sp = sent packets
|
||||
# - sb = sent bytes
|
||||
{ lib, ... }:
|
||||
let
|
||||
# TURN port range (inclusive).
|
||||
# default coturn behavior is to use the upper quarter of all ports. i.e. 49152 - 65535.
|
||||
# i believe TURN allocations expire after either 5 or 10 minutes of inactivity.
|
||||
turnPortLow = 49152; # 49152 = 0xc000
|
||||
turnPortHigh = turnPortLow + 256;
|
||||
# TODO: this range could be larger, but right now that's costly because each element is its own UPnP forward
|
||||
# TURN port range (inclusive)
|
||||
turnPortLow = 49152;
|
||||
turnPortHigh = 49167;
|
||||
turnPortRange = lib.range turnPortLow turnPortHigh;
|
||||
in
|
||||
{
|
||||
# the port definitions are only needed if running in the root net namespace
|
||||
# sane.ports.ports = lib.mkMerge ([
|
||||
# {
|
||||
# "3478" = {
|
||||
# # this is the "control" port.
|
||||
# # i.e. no client data is forwarded through it, but it's where clients request tunnels.
|
||||
# protocol = [ "tcp" "udp" ];
|
||||
# # visibleTo.lan = true;
|
||||
# # visibleTo.wan = true;
|
||||
# visibleTo.ovpn = true; # forward traffic from the VPN to the root NS
|
||||
# description = "colin-stun-turn";
|
||||
# };
|
||||
# "5349" = {
|
||||
# # the other port 3478 also supports TLS/DTLS, but presumably clients wanting TLS will default 5349
|
||||
# protocol = [ "tcp" ];
|
||||
# # visibleTo.lan = true;
|
||||
# # visibleTo.wan = true;
|
||||
# visibleTo.ovpn = true;
|
||||
# description = "colin-stun-turn-over-tls";
|
||||
# };
|
||||
# }
|
||||
# ] ++ (builtins.map
|
||||
# (port: {
|
||||
# "${builtins.toString port}" = let
|
||||
# count = port - turnPortLow + 1;
|
||||
# numPorts = turnPortHigh - turnPortLow + 1;
|
||||
# in {
|
||||
# protocol = [ "tcp" "udp" ];
|
||||
# # visibleTo.lan = true;
|
||||
# # visibleTo.wan = true;
|
||||
# visibleTo.ovpn = true;
|
||||
# description = "colin-turn-${builtins.toString count}-of-${builtins.toString numPorts}";
|
||||
# };
|
||||
# })
|
||||
# turnPortRange
|
||||
# ));
|
||||
sane.ports.ports = lib.mkMerge ([
|
||||
{
|
||||
"3478" = {
|
||||
# this is the "control" port.
|
||||
# i.e. no client data is forwarded through it, but it's where clients request tunnels.
|
||||
protocol = [ "tcp" "udp" ];
|
||||
# visibleTo.lan = true;
|
||||
# visibleTo.wan = true;
|
||||
visibleTo.ovpn = true;
|
||||
description = "colin-stun-turn";
|
||||
};
|
||||
"5349" = {
|
||||
# the other port 3478 also supports TLS/DTLS, but presumably clients wanting TLS will default 5349
|
||||
protocol = [ "tcp" ];
|
||||
# visibleTo.lan = true;
|
||||
# visibleTo.wan = true;
|
||||
visibleTo.ovpn = true;
|
||||
description = "colin-stun-turn-over-tls";
|
||||
};
|
||||
}
|
||||
] ++ (builtins.map
|
||||
(port: {
|
||||
"${builtins.toString port}" = let
|
||||
count = port - turnPortLow + 1;
|
||||
numPorts = turnPortHigh - turnPortLow + 1;
|
||||
in {
|
||||
protocol = [ "tcp" "udp" ];
|
||||
# visibleTo.lan = true;
|
||||
# visibleTo.wan = true;
|
||||
visibleTo.ovpn = true;
|
||||
description = "colin-turn-${builtins.toString count}-of-${builtins.toString numPorts}";
|
||||
};
|
||||
})
|
||||
turnPortRange
|
||||
));
|
||||
|
||||
services.nginx.virtualHosts."turn.uninsane.org" = {
|
||||
# allow ACME to procure a cert via nginx for this domain
|
||||
|
@ -117,28 +103,22 @@ in
|
|||
services.coturn.realm = "turn.uninsane.org";
|
||||
services.coturn.cert = "/var/lib/acme/turn.uninsane.org/fullchain.pem";
|
||||
services.coturn.pkey = "/var/lib/acme/turn.uninsane.org/key.pem";
|
||||
|
||||
#v disable to allow unauthenticated access (or set `services.coturn.no-auth = true`)
|
||||
services.coturn.use-auth-secret = true;
|
||||
services.coturn.static-auth-secret-file = "/var/lib/coturn/shared_secret.bin";
|
||||
services.coturn.lt-cred-mech = true; #< XXX: use-auth-secret overrides lt-cred-mech
|
||||
|
||||
services.coturn.lt-cred-mech = true;
|
||||
services.coturn.min-port = turnPortLow;
|
||||
services.coturn.max-port = turnPortHigh;
|
||||
# services.coturn.secure-stun = true;
|
||||
services.coturn.extraConfig = lib.concatStringsSep "\n" [
|
||||
"verbose"
|
||||
# "Verbose" #< even MORE verbosity than "verbose" (it's TOO MUCH verbosity really)
|
||||
"no-multicast-peers" # disables sending to IPv4 broadcast addresses (e.g. 224.0.0.0/3)
|
||||
# "listening-ip=10.0.1.5" "external-ip=185.157.162.178" #< 2024/04/25: works, if running in root namespace
|
||||
"listening-ip=185.157.162.178" "external-ip=185.157.162.178"
|
||||
|
||||
# old attempts:
|
||||
# "Verbose" #< even MORE verbosity than "verbose"
|
||||
# "no-multicast-peers" # disables sending to IPv4 broadcast addresses (e.g. 224.0.0.0/3)
|
||||
"listening-ip=10.0.1.5"
|
||||
# "external-ip=185.157.162.178/10.0.1.5"
|
||||
"external-ip=185.157.162.178"
|
||||
# "listening-ip=10.78.79.51" # can be specified multiple times; omit for *
|
||||
# "external-ip=97.113.128.229/10.78.79.51"
|
||||
# "external-ip=97.113.128.229"
|
||||
# "mobility" # "mobility with ICE (MICE) specs support" (?)
|
||||
];
|
||||
systemd.services.coturn.serviceConfig.NetworkNamespacePath = "/run/netns/ovpns";
|
||||
}
|
||||
|
|
|
@ -1,22 +1,6 @@
|
|||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ps.pyln-client ])"
|
||||
|
||||
"""
|
||||
clightning-sane: helper to perform common Lightning node admin operations:
|
||||
- view channel balances
|
||||
- rebalance channels
|
||||
|
||||
COMMON OPERATIONS:
|
||||
- view channel balances: `clightning-sane status`
|
||||
- rebalance channels to improve routability (without paying any fees): `clightning-sane autobalance`
|
||||
|
||||
FULL OPERATION:
|
||||
- `clightning-sane status --full`
|
||||
- `P$`: represents how many msats i've captured in fees from this channel.
|
||||
- `COST`: rough measure of how much it's "costing" me to let my channel partner hold funds on his side of the channel.
|
||||
this is based on the notion that i only capture fees from outbound transactions, and so the channel partner holding all liquidity means i can't capture fees on that liquidity.
|
||||
"""
|
||||
|
||||
# pyln-client docs: <https://github.com/ElementsProject/lightning/tree/master/contrib/pyln-client>
|
||||
# terminology:
|
||||
# - "scid": "Short Channel ID", e.g. 123456x7890x0
|
||||
|
@ -742,7 +726,7 @@ def main():
|
|||
logging.basicConfig()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser = argparse.ArgumentParser(description="rebalance lightning channel balances")
|
||||
parser.add_argument("--verbose", action="store_true", help="more logging")
|
||||
parser.add_argument("--min-msat", default="999", help="min transaction size")
|
||||
parser.add_argument("--max-msat", default="1000000", help="max transaction size")
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
./navidrome.nix
|
||||
./nginx.nix
|
||||
./nixos-prebuild.nix
|
||||
./nixserve.nix
|
||||
./ntfy
|
||||
./pict-rs.nix
|
||||
./pleroma.nix
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
imports = [
|
||||
./nfs.nix
|
||||
./sftpgo
|
||||
./sftpgo.nix
|
||||
];
|
||||
|
||||
users.groups.export = {};
|
||||
|
|
|
@ -9,31 +9,23 @@
|
|||
|
||||
{ config, lib, pkgs, sane-lib, ... }:
|
||||
let
|
||||
external_auth_hook = pkgs.static-nix-shell.mkPython3Bin {
|
||||
pname = "external_auth_hook";
|
||||
sftpgo_external_auth_hook = pkgs.static-nix-shell.mkPython3Bin {
|
||||
pname = "sftpgo_external_auth_hook";
|
||||
srcRoot = ./.;
|
||||
};
|
||||
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.
|
||||
passiveStart = 50050;
|
||||
passiveEnd = 50070;
|
||||
in
|
||||
{
|
||||
sane.ports.ports = {
|
||||
"21" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
# visibleTo.wan = true;
|
||||
description = "colin-FTP server";
|
||||
};
|
||||
"990" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-FTPS server";
|
||||
description = "colin-FTP server";
|
||||
};
|
||||
} // (sane-lib.mapToAttrs
|
||||
(port: {
|
||||
|
@ -45,42 +37,12 @@ in
|
|||
description = "colin-FTP server data port range";
|
||||
};
|
||||
})
|
||||
(lib.range passiveStart passiveEnd)
|
||||
(lib.range 50050 50100)
|
||||
);
|
||||
|
||||
# use nginx/acme to produce a cert for FTPS
|
||||
services.nginx.virtualHosts."ftp.uninsane.org" = {
|
||||
addSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
sane.dns.zones."uninsane.org".inet.CNAME."ftp" = "native";
|
||||
|
||||
services.sftpgo = {
|
||||
enable = true;
|
||||
group = "export";
|
||||
|
||||
package = lib.warnIf (lib.versionOlder "2.5.6" pkgs.sftpgo.version) "sftpgo update: safe to use nixpkgs' sftpgo but keep my own `patches`" pkgs.buildGoModule {
|
||||
inherit (pkgs.sftpgo) name ldflags nativeBuildInputs doCheck subPackages postInstall passthru meta;
|
||||
version = "2.5.6-unstable-2024-04-18";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
# need to use > 2.5.6 for sftpgo_safe_fileinfo.patch to apply
|
||||
owner = "drakkan";
|
||||
repo = "sftpgo";
|
||||
rev = "950cf67e4c03a12c7e439802cabbb0b42d4ee5f5";
|
||||
hash = "sha256-UfiFd9NK3DdZ1J+FPGZrM7r2mo9xlKi0dsSlLEinYXM=";
|
||||
};
|
||||
vendorHash = "sha256-n1/9A2em3BCtFX+132ualh4NQwkwewMxYIMOphJEamg=";
|
||||
patches = (pkgs.sftpgo.patches or []) ++ [
|
||||
# fix for compatibility with kodi:
|
||||
# ftp LIST operation returns entries over-the-wire like:
|
||||
# - dgrwxrwxr-x 1 ftp ftp 9 Apr 9 15:05 Videos
|
||||
# however not all clients understand all mode bits (like that `g`, indicating SGID / group sticky bit).
|
||||
# instead, only send mode bits which are well-understood.
|
||||
# the full set of bits, from which i filter, is found here: <https://pkg.go.dev/io/fs#FileMode>
|
||||
./safe_fileinfo.patch
|
||||
];
|
||||
};
|
||||
|
||||
settings = {
|
||||
ftpd = {
|
||||
bindings = [
|
||||
|
@ -96,33 +58,16 @@ in
|
|||
port = 21;
|
||||
debug = true;
|
||||
}
|
||||
{
|
||||
# binding this means any wireguard client can connect
|
||||
address = "10.0.10.5";
|
||||
port = 990;
|
||||
debug = true;
|
||||
tls_mode = 2; # 2 = "implicit FTPS": client negotiates TLS before any FTP command.
|
||||
}
|
||||
{
|
||||
# binding this means any LAN client can connect (also WAN traffic forwarded from the gateway)
|
||||
address = "10.78.79.51";
|
||||
port = 990;
|
||||
debug = true;
|
||||
tls_mode = 2; # 2 = "implicit FTPS": client negotiates TLS before any FTP command.
|
||||
}
|
||||
];
|
||||
|
||||
# active mode is susceptible to "bounce attacks", without much benefit over passive mode
|
||||
disable_active_mode = true;
|
||||
hash_support = true;
|
||||
passive_port_range = {
|
||||
start = passiveStart;
|
||||
end = passiveEnd;
|
||||
start = 50050;
|
||||
end = 50100;
|
||||
};
|
||||
|
||||
certificate_file = "/var/lib/acme/ftp.uninsane.org/full.pem";
|
||||
certificate_key_file = "/var/lib/acme/ftp.uninsane.org/key.pem";
|
||||
|
||||
banner = ''
|
||||
Welcome, friends, to Colin's FTP server! Also available via NFS on the same host, but LAN-only.
|
||||
|
||||
|
@ -130,14 +75,14 @@ in
|
|||
Username: "anonymous"
|
||||
Password: "anonymous"
|
||||
|
||||
CONFIGURE YOUR CLIENT FOR "PASSIVE" MODE, e.g. `ftp --passive ftp.uninsane.org`.
|
||||
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 :)
|
||||
'';
|
||||
|
||||
};
|
||||
data_provider = {
|
||||
driver = "memory";
|
||||
external_auth_hook = "${external_auth_hook}/bin/external_auth_hook";
|
||||
external_auth_hook = "${sftpgo_external_auth_hook}/bin/sftpgo_external_auth_hook";
|
||||
# track_quota:
|
||||
# - 0: disable quota tracking
|
||||
# - 1: quota is updated on every upload/delete, even if user has no quota restriction
|
||||
|
@ -150,7 +95,6 @@ in
|
|||
users.users.sftpgo.extraGroups = [
|
||||
"export"
|
||||
"media"
|
||||
"nginx" # to access certs
|
||||
];
|
||||
|
||||
systemd.services.sftpgo = {
|
|
@ -1,32 +0,0 @@
|
|||
diff --git a/internal/ftpd/handler.go b/internal/ftpd/handler.go
|
||||
index 036c3977..33211261 100644
|
||||
--- a/internal/ftpd/handler.go
|
||||
+++ b/internal/ftpd/handler.go
|
||||
@@ -169,7 +169,7 @@ func (c *Connection) Stat(name string) (os.FileInfo, error) {
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
- return fi, nil
|
||||
+ return vfs.NewFileInfo(name, fi.IsDir(), fi.Size(), fi.ModTime(), false), nil
|
||||
}
|
||||
|
||||
// Name returns the name of this connection
|
||||
@@ -315,7 +315,17 @@ func (c *Connection) ReadDir(name string) (ftpserver.DirLister, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
- return c.ListDir(name)
|
||||
+ lister, err := c.ListDir(name)
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ return &patternDirLister{
|
||||
+ DirLister: lister,
|
||||
+ pattern: "*",
|
||||
+ lastCommand: c.clientContext.GetLastCommand(),
|
||||
+ dirName: name,
|
||||
+ connectionPath: c.clientContext.Path(),
|
||||
+ }, nil
|
||||
}
|
||||
|
||||
// GetHandle implements ClientDriverExtentionFileTransfer
|
|
@ -20,7 +20,7 @@
|
|||
--ignore-panel=HOSTS \
|
||||
--ws-url=wss://sink.uninsane.org:443/ws \
|
||||
--port=7890 \
|
||||
-o /var/lib/goaccess/index.html
|
||||
-o /var/lib/uninsane/sink/index.html
|
||||
'';
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
Type = "simple";
|
||||
|
@ -28,19 +28,17 @@
|
|||
RestartSec = "10s";
|
||||
|
||||
# hardening
|
||||
# TODO: run as `goaccess` user and add `goaccess` user to group `nginx`.
|
||||
WorkingDirectory = "/tmp";
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = "yes";
|
||||
PrivateTmp = true;
|
||||
ProtectHome = "read-only";
|
||||
ProtectSystem = "strict";
|
||||
SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @privileged @reboot @resources @setuid @swap @raw-io";
|
||||
ReadOnlyPaths = "/";
|
||||
ReadWritePaths = [ "/proc/self" "/var/lib/uninsane/sink" ];
|
||||
PrivateDevices = "yes";
|
||||
ProtectKernelModules = "yes";
|
||||
ProtectKernelTunables = "yes";
|
||||
ProtectSystem = "strict";
|
||||
ReadOnlyPaths = [ "/var/log/nginx" ];
|
||||
ReadWritePaths = [ "/proc/self" "/var/lib/goaccess" ];
|
||||
StateDirectory = "goaccess";
|
||||
SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @privileged @reboot @resources @setuid @swap @raw-io";
|
||||
WorkingDirectory = "/var/lib/goaccess";
|
||||
};
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
@ -51,7 +49,7 @@
|
|||
addSSL = true;
|
||||
enableACME = true;
|
||||
# inherit kTLS;
|
||||
root = "/var/lib/goaccess";
|
||||
root = "/var/lib/uninsane/sink";
|
||||
|
||||
locations."/ws" = {
|
||||
proxyPass = "http://127.0.0.1:7890";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{ lib, pkgs, ... }:
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
|
@ -12,8 +12,6 @@
|
|||
systemd.services.jackett.serviceConfig = {
|
||||
# run this behind the OVPN static VPN
|
||||
NetworkNamespacePath = "/run/netns/ovpns";
|
||||
ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect 185.157.162.178" ]; # abort if public IP is not as expected
|
||||
|
||||
# patch jackett to listen on the public interfaces
|
||||
# ExecStart = lib.mkForce "${pkgs.jackett}/bin/Jackett --NoUpdates --DataFolder /var/lib/jackett/.config/Jackett --ListenPublic";
|
||||
};
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
# Jellyfin multimedia server
|
||||
# this is mostly taken from the official jellfin.org docs
|
||||
services.nginx.virtualHosts."jelly.uninsane.org" = {
|
||||
forceSSL = true;
|
||||
addSSL = true;
|
||||
enableACME = true;
|
||||
# inherit kTLS;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ in
|
|||
services.komga.port = 11319; # chosen at random
|
||||
|
||||
services.nginx.virtualHosts."komga.uninsane.org" = {
|
||||
forceSSL = true;
|
||||
addSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:${builtins.toString port}";
|
||||
|
|
|
@ -89,16 +89,6 @@ in
|
|||
disable_symlinks on;
|
||||
'';
|
||||
};
|
||||
locations."/share/Milkbags/" = {
|
||||
alias = "/var/media/Videos/Milkbags/";
|
||||
extraConfig = ''
|
||||
# autoindex => render directory listings
|
||||
autoindex on;
|
||||
# don't follow any symlinks when serving files
|
||||
# otherwise it allows a directory escape
|
||||
disable_symlinks on;
|
||||
'';
|
||||
};
|
||||
|
||||
# allow matrix users to discover that @user:uninsane.org is reachable via matrix.uninsane.org
|
||||
locations."= /.well-known/matrix/server".extraConfig =
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
services.nginx.virtualHosts."nixcache.uninsane.org" = {
|
||||
addSSL = true;
|
||||
enableACME = true;
|
||||
# inherit kTLS;
|
||||
# serverAliases = [ "nixcache" ];
|
||||
locations."/".extraConfig = ''
|
||||
proxy_pass http://localhost:${toString config.services.nix-serve.port};
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
'';
|
||||
};
|
||||
|
||||
sane.dns.zones."uninsane.org".inet.CNAME."nixcache" = "native";
|
||||
|
||||
sane.services.nixserve.enable = true;
|
||||
sane.services.nixserve.secretKeyFile = config.sops.secrets.nix_serve_privkey.path;
|
||||
}
|
|
@ -25,7 +25,7 @@ in
|
|||
|
||||
config :pleroma, Pleroma.Web.Endpoint,
|
||||
url: [host: "fed.uninsane.org", scheme: "https", port: 443],
|
||||
http: [ip: {127, 0, 0, 1}, port: 4040]
|
||||
http: [ip: {127, 0, 0, 1}, port: 4000]
|
||||
# secret_key_base: "{secrets.pleroma.secret_key_base}",
|
||||
# signing_salt: "{secrets.pleroma.signing_salt}"
|
||||
|
||||
|
@ -167,7 +167,7 @@ in
|
|||
enableACME = true;
|
||||
# inherit kTLS;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:4040";
|
||||
proxyPass = "http://127.0.0.1:4000";
|
||||
recommendedProxySettings = true;
|
||||
# documented: https://git.pleroma.social/pleroma/pleroma/-/blob/develop/installation/pleroma.nginx
|
||||
extraConfig = ''
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
# 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, pkgs, ... }:
|
||||
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "slskd"; group = "media"; path = "/var/lib/slskd"; method = "bind"; }
|
||||
|
@ -69,12 +68,12 @@
|
|||
# flags.volatile = true; # store searches and active transfers in RAM (completed transfers still go to disk). rec for btrfs/zfs
|
||||
};
|
||||
|
||||
systemd.services.slskd.serviceConfig = {
|
||||
# run this behind the OVPN static VPN
|
||||
NetworkNamespacePath = "/run/netns/ovpns";
|
||||
ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect 185.157.162.178" ]; # abort if public IP is not as expected
|
||||
|
||||
Restart = lib.mkForce "always"; # exits "success" when it fails to connect to soulseek server
|
||||
RestartSec = "60s";
|
||||
systemd.services.slskd = {
|
||||
serviceConfig = {
|
||||
# run this behind the OVPN static VPN
|
||||
NetworkNamespacePath = "/run/netns/ovpns";
|
||||
Restart = lib.mkForce "always"; # exits "success" when it fails to connect to soulseek server
|
||||
RestartSec = "60s";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -26,9 +26,6 @@ let
|
|||
torrent-done = pkgs.writeShellApplication {
|
||||
name = "torrent-done";
|
||||
runtimeInputs = with pkgs; [
|
||||
acl
|
||||
coreutils
|
||||
findutils
|
||||
rsync
|
||||
util-linux
|
||||
];
|
||||
|
@ -55,27 +52,6 @@ let
|
|||
|
||||
destructive mkdir -p "$(dirname "$MEDIA_DIR")"
|
||||
destructive rsync -arv "$TR_TORRENT_DIR/" "$MEDIA_DIR/"
|
||||
# make the media rwx by anyone in the group
|
||||
destructive find "$MEDIA_DIR" -type d -exec setfacl --recursive --modify d:g::rwx,o::rx {} \;
|
||||
destructive find "$MEDIA_DIR" -type d -exec chmod g+rw,a+rx {} \;
|
||||
|
||||
# if there's a single directory inside the media dir, then inline that
|
||||
subdirs=("$MEDIA_DIR"/*)
|
||||
if [ ''${#subdirs} -eq 1 ]; then
|
||||
dirname="''${subdirs[0]}"
|
||||
if [ -d "$dirname" ]; then
|
||||
mv "$dirname"/* "$MEDIA_DIR/" && rmdir "$dirname"
|
||||
fi
|
||||
fi
|
||||
|
||||
# remove noisy files:
|
||||
find "$MEDIA_DIR/" -type f \(\
|
||||
-iname 'www.YTS.*.jpg' \
|
||||
-o -iname 'WWW.YIFY*.COM.jpg' \
|
||||
-o -iname 'YIFY*.com.txt' \
|
||||
-o -iname 'YTS*.com.txt' \
|
||||
\) -exec rm {} \;
|
||||
|
||||
# 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
|
||||
|
@ -105,8 +81,8 @@ in
|
|||
# DOCUMENTATION/options list: <https://github.com/transmission/transmission/blob/main/docs/Editing-Configuration-Files.md#options>
|
||||
|
||||
# message-level = 3; #< enable for debug logging. 0-3, default is 2.
|
||||
# 10.0.1.6 => allow rpc only from the root servo ns. it'll tunnel things to the net, if need be.
|
||||
rpc-bind-address = "10.0.1.6";
|
||||
# 0.0.0.0 => allow rpc from any host: we gate it via firewall and auth requirement
|
||||
rpc-bind-address = "0.0.0.0";
|
||||
#rpc-host-whitelist = "bt.uninsane.org";
|
||||
#rpc-whitelist = "*.*.*.*";
|
||||
rpc-authentication-required = true;
|
||||
|
@ -116,10 +92,6 @@ in
|
|||
rpc-password = "{503fc8928344f495efb8e1f955111ca5c862ce0656SzQnQ5";
|
||||
rpc-whitelist-enabled = false;
|
||||
|
||||
# force behind ovpns in case the NetworkNamespace fails somehow
|
||||
bind-address-ipv4 = "185.157.162.178";
|
||||
port-forwarding-enabled = false;
|
||||
|
||||
# hopefully, make the downloads world-readable
|
||||
# umask = 0; #< default is 2: i.e. deny writes from world
|
||||
|
||||
|
@ -159,8 +131,6 @@ in
|
|||
systemd.services.transmission.serviceConfig = {
|
||||
# run this behind the OVPN static VPN
|
||||
NetworkNamespacePath = "/run/netns/ovpns";
|
||||
ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect 185.157.162.178" ]; # abort if public IP is not as expected
|
||||
|
||||
Restart = "on-failure";
|
||||
RestartSec = "30s";
|
||||
BindPaths = [ "/var/media" ]; #< so it can move completed torrents into the media library
|
||||
|
|
|
@ -2,11 +2,19 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
dyn-dns = config.sane.services.dyn-dns;
|
||||
nativeAddrs = lib.mapAttrs (_name: builtins.head) config.sane.dns.zones."uninsane.org".inet.A;
|
||||
bindOvpn = "10.0.1.5";
|
||||
in
|
||||
in lib.mkMerge [
|
||||
{
|
||||
services.trust-dns.enable = true;
|
||||
|
||||
# don't bind to IPv6 until i explicitly test that stack
|
||||
services.trust-dns.settings.listen_addrs_ipv6 = [];
|
||||
services.trust-dns.quiet = true;
|
||||
# FIXME(2023/11/26): services.trust-dns.debug doesn't log requests: use RUST_LOG=debug env for that.
|
||||
# - see: <https://github.com/hickory-dns/hickory-dns/issues/2082>
|
||||
# services.trust-dns.debug = true;
|
||||
|
||||
sane.ports.ports."53" = {
|
||||
protocol = [ "udp" "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
|
@ -58,6 +66,23 @@ in
|
|||
|
||||
services.trust-dns.settings.zones = [ "uninsane.org" ];
|
||||
|
||||
# TODO: can i transform this into some sort of service group?
|
||||
# have `systemctl restart trust-dns.service` restart all the individual services?
|
||||
systemd.services.trust-dns.serviceConfig = {
|
||||
DynamicUser = lib.mkForce false;
|
||||
User = "trust-dns";
|
||||
Group = "trust-dns";
|
||||
wantedBy = lib.mkForce [];
|
||||
};
|
||||
systemd.services.trust-dns.enable = false;
|
||||
|
||||
users.groups.trust-dns = {};
|
||||
users.users.trust-dns = {
|
||||
group = "trust-dns";
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
# sane.services.dyn-dns.restartOnChange = [ "trust-dns.service" ];
|
||||
|
||||
networking.nat.enable = true;
|
||||
networking.nat.extraCommands = ''
|
||||
|
@ -82,73 +107,98 @@ in
|
|||
visibleTo.lan = true;
|
||||
description = "colin-redirected-dns-for-lan-namespace";
|
||||
};
|
||||
}
|
||||
{
|
||||
systemd.services =
|
||||
let
|
||||
sed = "${pkgs.gnused}/bin/sed";
|
||||
stateDir = "/var/lib/trust-dns";
|
||||
zoneTemplate = pkgs.writeText "uninsane.org.zone.in" config.sane.dns.zones."uninsane.org".rendered;
|
||||
|
||||
zoneDirFor = flavor: "${stateDir}/${flavor}";
|
||||
zoneFor = flavor: "${zoneDirFor flavor}/uninsane.org.zone";
|
||||
mkTrustDnsService = opts: flavor: let
|
||||
flags = let baseCfg = config.services.trust-dns; in
|
||||
(lib.optional baseCfg.debug "--debug") ++ (lib.optional baseCfg.quiet "--quiet");
|
||||
flagsStr = builtins.concatStringsSep " " flags;
|
||||
|
||||
sane.services.trust-dns.enable = true;
|
||||
sane.services.trust-dns.instances = let
|
||||
mkSubstitutions = flavor: {
|
||||
"%AWAN%" = "$(cat '${dyn-dns.ipPath}')";
|
||||
"%CNAMENATIVE%" = "servo.${flavor}";
|
||||
"%ANATIVE%" = nativeAddrs."servo.${flavor}";
|
||||
"%AOVPNS%" = "185.157.162.178";
|
||||
anative = nativeAddrs."servo.${flavor}";
|
||||
|
||||
toml = pkgs.formats.toml { };
|
||||
configTemplate = opts.config or (toml.generate "trust-dns-${flavor}.toml" (
|
||||
(
|
||||
lib.filterAttrsRecursive (_: v: v != null) config.services.trust-dns.settings
|
||||
) // {
|
||||
listen_addrs_ipv4 = opts.listen or [ anative ];
|
||||
}
|
||||
));
|
||||
configFile = "${stateDir}/${flavor}-config.toml";
|
||||
|
||||
port = opts.port or 53;
|
||||
in {
|
||||
description = "trust-dns Domain Name Server (serving ${flavor})";
|
||||
unitConfig.Documentation = "https://trust-dns.org/";
|
||||
|
||||
preStart = ''
|
||||
wan=$(cat '${config.sane.services.dyn-dns.ipPath}')
|
||||
${sed} s/%AWAN%/$wan/ ${configTemplate} > ${configFile}
|
||||
'' + lib.optionalString (!opts ? config) ''
|
||||
mkdir -p ${zoneDirFor flavor}
|
||||
${sed} \
|
||||
-e s/%CNAMENATIVE%/servo.${flavor}/ \
|
||||
-e s/%ANATIVE%/${anative}/ \
|
||||
-e s/%AWAN%/$wan/ \
|
||||
-e s/%AOVPNS%/185.157.162.178/ \
|
||||
${zoneTemplate} > ${zoneFor flavor}
|
||||
'';
|
||||
serviceConfig = config.systemd.services.trust-dns.serviceConfig // {
|
||||
ExecStart = ''
|
||||
${pkgs.trust-dns}/bin/${pkgs.trust-dns.meta.mainProgram} \
|
||||
--port ${builtins.toString port} \
|
||||
--zonedir ${zoneDirFor flavor}/ \
|
||||
--config ${configFile} ${flagsStr}
|
||||
'';
|
||||
};
|
||||
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
in {
|
||||
trust-dns-wan = mkTrustDnsService { listen = [ nativeAddrs."servo.lan" bindOvpn ]; } "wan";
|
||||
trust-dns-lan = mkTrustDnsService { port = 1053; } "lan";
|
||||
trust-dns-hn = mkTrustDnsService { port = 1053; } "hn";
|
||||
trust-dns-hn-resolver = mkTrustDnsService {
|
||||
config = pkgs.writeText "hn-resolver-config.toml" ''
|
||||
# i host a resolver in the wireguard VPN so that clients can resolve DNS through the VPN.
|
||||
# (that's what this file achieves).
|
||||
#
|
||||
# one would expect this resolver could host the authoritative zone for `uninsane.org`, and then forward everything else to the system resolver...
|
||||
# and while that works for `dig`, it breaks for `nslookup` (and so `ssh`, etc).
|
||||
#
|
||||
# DNS responses include a flag for if the responding server is the authority of the zone queried.
|
||||
# it seems that default Linux stub resolvers either:
|
||||
# - expect DNSSEC when the response includes that bit, or
|
||||
# - expect A records to be in the `answer` section instead of `additional` section.
|
||||
# or perhaps something more nuanced. but for `nslookup` to be reliable, it has to talk to an
|
||||
# instance of trust-dns which is strictly a resolver, with no authority.
|
||||
# hence, this config: a resolver which forwards to the actual authority.
|
||||
|
||||
listen_addrs_ipv4 = ["${nativeAddrs."servo.hn"}"]
|
||||
listen_addrs_ipv6 = []
|
||||
|
||||
[[zones]]
|
||||
zone = "uninsane.org"
|
||||
zone_type = "Forward"
|
||||
stores = { type = "forward", name_servers = [{ socket_addr = "${nativeAddrs."servo.hn"}:1053", protocol = "udp", trust_nx_responses = true }] }
|
||||
|
||||
[[zones]]
|
||||
# forward the root zone to the local DNS resolver
|
||||
zone = "."
|
||||
zone_type = "Forward"
|
||||
stores = { type = "forward", name_servers = [{ socket_addr = "127.0.0.53:53", protocol = "udp", trust_nx_responses = true }] }
|
||||
'';
|
||||
} "hn-resolver";
|
||||
};
|
||||
in
|
||||
{
|
||||
wan = {
|
||||
substitutions = mkSubstitutions "wan";
|
||||
listenAddrsIpv4 = [
|
||||
nativeAddrs."servo.lan"
|
||||
bindOvpn
|
||||
];
|
||||
};
|
||||
lan = {
|
||||
substitutions = mkSubstitutions "lan";
|
||||
listenAddrsIpv4 = [ nativeAddrs."servo.lan" ];
|
||||
port = 1053;
|
||||
};
|
||||
hn = {
|
||||
substitutions = mkSubstitutions "hn";
|
||||
listenAddrsIpv4 = [ nativeAddrs."servo.hn" ];
|
||||
port = 1053;
|
||||
};
|
||||
# hn-resolver = {
|
||||
# # don't need %AWAN% here because we forward to the hn instance.
|
||||
# listenAddrsIpv4 = [ nativeAddrs."servo.hn" ];
|
||||
# extraConfig = {
|
||||
# zones = [
|
||||
# {
|
||||
# zone = "uninsane.org";
|
||||
# zone_type = "Forward";
|
||||
# stores = {
|
||||
# type = "forward";
|
||||
# name_servers = [
|
||||
# {
|
||||
# socket_addr = "${nativeAddrs."servo.hn"}:1053";
|
||||
# protocol = "udp";
|
||||
# trust_nx_responses = true;
|
||||
# }
|
||||
# ];
|
||||
# };
|
||||
# }
|
||||
# {
|
||||
# # forward the root zone to the local DNS resolver
|
||||
# zone = ".";
|
||||
# zone_type = "Forward";
|
||||
# stores = {
|
||||
# type = "forward";
|
||||
# name_servers = [
|
||||
# {
|
||||
# socket_addr = "127.0.0.53:53";
|
||||
# protocol = "udp";
|
||||
# trust_nx_responses = true;
|
||||
# }
|
||||
# ];
|
||||
# };
|
||||
# }
|
||||
# ];
|
||||
# };
|
||||
# };
|
||||
};
|
||||
|
||||
sane.services.dyn-dns.restartOnChange = [
|
||||
"trust-dns-wan.service"
|
||||
|
@ -157,3 +207,4 @@ in
|
|||
# "trust-dns-hn-resolver.service" # doesn't need restart because it doesn't know about WAN IP
|
||||
];
|
||||
}
|
||||
]
|
||||
|
|
|
@ -81,14 +81,11 @@ let
|
|||
(fromDb "feeds.simplecast.com/xKJ93w_w" // uncat) # Atlas Obscura
|
||||
(fromDb "feeds.transistor.fm/acquired" // tech)
|
||||
(fromDb "fulltimenix.com" // tech)
|
||||
(fromDb "futureofcoding.org/episodes" // tech)
|
||||
(fromDb "hackerpublicradio.org" // tech)
|
||||
(fromDb "lexfridman.com/podcast" // rat)
|
||||
(fromDb "mapspodcast.libsyn.com" // uncat) # Multidisciplinary Association for Psychedelic Studies
|
||||
(fromDb "microarch.club" // tech)
|
||||
(fromDb "omegataupodcast.net" // tech) # 3/4 German; 1/4 eps are English
|
||||
(fromDb "omny.fm/shows/cool-people-who-did-cool-stuff" // pol) # Maggie Killjoy -- referenced by Cory Doctorow
|
||||
(fromDb "omny.fm/shows/money-stuff-the-podcast") # Matt Levine
|
||||
(fromDb "omny.fm/shows/the-dollop-with-dave-anthony-and-gareth-reynolds") # The Dollop history/comedy
|
||||
(fromDb "originstories.libsyn.com" // uncat)
|
||||
(fromDb "podcast.posttv.com/itunes/post-reports.xml" // pol)
|
||||
|
@ -104,8 +101,6 @@ let
|
|||
(fromDb "sharkbytes.transistor.fm" // tech) # Wireshark Podcast o_0
|
||||
(fromDb "sscpodcast.libsyn.com" // rat) # Astral Codex Ten
|
||||
(fromDb "talesfromthebridge.buzzsprout.com" // tech) # Sci-Fi? has Peter Watts; author of No Moods, Ads or Cutesy Fucking Icons (rifters.com)
|
||||
(fromDb "theamphour.com" // tech)
|
||||
(fromDb "techtalesshow.com" // tech) # Corbin Davenport
|
||||
(fromDb "techwontsave.us" // pol) # rec by Cory Doctorow
|
||||
(fromDb "wakingup.libsyn.com" // pol) # Sam Harris
|
||||
(fromDb "werenotwrong.fireside.fm" // pol)
|
||||
|
@ -129,11 +124,11 @@ let
|
|||
(fromDb "acoup.blog/feed") # history, states. author: <https://historians.social/@bretdevereaux/following>
|
||||
(fromDb "amosbbatto.wordpress.com" // tech)
|
||||
(fromDb "anish.lakhwara.com" // tech)
|
||||
(fromDb "apenwarr.ca/log/rss.php" // tech) # CEO of tailscale
|
||||
(fromDb "applieddivinitystudies.com" // rat)
|
||||
(fromDb "artemis.sh" // tech)
|
||||
(fromDb "ascii.textfiles.com" // tech) # Jason Scott
|
||||
(fromDb "austinvernon.site" // tech)
|
||||
# (fromDb "balajis.com" // pol) # Balaji
|
||||
(fromDb "ben-evans.com/benedictevans" // pol)
|
||||
(fromDb "bitbashing.io" // tech)
|
||||
(fromDb "bitsaboutmoney.com" // uncat)
|
||||
|
@ -144,6 +139,8 @@ let
|
|||
(fromDb "blog.thalheim.io" // tech) # Mic92
|
||||
(fromDb "bunniestudios.com" // tech) # Bunnie Juang
|
||||
(fromDb "capitolhillseattle.com" // pol)
|
||||
# (fromDb "drewdevault.com" // tech)
|
||||
# (fromDb "econlib.org" // pol)
|
||||
(fromDb "edwardsnowden.substack.com" // pol // text)
|
||||
(fromDb "fasterthanli.me" // tech)
|
||||
(fromDb "gwern.net" // rat)
|
||||
|
@ -154,9 +151,9 @@ let
|
|||
(fromDb "interconnected.org/home/feed" // rat) # Matt Webb -- engineering-ish, but dreamy
|
||||
(fromDb "jeffgeerling.com" // tech)
|
||||
(fromDb "jefftk.com" // tech)
|
||||
(fromDb "jwz.org/blog" // tech // pol) # DNA lounge guy, loooong-time blogger
|
||||
(fromDb "kill-the-newsletter.com/feeds/joh91bv7am2pnznv.xml" // pol) # Matt Levine - Money Stuff
|
||||
(fromDb "kosmosghost.github.io/index.xml" // tech)
|
||||
# (fromDb "lesswrong.com" // rat)
|
||||
(fromDb "linmob.net" // tech)
|
||||
(fromDb "lwn.net" // tech)
|
||||
(fromDb "lynalden.com" // pol)
|
||||
|
@ -171,13 +168,13 @@ let
|
|||
(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
|
||||
(fromDb "putanumonit.com" // rat) # mostly dating topics. not advice, or humor, but looking through a social lens
|
||||
(fromDb "richardcarrier.info" // rat)
|
||||
(fromDb "rifters.com/crawl" // uncat) # No Moods, Ads or Cutesy Fucking Icons
|
||||
(fromDb "righto.com" // tech) # Ken Shirriff
|
||||
(fromDb "rootsofprogress.org" // rat) # Jason Crawford
|
||||
(fromDb "samuel.dionne-riel.com" // tech) # SamuelDR
|
||||
(fromDb "sagacioussuricata.com" // tech) # ian (Sanctuary)
|
||||
(fromDb "semiaccurate.com" // tech)
|
||||
(fromDb "sideways-view.com" // rat) # Paul Christiano
|
||||
|
@ -186,40 +183,33 @@ let
|
|||
(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)
|
||||
(fromDb "uninsane.org" // tech)
|
||||
(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 "xn--gckvb8fzb.com" // tech)
|
||||
(mkSubstack "astralcodexten" // rat // daily) # Scott Alexander
|
||||
# (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://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://www.stratechery.com/rss" // pol // weekly) # Ben Thompson
|
||||
|
||||
# (fromDb "balajis.com" // pol) # Balaji
|
||||
# (fromDb "drewdevault.com" // tech)
|
||||
# (fromDb "econlib.org" // pol)
|
||||
# (fromDb "lesswrong.com" // rat)
|
||||
# (fromDb "profectusmag.com" // pol) # some conservative/libertarian think tank
|
||||
# (fromDb "thesideview.co" // uncat) # spiritual journal; RSS items are stubs
|
||||
# (fromDb "theregister.com" // tech)
|
||||
# (fromDb "vitalik.ca" // tech) # moved to vitalik.eth.limo
|
||||
# (fromDb "webcurious.co.uk" // uncat) # link aggregator; defunct?
|
||||
# (mkSubstack "doomberg" // tech // weekly) # articles are all pay-walled
|
||||
# (mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
|
||||
# (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
|
||||
];
|
||||
|
||||
videos = [
|
||||
|
@ -231,20 +221,18 @@ let
|
|||
(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)
|
||||
(fromDb "youtube.com/@TheB1M")
|
||||
(fromDb "youtube.com/@TomScottGo")
|
||||
(fromDb "youtube.com/@Vihart")
|
||||
(fromDb "youtube.com/@Vox")
|
||||
(fromDb "youtube.com/@Vsauce")
|
||||
|
||||
# (fromDb "youtube.com/@rossmanngroup" // pol // tech) # Louis Rossmann
|
||||
];
|
||||
|
||||
images = [
|
||||
(fromDb "catandgirl.com" // img // humor)
|
||||
(fromDb "davidrevoy.com" // img // art)
|
||||
(fromDb "grumpy.website" // img // humor)
|
||||
(fromDb "miniature-calendar.com" // img // art // daily)
|
||||
(fromDb "pbfcomics.com" // img // humor)
|
||||
(fromDb "poorlydrawnlines.com/feed" // img // humor)
|
||||
|
|
|
@ -107,8 +107,7 @@ let
|
|||
ftp = common ++ fuseColin ++ [
|
||||
# "ftpfs_debug=2"
|
||||
"user=colin:ipauth"
|
||||
# connect_timeout=10: casting shows to T.V. fails partway through about half the time
|
||||
"connect_timeout=20"
|
||||
"connect_timeout=10"
|
||||
];
|
||||
};
|
||||
remoteHome = host: {
|
||||
|
@ -226,10 +225,10 @@ lib.mkMerge [
|
|||
(remoteServo "media/Books")
|
||||
(remoteServo "media/collections")
|
||||
# (remoteServo "media/datasets")
|
||||
(remoteServo "media/freeleech")
|
||||
(remoteServo "media/games")
|
||||
(remoteServo "media/Music")
|
||||
(remoteServo "media/Pictures/macros")
|
||||
(remoteServo "media/torrents")
|
||||
(remoteServo "media/Videos")
|
||||
(remoteServo "playground")
|
||||
]
|
||||
|
|
|
@ -80,12 +80,14 @@
|
|||
# - query details with `sudo cpupower frequency-info`
|
||||
powerManagement.cpuFreqGovernor = "ondemand";
|
||||
|
||||
# see: `man logind.conf`
|
||||
# don’t shutdown when power button is short-pressed (commonly done an accident, or by cats).
|
||||
# but do on long-press: useful to gracefully power-off server.
|
||||
services.logind.powerKey = "lock";
|
||||
services.logind.powerKeyLongPress = "poweroff";
|
||||
services.logind.lidSwitch = "lock";
|
||||
services.logind.extraConfig = ''
|
||||
# see: `man logind.conf`
|
||||
# don’t shutdown when power button is short-pressed (commonly done an accident, or by cats).
|
||||
# but do on long-press: useful to gracefully power-off server.
|
||||
HandlePowerKey=lock
|
||||
HandlePowerKeyLongPress=poweroff
|
||||
HandleLidSwitch=lock
|
||||
'';
|
||||
|
||||
# services.snapper.configs = {
|
||||
# root = {
|
||||
|
|
|
@ -3,15 +3,11 @@
|
|||
{
|
||||
# XDG defines things like ~/Desktop, ~/Downloads, etc.
|
||||
# these clutter the home, so i mostly don't use them.
|
||||
# note that several of these are not actually standardized anywhere.
|
||||
# some are even non-conventional, like:
|
||||
# - XDG_PHOTOS_DIR: only works because i patch e.g. megapixels
|
||||
sane.user.fs.".config/user-dirs.dirs".symlink.text = ''
|
||||
XDG_DESKTOP_DIR="$HOME/.xdg/Desktop"
|
||||
XDG_DOCUMENTS_DIR="$HOME/dev"
|
||||
XDG_DOWNLOAD_DIR="$HOME/tmp"
|
||||
XDG_MUSIC_DIR="$HOME/Music"
|
||||
XDG_PHOTOS_DIR="$HOME/Pictures/Photos"
|
||||
XDG_PICTURES_DIR="$HOME/Pictures"
|
||||
XDG_PUBLICSHARE_DIR="$HOME/.xdg/Public"
|
||||
XDG_SCREENSHOTS_DIR="$HOME/Pictures/Screenshots"
|
||||
|
|
|
@ -60,5 +60,8 @@
|
|||
networking.networkmanager.plugins = lib.mkForce [];
|
||||
|
||||
# keyfile.path = where networkmanager should look for connection credentials
|
||||
networking.networkmanager.settings.keyfile.path = "/var/lib/NetworkManager/system-connections";
|
||||
networking.networkmanager.extraConfig = ''
|
||||
[keyfile]
|
||||
path=/var/lib/NetworkManager/system-connections
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -19,15 +19,10 @@
|
|||
#
|
||||
# namespacing:
|
||||
# - each namespace can use a different /etc/resolv.conf to specify different DNS servers (see `firejail --dns=...`)
|
||||
# - nscd breaks namespacing: the host nscd is unaware of the guest's /etc/resolv.conf, and so directs the guest's DNS requests to the host's servers.
|
||||
# - nscd breaks namespacing: the host nscd is unaware of the guest's /etc/resolv.conf, and so direct's the guest's DNS requests to the host's servers.
|
||||
# - this is fixed by either `firejail --blacklist=/var/run/nscd/socket`, or disabling nscd altogether.
|
||||
{ config, lib, ... }:
|
||||
lib.mkMerge [
|
||||
{ lib, ... }:
|
||||
{
|
||||
sane.services.trust-dns.enable = lib.mkDefault config.sane.services.trust-dns.asSystemResolver;
|
||||
sane.services.trust-dns.asSystemResolver = lib.mkDefault true;
|
||||
}
|
||||
(lib.mkIf (!config.sane.services.trust-dns.asSystemResolver) {
|
||||
# use systemd's stub resolver.
|
||||
# /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link).
|
||||
# instead, running the stub resolver on a known address in the root ns lets us rewrite packets
|
||||
|
@ -49,8 +44,7 @@ lib.mkMerge [
|
|||
# stub resolver (just forwards upstream) lives on 127.0.0.54
|
||||
"127.0.0.53"
|
||||
];
|
||||
})
|
||||
{
|
||||
|
||||
# nscd -- the Name Service Caching Daemon -- caches DNS query responses
|
||||
# in a way that's unaware of my VPN routing, so routes are frequently poor against
|
||||
# services which advertise different IPs based on geolocation.
|
||||
|
@ -71,4 +65,3 @@ lib.mkMerge [
|
|||
services.nscd.enable = false;
|
||||
system.nssModules = lib.mkForce [];
|
||||
}
|
||||
]
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
|
||||
# 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.maxBuildCost >= 2) [
|
||||
nix.nixPath = (lib.optionals config.sane.enableSlowPrograms [
|
||||
"nixpkgs=${pkgs.path}"
|
||||
]) ++ [
|
||||
# note the import starts at repo root: this allows `./overlay/default.nix` to access the stuff at the root
|
||||
|
@ -65,10 +65,10 @@
|
|||
|
||||
# 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.maxBuildCost >= 3) {
|
||||
environment.etc."nixos" = lib.mkIf config.sane.enableSlowPrograms {
|
||||
source = ../../..;
|
||||
};
|
||||
environment.etc."nix/registry.json" = lib.mkIf (config.sane.maxBuildCost < 3) {
|
||||
environment.etc."nix/registry.json" = lib.mkIf (!config.sane.enableSlowPrograms) {
|
||||
enable = false;
|
||||
};
|
||||
|
||||
|
|
|
@ -30,8 +30,6 @@
|
|||
});
|
||||
};
|
||||
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
|
|
|
@ -63,7 +63,6 @@ in
|
|||
"jq"
|
||||
"killall"
|
||||
"less"
|
||||
"lftp"
|
||||
# "libcap_ng" # for `netcap`
|
||||
"lsof"
|
||||
# "miniupnpc"
|
||||
|
@ -90,7 +89,6 @@ in
|
|||
"tree"
|
||||
"usbutils" # lsusb
|
||||
"util-linux" # lsblk, lscpu, etc
|
||||
"valgrind"
|
||||
"wget"
|
||||
"wirelesstools" # iwlist
|
||||
# "xq" # jq for XML
|
||||
|
@ -110,6 +108,7 @@ in
|
|||
# - debugging?
|
||||
consoleUtils = declPackageSet [
|
||||
"alsaUtils" # for aplay, speaker-test
|
||||
"strings"
|
||||
# "cdrtools"
|
||||
# "clinfo"
|
||||
# "dmidecode"
|
||||
|
@ -138,7 +137,6 @@ in
|
|||
"nmon"
|
||||
# "node2nix"
|
||||
# "oathToolkit" # for oathtool
|
||||
"objdump"
|
||||
# "ponymix"
|
||||
"pulsemixer"
|
||||
"python3-repl"
|
||||
|
@ -151,7 +149,6 @@ in
|
|||
"sops" # for manually viewing secrets; outside `sane-secrets` (TODO: improve sane-secrets!)
|
||||
"speedtest-cli"
|
||||
# "ssh-to-age"
|
||||
"strings"
|
||||
"sudo"
|
||||
# "tageditor" # music tagging
|
||||
# "unar"
|
||||
|
@ -176,7 +173,6 @@ in
|
|||
"ffmpeg"
|
||||
"go2tv" # cast videos to UPNP/DLNA device (i.e. tv).
|
||||
"imagemagick"
|
||||
"sane-cast" # cast videos to UPNP/DLNA, with compatibility
|
||||
"sox"
|
||||
"yt-dlp"
|
||||
];
|
||||
|
@ -215,7 +211,6 @@ in
|
|||
|
||||
backblaze-b2 = {};
|
||||
|
||||
blanket.buildCost = 1;
|
||||
blanket.sandbox.method = "bwrap";
|
||||
blanket.sandbox.whitelistAudio = true;
|
||||
# blanket.sandbox.whitelistDbus = [ "user" ]; # TODO: untested
|
||||
|
@ -268,14 +263,13 @@ in
|
|||
ddrescue.sandbox.method = "landlock"; # TODO:sandbox: untested
|
||||
ddrescue.sandbox.autodetectCliPaths = "existingOrParent";
|
||||
|
||||
delfin.buildCost = 1;
|
||||
# auth token, preferences
|
||||
delfin.sandbox.method = "bwrap";
|
||||
delfin.sandbox.whitelistAudio = true;
|
||||
delfin.sandbox.whitelistDbus = [ "user" ]; # else `mpris` plugin crashes the player
|
||||
delfin.sandbox.whitelistDri = true;
|
||||
delfin.sandbox.whitelistWayland = true;
|
||||
delfin.sandbox.net = "clearnet";
|
||||
# auth token, preferences
|
||||
delfin.persist.byStore.private = [ ".config/delfin" ];
|
||||
|
||||
dig.sandbox.method = "bwrap";
|
||||
|
@ -304,6 +298,10 @@ in
|
|||
dtc.sandbox.method = "bwrap";
|
||||
dtc.sandbox.autodetectCliPaths = true; # TODO:sandbox: untested
|
||||
|
||||
dtrx.sandbox.method = "bwrap";
|
||||
dtrx.sandbox.whitelistPwd = true;
|
||||
dtrx.sandbox.autodetectCliPaths = "existing"; #< for the archive
|
||||
|
||||
duplicity = {};
|
||||
|
||||
e2fsprogs.sandbox.method = "landlock";
|
||||
|
@ -316,13 +314,11 @@ in
|
|||
|
||||
eg25-control = {};
|
||||
|
||||
electrum.buildCost = 1;
|
||||
electrum.sandbox.method = "bwrap"; # TODO:sandbox: untested
|
||||
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.buildCost = 1;
|
||||
endless-sky.persist.byStore.plaintext = [ ".local/share/endless-sky" ];
|
||||
endless-sky.sandbox.method = "bwrap";
|
||||
endless-sky.sandbox.whitelistAudio = true;
|
||||
|
@ -361,7 +357,6 @@ in
|
|||
".persist/plaintext"
|
||||
];
|
||||
|
||||
ffmpeg.buildCost = 1;
|
||||
ffmpeg.sandbox.method = "bwrap";
|
||||
ffmpeg.sandbox.autodetectCliPaths = "existingFileOrParent"; # it outputs uncreated files -> parent dir needs mounting
|
||||
|
||||
|
@ -379,9 +374,7 @@ in
|
|||
|
||||
fluffychat-moby.persist.byStore.plaintext = [ ".local/share/chat.fluffy.fluffychat" ];
|
||||
|
||||
font-manager.buildCost = 1;
|
||||
font-manager.sandbox.method = "bwrap";
|
||||
font-manager.sandbox.whitelistWayland = true;
|
||||
font-manager.packageUnwrapped = pkgs.rmDbusServicesInPlace (pkgs.font-manager.override {
|
||||
# build without the "Google Fonts" integration feature, to save closure / avoid webkitgtk_4_0
|
||||
withWebkit = false;
|
||||
|
@ -389,7 +382,7 @@ in
|
|||
|
||||
forkstat.sandbox.method = "landlock"; #< doesn't seem to support bwrap
|
||||
forkstat.sandbox.extraConfig = [
|
||||
"--sanebox-keep-namespace" "pid"
|
||||
"--sane-sandbox-keep-namespace" "pid"
|
||||
];
|
||||
forkstat.sandbox.extraPaths = [
|
||||
"/proc"
|
||||
|
@ -416,7 +409,6 @@ in
|
|||
# TODO: we can populate gh's stuff statically; it even lets us use the same oauth across machines
|
||||
gh.persist.byStore.private = [ ".config/gh" ];
|
||||
|
||||
gimp.buildCost = 1;
|
||||
gimp.sandbox.method = "bwrap";
|
||||
gimp.sandbox.whitelistX = true;
|
||||
gimp.sandbox.whitelistWayland = true;
|
||||
|
@ -436,39 +428,32 @@ in
|
|||
"/tmp" # "Cannot open display:" if it can't mount /tmp 👀
|
||||
];
|
||||
|
||||
"gnome.gnome-calculator".buildCost = 1;
|
||||
"gnome.gnome-calculator".sandbox.method = "bwrap";
|
||||
"gnome.gnome-calculator".sandbox.whitelistWayland = true;
|
||||
|
||||
"gnome.gnome-calendar".buildCost = 1;
|
||||
# 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.whitelistWayland = true;
|
||||
|
||||
"gnome.gnome-clocks".sandbox.method = "bwrap";
|
||||
"gnome.gnome-clocks".sandbox.whitelistWayland = true;
|
||||
"gnome.gnome-clocks".suggestedPrograms = [ "dconf" ];
|
||||
|
||||
# gnome-disks
|
||||
"gnome.gnome-disk-utility".buildCost = 1;
|
||||
"gnome.gnome-disk-utility".sandbox.method = "bwrap";
|
||||
"gnome.gnome-disk-utility".sandbox.whitelistDbus = [ "system" ];
|
||||
"gnome.gnome-disk-utility".sandbox.whitelistWayland = true;
|
||||
"gnome.gnome-disk-utility".sandbox.extraHomePaths = [
|
||||
"tmp"
|
||||
"use/iso"
|
||||
# TODO: probably need /dev and such
|
||||
];
|
||||
|
||||
# seahorse: dump gnome-keyring secrets.
|
||||
"gnome.seahorse".buildCost = 1;
|
||||
# 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.whitelistDbus = [ "user" ];
|
||||
"gnome.seahorse".sandbox.whitelistWayland = true;
|
||||
|
||||
gnome-2048.buildCost = 1;
|
||||
gnome-2048.sandbox.method = "bwrap";
|
||||
gnome-2048.sandbox.whitelistWayland = true;
|
||||
gnome-2048.persist.byStore.plaintext = [ ".local/share/gnome-2048/scores" ];
|
||||
|
||||
gnome-frog.buildCost = 1;
|
||||
gnome-frog.sandbox.method = "bwrap";
|
||||
gnome-frog.sandbox.whitelistWayland = true;
|
||||
gnome-frog.sandbox.whitelistDbus = [ "user" ];
|
||||
|
@ -495,7 +480,6 @@ in
|
|||
# 1. no number may appear unshaded more than once in the same row/column
|
||||
# 2. no two shaded tiles can be direct N/S/E/W neighbors
|
||||
# - win once (1) and (2) are satisfied
|
||||
"gnome.hitori".buildCost = 1;
|
||||
"gnome.hitori".sandbox.method = "bwrap";
|
||||
"gnome.hitori".sandbox.whitelistWayland = true;
|
||||
|
||||
|
@ -525,7 +509,6 @@ in
|
|||
grim.sandbox.autodetectCliPaths = "existingOrParent";
|
||||
grim.sandbox.whitelistWayland = true;
|
||||
|
||||
hase.buildCost = 1;
|
||||
hase.sandbox.method = "bwrap";
|
||||
hase.sandbox.net = "clearnet";
|
||||
hase.sandbox.whitelistAudio = true;
|
||||
|
@ -546,7 +529,6 @@ in
|
|||
# N.B.: inetutils' `ping` is shadowed by iputils' ping (by nixos, intentionally).
|
||||
inetutils.sandbox.method = "landlock"; # want to keep the same netns, at least.
|
||||
|
||||
inkscape.buildCost = 1;
|
||||
inkscape.sandbox.method = "bwrap";
|
||||
inkscape.sandbox.whitelistWayland = true;
|
||||
inkscape.sandbox.extraHomePaths = [
|
||||
|
@ -598,7 +580,6 @@ in
|
|||
"/proc"
|
||||
];
|
||||
|
||||
krita.buildCost = 1;
|
||||
krita.sandbox.method = "bwrap";
|
||||
krita.sandbox.whitelistWayland = true;
|
||||
krita.sandbox.autodetectCliPaths = "existing";
|
||||
|
@ -619,7 +600,6 @@ in
|
|||
libnotify.sandbox.method = "bwrap";
|
||||
libnotify.sandbox.whitelistDbus = [ "user" ]; # notify-send
|
||||
|
||||
losslesscut-bin.buildCost = 1;
|
||||
losslesscut-bin.sandbox.method = "bwrap";
|
||||
losslesscut-bin.sandbox.extraHomePaths = [
|
||||
"Music"
|
||||
|
@ -635,7 +615,6 @@ 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.capabilities = [ "dac_override" "sys_ptrace" ];
|
||||
|
||||
lua = {};
|
||||
|
||||
|
@ -644,7 +623,6 @@ in
|
|||
mercurial.sandbox.whitelistPwd = true;
|
||||
|
||||
# actual monero blockchain (not wallet/etc; safe to delete, just slow to regenerate)
|
||||
monero-gui.buildCost = 1;
|
||||
# XXX: is it really safe to persist this? it doesn't have info that could de-anonymize if captured?
|
||||
monero-gui.persist.byStore.plaintext = [ ".bitmonero" ];
|
||||
monero-gui.sandbox.method = "bwrap";
|
||||
|
@ -653,7 +631,6 @@ in
|
|||
"records/finance/cryptocurrencies/monero"
|
||||
];
|
||||
|
||||
mumble.buildCost = 1;
|
||||
mumble.persist.byStore.private = [ ".local/share/Mumble" ];
|
||||
|
||||
nano.sandbox.method = "bwrap";
|
||||
|
@ -744,7 +721,7 @@ in
|
|||
# procps: free, pgrep, pidof, pkill, ps, pwait, top, uptime, couple others
|
||||
procps.sandbox.method = "bwrap";
|
||||
procps.sandbox.extraConfig = [
|
||||
"--sanebox-keep-namespace" "pid"
|
||||
"--sane-sandbox-keep-namespace" "pid"
|
||||
];
|
||||
|
||||
pstree.sandbox.method = "landlock";
|
||||
|
@ -757,14 +734,11 @@ in
|
|||
pulsemixer.sandbox.method = "landlock";
|
||||
pulsemixer.sandbox.whitelistAudio = true;
|
||||
|
||||
pwvucontrol.buildCost = 1;
|
||||
pwvucontrol.sandbox.method = "bwrap";
|
||||
pwvucontrol.sandbox.whitelistAudio = true;
|
||||
pwvucontrol.sandbox.whitelistDri = true; # else perf on moby is unusable
|
||||
pwvucontrol.sandbox.whitelistWayland = true;
|
||||
|
||||
python3-repl.packageUnwrapped = pkgs.python3.withPackages (ps: with ps; [
|
||||
psutil
|
||||
requests
|
||||
]);
|
||||
python3-repl.sandbox.method = "bwrap";
|
||||
|
@ -775,7 +749,7 @@ in
|
|||
];
|
||||
|
||||
qemu.sandbox.enable = false; #< it's a launcher
|
||||
qemu.buildCost = 2;
|
||||
qemu.slowToBuild = true;
|
||||
|
||||
rsync.sandbox.method = "bwrap";
|
||||
rsync.sandbox.net = "clearnet";
|
||||
|
@ -783,9 +757,10 @@ in
|
|||
|
||||
rustc = {};
|
||||
|
||||
sane-cast = {}; #< TODO: sandbox this the same way i sandbox go2tv
|
||||
|
||||
sane-die-with-parent.sandbox.enable = false; #< it's a launcher; can't sandbox
|
||||
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
|
||||
|
||||
|
@ -793,7 +768,6 @@ in
|
|||
sequoia.sandbox.whitelistPwd = true;
|
||||
sequoia.sandbox.autodetectCliPaths = true;
|
||||
|
||||
shattered-pixel-dungeon.buildCost = 1;
|
||||
shattered-pixel-dungeon.persist.byStore.plaintext = [ ".local/share/.shatteredpixel/shattered-pixel-dungeon" ];
|
||||
shattered-pixel-dungeon.sandbox.method = "bwrap";
|
||||
shattered-pixel-dungeon.sandbox.whitelistAudio = true;
|
||||
|
@ -801,7 +775,6 @@ in
|
|||
shattered-pixel-dungeon.sandbox.whitelistWayland = true;
|
||||
|
||||
# printer/filament settings
|
||||
slic3r.buildCost = 1;
|
||||
slic3r.persist.byStore.plaintext = [ ".Slic3r" ];
|
||||
|
||||
slurp.sandbox.method = "bwrap";
|
||||
|
@ -816,13 +789,12 @@ in
|
|||
sops.sandbox.method = "bwrap"; # TODO:sandbox: untested
|
||||
sops.sandbox.extraHomePaths = [
|
||||
".config/sops"
|
||||
"nixos"
|
||||
"dev/nixos"
|
||||
# TODO: sops should only need access to knowledge/secrets,
|
||||
# except that i currently put its .sops.yaml config in the root of ~/knowledge
|
||||
"knowledge"
|
||||
];
|
||||
|
||||
soundconverter.buildCost = 1;
|
||||
soundconverter.sandbox.method = "bwrap";
|
||||
soundconverter.sandbox.whitelistWayland = true;
|
||||
soundconverter.sandbox.extraHomePaths = [
|
||||
|
@ -840,7 +812,6 @@ in
|
|||
sox.sandbox.autodetectCliPaths = "existingFileOrParent";
|
||||
sox.sandbox.whitelistAudio = true;
|
||||
|
||||
space-cadet-pinball.buildCost = 1;
|
||||
space-cadet-pinball.persist.byStore.plaintext = [ ".local/share/SpaceCadetPinball" ];
|
||||
space-cadet-pinball.sandbox.method = "bwrap";
|
||||
space-cadet-pinball.sandbox.whitelistAudio = true;
|
||||
|
@ -861,7 +832,6 @@ in
|
|||
subversion.sandbox.whitelistPwd = true;
|
||||
sudo.sandbox.enable = false;
|
||||
|
||||
superTux.buildCost = 1;
|
||||
superTux.sandbox.method = "bwrap";
|
||||
superTux.sandbox.wrapperType = "inplace"; # package Makefile incorrectly installs to $out/games/superTux instead of $out/share/games
|
||||
superTux.sandbox.whitelistAudio = true;
|
||||
|
@ -880,14 +850,12 @@ in
|
|||
|
||||
tdesktop.persist.byStore.private = [ ".local/share/TelegramDesktop" ];
|
||||
|
||||
tokodon.buildCost = 1;
|
||||
tokodon.persist.byStore.private = [ ".cache/KDE/tokodon" ];
|
||||
|
||||
tree.sandbox.method = "landlock";
|
||||
tree.sandbox.autodetectCliPaths = true;
|
||||
tree.sandbox.whitelistPwd = true;
|
||||
|
||||
tumiki-fighters.buildCost = 1;
|
||||
tumiki-fighters.sandbox.method = "bwrap";
|
||||
tumiki-fighters.sandbox.whitelistAudio = true;
|
||||
tumiki-fighters.sandbox.whitelistDri = true; #< not strictly necessary, but triples CPU perf
|
||||
|
@ -906,16 +874,12 @@ in
|
|||
"/sys/bus/usb"
|
||||
];
|
||||
|
||||
valgrind.buildCost = 1;
|
||||
valgrind.sandbox.enable = false; #< it's a launcher: can't sandbox
|
||||
|
||||
visidata.sandbox.method = "bwrap"; # TODO:sandbox: untested
|
||||
visidata.sandbox.autodetectCliPaths = true;
|
||||
|
||||
# `vulkaninfo`, `vkcube`
|
||||
vulkan-tools.sandbox.method = "landlock";
|
||||
|
||||
vvvvvv.buildCost = 1;
|
||||
vvvvvv.sandbox.method = "bwrap";
|
||||
vvvvvv.sandbox.whitelistAudio = true;
|
||||
vvvvvv.sandbox.whitelistDri = true; #< playable without, but burns noticably more CPU
|
||||
|
@ -936,7 +900,6 @@ in
|
|||
wget.sandbox.net = "all";
|
||||
wget.sandbox.whitelistPwd = true; # saves to pwd by default
|
||||
|
||||
whalebird.buildCost = 1;
|
||||
whalebird.persist.byStore.private = [ ".config/Whalebird" ];
|
||||
|
||||
# `wg`, `wg-quick`
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
};
|
||||
};
|
||||
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
#!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
|
||||
|
||||
|
@ -13,9 +16,53 @@ logger = logging.getLogger(__name__)
|
|||
# map from known devices -> required flags
|
||||
DEVICE_MAP = {
|
||||
"Theater TV": [],
|
||||
"Cuddlevision": [ "-usewav" ],
|
||||
"[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
|
||||
|
@ -49,6 +96,8 @@ class BlastDriver:
|
|||
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 = []
|
||||
|
@ -153,11 +202,15 @@ 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()
|
||||
|
|
|
@ -42,9 +42,9 @@ in
|
|||
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.
|
||||
"--sanebox-keep-namespace" "pid"
|
||||
"--sane-sandbox-keep-namespace" "pid"
|
||||
];
|
||||
suggestedPrograms = [ "blast-ugjka" "sane-die-with-parent" ];
|
||||
suggestedPrograms = [ "blast-ugjka" ];
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf cfg.enabled [ 9000 ];
|
||||
|
|
|
@ -99,12 +99,18 @@ in
|
|||
type = types.listOf transitionType;
|
||||
default = [];
|
||||
};
|
||||
configFile = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.writeText "bonsai_tree.json" (builtins.toJSON cfg.config.transitions);
|
||||
description = ''
|
||||
configuration file to pass to bonsai.
|
||||
usually auto-generated from the sibling options; exposed mainly for debugging or convenience.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fs.".config/bonsai/bonsai_tree.json".symlink.text = builtins.toJSON cfg.config.transitions;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.extraRuntimePaths = [
|
||||
"/" #< just needs "bonsai", but needs to create it first...
|
||||
|
@ -113,8 +119,7 @@ in
|
|||
services.bonsaid = {
|
||||
description = "bonsai: programmable input dispatcher";
|
||||
partOf = [ "graphical-session" ];
|
||||
# nice -n -11 chosen arbitrarily. i hope this will allow for faster response to inputs, but without audio underruns (pipewire is -21, dino -15-ish)
|
||||
command = "nice -n -11 bonsaid -t $HOME/.config/bonsai/bonsai_tree.json";
|
||||
command = "bonsaid -t ${cfg.config.configFile}";
|
||||
cleanupCommand = "rm -f $XDG_RUNTIME_DIR/bonsai";
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
# https://gitlab.com/mobian1/callaudiod
|
||||
# note: on desko it's expected that callaudiod doesn't really achieve anything (i.e. no mic muting).
|
||||
# - "Card 'alsa_card.pci-0000_0b_00.1' lacks speaker and/or earpiece port, skipping"
|
||||
# - "callaudiod-pulse-CRITICAL **: 07:45:48.092: No suitable card found, stopping here..."
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.callaudiod = {
|
||||
packageUnwrapped = pkgs.rmDbusServices pkgs.callaudiod;
|
||||
|
||||
services.callaudiod = {
|
||||
description = "callaudiod: dbus service to switch audio profiles and mute microphone";
|
||||
partOf = [ "default" ];
|
||||
command = "callaudiod";
|
||||
};
|
||||
};
|
||||
}
|
|
@ -7,14 +7,14 @@
|
|||
# - message @cheogram.com "reset sip account" (this is not destructive, despite the name)
|
||||
# - the bot will reply with auto-generated username/password plus a SIP server endpoint.
|
||||
# just copy those into gnome-calls' GUI configurator
|
||||
# - now gnome-calls can do outbound calls. inbound calls can be routed by messaging the bot: "configure calls"
|
||||
# - now gnome-calls can do outbound calls. inbound calls requires more chatting with the help bot
|
||||
#
|
||||
# my setup here is still very WIP.
|
||||
# open questions:
|
||||
# - can i receive calls even with GUI closed?
|
||||
# - e.g. activated by callaudiod?
|
||||
# - looks like `gnome-calls --daemon` does that?
|
||||
{ config, lib, pkgs, ... }:
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.calls;
|
||||
in
|
||||
|
@ -25,40 +25,20 @@ in
|
|||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
packageUnwrapped = pkgs.calls.overrideAttrs (upstream: {
|
||||
patches = (upstream.patches or []) ++ [
|
||||
(pkgs.fetchpatch {
|
||||
# usability improvement... if the UI is visible, then i can receive calls. otherwise, i can't!
|
||||
url = "https://git.uninsane.org/colin/gnome-calls/commit/a19166d85927e59662fae189a780eed18bf876ce.patch";
|
||||
name = "exit on close (i.e. never daemonize)";
|
||||
hash = "sha256-NoVQV2TlkCcsBt0uwSyK82hBKySUW4pADrJVfLFvWgU=";
|
||||
})
|
||||
];
|
||||
});
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.net = "clearnet";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistDbus = [ "user" ]; # necessary for secrets, at the minimum
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
persist.byStore.private = [
|
||||
# ".cache/folks" # contact avatars?
|
||||
# ".config/calls"
|
||||
".local/share/calls" # call "records"
|
||||
# .local/share/folks # contacts?
|
||||
];
|
||||
# this is only the username/endpoint: the actual password appears to be stored in gnome-keyring
|
||||
secrets.".config/calls/sip-account.cfg" = ../../../secrets/common/gnome_calls_sip-account.cfg.bin;
|
||||
suggestedPrograms = [
|
||||
"callaudiod" # runtime dependency (optional, but probably needed for mic muting?)
|
||||
"feedbackd" # needs `phone-incoming-call`, in particular
|
||||
"gnome-keyring" # to remember the password
|
||||
];
|
||||
|
||||
services.gnome-calls = {
|
||||
|
@ -66,12 +46,10 @@ in
|
|||
description = "gnome-calls daemon to monitor incoming SIP calls";
|
||||
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
|
||||
# add --verbose for more debugging
|
||||
# add --daemon to avoid showing UI on launch.
|
||||
# note that no matter the flags, it returns to being a daemon whenever the UI is manually closed,
|
||||
# revealed when launched.
|
||||
# default latency is 10ms, which is too low and i get underruns on moby.
|
||||
# 50ms is copied from dino, not at all tuned.
|
||||
command = "env G_MESSAGES_DEBUG=all PULSE_LATENCY_MSEC=50 gnome-calls";
|
||||
command = "env G_MESSAGES_DEBUG=all gnome-calls --daemon";
|
||||
};
|
||||
};
|
||||
programs.calls = lib.mkIf cfg.enabled {
|
||||
enable = true;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
{ ... }:
|
||||
{
|
||||
sane.programs.celeste64 = {
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistDri = true;
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
'';
|
||||
});
|
||||
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap"; # landlock gives: _multiprocessing.SemLock: Permission Denied
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistDbus = [ "user" ]; # mpris
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
# docs: <https://wiki.nixos.org/wiki/Printing>
|
||||
# to add a printer:
|
||||
# 1. <http://localhost:631/admin/>
|
||||
# 2. click "find new printers" and follow prompts
|
||||
# - prefer to use the "Generic IPP Everywhere Printer" driver
|
||||
# alternatively, add/modify printers by running
|
||||
# - `system-config-printer`
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.cups;
|
||||
in
|
||||
{
|
||||
sane.programs.cups = {
|
||||
suggestedPrograms = [
|
||||
"system-config-printer"
|
||||
];
|
||||
};
|
||||
sane.programs.system-config-printer = {};
|
||||
|
||||
services.printing = lib.mkIf cfg.enabled {
|
||||
enable = true;
|
||||
startWhenNeeded = false; #< a.k.a. socket activated?
|
||||
# webInterface = false;
|
||||
# logLevel = "debug"; # default: "info"
|
||||
# extraConfig = "<lines ... >";
|
||||
# drivers = [ <cups driver packages...> ]
|
||||
};
|
||||
# services.avahi = lib.mkIf cfg.enabled {
|
||||
# # only needed for wireless printing
|
||||
# enable = true;
|
||||
# nssmdns4 = true;
|
||||
# openFirewall = true;
|
||||
# };
|
||||
}
|
|
@ -21,15 +21,5 @@
|
|||
ln -s curlftpfs $out/bin/mount.curlftpfs
|
||||
'';
|
||||
});
|
||||
|
||||
# TODO: try to sandbox this better? maybe i can have fuse (unsandboxed) invoke curlftpfs (sandboxed)?
|
||||
# - landlock gives EPERM
|
||||
# - bwrap just silently doesn't mount it, maybe because of setuid stuff around fuse?
|
||||
# sandbox.method = "capshonly";
|
||||
# sandbox.net = "all";
|
||||
# sandbox.capabilities = [
|
||||
# "sys_admin"
|
||||
# "sys_module"
|
||||
# ];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
./bonsai.nix
|
||||
./brave.nix
|
||||
./bubblewrap.nix
|
||||
./callaudiod.nix
|
||||
./calls.nix
|
||||
./cantata.nix
|
||||
./catt.nix
|
||||
|
@ -22,15 +21,12 @@
|
|||
./chatty.nix
|
||||
./conky
|
||||
./cozy.nix
|
||||
./cups.nix
|
||||
./curlftpfs.nix
|
||||
./dconf.nix
|
||||
./deadd-notification-center
|
||||
./dialect.nix
|
||||
./dino.nix
|
||||
./dissent.nix
|
||||
./dtrx.nix
|
||||
./eg25-control.nix
|
||||
./element-desktop.nix
|
||||
./engrampa.nix
|
||||
./epiphany.nix
|
||||
|
@ -49,7 +45,6 @@
|
|||
./gdbus.nix
|
||||
./geary.nix
|
||||
./git.nix
|
||||
./gnome-clocks.nix
|
||||
./gnome-feeds.nix
|
||||
./gnome-keyring
|
||||
./gnome-maps.nix
|
||||
|
@ -58,7 +53,6 @@
|
|||
./gpodder.nix
|
||||
./grimshot.nix
|
||||
./gthumb.nix
|
||||
./gvfs.nix
|
||||
./handbrake.nix
|
||||
./helix.nix
|
||||
./htop
|
||||
|
@ -68,7 +62,6 @@
|
|||
./komikku.nix
|
||||
./koreader
|
||||
./less.nix
|
||||
./lftp.nix
|
||||
./libreoffice.nix
|
||||
./lemoa.nix
|
||||
./loupe.nix
|
||||
|
@ -76,20 +69,17 @@
|
|||
./megapixels.nix
|
||||
./mepo.nix
|
||||
./mimeo
|
||||
./modemmanager.nix
|
||||
./mopidy.nix
|
||||
./mpv
|
||||
./msmtp.nix
|
||||
./nautilus.nix
|
||||
./neovim.nix
|
||||
./networkmanager.nix
|
||||
./newsflash.nix
|
||||
./nheko.nix
|
||||
./nicotine-plus.nix
|
||||
./nix-index.nix
|
||||
./notejot.nix
|
||||
./ntfy-sh.nix
|
||||
./objdump.nix
|
||||
./obsidian.nix
|
||||
./offlineimap.nix
|
||||
./open-in-mpv.nix
|
||||
|
@ -100,13 +90,10 @@
|
|||
./rhythmbox.nix
|
||||
./ripgrep.nix
|
||||
./rofi
|
||||
./rtkit.nix
|
||||
./s6-rc.nix
|
||||
./sane-input-handler
|
||||
./sane-open.nix
|
||||
./sane-screenshot.nix
|
||||
./sane-scripts.nix
|
||||
./sanebox.nix
|
||||
./schlock.nix
|
||||
./sfeed.nix
|
||||
./signal-desktop.nix
|
||||
|
@ -135,7 +122,6 @@
|
|||
./wine.nix
|
||||
./wireplumber.nix
|
||||
./wireshark.nix
|
||||
./wpa_supplicant.nix
|
||||
./wvkbd.nix
|
||||
./xarchiver.nix
|
||||
./xdg-desktop-portal.nix
|
||||
|
@ -146,7 +132,6 @@
|
|||
./zathura.nix
|
||||
./zeal.nix
|
||||
./zecwallet-lite.nix
|
||||
./zulip.nix
|
||||
./zsh
|
||||
];
|
||||
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.dialect = {
|
||||
sandbox.method = "bwrap";
|
||||
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: {
|
||||
# TODO: send upstream
|
||||
# TODO: figure out how to get audio working
|
||||
|
@ -9,17 +18,5 @@
|
|||
pkgs.glib-networking # for TLS
|
||||
];
|
||||
});
|
||||
|
||||
suggestedPrograms = [ "dconf" ]; #< to persist settings
|
||||
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
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
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -22,17 +22,17 @@
|
|||
# - 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)
|
||||
# - on lappy/desktop, right-clicking the mic button allows to toggle audio devices, but impossible to trigger this on moby/touch screen!
|
||||
# - TODO: dino should have more optimal niceness/priority to ensure it can process its buffers
|
||||
# - possibly this is solved by enabling RealtimeKit (rtkit)
|
||||
# - TODO: see if Dino calls work better with `echo full > /sys/kernel/debug/sched/preempt`
|
||||
#
|
||||
# probably fixed:
|
||||
# - once per 1-2 minutes dino will temporarily drop mic input:
|
||||
# - `rtp-WARNING: plugin.vala:148: Warning in pipeline: Can't record audio fast enough
|
||||
# - `rtp-WRNING: plugin.vala:148: Warning in pipeline: Can't record audio fast enough
|
||||
# - this was *partially* fixed by bumping the pipewire mic buffer to 2048 samples (from ~512)
|
||||
# - this was further fixed by setting PULSE_LATENCY_MSEC=20.
|
||||
# - 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.
|
||||
# - dino *should* be able to use Pipewire directly for calls instead of going through pulse, but had trouble achieving that in actuality
|
||||
#
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
|
@ -50,25 +50,16 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
packageUnwrapped = (pkgs.dino.override {
|
||||
# XXX(2024/04/24): build without echo cancelation (i.e. force WITH_VOICE_PROCESSOR to be undefined).
|
||||
# this means that if the other end of the call is on speaker phone, i'm liable to hear my own voice
|
||||
# leave their speaker, enter their mic, and then return to me.
|
||||
# the benefit is a >50% reduction in CPU use. insignificant on any modern PC; make-or-break on a low-power Pinephone.
|
||||
webrtc-audio-processing = null;
|
||||
}).overrideAttrs (upstream: {
|
||||
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-28";
|
||||
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 = "657502955567dd538e56f300e075c7db52e25d74";
|
||||
hash = "sha256-SApJy9FgxxLOB5A/zGtpdFZtSqSiS03vggRrCte1tFE=";
|
||||
rev = "d9fa4daa6a7d16f5f0e2183a77ee2d07849dd9f3";
|
||||
hash = "sha256-vJBIMsMLlK8Aw19fD2aFNtegXkjOqEgb3m1hi3fE5DE=";
|
||||
};
|
||||
# avoid double-application of upstreamed patches
|
||||
# https://github.com/NixOS/nixpkgs/pull/309265
|
||||
patches = [];
|
||||
checkPhase = ''
|
||||
runHook preCheck
|
||||
./xmpp-vala-test
|
||||
|
@ -105,29 +96,17 @@ in
|
|||
# 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 (by default), but this env var supports only whole numbers, which isn't quite reconcilable:
|
||||
# - 1024/48000 = 21.33ms
|
||||
# - 2048/48000 = 42.67ms
|
||||
# - 4096/48000 = 85.33ms
|
||||
# also, Dino's likely still doing things in 10ms batches internally.
|
||||
#
|
||||
# note that this number supposedly is just the buffer size which Dino asks Pulse (pipewire) to share with it.
|
||||
# in theory, it's equivalent to adjusting pipewire's quanta setting, and so isn't additive to the existing pipewire buffers.
|
||||
# (and would also be overriden by pipewire's quanta.min setting).
|
||||
# but in practice, setting this seems to have some more effect beyond just the buffer sizes visible in `pw-top`.
|
||||
# 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 (and seems to have very little impact in practice anyway).
|
||||
# buffer size:
|
||||
# - 1024 (PULSE_LATENCY_MSEC=20): `pw-top` shows several underruns per second.
|
||||
# - 2048 (PULSE_LATENCY_MSEC=50): `pw-top` shows very few underruns: maybe 1-5 per minute. with voice processor disabled, this works well. with it enabled, i still get gaps in which the mic "disappears".
|
||||
# - 4096 (PULSE_LATENCY_MSEC=100): `pw-top` shows 0 underruns. with voice processor disabled, i seem to be permanently muted. with it enabled, this works well.
|
||||
# nice -n -15 chosen arbitrarily; not optimized
|
||||
#
|
||||
# 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=50 nice -n -15 dino";
|
||||
command = "env PULSE_LATENCY_MSEC=20 nice -n -15 dino";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.dtrx = {
|
||||
packageUnwrapped = pkgs.dtrx.override {
|
||||
# `binutils` is the nix wrapper, which reads nix-related env vars
|
||||
# before passing on to e.g. `ld`.
|
||||
# dtrx probably only needs `ar` at runtime, not even `ld`.
|
||||
binutils = pkgs.binutils-unwrapped;
|
||||
# build without rpm support, since `rpm` package doesn't cross-compile.
|
||||
rpm = null;
|
||||
};
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistPwd = true;
|
||||
sandbox.autodetectCliPaths = "existing"; #< for the archive
|
||||
};
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.eg25-control;
|
||||
in
|
||||
{
|
||||
sane.programs.eg25-control = {
|
||||
suggestedPrograms = [ "modemmanager" ];
|
||||
|
||||
services.eg25-control-powered = {
|
||||
description = "eg25-control-powered: power to the Qualcomm eg25 modem used by PinePhone";
|
||||
startCommand = "eg25-control --power-on --verbose";
|
||||
cleanupCommand = "eg25-control --power-off --verbose";
|
||||
# depends = [ "ModemManager" ]
|
||||
};
|
||||
|
||||
services.eg25-control-gps = {
|
||||
# TODO: separate almanac upload from GPS enablement
|
||||
# - don't want to re-upload the almanac everytime the GPS is toggled
|
||||
# - want to upload almanac even when GPS *isn't* enabled, if we have internet connection.
|
||||
description = "eg25-control-gps: background GPS tracking";
|
||||
startCommand = "eg25-control --enable-gps --dump-debug-info --verbose";
|
||||
cleanupCommand = "eg25-control --disable-gps --dump-debug-info --verbose";
|
||||
depends = [ "eg25-control-powered" ];
|
||||
};
|
||||
};
|
||||
|
||||
# TODO: port to s6
|
||||
systemd.services.eg25-control-freshen-agps = lib.mkIf cfg.enabled {
|
||||
description = "keep assisted-GPS data fresh";
|
||||
serviceConfig = {
|
||||
# XXX: this can have a race condition with eg25-control-gps
|
||||
# - eg25-control-gps initiates DL of new/<agps>
|
||||
# - eg25-control-gps tests new/<agps>: it works
|
||||
# - eg25-control-freshen-agps initiates DL of new/<agps>
|
||||
# - eg25-control-gps: moves new/<agps> into cache/
|
||||
# - but it moved the result (possibly incomplete) of eg25-control-freshen-agps, incorrectly
|
||||
# in practice, i don't expect much issue from this.
|
||||
ExecStart = "${cfg.package}/bin/eg25-control --ensure-agps-cache --verbose";
|
||||
Restart = "no";
|
||||
|
||||
User = "eg25-control";
|
||||
WorkingDirectory = "/var/lib/eg25-control";
|
||||
StateDirectory = "eg25-control";
|
||||
};
|
||||
startAt = "hourly"; # this is a bit more than necessary, but idk systemd calendar syntax
|
||||
after = [ "network-online.target" "nss-lookup.target" ];
|
||||
requires = [ "network-online.target" ];
|
||||
# wantedBy = [ "network-online.target" ]; # auto-start immediately after boot
|
||||
};
|
||||
|
||||
users = lib.mkIf cfg.enabled {
|
||||
groups.eg25-control = {};
|
||||
users.eg25-control = {
|
||||
group = "eg25-control";
|
||||
isSystemUser = true;
|
||||
home = "/var/lib/eg25-control";
|
||||
extraGroups = [
|
||||
"dialout" # required to read /dev/ttyUSB1
|
||||
"networkmanager" # required to authenticate with mmcli
|
||||
];
|
||||
};
|
||||
};
|
||||
sane.persist.sys.byStore.plaintext = lib.mkIf cfg.enabled [
|
||||
# to persist agps data, i think.
|
||||
{ user = "eg25-control"; group = "eg25-control"; path = "/var/lib/eg25-control"; }
|
||||
];
|
||||
services.udev.extraRules = let
|
||||
chmod = "${pkgs.coreutils}/bin/chmod";
|
||||
chown = "${pkgs.coreutils}/bin/chown";
|
||||
in ''
|
||||
# make Modem controllable by user
|
||||
DRIVER=="modem-power", RUN+="${chmod} g+w /sys%p/powered", RUN+="${chown} :networkmanager /sys%p/powered"
|
||||
'';
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
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-bin;
|
||||
electron = pkgs.electron_28-bin;
|
||||
}).overrideAttrs (upstream: {
|
||||
# fix to use wayland instead of Xwayland:
|
||||
# - replace `NIXOS_OZONE_WL` non-empty check with `WAYLAND_DISPLAY`
|
||||
|
@ -25,8 +25,6 @@
|
|||
"gnome-keyring"
|
||||
];
|
||||
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.net = "clearnet";
|
||||
sandbox.whitelistAudio = true;
|
||||
|
|
|
@ -23,8 +23,6 @@
|
|||
"tmp"
|
||||
];
|
||||
|
||||
buildCost = 2;
|
||||
|
||||
# XXX(2023/07/08): running on moby without `WEBKIT_DISABLE_SANDBOX...` fails, with:
|
||||
# - `bwrap: Can't make symlink at /var/run: File exists`
|
||||
# this could be due to:
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
{ ... }:
|
||||
{
|
||||
sane.programs.evince = {
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.autodetectCliPaths = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
|
|
|
@ -207,10 +207,6 @@ in
|
|||
package = pkgs.firefox-extensions.i2p-in-private-browsing;
|
||||
enable = lib.mkDefault config.services.i2p.enable;
|
||||
};
|
||||
i-still-dont-care-about-cookies = {
|
||||
package = pkgs.firefox-extensions.i-still-dont-care-about-cookies;
|
||||
enable = lib.mkDefault true;
|
||||
};
|
||||
open-in-mpv = {
|
||||
# test: `open-in-mpv 'mpv:///open?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ'`
|
||||
package = pkgs.firefox-extensions.open-in-mpv;
|
||||
|
@ -314,8 +310,8 @@ in
|
|||
// scrollbar configuration, see: <https://artemis.sh/2023/10/12/scrollbars.html>
|
||||
// style=4 gives rectangular scrollbars
|
||||
// could also enable "always show scrollbars" in about:preferences -- not sure what the actual pref name for that is
|
||||
// note that too-large scrollbars (like 50px wide, even 20px) tend to obscure content (and make buttons unclickable)
|
||||
defaultPref("widget.non-native-theme.scrollbar.size.override", 14);
|
||||
// note that too-large scrollbars (like 50px wide) tend to obscure content (and make buttons unclickable)
|
||||
defaultPref("widget.non-native-theme.scrollbar.size.override", 20);
|
||||
defaultPref("widget.non-native-theme.scrollbar.style", 4);
|
||||
|
||||
// disable inertial/kinetic/momentum scrolling because it just gets in the way on touchpads
|
||||
|
|
|
@ -30,30 +30,6 @@ let
|
|||
nerdfontPkgs = builtins.map
|
||||
(f: pkgs.nerdfonts.override { fonts = [ f ]; })
|
||||
wantedNerdfonts;
|
||||
|
||||
# see: <repo:nixos/nixpkgs:nixos/modules/config/fonts/fontconfig.nix>
|
||||
# and: <repo:nixos/nixpkgs:pkgs/development/libraries/fontconfig/make-fonts-cache.nix>
|
||||
# nixpkgs creates a fontconfig cache, but only when *not* cross compiling.
|
||||
# but the alternative is that fonts are cached purely at runtime, in ~/.cache/fontconfig,
|
||||
# and that needs to either be added to the sandbox of *every* app,
|
||||
# or font-heavy apps are several *seconds* slower to launch.
|
||||
#
|
||||
# TODO: upstream this into `make-fonts-cache.nix`?
|
||||
cache = (pkgs.makeFontsCache { fontDirectories = config.fonts.packages; }).overrideAttrs (upstream: {
|
||||
buildCommand = lib.replaceStrings
|
||||
[ "fc-cache" ]
|
||||
[ "${pkgs.stdenv.hostPlatform.emulator pkgs.buildPackages} ${pkgs.fontconfig.bin}/bin/fc-cache" ]
|
||||
upstream.buildCommand
|
||||
;
|
||||
});
|
||||
cacheConf = pkgs.writeTextDir "etc/fonts/conf.d/01-nixos-cache-cross.conf" ''
|
||||
<?xml version='1.0'?>
|
||||
<!DOCTYPE fontconfig SYSTEM 'urn:fontconfig:fonts.dtd'>
|
||||
<fontconfig>
|
||||
<!-- Pre-generated font caches -->
|
||||
<cachedir>${cache}</cachedir>
|
||||
</fontconfig>
|
||||
'';
|
||||
in
|
||||
{
|
||||
sane.programs.fontconfig = {
|
||||
|
@ -88,8 +64,6 @@ in
|
|||
"DejaVu Sans"
|
||||
];
|
||||
};
|
||||
# nixpkgs builds a cache file, but only for non-cross. i want it always, so add my own cache -- but ONLY for cross.
|
||||
fontconfig.confPackages = lib.mkIf (pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform) [ cacheConf ];
|
||||
#vvv enables dejavu_fonts, freefont_ttf, gyre-fonts, liberation_ttf, unifont, noto-fonts-emoji
|
||||
enableDefaultPackages = false;
|
||||
packages = with pkgs; [
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.frozen-bubble = {
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.net = "clearnet"; # net play
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
packageUnwrapped = pkgs.frozen-bubble.overrideAttrs (upstream: {
|
||||
# patch so it stores its dot-files not in root ~.
|
||||
postPatch = (upstream.postPatch or "") + ''
|
||||
|
@ -9,12 +14,6 @@
|
|||
--replace-fail '$FBHOME = "$ENV{HOME}/.frozen-bubble"' '$FBHOME = "$ENV{HOME}/.local/share/frozen-bubble"'
|
||||
'';
|
||||
});
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.net = "clearnet"; # net play
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
persist.byStore.plaintext = [
|
||||
".local/share/frozen-bubble" # preferences, high scores
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
{ ... }:
|
||||
{
|
||||
sane.programs.g4music = {
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistDbus = [ "user" ]; # mpris
|
||||
|
|
|
@ -37,7 +37,7 @@ in
|
|||
# fs.".config/geary".dir = {};
|
||||
# fs.".local/share/folks".dir = {};
|
||||
|
||||
buildCost = 3; # uses webkitgtk 4.1
|
||||
slowToBuild = true; # uses webkitgtk 4.1
|
||||
persist.byStore.private = [
|
||||
# attachments, and email -- contained in a sqlite db
|
||||
".local/share/geary"
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
{ pkgs, ... }: {
|
||||
sane.programs."gnome.gnome-clocks" = {
|
||||
packageUnwrapped = pkgs.gnome.gnome-clocks.overrideAttrs (upstream: {
|
||||
# TODO: upstream this
|
||||
buildInputs = upstream.buildInputs ++ (with pkgs; [
|
||||
# gnome-clocks needs `playbin` (gst-plugins-base) and `scaletempo` (gst-plugins-good)
|
||||
# to play the alarm when a timer expires
|
||||
gst_all_1.gstreamer
|
||||
gst_all_1.gst-plugins-base
|
||||
gst_all_1.gst-plugins-good
|
||||
]);
|
||||
});
|
||||
|
||||
buildCost = 1;
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistDbus = [ "user" ]; #< required (alongside .config/dconf) to remember timers
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.extraPaths = [
|
||||
".config/dconf" # required (alongside dbus) to remember timers
|
||||
];
|
||||
suggestedPrograms = [ "dconf" ];
|
||||
};
|
||||
}
|
|
@ -3,8 +3,6 @@
|
|||
{ ... }:
|
||||
{
|
||||
sane.programs."gnome.gnome-weather" = {
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "inplace"; #< /share/org.gnome.Weather/org.gnome.Weather file refers to bins by full path
|
||||
sandbox.whitelistWayland = true;
|
||||
|
|
|
@ -2,20 +2,8 @@
|
|||
# - turn the tv off and on again (no, really...)
|
||||
#
|
||||
# SANITY CHECKS:
|
||||
# - `go2tv -u 'https://uninsane.org/share/Milkbags/AmenBreak.mp4'`
|
||||
# - `go2tv -u 'https://uninsane.org/share/AmenBreak.mp4'`
|
||||
# - LGTV: works, but not seekable
|
||||
# - Samsung: "Cannot play video."
|
||||
# - `go2tv -v /mnt/servo/media/Videos/Milkbags/AmenBreak.mp4`
|
||||
# - Samsung: works
|
||||
# - `go2tv -v /mnt/servo/media/Videos/Milkbags/COLIN.webm`
|
||||
# - Samsung: works
|
||||
# - `go2tv -v /mnt/servo/media/Videos/Shows/Lucky.Star/S01/S01E01-The.Girl.who.Dashes.Off.mkv`
|
||||
# - Samsung: error 500
|
||||
# - `go2tv -tc -v /mnt/servo/media/Videos/Shows/Lucky.Star/S01/S01E01-The.Girl.who.Dashes.Off.mkv`
|
||||
# - Samsung: error 500
|
||||
# - note that it still advertized .mkv to the TV
|
||||
# - `cp /mnt/servo/media/Videos/Shows/Lucky.Star/S01/S01E01-The.Girl.who.Dashes.Off.mkv S01E01-The.Girl.who.Dashes.Off.mp4 && go2tv -v S01E01-The.Girl.who.Dashes.Off.mp4`
|
||||
# - Samsung: WORKS
|
||||
# - `go2tv -u 'https://youtu.be/p3G5IXn0K7A'`
|
||||
# - LGTV: FAILS ("this file cannot be recognized")
|
||||
# - no fix via transcoding, altering the URI, etc.
|
||||
|
@ -29,8 +17,6 @@
|
|||
# - LGTV: works
|
||||
#
|
||||
# WHEN TO TRANSCODE:
|
||||
# - mkv container + *:
|
||||
# - Samsung: rename to .mp4 and cast that: no need to transcode
|
||||
# - mkv container + mpeg-2 video + AC-3/48k stereo audio:
|
||||
# - LGTV: no transcoding needed
|
||||
# - mkv container + H.264 video + AAC/48k 5.1 audio:
|
||||
|
@ -41,7 +27,6 @@
|
|||
# - LGTV: no transcoding needed
|
||||
# - mkv container + H.265 video + E-AC-3/48k stereo audio:
|
||||
# - LGTV: no transcoding needed
|
||||
#
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.go2tv;
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
# gvfs is used by e.g. nautilus to mount remote filesystems (ftp://, etc)
|
||||
# TODO: gvfs depends on udisks, depends on gnupg,
|
||||
# and as part of this `keyboxd` gpg daemon gets started and does background work every minute even though i totally don't use it.
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.gvfs;
|
||||
in
|
||||
{
|
||||
sane.programs.gvfs = {
|
||||
packageUnwrapped = pkgs.gvfs.override {
|
||||
# i don't need to mount samba shares, and samba build is expensive/flaky (mostly for cross, but even problematic on native)
|
||||
samba = null;
|
||||
};
|
||||
};
|
||||
|
||||
services.gvfs = {
|
||||
inherit (cfg) package;
|
||||
enable = cfg.enabled;
|
||||
};
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.handbrake = {
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "landlock"; #< also supports bwrap, but landlock ensures we don't write to non-mounted tmpfs dir
|
||||
sandbox.whitelistDbus = [ "user" ]; # notifications
|
||||
sandbox.whitelistWayland = true;
|
||||
|
|
|
@ -45,7 +45,7 @@ sort_direction=-1
|
|||
tree_sort_direction=1
|
||||
tree_view_always_by_pid=0
|
||||
all_branches_collapsed=0
|
||||
screen:Main=PID USER TTY PRIORITY NICE M_RESIDENT M_PRIV STATE PERCENT_CPU PERCENT_MEM TIME Command
|
||||
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
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.imagemagick = {
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "inplace"; # /etc/ImageMagick-7/delegates.xml refers to bins by absolute path
|
||||
sandbox.whitelistPwd = true;
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.kdenlive = {
|
||||
packageUnwrapped = pkgs.kdenlive.override {
|
||||
ffmpeg-full = pkgs.ffmpeg-full.override {
|
||||
# avoid expensive samba build for a feature i don't use
|
||||
withSamba = false;
|
||||
};
|
||||
};
|
||||
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.extraHomePaths = [
|
||||
"Music"
|
||||
|
@ -23,5 +14,12 @@
|
|||
sandbox.whitelistDbus = [ "user" ]; # notifications
|
||||
sandbox.whitelistDri = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
packageUnwrapped = pkgs.kdenlive.override {
|
||||
ffmpeg-full = pkgs.ffmpeg-full.override {
|
||||
# avoid expensive samba build for a feature i don't use
|
||||
withSamba = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
sandbox.whitelistDri = true; #< required
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
buildCost = 2;
|
||||
|
||||
secrets.".local/share/komikku/keyrings/plaintext.keyring" = ../../../secrets/common/komikku_accounts.json.bin;
|
||||
# downloads end up here, and without the toplevel database komikku doesn't know they exist.
|
||||
persist.byStore.plaintext = [
|
||||
|
|
|
@ -47,7 +47,6 @@ in {
|
|||
packageUnwrapped = pkgs.koreader-from-src;
|
||||
sandbox.method = "bwrap"; # sandboxes fine under landlock too, except for FTP
|
||||
sandbox.net = "clearnet";
|
||||
sandbox.whitelistDbus = [ "user" ]; # for opening the web browser via portal
|
||||
sandbox.whitelistDri = true; # reduces startup time and subjective page flip time
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.extraHomePaths = [
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
{ ... }:
|
||||
{
|
||||
sane.programs.lemoa = {
|
||||
buildCost = 1;
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.net = "clearnet";
|
||||
sandbox.whitelistDbus = [ "user" ]; # for clicking links
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
{ ... }:
|
||||
{
|
||||
sane.programs.lftp = {
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.net = "all";
|
||||
sandbox.extraPaths = [
|
||||
"Music"
|
||||
"Videos/local"
|
||||
"Videos/servo"
|
||||
"tmp"
|
||||
];
|
||||
};
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
"tmp"
|
||||
];
|
||||
|
||||
buildCost = 3;
|
||||
slowToBuild = true;
|
||||
|
||||
# disable first-run stuff
|
||||
fs.".config/libreoffice/4/user/registrymodifications.xcu".symlink.text = ''
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
];
|
||||
|
||||
mime.associations = {
|
||||
"image/avif" = "org.gnome.Loupe.desktop";
|
||||
"image/gif" = "org.gnome.Loupe.desktop";
|
||||
"image/heif" = "org.gnome.Loupe.desktop"; # apple codec
|
||||
"image/png" = "org.gnome.Loupe.desktop";
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
{ pkgs, ... }:
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.megapixels = {
|
||||
packageUnwrapped = pkgs.megapixels.overrideAttrs (upstream: {
|
||||
postPatch = (upstream.postPatch or "") + ''
|
||||
# 2024/04/21: patch it to save photos in a more specific directory
|
||||
substituteInPlace src/process_pipeline.c \
|
||||
--replace-fail 'XDG_PICTURES_DIR' 'XDG_PHOTOS_DIR'
|
||||
# 2024/04/21: patch it so the folder button works
|
||||
substituteInPlace src/main.c \
|
||||
--replace-fail 'g_get_user_special_dir(G_USER_DIRECTORY_PICTURES)' 'getenv("XDG_PHOTOS_DIR")'
|
||||
'';
|
||||
});
|
||||
# megapixels sandboxing is tough:
|
||||
# if misconfigured, preview will alternately be OK, black, or only 1/4 of it will be rendered -- with no obvious pattern.
|
||||
# adding all of ~ to the sandbox will sometimes (?) fix the flakiness, even when `strace` doesn't show it accessing any files...
|
||||
|
@ -26,8 +16,10 @@
|
|||
sandbox.extraHomePaths = [
|
||||
".config/dconf" #< else it segfaults during post-process
|
||||
# ".config/megapixels"
|
||||
".local/share/applications" #< needed for viewing photos, until i can sort out the portal stuff
|
||||
".cache/mesa_shader_cache" # loads way faster
|
||||
"Pictures/Photos"
|
||||
"tmp"
|
||||
"Pictures" #< TODO: make this Pictures/Photos and save photos there
|
||||
# also it addresses a lot via relative path.
|
||||
];
|
||||
sandbox.extraPaths = [
|
||||
|
@ -45,12 +37,6 @@
|
|||
sandbox.extraRuntimePaths = [
|
||||
"dconf" #< else it's very spammy, and slow
|
||||
];
|
||||
sandbox.extraConfig = [
|
||||
# XXX(2024/04/21): without this it fails to convert .dng -> .jpg.
|
||||
# "bwrap: open /proc/34/ns/ns failed: No such file or directory"
|
||||
"--sanebox-keep-namespace" "pid"
|
||||
];
|
||||
|
||||
suggestedPrograms = [ "dconf" ]; #< not sure if necessary
|
||||
};
|
||||
}
|
||||
|
|
|
@ -57,7 +57,6 @@ in
|
|||
desktopName = "Mimeo";
|
||||
exec = "mimeo %U";
|
||||
comment = "Open files by MIME-type or file name using regular expressions.";
|
||||
noDisplay = true;
|
||||
})
|
||||
];
|
||||
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.modemmanager;
|
||||
in
|
||||
{
|
||||
sane.programs.modemmanager = {
|
||||
# mmcli needs /run/current-system/sw/share/dbus-1 files to function
|
||||
enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true;
|
||||
};
|
||||
|
||||
systemd.services.ModemManager = lib.mkIf cfg.enabled {
|
||||
aliases = [ "dbus-org.freedesktop.ModemManager1.service" ];
|
||||
after = [ "polkit.service" ];
|
||||
requires = [ "polkit.service" ];
|
||||
wantedBy = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
Type = "dbus";
|
||||
BusName = "org.freedesktop.ModemManager1";
|
||||
# only if started with `--debug` does mmcli let us issue AT commands like
|
||||
# `mmcli --modem any --command=<AT_CMD>`
|
||||
ExecStart = "${cfg.package}/bin/ModemManager --debug";
|
||||
# --debug sets DEBUG level logging: so reset
|
||||
ExecStartPost = "${cfg.package}/bin/mmcli --set-logging=INFO";
|
||||
|
||||
Restart = "on-abort";
|
||||
StandardError = "null";
|
||||
CapabilityBoundingSet = "CAP_SYS_ADMIN CAP_NET_ADMIN";
|
||||
ProtectSystem = true;
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
RestrictAddressFamilies = "AF_NETLINK AF_UNIX AF_QIPCRTR";
|
||||
NoNewPrivileges = true;
|
||||
};
|
||||
};
|
||||
|
||||
# so that ModemManager can discover when the modem appears
|
||||
services.udev.packages = lib.mkIf cfg.enabled [ cfg.package ];
|
||||
}
|
|
@ -91,37 +91,22 @@ let
|
|||
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.
|
||||
### 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`
|
||||
### 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'" \
|
||||
--replace-fail \
|
||||
"mp.observe_property('mute'" \
|
||||
"mp.observe_property('user-data/sane_sysvol/mute'"
|
||||
"mp.observe_property('user-data/sane-sysvol/volume'"
|
||||
substituteInPlace src/uosc/elements/Volume.lua \
|
||||
--replace-fail \
|
||||
"mp.commandv('set', 'volume'" \
|
||||
"mp.set_property_number('user-data/sane_sysvol/volume'" \
|
||||
"mp.set_property_native('user-data/sane-sysvol/volume'" \
|
||||
--replace-fail \
|
||||
"mp.set_property_native('volume'" \
|
||||
"mp.set_property_number('user-data/sane_sysvol/volume'" \
|
||||
--replace-fail \
|
||||
"mp.set_property_native('mute'" \
|
||||
"mp.set_property_bool('user-data/sane_sysvol/mute'" \
|
||||
--replace-fail \
|
||||
"mp.commandv('cycle', 'mute')" \
|
||||
"mp.set_property_bool('user-data/sane_sysvol/mute', not mp.get_property_bool('user-data/sane_sysvol/mute'))"
|
||||
|
||||
# tweak the top-bar "maximize" button to actually act as a "fullscreen" button.
|
||||
substituteInPlace src/uosc/elements/TopBar.lua \
|
||||
--replace-fail \
|
||||
'get_maximized_command,' \
|
||||
'"cycle fullscreen",'
|
||||
"mp.set_property_native('user-data/sane-sysvol/volume'"
|
||||
'';
|
||||
});
|
||||
mpv-unwrapped = pkgs.mpv-unwrapped.overrideAttrs (upstream: {
|
||||
|
@ -137,55 +122,47 @@ let
|
|||
in
|
||||
{
|
||||
sane.programs.mpv = {
|
||||
packageUnwrapped = pkgs.wrapMpv
|
||||
(mpv-unwrapped.override rec {
|
||||
# N.B.: populating `self` to `luajit` is necessary for the resulting `lua.withPackages` function to preserve my override.
|
||||
# i use enable52Compat in order to get `table.unpack`.
|
||||
# i think using `luajit` here instead of `lua` is optional, just i get better perf with it :)
|
||||
lua = pkgs.luajit.override { enable52Compat = true; self = lua; };
|
||||
})
|
||||
{
|
||||
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}"
|
||||
# ];
|
||||
};
|
||||
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"
|
||||
"sane-cast"
|
||||
"sane-die-with-parent"
|
||||
"go2tv"
|
||||
"xdg-terminal-exec"
|
||||
];
|
||||
|
||||
|
@ -198,7 +175,7 @@ in
|
|||
sandbox.whitelistWayland = true;
|
||||
sandbox.extraHomePaths = [
|
||||
".config/mpv" #< else mpris plugin crashes on launch
|
||||
".local/share/applications" #< for xdg-terminal-exec (sane-cast)
|
||||
".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"
|
||||
|
@ -213,9 +190,9 @@ in
|
|||
# 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/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;
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
POWER ignore
|
||||
VOLUME_UP ignore
|
||||
VOLUME_DOWN ignore
|
||||
# disable "double-click to toggle fullscreen", else that limits the rate at which i can seek
|
||||
MBTN_LEFT_DBL ignore
|
||||
|
||||
# uosc menu
|
||||
# text after the shebang is parsed by uosc to construct the menu and names
|
||||
|
@ -33,5 +31,7 @@ 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/sane-cast #! Cast
|
||||
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
|
||||
|
|
|
@ -10,9 +10,7 @@ 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
|
||||
# actually, prefer just a subset of what's enabled by pseudo-gui, else logging breaks
|
||||
force-window=yes
|
||||
player-operation-mode=pseudo-gui
|
||||
|
||||
# use uosc instead (for On Screen Controls)
|
||||
osc=no
|
||||
|
@ -21,8 +19,8 @@ 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
|
||||
# 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
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
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')
|
|
@ -1,16 +1,15 @@
|
|||
msg = require("mp.msg")
|
||||
msg.trace("load: begin")
|
||||
msg = require('mp.msg')
|
||||
msg.trace('sane-sysvol: load: begin')
|
||||
|
||||
non_blocking_popen = require("non_blocking_popen")
|
||||
|
||||
RD_SIZE = 65536
|
||||
RD_SIZE = 4096
|
||||
|
||||
function startswith(superstring, substring)
|
||||
return superstring:sub(1, substring:len()) == substring
|
||||
end
|
||||
function strip_prefix(superstring, substring)
|
||||
assert(startswith(superstring, substring))
|
||||
return superstring:sub(1 + substring:len())
|
||||
return superstring:sub(substring:len())
|
||||
end
|
||||
|
||||
function ltrim(s)
|
||||
|
@ -26,7 +25,7 @@ function subprocess(args)
|
|||
mp.command_native({
|
||||
name = "subprocess",
|
||||
args = args,
|
||||
-- these arguments below probably don't matter: copied from sane_cast
|
||||
-- these arguments below probably don't matter: copied from sane-cast
|
||||
detach = false,
|
||||
capture_stdout = false,
|
||||
capture_stderr = false,
|
||||
|
@ -40,7 +39,6 @@ function sysvol_new()
|
|||
-- sysvol is pipewire-native volume
|
||||
-- it's the cube of the equivalent 0-100% value represented inside mpv
|
||||
sysvol = nil,
|
||||
sysmute = nil,
|
||||
change_sysvol = function(self, mpv_vol)
|
||||
-- called when mpv wants to set the system-wide volume
|
||||
if mpv_vol == nil then
|
||||
|
@ -51,16 +49,12 @@ function sysvol_new()
|
|||
if self.sysvol ~= nil then
|
||||
old_mpv_vol = 100 * self.sysvol^(1/3)
|
||||
end
|
||||
if old_mpv_vol ~= nil and math.abs(mpv_vol - old_mpv_vol) < 1.0 then
|
||||
-- avoid near-infinite loop where we react to our own volume change.
|
||||
-- consider that we might be a couple messages behind in parsing pipewire when we issue this command,
|
||||
-- hence a check on only the pipewire -> mpv side wouldn't prevent oscillation
|
||||
msg.debug("NOT setting system-wide volume:", old_mpv_vol, volstr)
|
||||
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:", old_mpv_vol, volstr)
|
||||
msg.debug("setting system-wide volume:", volstr)
|
||||
self.sysvol = (0.01*mpv_vol)^3
|
||||
subprocess({
|
||||
"wpctl",
|
||||
|
@ -70,11 +64,10 @@ function sysvol_new()
|
|||
})
|
||||
end,
|
||||
on_sysvol_change = function(self, sysvol)
|
||||
-- called when the pipewire system volume is changed (either by us, or an external application)
|
||||
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
|
||||
|
@ -82,47 +75,13 @@ function sysvol_new()
|
|||
end
|
||||
|
||||
if old_mpv_vol ~= nil and math.abs(new_mpv_vol - old_mpv_vol) < 1.0 then
|
||||
-- avoid an infinite loop where we react to our own volume change
|
||||
msg.debug("NOT announcing volume change to mpv (because it was what triggered the change):", old_mpv_vol, new_mpv_vol)
|
||||
return
|
||||
end
|
||||
|
||||
msg.debug("announcing volume change to mpv:", old_mpv_vol, new_mpv_vol)
|
||||
self.sysvol = sysvol
|
||||
mp.set_property_number("user-data/sane_sysvol/volume", new_mpv_vol)
|
||||
end,
|
||||
change_sysmute = function(self, mute)
|
||||
if mute == nil then
|
||||
return
|
||||
end
|
||||
if mute == self.sysmute then
|
||||
msg.debug("NOT setting system-wide mute (because it didn't change)", mute)
|
||||
return
|
||||
end
|
||||
|
||||
local mutestr
|
||||
if mute then
|
||||
mutestr = "1"
|
||||
else
|
||||
mutestr = "0"
|
||||
end
|
||||
msg.debug("setting system-wide mute:", mutestr)
|
||||
self.sysmute = mute
|
||||
subprocess({
|
||||
"wpctl",
|
||||
"set-mute",
|
||||
"@DEFAULT_AUDIO_SINK@",
|
||||
mutestr
|
||||
})
|
||||
end,
|
||||
on_sysmute_change = function(self, mute)
|
||||
if mute == nil then
|
||||
return
|
||||
end
|
||||
|
||||
msg.debug("announcing mute to mpv:", mute)
|
||||
self.sysmute = mute
|
||||
mp.set_property_bool("user-data/sane_sysvol/mute", mute)
|
||||
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
|
||||
|
@ -132,10 +91,13 @@ function pwmon_parser_new()
|
|||
-- 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 = nil, -- number
|
||||
mute = nil, -- bool
|
||||
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,
|
||||
|
@ -143,19 +105,34 @@ function pwmon_parser_new()
|
|||
in_mute = false,
|
||||
|
||||
feed_line = function(self, line)
|
||||
msg.trace("pw-mon:", line)
|
||||
line = ltrim(line)
|
||||
if startswith(line, "changed:") or startswith(line, "added:") or startswith(line, "removed:") then
|
||||
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, "type: ") then
|
||||
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_device then
|
||||
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
|
||||
|
@ -163,45 +140,51 @@ function pwmon_parser_new()
|
|||
-- 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:mute")
|
||||
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_device and self.in_output and self.in_vol then
|
||||
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_device and self.in_output and self.in_mute then
|
||||
value = strip_prefix(line, "Bool ") == "true"
|
||||
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_device then
|
||||
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:", vol)
|
||||
self.volume = vol
|
||||
msg.debug("volume:", self.changed_id, vol)
|
||||
self.volume[self.changed_id] = vol
|
||||
end,
|
||||
feed_mute = function(self, mute)
|
||||
msg.debug("mute:", mute)
|
||||
self.mute = mute
|
||||
msg.debug("mute:", self.changed_id, mute)
|
||||
self.mute[self.changed_id] = mute
|
||||
end,
|
||||
-- get_effective_volume = function(self)
|
||||
-- if self.mute then
|
||||
-- return 0
|
||||
-- else
|
||||
-- return self.volume
|
||||
-- end
|
||||
-- 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.
|
||||
-- we have to use `sane-die-with-parent` otherwise `pw-mon` will still be active even after mpv exits.
|
||||
handle = non_blocking_popen.non_blocking_popen("sane-die-with-parent --descendants pw-mon", RD_SIZE),
|
||||
-- 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)
|
||||
|
@ -212,21 +195,8 @@ function pwmon_new()
|
|||
msg.debug("pw-mon unexpectedly closed!")
|
||||
end
|
||||
if buf ~= nil then
|
||||
local old_vol = self.pwmon_parser.volume
|
||||
local old_mute = self.pwmon_parser.mute
|
||||
self.stdout_unparsed = self.stdout_unparsed .. buf
|
||||
self:consume_stdout()
|
||||
local new_vol = self.pwmon_parser.volume
|
||||
local new_mute = self.pwmon_parser.mute
|
||||
|
||||
if new_vol ~= old_vol then
|
||||
msg.debug("pipewire volume change:", old_vol, new_vol)
|
||||
mp.set_property_number("user-data/sane_sysvol/pw-mon-volume", new_vol)
|
||||
end
|
||||
if new_mute ~= old_mute then
|
||||
msg.debug("pipewire mute change:", old_mute, new_mute)
|
||||
mp.set_property_bool("user-data/sane_sysvol/pw-mon-mute", new_mute)
|
||||
end
|
||||
end
|
||||
end,
|
||||
consume_stdout = function(self)
|
||||
|
@ -234,48 +204,37 @@ function pwmon_new()
|
|||
while next_newline ~= nil do
|
||||
next_newline = self.stdout_unparsed:find("\n", idx_newline + 1, true)
|
||||
if next_newline ~= nil then
|
||||
self.pwmon_parser:feed_line(self.stdout_unparsed:sub(idx_newline + 1, next_newline - 1))
|
||||
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_number("user-data/sane_sysvol/volume", 0)
|
||||
mp.set_property_bool("user-data/sane_sysvol/mute", true)
|
||||
|
||||
mp.set_property_native("user-data/sane-sysvol/volume", 0)
|
||||
|
||||
local sysvol = sysvol_new()
|
||||
local first_sysvol_announcement = true
|
||||
mp.observe_property("user-data/sane_sysvol/volume", "native", function(_, val)
|
||||
-- we must set the volume property early -- before we actually know the volume
|
||||
-- else other modules will think it's `nil` and error.
|
||||
-- but we DON'T want the value we set to actually impact the system volume
|
||||
if not first_sysvol_announcement then
|
||||
sysvol:change_sysvol(val)
|
||||
end
|
||||
first_sysvol_announcement = false
|
||||
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)
|
||||
mp.observe_property("user-data/sane-sysvol/pw-mon-volume", "native", function(_, val)
|
||||
sysvol:on_sysvol_change(val)
|
||||
end)
|
||||
|
||||
local first_sysmute_announcement = true
|
||||
mp.observe_property("user-data/sane_sysvol/mute", "native", function(_, val)
|
||||
-- we must set the mute property early -- before we actually know the mute
|
||||
-- else other modules will think it's `nil` and error.
|
||||
-- but we DON'T want the value we set to actually impact the system mute
|
||||
if not first_sysmute_announcement then
|
||||
sysvol:change_sysmute(val)
|
||||
end
|
||||
first_sysmute_announcement = false
|
||||
end)
|
||||
mp.observe_property("user-data/sane_sysvol/pw-mon-mute", "native", function(_, val)
|
||||
sysvol:on_sysmute_change(val)
|
||||
end)
|
||||
|
||||
local pwmon = pwmon_new()
|
||||
mp.register_event("tick", function() pwmon:service() end)
|
||||
mp.register_event('tick', function() pwmon:service() end)
|
||||
|
||||
msg.trace("load: complete")
|
||||
msg.trace("sane-sysvol: load: complete")
|
|
@ -0,0 +1,80 @@
|
|||
-- 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
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
msg = require("mp.msg")
|
||||
msg.trace("load: begin")
|
||||
|
||||
function subprocess(in_terminal, args)
|
||||
if in_terminal then
|
||||
args = { "xdg-terminal-exec", table.unpack(args) }
|
||||
end
|
||||
msg.info(table.concat(args, " "))
|
||||
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_paused(in_terminal, args)
|
||||
mp.commandv("set", "pause", "yes")
|
||||
for k, v in ipairs(args) do
|
||||
if v == "@FILE@" then
|
||||
args[k] = mp.get_property("stream-open-filename")
|
||||
end
|
||||
end
|
||||
subprocess(in_terminal, args)
|
||||
end
|
||||
|
||||
|
||||
-- invoke blast in a way where it dies when we die, because:
|
||||
-- 1. when mpv exits, it `SIGKILL`s this toplevel subprocess.
|
||||
-- 2. `blast-to-default` could be a sandbox wrapper.
|
||||
-- 3. bwrap does not pass SIGKILL or SIGTERM to its child.
|
||||
-- 4. hence, to properly kill blast, we have to kill all the descendants.
|
||||
mp.add_key_binding(nil, "blast", function() subprocess(false, { "sane-die-with-parent", "--descendants", "--use-pgroup", "--catch-sigkill", "blast-to-default" }) end)
|
||||
mp.add_key_binding(nil, "sane-cast", function() invoke_paused(true, { "sane-cast", "--verbose", "@FILE@" }) end)
|
||||
|
||||
msg.trace("load: complete")
|
|
@ -1,91 +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
|
||||
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)
|
||||
F_SETFL = 4
|
||||
O_NONBLOCK = 2048
|
||||
EAGAIN = 11
|
||||
|
||||
-- this "array" holds the errno variable
|
||||
_errno = ffi.C.__errno_location()
|
||||
|
||||
popen_meta = {
|
||||
__index = {
|
||||
-- close the process, prevent reading, allow garbage colletion
|
||||
close = function(self)
|
||||
if self._file ~= nil then
|
||||
local _file = self._file
|
||||
self._file = nil
|
||||
self._fd = nil
|
||||
self._read_buffer = nil
|
||||
ffi.C.pclose(_file)
|
||||
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
|
||||
read = function(self, size)
|
||||
if self._fd == nil then
|
||||
return nil, "closed"
|
||||
end
|
||||
|
||||
size = math.min(self._read_buffer_size, size)
|
||||
local nbytes = ffi.C.read(self._fd, self._read_buffer, size)
|
||||
|
||||
if nbytes > 0 then
|
||||
local data = ffi.string(self._read_buffer, nbytes)
|
||||
return data, nbytes
|
||||
elseif (nbytes == -1) and (_errno[0] == EAGAIN) then
|
||||
return nil, "EAGAIN"
|
||||
else
|
||||
self:close()
|
||||
return nil, "closed"
|
||||
end
|
||||
end
|
||||
}
|
||||
-- __gc = function(self) self:close() end
|
||||
-- __close = function(self) p:close() end
|
||||
}
|
||||
|
||||
local function non_blocking_popen(cmd, read_buffer_size)
|
||||
-- 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")
|
||||
|
||||
local p = {
|
||||
_fd = fd,
|
||||
_file = file,
|
||||
_read_buffer = read_buffer,
|
||||
_read_buffer_size = read_buffer_size,
|
||||
}
|
||||
|
||||
setmetatable(p, popen_meta)
|
||||
|
||||
return p
|
||||
end
|
||||
|
||||
return {
|
||||
non_blocking_popen = non_blocking_popen
|
||||
}
|
|
@ -24,7 +24,7 @@ font_scale=1.5
|
|||
font_bold=yes
|
||||
# refine=text_width: slightly better text rendering
|
||||
refine=text_width
|
||||
color=foreground=ff968b,background_text=ff968b
|
||||
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
|
||||
|
|
|
@ -10,10 +10,6 @@
|
|||
]);
|
||||
}));
|
||||
|
||||
suggestedPrograms = [
|
||||
"gvfs" # browse ftp://, etc
|
||||
];
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistDbus = [ "user" ]; # for portals launching apps
|
||||
sandbox.whitelistWayland = true;
|
||||
|
|
|
@ -103,61 +103,54 @@ in
|
|||
# "use"
|
||||
];
|
||||
|
||||
packageUnwrapped = let
|
||||
configArgs = {
|
||||
withRuby = false; #< doesn't cross-compile w/o binfmt
|
||||
viAlias = true;
|
||||
vimAlias = true;
|
||||
plugins = plugin-packages;
|
||||
customRC = ''
|
||||
" let the terminal handle mouse events, that way i get OS-level ctrl+shift+c/etc
|
||||
" this used to be default, until <https://github.com/neovim/neovim/pull/19290>
|
||||
set mouse=
|
||||
# packageUnwrapped = config.programs.neovim.finalPackage;
|
||||
packageUnwrapped = pkgs.wrapNeovimUnstable pkgs.neovim-unwrapped (pkgs.neovimUtils.makeNeovimConfig {
|
||||
withRuby = false; #< doesn't cross-compile w/o binfmt
|
||||
viAlias = true;
|
||||
vimAlias = true;
|
||||
plugins = plugin-packages;
|
||||
customRC = ''
|
||||
" let the terminal handle mouse events, that way i get OS-level ctrl+shift+c/etc
|
||||
" this used to be default, until <https://github.com/neovim/neovim/pull/19290>
|
||||
set mouse=
|
||||
|
||||
" copy/paste to system clipboard
|
||||
set clipboard=unnamedplus
|
||||
" copy/paste to system clipboard
|
||||
set clipboard=unnamedplus
|
||||
|
||||
" screw tabs; always expand them into spaces
|
||||
set expandtab
|
||||
" screw tabs; always expand them into spaces
|
||||
set expandtab
|
||||
|
||||
" at least don't open files with sections folded by default
|
||||
set nofoldenable
|
||||
" at least don't open files with sections folded by default
|
||||
set nofoldenable
|
||||
|
||||
" allow text substitutions for certain glyphs.
|
||||
" higher number = more aggressive substitution (0, 1, 2, 3)
|
||||
" i only make use of this for tex, but it's unclear how to
|
||||
" apply that *just* to tex and retain the SyntaxRange stuff.
|
||||
set conceallevel=2
|
||||
" allow text substitutions for certain glyphs.
|
||||
" higher number = more aggressive substitution (0, 1, 2, 3)
|
||||
" i only make use of this for tex, but it's unclear how to
|
||||
" apply that *just* to tex and retain the SyntaxRange stuff.
|
||||
set conceallevel=2
|
||||
|
||||
" horizontal rule under the active line
|
||||
" set cursorline
|
||||
" horizontal rule under the active line
|
||||
" set cursorline
|
||||
|
||||
" highlight trailing space & related syntax errors (doesn't seem to work??)
|
||||
" let c_space_errors=1
|
||||
" let python_space_errors=1
|
||||
" highlight trailing space & related syntax errors (doesn't seem to work??)
|
||||
" let c_space_errors=1
|
||||
" let python_space_errors=1
|
||||
|
||||
" enable highlighting of leading/trailing spaces,
|
||||
" and especially tabs
|
||||
" source: https://www.reddit.com/r/neovim/comments/chlmfk/highlight_trailing_whitespaces_in_neovim/
|
||||
set list
|
||||
set listchars=tab:▷\·,trail:·,extends:◣,precedes:◢,nbsp:○
|
||||
" enable highlighting of leading/trailing spaces,
|
||||
" and especially tabs
|
||||
" source: https://www.reddit.com/r/neovim/comments/chlmfk/highlight_trailing_whitespaces_in_neovim/
|
||||
set list
|
||||
set listchars=tab:▷\·,trail:·,extends:◣,precedes:◢,nbsp:○
|
||||
|
||||
""""" PLUGIN CONFIG (vim)
|
||||
${plugin-config-viml}
|
||||
""""" PLUGIN CONFIG (vim)
|
||||
${plugin-config-viml}
|
||||
|
||||
""""" PLUGIN CONFIG (lua)
|
||||
lua <<EOF
|
||||
${plugin-config-lua}
|
||||
EOF
|
||||
'';
|
||||
};
|
||||
in pkgs.wrapNeovimUnstable
|
||||
pkgs.neovim-unwrapped
|
||||
# XXX(2024/05/13): manifestRc must be null for cross-compilation to work.
|
||||
# wrapper invokes `neovim` with all plugins enabled at build time i guess to generate caches and stuff?
|
||||
# alternative is to emulate `nvim-wrapper` during build.
|
||||
((pkgs.neovimUtils.makeNeovimConfig configArgs) // { manifestRc = null; })
|
||||
;
|
||||
""""" PLUGIN CONFIG (lua)
|
||||
lua <<EOF
|
||||
${plugin-config-lua}
|
||||
EOF
|
||||
'';
|
||||
});
|
||||
|
||||
# private because there could be sensitive things in the swap
|
||||
persist.byStore.private = [ ".cache/vim-swap" ];
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
# Network Manager:
|
||||
# i manage this myself because the nixos service is not flexible enough.
|
||||
# - it unconditionally puts modemmanager onto the system path, preventing me from patching modemmanager's service file (without an overlay).
|
||||
#
|
||||
# XXX: it's normal to see error messages on an ethernet-only host, even when using nixos' official networkmanager service:
|
||||
# - `Couldn't initialize supplicant interface: Failed to D-Bus activate wpa_supplicant service`
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.networkmanager;
|
||||
in
|
||||
{
|
||||
sane.programs.networkmanager = {
|
||||
suggestedPrograms = [ "wpa_supplicant" ];
|
||||
enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true;
|
||||
};
|
||||
|
||||
# add to systemd.packages so we get the service file it ships, then override what we need to customize (taken from nixpkgs)
|
||||
systemd.packages = lib.mkIf cfg.enabled [ cfg.package ];
|
||||
systemd.services.NetworkManager = lib.mkIf cfg.enabled {
|
||||
wantedBy = [ "network.target" ];
|
||||
aliases = [ "dbus-org.freedesktop.NetworkManager.service" ];
|
||||
|
||||
serviceConfig = {
|
||||
StateDirectory = "NetworkManager";
|
||||
StateDirectoryMode = 755; # not sure if this really needs to be 755
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.NetworkManager-wait-online = lib.mkIf cfg.enabled {
|
||||
wantedBy = [ "network-online.target" ];
|
||||
};
|
||||
|
||||
systemd.services.NetworkManager-dispatcher = lib.mkIf cfg.enabled {
|
||||
wantedBy = [ "NetworkManager.service" ];
|
||||
# to debug, add NM_DISPATCHER_DEBUG_LOG=1
|
||||
serviceConfig.ExecStart = [
|
||||
"" # first blank line is to clear the upstream `ExecStart` field.
|
||||
"${cfg.package}/libexec/nm-dispatcher --persist" # --persist is needed for it to actually run as a daemon
|
||||
];
|
||||
serviceConfig.Restart = "always";
|
||||
serviceConfig.RestartSec = "1s";
|
||||
};
|
||||
|
||||
environment.etc = lib.mkIf cfg.enabled {
|
||||
"NetworkManager/system-connections".source = "/var/lib/NetworkManager/system-connections";
|
||||
"NetworkManager/NetworkManager.conf".text = ''
|
||||
[device]
|
||||
# wifi.backend: wpa_supplicant or iwd
|
||||
wifi.backend=wpa_supplicant
|
||||
wifi.scan-rand-mac-address=true
|
||||
|
||||
[logging]
|
||||
audit=false
|
||||
# level: TRACE, DEBUG, INFO, WARN, ERR, OFF
|
||||
level=INFO
|
||||
# domain=...
|
||||
|
||||
[main]
|
||||
# dhcp:
|
||||
# - `internal` (default)
|
||||
# - `dhclient` (requires dhclient to be installed)
|
||||
# - `dhcpcd` (requires dhcpcd to be installed)
|
||||
dhcp=internal
|
||||
# dns:
|
||||
# - `default`: update /etc/resolv.conf with nameservers provided by the active connection
|
||||
# - `none`: NM won't update /etc/resolv.conf
|
||||
# - `systemd-resolved`: push DNS config to systemd-resolved
|
||||
# - `dnsmasq`: run a local caching nameserver
|
||||
dns=${if config.services.resolved.enable then
|
||||
"systemd-resolved"
|
||||
else if config.sane.services.trust-dns.enable && config.sane.services.trust-dns.asSystemResolver then
|
||||
"none"
|
||||
else
|
||||
"internal"
|
||||
}
|
||||
plugins=keyfile
|
||||
# rc-manager: how NM should write to /etc/resolv.conf
|
||||
# - regardless of this setting, NM will write /var/lib/NetworkManager/resolv.conf
|
||||
rc-manager=unmanaged
|
||||
# systemd-resolved: send DNS config to systemd-resolved?
|
||||
# this setting has no effect if dns="systemd-resolved"; it's supplementary, not absolute.
|
||||
systemd-resolved=false
|
||||
# debug=... (see also: NM_DEBUG env var)
|
||||
'';
|
||||
};
|
||||
hardware.wirelessRegulatoryDatabase = lib.mkIf cfg.enabled true;
|
||||
networking.useDHCP = lib.mkIf cfg.enabled false;
|
||||
users.groups = lib.mkIf cfg.enabled {
|
||||
networkmanager.gid = config.ids.gids.networkmanager;
|
||||
};
|
||||
services.udev.packages = lib.mkIf cfg.enabled [ cfg.package ];
|
||||
security.polkit.enable = lib.mkIf cfg.enabled true;
|
||||
security.polkit.extraConfig = ''
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (
|
||||
subject.isInGroup("networkmanager")
|
||||
&& (action.id.indexOf("org.freedesktop.NetworkManager.") == 0
|
||||
|| action.id.indexOf("org.freedesktop.ModemManager") == 0
|
||||
))
|
||||
{ return polkit.Result.YES; }
|
||||
});
|
||||
'';
|
||||
|
||||
boot.kernelModules = [ "ctr" ]; #< TODO: needed (what even is this)?
|
||||
# TODO: polkit?
|
||||
# TODO: NetworkManager-ensure-profiles?
|
||||
}
|
|
@ -13,7 +13,7 @@ let
|
|||
wanted-feeds = feeds.filterByFormat [ "text" "image" "podcast" "video" ] all-feeds;
|
||||
in {
|
||||
sane.programs.newsflash = {
|
||||
buildCost = 2; # mainly for desktop: webkitgtk-6.0
|
||||
slowToBuild = true; # mainly for desktop: webkitgtk-6.0
|
||||
persist.byStore.plaintext = [ ".local/share/news-flash" ];
|
||||
fs.".config/newsflashFeeds.opml".symlink.text =
|
||||
feeds.feedsToOpml wanted-feeds
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
sane.programs.notejot = {
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.extraPaths = [ ".config/dconf" ]; #< for legacy notes (moby), loaded via dconf
|
||||
suggestedPrograms = [ "dconf" ]; #< else it can't persist notes
|
||||
|
||||
persist.byStore.private = [
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.objdump = {
|
||||
# binutils-unwrapped is like 80 MiB, just for this one binary;
|
||||
# dynamic linking means copying the binary doesn't reduce the closure much at all compared to just symlinking it.
|
||||
packageUnwrapped = pkgs.linkIntoOwnPackage pkgs.binutils-unwrapped "bin/objdump";
|
||||
};
|
||||
}
|
|
@ -1,74 +1,33 @@
|
|||
# administer with pw-cli, pw-mon, pw-top commands
|
||||
#
|
||||
# performance tuning: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Performance-tuning>
|
||||
#
|
||||
# HAZARDS FOR MOBY:
|
||||
# - high-priority threads are liable to stall the lima GPU driver, and leave a half-functional OS state.
|
||||
# - symptom is messages like this (with stack traces) in dmesg or journalctl:
|
||||
# - "[drm:lima_sched_timedout_job] *ERROR* lima job timeout"
|
||||
# - and the UI locks up for a couple seconds, and then pipewire + wireplumber crash (but not pipewire-pulse)
|
||||
# - related, unconfirmed symptoms:
|
||||
# - "sched: RT throttling activated"
|
||||
# - "BUG: KFENCE: use-after-free read in vchan_complete"
|
||||
# - this one seems to be recoverable
|
||||
# - likely to be triggered when using a small pipewire buffer (512 samples), by simple tasks like opening pavucontrol.
|
||||
# - but a lengthier buffer is no sure way to dodge it: it will happen (less frequently) even for buffers of 2048 samples.
|
||||
# - seems ANY priority < 0 triggers this, independent of the `nice` setting.
|
||||
# - i only tried SCHED_FIFO, not SCHED_RR (round robin) for the realtime threads.
|
||||
# - solution is some combination of:
|
||||
# - DON'T USE RTKIT. rtkit only supports SCHED_FIFO and SCHED_RR: there's no way to use it only for adjusting `nice` values.
|
||||
# - in pipewire.conf, remove all reference to libpipewire-module-rt.
|
||||
# - it's loaded by default. i can either provide a custom pipewire.conf which doesn't load it, or adjust its config so that it intentionally fails.
|
||||
# - without rtkit working, pipewire's module-rt doesn't allow niceness < -11. adjusting `nice`ness here seems to have little effect anyway.
|
||||
# - longer term, rtkit (or just rlimit based pipewire module-rt) would be cool to enable: it *does* reduce underruns.
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.pipewire;
|
||||
in
|
||||
{
|
||||
sane.programs.pipewire = {
|
||||
configOption = with lib; mkOption {
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options.min-quantum = mkOption {
|
||||
type = types.int;
|
||||
default = 16;
|
||||
};
|
||||
options.max-quantum = mkOption {
|
||||
type = types.int;
|
||||
default = 2048;
|
||||
};
|
||||
};
|
||||
};
|
||||
suggestedPrograms = [ "wireplumber" ];
|
||||
|
||||
suggestedPrograms = [
|
||||
# "rtkit"
|
||||
"wireplumber"
|
||||
# sandbox.method = "landlock"; #< also works
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistDbus = [
|
||||
# dbus is used for rtkit integration
|
||||
# rtkit runs on the system bus.
|
||||
# xdg-desktop-portal then exposes this to the user bus.
|
||||
# therefore, user bus should be all that's needed, but...
|
||||
# xdg-desktop-portal-wlr depends on pipewire, hence pipewire has to start before xdg-desktop-portal.
|
||||
# then, pipewire has to talk specifically to rtkit (system) and not go through xdp.
|
||||
# "user"
|
||||
"system"
|
||||
];
|
||||
|
||||
# sandbox.method = "landlock"; #< works, including without rtkit
|
||||
sandbox.method = "bwrap"; #< also works, but can't claim the full scheduling priority it wants
|
||||
sandbox.whitelistAudio = true;
|
||||
# sandbox.whitelistDbus = [
|
||||
# # dbus is used for rtkit integration
|
||||
# # rtkit runs on the system bus.
|
||||
# # xdg-desktop-portal then exposes this to the user bus.
|
||||
# # therefore, user bus should be all that's needed, but...
|
||||
# # xdg-desktop-portal-wlr depends on pipewire, hence pipewire has to start before xdg-desktop-portal.
|
||||
# # then, pipewire has to talk specifically to rtkit (system) and not go through xdp.
|
||||
# # "user"
|
||||
# "system"
|
||||
# ];
|
||||
sandbox.wrapperType = "inplace"; #< its config files refer to its binaries by full path
|
||||
sandbox.extraConfig = [
|
||||
"--sanebox-keep-namespace" "pid"
|
||||
];
|
||||
sandbox.capabilities = [
|
||||
# if rtkit isn't present, and sandboxing is via landlock, these capabilities allow pipewire to claim higher scheduling priority
|
||||
"ipc_lock"
|
||||
"sys_nice"
|
||||
"--sane-sandbox-keep-namespace" "pid"
|
||||
];
|
||||
sandbox.usePortal = false;
|
||||
# needs to *create* the various device files, so needs write access to the /run/user/$uid directory itself
|
||||
sandbox.extraRuntimePaths = [ "/" ];
|
||||
sandbox.extraPaths = [
|
||||
"/dev/snd"
|
||||
# desko/lappy don't need these, but moby complains if not present
|
||||
|
@ -79,69 +38,8 @@ in
|
|||
sandbox.extraHomePaths = [
|
||||
# pulseaudio cookie
|
||||
".config/pulse"
|
||||
".config/pipewire"
|
||||
];
|
||||
|
||||
# note the .conf.d approach: using ~/.config/pipewire/pipewire.conf directly breaks all audio,
|
||||
# presumably because that deletes the defaults entirely whereas the .conf.d approach selectively overrides defaults
|
||||
fs.".config/pipewire/pipewire.conf.d/10-sane-config.conf".symlink.text = ''
|
||||
# config docs: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-PipeWire#properties>
|
||||
# - <repo:pipewire/pipewire:src/daemon/pipewire.conf.in>
|
||||
# useful to run `pw-top` to see that these settings are actually having effect,
|
||||
# and `pw-metadata` to see if any settings conflict (e.g. max-quantum < min-quantum)
|
||||
#
|
||||
# restart pipewire after editing these files:
|
||||
# - `systemctl --user restart pipewire`
|
||||
# - pipewire users will likely stop outputting audio until they are also restarted
|
||||
#
|
||||
# there's seemingly two buffers for the mic (see: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#pipewire-buffering-explained>)
|
||||
# 1. Pipewire buffering out of the driver and into its own member.
|
||||
# 2. Pipewire buffering into each specific app (e.g. Dino).
|
||||
# note that pipewire default config includes `clock.power-of-two-quantum = true`
|
||||
context.properties = {
|
||||
default.clock.min-quantum = ${builtins.toString cfg.config.min-quantum}
|
||||
default.clock.max-quantum = ${builtins.toString cfg.config.max-quantum}
|
||||
}
|
||||
'';
|
||||
# reduce realtime scheduling priority to prevent GPU instability,
|
||||
# but see the top of this file for other solutions.
|
||||
# fs.".config/pipewire/pipewire.conf.d/20-sane-rtkit.conf".symlink.text = ''
|
||||
# # documented inside <repo:pipewire/pipewire:/src/modules/module-rt.c>
|
||||
# context.modules = [{
|
||||
# name = libpipewire-module-rt
|
||||
# args = {
|
||||
# nice.level = 0
|
||||
# rt.prio = 0
|
||||
# #rt.time.soft = -1
|
||||
# #rt.time.hard = -1
|
||||
# rlimits.enabled = false
|
||||
# rtportal.enabled = false
|
||||
# rtkit.enabled = true
|
||||
# #uclamp.min = 0
|
||||
# #uclamp.max = 1024
|
||||
# }
|
||||
# flags = [ ifexists nofail ]
|
||||
# }]
|
||||
# '';
|
||||
# fs.".config/pipewire/pipewire-pulse.conf.d/20-sane-rtkit.conf".symlink.text = ''
|
||||
# # documented: <repo:pipewire/pipewire:src/daemon/pipewire-pulse.conf.in>
|
||||
# context.modules = [{
|
||||
# name = libpipewire-module-rt
|
||||
# args = {
|
||||
# nice.level = 0
|
||||
# rt.prio = 0
|
||||
# #rt.time.soft = -1
|
||||
# #rt.time.hard = -1
|
||||
# rlimits.enabled = false
|
||||
# rtportal.enabled = false
|
||||
# rtkit.enabled = true
|
||||
# #uclamp.min = 0
|
||||
# #uclamp.max = 1024
|
||||
# }
|
||||
# flags = [ ifexists nofail ]
|
||||
# }]
|
||||
# '';
|
||||
|
||||
# see: <https://docs.pipewire.org/page_module_protocol_native.html>
|
||||
# defaults to placing the socket in /run/user/$id/{pipewire-0,pipewire-0-manager,...}
|
||||
# but that's trickier to sandbox
|
||||
|
@ -150,14 +48,12 @@ in
|
|||
services.pipewire = {
|
||||
description = "pipewire: multimedia service";
|
||||
partOf = [ "sound" ];
|
||||
# depends = [ "rtkit" ];
|
||||
# depends = [ "xdg-desktop-portal" ]; # for Realtime portal (dependency cycle)
|
||||
# env PIPEWIRE_LOG_SYSTEMD=false"
|
||||
# env PIPEWIRE_DEBUG="*:3,mod.raop*:5,pw.rtsp-client*:5"
|
||||
# env PIPEWIRE_DEBUG"*:3,mod.raop*:5,pw.rtsp-client*:5"
|
||||
command = pkgs.writeShellScript "pipewire-start" ''
|
||||
mkdir -p $PIPEWIRE_RUNTIME_DIR
|
||||
# nice -n -21 comes from pipewire defaults (niceness: -11)
|
||||
exec nice -n -21 pipewire
|
||||
exec pipewire
|
||||
'';
|
||||
readiness.waitExists = [
|
||||
"$PIPEWIRE_RUNTIME_DIR/pipewire-0"
|
||||
|
@ -169,10 +65,7 @@ in
|
|||
description = "pipewire-pulse: Pipewire compatibility layer for PulseAudio clients";
|
||||
depends = [ "pipewire" ];
|
||||
partOf = [ "sound" ];
|
||||
command = pkgs.writeShellScript "pipewire-pulse-start" ''
|
||||
mkdir -p $XDG_RUNTIME_DIR/pulse
|
||||
exec nice -n -21 pipewire-pulse
|
||||
'';
|
||||
command = "pipewire-pulse";
|
||||
readiness.waitExists = [
|
||||
"$XDG_RUNTIME_DIR/pulse/native"
|
||||
"$XDG_RUNTIME_DIR/pulse/pid"
|
||||
|
@ -200,4 +93,12 @@ in
|
|||
services.udev.packages = lib.mkIf cfg.enabled [
|
||||
cfg.package
|
||||
];
|
||||
|
||||
# rtkit/RealtimeKit: allow applications which want realtime audio (e.g. Dino? Pulseaudio server?) to request it.
|
||||
# this might require more configuration (e.g. polkit-related) to work exactly as desired.
|
||||
# - readme outlines requirements: <https://github.com/heftig/rtkit>
|
||||
# XXX(2023/10/12): rtkit does not play well on moby. any application sending audio out dies after 10s.
|
||||
# - note that `rtkit-daemon` can be launched with a lot of config: pipewire docs (top of this file)
|
||||
# suggest using a much less aggressive canary. maybe try that?
|
||||
security.rtkit.enable = lib.mkIf cfg.enabled true;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
".local/share/io.github.alainm23.planify"
|
||||
];
|
||||
|
||||
buildCost = 2; # webkitgtk-6.0; slow for desktop
|
||||
slowToBuild = true; # webkitgtk-6.0; slow for desktop
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,13 +2,11 @@
|
|||
* to show keybindings: `rofi -show keys`
|
||||
* to show theme config: `rofi -dump-theme`
|
||||
* - for live theme switching: `rofi-theme-selector`
|
||||
*
|
||||
* to see <span ...> markup: <https://docs.gtk.org/Pango/pango_markup.html>
|
||||
*/
|
||||
|
||||
configuration {
|
||||
modes: "combi";
|
||||
font: "sans 19";
|
||||
font: "mono 20";
|
||||
show-icons: true;
|
||||
kb-accept-entry: "Return,KP_Enter,XF86PowerOff";
|
||||
kb-row-up: "Up,XF86AudioRaiseVolume";
|
||||
|
@ -24,14 +22,11 @@ configuration {
|
|||
}
|
||||
/* combi-display-format: "{mode} {text}"; */
|
||||
/* combi-display-format: "{text}"; */
|
||||
/* HACK: combi-display-format: "{mode}...</span>" expects `{mode}` to include <span>, allowing each mode to use custom styling */
|
||||
combi-display-format: "{mode}{text}</span>";
|
||||
combi-display-format: "{mode}{text}";
|
||||
combi-modes: "filebrowser,drun";
|
||||
|
||||
drun {
|
||||
display-name: "<span>";
|
||||
/* icons in /run/current-system/sw/share/icons/Adwaita/16x16/mimetypes */
|
||||
fallback-icon: "application-x-executable";
|
||||
display-name: " ";
|
||||
}
|
||||
drun-use-desktop-cache: true;
|
||||
|
||||
|
@ -40,19 +35,17 @@ configuration {
|
|||
/* directory: "/home"; */
|
||||
|
||||
/* display-name: text to prepend in combi mode */
|
||||
display-name: "<span stretch='semicondensed' size='90%'>";
|
||||
display-name: "/";
|
||||
/* `command` is the prefix to prepend (along with a space) *before* passing it off to `run-command` */
|
||||
command: "xdg-open";
|
||||
directories-first: true;
|
||||
/* sorting-method: name/atime/ctime/mtime */
|
||||
sorting-method: "name";
|
||||
show-hidden: false;
|
||||
fallback-icon: "application-x-generic";
|
||||
}
|
||||
|
||||
run {
|
||||
display-name: "run ";
|
||||
fallback-icon: "application-x-executable";
|
||||
}
|
||||
/* launch applications via my own launcher, which directs them through to xdg-desktop-portal */
|
||||
run-command: "rofi-run-command '{app_id}.desktop' {cmd}";
|
||||
|
@ -114,27 +107,11 @@ window {
|
|||
/* 482px @ font size 20 gives 12 rows + filter */
|
||||
/* 446px @ font size 20 gives 11 rows + filter */
|
||||
/* 90.5% @ font size 20, sway scale 2.0, moby in landscape mode: gives 7 rows + filter */
|
||||
/* 94% @ font size 19, sway scale 2.0, moby in landscape mode: gives 7 rows + filter */
|
||||
/* 465 @ font size 19, sway scale 2.0 gives 11 rows + filter */
|
||||
/* 427 @ font size 19, sway scale 2.0 gives 10 rows + filter */
|
||||
height: calc(429 min 94.0%);
|
||||
height: calc(446 min 90.5%);
|
||||
|
||||
/* anchor the *north* edge of the window at the *north* location of the screen */
|
||||
anchor: north;
|
||||
location: north;
|
||||
/* 8.2% lines up nicely with Firefox */
|
||||
y-offset: 8.2%;
|
||||
|
||||
/* padding: top right bottom left */
|
||||
padding: 4px 0px 1px 0px;
|
||||
}
|
||||
element {
|
||||
border: 0;
|
||||
margin: 0.5px;
|
||||
/* padding: top right bottom left */
|
||||
padding: 0 0 0 4px;
|
||||
spacing: 6px; /* spacing between icon and text */
|
||||
}
|
||||
element-icon {
|
||||
size: 0.8em;
|
||||
/* 11.2% lines up nicely with Firefox */
|
||||
y-offset: 11.2%;
|
||||
}
|
||||
|
|
|
@ -37,25 +37,6 @@ let
|
|||
hash = "sha256-gz3N4uo7IWzzqaPHHVhby/e9NbtzcFJRQwgdNYxO/Yw=";
|
||||
})
|
||||
];
|
||||
|
||||
nativeBuildInputs = (upstream.nativeBuildInputs or []) ++ [
|
||||
pkgs.copyDesktopItems
|
||||
];
|
||||
desktopItems = (upstream.desktopItems or []) ++ [
|
||||
(pkgs.makeDesktopItem {
|
||||
name = "rofi-filebrowser";
|
||||
# alternatively: `rofi -modes filebrowser -show`, however this would require theme tweaking to look good
|
||||
exec = "rofi -combi-modes filebrowser -show";
|
||||
desktopName = "rofi filebrowser";
|
||||
})
|
||||
(pkgs.makeDesktopItem {
|
||||
name = "rofi-applications";
|
||||
exec = "rofi -combi-modes drun -show";
|
||||
desktopName = "rofi applications";
|
||||
mimeTypes = [ "application/x-desktop" ];
|
||||
noDisplay = true;
|
||||
})
|
||||
];
|
||||
});
|
||||
# rofi-emoji = pkgs.rofi-emoji.override {
|
||||
# # plugins must be compiled against the same rofi they're loaded by
|
||||
|
@ -92,7 +73,6 @@ in
|
|||
sandbox.whitelistWayland = true;
|
||||
sandbox.extraHomePaths = [
|
||||
".local/share/applications" #< to locate .desktop files
|
||||
"Apps" #< provide a means to transition from the filebrowser to the app launcher
|
||||
"Books/local"
|
||||
"Books/servo"
|
||||
"Music"
|
||||
|
@ -112,12 +92,8 @@ in
|
|||
"/mnt/servo/media"
|
||||
"/mnt/servo/playground"
|
||||
];
|
||||
sandbox.extraConfig = [
|
||||
"--sanebox-keep-namespace" "pid" # for sane-open to toggle keyboard
|
||||
];
|
||||
|
||||
fs.".config/rofi/config.rasi".symlink.target = ./config.rasi;
|
||||
fs."Apps".symlink.target = ".local/share/applications/rofi-applications.desktop";
|
||||
persist.byStore.cryptClearOnBoot = [
|
||||
# this gets us a few things:
|
||||
# - file browser remembers its last directory
|
||||
|
@ -130,12 +106,12 @@ in
|
|||
packageUnwrapped = pkgs.static-nix-shell.mkBash {
|
||||
pname = "rofi-run-command";
|
||||
srcRoot = ./.;
|
||||
pkgs = [ "sane-open" ];
|
||||
pkgs = [ "sane-open-desktop" "xdg-utils" ];
|
||||
};
|
||||
sandbox.enable = false; #< trivial script, and all our deps are sandboxed
|
||||
|
||||
suggestedPrograms = [
|
||||
"sane-open"
|
||||
"sane-open-desktop"
|
||||
"xdg-utils"
|
||||
];
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p sane-open
|
||||
#!nix-shell -i bash -p sane-open-desktop -p xdg-utils
|
||||
|
||||
# use:
|
||||
# rofi-run-command <handler>.desktop [cmd [args ...]]
|
||||
|
@ -14,10 +14,16 @@ shift
|
|||
binArgs=("$@")
|
||||
|
||||
if [ "$desktop" != .desktop ]; then
|
||||
exec sane-open --auto-keyboard --application "$desktop"
|
||||
# launching an app; the file browser position is no longer interesting: clear it so it opens in ~ next time.
|
||||
# better UX would be to manage this in the other branch:
|
||||
# - open in ~ by default, regardless of last directory
|
||||
# - after launching a *file*, when that file is closed, re-open rofi in that file's directory.
|
||||
# however, `xdg-open` and the `OpenFile` xdg-desktop-portal API don't give any obvious way to block for the app to close.
|
||||
rm -f ~/.cache/rofi/rofi3.filebrowsercache
|
||||
exec sane-open-desktop "$desktop"
|
||||
elif [ "$binary" = "xdg-open" ]; then
|
||||
exec sane-open --auto-keyboard --file "${binArgs[@]}"
|
||||
exec xdg-open "$@"
|
||||
fi
|
||||
|
||||
printf "no .desktop file, and unexpected binary; not invoking: %s %s\n" "$binary" "${binArgs[*]}" >&2
|
||||
printf "no .desktop file, and unexpected binary; not invoking: %s %s" "$binary" "${binArgs[*]}" > /dev/null
|
||||
exit 1
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
# rtkit/RealtimeKit: allow applications which want realtime audio (e.g. Dino? Pulseaudio server?) to request it.
|
||||
# this might require more configuration (e.g. polkit-related) to work exactly as desired.
|
||||
# - readme outlines requirements: <https://github.com/heftig/rtkit>
|
||||
# XXX(2023/10/12): rtkit does not play well on moby. any application sending audio out dies after 10s.
|
||||
# - note that `rtkit-daemon` can be launched with a lot of config
|
||||
# - suggest using a much less aggressive canary. maybe try that?
|
||||
# - see: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Performance-tuning>
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.rtkit;
|
||||
in
|
||||
{
|
||||
sane.programs.rtkit = {
|
||||
packageUnwrapped = pkgs.rmDbusServices pkgs.rtkit;
|
||||
# services.rtkit = {
|
||||
# description = "rtkit: grant realtime scheduling privileges to select processes";
|
||||
# command = "${cfg.package}/libexec/rtkit-daemon";
|
||||
# };
|
||||
};
|
||||
|
||||
systemd.services.rtkit-daemon = lib.mkIf cfg.enabled {
|
||||
description = "rtkit: grant realtime scheduling privileges to select processes";
|
||||
wantedBy = [ "default.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = lib.escapeShellArgs [
|
||||
"${cfg.package}/libexec/rtkit-daemon"
|
||||
"--scheduling-policy=FIFO"
|
||||
"--our-realtime-priority=79"
|
||||
"--max-realtime-priority=78" # N.B.: setting this too aggressively can hang weak devices!
|
||||
"--min-nice-level=-19"
|
||||
"--rttime-usec-max=2000000"
|
||||
"--users-max=100"
|
||||
"--processes-per-user-max=1000"
|
||||
"--threads-per-user-max=10000"
|
||||
"--actions-burst-sec=10"
|
||||
"--actions-per-burst-max=1000"
|
||||
"--canary-cheep-msec=30000"
|
||||
"--canary-watchdog-msec=60000"
|
||||
];
|
||||
|
||||
Type = "simple";
|
||||
# Type = "dbus";
|
||||
# BusName = "org.freedesktop.RealtimeKit1";
|
||||
Restart = "on-failure";
|
||||
# User = "rtkit"; # it wants starts as root
|
||||
# Group = "rtkit";
|
||||
# wantedBy = [ "default.target" ];
|
||||
# TODO: harden
|
||||
CapabilityBoundingSet = "CAP_SYS_NICE CAP_DAC_READ_SEARCH CAP_SYS_CHROOT CAP_SETGID CAP_SETUID";
|
||||
};
|
||||
};
|
||||
users.users.rtkit = lib.mkIf cfg.enabled {
|
||||
isSystemUser = true;
|
||||
group = "rtkit";
|
||||
description = "RealtimeKit daemon";
|
||||
};
|
||||
users.groups.rtkit = lib.mkIf cfg.enabled {};
|
||||
|
||||
|
||||
environment.systemPackages = lib.mkIf cfg.enabled [
|
||||
# for /share/polkit-1, but unclear if actually needed
|
||||
cfg.package
|
||||
];
|
||||
security.polkit = lib.mkIf cfg.enabled {
|
||||
enable = true;
|
||||
};
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
'';
|
||||
});
|
||||
|
||||
persist.byStore.private = [
|
||||
persist.private = [
|
||||
".local/share/s6/logs"
|
||||
];
|
||||
|
||||
|
|
|
@ -88,24 +88,18 @@ in
|
|||
pname = "sane-input-handler";
|
||||
srcRoot = ./.;
|
||||
pkgs = {
|
||||
inherit (pkgs) coreutils jq killall playerctl procps sane-open util-linux wireplumber;
|
||||
inherit (pkgs) coreutils killall playerctl procps sane-open-desktop util-linux wireplumber;
|
||||
sway = config.sane.programs.sway.package.sway-unwrapped;
|
||||
};
|
||||
};
|
||||
suggestedPrograms = [
|
||||
"bonsai"
|
||||
# dependencies which get pulled in unconditionally:
|
||||
"jq"
|
||||
"killall"
|
||||
"playerctl"
|
||||
"procps"
|
||||
"sane-open"
|
||||
"sane-open-desktop"
|
||||
"sway"
|
||||
"wireplumber"
|
||||
# optional integrations:
|
||||
"megapixels"
|
||||
"rofi"
|
||||
"xdg-terminal-exec"
|
||||
"wvkbd"
|
||||
];
|
||||
sandbox.method = "bwrap";
|
||||
|
@ -113,7 +107,7 @@ in
|
|||
sandbox.whitelistDbus = [ "user" ]; #< to launch applications
|
||||
sandbox.extraRuntimePaths = [ "sway" ];
|
||||
sandbox.extraConfig = [
|
||||
"--sanebox-keep-namespace" "pid"
|
||||
"--sane-sandbox-keep-namespace" "pid"
|
||||
];
|
||||
};
|
||||
|
||||
|
@ -144,27 +138,32 @@ in
|
|||
sane.programs.bonsai.config.transitions = lib.mkIf cfg.enabled (friendlyToBonsai {
|
||||
# map sequences of "events" to an argument to pass to sane-input-handler
|
||||
|
||||
# map: power (tap), power (tap) x2
|
||||
power_pressed.power_released.trigger = "power_tap_1";
|
||||
power_pressed.power_released.timeout.ms = 600; # max time within which a second power press will be recognized
|
||||
power_pressed.power_released.power_pressed.power_released.trigger = "power_tap_2";
|
||||
# map power (hold), power tap -> hold:
|
||||
power_pressed.timeout.trigger = "power_hold";
|
||||
power_pressed.timeout.ms = 600;
|
||||
power_pressed.power_released.power_pressed.timeout.trigger = "power_tap_1_hold";
|
||||
power_pressed.power_released.power_pressed.timeout.ms = 750; # this is a long timeout because it's tied to the "kill window" action.
|
||||
|
||||
# map: power (tap) -> volup/voldown
|
||||
power_pressed.power_released.volup_pressed.trigger = "power_then_volup";
|
||||
power_pressed.power_released.voldown_pressed.trigger = "power_then_voldown";
|
||||
|
||||
# map: power + volup/voldown
|
||||
power_pressed.volup_pressed.trigger = "power_and_volup";
|
||||
power_pressed.voldown_pressed.trigger = "power_and_voldown";
|
||||
# map: power (short), power (short) x2, power (long)
|
||||
power_pressed.timeout.ms = 900; # press w/o release. this is a long timeout because it's tied to the "kill window" action.
|
||||
power_pressed.timeout.trigger = "powerhold";
|
||||
power_pressed.power_released.timeout.trigger = "powerbutton_one";
|
||||
power_pressed.power_released.timeout.ms = 300;
|
||||
power_pressed.power_released.power_pressed.trigger = "powerbutton_two";
|
||||
# map power (short) -> volup/voldown
|
||||
power_pressed.power_released.volup_pressed.trigger = "powerbutton_volup";
|
||||
power_pressed.power_released.voldown_pressed.trigger = "powerbutton_voldown";
|
||||
|
||||
# map: volume taps and holds
|
||||
volup_pressed = (volumeActions {}).volup_pressed // {
|
||||
trigger = "volup_start";
|
||||
volup_pressed = (recurseHold "volup" {}) // {
|
||||
# this either becomes volup_hold_* (via recurseHold, above) or:
|
||||
# - a short volup_tap_1 followed by:
|
||||
# - a *finalized* volup_1 (i.e. end of action)
|
||||
# - more taps/holds, in which case we prefix it with `modal_<action>`
|
||||
# to denote that we very explicitly entered this state.
|
||||
#
|
||||
# it's clunky: i do it this way so that voldown can map to keyboard/terminal in unlock mode
|
||||
# but trigger media controls in screenoff
|
||||
# in a way which *still* allows media controls if explicitly entered into via a tap on volup first
|
||||
volup_released = (volumeActions { prefix = "modal_"; }) // {
|
||||
trigger = "volup_tap_1";
|
||||
timeout.ms = 300;
|
||||
timeout.trigger = "volup_1";
|
||||
};
|
||||
};
|
||||
voldown_pressed = (volumeActions {}).voldown_pressed // {
|
||||
trigger = "voldown_start";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p coreutils -p jq -p killall -p playerctl -p procps -p sane-open -p sway -p util-linux -p wireplumber
|
||||
#!nix-shell -i bash -p coreutils -p killall -p playerctl -p procps -p sane-open-desktop -p sway -p util-linux -p wireplumber
|
||||
|
||||
# input map considerations
|
||||
# - using compound actions causes delays.
|
||||
|
@ -12,46 +12,27 @@
|
|||
#
|
||||
# example of a design which considers these things:
|
||||
# - when unlocked:
|
||||
# - volup tap -> file browser
|
||||
# - volup hold -> app menu
|
||||
# - voldown press -> keyboard
|
||||
# - voldown hold -> terminal
|
||||
# - power x2 -> screenoff
|
||||
# - power tap->hold -> kill app
|
||||
# - power,volup -> screen rotate CCW
|
||||
# - power,voldown -> screen rotate CW
|
||||
# - power+volup -> screenshot
|
||||
# - power+voldown -> camera
|
||||
# - volup toggle -> app menu
|
||||
# - voldown press -> keyboard
|
||||
# - voldown hold -> terminal
|
||||
# - power x2 -> screenoff
|
||||
# - hold power -> kill app
|
||||
# - when locked:
|
||||
# - volup tap -> volume up
|
||||
# - volup hold -> media seek forward
|
||||
# - voldown tap -> volume down
|
||||
# - voldown hold -> media seek backward
|
||||
# - power tap -> screen on
|
||||
# - power hold -> play/pause media
|
||||
# - volup tap -> volume up
|
||||
# - volup hold -> media seek forward
|
||||
# - voldown tap -> volume down
|
||||
# - voldown hold -> media seek backward
|
||||
# - power x1 -> screen on
|
||||
# - power x2 -> play/pause media
|
||||
# some trickiness allows for media controls in unlocked mode:
|
||||
# - volup tap -> enter media mode
|
||||
# - i.e. in this state, vol tap/hold is mapped to volume/seek
|
||||
# - if, after entering media mode, no more taps occur, then we trigger the default app-menu action
|
||||
# limitations/downsides:
|
||||
# - voldown hold is over eager: easy to open terminals when phone is slow.
|
||||
# - remap to voldown tap->hold ?
|
||||
#
|
||||
# EXAMPLE EVENT FIRINGS:
|
||||
# - double-tap voldown:
|
||||
# - voldown_start
|
||||
# - voldown_tap_1
|
||||
# - voldown_tap_2
|
||||
# - hold voldown:
|
||||
# - voldown_start
|
||||
# - voldown_hold_1
|
||||
# - voldown_hold_2
|
||||
# - voldown_hold_3
|
||||
# - hold power:
|
||||
# - power_hold (notice: it doesn't fire power_start)
|
||||
# - double-tap power:
|
||||
# - power_tap_1
|
||||
# - power_tap_2
|
||||
# - power tap-then-hold:
|
||||
# - power_tap_1
|
||||
# - power_tap_1_hold
|
||||
|
||||
# - power mappings means phone is artificially slow to unlock.
|
||||
# - media controls when unlocked have quirks:
|
||||
# - mashing voldown to decrease the volume will leave you with a toggled keyboard.
|
||||
# - seeking backward isn't possible except by first tapping volup.
|
||||
|
||||
|
||||
# increments to use for volume adjustment (in %)
|
||||
|
@ -60,12 +41,6 @@ KEYBOARD="${KEYBOARD:-wvkbd-mobintl}"
|
|||
|
||||
action="$1"
|
||||
|
||||
log() {
|
||||
printf "sane-input-handler: %s\n" "$1"
|
||||
}
|
||||
|
||||
## HELPERS
|
||||
|
||||
isTouchOn() {
|
||||
# success if all touch inputs have their events enabled
|
||||
swaymsg -t get_inputs --raw \
|
||||
|
@ -87,23 +62,7 @@ isInhibited() {
|
|||
pidof rofi
|
||||
}
|
||||
|
||||
handleWith() {
|
||||
local state=
|
||||
if [ -n "$_isInhibited" ]; then
|
||||
state="inhibited+"
|
||||
fi
|
||||
if [ -n "$_isAllOn" ]; then
|
||||
state="${state}on"
|
||||
else
|
||||
state="${state}off"
|
||||
fi
|
||||
log "state=$state action=$action: handleWith: $*"
|
||||
"$@"
|
||||
exit $?
|
||||
}
|
||||
|
||||
|
||||
## HANDLERS
|
||||
ignore() {
|
||||
true
|
||||
}
|
||||
|
@ -124,98 +83,106 @@ allOff() {
|
|||
}
|
||||
|
||||
toggleKeyboard() {
|
||||
local keyboardPid=$(pidof "$KEYBOARD")
|
||||
if [ -z "$keyboardPid" ]; then
|
||||
log "cannot find $KEYBOARD"
|
||||
return
|
||||
local kbpid=$(pidof "$KEYBOARD")
|
||||
if [ -z "$kbpid" ] || ! ( env kill -s RTMIN+0 "$kbpid" ); then
|
||||
echo "sane-input-handler: failed to toggle keyboard: $KEYBOARD"
|
||||
fi
|
||||
|
||||
# `env` so that we get the right `kill` binary instead of bash's builtin
|
||||
env kill -s RTMIN+0 "$keyboardPid"
|
||||
}
|
||||
|
||||
## DISPATCHERS
|
||||
handleWith() {
|
||||
state=
|
||||
if [ -n "$_isInhibited" ]; then
|
||||
state="inhibited+"
|
||||
fi
|
||||
if [ -n "$_isAllOn" ]; then
|
||||
state="${state}on"
|
||||
else
|
||||
state="${state}off"
|
||||
fi
|
||||
echo "sane-input-handler: state=$state action=$action: handleWith: $@"
|
||||
"$@"
|
||||
exit 0
|
||||
}
|
||||
|
||||
dispatchDefault() {
|
||||
case "$action" in
|
||||
"power_tap_2")
|
||||
"powerbutton_one")
|
||||
# power once => unlock
|
||||
handleWith allOn
|
||||
;;
|
||||
"powerbutton_two")
|
||||
# power twice => screenoff
|
||||
handleWith allOff
|
||||
;;
|
||||
"power_hold")
|
||||
# power twice => toggle media player
|
||||
handleWith playerctl play-pause
|
||||
;;
|
||||
# powerbutton_three: intentional no-op because overloading the kill-window handler is risky
|
||||
|
||||
volup_tap*)
|
||||
volup_tap*|modal_volup_tap*)
|
||||
handleWith wpctl set-volume @DEFAULT_AUDIO_SINK@ "$VOL_INCR"%+
|
||||
;;
|
||||
voldown_tap*)
|
||||
voldown_tap*|modal_voldown_tap*)
|
||||
handleWith wpctl set-volume @DEFAULT_AUDIO_SINK@ "$VOL_INCR"%-
|
||||
;;
|
||||
|
||||
volup_hold*|modal_volup_hold*)
|
||||
handleWith playerctl position 30+
|
||||
;;
|
||||
voldown_hold*|modal_voldown_hold*)
|
||||
handleWith playerctl position 10-
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
dispatchOff() {
|
||||
case "$action" in
|
||||
"power_tap_1")
|
||||
# power once => unlock
|
||||
"powerbutton_two")
|
||||
# power twice => toggle media player
|
||||
handleWith playerctl play-pause
|
||||
;;
|
||||
"powerhold")
|
||||
# power toggle during deep sleep often gets misread as power hold, so treat same
|
||||
handleWith allOn
|
||||
;;
|
||||
volup_hold*)
|
||||
handleWith playerctl position 30+
|
||||
;;
|
||||
voldown_hold*)
|
||||
handleWith playerctl position 10-
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
dispatchOn() {
|
||||
case "$action" in
|
||||
# power_tap_1: intentional default to no-op (it's important this be unmapped, because events can be misordered with power_tap_1 arriving *after* power_tap_2)
|
||||
# power_tap_2: intentional default to screenoff
|
||||
"power_tap_1_hold")
|
||||
# power tap->hold: kill active window
|
||||
# powerbutton_one: intentional default to no-op
|
||||
# powerbutton_two: intentional default to screenoff
|
||||
"powerhold")
|
||||
# power thrice: kill active window
|
||||
# TODO: disable this if locked (with e.g. schlock, swaylock, etc)
|
||||
handleWith swaymsg kill
|
||||
;;
|
||||
"power_and_volup")
|
||||
# power (hold) -> volup: take screenshot
|
||||
handleWith sane-open --application sane-screenshot.desktop
|
||||
;;
|
||||
"power_and_voldown")
|
||||
# power (hold) -> voldown: open camera
|
||||
handleWith sane-open --auto-keyboard --application org.postmarketos.Megapixels.desktop
|
||||
;;
|
||||
"power_then_volup")
|
||||
"powerbutton_volup")
|
||||
# power (tap) -> volup: rotate CCW
|
||||
handleWith swaymsg -- output '-' transform 90 anticlockwise
|
||||
;;
|
||||
"power_then_voldown")
|
||||
"powerbutton_voldown")
|
||||
# power (tap) -> voldown: rotate CW
|
||||
handleWith swaymsg -- output '-' transform 90 clockwise
|
||||
;;
|
||||
|
||||
"volup_tap_1")
|
||||
# volume up once: filesystem browser
|
||||
handleWith sane-open --auto-keyboard --application rofi-filebrowser.desktop
|
||||
# swallow: this could be the start to a media control (multi taps / holds),
|
||||
# or it could be just a single tap -> release, handled next/below
|
||||
handleWith ignore
|
||||
;;
|
||||
"volup_hold_1")
|
||||
# volume up hold: browse files and apps
|
||||
# reset fs directory: useful in case you get stuck in broken directory (e.g. one which lacks a `..` entry)
|
||||
rm -f ~/.cache/rofi/rofi3.filebrowsercache
|
||||
handleWith sane-open --auto-keyboard --application rofi.desktop
|
||||
"volup_1")
|
||||
# volume up once: system menu
|
||||
handleWith sane-open-desktop rofi.desktop
|
||||
;;
|
||||
|
||||
"voldown_start")
|
||||
# volume down once: toggle keyboard
|
||||
handleWith toggleKeyboard
|
||||
;;
|
||||
"voldown_hold_1")
|
||||
"voldown_hold_2")
|
||||
# hold voldown to launch terminal
|
||||
# note we already triggered the keyboard; that's fine: usually keyboard + terminal go together :)
|
||||
handleWith sane-open --auto-keyboard --application xdg-terminal-exec.desktop
|
||||
# voldown_hold_1 frequently triggers during short taps meant only to reveal the keyboard,
|
||||
# so prefer a longer hold duration
|
||||
handleWith sane-open-desktop xdg-terminal-exec.desktop
|
||||
;;
|
||||
"voldown_tap_1")
|
||||
# swallow, to prevent keyboard from also triggering media controls
|
||||
|
@ -230,8 +197,8 @@ dispatchOn() {
|
|||
|
||||
dispatchInhibited() {
|
||||
case "$action" in
|
||||
"power_tap_1_hold")
|
||||
# power hold: escape hatch in case rofi has hung
|
||||
"powerhold")
|
||||
# power thrice: escape hatch in case rofi has hung
|
||||
handleWith killall -9 rofi
|
||||
;;
|
||||
*)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue