Compare commits
195 Commits
wip-s6-2
...
wip-mpv-sy
Author | SHA1 | Date | |
---|---|---|---|
72c7287445 | |||
b715fd346f | |||
1b9b0ac0f6 | |||
79c4e2c405 | |||
17a3f90825 | |||
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 |
5
TODO.md
5
TODO.md
@@ -1,9 +1,14 @@
|
||||
## BUGS
|
||||
- Signal restart loop drains battery
|
||||
- decrease s6 restart time?
|
||||
- mpv `player-mode=pseudo-gui` swallows all loggin
|
||||
- ringer (i.e. dino incoming call) doesn't prevent moby from sleeping
|
||||
- sway mouse/kb hotplug doesn't work
|
||||
- `nix` operations from lappy hang when `desko` is unreachable
|
||||
- could at least direct the cache to `http://desko-hn:5001`
|
||||
|
||||
## REFACTORING:
|
||||
- REMOVE DEPRECATED `crypt` from sftpgo_auth_hook
|
||||
- consolidate ~/dev and ~/ref
|
||||
- ~/dev becomes a link to ~/ref/cat/mine
|
||||
- fold hosts/common/home/ssh.nix -> hosts/common/users/colin.nix
|
||||
|
50
flake.lock
generated
50
flake.lock
generated
@@ -61,11 +61,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710184940,
|
||||
"narHash": "sha256-FzYm4td3FJfzOAuEkCXt3KdUgZuA072OAQXqIq+IAMo=",
|
||||
"lastModified": 1711886936,
|
||||
"narHash": "sha256-D2WENp9GuaCostvNcQ7vElekk0V5cuMdnFZ7NfRhVrQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "lib-aggregate",
|
||||
"rev": "45b75bf534592c0c1c881a1c447f7fdb37a87eaf",
|
||||
"rev": "9c06929b83e57c18d125f1105ba6a423f24083d2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -152,11 +152,11 @@
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1710031547,
|
||||
"narHash": "sha256-pkUg3hOKuGWMGF9WEMPPN/G4pqqdbNGJQ54yhyQYDVY=",
|
||||
"lastModified": 1711846064,
|
||||
"narHash": "sha256-cqfX0QJNEnge3a77VnytM0Q6QZZ0DziFXt6tSCV8ZSc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "630ebdc047ca96d8126e16bb664c7730dc52f6e6",
|
||||
"rev": "90b1a963ff84dc532db92f678296ff2499a60a87",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -167,11 +167,11 @@
|
||||
},
|
||||
"nixpkgs-next-unpatched": {
|
||||
"locked": {
|
||||
"lastModified": 1710337169,
|
||||
"narHash": "sha256-u2/74bhQuWykUZDWUIhHd6IpZiaQ0hSpTBbx0y9opkE=",
|
||||
"lastModified": 1712383280,
|
||||
"narHash": "sha256-YL8miM11o/jMqOwt5DsdyhPgh/JgCl1kOIzvX7ukniY=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4ee0840ba2ecc50458ab1677d108afcd691f4815",
|
||||
"rev": "7c74352f2f7eca1925729f5c9c80cb89df8e74a2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -183,11 +183,11 @@
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1710033658,
|
||||
"narHash": "sha256-yiZiVKP5Ya813iYLho2+CcFuuHpaqKc/CoxOlANKcqM=",
|
||||
"lastModified": 1711819797,
|
||||
"narHash": "sha256-tNeB6emxj74Y6ctwmsjtMlzUMn458sBmwnD35U5KIM4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b17375d3bb7c79ffc52f3538028b2ec06eb79ef8",
|
||||
"rev": "2b4e3ca0091049c6fbb4908c66b05b77eaef9f0c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -199,11 +199,11 @@
|
||||
},
|
||||
"nixpkgs-unpatched": {
|
||||
"locked": {
|
||||
"lastModified": 1710339354,
|
||||
"narHash": "sha256-+P5ccUPiLouHexb8aJrUOVOIja9qm+fG57pgAu7uIRs=",
|
||||
"lastModified": 1712398506,
|
||||
"narHash": "sha256-oopwPeBKBXQEw2BlyK2jEs2farZ5uMjAZU7H4FpGuGE=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2dbc8f62d8af7a1ab962e4b20d12b25ddcb86ced",
|
||||
"rev": "c58702222e0a29fd01cc42d70737d699995f6389",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -223,11 +223,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710317949,
|
||||
"narHash": "sha256-bwReMiWPA2wYBvKEMhO8pJcu+o+7ocy5hGkSoawTHu0=",
|
||||
"lastModified": 1712237761,
|
||||
"narHash": "sha256-NoMBBCADTms3yx5BL+sbc7vfDivNiYULO6t9GBAsPt0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs-wayland",
|
||||
"rev": "771cb198c281db6918829651f194bf4db32e342d",
|
||||
"rev": "9b77653338f52da4b498abdf4835efb6ff6e453e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -254,11 +254,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710195194,
|
||||
"narHash": "sha256-KFxCJp0T6TJOz1IOKlpRdpsCr9xsvlVuWY/VCiAFnTE=",
|
||||
"lastModified": 1711855048,
|
||||
"narHash": "sha256-HxegAPnQJSC4cbEbF4Iq3YTlFHZKLiNTk8147EbLdGg=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "e52d8117b330f690382f1d16d81ae43daeb4b880",
|
||||
"rev": "99b1e37f9fc0960d064a7862eb7adfb92e64fa10",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -311,11 +311,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1707981105,
|
||||
"narHash": "sha256-YCU1eNslBHabjP+OCY+BxPycEFO9SRUts10MrN9QORE=",
|
||||
"lastModified": 1711371733,
|
||||
"narHash": "sha256-+brjlMyLVnVADY31sN82Ap0IsPE2WZEwHUd94sY6BXI=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "bb10cd8853d05191e4d62947d93687c462e92c30",
|
||||
"revCount": 235,
|
||||
"rev": "b9502e6f190752d327f8cee7fa4b139094bd7c16",
|
||||
"revCount": 237,
|
||||
"type": "git",
|
||||
"url": "https://git.uninsane.org/colin/uninsane"
|
||||
},
|
||||
|
@@ -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
|
||||
'';
|
||||
}
|
||||
|
@@ -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"
|
||||
|
@@ -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"
|
||||
|
@@ -6,107 +6,25 @@
|
||||
# - nixos example: <repo:nixos/nixpkgs:nixos/tests/sftpgo.nix>
|
||||
#
|
||||
# sftpgo is a FTP server that also supports WebDAV, SFTP, and web clients.
|
||||
#
|
||||
# TODO: change umask so sftpgo-created files default to 644.
|
||||
# - it does indeed appear that the 600 is not something sftpgo is explicitly doing.
|
||||
|
||||
|
||||
{ config, lib, pkgs, sane-lib, ... }:
|
||||
let
|
||||
# user permissions:
|
||||
# - see <repo:drakkan/sftpgo:internal/dataprovider/user.go>
|
||||
# - "*" = grant all permissions
|
||||
# - read-only perms:
|
||||
# - "list" = list files and directories
|
||||
# - "download"
|
||||
# - rw perms:
|
||||
# - "upload"
|
||||
# - "overwrite" = allow uploads to replace existing files
|
||||
# - "delete" = delete files and directories
|
||||
# - "delete_files"
|
||||
# - "delete_dirs"
|
||||
# - "rename" = rename files and directories
|
||||
# - "rename_files"
|
||||
# - "rename_dirs"
|
||||
# - "create_dirs"
|
||||
# - "create_symlinks"
|
||||
# - "chmod"
|
||||
# - "chown"
|
||||
# - "chtimes" = change atime/mtime (access and modification times)
|
||||
#
|
||||
# home_dir:
|
||||
# - it seems (empirically) that a user can't cd above their home directory.
|
||||
# though i don't have a reference for that in the docs.
|
||||
authResponseSuccess = {
|
||||
status = 1;
|
||||
username = "anonymous";
|
||||
expiration_date = 0;
|
||||
home_dir = "/var/export";
|
||||
# uid/gid 0 means to inherit sftpgo uid.
|
||||
# - i.e. users can't read files which Linux user `sftpgo` can't read
|
||||
# - uploaded files belong to Linux user `sftpgo`
|
||||
# other uid/gid values aren't possible for localfs backend, unless i let sftpgo use `sudo`.
|
||||
uid = 0;
|
||||
gid = 0;
|
||||
# uid = 65534;
|
||||
# gid = 65534;
|
||||
max_sessions = 0;
|
||||
# quota_*: 0 means to not use SFTP's quota system
|
||||
quota_size = 0;
|
||||
quota_files = 0;
|
||||
permissions = {
|
||||
"/" = [ "list" "download" ];
|
||||
"/playground" = [
|
||||
# read-only:
|
||||
"list"
|
||||
"download"
|
||||
# write:
|
||||
"upload"
|
||||
"overwrite"
|
||||
"delete"
|
||||
"rename"
|
||||
"create_dirs"
|
||||
"create_symlinks"
|
||||
# intentionally omitted:
|
||||
# "chmod"
|
||||
# "chown"
|
||||
# "chtimes"
|
||||
];
|
||||
};
|
||||
upload_bandwidth = 0;
|
||||
download_bandwidth = 0;
|
||||
filters = {
|
||||
allowed_ip = [];
|
||||
denied_ip = [];
|
||||
};
|
||||
public_keys = [];
|
||||
# other fields:
|
||||
# ? groups
|
||||
# ? virtual_folders
|
||||
};
|
||||
authResponseFail = {
|
||||
username = "";
|
||||
};
|
||||
authSuccessJson = pkgs.writeText "sftp-auth-success.json" (builtins.toJSON authResponseSuccess);
|
||||
authFailJson = pkgs.writeText "sftp-auth-fail.json" (builtins.toJSON authResponseFail);
|
||||
unwrappedAuthProgram = pkgs.static-nix-shell.mkBash {
|
||||
sftpgo_external_auth_hook = pkgs.static-nix-shell.mkPython3Bin {
|
||||
pname = "sftpgo_external_auth_hook";
|
||||
srcRoot = ./.;
|
||||
pkgs = [ "coreutils" ];
|
||||
};
|
||||
authProgram = pkgs.writeShellScript "sftpgo-auth-hook" ''
|
||||
${unwrappedAuthProgram}/bin/sftpgo_external_auth_hook ${authFailJson} ${authSuccessJson}
|
||||
'';
|
||||
in
|
||||
{
|
||||
# Client initiates a FTP "control connection" on port 21.
|
||||
# - this handles the client -> server commands, and the server -> client status, but not the actual data
|
||||
# - file data, directory listings, etc need to be transferred on an ephemeral "data port".
|
||||
# - 50000-50100 is a common port range for this.
|
||||
# 50000 is used by soulseek.
|
||||
sane.ports.ports = {
|
||||
"21" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-FTP server";
|
||||
};
|
||||
} // (sane-lib.mapToAttrs
|
||||
@@ -115,10 +33,11 @@ in
|
||||
value = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-FTP server data port range";
|
||||
};
|
||||
})
|
||||
(lib.range 50000 50100)
|
||||
(lib.range 50050 50100)
|
||||
);
|
||||
|
||||
services.sftpgo = {
|
||||
@@ -134,7 +53,7 @@ in
|
||||
debug = true;
|
||||
}
|
||||
{
|
||||
# binding this means any LAN client can connect
|
||||
# binding this means any LAN client can connect (also WAN traffic forwarded from the gateway)
|
||||
address = "10.78.79.51";
|
||||
port = 21;
|
||||
debug = true;
|
||||
@@ -145,22 +64,25 @@ in
|
||||
disable_active_mode = true;
|
||||
hash_support = true;
|
||||
passive_port_range = {
|
||||
start = 50000;
|
||||
start = 50050;
|
||||
end = 50100;
|
||||
};
|
||||
|
||||
banner = ''
|
||||
Welcome, friends, to Colin's read-only FTP server! Also available via NFS on the same host.
|
||||
Welcome, friends, to Colin's FTP server! Also available via NFS on the same host, but LAN-only.
|
||||
|
||||
Read-only access (LAN-restricted):
|
||||
Username: "anonymous"
|
||||
Password: "anonymous"
|
||||
CONFIGURE YOUR CLIENT FOR "PASSIVE" mode, e.g. `ftp --passive uninsane.org`
|
||||
Please let me know if anything's broken or not as it should be. Otherwise, browse and DL freely :)
|
||||
|
||||
CONFIGURE YOUR CLIENT FOR "PASSIVE" mode, e.g. `ftp --passive uninsane.org`.
|
||||
Please let me know if anything's broken or not as it should be. Otherwise, browse and transfer freely :)
|
||||
'';
|
||||
|
||||
};
|
||||
data_provider = {
|
||||
driver = "memory";
|
||||
external_auth_hook = "${authProgram}";
|
||||
external_auth_hook = "${sftpgo_external_auth_hook}/bin/sftpgo_external_auth_hook";
|
||||
# track_quota:
|
||||
# - 0: disable quota tracking
|
||||
# - 1: quota is updated on every upload/delete, even if user has no quota restriction
|
||||
@@ -170,17 +92,20 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
users.users.sftpgo.extraGroups = [ "export" ];
|
||||
users.users.sftpgo.extraGroups = [
|
||||
"export"
|
||||
"media"
|
||||
];
|
||||
|
||||
systemd.services.sftpgo = {
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
ReadOnlyPaths = [ "/var/export" ];
|
||||
ReadWritePaths = [ "/var/export/playground" ];
|
||||
ReadWritePaths = [ "/var/export" ];
|
||||
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
UMask = lib.mkForce "0002";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p coreutils
|
||||
# vim: set filetype=bash :
|
||||
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])"
|
||||
# vim: set filetype=python :
|
||||
#
|
||||
# available environment variables:
|
||||
# - SFTPGO_AUTHD_USERNAME
|
||||
@@ -12,12 +12,146 @@
|
||||
# - SFTPGO_AUTHD_KEYBOARD_INTERACTIVE
|
||||
# - SFTPGO_AUTHD_TLS_CERT
|
||||
#
|
||||
# user permissions:
|
||||
# - see <repo:drakkan/sftpgo:internal/dataprovider/user.go>
|
||||
# - "*" = grant all permissions
|
||||
# - read-only perms:
|
||||
# - "list" = list files and directories
|
||||
# - "download"
|
||||
# - rw perms:
|
||||
# - "upload"
|
||||
# - "overwrite" = allow uploads to replace existing files
|
||||
# - "delete" = delete files and directories
|
||||
# - "delete_files"
|
||||
# - "delete_dirs"
|
||||
# - "rename" = rename files and directories
|
||||
# - "rename_files"
|
||||
# - "rename_dirs"
|
||||
# - "create_dirs"
|
||||
# - "create_symlinks"
|
||||
# - "chmod"
|
||||
# - "chown"
|
||||
# - "chtimes" = change atime/mtime (access and modification times)
|
||||
#
|
||||
# call with <script_name> /path/to/fail/response.json /path/to/success/response.json
|
||||
# home_dir:
|
||||
# - it seems (empirically) that a user can't cd above their home directory.
|
||||
# though i don't have a reference for that in the docs.
|
||||
|
||||
import crypt
|
||||
import json
|
||||
import os
|
||||
|
||||
if [ "$SFTPGO_AUTHD_USERNAME" = "anonymous" ]; then
|
||||
cat "$2"
|
||||
else
|
||||
cat "$1"
|
||||
fi
|
||||
from hmac import compare_digest
|
||||
|
||||
authFail = dict(username="")
|
||||
|
||||
PERM_RO = [ "list", "download" ]
|
||||
PERM_RW = [
|
||||
# read-only:
|
||||
"list",
|
||||
"download",
|
||||
# write:
|
||||
"upload",
|
||||
"overwrite",
|
||||
"delete",
|
||||
"rename",
|
||||
"create_dirs",
|
||||
"create_symlinks",
|
||||
# intentionally omitted:
|
||||
# "chmod",
|
||||
# "chown",
|
||||
# "chtimes",
|
||||
]
|
||||
|
||||
TRUSTED_CREDS = [
|
||||
# /etc/shadow style creds.
|
||||
# mkpasswd -m sha-512
|
||||
# $<method>$<salt>$<hash>
|
||||
"$6$Zq3c2u4ghUH4S6EP$pOuRt13sEKfX31OqPbbd1LuhS21C9MICMc94iRdTAgdAcJ9h95gQH/6Jf6Ie4Obb0oxQtojRJ1Pd/9QHOlFMW." #< m. rocket boy
|
||||
]
|
||||
|
||||
def mkAuthOk(username: str, permissions: dict[str, list[str]]) -> dict:
|
||||
return dict(
|
||||
status = 1,
|
||||
username = username,
|
||||
expiration_date = 0,
|
||||
home_dir = "/var/export",
|
||||
# uid/gid 0 means to inherit sftpgo uid.
|
||||
# - i.e. users can't read files which Linux user `sftpgo` can't read
|
||||
# - uploaded files belong to Linux user `sftpgo`
|
||||
# other uid/gid values aren't possible for localfs backend, unless i let sftpgo use `sudo`.
|
||||
uid = 0,
|
||||
gid = 0,
|
||||
# uid = 65534,
|
||||
# gid = 65534,
|
||||
max_sessions = 0,
|
||||
# quota_*: 0 means to not use SFTP's quota system
|
||||
quota_size = 0,
|
||||
quota_files = 0,
|
||||
permissions = permissions,
|
||||
upload_bandwidth = 0,
|
||||
download_bandwidth = 0,
|
||||
filters = dict(
|
||||
allowed_ip = [],
|
||||
denied_ip = [],
|
||||
),
|
||||
public_keys = [],
|
||||
# other fields:
|
||||
# ? groups
|
||||
# ? virtual_folders
|
||||
)
|
||||
|
||||
def isLan(ip: str) -> bool:
|
||||
return ip.startswith("10.78.76.") \
|
||||
or ip.startswith("10.78.77.") \
|
||||
or ip.startswith("10.78.78.") \
|
||||
or ip.startswith("10.78.79.")
|
||||
|
||||
def isWireguard(ip: str) -> bool:
|
||||
return ip.startswith("10.0.10.")
|
||||
|
||||
def isTrustedCred(password: str) -> bool:
|
||||
for cred in TRUSTED_CREDS:
|
||||
_, method, salt, hash_ = cred.split("$")
|
||||
# assert method == "6", f"unrecognized crypt entry: {cred}"
|
||||
if crypt.crypt(password, f"${method}${salt}") == cred:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def getAuthResponse(ip: str, username: str, password: str) -> dict:
|
||||
"""
|
||||
return a sftpgo auth response either denying the user or approving them
|
||||
with a set of permissions.
|
||||
"""
|
||||
if isTrustedCred(password) and username != "colin":
|
||||
# allow r/w access from those with a special token
|
||||
return mkAuthOk(username, permissions = {
|
||||
"/": PERM_RW,
|
||||
"/playground": PERM_RW,
|
||||
})
|
||||
if isWireguard(ip):
|
||||
# allow any user from wireguard
|
||||
return mkAuthOk(username, permissions = {
|
||||
"/": PERM_RW,
|
||||
"/playground": PERM_RW,
|
||||
})
|
||||
if isLan(ip):
|
||||
if username == "anonymous":
|
||||
# allow anonymous users on the LAN
|
||||
return mkAuthOk("anonymous", permissions = {
|
||||
"/": PERM_RO,
|
||||
"/playground": PERM_RW,
|
||||
})
|
||||
|
||||
return authFail
|
||||
|
||||
def main():
|
||||
ip = os.environ.get("SFTPGO_AUTHD_IP", "")
|
||||
username = os.environ.get("SFTPGO_AUTHD_USERNAME", "")
|
||||
password = os.environ.get("SFTPGO_AUTHD_PASSWORD", "")
|
||||
resp = getAuthResponse(ip, username, password)
|
||||
print(json.dumps(resp))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@@ -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;
|
||||
|
@@ -3,10 +3,14 @@
|
||||
#
|
||||
# config precedence (higher precedence overrules lower precedence):
|
||||
# - Default Values < Environment Variables < YAML Configuraiton File < Command Line Arguments
|
||||
#
|
||||
# debugging:
|
||||
# - soulseek is just *flaky*. if you see e.g. DNS errors, even though you can't replicate them via `dig` or `getent ahostsv4`, just give it 10 minutes to work out:
|
||||
# - "Soulseek.AddressException: Failed to resolve address 'vps.slsknet.org': Resource temporarily unavailable"
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "slskd"; group = "slskd"; path = "/var/lib/slskd"; method = "bind"; }
|
||||
{ user = "slskd"; group = "media"; path = "/var/lib/slskd"; method = "bind"; }
|
||||
];
|
||||
sops.secrets."slskd_env" = {
|
||||
owner = config.users.users.slskd.name;
|
||||
@@ -15,7 +19,7 @@
|
||||
|
||||
users.users.slskd.extraGroups = [ "media" ];
|
||||
|
||||
sane.ports.ports."50000" = {
|
||||
sane.ports.ports."50300" = {
|
||||
protocol = [ "tcp" ];
|
||||
# not visible to WAN: i run this in a separate netns
|
||||
visibleTo.ovpn = true;
|
||||
@@ -28,12 +32,14 @@
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://10.0.1.6:5001";
|
||||
proxyPass = "http://10.0.1.6:5030";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
|
||||
services.slskd.enable = true;
|
||||
services.slskd.domain = null; # i'll manage nginx for it
|
||||
services.slskd.group = "media";
|
||||
# env file, for auth (SLSKD_SLSK_PASSWORD, SLSKD_SLSK_USERNAME)
|
||||
services.slskd.environmentFile = config.sops.secrets.slskd_env.path;
|
||||
services.slskd.settings = {
|
||||
@@ -68,7 +74,6 @@
|
||||
NetworkNamespacePath = "/run/netns/ovpns";
|
||||
Restart = lib.mkForce "always"; # exits "success" when it fails to connect to soulseek server
|
||||
RestartSec = "60s";
|
||||
Group = "media";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -22,6 +22,41 @@ let
|
||||
--replace-fail 'set(TR_USER_AGENT_PREFIX "''${TR_SEMVER}")' 'set(TR_USER_AGENT_PREFIX "3.00")'
|
||||
'';
|
||||
});
|
||||
download-dir = "/var/media/torrents";
|
||||
torrent-done = pkgs.writeShellApplication {
|
||||
name = "torrent-done";
|
||||
runtimeInputs = with pkgs; [
|
||||
rsync
|
||||
util-linux
|
||||
];
|
||||
text = ''
|
||||
destructive() {
|
||||
if [ -n "''${TR_DRY_RUN-}" ]; then
|
||||
echo "$*"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
if [[ "$TR_TORRENT_DIR" =~ ^.*freeleech.*$ ]]; then
|
||||
# freeleech torrents have no place in my permanent library
|
||||
echo "freeleech: nothing to do"
|
||||
exit 0
|
||||
fi
|
||||
if ! [[ "$TR_TORRENT_DIR" =~ ^${download-dir}/.*$ ]]; then
|
||||
echo "unexpected torrent dir, aborting: $TR_TORRENT_DIR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
REL_DIR="''${TR_TORRENT_DIR#${download-dir}/}"
|
||||
MEDIA_DIR="/var/media/$REL_DIR"
|
||||
|
||||
destructive mkdir -p "$(dirname "$MEDIA_DIR")"
|
||||
destructive rsync -arv "$TR_TORRENT_DIR/" "$MEDIA_DIR/"
|
||||
# dedupe the whole media library.
|
||||
# yeah, a bit excessive: move this to a cron job if that's problematic.
|
||||
destructive hardlink /var/media --reflink=always --ignore-time --verbose
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
@@ -72,11 +107,23 @@ in
|
||||
# see: https://git.zknt.org/mirror/transmission/commit/cfce6e2e3a9b9d31a9dafedd0bdc8bf2cdb6e876?lang=bg-BG
|
||||
anti-brute-force-enabled = false;
|
||||
|
||||
download-dir = "/var/media";
|
||||
incomplete-dir = "/var/media/incomplete";
|
||||
inherit download-dir;
|
||||
incomplete-dir = "${download-dir}/incomplete";
|
||||
# transmission regularly fails to move stuff from the incomplete dir to the main one, so disable:
|
||||
# TODO: uncomment this line!
|
||||
incomplete-dir-enabled = false;
|
||||
|
||||
# env vars available in script:
|
||||
# - TR_APP_VERSION - Transmission's short version string, e.g. `4.0.0`
|
||||
# - TR_TIME_LOCALTIME
|
||||
# - TR_TORRENT_BYTES_DOWNLOADED - Number of bytes that were downloaded for this torrent
|
||||
# - TR_TORRENT_DIR - Location of the downloaded data
|
||||
# - TR_TORRENT_HASH - The torrent's info hash
|
||||
# - TR_TORRENT_ID
|
||||
# - TR_TORRENT_LABELS - A comma-delimited list of the torrent's labels
|
||||
# - TR_TORRENT_NAME - Name of torrent (not filename)
|
||||
# - TR_TORRENT_TRACKERS - A comma-delimited list of the torrent's trackers' announce URLs
|
||||
script-torrent-done-enabled = true;
|
||||
script-torrent-done-filename = "${torrent-done}/bin/torrent-done";
|
||||
};
|
||||
|
||||
systemd.services.transmission.after = [ "wireguard-wg-ovpns.service" ];
|
||||
@@ -86,6 +133,7 @@ in
|
||||
NetworkNamespacePath = "/run/netns/ovpns";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "30s";
|
||||
BindPaths = [ "/var/media" ]; #< so it can move completed torrents into the media library
|
||||
};
|
||||
|
||||
# service to automatically backup torrents i add to transmission
|
||||
|
@@ -81,6 +81,7 @@ let
|
||||
(fromDb "feeds.simplecast.com/xKJ93w_w" // uncat) # Atlas Obscura
|
||||
(fromDb "feeds.transistor.fm/acquired" // tech)
|
||||
(fromDb "fulltimenix.com" // tech)
|
||||
(fromDb "hackerpublicradio.org" // tech)
|
||||
(fromDb "lexfridman.com/podcast" // rat)
|
||||
(fromDb "mapspodcast.libsyn.com" // uncat) # Multidisciplinary Association for Psychedelic Studies
|
||||
(fromDb "omegataupodcast.net" // tech) # 3/4 German; 1/4 eps are English
|
||||
@@ -103,6 +104,7 @@ let
|
||||
(fromDb "techwontsave.us" // pol) # rec by Cory Doctorow
|
||||
(fromDb "wakingup.libsyn.com" // pol) # Sam Harris
|
||||
(fromDb "werenotwrong.fireside.fm" // pol)
|
||||
(mkPod "https://sfconservancy.org/casts/the-corresponding-source/feeds/ogg/" // tech)
|
||||
|
||||
# (fromDb "feeds.libsyn.com/421877" // rat) # Less Wrong Curated
|
||||
# (fromDb "feeds.megaphone.fm/hubermanlab" // uncat) # Daniel Huberman on sleep
|
||||
@@ -142,6 +144,7 @@ let
|
||||
(fromDb "edwardsnowden.substack.com" // pol // text)
|
||||
(fromDb "fasterthanli.me" // tech)
|
||||
(fromDb "gwern.net" // rat)
|
||||
(fromDb "hardcoresoftware.learningbyshipping.com" // tech) # Steven Sinofsky
|
||||
(fromDb "harihareswara.net" // tech // pol) # rec by Cory Doctorow
|
||||
(fromDb "ianthehenry.com" // tech)
|
||||
(fromDb "idiomdrottning.org" // uncat)
|
||||
@@ -158,6 +161,7 @@ let
|
||||
(fromDb "mg.lol" // tech)
|
||||
(fromDb "mindingourway.com" // rat)
|
||||
(fromDb "morningbrew.com/feed" // pol)
|
||||
(fromDb "nixpkgs.news" // tech)
|
||||
(fromDb "overcomingbias.com" // rat) # Robin Hanson
|
||||
(fromDb "palladiummag.com" // uncat)
|
||||
(fromDb "philosopher.coach" // rat) # Peter Saint-Andre -- side project of stpeter.im
|
||||
@@ -187,6 +191,7 @@ let
|
||||
# (fromDb "vitalik.ca" // tech) # moved to vitalik.eth.limo
|
||||
(fromDb "vitalik.eth.limo" // tech) # Vitalik Buterin
|
||||
# (fromDb "webcurious.co.uk" // uncat) # link aggregator; defunct?
|
||||
(fromDb "willow.phantoma.online") # wizard@xyzzy.link
|
||||
(fromDb "xn--gckvb8fzb.com" // tech)
|
||||
(mkSubstack "astralcodexten" // rat // daily) # Scott Alexander
|
||||
# (mkSubstack "doomberg" // tech // weekly) # articles are all pay-walled
|
||||
@@ -201,6 +206,7 @@ let
|
||||
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
|
||||
(mkText "https://nixos.org/blog/announcements-rss.xml" // tech // infrequent) # more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html>
|
||||
(mkText "https://nixos.org/blog/stories-rss.xml" // tech // weekly)
|
||||
(mkText "https://solar.lowtechmagazine.com/posts/index.xml" // tech // weekly)
|
||||
# (mkText "https://til.simonwillison.net/tils/feed.atom" // tech // weekly)
|
||||
# (mkText "https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" // pol // weekly) # Matt Levine (preview/paywalled)
|
||||
(mkText "https://www.stratechery.com/rss" // pol // weekly) # Ben Thompson
|
||||
@@ -226,6 +232,7 @@ let
|
||||
|
||||
images = [
|
||||
(fromDb "catandgirl.com" // img // humor)
|
||||
(fromDb "davidrevoy.com" // img // art)
|
||||
(fromDb "miniature-calendar.com" // img // art // daily)
|
||||
(fromDb "pbfcomics.com" // img // humor)
|
||||
(fromDb "poorlydrawnlines.com/feed" // img // humor)
|
||||
|
@@ -2,7 +2,7 @@
|
||||
# - x-systemd options: <https://www.freedesktop.org/software/systemd/man/systemd.mount.html>
|
||||
# - fuse options: `man mount.fuse`
|
||||
|
||||
{ lib, pkgs, sane-lib, ... }:
|
||||
{ config, lib, pkgs, sane-lib, utils, ... }:
|
||||
|
||||
let
|
||||
fsOpts = rec {
|
||||
@@ -23,15 +23,15 @@ let
|
||||
# N.B.: `remote-fs.target` is a dependency of multi-user.target, itself of graphical.target.
|
||||
# hence, omitting `noauto` can slow down boots.
|
||||
noauto = [ "noauto" ];
|
||||
# lazyMount: defer mounting until first access from userspace
|
||||
# lazyMount: defer mounting until first access from userspace.
|
||||
# see: `man systemd.automount`, `man automount`, `man autofs`
|
||||
lazyMount = noauto ++ automount;
|
||||
wg = [
|
||||
"x-systemd.requires=wireguard-wg-home.service"
|
||||
"x-systemd.after=wireguard-wg-home.service"
|
||||
];
|
||||
|
||||
ssh = common ++ [
|
||||
"identityfile=/home/colin/.ssh/id_ed25519"
|
||||
fuse = [
|
||||
"allow_other" # allow users other than the one who mounts it to access it. needed, if systemd is the one mounting this fs (as root)
|
||||
# allow_root: allow root to access files on this fs (if mounted by non-root, else it can always access them).
|
||||
# N.B.: if both allow_root and allow_other are specified, then only allow_root takes effect.
|
||||
@@ -44,7 +44,18 @@ let
|
||||
# with default_permissions, sshfs doesn't tunnel file ops from users until checking that said user could perform said op on an equivalent local fs.
|
||||
"default_permissions"
|
||||
];
|
||||
sshColin = ssh ++ [
|
||||
fuseColin = fuse ++ [
|
||||
"uid=1000"
|
||||
"gid=100"
|
||||
];
|
||||
|
||||
ssh = common ++ fuse ++ [
|
||||
"identityfile=/home/colin/.ssh/id_ed25519"
|
||||
# i *think* idmap=user means that `colin` on `localhost` and `colin` on the remote are actually treated as the same user, even if their uid/gid differs?
|
||||
# i.e., local colin's id is translated to/from remote colin's id on every operation?
|
||||
"idmap=user"
|
||||
];
|
||||
sshColin = ssh ++ fuseColin ++ [
|
||||
# follow_symlinks: remote files which are symlinks are presented to the local system as ordinary files (as the target of the symlink).
|
||||
# if the symlink target does not exist, the presentation is unspecified.
|
||||
# symlinks which point outside the mount ARE followed. so this is more capable than `transform_symlinks`
|
||||
@@ -52,9 +63,6 @@ let
|
||||
# symlinks on the remote fs which are absolute paths are presented to the local system as relative symlinks pointing to the expected data on the remote fs.
|
||||
# only symlinks which would point inside the mountpoint are translated.
|
||||
"transform_symlinks"
|
||||
"idmap=user"
|
||||
"uid=1000"
|
||||
"gid=100"
|
||||
];
|
||||
# sshRoot = ssh ++ [
|
||||
# # we don't transform_symlinks because that breaks the validity of remote /nix stores
|
||||
@@ -67,21 +75,43 @@ let
|
||||
# actimeo=n = how long (in seconds) to cache file/dir attributes (default: 3-60s)
|
||||
# bg = retry failed mounts in the background
|
||||
# retry=n = for how many minutes `mount` will retry NFS mount operation
|
||||
# intr = allow Ctrl+C to abort I/O (it will error with `EINTR`)
|
||||
# soft = on "major timeout", report I/O error to userspace
|
||||
# softreval = on "major timeout", service the request using known-stale cache results instead of erroring -- if such cache data exists
|
||||
# retrans=n = how many times to retry a NFS request before giving userspace a "server not responding" error (default: 3)
|
||||
# timeo=n = number of *deciseconds* to wait for a response before retrying it (default: 600)
|
||||
# note: client uses a linear backup, so the second request will have double this timeout, then triple, etc.
|
||||
# proto=udp = encapsulate protocol ops inside UDP packets instead of a TCP session.
|
||||
# requires `nfsvers=3` and a kernel compiled with `NFS_DISABLE_UDP_SUPPORT=n`.
|
||||
# UDP might be preferable to TCP because the latter is liable to hang for ~100s (kernel TCP timeout) after a link drop.
|
||||
# however, even UDP has issues with `umount` hanging.
|
||||
#
|
||||
# N.B.: don't change these without first testing the behavior of sandboxed apps on a flaky network.
|
||||
nfs = common ++ [
|
||||
# "actimeo=10"
|
||||
"bg"
|
||||
"retrans=4"
|
||||
# "actimeo=5"
|
||||
# "bg"
|
||||
"retrans=1"
|
||||
"retry=0"
|
||||
# "intr"
|
||||
"soft"
|
||||
"timeo=15"
|
||||
"softreval"
|
||||
"timeo=30"
|
||||
"nofail" # don't fail remote-fs.target when this mount fails (not an option for sshfs else would be common)
|
||||
# "proto=udp" # default kernel config doesn't support NFS over UDP: <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1964093> (see comment 11).
|
||||
# "nfsvers=3" # NFSv4+ doesn't support UDP at *all*. it's ok to omit nfsvers -- server + client will negotiate v3 based on udp requirement. but omitting causes confusing mount errors when the server is *offline*, because the client defaults to v4 and thinks the udp option is a config error.
|
||||
# "x-systemd.idle-timeout=10" # auto-unmount after this much inactivity
|
||||
];
|
||||
|
||||
# manually perform a ftp mount via e.g.
|
||||
# curlftpfs -o ftpfs_debug=2,user=anonymous:anonymous,connect_timeout=10 -f -s ftp://servo-hn /mnt/my-ftp
|
||||
ftp = common ++ fuseColin ++ [
|
||||
# "ftpfs_debug=2"
|
||||
"user=colin:ipauth"
|
||||
"connect_timeout=10"
|
||||
];
|
||||
};
|
||||
remoteHome = host: {
|
||||
sane.programs.sshfs-fuse.enableFor.system = true;
|
||||
fileSystems."/mnt/${host}/home" = {
|
||||
device = "colin@${host}:/home/colin";
|
||||
fsType = "fuse.sshfs";
|
||||
@@ -94,6 +124,54 @@ let
|
||||
dir.acl.mode = "0700";
|
||||
};
|
||||
};
|
||||
remoteServo = subdir: {
|
||||
sane.programs.curlftpfs.enableFor.system = true;
|
||||
sane.fs."/mnt/servo/${subdir}" = sane-lib.fs.wanted {
|
||||
dir.acl.user = "colin";
|
||||
dir.acl.group = "users";
|
||||
dir.acl.mode = "0750";
|
||||
};
|
||||
fileSystems."/mnt/servo/${subdir}" = {
|
||||
device = "ftp://servo-hn:/${subdir}";
|
||||
noCheck = true;
|
||||
fsType = "fuse.curlftpfs";
|
||||
options = fsOpts.ftp ++ fsOpts.noauto ++ fsOpts.wg;
|
||||
# fsType = "nfs";
|
||||
# options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg;
|
||||
};
|
||||
systemd.services."automount-servo-${utils.escapeSystemdPath subdir}" = let
|
||||
fs = config.fileSystems."/mnt/servo/${subdir}";
|
||||
in {
|
||||
# this is a *flaky* network mount, especially on moby.
|
||||
# if done as a normal autofs mount, access will eternally block when network is dropped.
|
||||
# notably, this would block *any* sandboxed app which allows media access, whether they actually try to use that media or not.
|
||||
# a practical solution is this: mount as a service -- instead of autofs -- and unmount on timeout error, in a restart loop.
|
||||
# until the ftp handshake succeeds, nothing is actually mounted to the vfs, so this doesn't slow down any I/O when network is down.
|
||||
description = "automount /mnt/servo/${subdir} in a fault-tolerant and non-blocking manner";
|
||||
after = [ "network-online.target" ];
|
||||
requires = [ "network-online.target" ];
|
||||
wantedBy = [ "default.target" ];
|
||||
|
||||
serviceConfig.Type = "simple";
|
||||
serviceConfig.ExecStart = lib.escapeShellArgs [
|
||||
"/usr/bin/env"
|
||||
"PATH=/run/current-system/sw/bin"
|
||||
"mount.${fs.fsType}"
|
||||
"-f" # foreground (i.e. don't daemonize)
|
||||
"-s" # single-threaded (TODO: it's probably ok to disable this?)
|
||||
"-o"
|
||||
(lib.concatStringsSep "," (lib.filter (o: !lib.hasPrefix "x-systemd." o) fs.options))
|
||||
fs.device
|
||||
"/mnt/servo/${subdir}"
|
||||
];
|
||||
# not sure if this configures a linear, or exponential backoff.
|
||||
# but the first restart will be after `RestartSec`, and the n'th restart (n = RestartSteps) will be RestartMaxDelaySec after the n-1'th exit.
|
||||
serviceConfig.Restart = "always";
|
||||
serviceConfig.RestartSec = "10s";
|
||||
serviceConfig.RestartMaxDelaySec = "120s";
|
||||
serviceConfig.RestartSteps = "5";
|
||||
};
|
||||
};
|
||||
in
|
||||
lib.mkMerge [
|
||||
{
|
||||
@@ -128,35 +206,6 @@ lib.mkMerge [
|
||||
# but it decreases working memory under the heaviest of loads by however much space the compressed memory occupies (e.g. 50% if 2:1; 25% if 4:1)
|
||||
zramSwap.memoryPercent = 100;
|
||||
|
||||
# fileSystems."/mnt/servo-nfs" = {
|
||||
# device = "servo-hn:/";
|
||||
# noCheck = true;
|
||||
# fsType = "nfs";
|
||||
# options = fsOpts.nfs ++ fsOpts.automount ++ fsOpts.wg;
|
||||
# };
|
||||
fileSystems."/mnt/servo/media" = {
|
||||
device = "servo-hn:/media";
|
||||
noCheck = true;
|
||||
fsType = "nfs";
|
||||
options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg;
|
||||
};
|
||||
sane.fs."/mnt/servo/media" = sane-lib.fs.wanted {
|
||||
dir.acl.user = "colin";
|
||||
dir.acl.group = "users";
|
||||
dir.acl.mode = "0750";
|
||||
};
|
||||
fileSystems."/mnt/servo/playground" = {
|
||||
device = "servo-hn:/playground";
|
||||
noCheck = true;
|
||||
fsType = "nfs";
|
||||
options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg;
|
||||
};
|
||||
sane.fs."/mnt/servo/playground" = sane-lib.fs.wanted {
|
||||
dir.acl.user = "colin";
|
||||
dir.acl.group = "users";
|
||||
dir.acl.mode = "0750";
|
||||
};
|
||||
|
||||
# environment.pathsToLink = [
|
||||
# # needed to achieve superuser access for user-mounted filesystems (see sshRoot above)
|
||||
# # we can only link whole directories here, even though we're only interested in pkgs.openssh
|
||||
@@ -164,13 +213,23 @@ lib.mkMerge [
|
||||
# ];
|
||||
|
||||
programs.fuse.userAllowOther = true; #< necessary for `allow_other` or `allow_root` options.
|
||||
environment.systemPackages = [
|
||||
pkgs.sshfs-fuse
|
||||
];
|
||||
}
|
||||
|
||||
(remoteHome "desko")
|
||||
(remoteHome "lappy")
|
||||
(remoteHome "moby")
|
||||
# this granularity of servo media mounts is necessary to support sandboxing:
|
||||
# for flaky mounts, we can only bind the mountpoint itself into the sandbox,
|
||||
# so it's either this or unconditionally bind all of media/.
|
||||
(remoteServo "media/archive")
|
||||
(remoteServo "media/Books")
|
||||
(remoteServo "media/collections")
|
||||
# (remoteServo "media/datasets")
|
||||
(remoteServo "media/freeleech")
|
||||
(remoteServo "media/games")
|
||||
(remoteServo "media/Music")
|
||||
(remoteServo "media/Pictures/macros")
|
||||
(remoteServo "media/Videos")
|
||||
(remoteServo "playground")
|
||||
]
|
||||
|
||||
|
@@ -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";
|
||||
|
||||
|
@@ -10,6 +10,7 @@
|
||||
XDG_MUSIC_DIR="$HOME/Music"
|
||||
XDG_PICTURES_DIR="$HOME/Pictures"
|
||||
XDG_PUBLICSHARE_DIR="$HOME/.xdg/Public"
|
||||
XDG_SCREENSHOTS_DIR="$HOME/Pictures/Screenshots"
|
||||
XDG_TEMPLATES_DIR="$HOME/.xdg/Templates"
|
||||
XDG_VIDEOS_DIR="$HOME/Videos"
|
||||
'';
|
||||
@@ -17,4 +18,12 @@
|
||||
# prevent `xdg-user-dirs-update` from overriding/updating our config
|
||||
# see <https://manpages.ubuntu.com/manpages/bionic/man5/user-dirs.conf.5.html>
|
||||
sane.user.fs.".config/user-dirs.conf".symlink.text = "enabled=False";
|
||||
|
||||
sane.user.fs.".profile".symlink.text = ''
|
||||
# configure XDG_<type>_DIR preferences (e.g. for downloads, screenshots, etc)
|
||||
# surround with `set -o allexport` since user-dirs.dirs doesn't `export` its vars
|
||||
set -a
|
||||
source $HOME/.config/user-dirs.dirs
|
||||
set +a
|
||||
'';
|
||||
}
|
||||
|
@@ -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'
|
||||
'';
|
||||
});
|
||||
|
||||
|
@@ -79,6 +79,7 @@ in
|
||||
"powertop"
|
||||
"pstree"
|
||||
"ripgrep"
|
||||
"s6-rc" # service manager
|
||||
"screen"
|
||||
"smartmontools" # smartctl
|
||||
# "socat"
|
||||
@@ -122,6 +123,7 @@ in
|
||||
# "gopass"
|
||||
# "gopass-jsonapi"
|
||||
# "helix" # text editor
|
||||
"htop" # needed as a user package, for ~/.config/htop
|
||||
# "libsecret" # for managing user keyrings (secret-tool)
|
||||
# "lm_sensors" # for sensors-detect
|
||||
# "lshw"
|
||||
@@ -274,13 +276,24 @@ in
|
||||
dig.sandbox.net = "all";
|
||||
|
||||
# creds, but also 200 MB of node modules, etc
|
||||
discord.persist.byStore.private = [ ".config/discord" ];
|
||||
discord.suggestedPrograms = [ "xwayland" ];
|
||||
discord.sandbox.method = "bwrap";
|
||||
discord.sandbox.wrapperType = "inplace"; #< /opt-style packaging
|
||||
discord.sandbox.whitelistAudio = true;
|
||||
discord.sandbox.whitelistDbus = [ "user" ]; # needed for xdg-open
|
||||
discord.sandbox.whitelistWayland = true;
|
||||
discord.sandbox.whitelistX = true;
|
||||
discord.sandbox.net = "clearnet";
|
||||
discord.persist.byStore.private = [ ".config/discord" ];
|
||||
discord.sandbox.extraHomePaths = [
|
||||
# still needs these paths despite it using the portal's file-chooser :?
|
||||
"Pictures/cat"
|
||||
"Pictures/Screenshots"
|
||||
"Pictures/servo-macros"
|
||||
"Videos/local"
|
||||
"Videos/servo"
|
||||
"tmp"
|
||||
];
|
||||
|
||||
dtc.sandbox.method = "bwrap";
|
||||
dtc.sandbox.autodetectCliPaths = true; # TODO:sandbox: untested
|
||||
@@ -397,7 +410,7 @@ in
|
||||
gh.persist.byStore.private = [ ".config/gh" ];
|
||||
|
||||
gimp.sandbox.method = "bwrap";
|
||||
gimp.sandbox.net = "clearnet"; #< for Xwayland
|
||||
gimp.sandbox.whitelistX = true;
|
||||
gimp.sandbox.whitelistWayland = true;
|
||||
gimp.sandbox.extraHomePaths = [
|
||||
"Pictures/albums"
|
||||
@@ -509,16 +522,6 @@ in
|
||||
host.sandbox.method = "landlock";
|
||||
host.sandbox.net = "all"; #< technically, only needs to contact localhost's DNS server
|
||||
|
||||
htop.sandbox.method = "landlock";
|
||||
htop.sandbox.extraPaths = [
|
||||
"/proc"
|
||||
"/sys/devices"
|
||||
];
|
||||
htop.persist.byStore.plaintext = [
|
||||
# consider setting `show_program_path=0` and either `hide_userland_threads=1` or `show_thread_names=1`
|
||||
".config/htop"
|
||||
];
|
||||
|
||||
iftop.sandbox.method = "landlock";
|
||||
iftop.sandbox.capabilities = [ "net_raw" ];
|
||||
|
||||
@@ -551,6 +554,10 @@ in
|
||||
iproute2.sandbox.method = "landlock";
|
||||
iproute2.sandbox.net = "all";
|
||||
iproute2.sandbox.capabilities = [ "net_admin" ];
|
||||
iproute2.sandbox.extraPaths = [
|
||||
"/run/netns" # for `ip netns ...` to work
|
||||
"/var/run/netns"
|
||||
];
|
||||
|
||||
iptables.sandbox.method = "landlock";
|
||||
iptables.sandbox.net = "all";
|
||||
@@ -611,19 +618,6 @@ in
|
||||
|
||||
lua = {};
|
||||
|
||||
"mate.engrampa".packageUnwrapped = pkgs.rmDbusServices pkgs.mate.engrampa;
|
||||
"mate.engrampa".sandbox.method = "bwrap"; # TODO:sandbox: untested
|
||||
"mate.engrampa".sandbox.whitelistWayland = true;
|
||||
"mate.engrampa".sandbox.autodetectCliPaths = "existingOrParent";
|
||||
"mate.engrampa".sandbox.extraHomePaths = [
|
||||
"archive"
|
||||
"Books/local"
|
||||
"Books/servo"
|
||||
"records"
|
||||
"ref"
|
||||
"tmp"
|
||||
];
|
||||
|
||||
mercurial.sandbox.method = "bwrap"; # TODO:sandbox: untested
|
||||
mercurial.sandbox.net = "clearnet";
|
||||
mercurial.sandbox.whitelistPwd = true;
|
||||
@@ -829,6 +823,8 @@ in
|
||||
|
||||
sqlite = {};
|
||||
|
||||
sshfs-fuse = {}; # used by fs.nix
|
||||
|
||||
strace.sandbox.enable = false; #< needs to `exec` its args, and therefore support *anything*
|
||||
|
||||
subversion.sandbox.method = "bwrap";
|
||||
@@ -922,7 +918,7 @@ in
|
||||
xwayland.sandbox.method = "bwrap";
|
||||
xwayland.sandbox.wrapperType = "inplace"; #< consumers use it as a library (e.g. wlroots)
|
||||
xwayland.sandbox.whitelistWayland = true; #< just assuming this is needed
|
||||
xwayland.sandbox.net = "clearnet"; #< just assuming this is needed (X11 traffic)
|
||||
xwayland.sandbox.whitelistX = true;
|
||||
xwayland.sandbox.whitelistDri = true; #< would assume this gives better gfx perf
|
||||
|
||||
xterm.sandbox.enable = false; # need to be able to do everything
|
||||
|
@@ -1,3 +1,8 @@
|
||||
# tips/tricks
|
||||
# - audio recording
|
||||
# - default recording input will be silent, on lappy.
|
||||
# - Audio Setup -> Rescan Audio Devices ...
|
||||
# - Audio Setup -> Recording device -> sysdefault
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.audacity = {
|
||||
@@ -20,6 +25,9 @@
|
||||
# audacity needs the entire config dir mounted if running in a sandbox
|
||||
".config/audacity"
|
||||
];
|
||||
sandbox.extraPaths = [
|
||||
"/dev/snd" # for recording audio inputs to work
|
||||
];
|
||||
|
||||
# disable first-run splash screen
|
||||
fs.".config/audacity/audacity.cfg".file.text = ''
|
||||
|
@@ -118,18 +118,9 @@ in
|
||||
|
||||
services.bonsaid = {
|
||||
description = "bonsai: programmable input dispatcher";
|
||||
after = [ "graphical-session.target" ];
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
|
||||
script = ''
|
||||
${pkgs.coreutils}/bin/rm -f $XDG_RUNTIME_DIR/bonsai
|
||||
exec ${cfg.package}/bin/bonsaid -t ${cfg.config.configFile}
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
};
|
||||
partOf = [ "graphical-session" ];
|
||||
command = "bonsaid -t ${cfg.config.configFile}";
|
||||
cleanupCommand = "rm -f $XDG_RUNTIME_DIR/bonsai";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -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 = {
|
||||
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
|
||||
# add --verbose for more debugging
|
||||
ExecStart = "${cfg.package}/bin/gnome-calls --daemon";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
environment.G_MESSAGES_DEBUG = "all";
|
||||
command = "env G_MESSAGES_DEBUG=all gnome-calls --daemon";
|
||||
};
|
||||
};
|
||||
programs.calls = lib.mkIf cfg.enabled {
|
||||
|
@@ -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";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
25
hosts/common/programs/curlftpfs.nix
Normal file
25
hosts/common/programs/curlftpfs.nix
Normal file
@@ -0,0 +1,25 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.curlftpfs = {
|
||||
packageUnwrapped = pkgs.curlftpfs.overrideAttrs (upstream: {
|
||||
# my fork includes:
|
||||
# - per-operation timeouts (CURLOPT_TIMEOUT; would use CURLOPT_LOW_SPEED_TIME/CURLOPT_LOW_SPEED_LIMIT but they don't apply)
|
||||
# - exit on timeout (so that one knows to abort the mount, instead of waiting indefinitely)
|
||||
# - support for "meta" keys found in /etc/fstab
|
||||
src = pkgs.fetchFromGitea {
|
||||
domain = "git.uninsane.org";
|
||||
owner = "colin";
|
||||
repo = "curlftpfs";
|
||||
rev = "0890d32e709b5a01153f00d29ed4c00299744f5d";
|
||||
hash = "sha256-M28PzHqEAkezQdtPeL16z56prwl3BfMZqry0dlpXJls=";
|
||||
};
|
||||
# `mount` clears PATH before calling the mount helper (see util-linux/lib/env.c),
|
||||
# so the traditional /etc/fstab approach of fstype=fuse and device = curlftpfs#URI doesn't work.
|
||||
# instead, install a `mount.curlftpfs` mount helper. this is what programs like `gocryptfs` do.
|
||||
postInstall = (upstream.postInstall or "") + ''
|
||||
ln -s curlftpfs $out/bin/mount.fuse.curlftpfs
|
||||
ln -s curlftpfs $out/bin/mount.curlftpfs
|
||||
'';
|
||||
});
|
||||
};
|
||||
}
|
@@ -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,12 +21,14 @@
|
||||
./chatty.nix
|
||||
./conky
|
||||
./cozy.nix
|
||||
./curlftpfs.nix
|
||||
./dconf.nix
|
||||
./deadd-notification-center
|
||||
./dialect.nix
|
||||
./dino.nix
|
||||
./dissent.nix
|
||||
./element-desktop.nix
|
||||
./engrampa.nix
|
||||
./epiphany.nix
|
||||
./evince.nix
|
||||
./fcitx5.nix
|
||||
@@ -53,6 +55,7 @@
|
||||
./gthumb.nix
|
||||
./handbrake.nix
|
||||
./helix.nix
|
||||
./htop
|
||||
./imagemagick.nix
|
||||
./jellyfin-media-player.nix
|
||||
./kdenlive.nix
|
||||
@@ -87,6 +90,7 @@
|
||||
./rhythmbox.nix
|
||||
./ripgrep.nix
|
||||
./rofi
|
||||
./s6-rc.nix
|
||||
./sane-input-handler
|
||||
./sane-screenshot.nix
|
||||
./sane-scripts.nix
|
||||
@@ -118,7 +122,6 @@
|
||||
./wine.nix
|
||||
./wireplumber.nix
|
||||
./wireshark.nix
|
||||
./wob
|
||||
./wvkbd.nix
|
||||
./xarchiver.nix
|
||||
./xdg-desktop-portal.nix
|
||||
|
@@ -14,6 +14,11 @@
|
||||
# but at present it has no "start in tray" type of option: it must render a window.
|
||||
#
|
||||
# outstanding bugs:
|
||||
# - NAT holepunching burns CPU/NET when multiple interfaces are up
|
||||
# - fix by just `ip link set ovpnd-xyz down`
|
||||
# - setting `wg-home` down *seems* to be not necessary
|
||||
# - characterized by UPnP/SOAP error 500/718 in wireshark
|
||||
# - seems it asks router A to open a port mapping for an IP address which belongs to a different subnet...
|
||||
# - mic is sometimes disabled at call start despite presenting as enabled
|
||||
# - fix is to toggle it off -> on in the Dino UI
|
||||
# - default mic gain is WAY TOO MUCH (heavily distorted)
|
||||
@@ -29,7 +34,7 @@
|
||||
# - possibly Dino should be updated internally: `info.rate / 100` -> `info.rate / 50`.
|
||||
# - i think that affects the batching for echo cancellation, adaptive gain control, etc.
|
||||
#
|
||||
{ config, lib, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.dino;
|
||||
in
|
||||
@@ -45,6 +50,24 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
packageUnwrapped = pkgs.dino.overrideAttrs (upstream: {
|
||||
# i'm updating experimentally to see if it improves call performance.
|
||||
# i don't *think* this is actually necessary; i don't notice any difference.
|
||||
version = "0.4.3-unstable-2024-04-01";
|
||||
src = lib.warnIf (lib.versionOlder "0.4.3" upstream.version) "dino update: safe to remove sane patches" pkgs.fetchFromGitHub {
|
||||
owner = "dino";
|
||||
repo = "dino";
|
||||
rev = "d9fa4daa6a7d16f5f0e2183a77ee2d07849dd9f3";
|
||||
hash = "sha256-vJBIMsMLlK8Aw19fD2aFNtegXkjOqEgb3m1hi3fE5DE=";
|
||||
};
|
||||
checkPhase = ''
|
||||
runHook preCheck
|
||||
./xmpp-vala-test
|
||||
# ./signal-protocol-vala-test # doesn't exist anymore
|
||||
runHook postCheck
|
||||
'';
|
||||
});
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.net = "clearnet";
|
||||
sandbox.whitelistAudio = true;
|
||||
@@ -68,26 +91,22 @@ in
|
||||
|
||||
services.dino = {
|
||||
description = "dino XMPP client";
|
||||
after = [ "graphical-session.target" ];
|
||||
# partOf = [ "graphical-session.target" ];
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/dino";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
|
||||
|
||||
# audio buffering; see: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#pipewire-buffering-explained>
|
||||
# dino defaults to 10ms mic buffer, which causes underruns, which Dino handles *very* poorly
|
||||
# as in, the other end of the call will just not receive sound from us for a couple seconds.
|
||||
# pipewire uses power-of-two buffering for the mic itself. that would put us at 21.33 ms, but this env var supports only whole numbers (21ms ends up not power-of-two).
|
||||
# also, Dino's likely still doing things in 10ms batches internally anyway.
|
||||
environment.PULSE_LATENCY_MSEC = "20";
|
||||
|
||||
#
|
||||
# further: decrease the "niceness" of dino, so that it can take precedence over anything else.
|
||||
# ideally this would target just the audio processing, rather than the whole program.
|
||||
# pipewire is the equivalent of `nice -n -21`, so probably don't want to go any more extreme than that.
|
||||
# nice -n -15 chosen arbitrarily; not optimized
|
||||
#
|
||||
# note that debug logging during calls produces so much journal spam that it pegs the CPU and causes dropped audio
|
||||
# environment.G_MESSAGES_DEBUG = "all";
|
||||
# env G_MESSAGES_DEBUG = "all";
|
||||
command = "env PULSE_LATENCY_MSEC=20 nice -n -15 dino";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ in
|
||||
# - <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";
|
||||
@@ -57,15 +57,8 @@ in
|
||||
|
||||
services.dissent = {
|
||||
description = "dissent Discord client";
|
||||
after = [ "graphical-session.target" ];
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/dissent";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
|
||||
command = "dissent";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -4,16 +4,25 @@
|
||||
# - <https://github.com/vector-im/element-desktop/issues/1029#issuecomment-1632688224>
|
||||
# - `rm -rf ~/.config/Element/GPUCache`
|
||||
# - <https://github.com/NixOS/nixpkgs/issues/244486>
|
||||
{ pkgs, ... }:
|
||||
{ lib, pkgs, ... }:
|
||||
{
|
||||
sane.programs.element-desktop = {
|
||||
packageUnwrapped = pkgs.element-desktop.override {
|
||||
# use pre-build electron because otherwise it takes 4 hrs to build from source.
|
||||
electron = pkgs.electron-bin;
|
||||
};
|
||||
packageUnwrapped = (pkgs.element-desktop.override {
|
||||
# use pre-built electron because otherwise it takes 4 hrs to build from source.
|
||||
electron = pkgs.electron_28-bin;
|
||||
}).overrideAttrs (upstream: {
|
||||
# fix to use wayland instead of Xwayland:
|
||||
# - replace `NIXOS_OZONE_WL` non-empty check with `WAYLAND_DISPLAY`
|
||||
# - use `wayland` instead of `auto` because --ozone-platform-hint=auto still prefers X over wayland when both are available
|
||||
# alternatively, set env var: `ELECTRON_OZONE_PLATFORM_HINT=wayland` and ignore all of this
|
||||
installPhase = lib.replaceStrings
|
||||
[ "NIXOS_OZONE_WL" "--ozone-platform-hint=auto" ]
|
||||
[ "WAYLAND_DISPLAY" "--ozone-platform-hint=wayland" ]
|
||||
upstream.installPhase
|
||||
;
|
||||
});
|
||||
suggestedPrograms = [
|
||||
"gnome-keyring"
|
||||
"xwayland"
|
||||
];
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
|
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"
|
||||
];
|
||||
};
|
||||
}
|
@@ -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"
|
||||
]);
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -68,17 +68,10 @@ in
|
||||
|
||||
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";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -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,7 +1,4 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.gnome-keyring;
|
||||
in
|
||||
{ lib, pkgs, ... }:
|
||||
{
|
||||
sane.programs.gnome-keyring = {
|
||||
packageUnwrapped = pkgs.rmDbusServices pkgs.gnome.gnome-keyring;
|
||||
@@ -29,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.
|
||||
@@ -44,22 +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";
|
||||
ExecStartPre = "${pkgs.coreutils}/bin/mkdir -m 0700 -p %t/keyring";
|
||||
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";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@
|
||||
|
||||
# disable expensive sambda dependency; i don't use it.
|
||||
packageUnwrapped = pkgs.handbrake.override {
|
||||
ffmpeg_5-full = pkgs.ffmpeg_5-full.override {
|
||||
ffmpeg-full = pkgs.ffmpeg-full.override {
|
||||
withSamba = false;
|
||||
};
|
||||
};
|
||||
|
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 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
|
@@ -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";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -9,7 +9,18 @@
|
||||
# - list: <https://github.com/stax76/awesome-mpv>
|
||||
# - list: <https://nudin.github.io/mpv-script-directory/>
|
||||
# - browse DLNA shares: <https://github.com/chachmu/mpvDLNA>
|
||||
# - act as a DLNS renderer (sink): <https://github.com/xfangfang/Macast>
|
||||
# - act as a DLNA renderer (sink): <https://github.com/xfangfang/Macast>
|
||||
# - update watch_later periodically -- not just on exit: <https://gist.github.com/CyberShadow/2f71a97fb85ed42146f6d9f522bc34ef>
|
||||
# - <https://github.com/AN3223/dotfiles/blob/master/.config/mpv/scripts/auto-save-state.lua>
|
||||
# - touch shortcuts (double-tap L/R portions of window to seek, etc): <https://github.com/christoph-heinrich/mpv-touch-gestures>
|
||||
# - <https://github.com/omeryagmurlu/mpv-gestures>
|
||||
# - jellyfin client: <https://github.com/EmperorPenguin18/mpv-jellyfin>
|
||||
# - DLNA client (player only: no casting): <https://github.com/chachmu/mpvDLNA>
|
||||
# - search videos on Youtube: <https://github.com/rozari0/mpv-youtube-search>
|
||||
# - <https://github.com/CogentRedTester/mpv-scripts/blob/master/youtube-search.lua>
|
||||
# - sponsorblock: <https://codeberg.org/jouni/mpv_sponsorblock_minimal>
|
||||
# - screenshot-to-clipboard: <https://github.com/zc62/mpv-scripts/blob/master/screenshot-to-clipboard.js>
|
||||
# - mpv-as-image-viewer: <https://github.com/guidocella/mpv-image-config>
|
||||
# debugging:
|
||||
# - enter console by pressing backtick.
|
||||
# > `set volume 50` -> sets application volume to 50%
|
||||
@@ -17,6 +28,7 @@
|
||||
# > `show-text "vol: ${volume}"` -> get the volume
|
||||
# - show script output by running mpv with `--msg-level=all=trace`
|
||||
# - and then just `print(...)` from lua & it'll show in terminal
|
||||
# - requires that mpv.conf NOT include player-operation-mode=pseudo-gui
|
||||
# - invoke mpv with `--no-config` to have it not read ~/.config/mpv/*
|
||||
# - press `i` to show decoder info
|
||||
#
|
||||
@@ -28,53 +40,92 @@
|
||||
let
|
||||
cfg = config.sane.programs.mpv;
|
||||
uosc = pkgs.mpvScripts.uosc.overrideAttrs (upstream: {
|
||||
# patch so that the volume control corresponds to `ao-volume`, i.e. the system-wide volume.
|
||||
# this is particularly nice for moby, because it avoids the awkwardness that system volume
|
||||
# is hard to adjust while screen is on.
|
||||
# note that only under alsa (`-ao=alsa`) does `ao-volume` actually correspond to system volume.
|
||||
postPatch = (upstream.postPatch or "") + ''
|
||||
substituteInPlace src/uosc/main.lua \
|
||||
--replace-fail "mp.observe_property('volume'" "mp.observe_property('ao-volume'"
|
||||
substituteInPlace src/uosc/elements/Volume.lua \
|
||||
--replace-fail "mp.commandv('set', 'volume'" "mp.commandv('set', 'ao-volume'" \
|
||||
--replace-fail "mp.set_property_native('volume'" "mp.set_property('ao-volume'"
|
||||
version = "5.2.0-unstable-2024-03-13";
|
||||
src = lib.warnIf (lib.versionOlder "5.2.0" upstream.version) "uosc outdated; remove patch?" pkgs.fetchFromGitHub {
|
||||
owner = "tomasklaen";
|
||||
repo = "uosc";
|
||||
rev = "6fa34c31d0a5290dee83282205768d15111df7d8";
|
||||
hash = "sha256-qxyNZHmH33bKRp4heFSC+RtvSApIfbVFt4otfS351nE=";
|
||||
};
|
||||
# src = pkgs.fetchFromGitea {
|
||||
# domain = "git.uninsane.org";
|
||||
# owner = "colin";
|
||||
# repo = "uosc";
|
||||
# rev = "dev-sane-5.2.0";
|
||||
# hash = "sha256-lpqk4nnCxDZr/Y7/seM4VyR30fVrDAT4VP7C8n88lvA=";
|
||||
# };
|
||||
|
||||
# `ao-volume` isn't actually an observable property.
|
||||
# as of 2024/03/02, they *may* be working on that:
|
||||
# - <https://github.com/mpv-player/mpv/pull/13604#issuecomment-1971665736>
|
||||
# in the meantime, just query the volume every tick (i.e. frame).
|
||||
# alternative is mpv's JSON IPC feature, where i could notify its socket whenever pipewire volume changes.
|
||||
cat <<EOF >> src/uosc/main.lua
|
||||
function update_ao_volume()
|
||||
local vol = mp.get_property('ao-volume')
|
||||
if vol ~= nil then
|
||||
vol = tonumber(vol)
|
||||
if vol ~= state.volume then
|
||||
set_state('volume', vol)
|
||||
request_render()
|
||||
postPatch = (upstream.postPatch or "") + ''
|
||||
### patch so touch controls work well with sway 1.9+
|
||||
### in particular, "mouse.hover" is *always* false for touch events (i guess this is a bug in mpv?)
|
||||
### and a touch release event is always followed by a mouse move to the cursor (that's a sway thing) which doesn't make sense.
|
||||
# 1. always listen for mbtn_left events, even before a hover event would activate a zone:
|
||||
substituteInPlace src/uosc/lib/cursor.lua \
|
||||
--replace-fail \
|
||||
"if binding and cursor:collides_with(zone.hitbox)" \
|
||||
"if binding"
|
||||
# 2. uosc already simulates mouse movements on touch down, but because of the hover handling, they get misunderstood as mouse leaves.
|
||||
# so, bypass the cursor:leave() check.
|
||||
substituteInPlace src/uosc/lib/cursor.lua \
|
||||
--replace-fail \
|
||||
"handle_mouse_pos(nil, mp.get_property_native('mouse-pos'))" \
|
||||
"local mpos = mp.get_property_native('mouse-pos')
|
||||
cursor:move(mpos.x, mpos.y)
|
||||
cursor.hover_raw = mpos.hover"
|
||||
# 3. explicitly fire a cursor:leave on touch release, so that all zones are deactivated (and control visibility goes back to default state)
|
||||
substituteInPlace src/uosc/lib/cursor.lua \
|
||||
--replace-fail \
|
||||
"cursor:create_handler('primary_up')" \
|
||||
"function(...)
|
||||
cursor:trigger('primary_up', ...)
|
||||
if not cursor.hover_raw then
|
||||
cursor:leave()
|
||||
end
|
||||
end
|
||||
end
|
||||
-- tick seems to occur on every redraw (even when volume is hidden).
|
||||
-- in practice: for every new frame of the source, or whenever the cursor is moved.
|
||||
mp.register_event('tick', update_ao_volume)
|
||||
-- if paused and cursor isn't moving, then `tick` isn't called. fallback to a timer.
|
||||
mp.add_periodic_timer(2, update_ao_volume)
|
||||
-- invoke immediately to ensure state.volume is non-nil
|
||||
update_ao_volume()
|
||||
if state.volume == nil then
|
||||
state.volume = 0
|
||||
end
|
||||
EOF
|
||||
end"
|
||||
# 4. sometimes we get a touch movement shortly AFTER touch is released:
|
||||
# detect that and ignore it
|
||||
substituteInPlace src/uosc/lib/cursor.lua \
|
||||
--replace-fail \
|
||||
"cursor:move(mouse.x, mouse.y)" \
|
||||
"local last_down = cursor.last_event['primary_down'] or { time = 0 }
|
||||
local last_up = cursor.last_event['primary_up'] or { time = 0 }
|
||||
if cursor.hover_raw or last_down.time >= last_up.time then cursor:move(mouse.x, mouse.y) end"
|
||||
|
||||
### patch so that uosc volume control is routed to sane-sysvol.
|
||||
### this is particularly nice for moby, because it avoids the awkwardness that system volume
|
||||
### is hard to adjust while screen is on.
|
||||
### previously i used ao-volume instead of sane-sysvol: but that forced `ao=alsa`
|
||||
### and came with heavy perf penalties (especially when adjusting the volume)
|
||||
substituteInPlace src/uosc/main.lua \
|
||||
--replace-fail \
|
||||
"mp.observe_property('volume'" \
|
||||
"mp.observe_property('user-data/sane-sysvol/volume'"
|
||||
substituteInPlace src/uosc/elements/Volume.lua \
|
||||
--replace-fail \
|
||||
"mp.commandv('set', 'volume'" \
|
||||
"mp.set_property_native('user-data/sane-sysvol/volume'" \
|
||||
--replace-fail \
|
||||
"mp.set_property_native('volume'" \
|
||||
"mp.set_property_native('user-data/sane-sysvol/volume'"
|
||||
'';
|
||||
});
|
||||
mpv-unwrapped = pkgs.mpv-unwrapped.overrideAttrs (upstream: {
|
||||
version = "0.37.0-unstable-2024-03-31";
|
||||
src = lib.warnIf (lib.versionOlder "0.37.0" upstream.version) "mpv outdated; remove patch?" pkgs.fetchFromGitHub {
|
||||
owner = "mpv-player";
|
||||
repo = "mpv";
|
||||
rev = "4ce4bf1795e6dfd6f1ddf07fb348ce5d191ab1dc";
|
||||
hash = "sha256-nOGuHq7SWDAygROV7qHtezDv1AsMpseImI8TVd3F+Oc=";
|
||||
};
|
||||
patches = [];
|
||||
});
|
||||
in
|
||||
{
|
||||
sane.programs.mpv = {
|
||||
packageUnwrapped = with pkgs; wrapMpv mpv-unwrapped {
|
||||
packageUnwrapped = pkgs.wrapMpv (mpv-unwrapped.override { lua = pkgs.luajit; }) {
|
||||
scripts = [
|
||||
mpvScripts.mpris
|
||||
mpvScripts.mpv-playlistmanager
|
||||
pkgs.mpvScripts.mpris
|
||||
pkgs.mpvScripts.mpv-playlistmanager
|
||||
uosc
|
||||
# pkgs.mpv-uosc-latest
|
||||
];
|
||||
@@ -139,7 +190,9 @@ in
|
||||
# for `watch_later`
|
||||
".local/state/mpv"
|
||||
];
|
||||
fs.".config/mpv/scripts/sane/main.lua".symlink.target = ./sane-main.lua;
|
||||
fs.".config/mpv/scripts/sane-cast/main.lua".symlink.target = ./sane-cast-main.lua;
|
||||
fs.".config/mpv/scripts/sane-sysvol/main.lua".symlink.target = ./sane-sysvol/main.lua;
|
||||
fs.".config/mpv/scripts/sane-sysvol/non_blocking_popen.lua".symlink.target = ./sane-sysvol/non_blocking_popen.lua;
|
||||
fs.".config/mpv/input.conf".symlink.target = ./input.conf;
|
||||
fs.".config/mpv/mpv.conf".symlink.target = ./mpv.conf;
|
||||
fs.".config/mpv/script-opts/osc.conf".symlink.target = ./osc.conf;
|
||||
|
@@ -31,7 +31,7 @@ ctrl+s async screenshot #! Utils > Screenshot
|
||||
alt+i script-binding uosc/keybinds #! Utils > Key bindings
|
||||
O script-binding uosc/show-in-directory #! Utils > Show in directory
|
||||
# script-binding uosc/open-config-directory #! Utils > Open config directory
|
||||
ctrl+r script-binding sane/blast #! Audiocast
|
||||
ctrl+t script-binding sane/go2tv-video #! Cast
|
||||
# script-binding sane/go2tv-stream #! Cast (...) > Stream
|
||||
# script-binding sane/go2tv-gui #! Cast (...) > GUI
|
||||
ctrl+r script-binding sane-cast/blast #! Audiocast
|
||||
ctrl+t script-binding sane-cast/go2tv-video #! Cast
|
||||
# script-binding sane-cast/go2tv-stream #! Cast (...) > Stream
|
||||
# script-binding sane-cast/go2tv-gui #! Cast (...) > GUI
|
||||
|
240
hosts/common/programs/mpv/sane-sysvol/main.lua
Normal file
240
hosts/common/programs/mpv/sane-sysvol/main.lua
Normal file
@@ -0,0 +1,240 @@
|
||||
msg = require('mp.msg')
|
||||
msg.trace('sane-sysvol: load: begin')
|
||||
|
||||
non_blocking_popen = require("non_blocking_popen")
|
||||
|
||||
RD_SIZE = 4096
|
||||
|
||||
function startswith(superstring, substring)
|
||||
return superstring:sub(1, substring:len()) == substring
|
||||
end
|
||||
function strip_prefix(superstring, substring)
|
||||
return superstring:sub(substring:len())
|
||||
end
|
||||
|
||||
function ltrim(s)
|
||||
-- remove all leading whitespace from `s`
|
||||
local i = 1
|
||||
while s:sub(i, i) == " " or s:sub(i, i) == "\t" do
|
||||
i = i + 1
|
||||
end
|
||||
return s:sub(i)
|
||||
end
|
||||
|
||||
function subprocess(args)
|
||||
mp.command_native({
|
||||
name = "subprocess",
|
||||
args = args,
|
||||
-- these arguments below probably don't matter: copied from sane-cast
|
||||
detach = false,
|
||||
capture_stdout = false,
|
||||
capture_stderr = false,
|
||||
passthrough_stdin = false,
|
||||
playback_only = false,
|
||||
})
|
||||
end
|
||||
|
||||
function sysvol_new()
|
||||
return {
|
||||
-- sysvol is pipewire-native volume
|
||||
-- it's the cube of the equivalent 0-100% value represented inside mpv
|
||||
sysvol = nil,
|
||||
change_sysvol = function(self, mpv_vol)
|
||||
-- called when mpv wants to set the system-wide volume
|
||||
if mpv_vol == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local old_mpv_vol = nil
|
||||
if self.sysvol ~= nil then
|
||||
old_mpv_vol = 100 * self.sysvol^(1/3)
|
||||
end
|
||||
if old_mpv_vol ~= nil and math.floor(old_mpv_vol) == math.floor(mpv_vol) then
|
||||
return
|
||||
end
|
||||
|
||||
local volstr = tostring(mpv_vol) .. "%"
|
||||
msg.debug("setting system-wide volume:", volstr)
|
||||
self.sysvol = (0.01*mpv_vol)^3
|
||||
subprocess({
|
||||
"wpctl",
|
||||
"set-volume",
|
||||
"@DEFAULT_AUDIO_SINK@",
|
||||
volstr
|
||||
})
|
||||
end,
|
||||
on_sysvol_change = function(self, sysvol)
|
||||
if sysvol == nil then
|
||||
return
|
||||
end
|
||||
-- called when the pipewire system volume is changed (either by us, or an external application)
|
||||
local new_mpv_vol = 100 * sysvol^(1/3)
|
||||
local old_mpv_vol = nil
|
||||
if self.sysvol ~= nil then
|
||||
old_mpv_vol = 100 * self.sysvol^(1/3)
|
||||
end
|
||||
|
||||
if old_mpv_vol ~= nil and math.abs(new_mpv_vol - old_mpv_vol) < 1.0 then
|
||||
msg.debug("NOT announcing volume change to mpv (because it was what triggered the change):", old_mpv_vol, new_mpv_vol)
|
||||
return
|
||||
end
|
||||
|
||||
self.sysvol = sysvol
|
||||
msg.debug("announcing volume change to mpv:", old_mpv_vol, new_mpv_vol)
|
||||
mp.set_property_native("user-data/sane-sysvol/volume", new_mpv_vol)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
function pwmon_parser_new()
|
||||
return {
|
||||
-- volume: pipewire-native volume. usually 0.0 - 1.0, but can go higher (e.g. 3.25)
|
||||
-- `wpctl get-volume` and this volume are related, in that the volume reported by
|
||||
-- wpctl is the cube-root of this one.
|
||||
volume = {}, -- object-id (number) -> volume (number)
|
||||
mute = {}, -- object-id (number) -> mute (bool)
|
||||
last_audio_device_id = nil, -- TODO: might not actually be necessary
|
||||
|
||||
-- parser state:
|
||||
in_changed = false,
|
||||
changed_id = nil,
|
||||
in_device = false,
|
||||
in_direction = false,
|
||||
in_output = false,
|
||||
in_vol = false,
|
||||
in_mute = false,
|
||||
|
||||
feed_line = function(self, line)
|
||||
line = ltrim(line)
|
||||
if startswith(line, "changed:") then
|
||||
self.in_changed = true
|
||||
self.changed_id = nil
|
||||
self.in_device = false
|
||||
self.in_direction = false
|
||||
self.in_output = false
|
||||
self.in_vol = false
|
||||
self.in_mute = false
|
||||
self.in_properties = false
|
||||
elseif startswith(line, "added:") or startswith(line, "removed:") then
|
||||
self.in_changed = false
|
||||
self.changed_id = nil
|
||||
self.in_device = false
|
||||
self.in_direction = false
|
||||
self.in_output = false
|
||||
self.in_vol = false
|
||||
self.in_mute = false
|
||||
self.in_properties = false
|
||||
elseif startswith(line, "id: ") and self.in_changed then
|
||||
if self.changed_id == nil then
|
||||
self.changed_id = tonumber(strip_prefix(line, "id: "))
|
||||
msg.debug("changed_id:", self.changed_id)
|
||||
end
|
||||
elseif startswith(line, "type: ") and self.in_changed then
|
||||
self.in_device = startswith(line, "type: PipeWire:Interface:Device")
|
||||
msg.trace("parsed type:", line, self.in_device)
|
||||
elseif startswith(line, "Prop: ") and self.in_changed and self.in_device then
|
||||
self.in_direction = startswith(line, "Prop: key Spa:Pod:Object:Param:Route:direction")
|
||||
if self.in_direction then
|
||||
self.in_output = false
|
||||
end
|
||||
-- which of the *Volumes params we read is unclear.
|
||||
-- alternative to this is to just detect the change, and then cal wpctl get-volume @DEFAULT_AUDIO_SINK@
|
||||
self.in_vol = startswith(line, "Prop: key Spa:Pod:Object:Param:Props:channelVolumes")
|
||||
self.in_mute = startswith(line, "Prop: key Spa:Pod:Object:Param:Props:softMute")
|
||||
msg.trace("parsed `Prop:`", line, self.in_vol)
|
||||
elseif line:find("Spa:Enum:Direction:Output", 1, true) and self.in_direction then
|
||||
self.in_output = true
|
||||
elseif startswith(line, "Float ") and self.in_changed and self.in_device and self.in_output and self.in_vol then
|
||||
value = tonumber(strip_prefix(line, "Float "))
|
||||
self:feed_volume(value)
|
||||
elseif startswith(line, "Bool ") and self.in_changed and self.in_device and self.in_output and self.in_mute then
|
||||
value = tonumber(strip_prefix(line, "Bool ")) == "true"
|
||||
self:feed_mute(value)
|
||||
elseif startswith(line, "properties:") and self.in_changed and self.in_device then
|
||||
self.in_properties = true
|
||||
elseif line == 'media.class = "Audio/Device"' and self.in_changed and self.in_device and self.in_properties then
|
||||
self.last_audio_device_id = self.changed_id
|
||||
msg.debug("last_audio_device_id:", self.changed_id)
|
||||
end
|
||||
end,
|
||||
|
||||
feed_volume = function(self, vol)
|
||||
msg.debug("volume:", self.changed_id, vol)
|
||||
self.volume[self.changed_id] = vol
|
||||
end,
|
||||
feed_mute = function(self, mute)
|
||||
msg.debug("mute:", self.changed_id, mute)
|
||||
self.mute[self.changed_id] = mute
|
||||
end,
|
||||
get_effective_volume = function(self, id)
|
||||
if id == nil then
|
||||
id = self.last_audio_device_id
|
||||
end
|
||||
|
||||
if self.mute[id] then
|
||||
return 0
|
||||
else
|
||||
return self.volume[id]
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
function pwmon_new()
|
||||
return {
|
||||
-- non_blocking_popen handle for the pw-mon process
|
||||
-- which can be periodically read and parsed to detect volume changes
|
||||
handle = non_blocking_popen.non_blocking_popen("pw-mon", RD_SIZE),
|
||||
stdout_unparsed = "",
|
||||
pwmon_parser = pwmon_parser_new(),
|
||||
service = function(self)
|
||||
-- do a single non-blocking read, and parse the result
|
||||
-- in the *rare* case in which more than RD_SIZE data is ready, we service that remaining data on the next call
|
||||
local buf, res = self.handle:read(RD_SIZE)
|
||||
if res == "closed" then
|
||||
msg.debug("pw-mon unexpectedly closed!")
|
||||
end
|
||||
if buf ~= nil then
|
||||
self.stdout_unparsed = self.stdout_unparsed .. buf
|
||||
self:consume_stdout()
|
||||
end
|
||||
end,
|
||||
consume_stdout = function(self)
|
||||
local idx_newline, next_newline = 0, 0
|
||||
while next_newline ~= nil do
|
||||
next_newline = self.stdout_unparsed:find("\n", idx_newline + 1, true)
|
||||
if next_newline ~= nil then
|
||||
self:ingest_line(self.stdout_unparsed:sub(idx_newline + 1, next_newline - 1))
|
||||
idx_newline = next_newline
|
||||
end
|
||||
end
|
||||
self.stdout_unparsed = self.stdout_unparsed:sub(idx_newline + 1)
|
||||
end,
|
||||
ingest_line = function(self, line)
|
||||
msg.trace("pw-mon:", line)
|
||||
local old_vol = self.pwmon_parser:get_effective_volume()
|
||||
self.pwmon_parser:feed_line(line)
|
||||
local new_vol = self.pwmon_parser:get_effective_volume()
|
||||
if new_vol ~= old_vol then
|
||||
msg.debug("pipewire volume change:", old_vol, new_vol)
|
||||
mp.set_property_native("user-data/sane-sysvol/pw-mon-volume", new_vol)
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
mp.set_property_native("user-data/sane-sysvol/volume", 0)
|
||||
|
||||
local sysvol = sysvol_new()
|
||||
mp.observe_property("user-data/sane-sysvol/volume", "native", function(_, val)
|
||||
sysvol:change_sysvol(val)
|
||||
end)
|
||||
mp.observe_property("user-data/sane-sysvol/pw-mon-volume", "native", function(_, val)
|
||||
sysvol:on_sysvol_change(val)
|
||||
end)
|
||||
|
||||
local pwmon = pwmon_new()
|
||||
mp.register_event('tick', function() pwmon:service() end)
|
||||
|
||||
msg.trace("sane-sysvol: load: complete")
|
80
hosts/common/programs/mpv/sane-sysvol/non_blocking_popen.lua
Normal file
80
hosts/common/programs/mpv/sane-sysvol/non_blocking_popen.lua
Normal file
@@ -0,0 +1,80 @@
|
||||
-- source: <https://gist.github.com/max1220/c19ccd4d90ed32d41b879eba727cbcbd>
|
||||
-- requires: luajit
|
||||
--
|
||||
-- Implements a basic binding for popen that allows non-blocking reads
|
||||
-- returned "file" table only supports :read(with an optional size argument, no mode etc.) and :close
|
||||
local function non_blocking_popen(cmd, read_buffer_size)
|
||||
local ffi = require("ffi")
|
||||
|
||||
-- C functions that we need
|
||||
ffi.cdef([[
|
||||
void* popen(const char* cmd, const char* mode);
|
||||
int pclose(void* stream);
|
||||
int fileno(void* stream);
|
||||
int fcntl(int fd, int cmd, int arg);
|
||||
int *__errno_location ();
|
||||
ssize_t read(int fd, void* buf, size_t count);
|
||||
]])
|
||||
|
||||
-- you can compile a simple C programm to find these values(Or look in the headers)
|
||||
local F_SETFL = 4
|
||||
local O_NONBLOCK = 2048
|
||||
local EAGAIN = 11
|
||||
|
||||
-- this "array" holds the errno variable
|
||||
local _errno = ffi.C.__errno_location()
|
||||
|
||||
-- the buffer for reading from the process
|
||||
local read_buffer_size = tonumber(read_buffer_size) or 2048
|
||||
local read_buffer = ffi.new('uint8_t[?]',read_buffer_size)
|
||||
|
||||
-- get a FILE* for our command
|
||||
local file = assert(ffi.C.popen(cmd, "r"))
|
||||
|
||||
-- turn the FILE* to a fd(int) for fcntl
|
||||
local fd = ffi.C.fileno(file)
|
||||
|
||||
-- set non-blocking mode for read
|
||||
assert(ffi.C.fcntl(fd, F_SETFL, O_NONBLOCK)==0, "fcntl failed")
|
||||
|
||||
-- close the process, prevent reading, allow garbage colletion
|
||||
function file_close(self)
|
||||
ffi.C.pclose(file)
|
||||
self.read_buffer = nil
|
||||
read_buffer = nil
|
||||
self.read = function() return nil, "closed"end
|
||||
end
|
||||
|
||||
-- read up to size bytes from the process. Returns data(string) and number of bytes read if successfull,
|
||||
-- nil, "EAGAIN" if there is no data aviable, and
|
||||
-- nil, "closed" if the process has ended
|
||||
local read = ffi.C.read
|
||||
function file_read(self, size)
|
||||
local _size = math.min(read_buffer_size, size)
|
||||
while true do
|
||||
local nbytes = read(fd,read_buffer,_size)
|
||||
if nbytes > 0 then
|
||||
local data = ffi.string(read_buffer, nbytes)
|
||||
return data, nbytes
|
||||
elseif (nbytes == -1) and (_errno[0] == EAGAIN) then
|
||||
return nil, "EAGAIN"
|
||||
else
|
||||
file_close(self)
|
||||
return nil, "closed"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
_fd = fd,
|
||||
_file = file,
|
||||
_read_buffer = read_buffer,
|
||||
_read_buffer_size = read_buffer_size,
|
||||
read = file_read,
|
||||
close = file_close
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
non_blocking_popen = non_blocking_popen
|
||||
}
|
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# send a test notification with:
|
||||
# - `ntfy pub "https://ntfy.uninsane.org/$(cat ~/.config/ntfy-sh/topic)" test`
|
||||
{ config, lib, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.ntfy-sh;
|
||||
in
|
||||
@@ -27,16 +27,13 @@ in
|
||||
|
||||
services.ntfy-sub = {
|
||||
description = "listen for push-notifications";
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
||||
script = ''
|
||||
partOf = lib.mkIf cfg.config.autostart [ "default" ];
|
||||
command = let
|
||||
sub = pkgs.writeShellScriptBin "ntfy-sub" ''
|
||||
topic=$(cat ~/.config/ntfy-sh/topic)
|
||||
ntfy sub "https://ntfy.uninsane.org:2587/$topic"
|
||||
exec ntfy sub "https://ntfy.uninsane.org:2587/$topic"
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
in "${sub}/bin/ntfy-sub";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,4 +1,6 @@
|
||||
# administer with pw-cli, pw-mon, pw-top commands
|
||||
#
|
||||
# performance tuning: <https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Performance-tuning>
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.pipewire;
|
||||
@@ -9,6 +11,16 @@ in
|
||||
|
||||
# sandbox.method = "landlock"; #< also works
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistDbus = [
|
||||
# dbus is used for rtkit integration
|
||||
# rtkit runs on the system bus.
|
||||
# xdg-desktop-portal then exposes this to the user bus.
|
||||
# therefore, user bus should be all that's needed, but...
|
||||
# xdg-desktop-portal-wlr depends on pipewire, hence pipewire has to start before xdg-desktop-portal.
|
||||
# then, pipewire has to talk specifically to rtkit (system) and not go through xdp.
|
||||
# "user"
|
||||
"system"
|
||||
];
|
||||
sandbox.wrapperType = "inplace"; #< its config files refer to its binaries by full path
|
||||
sandbox.extraConfig = [
|
||||
"--sane-sandbox-keep-namespace" "pid"
|
||||
@@ -28,50 +40,37 @@ in
|
||||
".config/pulse"
|
||||
];
|
||||
|
||||
# see: <https://docs.pipewire.org/page_module_protocol_native.html>
|
||||
# defaults to placing the socket in /run/user/$id/{pipewire-0,pipewire-0-manager,...}
|
||||
# but that's trickier to sandbox
|
||||
env.PIPEWIRE_RUNTIME_DIR = "$XDG_RUNTIME_DIR/pipewire";
|
||||
|
||||
services.pipewire = {
|
||||
description = "pipewire: multimedia service";
|
||||
after = [ "graphical-session.target" ];
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/pipewire";
|
||||
ExecStartPost = pkgs.writeShellScript "pipewire-wait-started" ''
|
||||
waitFor() {
|
||||
while [ ! -e "$1" ]; do
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
waitFor "$XDG_RUNTIME_DIR/pipewire-0"
|
||||
waitFor "$XDG_RUNTIME_DIR/pipewire-0-manager"
|
||||
partOf = [ "sound" ];
|
||||
# depends = [ "xdg-desktop-portal" ]; # for Realtime portal (dependency cycle)
|
||||
# env PIPEWIRE_LOG_SYSTEMD=false"
|
||||
# env PIPEWIRE_DEBUG"*:3,mod.raop*:5,pw.rtsp-client*:5"
|
||||
command = pkgs.writeShellScript "pipewire-start" ''
|
||||
mkdir -p $PIPEWIRE_RUNTIME_DIR
|
||||
exec pipewire
|
||||
'';
|
||||
ExecStopPost = ''rm -f "$XDG_RUNTIME_DIR/{pipewire-0,pipewire-0.lock,pipewire-0-manager,pipewire-0-manager.lock}"'';
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
};
|
||||
|
||||
# environment.PIPEWIRE_LOG_SYSTEMD = "false";
|
||||
# environment.PIPEWIRE_DEBUG = "*:3,mod.raop*:5,pw.rtsp-client*:5";
|
||||
readiness.waitExists = [
|
||||
"$PIPEWIRE_RUNTIME_DIR/pipewire-0"
|
||||
"$PIPEWIRE_RUNTIME_DIR/pipewire-0-manager"
|
||||
];
|
||||
cleanupCommand = ''rm -f "$PIPEWIRE_RUNTIME_DIR/{pipewire-0,pipewire-0.lock,pipewire-0-manager,pipewire-0-manager.lock}"'';
|
||||
};
|
||||
services.pipewire-pulse = {
|
||||
description = "pipewire-pulse: Pipewire compatibility layer for PulseAudio clients";
|
||||
after = [ "pipewire.service" ];
|
||||
wantedBy = [ "pipewire.service" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/pipewire-pulse";
|
||||
ExecStartPost = pkgs.writeShellScript "pipewire-pulse-wait-started" ''
|
||||
waitFor() {
|
||||
while [ ! -e "$1" ]; do
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
waitFor "$XDG_RUNTIME_DIR/pulse/native"
|
||||
waitFor "$XDG_RUNTIME_DIR/pulse/pid"
|
||||
'';
|
||||
ExecStopPost = ''rm -f "$XDG_RUNTIME_DIR/pulse/{native,pid}"'';
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
};
|
||||
depends = [ "pipewire" ];
|
||||
partOf = [ "sound" ];
|
||||
command = "pipewire-pulse";
|
||||
readiness.waitExists = [
|
||||
"$XDG_RUNTIME_DIR/pulse/native"
|
||||
"$XDG_RUNTIME_DIR/pulse/pid"
|
||||
];
|
||||
cleanupCommand = ''rm -f "$XDG_RUNTIME_DIR/pulse/{native,pid}"'';
|
||||
};
|
||||
};
|
||||
|
||||
@@ -99,5 +98,7 @@ in
|
||||
# this might require more configuration (e.g. polkit-related) to work exactly as desired.
|
||||
# - readme outlines requirements: <https://github.com/heftig/rtkit>
|
||||
# XXX(2023/10/12): rtkit does not play well on moby. any application sending audio out dies after 10s.
|
||||
# security.rtkit.enable = lib.mkIf cfg.enabled true;
|
||||
# - note that `rtkit-daemon` can be launched with a lot of config: pipewire docs (top of this file)
|
||||
# suggest using a much less aggressive canary. maybe try that?
|
||||
security.rtkit.enable = lib.mkIf cfg.enabled true;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{ config, ... }:
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.playerctl = {
|
||||
sandbox.method = "bwrap";
|
||||
@@ -8,13 +8,9 @@
|
||||
services.playerctld = {
|
||||
description = "playerctl daemon to keep track of which MPRIS players were recently active";
|
||||
documentation = [ "https://github.com/altdesktop/playerctl/issues/161" ];
|
||||
wantedBy = [ "default.target" ]; #< TODO: maybe better to zero `wantedBy` here and have the specific consumers (e.g. swaync) explicitly depend on this.
|
||||
serviceConfig.ExecStart = "${config.sane.programs.playerctl.package}/bin/playerctld";
|
||||
# serviceConfig.Type = "dbus";
|
||||
# serviceConfig.BusName = "org.mpris.MediaPlayer2.Player";
|
||||
serviceConfig.Type = "simple"; # playerctl also supports a --daemon option, idk if that's better
|
||||
serviceConfig.Restart = "on-failure";
|
||||
serviceConfig.RestartSec = "10s";
|
||||
partOf = [ "default" ]; #< TODO: maybe better to zero `wantedBy` here and have the specific consumers (e.g. swaync) explicitly depend on this.
|
||||
command = "playerctld";
|
||||
# readiness.waitDbus = "org.mpris.MediaPlayer2.Player"; #< doesn't work... did the endpoint change?
|
||||
};
|
||||
};
|
||||
}
|
||||
|
34
hosts/common/programs/s6-rc.nix
Normal file
34
hosts/common/programs/s6-rc.nix
Normal file
@@ -0,0 +1,34 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.s6-rc = {
|
||||
packageUnwrapped = pkgs.s6-rc.overrideAttrs (upstream: {
|
||||
nativeBuildInputs = (upstream.nativeBuildInputs or []) ++ (with pkgs; [
|
||||
makeWrapper
|
||||
]);
|
||||
# s6-rc looks for files in /run/s6/{live,compiled,...} by default.
|
||||
# let's patch that to be a user-specific runtime dir, since i run it as an ordinary user.
|
||||
# note that one can still manually specify --live; later definitions will override earlier definitions.
|
||||
postInstall = (upstream.postInstall or "") + ''
|
||||
for prog in s6-rc s6-rc-bundle s6-rc-db s6-rc-format-upgrade s6-rc-init s6-rc-update; do
|
||||
wrapProgram "$bin/bin/$prog" \
|
||||
--add-flags '-l $XDG_RUNTIME_DIR/s6/live'
|
||||
done
|
||||
'';
|
||||
});
|
||||
|
||||
persist.private = [
|
||||
".local/share/s6/logs"
|
||||
];
|
||||
|
||||
sandbox.enable = false; # service manager
|
||||
suggestedPrograms = [
|
||||
"s6-rc-man-pages"
|
||||
"s6" #< TODO: i think i only need s6-svscan?
|
||||
"s6-man-pages"
|
||||
];
|
||||
};
|
||||
|
||||
sane.programs.s6.sandbox.enable = false; # service manager
|
||||
sane.programs.s6-man-pages.sandbox.enable = false; # no binaries
|
||||
sane.programs.s6-rc-man-pages.sandbox.enable = false; # no binaries
|
||||
}
|
@@ -105,7 +105,7 @@ in
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistDbus = [ "user" ]; #< to launch applications
|
||||
sandbox.extraRuntimePaths = [ "sway-ipc.sock" ];
|
||||
sandbox.extraRuntimePaths = [ "sway" ];
|
||||
sandbox.extraConfig = [
|
||||
"--sane-sandbox-keep-namespace" "pid"
|
||||
];
|
||||
@@ -127,12 +127,7 @@ in
|
||||
# after = [ "graphical-session.target" ];
|
||||
# wantedBy = [ "graphical-session.target" ];
|
||||
|
||||
# serviceConfig = {
|
||||
# ExecStart = "${config.sane.programs.actkbd.package}/bin/actkbd -c /home/colin/.config/actkbd/actkbd.conf";
|
||||
# Type = "simple";
|
||||
# Restart = "always";
|
||||
# RestartSec = "5s";
|
||||
# };
|
||||
# serviceConfig.ExecStart = "${config.sane.programs.actkbd.package}/bin/actkbd -c /home/colin/.config/actkbd/actkbd.conf";
|
||||
# };
|
||||
# };
|
||||
|
||||
|
@@ -209,16 +209,16 @@ dispatchInhibited() {
|
||||
}
|
||||
|
||||
_isAllOn="$(isAllOn && echo 1 || true)"
|
||||
_isInhibited="$(isInhibited && echo 1 || true)"
|
||||
|
||||
if [ -z "$_isAllOn" ]; then
|
||||
dispatchOff
|
||||
else
|
||||
_isInhibited="$(isInhibited && echo 1 || true)"
|
||||
if [ -n "$_isInhibited" ]; then
|
||||
dispatchInhibited
|
||||
fi
|
||||
|
||||
if [ -n "$_isAllOn" ]; then
|
||||
dispatchOn
|
||||
else
|
||||
dispatchOff
|
||||
dispatchOn
|
||||
fi
|
||||
fi
|
||||
|
||||
dispatchDefault
|
||||
|
@@ -54,6 +54,7 @@ in
|
||||
|
||||
"sane-scripts.bt-add".sandbox = {
|
||||
method = "bwrap";
|
||||
autodetectCliPaths = "existing"; #< for adding a .torrent from disk
|
||||
net = "clearnet";
|
||||
# TODO: migrate `transmission_passwd` to `secrets` api
|
||||
extraPaths = [ "/run/secrets/transmission_passwd" ];
|
||||
@@ -254,7 +255,8 @@ in
|
||||
|
||||
"sane-scripts.wipe".sandbox = {
|
||||
method = "bwrap";
|
||||
whitelistDbus = [ "user" ]; #< for `secret-tool` and `systemd --user stop <service>
|
||||
whitelistDbus = [ "user" ]; #< for `secret-tool`
|
||||
whitelistS6 = true; #< for stopping services before wiping
|
||||
extraHomePaths = [
|
||||
# could be more specific, but at a maintenance cost.
|
||||
# TODO: needs updating, now that persisted data lives behind symlinks!
|
||||
|
@@ -46,18 +46,11 @@ in
|
||||
|
||||
services.signal-desktop = {
|
||||
description = "signal-desktop Signal Messenger client";
|
||||
after = [ "graphical-session.target" ];
|
||||
# partOf = [ "graphical-session.target" ];
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
|
||||
# depends = [ "graphical-session" ];
|
||||
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/signal-desktop";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
# for some reason the --ozone-platform-hint=auto flag fails when signal-desktop is launched from a service
|
||||
environment.NIXOS_OZONE_WL = "1";
|
||||
command = "env NIXOS_OZONE_WL=1 signal-desktop";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -31,14 +31,14 @@ in
|
||||
|
||||
services.sway-autoscaler = {
|
||||
description = "adjust global desktop scale to match the activate application";
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/sway-autoscaler --loop-sec ${builtins.toString cfg.config.interval}";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
environment.SWAY_DEFAULT_SCALE = builtins.toString cfg.config.defaultScale;
|
||||
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];
|
||||
command = lib.escapeShellArgs [
|
||||
"env"
|
||||
"SWAY_DEFAULT_SCALE=${builtins.toString cfg.config.defaultScale}"
|
||||
"sway-autoscaler"
|
||||
"--loop-sec"
|
||||
(builtins.toString cfg.config.interval)
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -5,18 +5,16 @@
|
||||
let
|
||||
cfg = config.sane.programs.sway;
|
||||
wrapSway = configuredSway: let
|
||||
# `wrapSway` exists to launch sway with our desired debugging facilities.
|
||||
# i.e. redirect output to syslog.
|
||||
systemd-cat = "${lib.getBin pkgs.systemd}/bin/systemd-cat";
|
||||
swayLauncher = pkgs.writeShellScriptBin "sway" ''
|
||||
# sway defaults to auto-generating a unix domain socket named "sway-ipc.$UID.NNNN.sock",
|
||||
# which allows for multiple sway sessions under the same user.
|
||||
# but the unpredictability makes static sandboxing & such difficult, so hardcode it:
|
||||
export SWAYSOCK="$XDG_RUNTIME_DIR/sway-ipc.sock"
|
||||
export XDG_CURRENT_DESKTOP=sway
|
||||
|
||||
echo "launching sway (sway.desktop)..." | ${systemd-cat} --identifier=sway
|
||||
exec ${configuredSway}/bin/sway 2>&1 | ${systemd-cat} --identifier=sway
|
||||
test -e "$(dirname "$SWAYSOCK")" || \
|
||||
echo "warning: required directory not found (create it?): $(dirname "$SWAYSOCK")"
|
||||
test -e "$(dirname "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY")" || \
|
||||
echo "warning: required directory not found (create it?): $(dirname "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY")"
|
||||
test -e /tmp/.X11-unix || \
|
||||
echo "warning: required directory not found (create it?): /tmp/.X11-unix"
|
||||
# delete DISPLAY-related vars from env before launch, else sway will try to connect to a remote display.
|
||||
# (consider: nested sway sessions, where sway actually has a reason to read these)
|
||||
exec env -u DISPLAY -u WAYLAND_DISPLAY "DESIRED_WAYLAND_DISPLAY=$WAYLAND_DISPLAY" ${configuredSway}/bin/sway 2>&1
|
||||
'';
|
||||
in
|
||||
pkgs.symlinkJoin {
|
||||
@@ -149,7 +147,6 @@ in
|
||||
"wdisplays" # like xrandr
|
||||
"wireplumber" # used by sway config
|
||||
"wl-clipboard"
|
||||
# "wob" # render volume changes on-screen
|
||||
"xdg-desktop-portal"
|
||||
# xdg-desktop-portal-gtk provides portals for:
|
||||
# - org.freedesktop.impl.portal.Access
|
||||
@@ -173,16 +170,16 @@ in
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "inplace";
|
||||
sandbox.net = "clearnet"; #< for Xwayland (TODO: separate!)
|
||||
sandbox.net = "all"; # TODO: shouldn't be needed! but without this, mouse/kb hotplug doesn't work.
|
||||
sandbox.whitelistAudio = true; # it runs playerctl directly
|
||||
sandbox.whitelistDbus = [ "system" "user" ]; # to e.g. launch apps
|
||||
sandbox.whitelistDri = true;
|
||||
sandbox.whitelistX = true; # sway invokes xwayland itself
|
||||
sandbox.whitelistWayland = true;
|
||||
# needs to *create* the sway socket. could move the sway socket into its own directory, and whitelist just that, but doesn't buy me much.
|
||||
sandbox.extraRuntimePaths = [ "/" ];
|
||||
sandbox.extraRuntimePaths = [ "/" ]; # TODO: should need just "sway". but even if i sandbox EVERY entry under run individually, it fails!
|
||||
sandbox.extraPaths = [
|
||||
"/dev/input"
|
||||
"/run/systemd"
|
||||
"/run/systemd/sessions"
|
||||
"/run/udev"
|
||||
"/sys/class/backlight"
|
||||
"/sys/class/drm"
|
||||
@@ -195,10 +192,6 @@ in
|
||||
# this way `swaymsg -- reload` can work even if the fd for ~/.config/sway/config changes.
|
||||
".config/sway"
|
||||
];
|
||||
sandbox.extraConfig = [
|
||||
"--sane-sandbox-keep-namespace" "pid"
|
||||
];
|
||||
|
||||
|
||||
fs.".config/xdg-desktop-portal/sway-portals.conf".symlink.text = ''
|
||||
# portals.conf docs: <https://flatpak.github.io/xdg-desktop-portal/docs/portals.conf.html>
|
||||
@@ -218,30 +211,32 @@ in
|
||||
xwayland = if config.sane.programs.xwayland.enabled then "enable" else "disable";
|
||||
};
|
||||
|
||||
services.sway-session = {
|
||||
description = "sway-session: active iff sway desktop environment is baseline operational";
|
||||
documentation = [
|
||||
"https://github.com/swaywm/sway/issues/7862"
|
||||
"https://github.com/alebastr/sway-systemd"
|
||||
];
|
||||
env.XDG_CURRENT_DESKTOP = "sway";
|
||||
# sway defaults to auto-generating a unix domain socket named "sway-ipc.$UID.NNNN.sock",
|
||||
# which allows for multiple sway sessions under the same user.
|
||||
# but the unpredictability makes static sandboxing & such difficult, so hardcode it.
|
||||
# place it in a subdirectory for the same reason:
|
||||
env.SWAYSOCK = "$XDG_RUNTIME_DIR/sway/sway-ipc.sock";
|
||||
# TODO: ensure this is reliable? might not work across sway restarts, etc.
|
||||
env.DISPLAY = ":0";
|
||||
# docs: <https://discourse.ubuntu.com/t/environment-variables-for-wayland-hackers/12750>
|
||||
# N.B.: gtk apps support absolute paths for this; webkit apps (e.g. geary) support only relative paths (relative to $XDG_RUNTIME_DIR)
|
||||
env.WAYLAND_DISPLAY = "wayland/wayland-1";
|
||||
|
||||
# we'd like to start graphical-session after sway is ready, but it's marked `RefuseManualStart` because Lennart.
|
||||
# instead, create `sway-session.service` which `bindsTo` `graphical-session.target`.
|
||||
# we can manually start `sway-session`, and the `bindsTo` means that it will start `graphical-session`,
|
||||
# and then track `graphical-session`s state (that is: it'll stop when graphical-session stops).
|
||||
#
|
||||
# additionally, set `ConditionEnvironment` to guard that the sway environment variables *really have* been imported into systemd.
|
||||
unitConfig.ConditionEnvironment = "SWAYSOCK";
|
||||
# requiredBy = [ "graphical-session.target" ];
|
||||
before = [ "graphical-session.target" ];
|
||||
bindsTo = [ "graphical-session.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.coreutils}/bin/true";
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
services.sway = {
|
||||
description = "sway: tiling wayland desktop environment";
|
||||
dependencyOf = [ "graphical-session" ];
|
||||
command = pkgs.writeShellScript "sway-start" ''
|
||||
# have to create these directories before launching sway so that they're available in the sandbox
|
||||
mkdir -p "$(dirname "$SWAYSOCK")"
|
||||
mkdir -p "$(dirname "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY")"
|
||||
mkdir -p /tmp/.X11-unix # for Xwayland
|
||||
exec sway
|
||||
'';
|
||||
readiness.waitExists = [ "$SWAYSOCK" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ];
|
||||
};
|
||||
# link the graphical-session into the default target, so sway gets auto-started
|
||||
services.graphical-session.partOf = [ "default" ];
|
||||
};
|
||||
|
||||
|
||||
|
@@ -236,6 +236,13 @@ output $out_moby {
|
||||
# scale 1.6
|
||||
}
|
||||
|
||||
# move the wayland socket that sway implicitly created to the place which other apps expect to find it
|
||||
exec --no-startup-id mv $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY $XDG_RUNTIME_DIR/$DESIRED_WAYLAND_DISPLAY
|
||||
exec --no-startup-id mv $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY.lock $XDG_RUNTIME_DIR/$DESIRED_WAYLAND_DISPLAY.lock
|
||||
# i'm sure there's a simpler way. not certain that this actually updates the sway environment variables anyway
|
||||
# the double-$ means to set the variable at *runtime*, not at "compile-time" (so that it doesn't impact the line immediately above us
|
||||
set $$WAYLAND_DISPLAY "$(echo $DESIRED_WAYLAND_DISPLAY)"
|
||||
|
||||
|
||||
# manually export PATH here, since all my user services need that, and sane-sandboxed implementation depends on it.
|
||||
# also, manually export XDG_DATA_DIRS. glib fails in weird ways (e.g. thinks everything is application/x-octet-stream mime type) without it.
|
||||
@@ -243,8 +250,8 @@ output $out_moby {
|
||||
#
|
||||
# XXX: dbus-update-activation-environment --systemd is ASYNCHRONOUS. it returns before the systemd environment is actually updated.
|
||||
# hence, call `systemctl import-environment` ourselves. i could probably remove the dbus stuff and be safe, but at least for now it's an OK backup.
|
||||
exec --no-startup-id systemctl --user import-environment PATH XDG_DATA_DIRS DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
|
||||
exec --no-startup-id dbus-update-activation-environment --systemd PATH XDG_DATA_DIRS DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
|
||||
# exec --no-startup-id systemctl --user import-environment PATH XDG_DATA_DIRS DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
|
||||
# exec --no-startup-id dbus-update-activation-environment --systemd PATH XDG_DATA_DIRS DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
|
||||
|
||||
# previously: `include /etc/sway/config.d/*` was needed for xdg-desktop-portal-* to work.
|
||||
# stock nixos `programs.sway` would setup /etc/sway/config.d with additional variables to import to the dbus env.
|
||||
@@ -253,11 +260,5 @@ exec --no-startup-id dbus-update-activation-environment --systemd PATH XDG_DATA_
|
||||
# - WAYLAND_DISPLAY
|
||||
# - SWAYSOCK
|
||||
# - XDG_CURRENT_DESKTOP
|
||||
# include /etc/sway/config.d/*
|
||||
|
||||
# signal to systemd that sway is active,
|
||||
# and therefore let it start any downstream services (e.g. apps that would like to auto-start)
|
||||
# see `systemctl --user cat sway-session` for links to docs
|
||||
exec --no-startup-id systemctl start --user sway-session.service
|
||||
|
||||
@extra_lines@
|
||||
|
@@ -50,17 +50,15 @@ in
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistDbus = [ "user" ]; #< might need system too, for inhibitors
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.extraRuntimePaths = [ "sway-ipc.sock" ];
|
||||
sandbox.extraRuntimePaths = [ "sway" ];
|
||||
|
||||
services.swayidle = {
|
||||
description = "swayidle: perform actions when sway session is idle";
|
||||
after = [ "graphical-session.target" ];
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
partOf = [ "graphical-session" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = lib.escapeShellArgs (
|
||||
command = lib.escapeShellArgs (
|
||||
[
|
||||
"${cfg.package}/bin/swayidle"
|
||||
"swayidle"
|
||||
"-w"
|
||||
] ++ lib.flatten (
|
||||
lib.mapAttrsToList
|
||||
@@ -72,10 +70,6 @@ in
|
||||
cfg.config.actions
|
||||
)
|
||||
);
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
23
hosts/common/programs/swaynotificationcenter/buttons.nix
Normal file
23
hosts/common/programs/swaynotificationcenter/buttons.nix
Normal file
@@ -0,0 +1,23 @@
|
||||
{ pkgs }:
|
||||
let
|
||||
serviceButton = svcType: name: label: {
|
||||
inherit label;
|
||||
type = "toggle";
|
||||
command = "swaync-service-dispatcher toggle ${svcType} ${name}";
|
||||
update-command = "swaync-service-dispatcher print ${svcType} ${name}";
|
||||
active = true;
|
||||
};
|
||||
in
|
||||
{
|
||||
gps = serviceButton "systemd" "eg25-control-gps" ""; # GPS services; other icons: gps, ⌖, 🛰, 🌎,
|
||||
cell-modem = serviceButton "systemd" "eg25-control-powered" ""; # icons: 5g, 📡, 📱, ᯤ, ⚡, , 🌐, 📶, 🗼, , , ,
|
||||
vpn = serviceButton "systemd" "wg-quick-vpn-servo" "vpn::hn";
|
||||
|
||||
gnome-calls = serviceButton "s6" "gnome-calls" "SIP";
|
||||
geary = serviceButton "s6" "geary" ""; # email (Geary); other icons: ✉, [E], 📧,
|
||||
abaddon = serviceButton "s6" "abaddon" ""; # Discord chat client; icons: , 🎮
|
||||
dissent = serviceButton "s6" "dissent" ""; # Discord chat client; icons: , 🎮
|
||||
signal-desktop = serviceButton "s6" "signal-desktop" "💬"; # Signal messenger; other icons:
|
||||
dino = serviceButton "s6" "dino" "XMPP"; # XMPP calls (jingle)
|
||||
fractal = serviceButton "s6" "fractal" "[m]"; # Matrix messages
|
||||
}
|
@@ -17,101 +17,22 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.swaynotificationcenter;
|
||||
|
||||
fbcli-wrapper = pkgs.writeShellApplication {
|
||||
name = "swaync-fbcli";
|
||||
runtimeInputs = [
|
||||
config.sane.programs.feedbackd.package
|
||||
pkgs.procps # for pkill
|
||||
cfg.package
|
||||
];
|
||||
text = ''
|
||||
# if in Do Not Disturb, don't do any feedback
|
||||
# TODO: better solution is to actually make use of feedbackd profiles.
|
||||
# i.e. set profile to `quiet` when in DnD mode
|
||||
if [ "$SWAYNC_URGENCY" != "Critical" ] && [ "$(swaync-client --get-dnd)" = "true" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
# kill children if killed, to allow that killing this parent process will end the real fbcli call
|
||||
cleanup() {
|
||||
echo "aborting fbcli notification (PID $child)"
|
||||
pkill -P "$child"
|
||||
exit 0 # exit cleanly to avoid swaync alerting a script failure
|
||||
}
|
||||
trap cleanup SIGINT SIGQUIT SIGTERM
|
||||
|
||||
# feedbackd stops playback when the caller exits
|
||||
# and fbcli will exit immediately if it has no stdin.
|
||||
# so spoof a stdin:
|
||||
/bin/sh -c "true | fbcli $*" &
|
||||
child=$!
|
||||
wait
|
||||
'';
|
||||
};
|
||||
fbcli = "${fbcli-wrapper}/bin/swaync-fbcli";
|
||||
|
||||
# we do this because swaync's exec naively splits the command on space to produce its argv, rather than parsing the shell.
|
||||
# [ "pkill" "-f" "fbcli" "--event" ... ] -> breaks pkill
|
||||
# [ "pkill" "-f" "fbcli --event ..." ] -> is what we want
|
||||
fbcli-stop-wrapper = pkgs.writeShellApplication {
|
||||
name = "fbcli-stop";
|
||||
runtimeInputs = [
|
||||
pkgs.procps # for pkill
|
||||
];
|
||||
text = ''
|
||||
pkill -e -f "${fbcli} $*"
|
||||
'';
|
||||
};
|
||||
fbcli-stop = "${fbcli-stop-wrapper}/bin/fbcli-stop";
|
||||
|
||||
kill-singleton_ = pkgs.writeShellApplication {
|
||||
name = "kill-singleton";
|
||||
runtimeInputs = [
|
||||
pkgs.procps # for pgrep
|
||||
pkgs.gnugrep
|
||||
];
|
||||
text = ''
|
||||
pids=$(pgrep --full "$*" | tr '\n' ' ') || true
|
||||
# only act if there's exactly one pid
|
||||
if echo "$pids" | grep -Eq '^[0-9]+ ?$'; then
|
||||
kill "$pids"
|
||||
else
|
||||
echo "kill-singleton: skipping because multiple pids match: $pids"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
kill-singleton = "${kill-singleton_}/bin/kill-singleton";
|
||||
|
||||
systemctl-toggle = pkgs.writeShellApplication {
|
||||
name = "systemctl-toggle";
|
||||
runtimeInputs = [
|
||||
pkgs.systemd
|
||||
];
|
||||
text = ''
|
||||
if systemctl is-active "$@"; then
|
||||
systemctl stop "$@"
|
||||
else
|
||||
systemctl start "$@"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
printIsActive = pkgs.writeShellApplication {
|
||||
name = "print-is-active";
|
||||
runtimeInputs = [
|
||||
pkgs.systemd
|
||||
];
|
||||
text = ''
|
||||
if systemctl is-active "$@"; then
|
||||
echo true
|
||||
else
|
||||
echo false
|
||||
fi
|
||||
'';
|
||||
};
|
||||
buttons = import ./buttons.nix { inherit pkgs; };
|
||||
scripts = import ./scripts.nix { inherit pkgs; };
|
||||
in
|
||||
{
|
||||
sane.programs.swaync-service-dispatcher = {
|
||||
packageUnwrapped = pkgs.static-nix-shell.mkBash {
|
||||
pname = "swaync-service-dispatcher";
|
||||
srcRoot = ./.;
|
||||
pkgs = [
|
||||
"s6"
|
||||
"s6-rc"
|
||||
"systemd"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
sane.programs.swaynotificationcenter = {
|
||||
configOption = with lib; mkOption {
|
||||
type = types.submodule {
|
||||
@@ -137,6 +58,7 @@ in
|
||||
"user" # mpris; portal
|
||||
"system" # backlight
|
||||
];
|
||||
sandbox.whitelistS6 = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.extraPaths = [
|
||||
"/sys/class/backlight"
|
||||
@@ -168,7 +90,10 @@ in
|
||||
# the glib code which consumes this is `g_notification_backend_new_default`, calling into `_g_io_module_get_default_type`.
|
||||
env.GNOTIFICATION_BACKEND = "freedesktop";
|
||||
|
||||
suggestedPrograms = [ "feedbackd" ];
|
||||
suggestedPrograms = [
|
||||
"feedbackd"
|
||||
"swaync-service-dispatcher" #< used when toggling buttons
|
||||
];
|
||||
|
||||
fs.".config/swaync/style.css".symlink.target = ./style.css;
|
||||
fs.".config/swaync/config.json".symlink.text = builtins.toJSON {
|
||||
@@ -207,131 +132,7 @@ in
|
||||
hide-on-clear = true; #< hide control center when clicking "clear all"
|
||||
hide-on-action = true;
|
||||
script-fail-notify = true;
|
||||
scripts = {
|
||||
# a script can match regex on these fields. only fired if all listed fields match:
|
||||
# - app-name
|
||||
# - desktop-entry
|
||||
# - summary
|
||||
# - body
|
||||
# - urgency (Low/Normal/Critical)
|
||||
# - category
|
||||
# additionally, the script can be run either on receipt or action:
|
||||
# - run-on = "receive" or "action"
|
||||
# when script is run, these env vars are available:
|
||||
# - SWAYNC_BODY
|
||||
# - SWAYNC_DESKTOP_ENTRY
|
||||
# - SWAYNC_URGENCY
|
||||
# - SWAYNC_TIME
|
||||
# - SWAYNC_APP_NAME
|
||||
# - SWAYNC_CATEGORY
|
||||
# - SWAYNC_REPLACES_ID
|
||||
# - SWAYNC_ID
|
||||
# - SWAYNC_SUMMARY
|
||||
|
||||
# rules to use for testing. trigger with:
|
||||
# - `notify-send test test:message` (etc)
|
||||
# should also be possible to trigger via any messaging app
|
||||
fbcli-test-im = {
|
||||
body = "test:message";
|
||||
exec = "${fbcli} --event proxied-message-new-instant";
|
||||
};
|
||||
fbcli-test-call = {
|
||||
body = "test:call";
|
||||
exec = "${fbcli} --event phone-incoming-call -t 20";
|
||||
};
|
||||
fbcli-test-call-stop = {
|
||||
body = "test:call-stop";
|
||||
exec = "${fbcli-stop} --event phone-incoming-call -t 20";
|
||||
};
|
||||
fbcli-test-timer = {
|
||||
body = "test:timer";
|
||||
exec = "${fbcli} --event timeout-completed";
|
||||
};
|
||||
|
||||
incoming-im-known-app-name = {
|
||||
# trigger notification sound on behalf of these IM clients.
|
||||
app-name = "(Chats|Dino|discord|dissent|Element|Fractal)";
|
||||
body = "^(?!Incoming call).*$"; #< don't match Dino Incoming calls
|
||||
exec = "${fbcli} --event proxied-message-new-instant";
|
||||
};
|
||||
incoming-im-known-desktop-entry = {
|
||||
# trigger notification sound on behalf of these IM clients.
|
||||
# these clients don't have an app-name (listed as "<unknown>"), but do have a desktop-entry
|
||||
desktop-entry = "com.github.uowuo.abaddon";
|
||||
exec = "${fbcli} --event proxied-message-new-instant";
|
||||
};
|
||||
incoming-call = {
|
||||
app-name = "Dino";
|
||||
body = "^Incoming call$";
|
||||
exec = "${fbcli} --event phone-incoming-call -t 20";
|
||||
};
|
||||
incoming-call-acted-on = {
|
||||
# when the notification is clicked, stop sounding the ringer
|
||||
app-name = "Dino";
|
||||
body = "^Incoming call$";
|
||||
run-on = "action";
|
||||
exec = "${fbcli-stop} --event phone-incoming-call -t 20";
|
||||
};
|
||||
timer-done = {
|
||||
# sxmo_timer.sh fires off notifications like "Done with 10m" when a 10minute timer completes.
|
||||
# it sends such a notification every second until dismissed
|
||||
app-name = "notify-send";
|
||||
summary = "^Done with .*$";
|
||||
# XXX: could use alarm-clock-elapsed, but that's got a duration > 1s
|
||||
# which isn't great for sxmo's 1s repeat.
|
||||
# TODO: maybe better to have sxmo only notify once, and handle this like with Dino's incoming call
|
||||
exec = "${fbcli} --event timeout-completed";
|
||||
};
|
||||
timer-done-acted-on = {
|
||||
# when the notification is clicked, kill whichever sxmo process is sending it
|
||||
app-name = "notify-send";
|
||||
summary = "^Done with .*$";
|
||||
run-on = "action";
|
||||
# process tree looks like:
|
||||
# - foot -T <...> /nix/store/.../sh /nix/store/.../.sxmo_timer.sh-wrapped timerrun <duration>
|
||||
# - /nix/store/.../sh /nix/store/.../.sxmo_timer.sh-wrapped timerrun duration
|
||||
# we want to match exactly one of those, reliably.
|
||||
# foot might not be foot, but alacritty, kitty, or any other terminal.
|
||||
exec = "${kill-singleton} ^[^ ]* ?[^ ]*sxmo_timer.sh(-wrapped)? timerrun";
|
||||
};
|
||||
};
|
||||
notification-visibility = {
|
||||
# match incoming notifications and decide if they should be visible.
|
||||
# map of rule-name => { criteria and effect };
|
||||
# keys:
|
||||
# - `state`: "ignored"|"muted"|"transient"|"enabled"
|
||||
# => which visibility to apply to matched notifications
|
||||
# => "ignored" behaves as if the notification was never sent.
|
||||
# => "muted" adds it to the sidebar & sets the notif indicator but doesn't display it on main display
|
||||
# - `override-urgency`: "unset"|"low"|"normal"|"critical"
|
||||
# => which urgency to apply to matched notifs
|
||||
# critera: each key is optional, value is regex; rule applies if *all* specified are matched
|
||||
# - `app-name`: string
|
||||
# - `desktop-entry`: string
|
||||
# - `summary`: string
|
||||
# - `body`: string
|
||||
# - `urgency`: "Low"|"Normal"|"Critical"
|
||||
# - `category`: string
|
||||
#
|
||||
# test rules by using `notify-send` (libnotify)
|
||||
sxmo-extraneous-daemons = {
|
||||
app-name = "notify-send";
|
||||
summary = "(sxmo_hook_lisgd|Autorotate) (Stopped|Started)";
|
||||
state = "ignored";
|
||||
};
|
||||
sxmo-extraneous-warnings = {
|
||||
app-name = "notify-send";
|
||||
# "Modem crashed! 30s recovery.": happens on sxmo_hook_postwake.sh (i.e. unlock)
|
||||
summary = "^Modem crashed.*$";
|
||||
state = "ignored";
|
||||
};
|
||||
sxmo-timer = {
|
||||
# force timer announcements to bypass DND
|
||||
app-name = "notify-send";
|
||||
summary = "^Done with .*$";
|
||||
override-urgency = "critical";
|
||||
};
|
||||
};
|
||||
inherit scripts;
|
||||
widgets = [
|
||||
# what to show in the notification center (and in which order).
|
||||
# these are configurable further via `widget-config`.
|
||||
@@ -355,94 +156,26 @@ in
|
||||
};
|
||||
buttons-grid = {
|
||||
actions =
|
||||
# {
|
||||
# type = "toggle";
|
||||
# label = "feedbackd";
|
||||
# command = "${systemctl-toggle}/bin/systemctl-toggle --user feedbackd";
|
||||
# active = "${pkgs.systemd}/bin/systemctl is-active --user feedbackd.service";
|
||||
# }
|
||||
lib.optionals config.sane.programs.eg25-control.enabled [
|
||||
{
|
||||
type = "toggle";
|
||||
label = ""; # GPS services; other icons: gps, ⌖, 🛰, 🌎,
|
||||
command = "/run/wrappers/bin/sudo ${systemctl-toggle}/bin/systemctl-toggle eg25-control-gps";
|
||||
update-command = "${printIsActive}/bin/print-is-active eg25-control-gps.service";
|
||||
active = true;
|
||||
}
|
||||
{
|
||||
type = "toggle";
|
||||
label = ""; # icons: 5g, 📡, 📱, ᯤ, ⚡, , 🌐, 📶, 🗼, , , ,
|
||||
# modem and NetworkManager auto-establishes a connection when powered.
|
||||
# though some things like `wg-home` VPN tunnel will remain routed over the old interface.
|
||||
command = "/run/wrappers/bin/sudo ${systemctl-toggle}/bin/systemctl-toggle eg25-control-powered";
|
||||
update-command = "${printIsActive}/bin/print-is-active eg25-control-powered.service";
|
||||
active = true;
|
||||
}
|
||||
buttons.gps
|
||||
buttons.cell-modem
|
||||
] ++ lib.optionals false [
|
||||
{
|
||||
type = "toggle";
|
||||
label = "vpn::hn"; # route all traffic through servo; useful to debug moby's networking
|
||||
command = "/run/wrappers/bin/sudo ${systemctl-toggle}/bin/systemctl-toggle wg-quick-vpn-servo";
|
||||
update-command = "${printIsActive}/bin/print-is-active wg-quick-vpn-servo.service";
|
||||
active = true;
|
||||
}
|
||||
buttons.vpn
|
||||
] ++ lib.optionals config.sane.programs.calls.config.autostart [
|
||||
{
|
||||
type = "toggle";
|
||||
label = "SIP";
|
||||
command = "${systemctl-toggle}/bin/systemctl-toggle --user gnome-calls";
|
||||
update-command = "${printIsActive}/bin/print-is-active --user gnome-calls";
|
||||
active = true;
|
||||
}
|
||||
buttons.gnome-calls
|
||||
] ++ lib.optionals config.sane.programs."gnome.geary".enabled [
|
||||
{
|
||||
type = "toggle";
|
||||
label = ""; # email (Geary); other icons: ✉, [E], 📧,
|
||||
command = "${systemctl-toggle}/bin/systemctl-toggle --user geary";
|
||||
update-command = "${printIsActive}/bin/print-is-active --user geary";
|
||||
active = true;
|
||||
}
|
||||
buttons.geary
|
||||
# ] ++ lib.optionals config.sane.programs.abaddon.enabled [
|
||||
# # XXX: disabled in favor of dissent: abaddon has troubles auto-connecting at start
|
||||
# {
|
||||
# type = "toggle";
|
||||
# label = ""; # Discord chat client; icons: , 🎮
|
||||
# command = "${systemctl-toggle}/bin/systemctl-toggle --user abaddon";
|
||||
# update-command = "${printIsActive}/bin/print-is-active --user abaddon";
|
||||
# active = true;
|
||||
# }
|
||||
# buttons.abaddon
|
||||
] ++ lib.optionals config.sane.programs.dissent.enabled [
|
||||
{
|
||||
type = "toggle";
|
||||
label = ""; # Discord chat client; icons: , 🎮
|
||||
command = "${systemctl-toggle}/bin/systemctl-toggle --user dissent";
|
||||
update-command = "${printIsActive}/bin/print-is-active --user dissent";
|
||||
active = true;
|
||||
}
|
||||
buttons.dissent
|
||||
] ++ lib.optionals config.sane.programs.signal-desktop.enabled [
|
||||
{
|
||||
type = "toggle";
|
||||
label = "💬"; # Signal messenger; other icons:
|
||||
command = "${systemctl-toggle}/bin/systemctl-toggle --user signal-desktop";
|
||||
update-command = "${printIsActive}/bin/print-is-active --user signal-desktop";
|
||||
active = true;
|
||||
}
|
||||
buttons.signal-desktop
|
||||
] ++ lib.optionals config.sane.programs.dino.enabled [
|
||||
{
|
||||
type = "toggle";
|
||||
label = "XMPP"; # XMPP calls (jingle)
|
||||
command = "${systemctl-toggle}/bin/systemctl-toggle --user dino";
|
||||
update-command = "${printIsActive}/bin/print-is-active --user dino";
|
||||
active = true;
|
||||
}
|
||||
buttons.dino
|
||||
] ++ lib.optionals config.sane.programs.fractal.enabled [
|
||||
{
|
||||
type = "toggle";
|
||||
label = "[m]"; # Matrix messages
|
||||
command = "${systemctl-toggle}/bin/systemctl-toggle --user fractal";
|
||||
update-command = "${printIsActive}/bin/print-is-active --user fractal";
|
||||
active = true;
|
||||
}
|
||||
buttons.fractal
|
||||
];
|
||||
};
|
||||
dnd = {
|
||||
@@ -471,21 +204,11 @@ in
|
||||
# swaync ships its own service, but i want to add `environment` variables and flags for easier debugging.
|
||||
# seems that's not possible without defining an entire nix-native service (i.e. this).
|
||||
description = "swaynotificationcenter (swaync) desktop notification daemon";
|
||||
after = [
|
||||
"graphical-session.target"
|
||||
"pipewire-pulse.service" #< TODO: else it will NEVER see the pulse socket in its sandbox
|
||||
];
|
||||
# partOf = [ "graphical-session.target" ];
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
depends = [ "sound" ]; #< TODO: else it will NEVER see the pulse socket in its sandbox
|
||||
partOf = [ "graphical-session" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/swaync";
|
||||
Type = "simple";
|
||||
# BusName = "org.freedesktop.Notifications";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
environment.G_MESSAGES_DEBUG = "all";
|
||||
command = "env G_MESSAGES_DEBUG=all swaync";
|
||||
readiness.waitDbus = "org.freedesktop.Notifications";
|
||||
};
|
||||
};
|
||||
|
||||
|
114
hosts/common/programs/swaynotificationcenter/scripts.nix
Normal file
114
hosts/common/programs/swaynotificationcenter/scripts.nix
Normal file
@@ -0,0 +1,114 @@
|
||||
# this file defines the `scripts` entry within ~/.config/swaync/config.json.
|
||||
# it describes special things to do in response to specific notifications,
|
||||
# e.g. sound a ringer when we get a call, ...
|
||||
{ pkgs }:
|
||||
let
|
||||
fbcli-wrapper = pkgs.writeShellApplication {
|
||||
name = "swaync-fbcli";
|
||||
runtimeInputs = [
|
||||
pkgs.feedbackd
|
||||
pkgs.procps # for pkill
|
||||
pkgs.swaynotificationcenter
|
||||
];
|
||||
text = ''
|
||||
# if in Do Not Disturb, don't do any feedback
|
||||
# TODO: better solution is to actually make use of feedbackd profiles.
|
||||
# i.e. set profile to `quiet` when in DnD mode
|
||||
if [ "$SWAYNC_URGENCY" != "Critical" ] && [ "$(swaync-client --get-dnd)" = "true" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
# kill children if killed, to allow that killing this parent process will end the real fbcli call
|
||||
cleanup() {
|
||||
echo "aborting fbcli notification (PID $child)"
|
||||
pkill -P "$child"
|
||||
exit 0 # exit cleanly to avoid swaync alerting a script failure
|
||||
}
|
||||
trap cleanup SIGINT SIGQUIT SIGTERM
|
||||
|
||||
# feedbackd stops playback when the caller exits
|
||||
# and fbcli will exit immediately if it has no stdin.
|
||||
# so spoof a stdin:
|
||||
/bin/sh -c "true | fbcli $*" &
|
||||
child=$!
|
||||
wait
|
||||
'';
|
||||
};
|
||||
fbcli = "${fbcli-wrapper}/bin/swaync-fbcli";
|
||||
|
||||
# we do this because swaync's exec naively splits the command on space to produce its argv, rather than parsing the shell.
|
||||
# [ "pkill" "-f" "fbcli" "--event" ... ] -> breaks pkill
|
||||
# [ "pkill" "-f" "fbcli --event ..." ] -> is what we want
|
||||
fbcli-stop-wrapper = pkgs.writeShellApplication {
|
||||
name = "fbcli-stop";
|
||||
runtimeInputs = [
|
||||
pkgs.procps # for pkill
|
||||
];
|
||||
text = ''
|
||||
pkill -e -f "${fbcli} $*"
|
||||
'';
|
||||
};
|
||||
fbcli-stop = "${fbcli-stop-wrapper}/bin/fbcli-stop";
|
||||
in
|
||||
{
|
||||
# a script can match regex on these fields. only fired if all listed fields match:
|
||||
# - app-name
|
||||
# - desktop-entry
|
||||
# - summary
|
||||
# - body
|
||||
# - urgency (Low/Normal/Critical)
|
||||
# - category
|
||||
# additionally, the script can be run either on receipt or action:
|
||||
# - run-on = "receive" or "action"
|
||||
# when script is run, these env vars are available:
|
||||
# - SWAYNC_BODY
|
||||
# - SWAYNC_DESKTOP_ENTRY
|
||||
# - SWAYNC_URGENCY
|
||||
# - SWAYNC_TIME
|
||||
# - SWAYNC_APP_NAME
|
||||
# - SWAYNC_CATEGORY
|
||||
# - SWAYNC_REPLACES_ID
|
||||
# - SWAYNC_ID
|
||||
# - SWAYNC_SUMMARY
|
||||
|
||||
# rules to use for testing. trigger with:
|
||||
# - `notify-send test test:message` (etc)
|
||||
# should also be possible to trigger via any messaging app
|
||||
fbcli-test-im = {
|
||||
body = "test:message";
|
||||
exec = "${fbcli} --event proxied-message-new-instant";
|
||||
};
|
||||
fbcli-test-call = {
|
||||
body = "test:call";
|
||||
exec = "${fbcli} --event phone-incoming-call -t 20";
|
||||
};
|
||||
fbcli-test-call-stop = {
|
||||
body = "test:call-stop";
|
||||
exec = "${fbcli-stop} --event phone-incoming-call -t 20";
|
||||
};
|
||||
|
||||
incoming-im-known-app-name = {
|
||||
# trigger notification sound on behalf of these IM clients.
|
||||
app-name = "(Chats|Dino|discord|dissent|Element|Fractal)";
|
||||
body = "^(?!Incoming call).*$"; #< don't match Dino Incoming calls
|
||||
exec = "${fbcli} --event proxied-message-new-instant";
|
||||
};
|
||||
incoming-im-known-desktop-entry = {
|
||||
# trigger notification sound on behalf of these IM clients.
|
||||
# these clients don't have an app-name (listed as "<unknown>"), but do have a desktop-entry
|
||||
desktop-entry = "com.github.uowuo.abaddon";
|
||||
exec = "${fbcli} --event proxied-message-new-instant";
|
||||
};
|
||||
incoming-call = {
|
||||
app-name = "Dino";
|
||||
body = "^Incoming call$";
|
||||
exec = "${fbcli} --event phone-incoming-call -t 20";
|
||||
};
|
||||
incoming-call-acted-on = {
|
||||
# when the notification is clicked, stop sounding the ringer
|
||||
app-name = "Dino";
|
||||
body = "^Incoming call$";
|
||||
run-on = "action";
|
||||
exec = "${fbcli-stop} --event phone-incoming-call -t 20";
|
||||
};
|
||||
}
|
65
hosts/common/programs/swaynotificationcenter/swaync-service-dispatcher
Executable file
65
hosts/common/programs/swaynotificationcenter/swaync-service-dispatcher
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p s6 -p s6-rc -p systemd
|
||||
|
||||
# for default $PATH to take precedence over nix-shell PATH if invoked interactively,
|
||||
# otherwise we invoke a s6-rc which does not know where to find files.
|
||||
export PATH="/etc/profiles/per-user/$(whoami)/bin:/run/current-system/sw/bin:$PATH"
|
||||
|
||||
action="$1"
|
||||
type="$2"
|
||||
service="$3"
|
||||
|
||||
log() {
|
||||
if [ -n "$SWAYNC_SERVICES_DEBUG" ]; then
|
||||
printf "%s\n" "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
checkActive() {
|
||||
case "$type" in
|
||||
systemd)
|
||||
systemctl is-active "$service.service" > /dev/null && echo true || echo false
|
||||
;;
|
||||
s6)
|
||||
s6-svstat -o wantedup "$XDG_RUNTIME_DIR/s6/live/servicedirs/$service"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
startService() {
|
||||
log "startService: $service"
|
||||
case "$type" in
|
||||
systemd)
|
||||
/run/wrappers/bin/sudo systemctl start "$service"
|
||||
;;
|
||||
s6)
|
||||
s6-rc start "$service"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
stopService() {
|
||||
log "stopService: $service"
|
||||
case "$type" in
|
||||
systemd)
|
||||
/run/wrappers/bin/sudo systemctl stop "$service"
|
||||
;;
|
||||
s6)
|
||||
s6-rc stop "$service"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
case "$action" in
|
||||
print)
|
||||
checkActive
|
||||
;;
|
||||
toggle)
|
||||
case "$(checkActive)" in
|
||||
false)
|
||||
startService
|
||||
;;
|
||||
true)
|
||||
stopService
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
@@ -1,19 +1,6 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.sysvol;
|
||||
in
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.sysvol = {
|
||||
configOption = with lib; mkOption {
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
@@ -65,10 +52,9 @@ in
|
||||
|
||||
services."sysvol" = {
|
||||
description = "sysvol: volume monitor/notifier";
|
||||
after = [ "graphical-session.target" "pipewire-pulse.service" ];
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
|
||||
depends = [ "sound" ]; #< specifically wireplumber-pulse
|
||||
partOf = [ "graphical-session" ];
|
||||
|
||||
serviceConfig = {
|
||||
# options:
|
||||
# -p {0,1,2,3} to attach to top/right/bottom/left screen edge
|
||||
# -t N for the notifier to be dismissed after N seconds (integer only)
|
||||
@@ -77,11 +63,7 @@ in
|
||||
# -{H,W} N to set the height/width of the notifier, in px.
|
||||
# -i N to set the size of the volume icon
|
||||
# -P to hide percentage text
|
||||
ExecStart = "${cfg.package}/bin/sysvol -p 0 -t 1 -m 22 -H 39 -W 256 -i 32 -P";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
command = "sysvol -p 0 -t 1 -m 22 -H 39 -W 256 -i 32 -P";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -30,42 +30,6 @@ let
|
||||
login -p ${cfg.config.user}
|
||||
'';
|
||||
};
|
||||
tryLaunchDefaultDesktop = pkgs.writeShellScriptBin "tryLaunchDefaultDesktop" ''
|
||||
# return an array of paths to .desktop files which contain wayland sessions.
|
||||
getWaylandDesktops() {
|
||||
_desktops=()
|
||||
_xdgDirs=(''${XDG_DATA_DIRS//:/ })
|
||||
for _dataDir in ''${_xdgDirs[@]}; do
|
||||
for _maybeDesktop in $_dataDir/wayland-sessions/*.desktop; do
|
||||
# if no matches, bash will give literally "<path>/*.desktop", with the asterisk
|
||||
if [ -e "$_maybeDesktop" ]; then
|
||||
_desktops+=("$_maybeDesktop")
|
||||
fi
|
||||
done
|
||||
done
|
||||
echo "''${_desktops[@]}"
|
||||
}
|
||||
|
||||
# IF there's any desktop files, then launch the first one
|
||||
tryLaunchDefaultDesktop() {
|
||||
_desktops=($(getWaylandDesktops))
|
||||
if [ -n "$_desktops" ]; then
|
||||
_firstDesktop="''${_desktops[0]}"
|
||||
if command -v gio > /dev/null; then
|
||||
_gio="gio"
|
||||
else
|
||||
_gio="${lib.getBin pkgs.glib}/bin/gio"
|
||||
fi
|
||||
|
||||
echo "launching $(basename $_firstDesktop) session in ${builtins.toString cfg.config.delay}s"
|
||||
# if the `sleep` call here is `Ctrl+C'd`, then it'll exit false and the desktop isn't launched.
|
||||
sleep ${builtins.toString cfg.config.delay} && \
|
||||
"$_gio" launch "$_firstDesktop"
|
||||
fi
|
||||
}
|
||||
|
||||
tryLaunchDefaultDesktop
|
||||
'';
|
||||
in
|
||||
{
|
||||
sane.programs.unl0kr = {
|
||||
@@ -119,15 +83,29 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
fs.".profile".symlink.text = lib.mkMerge [
|
||||
(lib.mkBefore ''
|
||||
# setup primarySessionCommands here and let any other nix config populate it later
|
||||
primarySessionCommands=()
|
||||
initPrimarySession() {
|
||||
for c in "''${primarySessionCommands[@]}"; do
|
||||
eval "$c"
|
||||
done
|
||||
}
|
||||
'')
|
||||
# lib.mkAfter so that launching the DE happens *after* any other .profile setup.
|
||||
# alternatively, we could recurse: exec a new login shell with some env-var signalling to not launch the DE,
|
||||
# run with `-c "{cfg.afterLogin}"`
|
||||
fs.".profile".symlink.text = lib.mkAfter ''
|
||||
(lib.mkAfter ''
|
||||
# if already running a desktop environment, or if running from ssh, then `tty` will show /dev/pts/NN.
|
||||
if [ "$(tty)" = "/dev/${tty}" ]; then
|
||||
${tryLaunchDefaultDesktop}/bin/tryLaunchDefaultDesktop
|
||||
if (( ''${#primarySessionCommands[@]} )); then
|
||||
echo "launching primary session commands in ${builtins.toString cfg.config.delay}s: ''${primarySessionCommands[*]}"
|
||||
# if the `sleep` call here is `Ctrl+C'd`, then it'll exit false and the desktop isn't launched.
|
||||
sleep ${builtins.toString cfg.config.delay} && \
|
||||
initPrimarySession
|
||||
fi
|
||||
'';
|
||||
fi
|
||||
'')
|
||||
];
|
||||
|
||||
# N.B.: this sandboxing applies to `unl0kr` itself -- the on-screen-keyboard;
|
||||
# NOT to the wrapper which invokes `login`.
|
||||
@@ -149,11 +127,6 @@ in
|
||||
# unl0kr is run as root, and especially with sandboxing, needs to be installed for root if expected to work.
|
||||
sane.programs.unl0kr.enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true;
|
||||
|
||||
environment.pathsToLink = lib.mkIf cfg.enabled [
|
||||
# so we can figure out what to auto-launch
|
||||
"/share/wayland-sessions"
|
||||
];
|
||||
|
||||
systemd = lib.mkIf cfg.enabled {
|
||||
# prevent nixos-rebuild from killing us after a redeploy
|
||||
services."autovt@${tty}".enable = false;
|
||||
|
@@ -90,7 +90,7 @@ in
|
||||
];
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.extraRuntimePaths = [
|
||||
"sway-ipc.sock"
|
||||
"sway"
|
||||
# "sxmo_status" #< only necessary if relying on sxmo's statusbar periodicals service
|
||||
];
|
||||
sandbox.extraPaths = [
|
||||
@@ -115,17 +115,10 @@ in
|
||||
|
||||
services.waybar = {
|
||||
description = "swaybar graphical header bar/tray for sway";
|
||||
after = [ "graphical-session.target" ];
|
||||
# partOf = [ "graphical-session.target" ];
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
partOf = [ "graphical-session" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/waybar";
|
||||
Type = "simple";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
# environment.G_MESSAGES_DEBUG = "all";
|
||||
# env G_MESSAGES_DEBUG=all
|
||||
command = "waybar";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ in
|
||||
max-length = 50;
|
||||
};
|
||||
|
||||
"sway/workspaces".persistent_workspaces = lib.mkIf (persistWorkspaces != []) (
|
||||
"sway/workspaces".persistent-workspaces = lib.mkIf (persistWorkspaces != []) (
|
||||
lib.genAttrs persistWorkspaces (_: [])
|
||||
);
|
||||
|
||||
|
@@ -1,7 +1,4 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.wireplumber;
|
||||
in
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.wireplumber = {
|
||||
sandbox.method = "bwrap";
|
||||
@@ -34,15 +31,9 @@ in
|
||||
|
||||
services.wireplumber = {
|
||||
description = "wireplumber: pipewire Multimedia Service Session Manager";
|
||||
after = [ "pipewire.service" ];
|
||||
bindsTo = [ "pipewire.service" ];
|
||||
wantedBy = [ "pipewire.service" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/wireplumber";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
};
|
||||
depends = [ "pipewire" ];
|
||||
partOf = [ "sound" ];
|
||||
command = "wireplumber";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,118 +0,0 @@
|
||||
# docs:
|
||||
# - <https://github.com/francma/wob/blob/master/wob.ini.5.scd>
|
||||
# - `wob -vv` to see config defaults
|
||||
#
|
||||
# the wob services defined here are largely based on those from SXMO.
|
||||
#
|
||||
# this should arguably be just a (user) service. nothing actually needs `wob` on the PATH.
|
||||
#
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
wob-audio = pkgs.static-nix-shell.mkPython3Bin {
|
||||
pname = "wob-audio";
|
||||
srcRoot = ./.;
|
||||
pkgs = [ "wireplumber" ];
|
||||
};
|
||||
cfg = config.sane.programs.wob;
|
||||
in
|
||||
{
|
||||
sane.programs.wob = {
|
||||
configOption = with lib; mkOption {
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
options.sock = mkOption {
|
||||
type = types.str;
|
||||
default = "wob.sock";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
suggestedPrograms = [
|
||||
"wob-audio"
|
||||
];
|
||||
|
||||
fs.".config/wob/wob.ini".symlink.text = ''
|
||||
timeout = 900
|
||||
|
||||
anchor = top right
|
||||
orientation = vertical
|
||||
# margin top right bottom left
|
||||
# note that wob is "aware" of the sway bar, so margin 0 never overlaps it.
|
||||
# however it's not aware of sway's window title
|
||||
margin = 54 3 54 3
|
||||
|
||||
height = 164
|
||||
width = 30
|
||||
|
||||
border_offset = 0
|
||||
border_size = 2
|
||||
bar_padding = 0
|
||||
|
||||
# very light teal, derived from conky background
|
||||
bar_color = e1f0efDC
|
||||
background_color = 000000B4
|
||||
border_color = 000000C8
|
||||
|
||||
overflow_bar_color = FF4040DC
|
||||
overflow_background_color = FFFFFFC8
|
||||
overflow_border_color = FF4040DC
|
||||
'';
|
||||
|
||||
services.wob = {
|
||||
description = "Wayland Overlay Bar (wob) renders volume/backlight levels when requested";
|
||||
after = [ "graphical-session.target" ];
|
||||
partOf = [ "graphical-session.target" ];
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
# ExecStart = "${cfg.package}/bin/wob";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
script = ''
|
||||
wobsock="$XDG_RUNTIME_DIR/$WOBSOCK_NAME"
|
||||
rm -f "$wobsock" || true
|
||||
mkfifo "$wobsock" && wob <> "$wobsock"
|
||||
|
||||
# TODO: cleanup should be done in a systemd OnFailure, or OnExit, or whatever
|
||||
rm -f "$wobsock"
|
||||
'';
|
||||
environment.WOBSOCK_NAME = cfg.config.sock;
|
||||
};
|
||||
};
|
||||
|
||||
sane.programs.wob-audio = {
|
||||
packageUnwrapped = wob-audio;
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.extraRuntimePaths = [
|
||||
cfg.config.sock
|
||||
];
|
||||
|
||||
suggestedPrograms = [
|
||||
"wireplumber" #< TODO: replace with just the one binary we need.
|
||||
];
|
||||
|
||||
services.wob-audio = {
|
||||
description = "wob-audio: monitor audio and display volume changes on-screen";
|
||||
after = [ "wob.service" ];
|
||||
wantedBy = [ "wob.service" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${wob-audio}/bin/wob-audio";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
# environment.WOB_VERBOSE = "1";
|
||||
environment.WOBSOCK_NAME = cfg.config.sock;
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,97 +0,0 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ])" -p wireplumber
|
||||
# vim: set filetype=python :
|
||||
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class PwMonConsumer:
|
||||
last_volume: float | None = None
|
||||
last_mute: bool | None = None
|
||||
last_effective_volume: float | None = None
|
||||
|
||||
# parser state:
|
||||
in_changed: bool = False
|
||||
in_node: bool = False
|
||||
in_vol: bool = False
|
||||
in_mute: bool = False
|
||||
|
||||
def feed_line(self, line: str) -> float | None:
|
||||
""" consume a line and *maybe* return the volume """
|
||||
|
||||
logger.debug("got pw-mon line: %s", line.rstrip())
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith("changed:"):
|
||||
self.in_changed = True
|
||||
elif line.startswith("added:") or line.startswith("removed:"):
|
||||
self.in_changed = False
|
||||
elif line.startswith("type: "):
|
||||
self.in_node = line.startswith("type: PipeWire:Interface:Node")
|
||||
logger.debug("parsed `type:` %s %d", line, self.in_node)
|
||||
elif line.startswith("Prop: "):
|
||||
# which of the *Volumes params we read is unclear.
|
||||
# alternative to this is to just detect the change, and then cal wpctl get-volume @DEFAULT_AUDIO_SINK@
|
||||
self.in_vol = line.startswith("Prop: key Spa:Pod:Object:Param:Props:channelVolumes")
|
||||
self.in_mute = line.startswith("Prop: key Spa:Pod:Object:Param:Props:softMute")
|
||||
logger.debug("parsed `Prop:` %s %d", line, self.in_vol)
|
||||
elif line.startswith("Float ") and self.in_changed and self.in_node and self.in_vol:
|
||||
value = float(line[len("Float "):])
|
||||
self.feed_volume(value)
|
||||
elif line.startswith("Bool ") and self.in_changed and self.in_node and self.in_mute:
|
||||
value = line[len("Bool "):] == "true"
|
||||
self.feed_mute(value)
|
||||
|
||||
def feed_volume(self, new: float) -> None:
|
||||
logger.debug("feed volume: %f -> %f", self.last_volume or 0.0, new)
|
||||
self.last_volume = new
|
||||
self.check_effective_volume()
|
||||
|
||||
def feed_mute(self, new: bool) -> None:
|
||||
logger.debug("feed mute: %d -> %d", self.last_mute or False, new)
|
||||
self.last_mute = new
|
||||
self.check_effective_volume()
|
||||
|
||||
def check_effective_volume(self) -> None:
|
||||
eff_volume = 0.0 if self.last_mute else self.last_volume
|
||||
if eff_volume != self.last_effective_volume:
|
||||
logger.info("new effective volume: %f", eff_volume)
|
||||
self.on_new_volume(eff_volume)
|
||||
self.last_effective_volume = eff_volume
|
||||
|
||||
|
||||
class PwMonWobConsumer(PwMonConsumer):
|
||||
def __init__(self):
|
||||
self.sock_name = os.path.join(
|
||||
os.environ.get("XDG_RUNTIME_DIR", "/run"),
|
||||
os.environ.get("WOBSOCK_NAME", "wob.sock"),
|
||||
)
|
||||
self.wob_sock = open(self.sock_name, "w")
|
||||
|
||||
def on_new_volume(self, vol: float) -> None:
|
||||
# pipewire volume is between 0 and 3.375.
|
||||
# wob is between 0 - 1, or 1 - 2 if overdriven.
|
||||
# idk where vol ** (1/3) comes from, but it precisely matches what wpctl shows,
|
||||
# getting the range to 0.00 - 1.50 with precise 0.05 increments.
|
||||
int_vol = int(round(vol ** 0.3333 * 100))
|
||||
logger.info("writing to %s: %d", self.sock_name, int_vol)
|
||||
self.wob_sock.write(f"{int_vol}\n")
|
||||
self.wob_sock.flush()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
logging.basicConfig()
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
if os.environ.get("WOB_VERBOSE", "") == "1":
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
consumer = PwMonWobConsumer()
|
||||
proc = subprocess.Popen(["pw-mon"], stdout=subprocess.PIPE)
|
||||
for line in iter(proc.stdout.readline, b''):
|
||||
consumer.feed_line(line.decode("utf-8"))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@@ -1,7 +1,4 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.wvkbd;
|
||||
in
|
||||
{ lib, pkgs, ... }:
|
||||
{
|
||||
sane.programs.wvkbd = {
|
||||
packageUnwrapped = pkgs.wvkbd.overrideAttrs (base: {
|
||||
@@ -18,16 +15,11 @@ in
|
||||
|
||||
services.wvkbd = {
|
||||
description = "wvkbd: wayland virtual keyboard";
|
||||
after = [ "graphical-session.target" ];
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
serviceConfig = {
|
||||
# depends = [ "graphical-session" ];
|
||||
partOf = [ "graphical-session" ];
|
||||
command = lib.escapeShellArgs [
|
||||
"env"
|
||||
# --hidden: send SIGUSR2 to unhide
|
||||
ExecStart = "${cfg.package}/bin/wvkbd-mobintl --hidden";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "3s";
|
||||
};
|
||||
|
||||
# wvkbd layers:
|
||||
# - full
|
||||
# - landscape
|
||||
@@ -42,12 +34,15 @@ in
|
||||
# - persian
|
||||
# - greek
|
||||
# - georgian
|
||||
environment.WVKBD_LANDSCAPE_LAYERS = "landscape,special,emoji";
|
||||
environment.WVKBD_LAYERS = "full,special,emoji";
|
||||
environment.WVKBD_HEIGHT = "216"; #< default: 250 (pixels)
|
||||
# environment.WVKBD_LANDSCAPE_HEIGHT = "??"; #< default: 120 (pixels)
|
||||
"WVKBD_LANDSCAPE_LAYERS=landscape,special,emoji"
|
||||
"WVKBD_LAYERS=full,special,emoji"
|
||||
"WVKBD_HEIGHT=216" #< default: 250 (pixels)
|
||||
# "WVKBD_LANDSCAPE_HEIGHT=??" #< default: 120 (pixels)
|
||||
# more settings tunable inside config.h when compiling:
|
||||
# - KBD_KEY_BORDER = 2
|
||||
"wvkbd-mobintl"
|
||||
"--hidden"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -41,17 +41,11 @@ in
|
||||
|
||||
services.xdg-desktop-portal-gtk = {
|
||||
description = "xdg-desktop-portal-gtk backend (provides graphical dialogs for xdg-desktop-portal)";
|
||||
after = [ "graphical-session.target" ];
|
||||
before = [ "xdg-desktop-portal.service" ];
|
||||
wantedBy = [ "xdg-desktop-portal.service" ];
|
||||
# depends = [ "graphical-session" ];
|
||||
dependencyOf = [ "xdg-desktop-portal" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart="${cfg.package}/libexec/xdg-desktop-portal-gtk";
|
||||
Type = "dbus";
|
||||
BusName = "org.freedesktop.impl.portal.desktop.gtk";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
command = "${cfg.package}/libexec/xdg-desktop-portal-gtk";
|
||||
readiness.waitDbus = "org.freedesktop.impl.portal.desktop.gtk";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -26,17 +26,12 @@ in
|
||||
|
||||
services.xdg-desktop-portal-wlr = {
|
||||
description = "xdg-desktop-portal-wlr backend (provides screenshot functionality for xdg-desktop-portal)";
|
||||
after = [ "graphical-session.target" ];
|
||||
before = [ "xdg-desktop-portal.service" ];
|
||||
wantedBy = [ "xdg-desktop-portal.service" ];
|
||||
depends = [ "pipewire" ]; # refuses to start without it
|
||||
dependencyOf = [ "xdg-desktop-portal" ];
|
||||
# partOf = [ "graphical-session" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart="${cfg.package}/libexec/xdg-desktop-portal-wlr";
|
||||
Type = "dbus";
|
||||
BusName = "org.freedesktop.impl.portal.desktop.wlr";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
command = "${cfg.package}/libexec/xdg-desktop-portal-wlr";
|
||||
readiness.waitDbus = "org.freedesktop.impl.portal.desktop.wlr";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -15,6 +15,9 @@
|
||||
# - whereas OpenURI requires a URI argument, DynamicLauncher is just "launch an app by <app_id>.desktop"
|
||||
# - example (glib): `gdbus call --session --timeout 10 --dest org.freedesktop.portal.Desktop --object-path /org/freedesktop/portal/desktop --method org.freedesktop.portal.DynamicLauncher.Launch 'audacity.desktop' "{}"`
|
||||
# - .desktop files are searched for in ~/.local/share/xdg-desktop-portal/applications
|
||||
#
|
||||
# debugging:
|
||||
# - show available portals: <https://codeberg.org/tytan652/door-knocker>
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.xdg-desktop-portal;
|
||||
@@ -54,44 +57,35 @@ in
|
||||
|
||||
services.xdg-desktop-portal = {
|
||||
description = "xdg-desktop-portal freedesktop.org portal (URI opener, file chooser, etc)";
|
||||
after = [ "graphical-session.target" ];
|
||||
# partOf = [ "graphical-session.target" ];
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart="${cfg.package}/libexec/xdg-desktop-portal";
|
||||
Type = "dbus";
|
||||
BusName = "org.freedesktop.portal.Desktop";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
partOf = [ "graphical-session" ];
|
||||
|
||||
# tracking issue for having xdg-desktop-portal locate portals via more standard directories, obviating this var:
|
||||
# - <https://github.com/flatpak/xdg-desktop-portal/issues/603>
|
||||
# i can actually almost omit it today; problem is that if you don't set it it'll look for `sway-portals.conf` in ~/.config/xdg-desktop-portal
|
||||
# but then will check its *own* output dir for {gtk,wlr}.portal.
|
||||
# arguable if that's a packaging bug, or limitation...
|
||||
environment.XDG_DESKTOP_PORTAL_DIR = "%E/xdg-desktop-portal";
|
||||
|
||||
# environment.G_MESSAGES_DEBUG = "all"; #< also applies to all apps launched by the portal
|
||||
command = lib.concatStringsSep " " [
|
||||
"env"
|
||||
"XDG_DESKTOP_PORTAL_DIR=$HOME/.config/xdg-desktop-portal"
|
||||
"G_MESSAGES_DEBUG=xdg-desktop-portal=all"
|
||||
"${cfg.package}/libexec/xdg-desktop-portal"
|
||||
];
|
||||
readiness.waitDbus = "org.freedesktop.portal.Desktop";
|
||||
};
|
||||
|
||||
services.xdg-permission-store = {
|
||||
# xdg-desktop-portal would *usually* dbus-activate this.
|
||||
# this service might not strictly be necssary. xdg-desktop-portal does warn if it's not present, though.
|
||||
description = "xdg-permission-store: lets xdg-desktop-portal know which handlers are 'safe'";
|
||||
after = [ "graphical-session.target" ];
|
||||
before = [ "xdg-desktop-portal.service" ];
|
||||
wantedBy = [ "xdg-desktop-portal.service" ];
|
||||
# after = [ "graphical-session" ];
|
||||
dependencyOf = [ "xdg-desktop-portal" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart="${cfg.package}/libexec/xdg-permission-store";
|
||||
Type = "dbus";
|
||||
BusName = "org.freedesktop.impl.portal.PermissionStore";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
environment.XDG_DESKTOP_PORTAL_DIR = "%E/xdg-desktop-portal";
|
||||
command = lib.concatStringsSep " " [
|
||||
"env"
|
||||
"XDG_DESKTOP_PORTAL_DIR=$HOME/.config/xdg-desktop-portal"
|
||||
"${cfg.package}/libexec/xdg-permission-store"
|
||||
];
|
||||
readiness.waitDbus = "org.freedesktop.impl.portal.PermissionStore";
|
||||
};
|
||||
# also available: ${cfg.package}/libexec/xdg-document-portal
|
||||
# - <https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Documents.html>
|
||||
|
@@ -1,9 +1,16 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.xdg-terminal-exec = {
|
||||
packageUnwrapped = pkgs.xdg-terminal-exec.overrideAttrs (upstream: {
|
||||
# fix for <https://github.com/Vladimir-csp/xdg-terminal-exec/issues/50>
|
||||
postPatch = (upstream.postPatch or "") + ''
|
||||
sed '2i\
|
||||
unset TERMINAL\
|
||||
' -i xdg-terminal-exec
|
||||
'';
|
||||
|
||||
# give the package a .desktop item.
|
||||
# this way anyone can launch a terminal via the xdg-desktop-portal.
|
||||
packageUnwrapped = pkgs.xdg-terminal-exec.overrideAttrs (upstream: {
|
||||
nativeBuildInputs = (upstream.nativeBuildInputs or []) ++ [
|
||||
pkgs.copyDesktopItems
|
||||
];
|
||||
|
@@ -18,8 +18,6 @@ in
|
||||
"animatch"
|
||||
"gnome-2048"
|
||||
"gnome.hitori" # like sudoku
|
||||
"superTux" # keyboard-only controls
|
||||
"superTuxKart" # poor FPS on pinephone
|
||||
];
|
||||
sane.programs.pcGameApps = declPackageSet [
|
||||
# "andyetitmoves" # TODO: fix build!
|
||||
@@ -42,6 +40,8 @@ in
|
||||
# "powermanga" # STYLISH space invaders derivative (keyboard-only)
|
||||
"shattered-pixel-dungeon" # doesn't cross compile
|
||||
"space-cadet-pinball" # LMB/RMB controls (bindable though. volume buttons?)
|
||||
"superTux" # keyboard-only controls
|
||||
"superTuxKart" # poor FPS on pinephone
|
||||
"tumiki-fighters" # keyboard-only
|
||||
"vvvvvv" # keyboard-only controls
|
||||
# "wine"
|
||||
@@ -57,7 +57,7 @@ in
|
||||
# "abaddon" # discord client
|
||||
"alacritty" # terminal emulator
|
||||
"dconf" # required by many packages, but not well-documented :(
|
||||
"delfin" # Jellyfin client
|
||||
# "delfin" # Jellyfin client
|
||||
"dialect" # language translation
|
||||
"dino" # XMPP client
|
||||
"dissent" # Discord client (formerly known as: gtkcord4)
|
||||
@@ -134,7 +134,7 @@ in
|
||||
"pcTuiApps"
|
||||
] ++ [
|
||||
"audacity"
|
||||
"blanket" # ambient noise generator
|
||||
# "blanket" # ambient noise generator
|
||||
"brave" # for the integrated wallet -- as a backup
|
||||
# "cantata" # music player (mpd frontend)
|
||||
# "chromium" # chromium takes hours to build. brave is chromium-based, distributed in binary form, so prefer it.
|
||||
@@ -173,7 +173,7 @@ in
|
||||
"tor-browser" # x86-only
|
||||
# "vlc"
|
||||
"wireshark" # could maybe ship the cli as sysadmin pkg
|
||||
"xterm" # requires Xwayland
|
||||
# "xterm" # requires Xwayland
|
||||
# "zecwallet-lite" # x86-only
|
||||
]
|
||||
);
|
||||
|
@@ -21,7 +21,7 @@
|
||||
"sway/workspaces" = {
|
||||
all-outputs = true;
|
||||
# force the bar to always show even empty workspaces
|
||||
persistent_workspaces = {
|
||||
persistent-workspaces = {
|
||||
"1" = [];
|
||||
"2" = [];
|
||||
"3" = [];
|
||||
|
9
modules/data/feeds/sources/davidrevoy.com/default.json
Normal file
9
modules/data/feeds/sources/davidrevoy.com/default.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"description": "Artist, Instructor, using only Free/Libre and Open-Source software since 2009.",
|
||||
"is_podcast": false,
|
||||
"site_name": "David Revoy",
|
||||
"site_url": "https://www.davidrevoy.com",
|
||||
"title": "David Revoy",
|
||||
"url": "https://www.davidrevoy.com/feed/rss",
|
||||
"velocity": 0.093
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"description": "Hacker Public Radio is an podcast that releases shows every weekday Monday through Friday. Our shows are produced by the community (you) and can be on any topic that are of interest to hackers and hobbyists.",
|
||||
"is_podcast": true,
|
||||
"site_name": "Hacker Public Radio ~ The Technology Community Podcast",
|
||||
"site_url": "https://hackerpublicradio.org",
|
||||
"title": "Hacker Public Radio",
|
||||
"url": "https://hackerpublicradio.org/hpr_spx_rss.php",
|
||||
"velocity": 0.692
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"description": "Personal stories and lessons from inside the rise and fall of the PC revolution",
|
||||
"is_podcast": false,
|
||||
"site_name": "Hardcore Software by Steven Sinofsky | Substack",
|
||||
"site_url": "https://hardcoresoftware.learningbyshipping.com",
|
||||
"title": "Hardcore Software by Steven Sinofsky",
|
||||
"url": "https://hardcoresoftware.learningbyshipping.com/feed",
|
||||
"velocity": 0.039
|
||||
}
|
9
modules/data/feeds/sources/nixpkgs.news/default.json
Normal file
9
modules/data/feeds/sources/nixpkgs.news/default.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"description": "A weekly recap of news from the Nix ecosystem.",
|
||||
"is_podcast": false,
|
||||
"site_name": "nixpkgs.news",
|
||||
"site_url": "https://nixpkgs.news",
|
||||
"title": "nixpkgs.news",
|
||||
"url": "https://nixpkgs.news/rss.xml",
|
||||
"velocity": 0.143
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"description": "willow's blog",
|
||||
"is_podcast": false,
|
||||
"site_name": "willow's website",
|
||||
"site_url": "https://willow.phantoma.online",
|
||||
"title": "willow's website",
|
||||
"url": "https://willow.phantoma.online/rss.xml",
|
||||
"velocity": 0
|
||||
}
|
@@ -1,5 +1,15 @@
|
||||
{ config, lib, pkgs, utils, ... }:
|
||||
# this builds a disk image which can be flashed onto a HDD, SD card, etc, and boot a working image.
|
||||
# debug the image by building one of:
|
||||
# - `nix build '.#imgs.$host' --builders "" -v`
|
||||
# - `nix build '.#imgs.$host.passthru.{bootFsImg,nixFsImg,withoutBootloader}'`
|
||||
# then loop-mounting it:
|
||||
# - `sudo losetup -Pf ./result/nixos.img`
|
||||
# - `mkdir /tmp/nixos.boot`
|
||||
# - `sudo mount /dev/loop0p1 /tmp/nixos.boot`, and look inside
|
||||
#
|
||||
# TODO: replace mobile-nixos parts with Disko <https://github.com/nix-community/disko>
|
||||
# or just inline them here.
|
||||
{ config, lib, pkgs, utils, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
@@ -62,6 +72,14 @@ in
|
||||
N.B.: setting this to something other than 512B is not well tested.
|
||||
'';
|
||||
};
|
||||
sane.image.installBootloader = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
command which takes the full disk image and installs hw-specific bootloader (u-boot, tow-boot, etc).
|
||||
for EFI-native systems (most x86_64), can be left empty.
|
||||
'';
|
||||
};
|
||||
};
|
||||
config = let
|
||||
# return true if super starts with sub
|
||||
@@ -90,19 +108,8 @@ in
|
||||
"ext4" = pkgs.imageBuilder.fileSystem.makeExt4;
|
||||
"btrfs" = pkgs.imageBuilder.fileSystem.makeBtrfs;
|
||||
};
|
||||
in
|
||||
lib.mkIf cfg.enable
|
||||
{
|
||||
system.build.img-without-firmware = with pkgs; pkgs.imageBuilder.diskImage.makeGPT {
|
||||
name = "nixos";
|
||||
diskID = vfatUuidFromFs bootFs;
|
||||
# leave some space for firmware
|
||||
# TODO: we'd prefer to turn this into a protected firmware partition, rather than reserving space in the GPT header itself
|
||||
# Tow-Boot manages to do that; not sure how.
|
||||
headerHole = cfg.extraGPTPadding;
|
||||
partitions = [
|
||||
(pkgs.imageBuilder.gap cfg.firstPartGap)
|
||||
(fsBuilderMapBoot."${bootFs.fsType}" {
|
||||
|
||||
bootFsImg = fsBuilderMapBoot."${bootFs.fsType}" {
|
||||
# fs properties
|
||||
name = "ESP";
|
||||
partitionID = vfatUuidFromFs bootFs;
|
||||
@@ -117,13 +124,13 @@ in
|
||||
extras = builtins.toString (builtins.map (d: "cp -R ${d}/* ./") cfg.extraBootFiles);
|
||||
in ''
|
||||
echo "running installBootLoader"
|
||||
${config.system.build.installBootLoader} ${config.system.build.toplevel} -d .
|
||||
${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d .
|
||||
echo "ran installBootLoader"
|
||||
${extras}
|
||||
echo "copied extraBootFiles"
|
||||
'';
|
||||
})
|
||||
(fsBuilderMapNix."${nixFs.fsType}" {
|
||||
};
|
||||
nixFsImg = fsBuilderMapNix."${nixFs.fsType}" {
|
||||
# fs properties
|
||||
name = "NIXOS_SYSTEM";
|
||||
partitionID = uuidFromFs nixFs;
|
||||
@@ -133,7 +140,7 @@ in
|
||||
# inherit (cfg) sectorSize; # imageBuilder only supports sectorSize for vfat. btrfs defaults to a 4096B sector size, somehow it abstracts over the drive's sector size?
|
||||
|
||||
populateCommands = let
|
||||
closureInfo = buildPackages.closureInfo { rootPaths = config.system.build.toplevel; };
|
||||
closureInfo = pkgs.buildPackages.closureInfo { rootPaths = config.system.build.toplevel; };
|
||||
extraRelPaths = builtins.toString (builtins.map (p: "./" + builtins.toString(relPath nixFs.mountPoint p)) cfg.extraDirectories);
|
||||
in ''
|
||||
mkdir -p ./${storeRelPath} ${extraRelPaths}
|
||||
@@ -145,9 +152,38 @@ in
|
||||
echo "Done copying system closure..."
|
||||
cp -v ${closureInfo}/registration ./nix-path-registration
|
||||
'';
|
||||
})
|
||||
];
|
||||
};
|
||||
system.build.img = lib.mkDefault config.system.build.img-without-firmware;
|
||||
img = (pkgs.imageBuilder.diskImage.makeGPT {
|
||||
name = "nixos";
|
||||
diskID = vfatUuidFromFs bootFs;
|
||||
# leave some space for firmware
|
||||
# TODO: we'd prefer to turn this into a protected firmware partition, rather than reserving space in the GPT header itself
|
||||
# Tow-Boot manages to do that; not sure how.
|
||||
headerHole = cfg.extraGPTPadding;
|
||||
partitions = [
|
||||
(pkgs.imageBuilder.gap cfg.firstPartGap)
|
||||
bootFsImg
|
||||
nixFsImg
|
||||
];
|
||||
}) // {
|
||||
passthru = {
|
||||
inherit bootFsImg nixFsImg;
|
||||
};
|
||||
};
|
||||
in
|
||||
lib.mkIf cfg.enable
|
||||
{
|
||||
system.build.img = (if cfg.installBootloader == null then
|
||||
img
|
||||
else pkgs.runCommand "nixos-with-bootloader" {} ''
|
||||
cp -vR ${img} $out
|
||||
chmod -R +w $out
|
||||
${cfg.installBootloader}
|
||||
'') // {
|
||||
passthru = {
|
||||
inherit bootFsImg nixFsImg;
|
||||
withoutBootloader = img;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -68,12 +68,7 @@ let
|
||||
fullRuntimePaths = lib.optionals (userName != null) (
|
||||
builtins.map
|
||||
(p: path-lib.concat [ xdgRuntimeDir p ])
|
||||
(
|
||||
sandbox.extraRuntimePaths
|
||||
++ lib.optionals sandbox.whitelistAudio [ "pipewire-0" "pipewire-0.lock" "pulse" ] # also pipewire-0-manager, unknown purpose
|
||||
++ lib.optionals (builtins.elem "user" sandbox.whitelistDbus) [ "bus" ]
|
||||
++ lib.optionals sandbox.whitelistWayland [ "wayland-1" "wayland-1.lock" ] # app can still communicate with wayland server w/o this, if it has net access
|
||||
)
|
||||
);
|
||||
allowedPaths = [
|
||||
"/nix/store"
|
||||
@@ -386,6 +381,13 @@ let
|
||||
allow the program full access to whichever directory it was launched from.
|
||||
'';
|
||||
};
|
||||
sandbox.whitelistS6 = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
allow the program to start/stop s6 services.
|
||||
'';
|
||||
};
|
||||
sandbox.whitelistWayland = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
@@ -479,15 +481,22 @@ let
|
||||
# this gets the symlink into the sandbox, but not the actual secret.
|
||||
fs = lib.mapAttrs (_homePath: _secretSrc: {}) config.secrets;
|
||||
|
||||
sandbox.net = lib.mkIf config.sandbox.whitelistX "localhost";
|
||||
|
||||
sandbox.extraPaths = lib.mkIf config.sandbox.whitelistDri [
|
||||
sandbox.extraPaths =
|
||||
lib.optionals config.sandbox.whitelistDri [
|
||||
# /dev/dri/renderD128: requested by wayland-egl (e.g. KOreader, animatch, geary)
|
||||
# - but everything seems to gracefully fallback to *something* (MESA software rendering?)
|
||||
# - CPU usage difference between playing videos in Gtk apps (e.g. fractal) with v.s. without DRI is 10% v.s. 90%.
|
||||
# - GPU attack surface is *large*: <https://security.stackexchange.com/questions/182501/modern-linux-gpu-driver-security>
|
||||
"/dev/dri" "/sys/dev/char" "/sys/devices" # (lappy: "/sys/devices/pci0000:00", moby needs something different)
|
||||
];
|
||||
]
|
||||
++ lib.optionals config.sandbox.whitelistX [ "/tmp/.X11-unix" ]
|
||||
;
|
||||
sandbox.extraRuntimePaths =
|
||||
lib.optionals config.sandbox.whitelistAudio [ "pipewire" "pulse" ] # this includes pipewire/pipewire-0-manager: is that ok?
|
||||
++ lib.optionals (builtins.elem "user" config.sandbox.whitelistDbus) [ "bus" ]
|
||||
++ lib.optionals config.sandbox.whitelistWayland [ "wayland" ] # app can still communicate with wayland server w/o this, if it has net access
|
||||
++ lib.optionals config.sandbox.whitelistS6 [ "s6" ] # TODO: this allows re-writing the services themselves: don't allow that!
|
||||
;
|
||||
sandbox.extraConfig = lib.mkIf config.sandbox.usePortal [
|
||||
"--sane-sandbox-portal"
|
||||
];
|
||||
|
@@ -25,7 +25,7 @@ let
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
substituteAll "$src" sane-sandboxed \
|
||||
--replace '@out@' "$out"
|
||||
--replace-fail '@out@' "$out"
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
|
@@ -1,12 +1,11 @@
|
||||
{ config, lib, options, sane-lib, ... }:
|
||||
{ config, lib, options, pkgs, sane-lib, ... }:
|
||||
|
||||
let
|
||||
sane-user-cfg = config.sane.user;
|
||||
cfg = config.sane.users;
|
||||
path-lib = sane-lib.path;
|
||||
serviceType = with lib; types.submodule {
|
||||
serviceType = with lib; types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
# these aoptions are mostly copied from systemd. could be improved.
|
||||
description = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
@@ -17,85 +16,77 @@ let
|
||||
references and links for where to find documentation about this service.
|
||||
'';
|
||||
};
|
||||
after = mkOption {
|
||||
depends = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
bindsTo = mkOption {
|
||||
dependencyOf = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
before = mkOption {
|
||||
partOf = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
wantedBy = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
wants = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
script = mkOption {
|
||||
type = types.nullOr types.lines;
|
||||
default = null;
|
||||
};
|
||||
|
||||
environment = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
description = ''
|
||||
environment variables to set within the service.
|
||||
"bundles" to which this service belongs.
|
||||
e.g. `partOf = [ "default" ];` describes services which should be started "by default".
|
||||
'';
|
||||
};
|
||||
|
||||
serviceConfig.Type = mkOption {
|
||||
type = types.enum [ "dbus" "oneshot" "simple" ];
|
||||
};
|
||||
serviceConfig.ExecStart = mkOption {
|
||||
command = mkOption {
|
||||
type = types.nullOr (types.coercedTo types.package toString types.str);
|
||||
default = null;
|
||||
description = ''
|
||||
long-running command which represents this service.
|
||||
when the command returns, the service is considered "failed", and restarted unless explicitly `down`d.
|
||||
'';
|
||||
};
|
||||
serviceConfig.ExecStartPre = mkOption {
|
||||
type = types.nullOr (types.coercedTo types.package toString types.str);
|
||||
default = null;
|
||||
};
|
||||
serviceConfig.ExecStartPost = mkOption {
|
||||
type = types.nullOr (types.coercedTo types.package toString types.str);
|
||||
default = null;
|
||||
};
|
||||
serviceConfig.ExecStopPost = mkOption {
|
||||
cleanupCommand = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
command which is run after the service has exited.
|
||||
restart of the service (if applicable) is blocked on this command.
|
||||
'';
|
||||
};
|
||||
serviceConfig.BusName = mkOption {
|
||||
readiness.waitCommand = mkOption {
|
||||
type = types.nullOr (types.coercedTo types.package toString types.str);
|
||||
default = null;
|
||||
description = ''
|
||||
command or path to executable which exits zero only when the service is ready.
|
||||
this may be invoked repeatedly (with delay),
|
||||
though it's not an error for it to block either (it may, though, be killed and restarted if it blocks too long)
|
||||
'';
|
||||
};
|
||||
readiness.waitDbus = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
name of the dbus name this service is expected to register.
|
||||
only once the name is registered will the service be considered "active".
|
||||
only once the name is registered will the service be considered "ready".
|
||||
'';
|
||||
};
|
||||
serviceConfig.RemainAfterExit = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
serviceConfig.Restart = mkOption {
|
||||
type = types.nullOr (types.enum [ "always" "on-failure" ]);
|
||||
default = null;
|
||||
# N.B.: systemd doesn't allow always/on-failure for Type="oneshot" services
|
||||
};
|
||||
serviceConfig.RestartSec = mkOption {
|
||||
type = types.str;
|
||||
default = "20s";
|
||||
};
|
||||
unitConfig.ConditionEnvironment = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
readiness.waitExists = mkOption {
|
||||
type = types.coercedTo types.str toList (types.listOf types.str);
|
||||
default = [];
|
||||
description = ''
|
||||
path to a directory or file whose existence signals the service's readiness.
|
||||
this is expanded as a shell expression, and may contain variables like `$HOME`, etc.
|
||||
'';
|
||||
};
|
||||
};
|
||||
config = {
|
||||
readiness.waitCommand = lib.mkMerge [
|
||||
(lib.mkIf (config.readiness.waitDbus != null)
|
||||
''${pkgs.systemdMinimal}/bin/busctl --user status "${config.readiness.waitDbus}" > /dev/null''
|
||||
)
|
||||
(lib.mkIf (config.readiness.waitExists != [])
|
||||
# e.g.: test -e /foo -a -e /bar
|
||||
("test -e " + (lib.concatStringsSep " -a -e " config.readiness.waitExists))
|
||||
)
|
||||
];
|
||||
};
|
||||
});
|
||||
userOptions = {
|
||||
options = with lib; {
|
||||
fs = mkOption {
|
||||
@@ -136,31 +127,14 @@ let
|
||||
};
|
||||
|
||||
services = mkOption {
|
||||
# see: <repo:nixos/nixpkgs:nixos/lib/utils.nix>
|
||||
# type = utils.systemdUtils.types.services;
|
||||
# `utils.systemdUtils.types.services` is nearly what we want, but remove `stage2ServiceConfig`,
|
||||
# as we don't want to force a PATH for every service.
|
||||
type = types.attrsOf serviceType;
|
||||
default = {};
|
||||
description = ''
|
||||
services to define for this user.
|
||||
populates files in ~/.config/systemd.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
defaultUserOptions = with lib; {
|
||||
options = userOptions.options // {
|
||||
services = mkOption {
|
||||
# type = utils.systemdUtils.types.services;
|
||||
# map to listOf attrs so that we can pass through
|
||||
# w/o worrying about merging at this layer
|
||||
type = types.attrsOf (types.coercedTo types.attrs (a: [ a ]) (types.listOf types.attrs));
|
||||
default = {};
|
||||
inherit (userOptions.options.services) description;
|
||||
};
|
||||
};
|
||||
};
|
||||
userModule = let
|
||||
nixConfig = config;
|
||||
in with lib; types.submodule ({ name, config, ... }: {
|
||||
@@ -217,6 +191,18 @@ let
|
||||
set +a
|
||||
done
|
||||
'';
|
||||
|
||||
services."default" = {
|
||||
description = "service (bundle) which is started by default upon login";
|
||||
};
|
||||
services."graphical-session" = {
|
||||
description = "service (bundle) which is started upon successful graphical login";
|
||||
# partOf = [ "default" ]; #< leave it to the DEs to set this
|
||||
};
|
||||
services."sound" = {
|
||||
description = "service (bundle) which represents functional sound input/output when active";
|
||||
partOf = [ "default" ];
|
||||
};
|
||||
}
|
||||
];
|
||||
});
|
||||
@@ -242,7 +228,6 @@ in
|
||||
{
|
||||
imports = [
|
||||
./s6-rc.nix
|
||||
./systemd.nix
|
||||
];
|
||||
|
||||
options = with lib; {
|
||||
@@ -258,7 +243,7 @@ in
|
||||
};
|
||||
|
||||
sane.user = mkOption {
|
||||
type = types.nullOr (types.submodule defaultUserOptions);
|
||||
type = types.nullOr (types.submodule userOptions);
|
||||
default = null;
|
||||
description = ''
|
||||
options to pass down to the default user
|
||||
|
@@ -1,61 +1,151 @@
|
||||
{ lib, pkgs, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
normalizeName = name: lib.removeSuffix ".service" (lib.removeSuffix ".target" name);
|
||||
config' = config;
|
||||
logBase = "$HOME/.local/share/s6/logs";
|
||||
maybe = cond: value: if cond then value else null;
|
||||
|
||||
# infers the service type from the arguments and dispatches appropriately
|
||||
genService = { name, run, depends }: if run != null then
|
||||
genService' (normalizeName name) "longrun" depends [
|
||||
(pkgs.writeTextFile {
|
||||
name = "s6-${name}-run";
|
||||
destination = "/${normalizeName name}/run";
|
||||
executable = true;
|
||||
# text = run;
|
||||
text = ''
|
||||
#!/bin/sh
|
||||
${run}
|
||||
'';
|
||||
})
|
||||
]
|
||||
else
|
||||
# TODO: a bundle can totally have dependencies. i can't just map them *all* to contents.
|
||||
# genService' (normalizeName name) "bundle" [] (
|
||||
# (builtins.map
|
||||
# (d: pkgs.writeTextFile {
|
||||
# name = "s6-${name}-contains-${d}";
|
||||
# destination = "/${normalizeName name}/contents.d/${normalizeName d}";
|
||||
# text = "";
|
||||
# })
|
||||
# depends
|
||||
# ) ++ [
|
||||
# # in case the bundle has no contents, ensure `contents.d` still gets made
|
||||
# (pkgs.runCommandLocal "s6-${name}-contains.d" {} ''
|
||||
# mkdir -p $out/"${normalizeName name}"/contents.d
|
||||
# '')
|
||||
# ]
|
||||
# )
|
||||
genService' (normalizeName name) "bundle" [] [
|
||||
(pkgs.writeTextFile {
|
||||
name = "s6-${name}-contents";
|
||||
destination = "/${normalizeName name}/contents";
|
||||
text = lib.concatStringsSep "\n" (builtins.map normalizeName depends);
|
||||
})
|
||||
]
|
||||
# create a derivation whose output is the on-disk representation of some attrset.
|
||||
# @path: /foo/bar/...
|
||||
# @obj: one of:
|
||||
# - { file = { text = "foo bar"; executable = true|false; } }
|
||||
# - { dir = <obj>; }
|
||||
fsItemToDerivation = path: obj: if obj ? dir then
|
||||
pkgs.symlinkJoin {
|
||||
name = "s6-${path}";
|
||||
paths = lib.mapAttrsToList
|
||||
(n: v: fsItemToDerivation "${path}/${n}" v)
|
||||
obj.dir
|
||||
;
|
||||
genService' = name: type: depends: others: pkgs.symlinkJoin {
|
||||
name = "s6-${name}";
|
||||
paths = others ++ [
|
||||
(pkgs.writeTextFile {
|
||||
name = "s6-${name}-type";
|
||||
destination = "/${name}/type";
|
||||
text = type;
|
||||
})
|
||||
] ++ builtins.map
|
||||
(d: pkgs.writeTextFile {
|
||||
name = "s6-${name}-depends-${d}";
|
||||
destination = "/${name}/dependencies.d/${normalizeName d}";
|
||||
text = "";
|
||||
})
|
||||
depends;
|
||||
}
|
||||
else if obj ? text then
|
||||
if obj.text != null then
|
||||
pkgs.writeTextFile {
|
||||
name = "s6-${path}";
|
||||
destination = path;
|
||||
text = obj.text;
|
||||
}
|
||||
else
|
||||
pkgs.emptyDirectory
|
||||
else if obj ? executable then
|
||||
if obj.executable != null then
|
||||
pkgs.writeTextFile {
|
||||
name = "s6-${path}";
|
||||
destination = path;
|
||||
executable = true;
|
||||
text = obj.executable;
|
||||
}
|
||||
else
|
||||
pkgs.emptyDirectory
|
||||
else if obj ? symlink then
|
||||
if obj.symlink != null then
|
||||
pkgs.runCommandLocal "s6-${path}" { } ''
|
||||
mkdir -p "$out/$(dirname "${path}")"
|
||||
ln -s "${obj.symlink}" "$out/${path}"
|
||||
''
|
||||
else
|
||||
pkgs.emptyDirectory
|
||||
else throw "don't know how to convert fs item at path ${path} to derivation: ${obj}";
|
||||
|
||||
# call with an AttrSet of fs items:
|
||||
# example:
|
||||
# ```
|
||||
# fsToDerivation {
|
||||
# usr.dir = {
|
||||
# normal.text = "i'm /usr/normal";
|
||||
# exec.executable = ''
|
||||
# #!/bin/sh
|
||||
# echo "i'm executable"
|
||||
# '';
|
||||
# lib.dir = { ... };
|
||||
# };
|
||||
# bin.dir = { ... };
|
||||
# }
|
||||
fsToDerivation = fs: fsItemToDerivation "/" { dir = fs; };
|
||||
|
||||
# infers the service type from the arguments and creates an attrset usable by `fsToDerivation`.
|
||||
# also configures the service for logging, if applicable.
|
||||
serviceToFs = { name, check, contents, depends, finish, run }: let
|
||||
type = if run != null then "longrun" else "bundle";
|
||||
logger = serviceToFs' {
|
||||
name = "logger:${name}";
|
||||
consumerFor = name;
|
||||
run = ''s6-log -- T p'${name}:' "${logBase}/${name}"'';
|
||||
type = "longrun";
|
||||
};
|
||||
in (serviceToFs' {
|
||||
inherit name check depends finish run type;
|
||||
contents = maybe (type == "bundle") contents;
|
||||
producerFor = maybe (type == "longrun") "logger:${name}";
|
||||
}) // (lib.optionalAttrs (type == "longrun") logger);
|
||||
|
||||
serviceToFs'= {
|
||||
name,
|
||||
type,
|
||||
check ? null, #< command to poll to check for service readiness
|
||||
consumerFor ? null,
|
||||
contents ? null, #< bundle contents
|
||||
depends ? [],
|
||||
finish ? null,
|
||||
producerFor ? null,
|
||||
run ? null,
|
||||
}: let
|
||||
maybe-notify = lib.optionalString (check != null) "s6-notifyoncheck -n 0 ";
|
||||
in {
|
||||
"${name}".dir = {
|
||||
"type".text = type;
|
||||
"contents".text = maybe (contents != null) (
|
||||
lib.concatStringsSep "\n" contents
|
||||
);
|
||||
"consumer-for".text = maybe (consumerFor != null) consumerFor;
|
||||
"data".dir = {
|
||||
"check".executable = maybe (check != null) ''
|
||||
#!/bin/sh
|
||||
${check}
|
||||
'';
|
||||
};
|
||||
# N.B.: if this service is a bundle, then dependencies.d is ignored
|
||||
"dependencies.d".dir = lib.genAttrs
|
||||
depends
|
||||
(dep: { text = ""; })
|
||||
;
|
||||
"finish".executable = maybe (finish != null) ''
|
||||
#!/bin/sh
|
||||
${finish}
|
||||
'';
|
||||
"notification-fd".text = maybe (check != null) "3";
|
||||
"producer-for".text = maybe (producerFor != null) producerFor;
|
||||
"run".executable = maybe (run != null) ''
|
||||
#!/bin/sh
|
||||
log() {
|
||||
printf 's6[%s]: %s\n' '${name}' "$1" | tee /dev/stderr
|
||||
}
|
||||
log "preparing"
|
||||
|
||||
# s6 is too gentle when i ask it to stop a service,
|
||||
# so explicitly kill children on exit.
|
||||
# see: <https://stackoverflow.com/a/2173421>
|
||||
# before changing this, test that the new version actually kills a service with `s6-rc down <svcname>`
|
||||
down() {
|
||||
log "trapped on abort signal"
|
||||
# "trap -": to avoid recursing
|
||||
trap - SIGINT SIGQUIT SIGTERM
|
||||
log "killing process group"
|
||||
# "kill 0" means kill the current process group (i.e. all descendants)
|
||||
kill 0
|
||||
exit 0
|
||||
}
|
||||
trap down SIGINT SIGQUIT SIGTERM
|
||||
|
||||
log "starting"
|
||||
# run the service from $HOME by default.
|
||||
# particularly, this impacts things like "what directory does my terminal open in".
|
||||
# N.B. do not run the notifier from $HOME, else it won't know where to find the `data/check` program.
|
||||
# N.B. must be run with `&` + `wait`, else we lose the ability to `trap`.
|
||||
${maybe-notify}env --chdir="$HOME" ${run} <&0 &
|
||||
wait
|
||||
log "exiting"
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# create a directory, containing N subdirectories:
|
||||
@@ -66,10 +156,11 @@ let
|
||||
# - type
|
||||
# - run
|
||||
# - ...
|
||||
genServices = svcs: pkgs.symlinkJoin {
|
||||
name = "s6-user-services";
|
||||
paths = builtins.map genService svcs;
|
||||
};
|
||||
genServices = svcs: fsToDerivation (lib.foldl'
|
||||
(acc: srv: acc // (serviceToFs srv))
|
||||
{}
|
||||
svcs
|
||||
);
|
||||
|
||||
# output is a directory containing:
|
||||
# - db
|
||||
@@ -97,41 +188,160 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
# to decrease sandbox escaping, i want to run s6-svscan on a read-only directory
|
||||
# so other programs can't edit the service scripts.
|
||||
# in practice, that means putting the servicedirs in /nix/store, and linking selective pieces of state
|
||||
# towards /run/user/{uid}/s6/live/..., the latter is shared with s6-rc.
|
||||
mkScanDir = livedir: compiled: pkgs.runCommandLocal "s6-scandir" { } ''
|
||||
cp -R "${compiled}/servicedirs" "$out"
|
||||
cd "$out"
|
||||
chmod u+w .
|
||||
for d in *; do
|
||||
chmod u+w "$d"
|
||||
ln -s "${livedir}/servicedirs/$d/down" "$d"
|
||||
ln -s "${livedir}/servicedirs/$d/event" "$d"
|
||||
ln -s "${livedir}/servicedirs/$d/supervise" "$d"
|
||||
done
|
||||
# these builtin s6-rc services try to create `s` (socket) and `s.lock` inside their directory, when launched.
|
||||
# they don't seem to follow symlinks, so patch in a full path.
|
||||
# note that other services will try to connect directly to this service's `./s` file, so keeping symlinks around as well is a good idea.
|
||||
substituteInPlace "s6rc-fdholder/run" \
|
||||
--replace-fail 's6-fdholder-daemon -1 -i data/rules -- s' 's6-fdholder-daemon -1 -i data/rules -- ${livedir}/servicedirs/s6rc-fdholder/s' \
|
||||
--replace-fail 's6-ipcclient -l0 -- s' 's6-ipcclient -l0 -- ${livedir}/servicedirs/s6rc-fdholder/s'
|
||||
substituteInPlace "s6rc-oneshot-runner/run" \
|
||||
--replace-fail 's6-ipcserver-socketbinder -- s' 's6-ipcserver-socketbinder -- ${livedir}/servicedirs/s6rc-oneshot-runner/s'
|
||||
ln -s "${livedir}/servicedirs/s6rc-fdholder/s" s6rc-fdholder/s
|
||||
ln -s "${livedir}/servicedirs/s6rc-fdholder/s.lock" s6rc-fdholder/s.lock
|
||||
ln -s "${livedir}/servicedirs/s6rc-oneshot-runner/s" s6rc-oneshot-runner/s
|
||||
ln -s "${livedir}/servicedirs/s6rc-oneshot-runner/s.lock" s6rc-oneshot-runner/s.lock
|
||||
|
||||
rm -rf .s6-svscan
|
||||
ln -s "${livedir}/servicedirs/.s6-svscan" .
|
||||
'';
|
||||
|
||||
# transform the `user.services` attrset into a s6 services list.
|
||||
s6SvcsFromConfigServices = services: lib.mapAttrsToList
|
||||
(name: service: {
|
||||
inherit name;
|
||||
run = service.serviceConfig.ExecStart;
|
||||
depends = service.wants ++ builtins.attrNames (
|
||||
lib.filterAttrs (_: cfg: lib.elem name cfg.wantedBy) services
|
||||
check = service.readiness.waitCommand;
|
||||
contents = builtins.attrNames (
|
||||
lib.filterAttrs (_: cfg: lib.elem name cfg.partOf) services
|
||||
);
|
||||
depends = service.depends ++ builtins.attrNames (
|
||||
lib.filterAttrs (_: cfg: lib.elem name cfg.dependencyOf) services
|
||||
);
|
||||
finish = service.cleanupCommand;
|
||||
run = service.command;
|
||||
})
|
||||
services
|
||||
;
|
||||
# return a list of bundles (AttrSets) which contain this service
|
||||
containedBy = services: name: lib.filter (svc: lib.elem name svc.contents) services;
|
||||
# for each bundle to which this service belongs, add that bundle's dependencies as direct dependencies of this service.
|
||||
# this is to overcome that s6 doesn't support bundles having dependencies.
|
||||
pushDownDependencies = services: builtins.map
|
||||
(svc: svc // {
|
||||
depends = lib.unique (
|
||||
svc.depends ++ lib.concatMap
|
||||
(bundle: bundle.depends)
|
||||
(containedBy services svc.name)
|
||||
);
|
||||
})
|
||||
services
|
||||
;
|
||||
|
||||
# in the systemd service management, these targets are implicitly defined and used
|
||||
# to accomplish something like run-levels, or service groups.
|
||||
# map them onto s6 "bundles". their contents are determined via reverse dependency mapping (`wantedBy` of every other service).
|
||||
implicitServices = {
|
||||
"default.target" = {
|
||||
serviceConfig.ExecStart = null;
|
||||
wants = [];
|
||||
wantedBy = [];
|
||||
};
|
||||
"graphical-session.target" = {
|
||||
serviceConfig.ExecStart = null;
|
||||
wants = [];
|
||||
wantedBy = [];
|
||||
};
|
||||
};
|
||||
# create a template s6 "live" dir, which can be copied at runtime in /run/user/{uid}/s6/live.
|
||||
# this is like a minimal versio of `s6-rc-init`, but tightly coupled to my setup
|
||||
# wherein the scandir is external and selectively links back to the livedir
|
||||
mkLiveDir = compiled: pkgs.runCommandLocal "s6-livedir" {} ''
|
||||
mkdir $out
|
||||
touch $out/lock
|
||||
touch $out/prefix # no prefix: empty
|
||||
|
||||
mkdir $out/compiled
|
||||
cp ${compiled}/{db,lock,n,resolve.cdb} $out/compiled
|
||||
|
||||
touch $out/state
|
||||
mkdir $out/servicedirs
|
||||
mkdir $out/servicedirs/.s6-svscan
|
||||
for svc in $(ls ${compiled}/servicedirs); do
|
||||
# s6-rc-init gives each service one byte of state, initialized to zero. i mimic that here:
|
||||
echo -n '\x00' >> $out/state
|
||||
mkdir $out/servicedirs/$svc
|
||||
# don't auto-start the service when i add the supervisor
|
||||
touch $out/servicedirs/$svc/down
|
||||
# s6-rc needs to know if the service supports readiness notifications,
|
||||
# as this will determine if it waits for it when starting.
|
||||
ln -s ${compiled}/servicedirs/$svc/notification-fd $out/servicedirs/$svc/notification-fd
|
||||
done
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.sane.users = with lib; mkOption {
|
||||
type = types.attrsOf (types.submodule ({ config, ...}: let
|
||||
sources = genServices (s6SvcsFromConfigServices (implicitServices // config.services));
|
||||
type = types.attrsOf (types.submodule ({ config, name, ...}: let
|
||||
sources = genServices (
|
||||
lib.converge pushDownDependencies (
|
||||
s6SvcsFromConfigServices config.services
|
||||
)
|
||||
);
|
||||
compiled = compileServices sources;
|
||||
uid = config'.users.users."${name}".uid;
|
||||
scanDir = mkScanDir "/run/user/${builtins.toString uid}/s6/live" compiled;
|
||||
liveDir = mkLiveDir compiled;
|
||||
in {
|
||||
fs.".config/s6/live".symlink.target = liveDir;
|
||||
fs.".config/s6/scandir".symlink.target = scanDir;
|
||||
fs.".config/s6/compiled".symlink.target = compiled;
|
||||
# exposed only for convenience
|
||||
fs.".config/s6/sources".symlink.target = sources;
|
||||
fs.".config/s6/compiled".symlink.target = compileServices sources;
|
||||
|
||||
fs.".profile".symlink.text = ''
|
||||
function initS6Dirs() {
|
||||
local LIVE="$XDG_RUNTIME_DIR/s6/live"
|
||||
|
||||
mkdir -p "$LIVE" # create parent dirs
|
||||
rm -rf "$LIVE"/* # remove old state
|
||||
# the live dir needs to be read+write. initialize it via the template in ~/.config/s6:
|
||||
cp -R "$HOME/.config/s6/live"/* "$LIVE"
|
||||
chmod -R 0700 "$LIVE"
|
||||
|
||||
# ensure the log dir, since that'll be needed by every service.
|
||||
# the log dir should already exist by now (nixos persistence); create it just in case something went wrong.
|
||||
mkdir -p "${logBase}"
|
||||
}
|
||||
function startS6() {
|
||||
local S6_TARGET="''${1-default}"
|
||||
|
||||
local LIVE="$XDG_RUNTIME_DIR/s6/live"
|
||||
|
||||
test -e "$LIVE" || initS6Dirs
|
||||
|
||||
s6-svscan "$(realpath "$HOME/.config/s6/scandir")" &
|
||||
local SVSCAN=$!
|
||||
|
||||
# wait for `supervise` processes to appear.
|
||||
# this part would normally be done by `s6-rc-init`.
|
||||
# maybe there's a more clever way: don't kill `s6-svscan` above, somehow run it on the /nix/store/... path from the very beginning.
|
||||
for d in $LIVE/servicedirs/*; do
|
||||
while ! s6-svok "$d" ; do
|
||||
sleep 0.1
|
||||
done
|
||||
done
|
||||
|
||||
if [ -n "$S6_TARGET" ]; then
|
||||
s6-rc -b -l "$LIVE" start "$S6_TARGET"
|
||||
fi
|
||||
|
||||
echo 's6 initialized: Ctrl+C to stop'
|
||||
wait "$SVSCAN"
|
||||
}
|
||||
function startS6WithLogging() {
|
||||
mkdir -p "${logBase}" #< needs to exist for parallel s6-log call
|
||||
startS6 2>&1 | s6-log -- T "${logBase}/catchall"
|
||||
}
|
||||
|
||||
primarySessionCommands+=('startS6WithLogging &')
|
||||
'';
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
@@ -1,69 +0,0 @@
|
||||
{ lib, utils, ... }:
|
||||
let
|
||||
# see: <repo:nixos/nixpkgs:nixos/lib/utils.nix>
|
||||
# see: <repo:nix-community/home-manager:modules/systemd.nix>
|
||||
mkUnit = serviceName: value: utils.systemdUtils.lib.serviceToUnit serviceName {
|
||||
inherit (value)
|
||||
script
|
||||
wantedBy
|
||||
;
|
||||
serviceConfig = lib.filterAttrs (_: v: v != null) value.serviceConfig;
|
||||
unitConfig = {
|
||||
inherit (value.unitConfig)
|
||||
ConditionEnvironment
|
||||
;
|
||||
After = value.after;
|
||||
Before = value.before;
|
||||
BindsTo = value.bindsTo;
|
||||
Description = value.description;
|
||||
Documentation = value.documentation;
|
||||
Wants = value.wants;
|
||||
};
|
||||
environment = {
|
||||
# clear PATH to allow inheriting it from environment.
|
||||
# otherwise, nixos would force it to `systemd.globalEnvironment.PATH`, which is mostly tools like sed/find/etc.
|
||||
# clearing PATH here allows user services to inherit whatever PATH the graphical session sets
|
||||
# (see `dbus-update-activation-environment` call in ~/.config/sway/config),
|
||||
# which is critical to making it so user services can see user *programs*/packages.
|
||||
#
|
||||
# note that systemd provides no way to *append* to the PATH, only to override it (or not).
|
||||
# nor do they intend to ever support that:
|
||||
# - <https://github.com/systemd/systemd/issues/1082>
|
||||
PATH = null;
|
||||
} // (value.environment or {});
|
||||
};
|
||||
in
|
||||
{
|
||||
# create fs entries for every service, in the systemd user dir.
|
||||
options.sane.users = with lib; mkOption {
|
||||
type = types.attrsOf (types.submodule ({ config, ...}: {
|
||||
fs = lib.concatMapAttrs
|
||||
(serviceName: value: let
|
||||
cleanName = utils.systemdUtils.lib.mkPathSafeName serviceName;
|
||||
generatedUnit = mkUnit serviceName value;
|
||||
#^ generatedUnit contains keys:
|
||||
# - text
|
||||
# - aliases (IGNORED)
|
||||
# - wantedBy
|
||||
# - requiredBy
|
||||
# - enabled (IGNORED)
|
||||
# - overrideStrategy (IGNORED)
|
||||
# TODO: error if one of the above ignored fields are set
|
||||
symlinkData = {
|
||||
text = generatedUnit.text;
|
||||
targetName = "${cleanName}.service"; # systemd derives unit name from symlink target
|
||||
};
|
||||
serviceEntry = {
|
||||
".config/systemd/user/${serviceName}.service".symlink = symlinkData;
|
||||
};
|
||||
wants = builtins.map (wantedBy: {
|
||||
".config/systemd/user/${wantedBy}.wants/${serviceName}.service".symlink = symlinkData;
|
||||
}) generatedUnit.wantedBy;
|
||||
in
|
||||
lib.mergeAttrsList ([ serviceEntry ] ++ wants)
|
||||
)
|
||||
config.services
|
||||
;
|
||||
}));
|
||||
};
|
||||
}
|
@@ -32,6 +32,14 @@ in [
|
||||
# etc, where "date" is like "20240228181608"
|
||||
# and can be found with `nix-repl > :lf . > lastModifiedDate`
|
||||
|
||||
(fetchpatch' {
|
||||
prUrl = "https://github.com/NixOS/nixpkgs/pull/298001";
|
||||
saneCommit = "d599839060400762a67d2c01d15b102ffe75e703";
|
||||
title = "gnupg: fix cross compilation";
|
||||
hash = "sha256-d3kD2/UyMzzdBkiEdWtCibbWiPWBZLUWRry1TMkS25g=";
|
||||
merged.staging = "20240326000000";
|
||||
})
|
||||
|
||||
(fetchpatch' {
|
||||
prUrl = "https://github.com/NixOS/nixpkgs/pull/292868";
|
||||
saneCommit = "f090c1cd6bb5bbb14a86ec90ced78ca1a165a4fe";
|
||||
@@ -40,37 +48,12 @@ in [
|
||||
hash = "sha256-AzkMYm9Pm85Xfm+nd44oKIULZYGyMXulmjFbutouysc=";
|
||||
})
|
||||
|
||||
(fetchpatch' {
|
||||
prUrl = "https://github.com/NixOS/nixpkgs/pull/288518";
|
||||
saneCommit = "20c9492d303be7cbad560e3d83bc47ab4b1e93f7";
|
||||
title = "procmail: support cross compilation";
|
||||
# hash = "sha256-cC9GBF5tCeJ2GDSjMjlG4hYStIJPEoRBAK9/KhJiiIo=";
|
||||
hash = "sha256-MzAwT1Ss3ltlljton+8atyz6PomSr1u1TuHXkigAovo=";
|
||||
merged.staging = "20240312120000";
|
||||
merged.staging-next = "20240312120000";
|
||||
})
|
||||
|
||||
(fetchpatch' {
|
||||
prUrl = "https://github.com/NixOS/nixpkgs/pull/292415";
|
||||
title = "sway/hyprland: cross compilation fixes";
|
||||
hash = "sha256-3BGZK5Plx1IJzKHv19RwcRWQ4S+cbUbsPbEzpxFEYsI=";
|
||||
hash = "sha256-IDf8OcZzFgw0DalxzBqbqP7TZVnZkzoRHQ51RlR1xWc=";
|
||||
})
|
||||
|
||||
# (fetchpatch' {
|
||||
# title = "nixos/slskd: allow omitting username from yaml config";
|
||||
# saneCommit = "541c37e8689b6422ea07be1395f1a63357bb0c63";
|
||||
# hash = "sha256-xQEj/oIfNcE4td9jxzDzhlnIYpncOOdXZuswkmcLNuk=";
|
||||
# })
|
||||
# (fetchpatch' {
|
||||
# title = "nixos/slskd: don't enable nginx unless nginx.enable was set";
|
||||
# saneCommit = "ea084e5739a68436cc240aeca5c10b92de1e3138";
|
||||
# hash = "sha256-25zB5eM1WBVEigmrq1mY9GXwEkS/jf5v7BCfmN6Wux4=";
|
||||
# })
|
||||
(fetchpatch' {
|
||||
title = "nixos/slskd: option fixes";
|
||||
prUrl = "https://github.com/NixOS/nixpkgs/pull/270646";
|
||||
hash = "sha256-5brmmPfYp7G+5Dr5q2skWSwkrEwsRAe/UetoN0AqGjY=";
|
||||
})
|
||||
(fetchpatch' {
|
||||
title = "gcr: remove build gnupg from runtime closure";
|
||||
prUrl = "https://github.com/NixOS/nixpkgs/pull/263158";
|
||||
|
@@ -29,7 +29,7 @@ buildGoModule rec {
|
||||
meta = with lib; {
|
||||
description = "blast your linux audio to DLNA receivers";
|
||||
# license = licenses.mit; # MIT + NoAI
|
||||
homepage = src.homepage;
|
||||
inherit (src.meta) homepage;
|
||||
maintainers = with maintainers; [ colinsane ];
|
||||
platforms = platforms.unix;
|
||||
};
|
||||
|
16807
pkgs/additional/coppwr/Cargo.nix
Normal file
16807
pkgs/additional/coppwr/Cargo.nix
Normal file
File diff suppressed because one or more lines are too long
7
pkgs/additional/coppwr/crate-hashes.json
Normal file
7
pkgs/additional/coppwr/crate-hashes.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"egui_node_graph 0.4.0 (git+https://github.com/dimtpap/egui_node_graph.git?rev=a2e93a2826f90c21f13fa8fecf9076da611432fd#a2e93a2826f90c21f13fa8fecf9076da611432fd)": "1nnxm7h0ai0mipisi553ah9vsg0zjl4wsxcfrjlv10g5i7ldlya2",
|
||||
"libspa 0.8.0 (git+https://gitlab.freedesktop.org/dimtpap/pipewire-rs.git?rev=605d15996f3258b3e1cc34e445dfbdf16a366c7e#605d15996f3258b3e1cc34e445dfbdf16a366c7e)": "1x7294f8i5sq18dv13scjgs8k4c843q38imxcdcwrf4gvcpb1jaz",
|
||||
"libspa-sys 0.8.0 (git+https://gitlab.freedesktop.org/dimtpap/pipewire-rs.git?rev=605d15996f3258b3e1cc34e445dfbdf16a366c7e#605d15996f3258b3e1cc34e445dfbdf16a366c7e)": "1x7294f8i5sq18dv13scjgs8k4c843q38imxcdcwrf4gvcpb1jaz",
|
||||
"pipewire 0.8.0 (git+https://gitlab.freedesktop.org/dimtpap/pipewire-rs.git?rev=605d15996f3258b3e1cc34e445dfbdf16a366c7e#605d15996f3258b3e1cc34e445dfbdf16a366c7e)": "1x7294f8i5sq18dv13scjgs8k4c843q38imxcdcwrf4gvcpb1jaz",
|
||||
"pipewire-sys 0.8.0 (git+https://gitlab.freedesktop.org/dimtpap/pipewire-rs.git?rev=605d15996f3258b3e1cc34e445dfbdf16a366c7e#605d15996f3258b3e1cc34e445dfbdf16a366c7e)": "1x7294f8i5sq18dv13scjgs8k4c843q38imxcdcwrf4gvcpb1jaz"
|
||||
}
|
13
pkgs/additional/coppwr/default.nix
Normal file
13
pkgs/additional/coppwr/default.nix
Normal file
@@ -0,0 +1,13 @@
|
||||
# Cargo.nix and crate-hashes.json were created with:
|
||||
# - `nix run '.#crate2nix' -- generate -f ~/ref/repos/dimtpap/coppwr/Cargo.toml`
|
||||
# to update:
|
||||
# - `git fetch` in `~/ref/repos/dimtpap/coppwr`
|
||||
# - re-run that crate2nix step
|
||||
{ pkgs
|
||||
, defaultCrateOverrides
|
||||
}:
|
||||
let
|
||||
cargoNix = import ./Cargo.nix {
|
||||
inherit defaultCrateOverrides pkgs;
|
||||
};
|
||||
in cargoNix.rootCrate.build
|
@@ -22,7 +22,7 @@
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "delfin";
|
||||
version = "0.4.1";
|
||||
version = "0.4.2";
|
||||
|
||||
src = if devBuild then fetchFromGitea {
|
||||
domain = "git.uninsane.org";
|
||||
@@ -35,13 +35,13 @@ stdenv.mkDerivation rec {
|
||||
owner = "avery42";
|
||||
repo = "delfin";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-LBdHWEGz6dujcF3clrJbViohgiBTyWR7Y70totimVJ8=";
|
||||
hash = "sha256-7GHwwwFibmwBcrlC2zSpEUZ2dca14wZFU6PJWjincPQ=";
|
||||
};
|
||||
|
||||
cargoDeps = rustPlatform.fetchCargoTarball {
|
||||
inherit src;
|
||||
name = "${pname}-${version}";
|
||||
hash = "sha256-TaUYqq4rkMBXhIM+0ZH6O0F+SUOpT1ImgLx2HCzJPrM=";
|
||||
hash = "sha256-zlecw6230AC/+y537iEhJU+BgWRs2WCFP0AIcxchZBA=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
|
@@ -48,10 +48,10 @@ in stdenv.mkDerivation {
|
||||
#
|
||||
# alternative would be to patchShebangs in the node_modules dir.
|
||||
substituteInPlace src/Makefile \
|
||||
--replace "yarn install" "true" \
|
||||
--replace ' $(PRETTIER)' ' node $(PRETTIER)' \
|
||||
--replace ' $(LESSC)' ' node $(LESSC)' \
|
||||
--replace ' $(BROWSERIFY)' ' node $(BROWSERIFY)'
|
||||
--replace-fail "yarn install" "true" \
|
||||
--replace-fail ' $(PRETTIER)' ' node $(PRETTIER)' \
|
||||
--replace-fail ' $(LESSC)' ' node $(LESSC)' \
|
||||
--replace-fail ' $(BROWSERIFY)' ' node $(BROWSERIFY)'
|
||||
'';
|
||||
|
||||
preBuild = ''
|
||||
|
@@ -7,12 +7,12 @@
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "bypass-paywalls-clean";
|
||||
version = "3.5.9.0";
|
||||
version = "3.6.0.0";
|
||||
src = fetchFromGitLab {
|
||||
owner = "magnolia1234";
|
||||
repo = "bypass-paywalls-firefox-clean";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-hdD+uUoU2nN5ZFB9vR1xojqGRFfQIdXbFcmtT78bUQ8=";
|
||||
hash = "sha256-oP9ha4PXBF/EalmA8tt/PHJb/KgCi9mXvvyvtHDmTGM=";
|
||||
};
|
||||
|
||||
patches = [
|
||||
@@ -26,8 +26,8 @@ stdenv.mkDerivation rec {
|
||||
# which avoids breaking manual updates
|
||||
postPatch = ''
|
||||
substituteAllInPlace background.js \
|
||||
--replace 'ext_api.runtime.openOptionsPage()' 'true' \
|
||||
--replace ' fetch(' ' false && fetch('
|
||||
--replace-fail 'ext_api.runtime.openOptionsPage()' 'true' \
|
||||
--replace-fail ' fetch(' ' false && fetch('
|
||||
'';
|
||||
|
||||
nativeBuildInputs = [ zip ];
|
||||
|
@@ -122,8 +122,8 @@ in (lib.makeScope newScope (self: with self; {
|
||||
extid = "webextension@metamask.io";
|
||||
pname = "ether-metamask";
|
||||
url = "https://github.com/MetaMask/metamask-extension/releases/download/v${version}/metamask-firefox-${version}.zip";
|
||||
version = "11.11.4";
|
||||
hash = "sha256-vFpZ8l1xlxoT9iCvKnS+4b4wXplovaIySEazPJaYAnc=";
|
||||
version = "11.12.4";
|
||||
hash = "sha256-70elcOd4U+MEfg7IM0FfTBcWoKCKec63wLVdd3emW8M=";
|
||||
};
|
||||
fx_cast = fetchVersionedAddon rec {
|
||||
extid = "fx_cast@matt.tf";
|
||||
@@ -152,8 +152,8 @@ in (lib.makeScope newScope (self: with self; {
|
||||
extid = "sponsorBlocker@ajay.app";
|
||||
pname = "sponsorblock";
|
||||
url = "https://github.com/ajayyy/SponsorBlock/releases/download/${version}/FirefoxSignedInstaller.xpi";
|
||||
version = "5.5.8";
|
||||
hash = "sha256-2amym6QQS4bueZTGlV8Oo8b1e1vANcGwES/ZoGH8s9U=";
|
||||
version = "5.5.9";
|
||||
hash = "sha256-lyBrszbjdfMOWZbYwE6DjNtM8wq0Vv1eCcobBKNelWw=";
|
||||
};
|
||||
ublacklist = fetchVersionedAddon rec {
|
||||
extid = "@ublacklist";
|
||||
@@ -168,8 +168,8 @@ in (lib.makeScope newScope (self: with self; {
|
||||
# N.B.: a handful of versions are released unsigned
|
||||
# url = "https://github.com/gorhill/uBlock/releases/download/${version}/uBlock0_${version}.signed.xpi";
|
||||
url = "https://github.com/gorhill/uBlock/releases/download/${version}/uBlock0_${version}.firefox.signed.xpi";
|
||||
version = "1.56.1b14";
|
||||
hash = "sha256-IpPCMMwXxnGEyVThccEYsMFNyFRMe2lyV3igsKNQhiA=";
|
||||
version = "1.56.1rc2";
|
||||
hash = "sha256-B8YHcl24fYS6dqZHS6xgKx4G1j+ts3DBpySYNA1kcYc=";
|
||||
};
|
||||
};
|
||||
})).overrideScope (self: super:
|
||||
@@ -185,6 +185,7 @@ in (lib.makeScope newScope (self: with self; {
|
||||
# (if you have one, feel free to share your nix package)
|
||||
#
|
||||
# NB: in source this is `alreadyInstalled: false`, but the build process hates Booleans or something
|
||||
# TODO(2024/03/23): this is broken (replacement doesn't match). but maybe not necessary anymore?
|
||||
substituteInPlace js/*.js \
|
||||
--replace 'alreadyInstalled:!1' 'alreadyInstalled:!0'
|
||||
'';
|
||||
|
@@ -106,9 +106,9 @@ let
|
||||
# patch so meson will invoke our `crate2nix_cmd.sh` instead of cargo
|
||||
postPatch = ''
|
||||
substituteInPlace src/meson.build \
|
||||
--replace 'cargo_options,' "" \
|
||||
--replace "cargo, 'build'," "'bash', 'crate2nix_cmd.sh'," \
|
||||
--replace "'src' / rust_target" "'target/bin'"
|
||||
--replace-fail 'cargo_options,' "" \
|
||||
--replace-fail "cargo, 'build'," "'bash', 'crate2nix_cmd.sh'," \
|
||||
--replace-fail "'src' / rust_target" "'target/bin'"
|
||||
'';
|
||||
postConfigure = ''
|
||||
# copied from <pkgs/development/tools/build-managers/meson/setup-hook.sh>
|
||||
@@ -318,7 +318,7 @@ let
|
||||
# just update this patch to reflect the right-hand side
|
||||
# CARGO_MANIFEST_LINKS = "ring_core_0_17_7";
|
||||
postPatch = (attrs.postPatch or "") + ''
|
||||
substituteInPlace build.rs --replace \
|
||||
substituteInPlace build.rs --replace-fail \
|
||||
'links = std::env::var("CARGO_MANIFEST_LINKS").unwrap();' 'links = "ring_core_0_17_7".to_string();'
|
||||
'';
|
||||
};
|
||||
|
@@ -1,3 +1,11 @@
|
||||
# to update:
|
||||
# - first, figure the rev for `koreader-base`:
|
||||
# - inside `koreader` repo:
|
||||
# - `git submodule status base`
|
||||
# - or `git log base`
|
||||
# - inside `koreader-base` repo:
|
||||
# - `git diff old-rev..new-rev thirdparty`
|
||||
#
|
||||
# koreader's native build process
|
||||
# 1. git clone each dependency lib into base/thirdparty/$lib
|
||||
# 2. git checkout a specific rev into base/thirdparty/$lib/build/$platform
|
||||
@@ -15,39 +23,25 @@
|
||||
#
|
||||
# TODO:
|
||||
# - don't vendor fonts
|
||||
# - package enough of KOReader's deps to remove `sources.nix`
|
||||
# - SDL2 (only used by macos??)
|
||||
# - FBINK
|
||||
# - NANOSVG (slightly complicated; koreader needs access to its source code)
|
||||
# - SRELL
|
||||
# - build crengine dep via nixpkgs `coolreader` pkg (with source patched to <https://github.com/koreader/crengine>)?
|
||||
{ lib
|
||||
, autoPatchelfHook
|
||||
, autoconf
|
||||
, automake
|
||||
, buildPackages
|
||||
, callPackage
|
||||
, cmake
|
||||
, dpkg
|
||||
, fetchFromGitHub
|
||||
, fetchgit
|
||||
, fetchFromGitLab
|
||||
, fetchurl
|
||||
, gettext
|
||||
, git
|
||||
, libtool
|
||||
, luajit
|
||||
, makeWrapper
|
||||
, perl
|
||||
, pkg-config
|
||||
, pkgs
|
||||
, python3
|
||||
, ragel
|
||||
, stdenv
|
||||
, substituteAll
|
||||
, which
|
||||
, symlinkJoin
|
||||
|
||||
# third-party dependencies which KOReader would ordinarily vendor
|
||||
, symlinkJoin
|
||||
, curl
|
||||
, czmq
|
||||
, djvulibre
|
||||
@@ -64,6 +58,7 @@
|
||||
, libpng
|
||||
, libunibreak
|
||||
, libwebp
|
||||
, nanosvg
|
||||
, openssl
|
||||
, openssh
|
||||
, sdcv
|
||||
@@ -76,7 +71,6 @@
|
||||
, zsync
|
||||
}:
|
||||
let
|
||||
sources = callPackage ./sources.nix { luajit = luajit52; };
|
||||
version = "2024.03";
|
||||
src = fetchFromGitHub {
|
||||
owner = "koreader";
|
||||
@@ -86,6 +80,115 @@ let
|
||||
rev = "v${version}";
|
||||
hash = "sha256-/51pOGSAoaS0gOKlqNKruwaKY5qylzCpeNUrWyzYTpA=";
|
||||
};
|
||||
|
||||
fbink-src-ko = fetchFromGitHub {
|
||||
owner = "NiLuJe";
|
||||
repo = "FBInk";
|
||||
name = "fbink"; # where to unpack this in `srcs`
|
||||
rev = "1a989b30a195ca240a3cf37f9de61b4b3c7e891c";
|
||||
hash = "sha256-lXjAX0BoHW3L1E54d5J+wiAlAZXVmj9Y1Un8yaCwO8w=";
|
||||
};
|
||||
|
||||
kobo-usbms-src-ko = fetchFromGitHub {
|
||||
owner = "koreader";
|
||||
repo = "KoboUSBMS";
|
||||
name = "kobo-usbms"; # where to unpack this in `srcs`
|
||||
rev = "v1.3.9";
|
||||
hash = "sha256-91B0FUnmpE6TP4Lg5mj6z/U1DZQTKiPhG3ccCSgY4mQ=";
|
||||
};
|
||||
|
||||
leptonica-src-ko = fetchFromGitHub {
|
||||
# k2pdf needs leptonica src, because it actually patches it and builds it itself:
|
||||
# - `cp -f $(LEPTONICA_MOD)/dewarp2.c $(LEPTONICA_DIR)/src/dewarp2.c`
|
||||
# - i.e. cp -f /build/koreader/base/thirdparty/libk2pdfopt/build/aarch64-unknown-linux-gnu/libk2pdfopt-prefix/src/libk2pdfopt/leptonica_mod/dewarp2.c ...
|
||||
# k2pdf uses an old leptonica -- like 2015-2017-ish (1.74.1).
|
||||
# seems it can be at least partially updated, by replacing `numaGetMedianVariation` with `numaGetMedianDevFromMedian` (drop-in replacement)
|
||||
# and replacing references to `liblept.so` with `libleptonica.so`,
|
||||
# but eventually this requires patching the tesseract Makefiles. could get intense, idk.
|
||||
owner = "DanBloomberg";
|
||||
repo = "leptonica";
|
||||
name = "leptonica"; # where to unpack this in `srcs`
|
||||
rev = "1.74.1";
|
||||
hash = "sha256-SDXKam768xvZZvTbXe3sssvZyeLEEiY97Vrzx8hoc6g=";
|
||||
};
|
||||
|
||||
libk2pdfopt-src-ko = fetchFromGitHub {
|
||||
owner = "koreader";
|
||||
repo = "libk2pdfopt";
|
||||
name = "libk2pdfopt"; # where to unpack this in `srcs`
|
||||
rev = "09f1e011a618c8ec06b4caa67079682119d2aaa7";
|
||||
hash = "sha256-37sZ46dG6Z1Wk7NrhKAKl5j9r1bN6g01cd5Iyt/2coM=";
|
||||
};
|
||||
|
||||
lodepng-src-ko = fetchFromGitHub {
|
||||
owner = "lvandeve";
|
||||
repo = "lodepng";
|
||||
name = "lodepng"; # where to unpack this in `srcs`
|
||||
rev = "d398e0f10d152a5d17fa30463474dc9f56523f9c";
|
||||
hash = "sha256-ApOHUgU6X1rHwyjAHA/0Nt+buDFqY2ttXEnEvdrRl3A=";
|
||||
};
|
||||
|
||||
lunasvg-src-ko = fetchFromGitHub {
|
||||
owner = "sammycage";
|
||||
repo = "lunasvg";
|
||||
name = "lunasvg"; # where to unpack this in `srcs`
|
||||
rev = "59d6f6ba835c1b7c7a0f9d4ea540ec3981777885";
|
||||
hash = "sha256-gW2ikakS6Omz5upmy26nAo/jkGHYO2kjlB3UmKJBh1k=";
|
||||
};
|
||||
|
||||
minizip-src-ko = fetchFromGitHub {
|
||||
# this is actually just a very old version (2015) of `minizip-ng`
|
||||
owner = "nmoinvaz";
|
||||
repo = "minizip";
|
||||
name = "minizip"; # where to unpack this in `srcs`
|
||||
rev = "0b46a2b4ca317b80bc53594688883f7188ac4d08";
|
||||
hash = "sha256-P/3MMMGYDqD9NmkYvw/thKpUNa3wNOSlBBjANHSonAg=";
|
||||
};
|
||||
|
||||
mupdf-src-ko = fetchFromGitHub {
|
||||
owner = "ArtifexSoftware";
|
||||
repo = "mupdf";
|
||||
name = "mupdf"; # where to unpack this in `srcs`
|
||||
fetchSubmodules = true; # specifically for jbig2dec, mujs, openjpeg
|
||||
rev = "1.13.0";
|
||||
hash = "sha256-pQejRon9fO9A1mhz3oLjBr1j4HveDLcQIWjR1/Rpy5Q=";
|
||||
};
|
||||
|
||||
nanosvg-headers-ko = symlinkJoin {
|
||||
# koreader's heavily-patched mupdf is dependent on a koreader-specific `stb_image_write` extension to nanosvg.
|
||||
# nanosvg is used as a header-only library, so just patch that extension straight into the src.
|
||||
name = "nanosvg-headers-ko";
|
||||
paths = [
|
||||
"${nanosvg.src}/src"
|
||||
"${src}/base/thirdparty/nanosvg"
|
||||
];
|
||||
};
|
||||
|
||||
popen-noshell-src-ko = fetchFromGitHub {
|
||||
owner = "famzah";
|
||||
repo = "popen-noshell";
|
||||
name = "popen-noshell";
|
||||
rev = "e715396a4951ee91c40a98d2824a130f158268bb";
|
||||
hash = "sha256-JeBZMsg6ZUGSnyZ4eds4w63gM/L73EsAnLaHOPpL6iM=";
|
||||
};
|
||||
|
||||
tesseract-src-ko = fetchFromGitHub {
|
||||
# TODO: try using nixpkgs' tesseract.src (i doubt it will work)
|
||||
owner = "tesseract-ocr";
|
||||
repo = "tesseract";
|
||||
name = "tesseract";
|
||||
rev = "60176fc5ae5e7f6bdef60c926a4b5ea03de2bfa7";
|
||||
hash = "sha256-FQvlrJ+Uy7+wtUxBuS5NdoToUwNRhYw2ju8Ya8MLyQw=";
|
||||
};
|
||||
|
||||
turbo-src-ko = fetchFromGitHub {
|
||||
owner = "kernelsauce";
|
||||
repo = "turbo";
|
||||
name = "turbo";
|
||||
rev = "v2.1.3";
|
||||
hash = "sha256-vBRkFdc5a0FIt15HBz3TnqMZ+GGsqjEefnfJEpuVTBs=";
|
||||
};
|
||||
|
||||
# XXX: for some inscrutable reason, `enable52Compat` is *partially* broken, only when cross compiling.
|
||||
# `table.unpack` is non-nil, but `table.pack` is nil.
|
||||
# the normal path is for `enable52Compat` to set `env.NIX_CFLAGS_COMPILE = "-DLUAJIT_ENABLE_LUA52COMPAT";`
|
||||
@@ -97,13 +200,16 @@ let
|
||||
"${src}/base/thirdparty/luajit/koreader-luajit-enable-table_pack.patch"
|
||||
];
|
||||
});
|
||||
luaEnv = luajit52.withPackages (ps: with ps; [
|
||||
(buildLuarocksPackage {
|
||||
|
||||
overlayedLuaPkgs = luaPkgs: let
|
||||
ps = with ps; {
|
||||
luajson = buildLuarocksPackage rec {
|
||||
# needed by KOReader's lua-Spore
|
||||
pname = "luajson";
|
||||
version = "1.3.4-1";
|
||||
src = fetchgit {
|
||||
url = "https://github.com/harningt/luajson.git";
|
||||
src = fetchFromGitHub {
|
||||
owner = "harningt";
|
||||
repo = "luajson";
|
||||
# rev = "1.3.4";
|
||||
# 1.3.4 (released 2017) has some incompatible bugs with lpeg library.
|
||||
# see: <https://github.com/harningt/luajson/commit/6ecaf9bea8b121a9ffca5a470a2080298557b55d>
|
||||
@@ -111,20 +217,57 @@ let
|
||||
hash = "sha256-56G0NqIpavKHMQWUxy+Bp7G4ZKrQwUZ2C5e7GJxUJeg=";
|
||||
};
|
||||
knownRockspec = (fetchurl {
|
||||
url = "mirror://luarocks/luajson-1.3.4-1.rockspec";
|
||||
url = "mirror://luarocks/${pname}-${version}.rockspec";
|
||||
hash = "sha256-+S4gfa6QaOMmOCDX8TxBq3kFWlbaEeiSMxCfefYakv0=";
|
||||
}).outPath;
|
||||
propagatedBuildInputs = [ lpeg ];
|
||||
})
|
||||
};
|
||||
htmlparser = buildLuarocksPackage rec {
|
||||
pname = "htmlparser"; #< name of the rockspec, not the repo
|
||||
version = "0.3.9-1";
|
||||
src = fetchFromGitHub {
|
||||
owner = "msva";
|
||||
repo = "lua-htmlparser";
|
||||
# the rockspec was added to the repo *after* v0.3.9 was tagged
|
||||
rev = "5ce9a775a345cf458c0388d7288e246bb1b82bff";
|
||||
hash = "sha256-aSTLSfqz/MIDFVRwtBlDNBUhPb7KqOl32/Y62Hdec1s=";
|
||||
};
|
||||
knownRockspec = "${src}/rockspecs/${pname}-${version}.rockspec";
|
||||
};
|
||||
lua-spore = buildLuarocksPackage rec {
|
||||
pname = "lua-spore"; #< name of the rockspec, not the repo
|
||||
version = "0.3.3-1";
|
||||
src = fetchFromGitLab {
|
||||
domain = "framagit.org";
|
||||
owner = "fperrad";
|
||||
repo = "lua-Spore";
|
||||
rev = "0.3.3";
|
||||
hash = "sha256-wb7ykJsndoq0DazHpfXieUcBBptowYqD/eTTN/EK/6g=";
|
||||
};
|
||||
knownRockspec = "${src}/rockspec/${pname}-${version}.rockspec";
|
||||
propagatedBuildInputs = [
|
||||
luajson
|
||||
luasocket
|
||||
];
|
||||
};
|
||||
} // luaPkgs;
|
||||
in ps;
|
||||
|
||||
luaEnv = luajit52.withPackages (ps: with (overlayedLuaPkgs ps); [
|
||||
luajson
|
||||
htmlparser
|
||||
lua-spore
|
||||
lpeg
|
||||
luasec
|
||||
luasocket
|
||||
rapidjson
|
||||
]);
|
||||
|
||||
rockspecFor = luaPkgName: let
|
||||
pkg = luaEnv.pkgs."${luaPkgName}";
|
||||
pkg = (overlayedLuaPkgs luaEnv.pkgs)."${luaPkgName}";
|
||||
in
|
||||
"${luaEnv}/${pkg.rocksSubdir}/${luaPkgName}/${pkg.rockspecVersion}/${luaPkgName}-${pkg.rockspecVersion}.rockspec";
|
||||
|
||||
crossTargets = {
|
||||
# koreader-base Makefile targets to use when compiling for the given host platform
|
||||
# only used when cross compiling
|
||||
@@ -135,24 +278,6 @@ let
|
||||
else
|
||||
crossTargets."${stdenv.hostPlatform.parsed.cpu.name}";
|
||||
|
||||
fakeBuildDep = buildPackages.writeShellScript "fake-build-ko-dep" ''
|
||||
set -x
|
||||
lib="$1"
|
||||
build_dir="$2"
|
||||
prebuilt="$3"
|
||||
mkdir -p "$build_dir/$lib-prefix/src"
|
||||
rm -rf "$build_dir/$lib-prefix/src/$lib"
|
||||
rm -rf "$build_dir/$lib-prefix/src/$lib-build"
|
||||
# the library build directory koreader uses isn't consistently named, but we can cover most cases ($lib or $lib-build).
|
||||
# we have to copy the full tree rather than just symlink because koreader/base/Makefile.third
|
||||
# is copying lib/*.so into include/.
|
||||
# seriously, wtf are they doing over there.
|
||||
cp -R "$prebuilt" "$build_dir/$lib-prefix/src/$lib"
|
||||
cp -R "$prebuilt" "$build_dir/$lib-prefix/src/$lib-build"
|
||||
# ln -s "$prebuilt" "$build_dir/$lib-prefix/src/$lib"
|
||||
# ln -s "$prebuilt" "$build_dir/$lib-prefix/src/$lib-build"
|
||||
'';
|
||||
|
||||
getContrib = pkg: stdenv.mkDerivation {
|
||||
inherit (pkg) name src;
|
||||
dontConfigure = true;
|
||||
@@ -194,7 +319,6 @@ let
|
||||
|
||||
# values to provide to koreader/base/Makefile.defs.
|
||||
# should be ok to put this in `makeFlags` array, but i can't get that to work!
|
||||
# LUAROCKS_BINARY substitution is to support the cross-compilation case (i.e. emulate it during the build process)
|
||||
makefileDefs = ''
|
||||
CURL_LIB="${lib.getLib curl}/lib/libcurl.so" \
|
||||
CURL_DIR="${lib.getDev curl}" \
|
||||
@@ -203,6 +327,7 @@ let
|
||||
DJVULIBRE_LIB="${lib.getLib djvulibre}/lib/libdjvulibre.so" \
|
||||
DJVULIBRE_LIB_LINK_FLAG="-L ${lib.getLib djvulibre}/lib -l:libdjvulibre.so" \
|
||||
DJVULIBRE_DIR="${djvulibreAll}" \
|
||||
FBINK_DIR="$NIX_BUILD_TOP/fbink" \
|
||||
FREETYPE_LIB="${lib.getLib freetype}/lib/libfreetype.so" \
|
||||
FREETYPE_LIB_LINK_FLAG="-L ${lib.getLib freetype}/lib -l:libfreetype.so" \
|
||||
FREETYPE_DIR="${lib.getDev freetype}" \
|
||||
@@ -222,6 +347,9 @@ let
|
||||
JPEG_LIB_LINK_FLAG="-L ${lib.getLib libjpeg_turbo}/lib -l:libjpeg.so" \
|
||||
JPEG_DIR="${lib.getDev libjpeg_turbo}" \
|
||||
TURBOJPEG_LIB="${lib.getLib libjpeg_turbo}/lib/libturbojpeg.so" \
|
||||
K2PDFOPT_DIR="$NIX_BUILD_TOP/libk2pdfopt" \
|
||||
KOBO_USBMS_DIR="$NIX_BUILD_TOP/kobo-usbms" \
|
||||
LEPTONICA_DIR="$NIX_BUILD_TOP/leptonica" \
|
||||
LIBICONV="${lib.getLib libiconvReal}/lib/libiconv.so" \
|
||||
LIBICONV_DIR="${lib.getDev libiconvReal}" \
|
||||
LIBUNIBREAK_LIB="${lib.getLib libunibreak}/lib/libunibreak.so" \
|
||||
@@ -231,24 +359,34 @@ let
|
||||
LIBWEBPDEMUX_LIB="${lib.getLib libwebp}/lib/libwebpdemux.so" \
|
||||
LIBWEBPSHARPYUV_LIB="${lib.getLib libwebp}/lib/libwebpsharpyuv.so" \
|
||||
LIBWEBP_DIR="${lib.getDev libwebp}" \
|
||||
LODEPNG_DIR="$NIX_BUILD_TOP/lodepng" \
|
||||
LPEG_ROCK="${rockspecFor "lpeg"}" \
|
||||
LUAROCKS_BINARY="${lib.optionalString (!stdenv.buildPlatform.canExecute stdenv.hostPlatform) (stdenv.hostPlatform.emulator buildPackages)} ${luajit52}/bin/lua ${luaEnv.pkgs.luarocks}/bin/.luarocks-wrapped" \
|
||||
LUNASVG_DIR="$NIX_BUILD_TOP/lunasvg" \
|
||||
LUAJIT="${luaEnv}/bin/luajit" \
|
||||
LUAJIT_JIT="${luaEnv}/share/lua/5.1/jit" \
|
||||
LUAJIT_LIB="${lib.getLib luaEnv}/lib/libluajit-5.1.so" \
|
||||
LUASEC="${luaEnv}/share/lua/5.1/ssl/" \
|
||||
LUASOCKET="${luaEnv}/share/lua/5.1/socket/" \
|
||||
LUA_HTMLPARSER_ROCK="${rockspecFor "htmlparser"}" \
|
||||
LUA_INCDIR="${lib.getDev luaEnv}/include" \
|
||||
LUA_LIBDIR="${lib.getLib luaEnv}/lib/libluajit-5.1.so" \
|
||||
LUA_RAPIDJSON_ROCK="${rockspecFor "rapidjson"}" \
|
||||
LUA_SPORE_ROCK="${rockspecFor "lua-spore"}" \
|
||||
MINIZIP_DIR="$NIX_BUILD_TOP/minizip" \
|
||||
MUPDF_DIR="$NIX_BUILD_TOP/mupdf" \
|
||||
NANOSVG_HEADERS="${nanosvg-headers-ko}" \
|
||||
NANOSVG_INCLUDE_DIR="${nanosvg-headers-ko}" \
|
||||
OPENSSL_LIB="${lib.getLib openssl}/lib/libssl.so" \
|
||||
OPENSSL_DIR="${opensslAll}" \
|
||||
SSL_LIB="${lib.getLib openssl}/lib/libssl.so.3" \
|
||||
CRYPTO_LIB="${lib.getLib openssl}/lib/libcrypto.so" \
|
||||
PNG_LIB="${lib.getLib libpng}/lib/libpng.so" \
|
||||
PNG_DIR="${libAndDev libpng}" \
|
||||
POPEN_NOSHELL_DIR="$NIX_BUILD_TOP/popen-noshell" \
|
||||
SQLITE_LIB="${lib.getLib sqlite}/lib/libsqlite3.so" \
|
||||
SQLITE_DIR="${lib.getDev sqlite}" \
|
||||
TESSERACT_DIR="$NIX_BUILD_TOP/tesseract" \
|
||||
TURBO_DIR="$NIX_BUILD_TOP/turbo" \
|
||||
UTF8PROC_LIB="${lib.getLib utf8proc}/lib/libutf8proc.so" \
|
||||
UTF8PROC_DIR="${utf8procAll}" \
|
||||
ZLIB="${lib.getLib zlib}/lib/libz.so" \
|
||||
@@ -308,30 +446,30 @@ in
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "koreader-from-src";
|
||||
inherit version;
|
||||
srcs = [ src ] ++ (lib.mapAttrsToList
|
||||
(name: src: fetchgit (
|
||||
{
|
||||
inherit name;
|
||||
} // src.source // {
|
||||
# koreader sometimes specifies the rev as `tags/FOO`.
|
||||
# we need to remember that to place the repo where it expects, but we have to strip it here for fetchgit to succeed.
|
||||
rev = lib.removePrefix "tags/" src.source.rev;
|
||||
}
|
||||
))
|
||||
sources.thirdparty
|
||||
);
|
||||
srcs = [
|
||||
src
|
||||
fbink-src-ko
|
||||
kobo-usbms-src-ko
|
||||
leptonica-src-ko
|
||||
libk2pdfopt-src-ko
|
||||
lodepng-src-ko
|
||||
lunasvg-src-ko
|
||||
minizip-src-ko
|
||||
mupdf-src-ko
|
||||
popen-noshell-src-ko
|
||||
tesseract-src-ko
|
||||
turbo-src-ko
|
||||
];
|
||||
|
||||
patches = [
|
||||
./debug.patch #< not needed to build, just helps debug packaging issues
|
||||
./no_rm_build_dirs.patch
|
||||
./lua-Spore-no-luajson.patch #< TODO: test this at runtime! we ship luajson, but just don't expose it via luarocks
|
||||
# ./debug.patch #< not needed to build, just helps debug packaging issues
|
||||
./rss-no-interrupt-on-image-failure.patch # just a preference
|
||||
];
|
||||
|
||||
sourceRoot = "koreader";
|
||||
|
||||
nativeBuildInputs = [
|
||||
buildPackages.stdenv.cc # TODO: move to depsBuildBuild?
|
||||
buildPackages.stdenv.cc
|
||||
autoconf # autotools is used by some thirdparty libraries
|
||||
automake
|
||||
autoPatchelfHook # used by us, in fixupPhase, to ensure substituted thirdparty deps can be loaded at runtime
|
||||
@@ -340,17 +478,15 @@ stdenv.mkDerivation rec {
|
||||
libtool
|
||||
makeWrapper
|
||||
pkg-config
|
||||
luaEnv.pkgs.luarocks
|
||||
];
|
||||
buildInputs = [
|
||||
# luajson
|
||||
luaEnv
|
||||
luaEnv #< specifically for lua.h
|
||||
];
|
||||
|
||||
postPatch = ''
|
||||
# patch for newer openssl
|
||||
substituteInPlace --fail base/ffi/crypto.lua \
|
||||
--replace 'ffi.load("libs/libcrypto.so.1.1")' 'ffi.load("libcrypto.so")'
|
||||
substituteInPlace base/ffi/crypto.lua \
|
||||
--replace-fail 'ffi.load("libs/libcrypto.so.1.1")' 'ffi.load("libcrypto.so")'
|
||||
|
||||
# dlopen libraries by name only, allowing them to be found via LD_LIBRARY_PATH
|
||||
# instead of just via $out/libs. this is required whenever we direct KOreader to use system libs instead of its vendored libs.
|
||||
@@ -359,6 +495,18 @@ stdenv.mkDerivation rec {
|
||||
--replace-quiet 'ffi.load("libs/' 'ffi.load("'
|
||||
done
|
||||
|
||||
# don't force-rebuild third-party components, else we can't replace them with our own
|
||||
substituteInPlace base/Makefile.third \
|
||||
--replace-fail ' -rm ' ' # -rm'
|
||||
|
||||
# make some sources writable, particularly so koreader can apply its patches (by default only the `sourceRoot` is writable)
|
||||
chmod -R u+w "$NIX_BUILD_TOP"/{fbink,kobo-usbms,leptonica,libk2pdfopt,lodepng,lunasvg,minizip,mupdf,popen-noshell,tesseract,turbo}
|
||||
# koreader builds these deps itself: we mock out the download stage, and it does the rest
|
||||
for dep in fbink kobo-usbms libk2pdfopt lodepng lunasvg minizip mupdf popen-noshell turbo; do
|
||||
# sed -i 's/DOWNLOAD_COMMAND .*/DOWNLOAD_COMMAND ""/' "base/thirdparty/$dep/CMakeLists.txt"
|
||||
sed -i "s:DOWNLOAD_COMMAND .*:DOWNLOAD_COMMAND rm -fd $dep $dep-build \\&\\& ln -s $NIX_BUILD_TOP/$dep $dep \\&\\& ln -s $NIX_BUILD_TOP/$dep $dep-build :" "base/thirdparty/$dep/CMakeLists.txt"
|
||||
done
|
||||
|
||||
# lots of places in Makefile.third (incorrectly) assume lib paths are relative to CURDIR,
|
||||
# so link /nix into CURDIR to allow them to work anyway
|
||||
ln -s /nix base/nix
|
||||
@@ -366,79 +514,12 @@ stdenv.mkDerivation rec {
|
||||
|
||||
dontConfigure = true;
|
||||
buildPhase = ''
|
||||
link_lib_into_build_dir() {
|
||||
lib="$1"
|
||||
rev="$2"
|
||||
platform="$3"
|
||||
prebuilt="$4"
|
||||
|
||||
lib_src="../$lib"
|
||||
cmake_lists="base/thirdparty/$lib/CMakeLists.txt"
|
||||
build_dir="base/thirdparty/$lib/build/$platform"
|
||||
|
||||
# link the nix clone into the directory koreader would use for checkout
|
||||
# ref="base/thirdparty/$l/build/git_checkout"
|
||||
# echo "linking thirdparty library $l $ref -> $deref"
|
||||
# mkdir -p "$ref"
|
||||
# ln -s "$deref" "$ref/$l"
|
||||
# mv "$deref" "$ref/$l"
|
||||
# cp -R "$deref" "$ref/$l"
|
||||
# needs to be writable for koreader to checkout it specific revision
|
||||
# chmod u+w -R "$ref/$l/.git"
|
||||
|
||||
# koreader wants to clone each library into this git_checkout dir,
|
||||
# then checkout a specific revision,
|
||||
# and then copy that checkout into the build/working directory further down.
|
||||
# instead, we replicate that effect here, and by creating these "stamp" files
|
||||
# koreader will know to skip the `git clone` and `git checkout` calls.
|
||||
# the logic we're spoofing lives in koreader/base/thirdparty/cmake_modules/koreader_thirdparty_git.cmake
|
||||
stamp_dir="$build_dir/git_checkout/stamp"
|
||||
stamp_info="$stamp_dir/$lib-gitinfo-$rev.txt"
|
||||
stamp_clone="$stamp_dir/$lib-gitclone-lastrun.txt"
|
||||
echo "creating stamps for $lib: $stamp_clone > $stamp_info"
|
||||
# mkdir $(dirname ..) to handle the case where `$rev` contains slashes
|
||||
mkdir -p $(dirname "$stamp_info")
|
||||
# koreader-base decides whether to redo the git checkout based on a timestamp compare of these two stamp files
|
||||
touch -d "last week" $(dirname "$stamp_info") #< XXX: necessary?
|
||||
touch -d "last week" "$stamp_info"
|
||||
touch -d "next week" "$stamp_clone"
|
||||
|
||||
# koreader would copy the checkout into this build/working directory,
|
||||
# but because we spoof the stamps to work around other git errors,
|
||||
# copy it there on koreader's behalf
|
||||
prefix="$build_dir/$lib-prefix"
|
||||
mkdir -p "$prefix/src"
|
||||
cp -R "$lib_src" "$prefix/src/$lib"
|
||||
# src dir needs to be writable for koreader to apply its own patches
|
||||
chmod u+w -R "$prefix/src/$lib"
|
||||
|
||||
if [ -n "$prebuilt" ]; then
|
||||
abs_build_dir="$(realpath "$build_dir")"
|
||||
sed -i 's/INSTALL_COMMAND .*/INSTALL_COMMAND ""/' "$cmake_lists"
|
||||
sed -i \
|
||||
"s:BUILD_COMMAND .*:BUILD_COMMAND ${fakeBuildDep} $lib $abs_build_dir $prebuilt:" \
|
||||
"$cmake_lists"
|
||||
fi
|
||||
}
|
||||
|
||||
${builtins.concatStringsSep "\n" (lib.mapAttrsToList
|
||||
(name: src:
|
||||
let
|
||||
# for machine-agnostic libraries (e.g. pure lua), koreader doesn't build them in a flavored directory
|
||||
machine = if src.machineAgnostic or false then "" else stdenv.hostPlatform.config;
|
||||
in
|
||||
''link_lib_into_build_dir "${name}" "${src.source.rev}" "${machine}" "${src.package or ""}"''
|
||||
)
|
||||
sources.thirdparty
|
||||
)}
|
||||
|
||||
# outDir should match OUTPUT_DIR in koreader-base
|
||||
outDir="/build/koreader/base/build/${stdenv.hostPlatform.config}"
|
||||
outDir="$NIX_BUILD_TOP/koreader/base/build/${stdenv.hostPlatform.config}"
|
||||
mkdir -p "$outDir"
|
||||
${symlinkThirdpartyBins "$outDir"}
|
||||
|
||||
make ${makeFlags}
|
||||
|
||||
'';
|
||||
|
||||
env = lib.optionalAttrs (stdenv.buildPlatform != stdenv.hostPlatform) {
|
||||
@@ -476,7 +557,12 @@ stdenv.mkDerivation rec {
|
||||
|
||||
passthru = {
|
||||
# exposed for debugging
|
||||
inherit luajit52 luaEnv;
|
||||
inherit luajit52 luaEnv mupdf-src-ko nanosvg-headers-ko rockspecFor;
|
||||
inherit (overlayedLuaPkgs luaEnv.pkgs)
|
||||
luajson
|
||||
htmlparser
|
||||
lua-spore
|
||||
;
|
||||
};
|
||||
|
||||
meta = with lib; {
|
||||
|
@@ -1,12 +0,0 @@
|
||||
diff --git a/base/thirdparty/lua-Spore/CMakeLists.txt b/base/thirdparty/lua-Spore/CMakeLists.txt
|
||||
index 15593193..31140257 100644
|
||||
--- a/base/thirdparty/lua-Spore/CMakeLists.txt
|
||||
+++ b/base/thirdparty/lua-Spore/CMakeLists.txt
|
||||
@@ -26,6 +26,7 @@ list(APPEND BUILD_CMD COMMAND ${ROCKS_CMD} make --tree=${OUTPUT_DIR}/rocks ${LUA_SPORE_ROCKSPEC})
|
||||
list(APPEND BUILD_CMD "LUA_INCDIR=${LUA_INCDIR}" "LUA_LIBDIR=${LUA_LIBDIR}")
|
||||
|
||||
list(APPEND PATCH_CMD COMMAND mkdir -p doc)
|
||||
+list(APPEND PATCH_CMD COMMAND ${ISED} "s| 'luajson|--&|g" ${LUA_SPORE_ROCKSPEC})
|
||||
list(APPEND PATCH_CMD COMMAND ${ISED} "s| 'luasocket|--&|g" ${LUA_SPORE_ROCKSPEC})
|
||||
|
||||
ko_write_gitclone_script(
|
@@ -1,20 +0,0 @@
|
||||
diff --git a/base/Makefile.third b/base/Makefile.third
|
||||
index f6a80523..1080ab98 100644
|
||||
--- a/base/Makefile.third
|
||||
+++ b/base/Makefile.third
|
||||
@@ -138,7 +138,6 @@ $(MUPDF_LIB): $(JPEG_LIB) \
|
||||
$(LIBWEBP_LIB) \
|
||||
$(ZLIB) $(AES_LIB) \
|
||||
$(THIRDPARTY_DIR)/mupdf/*.*
|
||||
- -rm -rf $(MUPDF_BUILD_DIR)
|
||||
install -d $(MUPDF_BUILD_DIR)
|
||||
cd $(MUPDF_BUILD_DIR) && \
|
||||
$(CMAKE) $(CMAKE_FLAGS) \
|
||||
@@ -665,7 +664,6 @@ $(OUTPUT_DIR)/data/KoboUSBMS.tar.gz: $(THIRDPARTY_DIR)/kobo-usbms/*.*
|
||||
# ===========================================================================
|
||||
# common lua library for networking
|
||||
$(LUASOCKET): $(THIRDPARTY_DIR)/luasocket/*.*
|
||||
- -rm -rf $(LUASOCKET) $(LUASOCKET_BUILD_DIR)
|
||||
install -d $(LUASOCKET_BUILD_DIR)
|
||||
cd $(LUASOCKET_BUILD_DIR) && \
|
||||
$(CMAKE) $(CMAKE_FLAGS) \
|
@@ -1,307 +0,0 @@
|
||||
# to update:
|
||||
# - first, figure the rev for `koreader-base`:
|
||||
# - inside `koreader` repo:
|
||||
# - `git submodule status base`
|
||||
# - or `git log base`
|
||||
# - inside `koreader-base` repo:
|
||||
# - `git diff old-rev..new-rev thirdparty`
|
||||
# - update `source.rev` everywhere here that changed upstream
|
||||
# - zero the hashes here and correct them based on build errors
|
||||
# - tweak ./vendor-external-projects.patch until it applies
|
||||
# - usually just upstream changed a URL or something minor
|
||||
#
|
||||
# a full rebuild takes approximately 10 minutes on a mid-range desktop
|
||||
#
|
||||
# the following build output may look like an error, but is safe to ignore:
|
||||
# - "awk: fatal: cannot open file `3.9' for reading: No such file or directory"
|
||||
# - this number comes from the luarocks version
|
||||
#
|
||||
# how to automate koreader updates?
|
||||
# - it may be that koreader-base is more strongly decoupled from `koreader` than first appears:
|
||||
# - most `koreader` commits which update base simply bump its rev and nothing more.
|
||||
# - then, `koreader-base` could be its own package, updated independently from the main koreader.
|
||||
{ lib
|
||||
, symlinkJoin
|
||||
, curl
|
||||
, czmq
|
||||
, djvulibre
|
||||
, freetype
|
||||
, fribidi
|
||||
, giflib
|
||||
, glib
|
||||
, harfbuzz
|
||||
, k2pdfopt
|
||||
, leptonica
|
||||
, libjpeg_turbo
|
||||
, libpng
|
||||
, libunibreak
|
||||
, libwebp
|
||||
, luajit
|
||||
, minizip
|
||||
, mupdf
|
||||
, mupdf_1_17
|
||||
, nanosvg
|
||||
, openssh
|
||||
, openssl_1_1
|
||||
, sdcv
|
||||
, tesseract
|
||||
, turbo
|
||||
, utf8proc
|
||||
, zeromq4
|
||||
, zstd
|
||||
, zsync
|
||||
}:
|
||||
let
|
||||
libAndDev = pkg: symlinkJoin {
|
||||
inherit (pkg) name;
|
||||
paths = [
|
||||
(lib.getLib pkg)
|
||||
(lib.getDev pkg)
|
||||
];
|
||||
};
|
||||
in
|
||||
{
|
||||
thirdparty = {
|
||||
# providing `package` is just a way to optimize builds, by getting KOReader to use the built nixpkg instead of building it itself from source.
|
||||
# if it fails during an update, it should always be safe to delete the package key.
|
||||
# curl = {
|
||||
# source.url = "https://github.com/curl/curl.git";
|
||||
# source.rev = "tags/curl-7_80_0";
|
||||
# source.hash = "sha256-kzozc0Io+1f4UMivSV2IhzJDQXmad4wNhXN/Y2Lsg3Q=";
|
||||
# package = curl;
|
||||
# };
|
||||
# czmq = {
|
||||
# source.url = "https://github.com/zeromq/czmq.git";
|
||||
# source.rev = "2a0ddbc4b2dde623220d7f4980ddd60e910cfa78";
|
||||
# source.hash = "sha256-p4Cl2PLVgRQ0S4qr3VClJXjvAd2LUBU9oRUvOCfVnyw=";
|
||||
# # package = czmq; # koreader wants v1, nixpkgs has v4
|
||||
# };
|
||||
# djvulibre = {
|
||||
# source.url = "https://gitlab.com/koreader/djvulibre.git";
|
||||
# source.rev = "6a1e5ba1c9ef81c205a4b270c3f121a1e106f4fc";
|
||||
# source.hash = "sha256-OWSbxdr93FH3ed0D+NSFWIah7VDTcL3LIGOciY+f4dk=";
|
||||
# # package = djvulibre; # "cp -fL /build/koreader/base/thirdparty/djvulibre/build/aarch64-unknown-linux-gnu/djvulibre-prefix/src/djvulibre/libdjvu/.libs/libdjvulibre.so.21 ..."
|
||||
# };
|
||||
fbink = {
|
||||
source.url = "https://github.com/NiLuJe/FBInk.git";
|
||||
source.rev = "1a989b30a195ca240a3cf37f9de61b4b3c7e891c";
|
||||
source.hash = "sha256-O3bZzvuj/BRVV+UoutaaZZgGZws2J/i5ArfBHbz6omI=";
|
||||
# package: not packaged for nix
|
||||
};
|
||||
# freetype2 = {
|
||||
# source.url = "https://gitlab.com/koreader/freetype2.git";
|
||||
# source.rev = "VER-2-13-2";
|
||||
# source.hash = "sha256-yylSmVM3D5xnbFx9qEEHFIP/K0x/WDXZr0MA4C7ng7k=";
|
||||
# package = libAndDev freetype;
|
||||
# };
|
||||
# fribidi = {
|
||||
# source.url = "https://github.com/fribidi/fribidi.git";
|
||||
# source.rev = "tags/v1.0.12";
|
||||
# source.hash = "sha256-L4m/F9rs8fiv9rSf8oy7P6cthhupc6R/lCv30PLiQ4M=";
|
||||
# package = libAndDev fribidi;
|
||||
# };
|
||||
# giflib = {
|
||||
# source.url = "https://gitlab.com/koreader/giflib.git";
|
||||
# source.rev = "5.1.4";
|
||||
# source.hash = "sha256-znbY4tliXHXVLBd8sTKrbglOdCUb7xhcCQsDDWcQfhw=";
|
||||
# package = giflib;
|
||||
# };
|
||||
# glib = {
|
||||
# source.url = "https://github.com/GNOME/glib.git";
|
||||
# source.rev = "2.58.3";
|
||||
# source.hash = "sha256-KmJXCJ6h2QhPyK1axk+Y9+yJzO0wnCczcogopxGShJc=";
|
||||
# # package = libAndDev glib; # breaks sdcv build
|
||||
# };
|
||||
# harfbuzz = {
|
||||
# source.url = "https://github.com/harfbuzz/harfbuzz.git";
|
||||
# source.rev = "8.3.0";
|
||||
# source.hash = "sha256-sO0Kd2wAbMm+Auf7tXsDNal7hqND8iwkb0M/9WWt9sI=";
|
||||
# # package = harfbuzz;
|
||||
# package = libAndDev harfbuzz;
|
||||
# };
|
||||
kobo-usbms = {
|
||||
source.url = "https://github.com/koreader/KoboUSBMS.git";
|
||||
source.rev = "v1.3.9";
|
||||
source.hash = "sha256-/yYpagekWlfTrXu/1DNTmBmdd3IkCDjRtslRv13mtCg=";
|
||||
# package: not in nixpkgs
|
||||
};
|
||||
leptonica = {
|
||||
source.url = "https://github.com/DanBloomberg/leptonica.git";
|
||||
source.rev = "1.74.1";
|
||||
source.hash = "sha256-SDXKam768xvZZvTbXe3sssvZyeLEEiY97Vrzx8hoc6g=";
|
||||
# package = leptonica; # k2pdf needs leptonica src. # cp -f /build/koreader/base/thirdparty/libk2pdfopt/build/aarch64-unknown-linux-gnu/libk2pdfopt-prefix/src/libk2pdfopt/leptonica_mod/dewarp2.c
|
||||
};
|
||||
# libjpeg-turbo = {
|
||||
# source.url = "https://github.com/libjpeg-turbo/libjpeg-turbo.git";
|
||||
# source.rev = "3.0.1";
|
||||
# source.hash = "sha256-ofdecix4m0FA9gdyQh7zYn99SYBbH2+a7jfoZlsadoA=";
|
||||
# # package = libAndDev libjpeg_turbo;
|
||||
# };
|
||||
libk2pdfopt = {
|
||||
source.url = "https://github.com/koreader/libk2pdfopt.git";
|
||||
source.rev = "09f1e011a618c8ec06b4caa67079682119d2aaa7";
|
||||
source.hash = "sha256-37sZ46dG6Z1Wk7NrhKAKl5j9r1bN6g01cd5Iyt/2coM=";
|
||||
# package = k2pdfopt; # nixpkgs k2pdfopt does not compile (broken deps). also, uses old insecure mupdf 1.17 (oh well, koreader is even older)
|
||||
};
|
||||
# libpng = {
|
||||
# source.url = "https://github.com/glennrp/libpng.git";
|
||||
# source.rev = "v1.6.40";
|
||||
# source.hash = "sha256-Rad7Y5Z9PUCipBTQcB7LEP8fIVTG3JsnMeknUkZ/rRg=";
|
||||
# # package = libAndDev libpng; # "/build/koreader/base/thirdparty/libpng/build/aarch64-unknown-linux-gnu/libpng-prefix/src/libpng-build/.libs/libpng16.so.16"
|
||||
# };
|
||||
# libunibreak = {
|
||||
# source.url = "https://github.com/adah1972/libunibreak.git";
|
||||
# source.rev = "tags/libunibreak_5_1";
|
||||
# source.hash = "sha256-hjgT5DCQ6KFXKlxk9LLzxGHz6B71X/3Ot7ipK3KY85A=";
|
||||
# # package = libAndDev libunibreak; # nixpkgs version is incompatible (kpvcrlib/crengine #includes libunibreak and then fails, calling into undefined functions)
|
||||
# };
|
||||
# libwebp = {
|
||||
# source.url = "https://github.com/webmproject/libwebp.git";
|
||||
# source.rev = "v1.3.2";
|
||||
# source.hash = "sha256-gfwUlJ44biO1lB/3SKfMkM/YBiYcz6RqeMOw+0o6Z/Q=";
|
||||
# package = libAndDev libwebp;
|
||||
# };
|
||||
# libzmq = {
|
||||
# source.url = "https://github.com/zeromq/libzmq";
|
||||
# source.rev = "883e95b22e0bffffa72312ea1fec76199afbe458";
|
||||
# source.hash = "sha256-R76EREtHsqcoKxKrgT8gfEf9pIWdLTBXvF9cDvjEf3E=";
|
||||
# # package = zeromq4; # despite the name, it's libzmq.so.5 instead of libzmq.so.4
|
||||
# };
|
||||
lj-wpaclient = {
|
||||
source.url = "https://github.com/koreader/lj-wpaclient.git";
|
||||
source.rev = "2f93beb3071e6ebb57c783bd5b92f83aa5ebb757";
|
||||
source.hash = "sha256-ilJviGZTvL2i1TN5lHQ4eA9pFiM7NlXD+v9ofv520b8=";
|
||||
machineAgnostic = true;
|
||||
# package: not in nixpkgs; not even a non-luajit `wpaclient`
|
||||
};
|
||||
lodepng = {
|
||||
source.url = "https://github.com/lvandeve/lodepng.git";
|
||||
source.rev = "d398e0f10d152a5d17fa30463474dc9f56523f9c";
|
||||
source.hash = "sha256-ApOHUgU6X1rHwyjAHA/0Nt+buDFqY2ttXEnEvdrRl3A=";
|
||||
# package: not in nixpkgs, except in source-only form (mujoco.pin.lodepng)
|
||||
};
|
||||
lua-htmlparser = {
|
||||
source.url = "https://github.com/msva/lua-htmlparser";
|
||||
source.rev = "5ce9a775a345cf458c0388d7288e246bb1b82bff";
|
||||
source.hash = "sha256-aSTLSfqz/MIDFVRwtBlDNBUhPb7KqOl32/Y62Hdec1s=";
|
||||
# package: not in nixpkgs
|
||||
};
|
||||
# luajit = {
|
||||
# source.url = "https://github.com/LuaJIT/LuaJIT";
|
||||
# source.rev = "29b0b282f59ac533313199f4f7be79490b7eee51";
|
||||
# source.hash = "sha256-S57/NR+0hF1KTdn+cbVkJh3MTfklSwtZua1CYKduVlk=";
|
||||
# # package = luajit; #< could be fixed; follows a different install structure
|
||||
# };
|
||||
# lua-rapidjson = {
|
||||
# source.url = "https://github.com/xpol/lua-rapidjson";
|
||||
# source.rev = "242b40c8eaceb0cc43bcab88309736461cac1234";
|
||||
# source.hash = "sha256-y/czEVPtCt4uN1n49Qi7BrgZmkG+SDXlM5D2GvvO2qg=";
|
||||
# # package: TODO: packaged in nix as a luarocks package
|
||||
# };
|
||||
# luasec = {
|
||||
# source.url = "https://github.com/brunoos/luasec";
|
||||
# source.rev = "tags/v1.3.1";
|
||||
# source.hash = "sha256-3iYRNQoVk5HFjDSqRRmg1taSqeT2cHFil36vxjrEofo=";
|
||||
# # package: TODO: packaged in nix as a luarocks package
|
||||
# };
|
||||
# luasocket = {
|
||||
# source.url = "https://github.com/lunarmodules/luasocket";
|
||||
# source.rev = "8c2ff7217e2a205eb107a6f48b04ff1b2b3090a1";
|
||||
# source.hash = "sha256-Y35QYNLznQmErr6rIjxLzw0/6Y7y8TbzD4yaEdgEljA=";
|
||||
# # package: TODO: packaged in nix as a luarocks package
|
||||
# };
|
||||
lua-Spore = {
|
||||
# Complete... ish?
|
||||
# this originally failed like so:
|
||||
# Missing dependencies for lua-spore 0.3.1-1:
|
||||
# luajson >= 1.3 (not installed)
|
||||
# it passes now only because we patch out its build-time check for luajson (which we DO provide at runtime)
|
||||
source.url = "https://framagit.org/fperrad/lua-Spore";
|
||||
source.rev = "tags/0.3.3";
|
||||
source.hash = "sha256-wb7ykJsndoq0DazHpfXieUcBBptowYqD/eTTN/EK/6g=";
|
||||
# package: not in nixpkgs
|
||||
};
|
||||
lunasvg = {
|
||||
source.url = "https://github.com/sammycage/lunasvg.git";
|
||||
source.rev = "59d6f6ba835c1b7c7a0f9d4ea540ec3981777885";
|
||||
source.hash = "sha256-gW2ikakS6Omz5upmy26nAo/jkGHYO2kjlB3UmKJBh1k=";
|
||||
# package: not in nixpkgs
|
||||
};
|
||||
minizip = {
|
||||
source.url = "https://github.com/nmoinvaz/minizip";
|
||||
source.rev = "0b46a2b4ca317b80bc53594688883f7188ac4d08";
|
||||
source.hash = "sha256-P/3MMMGYDqD9NmkYvw/thKpUNa3wNOSlBBjANHSonAg=";
|
||||
# package = libAndDev minizip; # weird #include incompatibilities... maybe resolvable.
|
||||
};
|
||||
mupdf = {
|
||||
source.url = "https://github.com/ArtifexSoftware/mupdf.git";
|
||||
source.rev = "tags/1.13.0";
|
||||
source.hash = "sha256-pQejRon9fO9A1mhz3oLjBr1j4HveDLcQIWjR1/Rpy5Q=";
|
||||
# package = libAndDev mupdf; # nixpkgs' mupdf is incompatible with koreader's `libwrap-mupdf`
|
||||
# package = libAndDev mupdf_1_17; # does not compile
|
||||
};
|
||||
nanosvg = {
|
||||
source.url = "https://github.com/memononen/nanosvg.git";
|
||||
source.rev = "93ce879dc4c04a3ef1758428ec80083c38610b1f";
|
||||
source.hash = "sha256-ZtenaXJqMZr2+BxYENG1zUoQ+Qoxlxy0m/1YfJBKAFk=";
|
||||
machineAgnostic = true;
|
||||
package = nanosvg.src; # KOReader only wants the .h files, but decides to do that without even building it.
|
||||
};
|
||||
# openssh = {
|
||||
# source.url = "https://github.com/openssh/openssh-portable.git";
|
||||
# source.rev = "V_8_6_P1";
|
||||
# source.hash = "sha256-yjIpSbe5pt9sEV2MZYGztxejg/aBFfKO8ieRvoLN2KA=";
|
||||
# package = openssh;
|
||||
# };
|
||||
# openssl = {
|
||||
# source.url = "https://github.com/openssl/openssl.git";
|
||||
# source.rev = "OpenSSL_1_1_1u";
|
||||
# source.hash = "sha256-JOcUj4ovA6621+1k2HUsvhGX1B9BjvaMbCaSx680nSs=";
|
||||
# # TODO: i think we can use nixpkgs openssl, just lift lib/* up to the root of the package directory
|
||||
# # package = lib.getLib openssl_1_1; # N.B.: requires building with `NIXPKGS_ALLOW_INSECURE=1 nix build --impure ...`
|
||||
# };
|
||||
popen-noshell = {
|
||||
source.url = "https://github.com/famzah/popen-noshell.git";
|
||||
source.rev = "e715396a4951ee91c40a98d2824a130f158268bb";
|
||||
source.hash = "sha256-JeBZMsg6ZUGSnyZ4eds4w63gM/L73EsAnLaHOPpL6iM=";
|
||||
# package: not in nixpkgs
|
||||
};
|
||||
# sdcv = {
|
||||
# # upstream is (temporarily?) acquiring this via `download_project` machinery
|
||||
# source.url = "https://github.com/Dushistov/sdcv.git";
|
||||
# source.rev = "v0.5.5";
|
||||
# source.hash = "sha256-EyvljVXhOsdxIYOGTzD+T16nvW7/RNx3DuQ2OdhjXJ4=";
|
||||
# package = sdcv;
|
||||
# };
|
||||
tesseract = {
|
||||
source.url = "https://github.com/tesseract-ocr/tesseract.git";
|
||||
source.rev = "60176fc5ae5e7f6bdef60c926a4b5ea03de2bfa7";
|
||||
source.hash = "sha256-FQvlrJ+Uy7+wtUxBuS5NdoToUwNRhYw2ju8Ya8MLyQw=";
|
||||
# package = tesseract; # i guess koreader's k2pdf also builds tessearct??
|
||||
};
|
||||
turbo = {
|
||||
source.url = "https://github.com/kernelsauce/turbo";
|
||||
source.rev = "tags/v2.1.3";
|
||||
source.hash = "sha256-vBRkFdc5a0FIt15HBz3TnqMZ+GGsqjEefnfJEpuVTBs=";
|
||||
# package = turbo; # nixpkgs' turbo is a totally different thing
|
||||
};
|
||||
# utf8proc = {
|
||||
# source.url = "https://github.com/JuliaStrings/utf8proc.git";
|
||||
# source.rev = "v2.9.0";
|
||||
# source.hash = "sha256-Sgh8vTbclUV+lFZdR29PtNUy8F+9L/OAXk647B+l2mg=";
|
||||
# # package = libAndDev utf8proc; # nixpkgs is v3, not v2; incompatible .so name. /build/koreader/base/thirdparty/utf8proc/build/aarch64-unknown-linux-gnu/utf8proc-prefix/src/utf8proc/libutf8proc.so.2
|
||||
# };
|
||||
# zstd = {
|
||||
# source.url = "https://github.com/facebook/zstd.git";
|
||||
# source.rev = "tags/v1.5.5";
|
||||
# source.hash = "sha256-tHHHIsQU7vJySrVhJuMKUSq11MzkmC+Pcsj00uFJdnQ=";
|
||||
# package = libAndDev zstd;
|
||||
# };
|
||||
# zsync2 = {
|
||||
# source.url = "https://github.com/NiLuJe/zsync2.git";
|
||||
# source.rev = "e618d18f6a7cbf350cededa17ddfe8f76bdf0b5c";
|
||||
# source.hash = "sha256-S0vxCON1l6S+NWlnRPfm7R07DVkvkG+6QW5LNvXBlA8=";
|
||||
# package = zsync; # possibly a different thing than koreader's
|
||||
# };
|
||||
};
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user