Compare commits
456 Commits
wip-s6-2
...
wip-trust-
Author | SHA1 | Date | |
---|---|---|---|
a8293f348c | |||
0fbf35e0a9 | |||
f037e6dc72 | |||
fe5af959f4 | |||
bef0099eec | |||
67434caf45 | |||
be84ab1f45 | |||
43d32641f3 | |||
9bf0dbabae | |||
8c7880774e | |||
5774aa4a8f | |||
6c6d11578e | |||
f33e960bdf | |||
14202a5bcc | |||
3d2babf2bb | |||
9d51b2ecc7 | |||
0b855efb5f | |||
2ae286ff75 | |||
a05fa53ee1 | |||
46d95805e9 | |||
9346a066d1 | |||
567531727e | |||
6c65e4b313 | |||
e6b13adb61 | |||
bd3e06982b | |||
660ba94c7c | |||
11ddce043d | |||
980fe6b33c | |||
016df3ff74 | |||
d827235d31 | |||
f7a25d1421 | |||
d148b19767 | |||
89135d08cb | |||
15a5afd2c4 | |||
e63e359417 | |||
3c1a74998e | |||
eadf85f66d | |||
536eb2154a | |||
e62365436c | |||
4b04c283b6 | |||
def5816003 | |||
6f2ae56126 | |||
a40f05260a | |||
2b539fafcd | |||
954c5c8344 | |||
2a8ecf0423 | |||
a056ca84be | |||
8d8bf00a34 | |||
f58bcb4767 | |||
4f56acc316 | |||
fdf1b20368 | |||
c12691a3a0 | |||
06bfa05ec1 | |||
32e06ce998 | |||
c0a7b831cd | |||
8c04023eba | |||
cf3cfc5249 | |||
a1625ea41d | |||
2cedd2beb4 | |||
6544b9aca4 | |||
08a9b838e5 | |||
b7dd40e558 | |||
7f2abf7e6e | |||
825812e511 | |||
69e5495cc9 | |||
b422f15b59 | |||
889b332ade | |||
46d1a49f0f | |||
1b156dcb0a | |||
efd2f14a13 | |||
d4d06d037c | |||
9525c09c41 | |||
e5876a1e5b | |||
1757ea4492 | |||
39d31c3ffd | |||
c50ef59102 | |||
5fc4ca6dad | |||
35c7527946 | |||
7e8a014f37 | |||
4ce951bbed | |||
f784550b9b | |||
1f2bbd4aec | |||
2389757581 | |||
9021ab9f05 | |||
547d71c19a | |||
79bba42768 | |||
8dd4fe06f3 | |||
19115dfb65 | |||
4c0ae75b00 | |||
34842c00fe | |||
46a513b263 | |||
6129fbf2b3 | |||
242541304e | |||
f3d2dee470 | |||
3d207ab7bb | |||
95447eb765 | |||
593268f620 | |||
5c98a51cd6 | |||
4453bde815 | |||
7843f9650a | |||
82dce71b9c | |||
8a981c3ca9 | |||
9c00c2c5cc | |||
aad645f2c5 | |||
d01e49f566 | |||
7447d4879e | |||
215864e3d4 | |||
0cbb81cfea | |||
9d7816a1cd | |||
461aa5ede0 | |||
b5874f4b49 | |||
10fc7bbb84 | |||
87e3f2a9ef | |||
5006692594 | |||
9481131daf | |||
ae418fb2d1 | |||
c174eddddf | |||
6d74c6616c | |||
7db40fbf47 | |||
152a5d4c92 | |||
e33b7d7701 | |||
01e176d902 | |||
fd771cdb2c | |||
243e8b831b | |||
9a8b4395f1 | |||
4c7cd06212 | |||
d0de6a9254 | |||
12f2798140 | |||
bd92076291 | |||
9cbe774c5a | |||
b4653b20a2 | |||
2c011df252 | |||
9d472bb290 | |||
95b21cbed9 | |||
82007c9b40 | |||
50c72de4f9 | |||
36237a3201 | |||
eb9df628e6 | |||
6e04e288ea | |||
a92960d778 | |||
ef9b0e9309 | |||
350e00e0cd | |||
e924363dfb | |||
b12f31652c | |||
b77e811ad4 | |||
a000a722ba | |||
4dde01245e | |||
f50c0a98c2 | |||
0625bfdd10 | |||
4dfee58d09 | |||
a7b8eb179b | |||
f10bb6c86c | |||
a59a7b5346 | |||
1bd715e57e | |||
b7d2020f10 | |||
317996b609 | |||
e197f6f54d | |||
135f63480b | |||
d9ffa5bb5a | |||
f59f13588f | |||
c668a895d4 | |||
9442a87311 | |||
3bd56fb565 | |||
bdc3b1ed0e | |||
40af93a7fb | |||
cd4fc97bde | |||
a36ff517e7 | |||
3642ead646 | |||
60c370df3f | |||
d80852c6c1 | |||
62b3047fff | |||
de2c3a30ff | |||
c08280589d | |||
9a9ffcbea9 | |||
733efcfaf7 | |||
b34d984572 | |||
e2b58e1b77 | |||
b7e5bc5972 | |||
831b4ad72a | |||
54cefa247a | |||
e3898449b7 | |||
e5bad6a74c | |||
254343a6af | |||
28bfd75114 | |||
b7fd5e78cc | |||
79985ff009 | |||
62f5b9276f | |||
276844af0b | |||
13c1f01a6b | |||
5f281f57de | |||
089e434e3f | |||
b068b50d7c | |||
14911868e4 | |||
539fe48947 | |||
259c3af526 | |||
6b5e0e57bc | |||
b55c903a81 | |||
4f06f0dc5e | |||
c0dde0e540 | |||
6779063578 | |||
2eea562d1f | |||
0385c09f23 | |||
bd57b95598 | |||
465da7c939 | |||
44f76e656a | |||
824046aca1 | |||
827c50ff43 | |||
0230291bb2 | |||
3d3618256d | |||
590cb2dd7f | |||
d9dcab544c | |||
4ee0f0c659 | |||
7692ab0b3e | |||
2af5bb3d78 | |||
e72a0a4300 | |||
6b0cbf684f | |||
62af314238 | |||
e8745b4312 | |||
5a10173ba3 | |||
2d8fe1d3e5 | |||
0741d87bcb | |||
2587c27f89 | |||
2d74d0725d | |||
b0d1d2e1af | |||
61dc79a2ea | |||
7804236499 | |||
f908762cf8 | |||
feb36d19ac | |||
06185ac870 | |||
a8915661a7 | |||
d57aa473ac | |||
a28b7d5616 | |||
dd58ba8b00 | |||
a21508b6ba | |||
94cff99f53 | |||
8aa8d773de | |||
0a888e205e | |||
898dc89c8f | |||
4b22fd95bf | |||
527a9e7612 | |||
3686e6e508 | |||
cda50db23b | |||
344e24fcd2 | |||
9be6960bbf | |||
a45aabfb72 | |||
602bf59843 | |||
895d7f6f20 | |||
876103ff7b | |||
46cda87d5e | |||
d728dfcd70 | |||
19fcd0318c | |||
030f6d1a99 | |||
86b495cb9f | |||
c897f4fa4b | |||
8181a0664d | |||
c37e94493f | |||
b9e107510d | |||
976ae65529 | |||
0f4c1ccfe3 | |||
9d9413c790 | |||
64c28ae657 | |||
d221625eb3 | |||
1f4c885748 | |||
0545b178af | |||
fe4b6c36c4 | |||
fce3436c88 | |||
03bec6aab2 | |||
3aba91b360 | |||
907933612d | |||
0db546bf82 | |||
b4877a488e | |||
4b3975367a | |||
38c8d96e5a | |||
28110c3e85 | |||
43aa498ff9 | |||
f7e4504764 | |||
4942fa8a38 | |||
7ab148ea58 | |||
0dfeec3260 | |||
eb2317a743 | |||
1a0ef28377 | |||
7c3ad85d75 | |||
7766e1cec1 | |||
158e674f83 | |||
410097480f | |||
f5fadbe4cf | |||
a0550660e7 | |||
bad6c353ed | |||
a814832e48 | |||
a4312f1494 | |||
747032d9a4 | |||
9b2e35b93f | |||
d2751237c1 | |||
ae87160de3 | |||
a90a213cc0 | |||
24c04b8fc0 | |||
d0b022d1c6 | |||
9d9791814a | |||
b85d4b20f8 | |||
331e673589 | |||
bbb93600b7 | |||
c0de54c11a | |||
0d29722443 | |||
1c2a375b6d | |||
b6840a3ed4 | |||
74e994598e | |||
856b6fcd7a | |||
2404fb66f3 | |||
cd6a91e995 | |||
89d4b0ae0b | |||
ade680d9d2 | |||
6d4a43fa0d | |||
d3ad661970 | |||
c9632b05f9 | |||
1e7de43da8 | |||
eff37765ae | |||
a65673847a | |||
930c5e2412 | |||
aff2a78ec3 | |||
f01758503c | |||
e855be4796 | |||
701e10b121 | |||
eadb2057d9 | |||
5ed29ceb47 | |||
725ab13628 | |||
32e691b85b | |||
0108502055 | |||
6c5b32aac2 | |||
f59dd99470 | |||
55c8a98c33 | |||
7bb67391ae | |||
c6a1f310a0 | |||
1d494513a9 | |||
fb79ca4c8e | |||
3cf42db7dc | |||
aba5eee837 | |||
5cd9f34884 | |||
2cabe51956 | |||
cb8e9b7a23 | |||
4eb6b5735e | |||
5d3899959b | |||
ad951ad919 | |||
5ecabc57bf | |||
48a4c1bd26 | |||
1f47c5ba2e | |||
febedb9323 | |||
aed5ea4b2e | |||
4e74ba5bab | |||
03fbb780b2 | |||
9c0b175260 | |||
e62be121e2 | |||
774066e53c | |||
86400f45d6 | |||
ddef2d0bfc | |||
0172aa0b69 | |||
ce991c8887 | |||
92d8d42997 | |||
1c4ef84ec7 | |||
a820ae57c0 | |||
89f913cadc | |||
d14fda2e62 | |||
f680a4a25c | |||
7c461cee2f | |||
47d37b4ce5 | |||
a1cc045837 | |||
72dd556b72 | |||
ff9e1111b3 | |||
7f8cae42ff | |||
5b83d4d944 | |||
f16a68f5bb | |||
6646a21089 | |||
4bf43d884a | |||
46fe6c690b | |||
dd7b1dae5f | |||
2e58353b0e | |||
f65d3d04dc | |||
6102a0301d | |||
39de5b84c2 | |||
5205251f6f | |||
8c48adefa5 | |||
db2801c652 | |||
4418c16967 | |||
8008fd35cb | |||
36ea5b53ad | |||
552d14b1b5 | |||
c404c8b2ae | |||
d129ae2c03 | |||
58341b75f2 | |||
373388c5b8 | |||
8d45aad534 | |||
a783bc9577 | |||
267d374b19 | |||
e67ce7576b | |||
ce770dbea9 | |||
e7a65abd0b | |||
702a6cc7fa | |||
f889543aa5 | |||
98073f5e19 | |||
96c330813f | |||
a6d9c62bcf | |||
8ff34d8518 | |||
e11dd0ecb0 | |||
3b6dfea2d0 | |||
22254db74c | |||
a316c87db6 | |||
fe2fb40565 | |||
cd63fdb510 | |||
1e25f37774 | |||
cdac23211c | |||
e6c00e6215 | |||
fff9d69e3e | |||
4fa7e6113d | |||
16ca71188f | |||
c5c37e79ac | |||
d2f6648bce | |||
5c9c7f8073 | |||
218072b2fe | |||
d4f217a4f5 | |||
40f6f88a64 | |||
fbbb09322a | |||
e7153ce4a1 | |||
b13e7c38c7 | |||
058c95bb2c | |||
9b793ef4b8 | |||
1417497001 | |||
db12e03f64 | |||
dee4866737 | |||
81a6c53c26 | |||
9afd9725d1 | |||
384bc9e816 | |||
452619dbfc | |||
6c6e10e470 | |||
dcdf58e1ab | |||
48b2280f2e | |||
8bedc860ae | |||
cbecdc4a95 | |||
e1001f57c5 | |||
291e704477 | |||
d199e9df99 | |||
2336767059 | |||
63af94383b | |||
05b37669e3 | |||
ea9768c6ab | |||
38353dbc29 | |||
ef4a8e1989 | |||
acc9a9cb48 | |||
0335b89a12 | |||
0a6b0cbec7 | |||
df2310d590 | |||
70b5c57b50 | |||
c28ac38652 | |||
52133fde30 | |||
098cd2051e | |||
691a7d7ff7 | |||
c7c2785ad8 | |||
4c1a7fc910 |
@@ -22,7 +22,7 @@ you might specifically be interested in these files (elaborated further in #key-
|
||||
- my way of deploying dotfiles/configuring programs per-user:
|
||||
- [modules/fs/](./modules/fs/default.nix)
|
||||
- [modules/programs/](./modules/programs/default.nix)
|
||||
- [modules/users.nix](./modules/users.nix)
|
||||
- [modules/users/](./modules/users/default.nix)
|
||||
|
||||
[nixpkgs]: https://github.com/NixOS/nixpkgs
|
||||
[sops]: https://github.com/Mic92/sops-nix
|
||||
@@ -109,9 +109,10 @@ i.e. you might find value in using these in your own config:
|
||||
- `sane.programs.firefox.sandbox.whitelistWayland = true; # allow it to render a wayland window`
|
||||
- `sane.programs.firefox.sandbox.extraHomePaths = [ "Downloads" ]; # allow it read/write access to ~/Downloads`
|
||||
- integrated with `fs` and `persist` modules so that programs' config files and persisted data stores are linked into the sandbox w/o any extra involvement.
|
||||
- `modules/users.nix`
|
||||
- `modules/users/`
|
||||
- convenience layer atop the above modules so that you can just write
|
||||
`fs.".config/git"` instead of `fs."/home/colin/.config/git"`
|
||||
- per-user services managed by [s6-rc](https://www.skarnet.org/software/s6-rc/)
|
||||
|
||||
some things in here could easily find broader use. if you would find benefit in
|
||||
them being factored out of my config, message me and we could work to make that happen.
|
||||
|
21
TODO.md
21
TODO.md
@@ -1,9 +1,25 @@
|
||||
## BUGS
|
||||
- moby: my mobile ISP is adding spoofed AAAA records that break things like wireguard
|
||||
- it only does this when i use their DNS resolvers though: if i run my own recursive resolver, they won't mess with it.
|
||||
- moby: mpv uosc always starts at 40% volume
|
||||
- is this just mpv remembering its last-played volume?
|
||||
- moby: rofi crashes sporadically
|
||||
- mpv: no way to exit fullscreen video on moby
|
||||
- uosc hides controls on FS, and touch doesn't support unhiding
|
||||
- i accidentally create sub-splits in sway all the time
|
||||
- especially on moby => unusable
|
||||
- like toplevel is split L/R, and then the L is a tabbed view and the R is a tabbed view
|
||||
- Signal restart loop drains battery
|
||||
- decrease s6 restart time?
|
||||
- `ssh` access doesn't grant same linux capabilities as login
|
||||
- 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`
|
||||
- sysvol (volume overlay): when casting with `blast`, sysvol doesn't react to volume changes
|
||||
|
||||
## 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
|
||||
@@ -28,6 +44,7 @@
|
||||
|
||||
## IMPROVEMENTS:
|
||||
### security/resilience
|
||||
- add FTPS support for WAN users of uninsane.org (and possibly require it?)
|
||||
- validate duplicity backups!
|
||||
- encrypt more ~ dirs (~/archives, ~/records, ..?)
|
||||
- best to do this after i know for sure i have good backups
|
||||
@@ -55,6 +72,8 @@
|
||||
- integrate `nix check` into Gitea actions?
|
||||
|
||||
### user experience
|
||||
- rofi: sort items case-insensitively
|
||||
- give `mpv` better `nice`ness?
|
||||
- xdg-desktop-portal shouldn't kill children on exit
|
||||
- *maybe* a job for `setsid -f`?
|
||||
- replace starship prompt with something more efficient
|
||||
@@ -79,6 +98,8 @@
|
||||
- numberlink (generic name for Flow Free). not packaged in Nix
|
||||
- Neverball (https://neverball.org/screenshots.php). nix: as `neverball`
|
||||
- blurble (https://linuxphoneapps.org/games/app.drey.blurble/). nix: not as of 2024-02-05
|
||||
- Trivia Quiz (https://linuxphoneapps.org/games/io.github.nokse22.trivia-quiz/)
|
||||
- sane-sync-music: remove empty dirs
|
||||
|
||||
#### moby
|
||||
- fix cpuidle (gets better power consumption): <https://xnux.eu/log/077.html>
|
||||
|
80
flake.lock
generated
80
flake.lock
generated
@@ -24,11 +24,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1701473968,
|
||||
"narHash": "sha256-YcVE5emp1qQ8ieHUnxt1wCZCC3ZfAS+SRRWZ2TMda7E=",
|
||||
"lastModified": 1712014858,
|
||||
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "34fed993f1674c8d06d58b37ce1e0fe5eebcb9f5",
|
||||
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -61,11 +61,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710184940,
|
||||
"narHash": "sha256-FzYm4td3FJfzOAuEkCXt3KdUgZuA072OAQXqIq+IAMo=",
|
||||
"lastModified": 1715515815,
|
||||
"narHash": "sha256-yaLScMHNFCH6SbB0HSA/8DWDgK0PyOhCXoFTdHlWkhk=",
|
||||
"owner": "nix-community",
|
||||
"repo": "lib-aggregate",
|
||||
"rev": "45b75bf534592c0c1c881a1c447f7fdb37a87eaf",
|
||||
"rev": "09883ca828e8cfaacdb09e29190a7b84ad1d9925",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -99,11 +99,11 @@
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1705242886,
|
||||
"narHash": "sha256-TLj334vRwFtSym3m+NnKcNCnKKPNoTC/TDZL40vmOso=",
|
||||
"lastModified": 1715248291,
|
||||
"narHash": "sha256-npC9Swu4VIlRIiEP0XFGoIukd6vOufS/M3PdHk6rQpc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-eval-jobs",
|
||||
"rev": "6b03a93296faf174b97546fd573c8b379f523a8d",
|
||||
"rev": "63154bdfb22091041b307d17863bdc0e01a32a00",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -121,11 +121,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1701208414,
|
||||
"narHash": "sha256-xrQ0FyhwTZK6BwKhahIkUVZhMNk21IEI1nUcWSONtpo=",
|
||||
"lastModified": 1703863825,
|
||||
"narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "93e39cc1a087d65bcf7a132e75a650c44dd2b734",
|
||||
"rev": "5163432afc817cf8bd1f031418d1869e4c9d5547",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -136,11 +136,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1703134684,
|
||||
"narHash": "sha256-SQmng1EnBFLzS7WSRyPM9HgmZP2kLJcPAz+Ug/nug6o=",
|
||||
"lastModified": 1715037484,
|
||||
"narHash": "sha256-OUt8xQFmBU96Hmm4T9tOWTu4oCswCzoVl+pxSq/kiFc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d6863cbcbbb80e71cecfc03356db1cda38919523",
|
||||
"rev": "ad7efee13e0d216bf29992311536fce1d3eefbef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -152,11 +152,11 @@
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1710031547,
|
||||
"narHash": "sha256-pkUg3hOKuGWMGF9WEMPPN/G4pqqdbNGJQ54yhyQYDVY=",
|
||||
"lastModified": 1715474941,
|
||||
"narHash": "sha256-CNCqCGOHdxuiVnVkhTpp2WcqSSmSfeQjubhDOcgwGjU=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "630ebdc047ca96d8126e16bb664c7730dc52f6e6",
|
||||
"rev": "58e03b95f65dfdca21979a081aa62db0eed6b1d8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -167,11 +167,11 @@
|
||||
},
|
||||
"nixpkgs-next-unpatched": {
|
||||
"locked": {
|
||||
"lastModified": 1710337169,
|
||||
"narHash": "sha256-u2/74bhQuWykUZDWUIhHd6IpZiaQ0hSpTBbx0y9opkE=",
|
||||
"lastModified": 1715601680,
|
||||
"narHash": "sha256-Gmz6U8NMZVVnP6AGX4sMl4X6RcQBASPl/2Gj9R5k1Pk=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4ee0840ba2ecc50458ab1677d108afcd691f4815",
|
||||
"rev": "eda36d7cf3391ad06097009b08822fb74acd5e00",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -183,11 +183,11 @@
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1710033658,
|
||||
"narHash": "sha256-yiZiVKP5Ya813iYLho2+CcFuuHpaqKc/CoxOlANKcqM=",
|
||||
"lastModified": 1715458492,
|
||||
"narHash": "sha256-q0OFeZqKQaik2U8wwGDsELEkgoZMK7gvfF6tTXkpsqE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b17375d3bb7c79ffc52f3538028b2ec06eb79ef8",
|
||||
"rev": "8e47858badee5594292921c2668c11004c3b0142",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -199,11 +199,11 @@
|
||||
},
|
||||
"nixpkgs-unpatched": {
|
||||
"locked": {
|
||||
"lastModified": 1710339354,
|
||||
"narHash": "sha256-+P5ccUPiLouHexb8aJrUOVOIja9qm+fG57pgAu7uIRs=",
|
||||
"lastModified": 1715616096,
|
||||
"narHash": "sha256-rxh2XECb5hRzgNR4Xqj3aAjg6821LmNTVRfF6sUW6fI=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2dbc8f62d8af7a1ab962e4b20d12b25ddcb86ced",
|
||||
"rev": "0a949cf2618e8eab83aa008f1f8e03db137ed36c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -223,11 +223,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710317949,
|
||||
"narHash": "sha256-bwReMiWPA2wYBvKEMhO8pJcu+o+7ocy5hGkSoawTHu0=",
|
||||
"lastModified": 1715609745,
|
||||
"narHash": "sha256-z2lQ7G1AxljvYeqrHWjc1ctOI4QZP06vPtvLYJWfZSc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs-wayland",
|
||||
"rev": "771cb198c281db6918829651f194bf4db32e342d",
|
||||
"rev": "ed18785b8816fa878bdd9df7f2e8722695401ef8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -254,11 +254,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710195194,
|
||||
"narHash": "sha256-KFxCJp0T6TJOz1IOKlpRdpsCr9xsvlVuWY/VCiAFnTE=",
|
||||
"lastModified": 1715482972,
|
||||
"narHash": "sha256-y1uMzXNlrVOWYj1YNcsGYLm4TOC2aJrwoUY1NjQs9fM=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "e52d8117b330f690382f1d16d81ae43daeb4b880",
|
||||
"rev": "b6cb5de2ce57acb10ecdaaf9bbd62a5ff24fa02e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -291,11 +291,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1702979157,
|
||||
"narHash": "sha256-RnFBbLbpqtn4AoJGXKevQMCGhra4h6G2MPcuTSZZQ+g=",
|
||||
"lastModified": 1711963903,
|
||||
"narHash": "sha256-N3QDhoaX+paWXHbEXZapqd1r95mdshxToGowtjtYkGI=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "2961375283668d867e64129c22af532de8e77734",
|
||||
"rev": "49dc4a92b02b8e68798abd99184f228243b6e3ac",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -311,11 +311,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1707981105,
|
||||
"narHash": "sha256-YCU1eNslBHabjP+OCY+BxPycEFO9SRUts10MrN9QORE=",
|
||||
"lastModified": 1713198740,
|
||||
"narHash": "sha256-8SUaqMJdAkMOI9zhvlToL7eCr5Sl+2o2pDQ7nq+HoJU=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "bb10cd8853d05191e4d62947d93687c462e92c30",
|
||||
"revCount": 235,
|
||||
"rev": "af8420d1c256d990b5e24de14ad8592a5d85bf77",
|
||||
"revCount": 239,
|
||||
"type": "git",
|
||||
"url": "https://git.uninsane.org/colin/uninsane"
|
||||
},
|
||||
|
33
flake.nix
33
flake.nix
@@ -108,7 +108,7 @@
|
||||
nixpkgs' = patchNixpkgs "master" nixpkgs-unpatched;
|
||||
nixpkgsCompiledBy = system: nixpkgs'.legacyPackages."${system}";
|
||||
|
||||
evalHost = { name, local, target, light ? false, nixpkgs ? nixpkgs' }: nixpkgs.lib.nixosSystem {
|
||||
evalHost = { name, local, target, variant ? null, nixpkgs ? nixpkgs' }: nixpkgs.lib.nixosSystem {
|
||||
system = target;
|
||||
modules = [
|
||||
{
|
||||
@@ -120,8 +120,11 @@
|
||||
# so avoid specifying hostPlatform.system on non-cross builds, so i can use upstream caches.
|
||||
nixpkgs.hostPlatform.system = target;
|
||||
})
|
||||
(optionalAttrs light {
|
||||
sane.enableSlowPrograms = false;
|
||||
(optionalAttrs (variant == "light") {
|
||||
sane.maxBuildCost = 2;
|
||||
})
|
||||
(optionalAttrs (variant == "min") {
|
||||
sane.maxBuildCost = 0;
|
||||
})
|
||||
(import ./hosts/instantiate.nix { hostName = name; })
|
||||
self.nixosModules.default
|
||||
@@ -139,11 +142,13 @@
|
||||
hosts = {
|
||||
servo = { name = "servo"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
desko = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
desko-light = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; light = true; };
|
||||
desko-light = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; variant = "light"; };
|
||||
lappy = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
lappy-light = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; light = true; };
|
||||
lappy-light = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; variant = "light"; };
|
||||
lappy-min = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; variant = "min"; };
|
||||
moby = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; };
|
||||
moby-light = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; light = true; };
|
||||
moby-light = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; variant = "light"; };
|
||||
moby-min = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; variant = "min"; };
|
||||
rescue = { name = "rescue"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
};
|
||||
hostsNext = mapAttrs' (h: v: {
|
||||
@@ -408,14 +413,17 @@
|
||||
desko-light = deployApp "desko-light" "desko" "switch";
|
||||
lappy = deployApp "lappy" "lappy" "switch";
|
||||
lappy-light = deployApp "lappy-light" "lappy" "switch";
|
||||
lappy-min = deployApp "lappy-min" "lappy" "switch";
|
||||
moby = deployApp "moby" "moby" "switch";
|
||||
moby-light = deployApp "moby-light" "moby" "switch";
|
||||
moby-min = deployApp "moby-min" "moby" "switch";
|
||||
moby-test = deployApp "moby" "moby" "test";
|
||||
servo = deployApp "servo" "servo" "switch";
|
||||
|
||||
# like `nixos-rebuild --flake . switch`
|
||||
self = deployApp "$(hostname)" "" "switch";
|
||||
self-light = deployApp "$(hostname)-light" "" "switch";
|
||||
self = deployApp "$(hostname)" "" "switch";
|
||||
self-light = deployApp "$(hostname)-light" "" "switch";
|
||||
self-min = deployApp "$(hostname)-min" "" "switch";
|
||||
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "deploy-all" ''
|
||||
@@ -431,12 +439,16 @@
|
||||
desko-light = deployApp "desko-light" "desko" null;
|
||||
lappy = deployApp "lappy" "lappy" null;
|
||||
lappy-light = deployApp "lappy-light" "lappy" null;
|
||||
lappy-min = deployApp "lappy-min" "lappy" null;
|
||||
moby = deployApp "moby" "moby" null;
|
||||
moby-light = deployApp "moby-light" "moby" null;
|
||||
moby-min = deployApp "moby-min" "moby" null;
|
||||
servo = deployApp "servo" "servo" null;
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "predeploy-all" ''
|
||||
# copy the -light variants first; this might be run while waiting on a full build. or the full build failed.
|
||||
# copy the -min/-light variants first; this might be run while waiting on a full build. or the full build failed.
|
||||
nix run '.#preDeploy.moby-min' -- "$@"
|
||||
nix run '.#preDeploy.lappy-min' -- "$@"
|
||||
nix run '.#preDeploy.moby-light' -- "$@"
|
||||
nix run '.#preDeploy.lappy-light' -- "$@"
|
||||
nix run '.#preDeploy.desko-light' -- "$@"
|
||||
@@ -541,6 +553,9 @@
|
||||
''
|
||||
# build minimally-usable hosts first, then their full image.
|
||||
# this gives me a minimal image i can deploy or copy over, early.
|
||||
${checkHost "lappy-min"}
|
||||
${checkHost "moby-min"}
|
||||
|
||||
${checkHost "desko-light"}
|
||||
${checkHost "moby-light"}
|
||||
${checkHost "lappy-light"}
|
||||
|
@@ -28,6 +28,7 @@
|
||||
sane.nixcache.substituters.desko = false;
|
||||
sane.nixcache.remote-builders.desko = false;
|
||||
|
||||
sane.programs.cups.enableFor.user.colin = true;
|
||||
sane.programs.sway.enableFor.user.colin = true;
|
||||
sane.programs.iphoneUtils.enableFor.user.colin = true;
|
||||
sane.programs.steam.enableFor.user.colin = true;
|
||||
|
@@ -14,10 +14,12 @@
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||
|
||||
sane.programs.cups.enableFor.user.colin = true;
|
||||
sane.programs.stepmania.enableFor.user.colin = true;
|
||||
sane.programs.sway.enableFor.user.colin = true;
|
||||
|
||||
sane.programs."gnome.geary".config.autostart = true;
|
||||
sane.programs.signal-desktop.config.autostart = true;
|
||||
sane.programs.stepmania.enableFor.user.colin = true;
|
||||
|
||||
sops.secrets.colin-passwd.neededForUsers = true;
|
||||
|
||||
|
@@ -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
|
||||
'';
|
||||
}
|
||||
|
@@ -62,18 +62,8 @@
|
||||
# HACK/TODO: make `programs.P.env.VAR` behave according to `mime.priority`
|
||||
sane.programs.firefox.env = lib.mkForce {};
|
||||
sane.programs.epiphany.env.BROWSER = "epiphany";
|
||||
|
||||
# note the .conf.d approach: using ~/.config/pipewire/pipewire.conf directly breaks all audio,
|
||||
# presumably because that deletes the defaults entirely whereas the .conf.d approach selectively overrides defaults
|
||||
sane.user.fs.".config/pipewire/pipewire.conf.d/10-fix-dino-mic-cutout.conf".symlink.text = ''
|
||||
# config docs: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-PipeWire#properties>
|
||||
# useful to run `pw-top` to see that these settings are actually having effect,
|
||||
# and `pw-metadata` to see if any settings conflict (e.g. max-quantum < min-quantum)
|
||||
#
|
||||
# restart pipewire after editing these files:
|
||||
# - `systemctl --user restart pipewire`
|
||||
# - pipewire users will likely stop outputting audio until they are also restarted
|
||||
#
|
||||
sane.programs.pipewire.config = {
|
||||
# tune so Dino doesn't drop audio
|
||||
# there's seemingly two buffers for the mic (see: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#pipewire-buffering-explained>)
|
||||
# 1. Pipewire buffering out of the driver and into its own member.
|
||||
# 2. Pipewire buffering into Dino.
|
||||
@@ -84,11 +74,9 @@
|
||||
# `pw-metadata -n settings 0 clock.force-quantum 1024` reduces to about 1 error per second.
|
||||
# `pw-metadata -n settings 0 clock.force-quantum 2048` reduces to 1 error every < 10s.
|
||||
# pipewire default config includes `clock.power-of-two-quantum = true`
|
||||
context.properties = {
|
||||
default.clock.min-quantum = 2048
|
||||
default.clock.max-quantum = 8192
|
||||
}
|
||||
'';
|
||||
min-quantum = 2048;
|
||||
max-quantum = 8192;
|
||||
};
|
||||
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
# /boot space is at a premium. default was 20.
|
||||
@@ -128,14 +116,6 @@
|
||||
# enable rotation sensor
|
||||
hardware.sensor.iio.enable = true;
|
||||
|
||||
# TODO: move elsewhere...
|
||||
systemd.services.ModemManager.serviceConfig = {
|
||||
# N.B.: the extra "" in ExecStart serves to force upstream ExecStart to be ignored
|
||||
ExecStart = [ "" "${pkgs.modemmanager}/bin/ModemManager --debug" ];
|
||||
# --debug sets DEBUG level logging: so reset
|
||||
ExecStartPost = [ "${pkgs.modemmanager}/bin/mmcli --set-logging=INFO" ];
|
||||
};
|
||||
|
||||
services.udev.extraRules = let
|
||||
chmod = "${pkgs.coreutils}/bin/chmod";
|
||||
chown = "${pkgs.coreutils}/bin/chown";
|
||||
|
@@ -64,6 +64,5 @@
|
||||
"dialout" # TODO: figure out if dialout is required. that's for /dev/ttyUSB1, but geoclue probably doesn't read that?
|
||||
];
|
||||
|
||||
sane.services.eg25-control.enable = true;
|
||||
sane.programs.where-am-i.enableFor.user.colin = true;
|
||||
}
|
||||
|
@@ -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 = [
|
||||
|
@@ -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"
|
||||
|
@@ -24,7 +24,7 @@ lib.mkIf false
|
||||
# services.calibre-web.options.calibreLibrary = svc-dir;
|
||||
|
||||
services.nginx.virtualHosts."calibre.uninsane.org" = {
|
||||
addSSL = true;
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://${ip}:${builtins.toString port}";
|
||||
|
@@ -24,50 +24,57 @@
|
||||
# that is NOT the case when the STUN server and client A are on the same LAN
|
||||
# even if client A contacts the STUN server via its WAN address with port reflection enabled.
|
||||
# hence, there's no obvious way to put the STUN server on the same LAN as either client and expect the rest to work.
|
||||
# - there an old version which *half worked*, which is:
|
||||
# - run the turn server in the root namespace.
|
||||
# - bind the turn server to the veth connecting it to the VPN namespace (so it sends outgoing traffic to the right place).
|
||||
# - NAT the turn port range from VPN into root namespace (so it receives incomming traffic).
|
||||
# - this approach would fail the prosody conversations.im check, but i didn't notice *obvious* call routing errors.
|
||||
{ lib, ... }:
|
||||
let
|
||||
# TODO: this range could be larger, but right now that's costly because each element is its own UPnP forward
|
||||
# TURN port range (inclusive)
|
||||
turnPortLow = 49152;
|
||||
turnPortHigh = 49167;
|
||||
# TURN port range (inclusive).
|
||||
# default coturn behavior is to use the upper quarter of all ports. i.e. 49152 - 65535.
|
||||
# i believe TURN allocations expire after either 5 or 10 minutes of inactivity.
|
||||
turnPortLow = 49152; # 49152 = 0xc000
|
||||
turnPortHigh = turnPortLow + 256;
|
||||
turnPortRange = lib.range turnPortLow turnPortHigh;
|
||||
in
|
||||
{
|
||||
sane.ports.ports = lib.mkMerge ([
|
||||
{
|
||||
"3478" = {
|
||||
# this is the "control" port.
|
||||
# i.e. no client data is forwarded through it, but it's where clients request tunnels.
|
||||
protocol = [ "tcp" "udp" ];
|
||||
# visibleTo.lan = true;
|
||||
# visibleTo.wan = true;
|
||||
visibleTo.ovpn = true;
|
||||
description = "colin-stun-turn";
|
||||
};
|
||||
"5349" = {
|
||||
# the other port 3478 also supports TLS/DTLS, but presumably clients wanting TLS will default 5349
|
||||
protocol = [ "tcp" ];
|
||||
# visibleTo.lan = true;
|
||||
# visibleTo.wan = true;
|
||||
visibleTo.ovpn = true;
|
||||
description = "colin-stun-turn-over-tls";
|
||||
};
|
||||
}
|
||||
] ++ (builtins.map
|
||||
(port: {
|
||||
"${builtins.toString port}" = let
|
||||
count = port - turnPortLow + 1;
|
||||
numPorts = turnPortHigh - turnPortLow + 1;
|
||||
in {
|
||||
protocol = [ "tcp" "udp" ];
|
||||
# visibleTo.lan = true;
|
||||
# visibleTo.wan = true;
|
||||
visibleTo.ovpn = true;
|
||||
description = "colin-turn-${builtins.toString count}-of-${builtins.toString numPorts}";
|
||||
};
|
||||
})
|
||||
turnPortRange
|
||||
));
|
||||
# the port definitions are only needed if running in the root net namespace
|
||||
# sane.ports.ports = lib.mkMerge ([
|
||||
# {
|
||||
# "3478" = {
|
||||
# # this is the "control" port.
|
||||
# # i.e. no client data is forwarded through it, but it's where clients request tunnels.
|
||||
# protocol = [ "tcp" "udp" ];
|
||||
# # visibleTo.lan = true;
|
||||
# # visibleTo.wan = true;
|
||||
# visibleTo.ovpn = true; # forward traffic from the VPN to the root NS
|
||||
# description = "colin-stun-turn";
|
||||
# };
|
||||
# "5349" = {
|
||||
# # the other port 3478 also supports TLS/DTLS, but presumably clients wanting TLS will default 5349
|
||||
# protocol = [ "tcp" ];
|
||||
# # visibleTo.lan = true;
|
||||
# # visibleTo.wan = true;
|
||||
# visibleTo.ovpn = true;
|
||||
# description = "colin-stun-turn-over-tls";
|
||||
# };
|
||||
# }
|
||||
# ] ++ (builtins.map
|
||||
# (port: {
|
||||
# "${builtins.toString port}" = let
|
||||
# count = port - turnPortLow + 1;
|
||||
# numPorts = turnPortHigh - turnPortLow + 1;
|
||||
# in {
|
||||
# protocol = [ "tcp" "udp" ];
|
||||
# # visibleTo.lan = true;
|
||||
# # visibleTo.wan = true;
|
||||
# visibleTo.ovpn = true;
|
||||
# description = "colin-turn-${builtins.toString count}-of-${builtins.toString numPorts}";
|
||||
# };
|
||||
# })
|
||||
# turnPortRange
|
||||
# ));
|
||||
|
||||
services.nginx.virtualHosts."turn.uninsane.org" = {
|
||||
# allow ACME to procure a cert via nginx for this domain
|
||||
@@ -113,12 +120,15 @@ in
|
||||
"verbose"
|
||||
# "Verbose" #< even MORE verbosity than "verbose"
|
||||
# "no-multicast-peers" # disables sending to IPv4 broadcast addresses (e.g. 224.0.0.0/3)
|
||||
"listening-ip=10.0.1.5"
|
||||
# "listening-ip=10.0.1.5" "external-ip=185.157.162.178" #< 2024/04/25: works, if running in root namespace
|
||||
"listening-ip=185.157.162.178" "external-ip=185.157.162.178"
|
||||
|
||||
# old attempts:
|
||||
# "external-ip=185.157.162.178/10.0.1.5"
|
||||
"external-ip=185.157.162.178"
|
||||
# "listening-ip=10.78.79.51" # can be specified multiple times; omit for *
|
||||
# "external-ip=97.113.128.229/10.78.79.51"
|
||||
# "external-ip=97.113.128.229"
|
||||
# "mobility" # "mobility with ICE (MICE) specs support" (?)
|
||||
];
|
||||
systemd.services.coturn.serviceConfig.NetworkNamespacePath = "/run/netns/ovpns";
|
||||
}
|
||||
|
@@ -1,6 +1,22 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ps.pyln-client ])"
|
||||
|
||||
"""
|
||||
clightning-sane: helper to perform common Lightning node admin operations:
|
||||
- view channel balances
|
||||
- rebalance channels
|
||||
|
||||
COMMON OPERATIONS:
|
||||
- view channel balances: `clightning-sane status`
|
||||
- rebalance channels to improve routability (without paying any fees): `clightning-sane autobalance`
|
||||
|
||||
FULL OPERATION:
|
||||
- `clightning-sane status --full`
|
||||
- `P$`: represents how many msats i've captured in fees from this channel.
|
||||
- `COST`: rough measure of how much it's "costing" me to let my channel partner hold funds on his side of the channel.
|
||||
this is based on the notion that i only capture fees from outbound transactions, and so the channel partner holding all liquidity means i can't capture fees on that liquidity.
|
||||
"""
|
||||
|
||||
# pyln-client docs: <https://github.com/ElementsProject/lightning/tree/master/contrib/pyln-client>
|
||||
# terminology:
|
||||
# - "scid": "Short Channel ID", e.g. 123456x7890x0
|
||||
@@ -726,7 +742,7 @@ def main():
|
||||
logging.basicConfig()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
parser = argparse.ArgumentParser(description="rebalance lightning channel balances")
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--verbose", action="store_true", help="more logging")
|
||||
parser.add_argument("--min-msat", default="999", help="min transaction size")
|
||||
parser.add_argument("--max-msat", default="1000000", help="max transaction size")
|
||||
|
@@ -2,7 +2,7 @@
|
||||
{
|
||||
imports = [
|
||||
./nfs.nix
|
||||
./sftpgo.nix
|
||||
./sftpgo
|
||||
];
|
||||
|
||||
users.groups.export = {};
|
||||
|
@@ -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"
|
||||
|
@@ -1,186 +0,0 @@
|
||||
# docs:
|
||||
# - <https://github.com/drakkan/sftpgo>
|
||||
# - config options: <https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md>
|
||||
# - config defaults: <https://github.com/drakkan/sftpgo/blob/main/sftpgo.json>
|
||||
# - nixos options: <repo:nixos/nixpkgs:nixos/modules/services/web-apps/sftpgo.nix>
|
||||
# - 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 {
|
||||
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.
|
||||
sane.ports.ports = {
|
||||
"21" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
description = "colin-FTP server";
|
||||
};
|
||||
} // (sane-lib.mapToAttrs
|
||||
(port: {
|
||||
name = builtins.toString port;
|
||||
value = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
description = "colin-FTP server data port range";
|
||||
};
|
||||
})
|
||||
(lib.range 50000 50100)
|
||||
);
|
||||
|
||||
services.sftpgo = {
|
||||
enable = true;
|
||||
group = "export";
|
||||
settings = {
|
||||
ftpd = {
|
||||
bindings = [
|
||||
{
|
||||
# binding this means any wireguard client can connect
|
||||
address = "10.0.10.5";
|
||||
port = 21;
|
||||
debug = true;
|
||||
}
|
||||
{
|
||||
# binding this means any LAN client can connect
|
||||
address = "10.78.79.51";
|
||||
port = 21;
|
||||
debug = true;
|
||||
}
|
||||
];
|
||||
|
||||
# active mode is susceptible to "bounce attacks", without much benefit over passive mode
|
||||
disable_active_mode = true;
|
||||
hash_support = true;
|
||||
passive_port_range = {
|
||||
start = 50000;
|
||||
end = 50100;
|
||||
};
|
||||
|
||||
banner = ''
|
||||
Welcome, friends, to Colin's read-only FTP server! Also available via NFS on the same host.
|
||||
Username: "anonymous"
|
||||
Password: "anonymous"
|
||||
CONFIGURE YOUR CLIENT FOR "PASSIVE" mode, e.g. `ftp --passive uninsane.org`
|
||||
Please let me know if anything's broken or not as it should be. Otherwise, browse and DL freely :)
|
||||
'';
|
||||
|
||||
};
|
||||
data_provider = {
|
||||
driver = "memory";
|
||||
external_auth_hook = "${authProgram}";
|
||||
# track_quota:
|
||||
# - 0: disable quota tracking
|
||||
# - 1: quota is updated on every upload/delete, even if user has no quota restriction
|
||||
# - 2: quota is updated on every upload/delete, but only if user/folder has a quota restriction (default, i think)
|
||||
# track_quota = 2;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
users.users.sftpgo.extraGroups = [ "export" ];
|
||||
|
||||
systemd.services.sftpgo = {
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
ReadOnlyPaths = [ "/var/export" ];
|
||||
ReadWritePaths = [ "/var/export/playground" ];
|
||||
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
};
|
||||
}
|
167
hosts/by-name/servo/services/export/sftpgo/default.nix
Normal file
167
hosts/by-name/servo/services/export/sftpgo/default.nix
Normal file
@@ -0,0 +1,167 @@
|
||||
# docs:
|
||||
# - <https://github.com/drakkan/sftpgo>
|
||||
# - config options: <https://github.com/drakkan/sftpgo/blob/main/docs/full-configuration.md>
|
||||
# - config defaults: <https://github.com/drakkan/sftpgo/blob/main/sftpgo.json>
|
||||
# - nixos options: <repo:nixos/nixpkgs:nixos/modules/services/web-apps/sftpgo.nix>
|
||||
# - nixos example: <repo:nixos/nixpkgs:nixos/tests/sftpgo.nix>
|
||||
#
|
||||
# sftpgo is a FTP server that also supports WebDAV, SFTP, and web clients.
|
||||
|
||||
{ config, lib, pkgs, sane-lib, ... }:
|
||||
let
|
||||
external_auth_hook = pkgs.static-nix-shell.mkPython3Bin {
|
||||
pname = "external_auth_hook";
|
||||
srcRoot = ./.;
|
||||
};
|
||||
# Client initiates a FTP "control connection" on port 21.
|
||||
# - this handles the client -> server commands, and the server -> client status, but not the actual data
|
||||
# - file data, directory listings, etc need to be transferred on an ephemeral "data port".
|
||||
# - 50000-50100 is a common port range for this.
|
||||
# 50000 is used by soulseek.
|
||||
passiveStart = 50050;
|
||||
passiveEnd = 50070;
|
||||
in
|
||||
{
|
||||
sane.ports.ports = {
|
||||
"21" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
# visibleTo.wan = true;
|
||||
description = "colin-FTP server";
|
||||
};
|
||||
"990" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-FTPS server";
|
||||
};
|
||||
} // (sane-lib.mapToAttrs
|
||||
(port: {
|
||||
name = builtins.toString port;
|
||||
value = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-FTP server data port range";
|
||||
};
|
||||
})
|
||||
(lib.range passiveStart passiveEnd)
|
||||
);
|
||||
|
||||
# use nginx/acme to produce a cert for FTPS
|
||||
services.nginx.virtualHosts."ftp.uninsane.org" = {
|
||||
addSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
sane.dns.zones."uninsane.org".inet.CNAME."ftp" = "native";
|
||||
|
||||
services.sftpgo = {
|
||||
enable = true;
|
||||
group = "export";
|
||||
|
||||
package = lib.warnIf (lib.versionOlder "2.5.6" pkgs.sftpgo.version) "sftpgo update: safe to use nixpkgs' sftpgo but keep my own `patches`" pkgs.buildGoModule {
|
||||
inherit (pkgs.sftpgo) name ldflags nativeBuildInputs doCheck subPackages postInstall passthru meta;
|
||||
version = "2.5.6-unstable-2024-04-18";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
# need to use > 2.5.6 for sftpgo_safe_fileinfo.patch to apply
|
||||
owner = "drakkan";
|
||||
repo = "sftpgo";
|
||||
rev = "950cf67e4c03a12c7e439802cabbb0b42d4ee5f5";
|
||||
hash = "sha256-UfiFd9NK3DdZ1J+FPGZrM7r2mo9xlKi0dsSlLEinYXM=";
|
||||
};
|
||||
vendorHash = "sha256-n1/9A2em3BCtFX+132ualh4NQwkwewMxYIMOphJEamg=";
|
||||
patches = (pkgs.sftpgo.patches or []) ++ [
|
||||
# fix for compatibility with kodi:
|
||||
# ftp LIST operation returns entries over-the-wire like:
|
||||
# - dgrwxrwxr-x 1 ftp ftp 9 Apr 9 15:05 Videos
|
||||
# however not all clients understand all mode bits (like that `g`, indicating SGID / group sticky bit).
|
||||
# instead, only send mode bits which are well-understood.
|
||||
# the full set of bits, from which i filter, is found here: <https://pkg.go.dev/io/fs#FileMode>
|
||||
./safe_fileinfo.patch
|
||||
];
|
||||
};
|
||||
|
||||
settings = {
|
||||
ftpd = {
|
||||
bindings = [
|
||||
{
|
||||
# binding this means any wireguard client can connect
|
||||
address = "10.0.10.5";
|
||||
port = 21;
|
||||
debug = true;
|
||||
}
|
||||
{
|
||||
# binding this means any LAN client can connect (also WAN traffic forwarded from the gateway)
|
||||
address = "10.78.79.51";
|
||||
port = 21;
|
||||
debug = true;
|
||||
}
|
||||
{
|
||||
# binding this means any wireguard client can connect
|
||||
address = "10.0.10.5";
|
||||
port = 990;
|
||||
debug = true;
|
||||
tls_mode = 2; # 2 = "implicit FTPS": client negotiates TLS before any FTP command.
|
||||
}
|
||||
{
|
||||
# binding this means any LAN client can connect (also WAN traffic forwarded from the gateway)
|
||||
address = "10.78.79.51";
|
||||
port = 990;
|
||||
debug = true;
|
||||
tls_mode = 2; # 2 = "implicit FTPS": client negotiates TLS before any FTP command.
|
||||
}
|
||||
];
|
||||
|
||||
# active mode is susceptible to "bounce attacks", without much benefit over passive mode
|
||||
disable_active_mode = true;
|
||||
hash_support = true;
|
||||
passive_port_range = {
|
||||
start = passiveStart;
|
||||
end = passiveEnd;
|
||||
};
|
||||
|
||||
certificate_file = "/var/lib/acme/ftp.uninsane.org/full.pem";
|
||||
certificate_key_file = "/var/lib/acme/ftp.uninsane.org/key.pem";
|
||||
|
||||
banner = ''
|
||||
Welcome, friends, to Colin's FTP server! Also available via NFS on the same host, but LAN-only.
|
||||
|
||||
Read-only access (LAN-restricted):
|
||||
Username: "anonymous"
|
||||
Password: "anonymous"
|
||||
|
||||
CONFIGURE YOUR CLIENT FOR "PASSIVE" MODE, e.g. `ftp --passive ftp.uninsane.org`.
|
||||
Please let me know if anything's broken or not as it should be. Otherwise, browse and transfer freely :)
|
||||
'';
|
||||
|
||||
};
|
||||
data_provider = {
|
||||
driver = "memory";
|
||||
external_auth_hook = "${external_auth_hook}/bin/external_auth_hook";
|
||||
# track_quota:
|
||||
# - 0: disable quota tracking
|
||||
# - 1: quota is updated on every upload/delete, even if user has no quota restriction
|
||||
# - 2: quota is updated on every upload/delete, but only if user/folder has a quota restriction (default, i think)
|
||||
# track_quota = 2;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
users.users.sftpgo.extraGroups = [
|
||||
"export"
|
||||
"media"
|
||||
"nginx" # to access certs
|
||||
];
|
||||
|
||||
systemd.services.sftpgo = {
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
ReadWritePaths = [ "/var/export" ];
|
||||
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
UMask = lib.mkForce "0002";
|
||||
};
|
||||
};
|
||||
}
|
157
hosts/by-name/servo/services/export/sftpgo/external_auth_hook
Executable file
157
hosts/by-name/servo/services/export/sftpgo/external_auth_hook
Executable file
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])"
|
||||
# vim: set filetype=python :
|
||||
#
|
||||
# available environment variables:
|
||||
# - SFTPGO_AUTHD_USERNAME
|
||||
# - SFTPGO_AUTHD_USER
|
||||
# - SFTPGO_AUTHD_IP
|
||||
# - SFTPGO_AUTHD_PROTOCOL = { "DAV", "FTP", "HTTP", "SSH" }
|
||||
# - SFTPGO_AUTHD_PASSWORD
|
||||
# - SFTPGO_AUTHD_PUBLIC_KEY
|
||||
# - SFTPGO_AUTHD_KEYBOARD_INTERACTIVE
|
||||
# - SFTPGO_AUTHD_TLS_CERT
|
||||
#
|
||||
# user permissions:
|
||||
# - see <repo:drakkan/sftpgo:internal/dataprovider/user.go>
|
||||
# - "*" = grant all permissions
|
||||
# - read-only perms:
|
||||
# - "list" = list files and directories
|
||||
# - "download"
|
||||
# - rw perms:
|
||||
# - "upload"
|
||||
# - "overwrite" = allow uploads to replace existing files
|
||||
# - "delete" = delete files and directories
|
||||
# - "delete_files"
|
||||
# - "delete_dirs"
|
||||
# - "rename" = rename files and directories
|
||||
# - "rename_files"
|
||||
# - "rename_dirs"
|
||||
# - "create_dirs"
|
||||
# - "create_symlinks"
|
||||
# - "chmod"
|
||||
# - "chown"
|
||||
# - "chtimes" = change atime/mtime (access and modification times)
|
||||
#
|
||||
# home_dir:
|
||||
# - it seems (empirically) that a user can't cd above their home directory.
|
||||
# though i don't have a reference for that in the docs.
|
||||
|
||||
import crypt
|
||||
import json
|
||||
import os
|
||||
|
||||
from hmac import compare_digest
|
||||
|
||||
authFail = dict(username="")
|
||||
|
||||
PERM_RO = [ "list", "download" ]
|
||||
PERM_RW = [
|
||||
# read-only:
|
||||
"list",
|
||||
"download",
|
||||
# write:
|
||||
"upload",
|
||||
"overwrite",
|
||||
"delete",
|
||||
"rename",
|
||||
"create_dirs",
|
||||
"create_symlinks",
|
||||
# intentionally omitted:
|
||||
# "chmod",
|
||||
# "chown",
|
||||
# "chtimes",
|
||||
]
|
||||
|
||||
TRUSTED_CREDS = [
|
||||
# /etc/shadow style creds.
|
||||
# mkpasswd -m sha-512
|
||||
# $<method>$<salt>$<hash>
|
||||
"$6$Zq3c2u4ghUH4S6EP$pOuRt13sEKfX31OqPbbd1LuhS21C9MICMc94iRdTAgdAcJ9h95gQH/6Jf6Ie4Obb0oxQtojRJ1Pd/9QHOlFMW." #< m. rocket boy
|
||||
]
|
||||
|
||||
def mkAuthOk(username: str, permissions: dict[str, list[str]]) -> dict:
|
||||
return dict(
|
||||
status = 1,
|
||||
username = username,
|
||||
expiration_date = 0,
|
||||
home_dir = "/var/export",
|
||||
# uid/gid 0 means to inherit sftpgo uid.
|
||||
# - i.e. users can't read files which Linux user `sftpgo` can't read
|
||||
# - uploaded files belong to Linux user `sftpgo`
|
||||
# other uid/gid values aren't possible for localfs backend, unless i let sftpgo use `sudo`.
|
||||
uid = 0,
|
||||
gid = 0,
|
||||
# uid = 65534,
|
||||
# gid = 65534,
|
||||
max_sessions = 0,
|
||||
# quota_*: 0 means to not use SFTP's quota system
|
||||
quota_size = 0,
|
||||
quota_files = 0,
|
||||
permissions = permissions,
|
||||
upload_bandwidth = 0,
|
||||
download_bandwidth = 0,
|
||||
filters = dict(
|
||||
allowed_ip = [],
|
||||
denied_ip = [],
|
||||
),
|
||||
public_keys = [],
|
||||
# other fields:
|
||||
# ? groups
|
||||
# ? virtual_folders
|
||||
)
|
||||
|
||||
def isLan(ip: str) -> bool:
|
||||
return ip.startswith("10.78.76.") \
|
||||
or ip.startswith("10.78.77.") \
|
||||
or ip.startswith("10.78.78.") \
|
||||
or ip.startswith("10.78.79.")
|
||||
|
||||
def isWireguard(ip: str) -> bool:
|
||||
return ip.startswith("10.0.10.")
|
||||
|
||||
def isTrustedCred(password: str) -> bool:
|
||||
for cred in TRUSTED_CREDS:
|
||||
_, method, salt, hash_ = cred.split("$")
|
||||
# assert method == "6", f"unrecognized crypt entry: {cred}"
|
||||
if crypt.crypt(password, f"${method}${salt}") == cred:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def getAuthResponse(ip: str, username: str, password: str) -> dict:
|
||||
"""
|
||||
return a sftpgo auth response either denying the user or approving them
|
||||
with a set of permissions.
|
||||
"""
|
||||
if isTrustedCred(password) and username != "colin":
|
||||
# allow r/w access from those with a special token
|
||||
return mkAuthOk(username, permissions = {
|
||||
"/": PERM_RW,
|
||||
"/playground": PERM_RW,
|
||||
})
|
||||
if isWireguard(ip):
|
||||
# allow any user from wireguard
|
||||
return mkAuthOk(username, permissions = {
|
||||
"/": PERM_RW,
|
||||
"/playground": PERM_RW,
|
||||
})
|
||||
if isLan(ip):
|
||||
if username == "anonymous":
|
||||
# allow anonymous users on the LAN
|
||||
return mkAuthOk("anonymous", permissions = {
|
||||
"/": PERM_RO,
|
||||
"/playground": PERM_RW,
|
||||
})
|
||||
|
||||
return authFail
|
||||
|
||||
def main():
|
||||
ip = os.environ.get("SFTPGO_AUTHD_IP", "")
|
||||
username = os.environ.get("SFTPGO_AUTHD_USERNAME", "")
|
||||
password = os.environ.get("SFTPGO_AUTHD_PASSWORD", "")
|
||||
resp = getAuthResponse(ip, username, password)
|
||||
print(json.dumps(resp))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@@ -0,0 +1,32 @@
|
||||
diff --git a/internal/ftpd/handler.go b/internal/ftpd/handler.go
|
||||
index 036c3977..33211261 100644
|
||||
--- a/internal/ftpd/handler.go
|
||||
+++ b/internal/ftpd/handler.go
|
||||
@@ -169,7 +169,7 @@ func (c *Connection) Stat(name string) (os.FileInfo, error) {
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
- return fi, nil
|
||||
+ return vfs.NewFileInfo(name, fi.IsDir(), fi.Size(), fi.ModTime(), false), nil
|
||||
}
|
||||
|
||||
// Name returns the name of this connection
|
||||
@@ -315,7 +315,17 @@ func (c *Connection) ReadDir(name string) (ftpserver.DirLister, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
- return c.ListDir(name)
|
||||
+ lister, err := c.ListDir(name)
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ return &patternDirLister{
|
||||
+ DirLister: lister,
|
||||
+ pattern: "*",
|
||||
+ lastCommand: c.clientContext.GetLastCommand(),
|
||||
+ dirName: name,
|
||||
+ connectionPath: c.clientContext.Path(),
|
||||
+ }, nil
|
||||
}
|
||||
|
||||
// GetHandle implements ClientDriverExtentionFileTransfer
|
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p coreutils
|
||||
# vim: set filetype=bash :
|
||||
#
|
||||
# available environment variables:
|
||||
# - SFTPGO_AUTHD_USERNAME
|
||||
# - SFTPGO_AUTHD_USER
|
||||
# - SFTPGO_AUTHD_IP
|
||||
# - SFTPGO_AUTHD_PROTOCOL = { "DAV", "FTP", "HTTP", "SSH" }
|
||||
# - SFTPGO_AUTHD_PASSWORD
|
||||
# - SFTPGO_AUTHD_PUBLIC_KEY
|
||||
# - SFTPGO_AUTHD_KEYBOARD_INTERACTIVE
|
||||
# - SFTPGO_AUTHD_TLS_CERT
|
||||
#
|
||||
#
|
||||
# call with <script_name> /path/to/fail/response.json /path/to/success/response.json
|
||||
|
||||
|
||||
if [ "$SFTPGO_AUTHD_USERNAME" = "anonymous" ]; then
|
||||
cat "$2"
|
||||
else
|
||||
cat "$1"
|
||||
fi
|
@@ -20,7 +20,7 @@
|
||||
--ignore-panel=HOSTS \
|
||||
--ws-url=wss://sink.uninsane.org:443/ws \
|
||||
--port=7890 \
|
||||
-o /var/lib/uninsane/sink/index.html
|
||||
-o /var/lib/goaccess/index.html
|
||||
'';
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
Type = "simple";
|
||||
@@ -28,17 +28,19 @@
|
||||
RestartSec = "10s";
|
||||
|
||||
# hardening
|
||||
WorkingDirectory = "/tmp";
|
||||
# TODO: run as `goaccess` user and add `goaccess` user to group `nginx`.
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = "yes";
|
||||
PrivateTmp = true;
|
||||
ProtectHome = "read-only";
|
||||
ProtectSystem = "strict";
|
||||
SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @privileged @reboot @resources @setuid @swap @raw-io";
|
||||
ReadOnlyPaths = "/";
|
||||
ReadWritePaths = [ "/proc/self" "/var/lib/uninsane/sink" ];
|
||||
PrivateDevices = "yes";
|
||||
ProtectKernelModules = "yes";
|
||||
ProtectKernelTunables = "yes";
|
||||
ProtectSystem = "strict";
|
||||
ReadOnlyPaths = [ "/var/log/nginx" ];
|
||||
ReadWritePaths = [ "/proc/self" "/var/lib/goaccess" ];
|
||||
StateDirectory = "goaccess";
|
||||
SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @privileged @reboot @resources @setuid @swap @raw-io";
|
||||
WorkingDirectory = "/var/lib/goaccess";
|
||||
};
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
@@ -49,7 +51,7 @@
|
||||
addSSL = true;
|
||||
enableACME = true;
|
||||
# inherit kTLS;
|
||||
root = "/var/lib/uninsane/sink";
|
||||
root = "/var/lib/goaccess";
|
||||
|
||||
locations."/ws" = {
|
||||
proxyPass = "http://127.0.0.1:7890";
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{ ... }:
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
@@ -12,6 +12,8 @@
|
||||
systemd.services.jackett.serviceConfig = {
|
||||
# run this behind the OVPN static VPN
|
||||
NetworkNamespacePath = "/run/netns/ovpns";
|
||||
ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect 185.157.162.178" ]; # abort if public IP is not as expected
|
||||
|
||||
# patch jackett to listen on the public interfaces
|
||||
# ExecStart = lib.mkForce "${pkgs.jackett}/bin/Jackett --NoUpdates --DataFolder /var/lib/jackett/.config/Jackett --ListenPublic";
|
||||
};
|
||||
|
@@ -75,7 +75,7 @@
|
||||
# Jellyfin multimedia server
|
||||
# this is mostly taken from the official jellfin.org docs
|
||||
services.nginx.virtualHosts."jelly.uninsane.org" = {
|
||||
addSSL = true;
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
# inherit kTLS;
|
||||
|
||||
|
@@ -12,7 +12,7 @@ in
|
||||
services.komga.port = 11319; # chosen at random
|
||||
|
||||
services.nginx.virtualHosts."komga.uninsane.org" = {
|
||||
addSSL = true;
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:${builtins.toString port}";
|
||||
|
@@ -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;
|
||||
|
@@ -89,6 +89,16 @@ in
|
||||
disable_symlinks on;
|
||||
'';
|
||||
};
|
||||
locations."/share/Milkbags/" = {
|
||||
alias = "/var/media/Videos/Milkbags/";
|
||||
extraConfig = ''
|
||||
# autoindex => render directory listings
|
||||
autoindex on;
|
||||
# don't follow any symlinks when serving files
|
||||
# otherwise it allows a directory escape
|
||||
disable_symlinks on;
|
||||
'';
|
||||
};
|
||||
|
||||
# allow matrix users to discover that @user:uninsane.org is reachable via matrix.uninsane.org
|
||||
locations."= /.well-known/matrix/server".extraConfig =
|
||||
|
@@ -25,7 +25,7 @@ in
|
||||
|
||||
config :pleroma, Pleroma.Web.Endpoint,
|
||||
url: [host: "fed.uninsane.org", scheme: "https", port: 443],
|
||||
http: [ip: {127, 0, 0, 1}, port: 4000]
|
||||
http: [ip: {127, 0, 0, 1}, port: 4040]
|
||||
# secret_key_base: "{secrets.pleroma.secret_key_base}",
|
||||
# signing_salt: "{secrets.pleroma.signing_salt}"
|
||||
|
||||
@@ -167,7 +167,7 @@ in
|
||||
enableACME = true;
|
||||
# inherit kTLS;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:4000";
|
||||
proxyPass = "http://127.0.0.1:4040";
|
||||
recommendedProxySettings = true;
|
||||
# documented: https://git.pleroma.social/pleroma/pleroma/-/blob/develop/installation/pleroma.nginx
|
||||
extraConfig = ''
|
||||
|
@@ -3,10 +3,17 @@
|
||||
#
|
||||
# config precedence (higher precedence overrules lower precedence):
|
||||
# - Default Values < Environment Variables < YAML Configuraiton File < Command Line Arguments
|
||||
{ config, lib, ... }:
|
||||
#
|
||||
# debugging:
|
||||
# - soulseek is just *flaky*. if you see e.g. DNS errors, even though you can't replicate them via `dig` or `getent ahostsv4`, just give it 10 minutes to work out:
|
||||
# - "Soulseek.AddressException: Failed to resolve address 'vps.slsknet.org': Resource temporarily unavailable"
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# TODO: re-enable once i'm satisfied this isn't escaping the net sandbox
|
||||
lib.mkIf false
|
||||
{
|
||||
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 +22,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 +35,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 = {
|
||||
@@ -62,13 +71,12 @@
|
||||
# flags.volatile = true; # store searches and active transfers in RAM (completed transfers still go to disk). rec for btrfs/zfs
|
||||
};
|
||||
|
||||
systemd.services.slskd = {
|
||||
serviceConfig = {
|
||||
# run this behind the OVPN static VPN
|
||||
NetworkNamespacePath = "/run/netns/ovpns";
|
||||
Restart = lib.mkForce "always"; # exits "success" when it fails to connect to soulseek server
|
||||
RestartSec = "60s";
|
||||
Group = "media";
|
||||
};
|
||||
systemd.services.slskd.serviceConfig = {
|
||||
# run this behind the OVPN static VPN
|
||||
NetworkNamespacePath = "/run/netns/ovpns";
|
||||
ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect 185.157.162.178" ]; # abort if public IP is not as expected
|
||||
|
||||
Restart = lib.mkForce "always"; # exits "success" when it fails to connect to soulseek server
|
||||
RestartSec = "60s";
|
||||
};
|
||||
}
|
||||
|
@@ -22,6 +22,65 @@ 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; [
|
||||
acl
|
||||
coreutils
|
||||
findutils
|
||||
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/"
|
||||
# make the media rwx by anyone in the group
|
||||
destructive find "$MEDIA_DIR" -type d -exec setfacl --recursive --modify d:g::rwx,o::rx {} \;
|
||||
destructive find "$MEDIA_DIR" -type d -exec chmod g+rw,a+rx {} \;
|
||||
|
||||
# if there's a single directory inside the media dir, then inline that
|
||||
subdirs=("$MEDIA_DIR"/*)
|
||||
if [ ''${#subdirs} -eq 1 ]; then
|
||||
dirname="''${subdirs[0]}"
|
||||
if [ -d "$dirname" ]; then
|
||||
mv "$dirname"/* "$MEDIA_DIR/" && rmdir "$dirname"
|
||||
fi
|
||||
fi
|
||||
|
||||
# remove noisy files:
|
||||
find "$MEDIA_DIR/" -type f \(\
|
||||
-iname 'www.YTS.*.jpg' \
|
||||
-o -iname 'WWW.YIFY*.COM.jpg' \
|
||||
-o -iname 'YIFY*.com.txt' \
|
||||
-o -iname 'YTS*.com.txt' \
|
||||
\) -exec rm {} \;
|
||||
|
||||
# dedupe the whole media library.
|
||||
# yeah, a bit excessive: move this to a cron job if that's problematic.
|
||||
destructive hardlink /var/media --reflink=always --ignore-time --verbose
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
@@ -46,8 +105,8 @@ in
|
||||
# DOCUMENTATION/options list: <https://github.com/transmission/transmission/blob/main/docs/Editing-Configuration-Files.md#options>
|
||||
|
||||
# message-level = 3; #< enable for debug logging. 0-3, default is 2.
|
||||
# 0.0.0.0 => allow rpc from any host: we gate it via firewall and auth requirement
|
||||
rpc-bind-address = "0.0.0.0";
|
||||
# 10.0.1.6 => allow rpc only from the root servo ns. it'll tunnel things to the net, if need be.
|
||||
rpc-bind-address = "10.0.1.6";
|
||||
#rpc-host-whitelist = "bt.uninsane.org";
|
||||
#rpc-whitelist = "*.*.*.*";
|
||||
rpc-authentication-required = true;
|
||||
@@ -57,6 +116,10 @@ in
|
||||
rpc-password = "{503fc8928344f495efb8e1f955111ca5c862ce0656SzQnQ5";
|
||||
rpc-whitelist-enabled = false;
|
||||
|
||||
# force behind ovpns in case the NetworkNamespace fails somehow
|
||||
bind-address-ipv4 = "185.157.162.178";
|
||||
port-forwarding-enabled = false;
|
||||
|
||||
# hopefully, make the downloads world-readable
|
||||
# umask = 0; #< default is 2: i.e. deny writes from world
|
||||
|
||||
@@ -72,11 +135,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" ];
|
||||
@@ -84,8 +159,11 @@ in
|
||||
systemd.services.transmission.serviceConfig = {
|
||||
# run this behind the OVPN static VPN
|
||||
NetworkNamespacePath = "/run/netns/ovpns";
|
||||
ExecStartPre = [ "${lib.getExe pkgs.sane-scripts.ip-check} --no-upnp --expect 185.157.162.178" ]; # abort if public IP is not as expected
|
||||
|
||||
Restart = "on-failure";
|
||||
RestartSec = "30s";
|
||||
BindPaths = [ "/var/media" ]; #< so it can move completed torrents into the media library
|
||||
};
|
||||
|
||||
# service to automatically backup torrents i add to transmission
|
||||
|
@@ -2,19 +2,11 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
dyn-dns = config.sane.services.dyn-dns;
|
||||
nativeAddrs = lib.mapAttrs (_name: builtins.head) config.sane.dns.zones."uninsane.org".inet.A;
|
||||
bindOvpn = "10.0.1.5";
|
||||
in lib.mkMerge [
|
||||
in
|
||||
{
|
||||
services.trust-dns.enable = true;
|
||||
|
||||
# don't bind to IPv6 until i explicitly test that stack
|
||||
services.trust-dns.settings.listen_addrs_ipv6 = [];
|
||||
services.trust-dns.quiet = true;
|
||||
# FIXME(2023/11/26): services.trust-dns.debug doesn't log requests: use RUST_LOG=debug env for that.
|
||||
# - see: <https://github.com/hickory-dns/hickory-dns/issues/2082>
|
||||
# services.trust-dns.debug = true;
|
||||
|
||||
sane.ports.ports."53" = {
|
||||
protocol = [ "udp" "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
@@ -66,23 +58,6 @@ in lib.mkMerge [
|
||||
|
||||
services.trust-dns.settings.zones = [ "uninsane.org" ];
|
||||
|
||||
# TODO: can i transform this into some sort of service group?
|
||||
# have `systemctl restart trust-dns.service` restart all the individual services?
|
||||
systemd.services.trust-dns.serviceConfig = {
|
||||
DynamicUser = lib.mkForce false;
|
||||
User = "trust-dns";
|
||||
Group = "trust-dns";
|
||||
wantedBy = lib.mkForce [];
|
||||
};
|
||||
systemd.services.trust-dns.enable = false;
|
||||
|
||||
users.groups.trust-dns = {};
|
||||
users.users.trust-dns = {
|
||||
group = "trust-dns";
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
# sane.services.dyn-dns.restartOnChange = [ "trust-dns.service" ];
|
||||
|
||||
networking.nat.enable = true;
|
||||
networking.nat.extraCommands = ''
|
||||
@@ -107,98 +82,73 @@ in lib.mkMerge [
|
||||
visibleTo.lan = true;
|
||||
description = "colin-redirected-dns-for-lan-namespace";
|
||||
};
|
||||
}
|
||||
{
|
||||
systemd.services =
|
||||
let
|
||||
sed = "${pkgs.gnused}/bin/sed";
|
||||
stateDir = "/var/lib/trust-dns";
|
||||
zoneTemplate = pkgs.writeText "uninsane.org.zone.in" config.sane.dns.zones."uninsane.org".rendered;
|
||||
|
||||
zoneDirFor = flavor: "${stateDir}/${flavor}";
|
||||
zoneFor = flavor: "${zoneDirFor flavor}/uninsane.org.zone";
|
||||
mkTrustDnsService = opts: flavor: let
|
||||
flags = let baseCfg = config.services.trust-dns; in
|
||||
(lib.optional baseCfg.debug "--debug") ++ (lib.optional baseCfg.quiet "--quiet");
|
||||
flagsStr = builtins.concatStringsSep " " flags;
|
||||
|
||||
anative = nativeAddrs."servo.${flavor}";
|
||||
|
||||
toml = pkgs.formats.toml { };
|
||||
configTemplate = opts.config or (toml.generate "trust-dns-${flavor}.toml" (
|
||||
(
|
||||
lib.filterAttrsRecursive (_: v: v != null) config.services.trust-dns.settings
|
||||
) // {
|
||||
listen_addrs_ipv4 = opts.listen or [ anative ];
|
||||
}
|
||||
));
|
||||
configFile = "${stateDir}/${flavor}-config.toml";
|
||||
|
||||
port = opts.port or 53;
|
||||
in {
|
||||
description = "trust-dns Domain Name Server (serving ${flavor})";
|
||||
unitConfig.Documentation = "https://trust-dns.org/";
|
||||
|
||||
preStart = ''
|
||||
wan=$(cat '${config.sane.services.dyn-dns.ipPath}')
|
||||
${sed} s/%AWAN%/$wan/ ${configTemplate} > ${configFile}
|
||||
'' + lib.optionalString (!opts ? config) ''
|
||||
mkdir -p ${zoneDirFor flavor}
|
||||
${sed} \
|
||||
-e s/%CNAMENATIVE%/servo.${flavor}/ \
|
||||
-e s/%ANATIVE%/${anative}/ \
|
||||
-e s/%AWAN%/$wan/ \
|
||||
-e s/%AOVPNS%/185.157.162.178/ \
|
||||
${zoneTemplate} > ${zoneFor flavor}
|
||||
'';
|
||||
serviceConfig = config.systemd.services.trust-dns.serviceConfig // {
|
||||
ExecStart = ''
|
||||
${pkgs.trust-dns}/bin/${pkgs.trust-dns.meta.mainProgram} \
|
||||
--port ${builtins.toString port} \
|
||||
--zonedir ${zoneDirFor flavor}/ \
|
||||
--config ${configFile} ${flagsStr}
|
||||
'';
|
||||
};
|
||||
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
in {
|
||||
trust-dns-wan = mkTrustDnsService { listen = [ nativeAddrs."servo.lan" bindOvpn ]; } "wan";
|
||||
trust-dns-lan = mkTrustDnsService { port = 1053; } "lan";
|
||||
trust-dns-hn = mkTrustDnsService { port = 1053; } "hn";
|
||||
trust-dns-hn-resolver = mkTrustDnsService {
|
||||
config = pkgs.writeText "hn-resolver-config.toml" ''
|
||||
# i host a resolver in the wireguard VPN so that clients can resolve DNS through the VPN.
|
||||
# (that's what this file achieves).
|
||||
#
|
||||
# one would expect this resolver could host the authoritative zone for `uninsane.org`, and then forward everything else to the system resolver...
|
||||
# and while that works for `dig`, it breaks for `nslookup` (and so `ssh`, etc).
|
||||
#
|
||||
# DNS responses include a flag for if the responding server is the authority of the zone queried.
|
||||
# it seems that default Linux stub resolvers either:
|
||||
# - expect DNSSEC when the response includes that bit, or
|
||||
# - expect A records to be in the `answer` section instead of `additional` section.
|
||||
# or perhaps something more nuanced. but for `nslookup` to be reliable, it has to talk to an
|
||||
# instance of trust-dns which is strictly a resolver, with no authority.
|
||||
# hence, this config: a resolver which forwards to the actual authority.
|
||||
|
||||
listen_addrs_ipv4 = ["${nativeAddrs."servo.hn"}"]
|
||||
listen_addrs_ipv6 = []
|
||||
|
||||
[[zones]]
|
||||
zone = "uninsane.org"
|
||||
zone_type = "Forward"
|
||||
stores = { type = "forward", name_servers = [{ socket_addr = "${nativeAddrs."servo.hn"}:1053", protocol = "udp", trust_nx_responses = true }] }
|
||||
|
||||
[[zones]]
|
||||
# forward the root zone to the local DNS resolver
|
||||
zone = "."
|
||||
zone_type = "Forward"
|
||||
stores = { type = "forward", name_servers = [{ socket_addr = "127.0.0.53:53", protocol = "udp", trust_nx_responses = true }] }
|
||||
'';
|
||||
} "hn-resolver";
|
||||
sane.services.trust-dns.enable = true;
|
||||
sane.services.trust-dns.instances = let
|
||||
mkSubstitutions = flavor: {
|
||||
"%AWAN%" = "$(cat '${dyn-dns.ipPath}')";
|
||||
"%CNAMENATIVE%" = "servo.${flavor}";
|
||||
"%ANATIVE%" = nativeAddrs."servo.${flavor}";
|
||||
"%AOVPNS%" = "185.157.162.178";
|
||||
};
|
||||
in
|
||||
{
|
||||
wan = {
|
||||
substitutions = mkSubstitutions "wan";
|
||||
listenAddrs = [
|
||||
nativeAddrs."servo.lan"
|
||||
bindOvpn
|
||||
];
|
||||
};
|
||||
lan = {
|
||||
substitutions = mkSubstitutions "lan";
|
||||
listenAddrs = [ nativeAddrs."servo.lan" ];
|
||||
port = 1053;
|
||||
};
|
||||
hn = {
|
||||
substitutions = mkSubstitutions "hn";
|
||||
listenAddrs = [ nativeAddrs."servo.hn" ];
|
||||
port = 1053;
|
||||
};
|
||||
hn-resolver = {
|
||||
# don't need %AWAN% here because we forward to the hn instance.
|
||||
listenAddrs = [ nativeAddrs."servo.hn" ];
|
||||
extraConfig = {
|
||||
zones = [
|
||||
{
|
||||
zone = "uninsane.org";
|
||||
zone_type = "Forward";
|
||||
stores = {
|
||||
type = "forward";
|
||||
name_servers = [
|
||||
{
|
||||
socket_addr = "${nativeAddrs."servo.hn"}:1053";
|
||||
protocol = "udp";
|
||||
trust_nx_responses = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
{
|
||||
# forward the root zone to the local DNS resolver
|
||||
zone = ".";
|
||||
zone_type = "Forward";
|
||||
stores = {
|
||||
type = "forward";
|
||||
name_servers = [
|
||||
{
|
||||
socket_addr = "127.0.0.53:53";
|
||||
protocol = "udp";
|
||||
trust_nx_responses = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
sane.services.dyn-dns.restartOnChange = [
|
||||
"trust-dns-wan.service"
|
||||
@@ -207,4 +157,3 @@ in lib.mkMerge [
|
||||
# "trust-dns-hn-resolver.service" # doesn't need restart because it doesn't know about WAN IP
|
||||
];
|
||||
}
|
||||
]
|
||||
|
@@ -81,10 +81,14 @@ let
|
||||
(fromDb "feeds.simplecast.com/xKJ93w_w" // uncat) # Atlas Obscura
|
||||
(fromDb "feeds.transistor.fm/acquired" // tech)
|
||||
(fromDb "fulltimenix.com" // tech)
|
||||
(fromDb "futureofcoding.org/episodes" // tech)
|
||||
(fromDb "hackerpublicradio.org" // tech)
|
||||
(fromDb "lexfridman.com/podcast" // rat)
|
||||
(fromDb "mapspodcast.libsyn.com" // uncat) # Multidisciplinary Association for Psychedelic Studies
|
||||
(fromDb "microarch.club" // tech)
|
||||
(fromDb "omegataupodcast.net" // tech) # 3/4 German; 1/4 eps are English
|
||||
(fromDb "omny.fm/shows/cool-people-who-did-cool-stuff" // pol) # Maggie Killjoy -- referenced by Cory Doctorow
|
||||
(fromDb "omny.fm/shows/money-stuff-the-podcast") # Matt Levine
|
||||
(fromDb "omny.fm/shows/the-dollop-with-dave-anthony-and-gareth-reynolds") # The Dollop history/comedy
|
||||
(fromDb "originstories.libsyn.com" // uncat)
|
||||
(fromDb "podcast.posttv.com/itunes/post-reports.xml" // pol)
|
||||
@@ -100,9 +104,12 @@ let
|
||||
(fromDb "sharkbytes.transistor.fm" // tech) # Wireshark Podcast o_0
|
||||
(fromDb "sscpodcast.libsyn.com" // rat) # Astral Codex Ten
|
||||
(fromDb "talesfromthebridge.buzzsprout.com" // tech) # Sci-Fi? has Peter Watts; author of No Moods, Ads or Cutesy Fucking Icons (rifters.com)
|
||||
(fromDb "theamphour.com" // tech)
|
||||
(fromDb "techtalesshow.com" // tech) # Corbin Davenport
|
||||
(fromDb "techwontsave.us" // pol) # rec by Cory Doctorow
|
||||
(fromDb "wakingup.libsyn.com" // pol) # Sam Harris
|
||||
(fromDb "werenotwrong.fireside.fm" // pol)
|
||||
(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
|
||||
@@ -122,11 +129,11 @@ let
|
||||
(fromDb "acoup.blog/feed") # history, states. author: <https://historians.social/@bretdevereaux/following>
|
||||
(fromDb "amosbbatto.wordpress.com" // tech)
|
||||
(fromDb "anish.lakhwara.com" // tech)
|
||||
(fromDb "apenwarr.ca/log/rss.php" // tech) # CEO of tailscale
|
||||
(fromDb "applieddivinitystudies.com" // rat)
|
||||
(fromDb "artemis.sh" // tech)
|
||||
(fromDb "ascii.textfiles.com" // tech) # Jason Scott
|
||||
(fromDb "austinvernon.site" // tech)
|
||||
# (fromDb "balajis.com" // pol) # Balaji
|
||||
(fromDb "ben-evans.com/benedictevans" // pol)
|
||||
(fromDb "bitbashing.io" // tech)
|
||||
(fromDb "bitsaboutmoney.com" // uncat)
|
||||
@@ -137,20 +144,19 @@ let
|
||||
(fromDb "blog.thalheim.io" // tech) # Mic92
|
||||
(fromDb "bunniestudios.com" // tech) # Bunnie Juang
|
||||
(fromDb "capitolhillseattle.com" // pol)
|
||||
# (fromDb "drewdevault.com" // tech)
|
||||
# (fromDb "econlib.org" // pol)
|
||||
(fromDb "edwardsnowden.substack.com" // pol // text)
|
||||
(fromDb "fasterthanli.me" // tech)
|
||||
(fromDb "gwern.net" // rat)
|
||||
(fromDb "hardcoresoftware.learningbyshipping.com" // tech) # Steven Sinofsky
|
||||
(fromDb "harihareswara.net" // tech // pol) # rec by Cory Doctorow
|
||||
(fromDb "ianthehenry.com" // tech)
|
||||
(fromDb "idiomdrottning.org" // uncat)
|
||||
(fromDb "interconnected.org/home/feed" // rat) # Matt Webb -- engineering-ish, but dreamy
|
||||
(fromDb "jeffgeerling.com" // tech)
|
||||
(fromDb "jefftk.com" // tech)
|
||||
(fromDb "jwz.org/blog" // tech // pol) # DNA lounge guy, loooong-time blogger
|
||||
(fromDb "kill-the-newsletter.com/feeds/joh91bv7am2pnznv.xml" // pol) # Matt Levine - Money Stuff
|
||||
(fromDb "kosmosghost.github.io/index.xml" // tech)
|
||||
# (fromDb "lesswrong.com" // rat)
|
||||
(fromDb "linmob.net" // tech)
|
||||
(fromDb "lwn.net" // tech)
|
||||
(fromDb "lynalden.com" // pol)
|
||||
@@ -158,19 +164,20 @@ let
|
||||
(fromDb "mg.lol" // tech)
|
||||
(fromDb "mindingourway.com" // rat)
|
||||
(fromDb "morningbrew.com/feed" // pol)
|
||||
(fromDb "nixpkgs.news" // tech)
|
||||
(fromDb "overcomingbias.com" // rat) # Robin Hanson
|
||||
(fromDb "palladiummag.com" // uncat)
|
||||
(fromDb "philosopher.coach" // rat) # Peter Saint-Andre -- side project of stpeter.im
|
||||
(fromDb "pomeroyb.com" // tech)
|
||||
(fromDb "postmarketos.org/blog" // tech)
|
||||
(fromDb "preposterousuniverse.com" // rat) # Sean Carroll
|
||||
(fromDb "profectusmag.com" // uncat)
|
||||
(fromDb "project-insanity.org" // tech) # shared blog by a few NixOS devs, notably onny
|
||||
(fromDb "putanumonit.com" // rat) # mostly dating topics. not advice, or humor, but looking through a social lens
|
||||
(fromDb "richardcarrier.info" // rat)
|
||||
(fromDb "rifters.com/crawl" // uncat) # No Moods, Ads or Cutesy Fucking Icons
|
||||
(fromDb "righto.com" // tech) # Ken Shirriff
|
||||
(fromDb "rootsofprogress.org" // rat) # Jason Crawford
|
||||
(fromDb "samuel.dionne-riel.com" // tech) # SamuelDR
|
||||
(fromDb "sagacioussuricata.com" // tech) # ian (Sanctuary)
|
||||
(fromDb "semiaccurate.com" // tech)
|
||||
(fromDb "sideways-view.com" // rat) # Paul Christiano
|
||||
@@ -179,31 +186,40 @@ let
|
||||
(fromDb "spectrum.ieee.org" // tech)
|
||||
(fromDb "stpeter.im/atom.xml" // pol)
|
||||
(fromDb "thediff.co" // pol) # Byrne Hobart
|
||||
# (fromDb "theregister.com" // tech)
|
||||
(fromDb "thisweek.gnome.org" // tech)
|
||||
(fromDb "tuxphones.com" // tech)
|
||||
(fromDb "uninsane.org" // tech)
|
||||
(fromDb "unintendedconsequenc.es" // rat)
|
||||
# (fromDb "vitalik.ca" // tech) # moved to vitalik.eth.limo
|
||||
(fromDb "vitalik.eth.limo" // tech) # Vitalik Buterin
|
||||
# (fromDb "webcurious.co.uk" // uncat) # link aggregator; defunct?
|
||||
(fromDb "willow.phantoma.online") # wizard@xyzzy.link
|
||||
(fromDb "xn--gckvb8fzb.com" // tech)
|
||||
(mkSubstack "astralcodexten" // rat // daily) # Scott Alexander
|
||||
# (mkSubstack "doomberg" // tech // weekly) # articles are all pay-walled
|
||||
(mkSubstack "eliqian" // rat // weekly)
|
||||
(mkSubstack "oversharing" // pol // daily)
|
||||
(mkSubstack "samkriss" // humor // infrequent)
|
||||
(mkText "http://benjaminrosshoffman.com/feed" // pol // weekly)
|
||||
(mkText "http://boginjr.com/feed" // tech // infrequent)
|
||||
(mkText "https://forum.merveilles.town/rss.xml" // pol // infrequent) #quality RSS list here: <https://forum.merveilles.town/thread/57/share-your-rss-feeds%21-6/>
|
||||
# (mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
|
||||
(mkText "https://jvns.ca/atom.xml" // tech // weekly) # Julia Evans
|
||||
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
|
||||
(mkText "https://nixos.org/blog/announcements-rss.xml" // tech // infrequent) # more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html>
|
||||
(mkText "https://nixos.org/blog/stories-rss.xml" // tech // weekly)
|
||||
(mkText "https://solar.lowtechmagazine.com/posts/index.xml" // tech // weekly)
|
||||
(mkText "https://www.stratechery.com/rss" // pol // weekly) # Ben Thompson
|
||||
|
||||
# (fromDb "balajis.com" // pol) # Balaji
|
||||
# (fromDb "drewdevault.com" // tech)
|
||||
# (fromDb "econlib.org" // pol)
|
||||
# (fromDb "lesswrong.com" // rat)
|
||||
# (fromDb "profectusmag.com" // pol) # some conservative/libertarian think tank
|
||||
# (fromDb "thesideview.co" // uncat) # spiritual journal; RSS items are stubs
|
||||
# (fromDb "theregister.com" // tech)
|
||||
# (fromDb "vitalik.ca" // tech) # moved to vitalik.eth.limo
|
||||
# (fromDb "webcurious.co.uk" // uncat) # link aggregator; defunct?
|
||||
# (mkSubstack "doomberg" // tech // weekly) # articles are all pay-walled
|
||||
# (mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
|
||||
# (mkText "https://til.simonwillison.net/tils/feed.atom" // tech // weekly)
|
||||
# (mkText "https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" // pol // weekly) # Matt Levine (preview/paywalled)
|
||||
(mkText "https://www.stratechery.com/rss" // pol // weekly) # Ben Thompson
|
||||
];
|
||||
|
||||
videos = [
|
||||
@@ -215,17 +231,19 @@ let
|
||||
(fromDb "youtube.com/@JackStauber")
|
||||
(fromDb "youtube.com/@NativLang")
|
||||
(fromDb "youtube.com/@PolyMatter")
|
||||
# (fromDb "youtube.com/@rossmanngroup" // pol // tech) # Louis Rossmann
|
||||
(fromDb "youtube.com/@TechnologyConnections" // tech)
|
||||
(fromDb "youtube.com/@TheB1M")
|
||||
(fromDb "youtube.com/@TomScottGo")
|
||||
(fromDb "youtube.com/@Vihart")
|
||||
(fromDb "youtube.com/@Vox")
|
||||
(fromDb "youtube.com/@Vsauce")
|
||||
|
||||
# (fromDb "youtube.com/@rossmanngroup" // pol // tech) # Louis Rossmann
|
||||
];
|
||||
|
||||
images = [
|
||||
(fromDb "catandgirl.com" // img // humor)
|
||||
(fromDb "davidrevoy.com" // img // art)
|
||||
(fromDb "miniature-calendar.com" // img // art // daily)
|
||||
(fromDb "pbfcomics.com" // img // humor)
|
||||
(fromDb "poorlydrawnlines.com/feed" // img // humor)
|
||||
|
@@ -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,44 @@ 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: casting shows to T.V. fails partway through about half the time
|
||||
"connect_timeout=20"
|
||||
];
|
||||
};
|
||||
remoteHome = host: {
|
||||
sane.programs.sshfs-fuse.enableFor.system = true;
|
||||
fileSystems."/mnt/${host}/home" = {
|
||||
device = "colin@${host}:/home/colin";
|
||||
fsType = "fuse.sshfs";
|
||||
@@ -94,6 +125,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 +207,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 +214,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/games")
|
||||
(remoteServo "media/Music")
|
||||
(remoteServo "media/Pictures/macros")
|
||||
(remoteServo "media/torrents")
|
||||
(remoteServo "media/Videos")
|
||||
(remoteServo "playground")
|
||||
]
|
||||
|
||||
|
@@ -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";
|
||||
|
||||
|
@@ -3,13 +3,18 @@
|
||||
{
|
||||
# XDG defines things like ~/Desktop, ~/Downloads, etc.
|
||||
# these clutter the home, so i mostly don't use them.
|
||||
# note that several of these are not actually standardized anywhere.
|
||||
# some are even non-conventional, like:
|
||||
# - XDG_PHOTOS_DIR: only works because i patch e.g. megapixels
|
||||
sane.user.fs.".config/user-dirs.dirs".symlink.text = ''
|
||||
XDG_DESKTOP_DIR="$HOME/.xdg/Desktop"
|
||||
XDG_DOCUMENTS_DIR="$HOME/dev"
|
||||
XDG_DOWNLOAD_DIR="$HOME/tmp"
|
||||
XDG_MUSIC_DIR="$HOME/Music"
|
||||
XDG_PHOTOS_DIR="$HOME/Pictures/Photos"
|
||||
XDG_PICTURES_DIR="$HOME/Pictures"
|
||||
XDG_PUBLICSHARE_DIR="$HOME/.xdg/Public"
|
||||
XDG_SCREENSHOTS_DIR="$HOME/Pictures/Screenshots"
|
||||
XDG_TEMPLATES_DIR="$HOME/.xdg/Templates"
|
||||
XDG_VIDEOS_DIR="$HOME/Videos"
|
||||
'';
|
||||
@@ -17,4 +22,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
|
||||
'';
|
||||
}
|
||||
|
@@ -60,8 +60,5 @@
|
||||
networking.networkmanager.plugins = lib.mkForce [];
|
||||
|
||||
# keyfile.path = where networkmanager should look for connection credentials
|
||||
networking.networkmanager.extraConfig = ''
|
||||
[keyfile]
|
||||
path=/var/lib/NetworkManager/system-connections
|
||||
'';
|
||||
networking.networkmanager.settings.keyfile.path = "/var/lib/NetworkManager/system-connections";
|
||||
}
|
||||
|
@@ -19,30 +19,40 @@
|
||||
#
|
||||
# namespacing:
|
||||
# - each namespace can use a different /etc/resolv.conf to specify different DNS servers (see `firejail --dns=...`)
|
||||
# - nscd breaks namespacing: the host nscd is unaware of the guest's /etc/resolv.conf, and so direct's the guest's DNS requests to the host's servers.
|
||||
# - nscd breaks namespacing: the host nscd is unaware of the guest's /etc/resolv.conf, and so directs the guest's DNS requests to the host's servers.
|
||||
# - this is fixed by either `firejail --blacklist=/var/run/nscd/socket`, or disabling nscd altogether.
|
||||
{ lib, ... }:
|
||||
{
|
||||
# use systemd's stub resolver.
|
||||
# /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link).
|
||||
# instead, running the stub resolver on a known address in the root ns lets us rewrite packets
|
||||
# in servo's ovnps namespace to use the provider's DNS resolvers.
|
||||
# a weakness is we can only query 1 NS at a time (unless we were to clone the packets?)
|
||||
# TODO: rework servo's netns to use `firejail`, which is capable of spoofing /etc/resolv.conf.
|
||||
services.resolved.enable = true; #< to disable, set ` = lib.mkForce false`, as other systemd features default to enabling `resolved`.
|
||||
# without DNSSEC:
|
||||
# - dig matrix.org => works
|
||||
# - curl https://matrix.org => works
|
||||
# with default DNSSEC:
|
||||
# - dig matrix.org => works
|
||||
# - curl https://matrix.org => fails
|
||||
# i don't know why. this might somehow be interfering with the DNS run on this device (trust-dns)
|
||||
services.resolved.dnssec = "false";
|
||||
# # use systemd's stub resolver.
|
||||
# # /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link).
|
||||
# # instead, running the stub resolver on a known address in the root ns lets us rewrite packets
|
||||
# # in servo's ovnps namespace to use the provider's DNS resolvers.
|
||||
# # a weakness is we can only query 1 NS at a time (unless we were to clone the packets?)
|
||||
# # TODO: rework servo's netns to use `firejail`, which is capable of spoofing /etc/resolv.conf.
|
||||
# services.resolved.enable = true; #< to disable, set ` = lib.mkForce false`, as other systemd features default to enabling `resolved`.
|
||||
# # without DNSSEC:
|
||||
# # - dig matrix.org => works
|
||||
# # - curl https://matrix.org => works
|
||||
# # with default DNSSEC:
|
||||
# # - dig matrix.org => works
|
||||
# # - curl https://matrix.org => fails
|
||||
# # i don't know why. this might somehow be interfering with the DNS run on this device (trust-dns)
|
||||
# services.resolved.dnssec = "false";
|
||||
# networking.nameservers = [
|
||||
# # use systemd-resolved resolver
|
||||
# # full resolver (which understands /etc/hosts) lives on 127.0.0.53
|
||||
# # stub resolver (just forwards upstream) lives on 127.0.0.54
|
||||
# "127.0.0.53"
|
||||
# ];
|
||||
|
||||
services.resolved.enable = lib.mkForce false;
|
||||
sane.services.trust-dns.enable = true;
|
||||
sane.services.trust-dns.instances.localhost = {
|
||||
listenAddrs = [ "127.0.0.1" ];
|
||||
enableRecursiveResolver = true;
|
||||
};
|
||||
networking.nameservers = [
|
||||
# use systemd-resolved resolver
|
||||
# full resolver (which understands /etc/hosts) lives on 127.0.0.53
|
||||
# stub resolver (just forwards upstream) lives on 127.0.0.54
|
||||
"127.0.0.53"
|
||||
"127.0.0.1"
|
||||
];
|
||||
|
||||
# nscd -- the Name Service Caching Daemon -- caches DNS query responses
|
||||
|
@@ -53,7 +53,7 @@
|
||||
|
||||
# allow `nix-shell` (and probably nix-index?) to locate our patched and custom packages.
|
||||
# this is actually a no-op, and the real action happens in assigning `nix.settings.nix-path`.
|
||||
nix.nixPath = (lib.optionals config.sane.enableSlowPrograms [
|
||||
nix.nixPath = (lib.optionals (config.sane.maxBuildCost >= 2) [
|
||||
"nixpkgs=${pkgs.path}"
|
||||
]) ++ [
|
||||
# note the import starts at repo root: this allows `./overlay/default.nix` to access the stuff at the root
|
||||
@@ -65,10 +65,10 @@
|
||||
|
||||
# ensure new deployments have a source of this repo with which they can bootstrap.
|
||||
# this however changes on every commit and can be slow to copy for e.g. `moby`.
|
||||
environment.etc."nixos" = lib.mkIf config.sane.enableSlowPrograms {
|
||||
environment.etc."nixos" = lib.mkIf (config.sane.maxBuildCost >= 2) {
|
||||
source = ../../..;
|
||||
};
|
||||
environment.etc."nix/registry.json" = lib.mkIf (!config.sane.enableSlowPrograms) {
|
||||
environment.etc."nix/registry.json" = lib.mkIf (config.sane.maxBuildCost < 2) {
|
||||
enable = false;
|
||||
};
|
||||
|
||||
|
@@ -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)";
|
||||
}
|
||||
|
@@ -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";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -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'
|
||||
'';
|
||||
});
|
||||
|
||||
|
@@ -30,6 +30,8 @@
|
||||
});
|
||||
};
|
||||
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
|
@@ -63,6 +63,7 @@ in
|
||||
"jq"
|
||||
"killall"
|
||||
"less"
|
||||
"lftp"
|
||||
# "libcap_ng" # for `netcap`
|
||||
"lsof"
|
||||
# "miniupnpc"
|
||||
@@ -79,6 +80,7 @@ in
|
||||
"powertop"
|
||||
"pstree"
|
||||
"ripgrep"
|
||||
"s6-rc" # service manager
|
||||
"screen"
|
||||
"smartmontools" # smartctl
|
||||
# "socat"
|
||||
@@ -88,6 +90,7 @@ in
|
||||
"tree"
|
||||
"usbutils" # lsusb
|
||||
"util-linux" # lsblk, lscpu, etc
|
||||
"valgrind"
|
||||
"wget"
|
||||
"wirelesstools" # iwlist
|
||||
# "xq" # jq for XML
|
||||
@@ -107,7 +110,6 @@ in
|
||||
# - debugging?
|
||||
consoleUtils = declPackageSet [
|
||||
"alsaUtils" # for aplay, speaker-test
|
||||
"strings"
|
||||
# "cdrtools"
|
||||
# "clinfo"
|
||||
# "dmidecode"
|
||||
@@ -122,6 +124,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"
|
||||
@@ -135,6 +138,7 @@ in
|
||||
"nmon"
|
||||
# "node2nix"
|
||||
# "oathToolkit" # for oathtool
|
||||
"objdump"
|
||||
# "ponymix"
|
||||
"pulsemixer"
|
||||
"python3-repl"
|
||||
@@ -147,6 +151,7 @@ in
|
||||
"sops" # for manually viewing secrets; outside `sane-secrets` (TODO: improve sane-secrets!)
|
||||
"speedtest-cli"
|
||||
# "ssh-to-age"
|
||||
"strings"
|
||||
"sudo"
|
||||
# "tageditor" # music tagging
|
||||
# "unar"
|
||||
@@ -171,6 +176,7 @@ in
|
||||
"ffmpeg"
|
||||
"go2tv" # cast videos to UPNP/DLNA device (i.e. tv).
|
||||
"imagemagick"
|
||||
"sane-cast" # cast videos to UPNP/DLNA, with compatibility
|
||||
"sox"
|
||||
"yt-dlp"
|
||||
];
|
||||
@@ -209,6 +215,7 @@ in
|
||||
|
||||
backblaze-b2 = {};
|
||||
|
||||
blanket.buildCost = 1;
|
||||
blanket.sandbox.method = "bwrap";
|
||||
blanket.sandbox.whitelistAudio = true;
|
||||
# blanket.sandbox.whitelistDbus = [ "user" ]; # TODO: untested
|
||||
@@ -261,34 +268,42 @@ in
|
||||
ddrescue.sandbox.method = "landlock"; # TODO:sandbox: untested
|
||||
ddrescue.sandbox.autodetectCliPaths = "existingOrParent";
|
||||
|
||||
# auth token, preferences
|
||||
delfin.buildCost = 1;
|
||||
delfin.sandbox.method = "bwrap";
|
||||
delfin.sandbox.whitelistAudio = true;
|
||||
delfin.sandbox.whitelistDbus = [ "user" ]; # else `mpris` plugin crashes the player
|
||||
delfin.sandbox.whitelistDri = true;
|
||||
delfin.sandbox.whitelistWayland = true;
|
||||
delfin.sandbox.net = "clearnet";
|
||||
# auth token, preferences
|
||||
delfin.persist.byStore.private = [ ".config/delfin" ];
|
||||
|
||||
dig.sandbox.method = "bwrap";
|
||||
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
|
||||
|
||||
dtrx.sandbox.method = "bwrap";
|
||||
dtrx.sandbox.whitelistPwd = true;
|
||||
dtrx.sandbox.autodetectCliPaths = "existing"; #< for the archive
|
||||
|
||||
duplicity = {};
|
||||
|
||||
e2fsprogs.sandbox.method = "landlock";
|
||||
@@ -301,11 +316,13 @@ in
|
||||
|
||||
eg25-control = {};
|
||||
|
||||
electrum.buildCost = 1;
|
||||
electrum.sandbox.method = "bwrap"; # TODO:sandbox: untested
|
||||
electrum.sandbox.net = "all"; # TODO: probably want to make this run behind a VPN, always
|
||||
electrum.sandbox.whitelistWayland = true;
|
||||
electrum.persist.byStore.cryptClearOnBoot = [ ".electrum" ]; #< TODO: use XDG dirs!
|
||||
|
||||
endless-sky.buildCost = 1;
|
||||
endless-sky.persist.byStore.plaintext = [ ".local/share/endless-sky" ];
|
||||
endless-sky.sandbox.method = "bwrap";
|
||||
endless-sky.sandbox.whitelistAudio = true;
|
||||
@@ -344,6 +361,7 @@ in
|
||||
".persist/plaintext"
|
||||
];
|
||||
|
||||
ffmpeg.buildCost = 1;
|
||||
ffmpeg.sandbox.method = "bwrap";
|
||||
ffmpeg.sandbox.autodetectCliPaths = "existingFileOrParent"; # it outputs uncreated files -> parent dir needs mounting
|
||||
|
||||
@@ -361,7 +379,9 @@ in
|
||||
|
||||
fluffychat-moby.persist.byStore.plaintext = [ ".local/share/chat.fluffy.fluffychat" ];
|
||||
|
||||
font-manager.buildCost = 1;
|
||||
font-manager.sandbox.method = "bwrap";
|
||||
font-manager.sandbox.whitelistWayland = true;
|
||||
font-manager.packageUnwrapped = pkgs.rmDbusServicesInPlace (pkgs.font-manager.override {
|
||||
# build without the "Google Fonts" integration feature, to save closure / avoid webkitgtk_4_0
|
||||
withWebkit = false;
|
||||
@@ -396,8 +416,9 @@ in
|
||||
# TODO: we can populate gh's stuff statically; it even lets us use the same oauth across machines
|
||||
gh.persist.byStore.private = [ ".config/gh" ];
|
||||
|
||||
gimp.buildCost = 1;
|
||||
gimp.sandbox.method = "bwrap";
|
||||
gimp.sandbox.net = "clearnet"; #< for Xwayland
|
||||
gimp.sandbox.whitelistX = true;
|
||||
gimp.sandbox.whitelistWayland = true;
|
||||
gimp.sandbox.extraHomePaths = [
|
||||
"Pictures/albums"
|
||||
@@ -415,32 +436,44 @@ in
|
||||
"/tmp" # "Cannot open display:" if it can't mount /tmp 👀
|
||||
];
|
||||
|
||||
"gnome.gnome-calculator".buildCost = 1;
|
||||
"gnome.gnome-calculator".sandbox.method = "bwrap";
|
||||
"gnome.gnome-calculator".sandbox.whitelistWayland = true;
|
||||
|
||||
"gnome.gnome-calendar".buildCost = 1;
|
||||
# gnome-calendar surely has data to persist, but i use it strictly to do date math, not track events.
|
||||
"gnome.gnome-calendar".sandbox.method = "bwrap";
|
||||
"gnome.gnome-calendar".sandbox.whitelistWayland = true;
|
||||
|
||||
"gnome.gnome-clocks".buildCost = 1;
|
||||
"gnome.gnome-clocks".sandbox.method = "bwrap";
|
||||
"gnome.gnome-clocks".sandbox.whitelistWayland = true;
|
||||
"gnome.gnome-clocks".suggestedPrograms = [ "dconf" ];
|
||||
|
||||
# gnome-disks
|
||||
"gnome.gnome-disk-utility".buildCost = 1;
|
||||
"gnome.gnome-disk-utility".sandbox.method = "bwrap";
|
||||
"gnome.gnome-disk-utility".sandbox.whitelistDbus = [ "system" ];
|
||||
"gnome.gnome-disk-utility".sandbox.whitelistWayland = true;
|
||||
"gnome.gnome-disk-utility".sandbox.extraHomePaths = [
|
||||
"tmp"
|
||||
"use/iso"
|
||||
# TODO: probably need /dev and such
|
||||
];
|
||||
|
||||
# seahorse: dump gnome-keyring secrets.
|
||||
"gnome.seahorse".buildCost = 1;
|
||||
# N.B.: it can also manage ~/.ssh keys, but i explicitly don't add those to the sandbox for now.
|
||||
"gnome.seahorse".sandbox.method = "bwrap";
|
||||
"gnome.seahorse".sandbox.whitelistDbus = [ "user" ];
|
||||
"gnome.seahorse".sandbox.whitelistWayland = true;
|
||||
|
||||
gnome-2048.buildCost = 1;
|
||||
gnome-2048.sandbox.method = "bwrap";
|
||||
gnome-2048.sandbox.whitelistWayland = true;
|
||||
gnome-2048.persist.byStore.plaintext = [ ".local/share/gnome-2048/scores" ];
|
||||
|
||||
gnome-frog.buildCost = 1;
|
||||
gnome-frog.sandbox.method = "bwrap";
|
||||
gnome-frog.sandbox.whitelistWayland = true;
|
||||
gnome-frog.sandbox.whitelistDbus = [ "user" ];
|
||||
@@ -467,6 +500,7 @@ in
|
||||
# 1. no number may appear unshaded more than once in the same row/column
|
||||
# 2. no two shaded tiles can be direct N/S/E/W neighbors
|
||||
# - win once (1) and (2) are satisfied
|
||||
"gnome.hitori".buildCost = 1;
|
||||
"gnome.hitori".sandbox.method = "bwrap";
|
||||
"gnome.hitori".sandbox.whitelistWayland = true;
|
||||
|
||||
@@ -496,6 +530,7 @@ in
|
||||
grim.sandbox.autodetectCliPaths = "existingOrParent";
|
||||
grim.sandbox.whitelistWayland = true;
|
||||
|
||||
hase.buildCost = 1;
|
||||
hase.sandbox.method = "bwrap";
|
||||
hase.sandbox.net = "clearnet";
|
||||
hase.sandbox.whitelistAudio = true;
|
||||
@@ -509,16 +544,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" ];
|
||||
|
||||
@@ -526,6 +551,7 @@ in
|
||||
# N.B.: inetutils' `ping` is shadowed by iputils' ping (by nixos, intentionally).
|
||||
inetutils.sandbox.method = "landlock"; # want to keep the same netns, at least.
|
||||
|
||||
inkscape.buildCost = 1;
|
||||
inkscape.sandbox.method = "bwrap";
|
||||
inkscape.sandbox.whitelistWayland = true;
|
||||
inkscape.sandbox.extraHomePaths = [
|
||||
@@ -551,6 +577,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";
|
||||
@@ -573,6 +603,7 @@ in
|
||||
"/proc"
|
||||
];
|
||||
|
||||
krita.buildCost = 1;
|
||||
krita.sandbox.method = "bwrap";
|
||||
krita.sandbox.whitelistWayland = true;
|
||||
krita.sandbox.autodetectCliPaths = "existing";
|
||||
@@ -593,6 +624,7 @@ in
|
||||
libnotify.sandbox.method = "bwrap";
|
||||
libnotify.sandbox.whitelistDbus = [ "user" ]; # notify-send
|
||||
|
||||
losslesscut-bin.buildCost = 1;
|
||||
losslesscut-bin.sandbox.method = "bwrap";
|
||||
losslesscut-bin.sandbox.extraHomePaths = [
|
||||
"Music"
|
||||
@@ -608,27 +640,16 @@ in
|
||||
losslesscut-bin.sandbox.whitelistX = true;
|
||||
|
||||
lsof.sandbox.method = "capshonly"; # lsof doesn't sandbox under bwrap or even landlock w/ full access to /
|
||||
lsof.sandbox.capabilities = [ "dac_override" "sys_ptrace" ];
|
||||
|
||||
lua = {};
|
||||
|
||||
"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;
|
||||
|
||||
# actual monero blockchain (not wallet/etc; safe to delete, just slow to regenerate)
|
||||
monero-gui.buildCost = 1;
|
||||
# XXX: is it really safe to persist this? it doesn't have info that could de-anonymize if captured?
|
||||
monero-gui.persist.byStore.plaintext = [ ".bitmonero" ];
|
||||
monero-gui.sandbox.method = "bwrap";
|
||||
@@ -637,6 +658,7 @@ in
|
||||
"records/finance/cryptocurrencies/monero"
|
||||
];
|
||||
|
||||
mumble.buildCost = 1;
|
||||
mumble.persist.byStore.private = [ ".local/share/Mumble" ];
|
||||
|
||||
nano.sandbox.method = "bwrap";
|
||||
@@ -740,11 +762,14 @@ in
|
||||
pulsemixer.sandbox.method = "landlock";
|
||||
pulsemixer.sandbox.whitelistAudio = true;
|
||||
|
||||
pwvucontrol.buildCost = 1;
|
||||
pwvucontrol.sandbox.method = "bwrap";
|
||||
pwvucontrol.sandbox.whitelistAudio = true;
|
||||
pwvucontrol.sandbox.whitelistDri = true; # else perf on moby is unusable
|
||||
pwvucontrol.sandbox.whitelistWayland = true;
|
||||
|
||||
python3-repl.packageUnwrapped = pkgs.python3.withPackages (ps: with ps; [
|
||||
psutil
|
||||
requests
|
||||
]);
|
||||
python3-repl.sandbox.method = "bwrap";
|
||||
@@ -755,7 +780,7 @@ in
|
||||
];
|
||||
|
||||
qemu.sandbox.enable = false; #< it's a launcher
|
||||
qemu.slowToBuild = true;
|
||||
qemu.buildCost = 2;
|
||||
|
||||
rsync.sandbox.method = "bwrap";
|
||||
rsync.sandbox.net = "clearnet";
|
||||
@@ -763,10 +788,9 @@ in
|
||||
|
||||
rustc = {};
|
||||
|
||||
sane-open-desktop.sandbox.enable = false; #< trivial script, and all our deps are sandboxed
|
||||
sane-open-desktop.suggestedPrograms = [
|
||||
"gdbus"
|
||||
];
|
||||
sane-cast = {}; #< TODO: sandbox this the same way i sandbox go2tv
|
||||
|
||||
sane-die-with-parent.sandbox.enable = false; #< it's a launcher; can't sandbox
|
||||
|
||||
screen.sandbox.enable = false; #< tty; needs to run anything
|
||||
|
||||
@@ -774,6 +798,7 @@ in
|
||||
sequoia.sandbox.whitelistPwd = true;
|
||||
sequoia.sandbox.autodetectCliPaths = true;
|
||||
|
||||
shattered-pixel-dungeon.buildCost = 1;
|
||||
shattered-pixel-dungeon.persist.byStore.plaintext = [ ".local/share/.shatteredpixel/shattered-pixel-dungeon" ];
|
||||
shattered-pixel-dungeon.sandbox.method = "bwrap";
|
||||
shattered-pixel-dungeon.sandbox.whitelistAudio = true;
|
||||
@@ -781,6 +806,7 @@ in
|
||||
shattered-pixel-dungeon.sandbox.whitelistWayland = true;
|
||||
|
||||
# printer/filament settings
|
||||
slic3r.buildCost = 1;
|
||||
slic3r.persist.byStore.plaintext = [ ".Slic3r" ];
|
||||
|
||||
slurp.sandbox.method = "bwrap";
|
||||
@@ -795,12 +821,13 @@ in
|
||||
sops.sandbox.method = "bwrap"; # TODO:sandbox: untested
|
||||
sops.sandbox.extraHomePaths = [
|
||||
".config/sops"
|
||||
"dev/nixos"
|
||||
"nixos"
|
||||
# TODO: sops should only need access to knowledge/secrets,
|
||||
# except that i currently put its .sops.yaml config in the root of ~/knowledge
|
||||
"knowledge"
|
||||
];
|
||||
|
||||
soundconverter.buildCost = 1;
|
||||
soundconverter.sandbox.method = "bwrap";
|
||||
soundconverter.sandbox.whitelistWayland = true;
|
||||
soundconverter.sandbox.extraHomePaths = [
|
||||
@@ -818,6 +845,7 @@ in
|
||||
sox.sandbox.autodetectCliPaths = "existingFileOrParent";
|
||||
sox.sandbox.whitelistAudio = true;
|
||||
|
||||
space-cadet-pinball.buildCost = 1;
|
||||
space-cadet-pinball.persist.byStore.plaintext = [ ".local/share/SpaceCadetPinball" ];
|
||||
space-cadet-pinball.sandbox.method = "bwrap";
|
||||
space-cadet-pinball.sandbox.whitelistAudio = true;
|
||||
@@ -829,6 +857,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";
|
||||
@@ -836,6 +866,7 @@ in
|
||||
subversion.sandbox.whitelistPwd = true;
|
||||
sudo.sandbox.enable = false;
|
||||
|
||||
superTux.buildCost = 1;
|
||||
superTux.sandbox.method = "bwrap";
|
||||
superTux.sandbox.wrapperType = "inplace"; # package Makefile incorrectly installs to $out/games/superTux instead of $out/share/games
|
||||
superTux.sandbox.whitelistAudio = true;
|
||||
@@ -854,12 +885,14 @@ in
|
||||
|
||||
tdesktop.persist.byStore.private = [ ".local/share/TelegramDesktop" ];
|
||||
|
||||
tokodon.buildCost = 1;
|
||||
tokodon.persist.byStore.private = [ ".cache/KDE/tokodon" ];
|
||||
|
||||
tree.sandbox.method = "landlock";
|
||||
tree.sandbox.autodetectCliPaths = true;
|
||||
tree.sandbox.whitelistPwd = true;
|
||||
|
||||
tumiki-fighters.buildCost = 1;
|
||||
tumiki-fighters.sandbox.method = "bwrap";
|
||||
tumiki-fighters.sandbox.whitelistAudio = true;
|
||||
tumiki-fighters.sandbox.whitelistDri = true; #< not strictly necessary, but triples CPU perf
|
||||
@@ -878,12 +911,16 @@ in
|
||||
"/sys/bus/usb"
|
||||
];
|
||||
|
||||
valgrind.buildCost = 1;
|
||||
valgrind.sandbox.enable = false; #< it's a launcher: can't sandbox
|
||||
|
||||
visidata.sandbox.method = "bwrap"; # TODO:sandbox: untested
|
||||
visidata.sandbox.autodetectCliPaths = true;
|
||||
|
||||
# `vulkaninfo`, `vkcube`
|
||||
vulkan-tools.sandbox.method = "landlock";
|
||||
|
||||
vvvvvv.buildCost = 1;
|
||||
vvvvvv.sandbox.method = "bwrap";
|
||||
vvvvvv.sandbox.whitelistAudio = true;
|
||||
vvvvvv.sandbox.whitelistDri = true; #< playable without, but burns noticably more CPU
|
||||
@@ -904,6 +941,7 @@ in
|
||||
wget.sandbox.net = "all";
|
||||
wget.sandbox.whitelistPwd = true; # saves to pwd by default
|
||||
|
||||
whalebird.buildCost = 1;
|
||||
whalebird.persist.byStore.private = [ ".config/Whalebird" ];
|
||||
|
||||
# `wg`, `wg-quick`
|
||||
@@ -922,7 +960,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
|
||||
|
@@ -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 = {
|
||||
@@ -9,6 +14,8 @@
|
||||
};
|
||||
};
|
||||
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
@@ -20,6 +27,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 = ''
|
||||
|
@@ -2,10 +2,7 @@
|
||||
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p blast-ugjka
|
||||
# vim: set filetype=python :
|
||||
|
||||
import ctypes
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
@@ -19,50 +16,6 @@ DEVICE_MAP = {
|
||||
"[LG] webOS TV OLED55C9PUA": [ "-usewav" ],
|
||||
}
|
||||
|
||||
def set_pdeathsig(sig=signal.SIGTERM):
|
||||
"""
|
||||
helper function to ensure once parent process exits, its children processes will automatically die.
|
||||
see: <https://stackoverflow.com/a/43152455>
|
||||
see: <https://www.man7.org/linux/man-pages/man2/prctl.2.html>
|
||||
"""
|
||||
libc = ctypes.CDLL("libc.so.6")
|
||||
return libc.prctl(1, sig)
|
||||
|
||||
MY_PID = None
|
||||
|
||||
def reap_children(sig=None, frame=None):
|
||||
global MY_PID
|
||||
# reset SIGTERM handler to avoid recursing
|
||||
signal.signal(signal.SIGTERM, signal.Handlers.SIG_DFL)
|
||||
logger.info("killing all children (of pid %d)", MY_PID)
|
||||
os.killpg(MY_PID, signal.SIGTERM)
|
||||
|
||||
def reap_on_exit():
|
||||
"""
|
||||
catch when the parent exits, and map that to SIGTERM for this process.
|
||||
when this process receives SIGTERM, also terminate all descendent processes.
|
||||
|
||||
this is done because:
|
||||
1. mpv invokes this, but (potentially) via the sandbox wrapper.
|
||||
2. when mpv exits, it `SIGKILL`s that sandbox wrapper.
|
||||
3. bwrap does not pass SIGKILL or SIGTERM to its child.
|
||||
4. hence, we neither receive that signal NOR can we pass it on simply by killing our immediate children
|
||||
(since any bwrap'd children wouldn't pass that signal on...)
|
||||
really, the proper fix would be on mpv's side:
|
||||
- mpv should create a new process group when it launches a command, and kill that process group on exit.
|
||||
or fix this in the sandbox wrapper:
|
||||
- why *doesn't* bwrap forward the signals?
|
||||
- there's --die-with-parent, but i can't apply that *system wide* and expect reasonably behavior
|
||||
<https://github.com/containers/bubblewrap/issues/529>
|
||||
"""
|
||||
global MY_PID
|
||||
MY_PID = os.getpid()
|
||||
# create a new process group, pgid = gid
|
||||
os.setpgid(MY_PID, MY_PID)
|
||||
|
||||
set_pdeathsig(signal.SIGTERM)
|
||||
signal.signal(signal.SIGTERM, reap_children)
|
||||
|
||||
def get_ranked_ip_addrs():
|
||||
"""
|
||||
return the IP addresses most likely to be LAN addresses
|
||||
@@ -96,8 +49,6 @@ class BlastDriver:
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
# this pdeathsig isn't necessary; seems it might result in leaked pulse outputs
|
||||
# preexec_fn=set_pdeathsig
|
||||
)
|
||||
self.blast_flags = list(blast_flags)
|
||||
self.receiver_names = []
|
||||
@@ -202,15 +153,11 @@ def main():
|
||||
logging.basicConfig()
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
reap_on_exit()
|
||||
|
||||
blast = try_blast()
|
||||
|
||||
if blast is not None:
|
||||
logger.info("waiting until blast exits")
|
||||
blast.blast.wait()
|
||||
|
||||
reap_children()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@@ -44,7 +44,7 @@ in
|
||||
# might be possible to remove this, but kinda hard to see a clean way.
|
||||
"--sane-sandbox-keep-namespace" "pid"
|
||||
];
|
||||
suggestedPrograms = [ "blast-ugjka" ];
|
||||
suggestedPrograms = [ "blast-ugjka" "sane-die-with-parent" ];
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf cfg.enabled [ 9000 ];
|
||||
|
@@ -99,18 +99,12 @@ in
|
||||
type = types.listOf transitionType;
|
||||
default = [];
|
||||
};
|
||||
configFile = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.writeText "bonsai_tree.json" (builtins.toJSON cfg.config.transitions);
|
||||
description = ''
|
||||
configuration file to pass to bonsai.
|
||||
usually auto-generated from the sibling options; exposed mainly for debugging or convenience.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fs.".config/bonsai/bonsai_tree.json".symlink.text = builtins.toJSON cfg.config.transitions;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.extraRuntimePaths = [
|
||||
"/" #< just needs "bonsai", but needs to create it first...
|
||||
@@ -118,18 +112,10 @@ 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" ];
|
||||
# nice -n -11 chosen arbitrarily. i hope this will allow for faster response to inputs, but without audio underruns (pipewire is -21, dino -15-ish)
|
||||
command = "nice -n -11 bonsaid -t $HOME/.config/bonsai/bonsai_tree.json";
|
||||
cleanupCommand = "rm -f $XDG_RUNTIME_DIR/bonsai";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -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'
|
||||
'';
|
||||
|
@@ -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 = {
|
||||
# add --verbose for more debugging
|
||||
ExecStart = "${cfg.package}/bin/gnome-calls --daemon";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
environment.G_MESSAGES_DEBUG = "all";
|
||||
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
|
||||
# add --verbose for more debugging
|
||||
command = "env G_MESSAGES_DEBUG=all gnome-calls --daemon";
|
||||
};
|
||||
};
|
||||
programs.calls = lib.mkIf cfg.enabled {
|
||||
|
@@ -1,6 +1,8 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.celeste64 = {
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistDri = true;
|
||||
|
@@ -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";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -13,6 +13,8 @@
|
||||
'';
|
||||
});
|
||||
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap"; # landlock gives: _multiprocessing.SemLock: Permission Denied
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistDbus = [ "user" ]; # mpris
|
||||
|
34
hosts/common/programs/cups.nix
Normal file
34
hosts/common/programs/cups.nix
Normal file
@@ -0,0 +1,34 @@
|
||||
# docs: <https://wiki.nixos.org/wiki/Printing>
|
||||
# to add a printer:
|
||||
# 1. <http://localhost:631/admin/>
|
||||
# 2. click "find new printers" and follow prompts
|
||||
# - prefer to use the "Generic IPP Everywhere Printer" driver
|
||||
# alternatively, add/modify printers by running
|
||||
# - `system-config-printer`
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.cups;
|
||||
in
|
||||
{
|
||||
sane.programs.cups = {
|
||||
suggestedPrograms = [
|
||||
"system-config-printer"
|
||||
];
|
||||
};
|
||||
sane.programs.system-config-printer = {};
|
||||
|
||||
services.printing = lib.mkIf cfg.enabled {
|
||||
enable = true;
|
||||
startWhenNeeded = false; #< a.k.a. socket activated?
|
||||
# webInterface = false;
|
||||
# logLevel = "debug"; # default: "info"
|
||||
# extraConfig = "<lines ... >";
|
||||
# drivers = [ <cups driver packages...> ]
|
||||
};
|
||||
# services.avahi = lib.mkIf cfg.enabled {
|
||||
# # only needed for wireless printing
|
||||
# enable = true;
|
||||
# nssmdns4 = true;
|
||||
# openFirewall = true;
|
||||
# };
|
||||
}
|
35
hosts/common/programs/curlftpfs.nix
Normal file
35
hosts/common/programs/curlftpfs.nix
Normal file
@@ -0,0 +1,35 @@
|
||||
{ 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
|
||||
'';
|
||||
});
|
||||
|
||||
# TODO: try to sandbox this better? maybe i can have fuse (unsandboxed) invoke curlftpfs (sandboxed)?
|
||||
# - landlock gives EPERM
|
||||
# - bwrap just silently doesn't mount it, maybe because of setuid stuff around fuse?
|
||||
# sandbox.method = "capshonly";
|
||||
# sandbox.net = "all";
|
||||
# sandbox.capabilities = [
|
||||
# "sys_admin"
|
||||
# "sys_module"
|
||||
# ];
|
||||
};
|
||||
}
|
@@ -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?),
|
||||
|
@@ -21,11 +21,17 @@
|
||||
./chatty.nix
|
||||
./conky
|
||||
./cozy.nix
|
||||
./cups.nix
|
||||
./curlftpfs.nix
|
||||
./dconf.nix
|
||||
./deadd-notification-center
|
||||
./dialect.nix
|
||||
./dino.nix
|
||||
./dissent.nix
|
||||
./dtrx.nix
|
||||
./eg25-control.nix
|
||||
./element-desktop.nix
|
||||
./engrampa.nix
|
||||
./epiphany.nix
|
||||
./evince.nix
|
||||
./fcitx5.nix
|
||||
@@ -50,15 +56,17 @@
|
||||
./gpodder.nix
|
||||
./grimshot.nix
|
||||
./gthumb.nix
|
||||
./gtkcord4.nix
|
||||
./gvfs.nix
|
||||
./handbrake.nix
|
||||
./helix.nix
|
||||
./htop
|
||||
./imagemagick.nix
|
||||
./jellyfin-media-player.nix
|
||||
./kdenlive.nix
|
||||
./komikku.nix
|
||||
./koreader
|
||||
./less.nix
|
||||
./lftp.nix
|
||||
./libreoffice.nix
|
||||
./lemoa.nix
|
||||
./loupe.nix
|
||||
@@ -66,17 +74,20 @@
|
||||
./megapixels.nix
|
||||
./mepo.nix
|
||||
./mimeo
|
||||
./modemmanager.nix
|
||||
./mopidy.nix
|
||||
./mpv
|
||||
./msmtp.nix
|
||||
./nautilus.nix
|
||||
./neovim.nix
|
||||
./networkmanager.nix
|
||||
./newsflash.nix
|
||||
./nheko.nix
|
||||
./nicotine-plus.nix
|
||||
./nix-index.nix
|
||||
./notejot.nix
|
||||
./ntfy-sh.nix
|
||||
./objdump.nix
|
||||
./obsidian.nix
|
||||
./offlineimap.nix
|
||||
./open-in-mpv.nix
|
||||
@@ -87,7 +98,11 @@
|
||||
./rhythmbox.nix
|
||||
./ripgrep.nix
|
||||
./rofi
|
||||
./rtkit.nix
|
||||
./s6-rc.nix
|
||||
./sane-input-handler
|
||||
./sane-open.nix
|
||||
./sane-sandboxed.nix
|
||||
./sane-screenshot.nix
|
||||
./sane-scripts.nix
|
||||
./schlock.nix
|
||||
@@ -118,7 +133,7 @@
|
||||
./wine.nix
|
||||
./wireplumber.nix
|
||||
./wireshark.nix
|
||||
./wob
|
||||
./wpa_supplicant.nix
|
||||
./wvkbd.nix
|
||||
./xarchiver.nix
|
||||
./xdg-desktop-portal.nix
|
||||
@@ -129,6 +144,7 @@
|
||||
./zathura.nix
|
||||
./zeal.nix
|
||||
./zecwallet-lite.nix
|
||||
./zulip.nix
|
||||
./zsh
|
||||
];
|
||||
|
||||
|
@@ -1,15 +1,6 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.dialect = {
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "inplace"; # share/search_providers/ calls back into the binary, weird wrap semantics
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.net = "clearnet";
|
||||
sandbox.extraHomePaths = [
|
||||
".config/dconf" # won't start without it
|
||||
];
|
||||
suggestedPrograms = [ "dconf" ]; #< to persist settings
|
||||
|
||||
packageUnwrapped = pkgs.dialect.overrideAttrs (upstream: {
|
||||
# TODO: send upstream
|
||||
# TODO: figure out how to get audio working
|
||||
@@ -18,5 +9,17 @@
|
||||
pkgs.glib-networking # for TLS
|
||||
];
|
||||
});
|
||||
|
||||
suggestedPrograms = [ "dconf" ]; #< to persist settings
|
||||
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "inplace"; # share/search_providers/ calls back into the binary, weird wrap semantics
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.net = "clearnet";
|
||||
sandbox.extraHomePaths = [
|
||||
".config/dconf" # won't start without it
|
||||
];
|
||||
};
|
||||
}
|
||||
|
@@ -14,22 +14,27 @@
|
||||
# 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)
|
||||
# - TODO: dino should have more optimal niceness/priority to ensure it can process its buffers
|
||||
# - possibly this is solved by enabling RealtimeKit (rtkit)
|
||||
# - on lappy/desktop, right-clicking the mic button allows to toggle audio devices, but impossible to trigger this on moby/touch screen!
|
||||
# - TODO: see if Dino calls work better with `echo full > /sys/kernel/debug/sched/preempt`
|
||||
#
|
||||
# probably fixed:
|
||||
# - once per 1-2 minutes dino will temporarily drop mic input:
|
||||
# - `rtp-WRNING: plugin.vala:148: Warning in pipeline: Can't record audio fast enough
|
||||
# - `rtp-WARNING: plugin.vala:148: Warning in pipeline: Can't record audio fast enough
|
||||
# - this was *partially* fixed by bumping the pipewire mic buffer to 2048 samples (from ~512)
|
||||
# - this was further fixed by setting PULSE_LATENCY_MSEC=20.
|
||||
# - possibly Dino should be updated internally: `info.rate / 100` -> `info.rate / 50`.
|
||||
# - i think that affects the batching for echo cancellation, adaptive gain control, etc.
|
||||
# - dino *should* be able to use Pipewire directly for calls instead of going through pulse, but had trouble achieving that in actuality
|
||||
#
|
||||
{ config, lib, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.dino;
|
||||
in
|
||||
@@ -45,6 +50,33 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
packageUnwrapped = (pkgs.dino.override {
|
||||
# XXX(2024/04/24): build without echo cancelation (i.e. force WITH_VOICE_PROCESSOR to be undefined).
|
||||
# this means that if the other end of the call is on speaker phone, i'm liable to hear my own voice
|
||||
# leave their speaker, enter their mic, and then return to me.
|
||||
# the benefit is a >50% reduction in CPU use. insignificant on any modern PC; make-or-break on a low-power Pinephone.
|
||||
webrtc-audio-processing = null;
|
||||
}).overrideAttrs (upstream: {
|
||||
# i'm updating experimentally to see if it improves call performance.
|
||||
# i don't *think* this is actually necessary; i don't notice any difference.
|
||||
version = "0.4.3-unstable-2024-04-28";
|
||||
src = lib.warnIf (lib.versionOlder "0.4.3" upstream.version) "dino update: safe to remove sane patches" pkgs.fetchFromGitHub {
|
||||
owner = "dino";
|
||||
repo = "dino";
|
||||
rev = "657502955567dd538e56f300e075c7db52e25d74";
|
||||
hash = "sha256-SApJy9FgxxLOB5A/zGtpdFZtSqSiS03vggRrCte1tFE=";
|
||||
};
|
||||
# avoid double-application of upstreamed patches
|
||||
# https://github.com/NixOS/nixpkgs/pull/309265
|
||||
patches = [];
|
||||
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 +100,34 @@ 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";
|
||||
|
||||
# pipewire uses power-of-two buffering for the mic itself (by default), but this env var supports only whole numbers, which isn't quite reconcilable:
|
||||
# - 1024/48000 = 21.33ms
|
||||
# - 2048/48000 = 42.67ms
|
||||
# - 4096/48000 = 85.33ms
|
||||
# also, Dino's likely still doing things in 10ms batches internally.
|
||||
#
|
||||
# note that this number supposedly is just the buffer size which Dino asks Pulse (pipewire) to share with it.
|
||||
# in theory, it's equivalent to adjusting pipewire's quanta setting, and so isn't additive to the existing pipewire buffers.
|
||||
# (and would also be overriden by pipewire's quanta.min setting).
|
||||
# but in practice, setting this seems to have some more effect beyond just the buffer sizes visible in `pw-top`.
|
||||
#
|
||||
# further: decrease the "niceness" of dino, so that it can take precedence over anything else.
|
||||
# ideally this would target just the audio processing, rather than the whole program.
|
||||
# pipewire is the equivalent of `nice -n -21`, so probably don't want to go any more extreme than that.
|
||||
# nice -n -15 chosen arbitrarily; not optimized (and seems to have very little impact in practice anyway).
|
||||
# buffer size:
|
||||
# - 1024 (PULSE_LATENCY_MSEC=20): `pw-top` shows several underruns per second.
|
||||
# - 2048 (PULSE_LATENCY_MSEC=50): `pw-top` shows very few underruns: maybe 1-5 per minute. with voice processor disabled, this works well. with it enabled, i still get gaps in which the mic "disappears".
|
||||
# - 4096 (PULSE_LATENCY_MSEC=100): `pw-top` shows 0 underruns. with voice processor disabled, i seem to be permanently muted. with it enabled, this works well.
|
||||
#
|
||||
# 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=50 nice -n -15 dino";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -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";
|
||||
};
|
||||
};
|
||||
}
|
16
hosts/common/programs/dtrx.nix
Normal file
16
hosts/common/programs/dtrx.nix
Normal file
@@ -0,0 +1,16 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.dtrx = {
|
||||
packageUnwrapped = pkgs.dtrx.override {
|
||||
# `binutils` is the nix wrapper, which reads nix-related env vars
|
||||
# before passing on to e.g. `ld`.
|
||||
# dtrx probably only needs `ar` at runtime, not even `ld`.
|
||||
binutils = pkgs.binutils-unwrapped;
|
||||
# build without rpm support, since `rpm` package doesn't cross-compile.
|
||||
rpm = null;
|
||||
};
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistPwd = true;
|
||||
sandbox.autodetectCliPaths = "existing"; #< for the archive
|
||||
};
|
||||
}
|
74
hosts/common/programs/eg25-control.nix
Normal file
74
hosts/common/programs/eg25-control.nix
Normal file
@@ -0,0 +1,74 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.eg25-control;
|
||||
in
|
||||
{
|
||||
sane.programs.eg25-control = {
|
||||
suggestedPrograms = [ "modemmanager" ];
|
||||
|
||||
services.eg25-control-powered = {
|
||||
description = "eg25-control-powered: power to the Qualcomm eg25 modem used by PinePhone";
|
||||
startCommand = "eg25-control --power-on --verbose";
|
||||
cleanupCommand = "eg25-control --power-off --verbose";
|
||||
# depends = [ "ModemManager" ]
|
||||
};
|
||||
|
||||
services.eg25-control-gps = {
|
||||
# TODO: separate almanac upload from GPS enablement
|
||||
# - don't want to re-upload the almanac everytime the GPS is toggled
|
||||
# - want to upload almanac even when GPS *isn't* enabled, if we have internet connection.
|
||||
description = "eg25-control-gps: background GPS tracking";
|
||||
startCommand = "eg25-control --enable-gps --dump-debug-info --verbose";
|
||||
cleanupCommand = "eg25-control --disable-gps --dump-debug-info --verbose";
|
||||
depends = [ "eg25-control-powered" ];
|
||||
};
|
||||
};
|
||||
|
||||
# TODO: port to s6
|
||||
systemd.services.eg25-control-freshen-agps = lib.mkIf cfg.enabled {
|
||||
description = "keep assisted-GPS data fresh";
|
||||
serviceConfig = {
|
||||
# XXX: this can have a race condition with eg25-control-gps
|
||||
# - eg25-control-gps initiates DL of new/<agps>
|
||||
# - eg25-control-gps tests new/<agps>: it works
|
||||
# - eg25-control-freshen-agps initiates DL of new/<agps>
|
||||
# - eg25-control-gps: moves new/<agps> into cache/
|
||||
# - but it moved the result (possibly incomplete) of eg25-control-freshen-agps, incorrectly
|
||||
# in practice, i don't expect much issue from this.
|
||||
ExecStart = "${cfg.package}/bin/eg25-control --ensure-agps-cache --verbose";
|
||||
Restart = "no";
|
||||
|
||||
User = "eg25-control";
|
||||
WorkingDirectory = "/var/lib/eg25-control";
|
||||
StateDirectory = "eg25-control";
|
||||
};
|
||||
startAt = "hourly"; # this is a bit more than necessary, but idk systemd calendar syntax
|
||||
after = [ "network-online.target" "nss-lookup.target" ];
|
||||
requires = [ "network-online.target" ];
|
||||
# wantedBy = [ "network-online.target" ]; # auto-start immediately after boot
|
||||
};
|
||||
|
||||
users = lib.mkIf cfg.enabled {
|
||||
groups.eg25-control = {};
|
||||
users.eg25-control = {
|
||||
group = "eg25-control";
|
||||
isSystemUser = true;
|
||||
home = "/var/lib/eg25-control";
|
||||
extraGroups = [
|
||||
"dialout" # required to read /dev/ttyUSB1
|
||||
"networkmanager" # required to authenticate with mmcli
|
||||
];
|
||||
};
|
||||
};
|
||||
sane.persist.sys.byStore.plaintext = lib.mkIf cfg.enabled [
|
||||
# to persist agps data, i think.
|
||||
{ user = "eg25-control"; group = "eg25-control"; path = "/var/lib/eg25-control"; }
|
||||
];
|
||||
services.udev.extraRules = let
|
||||
chmod = "${pkgs.coreutils}/bin/chmod";
|
||||
chown = "${pkgs.coreutils}/bin/chown";
|
||||
in ''
|
||||
# make Modem controllable by user
|
||||
DRIVER=="modem-power", RUN+="${chmod} g+w /sys%p/powered", RUN+="${chown} :networkmanager /sys%p/powered"
|
||||
'';
|
||||
}
|
@@ -4,18 +4,29 @@
|
||||
# - <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"
|
||||
];
|
||||
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.net = "clearnet";
|
||||
sandbox.whitelistAudio = true;
|
||||
|
17
hosts/common/programs/engrampa.nix
Normal file
17
hosts/common/programs/engrampa.nix
Normal 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"
|
||||
];
|
||||
};
|
||||
}
|
@@ -23,6 +23,8 @@
|
||||
"tmp"
|
||||
];
|
||||
|
||||
buildCost = 2;
|
||||
|
||||
# XXX(2023/07/08): running on moby without `WEBKIT_DISABLE_SANDBOX...` fails, with:
|
||||
# - `bwrap: Can't make symlink at /var/run: File exists`
|
||||
# this could be due to:
|
||||
|
@@ -1,6 +1,8 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.evince = {
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.autodetectCliPaths = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
|
@@ -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";
|
||||
|
@@ -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"
|
||||
]);
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -207,6 +207,10 @@ in
|
||||
package = pkgs.firefox-extensions.i2p-in-private-browsing;
|
||||
enable = lib.mkDefault config.services.i2p.enable;
|
||||
};
|
||||
i-still-dont-care-about-cookies = {
|
||||
package = pkgs.firefox-extensions.i-still-dont-care-about-cookies;
|
||||
enable = lib.mkDefault true;
|
||||
};
|
||||
open-in-mpv = {
|
||||
# test: `open-in-mpv 'mpv:///open?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ'`
|
||||
package = pkgs.firefox-extensions.open-in-mpv;
|
||||
@@ -310,8 +314,8 @@ in
|
||||
// scrollbar configuration, see: <https://artemis.sh/2023/10/12/scrollbars.html>
|
||||
// style=4 gives rectangular scrollbars
|
||||
// could also enable "always show scrollbars" in about:preferences -- not sure what the actual pref name for that is
|
||||
// note that too-large scrollbars (like 50px wide) tend to obscure content (and make buttons unclickable)
|
||||
defaultPref("widget.non-native-theme.scrollbar.size.override", 20);
|
||||
// note that too-large scrollbars (like 50px wide, even 20px) tend to obscure content (and make buttons unclickable)
|
||||
defaultPref("widget.non-native-theme.scrollbar.size.override", 14);
|
||||
defaultPref("widget.non-native-theme.scrollbar.style", 4);
|
||||
|
||||
// disable inertial/kinetic/momentum scrolling because it just gets in the way on touchpads
|
||||
|
@@ -30,6 +30,30 @@ let
|
||||
nerdfontPkgs = builtins.map
|
||||
(f: pkgs.nerdfonts.override { fonts = [ f ]; })
|
||||
wantedNerdfonts;
|
||||
|
||||
# see: <repo:nixos/nixpkgs:nixos/modules/config/fonts/fontconfig.nix>
|
||||
# and: <repo:nixos/nixpkgs:pkgs/development/libraries/fontconfig/make-fonts-cache.nix>
|
||||
# nixpkgs creates a fontconfig cache, but only when *not* cross compiling.
|
||||
# but the alternative is that fonts are cached purely at runtime, in ~/.cache/fontconfig,
|
||||
# and that needs to either be added to the sandbox of *every* app,
|
||||
# or font-heavy apps are several *seconds* slower to launch.
|
||||
#
|
||||
# TODO: upstream this into `make-fonts-cache.nix`?
|
||||
cache = (pkgs.makeFontsCache { fontDirectories = config.fonts.packages; }).overrideAttrs (upstream: {
|
||||
buildCommand = lib.replaceStrings
|
||||
[ "fc-cache" ]
|
||||
[ "${pkgs.stdenv.hostPlatform.emulator pkgs.buildPackages} ${pkgs.fontconfig.bin}/bin/fc-cache" ]
|
||||
upstream.buildCommand
|
||||
;
|
||||
});
|
||||
cacheConf = pkgs.writeTextDir "etc/fonts/conf.d/01-nixos-cache-cross.conf" ''
|
||||
<?xml version='1.0'?>
|
||||
<!DOCTYPE fontconfig SYSTEM 'urn:fontconfig:fonts.dtd'>
|
||||
<fontconfig>
|
||||
<!-- Pre-generated font caches -->
|
||||
<cachedir>${cache}</cachedir>
|
||||
</fontconfig>
|
||||
'';
|
||||
in
|
||||
{
|
||||
sane.programs.fontconfig = {
|
||||
@@ -64,6 +88,8 @@ in
|
||||
"DejaVu Sans"
|
||||
];
|
||||
};
|
||||
# nixpkgs builds a cache file, but only for non-cross. i want it always, so add my own cache -- but ONLY for cross.
|
||||
fontconfig.confPackages = lib.mkIf (pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform) [ cacheConf ];
|
||||
#vvv enables dejavu_fonts, freefont_ttf, gyre-fonts, liberation_ttf, unifont, noto-fonts-emoji
|
||||
enableDefaultPackages = false;
|
||||
packages = with pkgs; [
|
||||
|
@@ -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";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -2,11 +2,6 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.frozen-bubble = {
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.net = "clearnet"; # net play
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
packageUnwrapped = pkgs.frozen-bubble.overrideAttrs (upstream: {
|
||||
# patch so it stores its dot-files not in root ~.
|
||||
postPatch = (upstream.postPatch or "") + ''
|
||||
@@ -14,6 +9,12 @@
|
||||
--replace-fail '$FBHOME = "$ENV{HOME}/.frozen-bubble"' '$FBHOME = "$ENV{HOME}/.local/share/frozen-bubble"'
|
||||
'';
|
||||
});
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.net = "clearnet"; # net play
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
persist.byStore.plaintext = [
|
||||
".local/share/frozen-bubble" # preferences, high scores
|
||||
|
@@ -8,6 +8,8 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.g4music = {
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistDbus = [ "user" ]; # mpris
|
||||
|
@@ -37,7 +37,7 @@ in
|
||||
# fs.".config/geary".dir = {};
|
||||
# fs.".local/share/folks".dir = {};
|
||||
|
||||
slowToBuild = true; # uses webkitgtk 4.1
|
||||
buildCost = 3; # uses webkitgtk 4.1
|
||||
persist.byStore.private = [
|
||||
# attachments, and email -- contained in a sqlite db
|
||||
".local/share/geary"
|
||||
@@ -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";
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -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";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -3,6 +3,8 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs."gnome.gnome-weather" = {
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "inplace"; #< /share/org.gnome.Weather/org.gnome.Weather file refers to bins by full path
|
||||
sandbox.whitelistWayland = true;
|
||||
|
@@ -2,8 +2,20 @@
|
||||
# - turn the tv off and on again (no, really...)
|
||||
#
|
||||
# SANITY CHECKS:
|
||||
# - `go2tv -u 'https://uninsane.org/share/AmenBreak.mp4'`
|
||||
# - `go2tv -u 'https://uninsane.org/share/Milkbags/AmenBreak.mp4'`
|
||||
# - LGTV: works, but not seekable
|
||||
# - Samsung: "Cannot play video."
|
||||
# - `go2tv -v /mnt/servo/media/Videos/Milkbags/AmenBreak.mp4`
|
||||
# - Samsung: works
|
||||
# - `go2tv -v /mnt/servo/media/Videos/Milkbags/COLIN.webm`
|
||||
# - Samsung: works
|
||||
# - `go2tv -v /mnt/servo/media/Videos/Shows/Lucky.Star/S01/S01E01-The.Girl.who.Dashes.Off.mkv`
|
||||
# - Samsung: error 500
|
||||
# - `go2tv -tc -v /mnt/servo/media/Videos/Shows/Lucky.Star/S01/S01E01-The.Girl.who.Dashes.Off.mkv`
|
||||
# - Samsung: error 500
|
||||
# - note that it still advertized .mkv to the TV
|
||||
# - `cp /mnt/servo/media/Videos/Shows/Lucky.Star/S01/S01E01-The.Girl.who.Dashes.Off.mkv S01E01-The.Girl.who.Dashes.Off.mp4 && go2tv -v S01E01-The.Girl.who.Dashes.Off.mp4`
|
||||
# - Samsung: WORKS
|
||||
# - `go2tv -u 'https://youtu.be/p3G5IXn0K7A'`
|
||||
# - LGTV: FAILS ("this file cannot be recognized")
|
||||
# - no fix via transcoding, altering the URI, etc.
|
||||
@@ -17,6 +29,8 @@
|
||||
# - LGTV: works
|
||||
#
|
||||
# WHEN TO TRANSCODE:
|
||||
# - mkv container + *:
|
||||
# - Samsung: rename to .mp4 and cast that: no need to transcode
|
||||
# - mkv container + mpeg-2 video + AC-3/48k stereo audio:
|
||||
# - LGTV: no transcoding needed
|
||||
# - mkv container + H.264 video + AAC/48k 5.1 audio:
|
||||
@@ -27,6 +41,7 @@
|
||||
# - LGTV: no transcoding needed
|
||||
# - mkv container + H.265 video + E-AC-3/48k stereo audio:
|
||||
# - LGTV: no transcoding needed
|
||||
#
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.go2tv;
|
||||
|
20
hosts/common/programs/gvfs.nix
Normal file
20
hosts/common/programs/gvfs.nix
Normal file
@@ -0,0 +1,20 @@
|
||||
# gvfs is used by e.g. nautilus to mount remote filesystems (ftp://, etc)
|
||||
# TODO: gvfs depends on udisks, depends on gnupg,
|
||||
# and as part of this `keyboxd` gpg daemon gets started and does background work every minute even though i totally don't use it.
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.gvfs;
|
||||
in
|
||||
{
|
||||
sane.programs.gvfs = {
|
||||
packageUnwrapped = pkgs.gvfs.override {
|
||||
# i don't need to mount samba shares, and samba build is expensive/flaky (mostly for cross, but even problematic on native)
|
||||
samba = null;
|
||||
};
|
||||
};
|
||||
|
||||
services.gvfs = {
|
||||
inherit (cfg) package;
|
||||
enable = cfg.enabled;
|
||||
};
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.handbrake = {
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "landlock"; #< also supports bwrap, but landlock ensures we don't write to non-mounted tmpfs dir
|
||||
sandbox.whitelistDbus = [ "user" ]; # notifications
|
||||
sandbox.whitelistWayland = true;
|
||||
@@ -15,7 +17,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;
|
||||
};
|
||||
};
|
||||
|
11
hosts/common/programs/htop/default.nix
Normal file
11
hosts/common/programs/htop/default.nix
Normal file
@@ -0,0 +1,11 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.htop = {
|
||||
sandbox.method = "landlock";
|
||||
sandbox.extraPaths = [
|
||||
"/proc"
|
||||
"/sys/devices"
|
||||
];
|
||||
fs.".config/htop/htoprc".symlink.target = ./htoprc;
|
||||
};
|
||||
}
|
63
hosts/common/programs/htop/htoprc
Normal file
63
hosts/common/programs/htop/htoprc
Normal 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 PRIORITY 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
|
@@ -1,6 +1,8 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.imagemagick = {
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "inplace"; # /etc/ImageMagick-7/delegates.xml refers to bins by absolute path
|
||||
sandbox.whitelistPwd = true;
|
||||
|
@@ -1,6 +1,15 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.kdenlive = {
|
||||
packageUnwrapped = pkgs.kdenlive.override {
|
||||
ffmpeg-full = pkgs.ffmpeg-full.override {
|
||||
# avoid expensive samba build for a feature i don't use
|
||||
withSamba = false;
|
||||
};
|
||||
};
|
||||
|
||||
buildCost = 1;
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.extraHomePaths = [
|
||||
"Music"
|
||||
@@ -14,12 +23,5 @@
|
||||
sandbox.whitelistDbus = [ "user" ]; # notifications
|
||||
sandbox.whitelistDri = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
packageUnwrapped = pkgs.kdenlive.override {
|
||||
ffmpeg-full = pkgs.ffmpeg-full.override {
|
||||
# avoid expensive samba build for a feature i don't use
|
||||
withSamba = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -16,6 +16,8 @@
|
||||
sandbox.whitelistDri = true; #< required
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
buildCost = 2;
|
||||
|
||||
secrets.".local/share/komikku/keyrings/plaintext.keyring" = ../../../secrets/common/komikku_accounts.json.bin;
|
||||
# downloads end up here, and without the toplevel database komikku doesn't know they exist.
|
||||
persist.byStore.plaintext = [
|
||||
|
@@ -47,6 +47,7 @@ in {
|
||||
packageUnwrapped = pkgs.koreader-from-src;
|
||||
sandbox.method = "bwrap"; # sandboxes fine under landlock too, except for FTP
|
||||
sandbox.net = "clearnet";
|
||||
sandbox.whitelistDbus = [ "user" ]; # for opening the web browser via portal
|
||||
sandbox.whitelistDri = true; # reduces startup time and subjective page flip time
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.extraHomePaths = [
|
||||
|
@@ -1,6 +1,7 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.lemoa = {
|
||||
buildCost = 1;
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.net = "clearnet";
|
||||
sandbox.whitelistDbus = [ "user" ]; # for clicking links
|
||||
|
13
hosts/common/programs/lftp.nix
Normal file
13
hosts/common/programs/lftp.nix
Normal file
@@ -0,0 +1,13 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.lftp = {
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.net = "all";
|
||||
sandbox.extraPaths = [
|
||||
"Music"
|
||||
"Videos/local"
|
||||
"Videos/servo"
|
||||
"tmp"
|
||||
];
|
||||
};
|
||||
}
|
@@ -16,7 +16,7 @@
|
||||
"tmp"
|
||||
];
|
||||
|
||||
slowToBuild = true;
|
||||
buildCost = 3;
|
||||
|
||||
# disable first-run stuff
|
||||
fs.".config/libreoffice/4/user/registrymodifications.xcu".symlink.text = ''
|
||||
|
@@ -30,6 +30,7 @@
|
||||
];
|
||||
|
||||
mime.associations = {
|
||||
"image/avif" = "org.gnome.Loupe.desktop";
|
||||
"image/gif" = "org.gnome.Loupe.desktop";
|
||||
"image/heif" = "org.gnome.Loupe.desktop"; # apple codec
|
||||
"image/png" = "org.gnome.Loupe.desktop";
|
||||
|
@@ -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";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,6 +1,16 @@
|
||||
{ ... }:
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.megapixels = {
|
||||
packageUnwrapped = pkgs.megapixels.overrideAttrs (upstream: {
|
||||
postPatch = (upstream.postPatch or "") + ''
|
||||
# 2024/04/21: patch it to save photos in a more specific directory
|
||||
substituteInPlace src/process_pipeline.c \
|
||||
--replace-fail 'XDG_PICTURES_DIR' 'XDG_PHOTOS_DIR'
|
||||
# 2024/04/21: patch it so the folder button works
|
||||
substituteInPlace src/main.c \
|
||||
--replace-fail 'g_get_user_special_dir(G_USER_DIRECTORY_PICTURES)' 'getenv("XDG_PHOTOS_DIR")'
|
||||
'';
|
||||
});
|
||||
# megapixels sandboxing is tough:
|
||||
# if misconfigured, preview will alternately be OK, black, or only 1/4 of it will be rendered -- with no obvious pattern.
|
||||
# adding all of ~ to the sandbox will sometimes (?) fix the flakiness, even when `strace` doesn't show it accessing any files...
|
||||
@@ -16,10 +26,8 @@
|
||||
sandbox.extraHomePaths = [
|
||||
".config/dconf" #< else it segfaults during post-process
|
||||
# ".config/megapixels"
|
||||
".local/share/applications" #< needed for viewing photos, until i can sort out the portal stuff
|
||||
".cache/mesa_shader_cache" # loads way faster
|
||||
"tmp"
|
||||
"Pictures" #< TODO: make this Pictures/Photos and save photos there
|
||||
"Pictures/Photos"
|
||||
# also it addresses a lot via relative path.
|
||||
];
|
||||
sandbox.extraPaths = [
|
||||
@@ -37,6 +45,12 @@
|
||||
sandbox.extraRuntimePaths = [
|
||||
"dconf" #< else it's very spammy, and slow
|
||||
];
|
||||
sandbox.extraConfig = [
|
||||
# XXX(2024/04/21): without this it fails to convert .dng -> .jpg.
|
||||
# "bwrap: open /proc/34/ns/ns failed: No such file or directory"
|
||||
"--sane-sandbox-keep-namespace" "pid"
|
||||
];
|
||||
|
||||
suggestedPrograms = [ "dconf" ]; #< not sure if necessary
|
||||
};
|
||||
}
|
||||
|
@@ -57,6 +57,7 @@ in
|
||||
desktopName = "Mimeo";
|
||||
exec = "mimeo %U";
|
||||
comment = "Open files by MIME-type or file name using regular expressions.";
|
||||
noDisplay = true;
|
||||
})
|
||||
];
|
||||
|
||||
|
38
hosts/common/programs/modemmanager.nix
Normal file
38
hosts/common/programs/modemmanager.nix
Normal file
@@ -0,0 +1,38 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.modemmanager;
|
||||
in
|
||||
{
|
||||
sane.programs.modemmanager = {
|
||||
# mmcli needs /run/current-system/sw/share/dbus-1 files to function
|
||||
enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true;
|
||||
};
|
||||
|
||||
systemd.services.ModemManager = lib.mkIf cfg.enabled {
|
||||
aliases = [ "dbus-org.freedesktop.ModemManager1.service" ];
|
||||
after = [ "polkit.service" ];
|
||||
requires = [ "polkit.service" ];
|
||||
wantedBy = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
Type = "dbus";
|
||||
BusName = "org.freedesktop.ModemManager1";
|
||||
# only if started with `--debug` does mmcli let us issue AT commands like
|
||||
# `mmcli --modem any --command=<AT_CMD>`
|
||||
ExecStart = "${cfg.package}/bin/ModemManager --debug";
|
||||
# --debug sets DEBUG level logging: so reset
|
||||
ExecStartPost = "${cfg.package}/bin/mmcli --set-logging=INFO";
|
||||
|
||||
Restart = "on-abort";
|
||||
StandardError = "null";
|
||||
CapabilityBoundingSet = "CAP_SYS_ADMIN CAP_NET_ADMIN";
|
||||
ProtectSystem = true;
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
RestrictAddressFamilies = "AF_NETLINK AF_UNIX AF_QIPCRTR";
|
||||
NoNewPrivileges = true;
|
||||
};
|
||||
};
|
||||
|
||||
# so that ModemManager can discover when the modem appears
|
||||
services.udev.packages = lib.mkIf cfg.enabled [ cfg.package ];
|
||||
}
|
@@ -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,90 +40,152 @@
|
||||
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()
|
||||
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
|
||||
postPatch = (upstream.postPatch or "") + ''
|
||||
### patch so touch controls work well with sway 1.9+
|
||||
### in particular, "mouse.hover" is *always* false for touch events (i guess this is a bug in mpv?)
|
||||
### and a touch release event is always followed by a mouse move to the cursor (that's a sway thing) which doesn't make sense.
|
||||
# 1. always listen for mbtn_left events, even before a hover event would activate a zone:
|
||||
substituteInPlace src/uosc/lib/cursor.lua \
|
||||
--replace-fail \
|
||||
"if binding and cursor:collides_with(zone.hitbox)" \
|
||||
"if binding"
|
||||
# 2. uosc already simulates mouse movements on touch down, but because of the hover handling, they get misunderstood as mouse leaves.
|
||||
# so, bypass the cursor:leave() check.
|
||||
substituteInPlace src/uosc/lib/cursor.lua \
|
||||
--replace-fail \
|
||||
"handle_mouse_pos(nil, mp.get_property_native('mouse-pos'))" \
|
||||
"local mpos = mp.get_property_native('mouse-pos')
|
||||
cursor:move(mpos.x, mpos.y)
|
||||
cursor.hover_raw = mpos.hover"
|
||||
# 3. explicitly fire a cursor:leave on touch release, so that all zones are deactivated (and control visibility goes back to default state)
|
||||
substituteInPlace src/uosc/lib/cursor.lua \
|
||||
--replace-fail \
|
||||
"cursor:create_handler('primary_up')" \
|
||||
"function(...)
|
||||
cursor:trigger('primary_up', ...)
|
||||
if not cursor.hover_raw then
|
||||
cursor:leave()
|
||||
end
|
||||
end"
|
||||
# 4. sometimes we get a touch movement shortly AFTER touch is released:
|
||||
# detect that and ignore it
|
||||
substituteInPlace src/uosc/lib/cursor.lua \
|
||||
--replace-fail \
|
||||
"cursor:move(mouse.x, mouse.y)" \
|
||||
"local last_down = cursor.last_event['primary_down'] or { time = 0 }
|
||||
local last_up = cursor.last_event['primary_up'] or { time = 0 }
|
||||
if cursor.hover_raw or last_down.time >= last_up.time then cursor:move(mouse.x, mouse.y) end"
|
||||
|
||||
### patch so that uosc volume control is routed to sane_sysvol.
|
||||
### this is particularly nice for moby, because it avoids the awkwardness that system volume
|
||||
### is hard to adjust while screen is on.
|
||||
### previously i used ao-volume instead of sane_sysvol: but that forced `ao=alsa`
|
||||
### and came with heavy perf penalties (especially when adjusting the volume)
|
||||
substituteInPlace src/uosc/main.lua \
|
||||
--replace-fail \
|
||||
"mp.observe_property('volume'" \
|
||||
"mp.observe_property('user-data/sane_sysvol/volume'" \
|
||||
--replace-fail \
|
||||
"mp.observe_property('mute'" \
|
||||
"mp.observe_property('user-data/sane_sysvol/mute'"
|
||||
substituteInPlace src/uosc/elements/Volume.lua \
|
||||
--replace-fail \
|
||||
"mp.commandv('set', 'volume'" \
|
||||
"mp.set_property_number('user-data/sane_sysvol/volume'" \
|
||||
--replace-fail \
|
||||
"mp.set_property_native('volume'" \
|
||||
"mp.set_property_number('user-data/sane_sysvol/volume'" \
|
||||
--replace-fail \
|
||||
"mp.set_property_native('mute'" \
|
||||
"mp.set_property_bool('user-data/sane_sysvol/mute'" \
|
||||
--replace-fail \
|
||||
"mp.commandv('cycle', 'mute')" \
|
||||
"mp.set_property_bool('user-data/sane_sysvol/mute', not mp.get_property_bool('user-data/sane_sysvol/mute'))"
|
||||
|
||||
# tweak the top-bar "maximize" button to actually act as a "fullscreen" button.
|
||||
substituteInPlace src/uosc/elements/TopBar.lua \
|
||||
--replace-fail \
|
||||
'get_maximized_command,' \
|
||||
'"cycle fullscreen",'
|
||||
'';
|
||||
});
|
||||
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 {
|
||||
scripts = [
|
||||
mpvScripts.mpris
|
||||
mpvScripts.mpv-playlistmanager
|
||||
uosc
|
||||
# pkgs.mpv-uosc-latest
|
||||
];
|
||||
# extraMakeWrapperArgs = lib.optionals (cfg.config.vo != null) [
|
||||
# # 2023/08/29: fixes an error where mpv on moby launches with the message
|
||||
# # "DRM_IOCTL_MODE_CREATE_DUMB failed: Cannot allocate memory"
|
||||
# # audio still works, and controls, screenshotting, etc -- just not the actual rendering
|
||||
# #
|
||||
# # this is likely a regression for mpv 0.36.0.
|
||||
# # the actual error message *appears* to come from the mesa library, but it's tough to trace.
|
||||
# #
|
||||
# # 2024/03/02: no longer necessary, with mesa 23.3.1: <https://github.com/NixOS/nixpkgs/pull/265740>
|
||||
# #
|
||||
# # backend compatibility (2023/10/22):
|
||||
# # run with `--vo=help` to see a list of all output options.
|
||||
# # non-exhaustive (W=works, F=fails, A=audio-only, U=audio+ui only (no video))
|
||||
# # ? null Null video output
|
||||
# # A (default)
|
||||
# # A dmabuf-wayland Wayland dmabuf video output
|
||||
# # A libmpv render API for libmpv (mpv plays the audio, but doesn't even render a window)
|
||||
# # A vdpau VDPAU with X11
|
||||
# # F drm Direct Rendering Manager (software scaling)
|
||||
# # F gpu-next Video output based on libplacebo
|
||||
# # F vaapi VA API with X11
|
||||
# # F x11 X11 (software scaling)
|
||||
# # F xv X11/Xv
|
||||
# # U gpu Shader-based GPU Renderer
|
||||
# # W caca libcaca (terminal rendering)
|
||||
# # W sdl SDL 2.0 Renderer
|
||||
# # W wlshm Wayland SHM video output (software scaling)
|
||||
# "--add-flags" "--vo=${cfg.config.vo}"
|
||||
# ];
|
||||
};
|
||||
packageUnwrapped = pkgs.wrapMpv
|
||||
(mpv-unwrapped.override rec {
|
||||
# N.B.: populating `self` to `luajit` is necessary for the resulting `lua.withPackages` function to preserve my override.
|
||||
# i use enable52Compat in order to get `table.unpack`.
|
||||
# i think using `luajit` here instead of `lua` is optional, just i get better perf with it :)
|
||||
lua = pkgs.luajit.override { enable52Compat = true; self = lua; };
|
||||
})
|
||||
{
|
||||
scripts = [
|
||||
pkgs.mpvScripts.mpris
|
||||
pkgs.mpvScripts.mpv-playlistmanager
|
||||
uosc
|
||||
# pkgs.mpv-uosc-latest
|
||||
];
|
||||
# extraMakeWrapperArgs = lib.optionals (cfg.config.vo != null) [
|
||||
# # 2023/08/29: fixes an error where mpv on moby launches with the message
|
||||
# # "DRM_IOCTL_MODE_CREATE_DUMB failed: Cannot allocate memory"
|
||||
# # audio still works, and controls, screenshotting, etc -- just not the actual rendering
|
||||
# #
|
||||
# # this is likely a regression for mpv 0.36.0.
|
||||
# # the actual error message *appears* to come from the mesa library, but it's tough to trace.
|
||||
# #
|
||||
# # 2024/03/02: no longer necessary, with mesa 23.3.1: <https://github.com/NixOS/nixpkgs/pull/265740>
|
||||
# #
|
||||
# # backend compatibility (2023/10/22):
|
||||
# # run with `--vo=help` to see a list of all output options.
|
||||
# # non-exhaustive (W=works, F=fails, A=audio-only, U=audio+ui only (no video))
|
||||
# # ? null Null video output
|
||||
# # A (default)
|
||||
# # A dmabuf-wayland Wayland dmabuf video output
|
||||
# # A libmpv render API for libmpv (mpv plays the audio, but doesn't even render a window)
|
||||
# # A vdpau VDPAU with X11
|
||||
# # F drm Direct Rendering Manager (software scaling)
|
||||
# # F gpu-next Video output based on libplacebo
|
||||
# # F vaapi VA API with X11
|
||||
# # F x11 X11 (software scaling)
|
||||
# # F xv X11/Xv
|
||||
# # U gpu Shader-based GPU Renderer
|
||||
# # W caca libcaca (terminal rendering)
|
||||
# # W sdl SDL 2.0 Renderer
|
||||
# # W wlshm Wayland SHM video output (software scaling)
|
||||
# "--add-flags" "--vo=${cfg.config.vo}"
|
||||
# ];
|
||||
};
|
||||
|
||||
suggestedPrograms = [
|
||||
"blast-to-default"
|
||||
"go2tv"
|
||||
"sane-cast"
|
||||
"sane-die-with-parent"
|
||||
"xdg-terminal-exec"
|
||||
];
|
||||
|
||||
@@ -124,7 +198,7 @@ in
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.extraHomePaths = [
|
||||
".config/mpv" #< else mpris plugin crashes on launch
|
||||
".local/share/applications" #< for xdg-terminal-exec (go2tv)
|
||||
".local/share/applications" #< for xdg-terminal-exec (sane-cast)
|
||||
# it's common for album (or audiobook, podcast) images/lyrics/metadata to live adjacent to the primary file.
|
||||
# CLI detection is too poor to pick those up, so expose the common media dirs to the sandbox to make that *mostly* work.
|
||||
"Books/local"
|
||||
@@ -139,7 +213,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;
|
||||
|
@@ -9,6 +9,8 @@
|
||||
POWER ignore
|
||||
VOLUME_UP ignore
|
||||
VOLUME_DOWN ignore
|
||||
# disable "double-click to toggle fullscreen", else that limits the rate at which i can seek
|
||||
MBTN_LEFT_DBL ignore
|
||||
|
||||
# uosc menu
|
||||
# text after the shebang is parsed by uosc to construct the menu and names
|
||||
@@ -31,7 +33,5 @@ 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/sane-cast #! Cast
|
||||
|
@@ -10,7 +10,9 @@ keep-open-pause=no
|
||||
|
||||
# force GUI, even for tracks w/o album art
|
||||
# see: <https://www.reddit.com/r/mpv/comments/rvrrpt/oscosdgui_and_arch_linux/>
|
||||
player-operation-mode=pseudo-gui
|
||||
# player-operation-mode=pseudo-gui
|
||||
# actually, prefer just a subset of what's enabled by pseudo-gui, else logging breaks
|
||||
force-window=yes
|
||||
|
||||
# use uosc instead (for On Screen Controls)
|
||||
osc=no
|
||||
@@ -19,8 +21,8 @@ osd-bar=no
|
||||
# uosc will draw its own window controls if you disable window border
|
||||
border=no
|
||||
|
||||
# ao=alsa so that uosc can work with ao-volume (see my uosc patch)
|
||||
ao=alsa
|
||||
# with `ao-volume`, the max actually is 100.
|
||||
# to go higher you'll have to use the system's native controls.
|
||||
volume-max=100
|
||||
# # ao=alsa so that uosc can work with ao-volume (see my uosc patch)
|
||||
# ao=alsa
|
||||
# # with `ao-volume`, the max actually is 100.
|
||||
# # to go higher you'll have to use the system's native controls.
|
||||
# volume-max=100
|
||||
|
@@ -1,34 +0,0 @@
|
||||
function subprocess(in_terminal, args)
|
||||
if in_terminal then
|
||||
args = { "xdg-terminal-exec", table.unpack(args) }
|
||||
end
|
||||
mp.command_native({
|
||||
name = "subprocess",
|
||||
args = args,
|
||||
detach = false,
|
||||
capture_stdout = false,
|
||||
capture_stderr = false,
|
||||
-- capture_size=0,
|
||||
passthrough_stdin = false,
|
||||
playback_only = false,
|
||||
})
|
||||
end
|
||||
|
||||
function invoke_go2tv(in_terminal, args)
|
||||
mp.commandv("set", "pause", "yes")
|
||||
subprocess(in_terminal, { "go2tv", table.unpack(args) })
|
||||
end
|
||||
|
||||
function invoke_go2tv_on_open_file(mode)
|
||||
local path = mp.get_property("stream-open-filename");
|
||||
return invoke_go2tv(true, { mode, path })
|
||||
end
|
||||
|
||||
mp.add_key_binding(nil, "blast", function() subprocess(false, { "blast-to-default" }) end)
|
||||
mp.add_key_binding(nil, 'go2tv-gui', function() invoke_go2tv(false, {}) end)
|
||||
mp.add_key_binding(nil, 'go2tv-video', function() invoke_go2tv_on_open_file("-v") end)
|
||||
mp.add_key_binding(nil, 'go2tv-stream', function() invoke_go2tv_on_open_file("-s") end)
|
||||
|
||||
-- uncomment for debugging:
|
||||
-- if mpv fails to eval this script (e.g. syntax error), then it will fail to quit on launch
|
||||
-- mp.command('quit')
|
40
hosts/common/programs/mpv/sane_cast/main.lua
Normal file
40
hosts/common/programs/mpv/sane_cast/main.lua
Normal file
@@ -0,0 +1,40 @@
|
||||
msg = require("mp.msg")
|
||||
msg.trace("load: begin")
|
||||
|
||||
function subprocess(in_terminal, args)
|
||||
if in_terminal then
|
||||
args = { "xdg-terminal-exec", table.unpack(args) }
|
||||
end
|
||||
msg.info(table.concat(args, " "))
|
||||
mp.command_native({
|
||||
name = "subprocess",
|
||||
args = args,
|
||||
detach = false,
|
||||
capture_stdout = false,
|
||||
capture_stderr = false,
|
||||
-- capture_size=0,
|
||||
passthrough_stdin = false,
|
||||
playback_only = false,
|
||||
})
|
||||
end
|
||||
|
||||
function invoke_paused(in_terminal, args)
|
||||
mp.commandv("set", "pause", "yes")
|
||||
for k, v in ipairs(args) do
|
||||
if v == "@FILE@" then
|
||||
args[k] = mp.get_property("stream-open-filename")
|
||||
end
|
||||
end
|
||||
subprocess(in_terminal, args)
|
||||
end
|
||||
|
||||
|
||||
-- invoke blast in a way where it dies when we die, because:
|
||||
-- 1. when mpv exits, it `SIGKILL`s this toplevel subprocess.
|
||||
-- 2. `blast-to-default` could be a sandbox wrapper.
|
||||
-- 3. bwrap does not pass SIGKILL or SIGTERM to its child.
|
||||
-- 4. hence, to properly kill blast, we have to kill all the descendants.
|
||||
mp.add_key_binding(nil, "blast", function() subprocess(false, { "sane-die-with-parent", "--descendants", "--use-pgroup", "--catch-sigkill", "blast-to-default" }) end)
|
||||
mp.add_key_binding(nil, "sane-cast", function() invoke_paused(true, { "sane-cast", "--verbose", "@FILE@" }) end)
|
||||
|
||||
msg.trace("load: complete")
|
281
hosts/common/programs/mpv/sane_sysvol/main.lua
Normal file
281
hosts/common/programs/mpv/sane_sysvol/main.lua
Normal file
@@ -0,0 +1,281 @@
|
||||
msg = require("mp.msg")
|
||||
msg.trace("load: begin")
|
||||
|
||||
non_blocking_popen = require("non_blocking_popen")
|
||||
|
||||
RD_SIZE = 65536
|
||||
|
||||
function startswith(superstring, substring)
|
||||
return superstring:sub(1, substring:len()) == substring
|
||||
end
|
||||
function strip_prefix(superstring, substring)
|
||||
assert(startswith(superstring, substring))
|
||||
return superstring:sub(1 + substring:len())
|
||||
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,
|
||||
sysmute = 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.abs(mpv_vol - old_mpv_vol) < 1.0 then
|
||||
-- avoid near-infinite loop where we react to our own volume change.
|
||||
-- consider that we might be a couple messages behind in parsing pipewire when we issue this command,
|
||||
-- hence a check on only the pipewire -> mpv side wouldn't prevent oscillation
|
||||
msg.debug("NOT setting system-wide volume:", old_mpv_vol, volstr)
|
||||
return
|
||||
end
|
||||
|
||||
local volstr = tostring(mpv_vol) .. "%"
|
||||
msg.debug("setting system-wide volume:", old_mpv_vol, volstr)
|
||||
self.sysvol = (0.01*mpv_vol)^3
|
||||
subprocess({
|
||||
"wpctl",
|
||||
"set-volume",
|
||||
"@DEFAULT_AUDIO_SINK@",
|
||||
volstr
|
||||
})
|
||||
end,
|
||||
on_sysvol_change = function(self, sysvol)
|
||||
-- called when the pipewire system volume is changed (either by us, or an external application)
|
||||
if sysvol == nil then
|
||||
return
|
||||
end
|
||||
|
||||
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
|
||||
-- avoid an infinite loop where we react to our own volume change
|
||||
msg.debug("NOT announcing volume change to mpv (because it was what triggered the change):", old_mpv_vol, new_mpv_vol)
|
||||
return
|
||||
end
|
||||
|
||||
msg.debug("announcing volume change to mpv:", old_mpv_vol, new_mpv_vol)
|
||||
self.sysvol = sysvol
|
||||
mp.set_property_number("user-data/sane_sysvol/volume", new_mpv_vol)
|
||||
end,
|
||||
change_sysmute = function(self, mute)
|
||||
if mute == nil then
|
||||
return
|
||||
end
|
||||
if mute == self.sysmute then
|
||||
msg.debug("NOT setting system-wide mute (because it didn't change)", mute)
|
||||
return
|
||||
end
|
||||
|
||||
local mutestr
|
||||
if mute then
|
||||
mutestr = "1"
|
||||
else
|
||||
mutestr = "0"
|
||||
end
|
||||
msg.debug("setting system-wide mute:", mutestr)
|
||||
self.sysmute = mute
|
||||
subprocess({
|
||||
"wpctl",
|
||||
"set-mute",
|
||||
"@DEFAULT_AUDIO_SINK@",
|
||||
mutestr
|
||||
})
|
||||
end,
|
||||
on_sysmute_change = function(self, mute)
|
||||
if mute == nil then
|
||||
return
|
||||
end
|
||||
|
||||
msg.debug("announcing mute to mpv:", mute)
|
||||
self.sysmute = mute
|
||||
mp.set_property_bool("user-data/sane_sysvol/mute", mute)
|
||||
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 = nil, -- number
|
||||
mute = nil, -- bool
|
||||
|
||||
-- parser state:
|
||||
in_device = false,
|
||||
in_direction = false,
|
||||
in_output = false,
|
||||
in_vol = false,
|
||||
in_mute = false,
|
||||
|
||||
feed_line = function(self, line)
|
||||
msg.trace("pw-mon:", line)
|
||||
line = ltrim(line)
|
||||
if startswith(line, "changed:") or startswith(line, "added:") or startswith(line, "removed:") then
|
||||
self.in_device = false
|
||||
self.in_direction = false
|
||||
self.in_output = false
|
||||
self.in_vol = false
|
||||
self.in_mute = false
|
||||
self.in_properties = false
|
||||
elseif startswith(line, "type: ") then
|
||||
self.in_device = startswith(line, "type: PipeWire:Interface:Device")
|
||||
msg.trace("parsed type:", line, self.in_device)
|
||||
elseif startswith(line, "Prop: ") and self.in_device then
|
||||
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:mute")
|
||||
msg.trace("parsed `Prop:`", line, self.in_vol)
|
||||
elseif line:find("Spa:Enum:Direction:Output", 1, true) and self.in_direction then
|
||||
self.in_output = true
|
||||
elseif startswith(line, "Float ") and self.in_device and self.in_output and self.in_vol then
|
||||
value = tonumber(strip_prefix(line, "Float "))
|
||||
self:feed_volume(value)
|
||||
elseif startswith(line, "Bool ") and self.in_device and self.in_output and self.in_mute then
|
||||
value = strip_prefix(line, "Bool ") == "true"
|
||||
self:feed_mute(value)
|
||||
elseif startswith(line, "properties:") and self.in_device then
|
||||
self.in_properties = true
|
||||
end
|
||||
end,
|
||||
|
||||
feed_volume = function(self, vol)
|
||||
msg.debug("volume:", vol)
|
||||
self.volume = vol
|
||||
end,
|
||||
feed_mute = function(self, mute)
|
||||
msg.debug("mute:", mute)
|
||||
self.mute = mute
|
||||
end,
|
||||
-- get_effective_volume = function(self)
|
||||
-- if self.mute then
|
||||
-- return 0
|
||||
-- else
|
||||
-- return self.volume
|
||||
-- end
|
||||
-- end
|
||||
}
|
||||
end
|
||||
|
||||
function pwmon_new()
|
||||
return {
|
||||
-- non_blocking_popen handle for the pw-mon process
|
||||
-- which can be periodically read and parsed to detect volume changes.
|
||||
-- we have to use `sane-die-with-parent` otherwise `pw-mon` will still be active even after mpv exits.
|
||||
handle = non_blocking_popen.non_blocking_popen("sane-die-with-parent --descendants pw-mon", RD_SIZE),
|
||||
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
|
||||
local old_vol = self.pwmon_parser.volume
|
||||
local old_mute = self.pwmon_parser.mute
|
||||
self.stdout_unparsed = self.stdout_unparsed .. buf
|
||||
self:consume_stdout()
|
||||
local new_vol = self.pwmon_parser.volume
|
||||
local new_mute = self.pwmon_parser.mute
|
||||
|
||||
if new_vol ~= old_vol then
|
||||
msg.debug("pipewire volume change:", old_vol, new_vol)
|
||||
mp.set_property_number("user-data/sane_sysvol/pw-mon-volume", new_vol)
|
||||
end
|
||||
if new_mute ~= old_mute then
|
||||
msg.debug("pipewire mute change:", old_mute, new_mute)
|
||||
mp.set_property_bool("user-data/sane_sysvol/pw-mon-mute", new_mute)
|
||||
end
|
||||
end
|
||||
end,
|
||||
consume_stdout = function(self)
|
||||
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.pwmon_parser:feed_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,
|
||||
}
|
||||
end
|
||||
|
||||
mp.set_property_number("user-data/sane_sysvol/volume", 0)
|
||||
mp.set_property_bool("user-data/sane_sysvol/mute", true)
|
||||
|
||||
local sysvol = sysvol_new()
|
||||
local first_sysvol_announcement = true
|
||||
mp.observe_property("user-data/sane_sysvol/volume", "native", function(_, val)
|
||||
-- we must set the volume property early -- before we actually know the volume
|
||||
-- else other modules will think it's `nil` and error.
|
||||
-- but we DON'T want the value we set to actually impact the system volume
|
||||
if not first_sysvol_announcement then
|
||||
sysvol:change_sysvol(val)
|
||||
end
|
||||
first_sysvol_announcement = false
|
||||
end)
|
||||
mp.observe_property("user-data/sane_sysvol/pw-mon-volume", "native", function(_, val)
|
||||
sysvol:on_sysvol_change(val)
|
||||
end)
|
||||
|
||||
local first_sysmute_announcement = true
|
||||
mp.observe_property("user-data/sane_sysvol/mute", "native", function(_, val)
|
||||
-- we must set the mute property early -- before we actually know the mute
|
||||
-- else other modules will think it's `nil` and error.
|
||||
-- but we DON'T want the value we set to actually impact the system mute
|
||||
if not first_sysmute_announcement then
|
||||
sysvol:change_sysmute(val)
|
||||
end
|
||||
first_sysmute_announcement = false
|
||||
end)
|
||||
mp.observe_property("user-data/sane_sysvol/pw-mon-mute", "native", function(_, val)
|
||||
sysvol:on_sysmute_change(val)
|
||||
end)
|
||||
|
||||
local pwmon = pwmon_new()
|
||||
mp.register_event("tick", function() pwmon:service() end)
|
||||
|
||||
msg.trace("load: complete")
|
91
hosts/common/programs/mpv/sane_sysvol/non_blocking_popen.lua
Normal file
91
hosts/common/programs/mpv/sane_sysvol/non_blocking_popen.lua
Normal file
@@ -0,0 +1,91 @@
|
||||
-- source: <https://gist.github.com/max1220/c19ccd4d90ed32d41b879eba727cbcbd>
|
||||
-- requires: luajit
|
||||
--
|
||||
-- Implements a basic binding for popen that allows non-blocking reads
|
||||
-- returned "file" table only supports :read(with an optional size argument, no mode etc.) and :close
|
||||
ffi = require("ffi")
|
||||
-- C functions that we need
|
||||
ffi.cdef([[
|
||||
void* popen(const char* cmd, const char* mode);
|
||||
int pclose(void* stream);
|
||||
int fileno(void* stream);
|
||||
int fcntl(int fd, int cmd, int arg);
|
||||
int *__errno_location ();
|
||||
ssize_t read(int fd, void* buf, size_t count);
|
||||
]])
|
||||
|
||||
-- you can compile a simple C programm to find these values(Or look in the headers)
|
||||
F_SETFL = 4
|
||||
O_NONBLOCK = 2048
|
||||
EAGAIN = 11
|
||||
|
||||
-- this "array" holds the errno variable
|
||||
_errno = ffi.C.__errno_location()
|
||||
|
||||
popen_meta = {
|
||||
__index = {
|
||||
-- close the process, prevent reading, allow garbage colletion
|
||||
close = function(self)
|
||||
if self._file ~= nil then
|
||||
local _file = self._file
|
||||
self._file = nil
|
||||
self._fd = nil
|
||||
self._read_buffer = nil
|
||||
ffi.C.pclose(_file)
|
||||
end
|
||||
end,
|
||||
-- read up to size bytes from the process. Returns data(string) and number of bytes read if successfull,
|
||||
-- nil, "EAGAIN" if there is no data aviable, and
|
||||
-- nil, "closed" if the process has ended
|
||||
read = function(self, size)
|
||||
if self._fd == nil then
|
||||
return nil, "closed"
|
||||
end
|
||||
|
||||
size = math.min(self._read_buffer_size, size)
|
||||
local nbytes = ffi.C.read(self._fd, self._read_buffer, size)
|
||||
|
||||
if nbytes > 0 then
|
||||
local data = ffi.string(self._read_buffer, nbytes)
|
||||
return data, nbytes
|
||||
elseif (nbytes == -1) and (_errno[0] == EAGAIN) then
|
||||
return nil, "EAGAIN"
|
||||
else
|
||||
self:close()
|
||||
return nil, "closed"
|
||||
end
|
||||
end
|
||||
}
|
||||
-- __gc = function(self) self:close() end
|
||||
-- __close = function(self) p:close() end
|
||||
}
|
||||
|
||||
local function non_blocking_popen(cmd, read_buffer_size)
|
||||
-- the buffer for reading from the process
|
||||
local read_buffer_size = tonumber(read_buffer_size) or 2048
|
||||
local read_buffer = ffi.new('uint8_t[?]',read_buffer_size)
|
||||
|
||||
-- get a FILE* for our command
|
||||
local file = assert(ffi.C.popen(cmd, "r"))
|
||||
|
||||
-- turn the FILE* to a fd (int) for fcntl
|
||||
local fd = ffi.C.fileno(file)
|
||||
|
||||
-- set non-blocking mode for read
|
||||
assert(ffi.C.fcntl(fd, F_SETFL, O_NONBLOCK)==0, "fcntl failed")
|
||||
|
||||
local p = {
|
||||
_fd = fd,
|
||||
_file = file,
|
||||
_read_buffer = read_buffer,
|
||||
_read_buffer_size = read_buffer_size,
|
||||
}
|
||||
|
||||
setmetatable(p, popen_meta)
|
||||
|
||||
return p
|
||||
end
|
||||
|
||||
return {
|
||||
non_blocking_popen = non_blocking_popen
|
||||
}
|
@@ -24,7 +24,7 @@ font_scale=1.5
|
||||
font_bold=yes
|
||||
# refine=text_width: slightly better text rendering
|
||||
refine=text_width
|
||||
color=foreground=ff8080,background_text=ff8080
|
||||
color=foreground=ff968b,background_text=ff968b
|
||||
# N.B.: if `opacity=` is set non-empty, then ALL items must be specified (else they get 0 opacity).
|
||||
# opacity values *must* be a multiple of 0.1
|
||||
opacity=timeline=0.8,position=1,chapters=0.8,slider=0.8,slider_gauge=0.8,controls=0,speed=0.8,menu=1,submenu=0.4,border=1,title=0.8,tooltip=1,thumbnail=1,curtain=0.8,idle_indicator=0.8,audio_indicator=0.5,buffering_indicator=0.3,playlist_position=0.8
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user