Compare commits

...

195 Commits

Author SHA1 Message Date
72c7287445 mpv: sane-sysvol: monitor pipewire for changes and relay that to uosc 2024-04-09 06:41:48 +00:00
b715fd346f mpv: don't need to force uosc volume state to 0 by default; nil is OK 2024-04-07 00:29:41 +00:00
1b9b0ac0f6 todo.md: add work around signal, mpv 2024-04-07 00:26:15 +00:00
79c4e2c405 mpv: sane-sysvol script: init
it's a one-way volume control, but that's a start
2024-04-07 00:26:15 +00:00
17a3f90825 mpv: rename plugin: sane -> sane-cast 2024-04-06 23:44:01 +00:00
03bec6aab2 coppwr: init at 1.5.1-unstable-2024-03-28 (4e7239d4bd5b2f24f7ebec0a02c1054648a4425c) 2024-04-06 23:42:25 +00:00
3aba91b360 mpv: fix race condition in uosc/ao-volume monitoring 2024-04-06 23:41:59 +00:00
907933612d htop: statically populate config 2024-04-06 23:41:59 +00:00
0db546bf82 nixpkgs: 2024-04-05 -> 2024-04-06
```
• Updated input 'nixpkgs-next-unpatched':
    'github:nixos/nixpkgs/9a0c25978d78416219718437fd7e618668d9ad74' (2024-04-05)
  → 'github:nixos/nixpkgs/7c74352f2f7eca1925729f5c9c80cb89df8e74a2' (2024-04-06)
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/3541c7628de9414f999ced044c95a284584f4ec1' (2024-04-05)
  → 'github:nixos/nixpkgs/c58702222e0a29fd01cc42d70737d699995f6389' (2024-04-06)
```
2024-04-06 10:20:03 +00:00
b4877a488e discord: add media into sandbox 2024-04-06 09:36:55 +00:00
4b3975367a fix warnings: remove xdg-desktop-portal patch; fix mautrix-meta enable logic 2024-04-05 21:40:42 +00:00
38c8d96e5a nixpkgs: 2024-04-04 -> 2024-04-05; nixpkgs-wayland -> 2024-04-04
```
• Updated input 'nixpkgs-next-unpatched':
    'github:nixos/nixpkgs/1ea9e4ecab5010d29f557f7a1f8bb26ad3afd686' (2024-04-04)
  → 'github:nixos/nixpkgs/9a0c25978d78416219718437fd7e618668d9ad74' (2024-04-05)
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/e52ada4e6beea931c244bf8cc4a75d8a07d62373' (2024-04-04)
  → 'github:nixos/nixpkgs/3541c7628de9414f999ced044c95a284584f4ec1' (2024-04-05)
• Updated input 'nixpkgs-wayland':
    'github:nix-community/nixpkgs-wayland/30aa5f8c558cd8a699f9a9227cf7310f291f1e29' (2024-04-03)
  → 'github:nix-community/nixpkgs-wayland/9b77653338f52da4b498abdf4835efb6ff6e453e' (2024-04-04)
```
2024-04-05 09:28:25 +00:00
28110c3e85 fix system hang during vim ctrl+z (disable io_uring in libuv) 2024-04-05 07:29:55 +00:00
43aa498ff9 mpv: fix uosc touch controls 2024-04-05 07:29:15 +00:00
f7e4504764 pict-rs: remove no-transcoding patch (it doesnt apply anymore) 2024-04-04 19:09:12 +00:00
4942fa8a38 nixpkgs: 2024-04-02 -> 2024-04-04
```
• Updated input 'nixpkgs-next-unpatched':
    'github:nixos/nixpkgs/7a5ee8d0c786f9fd789b5bb07245d2a8b12193a4' (2024-04-02)
  → 'github:nixos/nixpkgs/1ea9e4ecab5010d29f557f7a1f8bb26ad3afd686' (2024-04-04)
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/7ab59131642a42b92f4d6554173ef9a35cbb39f0' (2024-04-02)
  → 'github:nixos/nixpkgs/e52ada4e6beea931c244bf8cc4a75d8a07d62373' (2024-04-04)
• Updated input 'nixpkgs-wayland':
    'github:nix-community/nixpkgs-wayland/8c4df2153c615554b516ba698f5f7bd64f7bf520' (2024-03-31)
  → 'github:nix-community/nixpkgs-wayland/30aa5f8c558cd8a699f9a9227cf7310f291f1e29' (2024-04-03)
```
2024-04-04 06:27:24 +00:00
7ab148ea58 servo: migrate /var/media to be 100% on zfs pool 2024-04-04 06:20:50 +00:00
0dfeec3260 mpv-uosc: 5.2.0 -> 5.2.0-unstable-2024-03-13 2024-04-04 06:19:15 +00:00
eb2317a743 mpv: 0.37.0 -> 0.37.0-unstable-2024-03-31 2024-04-04 06:19:15 +00:00
1a0ef28377 sane-bt-add: document how to *move* a torrent 2024-04-03 11:00:54 +00:00
7c3ad85d75 sane-bt-add: fix sandboxing 2024-04-03 09:48:21 +00:00
7766e1cec1 sane_bt: save torrents to /var/media/torrents by default 2024-04-03 09:48:21 +00:00
158e674f83 sane_bt.py: fix dict typo 2024-04-03 09:48:21 +00:00
410097480f docs: servo: fs: fix setfacl typo 2024-04-03 09:48:10 +00:00
f5fadbe4cf transmission: place torrents in a separate directory, and copy them to the main media directory on completion 2024-04-03 09:48:10 +00:00
a0550660e7 feeds: add The Corresponding Source podcast 2024-04-02 22:10:36 +00:00
bad6c353ed sane-bt: refactor 2024-04-02 20:53:01 +00:00
a814832e48 feeds: add Hacker Public Radio podcast 2024-04-02 19:34:42 +00:00
a4312f1494 nixpkgs: 2024-04-01 -> 2024-04-02
```
• Updated input 'nixpkgs-next-unpatched':
    'github:nixos/nixpkgs/409234ba0c73ce2cee676f14b68a88e2b5a52af8' (2024-04-01)
  → 'github:nixos/nixpkgs/7a5ee8d0c786f9fd789b5bb07245d2a8b12193a4' (2024-04-02)
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/e7de14ea98fe984aa73a5a6b2f540ca817471106' (2024-04-01)
  → 'github:nixos/nixpkgs/7ab59131642a42b92f4d6554173ef9a35cbb39f0' (2024-04-02)
```
2024-04-02 09:46:40 +00:00
747032d9a4 dino: run with higher scheduling priority 2024-04-02 09:02:44 +00:00
9b2e35b93f pipewire: ship rtkit and unlock better scheduling priority 2024-04-02 09:02:06 +00:00
d2751237c1 xdg-desktop-portal-wlr: propery document its dependency on pipewire 2024-04-02 09:01:35 +00:00
ae87160de3 dino: 0.4.3 -> 0.4.3-unstable-2024-04-01 2024-04-02 04:57:11 +00:00
a90a213cc0 apps: gui: disable under-used "blanket" noise generator 2024-04-02 04:18:53 +00:00
24c04b8fc0 docs: xdg-desktop-portal: link to Door Knocker debugging tool 2024-04-02 04:18:53 +00:00
d0b022d1c6 libnice: 0.1.21 -> 0.1.22 2024-04-02 04:18:35 +00:00
9d9791814a audacity: fix sandboxing 2024-04-02 02:56:51 +00:00
b85d4b20f8 nixpkgs: 2024-03-31 -> 2024-04-01, nixpkgs-wayland
```
• Updated input 'nixpkgs-next-unpatched':
    'github:nixos/nixpkgs/6b7b5776bef8eed645f63725ce31fa0946f997ff' (2024-03-31)
  → 'github:nixos/nixpkgs/409234ba0c73ce2cee676f14b68a88e2b5a52af8' (2024-04-01)
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/7848d6f048d38c42a8aeeff7fe7d36916ffb8284' (2024-03-31)
  → 'github:nixos/nixpkgs/e7de14ea98fe984aa73a5a6b2f540ca817471106' (2024-04-01)
• Updated input 'nixpkgs-wayland':
    'github:nix-community/nixpkgs-wayland/11544449e6b696e209463aae094e736368eef757' (2024-03-30)
  → 'github:nix-community/nixpkgs-wayland/8c4df2153c615554b516ba698f5f7bd64f7bf520' (2024-03-31)
• Updated input 'nixpkgs-wayland/lib-aggregate':
    'github:nix-community/lib-aggregate/120535dff702c551ec76a125a181d90131407264' (2024-03-24)
  → 'github:nix-community/lib-aggregate/9c06929b83e57c18d125f1105ba6a423f24083d2' (2024-03-31)
• Updated input 'nixpkgs-wayland/lib-aggregate/nixpkgs-lib':
    'github:nix-community/nixpkgs.lib/b2a1eeef8c185f6bd27432b053ff09d773244cbc' (2024-03-24)
  → 'github:nix-community/nixpkgs.lib/90b1a963ff84dc532db92f678296ff2499a60a87' (2024-03-31)
```
2024-04-01 07:42:47 +00:00
331e673589 common/fs: mount /mnt/servo/media/* directories more granularly
this benefits sandboxing
2024-04-01 07:31:25 +00:00
bbb93600b7 /mnt/servo/*: mount in a way which doesn't block sandboxes 2024-04-01 06:00:17 +00:00
c0de54c11a curlftpfs: exit on timeout error 2024-04-01 04:02:32 +00:00
0d29722443 common/fs: refactor and DRY 2024-04-01 02:12:06 +00:00
1c2a375b6d common/fs: split curlftpfs into sane.programs
this makes it easier to build outside of /etc/fstab context, and opens a future path to sandboxing
2024-04-01 00:50:14 +00:00
b6840a3ed4 curlftpfs: build via my own repo 2024-04-01 00:43:07 +00:00
74e994598e feeds: add David Revoy 2024-03-31 20:28:41 +00:00
856b6fcd7a feeds: add Willow 2024-03-31 18:20:49 +00:00
2404fb66f3 nixpkgs: 2024-03-26 -> 2024-0-31; nixpkgs-wayland, sops
```
• Updated input 'nixpkgs-next-unpatched':
    'github:nixos/nixpkgs/c904e6bf24e582e4fe28e988b7cdb7e9fda595c3' (2024-03-26)
  → 'github:nixos/nixpkgs/6b7b5776bef8eed645f63725ce31fa0946f997ff' (2024-03-31)
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/da0502af1164c4fd04ebcd3acde1e378a861bd19' (2024-03-26)
  → 'github:nixos/nixpkgs/7848d6f048d38c42a8aeeff7fe7d36916ffb8284' (2024-03-31)
• Updated input 'nixpkgs-wayland':
    'github:nix-community/nixpkgs-wayland/7274c73cf5708ed3a814edc9605463ec4a116619' (2024-03-25)
  → 'github:nix-community/nixpkgs-wayland/11544449e6b696e209463aae094e736368eef757' (2024-03-30)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/405987a66cce9a4a82f321f11b205982a7127c88' (2024-03-24)
  → 'github:Mic92/sops-nix/99b1e37f9fc0960d064a7862eb7adfb92e64fa10' (2024-03-31)
• Updated input 'sops-nix/nixpkgs-stable':
    'github:NixOS/nixpkgs/ac6bdf6181666ebb4f90dd20f31e2fa66ede6b68' (2024-03-23)
  → 'github:NixOS/nixpkgs/2b4e3ca0091049c6fbb4908c66b05b77eaef9f0c' (2024-03-30)
```
2024-03-31 06:30:10 +00:00
cd6a91e995 sway: tune sandboxing 2024-03-31 05:59:10 +00:00
89d4b0ae0b s6-rc: don't tee to /dev/stderr, as i don't want any logs going to the console and interfering with text entry 2024-03-31 05:20:33 +00:00
ade680d9d2 unl0kr: remove legacy wayland stuff (it's handled by s6 now) 2024-03-31 05:20:33 +00:00
6d4a43fa0d sway: warn when needed runtime dirs dont exist 2024-03-31 05:20:20 +00:00
d3ad661970 servo: zfs: enable reflink support 2024-03-31 03:48:34 +00:00
c9632b05f9 linux-megous: fix config so that it boots to graphics again
see: <https://github.com/NixOS/nixpkgs/pull/298332>
2024-03-31 03:24:33 +00:00
1e7de43da8 docs: sway: mention that hotplugging is broken 2024-03-31 03:24:33 +00:00
eff37765ae sane.image: fix so imgs.moby includes a working bootloader 2024-03-31 03:24:33 +00:00
a65673847a superTux, superTuxKard: don't ship on moby
i don't use it there; it wastes deploy time
2024-03-31 03:24:33 +00:00
930c5e2412 delfin: don't ship
i don't use it; it wastes deploy time
2024-03-31 03:24:33 +00:00
aff2a78ec3 sane-reclaim-boot-space: fix "self.format_attr" typo 2024-03-31 03:23:49 +00:00
f01758503c linux-megous: orange-pi-6.7-20240211-1928 -> orange-pi-6.7-20240306-2359
still getting PHY issues with it, but it boots to ssh
2024-03-29 18:18:37 +00:00
e855be4796 hosts/common: port /mnt/servo/* from NFS -> FTP
fuse ftp seems to be easier to debug than kernel nfs so far
2024-03-28 23:15:05 +00:00
701e10b121 hosts/common/fs: optimize NFS options 2024-03-28 23:15:05 +00:00
eadb2057d9 sane-wipe: port from systemd -> s6 2024-03-28 23:15:05 +00:00
5ed29ceb47 servo: /var/media: fixup permissions so everything is r/w by "media" group, including sftpgo 2024-03-28 23:14:40 +00:00
725ab13628 servo: nfs: allow UDP NFSv3 connections 2024-03-27 00:54:58 +00:00
32e691b85b feeds: add Hardcore Software by Steven Sinofsky 2024-03-26 14:08:13 +00:00
0108502055 feedbackd: fix so it depends on pipewire before launch 2024-03-26 13:48:13 +00:00
6c5b32aac2 s6-rc: fix so the service manager knows about readiness notifications again 2024-03-26 13:34:38 +00:00
f59dd99470 s6-rc: init services in the "down" state 2024-03-26 12:55:40 +00:00
55c8a98c33 s6-rc: pre-compute more stuff as nix exprs; don't even run s6-rc-init 2024-03-26 12:36:46 +00:00
7bb67391ae nixpkgs: remove obsoleted patches 2024-03-26 11:16:43 +00:00
c6a1f310a0 servo: net: actually assert that ovpns exists if we fail to add it 2024-03-26 11:13:10 +00:00
1d494513a9 slskd: document common errors/flakiness 2024-03-26 11:04:21 +00:00
fb79ca4c8e programs: iproute: use a less restrictive sandbox 2024-03-26 10:54:29 +00:00
3cf42db7dc slskd: fix for more recent nixpkgs 2024-03-26 10:47:20 +00:00
aba5eee837 nixpkgs: 2024-03-25 -> 2024-03-26; others
```
• Updated input 'nixpkgs-next-unpatched':
    'github:nixos/nixpkgs/8794d573a4fdd15b0002135cebf65a8f5d9adb6c' (2024-03-25)
  → 'github:nixos/nixpkgs/c904e6bf24e582e4fe28e988b7cdb7e9fda595c3' (2024-03-26)
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/0d2dc20762f21c3e43fc2acc03f9552adbc392bc' (2024-03-25)
  → 'github:nixos/nixpkgs/da0502af1164c4fd04ebcd3acde1e378a861bd19' (2024-03-26)
• Updated input 'nixpkgs-wayland':
    'github:nix-community/nixpkgs-wayland/59f7ad2c27737a7ea7ad16a48c95631f159ef822' (2024-03-24)
  → 'github:nix-community/nixpkgs-wayland/7274c73cf5708ed3a814edc9605463ec4a116619' (2024-03-25)
• Updated input 'uninsane-dot-org':
    'git+https://git.uninsane.org/colin/uninsane?ref=refs/heads/master&rev=9a3b2d74c3e6d177fd1317d03f4e3eecca7beb7b' (2024-03-19)
  → 'git+https://git.uninsane.org/colin/uninsane?ref=refs/heads/master&rev=b9502e6f190752d327f8cee7fa4b139094bd7c16' (2024-03-25)
```
2024-03-26 09:44:45 +00:00
5cd9f34884 s6-rc: remove more unnecessarily files from live dir 2024-03-26 00:45:24 +00:00
2cabe51956 s6-rc: remove a couple more unused files from the live dir 2024-03-26 00:22:14 +00:00
cb8e9b7a23 s6-rc: make it so, once started, other programs can start/stop services but NOT edit/create them 2024-03-26 00:11:02 +00:00
4eb6b5735e users/s6-rc: allow startS6 "" 2024-03-25 16:46:51 +00:00
5d3899959b users/s6-rc: split out compiled var 2024-03-25 14:56:41 +00:00
ad951ad919 users/s6-rc: add symlink capabilities to my fs abstraction 2024-03-25 14:46:43 +00:00
5ecabc57bf feeds: add low<-tech magazine 2024-03-25 13:13:41 +00:00
48a4c1bd26 feeds: add nixpkgs.news 2024-03-25 13:13:03 +00:00
1f47c5ba2e nixpkgs: 2024-03-23 -> 2024-03-25; nixpkgs-wayland; sops-nix
```
• Updated input 'nixpkgs-next-unpatched':
    'github:nixos/nixpkgs/f4a0cee9233d6db356a0bdab307e865327a9b96b' (2024-03-23)
  → 'github:nixos/nixpkgs/8794d573a4fdd15b0002135cebf65a8f5d9adb6c' (2024-03-25)
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/499583c00280ed90686b058e38a68fc9be2f4c4c' (2024-03-23)
  → 'github:nixos/nixpkgs/0d2dc20762f21c3e43fc2acc03f9552adbc392bc' (2024-03-25)
• Updated input 'nixpkgs-wayland':
    'github:nix-community/nixpkgs-wayland/0d291a959d01bb5d2bd2e20689b78258fa842cca' (2024-03-23)
  → 'github:nix-community/nixpkgs-wayland/59f7ad2c27737a7ea7ad16a48c95631f159ef822' (2024-03-24)
• Updated input 'nixpkgs-wayland/lib-aggregate':
    'github:nix-community/lib-aggregate/f890211817b941d9ed9de48d62ba8553fa2c20f3' (2024-03-17)
  → 'github:nix-community/lib-aggregate/120535dff702c551ec76a125a181d90131407264' (2024-03-24)
• Updated input 'nixpkgs-wayland/lib-aggregate/nixpkgs-lib':
    'github:nix-community/nixpkgs.lib/fa827dda806c5aa98f454da4c567991ab8ce422c' (2024-03-17)
  → 'github:nix-community/nixpkgs.lib/b2a1eeef8c185f6bd27432b053ff09d773244cbc' (2024-03-24)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/83b68a0e8c94b72cdd0a6e547a14ca7eb1c03616' (2024-03-17)
  → 'github:Mic92/sops-nix/405987a66cce9a4a82f321f11b205982a7127c88' (2024-03-24)
• Updated input 'sops-nix/nixpkgs-stable':
    'github:NixOS/nixpkgs/6dc11d9859d6a18ab0c5e5829a5b8e4810658de3' (2024-03-16)
  → 'github:NixOS/nixpkgs/ac6bdf6181666ebb4f90dd20f31e2fa66ede6b68' (2024-03-23)
```
2024-03-25 00:23:58 +00:00
febedb9323 nits: update --replace uses to --replace-{fail,quiet} as appropriate 2024-03-24 12:49:18 +00:00
aed5ea4b2e static-nix-shell: dont fail build if "nix-shell" occurs in the file as a non-directive 2024-03-24 12:28:23 +00:00
4e74ba5bab swaync-service-dispatcher: fix start/stop inversion 2024-03-24 12:22:54 +00:00
03fbb780b2 sane.programs: sandbox: refactor extraRuntimePaths computation 2024-03-24 12:03:38 +00:00
9c0b175260 swaync: allow toggling of s6 services 2024-03-24 11:54:12 +00:00
e62be121e2 users/services: s6: fix so s6-rc stop can actually kill processes 2024-03-24 11:48:41 +00:00
774066e53c swaync: factor out a "service-dispatcher" 2024-03-24 09:21:07 +00:00
86400f45d6 swaync: port to s6 2024-03-24 08:56:06 +00:00
ddef2d0bfc swaync: rename: print-is-active -> print-systemd-active 2024-03-24 08:54:27 +00:00
0172aa0b69 swaync: refactor 2024-03-24 08:54:27 +00:00
ce991c8887 swaync: refactor 2024-03-24 08:54:27 +00:00
92d8d42997 swaync: split the buttons out of the main config file 2024-03-24 08:54:27 +00:00
1c4ef84ec7 swaync: remove legacy sxmo-specific notification visibility rules 2024-03-24 08:54:27 +00:00
a820ae57c0 swaync: remove sxmo timer rules 2024-03-24 08:54:27 +00:00
89f913cadc xdg-desktop-portal: 1.18.2 -> 1.18.2-unstable-2024-03-11
this will *hopefully* improve stability
2024-03-24 08:08:48 +00:00
d14fda2e62 engrampa: remove custom patch (upstream released a new version) 2024-03-24 07:46:22 +00:00
f680a4a25c engrampa: patch the package via sane.programs, not nixpkgs overlay 2024-03-24 07:44:30 +00:00
7c461cee2f pkgs: remove gnome-control-center (unused) 2024-03-24 07:38:22 +00:00
47d37b4ce5 xdg-desktop-portal: enable logging 2024-03-24 07:19:33 +00:00
a1cc045837 waybar: update persistent_workspaces -> persistent-workspaces
the former is deprecated
2024-03-24 06:17:43 +00:00
72dd556b72 assorted: fix meta.homepage URLs for nur packages 2024-03-24 03:54:09 +00:00
ff9e1111b3 nixpkgs: 2024-03-22 -> 2024-03-23
```
• Updated input 'nixpkgs-next-unpatched':
    'github:nixos/nixpkgs/5132e88bc7caad27f4cf28cbaa7b04c915e2c261' (2024-03-22)
  → 'github:nixos/nixpkgs/f4a0cee9233d6db356a0bdab307e865327a9b96b' (2024-03-23)
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/e5d75daa715cbaee8b989f58884a31c0be00a8a0' (2024-03-22)
  → 'github:nixos/nixpkgs/499583c00280ed90686b058e38a68fc9be2f4c4c' (2024-03-23)
• Updated input 'nixpkgs-wayland':
    'github:nix-community/nixpkgs-wayland/61e42f8f6c9b0c2b557a4c978ea11874d3761083' (2024-03-22)
  → 'github:nix-community/nixpkgs-wayland/0d291a959d01bb5d2bd2e20689b78258fa842cca' (2024-03-23)
```
2024-03-23 21:49:57 +00:00
7f8cae42ff s6: migrate to /run/user/$id/s6 2024-03-23 21:33:08 +00:00
5b83d4d944 s6-rc: patch to use /run/user/$id/s6 as the default live dir 2024-03-23 20:52:42 +00:00
f16a68f5bb element-desktop: use native wayland, not Xwayland 2024-03-23 18:51:41 +00:00
6646a21089 nixpatches: fix handbrake hash 2024-03-23 18:02:45 +00:00
4bf43d884a nixpkgs: minor bump
```
• Updated input 'nixpkgs-next-unpatched':
    'github:nixos/nixpkgs/86b5ff8306a63bb266590018b21d2ae502a74880' (2024-03-22)
  → 'github:nixos/nixpkgs/5132e88bc7caad27f4cf28cbaa7b04c915e2c261' (2024-03-22)
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/6e147dce88054c47dd90c0be8c33500e023f8261' (2024-03-22)
  → 'github:nixos/nixpkgs/e5d75daa715cbaee8b989f58884a31c0be00a8a0' (2024-03-22)
• Updated input 'nixpkgs-wayland':
    'github:nix-community/nixpkgs-wayland/7d053c33b7a130ddada43ba09b089244390b3a23' (2024-03-19)
  → 'github:nix-community/nixpkgs-wayland/61e42f8f6c9b0c2b557a4c978ea11874d3761083' (2024-03-22)
```
2024-03-23 18:01:51 +00:00
46fe6c690b sway: fix WAYLAND_DISPLAY to be relative 2024-03-23 17:59:37 +00:00
dd7b1dae5f sway: remove unnecessary pidspace sandbox exception
i guess this was from when SWAYSOCK was named after the pid?
2024-03-23 17:35:39 +00:00
2e58353b0e refactor: users/services: have waitExists support waiting on multiple paths 2024-03-23 17:28:29 +00:00
f65d3d04dc sway: do the WAYLAND_DISPLAY moving inside sway config itself 2024-03-23 17:09:57 +00:00
6102a0301d sway: move $WAYLAND_DISPLAY into a subdir to make it easier to sandbox 2024-03-23 16:37:22 +00:00
39de5b84c2 sway: fix readiness check 2024-03-23 15:54:20 +00:00
5205251f6f programs: xwayland: sandbox it without exposing net access 2024-03-23 15:33:23 +00:00
8c48adefa5 pipewire: move sockets into a subdirectory for easier sandboxing 2024-03-23 13:34:13 +00:00
db2801c652 sway: don't launch s6 from within the sway session 2024-03-23 13:11:14 +00:00
4418c16967 users/services: s6: push bundle dependencies down onto the actual atomic services 2024-03-23 13:04:12 +00:00
8008fd35cb modules/users: allow readiness.pathExists 2024-03-23 13:03:11 +00:00
36ea5b53ad sway: place SWAYSOCK in a subdirectory 2024-03-23 11:33:58 +00:00
552d14b1b5 nixpatches: fix hashes 2024-03-23 09:29:34 +00:00
c404c8b2ae xdg-terminal-exec: fix bad interaction with TERMINAL 2024-03-23 09:28:26 +00:00
d129ae2c03 koreader-from-src: remove a bunch of old cruft
this is beginning to look upstream-ready :)
2024-03-22 20:16:33 +00:00
58341b75f2 koreader-from-src: popen-noshell,turbo: populate more directly 2024-03-22 20:05:22 +00:00
373388c5b8 koreader-from-src: mupdf: populate more directly 2024-03-22 19:54:13 +00:00
8d45aad534 xdg-dirs: populate env vars at login
otherwise i believe they dont actually take effect?
2024-03-22 19:31:04 +00:00
a783bc9577 koreader-from-src: minizip: populate in a simpler manner 2024-03-22 19:24:19 +00:00
267d374b19 xdg-dirs: specify XDG_SCREENSHOTS_DIR 2024-03-22 19:23:14 +00:00
e67ce7576b koreader-from-src: lunasvg: populate in a simpler way 2024-03-22 19:16:45 +00:00
ce770dbea9 koreader: lodepng: provide in a more direct manner 2024-03-22 18:59:17 +00:00
e7a65abd0b koreader-from-src: libk2pdfopt: populate in a more direct manner 2024-03-22 18:53:23 +00:00
702a6cc7fa koreader: kobo-usbms: link into build environment in a simpler fashion 2024-03-22 18:28:19 +00:00
f889543aa5 koreader-from-src: fbink: provide the sources more directly 2024-03-22 18:21:26 +00:00
98073f5e19 koreader-from-src: inject tesseract sources via make args 2024-03-22 17:58:41 +00:00
96c330813f xterm: remove
xdg-terminal-exec updated recently, and it seems to prefer xterm over alacritty for some reason
2024-03-22 17:28:26 +00:00
a6d9c62bcf lemonade: 2023.10.29 -> 2024.03.20
not much changed
2024-03-22 17:27:20 +00:00
8ff34d8518 koreader: migrate leptonica dep away from the sources hack
i hope that by using the makefile vars i can eventually get rid of the
"stamp" nonsense.
2024-03-22 17:16:19 +00:00
e11dd0ecb0 firefox-extensions: update to latest 2024-03-22 16:27:23 +00:00
3b6dfea2d0 koreader-from-src: notes about updating leptonica 2024-03-22 15:53:17 +00:00
22254db74c koreader-from-src: cleanup (remove unused "machineAgnostic" and "package" third-party dependency options) 2024-03-22 14:42:37 +00:00
a316c87db6 koreader-from-src: use nixpkgs nanosvg (or, most of it) 2024-03-22 14:29:23 +00:00
fe2fb40565 koreader-from-src: don't vendor lj-wpaclient (unused) 2024-03-22 13:40:09 +00:00
cd63fdb510 koreader: cleanup 2024-03-22 12:12:14 +00:00
1e25f37774 nixpkgs: 2024-03-21 -> 2024-03-22
```
• Updated input 'nixpkgs-next-unpatched':
    'github:nixos/nixpkgs/33cddc79aa062e243c59d3ac36b8b938f267748a' (2024-03-21)
  → 'github:nixos/nixpkgs/86b5ff8306a63bb266590018b21d2ae502a74880' (2024-03-22)
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/783b241f949bea90e3347ce516ad8af84bde3126' (2024-03-21)
  → 'github:nixos/nixpkgs/6e147dce88054c47dd90c0be8c33500e023f8261' (2024-03-22)
```
2024-03-22 11:56:51 +00:00
cdac23211c nixpkgs: 2024-03-13 -> 2024-03-21; others
```
• Updated input 'nixpkgs-next-unpatched':
    'github:nixos/nixpkgs/4ee0840ba2ecc50458ab1677d108afcd691f4815' (2024-03-13)
  → 'github:nixos/nixpkgs/33cddc79aa062e243c59d3ac36b8b938f267748a' (2024-03-21)
• Updated input 'nixpkgs-unpatched':
    'github:nixos/nixpkgs/2dbc8f62d8af7a1ab962e4b20d12b25ddcb86ced' (2024-03-13)
  → 'github:nixos/nixpkgs/783b241f949bea90e3347ce516ad8af84bde3126' (2024-03-21)
• Updated input 'nixpkgs-wayland':
    'github:nix-community/nixpkgs-wayland/771cb198c281db6918829651f194bf4db32e342d' (2024-03-13)
  → 'github:nix-community/nixpkgs-wayland/7d053c33b7a130ddada43ba09b089244390b3a23' (2024-03-19)
• Updated input 'nixpkgs-wayland/lib-aggregate':
    'github:nix-community/lib-aggregate/45b75bf534592c0c1c881a1c447f7fdb37a87eaf' (2024-03-11)
  → 'github:nix-community/lib-aggregate/f890211817b941d9ed9de48d62ba8553fa2c20f3' (2024-03-17)
• Updated input 'nixpkgs-wayland/lib-aggregate/nixpkgs-lib':
    'github:nix-community/nixpkgs.lib/630ebdc047ca96d8126e16bb664c7730dc52f6e6' (2024-03-10)
  → 'github:nix-community/nixpkgs.lib/fa827dda806c5aa98f454da4c567991ab8ce422c' (2024-03-17)
• Updated input 'sops-nix':
    'github:Mic92/sops-nix/e52d8117b330f690382f1d16d81ae43daeb4b880' (2024-03-11)
  → 'github:Mic92/sops-nix/83b68a0e8c94b72cdd0a6e547a14ca7eb1c03616' (2024-03-17)
• Updated input 'sops-nix/nixpkgs-stable':
    'github:NixOS/nixpkgs/b17375d3bb7c79ffc52f3538028b2ec06eb79ef8' (2024-03-10)
  → 'github:NixOS/nixpkgs/6dc11d9859d6a18ab0c5e5829a5b8e4810658de3' (2024-03-16)
• Updated input 'uninsane-dot-org':
    'git+https://git.uninsane.org/colin/uninsane?ref=refs/heads/master&rev=bb10cd8853d05191e4d62947d93687c462e92c30' (2024-02-15)
  → 'git+https://git.uninsane.org/colin/uninsane?ref=refs/heads/master&rev=9a3b2d74c3e6d177fd1317d03f4e3eecca7beb7b' (2024-03-19)
```
2024-03-21 18:03:37 +00:00
e6c00e6215 users/services: implement dbus readiness checks for s6-rc 2024-03-21 17:16:11 +00:00
fff9d69e3e users/services: s6-rc: implement readiness polling 2024-03-21 17:16:11 +00:00
4fa7e6113d users/services: s6: exec into the run/finish commands 2024-03-21 17:16:11 +00:00
16ca71188f users/services: simplify the before/after/wantedBy criteria, to match s6 concepts 2024-03-21 17:16:11 +00:00
c5c37e79ac users/services: actually remove the systemd backend 2024-03-21 17:16:11 +00:00
d2f6648bce users/services: refactor: replace ExecStart/ExecStopPost with command/cleanupCommand
note that this completely breaks the systemd backend (though easily fixable if wanted)
2024-03-21 17:16:11 +00:00
5c9c7f8073 modules/users/s6-rc: add per-service logging 2024-03-21 17:16:11 +00:00
218072b2fe refactor: modules/users/s6-rc.nix 2024-03-21 17:16:11 +00:00
d4f217a4f5 refactor: modules/users/s6-rc.nix 2024-03-21 17:16:11 +00:00
40f6f88a64 users/services: s6: remove broken log stuff
apparently the /log shorthand is only applicable to base `s6-supervise`,
and not `s6-rc`. "pipeline"s are the s6-rc equivalent:
<https://wiki.gentoo.org/wiki/S6-rc#Longrun_pipelining>
2024-03-21 17:16:11 +00:00
fbbb09322a users/services: s6-rc: support ExecStopPost option 2024-03-21 17:16:11 +00:00
e7153ce4a1 users/services: remove ExecStartPre option 2024-03-21 17:16:11 +00:00
b13e7c38c7 users/services: remove script option 2024-03-21 17:16:11 +00:00
058c95bb2c sysvol: remove autostart option (assume true) 2024-03-21 17:16:11 +00:00
9b793ef4b8 programs: services: no need to fully-qualify paths anymore (s6 doesn't require) 2024-03-21 17:16:11 +00:00
1417497001 users/services: remove serviceConfig.Type option 2024-03-21 17:16:11 +00:00
db12e03f64 users/services: remove oneshot service type 2024-03-21 17:16:11 +00:00
dee4866737 users/services: remove ConditionEnvironment option 2024-03-21 17:16:11 +00:00
81a6c53c26 users/services: remove RemainAfterExit option 2024-03-21 17:16:11 +00:00
9afd9725d1 users: services: remove no-longer-needed Restart and RestartSec options 2024-03-21 17:16:11 +00:00
384bc9e816 xdg-desktop-portal: fix to not over-escape $HOME in s6 service definition 2024-03-21 17:16:11 +00:00
452619dbfc s6: log when a service starts up
it still seems to be all logging into a single file though?
2024-03-21 17:16:11 +00:00
6c6e10e470 s6: install manpages 2024-03-21 17:16:11 +00:00
dcdf58e1ab sway: simplify wrapper 2024-03-21 17:16:11 +00:00
48b2280f2e feedbackd: fix over-escaping of $HOME 2024-03-21 17:16:11 +00:00
8bedc860ae s6: add some minimal logging
the root s6 call seems to be doing some logging, notably feedbackd; still don't know where the other logs are going
2024-03-21 17:16:11 +00:00
cbecdc4a95 s6: use exec in the run trampoline, to forward file descriptors and keep a cleaner process tree 2024-03-21 17:16:11 +00:00
e1001f57c5 modules/users: remove no-longer-need environment option 2024-03-21 17:16:11 +00:00
291e704477 programs: replace systemd-specific 'environment' option with generic 'env'
note, these services no longer work with systemd, because systemd expects absolute paths
2024-03-21 17:16:11 +00:00
d199e9df99 programs: wob (and wob-audio): remove
i don't use it, and its service file was no longer compatible with s6 (it used 'environment')
2024-03-21 17:16:11 +00:00
2336767059 port service manager to s6
still a lot of cleanup to do (e.g. support dbus service types), but it boots to a usable desktop
2024-03-21 17:16:11 +00:00
63af94383b sane-input-handler: only inhibit controls if screen is on 2024-03-21 17:16:11 +00:00
05b37669e3 s6-rc: fix service run file to have expected format 2024-03-21 17:16:11 +00:00
ea9768c6ab modules/users: prototype s6 integration: ~/.config/s6/{sources,compiled} 2024-03-21 17:16:11 +00:00
38353dbc29 modules/users: remove unused requiredBy service option 2024-03-21 17:16:11 +00:00
ef4a8e1989 modules: users: split services -> fs mapping into own systemd.nix file 2024-03-21 17:16:11 +00:00
acc9a9cb48 modules/users: make it a directory 2024-03-21 17:16:11 +00:00
0335b89a12 fractal: stop persisting old directories 2024-03-21 17:16:01 +00:00
0a6b0cbec7 gtkcord4: rename to dissent 2024-03-21 17:16:01 +00:00
df2310d590 gnome-keyring: ensure the keyring directory is created before entering the sandbox 2024-03-21 17:16:01 +00:00
70b5c57b50 modules/programs: enforce (or rather document) a stricter schema
this should make it easier to switch to a different service manager
2024-03-21 17:16:01 +00:00
c28ac38652 modules/users: refactor to remove inherits 2024-03-21 17:16:01 +00:00
52133fde30 delfin: 0.4.1 -> 0.4.2 2024-03-21 13:51:46 +00:00
098cd2051e sftpgo: expose to the WAN 2024-03-14 13:11:44 +00:00
691a7d7ff7 sftpgo: configure for credential-gated r/w access 2024-03-14 13:11:44 +00:00
c7c2785ad8 sftpgo_external_auth_hook: refactor 2024-03-14 13:11:44 +00:00
4c1a7fc910 sftpgo: port auth program to python 2024-03-14 13:11:44 +00:00
113 changed files with 19482 additions and 2109 deletions

View File

@@ -1,9 +1,14 @@
## BUGS
- Signal restart loop drains battery
- decrease s6 restart time?
- mpv `player-mode=pseudo-gui` swallows all loggin
- ringer (i.e. dino incoming call) doesn't prevent moby from sleeping
- sway mouse/kb hotplug doesn't work
- `nix` operations from lappy hang when `desko` is unreachable
- could at least direct the cache to `http://desko-hn:5001`
## REFACTORING:
- REMOVE DEPRECATED `crypt` from sftpgo_auth_hook
- consolidate ~/dev and ~/ref
- ~/dev becomes a link to ~/ref/cat/mine
- fold hosts/common/home/ssh.nix -> hosts/common/users/colin.nix

50
flake.lock generated
View File

@@ -61,11 +61,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1710184940,
"narHash": "sha256-FzYm4td3FJfzOAuEkCXt3KdUgZuA072OAQXqIq+IAMo=",
"lastModified": 1711886936,
"narHash": "sha256-D2WENp9GuaCostvNcQ7vElekk0V5cuMdnFZ7NfRhVrQ=",
"owner": "nix-community",
"repo": "lib-aggregate",
"rev": "45b75bf534592c0c1c881a1c447f7fdb37a87eaf",
"rev": "9c06929b83e57c18d125f1105ba6a423f24083d2",
"type": "github"
},
"original": {
@@ -152,11 +152,11 @@
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1710031547,
"narHash": "sha256-pkUg3hOKuGWMGF9WEMPPN/G4pqqdbNGJQ54yhyQYDVY=",
"lastModified": 1711846064,
"narHash": "sha256-cqfX0QJNEnge3a77VnytM0Q6QZZ0DziFXt6tSCV8ZSc=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "630ebdc047ca96d8126e16bb664c7730dc52f6e6",
"rev": "90b1a963ff84dc532db92f678296ff2499a60a87",
"type": "github"
},
"original": {
@@ -167,11 +167,11 @@
},
"nixpkgs-next-unpatched": {
"locked": {
"lastModified": 1710337169,
"narHash": "sha256-u2/74bhQuWykUZDWUIhHd6IpZiaQ0hSpTBbx0y9opkE=",
"lastModified": 1712383280,
"narHash": "sha256-YL8miM11o/jMqOwt5DsdyhPgh/JgCl1kOIzvX7ukniY=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "4ee0840ba2ecc50458ab1677d108afcd691f4815",
"rev": "7c74352f2f7eca1925729f5c9c80cb89df8e74a2",
"type": "github"
},
"original": {
@@ -183,11 +183,11 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1710033658,
"narHash": "sha256-yiZiVKP5Ya813iYLho2+CcFuuHpaqKc/CoxOlANKcqM=",
"lastModified": 1711819797,
"narHash": "sha256-tNeB6emxj74Y6ctwmsjtMlzUMn458sBmwnD35U5KIM4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b17375d3bb7c79ffc52f3538028b2ec06eb79ef8",
"rev": "2b4e3ca0091049c6fbb4908c66b05b77eaef9f0c",
"type": "github"
},
"original": {
@@ -199,11 +199,11 @@
},
"nixpkgs-unpatched": {
"locked": {
"lastModified": 1710339354,
"narHash": "sha256-+P5ccUPiLouHexb8aJrUOVOIja9qm+fG57pgAu7uIRs=",
"lastModified": 1712398506,
"narHash": "sha256-oopwPeBKBXQEw2BlyK2jEs2farZ5uMjAZU7H4FpGuGE=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "2dbc8f62d8af7a1ab962e4b20d12b25ddcb86ced",
"rev": "c58702222e0a29fd01cc42d70737d699995f6389",
"type": "github"
},
"original": {
@@ -223,11 +223,11 @@
]
},
"locked": {
"lastModified": 1710317949,
"narHash": "sha256-bwReMiWPA2wYBvKEMhO8pJcu+o+7ocy5hGkSoawTHu0=",
"lastModified": 1712237761,
"narHash": "sha256-NoMBBCADTms3yx5BL+sbc7vfDivNiYULO6t9GBAsPt0=",
"owner": "nix-community",
"repo": "nixpkgs-wayland",
"rev": "771cb198c281db6918829651f194bf4db32e342d",
"rev": "9b77653338f52da4b498abdf4835efb6ff6e453e",
"type": "github"
},
"original": {
@@ -254,11 +254,11 @@
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1710195194,
"narHash": "sha256-KFxCJp0T6TJOz1IOKlpRdpsCr9xsvlVuWY/VCiAFnTE=",
"lastModified": 1711855048,
"narHash": "sha256-HxegAPnQJSC4cbEbF4Iq3YTlFHZKLiNTk8147EbLdGg=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "e52d8117b330f690382f1d16d81ae43daeb4b880",
"rev": "99b1e37f9fc0960d064a7862eb7adfb92e64fa10",
"type": "github"
},
"original": {
@@ -311,11 +311,11 @@
]
},
"locked": {
"lastModified": 1707981105,
"narHash": "sha256-YCU1eNslBHabjP+OCY+BxPycEFO9SRUts10MrN9QORE=",
"lastModified": 1711371733,
"narHash": "sha256-+brjlMyLVnVADY31sN82Ap0IsPE2WZEwHUd94sY6BXI=",
"ref": "refs/heads/master",
"rev": "bb10cd8853d05191e4d62947d93687c462e92c30",
"revCount": 235,
"rev": "b9502e6f190752d327f8cee7fa4b139094bd7c16",
"revCount": 237,
"type": "git",
"url": "https://git.uninsane.org/colin/uninsane"
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -81,6 +81,7 @@ let
(fromDb "feeds.simplecast.com/xKJ93w_w" // uncat) # Atlas Obscura
(fromDb "feeds.transistor.fm/acquired" // tech)
(fromDb "fulltimenix.com" // tech)
(fromDb "hackerpublicradio.org" // tech)
(fromDb "lexfridman.com/podcast" // rat)
(fromDb "mapspodcast.libsyn.com" // uncat) # Multidisciplinary Association for Psychedelic Studies
(fromDb "omegataupodcast.net" // tech) # 3/4 German; 1/4 eps are English
@@ -103,6 +104,7 @@ let
(fromDb "techwontsave.us" // pol) # rec by Cory Doctorow
(fromDb "wakingup.libsyn.com" // pol) # Sam Harris
(fromDb "werenotwrong.fireside.fm" // pol)
(mkPod "https://sfconservancy.org/casts/the-corresponding-source/feeds/ogg/" // tech)
# (fromDb "feeds.libsyn.com/421877" // rat) # Less Wrong Curated
# (fromDb "feeds.megaphone.fm/hubermanlab" // uncat) # Daniel Huberman on sleep
@@ -142,6 +144,7 @@ let
(fromDb "edwardsnowden.substack.com" // pol // text)
(fromDb "fasterthanli.me" // tech)
(fromDb "gwern.net" // rat)
(fromDb "hardcoresoftware.learningbyshipping.com" // tech) # Steven Sinofsky
(fromDb "harihareswara.net" // tech // pol) # rec by Cory Doctorow
(fromDb "ianthehenry.com" // tech)
(fromDb "idiomdrottning.org" // uncat)
@@ -158,6 +161,7 @@ let
(fromDb "mg.lol" // tech)
(fromDb "mindingourway.com" // rat)
(fromDb "morningbrew.com/feed" // pol)
(fromDb "nixpkgs.news" // tech)
(fromDb "overcomingbias.com" // rat) # Robin Hanson
(fromDb "palladiummag.com" // uncat)
(fromDb "philosopher.coach" // rat) # Peter Saint-Andre -- side project of stpeter.im
@@ -187,6 +191,7 @@ let
# (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
@@ -201,6 +206,7 @@ let
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
(mkText "https://nixos.org/blog/announcements-rss.xml" // tech // infrequent) # more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html>
(mkText "https://nixos.org/blog/stories-rss.xml" // tech // weekly)
(mkText "https://solar.lowtechmagazine.com/posts/index.xml" // tech // weekly)
# (mkText "https://til.simonwillison.net/tils/feed.atom" // tech // weekly)
# (mkText "https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" // pol // weekly) # Matt Levine (preview/paywalled)
(mkText "https://www.stratechery.com/rss" // pol // weekly) # Ben Thompson
@@ -226,6 +232,7 @@ let
images = [
(fromDb "catandgirl.com" // img // humor)
(fromDb "davidrevoy.com" // img // art)
(fromDb "miniature-calendar.com" // img // art // daily)
(fromDb "pbfcomics.com" // img // humor)
(fromDb "poorlydrawnlines.com/feed" // img // humor)

View File

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

View File

@@ -35,6 +35,16 @@
# servo needs zfs though, which doesn't support every kernel.
boot.kernelPackages = lib.mkDefault pkgs.zfs.latestCompatibleLinuxPackages;
# TODO: remove after linux 6.9. see: <https://github.com/axboe/liburing/issues/1113>
# - <https://github.com/neovim/neovim/issues/28149>
# - <https://git.kernel.dk/cgit/linux/commit/?h=io_uring-6.9&id=e5444baa42e545bb929ba56c497e7f3c73634099>
# when removing, try starting and suspending (ctrl+z) two instances of neovim simultaneously.
# if the system doesn't freeze, then this is safe to remove.
# added 2024-04-04
sane.user.fs.".profile".symlink.text = lib.mkBefore ''
export UV_USE_IO_URING=0
'';
# hack in the `boot.shell_on_fail` arg since that doesn't always seem to work.
boot.initrd.preFailCommands = "allowShell=1";

View File

@@ -10,6 +10,7 @@
XDG_MUSIC_DIR="$HOME/Music"
XDG_PICTURES_DIR="$HOME/Pictures"
XDG_PUBLICSHARE_DIR="$HOME/.xdg/Public"
XDG_SCREENSHOTS_DIR="$HOME/Pictures/Screenshots"
XDG_TEMPLATES_DIR="$HOME/.xdg/Templates"
XDG_VIDEOS_DIR="$HOME/Videos"
'';
@@ -17,4 +18,12 @@
# prevent `xdg-user-dirs-update` from overriding/updating our config
# see <https://manpages.ubuntu.com/manpages/bionic/man5/user-dirs.conf.5.html>
sane.user.fs.".config/user-dirs.conf".symlink.text = "enabled=False";
sane.user.fs.".profile".symlink.text = ''
# configure XDG_<type>_DIR preferences (e.g. for downloads, screenshots, etc)
# surround with `set -o allexport` since user-dirs.dirs doesn't `export` its vars
set -a
source $HOME/.config/user-dirs.dirs
set +a
'';
}

View File

@@ -64,4 +64,9 @@
# pkgs.udisks
# pkgs.wpa_supplicant
];
# systemd by default forces shitty defaults for e.g. /tmp/.X11-unix.
# nixos propagates those in: <nixos/modules/system/boot/systemd/tmpfiles.nix>
# by overwriting this with an empty file, we can effectively remove it.
environment.etc."tmpfiles.d/x11.conf".text = "# (removed by Colin)";
}

View File

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

View File

@@ -35,15 +35,15 @@ in
# fix the self-contained ucm files i source from to have correct path within the alsa-ucm-conf source tree
substituteInPlace ucm2/Allwinner/A64/PinePhone/PinePhone.conf \
--replace 'HiFi.conf' '/Allwinner/A64/PinePhone/HiFi.conf'
--replace-fail 'HiFi.conf' '/Allwinner/A64/PinePhone/HiFi.conf'
substituteInPlace ucm2/Allwinner/A64/PinePhone/PinePhone.conf \
--replace 'VoiceCall.conf' '/Allwinner/A64/PinePhone/VoiceCall.conf'
--replace-fail 'VoiceCall.conf' '/Allwinner/A64/PinePhone/VoiceCall.conf'
'' + lib.optionalString cfg.config.preferEarpiece ''
# decrease the priority of the internal speaker so that sounds are routed
# to the earpiece by default.
# this is just personal preference.
substituteInPlace ucm2/Allwinner/A64/PinePhone/* \
--replace 'PlaybackPriority 300' 'PlaybackPriority 100'
substituteInPlace ucm2/Allwinner/A64/PinePhone/{HiFi.conf,VoiceCall.conf} \
--replace-fail 'PlaybackPriority 300' 'PlaybackPriority 100'
'';
});

View File

@@ -79,6 +79,7 @@ in
"powertop"
"pstree"
"ripgrep"
"s6-rc" # service manager
"screen"
"smartmontools" # smartctl
# "socat"
@@ -122,6 +123,7 @@ in
# "gopass"
# "gopass-jsonapi"
# "helix" # text editor
"htop" # needed as a user package, for ~/.config/htop
# "libsecret" # for managing user keyrings (secret-tool)
# "lm_sensors" # for sensors-detect
# "lshw"
@@ -274,13 +276,24 @@ in
dig.sandbox.net = "all";
# creds, but also 200 MB of node modules, etc
discord.persist.byStore.private = [ ".config/discord" ];
discord.suggestedPrograms = [ "xwayland" ];
discord.sandbox.method = "bwrap";
discord.sandbox.wrapperType = "inplace"; #< /opt-style packaging
discord.sandbox.whitelistAudio = true;
discord.sandbox.whitelistDbus = [ "user" ]; # needed for xdg-open
discord.sandbox.whitelistWayland = true;
discord.sandbox.whitelistX = true;
discord.sandbox.net = "clearnet";
discord.persist.byStore.private = [ ".config/discord" ];
discord.sandbox.extraHomePaths = [
# still needs these paths despite it using the portal's file-chooser :?
"Pictures/cat"
"Pictures/Screenshots"
"Pictures/servo-macros"
"Videos/local"
"Videos/servo"
"tmp"
];
dtc.sandbox.method = "bwrap";
dtc.sandbox.autodetectCliPaths = true; # TODO:sandbox: untested
@@ -397,7 +410,7 @@ in
gh.persist.byStore.private = [ ".config/gh" ];
gimp.sandbox.method = "bwrap";
gimp.sandbox.net = "clearnet"; #< for Xwayland
gimp.sandbox.whitelistX = true;
gimp.sandbox.whitelistWayland = true;
gimp.sandbox.extraHomePaths = [
"Pictures/albums"
@@ -509,16 +522,6 @@ in
host.sandbox.method = "landlock";
host.sandbox.net = "all"; #< technically, only needs to contact localhost's DNS server
htop.sandbox.method = "landlock";
htop.sandbox.extraPaths = [
"/proc"
"/sys/devices"
];
htop.persist.byStore.plaintext = [
# consider setting `show_program_path=0` and either `hide_userland_threads=1` or `show_thread_names=1`
".config/htop"
];
iftop.sandbox.method = "landlock";
iftop.sandbox.capabilities = [ "net_raw" ];
@@ -551,6 +554,10 @@ in
iproute2.sandbox.method = "landlock";
iproute2.sandbox.net = "all";
iproute2.sandbox.capabilities = [ "net_admin" ];
iproute2.sandbox.extraPaths = [
"/run/netns" # for `ip netns ...` to work
"/var/run/netns"
];
iptables.sandbox.method = "landlock";
iptables.sandbox.net = "all";
@@ -611,19 +618,6 @@ in
lua = {};
"mate.engrampa".packageUnwrapped = pkgs.rmDbusServices pkgs.mate.engrampa;
"mate.engrampa".sandbox.method = "bwrap"; # TODO:sandbox: untested
"mate.engrampa".sandbox.whitelistWayland = true;
"mate.engrampa".sandbox.autodetectCliPaths = "existingOrParent";
"mate.engrampa".sandbox.extraHomePaths = [
"archive"
"Books/local"
"Books/servo"
"records"
"ref"
"tmp"
];
mercurial.sandbox.method = "bwrap"; # TODO:sandbox: untested
mercurial.sandbox.net = "clearnet";
mercurial.sandbox.whitelistPwd = true;
@@ -829,6 +823,8 @@ in
sqlite = {};
sshfs-fuse = {}; # used by fs.nix
strace.sandbox.enable = false; #< needs to `exec` its args, and therefore support *anything*
subversion.sandbox.method = "bwrap";
@@ -922,7 +918,7 @@ in
xwayland.sandbox.method = "bwrap";
xwayland.sandbox.wrapperType = "inplace"; #< consumers use it as a library (e.g. wlroots)
xwayland.sandbox.whitelistWayland = true; #< just assuming this is needed
xwayland.sandbox.net = "clearnet"; #< just assuming this is needed (X11 traffic)
xwayland.sandbox.whitelistX = true;
xwayland.sandbox.whitelistDri = true; #< would assume this gives better gfx perf
xterm.sandbox.enable = false; # need to be able to do everything

View File

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

View File

@@ -118,18 +118,9 @@ in
services.bonsaid = {
description = "bonsai: programmable input dispatcher";
after = [ "graphical-session.target" ];
wantedBy = [ "graphical-session.target" ];
script = ''
${pkgs.coreutils}/bin/rm -f $XDG_RUNTIME_DIR/bonsai
exec ${cfg.package}/bin/bonsaid -t ${cfg.config.configFile}
'';
serviceConfig = {
Type = "simple";
Restart = "always";
RestartSec = "5s";
};
partOf = [ "graphical-session" ];
command = "bonsaid -t ${cfg.config.configFile}";
cleanupCommand = "rm -f $XDG_RUNTIME_DIR/bonsai";
};
};
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -34,15 +34,8 @@ in
services.dconf = {
description = "dconf configuration database/server";
after = [ "graphical-session.target" ];
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${lib.getLib cfg.package}/libexec/dconf-service";
Type = "simple";
Restart = "always";
RestartSec = "5s";
};
partOf = [ "graphical-session" ];
command = "${lib.getLib cfg.package}/libexec/dconf-service";
};
# supposedly necessary for packages which haven't been wrapped (i.e. wrapGtkApp?),

View File

@@ -21,11 +21,14 @@
./chatty.nix
./conky
./cozy.nix
./curlftpfs.nix
./dconf.nix
./deadd-notification-center
./dialect.nix
./dino.nix
./dissent.nix
./element-desktop.nix
./engrampa.nix
./epiphany.nix
./evince.nix
./fcitx5.nix
@@ -50,9 +53,9 @@
./gpodder.nix
./grimshot.nix
./gthumb.nix
./gtkcord4.nix
./handbrake.nix
./helix.nix
./htop
./imagemagick.nix
./jellyfin-media-player.nix
./kdenlive.nix
@@ -87,6 +90,7 @@
./rhythmbox.nix
./ripgrep.nix
./rofi
./s6-rc.nix
./sane-input-handler
./sane-screenshot.nix
./sane-scripts.nix
@@ -118,7 +122,6 @@
./wine.nix
./wireplumber.nix
./wireshark.nix
./wob
./wvkbd.nix
./xarchiver.nix
./xdg-desktop-portal.nix

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,10 +24,7 @@
# - nixpkgs has a few themes: `fcitx5-{material-color,nord,rose-pine}`
# - NUR has a few themes
# - <https://github.com/catppuccin/fcitx5>
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.fcitx5;
in
{ lib, pkgs, ... }:
{
sane.programs.fcitx5 = {
packageUnwrapped = pkgs.fcitx5-with-addons.override {
@@ -100,15 +97,8 @@ in
services.fcitx5 = {
description = "fcitx5: input method (IME) for emoji/internationalization";
after = [ "graphical-session.target" ];
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
ExecStart="${cfg.package}/bin/fcitx5";
Type = "simple";
Restart = "always";
RestartSec = "10s";
};
partOf = [ "graphical-session" ];
command = "fcitx5";
};
env.XMODIFIERS = "@im=fcitx";

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@
# disable expensive sambda dependency; i don't use it.
packageUnwrapped = pkgs.handbrake.override {
ffmpeg_5-full = pkgs.ffmpeg_5-full.override {
ffmpeg-full = pkgs.ffmpeg-full.override {
withSamba = false;
};
};

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,18 @@
# - list: <https://github.com/stax76/awesome-mpv>
# - list: <https://nudin.github.io/mpv-script-directory/>
# - browse DLNA shares: <https://github.com/chachmu/mpvDLNA>
# - act as a DLNS renderer (sink): <https://github.com/xfangfang/Macast>
# - act as a DLNA renderer (sink): <https://github.com/xfangfang/Macast>
# - update watch_later periodically -- not just on exit: <https://gist.github.com/CyberShadow/2f71a97fb85ed42146f6d9f522bc34ef>
# - <https://github.com/AN3223/dotfiles/blob/master/.config/mpv/scripts/auto-save-state.lua>
# - touch shortcuts (double-tap L/R portions of window to seek, etc): <https://github.com/christoph-heinrich/mpv-touch-gestures>
# - <https://github.com/omeryagmurlu/mpv-gestures>
# - jellyfin client: <https://github.com/EmperorPenguin18/mpv-jellyfin>
# - DLNA client (player only: no casting): <https://github.com/chachmu/mpvDLNA>
# - search videos on Youtube: <https://github.com/rozari0/mpv-youtube-search>
# - <https://github.com/CogentRedTester/mpv-scripts/blob/master/youtube-search.lua>
# - sponsorblock: <https://codeberg.org/jouni/mpv_sponsorblock_minimal>
# - screenshot-to-clipboard: <https://github.com/zc62/mpv-scripts/blob/master/screenshot-to-clipboard.js>
# - mpv-as-image-viewer: <https://github.com/guidocella/mpv-image-config>
# debugging:
# - enter console by pressing backtick.
# > `set volume 50` -> sets application volume to 50%
@@ -17,6 +28,7 @@
# > `show-text "vol: ${volume}"` -> get the volume
# - show script output by running mpv with `--msg-level=all=trace`
# - and then just `print(...)` from lua & it'll show in terminal
# - requires that mpv.conf NOT include player-operation-mode=pseudo-gui
# - invoke mpv with `--no-config` to have it not read ~/.config/mpv/*
# - press `i` to show decoder info
#
@@ -28,53 +40,92 @@
let
cfg = config.sane.programs.mpv;
uosc = pkgs.mpvScripts.uosc.overrideAttrs (upstream: {
# patch so that the volume control corresponds to `ao-volume`, i.e. the system-wide volume.
# this is particularly nice for moby, because it avoids the awkwardness that system volume
# is hard to adjust while screen is on.
# note that only under alsa (`-ao=alsa`) does `ao-volume` actually correspond to system volume.
postPatch = (upstream.postPatch or "") + ''
substituteInPlace src/uosc/main.lua \
--replace-fail "mp.observe_property('volume'" "mp.observe_property('ao-volume'"
substituteInPlace src/uosc/elements/Volume.lua \
--replace-fail "mp.commandv('set', 'volume'" "mp.commandv('set', 'ao-volume'" \
--replace-fail "mp.set_property_native('volume'" "mp.set_property('ao-volume'"
version = "5.2.0-unstable-2024-03-13";
src = lib.warnIf (lib.versionOlder "5.2.0" upstream.version) "uosc outdated; remove patch?" pkgs.fetchFromGitHub {
owner = "tomasklaen";
repo = "uosc";
rev = "6fa34c31d0a5290dee83282205768d15111df7d8";
hash = "sha256-qxyNZHmH33bKRp4heFSC+RtvSApIfbVFt4otfS351nE=";
};
# src = pkgs.fetchFromGitea {
# domain = "git.uninsane.org";
# owner = "colin";
# repo = "uosc";
# rev = "dev-sane-5.2.0";
# hash = "sha256-lpqk4nnCxDZr/Y7/seM4VyR30fVrDAT4VP7C8n88lvA=";
# };
# `ao-volume` isn't actually an observable property.
# as of 2024/03/02, they *may* be working on that:
# - <https://github.com/mpv-player/mpv/pull/13604#issuecomment-1971665736>
# in the meantime, just query the volume every tick (i.e. frame).
# alternative is mpv's JSON IPC feature, where i could notify its socket whenever pipewire volume changes.
cat <<EOF >> src/uosc/main.lua
function update_ao_volume()
local vol = mp.get_property('ao-volume')
if vol ~= nil then
vol = tonumber(vol)
if vol ~= state.volume then
set_state('volume', vol)
request_render()
postPatch = (upstream.postPatch or "") + ''
### patch so touch controls work well with sway 1.9+
### in particular, "mouse.hover" is *always* false for touch events (i guess this is a bug in mpv?)
### and a touch release event is always followed by a mouse move to the cursor (that's a sway thing) which doesn't make sense.
# 1. always listen for mbtn_left events, even before a hover event would activate a zone:
substituteInPlace src/uosc/lib/cursor.lua \
--replace-fail \
"if binding and cursor:collides_with(zone.hitbox)" \
"if binding"
# 2. uosc already simulates mouse movements on touch down, but because of the hover handling, they get misunderstood as mouse leaves.
# so, bypass the cursor:leave() check.
substituteInPlace src/uosc/lib/cursor.lua \
--replace-fail \
"handle_mouse_pos(nil, mp.get_property_native('mouse-pos'))" \
"local mpos = mp.get_property_native('mouse-pos')
cursor:move(mpos.x, mpos.y)
cursor.hover_raw = mpos.hover"
# 3. explicitly fire a cursor:leave on touch release, so that all zones are deactivated (and control visibility goes back to default state)
substituteInPlace src/uosc/lib/cursor.lua \
--replace-fail \
"cursor:create_handler('primary_up')" \
"function(...)
cursor:trigger('primary_up', ...)
if not cursor.hover_raw then
cursor:leave()
end
end
end
-- tick seems to occur on every redraw (even when volume is hidden).
-- in practice: for every new frame of the source, or whenever the cursor is moved.
mp.register_event('tick', update_ao_volume)
-- if paused and cursor isn't moving, then `tick` isn't called. fallback to a timer.
mp.add_periodic_timer(2, update_ao_volume)
-- invoke immediately to ensure state.volume is non-nil
update_ao_volume()
if state.volume == nil then
state.volume = 0
end
EOF
end"
# 4. sometimes we get a touch movement shortly AFTER touch is released:
# detect that and ignore it
substituteInPlace src/uosc/lib/cursor.lua \
--replace-fail \
"cursor:move(mouse.x, mouse.y)" \
"local last_down = cursor.last_event['primary_down'] or { time = 0 }
local last_up = cursor.last_event['primary_up'] or { time = 0 }
if cursor.hover_raw or last_down.time >= last_up.time then cursor:move(mouse.x, mouse.y) end"
### patch so that uosc volume control is routed to sane-sysvol.
### this is particularly nice for moby, because it avoids the awkwardness that system volume
### is hard to adjust while screen is on.
### previously i used ao-volume instead of sane-sysvol: but that forced `ao=alsa`
### and came with heavy perf penalties (especially when adjusting the volume)
substituteInPlace src/uosc/main.lua \
--replace-fail \
"mp.observe_property('volume'" \
"mp.observe_property('user-data/sane-sysvol/volume'"
substituteInPlace src/uosc/elements/Volume.lua \
--replace-fail \
"mp.commandv('set', 'volume'" \
"mp.set_property_native('user-data/sane-sysvol/volume'" \
--replace-fail \
"mp.set_property_native('volume'" \
"mp.set_property_native('user-data/sane-sysvol/volume'"
'';
});
mpv-unwrapped = pkgs.mpv-unwrapped.overrideAttrs (upstream: {
version = "0.37.0-unstable-2024-03-31";
src = lib.warnIf (lib.versionOlder "0.37.0" upstream.version) "mpv outdated; remove patch?" pkgs.fetchFromGitHub {
owner = "mpv-player";
repo = "mpv";
rev = "4ce4bf1795e6dfd6f1ddf07fb348ce5d191ab1dc";
hash = "sha256-nOGuHq7SWDAygROV7qHtezDv1AsMpseImI8TVd3F+Oc=";
};
patches = [];
});
in
{
sane.programs.mpv = {
packageUnwrapped = with pkgs; wrapMpv mpv-unwrapped {
packageUnwrapped = pkgs.wrapMpv (mpv-unwrapped.override { lua = pkgs.luajit; }) {
scripts = [
mpvScripts.mpris
mpvScripts.mpv-playlistmanager
pkgs.mpvScripts.mpris
pkgs.mpvScripts.mpv-playlistmanager
uosc
# pkgs.mpv-uosc-latest
];
@@ -139,7 +190,9 @@ in
# for `watch_later`
".local/state/mpv"
];
fs.".config/mpv/scripts/sane/main.lua".symlink.target = ./sane-main.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;

View File

@@ -31,7 +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/blast #! Audiocast
ctrl+t script-binding sane/go2tv-video #! Cast
# script-binding sane/go2tv-stream #! Cast (...) > Stream
# script-binding sane/go2tv-gui #! Cast (...) > GUI
ctrl+r script-binding sane-cast/blast #! Audiocast
ctrl+t script-binding sane-cast/go2tv-video #! Cast
# script-binding sane-cast/go2tv-stream #! Cast (...) > Stream
# script-binding sane-cast/go2tv-gui #! Cast (...) > GUI

View File

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

View File

@@ -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
}

View File

@@ -4,7 +4,7 @@
#
# send a test notification with:
# - `ntfy pub "https://ntfy.uninsane.org/$(cat ~/.config/ntfy-sh/topic)" test`
{ config, lib, ... }:
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.ntfy-sh;
in
@@ -27,16 +27,13 @@ in
services.ntfy-sub = {
description = "listen for push-notifications";
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
script = ''
partOf = lib.mkIf cfg.config.autostart [ "default" ];
command = let
sub = pkgs.writeShellScriptBin "ntfy-sub" ''
topic=$(cat ~/.config/ntfy-sh/topic)
ntfy sub "https://ntfy.uninsane.org:2587/$topic"
exec ntfy sub "https://ntfy.uninsane.org:2587/$topic"
'';
serviceConfig = {
Type = "simple";
Restart = "always";
RestartSec = "20s";
};
in "${sub}/bin/ntfy-sub";
};
};
}

View File

@@ -1,4 +1,6 @@
# administer with pw-cli, pw-mon, pw-top commands
#
# performance tuning: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Performance-tuning>
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.pipewire;
@@ -9,6 +11,16 @@ in
# 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.wrapperType = "inplace"; #< its config files refer to its binaries by full path
sandbox.extraConfig = [
"--sane-sandbox-keep-namespace" "pid"
@@ -28,50 +40,37 @@ in
".config/pulse"
];
# 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
env.PIPEWIRE_RUNTIME_DIR = "$XDG_RUNTIME_DIR/pipewire";
services.pipewire = {
description = "pipewire: multimedia service";
after = [ "graphical-session.target" ];
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/pipewire";
ExecStartPost = pkgs.writeShellScript "pipewire-wait-started" ''
waitFor() {
while [ ! -e "$1" ]; do
sleep 1
done
}
waitFor "$XDG_RUNTIME_DIR/pipewire-0"
waitFor "$XDG_RUNTIME_DIR/pipewire-0-manager"
partOf = [ "sound" ];
# 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"
command = pkgs.writeShellScript "pipewire-start" ''
mkdir -p $PIPEWIRE_RUNTIME_DIR
exec pipewire
'';
ExecStopPost = ''rm -f "$XDG_RUNTIME_DIR/{pipewire-0,pipewire-0.lock,pipewire-0-manager,pipewire-0-manager.lock}"'';
Type = "simple";
Restart = "always";
RestartSec = "5s";
};
# environment.PIPEWIRE_LOG_SYSTEMD = "false";
# environment.PIPEWIRE_DEBUG = "*:3,mod.raop*:5,pw.rtsp-client*:5";
readiness.waitExists = [
"$PIPEWIRE_RUNTIME_DIR/pipewire-0"
"$PIPEWIRE_RUNTIME_DIR/pipewire-0-manager"
];
cleanupCommand = ''rm -f "$PIPEWIRE_RUNTIME_DIR/{pipewire-0,pipewire-0.lock,pipewire-0-manager,pipewire-0-manager.lock}"'';
};
services.pipewire-pulse = {
description = "pipewire-pulse: Pipewire compatibility layer for PulseAudio clients";
after = [ "pipewire.service" ];
wantedBy = [ "pipewire.service" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/pipewire-pulse";
ExecStartPost = pkgs.writeShellScript "pipewire-pulse-wait-started" ''
waitFor() {
while [ ! -e "$1" ]; do
sleep 1
done
}
waitFor "$XDG_RUNTIME_DIR/pulse/native"
waitFor "$XDG_RUNTIME_DIR/pulse/pid"
'';
ExecStopPost = ''rm -f "$XDG_RUNTIME_DIR/pulse/{native,pid}"'';
Type = "simple";
Restart = "always";
RestartSec = "5s";
};
depends = [ "pipewire" ];
partOf = [ "sound" ];
command = "pipewire-pulse";
readiness.waitExists = [
"$XDG_RUNTIME_DIR/pulse/native"
"$XDG_RUNTIME_DIR/pulse/pid"
];
cleanupCommand = ''rm -f "$XDG_RUNTIME_DIR/pulse/{native,pid}"'';
};
};
@@ -99,5 +98,7 @@ in
# 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.
# security.rtkit.enable = lib.mkIf cfg.enabled true;
# - 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;
}

View File

@@ -1,4 +1,4 @@
{ config, ... }:
{ ... }:
{
sane.programs.playerctl = {
sandbox.method = "bwrap";
@@ -8,13 +8,9 @@
services.playerctld = {
description = "playerctl daemon to keep track of which MPRIS players were recently active";
documentation = [ "https://github.com/altdesktop/playerctl/issues/161" ];
wantedBy = [ "default.target" ]; #< TODO: maybe better to zero `wantedBy` here and have the specific consumers (e.g. swaync) explicitly depend on this.
serviceConfig.ExecStart = "${config.sane.programs.playerctl.package}/bin/playerctld";
# serviceConfig.Type = "dbus";
# serviceConfig.BusName = "org.mpris.MediaPlayer2.Player";
serviceConfig.Type = "simple"; # playerctl also supports a --daemon option, idk if that's better
serviceConfig.Restart = "on-failure";
serviceConfig.RestartSec = "10s";
partOf = [ "default" ]; #< TODO: maybe better to zero `wantedBy` here and have the specific consumers (e.g. swaync) explicitly depend on this.
command = "playerctld";
# readiness.waitDbus = "org.mpris.MediaPlayer2.Player"; #< doesn't work... did the endpoint change?
};
};
}

View File

@@ -0,0 +1,34 @@
{ pkgs, ... }:
{
sane.programs.s6-rc = {
packageUnwrapped = pkgs.s6-rc.overrideAttrs (upstream: {
nativeBuildInputs = (upstream.nativeBuildInputs or []) ++ (with pkgs; [
makeWrapper
]);
# s6-rc looks for files in /run/s6/{live,compiled,...} by default.
# let's patch that to be a user-specific runtime dir, since i run it as an ordinary user.
# note that one can still manually specify --live; later definitions will override earlier definitions.
postInstall = (upstream.postInstall or "") + ''
for prog in s6-rc s6-rc-bundle s6-rc-db s6-rc-format-upgrade s6-rc-init s6-rc-update; do
wrapProgram "$bin/bin/$prog" \
--add-flags '-l $XDG_RUNTIME_DIR/s6/live'
done
'';
});
persist.private = [
".local/share/s6/logs"
];
sandbox.enable = false; # service manager
suggestedPrograms = [
"s6-rc-man-pages"
"s6" #< TODO: i think i only need s6-svscan?
"s6-man-pages"
];
};
sane.programs.s6.sandbox.enable = false; # service manager
sane.programs.s6-man-pages.sandbox.enable = false; # no binaries
sane.programs.s6-rc-man-pages.sandbox.enable = false; # no binaries
}

View File

@@ -105,7 +105,7 @@ in
sandbox.method = "bwrap";
sandbox.whitelistAudio = true;
sandbox.whitelistDbus = [ "user" ]; #< to launch applications
sandbox.extraRuntimePaths = [ "sway-ipc.sock" ];
sandbox.extraRuntimePaths = [ "sway" ];
sandbox.extraConfig = [
"--sane-sandbox-keep-namespace" "pid"
];
@@ -127,12 +127,7 @@ in
# after = [ "graphical-session.target" ];
# wantedBy = [ "graphical-session.target" ];
# serviceConfig = {
# ExecStart = "${config.sane.programs.actkbd.package}/bin/actkbd -c /home/colin/.config/actkbd/actkbd.conf";
# Type = "simple";
# Restart = "always";
# RestartSec = "5s";
# };
# serviceConfig.ExecStart = "${config.sane.programs.actkbd.package}/bin/actkbd -c /home/colin/.config/actkbd/actkbd.conf";
# };
# };

View File

@@ -209,16 +209,16 @@ dispatchInhibited() {
}
_isAllOn="$(isAllOn && echo 1 || true)"
_isInhibited="$(isInhibited && echo 1 || true)"
if [ -n "$_isInhibited" ]; then
dispatchInhibited
fi
if [ -n "$_isAllOn" ]; then
dispatchOn
else
if [ -z "$_isAllOn" ]; then
dispatchOff
else
_isInhibited="$(isInhibited && echo 1 || true)"
if [ -n "$_isInhibited" ]; then
dispatchInhibited
else
dispatchOn
fi
fi
dispatchDefault

View File

@@ -54,6 +54,7 @@ in
"sane-scripts.bt-add".sandbox = {
method = "bwrap";
autodetectCliPaths = "existing"; #< for adding a .torrent from disk
net = "clearnet";
# TODO: migrate `transmission_passwd` to `secrets` api
extraPaths = [ "/run/secrets/transmission_passwd" ];
@@ -254,7 +255,8 @@ in
"sane-scripts.wipe".sandbox = {
method = "bwrap";
whitelistDbus = [ "user" ]; #< for `secret-tool` and `systemd --user stop <service>
whitelistDbus = [ "user" ]; #< for `secret-tool`
whitelistS6 = true; #< for stopping services before wiping
extraHomePaths = [
# could be more specific, but at a maintenance cost.
# TODO: needs updating, now that persisted data lives behind symlinks!

View File

@@ -46,18 +46,11 @@ in
services.signal-desktop = {
description = "signal-desktop Signal Messenger client";
after = [ "graphical-session.target" ];
# partOf = [ "graphical-session.target" ];
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
# depends = [ "graphical-session" ];
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/signal-desktop";
Type = "simple";
Restart = "always";
RestartSec = "20s";
};
# for some reason the --ozone-platform-hint=auto flag fails when signal-desktop is launched from a service
environment.NIXOS_OZONE_WL = "1";
command = "env NIXOS_OZONE_WL=1 signal-desktop";
};
};
}

View File

@@ -31,14 +31,14 @@ in
services.sway-autoscaler = {
description = "adjust global desktop scale to match the activate application";
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/sway-autoscaler --loop-sec ${builtins.toString cfg.config.interval}";
Type = "simple";
Restart = "always";
RestartSec = "10s";
};
environment.SWAY_DEFAULT_SCALE = builtins.toString cfg.config.defaultScale;
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
command = lib.escapeShellArgs [
"env"
"SWAY_DEFAULT_SCALE=${builtins.toString cfg.config.defaultScale}"
"sway-autoscaler"
"--loop-sec"
(builtins.toString cfg.config.interval)
];
};
};
}

View File

@@ -5,18 +5,16 @@
let
cfg = config.sane.programs.sway;
wrapSway = configuredSway: let
# `wrapSway` exists to launch sway with our desired debugging facilities.
# i.e. redirect output to syslog.
systemd-cat = "${lib.getBin pkgs.systemd}/bin/systemd-cat";
swayLauncher = pkgs.writeShellScriptBin "sway" ''
# sway defaults to auto-generating a unix domain socket named "sway-ipc.$UID.NNNN.sock",
# which allows for multiple sway sessions under the same user.
# but the unpredictability makes static sandboxing & such difficult, so hardcode it:
export SWAYSOCK="$XDG_RUNTIME_DIR/sway-ipc.sock"
export XDG_CURRENT_DESKTOP=sway
echo "launching sway (sway.desktop)..." | ${systemd-cat} --identifier=sway
exec ${configuredSway}/bin/sway 2>&1 | ${systemd-cat} --identifier=sway
test -e "$(dirname "$SWAYSOCK")" || \
echo "warning: required directory not found (create it?): $(dirname "$SWAYSOCK")"
test -e "$(dirname "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY")" || \
echo "warning: required directory not found (create it?): $(dirname "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY")"
test -e /tmp/.X11-unix || \
echo "warning: required directory not found (create it?): /tmp/.X11-unix"
# delete DISPLAY-related vars from env before launch, else sway will try to connect to a remote display.
# (consider: nested sway sessions, where sway actually has a reason to read these)
exec env -u DISPLAY -u WAYLAND_DISPLAY "DESIRED_WAYLAND_DISPLAY=$WAYLAND_DISPLAY" ${configuredSway}/bin/sway 2>&1
'';
in
pkgs.symlinkJoin {
@@ -149,7 +147,6 @@ in
"wdisplays" # like xrandr
"wireplumber" # used by sway config
"wl-clipboard"
# "wob" # render volume changes on-screen
"xdg-desktop-portal"
# xdg-desktop-portal-gtk provides portals for:
# - org.freedesktop.impl.portal.Access
@@ -173,16 +170,16 @@ in
sandbox.method = "bwrap";
sandbox.wrapperType = "inplace";
sandbox.net = "clearnet"; #< for Xwayland (TODO: separate!)
sandbox.net = "all"; # TODO: shouldn't be needed! but without this, mouse/kb hotplug doesn't work.
sandbox.whitelistAudio = true; # it runs playerctl directly
sandbox.whitelistDbus = [ "system" "user" ]; # to e.g. launch apps
sandbox.whitelistDri = true;
sandbox.whitelistX = true; # sway invokes xwayland itself
sandbox.whitelistWayland = true;
# needs to *create* the sway socket. could move the sway socket into its own directory, and whitelist just that, but doesn't buy me much.
sandbox.extraRuntimePaths = [ "/" ];
sandbox.extraRuntimePaths = [ "/" ]; # TODO: should need just "sway". but even if i sandbox EVERY entry under run individually, it fails!
sandbox.extraPaths = [
"/dev/input"
"/run/systemd"
"/run/systemd/sessions"
"/run/udev"
"/sys/class/backlight"
"/sys/class/drm"
@@ -195,10 +192,6 @@ in
# this way `swaymsg -- reload` can work even if the fd for ~/.config/sway/config changes.
".config/sway"
];
sandbox.extraConfig = [
"--sane-sandbox-keep-namespace" "pid"
];
fs.".config/xdg-desktop-portal/sway-portals.conf".symlink.text = ''
# portals.conf docs: <https://flatpak.github.io/xdg-desktop-portal/docs/portals.conf.html>
@@ -218,30 +211,32 @@ in
xwayland = if config.sane.programs.xwayland.enabled then "enable" else "disable";
};
services.sway-session = {
description = "sway-session: active iff sway desktop environment is baseline operational";
documentation = [
"https://github.com/swaywm/sway/issues/7862"
"https://github.com/alebastr/sway-systemd"
];
env.XDG_CURRENT_DESKTOP = "sway";
# sway defaults to auto-generating a unix domain socket named "sway-ipc.$UID.NNNN.sock",
# which allows for multiple sway sessions under the same user.
# but the unpredictability makes static sandboxing & such difficult, so hardcode it.
# place it in a subdirectory for the same reason:
env.SWAYSOCK = "$XDG_RUNTIME_DIR/sway/sway-ipc.sock";
# TODO: ensure this is reliable? might not work across sway restarts, etc.
env.DISPLAY = ":0";
# docs: <https://discourse.ubuntu.com/t/environment-variables-for-wayland-hackers/12750>
# N.B.: gtk apps support absolute paths for this; webkit apps (e.g. geary) support only relative paths (relative to $XDG_RUNTIME_DIR)
env.WAYLAND_DISPLAY = "wayland/wayland-1";
# we'd like to start graphical-session after sway is ready, but it's marked `RefuseManualStart` because Lennart.
# instead, create `sway-session.service` which `bindsTo` `graphical-session.target`.
# we can manually start `sway-session`, and the `bindsTo` means that it will start `graphical-session`,
# and then track `graphical-session`s state (that is: it'll stop when graphical-session stops).
#
# additionally, set `ConditionEnvironment` to guard that the sway environment variables *really have* been imported into systemd.
unitConfig.ConditionEnvironment = "SWAYSOCK";
# requiredBy = [ "graphical-session.target" ];
before = [ "graphical-session.target" ];
bindsTo = [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${pkgs.coreutils}/bin/true";
Type = "oneshot";
RemainAfterExit = true;
};
services.sway = {
description = "sway: tiling wayland desktop environment";
dependencyOf = [ "graphical-session" ];
command = pkgs.writeShellScript "sway-start" ''
# have to create these directories before launching sway so that they're available in the sandbox
mkdir -p "$(dirname "$SWAYSOCK")"
mkdir -p "$(dirname "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY")"
mkdir -p /tmp/.X11-unix # for Xwayland
exec sway
'';
readiness.waitExists = [ "$SWAYSOCK" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ];
};
# link the graphical-session into the default target, so sway gets auto-started
services.graphical-session.partOf = [ "default" ];
};

View File

@@ -164,7 +164,7 @@ assign [app_id="im.dino.Dino"] workspace number 1
assign [app_id="org.gnome.Fractal"] workspace number 1
assign [app_id="geary"] workspace number 1
assign [app_id="signal"] workspace number 1
assign [app_id="so.libdb.gtkcord4"] workspace number 1
assign [app_id="so.libdb.dissent"] workspace number 1
assign [app_id="abaddon"] workspace number 1
# window display settings
@@ -236,6 +236,13 @@ output $out_moby {
# scale 1.6
}
# move the wayland socket that sway implicitly created to the place which other apps expect to find it
exec --no-startup-id mv $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY $XDG_RUNTIME_DIR/$DESIRED_WAYLAND_DISPLAY
exec --no-startup-id mv $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY.lock $XDG_RUNTIME_DIR/$DESIRED_WAYLAND_DISPLAY.lock
# i'm sure there's a simpler way. not certain that this actually updates the sway environment variables anyway
# the double-$ means to set the variable at *runtime*, not at "compile-time" (so that it doesn't impact the line immediately above us
set $$WAYLAND_DISPLAY "$(echo $DESIRED_WAYLAND_DISPLAY)"
# manually export PATH here, since all my user services need that, and sane-sandboxed implementation depends on it.
# also, manually export XDG_DATA_DIRS. glib fails in weird ways (e.g. thinks everything is application/x-octet-stream mime type) without it.
@@ -243,8 +250,8 @@ output $out_moby {
#
# XXX: dbus-update-activation-environment --systemd is ASYNCHRONOUS. it returns before the systemd environment is actually updated.
# hence, call `systemctl import-environment` ourselves. i could probably remove the dbus stuff and be safe, but at least for now it's an OK backup.
exec --no-startup-id systemctl --user import-environment PATH XDG_DATA_DIRS DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
exec --no-startup-id dbus-update-activation-environment --systemd PATH XDG_DATA_DIRS DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
# exec --no-startup-id systemctl --user import-environment PATH XDG_DATA_DIRS DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
# exec --no-startup-id dbus-update-activation-environment --systemd PATH XDG_DATA_DIRS DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
# previously: `include /etc/sway/config.d/*` was needed for xdg-desktop-portal-* to work.
# stock nixos `programs.sway` would setup /etc/sway/config.d with additional variables to import to the dbus env.
@@ -253,11 +260,5 @@ exec --no-startup-id dbus-update-activation-environment --systemd PATH XDG_DATA_
# - WAYLAND_DISPLAY
# - SWAYSOCK
# - XDG_CURRENT_DESKTOP
# include /etc/sway/config.d/*
# signal to systemd that sway is active,
# and therefore let it start any downstream services (e.g. apps that would like to auto-start)
# see `systemctl --user cat sway-session` for links to docs
exec --no-startup-id systemctl start --user sway-session.service
@extra_lines@

View File

@@ -50,17 +50,15 @@ in
sandbox.method = "bwrap";
sandbox.whitelistDbus = [ "user" ]; #< might need system too, for inhibitors
sandbox.whitelistWayland = true;
sandbox.extraRuntimePaths = [ "sway-ipc.sock" ];
sandbox.extraRuntimePaths = [ "sway" ];
services.swayidle = {
description = "swayidle: perform actions when sway session is idle";
after = [ "graphical-session.target" ];
wantedBy = [ "graphical-session.target" ];
partOf = [ "graphical-session" ];
serviceConfig = {
ExecStart = lib.escapeShellArgs (
command = lib.escapeShellArgs (
[
"${cfg.package}/bin/swayidle"
"swayidle"
"-w"
] ++ lib.flatten (
lib.mapAttrsToList
@@ -72,10 +70,6 @@ in
cfg.config.actions
)
);
Type = "simple";
Restart = "always";
RestartSec = "20s";
};
};
};
}

View File

@@ -0,0 +1,23 @@
{ pkgs }:
let
serviceButton = svcType: name: label: {
inherit label;
type = "toggle";
command = "swaync-service-dispatcher toggle ${svcType} ${name}";
update-command = "swaync-service-dispatcher print ${svcType} ${name}";
active = true;
};
in
{
gps = serviceButton "systemd" "eg25-control-gps" ""; # GPS services; other icons: gps, ⌖, 🛰, 🌎, 
cell-modem = serviceButton "systemd" "eg25-control-powered" "󰺐"; # icons: 5g, 📡, 📱, ᯤ, ⚡, , 🌐, 📶, 🗼, 󰀂, , 󰺐, 󰩯
vpn = serviceButton "systemd" "wg-quick-vpn-servo" "vpn::hn";
gnome-calls = serviceButton "s6" "gnome-calls" "SIP";
geary = serviceButton "s6" "geary" "󰇮"; # email (Geary); other icons: ✉, [E], 📧, 󰇮
abaddon = serviceButton "s6" "abaddon" "󰊴"; # Discord chat client; icons: 󰊴, 🎮
dissent = serviceButton "s6" "dissent" "󰊴"; # Discord chat client; icons: 󰊴, 🎮
signal-desktop = serviceButton "s6" "signal-desktop" "💬"; # Signal messenger; other icons: 󰍦
dino = serviceButton "s6" "dino" "XMPP"; # XMPP calls (jingle)
fractal = serviceButton "s6" "fractal" "[m]"; # Matrix messages
}

View File

@@ -17,101 +17,22 @@
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.swaynotificationcenter;
fbcli-wrapper = pkgs.writeShellApplication {
name = "swaync-fbcli";
runtimeInputs = [
config.sane.programs.feedbackd.package
pkgs.procps # for pkill
cfg.package
];
text = ''
# if in Do Not Disturb, don't do any feedback
# TODO: better solution is to actually make use of feedbackd profiles.
# i.e. set profile to `quiet` when in DnD mode
if [ "$SWAYNC_URGENCY" != "Critical" ] && [ "$(swaync-client --get-dnd)" = "true" ]; then
exit
fi
# kill children if killed, to allow that killing this parent process will end the real fbcli call
cleanup() {
echo "aborting fbcli notification (PID $child)"
pkill -P "$child"
exit 0 # exit cleanly to avoid swaync alerting a script failure
}
trap cleanup SIGINT SIGQUIT SIGTERM
# feedbackd stops playback when the caller exits
# and fbcli will exit immediately if it has no stdin.
# so spoof a stdin:
/bin/sh -c "true | fbcli $*" &
child=$!
wait
'';
};
fbcli = "${fbcli-wrapper}/bin/swaync-fbcli";
# we do this because swaync's exec naively splits the command on space to produce its argv, rather than parsing the shell.
# [ "pkill" "-f" "fbcli" "--event" ... ] -> breaks pkill
# [ "pkill" "-f" "fbcli --event ..." ] -> is what we want
fbcli-stop-wrapper = pkgs.writeShellApplication {
name = "fbcli-stop";
runtimeInputs = [
pkgs.procps # for pkill
];
text = ''
pkill -e -f "${fbcli} $*"
'';
};
fbcli-stop = "${fbcli-stop-wrapper}/bin/fbcli-stop";
kill-singleton_ = pkgs.writeShellApplication {
name = "kill-singleton";
runtimeInputs = [
pkgs.procps # for pgrep
pkgs.gnugrep
];
text = ''
pids=$(pgrep --full "$*" | tr '\n' ' ') || true
# only act if there's exactly one pid
if echo "$pids" | grep -Eq '^[0-9]+ ?$'; then
kill "$pids"
else
echo "kill-singleton: skipping because multiple pids match: $pids"
fi
'';
};
kill-singleton = "${kill-singleton_}/bin/kill-singleton";
systemctl-toggle = pkgs.writeShellApplication {
name = "systemctl-toggle";
runtimeInputs = [
pkgs.systemd
];
text = ''
if systemctl is-active "$@"; then
systemctl stop "$@"
else
systemctl start "$@"
fi
'';
};
printIsActive = pkgs.writeShellApplication {
name = "print-is-active";
runtimeInputs = [
pkgs.systemd
];
text = ''
if systemctl is-active "$@"; then
echo true
else
echo false
fi
'';
};
buttons = import ./buttons.nix { inherit pkgs; };
scripts = import ./scripts.nix { inherit pkgs; };
in
{
sane.programs.swaync-service-dispatcher = {
packageUnwrapped = pkgs.static-nix-shell.mkBash {
pname = "swaync-service-dispatcher";
srcRoot = ./.;
pkgs = [
"s6"
"s6-rc"
"systemd"
];
};
};
sane.programs.swaynotificationcenter = {
configOption = with lib; mkOption {
type = types.submodule {
@@ -137,6 +58,7 @@ in
"user" # mpris; portal
"system" # backlight
];
sandbox.whitelistS6 = true;
sandbox.whitelistWayland = true;
sandbox.extraPaths = [
"/sys/class/backlight"
@@ -168,7 +90,10 @@ in
# the glib code which consumes this is `g_notification_backend_new_default`, calling into `_g_io_module_get_default_type`.
env.GNOTIFICATION_BACKEND = "freedesktop";
suggestedPrograms = [ "feedbackd" ];
suggestedPrograms = [
"feedbackd"
"swaync-service-dispatcher" #< used when toggling buttons
];
fs.".config/swaync/style.css".symlink.target = ./style.css;
fs.".config/swaync/config.json".symlink.text = builtins.toJSON {
@@ -207,131 +132,7 @@ in
hide-on-clear = true; #< hide control center when clicking "clear all"
hide-on-action = true;
script-fail-notify = true;
scripts = {
# a script can match regex on these fields. only fired if all listed fields match:
# - app-name
# - desktop-entry
# - summary
# - body
# - urgency (Low/Normal/Critical)
# - category
# additionally, the script can be run either on receipt or action:
# - run-on = "receive" or "action"
# when script is run, these env vars are available:
# - SWAYNC_BODY
# - SWAYNC_DESKTOP_ENTRY
# - SWAYNC_URGENCY
# - SWAYNC_TIME
# - SWAYNC_APP_NAME
# - SWAYNC_CATEGORY
# - SWAYNC_REPLACES_ID
# - SWAYNC_ID
# - SWAYNC_SUMMARY
# rules to use for testing. trigger with:
# - `notify-send test test:message` (etc)
# should also be possible to trigger via any messaging app
fbcli-test-im = {
body = "test:message";
exec = "${fbcli} --event proxied-message-new-instant";
};
fbcli-test-call = {
body = "test:call";
exec = "${fbcli} --event phone-incoming-call -t 20";
};
fbcli-test-call-stop = {
body = "test:call-stop";
exec = "${fbcli-stop} --event phone-incoming-call -t 20";
};
fbcli-test-timer = {
body = "test:timer";
exec = "${fbcli} --event timeout-completed";
};
incoming-im-known-app-name = {
# trigger notification sound on behalf of these IM clients.
app-name = "(Chats|Dino|discord|Element|Fractal|gtkcord4)";
body = "^(?!Incoming call).*$"; #< don't match Dino Incoming calls
exec = "${fbcli} --event proxied-message-new-instant";
};
incoming-im-known-desktop-entry = {
# trigger notification sound on behalf of these IM clients.
# these clients don't have an app-name (listed as "<unknown>"), but do have a desktop-entry
desktop-entry = "com.github.uowuo.abaddon";
exec = "${fbcli} --event proxied-message-new-instant";
};
incoming-call = {
app-name = "Dino";
body = "^Incoming call$";
exec = "${fbcli} --event phone-incoming-call -t 20";
};
incoming-call-acted-on = {
# when the notification is clicked, stop sounding the ringer
app-name = "Dino";
body = "^Incoming call$";
run-on = "action";
exec = "${fbcli-stop} --event phone-incoming-call -t 20";
};
timer-done = {
# sxmo_timer.sh fires off notifications like "Done with 10m" when a 10minute timer completes.
# it sends such a notification every second until dismissed
app-name = "notify-send";
summary = "^Done with .*$";
# XXX: could use alarm-clock-elapsed, but that's got a duration > 1s
# which isn't great for sxmo's 1s repeat.
# TODO: maybe better to have sxmo only notify once, and handle this like with Dino's incoming call
exec = "${fbcli} --event timeout-completed";
};
timer-done-acted-on = {
# when the notification is clicked, kill whichever sxmo process is sending it
app-name = "notify-send";
summary = "^Done with .*$";
run-on = "action";
# process tree looks like:
# - foot -T <...> /nix/store/.../sh /nix/store/.../.sxmo_timer.sh-wrapped timerrun <duration>
# - /nix/store/.../sh /nix/store/.../.sxmo_timer.sh-wrapped timerrun duration
# we want to match exactly one of those, reliably.
# foot might not be foot, but alacritty, kitty, or any other terminal.
exec = "${kill-singleton} ^[^ ]* ?[^ ]*sxmo_timer.sh(-wrapped)? timerrun";
};
};
notification-visibility = {
# match incoming notifications and decide if they should be visible.
# map of rule-name => { criteria and effect };
# keys:
# - `state`: "ignored"|"muted"|"transient"|"enabled"
# => which visibility to apply to matched notifications
# => "ignored" behaves as if the notification was never sent.
# => "muted" adds it to the sidebar & sets the notif indicator but doesn't display it on main display
# - `override-urgency`: "unset"|"low"|"normal"|"critical"
# => which urgency to apply to matched notifs
# critera: each key is optional, value is regex; rule applies if *all* specified are matched
# - `app-name`: string
# - `desktop-entry`: string
# - `summary`: string
# - `body`: string
# - `urgency`: "Low"|"Normal"|"Critical"
# - `category`: string
#
# test rules by using `notify-send` (libnotify)
sxmo-extraneous-daemons = {
app-name = "notify-send";
summary = "(sxmo_hook_lisgd|Autorotate) (Stopped|Started)";
state = "ignored";
};
sxmo-extraneous-warnings = {
app-name = "notify-send";
# "Modem crashed! 30s recovery.": happens on sxmo_hook_postwake.sh (i.e. unlock)
summary = "^Modem crashed.*$";
state = "ignored";
};
sxmo-timer = {
# force timer announcements to bypass DND
app-name = "notify-send";
summary = "^Done with .*$";
override-urgency = "critical";
};
};
inherit scripts;
widgets = [
# what to show in the notification center (and in which order).
# these are configurable further via `widget-config`.
@@ -355,94 +156,26 @@ in
};
buttons-grid = {
actions =
# {
# type = "toggle";
# label = "feedbackd";
# command = "${systemctl-toggle}/bin/systemctl-toggle --user feedbackd";
# active = "${pkgs.systemd}/bin/systemctl is-active --user feedbackd.service";
# }
lib.optionals config.sane.programs.eg25-control.enabled [
{
type = "toggle";
label = ""; # GPS services; other icons: gps, ⌖, 🛰, 🌎, 
command = "/run/wrappers/bin/sudo ${systemctl-toggle}/bin/systemctl-toggle eg25-control-gps";
update-command = "${printIsActive}/bin/print-is-active eg25-control-gps.service";
active = true;
}
{
type = "toggle";
label = "󰺐"; # icons: 5g, 📡, 📱, ᯤ, ⚡, , 🌐, 📶, 🗼, 󰀂, , 󰺐, 󰩯
# modem and NetworkManager auto-establishes a connection when powered.
# though some things like `wg-home` VPN tunnel will remain routed over the old interface.
command = "/run/wrappers/bin/sudo ${systemctl-toggle}/bin/systemctl-toggle eg25-control-powered";
update-command = "${printIsActive}/bin/print-is-active eg25-control-powered.service";
active = true;
}
buttons.gps
buttons.cell-modem
] ++ lib.optionals false [
{
type = "toggle";
label = "vpn::hn"; # route all traffic through servo; useful to debug moby's networking
command = "/run/wrappers/bin/sudo ${systemctl-toggle}/bin/systemctl-toggle wg-quick-vpn-servo";
update-command = "${printIsActive}/bin/print-is-active wg-quick-vpn-servo.service";
active = true;
}
buttons.vpn
] ++ lib.optionals config.sane.programs.calls.config.autostart [
{
type = "toggle";
label = "SIP";
command = "${systemctl-toggle}/bin/systemctl-toggle --user gnome-calls";
update-command = "${printIsActive}/bin/print-is-active --user gnome-calls";
active = true;
}
buttons.gnome-calls
] ++ lib.optionals config.sane.programs."gnome.geary".enabled [
{
type = "toggle";
label = "󰇮"; # email (Geary); other icons: ✉, [E], 📧, 󰇮
command = "${systemctl-toggle}/bin/systemctl-toggle --user geary";
update-command = "${printIsActive}/bin/print-is-active --user geary";
active = true;
}
buttons.geary
# ] ++ lib.optionals config.sane.programs.abaddon.enabled [
# # XXX: disabled in favor of gtkcord4: abaddon has troubles auto-connecting at start
# {
# type = "toggle";
# label = "󰊴"; # Discord chat client; icons: 󰊴, 🎮
# command = "${systemctl-toggle}/bin/systemctl-toggle --user abaddon";
# update-command = "${printIsActive}/bin/print-is-active --user abaddon";
# active = true;
# }
] ++ lib.optionals config.sane.programs.gtkcord4.enabled [
{
type = "toggle";
label = "󰊴"; # Discord chat client; icons: 󰊴, 🎮
command = "${systemctl-toggle}/bin/systemctl-toggle --user gtkcord4";
update-command = "${printIsActive}/bin/print-is-active --user gtkcord4";
active = true;
}
# # XXX: disabled in favor of dissent: abaddon has troubles auto-connecting at start
# buttons.abaddon
] ++ lib.optionals config.sane.programs.dissent.enabled [
buttons.dissent
] ++ lib.optionals config.sane.programs.signal-desktop.enabled [
{
type = "toggle";
label = "💬"; # Signal messenger; other icons: 󰍦
command = "${systemctl-toggle}/bin/systemctl-toggle --user signal-desktop";
update-command = "${printIsActive}/bin/print-is-active --user signal-desktop";
active = true;
}
buttons.signal-desktop
] ++ lib.optionals config.sane.programs.dino.enabled [
{
type = "toggle";
label = "XMPP"; # XMPP calls (jingle)
command = "${systemctl-toggle}/bin/systemctl-toggle --user dino";
update-command = "${printIsActive}/bin/print-is-active --user dino";
active = true;
}
buttons.dino
] ++ lib.optionals config.sane.programs.fractal.enabled [
{
type = "toggle";
label = "[m]"; # Matrix messages
command = "${systemctl-toggle}/bin/systemctl-toggle --user fractal";
update-command = "${printIsActive}/bin/print-is-active --user fractal";
active = true;
}
buttons.fractal
];
};
dnd = {
@@ -471,21 +204,11 @@ in
# swaync ships its own service, but i want to add `environment` variables and flags for easier debugging.
# seems that's not possible without defining an entire nix-native service (i.e. this).
description = "swaynotificationcenter (swaync) desktop notification daemon";
after = [
"graphical-session.target"
"pipewire-pulse.service" #< TODO: else it will NEVER see the pulse socket in its sandbox
];
# partOf = [ "graphical-session.target" ];
wantedBy = [ "graphical-session.target" ];
depends = [ "sound" ]; #< TODO: else it will NEVER see the pulse socket in its sandbox
partOf = [ "graphical-session" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/swaync";
Type = "simple";
# BusName = "org.freedesktop.Notifications";
Restart = "on-failure";
RestartSec = "10s";
};
environment.G_MESSAGES_DEBUG = "all";
command = "env G_MESSAGES_DEBUG=all swaync";
readiness.waitDbus = "org.freedesktop.Notifications";
};
};

View File

@@ -0,0 +1,114 @@
# this file defines the `scripts` entry within ~/.config/swaync/config.json.
# it describes special things to do in response to specific notifications,
# e.g. sound a ringer when we get a call, ...
{ pkgs }:
let
fbcli-wrapper = pkgs.writeShellApplication {
name = "swaync-fbcli";
runtimeInputs = [
pkgs.feedbackd
pkgs.procps # for pkill
pkgs.swaynotificationcenter
];
text = ''
# if in Do Not Disturb, don't do any feedback
# TODO: better solution is to actually make use of feedbackd profiles.
# i.e. set profile to `quiet` when in DnD mode
if [ "$SWAYNC_URGENCY" != "Critical" ] && [ "$(swaync-client --get-dnd)" = "true" ]; then
exit
fi
# kill children if killed, to allow that killing this parent process will end the real fbcli call
cleanup() {
echo "aborting fbcli notification (PID $child)"
pkill -P "$child"
exit 0 # exit cleanly to avoid swaync alerting a script failure
}
trap cleanup SIGINT SIGQUIT SIGTERM
# feedbackd stops playback when the caller exits
# and fbcli will exit immediately if it has no stdin.
# so spoof a stdin:
/bin/sh -c "true | fbcli $*" &
child=$!
wait
'';
};
fbcli = "${fbcli-wrapper}/bin/swaync-fbcli";
# we do this because swaync's exec naively splits the command on space to produce its argv, rather than parsing the shell.
# [ "pkill" "-f" "fbcli" "--event" ... ] -> breaks pkill
# [ "pkill" "-f" "fbcli --event ..." ] -> is what we want
fbcli-stop-wrapper = pkgs.writeShellApplication {
name = "fbcli-stop";
runtimeInputs = [
pkgs.procps # for pkill
];
text = ''
pkill -e -f "${fbcli} $*"
'';
};
fbcli-stop = "${fbcli-stop-wrapper}/bin/fbcli-stop";
in
{
# a script can match regex on these fields. only fired if all listed fields match:
# - app-name
# - desktop-entry
# - summary
# - body
# - urgency (Low/Normal/Critical)
# - category
# additionally, the script can be run either on receipt or action:
# - run-on = "receive" or "action"
# when script is run, these env vars are available:
# - SWAYNC_BODY
# - SWAYNC_DESKTOP_ENTRY
# - SWAYNC_URGENCY
# - SWAYNC_TIME
# - SWAYNC_APP_NAME
# - SWAYNC_CATEGORY
# - SWAYNC_REPLACES_ID
# - SWAYNC_ID
# - SWAYNC_SUMMARY
# rules to use for testing. trigger with:
# - `notify-send test test:message` (etc)
# should also be possible to trigger via any messaging app
fbcli-test-im = {
body = "test:message";
exec = "${fbcli} --event proxied-message-new-instant";
};
fbcli-test-call = {
body = "test:call";
exec = "${fbcli} --event phone-incoming-call -t 20";
};
fbcli-test-call-stop = {
body = "test:call-stop";
exec = "${fbcli-stop} --event phone-incoming-call -t 20";
};
incoming-im-known-app-name = {
# trigger notification sound on behalf of these IM clients.
app-name = "(Chats|Dino|discord|dissent|Element|Fractal)";
body = "^(?!Incoming call).*$"; #< don't match Dino Incoming calls
exec = "${fbcli} --event proxied-message-new-instant";
};
incoming-im-known-desktop-entry = {
# trigger notification sound on behalf of these IM clients.
# these clients don't have an app-name (listed as "<unknown>"), but do have a desktop-entry
desktop-entry = "com.github.uowuo.abaddon";
exec = "${fbcli} --event proxied-message-new-instant";
};
incoming-call = {
app-name = "Dino";
body = "^Incoming call$";
exec = "${fbcli} --event phone-incoming-call -t 20";
};
incoming-call-acted-on = {
# when the notification is clicked, stop sounding the ringer
app-name = "Dino";
body = "^Incoming call$";
run-on = "action";
exec = "${fbcli-stop} --event phone-incoming-call -t 20";
};
}

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p s6 -p s6-rc -p systemd
# for default $PATH to take precedence over nix-shell PATH if invoked interactively,
# otherwise we invoke a s6-rc which does not know where to find files.
export PATH="/etc/profiles/per-user/$(whoami)/bin:/run/current-system/sw/bin:$PATH"
action="$1"
type="$2"
service="$3"
log() {
if [ -n "$SWAYNC_SERVICES_DEBUG" ]; then
printf "%s\n" "$1"
fi
}
checkActive() {
case "$type" in
systemd)
systemctl is-active "$service.service" > /dev/null && echo true || echo false
;;
s6)
s6-svstat -o wantedup "$XDG_RUNTIME_DIR/s6/live/servicedirs/$service"
;;
esac
}
startService() {
log "startService: $service"
case "$type" in
systemd)
/run/wrappers/bin/sudo systemctl start "$service"
;;
s6)
s6-rc start "$service"
;;
esac
}
stopService() {
log "stopService: $service"
case "$type" in
systemd)
/run/wrappers/bin/sudo systemctl stop "$service"
;;
s6)
s6-rc stop "$service"
;;
esac
}
case "$action" in
print)
checkActive
;;
toggle)
case "$(checkActive)" in
false)
startService
;;
true)
stopService
;;
esac
;;
esac

View File

@@ -1,19 +1,6 @@
{ config, lib, ... }:
let
cfg = config.sane.programs.sysvol;
in
{ ... }:
{
sane.programs.sysvol = {
configOption = with lib; mkOption {
default = {};
type = types.submodule {
options.autostart = mkOption {
type = types.bool;
default = true;
};
};
};
sandbox.method = "bwrap";
sandbox.whitelistAudio = true;
sandbox.whitelistWayland = true;
@@ -65,10 +52,9 @@ in
services."sysvol" = {
description = "sysvol: volume monitor/notifier";
after = [ "graphical-session.target" "pipewire-pulse.service" ];
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
depends = [ "sound" ]; #< specifically wireplumber-pulse
partOf = [ "graphical-session" ];
serviceConfig = {
# options:
# -p {0,1,2,3} to attach to top/right/bottom/left screen edge
# -t N for the notifier to be dismissed after N seconds (integer only)
@@ -77,11 +63,7 @@ in
# -{H,W} N to set the height/width of the notifier, in px.
# -i N to set the size of the volume icon
# -P to hide percentage text
ExecStart = "${cfg.package}/bin/sysvol -p 0 -t 1 -m 22 -H 39 -W 256 -i 32 -P";
Type = "simple";
Restart = "always";
RestartSec = "10s";
};
command = "sysvol -p 0 -t 1 -m 22 -H 39 -W 256 -i 32 -P";
};
};
}

View File

@@ -30,42 +30,6 @@ let
login -p ${cfg.config.user}
'';
};
tryLaunchDefaultDesktop = pkgs.writeShellScriptBin "tryLaunchDefaultDesktop" ''
# return an array of paths to .desktop files which contain wayland sessions.
getWaylandDesktops() {
_desktops=()
_xdgDirs=(''${XDG_DATA_DIRS//:/ })
for _dataDir in ''${_xdgDirs[@]}; do
for _maybeDesktop in $_dataDir/wayland-sessions/*.desktop; do
# if no matches, bash will give literally "<path>/*.desktop", with the asterisk
if [ -e "$_maybeDesktop" ]; then
_desktops+=("$_maybeDesktop")
fi
done
done
echo "''${_desktops[@]}"
}
# IF there's any desktop files, then launch the first one
tryLaunchDefaultDesktop() {
_desktops=($(getWaylandDesktops))
if [ -n "$_desktops" ]; then
_firstDesktop="''${_desktops[0]}"
if command -v gio > /dev/null; then
_gio="gio"
else
_gio="${lib.getBin pkgs.glib}/bin/gio"
fi
echo "launching $(basename $_firstDesktop) session in ${builtins.toString cfg.config.delay}s"
# if the `sleep` call here is `Ctrl+C'd`, then it'll exit false and the desktop isn't launched.
sleep ${builtins.toString cfg.config.delay} && \
"$_gio" launch "$_firstDesktop"
fi
}
tryLaunchDefaultDesktop
'';
in
{
sane.programs.unl0kr = {
@@ -119,15 +83,29 @@ in
};
};
fs.".profile".symlink.text = lib.mkMerge [
(lib.mkBefore ''
# setup primarySessionCommands here and let any other nix config populate it later
primarySessionCommands=()
initPrimarySession() {
for c in "''${primarySessionCommands[@]}"; do
eval "$c"
done
}
'')
# lib.mkAfter so that launching the DE happens *after* any other .profile setup.
# alternatively, we could recurse: exec a new login shell with some env-var signalling to not launch the DE,
# run with `-c "{cfg.afterLogin}"`
fs.".profile".symlink.text = lib.mkAfter ''
(lib.mkAfter ''
# if already running a desktop environment, or if running from ssh, then `tty` will show /dev/pts/NN.
if [ "$(tty)" = "/dev/${tty}" ]; then
${tryLaunchDefaultDesktop}/bin/tryLaunchDefaultDesktop
if (( ''${#primarySessionCommands[@]} )); then
echo "launching primary session commands in ${builtins.toString cfg.config.delay}s: ''${primarySessionCommands[*]}"
# if the `sleep` call here is `Ctrl+C'd`, then it'll exit false and the desktop isn't launched.
sleep ${builtins.toString cfg.config.delay} && \
initPrimarySession
fi
'';
fi
'')
];
# N.B.: this sandboxing applies to `unl0kr` itself -- the on-screen-keyboard;
# NOT to the wrapper which invokes `login`.
@@ -149,11 +127,6 @@ in
# unl0kr is run as root, and especially with sandboxing, needs to be installed for root if expected to work.
sane.programs.unl0kr.enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true;
environment.pathsToLink = lib.mkIf cfg.enabled [
# so we can figure out what to auto-launch
"/share/wayland-sessions"
];
systemd = lib.mkIf cfg.enabled {
# prevent nixos-rebuild from killing us after a redeploy
services."autovt@${tty}".enable = false;

View File

@@ -90,7 +90,7 @@ in
];
sandbox.whitelistWayland = true;
sandbox.extraRuntimePaths = [
"sway-ipc.sock"
"sway"
# "sxmo_status" #< only necessary if relying on sxmo's statusbar periodicals service
];
sandbox.extraPaths = [
@@ -115,17 +115,10 @@ in
services.waybar = {
description = "swaybar graphical header bar/tray for sway";
after = [ "graphical-session.target" ];
# partOf = [ "graphical-session.target" ];
wantedBy = [ "graphical-session.target" ];
partOf = [ "graphical-session" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/waybar";
Type = "simple";
Restart = "on-failure";
RestartSec = "10s";
};
# environment.G_MESSAGES_DEBUG = "all";
# env G_MESSAGES_DEBUG=all
command = "waybar";
};
};
}

View File

@@ -25,7 +25,7 @@ in
max-length = 50;
};
"sway/workspaces".persistent_workspaces = lib.mkIf (persistWorkspaces != []) (
"sway/workspaces".persistent-workspaces = lib.mkIf (persistWorkspaces != []) (
lib.genAttrs persistWorkspaces (_: [])
);

View File

@@ -1,7 +1,4 @@
{ config, ... }:
let
cfg = config.sane.programs.wireplumber;
in
{ ... }:
{
sane.programs.wireplumber = {
sandbox.method = "bwrap";
@@ -34,15 +31,9 @@ in
services.wireplumber = {
description = "wireplumber: pipewire Multimedia Service Session Manager";
after = [ "pipewire.service" ];
bindsTo = [ "pipewire.service" ];
wantedBy = [ "pipewire.service" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/wireplumber";
Type = "simple";
Restart = "always";
RestartSec = "5s";
};
depends = [ "pipewire" ];
partOf = [ "sound" ];
command = "wireplumber";
};
};
}

View File

@@ -1,118 +0,0 @@
# docs:
# - <https://github.com/francma/wob/blob/master/wob.ini.5.scd>
# - `wob -vv` to see config defaults
#
# the wob services defined here are largely based on those from SXMO.
#
# this should arguably be just a (user) service. nothing actually needs `wob` on the PATH.
#
{ config, lib, pkgs, ... }:
let
wob-audio = pkgs.static-nix-shell.mkPython3Bin {
pname = "wob-audio";
srcRoot = ./.;
pkgs = [ "wireplumber" ];
};
cfg = config.sane.programs.wob;
in
{
sane.programs.wob = {
configOption = with lib; mkOption {
default = {};
type = types.submodule {
options.autostart = mkOption {
type = types.bool;
default = true;
};
options.sock = mkOption {
type = types.str;
default = "wob.sock";
};
};
};
sandbox.method = "bwrap";
sandbox.whitelistWayland = true;
suggestedPrograms = [
"wob-audio"
];
fs.".config/wob/wob.ini".symlink.text = ''
timeout = 900
anchor = top right
orientation = vertical
# margin top right bottom left
# note that wob is "aware" of the sway bar, so margin 0 never overlaps it.
# however it's not aware of sway's window title
margin = 54 3 54 3
height = 164
width = 30
border_offset = 0
border_size = 2
bar_padding = 0
# very light teal, derived from conky background
bar_color = e1f0efDC
background_color = 000000B4
border_color = 000000C8
overflow_bar_color = FF4040DC
overflow_background_color = FFFFFFC8
overflow_border_color = FF4040DC
'';
services.wob = {
description = "Wayland Overlay Bar (wob) renders volume/backlight levels when requested";
after = [ "graphical-session.target" ];
partOf = [ "graphical-session.target" ];
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
serviceConfig = {
# ExecStart = "${cfg.package}/bin/wob";
Type = "simple";
Restart = "always";
RestartSec = "20s";
};
script = ''
wobsock="$XDG_RUNTIME_DIR/$WOBSOCK_NAME"
rm -f "$wobsock" || true
mkfifo "$wobsock" && wob <> "$wobsock"
# TODO: cleanup should be done in a systemd OnFailure, or OnExit, or whatever
rm -f "$wobsock"
'';
environment.WOBSOCK_NAME = cfg.config.sock;
};
};
sane.programs.wob-audio = {
packageUnwrapped = wob-audio;
sandbox.method = "bwrap";
sandbox.whitelistAudio = true;
sandbox.extraRuntimePaths = [
cfg.config.sock
];
suggestedPrograms = [
"wireplumber" #< TODO: replace with just the one binary we need.
];
services.wob-audio = {
description = "wob-audio: monitor audio and display volume changes on-screen";
after = [ "wob.service" ];
wantedBy = [ "wob.service" ];
serviceConfig = {
ExecStart = "${wob-audio}/bin/wob-audio";
Type = "simple";
Restart = "always";
RestartSec = "20s";
};
# environment.WOB_VERBOSE = "1";
environment.WOBSOCK_NAME = cfg.config.sock;
};
};
}

View File

@@ -1,97 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p wireplumber
# vim: set filetype=python :
import logging
import os
import subprocess
logger = logging.getLogger(__name__)
class PwMonConsumer:
last_volume: float | None = None
last_mute: bool | None = None
last_effective_volume: float | None = None
# parser state:
in_changed: bool = False
in_node: bool = False
in_vol: bool = False
in_mute: bool = False
def feed_line(self, line: str) -> float | None:
""" consume a line and *maybe* return the volume """
logger.debug("got pw-mon line: %s", line.rstrip())
line = line.strip()
if line.startswith("changed:"):
self.in_changed = True
elif line.startswith("added:") or line.startswith("removed:"):
self.in_changed = False
elif line.startswith("type: "):
self.in_node = line.startswith("type: PipeWire:Interface:Node")
logger.debug("parsed `type:` %s %d", line, self.in_node)
elif line.startswith("Prop: "):
# 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 = line.startswith("Prop: key Spa:Pod:Object:Param:Props:channelVolumes")
self.in_mute = line.startswith("Prop: key Spa:Pod:Object:Param:Props:softMute")
logger.debug("parsed `Prop:` %s %d", line, self.in_vol)
elif line.startswith("Float ") and self.in_changed and self.in_node and self.in_vol:
value = float(line[len("Float "):])
self.feed_volume(value)
elif line.startswith("Bool ") and self.in_changed and self.in_node and self.in_mute:
value = line[len("Bool "):] == "true"
self.feed_mute(value)
def feed_volume(self, new: float) -> None:
logger.debug("feed volume: %f -> %f", self.last_volume or 0.0, new)
self.last_volume = new
self.check_effective_volume()
def feed_mute(self, new: bool) -> None:
logger.debug("feed mute: %d -> %d", self.last_mute or False, new)
self.last_mute = new
self.check_effective_volume()
def check_effective_volume(self) -> None:
eff_volume = 0.0 if self.last_mute else self.last_volume
if eff_volume != self.last_effective_volume:
logger.info("new effective volume: %f", eff_volume)
self.on_new_volume(eff_volume)
self.last_effective_volume = eff_volume
class PwMonWobConsumer(PwMonConsumer):
def __init__(self):
self.sock_name = os.path.join(
os.environ.get("XDG_RUNTIME_DIR", "/run"),
os.environ.get("WOBSOCK_NAME", "wob.sock"),
)
self.wob_sock = open(self.sock_name, "w")
def on_new_volume(self, vol: float) -> None:
# pipewire volume is between 0 and 3.375.
# wob is between 0 - 1, or 1 - 2 if overdriven.
# idk where vol ** (1/3) comes from, but it precisely matches what wpctl shows,
# getting the range to 0.00 - 1.50 with precise 0.05 increments.
int_vol = int(round(vol ** 0.3333 * 100))
logger.info("writing to %s: %d", self.sock_name, int_vol)
self.wob_sock.write(f"{int_vol}\n")
self.wob_sock.flush()
def main() -> None:
logging.basicConfig()
logging.getLogger().setLevel(logging.INFO)
if os.environ.get("WOB_VERBOSE", "") == "1":
logging.getLogger().setLevel(logging.DEBUG)
consumer = PwMonWobConsumer()
proc = subprocess.Popen(["pw-mon"], stdout=subprocess.PIPE)
for line in iter(proc.stdout.readline, b''):
consumer.feed_line(line.decode("utf-8"))
if __name__ == "__main__":
main()

View File

@@ -1,7 +1,4 @@
{ config, pkgs, ... }:
let
cfg = config.sane.programs.wvkbd;
in
{ lib, pkgs, ... }:
{
sane.programs.wvkbd = {
packageUnwrapped = pkgs.wvkbd.overrideAttrs (base: {
@@ -18,16 +15,11 @@ in
services.wvkbd = {
description = "wvkbd: wayland virtual keyboard";
after = [ "graphical-session.target" ];
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
# depends = [ "graphical-session" ];
partOf = [ "graphical-session" ];
command = lib.escapeShellArgs [
"env"
# --hidden: send SIGUSR2 to unhide
ExecStart = "${cfg.package}/bin/wvkbd-mobintl --hidden";
Type = "simple";
Restart = "always";
RestartSec = "3s";
};
# wvkbd layers:
# - full
# - landscape
@@ -42,12 +34,15 @@ in
# - persian
# - greek
# - georgian
environment.WVKBD_LANDSCAPE_LAYERS = "landscape,special,emoji";
environment.WVKBD_LAYERS = "full,special,emoji";
environment.WVKBD_HEIGHT = "216"; #< default: 250 (pixels)
# environment.WVKBD_LANDSCAPE_HEIGHT = "??"; #< default: 120 (pixels)
"WVKBD_LANDSCAPE_LAYERS=landscape,special,emoji"
"WVKBD_LAYERS=full,special,emoji"
"WVKBD_HEIGHT=216" #< default: 250 (pixels)
# "WVKBD_LANDSCAPE_HEIGHT=??" #< default: 120 (pixels)
# more settings tunable inside config.h when compiling:
# - KBD_KEY_BORDER = 2
"wvkbd-mobintl"
"--hidden"
];
};
};
}

View File

@@ -41,17 +41,11 @@ in
services.xdg-desktop-portal-gtk = {
description = "xdg-desktop-portal-gtk backend (provides graphical dialogs for xdg-desktop-portal)";
after = [ "graphical-session.target" ];
before = [ "xdg-desktop-portal.service" ];
wantedBy = [ "xdg-desktop-portal.service" ];
# depends = [ "graphical-session" ];
dependencyOf = [ "xdg-desktop-portal" ];
serviceConfig = {
ExecStart="${cfg.package}/libexec/xdg-desktop-portal-gtk";
Type = "dbus";
BusName = "org.freedesktop.impl.portal.desktop.gtk";
Restart = "always";
RestartSec = "10s";
};
command = "${cfg.package}/libexec/xdg-desktop-portal-gtk";
readiness.waitDbus = "org.freedesktop.impl.portal.desktop.gtk";
};
};
}

View File

@@ -26,17 +26,12 @@ in
services.xdg-desktop-portal-wlr = {
description = "xdg-desktop-portal-wlr backend (provides screenshot functionality for xdg-desktop-portal)";
after = [ "graphical-session.target" ];
before = [ "xdg-desktop-portal.service" ];
wantedBy = [ "xdg-desktop-portal.service" ];
depends = [ "pipewire" ]; # refuses to start without it
dependencyOf = [ "xdg-desktop-portal" ];
# partOf = [ "graphical-session" ];
serviceConfig = {
ExecStart="${cfg.package}/libexec/xdg-desktop-portal-wlr";
Type = "dbus";
BusName = "org.freedesktop.impl.portal.desktop.wlr";
Restart = "always";
RestartSec = "10s";
};
command = "${cfg.package}/libexec/xdg-desktop-portal-wlr";
readiness.waitDbus = "org.freedesktop.impl.portal.desktop.wlr";
};
};
}

View File

@@ -15,6 +15,9 @@
# - whereas OpenURI requires a URI argument, DynamicLauncher is just "launch an app by <app_id>.desktop"
# - example (glib): `gdbus call --session --timeout 10 --dest org.freedesktop.portal.Desktop --object-path /org/freedesktop/portal/desktop --method org.freedesktop.portal.DynamicLauncher.Launch 'audacity.desktop' "{}"`
# - .desktop files are searched for in ~/.local/share/xdg-desktop-portal/applications
#
# debugging:
# - show available portals: <https://codeberg.org/tytan652/door-knocker>
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.xdg-desktop-portal;
@@ -54,44 +57,35 @@ in
services.xdg-desktop-portal = {
description = "xdg-desktop-portal freedesktop.org portal (URI opener, file chooser, etc)";
after = [ "graphical-session.target" ];
# partOf = [ "graphical-session.target" ];
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
ExecStart="${cfg.package}/libexec/xdg-desktop-portal";
Type = "dbus";
BusName = "org.freedesktop.portal.Desktop";
Restart = "always";
RestartSec = "10s";
};
partOf = [ "graphical-session" ];
# tracking issue for having xdg-desktop-portal locate portals via more standard directories, obviating this var:
# - <https://github.com/flatpak/xdg-desktop-portal/issues/603>
# i can actually almost omit it today; problem is that if you don't set it it'll look for `sway-portals.conf` in ~/.config/xdg-desktop-portal
# but then will check its *own* output dir for {gtk,wlr}.portal.
# arguable if that's a packaging bug, or limitation...
environment.XDG_DESKTOP_PORTAL_DIR = "%E/xdg-desktop-portal";
# environment.G_MESSAGES_DEBUG = "all"; #< also applies to all apps launched by the portal
command = lib.concatStringsSep " " [
"env"
"XDG_DESKTOP_PORTAL_DIR=$HOME/.config/xdg-desktop-portal"
"G_MESSAGES_DEBUG=xdg-desktop-portal=all"
"${cfg.package}/libexec/xdg-desktop-portal"
];
readiness.waitDbus = "org.freedesktop.portal.Desktop";
};
services.xdg-permission-store = {
# xdg-desktop-portal would *usually* dbus-activate this.
# this service might not strictly be necssary. xdg-desktop-portal does warn if it's not present, though.
description = "xdg-permission-store: lets xdg-desktop-portal know which handlers are 'safe'";
after = [ "graphical-session.target" ];
before = [ "xdg-desktop-portal.service" ];
wantedBy = [ "xdg-desktop-portal.service" ];
# after = [ "graphical-session" ];
dependencyOf = [ "xdg-desktop-portal" ];
serviceConfig = {
ExecStart="${cfg.package}/libexec/xdg-permission-store";
Type = "dbus";
BusName = "org.freedesktop.impl.portal.PermissionStore";
Restart = "always";
RestartSec = "10s";
};
environment.XDG_DESKTOP_PORTAL_DIR = "%E/xdg-desktop-portal";
command = lib.concatStringsSep " " [
"env"
"XDG_DESKTOP_PORTAL_DIR=$HOME/.config/xdg-desktop-portal"
"${cfg.package}/libexec/xdg-permission-store"
];
readiness.waitDbus = "org.freedesktop.impl.portal.PermissionStore";
};
# also available: ${cfg.package}/libexec/xdg-document-portal
# - <https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Documents.html>

View File

@@ -1,9 +1,16 @@
{ pkgs, ... }:
{
sane.programs.xdg-terminal-exec = {
packageUnwrapped = pkgs.xdg-terminal-exec.overrideAttrs (upstream: {
# fix for <https://github.com/Vladimir-csp/xdg-terminal-exec/issues/50>
postPatch = (upstream.postPatch or "") + ''
sed '2i\
unset TERMINAL\
' -i xdg-terminal-exec
'';
# give the package a .desktop item.
# this way anyone can launch a terminal via the xdg-desktop-portal.
packageUnwrapped = pkgs.xdg-terminal-exec.overrideAttrs (upstream: {
nativeBuildInputs = (upstream.nativeBuildInputs or []) ++ [
pkgs.copyDesktopItems
];

View File

@@ -18,8 +18,6 @@ in
"animatch"
"gnome-2048"
"gnome.hitori" # like sudoku
"superTux" # keyboard-only controls
"superTuxKart" # poor FPS on pinephone
];
sane.programs.pcGameApps = declPackageSet [
# "andyetitmoves" # TODO: fix build!
@@ -42,6 +40,8 @@ in
# "powermanga" # STYLISH space invaders derivative (keyboard-only)
"shattered-pixel-dungeon" # doesn't cross compile
"space-cadet-pinball" # LMB/RMB controls (bindable though. volume buttons?)
"superTux" # keyboard-only controls
"superTuxKart" # poor FPS on pinephone
"tumiki-fighters" # keyboard-only
"vvvvvv" # keyboard-only controls
# "wine"
@@ -57,9 +57,10 @@ in
# "abaddon" # discord client
"alacritty" # terminal emulator
"dconf" # required by many packages, but not well-documented :(
"delfin" # Jellyfin client
# "delfin" # Jellyfin client
"dialect" # language translation
"dino" # XMPP client
"dissent" # Discord client (formerly known as: gtkcord4)
# "emote"
# "evince" # PDF viewer
# "flare-signal" # gtk4 signal client
@@ -82,7 +83,6 @@ in
"gnome-frog" # OCR/QR decoder
"gpodder"
# "gthumb"
"gtkcord4" # Discord client. 2023/11/21: disabled because v0.0.12 leaks memory
# "lemoa" # lemmy app
"libnotify" # for notify-send; debugging
# "lollypop"
@@ -134,7 +134,7 @@ in
"pcTuiApps"
] ++ [
"audacity"
"blanket" # ambient noise generator
# "blanket" # ambient noise generator
"brave" # for the integrated wallet -- as a backup
# "cantata" # music player (mpd frontend)
# "chromium" # chromium takes hours to build. brave is chromium-based, distributed in binary form, so prefer it.
@@ -173,7 +173,7 @@ in
"tor-browser" # x86-only
# "vlc"
"wireshark" # could maybe ship the cli as sysadmin pkg
"xterm" # requires Xwayland
# "xterm" # requires Xwayland
# "zecwallet-lite" # x86-only
]
);

View File

@@ -21,7 +21,7 @@
"sway/workspaces" = {
all-outputs = true;
# force the bar to always show even empty workspaces
persistent_workspaces = {
persistent-workspaces = {
"1" = [];
"2" = [];
"3" = [];

View File

@@ -0,0 +1,9 @@
{
"description": "Artist, Instructor, using only Free/Libre and Open-Source software since 2009.",
"is_podcast": false,
"site_name": "David Revoy",
"site_url": "https://www.davidrevoy.com",
"title": "David Revoy",
"url": "https://www.davidrevoy.com/feed/rss",
"velocity": 0.093
}

View File

@@ -0,0 +1,9 @@
{
"description": "Hacker Public Radio is an podcast that releases shows every weekday Monday through Friday. Our shows are produced by the community (you) and can be on any topic that are of interest to hackers and hobbyists.",
"is_podcast": true,
"site_name": "Hacker Public Radio ~ The Technology Community Podcast",
"site_url": "https://hackerpublicradio.org",
"title": "Hacker Public Radio",
"url": "https://hackerpublicradio.org/hpr_spx_rss.php",
"velocity": 0.692
}

View File

@@ -0,0 +1,9 @@
{
"description": "Personal stories and lessons from inside the rise and fall of the PC revolution",
"is_podcast": false,
"site_name": "Hardcore Software by Steven Sinofsky | Substack",
"site_url": "https://hardcoresoftware.learningbyshipping.com",
"title": "Hardcore Software by Steven Sinofsky",
"url": "https://hardcoresoftware.learningbyshipping.com/feed",
"velocity": 0.039
}

View File

@@ -0,0 +1,9 @@
{
"description": "A weekly recap of news from the Nix ecosystem.",
"is_podcast": false,
"site_name": "nixpkgs.news",
"site_url": "https://nixpkgs.news",
"title": "nixpkgs.news",
"url": "https://nixpkgs.news/rss.xml",
"velocity": 0.143
}

View File

@@ -0,0 +1,9 @@
{
"description": "willow's blog",
"is_podcast": false,
"site_name": "willow's website",
"site_url": "https://willow.phantoma.online",
"title": "willow's website",
"url": "https://willow.phantoma.online/rss.xml",
"velocity": 0
}

View File

@@ -14,7 +14,7 @@
./services
./sops.nix
./ssh.nix
./users.nix
./users
./vpn.nix
./warnings.nix
./wowlan.nix

View File

@@ -1,5 +1,15 @@
{ config, lib, pkgs, utils, ... }:
# this builds a disk image which can be flashed onto a HDD, SD card, etc, and boot a working image.
# debug the image by building one of:
# - `nix build '.#imgs.$host' --builders "" -v`
# - `nix build '.#imgs.$host.passthru.{bootFsImg,nixFsImg,withoutBootloader}'`
# then loop-mounting it:
# - `sudo losetup -Pf ./result/nixos.img`
# - `mkdir /tmp/nixos.boot`
# - `sudo mount /dev/loop0p1 /tmp/nixos.boot`, and look inside
#
# TODO: replace mobile-nixos parts with Disko <https://github.com/nix-community/disko>
# or just inline them here.
{ config, lib, pkgs, utils, ... }:
with lib;
let
@@ -62,6 +72,14 @@ in
N.B.: setting this to something other than 512B is not well tested.
'';
};
sane.image.installBootloader = mkOption {
default = null;
type = types.nullOr types.str;
description = ''
command which takes the full disk image and installs hw-specific bootloader (u-boot, tow-boot, etc).
for EFI-native systems (most x86_64), can be left empty.
'';
};
};
config = let
# return true if super starts with sub
@@ -90,19 +108,8 @@ in
"ext4" = pkgs.imageBuilder.fileSystem.makeExt4;
"btrfs" = pkgs.imageBuilder.fileSystem.makeBtrfs;
};
in
lib.mkIf cfg.enable
{
system.build.img-without-firmware = with pkgs; pkgs.imageBuilder.diskImage.makeGPT {
name = "nixos";
diskID = vfatUuidFromFs bootFs;
# leave some space for firmware
# TODO: we'd prefer to turn this into a protected firmware partition, rather than reserving space in the GPT header itself
# Tow-Boot manages to do that; not sure how.
headerHole = cfg.extraGPTPadding;
partitions = [
(pkgs.imageBuilder.gap cfg.firstPartGap)
(fsBuilderMapBoot."${bootFs.fsType}" {
bootFsImg = fsBuilderMapBoot."${bootFs.fsType}" {
# fs properties
name = "ESP";
partitionID = vfatUuidFromFs bootFs;
@@ -117,13 +124,13 @@ in
extras = builtins.toString (builtins.map (d: "cp -R ${d}/* ./") cfg.extraBootFiles);
in ''
echo "running installBootLoader"
${config.system.build.installBootLoader} ${config.system.build.toplevel} -d .
${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d .
echo "ran installBootLoader"
${extras}
echo "copied extraBootFiles"
'';
})
(fsBuilderMapNix."${nixFs.fsType}" {
};
nixFsImg = fsBuilderMapNix."${nixFs.fsType}" {
# fs properties
name = "NIXOS_SYSTEM";
partitionID = uuidFromFs nixFs;
@@ -133,7 +140,7 @@ in
# inherit (cfg) sectorSize; # imageBuilder only supports sectorSize for vfat. btrfs defaults to a 4096B sector size, somehow it abstracts over the drive's sector size?
populateCommands = let
closureInfo = buildPackages.closureInfo { rootPaths = config.system.build.toplevel; };
closureInfo = pkgs.buildPackages.closureInfo { rootPaths = config.system.build.toplevel; };
extraRelPaths = builtins.toString (builtins.map (p: "./" + builtins.toString(relPath nixFs.mountPoint p)) cfg.extraDirectories);
in ''
mkdir -p ./${storeRelPath} ${extraRelPaths}
@@ -145,9 +152,38 @@ in
echo "Done copying system closure..."
cp -v ${closureInfo}/registration ./nix-path-registration
'';
})
];
};
system.build.img = lib.mkDefault config.system.build.img-without-firmware;
img = (pkgs.imageBuilder.diskImage.makeGPT {
name = "nixos";
diskID = vfatUuidFromFs bootFs;
# leave some space for firmware
# TODO: we'd prefer to turn this into a protected firmware partition, rather than reserving space in the GPT header itself
# Tow-Boot manages to do that; not sure how.
headerHole = cfg.extraGPTPadding;
partitions = [
(pkgs.imageBuilder.gap cfg.firstPartGap)
bootFsImg
nixFsImg
];
}) // {
passthru = {
inherit bootFsImg nixFsImg;
};
};
in
lib.mkIf cfg.enable
{
system.build.img = (if cfg.installBootloader == null then
img
else pkgs.runCommand "nixos-with-bootloader" {} ''
cp -vR ${img} $out
chmod -R +w $out
${cfg.installBootloader}
'') // {
passthru = {
inherit bootFsImg nixFsImg;
withoutBootloader = img;
};
};
};
}

View File

@@ -68,12 +68,7 @@ let
fullRuntimePaths = lib.optionals (userName != null) (
builtins.map
(p: path-lib.concat [ xdgRuntimeDir p ])
(
sandbox.extraRuntimePaths
++ lib.optionals sandbox.whitelistAudio [ "pipewire-0" "pipewire-0.lock" "pulse" ] # also pipewire-0-manager, unknown purpose
++ lib.optionals (builtins.elem "user" sandbox.whitelistDbus) [ "bus" ]
++ lib.optionals sandbox.whitelistWayland [ "wayland-1" "wayland-1.lock" ] # app can still communicate with wayland server w/o this, if it has net access
)
);
allowedPaths = [
"/nix/store"
@@ -255,18 +250,12 @@ let
'';
};
services = mkOption {
# see: <repo:nixos/nixpkgs:nixos/lib/utils.nix>
# type = utils.systemdUtils.types.services;
# map to listOf attrs so that we can allow multiple assigners to the same service
# w/o worrying about merging at this layer, and defer merging to modules/users instead.
type = types.attrsOf (types.coercedTo types.attrs (a: [ a ]) (types.listOf types.attrs));
type = types.attrsOf types.anything; # options.sane.users.value.type;
default = {};
description = ''
systemd services to define if this package is enabled.
currently only defines USER services -- acts as noop for root-enabled packages.
conventions are similar to `systemd.services` or `sane.users.<user>.services`.
the type at this level is obscured only to as to allow passthrough to `sane.users` w/ proper option merging
user services to define if this package is enabled.
acts as noop for root-enabled packages.
see `sane.users.<user>.services` for options;
'';
};
slowToBuild = mkOption {
@@ -392,6 +381,13 @@ let
allow the program full access to whichever directory it was launched from.
'';
};
sandbox.whitelistS6 = mkOption {
type = types.bool;
default = false;
description = ''
allow the program to start/stop s6 services.
'';
};
sandbox.whitelistWayland = mkOption {
type = types.bool;
default = false;
@@ -485,15 +481,22 @@ let
# this gets the symlink into the sandbox, but not the actual secret.
fs = lib.mapAttrs (_homePath: _secretSrc: {}) config.secrets;
sandbox.net = lib.mkIf config.sandbox.whitelistX "localhost";
sandbox.extraPaths = lib.mkIf config.sandbox.whitelistDri [
sandbox.extraPaths =
lib.optionals config.sandbox.whitelistDri [
# /dev/dri/renderD128: requested by wayland-egl (e.g. KOreader, animatch, geary)
# - but everything seems to gracefully fallback to *something* (MESA software rendering?)
# - CPU usage difference between playing videos in Gtk apps (e.g. fractal) with v.s. without DRI is 10% v.s. 90%.
# - GPU attack surface is *large*: <https://security.stackexchange.com/questions/182501/modern-linux-gpu-driver-security>
"/dev/dri" "/sys/dev/char" "/sys/devices" # (lappy: "/sys/devices/pci0000:00", moby needs something different)
];
]
++ lib.optionals config.sandbox.whitelistX [ "/tmp/.X11-unix" ]
;
sandbox.extraRuntimePaths =
lib.optionals config.sandbox.whitelistAudio [ "pipewire" "pulse" ] # this includes pipewire/pipewire-0-manager: is that ok?
++ lib.optionals (builtins.elem "user" config.sandbox.whitelistDbus) [ "bus" ]
++ lib.optionals config.sandbox.whitelistWayland [ "wayland" ] # app can still communicate with wayland server w/o this, if it has net access
++ lib.optionals config.sandbox.whitelistS6 [ "s6" ] # TODO: this allows re-writing the services themselves: don't allow that!
;
sandbox.extraConfig = lib.mkIf config.sandbox.usePortal [
"--sane-sandbox-portal"
];
@@ -542,8 +545,7 @@ let
# conditionally persist relevant user dirs and create files
sane.users = lib.mapAttrs (user: en: lib.optionalAttrs (en && p.enabled) {
inherit (p) persist;
services = lib.mapAttrs (_: lib.mkMerge) p.services;
inherit (p) persist services;
environment = p.env;
fs = lib.mkMerge [
p.fs

View File

@@ -25,7 +25,7 @@ let
buildPhase = ''
runHook preBuild
substituteAll "$src" sane-sandboxed \
--replace '@out@' "$out"
--replace-fail '@out@' "$out"
runHook postBuild
'';

View File

@@ -1,13 +1,94 @@
{ config, lib, options, sane-lib, utils, ... }:
{ config, lib, options, pkgs, sane-lib, ... }:
let
inherit (builtins) attrValues;
inherit (lib) count mapAttrs' mapAttrsToList mkIf mkMerge mkOption types;
sane-user-cfg = config.sane.user;
cfg = config.sane.users;
path-lib = sane-lib.path;
userOptions = {
serviceType = with lib; types.submodule ({ config, ... }: {
options = {
description = mkOption {
type = types.str;
};
documentation = mkOption {
type = types.listOf types.str;
default = [];
description = ''
references and links for where to find documentation about this service.
'';
};
depends = mkOption {
type = types.listOf types.str;
default = [];
};
dependencyOf = mkOption {
type = types.listOf types.str;
default = [];
};
partOf = mkOption {
type = types.listOf types.str;
default = [];
description = ''
"bundles" to which this service belongs.
e.g. `partOf = [ "default" ];` describes services which should be started "by default".
'';
};
command = mkOption {
type = types.nullOr (types.coercedTo types.package toString types.str);
default = null;
description = ''
long-running command which represents this service.
when the command returns, the service is considered "failed", and restarted unless explicitly `down`d.
'';
};
cleanupCommand = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
command which is run after the service has exited.
restart of the service (if applicable) is blocked on this command.
'';
};
readiness.waitCommand = mkOption {
type = types.nullOr (types.coercedTo types.package toString types.str);
default = null;
description = ''
command or path to executable which exits zero only when the service is ready.
this may be invoked repeatedly (with delay),
though it's not an error for it to block either (it may, though, be killed and restarted if it blocks too long)
'';
};
readiness.waitDbus = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
name of the dbus name this service is expected to register.
only once the name is registered will the service be considered "ready".
'';
};
readiness.waitExists = mkOption {
type = types.coercedTo types.str toList (types.listOf types.str);
default = [];
description = ''
path to a directory or file whose existence signals the service's readiness.
this is expanded as a shell expression, and may contain variables like `$HOME`, etc.
'';
};
};
config = {
readiness.waitCommand = lib.mkMerge [
(lib.mkIf (config.readiness.waitDbus != null)
''${pkgs.systemdMinimal}/bin/busctl --user status "${config.readiness.waitDbus}" > /dev/null''
)
(lib.mkIf (config.readiness.waitExists != [])
# e.g.: test -e /foo -a -e /bar
("test -e " + (lib.concatStringsSep " -a -e " config.readiness.waitExists))
)
];
};
});
userOptions = {
options = with lib; {
fs = mkOption {
# map to listOf attrs so that we can allow multiple assigners to the same path
# w/o worrying about merging at this layer, and defer merging to modules/fs instead.
@@ -46,32 +127,17 @@ let
};
services = mkOption {
# see: <repo:nixos/nixpkgs:nixos/lib/utils.nix>
# type = utils.systemdUtils.types.services;
# `utils.systemdUtils.types.services` is nearly what we want, but remove `stage2ServiceConfig`,
# as we don't want to force a PATH for every service.
type = types.attrsOf (types.submodule [ utils.systemdUtils.unitOptions.stage2ServiceOptions utils.systemdUtils.lib.unitConfig ]);
type = types.attrsOf serviceType;
default = {};
description = ''
systemd user-services to define for this user.
populates files in ~/.config/systemd.
services to define for this user.
'';
};
};
};
defaultUserOptions = {
options = userOptions.options // {
services = mkOption {
# type = utils.systemdUtils.types.services;
# map to listOf attrs so that we can pass through
# w/o worrying about merging at this layer
type = types.attrsOf (types.coercedTo types.attrs (a: [ a ]) (types.listOf types.attrs));
default = {};
inherit (userOptions.options.services) description;
};
};
};
userModule = let nixConfig = config; in types.submodule ({ name, config, ... }: {
userModule = let
nixConfig = config;
in with lib; types.submodule ({ name, config, ... }: {
options = userOptions.options // {
default = mkOption {
type = types.bool;
@@ -92,7 +158,7 @@ let
config = lib.mkMerge [
# if we're the default user, inherit whatever settings were routed to the default user
(mkIf config.default {
(lib.mkIf config.default {
inherit (sane-user-cfg) fs persist environment;
services = lib.mapAttrs (_: lib.mkMerge) sane-user-cfg.services;
})
@@ -125,56 +191,24 @@ let
set +a
done
'';
}
{
fs = lib.mkMerge (mapAttrsToList (serviceName: value:
let
# see: <repo:nixos/nixpkgs:nixos/lib/utils.nix>
# see: <repo:nix-community/home-manager:modules/systemd.nix>
cleanName = utils.systemdUtils.lib.mkPathSafeName serviceName;
generatedUnit = utils.systemdUtils.lib.serviceToUnit serviceName (value // {
environment = lib.throwIf (value.path != []) "user service ${serviceName} specifies unsupported 'path' attribute (${builtins.toString value.path})" {
# clear PATH to allow inheriting it from environment.
# otherwise, nixos would force it to `systemd.globalEnvironment.PATH`, which is mostly tools like sed/find/etc.
# clearing PATH here allows user services to inherit whatever PATH the graphical session sets
# (see `dbus-update-activation-environment` call in ~/.config/sway/config),
# which is critical to making it so user services can see user *programs*/packages.
#
# note that systemd provides no way to *append* to the PATH, only to override it (or not).
# nor do they intend to ever support that:
# - <https://github.com/systemd/systemd/issues/1082>
PATH = null;
} // (value.environment or {});
});
#^ generatedUnit contains keys:
# - text
# - aliases (IGNORED)
# - wantedBy
# - requiredBy
# - enabled (IGNORED)
# - overrideStrategy (IGNORED)
# TODO: error if one of the above ignored fields are set
symlinkData = {
text = generatedUnit.text;
targetName = "${cleanName}.service"; # systemd derives unit name from symlink target
services."default" = {
description = "service (bundle) which is started by default upon login";
};
serviceEntry = {
".config/systemd/user/${serviceName}.service".symlink = symlinkData;
services."graphical-session" = {
description = "service (bundle) which is started upon successful graphical login";
# partOf = [ "default" ]; #< leave it to the DEs to set this
};
services."sound" = {
description = "service (bundle) which represents functional sound input/output when active";
partOf = [ "default" ];
};
wants = builtins.map (wantedBy: {
".config/systemd/user/${wantedBy}.wants/${serviceName}.service".symlink = symlinkData;
}) generatedUnit.wantedBy;
requires = builtins.map (requiredBy: {
".config/systemd/user/${requiredBy}.requires/${serviceName}.service".symlink = symlinkData;
}) generatedUnit.requiredBy;
in lib.mkMerge ([ serviceEntry ] ++ wants ++ requires)
) config.services);
}
];
});
processUser = user: defn:
let
prefixWithHome = mapAttrs' (path: value: {
prefixWithHome = lib.mapAttrs' (path: value: {
name = path-lib.concat [ defn.home path ];
inherit value;
});
@@ -192,7 +226,11 @@ let
};
in
{
options = {
imports = [
./s6-rc.nix
];
options = with lib; {
sane.users = mkOption {
type = types.attrsOf userModule;
default = {};
@@ -205,7 +243,7 @@ in
};
sane.user = mkOption {
type = types.nullOr (types.submodule defaultUserOptions);
type = types.nullOr (types.submodule userOptions);
default = null;
description = ''
options to pass down to the default user
@@ -224,14 +262,14 @@ in
};
config =
let
configs = mapAttrsToList processUser cfg;
num-default-users = count (u: u.default) (attrValues cfg);
configs = lib.mapAttrsToList processUser cfg;
num-default-users = lib.count (u: u.default) (lib.attrValues cfg);
take = f: {
sane.fs = f.sane.fs;
sane.persist.sys.byPath = f.sane.persist.sys.byPath;
sane.defaultUser = f.sane.defaultUser;
};
in mkMerge [
in lib.mkMerge [
(take (sane-lib.mkTypedMerge take configs))
{
assertions = [

347
modules/users/s6-rc.nix Normal file
View File

@@ -0,0 +1,347 @@
{ config, lib, pkgs, ... }:
let
config' = config;
logBase = "$HOME/.local/share/s6/logs";
maybe = cond: value: if cond then value else null;
# create a derivation whose output is the on-disk representation of some attrset.
# @path: /foo/bar/...
# @obj: one of:
# - { file = { text = "foo bar"; executable = true|false; } }
# - { dir = <obj>; }
fsItemToDerivation = path: obj: if obj ? dir then
pkgs.symlinkJoin {
name = "s6-${path}";
paths = lib.mapAttrsToList
(n: v: fsItemToDerivation "${path}/${n}" v)
obj.dir
;
}
else if obj ? text then
if obj.text != null then
pkgs.writeTextFile {
name = "s6-${path}";
destination = path;
text = obj.text;
}
else
pkgs.emptyDirectory
else if obj ? executable then
if obj.executable != null then
pkgs.writeTextFile {
name = "s6-${path}";
destination = path;
executable = true;
text = obj.executable;
}
else
pkgs.emptyDirectory
else if obj ? symlink then
if obj.symlink != null then
pkgs.runCommandLocal "s6-${path}" { } ''
mkdir -p "$out/$(dirname "${path}")"
ln -s "${obj.symlink}" "$out/${path}"
''
else
pkgs.emptyDirectory
else throw "don't know how to convert fs item at path ${path} to derivation: ${obj}";
# call with an AttrSet of fs items:
# example:
# ```
# fsToDerivation {
# usr.dir = {
# normal.text = "i'm /usr/normal";
# exec.executable = ''
# #!/bin/sh
# echo "i'm executable"
# '';
# lib.dir = { ... };
# };
# bin.dir = { ... };
# }
fsToDerivation = fs: fsItemToDerivation "/" { dir = fs; };
# infers the service type from the arguments and creates an attrset usable by `fsToDerivation`.
# also configures the service for logging, if applicable.
serviceToFs = { name, check, contents, depends, finish, run }: let
type = if run != null then "longrun" else "bundle";
logger = serviceToFs' {
name = "logger:${name}";
consumerFor = name;
run = ''s6-log -- T p'${name}:' "${logBase}/${name}"'';
type = "longrun";
};
in (serviceToFs' {
inherit name check depends finish run type;
contents = maybe (type == "bundle") contents;
producerFor = maybe (type == "longrun") "logger:${name}";
}) // (lib.optionalAttrs (type == "longrun") logger);
serviceToFs'= {
name,
type,
check ? null, #< command to poll to check for service readiness
consumerFor ? null,
contents ? null, #< bundle contents
depends ? [],
finish ? null,
producerFor ? null,
run ? null,
}: let
maybe-notify = lib.optionalString (check != null) "s6-notifyoncheck -n 0 ";
in {
"${name}".dir = {
"type".text = type;
"contents".text = maybe (contents != null) (
lib.concatStringsSep "\n" contents
);
"consumer-for".text = maybe (consumerFor != null) consumerFor;
"data".dir = {
"check".executable = maybe (check != null) ''
#!/bin/sh
${check}
'';
};
# N.B.: if this service is a bundle, then dependencies.d is ignored
"dependencies.d".dir = lib.genAttrs
depends
(dep: { text = ""; })
;
"finish".executable = maybe (finish != null) ''
#!/bin/sh
${finish}
'';
"notification-fd".text = maybe (check != null) "3";
"producer-for".text = maybe (producerFor != null) producerFor;
"run".executable = maybe (run != null) ''
#!/bin/sh
log() {
printf 's6[%s]: %s\n' '${name}' "$1" | tee /dev/stderr
}
log "preparing"
# s6 is too gentle when i ask it to stop a service,
# so explicitly kill children on exit.
# see: <https://stackoverflow.com/a/2173421>
# before changing this, test that the new version actually kills a service with `s6-rc down <svcname>`
down() {
log "trapped on abort signal"
# "trap -": to avoid recursing
trap - SIGINT SIGQUIT SIGTERM
log "killing process group"
# "kill 0" means kill the current process group (i.e. all descendants)
kill 0
exit 0
}
trap down SIGINT SIGQUIT SIGTERM
log "starting"
# run the service from $HOME by default.
# particularly, this impacts things like "what directory does my terminal open in".
# N.B. do not run the notifier from $HOME, else it won't know where to find the `data/check` program.
# N.B. must be run with `&` + `wait`, else we lose the ability to `trap`.
${maybe-notify}env --chdir="$HOME" ${run} <&0 &
wait
log "exiting"
'';
};
};
# create a directory, containing N subdirectories:
# - svc-a/
# - type
# - run
# - svc-b/
# - type
# - run
# - ...
genServices = svcs: fsToDerivation (lib.foldl'
(acc: srv: acc // (serviceToFs srv))
{}
svcs
);
# output is a directory containing:
# - db
# - lock
# - n
# - resolve.cdb
# - servicedirs/
# - svc-a/
# - svc-b/
# - ...
#
# this can then be used by s6-rc-init, like:
# s6-svscan scandir &
# s6-rc-init -c $compiled -l $PWD/live -d $PWD/scandir
# s6-rc -l $PWD/live start svc-a
#
# N.B.: it seems the $compiled dir needs to be rw, for s6 to write lock files within it.
# so `cp` and `chmod -R 600` it, first.
compileServices = sources: with pkgs; stdenv.mkDerivation {
name = "s6-user-services";
src = sources;
nativeBuildInputs = [ s6-rc ];
buildPhase = ''
s6-rc-compile $out $src
'';
};
# to decrease sandbox escaping, i want to run s6-svscan on a read-only directory
# so other programs can't edit the service scripts.
# in practice, that means putting the servicedirs in /nix/store, and linking selective pieces of state
# towards /run/user/{uid}/s6/live/..., the latter is shared with s6-rc.
mkScanDir = livedir: compiled: pkgs.runCommandLocal "s6-scandir" { } ''
cp -R "${compiled}/servicedirs" "$out"
cd "$out"
chmod u+w .
for d in *; do
chmod u+w "$d"
ln -s "${livedir}/servicedirs/$d/down" "$d"
ln -s "${livedir}/servicedirs/$d/event" "$d"
ln -s "${livedir}/servicedirs/$d/supervise" "$d"
done
# these builtin s6-rc services try to create `s` (socket) and `s.lock` inside their directory, when launched.
# they don't seem to follow symlinks, so patch in a full path.
# note that other services will try to connect directly to this service's `./s` file, so keeping symlinks around as well is a good idea.
substituteInPlace "s6rc-fdholder/run" \
--replace-fail 's6-fdholder-daemon -1 -i data/rules -- s' 's6-fdholder-daemon -1 -i data/rules -- ${livedir}/servicedirs/s6rc-fdholder/s' \
--replace-fail 's6-ipcclient -l0 -- s' 's6-ipcclient -l0 -- ${livedir}/servicedirs/s6rc-fdholder/s'
substituteInPlace "s6rc-oneshot-runner/run" \
--replace-fail 's6-ipcserver-socketbinder -- s' 's6-ipcserver-socketbinder -- ${livedir}/servicedirs/s6rc-oneshot-runner/s'
ln -s "${livedir}/servicedirs/s6rc-fdholder/s" s6rc-fdholder/s
ln -s "${livedir}/servicedirs/s6rc-fdholder/s.lock" s6rc-fdholder/s.lock
ln -s "${livedir}/servicedirs/s6rc-oneshot-runner/s" s6rc-oneshot-runner/s
ln -s "${livedir}/servicedirs/s6rc-oneshot-runner/s.lock" s6rc-oneshot-runner/s.lock
rm -rf .s6-svscan
ln -s "${livedir}/servicedirs/.s6-svscan" .
'';
# transform the `user.services` attrset into a s6 services list.
s6SvcsFromConfigServices = services: lib.mapAttrsToList
(name: service: {
inherit name;
check = service.readiness.waitCommand;
contents = builtins.attrNames (
lib.filterAttrs (_: cfg: lib.elem name cfg.partOf) services
);
depends = service.depends ++ builtins.attrNames (
lib.filterAttrs (_: cfg: lib.elem name cfg.dependencyOf) services
);
finish = service.cleanupCommand;
run = service.command;
})
services
;
# return a list of bundles (AttrSets) which contain this service
containedBy = services: name: lib.filter (svc: lib.elem name svc.contents) services;
# for each bundle to which this service belongs, add that bundle's dependencies as direct dependencies of this service.
# this is to overcome that s6 doesn't support bundles having dependencies.
pushDownDependencies = services: builtins.map
(svc: svc // {
depends = lib.unique (
svc.depends ++ lib.concatMap
(bundle: bundle.depends)
(containedBy services svc.name)
);
})
services
;
# create a template s6 "live" dir, which can be copied at runtime in /run/user/{uid}/s6/live.
# this is like a minimal versio of `s6-rc-init`, but tightly coupled to my setup
# wherein the scandir is external and selectively links back to the livedir
mkLiveDir = compiled: pkgs.runCommandLocal "s6-livedir" {} ''
mkdir $out
touch $out/lock
touch $out/prefix # no prefix: empty
mkdir $out/compiled
cp ${compiled}/{db,lock,n,resolve.cdb} $out/compiled
touch $out/state
mkdir $out/servicedirs
mkdir $out/servicedirs/.s6-svscan
for svc in $(ls ${compiled}/servicedirs); do
# s6-rc-init gives each service one byte of state, initialized to zero. i mimic that here:
echo -n '\x00' >> $out/state
mkdir $out/servicedirs/$svc
# don't auto-start the service when i add the supervisor
touch $out/servicedirs/$svc/down
# s6-rc needs to know if the service supports readiness notifications,
# as this will determine if it waits for it when starting.
ln -s ${compiled}/servicedirs/$svc/notification-fd $out/servicedirs/$svc/notification-fd
done
'';
in
{
options.sane.users = with lib; mkOption {
type = types.attrsOf (types.submodule ({ config, name, ...}: let
sources = genServices (
lib.converge pushDownDependencies (
s6SvcsFromConfigServices config.services
)
);
compiled = compileServices sources;
uid = config'.users.users."${name}".uid;
scanDir = mkScanDir "/run/user/${builtins.toString uid}/s6/live" compiled;
liveDir = mkLiveDir compiled;
in {
fs.".config/s6/live".symlink.target = liveDir;
fs.".config/s6/scandir".symlink.target = scanDir;
fs.".config/s6/compiled".symlink.target = compiled;
# exposed only for convenience
fs.".config/s6/sources".symlink.target = sources;
fs.".profile".symlink.text = ''
function initS6Dirs() {
local LIVE="$XDG_RUNTIME_DIR/s6/live"
mkdir -p "$LIVE" # create parent dirs
rm -rf "$LIVE"/* # remove old state
# the live dir needs to be read+write. initialize it via the template in ~/.config/s6:
cp -R "$HOME/.config/s6/live"/* "$LIVE"
chmod -R 0700 "$LIVE"
# ensure the log dir, since that'll be needed by every service.
# the log dir should already exist by now (nixos persistence); create it just in case something went wrong.
mkdir -p "${logBase}"
}
function startS6() {
local S6_TARGET="''${1-default}"
local LIVE="$XDG_RUNTIME_DIR/s6/live"
test -e "$LIVE" || initS6Dirs
s6-svscan "$(realpath "$HOME/.config/s6/scandir")" &
local SVSCAN=$!
# wait for `supervise` processes to appear.
# this part would normally be done by `s6-rc-init`.
# maybe there's a more clever way: don't kill `s6-svscan` above, somehow run it on the /nix/store/... path from the very beginning.
for d in $LIVE/servicedirs/*; do
while ! s6-svok "$d" ; do
sleep 0.1
done
done
if [ -n "$S6_TARGET" ]; then
s6-rc -b -l "$LIVE" start "$S6_TARGET"
fi
echo 's6 initialized: Ctrl+C to stop'
wait "$SVSCAN"
}
function startS6WithLogging() {
mkdir -p "${logBase}" #< needs to exist for parallel s6-log call
startS6 2>&1 | s6-log -- T "${logBase}/catchall"
}
primarySessionCommands+=('startS6WithLogging &')
'';
}));
};
}

View File

@@ -32,6 +32,14 @@ in [
# etc, where "date" is like "20240228181608"
# and can be found with `nix-repl > :lf . > lastModifiedDate`
(fetchpatch' {
prUrl = "https://github.com/NixOS/nixpkgs/pull/298001";
saneCommit = "d599839060400762a67d2c01d15b102ffe75e703";
title = "gnupg: fix cross compilation";
hash = "sha256-d3kD2/UyMzzdBkiEdWtCibbWiPWBZLUWRry1TMkS25g=";
merged.staging = "20240326000000";
})
(fetchpatch' {
prUrl = "https://github.com/NixOS/nixpkgs/pull/292868";
saneCommit = "f090c1cd6bb5bbb14a86ec90ced78ca1a165a4fe";
@@ -40,37 +48,12 @@ in [
hash = "sha256-AzkMYm9Pm85Xfm+nd44oKIULZYGyMXulmjFbutouysc=";
})
(fetchpatch' {
prUrl = "https://github.com/NixOS/nixpkgs/pull/288518";
saneCommit = "20c9492d303be7cbad560e3d83bc47ab4b1e93f7";
title = "procmail: support cross compilation";
# hash = "sha256-cC9GBF5tCeJ2GDSjMjlG4hYStIJPEoRBAK9/KhJiiIo=";
hash = "sha256-MzAwT1Ss3ltlljton+8atyz6PomSr1u1TuHXkigAovo=";
merged.staging = "20240312120000";
merged.staging-next = "20240312120000";
})
(fetchpatch' {
prUrl = "https://github.com/NixOS/nixpkgs/pull/292415";
title = "sway/hyprland: cross compilation fixes";
hash = "sha256-3BGZK5Plx1IJzKHv19RwcRWQ4S+cbUbsPbEzpxFEYsI=";
hash = "sha256-IDf8OcZzFgw0DalxzBqbqP7TZVnZkzoRHQ51RlR1xWc=";
})
# (fetchpatch' {
# title = "nixos/slskd: allow omitting username from yaml config";
# saneCommit = "541c37e8689b6422ea07be1395f1a63357bb0c63";
# hash = "sha256-xQEj/oIfNcE4td9jxzDzhlnIYpncOOdXZuswkmcLNuk=";
# })
# (fetchpatch' {
# title = "nixos/slskd: don't enable nginx unless nginx.enable was set";
# saneCommit = "ea084e5739a68436cc240aeca5c10b92de1e3138";
# hash = "sha256-25zB5eM1WBVEigmrq1mY9GXwEkS/jf5v7BCfmN6Wux4=";
# })
(fetchpatch' {
title = "nixos/slskd: option fixes";
prUrl = "https://github.com/NixOS/nixpkgs/pull/270646";
hash = "sha256-5brmmPfYp7G+5Dr5q2skWSwkrEwsRAe/UetoN0AqGjY=";
})
(fetchpatch' {
title = "gcr: remove build gnupg from runtime closure";
prUrl = "https://github.com/NixOS/nixpkgs/pull/263158";

View File

@@ -29,7 +29,7 @@ buildGoModule rec {
meta = with lib; {
description = "blast your linux audio to DLNA receivers";
# license = licenses.mit; # MIT + NoAI
homepage = src.homepage;
inherit (src.meta) homepage;
maintainers = with maintainers; [ colinsane ];
platforms = platforms.unix;
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
{
"egui_node_graph 0.4.0 (git+https://github.com/dimtpap/egui_node_graph.git?rev=a2e93a2826f90c21f13fa8fecf9076da611432fd#a2e93a2826f90c21f13fa8fecf9076da611432fd)": "1nnxm7h0ai0mipisi553ah9vsg0zjl4wsxcfrjlv10g5i7ldlya2",
"libspa 0.8.0 (git+https://gitlab.freedesktop.org/dimtpap/pipewire-rs.git?rev=605d15996f3258b3e1cc34e445dfbdf16a366c7e#605d15996f3258b3e1cc34e445dfbdf16a366c7e)": "1x7294f8i5sq18dv13scjgs8k4c843q38imxcdcwrf4gvcpb1jaz",
"libspa-sys 0.8.0 (git+https://gitlab.freedesktop.org/dimtpap/pipewire-rs.git?rev=605d15996f3258b3e1cc34e445dfbdf16a366c7e#605d15996f3258b3e1cc34e445dfbdf16a366c7e)": "1x7294f8i5sq18dv13scjgs8k4c843q38imxcdcwrf4gvcpb1jaz",
"pipewire 0.8.0 (git+https://gitlab.freedesktop.org/dimtpap/pipewire-rs.git?rev=605d15996f3258b3e1cc34e445dfbdf16a366c7e#605d15996f3258b3e1cc34e445dfbdf16a366c7e)": "1x7294f8i5sq18dv13scjgs8k4c843q38imxcdcwrf4gvcpb1jaz",
"pipewire-sys 0.8.0 (git+https://gitlab.freedesktop.org/dimtpap/pipewire-rs.git?rev=605d15996f3258b3e1cc34e445dfbdf16a366c7e#605d15996f3258b3e1cc34e445dfbdf16a366c7e)": "1x7294f8i5sq18dv13scjgs8k4c843q38imxcdcwrf4gvcpb1jaz"
}

View File

@@ -0,0 +1,13 @@
# Cargo.nix and crate-hashes.json were created with:
# - `nix run '.#crate2nix' -- generate -f ~/ref/repos/dimtpap/coppwr/Cargo.toml`
# to update:
# - `git fetch` in `~/ref/repos/dimtpap/coppwr`
# - re-run that crate2nix step
{ pkgs
, defaultCrateOverrides
}:
let
cargoNix = import ./Cargo.nix {
inherit defaultCrateOverrides pkgs;
};
in cargoNix.rootCrate.build

View File

@@ -22,7 +22,7 @@
stdenv.mkDerivation rec {
pname = "delfin";
version = "0.4.1";
version = "0.4.2";
src = if devBuild then fetchFromGitea {
domain = "git.uninsane.org";
@@ -35,13 +35,13 @@ stdenv.mkDerivation rec {
owner = "avery42";
repo = "delfin";
rev = "v${version}";
hash = "sha256-LBdHWEGz6dujcF3clrJbViohgiBTyWR7Y70totimVJ8=";
hash = "sha256-7GHwwwFibmwBcrlC2zSpEUZ2dca14wZFU6PJWjincPQ=";
};
cargoDeps = rustPlatform.fetchCargoTarball {
inherit src;
name = "${pname}-${version}";
hash = "sha256-TaUYqq4rkMBXhIM+0ZH6O0F+SUOpT1ImgLx2HCzJPrM=";
hash = "sha256-zlecw6230AC/+y537iEhJU+BgWRs2WCFP0AIcxchZBA=";
};
nativeBuildInputs = [

View File

@@ -48,10 +48,10 @@ in stdenv.mkDerivation {
#
# alternative would be to patchShebangs in the node_modules dir.
substituteInPlace src/Makefile \
--replace "yarn install" "true" \
--replace ' $(PRETTIER)' ' node $(PRETTIER)' \
--replace ' $(LESSC)' ' node $(LESSC)' \
--replace ' $(BROWSERIFY)' ' node $(BROWSERIFY)'
--replace-fail "yarn install" "true" \
--replace-fail ' $(PRETTIER)' ' node $(PRETTIER)' \
--replace-fail ' $(LESSC)' ' node $(LESSC)' \
--replace-fail ' $(BROWSERIFY)' ' node $(BROWSERIFY)'
'';
preBuild = ''

View File

@@ -7,12 +7,12 @@
stdenv.mkDerivation rec {
pname = "bypass-paywalls-clean";
version = "3.5.9.0";
version = "3.6.0.0";
src = fetchFromGitLab {
owner = "magnolia1234";
repo = "bypass-paywalls-firefox-clean";
rev = "v${version}";
hash = "sha256-hdD+uUoU2nN5ZFB9vR1xojqGRFfQIdXbFcmtT78bUQ8=";
hash = "sha256-oP9ha4PXBF/EalmA8tt/PHJb/KgCi9mXvvyvtHDmTGM=";
};
patches = [
@@ -26,8 +26,8 @@ stdenv.mkDerivation rec {
# which avoids breaking manual updates
postPatch = ''
substituteAllInPlace background.js \
--replace 'ext_api.runtime.openOptionsPage()' 'true' \
--replace ' fetch(' ' false && fetch('
--replace-fail 'ext_api.runtime.openOptionsPage()' 'true' \
--replace-fail ' fetch(' ' false && fetch('
'';
nativeBuildInputs = [ zip ];

View File

@@ -122,8 +122,8 @@ in (lib.makeScope newScope (self: with self; {
extid = "webextension@metamask.io";
pname = "ether-metamask";
url = "https://github.com/MetaMask/metamask-extension/releases/download/v${version}/metamask-firefox-${version}.zip";
version = "11.11.4";
hash = "sha256-vFpZ8l1xlxoT9iCvKnS+4b4wXplovaIySEazPJaYAnc=";
version = "11.12.4";
hash = "sha256-70elcOd4U+MEfg7IM0FfTBcWoKCKec63wLVdd3emW8M=";
};
fx_cast = fetchVersionedAddon rec {
extid = "fx_cast@matt.tf";
@@ -152,8 +152,8 @@ in (lib.makeScope newScope (self: with self; {
extid = "sponsorBlocker@ajay.app";
pname = "sponsorblock";
url = "https://github.com/ajayyy/SponsorBlock/releases/download/${version}/FirefoxSignedInstaller.xpi";
version = "5.5.8";
hash = "sha256-2amym6QQS4bueZTGlV8Oo8b1e1vANcGwES/ZoGH8s9U=";
version = "5.5.9";
hash = "sha256-lyBrszbjdfMOWZbYwE6DjNtM8wq0Vv1eCcobBKNelWw=";
};
ublacklist = fetchVersionedAddon rec {
extid = "@ublacklist";
@@ -168,8 +168,8 @@ in (lib.makeScope newScope (self: with self; {
# N.B.: a handful of versions are released unsigned
# url = "https://github.com/gorhill/uBlock/releases/download/${version}/uBlock0_${version}.signed.xpi";
url = "https://github.com/gorhill/uBlock/releases/download/${version}/uBlock0_${version}.firefox.signed.xpi";
version = "1.56.1b14";
hash = "sha256-IpPCMMwXxnGEyVThccEYsMFNyFRMe2lyV3igsKNQhiA=";
version = "1.56.1rc2";
hash = "sha256-B8YHcl24fYS6dqZHS6xgKx4G1j+ts3DBpySYNA1kcYc=";
};
};
})).overrideScope (self: super:
@@ -185,6 +185,7 @@ in (lib.makeScope newScope (self: with self; {
# (if you have one, feel free to share your nix package)
#
# NB: in source this is `alreadyInstalled: false`, but the build process hates Booleans or something
# TODO(2024/03/23): this is broken (replacement doesn't match). but maybe not necessary anymore?
substituteInPlace js/*.js \
--replace 'alreadyInstalled:!1' 'alreadyInstalled:!0'
'';

View File

@@ -106,9 +106,9 @@ let
# patch so meson will invoke our `crate2nix_cmd.sh` instead of cargo
postPatch = ''
substituteInPlace src/meson.build \
--replace 'cargo_options,' "" \
--replace "cargo, 'build'," "'bash', 'crate2nix_cmd.sh'," \
--replace "'src' / rust_target" "'target/bin'"
--replace-fail 'cargo_options,' "" \
--replace-fail "cargo, 'build'," "'bash', 'crate2nix_cmd.sh'," \
--replace-fail "'src' / rust_target" "'target/bin'"
'';
postConfigure = ''
# copied from <pkgs/development/tools/build-managers/meson/setup-hook.sh>
@@ -318,7 +318,7 @@ let
# just update this patch to reflect the right-hand side
# CARGO_MANIFEST_LINKS = "ring_core_0_17_7";
postPatch = (attrs.postPatch or "") + ''
substituteInPlace build.rs --replace \
substituteInPlace build.rs --replace-fail \
'links = std::env::var("CARGO_MANIFEST_LINKS").unwrap();' 'links = "ring_core_0_17_7".to_string();'
'';
};

View File

@@ -1,3 +1,11 @@
# to update:
# - first, figure the rev for `koreader-base`:
# - inside `koreader` repo:
# - `git submodule status base`
# - or `git log base`
# - inside `koreader-base` repo:
# - `git diff old-rev..new-rev thirdparty`
#
# koreader's native build process
# 1. git clone each dependency lib into base/thirdparty/$lib
# 2. git checkout a specific rev into base/thirdparty/$lib/build/$platform
@@ -15,39 +23,25 @@
#
# TODO:
# - don't vendor fonts
# - package enough of KOReader's deps to remove `sources.nix`
# - SDL2 (only used by macos??)
# - FBINK
# - NANOSVG (slightly complicated; koreader needs access to its source code)
# - SRELL
# - build crengine dep via nixpkgs `coolreader` pkg (with source patched to <https://github.com/koreader/crengine>)?
{ lib
, autoPatchelfHook
, autoconf
, automake
, buildPackages
, callPackage
, cmake
, dpkg
, fetchFromGitHub
, fetchgit
, fetchFromGitLab
, fetchurl
, gettext
, git
, libtool
, luajit
, makeWrapper
, perl
, pkg-config
, pkgs
, python3
, ragel
, stdenv
, substituteAll
, which
, symlinkJoin
# third-party dependencies which KOReader would ordinarily vendor
, symlinkJoin
, curl
, czmq
, djvulibre
@@ -64,6 +58,7 @@
, libpng
, libunibreak
, libwebp
, nanosvg
, openssl
, openssh
, sdcv
@@ -76,7 +71,6 @@
, zsync
}:
let
sources = callPackage ./sources.nix { luajit = luajit52; };
version = "2024.03";
src = fetchFromGitHub {
owner = "koreader";
@@ -86,6 +80,115 @@ let
rev = "v${version}";
hash = "sha256-/51pOGSAoaS0gOKlqNKruwaKY5qylzCpeNUrWyzYTpA=";
};
fbink-src-ko = fetchFromGitHub {
owner = "NiLuJe";
repo = "FBInk";
name = "fbink"; # where to unpack this in `srcs`
rev = "1a989b30a195ca240a3cf37f9de61b4b3c7e891c";
hash = "sha256-lXjAX0BoHW3L1E54d5J+wiAlAZXVmj9Y1Un8yaCwO8w=";
};
kobo-usbms-src-ko = fetchFromGitHub {
owner = "koreader";
repo = "KoboUSBMS";
name = "kobo-usbms"; # where to unpack this in `srcs`
rev = "v1.3.9";
hash = "sha256-91B0FUnmpE6TP4Lg5mj6z/U1DZQTKiPhG3ccCSgY4mQ=";
};
leptonica-src-ko = fetchFromGitHub {
# k2pdf needs leptonica src, because it actually patches it and builds it itself:
# - `cp -f $(LEPTONICA_MOD)/dewarp2.c $(LEPTONICA_DIR)/src/dewarp2.c`
# - i.e. cp -f /build/koreader/base/thirdparty/libk2pdfopt/build/aarch64-unknown-linux-gnu/libk2pdfopt-prefix/src/libk2pdfopt/leptonica_mod/dewarp2.c ...
# k2pdf uses an old leptonica -- like 2015-2017-ish (1.74.1).
# seems it can be at least partially updated, by replacing `numaGetMedianVariation` with `numaGetMedianDevFromMedian` (drop-in replacement)
# and replacing references to `liblept.so` with `libleptonica.so`,
# but eventually this requires patching the tesseract Makefiles. could get intense, idk.
owner = "DanBloomberg";
repo = "leptonica";
name = "leptonica"; # where to unpack this in `srcs`
rev = "1.74.1";
hash = "sha256-SDXKam768xvZZvTbXe3sssvZyeLEEiY97Vrzx8hoc6g=";
};
libk2pdfopt-src-ko = fetchFromGitHub {
owner = "koreader";
repo = "libk2pdfopt";
name = "libk2pdfopt"; # where to unpack this in `srcs`
rev = "09f1e011a618c8ec06b4caa67079682119d2aaa7";
hash = "sha256-37sZ46dG6Z1Wk7NrhKAKl5j9r1bN6g01cd5Iyt/2coM=";
};
lodepng-src-ko = fetchFromGitHub {
owner = "lvandeve";
repo = "lodepng";
name = "lodepng"; # where to unpack this in `srcs`
rev = "d398e0f10d152a5d17fa30463474dc9f56523f9c";
hash = "sha256-ApOHUgU6X1rHwyjAHA/0Nt+buDFqY2ttXEnEvdrRl3A=";
};
lunasvg-src-ko = fetchFromGitHub {
owner = "sammycage";
repo = "lunasvg";
name = "lunasvg"; # where to unpack this in `srcs`
rev = "59d6f6ba835c1b7c7a0f9d4ea540ec3981777885";
hash = "sha256-gW2ikakS6Omz5upmy26nAo/jkGHYO2kjlB3UmKJBh1k=";
};
minizip-src-ko = fetchFromGitHub {
# this is actually just a very old version (2015) of `minizip-ng`
owner = "nmoinvaz";
repo = "minizip";
name = "minizip"; # where to unpack this in `srcs`
rev = "0b46a2b4ca317b80bc53594688883f7188ac4d08";
hash = "sha256-P/3MMMGYDqD9NmkYvw/thKpUNa3wNOSlBBjANHSonAg=";
};
mupdf-src-ko = fetchFromGitHub {
owner = "ArtifexSoftware";
repo = "mupdf";
name = "mupdf"; # where to unpack this in `srcs`
fetchSubmodules = true; # specifically for jbig2dec, mujs, openjpeg
rev = "1.13.0";
hash = "sha256-pQejRon9fO9A1mhz3oLjBr1j4HveDLcQIWjR1/Rpy5Q=";
};
nanosvg-headers-ko = symlinkJoin {
# koreader's heavily-patched mupdf is dependent on a koreader-specific `stb_image_write` extension to nanosvg.
# nanosvg is used as a header-only library, so just patch that extension straight into the src.
name = "nanosvg-headers-ko";
paths = [
"${nanosvg.src}/src"
"${src}/base/thirdparty/nanosvg"
];
};
popen-noshell-src-ko = fetchFromGitHub {
owner = "famzah";
repo = "popen-noshell";
name = "popen-noshell";
rev = "e715396a4951ee91c40a98d2824a130f158268bb";
hash = "sha256-JeBZMsg6ZUGSnyZ4eds4w63gM/L73EsAnLaHOPpL6iM=";
};
tesseract-src-ko = fetchFromGitHub {
# TODO: try using nixpkgs' tesseract.src (i doubt it will work)
owner = "tesseract-ocr";
repo = "tesseract";
name = "tesseract";
rev = "60176fc5ae5e7f6bdef60c926a4b5ea03de2bfa7";
hash = "sha256-FQvlrJ+Uy7+wtUxBuS5NdoToUwNRhYw2ju8Ya8MLyQw=";
};
turbo-src-ko = fetchFromGitHub {
owner = "kernelsauce";
repo = "turbo";
name = "turbo";
rev = "v2.1.3";
hash = "sha256-vBRkFdc5a0FIt15HBz3TnqMZ+GGsqjEefnfJEpuVTBs=";
};
# XXX: for some inscrutable reason, `enable52Compat` is *partially* broken, only when cross compiling.
# `table.unpack` is non-nil, but `table.pack` is nil.
# the normal path is for `enable52Compat` to set `env.NIX_CFLAGS_COMPILE = "-DLUAJIT_ENABLE_LUA52COMPAT";`
@@ -97,13 +200,16 @@ let
"${src}/base/thirdparty/luajit/koreader-luajit-enable-table_pack.patch"
];
});
luaEnv = luajit52.withPackages (ps: with ps; [
(buildLuarocksPackage {
overlayedLuaPkgs = luaPkgs: let
ps = with ps; {
luajson = buildLuarocksPackage rec {
# needed by KOReader's lua-Spore
pname = "luajson";
version = "1.3.4-1";
src = fetchgit {
url = "https://github.com/harningt/luajson.git";
src = fetchFromGitHub {
owner = "harningt";
repo = "luajson";
# rev = "1.3.4";
# 1.3.4 (released 2017) has some incompatible bugs with lpeg library.
# see: <https://github.com/harningt/luajson/commit/6ecaf9bea8b121a9ffca5a470a2080298557b55d>
@@ -111,20 +217,57 @@ let
hash = "sha256-56G0NqIpavKHMQWUxy+Bp7G4ZKrQwUZ2C5e7GJxUJeg=";
};
knownRockspec = (fetchurl {
url = "mirror://luarocks/luajson-1.3.4-1.rockspec";
url = "mirror://luarocks/${pname}-${version}.rockspec";
hash = "sha256-+S4gfa6QaOMmOCDX8TxBq3kFWlbaEeiSMxCfefYakv0=";
}).outPath;
propagatedBuildInputs = [ lpeg ];
})
};
htmlparser = buildLuarocksPackage rec {
pname = "htmlparser"; #< name of the rockspec, not the repo
version = "0.3.9-1";
src = fetchFromGitHub {
owner = "msva";
repo = "lua-htmlparser";
# the rockspec was added to the repo *after* v0.3.9 was tagged
rev = "5ce9a775a345cf458c0388d7288e246bb1b82bff";
hash = "sha256-aSTLSfqz/MIDFVRwtBlDNBUhPb7KqOl32/Y62Hdec1s=";
};
knownRockspec = "${src}/rockspecs/${pname}-${version}.rockspec";
};
lua-spore = buildLuarocksPackage rec {
pname = "lua-spore"; #< name of the rockspec, not the repo
version = "0.3.3-1";
src = fetchFromGitLab {
domain = "framagit.org";
owner = "fperrad";
repo = "lua-Spore";
rev = "0.3.3";
hash = "sha256-wb7ykJsndoq0DazHpfXieUcBBptowYqD/eTTN/EK/6g=";
};
knownRockspec = "${src}/rockspec/${pname}-${version}.rockspec";
propagatedBuildInputs = [
luajson
luasocket
];
};
} // luaPkgs;
in ps;
luaEnv = luajit52.withPackages (ps: with (overlayedLuaPkgs ps); [
luajson
htmlparser
lua-spore
lpeg
luasec
luasocket
rapidjson
]);
rockspecFor = luaPkgName: let
pkg = luaEnv.pkgs."${luaPkgName}";
pkg = (overlayedLuaPkgs luaEnv.pkgs)."${luaPkgName}";
in
"${luaEnv}/${pkg.rocksSubdir}/${luaPkgName}/${pkg.rockspecVersion}/${luaPkgName}-${pkg.rockspecVersion}.rockspec";
crossTargets = {
# koreader-base Makefile targets to use when compiling for the given host platform
# only used when cross compiling
@@ -135,24 +278,6 @@ let
else
crossTargets."${stdenv.hostPlatform.parsed.cpu.name}";
fakeBuildDep = buildPackages.writeShellScript "fake-build-ko-dep" ''
set -x
lib="$1"
build_dir="$2"
prebuilt="$3"
mkdir -p "$build_dir/$lib-prefix/src"
rm -rf "$build_dir/$lib-prefix/src/$lib"
rm -rf "$build_dir/$lib-prefix/src/$lib-build"
# the library build directory koreader uses isn't consistently named, but we can cover most cases ($lib or $lib-build).
# we have to copy the full tree rather than just symlink because koreader/base/Makefile.third
# is copying lib/*.so into include/.
# seriously, wtf are they doing over there.
cp -R "$prebuilt" "$build_dir/$lib-prefix/src/$lib"
cp -R "$prebuilt" "$build_dir/$lib-prefix/src/$lib-build"
# ln -s "$prebuilt" "$build_dir/$lib-prefix/src/$lib"
# ln -s "$prebuilt" "$build_dir/$lib-prefix/src/$lib-build"
'';
getContrib = pkg: stdenv.mkDerivation {
inherit (pkg) name src;
dontConfigure = true;
@@ -194,7 +319,6 @@ let
# values to provide to koreader/base/Makefile.defs.
# should be ok to put this in `makeFlags` array, but i can't get that to work!
# LUAROCKS_BINARY substitution is to support the cross-compilation case (i.e. emulate it during the build process)
makefileDefs = ''
CURL_LIB="${lib.getLib curl}/lib/libcurl.so" \
CURL_DIR="${lib.getDev curl}" \
@@ -203,6 +327,7 @@ let
DJVULIBRE_LIB="${lib.getLib djvulibre}/lib/libdjvulibre.so" \
DJVULIBRE_LIB_LINK_FLAG="-L ${lib.getLib djvulibre}/lib -l:libdjvulibre.so" \
DJVULIBRE_DIR="${djvulibreAll}" \
FBINK_DIR="$NIX_BUILD_TOP/fbink" \
FREETYPE_LIB="${lib.getLib freetype}/lib/libfreetype.so" \
FREETYPE_LIB_LINK_FLAG="-L ${lib.getLib freetype}/lib -l:libfreetype.so" \
FREETYPE_DIR="${lib.getDev freetype}" \
@@ -222,6 +347,9 @@ let
JPEG_LIB_LINK_FLAG="-L ${lib.getLib libjpeg_turbo}/lib -l:libjpeg.so" \
JPEG_DIR="${lib.getDev libjpeg_turbo}" \
TURBOJPEG_LIB="${lib.getLib libjpeg_turbo}/lib/libturbojpeg.so" \
K2PDFOPT_DIR="$NIX_BUILD_TOP/libk2pdfopt" \
KOBO_USBMS_DIR="$NIX_BUILD_TOP/kobo-usbms" \
LEPTONICA_DIR="$NIX_BUILD_TOP/leptonica" \
LIBICONV="${lib.getLib libiconvReal}/lib/libiconv.so" \
LIBICONV_DIR="${lib.getDev libiconvReal}" \
LIBUNIBREAK_LIB="${lib.getLib libunibreak}/lib/libunibreak.so" \
@@ -231,24 +359,34 @@ let
LIBWEBPDEMUX_LIB="${lib.getLib libwebp}/lib/libwebpdemux.so" \
LIBWEBPSHARPYUV_LIB="${lib.getLib libwebp}/lib/libwebpsharpyuv.so" \
LIBWEBP_DIR="${lib.getDev libwebp}" \
LODEPNG_DIR="$NIX_BUILD_TOP/lodepng" \
LPEG_ROCK="${rockspecFor "lpeg"}" \
LUAROCKS_BINARY="${lib.optionalString (!stdenv.buildPlatform.canExecute stdenv.hostPlatform) (stdenv.hostPlatform.emulator buildPackages)} ${luajit52}/bin/lua ${luaEnv.pkgs.luarocks}/bin/.luarocks-wrapped" \
LUNASVG_DIR="$NIX_BUILD_TOP/lunasvg" \
LUAJIT="${luaEnv}/bin/luajit" \
LUAJIT_JIT="${luaEnv}/share/lua/5.1/jit" \
LUAJIT_LIB="${lib.getLib luaEnv}/lib/libluajit-5.1.so" \
LUASEC="${luaEnv}/share/lua/5.1/ssl/" \
LUASOCKET="${luaEnv}/share/lua/5.1/socket/" \
LUA_HTMLPARSER_ROCK="${rockspecFor "htmlparser"}" \
LUA_INCDIR="${lib.getDev luaEnv}/include" \
LUA_LIBDIR="${lib.getLib luaEnv}/lib/libluajit-5.1.so" \
LUA_RAPIDJSON_ROCK="${rockspecFor "rapidjson"}" \
LUA_SPORE_ROCK="${rockspecFor "lua-spore"}" \
MINIZIP_DIR="$NIX_BUILD_TOP/minizip" \
MUPDF_DIR="$NIX_BUILD_TOP/mupdf" \
NANOSVG_HEADERS="${nanosvg-headers-ko}" \
NANOSVG_INCLUDE_DIR="${nanosvg-headers-ko}" \
OPENSSL_LIB="${lib.getLib openssl}/lib/libssl.so" \
OPENSSL_DIR="${opensslAll}" \
SSL_LIB="${lib.getLib openssl}/lib/libssl.so.3" \
CRYPTO_LIB="${lib.getLib openssl}/lib/libcrypto.so" \
PNG_LIB="${lib.getLib libpng}/lib/libpng.so" \
PNG_DIR="${libAndDev libpng}" \
POPEN_NOSHELL_DIR="$NIX_BUILD_TOP/popen-noshell" \
SQLITE_LIB="${lib.getLib sqlite}/lib/libsqlite3.so" \
SQLITE_DIR="${lib.getDev sqlite}" \
TESSERACT_DIR="$NIX_BUILD_TOP/tesseract" \
TURBO_DIR="$NIX_BUILD_TOP/turbo" \
UTF8PROC_LIB="${lib.getLib utf8proc}/lib/libutf8proc.so" \
UTF8PROC_DIR="${utf8procAll}" \
ZLIB="${lib.getLib zlib}/lib/libz.so" \
@@ -308,30 +446,30 @@ in
stdenv.mkDerivation rec {
pname = "koreader-from-src";
inherit version;
srcs = [ src ] ++ (lib.mapAttrsToList
(name: src: fetchgit (
{
inherit name;
} // src.source // {
# koreader sometimes specifies the rev as `tags/FOO`.
# we need to remember that to place the repo where it expects, but we have to strip it here for fetchgit to succeed.
rev = lib.removePrefix "tags/" src.source.rev;
}
))
sources.thirdparty
);
srcs = [
src
fbink-src-ko
kobo-usbms-src-ko
leptonica-src-ko
libk2pdfopt-src-ko
lodepng-src-ko
lunasvg-src-ko
minizip-src-ko
mupdf-src-ko
popen-noshell-src-ko
tesseract-src-ko
turbo-src-ko
];
patches = [
./debug.patch #< not needed to build, just helps debug packaging issues
./no_rm_build_dirs.patch
./lua-Spore-no-luajson.patch #< TODO: test this at runtime! we ship luajson, but just don't expose it via luarocks
# ./debug.patch #< not needed to build, just helps debug packaging issues
./rss-no-interrupt-on-image-failure.patch # just a preference
];
sourceRoot = "koreader";
nativeBuildInputs = [
buildPackages.stdenv.cc # TODO: move to depsBuildBuild?
buildPackages.stdenv.cc
autoconf # autotools is used by some thirdparty libraries
automake
autoPatchelfHook # used by us, in fixupPhase, to ensure substituted thirdparty deps can be loaded at runtime
@@ -340,17 +478,15 @@ stdenv.mkDerivation rec {
libtool
makeWrapper
pkg-config
luaEnv.pkgs.luarocks
];
buildInputs = [
# luajson
luaEnv
luaEnv #< specifically for lua.h
];
postPatch = ''
# patch for newer openssl
substituteInPlace --fail base/ffi/crypto.lua \
--replace 'ffi.load("libs/libcrypto.so.1.1")' 'ffi.load("libcrypto.so")'
substituteInPlace base/ffi/crypto.lua \
--replace-fail 'ffi.load("libs/libcrypto.so.1.1")' 'ffi.load("libcrypto.so")'
# dlopen libraries by name only, allowing them to be found via LD_LIBRARY_PATH
# instead of just via $out/libs. this is required whenever we direct KOreader to use system libs instead of its vendored libs.
@@ -359,6 +495,18 @@ stdenv.mkDerivation rec {
--replace-quiet 'ffi.load("libs/' 'ffi.load("'
done
# don't force-rebuild third-party components, else we can't replace them with our own
substituteInPlace base/Makefile.third \
--replace-fail ' -rm ' ' # -rm'
# make some sources writable, particularly so koreader can apply its patches (by default only the `sourceRoot` is writable)
chmod -R u+w "$NIX_BUILD_TOP"/{fbink,kobo-usbms,leptonica,libk2pdfopt,lodepng,lunasvg,minizip,mupdf,popen-noshell,tesseract,turbo}
# koreader builds these deps itself: we mock out the download stage, and it does the rest
for dep in fbink kobo-usbms libk2pdfopt lodepng lunasvg minizip mupdf popen-noshell turbo; do
# sed -i 's/DOWNLOAD_COMMAND .*/DOWNLOAD_COMMAND ""/' "base/thirdparty/$dep/CMakeLists.txt"
sed -i "s:DOWNLOAD_COMMAND .*:DOWNLOAD_COMMAND rm -fd $dep $dep-build \\&\\& ln -s $NIX_BUILD_TOP/$dep $dep \\&\\& ln -s $NIX_BUILD_TOP/$dep $dep-build :" "base/thirdparty/$dep/CMakeLists.txt"
done
# lots of places in Makefile.third (incorrectly) assume lib paths are relative to CURDIR,
# so link /nix into CURDIR to allow them to work anyway
ln -s /nix base/nix
@@ -366,79 +514,12 @@ stdenv.mkDerivation rec {
dontConfigure = true;
buildPhase = ''
link_lib_into_build_dir() {
lib="$1"
rev="$2"
platform="$3"
prebuilt="$4"
lib_src="../$lib"
cmake_lists="base/thirdparty/$lib/CMakeLists.txt"
build_dir="base/thirdparty/$lib/build/$platform"
# link the nix clone into the directory koreader would use for checkout
# ref="base/thirdparty/$l/build/git_checkout"
# echo "linking thirdparty library $l $ref -> $deref"
# mkdir -p "$ref"
# ln -s "$deref" "$ref/$l"
# mv "$deref" "$ref/$l"
# cp -R "$deref" "$ref/$l"
# needs to be writable for koreader to checkout it specific revision
# chmod u+w -R "$ref/$l/.git"
# koreader wants to clone each library into this git_checkout dir,
# then checkout a specific revision,
# and then copy that checkout into the build/working directory further down.
# instead, we replicate that effect here, and by creating these "stamp" files
# koreader will know to skip the `git clone` and `git checkout` calls.
# the logic we're spoofing lives in koreader/base/thirdparty/cmake_modules/koreader_thirdparty_git.cmake
stamp_dir="$build_dir/git_checkout/stamp"
stamp_info="$stamp_dir/$lib-gitinfo-$rev.txt"
stamp_clone="$stamp_dir/$lib-gitclone-lastrun.txt"
echo "creating stamps for $lib: $stamp_clone > $stamp_info"
# mkdir $(dirname ..) to handle the case where `$rev` contains slashes
mkdir -p $(dirname "$stamp_info")
# koreader-base decides whether to redo the git checkout based on a timestamp compare of these two stamp files
touch -d "last week" $(dirname "$stamp_info") #< XXX: necessary?
touch -d "last week" "$stamp_info"
touch -d "next week" "$stamp_clone"
# koreader would copy the checkout into this build/working directory,
# but because we spoof the stamps to work around other git errors,
# copy it there on koreader's behalf
prefix="$build_dir/$lib-prefix"
mkdir -p "$prefix/src"
cp -R "$lib_src" "$prefix/src/$lib"
# src dir needs to be writable for koreader to apply its own patches
chmod u+w -R "$prefix/src/$lib"
if [ -n "$prebuilt" ]; then
abs_build_dir="$(realpath "$build_dir")"
sed -i 's/INSTALL_COMMAND .*/INSTALL_COMMAND ""/' "$cmake_lists"
sed -i \
"s:BUILD_COMMAND .*:BUILD_COMMAND ${fakeBuildDep} $lib $abs_build_dir $prebuilt:" \
"$cmake_lists"
fi
}
${builtins.concatStringsSep "\n" (lib.mapAttrsToList
(name: src:
let
# for machine-agnostic libraries (e.g. pure lua), koreader doesn't build them in a flavored directory
machine = if src.machineAgnostic or false then "" else stdenv.hostPlatform.config;
in
''link_lib_into_build_dir "${name}" "${src.source.rev}" "${machine}" "${src.package or ""}"''
)
sources.thirdparty
)}
# outDir should match OUTPUT_DIR in koreader-base
outDir="/build/koreader/base/build/${stdenv.hostPlatform.config}"
outDir="$NIX_BUILD_TOP/koreader/base/build/${stdenv.hostPlatform.config}"
mkdir -p "$outDir"
${symlinkThirdpartyBins "$outDir"}
make ${makeFlags}
'';
env = lib.optionalAttrs (stdenv.buildPlatform != stdenv.hostPlatform) {
@@ -476,7 +557,12 @@ stdenv.mkDerivation rec {
passthru = {
# exposed for debugging
inherit luajit52 luaEnv;
inherit luajit52 luaEnv mupdf-src-ko nanosvg-headers-ko rockspecFor;
inherit (overlayedLuaPkgs luaEnv.pkgs)
luajson
htmlparser
lua-spore
;
};
meta = with lib; {

View File

@@ -1,12 +0,0 @@
diff --git a/base/thirdparty/lua-Spore/CMakeLists.txt b/base/thirdparty/lua-Spore/CMakeLists.txt
index 15593193..31140257 100644
--- a/base/thirdparty/lua-Spore/CMakeLists.txt
+++ b/base/thirdparty/lua-Spore/CMakeLists.txt
@@ -26,6 +26,7 @@ list(APPEND BUILD_CMD COMMAND ${ROCKS_CMD} make --tree=${OUTPUT_DIR}/rocks ${LUA_SPORE_ROCKSPEC})
list(APPEND BUILD_CMD "LUA_INCDIR=${LUA_INCDIR}" "LUA_LIBDIR=${LUA_LIBDIR}")
list(APPEND PATCH_CMD COMMAND mkdir -p doc)
+list(APPEND PATCH_CMD COMMAND ${ISED} "s| 'luajson|--&|g" ${LUA_SPORE_ROCKSPEC})
list(APPEND PATCH_CMD COMMAND ${ISED} "s| 'luasocket|--&|g" ${LUA_SPORE_ROCKSPEC})
ko_write_gitclone_script(

View File

@@ -1,20 +0,0 @@
diff --git a/base/Makefile.third b/base/Makefile.third
index f6a80523..1080ab98 100644
--- a/base/Makefile.third
+++ b/base/Makefile.third
@@ -138,7 +138,6 @@ $(MUPDF_LIB): $(JPEG_LIB) \
$(LIBWEBP_LIB) \
$(ZLIB) $(AES_LIB) \
$(THIRDPARTY_DIR)/mupdf/*.*
- -rm -rf $(MUPDF_BUILD_DIR)
install -d $(MUPDF_BUILD_DIR)
cd $(MUPDF_BUILD_DIR) && \
$(CMAKE) $(CMAKE_FLAGS) \
@@ -665,7 +664,6 @@ $(OUTPUT_DIR)/data/KoboUSBMS.tar.gz: $(THIRDPARTY_DIR)/kobo-usbms/*.*
# ===========================================================================
# common lua library for networking
$(LUASOCKET): $(THIRDPARTY_DIR)/luasocket/*.*
- -rm -rf $(LUASOCKET) $(LUASOCKET_BUILD_DIR)
install -d $(LUASOCKET_BUILD_DIR)
cd $(LUASOCKET_BUILD_DIR) && \
$(CMAKE) $(CMAKE_FLAGS) \

View File

@@ -1,307 +0,0 @@
# to update:
# - first, figure the rev for `koreader-base`:
# - inside `koreader` repo:
# - `git submodule status base`
# - or `git log base`
# - inside `koreader-base` repo:
# - `git diff old-rev..new-rev thirdparty`
# - update `source.rev` everywhere here that changed upstream
# - zero the hashes here and correct them based on build errors
# - tweak ./vendor-external-projects.patch until it applies
# - usually just upstream changed a URL or something minor
#
# a full rebuild takes approximately 10 minutes on a mid-range desktop
#
# the following build output may look like an error, but is safe to ignore:
# - "awk: fatal: cannot open file `3.9' for reading: No such file or directory"
# - this number comes from the luarocks version
#
# how to automate koreader updates?
# - it may be that koreader-base is more strongly decoupled from `koreader` than first appears:
# - most `koreader` commits which update base simply bump its rev and nothing more.
# - then, `koreader-base` could be its own package, updated independently from the main koreader.
{ lib
, symlinkJoin
, curl
, czmq
, djvulibre
, freetype
, fribidi
, giflib
, glib
, harfbuzz
, k2pdfopt
, leptonica
, libjpeg_turbo
, libpng
, libunibreak
, libwebp
, luajit
, minizip
, mupdf
, mupdf_1_17
, nanosvg
, openssh
, openssl_1_1
, sdcv
, tesseract
, turbo
, utf8proc
, zeromq4
, zstd
, zsync
}:
let
libAndDev = pkg: symlinkJoin {
inherit (pkg) name;
paths = [
(lib.getLib pkg)
(lib.getDev pkg)
];
};
in
{
thirdparty = {
# providing `package` is just a way to optimize builds, by getting KOReader to use the built nixpkg instead of building it itself from source.
# if it fails during an update, it should always be safe to delete the package key.
# curl = {
# source.url = "https://github.com/curl/curl.git";
# source.rev = "tags/curl-7_80_0";
# source.hash = "sha256-kzozc0Io+1f4UMivSV2IhzJDQXmad4wNhXN/Y2Lsg3Q=";
# package = curl;
# };
# czmq = {
# source.url = "https://github.com/zeromq/czmq.git";
# source.rev = "2a0ddbc4b2dde623220d7f4980ddd60e910cfa78";
# source.hash = "sha256-p4Cl2PLVgRQ0S4qr3VClJXjvAd2LUBU9oRUvOCfVnyw=";
# # package = czmq; # koreader wants v1, nixpkgs has v4
# };
# djvulibre = {
# source.url = "https://gitlab.com/koreader/djvulibre.git";
# source.rev = "6a1e5ba1c9ef81c205a4b270c3f121a1e106f4fc";
# source.hash = "sha256-OWSbxdr93FH3ed0D+NSFWIah7VDTcL3LIGOciY+f4dk=";
# # package = djvulibre; # "cp -fL /build/koreader/base/thirdparty/djvulibre/build/aarch64-unknown-linux-gnu/djvulibre-prefix/src/djvulibre/libdjvu/.libs/libdjvulibre.so.21 ..."
# };
fbink = {
source.url = "https://github.com/NiLuJe/FBInk.git";
source.rev = "1a989b30a195ca240a3cf37f9de61b4b3c7e891c";
source.hash = "sha256-O3bZzvuj/BRVV+UoutaaZZgGZws2J/i5ArfBHbz6omI=";
# package: not packaged for nix
};
# freetype2 = {
# source.url = "https://gitlab.com/koreader/freetype2.git";
# source.rev = "VER-2-13-2";
# source.hash = "sha256-yylSmVM3D5xnbFx9qEEHFIP/K0x/WDXZr0MA4C7ng7k=";
# package = libAndDev freetype;
# };
# fribidi = {
# source.url = "https://github.com/fribidi/fribidi.git";
# source.rev = "tags/v1.0.12";
# source.hash = "sha256-L4m/F9rs8fiv9rSf8oy7P6cthhupc6R/lCv30PLiQ4M=";
# package = libAndDev fribidi;
# };
# giflib = {
# source.url = "https://gitlab.com/koreader/giflib.git";
# source.rev = "5.1.4";
# source.hash = "sha256-znbY4tliXHXVLBd8sTKrbglOdCUb7xhcCQsDDWcQfhw=";
# package = giflib;
# };
# glib = {
# source.url = "https://github.com/GNOME/glib.git";
# source.rev = "2.58.3";
# source.hash = "sha256-KmJXCJ6h2QhPyK1axk+Y9+yJzO0wnCczcogopxGShJc=";
# # package = libAndDev glib; # breaks sdcv build
# };
# harfbuzz = {
# source.url = "https://github.com/harfbuzz/harfbuzz.git";
# source.rev = "8.3.0";
# source.hash = "sha256-sO0Kd2wAbMm+Auf7tXsDNal7hqND8iwkb0M/9WWt9sI=";
# # package = harfbuzz;
# package = libAndDev harfbuzz;
# };
kobo-usbms = {
source.url = "https://github.com/koreader/KoboUSBMS.git";
source.rev = "v1.3.9";
source.hash = "sha256-/yYpagekWlfTrXu/1DNTmBmdd3IkCDjRtslRv13mtCg=";
# package: not in nixpkgs
};
leptonica = {
source.url = "https://github.com/DanBloomberg/leptonica.git";
source.rev = "1.74.1";
source.hash = "sha256-SDXKam768xvZZvTbXe3sssvZyeLEEiY97Vrzx8hoc6g=";
# package = leptonica; # k2pdf needs leptonica src. # cp -f /build/koreader/base/thirdparty/libk2pdfopt/build/aarch64-unknown-linux-gnu/libk2pdfopt-prefix/src/libk2pdfopt/leptonica_mod/dewarp2.c
};
# libjpeg-turbo = {
# source.url = "https://github.com/libjpeg-turbo/libjpeg-turbo.git";
# source.rev = "3.0.1";
# source.hash = "sha256-ofdecix4m0FA9gdyQh7zYn99SYBbH2+a7jfoZlsadoA=";
# # package = libAndDev libjpeg_turbo;
# };
libk2pdfopt = {
source.url = "https://github.com/koreader/libk2pdfopt.git";
source.rev = "09f1e011a618c8ec06b4caa67079682119d2aaa7";
source.hash = "sha256-37sZ46dG6Z1Wk7NrhKAKl5j9r1bN6g01cd5Iyt/2coM=";
# package = k2pdfopt; # nixpkgs k2pdfopt does not compile (broken deps). also, uses old insecure mupdf 1.17 (oh well, koreader is even older)
};
# libpng = {
# source.url = "https://github.com/glennrp/libpng.git";
# source.rev = "v1.6.40";
# source.hash = "sha256-Rad7Y5Z9PUCipBTQcB7LEP8fIVTG3JsnMeknUkZ/rRg=";
# # package = libAndDev libpng; # "/build/koreader/base/thirdparty/libpng/build/aarch64-unknown-linux-gnu/libpng-prefix/src/libpng-build/.libs/libpng16.so.16"
# };
# libunibreak = {
# source.url = "https://github.com/adah1972/libunibreak.git";
# source.rev = "tags/libunibreak_5_1";
# source.hash = "sha256-hjgT5DCQ6KFXKlxk9LLzxGHz6B71X/3Ot7ipK3KY85A=";
# # package = libAndDev libunibreak; # nixpkgs version is incompatible (kpvcrlib/crengine #includes libunibreak and then fails, calling into undefined functions)
# };
# libwebp = {
# source.url = "https://github.com/webmproject/libwebp.git";
# source.rev = "v1.3.2";
# source.hash = "sha256-gfwUlJ44biO1lB/3SKfMkM/YBiYcz6RqeMOw+0o6Z/Q=";
# package = libAndDev libwebp;
# };
# libzmq = {
# source.url = "https://github.com/zeromq/libzmq";
# source.rev = "883e95b22e0bffffa72312ea1fec76199afbe458";
# source.hash = "sha256-R76EREtHsqcoKxKrgT8gfEf9pIWdLTBXvF9cDvjEf3E=";
# # package = zeromq4; # despite the name, it's libzmq.so.5 instead of libzmq.so.4
# };
lj-wpaclient = {
source.url = "https://github.com/koreader/lj-wpaclient.git";
source.rev = "2f93beb3071e6ebb57c783bd5b92f83aa5ebb757";
source.hash = "sha256-ilJviGZTvL2i1TN5lHQ4eA9pFiM7NlXD+v9ofv520b8=";
machineAgnostic = true;
# package: not in nixpkgs; not even a non-luajit `wpaclient`
};
lodepng = {
source.url = "https://github.com/lvandeve/lodepng.git";
source.rev = "d398e0f10d152a5d17fa30463474dc9f56523f9c";
source.hash = "sha256-ApOHUgU6X1rHwyjAHA/0Nt+buDFqY2ttXEnEvdrRl3A=";
# package: not in nixpkgs, except in source-only form (mujoco.pin.lodepng)
};
lua-htmlparser = {
source.url = "https://github.com/msva/lua-htmlparser";
source.rev = "5ce9a775a345cf458c0388d7288e246bb1b82bff";
source.hash = "sha256-aSTLSfqz/MIDFVRwtBlDNBUhPb7KqOl32/Y62Hdec1s=";
# package: not in nixpkgs
};
# luajit = {
# source.url = "https://github.com/LuaJIT/LuaJIT";
# source.rev = "29b0b282f59ac533313199f4f7be79490b7eee51";
# source.hash = "sha256-S57/NR+0hF1KTdn+cbVkJh3MTfklSwtZua1CYKduVlk=";
# # package = luajit; #< could be fixed; follows a different install structure
# };
# lua-rapidjson = {
# source.url = "https://github.com/xpol/lua-rapidjson";
# source.rev = "242b40c8eaceb0cc43bcab88309736461cac1234";
# source.hash = "sha256-y/czEVPtCt4uN1n49Qi7BrgZmkG+SDXlM5D2GvvO2qg=";
# # package: TODO: packaged in nix as a luarocks package
# };
# luasec = {
# source.url = "https://github.com/brunoos/luasec";
# source.rev = "tags/v1.3.1";
# source.hash = "sha256-3iYRNQoVk5HFjDSqRRmg1taSqeT2cHFil36vxjrEofo=";
# # package: TODO: packaged in nix as a luarocks package
# };
# luasocket = {
# source.url = "https://github.com/lunarmodules/luasocket";
# source.rev = "8c2ff7217e2a205eb107a6f48b04ff1b2b3090a1";
# source.hash = "sha256-Y35QYNLznQmErr6rIjxLzw0/6Y7y8TbzD4yaEdgEljA=";
# # package: TODO: packaged in nix as a luarocks package
# };
lua-Spore = {
# Complete... ish?
# this originally failed like so:
# Missing dependencies for lua-spore 0.3.1-1:
# luajson >= 1.3 (not installed)
# it passes now only because we patch out its build-time check for luajson (which we DO provide at runtime)
source.url = "https://framagit.org/fperrad/lua-Spore";
source.rev = "tags/0.3.3";
source.hash = "sha256-wb7ykJsndoq0DazHpfXieUcBBptowYqD/eTTN/EK/6g=";
# package: not in nixpkgs
};
lunasvg = {
source.url = "https://github.com/sammycage/lunasvg.git";
source.rev = "59d6f6ba835c1b7c7a0f9d4ea540ec3981777885";
source.hash = "sha256-gW2ikakS6Omz5upmy26nAo/jkGHYO2kjlB3UmKJBh1k=";
# package: not in nixpkgs
};
minizip = {
source.url = "https://github.com/nmoinvaz/minizip";
source.rev = "0b46a2b4ca317b80bc53594688883f7188ac4d08";
source.hash = "sha256-P/3MMMGYDqD9NmkYvw/thKpUNa3wNOSlBBjANHSonAg=";
# package = libAndDev minizip; # weird #include incompatibilities... maybe resolvable.
};
mupdf = {
source.url = "https://github.com/ArtifexSoftware/mupdf.git";
source.rev = "tags/1.13.0";
source.hash = "sha256-pQejRon9fO9A1mhz3oLjBr1j4HveDLcQIWjR1/Rpy5Q=";
# package = libAndDev mupdf; # nixpkgs' mupdf is incompatible with koreader's `libwrap-mupdf`
# package = libAndDev mupdf_1_17; # does not compile
};
nanosvg = {
source.url = "https://github.com/memononen/nanosvg.git";
source.rev = "93ce879dc4c04a3ef1758428ec80083c38610b1f";
source.hash = "sha256-ZtenaXJqMZr2+BxYENG1zUoQ+Qoxlxy0m/1YfJBKAFk=";
machineAgnostic = true;
package = nanosvg.src; # KOReader only wants the .h files, but decides to do that without even building it.
};
# openssh = {
# source.url = "https://github.com/openssh/openssh-portable.git";
# source.rev = "V_8_6_P1";
# source.hash = "sha256-yjIpSbe5pt9sEV2MZYGztxejg/aBFfKO8ieRvoLN2KA=";
# package = openssh;
# };
# openssl = {
# source.url = "https://github.com/openssl/openssl.git";
# source.rev = "OpenSSL_1_1_1u";
# source.hash = "sha256-JOcUj4ovA6621+1k2HUsvhGX1B9BjvaMbCaSx680nSs=";
# # TODO: i think we can use nixpkgs openssl, just lift lib/* up to the root of the package directory
# # package = lib.getLib openssl_1_1; # N.B.: requires building with `NIXPKGS_ALLOW_INSECURE=1 nix build --impure ...`
# };
popen-noshell = {
source.url = "https://github.com/famzah/popen-noshell.git";
source.rev = "e715396a4951ee91c40a98d2824a130f158268bb";
source.hash = "sha256-JeBZMsg6ZUGSnyZ4eds4w63gM/L73EsAnLaHOPpL6iM=";
# package: not in nixpkgs
};
# sdcv = {
# # upstream is (temporarily?) acquiring this via `download_project` machinery
# source.url = "https://github.com/Dushistov/sdcv.git";
# source.rev = "v0.5.5";
# source.hash = "sha256-EyvljVXhOsdxIYOGTzD+T16nvW7/RNx3DuQ2OdhjXJ4=";
# package = sdcv;
# };
tesseract = {
source.url = "https://github.com/tesseract-ocr/tesseract.git";
source.rev = "60176fc5ae5e7f6bdef60c926a4b5ea03de2bfa7";
source.hash = "sha256-FQvlrJ+Uy7+wtUxBuS5NdoToUwNRhYw2ju8Ya8MLyQw=";
# package = tesseract; # i guess koreader's k2pdf also builds tessearct??
};
turbo = {
source.url = "https://github.com/kernelsauce/turbo";
source.rev = "tags/v2.1.3";
source.hash = "sha256-vBRkFdc5a0FIt15HBz3TnqMZ+GGsqjEefnfJEpuVTBs=";
# package = turbo; # nixpkgs' turbo is a totally different thing
};
# utf8proc = {
# source.url = "https://github.com/JuliaStrings/utf8proc.git";
# source.rev = "v2.9.0";
# source.hash = "sha256-Sgh8vTbclUV+lFZdR29PtNUy8F+9L/OAXk647B+l2mg=";
# # package = libAndDev utf8proc; # nixpkgs is v3, not v2; incompatible .so name. /build/koreader/base/thirdparty/utf8proc/build/aarch64-unknown-linux-gnu/utf8proc-prefix/src/utf8proc/libutf8proc.so.2
# };
# zstd = {
# source.url = "https://github.com/facebook/zstd.git";
# source.rev = "tags/v1.5.5";
# source.hash = "sha256-tHHHIsQU7vJySrVhJuMKUSq11MzkmC+Pcsj00uFJdnQ=";
# package = libAndDev zstd;
# };
# zsync2 = {
# source.url = "https://github.com/NiLuJe/zsync2.git";
# source.rev = "e618d18f6a7cbf350cededa17ddfe8f76bdf0b5c";
# source.hash = "sha256-S0vxCON1l6S+NWlnRPfm7R07DVkvkG+6QW5LNvXBlA8=";
# package = zsync; # possibly a different thing than koreader's
# };
};
}

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