Compare commits

...

145 Commits

Author SHA1 Message Date
9b12bd24c0 WIP: firefox: add a right-click context menu to open any URI with the system handler 2024-08-11 22:17:46 +00:00
d9a876e49a fractal: association with https://matrix.to links 2024-08-11 21:45:51 +00:00
5a9dd89475 sane-vpn: fix global up/down action 2024-08-11 21:01:11 +00:00
5e71e5a067 moby: enable bluetooth 2024-08-11 06:27:07 +00:00
f0b0d15ad7 evtest: ship 2024-08-11 06:26:58 +00:00
8aebc1fe87 feeds: subscribe to Oyez supreme court oral arguments 2024-08-10 11:16:54 +00:00
764c2a3276 Revert "nixpkgs-wayland: 0-unstable-2024-08-06 -> 0-unstable-2024-08-09"
This reverts commit ce1c3ec804.

while the nixpkgs-wayland bump works fine on lappy/desko,
it causes moby to be stuck on the white screen after unlocking.
2024-08-10 04:16:31 +00:00
a2f34be9d6 avahi: fix so the dbus policy files get installed system wide, again 2024-08-10 02:59:20 +00:00
bda172bc2d avahi: harden systemd service
N.B.: i haven't tested this on moby yet (which advertizes the gps-share service) nor on servo (which maybe uses it for jellyfin, idk)
2024-08-10 02:04:57 +00:00
a91a2d8a7f gps-share: fix sandboxing 2024-08-10 01:10:31 +00:00
875d919fa8 systemd-rfkill: harden 2024-08-10 00:34:13 +00:00
a323f321b5 snapper: disable
it wasn't working to begin with
2024-08-10 00:09:58 +00:00
f986936bbd wg-home-refresh: use the sandboxed wireguard-tools 2024-08-09 23:52:31 +00:00
3d773fe375 sanebox: improve the capsh stuff a bit more 2024-08-09 23:52:16 +00:00
055ad222e3 wg-home-refresh: harden systemd service 2024-08-09 23:05:58 +00:00
3aafcb0aa8 sanebox: capshonly: be more precise about preserving e/i/p capabilities
with this, gocryptfs works AND wg works
2024-08-09 22:55:02 +00:00
c85f02ca68 scripts/sync: dont rely on /mnt/$self existing 2024-08-09 21:41:56 +00:00
eeafc34ccf hare-ev: 2024-07-11 -> 2024-08-06 2024-08-09 21:41:56 +00:00
039ffcdcd4 newsflash: sandbox 2024-08-09 21:35:33 +00:00
2a35cb5379 mime.urlAssociations: clarify regexes by using $ terminator 2024-08-09 20:57:13 +00:00
3db009bc98 mpv: associate with all web audio/video resources 2024-08-09 20:55:00 +00:00
1e840e72b3 nixpkgs: 24.05-unstable-2024-08-06 -> 24.05-unstable-2024-08-09 2024-08-09 10:34:20 +00:00
ce1c3ec804 nixpkgs-wayland: 0-unstable-2024-08-06 -> 0-unstable-2024-08-09 2024-08-09 10:34:02 +00:00
09dd69a855 uassets: 0-unstable-2024-08-06 -> 0-unstable-2024-08-09 2024-08-09 10:33:29 +00:00
cbe71868ef newsflash: deploy 2024-08-09 10:25:53 +00:00
7b043d0c87 newsflash: simplify cross compilation 2024-08-09 10:14:59 +00:00
fd0188025d newsflash: fix cross compilation 2024-08-09 09:46:40 +00:00
1c57ffa798 newsflash-nixified: init at 3.3.4 (doesn't build!)
it's doing complicated things with glib macros that i can't get to work.
2024-08-09 03:50:36 +00:00
1d205a89bc sway: fix gnome-calls to always be on workspace 1 2024-08-08 23:59:19 +00:00
5ff643aa2f foliate: fix sandboxing 2024-08-08 23:58:02 +00:00
bfdf63e641 calls: 46.3 -> 47.0-beta 2024-08-08 23:57:47 +00:00
c695f7a979 foliate: ship 2024-08-08 21:08:51 +00:00
b3b38451b5 koreader-from-src: add an update script (only updates the toplevel, at the moment) 2024-08-08 18:47:03 +00:00
1ee81db537 switch xdg-desktop-portal-gtk -> xdg-desktop-portal-gnome
the gnome file chooser is far more responsive, on moby

though thumbnailing doesnt work, which may degrade the desktop experience :-(
2024-08-08 09:43:47 +00:00
b9947c05ca xdg-desktop-portal-gnome: fix cross compilation 2024-08-08 09:05:26 +00:00
2de6491583 xdg-desktop-portal-gnome: get working as a xdp backend, on lappy
probably needs some porting to moby before it works there
2024-08-08 08:52:24 +00:00
4525df58e0 rsyslog: disable 2024-08-08 07:40:59 +00:00
9d1ffc7c43 todo.md: document some connectivity issues 2024-08-08 03:41:52 +00:00
a69af91b7b add /mnt/servo/home to my hosts 2024-08-08 00:45:33 +00:00
7b5d655c91 scripts/check-backups: show how to view the host's last-completed 2024-08-07 23:00:44 +00:00
de6ffe6b75 flare-signal: update compatibility notes 2024-08-07 22:07:56 +00:00
f8aea34e96 sanebox: bwrap: make user namespace unsharing more obvious 2024-08-07 21:23:21 +00:00
49efb94a0a seatd: restrict capabilities 2024-08-07 20:30:29 +00:00
9b1e053ead seatd: place the socket in a place that lends itself to better sandboxing 2024-08-07 19:37:20 +00:00
235dc86155 todo.md: delete completed sandboxing items 2024-08-07 01:33:31 +00:00
6dad290cd5 duplicity: purge 2024-08-07 01:33:31 +00:00
cc6ed6c0ec flare-signal: annotate my notes on it with datestamps 2024-08-07 01:23:43 +00:00
cc3aba3cc2 servo: fix postfix: dont start until after the mail dir has been mounted 2024-08-07 00:17:17 +00:00
41f08125bb scripts/check-backups: convert timestamps to hours 2024-08-06 21:50:49 +00:00
27487fe870 scripts/check-admin: script to show which (if any) services require tending to registration attempts 2024-08-06 21:40:48 +00:00
d45ea622d1 servo: disable email-based registration gating 2024-08-06 21:39:32 +00:00
247fd3f807 less: tune flags, especially for systemd/journalctl 2024-08-06 19:25:10 +00:00
816e2a7065 nixpkgs: 24.05-unstable-2024-08-04 -> 24.05-unstable-2024-08-06 2024-08-06 19:08:39 +00:00
be842d5c5e nixpkgs-wayland: 0-unstable-2024-08-03 -> 0-unstable-2024-08-06 2024-08-06 19:08:30 +00:00
fa6ec981e0 sops-nix: assets-unstable-2024-07-27 -> assets-unstable-2024-08-05 2024-08-06 19:08:16 +00:00
52b4c1542a uassets: 0-unstable-2024-08-03 -> 0-unstable-2024-08-06 2024-08-06 19:08:00 +00:00
3ff59247da uninsane-dot-org: ? -> 2024-08-06 2024-08-06 19:07:44 +00:00
d9c0855c4e fractal-nixified: 7 -> 8 2024-08-06 08:26:50 +00:00
1a67a05238 transmission/torrent-done: make files readable (speculative) 2024-08-06 08:26:16 +00:00
1cdeedd9ec servo: partially ship ollama 2024-08-06 08:24:29 +00:00
6830bb7097 servo: transmission: simplify the rsync call
i'm having perms issues _somewhere_
2024-08-06 08:14:45 +00:00
316b0bee3a landlock-sandboxer: disable the ABI mismatch warnings 2024-08-06 07:20:00 +00:00
638655ff83 mnt-servo-*-reachable.service: harden systemd service 2024-08-06 06:43:10 +00:00
5e57e78411 /mnt/servo/*: fix to not hang the mount when fs is offline
this is an unfortunate effect of the drop_privileges part of fuse3,
that the mount is active as soon as the fs implementation is launched,
instead of when it enters `fuse_main`.
2024-08-06 05:54:31 +00:00
3859619ae0 curlftpfs-sane: implement "-o exit_after_connect" 2024-08-06 05:53:45 +00:00
646c2dd85a common/fs: mount curlftpfs using fuse3 2024-08-06 04:48:47 +00:00
0655b6906c curlftpfs: implement readdir
i think now all the stuff you'd expect of a basic fs now actually works
2024-08-06 04:11:32 +00:00
3019f90f5d curlftpfs-sane: upgrade to fuse3 2024-08-06 03:00:11 +00:00
020e5f8c6e /mnt/persist/private: split waiting on the keyfile out of the mount process 2024-08-06 02:03:55 +00:00
809c3af7fa /mnt/persist/private: minor improvements to file permissions 2024-08-06 01:26:53 +00:00
93cb1bc546 /mnt/persist/private: sandbox in a way that the actual gocryptfs instance doesn't get CAP_SYS_ADMIN 2024-08-06 00:52:48 +00:00
53acab834c refactor: persist/stores/ephemeral: move to its own source directory 2024-08-05 23:05:02 +00:00
3a0610b029 /mnt/persist/ephemeral: sandbox in a way that the actual gocryptfs instance doesn't get CAP_SYS_ADMIN
instead, only fuse does, and the capability is lost during the handoff between fuse and gocryptfs
2024-08-05 23:04:14 +00:00
9cee460d7e gocryptfs: sandbox with landlock
now /mnt/persist/ephemeral implementation can't access /mnt/persist/private; /mnt/persist/private can't access /mnt/desko/home, and so on
2024-08-05 23:01:38 +00:00
e657507a76 libfuse-sane: search for mount helpers on /run/current-system/sw/bin 2024-08-05 23:00:05 +00:00
c706a19836 landlock-sandboxer: rename the binary, so that it can be included on PATH without collisions 2024-08-05 22:59:14 +00:00
566e15286b sanebox: don't assume 'readlink' is available in the environment 2024-08-05 22:56:18 +00:00
d1b4e9c923 sanebox: allow /dev/fd in the sandbox by default 2024-08-05 22:55:12 +00:00
5eca45891b sanebox: fix landlock sandboxing IAB edgecases
i don't fully understand it. but adjusting the Inh capability set breaks things like gocryptfs. i think it isn't necessary: if we set E alone, and no-new-privs, then that gets us the same guarantees.
2024-08-05 22:54:20 +00:00
722fe8f368 libcap: ship the optional 'captree' component 2024-08-05 05:30:14 +00:00
e25dd98f6c spot/spotify: disable
i don't use spotify atm
2024-08-05 00:47:59 +00:00
54e9d4a0ae sane-vpn: switch back to bwrap instead of pasta 2024-08-04 18:56:17 +00:00
9f3a13eeb8 nixpkgs: 24.05-unstable-2024-08-02 -> 24.05-unstable-2024-08-04 2024-08-04 02:19:14 +00:00
5605ffda4b nixpkgs-wayland: 0-unstable-2024-08-02 -> 0-unstable-2024-08-03 2024-08-04 02:18:57 +00:00
9165925469 syshud: 0-unstable-2024-07-29 -> 0-unstable-2024-08-03 2024-08-04 02:18:37 +00:00
f65bf2b433 uassets: 0-unstable-2024-08-02 -> 0-unstable-2024-08-03 2024-08-04 02:18:14 +00:00
0f60a86ed4 hare-ev: 0-unstable-2024-07-07 -> 0-unstable-2024-07-11 2024-08-04 01:08:37 +00:00
b488b6748d nixos/pam: replace apparmor warnings with assertions 2024-08-04 00:50:41 +00:00
ef6b7cf175 syshud: 0-unstable-2024-07-16 -> 0-unstable-2024-07-29 2024-08-04 00:39:04 +00:00
0906d76f83 libcap_ng: ship 2024-08-03 23:27:53 +00:00
90c495e74c libfuse-sane: ship 2024-08-03 22:59:34 +00:00
74662df720 persist/{private,ephemeral}: mount via fuse
gocryptfs is compatible with --drop-permissions style of mount.fuse3. only, i can't actually use that today because i need to keep permissions :o

but maybe i'll enable that in the future
2024-08-03 18:51:58 +00:00
2b3278eb7f /mnt/$host/home: layer bwrap sandboxing after the drop-privileges passoff 2024-08-03 17:11:11 +00:00
9b4e91fbd9 /mnt/$host/home: harden systemd settings 2024-08-03 16:27:42 +00:00
734627232a /mnt/$host/home: mount with drop_privileges 2024-08-03 15:13:04 +00:00
3adbbe5fa7 /mnt/$host/home: run as user instead of as root 2024-08-03 15:13:04 +00:00
b4a244df7a rsync-net: fix /nix/persist/private path 2024-08-03 14:32:44 +00:00
97268e9b26 curlftpfs-sane: rename from curlftpfs
i already patched it significantly; i plan to port to fuse3 shortly
2024-08-03 14:32:01 +00:00
bebf6bdaeb rsync-net: hardcode fewer paths 2024-08-03 11:38:43 +00:00
04fc601c9c rsync-net: fix sandboxing (dont set PrivateUsers: we lose perms in the root ns doing that) 2024-08-03 11:25:50 +00:00
ee062d61d0 sane-tag-media: rework the tag extrapolation to be less intrusive 2024-08-03 07:58:43 +00:00
0dba9987c5 sane-tag-media: remove unused "confirm" function 2024-08-03 07:19:53 +00:00
4761690b6d sane-tag-media: have --derive + --override-existing NOT override the existing tags when the derived ones apear to be simply lower-quality versions of the same on-disk data 2024-08-03 07:19:08 +00:00
604782c3a6 sane-tag-media: refactor (simplify) 2024-08-03 03:57:09 +00:00
365d33c357 sane-tag-media: empty manual tags always overwrite tags, regardless of --override-existing flag 2024-08-03 03:38:22 +00:00
a39ad8a508 sane-tag-media: rename --force flag to --override-existing 2024-08-03 03:31:16 +00:00
c49e9a4c2b sane-tag-media: implement the --ignore-existing flag 2024-08-03 03:18:07 +00:00
36491842cc sanebox: bwrap: micro-optimize to not require env 2024-08-02 22:44:27 +00:00
81ea2210c9 sanebox: allow keeping the net namespace 2024-08-02 22:44:27 +00:00
f678508b33 sanebox: add --sanebox-capsh-arg flag 2024-08-02 22:44:27 +00:00
6135be5f72 sanebox: refactor: bwrapFlags -> bwrapArgs 2024-08-02 22:44:27 +00:00
c8989ca1a8 pasta: allow running as root 2024-08-02 22:44:26 +00:00
1d665f8ecc sanebox: support "--sanebox-cap all" special case 2024-08-02 22:43:52 +00:00
7c284ad8da sane-vpn: use pasta instead of full bwrap for net namespacing 2024-08-02 22:42:56 +00:00
1c26674da7 rsync-net: temporarily use only RestrictNetworkInterfaces option and disable the internal sane-vpn logic
this is temporary, until i can fix sane-vpn to preserve linux capabilities
2024-08-02 22:10:44 +00:00
dae8481176 firefox: ship a "stub DNS" desktop file variant
though note that my stub-dns seems to be broken recently...
2024-08-02 21:41:07 +00:00
42b27f0433 sane-vpn: fix broken doc on --verbose flag 2024-08-02 21:39:29 +00:00
84be0cae5a todo.md: note another website which doesnt resolve with trust-dns 2024-08-02 21:20:31 +00:00
fbfd0afca4 common/fs: only desclare /mnt/$host mounts for hosts this machine is authorized to access 2024-08-02 20:29:22 +00:00
e586b7b449 signal-desktop-from-src: 7.16.0 -> 7.18.0 2024-08-02 10:52:44 +00:00
222c37b056 uassets: 2024-07-29 -> 2024-08-02 2024-08-02 10:52:25 +00:00
53b17ec230 nixpkgs-wayland: 2024-07-28 -> 2024-08-02 2024-08-02 10:52:07 +00:00
7697704aff nixpkgs: 2024-07-31 -> 2024-08-02 2024-08-02 10:51:44 +00:00
c490b6e6ad common/polyunfill: simplify my config by using the new security.pam.package option 2024-08-02 10:04:20 +00:00
89d678c729 nixpkgs: 2024-07-29 -> 2024-07-31 2024-08-02 10:03:48 +00:00
c64163290c gocryptfs: return to running mainline
i don't need the bug fix anymore, since i don't use pam_mount anymore
2024-08-02 09:52:20 +00:00
eaeb8380dc fs: enable @basic-api everywhere, since its required by systemd restart logic 2024-08-02 09:13:55 +00:00
05a9e8e819 common: /mnt/servo: fix systemd mount files to be aware of the timeout, again 2024-08-02 08:16:13 +00:00
cf20230d96 sane.fs: cleanup
plumb systemd.{mounts,services} instead of the less detailed 'systemd'
2024-08-02 08:01:38 +00:00
9dbb2a6266 sane.fs: take in the role of generating systemd.mounts files 2024-08-02 07:33:21 +00:00
113b107d73 persist: fix ordering so stores arent required by local-fs.target
maybe they should be, but then there's weird stuff about getty depending on sysinit.target, and that being blocked by the private store...
2024-08-02 06:20:39 +00:00
96dfe79a8c fs: persist/private: harden systemd mount file 2024-08-02 05:17:44 +00:00
6e5bde17aa cleanup: persist/private: simplify 2024-08-02 05:00:55 +00:00
3eb66c098b trust-dns: make it a dependency of "network-online.target" 2024-08-02 04:54:58 +00:00
515aab5370 cleanup: persist/private: encode the dependencies more precisely, rather than just having it all depend on default.target 2024-08-02 04:50:33 +00:00
f925dd9a20 fs: isolate /mnt/servo/* and /mnt/persist/ephemeral a bit more 2024-08-02 04:45:14 +00:00
cbe6bdf158 hosts: fs: sandbox /mnt/servo/* mounts 2024-08-02 03:17:53 +00:00
949a52dee1 activationScripts.notifyActive: be quiet about sane-deadlines/sane-sysload 2024-08-02 01:11:19 +00:00
2ee1fb17c4 sane-deadlines, sane-sysload: fix ordering to not run before the environment is configured 2024-08-02 01:04:07 +00:00
48cc718700 login: remove systemd-user-sessions integration so that we dont block on remote-fs
tested on lappy. will it work on servo, with gitea?
2024-08-02 00:52:51 +00:00
6a7dd31755 vpn: fix warning about missing /32 syntax 2024-08-02 00:37:58 +00:00
2197951e12 NetworkManager-dispatcher: cleanup an ordering cycle between it and trust-dns-localhost 2024-08-02 00:36:54 +00:00
883db3e9ba todo.md: sync 2024-08-02 00:33:35 +00:00
312b0a5554 todo.md: sandbox the remaining filesystems 2024-08-01 22:50:03 +00:00
07de46c616 todo.md: remove completed items 2024-08-01 22:48:49 +00:00
109 changed files with 22311 additions and 5287 deletions

32
TODO.md
View File

@@ -7,13 +7,17 @@
- or try dnsmasq?
- trust-dns can't resolve `abs.twimg.com`
- trust-dns can't resolve `social.kernel.org`
- trust-dns can't resolve `pe.usps.com`
- trust-dns can't resolve `social.seattle.wa.us`
- trust-dns can't resolve `support.mozilla.org`
- sandbox: link cache means that if i update ~/.config/... files inline, sandboxed programs still see the old version
- mpv: continues to play past the end of some audio files
- mpv: audiocast has mpv sending its output to the builtin speakers unless manually changed
- mpv: no way to exit fullscreen video on moby
- uosc hides controls on FS, and touch doesn't support unhiding
- `ssh` access doesn't grant same linux capabilities as login
- syshud (volume overlay): when casting with `blast`, syshud doesn't react to volume changes
- moby: after bringing the modem up, powering it down loses *complete* net connectivity (i.e. wlan is gone as well)
- dissent: if i launch it without net connectivity, it gets stuck at the login, and never tries again
- calls: seems that it starts before net access, and then is forever disconnected (until i manually restart it)
- moby: kaslr is effectively disabled
- `dmesg | grep "KASLR disabled due to lack of seed"`
- fix by adding `kaslrseed` to uboot script before `booti`
@@ -50,8 +54,6 @@
## IMPROVEMENTS:
- systemd/journalctl: use a less shit pager
- there's an env var for it: SYSTEMD_PAGER? and a flag for journalctl
- kernels: ship the same kernel on every machine
- then i can tune the kernels for hardening, without duplicating that work 4 times
- zfs: replace this with something which doesn't require a custom kernel build
@@ -60,17 +62,18 @@
- safer (rust? actively maintained? sandboxable?)
- handles spaces/symbols in filenames
- has better multi-stream perf (e.g. `sane-sync-music` should be able to copy N items in parallel)
- firefox: open *all* links (http, https, ...) with system handler
- removes the need for open-in-mpv, firefox-xdg-open, etc.
- matrix room links *just work*.
- `network.protocol-handler.external.https = true` in about:config *seems* to do this,
but breaks some webpages (e.g. Pleroma)
### security/resilience
- validate duplicity backups!
- encrypt more ~ dirs (~/archives, ~/records, ..?)
- best to do this after i know for sure i have good backups
- enable `snapper` btrfs snapshots (`services.snapper`)
- /mnt/desko/home, etc, shouldn't include secrets (~/private)
- 95% of its use is for remote media access and stuff which isn't in VCS (~/records)
- port all sane.programs to be sandboxed
- sandbox `curlftpfs`
- sandbox `nix`
- sandbox `sshfs-fuse`
- enforce that all `environment.packages` has a sandbox profile (or explicitly opts out)
- revisit "non-sandboxable" apps and check that i'm not actually just missing mountpoints
- LL_FS_RW=/ isn't enough -- need all mount points like `=/:/proc:/sys:...`.
@@ -116,18 +119,17 @@
#### moby
- fix cpuidle (gets better power consumption): <https://xnux.eu/log/077.html>
- fix cpupower for better power/perf
- `journalctl -u cpupower --boot` (problem is present on lappy, at least)
- moby: tune keyboard layout
- SwayNC:
- don't show MPRIS if no players detected
- this is a problem of playerctld, i guess
- add option to change audio output
- SwayNC: add option to change audio output
- moby: tune GPS
- fix iio-sensor-proxy magnetometer scaling
- tune QGPS setting in eg25-control, for less jitter?
- configure geoclue to do some smoothing?
- manually do smoothing, as some layer between mepo and geoclue?
- email wigle.net people to unlock API access
- moby: port `freshen-agps` timer service to s6 (maybe i want some `s6-cron` or something)
- moby: show battery state on ssh login
- moby: improve gPodder launch time
- moby: theme GTK apps (i.e. non-adwaita styles)
- especially, make the menubar collapsible
@@ -136,6 +138,8 @@
#### non-moby
- RSS: integrate a paywall bypass
- e.g. self-hosted [ladder](https://github.com/everywall/ladder) (like 12ft.io)
- RSS: have podcasts get downloaded straight into ~/Videos/...
- and strip the ads out using Whisper transcription + asking a LLM where the ad breaks are
- neovim: set up language server (lsp; rnix-lsp; nvim-lspconfig)
- neovim: integrate LLMs
- Helix: make copy-to-system clipboard be the default

View File

@@ -28,7 +28,6 @@
sane.services.wg-home.ip = config.sane.hosts.by-name."desko".wg-home.ip;
sane.ovpn.addrV4 = "172.26.55.21";
# sane.ovpn.addrV6 = "fd00:0000:1337:cafe:1111:1111:20c1:a73c";
sane.services.duplicity.enable = true;
sane.services.rsync-net.enable = true;
sane.nixcache.remote-builders.desko = false;
@@ -53,15 +52,18 @@
# needed to use libimobiledevice/ifuse, for iphone sync
services.usbmuxd.enable = true;
# TODO: enable snapper (need to make `/nix` or `/nix/persist` a subvolume, somehow).
# default config: https://man.archlinux.org/man/snapper-configs.5
# defaults to something like:
# - hourly snapshots
# - auto cleanup; keep the last 10 hourlies, last 10 daylies, last 10 monthlys.
services.snapper.configs.nix = {
# TODO: for the impermanent setup, we'd prefer to just do /nix/persist,
# but that also requires setting up the persist dir as a subvol
SUBVOLUME = "/nix";
# TODO: ALLOW_USERS doesn't seem to work. still need `sudo snapper -c nix list`
ALLOW_USERS = [ "colin" ];
};
# to list snapshots: `sudo snapper --config nix list`
# to take a snapshot: `sudo snapper --config nix create`
# services.snapper.configs.nix = {
# # TODO: for the impermanent setup, we'd prefer to just do /nix/persist,
# # but that also requires setting up the persist dir as a subvol
# SUBVOLUME = "/nix";
# # TODO: ALLOW_USERS doesn't seem to work. still need `sudo snapper -c nix list`
# ALLOW_USERS = [ "colin" ];
# };
}

View File

@@ -28,14 +28,18 @@
sane.services.rsync-net.enable = true;
# TODO: enable snapper (need to make `/nix` or `/nix/persist` a subvolume, somehow).
# default config: https://man.archlinux.org/man/snapper-configs.5
# defaults to something like:
# - hourly snapshots
# - auto cleanup; keep the last 10 hourlies, last 10 daylies, last 10 monthlys.
services.snapper.configs.nix = {
# TODO: for the impermanent setup, we'd prefer to just do /nix/persist,
# but that also requires setting up the persist dir as a subvol
SUBVOLUME = "/nix";
ALLOW_USERS = [ "colin" ];
};
# to list snapshots: `sudo snapper --config nix list`
# to take a snapshot: `sudo snapper --config nix create`
# services.snapper.configs.nix = {
# # TODO: for the impermanent setup, we'd prefer to just do /nix/persist,
# # but that also requires setting up the persist dir as a subvol
# SUBVOLUME = "/nix";
# # TODO: ALLOW_USERS doesn't seem to work. still need `sudo snapper -c nix list`
# ALLOW_USERS = [ "colin" ];
# };
}

View File

@@ -31,7 +31,6 @@
# sane.ovpn.addrV6 = "fd00:0000:1337:cafe:1111:1111:8df3:14b0";
sane.nixcache.remote-builders.desko = false;
sane.nixcache.remote-builders.servo = false;
# sane.services.duplicity.enable = true; # TODO: re-enable after HW upgrade
sane.services.rsync-net.enable = true;
# automatically log in at the virtual consoles.

View File

@@ -20,6 +20,7 @@
./nginx.nix
./nixos-prebuild.nix
./ntfy
./ollama.nix
./pict-rs.nix
./pleroma.nix
./postgres.nix

View File

@@ -1,4 +1,11 @@
# postfix config options: <https://www.postfix.org/postconf.5.html>
# config files:
# - /etc/postfix/main.cf
# - /etc/postfix/master.cf
#
# logs:
# - postfix logs directly to *syslog*,
# so check e.g. ~/.local/share/rsyslog
{ config, lib, pkgs, ... }:
@@ -20,12 +27,12 @@ in
{
sane.persist.sys.byStore.private = [
# TODO: mode? could be more granular
{ user = "opendkim"; group = "opendkim"; path = "/var/lib/opendkim"; method = "bind"; }
{ user = "root"; group = "root"; path = "/var/lib/postfix"; method = "bind"; } #< probably not *all* of postfix needs to actually be persisted (e.g. not the conf dir)
{ user = "opendkim"; group = "opendkim"; path = "/var/lib/opendkim"; method = "bind"; } #< TODO: migrate to secrets
{ user = "root"; group = "root"; path = "/var/spool/mail"; method = "bind"; }
# *probably* don't need these dirs:
# "/var/lib/dhparams" # https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/security/dhparams.nix
# "/var/lib/dovecot"
# "/var/lib/postfix"
];
# XXX(2023/10/20): opening these ports in the firewall has the OPPOSITE effect as intended.
@@ -95,6 +102,7 @@ in
services.postfix.sslCert = "/var/lib/acme/mx.uninsane.org/fullchain.pem";
services.postfix.sslKey = "/var/lib/acme/mx.uninsane.org/key.pem";
# see: `man 5 virtual`
services.postfix.virtual = ''
notify.matrix@uninsane.org matrix-synapse
@uninsane.org colin
@@ -135,6 +143,20 @@ in
# smtpd_sender_restrictions = reject_unknown_sender_domain
};
# debugging options:
# services.postfix.masterConfig = {
# "proxymap".args = [ "-v" ];
# "proxywrite".args = [ "-v" ];
# "relay".args = [ "-v" ];
# "smtp".args = [ "-v" ];
# "smtp_inet".args = [ "-v" ];
# "submission".args = [ "-v" ];
# "submissions".args = [ "-v" ];
# "submissions".chroot = false;
# "submissions".private = false;
# "submissions".privileged = true;
# };
services.postfix.enableSubmission = true;
services.postfix.submissionOptions = submissionOptions;
services.postfix.enableSubmissions = true;
@@ -142,6 +164,10 @@ in
systemd.services.postfix.after = [ "wireguard-wg-ovpns.service" ];
systemd.services.postfix.partOf = [ "wireguard-wg-ovpns.service" ];
systemd.services.postfix.unitConfig.RequiresMountsFor = [
"/var/spool/mail" # spooky errors when postfix is run w/o this: `warning: connect #1 to subsystem private/proxymap: Connection refused`
"/var/lib/opendkim"
];
systemd.services.postfix.serviceConfig = {
# run this behind the OVPN static VPN
NetworkNamespacePath = "/run/netns/ovpns";
@@ -175,23 +201,30 @@ in
#### OUTGOING MESSAGE REWRITING:
services.postfix.enableHeaderChecks = true;
services.postfix.headerChecks = [
# intercept gitea registration confirmations and manually screen them
{
# headerChecks are somehow ignorant of alias rules: have to redirect to a real user
action = "REDIRECT colin@uninsane.org";
pattern = "/^Subject: Please activate your account/";
}
# intercept Matrix registration confirmations
{
action = "REDIRECT colin@uninsane.org";
pattern = "/^Subject:.*Validate your email/";
}
# XXX postfix only supports performing ONE action per header.
# {
# action = "REPLACE Subject: git application: Please activate your account";
# pattern = "/^Subject:.*activate your account/";
# }
];
# - `man 5 header_checks`
# - <https://www.postfix.org/header_checks.5.html>
# - populates `/var/lib/postfix/conf/header_checks`
# XXX(2024-08-06): registration gating via email matches is AWFUL:
# 1. bypassed if the service offers localization.
# 2. if i try to forward the registration request, it may match the filter again and get sent back to my inbox.
# 3. header checks are possibly under-used in the ecosystem, and may break postfix config.
# services.postfix.enableHeaderChecks = true;
# services.postfix.headerChecks = [
# # intercept gitea registration confirmations and manually screen them
# {
# # headerChecks are somehow ignorant of alias rules: have to redirect to a real user
# action = "REDIRECT colin@uninsane.org";
# pattern = "/^Subject: Please activate your account/";
# }
# # intercept Matrix registration confirmations
# {
# action = "REDIRECT colin@uninsane.org";
# pattern = "/^Subject:.*Validate your email/";
# }
# # XXX postfix only supports performing ONE action per header.
# # {
# # action = "REPLACE Subject: git application: Please activate your account";
# # pattern = "/^Subject:.*activate your account/";
# # }
# ];
}

View File

@@ -1,10 +1,14 @@
# config options: <https://docs.gitea.io/en-us/administration/config-cheat-sheet/>
# TODO: service shouldn't run as `git` user, but as `gitea`
{ config, pkgs, lib, ... }:
{
sane.persist.sys.byStore.private = [
{ user = "git"; group = "gitea"; mode = "0750"; path = "/var/lib/gitea"; method = "bind"; }
];
sane.programs.gitea.enableFor.user.colin = true; # for admin, and monitoring
services.gitea.enable = true;
services.gitea.user = "git"; # default is 'gitea'
services.gitea.database.type = "postgres";
@@ -40,14 +44,21 @@
# timeout for email approval. 5760 = 4 days. 10080 = 7 days
ACTIVE_CODE_LIVE_MINUTES = 10080;
# REGISTER_EMAIL_CONFIRM = false;
# REGISTER_MANUAL_CONFIRM = true;
REGISTER_EMAIL_CONFIRM = true;
# REGISTER_EMAIL_CONFIRM = true; #< override REGISTER_MANUAL_CONFIRM
REGISTER_MANUAL_CONFIRM = true;
# not sure what this notifies *on*...
ENABLE_NOTIFY_MAIL = true;
# defaults to image-based captcha.
# also supports recaptcha (with custom URLs) or hCaptcha.
ENABLE_CAPTCHA = true;
NOREPLY_ADDRESS = "noreply.anonymous.git@uninsane.org";
EMAIL_DOMAIN_BLOCKLIST = lib.concatStringsSep ", " [
"*.claychoen.top"
"*.gemmasmith.co.uk"
"*.jenniferlawrence.uk"
"*.sarahconnor.co.uk"
"*.marymarshall.co.uk"
];
};
session = {
COOKIE_SECURE = true;

View File

@@ -30,7 +30,7 @@
# services.matrix-synapse.enable_registration_captcha = true;
# services.matrix-synapse.enable_registration_without_verification = true;
enable_registration = true;
# enable_registration = true;
# services.matrix-synapse.registration_shared_secret = "<shared key goes here>";
# default for listeners is port = 8448, tls = true, x_forwarded = false.

View File

@@ -0,0 +1,23 @@
# ollama: <https://github.com/ollama/ollama>
# use: `ollama run llama3.1`
# or: `ollama run llama3.1:70b`
# or use a remote session: <https://github.com/ggozad/oterm>
{ lib, ... }:
lib.mkIf false #< WIP
{
sane.persist.sys.byStore.plaintext = [
{ user = "ollama"; group = "ollama"; path = "/var/lib/ollama"; method = "bind"; }
];
services.ollama.enable = true;
services.ollama.user = "ollama";
services.ollama.group = "ollama";
users.groups.ollama = {};
users.users.ollama = {
group = "ollama";
isSystemUser = true;
};
systemd.services.ollama.serviceConfig.DynamicUser = lib.mkForce false;
}

View File

@@ -40,10 +40,11 @@ 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/"
destructive rsync -rlv "$TR_TORRENT_DIR/" "$MEDIA_DIR/"
# make the media rwx by anyone in the group
destructive find "$MEDIA_DIR" -type d -exec setfacl --recursive --modify d:g::rwx,o::rx {} \;
destructive find "$MEDIA_DIR" -type d -exec chmod g+rw,a+rx {} \;
destructive find "$MEDIA_DIR" -type f -exec chmod g+rw,a+r {} \;
# if there's a single directory inside the media dir, then inline that
subdirs=("$MEDIA_DIR"/*)

View File

@@ -59,6 +59,7 @@ let
podcasts = [
(fromDb "acquiredlpbonussecretsecret.libsyn.com" // tech) # ACQ2 - more "Acquired" episodes
(fromDb "allinchamathjason.libsyn.com" // pol)
(fromDb "api.oyez.org/podcasts/oral-arguments/2015" // pol) # Supreme Court Oral Arguments ("2015" in URL means nothing -- it's still updated)
(fromDb "anchor.fm/s/34c7232c/podcast/rss" // tech) # Civboot -- https://anchor.fm/civboot
(fromDb "anchor.fm/s/2da69154/podcast/rss" // tech) # POD OF JAKE -- https://podofjake.com/
(fromDb "cast.postmarketos.org" // tech)

View File

@@ -45,7 +45,7 @@ let
"gid=100"
];
ssh = common ++ fuse ++ [
ssh = common ++ fuseColin ++ [
"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?
@@ -107,18 +107,64 @@ let
"connect_timeout=20"
];
};
ifSshAuthorized = lib.mkIf config.sane.hosts.by-name."${config.networking.hostName}".ssh.authorized;
remoteHome = host: {
sane.programs.sshfs-fuse.enableFor.system = true;
system.fsPackages = [
config.sane.programs.sshfs-fuse.package
];
fileSystems."/mnt/${host}/home" = {
device = "colin@${host}:/home/colin";
fsType = "fuse.sshfs";
options = fsOpts.sshColin ++ fsOpts.lazyMount;
device = "sshfs#colin@${host}:/home/colin";
fsType = "fuse3";
options = fsOpts.sshColin ++ fsOpts.lazyMount ++ [
# drop_privileges: after `mount.fuse3` opens /dev/fuse, it will drop all capabilities before invoking sshfs
"drop_privileges"
"auto_unmount" #< ensures that when the fs exits, it releases its mountpoint. then systemd can recognize it as failed.
];
noCheck = true;
};
sane.fs."/mnt/${host}/home" = sane-lib.fs.wanted {
sane.fs."/mnt/${host}/home" = {
dir.acl.user = "colin";
dir.acl.group = "users";
dir.acl.mode = "0700";
wantedBy = [ "default.target" ];
mount.depends = [ "network-online.target" ];
mount.mountConfig.ExecSearchPath = [ "/run/current-system/sw/bin" ];
mount.mountConfig.User = "colin";
mount.mountConfig.AmbientCapabilities = "CAP_SETPCAP CAP_SYS_ADMIN";
# hardening (systemd-analyze security mnt-desko-home.mount):
# TODO: i can't use ProtectSystem=full here, because i can't create a new mount space; but...
# with drop_privileges, i *could* sandbox the actual `sshfs` program using e.g. bwrap
mount.mountConfig.CapabilityBoundingSet = "CAP_SETPCAP CAP_SYS_ADMIN";
mount.mountConfig.LockPersonality = true;
mount.mountConfig.MemoryDenyWriteExecute = true;
mount.mountConfig.NoNewPrivileges = true;
mount.mountConfig.ProtectClock = true;
mount.mountConfig.ProtectHostname = true;
mount.mountConfig.RemoveIPC = true;
mount.mountConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
# see `systemd-analyze filesystems` for a full list
mount.mountConfig.RestrictFileSystems = "@common-block @basic-api fuse";
mount.mountConfig.RestrictRealtime = true;
mount.mountConfig.RestrictSUIDSGID = true;
mount.mountConfig.SystemCallArchitectures = "native";
mount.mountConfig.SystemCallFilter = [
"@system-service"
"@mount"
"~@chown"
"~@cpu-emulation"
"~@keyring"
# could remove almost all io calls, however one has to keep `open`, and `write`, to communicate with the fuse device.
# so that's pretty useless as a way to prevent write access
];
mount.mountConfig.IPAddressDeny = "any";
mount.mountConfig.IPAddressAllow = "10.0.0.0/8";
mount.mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mount.mountConfig.DeviceAllow = "/dev/fuse";
# mount.mountConfig.RestrictNamespaces = true; #< my sshfs sandboxing uses bwrap
};
};
remoteServo = subdir: let
@@ -126,36 +172,122 @@ let
systemdName = utils.escapeSystemdPath localPath;
in {
sane.programs.curlftpfs.enableFor.system = true;
sane.fs."${localPath}" = sane-lib.fs.wanted {
dir.acl.user = "colin";
dir.acl.group = "users";
dir.acl.mode = "0750";
};
system.fsPackages = [
config.sane.programs.curlftpfs.package
];
fileSystems."${localPath}" = {
device = "ftp://servo-hn:/${subdir}";
device = "curlftpfs#ftp://servo-hn:/${subdir}";
noCheck = true;
fsType = "fuse.curlftpfs";
options = fsOpts.ftp ++ fsOpts.noauto;
fsType = "fuse3";
options = fsOpts.ftp ++ fsOpts.noauto ++ [
# drop_privileges: after `mount.fuse3` opens /dev/fuse, it will drop all capabilities before invoking sshfs
"drop_privileges"
"auto_unmount" #< ensures that when the fs exits, it releases its mountpoint. then systemd can recognize it as failed.
];
# fsType = "nfs";
# options = fsOpts.nfs ++ fsOpts.lazyMount;
};
systemd.mounts = let
fsEntry = config.fileSystems."${localPath}";
in [{
#VVV repeat what systemd would ordinarily scrape from /etc/fstab
where = localPath;
what = fsEntry.device;
type = fsEntry.fsType;
options = lib.concatStringsSep "," fsEntry.options;
after = [ "network-online.target" ];
requires = [ "network-online.target" ];
wantedBy = [ "default.target" ]; #< TODO: move this into nixos fileSystems
sane.fs."${localPath}" = {
dir.acl.user = "colin";
dir.acl.group = "users";
dir.acl.mode = "0750";
wantedBy = [ "default.target" ];
mount.depends = [ "network-online.target" "${systemdName}-reachable.service" ];
#VVV patch so that when the mount fails, we start a timer to remount it.
# and for a disconnection after a good mount (onSuccess), restart the timer to be more aggressive
onFailure = [ "${systemdName}.timer" ];
onSuccess = [ "${systemdName}-restart-timer.target" ];
}];
mount.unitConfig.OnFailure = [ "${systemdName}.timer" ];
mount.unitConfig.OnSuccess = [ "${systemdName}-restart-timer.target" ];
mount.mountConfig.TimeoutSec = "10s";
mount.mountConfig.ExecSearchPath = [ "/run/current-system/sw/bin" ];
mount.mountConfig.User = "colin";
mount.mountConfig.AmbientCapabilities = "CAP_SETPCAP CAP_SYS_ADMIN";
# hardening (systemd-analyze security mnt-servo-playground.mount)
mount.mountConfig.CapabilityBoundingSet = "CAP_SETPCAP CAP_SYS_ADMIN";
mount.mountConfig.LockPersonality = true;
mount.mountConfig.MemoryDenyWriteExecute = true;
mount.mountConfig.NoNewPrivileges = true;
mount.mountConfig.ProtectClock = true;
mount.mountConfig.ProtectHostname = true;
mount.mountConfig.RemoveIPC = true;
mount.mountConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
# see `systemd-analyze filesystems` for a full list
mount.mountConfig.RestrictFileSystems = "@common-block @basic-api fuse";
mount.mountConfig.RestrictRealtime = true;
mount.mountConfig.RestrictSUIDSGID = true;
mount.mountConfig.SystemCallArchitectures = "native";
mount.mountConfig.SystemCallFilter = [
"@system-service"
"@mount"
"~@chown"
"~@cpu-emulation"
"~@keyring"
# could remove almost all io calls, however one has to keep `open`, and `write`, to communicate with the fuse device.
# so that's pretty useless as a way to prevent write access
];
mount.mountConfig.IPAddressDeny = "any";
mount.mountConfig.IPAddressAllow = "10.0.10.5";
mount.mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mount.mountConfig.DeviceAllow = "/dev/fuse";
# mount.mountConfig.RestrictNamespaces = true;
};
systemd.services."${systemdName}-reachable" = {
serviceConfig.ExecSearchPath = [ "/run/current-system/sw/bin" ];
serviceConfig.ExecStart = lib.escapeShellArgs [
"curlftpfs"
"ftp://servo-hn:/${subdir}"
"/dev/null"
"-o"
(lib.concatStringsSep "," ([ "exit_after_connect" ] ++ config.fileSystems."${localPath}".options))
];
serviceConfig.RemainAfterExit = true;
serviceConfig.Type = "oneshot";
unitConfig.BindsTo = [ "${systemdName}.mount" ];
# hardening (systemd-analyze security mnt-servo-playground-reachable.service)
serviceConfig.AmbientCapabilities = "";
serviceConfig.CapabilityBoundingSet = "";
serviceConfig.DynamicUser = true;
serviceConfig.LockPersonality = true;
serviceConfig.MemoryDenyWriteExecute = true;
serviceConfig.NoNewPrivileges = true;
serviceConfig.PrivateDevices = true;
serviceConfig.PrivateMounts = true;
serviceConfig.PrivateTmp = true;
serviceConfig.PrivateUsers = true;
serviceConfig.ProcSubset = "all";
serviceConfig.ProtectClock = true;
serviceConfig.ProtectControlGroups = true;
serviceConfig.ProtectHome = true;
serviceConfig.ProtectKernelModules = true;
serviceConfig.ProtectProc = "invisible";
serviceConfig.ProtectSystem = "strict";
serviceConfig.RemoveIPC = true;
serviceConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
# serviceConfig.RestrictFileSystems = "@common-block @basic-api"; #< NOPE
serviceConfig.RestrictRealtime = true;
serviceConfig.RestrictSUIDSGID = true;
serviceConfig.SystemCallArchitectures = "native";
serviceConfig.SystemCallFilter = [
"@system-service"
"@mount"
"~@chown"
"~@cpu-emulation"
"~@keyring"
# "~@privileged" #< NOPE
"~@resources"
# could remove some more probably
];
serviceConfig.IPAddressDeny = "any";
serviceConfig.IPAddressAllow = "10.0.10.5";
serviceConfig.DevicePolicy = "closed";
# exceptions
serviceConfig.ProtectHostname = false;
serviceConfig.ProtectKernelLogs = false;
serviceConfig.ProtectKernelTunables = false;
};
systemd.targets."${systemdName}-restart-timer" = {
# hack unit which, when started, stops the timer (if running), and then starts it again.
after = [ "${systemdName}.timer" ];
@@ -221,10 +353,11 @@ lib.mkMerge [
programs.fuse.userAllowOther = true; #< necessary for `allow_other` or `allow_root` options.
}
(remoteHome "crappy")
(remoteHome "desko")
(remoteHome "lappy")
(remoteHome "moby")
(ifSshAuthorized (remoteHome "crappy"))
(ifSshAuthorized (remoteHome "desko"))
(ifSshAuthorized (remoteHome "lappy"))
(ifSshAuthorized (remoteHome "moby"))
(ifSshAuthorized (remoteHome "servo"))
# 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/.

View File

@@ -63,6 +63,8 @@
sane.ids.nix-serve.uid = 2420;
sane.ids.nix-serve.gid = 2420;
sane.ids.plugdev.gid = 2421;
sane.ids.ollama.uid = 2422;
sane.ids.ollama.gid = 2422;
sane.ids.colin.uid = 1000;
sane.ids.guest.uid = 1100;

View File

@@ -122,7 +122,12 @@ in {
# fix NetworkManager-dispatcher to actually run as a daemon,
# and sandbox it a bit
systemd.services.NetworkManager-dispatcher = {
after = [ "trust-dns-localhost.service" ]; #< so that /var/lib/trust-dns will exist
#VVV so that /var/lib/trust-dns will exist (the hook needs to write here).
# but this creates a cycle: trust-dns-localhost > network.target > NetworkManager-dispatcher > trust-dns-localhost.
# (seemingly) impossible to remove the network.target dep on NetworkManager-dispatcher.
# beffore would be to have the dispatcher not write trust-dns files
# but rather just its own, and create a .path unit which restarts trust-dns appropriately.
# after = [ "trust-dns-localhost.service" ];
# serviceConfig.ExecStart = [
# "" # first blank line is to clear the upstream `ExecStart` field.
# "${cfg.package}/libexec/nm-dispatcher --persist" # --persist is needed for it to actually run as a daemon

View File

@@ -6,7 +6,6 @@ let
# nixpkgs' pam hardcodes unix_chkpwd path to the /run/wrappers one,
# but i don't want the wrapper, so undo that.
# ideally i would patch this via an overlay, but pam is in the bootstrap so that forces a full rebuild.
# TODO: add a `package` option to the nixos' pam module and substitute it that way.
postPatch = (if upstream.postPatch != null then upstream.postPatch else "") + ''
substituteInPlace modules/pam_unix/Makefile.am --replace-fail \
"/run/wrappers/bin/unix_chkpwd" "$out/bin/unix_chkpwd"
@@ -39,36 +38,29 @@ in
]));
};
options.security.pam.services = lib.mkOption {
apply = services: let
filtered = lib.filterAttrs (name: _: !(builtins.elem name [
# from <repo:nixos/nixpkgs:nixos/modules/security/pam.nix>
"i3lock"
"i3lock-color"
"vlock"
"xlock"
"xscreensaver"
"runuser"
"runuser-l"
# from ??
"chfn"
"chpasswd"
"chsh"
"groupadd"
"groupdel"
"groupmems"
"groupmod"
"useradd"
"userdel"
"usermod"
# from <repo:nixos/nixpkgs:nixos/modules/system/boot/systemd/user.nix>
"systemd-user" #< N.B.: this causes the `systemd --user` service manager to not be started!
])) services;
in lib.mapAttrs (_serviceName: service: service // {
# replace references with the old pam_unix, which calls into /run/wrappers/bin/unix_chkpwd,
# with a pam_unix that calls into unix_chkpwd via the nix store.
# TODO: use `security.pam.package` instead once <https://github.com/NixOS/nixpkgs/pull/314791> lands.
text = lib.replaceStrings [" pam_unix.so" ] [ " ${suidlessPam}/lib/security/pam_unix.so" ] service.text;
}) filtered;
apply = lib.filterAttrs (name: _: !(builtins.elem name [
# from <repo:nixos/nixpkgs:nixos/modules/security/pam.nix>
"i3lock"
"i3lock-color"
"vlock"
"xlock"
"xscreensaver"
"runuser"
"runuser-l"
# from ??
"chfn"
"chpasswd"
"chsh"
"groupadd"
"groupdel"
"groupmems"
"groupmod"
"useradd"
"userdel"
"usermod"
# from <repo:nixos/nixpkgs:nixos/modules/system/boot/systemd/user.nix>
"systemd-user" #< N.B.: this causes the `systemd --user` service manager to not be started!
]));
};
options.environment.systemPackages = lib.mkOption {
@@ -225,5 +217,7 @@ in
# systemd.packages = [ pkgs.lvm2 ];
# systemd.tmpfiles.packages = [ pkgs.lvm2.out ];
# environment.systemPackages = [ pkgs.lvm2 ];
security.pam.package = suidlessPam;
};
}

View File

@@ -47,6 +47,7 @@ in
"efibootmgr"
"errno"
"ethtool"
"evtest"
"fatresize"
"fd"
"file"
@@ -69,7 +70,7 @@ in
"killall"
"less"
"lftp"
# "libcap_ng" # for `netcap`
"libcap_ng" # for `netcap`, `pscap`, `captest`
"lsof"
"man-pages"
"man-pages-posix"
@@ -109,9 +110,6 @@ in
# "zfs" # doesn't cross-compile (requires samba)
];
sysadminExtraUtils = declPackageSet [
"backblaze-b2"
"duplicity"
"sane-scripts.backup"
"sqlite" # to debug sqlite3 databases
];
@@ -158,7 +156,7 @@ in
# "python3.pkgs.eyeD3" # music tagging
"ripgrep" # needed as a user package so that its user-level config file can be installed
"rsync"
"rsyslog" # KEEP THIS HERE if you want persistent logging
# "rsyslog" # KEEP THIS HERE if you want persistent logging (TODO: port to systemd, store in /var/log/...)
"sane-deadlines"
"sane-scripts.bittorrent"
"sane-scripts.cli"
@@ -283,7 +281,6 @@ in
# "emote"
# "evince" # PDF viewer
# "flare-signal" # gtk4 signal client
# "foliate" # e-book reader
"fractal" # matrix client
"g4music" # local music player
# "gnome.cheese"
@@ -314,14 +311,14 @@ in
"mpv"
"networkmanagerapplet" # for nm-connection-editor: it's better than not having any gui!
"ntfy-sh" # notification service
# "newsflash" # RSS viewer
"newsflash" # RSS viewer
"pavucontrol"
"pwvucontrol" # pipewire version of pavu
# "picard" # music tagging
# "libsForQt5.plasmatube" # Youtube player
"signal-desktop"
# "snapshot" # camera app
"spot" # Gnome Spotify client
# "spot" # Gnome Spotify client
# "sublime-music"
# "tdesktop" # broken on phosh
# "tokodon"
@@ -338,6 +335,7 @@ in
# "chatty" # matrix/xmpp/irc client (2023/12/29: disabled because broken cross build)
# "cozy" # audiobook player
"epiphany" # gnome's web browser
"foliate" # e-book reader
# "iotas" # note taking app
"komikku"
"koreader"
@@ -393,7 +391,7 @@ in
# "rhythmbox" # local music player
# "slic3r"
"soundconverter"
"spotify" # x86-only
# "spotify" # x86-only
"tor-browser" # x86-only
# "vlc"
"wireshark" # could maybe ship the cli as sysadmin pkg
@@ -529,6 +527,12 @@ in
ethtool.sandbox.method = "landlock";
ethtool.sandbox.capabilities = [ "net_admin" ];
evtest.sandbox.method = "bwrap";
evtest.sandbox.autodetectCliPaths = "existingFile"; # `evtest /dev/foo` to monitor events for a specific device
evtest.sandbox.extraPaths = [
"/dev/input"
];
# eza `ls` replacement
# bwrap causes `/proc` files to be listed differently (e.g. `eza /proc/sys/net/ipv6/conf/`)
# bwrap loses group info (so files owned by other users appear as owner "nobody")
@@ -623,6 +627,8 @@ in
"/tmp" # "Cannot open display:" if it can't mount /tmp 👀
];
gitea = {};
gnome-calculator.buildCost = 1;
gnome-calculator.sandbox.method = "bwrap";
gnome-calculator.sandbox.whitelistWayland = true;
@@ -808,6 +814,8 @@ in
"tmp"
];
landlock-sandboxer.sandbox.enable = false; #< sandbox helper
libcamera = {};
libcap.sandbox.enable = false; #< for `capsh`, which i use as a sandboxer
@@ -1093,7 +1101,17 @@ in
sqlite = {};
sshfs-fuse = {}; # used by fs.nix
sshfs-fuse.sandbox.enable = true; # used by fs.nix
sshfs-fuse.sandbox.method = "bwrap"; #< N.B. if you call this from the CLI -- without `mount.fuse` -- set this to `none`
sshfs-fuse.sandbox.net = "all";
sshfs-fuse.sandbox.autodetectCliPaths = "parent";
# sshfs-fuse.sandbox.extraPaths = [
# "/dev/fd" # fuse.mount3 -o drop_privileges passes us data over /dev/fd/3
# "/mnt" # XXX: not sure why i need all this, instead of just /mnt/desko, or /mnt/desko/home, etc
# ];
sshfs-fuse.sandbox.extraHomePaths = [
".ssh/id_ed25519" #< TODO: add -o foo,bar=path/to/thing style arguments to autodetection
];
strace.sandbox.enable = false; #< needs to `exec` its args, and therefore support *anything*
@@ -1238,7 +1256,7 @@ in
''
tryNotifyUser() {
local user="$1"
local new_path="$PATH:${pkgs.sudo}/bin:${pkgs.libnotify}/bin"
local new_path="$PATH:/etc/profiles/per-user/$user/bin:${pkgs.sudo}/bin:${pkgs.libnotify}/bin"
local version="$(cat $systemConfig/nixos-version)"
PATH="$new_path" sudo -u "$user" \
env PATH="$new_path" NIXOS_VERSION="$version" /bin/sh -c \
@@ -1246,7 +1264,7 @@ in
}
''
] ++ lib.mapAttrsToList
(user: en: lib.optionalString en "tryNotifyUser ${user}")
(user: en: lib.optionalString en "tryNotifyUser ${user} > /dev/null")
config.sane.programs.guiApps.enableFor.user
);
};

View File

@@ -10,14 +10,26 @@
# - `LD_LIBRARY_PATH=/nix/store/ngwj3jqmxh8k4qji2z0lj7y1f8vzqrn2-nss-mdns-0.15.1/lib getent hosts desko.local`
# nss-mdns goes through avahi-daemon, so there IS caching here
#
{ config, lib, ... }:
{ config, lib, pkgs, ... }:
{
sane.programs.avahi = {
packageUnwrapped = pkgs.avahi.overrideAttrs (upstream: {
# avahi wants to do its own sandboxing opaque to systemd & maybe in conflict with my bwrap.
# --no-drop-root disables that, so that i can e.g. run it as User=avahi, etc.
# do this here, because the service isn't so easily patched.
postInstall = (upstream.postInstall or "") + ''
wrapProgram "$out/sbin/avahi-daemon" \
--add-flags --no-drop-root
'';
nativeBuildInputs = upstream.nativeBuildInputs ++ [
pkgs.makeBinaryWrapper
];
});
sandbox.method = "bwrap";
sandbox.whitelistDbus = [ "system" ];
sandbox.net = "all"; #< otherwise it will show 'null' in place of each interface name.
sandbox.extraPaths = [
"/" #< else the daemon exits immediately. TODO: decrease this scope.
"/" #< TODO: decrease this, but be weary that the daemon might exit immediately
];
};
services.avahi = lib.mkIf config.sane.programs.avahi.enabled {
@@ -40,4 +52,40 @@
"wlp4s0" #< desko
];
};
systemd.services.avahi-daemon = lib.mkIf config.sane.programs.avahi.enabled {
# hardening: see `systemd-analyze security avahi-daemon`
serviceConfig.User = "avahi";
serviceConfig.Group = "avahi";
serviceConfig.AmbientCapabilities = "";
serviceConfig.CapabilityBoundingSet = "";
serviceConfig.LockPersonality = true;
serviceConfig.MemoryDenyWriteExecute = true;
serviceConfig.NoNewPrivileges = true;
serviceConfig.PrivateDevices = true;
serviceConfig.PrivateMounts = true;
serviceConfig.PrivateTmp = true;
serviceConfig.PrivateUsers = true;
serviceConfig.ProcSubset = "all";
serviceConfig.ProtectClock = true;
serviceConfig.ProtectControlGroups = true;
serviceConfig.ProtectHome = true;
serviceConfig.ProtectHostname = true;
serviceConfig.ProtectKernelLogs = true;
serviceConfig.ProtectKernelModules = true;
serviceConfig.ProtectKernelTunables = true;
serviceConfig.ProtectProc = "noaccess";
serviceConfig.ProtectSystem = "strict";
serviceConfig.RemoveIPC = true; #< this *might* slow down the initial connection?
serviceConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
serviceConfig.RestrictRealtime = true;
serviceConfig.RestrictSUIDSGID = true;
serviceConfig.SystemCallArchitectures = "native";
serviceConfig.SystemCallFilter = [
"@system-service"
"@mount"
"~@resources"
# "~@privileged"
];
};
}

View File

@@ -24,7 +24,23 @@ in
};
};
packageUnwrapped = pkgs.rmDbusServicesInPlace (pkgs.calls.overrideAttrs (upstream: {
packageUnwrapped = pkgs.rmDbusServicesInPlace ((pkgs.calls.override {
gtk3 = pkgs.gtk4;
libpeas = pkgs.libpeas2;
wrapGAppsHook3 = pkgs.wrapGAppsHook4;
}).overrideAttrs (upstream: {
# XXX(2024-08-08): v46.3 has a bug where if it has no network connection on launch, it forever stays disconnected & never retries
version = "47_beta.0-unstable-2024-08-08";
src = lib.warnIf (lib.versionOlder "47.0" upstream.version) "gnome-calls outdated; remove src override? (keep UI patches though!)" pkgs.fetchFromGitLab {
domain = "gitlab.gnome.org";
owner = "GNOME";
repo = "calls";
fetchSubmodules = true;
# rev = "main";
rev = "ff213579a52222e7c95e585843d97b5b817b2a8b";
hash = "sha256-0QYC8FJpfg/X2lIjBDooba2idUfpJNQhcpv8Z5I/B4k=";
};
patches = (upstream.patches or []) ++ [
(pkgs.fetchpatch {
# usability improvement... if the UI is visible, then i can receive calls. otherwise, i can't!
@@ -33,6 +49,14 @@ in
hash = "sha256-NoVQV2TlkCcsBt0uwSyK82hBKySUW4pADrJVfLFvWgU=";
})
];
nativeBuildInputs = upstream.nativeBuildInputs ++ [
pkgs.dbus #< for dbus-run-session (should be test only, but it's not)
];
buildInputs = upstream.buildInputs ++ [
pkgs.libadwaita
];
}));
sandbox.method = "bwrap";

View File

@@ -1,35 +1,8 @@
{ pkgs, ... }:
{
sane.programs.curlftpfs = {
packageUnwrapped = pkgs.curlftpfs.overrideAttrs (upstream: {
# my fork includes:
# - per-operation timeouts (CURLOPT_TIMEOUT; would use CURLOPT_LOW_SPEED_TIME/CURLOPT_LOW_SPEED_LIMIT but they don't apply)
# - exit on timeout (so that one knows to abort the mount, instead of waiting indefinitely)
# - support for "meta" keys found in /etc/fstab
src = pkgs.fetchFromGitea {
domain = "git.uninsane.org";
owner = "colin";
repo = "curlftpfs";
rev = "0890d32e709b5a01153f00d29ed4c00299744f5d";
hash = "sha256-M28PzHqEAkezQdtPeL16z56prwl3BfMZqry0dlpXJls=";
};
# `mount` clears PATH before calling the mount helper (see util-linux/lib/env.c),
# so the traditional /etc/fstab approach of fstype=fuse and device = curlftpfs#URI doesn't work.
# instead, install a `mount.curlftpfs` mount helper. this is what programs like `gocryptfs` do.
postInstall = (upstream.postInstall or "") + ''
ln -s curlftpfs $out/bin/mount.fuse.curlftpfs
ln -s curlftpfs $out/bin/mount.curlftpfs
'';
});
# TODO: try to sandbox this better? maybe i can have fuse (unsandboxed) invoke curlftpfs (sandboxed)?
# - landlock gives EPERM
# - bwrap just silently doesn't mount it, maybe because of setuid stuff around fuse?
# sandbox.method = "capshonly";
# sandbox.net = "all";
# sandbox.capabilities = [
# "sys_admin"
# "sys_module"
# ];
packageUnwrapped = pkgs.curlftpfs-sane;
sandbox.method = "bwrap";
sandbox.net = "all";
};
}

View File

@@ -46,7 +46,9 @@
./fcitx5.nix
./feedbackd.nix
./firefox.nix
./firefox-xdg-open.nix
./flare-signal.nix
./foliate.nix
./fontconfig.nix
./fractal.nix
./free.nix
@@ -66,6 +68,7 @@
./gnome-maps.nix
./gnome-weather.nix
./go2tv.nix
./gocryptfs.nix
./gpodder.nix
./gpsd.nix
./gps-share.nix
@@ -179,6 +182,7 @@
./wvkbd.nix
./xarchiver.nix
./xdg-desktop-portal.nix
./xdg-desktop-portal-gnome
./xdg-desktop-portal-gtk.nix
./xdg-desktop-portal-wlr.nix
./xdg-terminal-exec.nix

View File

@@ -0,0 +1,13 @@
{ pkgs, ... }:
{
sane.programs.firefox-xdg-open = {
packageUnwrapped = pkgs.firefox-extensions.firefox-xdg-open.systemComponent;
sandbox.method = "bwrap";
sandbox.whitelistDbus = [ "user" ]; # for xdg-open/portals
mime.associations."x-scheme-handler/xdg-open" = "xdg-open.desktop";
suggestedPrograms = [ "xdg-utils" ];
};
}

View File

@@ -113,9 +113,20 @@ let
name = "${cfg.browser.libName}-in-vpn";
desktopName = "${cfg.browser.libName} (VPN)";
genericName = "Web Browser";
# N.B.: --new-instance ensures we don't reuse an existing non-vpn instance.
# OTOH, it may error about "only one instance can run at a time": close the non-VPN instance if you see that.
exec = "${lib.getExe pkgs.sane-scripts.vpn} do - -- ${cfg.browser.libName} --new-instance";
# N.B.: --new-instance ensures we don't reuse an existing differenty-namespaced instance.
# OTOH, it may error about "only one instance can run at a time": close the other instance if you see that.
exec = "${lib.getExe pkgs.sane-scripts.vpn} do default -- ${cfg.browser.libName} --new-instance";
icon = cfg.browser.libName;
categories = [ "Network" "WebBrowser" ];
type = "Application";
})
(pkgs.makeDesktopItem {
name = "${cfg.browser.libName}-stub-dns";
desktopName = "${cfg.browser.libName} (stub DNS)";
genericName = "Web Browser";
# N.B.: --new-instance ensures we don't reuse an existing differently-namespaced instance.
# OTOH, it may error about "only one instance can run at a time": close the other instance if you see that.
exec = "${lib.getExe pkgs.sane-scripts.vpn} do none -- ${cfg.browser.libName} --new-instance";
icon = cfg.browser.libName;
categories = [ "Network" "WebBrowser" ];
type = "Application";
@@ -220,6 +231,10 @@ in
package = pkgs.firefox-extensions.ether-metamask;
enable = lib.mkDefault false; # until i can disable the first-run notification
};
firefox-xdg-open = {
package = pkgs.firefox-extensions.firefox-xdg-open;
enable = lib.mkDefault true;
};
i2p-in-private-browsing = {
package = pkgs.firefox-extensions.i2p-in-private-browsing;
enable = lib.mkDefault config.services.i2p.enable;
@@ -287,6 +302,7 @@ in
fs.".config/sops".dir = lib.mkIf cfg.addons.browserpass-extension.enable {}; #< needs to be created, not *just* added to the sandbox
suggestedPrograms = [
"firefox-xdg-open"
"open-in-mpv"
];

View File

@@ -1,12 +1,16 @@
# Flare is a 3rd-party GTK4 Signal app.
# UI is effectively a clone of Fractal.
#
### compatibility:
### compatibility (2023-10-30):
# - desko: works fine. pairs, and exchanges contact list (but not message history) with the paired device. exchanges future messages fine.
# - moby (cross compiled flare-signal-nixified): nope. it pairs, but can only *receive* messages and never *send* them.
# - even `rsync`ing the data and keyrings from desko -> moby, still fails in that same manner.
# - console shows error messages. quite possibly an endianness mismatch somewhere
# - moby (partially-emulated flare-signal): works! pairs and can send/receive messages, same as desko.
### compatibility (2024-08-07):
# - linking flare to iOS signal "works", but neither side can exchange messages nor contacts
# in iOS i see "A message from Colin could not be delivered"
# - registering as primary device does not work ("you are not authorized", or some such)
#
### debugging:
# - `RUST_LOG=flare=trace flare`
@@ -18,7 +22,7 @@
# ERROR presage::manager] Error opening envelope: ProtobufDecodeError(DecodeError { description: "invalid tag value: 0", stack: [("Content", "data_message")] }), message will be skipped!
# ERROR presage::manager] Error opening envelope: ProtobufDecodeError(DecodeError { description: "invalid tag value: 0", stack: [("Content", "data_message")] }), message will be skipped!
# ```
# - this occurs on moby, desko, `flare-signal` and `flare-signal-nixified`
# - this occurs on moby, desko, `flare-signal` and `flare-signal-nixified` (2023-12-14)
# - the Websocket error seems to be unrelated, occurs during normal/good operation
# - related issues: <https://github.com/whisperfish/presage/issues/152>
#
@@ -28,7 +32,7 @@
# No current session
# ERROR presage::manager] Error opening envelope: SignalProtocolError(InvalidKyberPreKeyId), message will be skipped!
# ```
# - but signal iOS will still read it.
# - but signal iOS will still read it (2023-12-14).
#
#### HTTP 405 when linking flare to iOS signal:
# [DEBUG libsignal_service_hyper::push_service] HTTP request PUT https://chat.signal.org/v1/devices/{uuid}.{timestamp?}:{b64-string}
@@ -43,7 +47,7 @@
# ),
# ),
# )
# flare matrix suggests the signal endpoint has changed:
# flare matrix suggests the signal endpoint has changed (2023-12-14):
# - "/v1/device/link instead of confirming via /v1/devices/{I'd}"
# - this endpoint is declared in libsignal-service-rs (used both by flare and presage)
# - libsignal-service/src/provisioning/manager.rs
@@ -73,5 +77,13 @@
# and it persists some dconf settings (e.g. device name). reset with:
# - `dconf reset -f /de/schmidhuberj/Flare/`.
];
#VVV flare complains if its data directory is a symlink, so put it in a subdirectory behind my persistence symlink.
env.FLARE_DATA_PATH = "$HOME/.local/share/flare/data";
# sandbox.method = "bwrap";
# sandbox.net = "clearnet";
# sandbox.whitelistWayland = true;
# sandbox.whitelistDbus = [
# "user" # so i can click on links, at least
# ];
};
}

View File

@@ -0,0 +1,42 @@
# foliate: <https://johnfactotum.github.io/foliate/>
{ ... }:
{
sane.programs.foliate = {
sandbox.method = "bwrap";
sandbox.net = "clearnet"; #< for dictionary, wikipedia, online book libraries
sandbox.whitelistDbus = [ "user" ]; #< when clicking on links
sandbox.whitelistDri = true; # reduces startup time and subjective page flip time
sandbox.whitelistWayland = true;
sandbox.extraHomePaths = [
"Books/local"
"Books/servo"
"tmp" #< for downloaded files
];
sandbox.extraPaths = [
# foliate sandboxes itself with bwrap, which needs these.
# but it actually only cares that /sys/{block,bus,class/block} *exist*: it doesn't care if there's anything in them.
# so bind empty (sub)directories
# and it looks like i might need to keep IPC namespace if i want TTS.
"/sys/block/loop7"
"/sys/bus/container/devices"
"/sys/class/block/loop7"
];
sandbox.autodetectCliPaths = "existing";
persist.byStore.plaintext = [
".local/share/com.github.johnfactotum.Foliate" #< books added, reading position
".cache/com.github.johnfactotum.Foliate" #< webkit cache
];
buildCost = 2; #< webkitgtk 6.0
# these associations were taken from its .desktop file
mime.associations."application/epub+zip" = "com.github.johnfactotum.Foliate.desktop";
mime.associations."application/x-mobipocket-ebook" = "com.github.johnfactotum.Foliate.desktop";
mime.associations."application/vnd.amazon.mobi8-ebook" = "com.github.johnfactotum.Foliate.desktop";
mime.associations."application/x-fictionbook+xml" = "com.github.johnfactotum.Foliate.desktop";
mime.associations."application/x-zip-compressed-fb2" = "com.github.johnfactotum.Foliate.desktop";
mime.associations."application/vnd.comicbook+zip" = "com.github.johnfactotum.Foliate.desktop"; # .cbz
mime.associations."x-scheme-handler/opds" = "com.github.johnfactotum.Foliate.desktop";
mime.priority = 120; #< default is 100; fallback to more specialized cbz handlers, e.g., but keep specializations for epub
};
}

View File

@@ -65,6 +65,9 @@ in
suggestedPrograms = [ "gnome-keyring" ];
# direct room links opened from other programs, to fractal.
mime.urlAssociations."^https?://matrix.to/#/.+$" = "org.gnome.Fractal.desktop";
services.fractal = {
description = "fractal Matrix client";
partOf = lib.mkIf cfg.config.autostart [ "graphical-session" ];

View File

@@ -10,7 +10,7 @@
];
sandbox.capabilities = [
# ipc_lock: used to `mlock` the secrets so they don't get swapped out.
# this is optional, and systemd likely doesn't propagate it anyway
# this is optional, and user namespacing (bwrap) likely doesn't propagate it anyway
"ipc_lock"
];

View File

@@ -0,0 +1,23 @@
{ ... }:
{
sane.programs.gocryptfs = {
sandbox.method = "landlock";
sandbox.autodetectCliPaths = "existing";
sandbox.capabilities = [
# CAP_SYS_ADMIN is only required if directly invoking gocryptfs
# i.e. not leverage a mount helper like `mount.fuse3-sane`.
"sys_admin"
"chown"
"dac_override"
"dac_read_search"
"fowner"
"lease"
"mknod"
"setgid"
"setuid"
];
suggestedPrograms = [
"util-linux" #< gocryptfs complains that it can't exec `logger`, otherwise
];
};
}

View File

@@ -25,6 +25,12 @@ in
"jq"
# and systemd, for udevadm
];
sandbox.method = "bwrap";
sandbox.net = "all";
sandbox.autodetectCliPaths = "existing"; #< N.B.: `test -f /dev/ttyUSB1` fails, we can't use `existingFile`
sandbox.whitelistDbus = [ "system" ]; #< to register with Avahi
services.gps-share = {
description = "gps-share: make local GPS serial readings available over Avahi";
# usage:
@@ -46,10 +52,6 @@ in
partOf = [ "gps" ];
depends = [ "eg25-control-powered" ];
};
sandbox.method = "bwrap";
sandbox.net = "all";
sandbox.autodetectCliPaths = "existing"; #< N.B.: `test -f /dev/ttyUSB1` fails, we can't use `existingFile`
};
# TODO: restrict this to just LAN devices!!

View File

@@ -16,7 +16,7 @@
sandbox.whitelistDri = true; #< required
sandbox.whitelistWayland = true;
buildCost = 2;
buildCost = 2; # webkitgtk
secrets.".local/share/komikku/keyrings/plaintext.keyring" = ../../../secrets/common/komikku_accounts.json.bin;
# downloads end up here, and without the toplevel database komikku doesn't know they exist.
@@ -27,5 +27,8 @@
persist.byStore.ephemeral = [
".cache/komikku"
];
# XXX(2024-08-08): komikku can handle URLs from sources it understands (maybe), but not files (even if encoded as file:// URI)
# mime.associations."application/vnd.comicbook+zip" = "info.febvre.Komikku.desktop"; # .cbz
};
}

View File

@@ -4,5 +4,15 @@
sandbox.method = "bwrap";
sandbox.autodetectCliPaths = "existingFile";
env.PAGER = "less";
# LESS flags:
# - F = quit if output fits on one screen
# - K = exit on ctrl+c
# - M = "long prompt"
# - R = output raw control characters
# - S = chop long lines instead of wrapping
# - X = Don't use termcap init/deinit strings (hence, `less` output is visible on the terminal even after exiting)
# SYSTEMD_LESS defaults to FRSXMK
env.LESS = "FRMK";
env.SYSTEMD_LESS = "FRMK"; #< used by journalctl
};
}

View File

@@ -243,13 +243,16 @@ in
mime.associations."video/webm" = "mpv.desktop";
mime.associations."video/x-flv" = "mpv.desktop";
mime.associations."video/x-matroska" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtu.be/.+" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtube.com/embed/.+" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtube.com/playlist\?.*list=.+" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtube.com/shorts/.+" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtube.com/v/.+" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtube.com/watch\?.*v=.+" = "mpv.desktop";
#v be the opener for YouTube videos
mime.urlAssociations."^https?://(m\.)?(www\.)?youtu.be/.+$" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtube.com/embed/.+$" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtube.com/playlist\?.*list=.+$" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtube.com/shorts/.+$" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtube.com/v/.+$" = "mpv.desktop";
mime.urlAssociations."^https?://(m\.)?(www\.)?youtube.com/watch\?.*v=.+$" = "mpv.desktop";
#v be the opener for A/V, generally. useful for e.g. feed readers like News Flash which open content through the portal
mime.urlAssociations."^https?://.*\.(mp3|mp4|ogg|ogv|opus|webm)(\\?.*)?$" = "mpv.desktop";
#v Loupe image viewer can't open URIs, so use mpv instead
mime.urlAssociations."^https?://i\.imgur.com/.+" = "mpv.desktop";
mime.urlAssociations."^https?://i\.imgur.com/.+$" = "mpv.desktop";
};
}

View File

@@ -1,8 +1,10 @@
# news-flash RSS viewer
# news-flash RSS viewer (exe: `io.gitlab.news_flash.NewsFlash`)
# - feeds have to be manually imported:
# - Local RSS -> Import OPML -> ~/.config/newsflashFeeds.opml
# - clicking article-embedded links doesn't work because of xdg portal stuff
# - need to either run unsandboxed, or install a org.freedesktop.portal.OpenURI handler
# option may be greyed out on first run: just restart it.
# takes about 20 minutes to import results from scratch.
# TODO: auto-import feeds
# - `newsflash -s` might allow importing individual feeds; not removing them, though
{ config, sane-lib, ... }:
let
@@ -13,8 +15,31 @@ let
wanted-feeds = feeds.filterByFormat [ "text" "image" "podcast" "video" ] all-feeds;
in {
sane.programs.newsflash = {
sandbox.method = "bwrap";
sandbox.net = "clearnet";
sandbox.whitelistAudio = true; #< for embedded videos
sandbox.whitelistDbus = [ "user" ];
sandbox.whitelistDri = true;
sandbox.whitelistWayland = true;
sandbox.extraPaths = [
# the app sandboxes itself with bwrap, which needs these.
# but it actually only cares that /sys/{block,bus,class/block} *exist*: it doesn't care if there's anything in them.
# so bind empty (sub)directories
"/sys/block/loop7"
"/sys/bus/container/devices"
"/sys/class/block/loop7"
];
buildCost = 2; # mainly for desktop: webkitgtk-6.0
persist.byStore.plaintext = [ ".local/share/news-flash" ];
persist.byStore.plaintext = [
".local/share/news-flash" #< sqlite database, the actually important stuff
# ".local/share/news_flash" #< device IDs (?)
".config/news-flash" #< includes `"backend": "local_rss"`
];
persist.byStore.ephemeral = [
".cache/news_flash" #< WebKit cache
];
#v for *manual* use:
fs.".config/newsflashFeeds.opml".symlink.text =
feeds.feedsToOpml wanted-feeds
;

View File

@@ -5,6 +5,9 @@
# since that component needs to run in initrd and before service setup.
#
# TODO: log rotation / retention policy. don't want to eat the whole HDD.
# TODO: store these logs in /var/log/...
# and at that point it makes more sense to use a systemd service.
# i.e. revert `3a6a5ffe014761ff23220f5b4ecb74d8a9fdb8fd`
{ config, lib, ... }:
{
sane.programs.rsyslog = {

View File

@@ -19,9 +19,12 @@ in
sandbox.extraHomePaths = [ "knowledge/planner/deadlines.tsv" ];
fs.".profile".symlink.text = lib.mkIf cfg.config.showOnLogin ''
if [ -z "$SSH_TTY" ]; then
sane-deadlines
fi
maybeShowDeadlines() {
if [ -z "$SSH_TTY" ]; then
sane-deadlines
fi
}
sessionCommands+=('maybeShowDeadlines')
'';
};
}

View File

@@ -7,10 +7,6 @@ let
in
{
sane.programs = {
"sane-scripts.backup" = declPackageSet [
"sane-scripts.backup-ls"
"sane-scripts.backup-restore"
];
"sane-scripts.bittorrent" = declPackageSet [
"sane-scripts.bt-add"
"sane-scripts.bt-rm"
@@ -46,9 +42,6 @@ in
"sane-scripts.sync-music"
];
"sane-scripts.backup-ls" = {};
"sane-scripts.backup-restore" = {};
"sane-scripts.bt-add".sandbox = {
method = "bwrap";
autodetectCliPaths = "existing"; #< for adding a .torrent from disk

View File

@@ -7,11 +7,14 @@
"/sys/devices"
];
fs.".profile".symlink.text = ''
# show ssh users the current resource usage.
# especially useful for moby (to see battery)
if [ -n "$SSH_TTY" ]; then
sane-sysload
fi
maybeShowSysload() {
# show ssh users the current resource usage.
# especially useful for moby (to see battery)
if [ -n "$SSH_TTY" ]; then
sane-sysload
fi
}
sessionCommands+=('maybeShowSysload')
'';
};
}

View File

@@ -22,11 +22,12 @@ in
iptables = cfg.iptables.package;
libcap = cfg.libcap.package;
passt = cfg.passt.package;
landlock-sandboxer = pkgs.landlock-sandboxer.override {
# not strictly necessary (landlock ABI is versioned), however when sandboxer version != kernel version,
# the sandboxer may nag about one or the other wanting to be updated.
linux = config.boot.kernelPackages.kernel;
};
landlock-sandboxer = cfg.landlock-sandboxer.package;
# landlock-sandboxer = pkgs.landlock-sandboxer.override {
# # not strictly necessary (landlock ABI is versioned), however when sandboxer version != kernel version,
# # the sandboxer may nag about one or the other wanting to be updated.
# linux = config.boot.kernelPackages.kernel;
# };
}).overrideAttrs (base: {
# create a directory which holds just the `sanebox` so that we
# can add sanebox as a dependency to binaries via `PATH=/run/current-system/libexec/sanebox` without forcing rebuild every time sanebox changes

View File

@@ -1,16 +1,26 @@
{ config, lib, ... }:
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.seatd;
seatdDir = "/run/seatd";
seatdSock = "${seatdDir}/seatd.sock";
in
lib.mkMerge [
{
sane.programs.seatd = {
packageUnwrapped = pkgs.seatd.overrideAttrs (base: {
# patch so seatd places its socket in a place that's easier to sandbox
mesonFlags = base.mesonFlags ++ [
"-Ddefaultpath=${seatdSock}"
];
});
sandbox.method = "bwrap";
sandbox.capabilities = [
"sys_tty_config" "sys_admin"
"chown"
"dac_override" #< TODO: is there no way to get rid of this?
# "chown"
"dac_override" #< TODO: is there no way to get rid of this? (use the `tty` group?)
# "sys_admin"
"sys_tty_config"
];
sandbox.isolateUsers = false;
sandbox.extraPaths = [
"/dev" #< TODO: this can be removed if i have seatd restart on client error such that seatd can discover devices as they appear
# "/dev/dri"
@@ -23,28 +33,48 @@ lib.mkMerge [
# "/dev/tty0"
# "/dev/tty1"
# "/proc"
"/run" #< TODO: confine this to some subdirectory
seatdDir
# "/sys"
];
env.SEATD_SOCK = seatdSock; #< client side configuration (i.e. tells sway where to look)
};
}
(lib.mkIf cfg.enabled {
users.groups.seat = {};
# TODO: /run/seatd.sock location can be configured, but only via compile-time flag
sane.fs."${seatdDir}".dir.acl = {
user = "root";
group = "seat";
mode = "0770";
};
systemd.services.seatd = {
description = "Seat management daemon";
documentation = [ "man:seatd(1)" ];
after = [ config.sane.fs."${seatdDir}".unit ];
wants = [ config.sane.fs."${seatdDir}".unit ];
wantedBy = [ "multi-user.target" ];
restartIfChanged = false;
serviceConfig = {
Type = "simple";
ExecStart = "${cfg.package}/bin/seatd -g seat";
Group = "seat";
# AmbientCapabilities = [ "CAP_SYS_TTY_CONFIG" "CAP_SYS_ADMIN" ];
};
serviceConfig.Type = "simple";
serviceConfig.ExecStart = "${cfg.package}/bin/seatd -g seat";
serviceConfig.Group = "seat";
# serviceConfig.AmbientCapabilities = [
# "CAP_DAC_OVERRIDE"
# "CAP_NET_ADMIN"
# "CAP_SYS_ADMIN"
# "CAP_SYS_TTY_CONFIG"
# ];
serviceConfig.CapabilityBoundingSet = [
# TODO: these can probably be reduced if i switch to landlock for sandboxing,
# or run as a user other than root
# "CAP_CHOWN"
"CAP_DAC_OVERRIDE" #< needed, to access /dev/tty
"CAP_NET_ADMIN" #< needed by bwrap, for some reason??
"CAP_SYS_ADMIN" #< needed by bwrap
"CAP_SYS_TTY_CONFIG"
];
};
})
]

View File

@@ -165,7 +165,8 @@ for_window [workspace="TV"] fullscreen enable
# mostly, messengers belong on WS 1
assign [app_id="abaddon"] workspace number 1
assign [app_id="geary"] workspace number 1
assign [app_id="gnome-calls"] workspace number 1
assign [app_id="gnome-calls"] workspace number 1 # gnome-calls <= 46.3
assign [app_id="org.gnome.Calls"] workspace number 1 # gnome-calls >= 47.0-beta
assign [app_id="im.dino.Dino"] workspace number 1
assign [app_id="org.gnome.Fractal"] workspace number 1
assign [app_id="signal"] workspace number 1

View File

@@ -172,6 +172,7 @@ in
"wireplumber" # used by sway config
"wl-clipboard"
"xdg-desktop-portal"
"xdg-desktop-portal-gnome"
# xdg-desktop-portal-gtk provides portals for:
# - org.freedesktop.impl.portal.Access
# - org.freedesktop.impl.portal.Account
@@ -186,7 +187,7 @@ in
# - org.freedesktop.impl.portal.Lockdown (@lockdown_iface@)
# - org.freedesktop.impl.portal.Settings (@settings_iface@)
# - org.freedesktop.impl.portal.Wallpaper (@wallpaper_iface@)
"xdg-desktop-portal-gtk"
# "xdg-desktop-portal-gtk"
# xdg-desktop-portal-wlr provides portals for screenshots/screen sharing
"xdg-desktop-portal-wlr"
"xdg-terminal-exec" # used by sway config
@@ -208,7 +209,7 @@ in
];
sandbox.extraPaths = [
# "/dev/input"
"/run/seatd.sock" #< required if not using `logind` systemd login manager
"/run/seatd" #< required if not using `logind` systemd login manager
# "/run/systemd/sessions"
"/run/udev"
"/sys/class/backlight"
@@ -226,7 +227,7 @@ in
fs.".config/xdg-desktop-portal/sway-portals.conf".symlink.text = ''
# portals.conf docs: <https://flatpak.github.io/xdg-desktop-portal/docs/portals.conf.html>
[preferred]
default=wlr;gtk
default=wlr;gnome;gtk
'';
fs.".config/sway/config".symlink.target = pkgs.substituteAll {

View File

@@ -18,6 +18,6 @@
persist.byStore.ephemeral = [
".local/share/tor-browser"
];
mime.urlAssociations."^https?://.+\.onion" = "torbrowser.desktop";
mime.urlAssociations."^https?://.+\.onion$" = "torbrowser.desktop";
};
}

View File

@@ -0,0 +1,30 @@
# XXX(2024-08-07): xdg-desktop-portal-gnome has a nicer filechooser than xdg-desktop-portal-gtk.
# especially, mobile friendly.
# but starting with 47.0 (unreleased), it will switch to Nautilus. so expect some work in porting.
{ config, pkgs, ... }:
let
cfg = config.sane.programs.xdg-desktop-portal-gnome;
in
{
sane.programs.xdg-desktop-portal-gnome = {
packageUnwrapped = pkgs.xdg-desktop-portal-gnome.overrideAttrs (base: {
patches = (base.patches or []) ++ [
./init_display_no_mutter.diff
];
});
fs.".config/xdg-desktop-portal/portals/gnome.portal".symlink.target =
"${cfg.packageUnwrapped}/share/xdg-desktop-portal/portals/gnome.portal";
# XXX: overcome bug when manually setting `$XDG_DESKTOP_PORTAL_DIR`
# which causes *.portal files to be looked for in the toplevel instead of under `portals/`
fs.".config/xdg-desktop-portal/gnome.portal".symlink.target = "portals/gnome.portal";
services.xdg-desktop-portal-gnome = {
description = "xdg-desktop-portal-gnome backend (provides file chooser and other functionality for xdg-desktop-portal)";
dependencyOf = [ "xdg-desktop-portal" ];
command = "XDG_SESSION_TYPE=wayland ${cfg.package}/libexec/xdg-desktop-portal-gnome";
readiness.waitDbus = "org.freedesktop.impl.portal.desktop.gnome";
};
};
}

View File

@@ -0,0 +1,38 @@
diff --git a/src/externalwindow-wayland.c b/src/externalwindow-wayland.c
index 4ed62c7..329b9a8 100644
--- a/src/externalwindow-wayland.c
+++ b/src/externalwindow-wayland.c
@@ -259,33 +259,6 @@ init_external_window_wayland_display (GError **error)
g_autofree char *fd_str = NULL;
GdkDisplay *display;
- proxy = org_gnome_mutter_service_channel_proxy_new_for_bus_sync (
- G_BUS_TYPE_SESSION,
- (G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START |
- G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
- G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS),
- "org.gnome.Mutter.ServiceChannel",
- "/org/gnome/Mutter/ServiceChannel",
- NULL, error);
- if (!proxy)
- return NULL;
-
- if (!org_gnome_mutter_service_channel_call_open_wayland_service_connection_sync (
- proxy,
- SERVICE_CLIENT_TYPE_PORTAL_BACKEND,
- NULL,
- &fd_variant,
- &fd_list,
- NULL, error))
- return NULL;
-
- fd = g_unix_fd_list_get (fd_list, g_variant_get_handle (fd_variant), error);
- if (fd < 0)
- return NULL;
-
- fd_str = g_strdup_printf ("%d", fd);
-
- g_setenv ("WAYLAND_SOCKET", fd_str, TRUE);
gdk_set_allowed_backends ("wayland");
display = gdk_display_open (NULL);
g_assert (display);

View File

@@ -12,6 +12,7 @@
".local/share/zathura"
];
mime.priority = 150; #< default is 100; fallback to more specialized cbz handlers, e.g.
mime.associations."application/pdf" = "org.pwmt.zathura.desktop";
mime.associations."application/vnd.comicbook+zip" = "org.pwmt.zathura.desktop"; # .cbz
mime.associations."application/vnd.comicbook-rar" = "org.pwmt.zathura.desktop"; # .cbr

View File

@@ -74,4 +74,38 @@ in
# DefaultTimeoutStopSec defaults to 90s, and frequently blocks overall system shutdown.
DefaultTimeoutStopSec=${builtins.toString haltTimeout}
'';
# hard base systemd services
# see: `systemd-analyze security`
systemd.services.systemd-rfkill.serviceConfig = {
AmbientCapabilities = "";
CapabilityBoundingSet = "";
DevicePolicy = "closed";
IPAddressDeny = "any";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateNetwork = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = "AF_UNIX";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
};
}

View File

@@ -118,4 +118,8 @@
sane.users.colin.default = true;
services.getty.autologinUser = lib.mkDefault "colin";
security.pam.services.login.startSession = lib.mkForce false; #< disable systemd integration
# systemd-user-sessions depends on remote-fs, causing login to take stupidly long
systemd.services."systemd-user-sessions".enable = false;
}

View File

@@ -249,14 +249,13 @@ in
# rtl_bt (bluetooth)
# anx7688-fw.bin (USB-C chip: power negotiation, HDMI/dock)
# ov5640_af.bin (camera module)
# hardware.firmware = [ config.mobile.device.firmware ];
# hardware.firmware = [ pkgs.rtl8723cs-firmware ];
hardware.firmware = [
(pkgs.linux-firmware-megous.override {
# rtl_bt = false probably means no bluetooth connectivity.
# N.B.: DON'T RE-ENABLE without first confirming that wake-on-lan works during suspend (rtcwake).
# it seems the rtl_bt stuff ("bluetooth coexist") might make wake-on-LAN radically more flaky.
rtl_bt = false;
# rtl_bt = false;
})
];

View File

@@ -1,7 +1,6 @@
{ ... }:
{
imports = [
./duplicity.nix
./rsync-net
];
}

View File

@@ -1,98 +0,0 @@
# docs: https://search.nixos.org/options?channel=21.11&query=duplicity
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.sane.services.duplicity;
in
{
options = {
sane.services.duplicity.enable = mkOption {
default = false;
type = types.bool;
};
};
config = mkIf cfg.enable {
# we need this mostly because of the size of duplicity's cache
sane.persist.sys.byStore.ephemeral = [{
path = "/var/lib/duplicity";
user = "root";
group = "root";
mode = "0700";
}];
services.duplicity.enable = true;
services.duplicity.targetUrl = "$DUPLICITY_URL";
# format: PASSPHRASE=<cleartext> \n DUPLICITY_URL=b2://...
# two sisters
# PASSPHRASE: remote backups will be encrypted using this passphrase (using gpg)
# DUPLICITY_URL: b2://$key_id:$app_key@$bucket
# create key with: backblaze-b2 create-key --bucket uninsane-host-duplicity uninsane-host-duplicity-safe listBuckets,listFiles,readBuckets,readFiles,writeFiles
# ^ run this until you get a key with no forward slashes :upside_down:
# web-created keys are allowed to delete files, which you probably don't want for an incremental backup program
# you need to create a new application key from the web in order to first get a key which can create new keys (use env vars in the above command)
# TODO: s/duplicity_passphrase/duplicity_env/
services.duplicity.secretFile = config.sops.secrets."duplicity_passphrase.env".path;
# NB: manually trigger with `systemctl start duplicity`
services.duplicity.frequency = "daily";
services.duplicity.extraFlags = [
# without --allow-source-mismatch, duplicity will abort if you change the hostname between backups
"--allow-source-mismatch"
# includes/exclude ordering matters, so we explicitly control it here.
# the first match decides a file's treatment. so here:
# - /nix/persist/home/colin/tmp is excluded
# - *other* /nix/persist/ files are included by default
# - anything else under `/` are excluded by default
"--exclude" "/nix/persist/home/colin/dev/home-logic/coremem/out" # this can reach > 1 TB
"--exclude" "/nix/persist/home/colin/use/iso" # might want to re-enable... but not critical
"--exclude" "/nix/persist/home/colin/.local/share/sublime-music" # music cache. better to just keep the HQ sources
"--exclude" "/nix/persist/home/colin/.local/share/Steam" # can just re-download games
"--exclude" "/nix/persist/home/colin/.bitmonero/lmdb" # monero blockchain
"--exclude" "/nix/persist/home/colin/.rustup"
"--exclude" "/nix/persist/home/colin/ref" # publicly available data: no point in duplicating it
"--exclude" "/nix/persist/home/colin/tmp"
"--exclude" "/nix/persist/home/colin/Videos"
"--exclude" "/nix/persist/var/lib/duplicity" # don't back up our own backup state!
"--include" "/nix/persist"
"--exclude" "/"
];
# set this for the FIRST backup, then remove it to enable incremental backups
# (that the first backup *isn't* full i think is a defect)
# services.duplicity.fullIfOlderThan = "always";
systemd.services.duplicity.serviceConfig = {
# rate-limit the read bandwidth in an effort to thereby prevent net upload saturation
# this could perhaps be done better by adding a duplicity config option to replace the binary with `trickle`
IOReadBandwidthMax = [
"/dev/sda1 5M"
"/dev/nvme0n1 5M"
"/dev/mmc0 5M"
];
};
# based on <nixpkgs:nixos/modules/services/backup/duplicity.nix> with changes:
# - remove the cleanup step: API key doesn't have delete perms
# - don't escape the targetUrl: it comes from an env var set in the secret file
systemd.services.duplicity.script = let
cfg = config.services.duplicity;
target = cfg.targetUrl;
extra = escapeShellArgs ([ "--archive-dir" "/var/lib/duplicity" ] ++ cfg.extraFlags);
dup = "${pkgs.duplicity}/bin/duplicity";
in lib.mkForce ''
set -x
# ${dup} cleanup ${target} --force ${extra}
# ${lib.optionalString (cfg.cleanup.maxAge != null) "${dup} remove-older-than ${lib.escapeShellArg cfg.cleanup.maxAge} ${target} --force ${extra}"}
# ${lib.optionalString (cfg.cleanup.maxFull != null) "${dup} remove-all-but-n-full ${builtins.toString cfg.cleanup.maxFull} ${target} --force ${extra}"}
# ${lib.optionalString (cfg.cleanup.maxIncr != null) "${dup} remove-all-inc-of-but-n-full ${toString cfg.cleanup.maxIncr} ${target} --force ${extra}"}
exec ${dup} ${if cfg.fullIfOlderThan == "always" then "full" else "incr"} ${lib.escapeShellArg cfg.root} ${target} ${lib.escapeShellArgs ([]
++ concatMap (p: [ "--include" p ]) cfg.include
++ concatMap (p: [ "--exclude" p ]) cfg.exclude
++ (lib.optionals (cfg.fullIfOlderThan != "never" && cfg.fullIfOlderThan != "always") [ "--full-if-older-than" cfg.fullIfOlderThan ])
)} ${extra}
'';
};
}

View File

@@ -25,8 +25,8 @@ in
list of directories to upload to rsync.net.
note that this module does NOT add any encryption to the files (layer that yourself).
'';
default = [
"/nix/persist/private"
default = lib.optionals config.sane.persist.enable [
"/nix/persist/private" #< XXX: make sure to do the encrypted version, not /mnt/persist/private!
];
};
};
@@ -39,22 +39,33 @@ in
serviceConfig.Restart = "no";
serviceConfig.User = "colin";
# hardening
serviceConfig.AmbientCapabilities = [
# needs to be able to read files owned by any user
"CAP_DAC_READ_SEARCH"
];
serviceConfig.RestrictNetworkInterfaces = [
# strictly forbid sending traffic over any non ethernet/wifi interface,
# because i don't want this e.g. consuming all my cellular data.
# TODO: test this. i don't know that the moby kernel/systemd actually supports these options
"lo" # for DNS
"eth0"
"wlan0"
];
# hardening
serviceConfig.CapabilityBoundingSet = [ "CAP_DAC_READ_SEARCH" ];
serviceConfig.ReadWritePaths = builtins.map (d: "${d}/zzz-rsync-net") cfg.dirs;
serviceConfig.ReadOnlyPaths = "/nix/persist/private";
serviceConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
serviceConfig.ReadOnlyPaths = cfg.dirs;
serviceConfig.RestrictAddressFamilies = "AF_INET AF_INET6";
serviceConfig.LockPersonality = true;
serviceConfig.MemoryDenyWriteExecute = true;
serviceConfig.NoNewPrivileges = true;
serviceConfig.PrivateDevices = true;
serviceConfig.PrivateMounts = true;
serviceConfig.PrivateUsers = true;
serviceConfig.PrivateTmp = true;
serviceConfig.ProcSubset = "pid";
serviceConfig.ProtectClock = "true";
serviceConfig.ProtectClock = true;
serviceConfig.ProtectControlGroups = true;
serviceConfig.ProtectHome = true;
serviceConfig.ProtectHostname = true;
@@ -64,13 +75,23 @@ in
serviceConfig.ProtectProc = "invisible";
serviceConfig.ProtectSystem = "strict";
serviceConfig.RemoveIPC = true;
serviceConfig.RestrictFileSystems = "@basic-api @common-block @temporary";
serviceConfig.RestrictNamespaces = true;
serviceConfig.RestrictSUIDSGID = true;
serviceConfig.SystemCallArchitectures = "native";
serviceConfig.SystemCallFilter = "@system-service @mount";
serviceConfig.SystemCallFilter = [
"@system-service"
"~@chown"
"~@cpu-emulation"
"~@keyring"
"~@setuid"
];
# hardening exceptions:
serviceConfig.NoNewPrivileges = false; #< bwrap'd dac_read_search
serviceConfig.PrivateDevices = false; #< passt/pasta
serviceConfig.RestrictNamespaces = false; #< bwrap
serviceConfig.PrivateUsers = false; #< CAP_DAC_READ_SEARCH in the root namespace means we can't do any user namespacing
# serviceConfig.NoNewPrivileges = false; #< bwrap'd dac_read_search
# serviceConfig.PrivateDevices = false; #< passt/pasta
# serviceConfig.RestrictNamespaces = false; #< bwrap
# serviceConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK"; #< AF_NETLINK is for passt/pasta
};
systemd.timers.rsync-net = {
wantedBy = [ "multi-user.target" ];

View File

@@ -26,9 +26,9 @@ for dir in "$@"; do
echo "syncing '$dir' to '$remote_dir'"
echo "$now" > "$dir"/zzz-rsync-net/last-attempted
# N.B.: manual flags instead of `-a -> -rlptgoD` because device files have a max path length which is too restricted
# if SANEBOX_PREPEND="--sanebox-disable" \
if SANEBOX_PREPEND="--sanebox-cap dac_read_search --sanebox-path $RN_ID" \
sane-vpn do unmetered -- \
# TODO: add `sane-vpn do unmetered --`, after fixing pasta/sane-vpn to preserve capabilities + not create a new user namespace unconditionally.
# until then, don't run over cellular!
if SANEBOX_PREPEND="--sanebox-method landlock --sanebox-cap dac_read_search --sanebox-path $RN_ID" \
rsync --exclude="$RN_ID" -e "ssh -i $RN_ID" --mkpath -rlptgov --delete "$dir" "$remote_dir"; \
then
echo "$now" > "$dir"/zzz-rsync-net/last-completed

View File

@@ -0,0 +1,9 @@
{
"description": "Oral arguments before the Supreme Court of the United States, from Oyez.org.",
"is_podcast": true,
"site_name": "User account | Oyez Backend",
"site_url": "https://api.oyez.org",
"title": "U.S. Supreme Court Oral Arguments",
"url": "https://api.oyez.org/podcasts/oral-arguments/2015",
"velocity": 0.123
}

View File

@@ -234,6 +234,20 @@ let
description = "name of the systemd unit which mounts this path";
default = mountNameFor path;
};
mountConfig = mkOption {
type = types.attrs;
description = ''
attrset to add to the [Mount] section of the systemd unit file.
'';
default = {};
};
unitConfig = mkOption {
type = types.attrs;
description = ''
attrset to add to the [Unit] section of the systemd unit file.
'';
default = {};
};
};
};
@@ -268,9 +282,9 @@ let
};
# given a mountEntry definition, evaluate its toplevel `config` output.
mkMountConfig = path: opt: (let
device = config.fileSystems."${path}".device;
underlying = cfg."${device}";
mkMountConfig = path: opt: let
fsEntry = config.fileSystems."${path}";
underlying = cfg."${fsEntry.device}";
isBind = opt.mount.bind != null;
ifBind = lib.mkIf isBind;
# before mounting:
@@ -278,12 +292,12 @@ let
# - prepare the source directory -- assuming it's not an external device
# - satisfy any user-specified prerequisites ("depends")
requires = [ opt.generated.unit ]
++ (if lib.hasPrefix "/dev/disk/" device then [] else [ underlying.unit ])
++ (if lib.hasPrefix "/dev/disk/" fsEntry.device || lib.hasPrefix "fuse" (fsEntry.fsType or "unknown") then [] else [ underlying.unit ])
++ opt.mount.depends;
in {
fileSystems."${path}" = {
device = ifBind opt.mount.bind;
options = (if isBind then ["bind"] else [])
options = (lib.optionals isBind [ "bind" ])
++ [
# disable defaults: don't require this to be mount as part of local-fs.target
# we'll handle that stuff precisely.
@@ -298,13 +312,24 @@ let
++ (builtins.map (unit: "x-systemd.wanted-by=${unit}") (opt.wantedBy ++ opt.wantedBeforeBy));
noCheck = ifBind true;
};
});
systemd.mounts = [{
where = path;
what = if fsEntry.device != null then fsEntry.device else "";
type = fsEntry.fsType;
options = lib.concatStringsSep "," fsEntry.options;
after = requires;
requires = requires;
before = opt.wantedBeforeBy;
wantedBy = opt.wantedBeforeBy;
inherit (opt.mount) mountConfig unitConfig;
}];
};
mkFsConfig = path: opt: lib.mkMerge [
(mkGeneratedConfig path opt)
(lib.mkIf (opt.mount != null) (mkMountConfig path opt))
];
mkFsConfig = path: opt: lib.mkMerge (
[ (mkGeneratedConfig path opt) ] ++
lib.optional (opt.mount != null) (mkMountConfig path opt)
);
# return all ancestors of this path.
# e.g. ancestorsOf "/foo/bar/baz" => [ "/" "/foo" "/foo/bar" ]
@@ -358,6 +383,7 @@ in {
let
configs = lib.mapAttrsToList mkFsConfig cfg;
take = f: {
systemd.mounts = f.systemd.mounts;
systemd.services = f.systemd.services;
fileSystems = f.fileSystems;
};

View File

@@ -2,9 +2,9 @@
{
imports = [
./ephemeral.nix
./ephemeral
./initrd.nix
./plaintext.nix
./private.nix
./private
];
}

View File

@@ -1,96 +0,0 @@
{ config, lib, pkgs, sane-lib, utils, ... }:
let
persist-base = "/nix/persist";
origin = config.sane.persist.stores."ephemeral".origin;
backing = sane-lib.path.concat [ persist-base "ephemeral" ];
gocryptfs-ephemeral = pkgs.writeShellApplication {
name = "mount.fuse.gocryptfs-ephemeral";
runtimeInputs = with pkgs; [
coreutils-full
gocryptfs
];
text = ''
# mount invokes us like this. not sure if that's a guarantee or not:
# <exe> <device> <mountpt> -o <flags>
backing=$1
# facing=$2
# backing might exist from the last boot, so wipe it:
rm -fr "$backing"
mkdir -p "$backing"
# the password shows up in /proc/.../env, briefly.
# that's inconsequential: we just care that it's not *persisted*.
pw=$(dd if=/dev/random bs=128 count=1 | base64 --wrap=0)
echo "$pw" | gocryptfs -quiet -passfile /dev/stdin -init "$backing"
echo "$pw" | gocryptfs -quiet -passfile /dev/stdin "$@"
'';
};
in
lib.mkIf config.sane.persist.enable
{
sane.persist.stores."ephemeral" = {
storeDescription = ''
stored to disk, but encrypted to an in-memory key and cleared on every boot
so that it's unreadable after power-off
'';
origin = lib.mkDefault "/mnt/persist/ephemeral";
};
fileSystems."${origin}" = {
device = backing;
fsType = "fuse.gocryptfs-ephemeral";
options = [
# "nodev" # "Unknown parameter 'nodev'". gocryptfs requires this be passed as `-ko nodev`
# "nosuid" # "Unknown parameter 'nosuid'". gocryptfs requires this be passed as `-ko nosuid` (also, nosuid is default)
"allow_other" # root ends up being the user that mounts this, so need to make it visible to other users.
# "defaults" # "unknown flag: --defaults. Try 'gocryptfs -help'"
];
noCheck = true;
};
# let sane.fs know about our fileSystem and automatically add the appropriate dependencies
sane.fs."${origin}".mount = { };
sane.fs."${backing}" = sane-lib.fs.wantedDir;
systemd.mounts = let
fsEntry = config.fileSystems."${origin}";
in [{
#VVV repeat what systemd would ordinarily scrape from /etc/fstab
where = origin;
what = fsEntry.device;
type = fsEntry.fsType;
options = lib.concatStringsSep "," fsEntry.options;
# sandbox options
mountConfig.AmbientCapabilities = "";
# CAP_LEASE is probably not necessary -- does any fs user use leases?
mountConfig.CapabilityBoundingSet = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
mountConfig.LockPersonality = true;
mountConfig.MemoryDenyWriteExecute = true;
mountConfig.NoNewPrivileges = true;
mountConfig.ProtectClock = true;
mountConfig.ProtectHostname = true;
mountConfig.RemoveIPC = true;
mountConfig.RestrictAddressFamilies = "AF_UNIX"; # "none" works, but then it can't connect to the logger
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
# see `systemd-analyze filesystems` for a full list
mountConfig.RestrictFileSystems = "@common-block devtmpfs fuse pipefs";
mountConfig.RestrictNamespaces = true;
mountConfig.RestrictNetworkInterfaces = "";
mountConfig.RestrictRealtime = true;
mountConfig.RestrictSUIDSGID = true;
mountConfig.SystemCallArchitectures = "native";
mountConfig.SystemCallFilter = [
# unfortunately, i need to keep @network-io (accept, bind, connect, listen, recv, send, socket, ...). not sure why (daemon control socket?).
# TODO: @module?
"@system-service" "@mount" "~@cpu-emulation" "~@keyring"
];
# note that anything which requires mount namespaces (ProtectHome, ReadWritePaths, ...) does NOT work.
# it's in theory possible, via mount propagation, but systemd provides no way for that.
# PrivateNetwork = true BREAKS the mount action; i think systemd or udev needs that internally to communicate with the service manager?
}];
system.fsPackages = [ gocryptfs-ephemeral ]; # fuse needs to find gocryptfs
}

View File

@@ -0,0 +1,97 @@
{ config, lib, pkgs, sane-lib, utils, ... }:
let
persist-base = "/nix/persist";
origin = config.sane.persist.stores."ephemeral".origin;
backing = sane-lib.path.concat [ persist-base "ephemeral" ];
in
lib.mkIf config.sane.persist.enable
{
sane.programs.gocryptfs-ephemeral = {
packageUnwrapped = pkgs.static-nix-shell.mkBash {
pname = "gocryptfs-ephemeral";
srcRoot = ./.;
pkgs = [
"coreutils-full"
"gocryptfs"
];
};
sandbox.method = "landlock";
sandbox.autodetectCliPaths = "existing";
sandbox.capabilities = [
# "sys_admin" #< omitted: not required if using fuse3-sane with -o pass_fuse_fd
"chown"
"dac_override"
"dac_read_search"
"fowner"
"lease"
"mknod"
"setgid"
"setuid"
];
suggestedPrograms = [ "gocryptfs" ];
};
sane.persist.stores."ephemeral" = {
storeDescription = ''
stored to disk, but encrypted to an in-memory key and cleared on every boot
so that it's unreadable after power-off
'';
origin = lib.mkDefault "/mnt/persist/ephemeral";
};
fileSystems."${origin}" = {
device = "gocryptfs-ephemeral#${backing}";
fsType = "fuse3.sane";
options = [
"nodev" # only works via mount.fuse; gocryptfs requires this be passed as `-ko nodev`
"nosuid" # only works via mount.fuse; gocryptfs requires this be passed as `-ko nosuid` (also, nosuid is default)
"allow_other" # root ends up being the user that mounts this, so need to make it visible to other users.
# "defaults" # "unknown flag: --defaults. Try 'gocryptfs -help'"
"pass_fuse_fd"
];
noCheck = true;
};
# let sane.fs know about our fileSystem and automatically add the appropriate dependencies
sane.fs."${origin}" = {
wantedBeforeBy = [ "local-fs.target" ];
mount.depends = [
config.sane.fs."${backing}".unit
];
# hardening (systemd-analyze security mnt-persist-ephemeral.mount)
mount.mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
# CAP_LEASE is probably not necessary -- does any fs user use leases?
mount.mountConfig.CapabilityBoundingSet = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
mount.mountConfig.LockPersonality = true;
mount.mountConfig.MemoryDenyWriteExecute = true;
mount.mountConfig.NoNewPrivileges = true;
mount.mountConfig.ProtectClock = true;
mount.mountConfig.ProtectHostname = true;
mount.mountConfig.RemoveIPC = true;
mount.mountConfig.RestrictAddressFamilies = "AF_UNIX"; # "none" works, but then it can't connect to the logger
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
# see `systemd-analyze filesystems` for a full list
mount.mountConfig.RestrictFileSystems = "@common-block @basic-api fuse pipefs";
mount.mountConfig.RestrictNamespaces = true;
mount.mountConfig.RestrictNetworkInterfaces = "";
mount.mountConfig.RestrictRealtime = true;
mount.mountConfig.RestrictSUIDSGID = true;
mount.mountConfig.SystemCallArchitectures = "native";
mount.mountConfig.SystemCallFilter = [
# unfortunately, i need to keep @network-io (accept, bind, connect, listen, recv, send, socket, ...). not sure why (daemon control socket?).
"@system-service" "@mount" "@sandbox" "~@cpu-emulation" "~@keyring"
];
mount.mountConfig.IPAddressDeny = "any";
mount.mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mount.mountConfig.DeviceAllow = "/dev/fuse";
mount.mountConfig.SocketBindDeny = "any";
# note that anything which requires mount namespaces (ProtectHome, ReadWritePaths, ...) does NOT work.
# it's in theory possible, via mount propagation, but systemd provides no way for that.
# PrivateNetwork = true BREAKS the mount action; i think systemd or udev needs that internally to communicate with the service manager?
};
sane.fs."${backing}".dir = {};
sane.programs.gocryptfs-ephemeral.enableFor.system = true;
system.fsPackages = [ pkgs.libfuse-sane ];
}

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p bash -p coreutils-full -p gocryptfs
# mount invokes us like this. not sure if that's a guarantee or not:
# <exe> <device> <mountpt> -o <flags>
backing=$1
# facing=$2
# backing might exist from the last boot, so wipe it:
rm -fr "$backing"
mkdir -p "$backing"
# the password shows up in /proc/.../env, briefly.
# that's inconsequential: we just care that it's not *persisted*.
pw=$(dd if=/dev/random bs=128 count=1 | base64 --wrap=0)
echo "$pw" | gocryptfs -quiet -passfile /dev/fd/0 -init "$backing"
echo "$pw" | exec gocryptfs -quiet -passfile /dev/fd/0 "$@"

View File

@@ -1,143 +0,0 @@
{ config, lib, pkgs, sane-lib, utils, ... }:
let
# TODO: parameterize!
persist-base = "/nix/persist";
origin = config.sane.persist.stores."private".origin;
backing = sane-lib.path.concat [ persist-base "private" ];
gocryptfs-private = pkgs.writeShellApplication {
name = "mount.fuse.gocryptfs-private";
runtimeInputs = with pkgs; [
coreutils-full
gocryptfs
inotify-tools
];
text = ''
# backing=$1
# facing=$2
mountArgs=("$@")
passdir=/run/gocryptfs
passfile="$passdir/private.key"
waitForPassfileOnce() {
local timeout=$1
if [ -f "$passfile" ]; then
return 0
else
# wait for some file to be created inside the directory.
# inotifywait returns 0 if the file was created. 1 or 2 if timeout was hit or it was interrupted by a different event.
inotifywait --timeout "$timeout" --event create "$passdir"
return 1 #< maybe it was created; we'll pick that up immediately, on next check
fi
}
waitForPassfile() {
# there's a race condition between testing the path and starting `inotifywait`.
# therefore, use a retry loop. exponential backoff to decrease the impact of the race condition,
# especially near the start of boot to allow for quick reboots even if/when i hit the race.
for timeout in 4 4 8 8 8 8 16 16 16 16 16 16 16 16; do
if waitForPassfileOnce "$timeout"; then
return 0
fi
done
while true; do
if waitForPassfileOnce 30; then
return 0
fi
done
}
tryOpenStore() {
# try to open the store (blocking), if it fails, then delete the passfile because the user probably entered the wrong password
echo "mounting with ''${mountArgs[*]}"
# gocryptfs will unlock the store, and *then* fork into the background.
# so when it returns, the files are either immediately accessible, or the mount failed (likely due to a bad password
if ! gocryptfs "''${mountArgs[@]}"; then
echo "failed mount (transient failure)"
rm -f "$passfile"
return 1
fi
}
waitForPassfile
while ! tryOpenStore; do
waitForPassfile
done
echo "mounted"
# mount is complete (successful), and backgrounded.
# remove the passfile even on successful mount, for vague safety reasons (particularly if the user were to explicitly unmount the private store).
rm -f "$passfile"
'';
};
in
lib.mkIf config.sane.persist.enable
{
sane.persist.stores."private" = {
storeDescription = ''
encrypted store which persists across boots.
typical use case is for the user to encrypt this store using their login password so that it
can be auto-unlocked at login.
'';
origin = lib.mkDefault "/mnt/persist/private";
defaultOrdering = let
private-unit = config.sane.fs."${origin}".unit;
in {
# auto create only after the store is mounted
wantedBy = [ private-unit ];
# we can't create things in private before local-fs.target
wantedBeforeBy = [ ];
};
defaultMethod = "symlink";
};
fileSystems."${origin}" = {
device = backing;
fsType = "fuse.gocryptfs-private";
options = [
"auto"
"nofail"
# "nodev" # "Unknown parameter 'nodev'". gocryptfs requires this be passed as `-ko nodev`
# "noexec" # handful of scripts in ~/knowledge that are executable
# "nosuid" # "Unknown parameter 'nosuid'". gocryptfs requires this be passed as `-ko nosuid` (also nosuid is default)
"allow_other" # root ends up being the user that mounts this, so need to make it visible to other users.
# "quiet"
# "defaults" # "unknown flag: --defaults. Try 'gocryptfs -help'"
"passfile=/run/gocryptfs/private.key"
# options so that we can block for the password file *without* systemd killing us.
# see: <https://bbs.archlinux.org/viewtopic.php?pid=1906174#p1906174>
"x-systemd.mount-timeout=infinity"
# "retry=10000"
# "fg"
];
noCheck = true;
};
# let sane.fs know about the mount
sane.fs."${origin}".mount = {};
# it also needs to know that the underlying device is an ordinary folder
sane.fs."${backing}" = sane-lib.fs.wanted {
dir.acl.user = config.sane.defaultUser;
};
sane.fs."/run/gocryptfs" = sane-lib.fs.wanted {
dir.acl.user = config.sane.defaultUser;
dir.acl.mode = "0700";
};
# in order for non-systemd `mount` to work, the mount point has to already be created, so make that a default target
systemd.units = let
originUnit = config.sane.fs."${origin}".generated.unit;
in {
"${originUnit}".wantedBy = [ "local-fs.target" ];
};
system.fsPackages = [ gocryptfs-private ];
sane.user.services.gocryptfs-private = {
description = "wait for /mnt/persist/private to be mounted";
startCommand = "${lib.getExe' pkgs.systemd "systemctl"} start mnt-persist-private.mount";
# command = "sleep infinity";
# readiness.waitExists = [ "/mnt/persist/private/init" ];
partOf = [ "private-storage" ];
};
}

View File

@@ -0,0 +1,149 @@
{ config, lib, pkgs, sane-lib, utils, ... }:
let
persist-base = "/nix/persist";
origin = config.sane.persist.stores."private".origin;
backing = sane-lib.path.concat [ persist-base "private" ];
in
lib.mkIf config.sane.persist.enable
{
sane.programs."provision-private-key" = {
packageUnwrapped = pkgs.static-nix-shell.mkBash {
pname = "provision-private-key";
srcRoot = ./.;
pkgs = [
"coreutils-full"
"gocryptfs"
"inotify-tools"
];
};
sandbox.method = "bwrap";
sandbox.autodetectCliPaths = "parent";
};
sane.programs.gocryptfs-private = {
packageUnwrapped = pkgs.static-nix-shell.mkBash {
pname = "gocryptfs-private";
srcRoot = ./.;
pkgs = [ "gocryptfs" ];
};
sandbox.method = "landlock";
sandbox.autodetectCliPaths = "existing";
sandbox.capabilities = [
# "sys_admin" #< omitted: not required if using fuse3-sane with -o pass_fuse_fd
"chown"
"dac_override"
"dac_read_search"
"fowner"
"lease"
"mknod"
"setgid"
"setuid"
];
sandbox.extraPaths = [
"/run/gocryptfs" #< TODO: teach sanebox about `-o FLAG1=VALUE1,FLAG2=VALUE2` style of argument passing, then use `existingOrParent` autodetect, and remove this
];
suggestedPrograms = [ "gocryptfs" ];
};
sane.persist.stores."private" = {
storeDescription = ''
encrypted store which persists across boots.
typical use case is for the user to encrypt this store using their login password so that it
can be auto-unlocked at login.
'';
origin = lib.mkDefault "/mnt/persist/private";
defaultOrdering = let
private-unit = config.sane.fs."${origin}".unit;
in {
# auto create only after the store is mounted
wantedBy = [ private-unit ];
# we can't create things in private before local-fs.target
wantedBeforeBy = [ ];
};
defaultMethod = "symlink";
};
fileSystems."${origin}" = {
device = "gocryptfs-private#${backing}";
fsType = "fuse3.sane";
options = [
# "auto"
"nofail"
# "noexec" # handful of scripts in ~/knowledge that are executable
"nodev" # only works via mount.fuse; gocryptfs requires this be passed as `-ko nodev`
"nosuid" # only works via mount.fuse; gocryptfs requires this be passed as `-ko nosuid` (also, nosuid is default)
"allow_other" # root ends up being the user that mounts this, so need to make it visible to other users.
# "quiet"
# "defaults" # "unknown flag: --defaults. Try 'gocryptfs -help'"
"passfile=/run/gocryptfs/private.key"
# options so that we can block for the password file *without* systemd killing us.
# see: <https://bbs.archlinux.org/viewtopic.php?pid=1906174#p1906174>
"x-systemd.mount-timeout=infinity"
# "retry=10000"
# "fg"
"pass_fuse_fd"
];
noCheck = true;
};
# let sane.fs know about the mount
sane.fs."${origin}" = {
wantedBy = [ "local-fs.target" ];
mount.depends = [
config.sane.fs."${backing}".unit
config.sane.fs."/run/gocryptfs/private.key".unit
];
# unitConfig.DefaultDependencies = "no";
mount.mountConfig.TimeoutSec = "infinity";
# hardening (systemd-analyze security mnt-persist-private.mount)
mount.mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
# CAP_LEASE is probably not necessary -- does any fs user use leases?
mount.mountConfig.CapabilityBoundingSet = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
mount.mountConfig.LockPersonality = true;
mount.mountConfig.MemoryDenyWriteExecute = true;
mount.mountConfig.NoNewPrivileges = true;
mount.mountConfig.ProtectClock = true;
mount.mountConfig.ProtectHostname = true;
mount.mountConfig.RemoveIPC = true;
mount.mountConfig.RestrictAddressFamilies = "AF_UNIX"; # "none" works, but then it can't connect to the logger
mount.mountConfig.RestrictFileSystems = "@common-block @basic-api fuse pipefs";
mount.mountConfig.RestrictNamespaces = true;
mount.mountConfig.RestrictNetworkInterfaces = "";
mount.mountConfig.RestrictRealtime = true;
mount.mountConfig.RestrictSUIDSGID = true;
mount.mountConfig.SystemCallArchitectures = "native";
mount.mountConfig.SystemCallFilter = [
# unfortunately, i need to keep @network-io (accept, bind, connect, listen, recv, send, socket, ...). not sure why (daemon control socket?).
"@system-service" "@mount" "@sandbox" "~@cpu-emulation" "~@keyring"
];
mount.mountConfig.IPAddressDeny = "any";
mount.mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
mount.mountConfig.DeviceAllow = "/dev/fuse";
mount.mountConfig.SocketBindDeny = "any";
};
# it also needs to know that the underlying device is an ordinary folder
sane.fs."${backing}".dir = {};
sane.fs."/run/gocryptfs".dir.acl = {
user = config.sane.defaultUser; #< must be user-writable so i can unlock it.
mode = "0770";
};
sane.fs."/run/gocryptfs/private.key".generated.command = [
"${lib.getExe config.sane.programs.provision-private-key.package}"
"/run/gocryptfs/private.key"
"${backing}/gocryptfs.conf"
];
sane.programs."gocryptfs-private".enableFor.system = true;
sane.programs."provision-private-key".enableFor.system = true;
system.fsPackages = [ pkgs.libfuse-sane ];
sane.user.services.gocryptfs-private = {
description = "wait for /mnt/persist/private to be mounted";
startCommand = "${lib.getExe' pkgs.systemd "systemctl"} start mnt-persist-private.mount";
# command = "sleep infinity";
# readiness.waitExists = [ "/mnt/persist/private/init" ];
partOf = [ "private-storage" ];
};
}

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p bash -p gocryptfs
passfile=/run/gocryptfs/private.key
gocryptfs --sanebox-path "$passfile" "$@"
rm "$passfile"

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p bash -p coreutils-full -p gocryptfs -p inotify-tools
passfile="$1" # e.g. /run/gocryptfs/private.key
conffile="$2" # e.g. /nix/persist/private/gocryptfs.conf
passdir=$(dirname "$passfile")
waitForPassfileOnce() {
local timeout=$1
if [ -f "$passfile" ]; then
return 0
else
# wait for some file to be created inside the directory.
# inotifywait returns 0 if the file was created. 1 or 2 if timeout was hit or it was interrupted by a different event.
inotifywait --timeout "$timeout" --event create "$passdir"
return 1 #< maybe it was created; we'll pick that up immediately, on next check
fi
}
waitForPassfile() {
# there's a race condition between testing the path and starting `inotifywait`.
# therefore, use a retry loop. exponential backoff to decrease the impact of the race condition,
# especially near the start of boot to allow for quick reboots even if/when i hit the race.
for timeout in 4 4 8 8 8 8 16 16 16 16 16 16 16 16; do
if waitForPassfileOnce "$timeout"; then
return 0
fi
done
while true; do
if waitForPassfileOnce 30; then
return 0
fi
done
}
validatePassword() {
if ! cat "$passfile" | gocryptfs-xray -dumpmasterkey "$conffile" > /dev/null; then
echo "failed key validation"
rm -f "$passfile"
return 1
fi
}
waitForPassfile
while ! validatePassword; do
waitForPassfile
done
echo "key provisioned"

View File

@@ -347,6 +347,13 @@ let
whether to place the process in a new PID namespace, if the sandboxer supports that.
'';
};
sandbox.isolateUsers = mkOption {
type = types.bool;
default = true;
description = ''
whether to place the process in a new user namespace, if the sandboxer supports that.
'';
};
sandbox.whitelistAudio = mkOption {
type = types.bool;
default = false;
@@ -472,8 +479,10 @@ let
;
suggestedPrograms = lib.optionals (config.sandbox.method == "bwrap") [
"bubblewrap" "passt" "iproute2" "iptables"
] ++ lib.optionals (config.sandbox.method == "landlock") [
"landlock-sandboxer" "libcap"
] ++ lib.optionals (config.sandbox.method == "pastaonly") [
"passt" "iproute2" "iptables"
"passt" "iproute2" "iptables" "libcap"
] ++ lib.optionals (config.sandbox.method == "capshonly") [
"libcap"
];
@@ -517,6 +526,8 @@ let
"--sanebox-portal"
] ++ lib.optionals (!config.sandbox.isolatePids) [
"--sanebox-keep-namespace" "pid"
] ++ lib.optionals (!config.sandbox.isolateUsers) [
"--sanebox-keep-namespace" "user"
];
};
});

View File

@@ -126,6 +126,7 @@ let
description = "trust-dns Domain Name Server (serving ${flavor})";
unitConfig.Documentation = "https://trust-dns.org/";
after = [ "network.target" ];
before = [ "network-online.target" ]; # most things assume they'll have DNS services alongside routability
wantedBy = [ "network.target" ];
preStart = lib.concatStringsSep "\n" (

View File

@@ -103,6 +103,12 @@ stores = {{ type = "forward", name_servers = [
return lines
def apply_zone(nm_config: NmConfig, ops: Ops) -> None:
# if we want to write /var/lib/trust-dns here, then we have to make sure the service is started, so systemd can create the directory.
# ops.exec_([
# "systemctl",
# "start",
# "trust-dns-localhost",
# ])
specialized_config = ""
for domain in nm_config.search_domains:
if is_valid_search_domain(domain) and nm_config.nameservers:

View File

@@ -154,7 +154,7 @@ let
systemd.network.networks."50-${name}" = {
# see: `man 5 systemd.network`
matchConfig.Name = name;
networkConfig.Address = [ addrV4 ];
networkConfig.Address = [ "${addrV4}/32" ];
networkConfig.DNS = dns;
# TODO: `sane-vpn up <vpn>` should configure DNS to be sent over the VPN
# DNSDefaultRoute: system DNS queries are sent to this link's DNS server
@@ -185,7 +185,7 @@ let
# periodically re-apply peers, to ensure DNS mappings stay fresh
# borrowed from <repo:nixos/nixpkgs:nixos/modules/services/networking/wireguard.nix>
wantedBy = [ "network.target" ];
path = with pkgs; [ wireguard-tools ];
path = [ config.sane.programs.wireguard-tools.package ];
serviceConfig.Restart = "always";
serviceConfig.RestartSec = "60"; #< retry delay when we fail (because e.g. there's no network)
serviceConfig.Type = "simple";
@@ -197,6 +197,32 @@ let
sleep 180
done
'';
# systemd hardening (systemd-analyze security wg-home-refresh.service)
serviceConfig.AmbientCapabilities = "CAP_NET_ADMIN";
serviceConfig.CapabilityBoundingSet = "CAP_NET_ADMIN";
serviceConfig.LockPersonality = true;
serviceConfig.MemoryDenyWriteExecute = true;
serviceConfig.NoNewPrivileges = true;
serviceConfig.ProtectClock = true;
serviceConfig.ProtectHostname = true;
serviceConfig.RemoveIPC = true;
serviceConfig.RestrictAddressFamilies = "AF_INET AF_INET6 AF_NETLINK";
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
# see `systemd-analyze filesystems` for a full list
serviceConfig.RestrictFileSystems = "@common-block @basic-api";
serviceConfig.RestrictRealtime = true;
serviceConfig.RestrictSUIDSGID = true;
serviceConfig.SystemCallArchitectures = "native";
serviceConfig.SystemCallFilter = [
"@system-service"
"@sandbox"
"~@chown"
"~@cpu-emulation"
"~@keyring"
];
serviceConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
# serviceConfig.DeviceAllow = "/dev/...";
serviceConfig.RestrictNamespaces = true;
};
# networking.firewall.extraCommands = with pkgs; ''

View File

@@ -728,6 +728,12 @@ in with final; {
# outputs = lib.remove "devdoc" upstream.outputs;
# });
libpeas2 = prev.libpeas2.overrideAttrs (upstream: {
mesonFlags = upstream.mesonFlags ++ [
"-Dlua51=false" #< fails to find lua (probably it incorrectly checks the build machine)
];
});
# libsForQt5 = prev.libsForQt5.overrideScope (self: super: {
# phonon = super.phonon.overrideAttrs (orig: {
# # fixes "ECM (required version >= 5.60), Extra CMake Modules"
@@ -857,6 +863,76 @@ in with final; {
# );
# 2023/07/31: upstreaming is blocked on vpnc cross compilation
# networkmanager-vpnc = mvToNativeInputs [ glib ] prev.networkmanager-vpnc;
newsflash = (prev.newsflash.override {
blueprint-compiler = buildPackages.writeShellScriptBin "blueprint-compiler" ''
export GI_TYPELIB_PATH=${typelibPath [
buildPackages.clapper
buildPackages.glib
buildPackages.gtk4
buildPackages.gst_all_1.gstreamer
buildPackages.gst_all_1.gst-plugins-base
buildPackages.gdk-pixbuf
buildPackages.pango
buildPackages.graphene
buildPackages.harfbuzz
buildPackages.libadwaita
]}
exec ${lib.getExe buildPackages.blueprint-compiler} "$@"
'';
}).overrideAttrs (upstream: {
postPatch = (upstream.postPatch or "") + ''
substituteInPlace src/meson.build --replace-fail \
"'src' / rust_target" \
"'src' / '${rust.toRustTarget stdenv.hostPlatform}' / rust_target"
rm build.rs
export OUT_DIR=$(pwd)
# from build.rs:
glib-compile-resources --sourcedir=data/resources --target=icons.gresource data/resources/icons.gresource.xml
glib-compile-resources --sourcedir=data/resources --target=styles.gresource data/resources/styles.gresource.xml
substitute data/io.gitlab.news_flash.NewsFlash.appdata.xml.in.in \
data/resources/io.gitlab.news_flash.NewsFlash.appdata.xml \
--replace-fail '@appid@' 'io.gitlab.news_flash.NewsFlash'
glib-compile-resources --sourcedir=data/resources --target=appdata.gresource data/resources/appdata.gresource.xml
'';
# nixpkgs sets CARGO_BUILD_TARGET to the build platform target, so correct that.
# fixes openssl not being able to find its library
buildPhase = ''
runHook preBuild
${rust.envVars.setEnv} "CARGO_BUILD_TARGET=${rust.toRustTarget stdenv.hostPlatform}" ninja -j$NIX_BUILD_CORES
runHook postBuild
'';
env = let
inherit buildPackages stdenv rust;
ccForBuild = "${buildPackages.stdenv.cc}/bin/${buildPackages.stdenv.cc.targetPrefix}cc";
cxxForBuild = "${buildPackages.stdenv.cc}/bin/${buildPackages.stdenv.cc.targetPrefix}c++";
ccForHost = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc";
cxxForHost = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}c++";
rustBuildPlatform = rust.toRustTarget stdenv.buildPlatform;
rustTargetPlatform = rust.toRustTarget stdenv.hostPlatform;
rustTargetPlatformSpec = rust.toRustTargetSpec stdenv.hostPlatform;
in (upstream.env or {}) // {
# taken from <pkgs/build-support/rust/hooks/default.nix>
# fixes "cargo:warning=aarch64-unknown-linux-gnu-gcc: error: unrecognized command-line option -m64"
# XXX: these aren't necessarily valid environment variables: the referenced nix file is more clever to get them to work.
"CC_${rustBuildPlatform}" = "${ccForBuild}";
"CXX_${rustBuildPlatform}" = "${cxxForBuild}";
"CC_${rustTargetPlatform}" = "${ccForHost}";
"CXX_${rustTargetPlatform}" = "${cxxForHost}";
# fails to fix "Failed to find OpenSSL development headers."
# OPENSSL_NO_VENDOR = 1;
# OPENSSL_LIB_DIR = "${lib.getLib openssl}/lib";
# OPENSSL_DIR = "${lib.getDev openssl}";
};
});
# fixes "properties/gresource.xml: Permission denied"
# - by providing glib-compile-resources
# 2024/05/31: upstreaming is blocked on qtsvg, qtimageformats, qtx11extras
@@ -1284,6 +1360,14 @@ in with final; {
# mvToNativeInputs [ gettext glib ] prev.xdg-desktop-portal-gnome
# )
# );
xdg-desktop-portal-gnome = prev.xdg-desktop-portal-gnome.override {
# xdp-gnome uses libjxl as a gdk pixbuf loader,
# but nixpkgs' libjxl disables the pixbuf loader when cross compiling,
# so xdp-gnome fails, expecting a pixbuf loader where there is none.
# solution: disable the libjxl pixbuf loader (by replacing it with a working pixbuf, already used by xdp-gnome).
# this means no jpeg thumbnailing.
libjxl = webp-pixbuf-loader;
};
# 2024/02/27: upstreaming is blocked on hyprland
# waybar = (prev.waybar.override {

View File

@@ -0,0 +1,29 @@
{
curlftpfs,
fetchFromGitea,
fuse3,
}:
(curlftpfs.override {
fuse = fuse3;
}).overrideAttrs (upstream: {
# my (master branch) 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
# my (fuse3 branch) fork includes the above plus:
# - implements the fuse3 API. this means it also supports `-o drop_privileges`
src = fetchFromGitea {
domain = "git.uninsane.org";
owner = "colin";
repo = "curlftpfs";
rev = "fuse3";
hash = "sha256-QwGbQuriNwnZscnYBEVp3Td6/ifiA8rtQcvtvmTnpbU=";
};
# `mount` clears PATH before calling the mount helper (see util-linux/lib/env.c),
# so the traditional /etc/fstab approach of fstype=fuse and device = curlftpfs#URI doesn't work.
# instead, install a `mount.curlftpfs` mount helper. this is what programs like `gocryptfs` do.
postInstall = (upstream.postInstall or "") + ''
ln -s curlftpfs $out/bin/mount.fuse.curlftpfs
ln -s curlftpfs $out/bin/mount.curlftpfs
'';
})

View File

@@ -34,10 +34,7 @@ let
extid = addon.passthru.extid;
# merge our requirements into the derivation args
args' = args // {
passthru = {
inherit extid;
original = addon;
} // (args.passthru or {});
passthru = addon.passthru // (args.passthru or {});
nativeBuildInputs = [
jq
strip-nondeterminism
@@ -137,6 +134,7 @@ in (lib.makeScope newScope (self: with self; {
browserpass-extension = callPackage ./browserpass-extension { };
bypass-paywalls-clean = callPackage ./bypass-paywalls-clean { };
ctrl-shift-c-should-copy = callPackage ./ctrl-shift-c-should-copy { };
firefox-xdg-open = callPackage ./firefox-xdg-open { };
i-still-dont-care-about-cookies = callPackage ./i-still-dont-care-about-cookies { };
open-in-mpv = callPackage ./open-in-mpv { };
sidebery = callPackage ./sidebery { };

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="background.js" type="module"></script>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,22 @@
//! largely copied from OpenInMPV browser extension
function xdgOpen(tabId, url) {
const code = `
var link = document.createElement('a')
link.href='xdg-open:${url}'
document.body.appendChild(link)
link.click()`
console.log(code)
chrome.tabs.executeScript(tabId, { code })
}
[["page", "pageUrl"], ["link", "linkUrl"], ["video", "srcUrl"], ["audio", "srcUrl"]].forEach(([item, linkType]) => {
chrome.contextMenus.create({
title: "xdg-open",
id: `open${item}inmpv`,
contexts: [item],
onclick: (info, tab) => {
xdgOpen(tab.id, info[linkType]);
},
});
});

View File

@@ -0,0 +1,47 @@
{
copyDesktopItems,
makeDesktopItem,
static-nix-shell,
stdenvNoCC,
zip,
}:
stdenvNoCC.mkDerivation {
pname = "firefox-xdg-open";
version = "0.1";
src = ./.;
nativeBuildInputs = [ zip ];
buildPhase = ''
runHook preBuild
zip -j firefox.zip \
background.html background.js manifest.json
runHook postBuild
'';
installPhase = ''
runHook preInstall
install firefox.zip $out
runHook postInstall
'';
passthru.extid = "@firefox-xdg-open";
passthru.systemComponent = static-nix-shell.mkBash {
pname = "xdg-open-scheme-handler";
src = ./.;
pkgs = [ "xdg-utils" ];
nativeBuildInputs = [
copyDesktopItems
];
desktopItems = [
(makeDesktopItem {
name = "xdg-open";
exec = "xdg-open-scheme-handler %U";
desktopName = "xdg-open";
comment = "Decodes xdg-open:... URIs, used to force applications to open links via the system handler";
noDisplay = true;
})
];
};
}

View File

@@ -0,0 +1,24 @@
{
"manifest_version": 2,
"browser_specific_settings": {
"gecko": {
"id": "@firefox-xdg-open"
}
},
"name": "Firefox XDG Open",
"description": "Open URIs with the system handler.",
"version": "0.1.0",
"options_ui": {},
"background": {
"page": "background.html"
},
"browser_action": {
"default_title": "xdg-open"
},
"permissions": [
"tabs",
"activeTab",
"contextMenus"
]
}

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p bash -p xdg-utils
uris=()
for u in "$@"; do
uris+=("${u/xdg-open:/}")
done
xdg-open "${uris[@]}"

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,20 @@
{
"git+https://github.com/matrix-org/matrix-authentication-service?rev=099eabd1371d2840a2f025a6372d6428039eb511#mas-http@0.8.0": "0zpykj45889vgvcay0qkcfby0pa5qczmbp6zybkrzz4kv8bk29i2",
"git+https://github.com/matrix-org/matrix-authentication-service?rev=099eabd1371d2840a2f025a6372d6428039eb511#mas-iana@0.8.0": "0zpykj45889vgvcay0qkcfby0pa5qczmbp6zybkrzz4kv8bk29i2",
"git+https://github.com/matrix-org/matrix-authentication-service?rev=099eabd1371d2840a2f025a6372d6428039eb511#mas-jose@0.8.0": "0zpykj45889vgvcay0qkcfby0pa5qczmbp6zybkrzz4kv8bk29i2",
"git+https://github.com/matrix-org/matrix-authentication-service?rev=099eabd1371d2840a2f025a6372d6428039eb511#mas-oidc-client@0.8.0": "0zpykj45889vgvcay0qkcfby0pa5qczmbp6zybkrzz4kv8bk29i2",
"git+https://github.com/matrix-org/matrix-authentication-service?rev=099eabd1371d2840a2f025a6372d6428039eb511#oauth2-types@0.8.0": "0zpykj45889vgvcay0qkcfby0pa5qczmbp6zybkrzz4kv8bk29i2",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=88c4dec35f05ae295e0f2bf0362d6f5d72606d92#matrix-sdk-base@0.7.0": "0x2k85v4bzp8fk0596pr7kvkjjfq76bqmh816g1s4avl7ks2vv5a",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=88c4dec35f05ae295e0f2bf0362d6f5d72606d92#matrix-sdk-common@0.7.0": "0x2k85v4bzp8fk0596pr7kvkjjfq76bqmh816g1s4avl7ks2vv5a",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=88c4dec35f05ae295e0f2bf0362d6f5d72606d92#matrix-sdk-crypto@0.7.0": "0x2k85v4bzp8fk0596pr7kvkjjfq76bqmh816g1s4avl7ks2vv5a",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=88c4dec35f05ae295e0f2bf0362d6f5d72606d92#matrix-sdk-indexeddb@0.7.0": "0x2k85v4bzp8fk0596pr7kvkjjfq76bqmh816g1s4avl7ks2vv5a",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=88c4dec35f05ae295e0f2bf0362d6f5d72606d92#matrix-sdk-qrcode@0.7.0": "0x2k85v4bzp8fk0596pr7kvkjjfq76bqmh816g1s4avl7ks2vv5a",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=88c4dec35f05ae295e0f2bf0362d6f5d72606d92#matrix-sdk-sqlite@0.7.0": "0x2k85v4bzp8fk0596pr7kvkjjfq76bqmh816g1s4avl7ks2vv5a",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=88c4dec35f05ae295e0f2bf0362d6f5d72606d92#matrix-sdk-store-encryption@0.7.0": "0x2k85v4bzp8fk0596pr7kvkjjfq76bqmh816g1s4avl7ks2vv5a",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=88c4dec35f05ae295e0f2bf0362d6f5d72606d92#matrix-sdk-ui@0.7.0": "0x2k85v4bzp8fk0596pr7kvkjjfq76bqmh816g1s4avl7ks2vv5a",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=88c4dec35f05ae295e0f2bf0362d6f5d72606d92#matrix-sdk@0.7.1": "0x2k85v4bzp8fk0596pr7kvkjjfq76bqmh816g1s4avl7ks2vv5a",
"git+https://github.com/matrix-org/vodozemac?rev=0c75746fc8a5eda4a0e490d345d1798b4c6cbd67#0.5.1": "10rqywmw1f14fsrjp5ibn1sykj18lhdglwajkzxdb64ivvmh4v8y",
"git+https://github.com/ruma/ruma.git?rev=4c00bd010dbdca6005bd599b52e90a0b7015d056#ruma-client-api@0.17.4": "12252g7yhqq5ha2kq2qc7g1zq5lbj0vhxm034pckalgx5lah97dn",
"git+https://github.com/ruma/ruma.git?rev=4c00bd010dbdca6005bd599b52e90a0b7015d056#ruma-common@0.12.1": "12252g7yhqq5ha2kq2qc7g1zq5lbj0vhxm034pckalgx5lah97dn",
"git+https://github.com/ruma/ruma.git?rev=4c00bd010dbdca6005bd599b52e90a0b7015d056#ruma-events@0.27.11": "12252g7yhqq5ha2kq2qc7g1zq5lbj0vhxm034pckalgx5lah97dn",
"git+https://github.com/ruma/ruma.git?rev=4c00bd010dbdca6005bd599b52e90a0b7015d056#ruma-federation-api@0.8.0": "12252g7yhqq5ha2kq2qc7g1zq5lbj0vhxm034pckalgx5lah97dn",
"git+https://github.com/ruma/ruma.git?rev=4c00bd010dbdca6005bd599b52e90a0b7015d056#ruma-html@0.1.0": "12252g7yhqq5ha2kq2qc7g1zq5lbj0vhxm034pckalgx5lah97dn",
"git+https://github.com/ruma/ruma.git?rev=4c00bd010dbdca6005bd599b52e90a0b7015d056#ruma-identifiers-validation@0.9.3": "12252g7yhqq5ha2kq2qc7g1zq5lbj0vhxm034pckalgx5lah97dn",
"git+https://github.com/ruma/ruma.git?rev=4c00bd010dbdca6005bd599b52e90a0b7015d056#ruma-macros@0.12.0": "12252g7yhqq5ha2kq2qc7g1zq5lbj0vhxm034pckalgx5lah97dn",
"git+https://github.com/ruma/ruma.git?rev=4c00bd010dbdca6005bd599b52e90a0b7015d056#ruma-push-gateway-api@0.8.0": "12252g7yhqq5ha2kq2qc7g1zq5lbj0vhxm034pckalgx5lah97dn",
"git+https://github.com/ruma/ruma.git?rev=4c00bd010dbdca6005bd599b52e90a0b7015d056#ruma@0.9.4": "12252g7yhqq5ha2kq2qc7g1zq5lbj0vhxm034pckalgx5lah97dn"
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=92b4c2a469f507696fa9db3d6bdb000a761e2694#matrix-sdk-base@0.7.0": "0bqgg2y8mfayxqlx21w0a2ldq5q47fq67m2y84arz6dbf4ck2nb6",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=92b4c2a469f507696fa9db3d6bdb000a761e2694#matrix-sdk-common@0.7.0": "0bqgg2y8mfayxqlx21w0a2ldq5q47fq67m2y84arz6dbf4ck2nb6",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=92b4c2a469f507696fa9db3d6bdb000a761e2694#matrix-sdk-crypto@0.7.1": "0bqgg2y8mfayxqlx21w0a2ldq5q47fq67m2y84arz6dbf4ck2nb6",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=92b4c2a469f507696fa9db3d6bdb000a761e2694#matrix-sdk-indexeddb@0.7.0": "0bqgg2y8mfayxqlx21w0a2ldq5q47fq67m2y84arz6dbf4ck2nb6",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=92b4c2a469f507696fa9db3d6bdb000a761e2694#matrix-sdk-qrcode@0.7.0": "0bqgg2y8mfayxqlx21w0a2ldq5q47fq67m2y84arz6dbf4ck2nb6",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=92b4c2a469f507696fa9db3d6bdb000a761e2694#matrix-sdk-sqlite@0.7.0": "0bqgg2y8mfayxqlx21w0a2ldq5q47fq67m2y84arz6dbf4ck2nb6",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=92b4c2a469f507696fa9db3d6bdb000a761e2694#matrix-sdk-store-encryption@0.7.0": "0bqgg2y8mfayxqlx21w0a2ldq5q47fq67m2y84arz6dbf4ck2nb6",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=92b4c2a469f507696fa9db3d6bdb000a761e2694#matrix-sdk-ui@0.7.0": "0bqgg2y8mfayxqlx21w0a2ldq5q47fq67m2y84arz6dbf4ck2nb6",
"git+https://github.com/matrix-org/matrix-rust-sdk.git?rev=92b4c2a469f507696fa9db3d6bdb000a761e2694#matrix-sdk@0.7.1": "0bqgg2y8mfayxqlx21w0a2ldq5q47fq67m2y84arz6dbf4ck2nb6",
"git+https://github.com/matrix-org/ruma.git?rev=4d3d8b46fd519012e4585ccf00dbea1eb602c028#ruma-client-api@0.18.0": "1a2s2hlh202h8bqlhmv4nm5bd6mlkbsbik7qmphdy9yn2398kw8b",
"git+https://github.com/matrix-org/ruma.git?rev=4d3d8b46fd519012e4585ccf00dbea1eb602c028#ruma-common@0.13.0": "1a2s2hlh202h8bqlhmv4nm5bd6mlkbsbik7qmphdy9yn2398kw8b",
"git+https://github.com/matrix-org/ruma.git?rev=4d3d8b46fd519012e4585ccf00dbea1eb602c028#ruma-events@0.28.1": "1a2s2hlh202h8bqlhmv4nm5bd6mlkbsbik7qmphdy9yn2398kw8b",
"git+https://github.com/matrix-org/ruma.git?rev=4d3d8b46fd519012e4585ccf00dbea1eb602c028#ruma-federation-api@0.9.0": "1a2s2hlh202h8bqlhmv4nm5bd6mlkbsbik7qmphdy9yn2398kw8b",
"git+https://github.com/matrix-org/ruma.git?rev=4d3d8b46fd519012e4585ccf00dbea1eb602c028#ruma-html@0.2.0": "1a2s2hlh202h8bqlhmv4nm5bd6mlkbsbik7qmphdy9yn2398kw8b",
"git+https://github.com/matrix-org/ruma.git?rev=4d3d8b46fd519012e4585ccf00dbea1eb602c028#ruma-identifiers-validation@0.9.5": "1a2s2hlh202h8bqlhmv4nm5bd6mlkbsbik7qmphdy9yn2398kw8b",
"git+https://github.com/matrix-org/ruma.git?rev=4d3d8b46fd519012e4585ccf00dbea1eb602c028#ruma-macros@0.13.0": "1a2s2hlh202h8bqlhmv4nm5bd6mlkbsbik7qmphdy9yn2398kw8b",
"git+https://github.com/matrix-org/ruma.git?rev=4d3d8b46fd519012e4585ccf00dbea1eb602c028#ruma-push-gateway-api@0.9.0": "1a2s2hlh202h8bqlhmv4nm5bd6mlkbsbik7qmphdy9yn2398kw8b",
"git+https://github.com/matrix-org/ruma.git?rev=4d3d8b46fd519012e4585ccf00dbea1eb602c028#ruma@0.10.1": "1a2s2hlh202h8bqlhmv4nm5bd6mlkbsbik7qmphdy9yn2398kw8b"
}

View File

@@ -57,8 +57,8 @@ let
domain = "gitlab.gnome.org";
owner = "GNOME";
repo = "fractal";
rev = "7";
hash = "sha256-IfcThpsGATMD3Uj9tvw/aK7IVbiVT8sdZ088gRUqnlg=";
rev = "8";
hash = "sha256-a77+lPH2eqWTLFrYfcBXSvbyyYC52zSo+Rh/diqKYx4=";
};
codegenUnits = 256; #< this does get plumbed, but doesn't seem to affect build speed
outputs = [ "out" ]; # default is "out" and "lib", but that somehow causes cycles

View File

@@ -71,135 +71,142 @@
, zsync
}:
let
version = "2024.04";
src = fetchFromGitHub {
owner = "koreader";
repo = "koreader";
name = "koreader"; # needed because `srcs = ` in the outer derivation is a list
fetchSubmodules = true;
rev = "v${version}";
hash = "sha256-BQnKoTj90wWZNxGn1C9iL8y1tozqdEHMgQDfQZo2axg=";
sourcesFor = pins: rec {
koreader = fetchFromGitHub {
owner = "koreader";
repo = "koreader";
name = "koreader"; # needed because `srcs = ` in the outer derivation is a list
fetchSubmodules = true;
rev = "v${pins.version}";
inherit (pins.koreader) hash;
};
fbink-src-ko = fetchFromGitHub {
owner = "NiLuJe";
repo = "FBInk";
name = "fbink"; # where to unpack this in `srcs`
inherit (pins.fbink) rev hash;
};
kobo-usbms-src-ko = fetchFromGitHub {
owner = "koreader";
repo = "KoboUSBMS";
name = "kobo-usbms"; # where to unpack this in `srcs`
inherit (pins.kobo-usbms) rev hash;
};
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`
inherit (pins.leptonica) rev hash;
};
libk2pdfopt-src-ko = fetchFromGitHub {
owner = "koreader";
repo = "libk2pdfopt";
name = "libk2pdfopt"; # where to unpack this in `srcs`
inherit (pins.libk2pdfopt) rev hash;
};
lodepng-src-ko = fetchFromGitHub {
owner = "lvandeve";
repo = "lodepng";
name = "lodepng"; # where to unpack this in `srcs`
inherit (pins.lodepng) rev hash;
};
lunasvg-src-ko = fetchFromGitHub {
owner = "sammycage";
repo = "lunasvg";
name = "lunasvg"; # where to unpack this in `srcs`
inherit (pins.lunasvg) rev hash;
};
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`
inherit (pins.minizip) rev hash;
};
mupdf-src-ko = fetchFromGitHub {
owner = "ArtifexSoftware";
repo = "mupdf";
name = "mupdf"; # where to unpack this in `srcs`
fetchSubmodules = true; # specifically for jbig2dec, mujs, openjpeg
inherit (pins.mupdf) rev hash;
};
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"
"${koreader}/base/thirdparty/nanosvg"
];
};
popen-noshell-src-ko = fetchFromGitHub {
owner = "famzah";
repo = "popen-noshell";
name = "popen-noshell";
inherit (pins.popen-noshell) rev hash;
};
tesseract-src-ko = fetchFromGitHub {
# TODO: try using nixpkgs' tesseract.src (i doubt it will work)
owner = "tesseract-ocr";
repo = "tesseract";
name = "tesseract";
inherit (pins.tesseract) rev hash;
};
turbo-src-ko = fetchFromGitHub {
owner = "kernelsauce";
repo = "turbo";
name = "turbo";
inherit (pins.turbo) rev hash;
};
};
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 = "47caea57aaf6200fc2b24669b6417fe6919926b7";
hash = "sha256-8Em4neXTovhrTb+GBhs6kDFEdsQSt5KiYoHURwdtjPQ=";
};
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";`
# which in turn sets `#define LJ_52 1`, and gates functions like `table.pack`, `table.unpack`.
# instead, koreader just removes the `#if LJ_52` gates. doing the same in nixpkgs seems to work.
# luajit52 = luajit.override { enable52Compat = true; self = luajit52; };
luajit52 = (luajit.override { self = luajit52; }).overrideAttrs (super: {
patches = (super.patches or []) ++ [
"${src}/base/thirdparty/luajit/koreader-luajit-enable-table_pack.patch"
];
});
thirdparty = [
curl
czmq
djvulibre
dropbear
freetype
fribidi
gettext
giflib
glib
gnutar
harfbuzz
libiconvReal
libjpeg_turbo
libpng
libunibreak
libwebp
openssl
openssh
sdcv
SDL2
sqlite
utf8proc
zlib
zeromq4
zstd
zsync
];
overlayedLuaPkgs = luaPkgs: let
ps = with ps; {
@@ -257,21 +264,6 @@ let
} // luaPkgs;
in ps;
luaEnv = luajit52.withPackages (ps: with (overlayedLuaPkgs ps); [
luajson
htmlparser
lua-spore
lpeg
luasec
luasocket
rapidjson
]);
rockspecFor = luaPkgName: let
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
@@ -310,6 +302,37 @@ let
# mostly for k2pdf, which expects lib/ and include/ for each dep to live side-by-side
libAndDev = pkg: fhsLib pkg { lib = true; include = true; };
in
stdenv.mkDerivation (finalAttrs: with finalAttrs; let
pins = lib.importJSON ./versions.json;
sources = sourcesFor pins;
# 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";`
# which in turn sets `#define LJ_52 1`, and gates functions like `table.pack`, `table.unpack`.
# instead, koreader just removes the `#if LJ_52` gates. doing the same in nixpkgs seems to work.
# luajit52 = luajit.override { enable52Compat = true; self = luajit52; };
luajit52 = (luajit.override { self = luajit52; }).overrideAttrs (super: {
patches = (super.patches or []) ++ [
"${sources.koreader}/base/thirdparty/luajit/koreader-luajit-enable-table_pack.patch"
];
});
luaEnv = luajit52.withPackages (ps: with (overlayedLuaPkgs ps); [
luajson
htmlparser
lua-spore
lpeg
luasec
luasocket
rapidjson
]);
rockspecFor = luaPkgName: let
pkg = (overlayedLuaPkgs luaEnv.pkgs)."${luaPkgName}";
in
"${luaEnv}/${pkg.rocksSubdir}/${luaPkgName}/${pkg.rockspecVersion}/${luaPkgName}-${pkg.rockspecVersion}.rockspec";
# these probably have more dirs than they really need.
djvulibreAll = fhsLib djvulibre { lib=true; include=true; flatInclude=true; };
@@ -323,7 +346,7 @@ 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!
makefileDefs = ''
makefileDefs = with sources; ''
CURL_LIB="${lib.getLib curl}/lib/libcurl.so" \
CURL_DIR="${lib.getDev curl}" \
CZMQ_LIB="${lib.getLib czmq}/lib/libczmq.so" \
@@ -419,52 +442,22 @@ let
ln -sf "${lib.getBin sdcv}/bin/sdcv" "${outdir}/sdcv"
ln -sf "${lib.getBin zsync}/bin/zsync" "${outdir}/zsync2"
'';
thirdparty = [
curl
czmq
djvulibre
dropbear
freetype
fribidi
gettext
giflib
glib
gnutar
harfbuzz
libiconvReal
libjpeg_turbo
libpng
libunibreak
libwebp
openssl
openssh
sdcv
SDL2
sqlite
utf8proc
zlib
zeromq4
zstd
zsync
];
in
stdenv.mkDerivation rec {
in {
pname = "koreader-from-src";
inherit version;
inherit (pins) version;
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
sources.koreader
sources.fbink-src-ko
sources.kobo-usbms-src-ko
sources.leptonica-src-ko
sources.libk2pdfopt-src-ko
sources.lodepng-src-ko
sources.lunasvg-src-ko
sources.minizip-src-ko
sources.mupdf-src-ko
sources.popen-noshell-src-ko
sources.tesseract-src-ko
sources.turbo-src-ko
];
patches = [
@@ -569,6 +562,8 @@ stdenv.mkDerivation rec {
htmlparser
lua-spore
;
# XXX: `update` doesn't update everything -- just the toplevel version/hash
updateScript = [ ./update ];
};
meta = with lib; {
@@ -580,4 +575,4 @@ stdenv.mkDerivation rec {
license = licenses.agpl3Only;
maintainers = with maintainers; [ colinsane contrun neonfuz];
};
}
})

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p bash curl jq moreutils nix-prefetch
# shellcheck shell=bash
#
# inspired by <repo:nixos/nixpkgs:pkgs/development/libraries/duckdb/update.sh>
cd /home/colin/nixos
# cd "$(dirname "${BASH_SOURCE[0]}")"
# nixpkgs=$(while [[ ! -e .git ]]; do [[ ${PWD} != / ]] || exit 1; cd ..; done; echo "${PWD}")
repo=koreader
owner=koreader
msg() {
echo "$*" >&2
}
get_latest() {
curl ${GITHUB_TOKEN:+" -u \":$GITHUB_TOKEN\""} -s \
"https://api.github.com/repos/${owner}/${repo}/releases/latest" | jq -r .tag_name
}
get_sha() {
curl ${GITHUB_TOKEN:+" -u \":$GITHUB_TOKEN\""} -s \
"https://api.github.com/repos/${owner}/${repo}/git/ref/tags/$1" | jq -r .object.sha
}
json_get() {
jq -r "$1" < 'versions.json'
}
json_set() {
jq --arg x "$2" "$1 = \$x" < 'versions.json' | sponge 'versions.json'
}
tag=$(get_latest)
version=${tag/v/}
msg "tag: $tag, version: $version"
[[ ${version} = $(json_get .version) ]] && { msg "${version} is up to date"; exit 0; }
sha=$(get_sha "${tag}")
sri=$(nix-prefetch --index 0 -E "koreader-from-src.overrideAttrs { version = \"${version}\"; }")
msg "sha: $sha, sri: $sri"
cd pkgs/additional/koreader-from-src
json_set ".version" "${version}"
json_set ".koreader.hash" "${sri}"

View File

@@ -0,0 +1,50 @@
{
"version": "2024.04",
"koreader": {
"hash": "sha256-BQnKoTj90wWZNxGn1C9iL8y1tozqdEHMgQDfQZo2axg="
},
"fbink": {
"rev": "1a989b30a195ca240a3cf37f9de61b4b3c7e891c",
"hash": "sha256-lXjAX0BoHW3L1E54d5J+wiAlAZXVmj9Y1Un8yaCwO8w="
},
"kobo-usbms": {
"rev": "v1.3.9",
"hash": "sha256-91B0FUnmpE6TP4Lg5mj6z/U1DZQTKiPhG3ccCSgY4mQ="
},
"leptonica": {
"rev": "1.74.1",
"hash": "sha256-SDXKam768xvZZvTbXe3sssvZyeLEEiY97Vrzx8hoc6g="
},
"libk2pdfopt": {
"rev": "47caea57aaf6200fc2b24669b6417fe6919926b7",
"hash": "sha256-8Em4neXTovhrTb+GBhs6kDFEdsQSt5KiYoHURwdtjPQ="
},
"lodepng": {
"rev": "d398e0f10d152a5d17fa30463474dc9f56523f9c",
"hash": "sha256-ApOHUgU6X1rHwyjAHA/0Nt+buDFqY2ttXEnEvdrRl3A="
},
"lunasvg": {
"rev": "59d6f6ba835c1b7c7a0f9d4ea540ec3981777885",
"hash": "sha256-gW2ikakS6Omz5upmy26nAo/jkGHYO2kjlB3UmKJBh1k="
},
"minizip": {
"rev": "0b46a2b4ca317b80bc53594688883f7188ac4d08",
"hash": "sha256-P/3MMMGYDqD9NmkYvw/thKpUNa3wNOSlBBjANHSonAg="
},
"mupdf": {
"rev": "1.13.0",
"hash": "sha256-pQejRon9fO9A1mhz3oLjBr1j4HveDLcQIWjR1/Rpy5Q="
},
"popen-noshell": {
"rev": "e715396a4951ee91c40a98d2824a130f158268bb",
"hash": "sha256-JeBZMsg6ZUGSnyZ4eds4w63gM/L73EsAnLaHOPpL6iM="
},
"tesseract": {
"rev": "60176fc5ae5e7f6bdef60c926a4b5ea03de2bfa7",
"hash": "sha256-FQvlrJ+Uy7+wtUxBuS5NdoToUwNRhYw2ju8Ya8MLyQw="
},
"turbo": {
"rev": "v2.1.3",
"hash": "sha256-vBRkFdc5a0FIt15HBz3TnqMZ+GGsqjEefnfJEpuVTBs="
}
}

View File

@@ -1,24 +1,30 @@
# N.B.: landlock is a relatively new thing as of 2024/01, and undergoing ABI revisions.
# the ABI is versioned, and the sandboxer will work when run against either a newer or older kernel than it was built from,
# but it will complain (stderr) about an update being available if kernel max ABI != sandbox max ABI.
{ stdenv
, linux
, makeLinuxHeaders
{
linux_latest,
makeLinuxHeaders,
stdenv,
}:
let
linuxHeaders = makeLinuxHeaders {
inherit (linux) src version;
inherit (linux_latest) src version;
};
in
stdenv.mkDerivation rec {
pname = "landlock-sandboxer";
version = linux.version;
src = linux.src;
version = linux_latest.version;
src = linux_latest.src;
buildInputs = [
linuxHeaders # to get the right linux headers!
];
patches = [
./no-warn-old-kernel.diff
];
# starting in 6.9, the sandboxer prints diagnostics on startup,
# which is annoying, and also risks breaking some users
postPatch = ''
@@ -34,7 +40,7 @@ stdenv.mkDerivation rec {
makeFlags = [ "sandboxer" ];
installPhase = ''
mkdir -p $out/bin
install -m755 sandboxer $out/bin
install -m755 sandboxer $out/bin/landlock-sandboxer
'';
passthru = {
@@ -46,7 +52,7 @@ stdenv.mkDerivation rec {
The goal of Landlock is to enable to restrict ambient rights (e.g. global filesystem access) for a set of processes.
'';
homepage = "https://landlock.io";
mainProgram = "sandboxer";
mainProgram = "landlock-sandboxer";
};
}

View File

@@ -0,0 +1,17 @@
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index e8223c3e781a..e4583b8447a1 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -326,12 +326,6 @@ int main(const int argc, char *const argv[], char *const *const envp)
case 4:
/* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;
-
- fprintf(stderr,
- "Hint: You should update the running kernel "
- "to leverage Landlock features "
- "provided by ABI version %d (instead of %d).\n",
- LANDLOCK_ABI_LAST, abi);
__attribute__((fallthrough));
case LANDLOCK_ABI_LAST:
break;

View File

@@ -0,0 +1,41 @@
{
fuse3,
makeBinaryWrapper,
}:
let
patched = fuse3.overrideAttrs (upstream: {
outputs = upstream.outputs ++ [ "sane" ];
defaultOutput = "sane";
patches = (upstream.patches or []) ++ [
./pass_fuse_fd.patch
];
nativeBuildInputs = (upstream.nativeBuildInputs or []) ++ [
makeBinaryWrapper
];
# wrap so that it looks for mount helpers in /run/current-system/sw/bin,
# and furthermore so that those mount helpers inherit the sandboxed wrappers in /run/current-system/sw/bin
postInstall = (upstream.postInstall or "") + ''
wrapProgram $out/sbin/mount.fuse3 \
--suffix PATH : /run/current-system/sw/bin
'';
postFixup = (upstream.postFixup or "") + ''
ln -s $out/bin/mount.fuse3 $out/bin/mount.fuse3.sane
moveToOutput bin/mount.fuse3.sane "$sane"
'';
meta = (upstream.meta or {}) // {
mainProgram = "mount.fuse3.sane";
description = ''
provides `mount.fuse3.sane`, which behaves identically to `mount.fuse3` except
it supports an additional mount flag, `-o pass_fuse_fd`.
when mounting with `-o pass_fuse_fd`, `mount.fuse3.sane` opens the `/dev/fuse` device (which requires CAP_SYS_ADMIN),
and then `exec`s the userspace implementation, which inherits this file descriptor.
`mount.fuse3.sane` invokes the userspace implementation with the device argument set to something like `/dev/fd/3`, indicating which fd holds the fuse device.
the aim of this flag is to provide a clear handoff point at which the filesystem may drop CAP_SYS_ADMIN.
in this regard, it's much like `-o drop_privileges`, only it leaves the responsibility for that to the fs impl,
in case the fs needs to preserve _other_ privileges besides CAP_SYS_ADMIN.
'';
};
});
in patched.sane

View File

@@ -0,0 +1,15 @@
diff --git a/util/mount.fuse.c b/util/mount.fuse.c
index b98fb2a..f46c4f7 100644
--- a/util/mount.fuse.c
+++ b/util/mount.fuse.c
@@ -327,6 +327,10 @@ int main(int argc, char *argv[])
if (strncmp(opt, "setuid=", 7) == 0) {
setuid_name = xstrdup(opt + 7);
ignore = 1;
+ } else if (strcmp(opt,
+ "pass_fuse_fd") == 0) {
+ pass_fuse_fd = 1;
+ ignore = 1;
} else if (strcmp(opt,
"drop_privileges") == 0) {
pass_fuse_fd = 1;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
{
"git+https://gitlab.com/news-flash/article_scraper.git#article_scraper@2.1.0": "0hslszjmz41bkl9micb4q43dcljvcqddxgk204npwpr4k4gxfc6j",
"git+https://gitlab.com/news-flash/newsblur_api.git#newsblur_api@0.3.0": "054yg4nww6c7p7y1r1yy00056p5nzadaal3hjwjfpka96zrpnvcv",
"git+https://gitlab.com/news_flash/news_flash.git#news-flash@2.3.0-alpha.0": "0fb85k4mm775iq3z0z6nvk58qx42knihnn77izcbqkxrf970njmw",
"git+https://gitlab.gnome.org/JanGernert/clapper-rs.git#clapper-gtk-sys@0.0.1": "0hxdczh0dxmql4bmh71jrxkyhc8b3vbzay074mq8db30cczl7z29",
"git+https://gitlab.gnome.org/JanGernert/clapper-rs.git#clapper-gtk@0.1.0": "0hxdczh0dxmql4bmh71jrxkyhc8b3vbzay074mq8db30cczl7z29",
"git+https://gitlab.gnome.org/JanGernert/clapper-rs.git#clapper-sys@0.0.1": "0hxdczh0dxmql4bmh71jrxkyhc8b3vbzay074mq8db30cczl7z29",
"git+https://gitlab.gnome.org/JanGernert/clapper-rs.git#clapper@0.1.0": "0hxdczh0dxmql4bmh71jrxkyhc8b3vbzay074mq8db30cczl7z29"
}

View File

@@ -0,0 +1,282 @@
# Cargo.nix and crate-hashes.json were created with:
# - `nix run '.#crate2nix' -- generate -f ~/ref/repos/news-flash/news_flash_gtk/Cargo.toml`
# - Cargo.nix is manually patched to fix build errors: apply the effects of `crate2NixOverrides` below inline
#
# the generated Cargo.nix points to an impure source (~/ref/...), but that's resolved by overriding `src` below.
{
lib,
appstream-glib,
clapper,
blueprint-compiler,
defaultCrateOverrides,
desktop-file-utils,
fetchFromGitLab,
gdk-pixbuf,
glib,
glib-networking,
gst_all_1,
gtk4,
libadwaita,
librsvg,
libxml2,
meson,
ninja,
openssl,
pkg-config,
pkgs,
rust,
sqlite,
stdenv,
webkitgtk_6_0,
wrapGAppsHook4,
writeText,
xdg-utils,
crateOverrideFn ? x: x,
}:
let
extraCrateOverrides = {
news_flash_gtk = attrs: attrs // {
src = fetchFromGitLab {
domain = "gitlab.com";
owner = "news-flash";
repo = "news_flash_gtk";
rev = "refs/tags/v.3.3.4";
hash = "sha256-N9UOvcQvunp9Ws5qQIqmGA/12YRuyj0M3MU1l44t8wU=";
#VVV fails "use of undeclared crate or module `gtk`"
# rev = "1c92539a20bc760cf3aed2c658f78c4e6b23c202"; #< 3.2.0-unstable: last commit before clapper
# hash = "sha256-iF7LwDrgyZB/OJHkdKqV7isQu8anVjrEYpHfOAkljgs=";
# rev = "refs/tags/v.3.2.0"; #< last release before clapper (i think)
# hash = "sha256-buXFQ/QAFOcdcywlacySuq8arqPEJIti1nK+yl3yWck=";
};
# codegenUnits = 16; # speeds up the build a bit
# outputs = [ "out" ]; # default is "out" and "lib", but that somehow causes cycles
# outputDev = [ "out" ];
nativeBuildInputs = [
appstream-glib
blueprint-compiler
desktop-file-utils
meson
ninja
pkg-config
wrapGAppsHook4
];
buildInputs = [
# clapper
# glib
# glib-networking #< TLS support for loading external content in webkitgtk_6_0 WebView
# gst_all_1.gst-plugins-bad
# gst_all_1.gst-plugins-base
# gst_all_1.gst-plugins-good
# gst_all_1.gstreamer
# gtk4
# libadwaita
# librsvg #< SVG support for gdk-pixbuf
# libxml2
# openssl
# sqlite
# webkitgtk_6_0
# xdg-utils
];
postPatch = ''
rm build.rs
'';
# mesonFlags = let
# # this gets meson to shutup about rustc not producing executables.
# # kinda silly though, since we patch out the actual cargo (rustc) invocations.
# crossFile = writeText "cross-file.conf" ''
# [binaries]
# rust = [ 'rustc', '--target', '${rust.toRustTargetSpec stdenv.hostPlatform}' ]
# '';
# in
# lib.optionals (stdenv.hostPlatform != stdenv.buildPlatform) [
# "--cross-file=${crossFile}"
# ];
# postPatch = ''
# rm build.rs
# # patch so meson will invoke our `crate2nix_cmd.sh` instead of cargo
# substituteInPlace src/meson.build \
# --replace-fail 'cargo_options,' "" \
# --replace-fail "cargo, 'build'," "'bash', 'crate2nix_cmd.sh'," \
# --replace-fail "'src' / rust_target" "'src/bin'"
# '';
# postConfigure = ''
# # copied from <pkgs/development/tools/build-managers/meson/setup-hook.sh>
# mesonFlags="--prefix=$prefix $mesonFlags"
# mesonFlags="\
# --libdir=''${!outputLib}/lib --libexecdir=''${!outputLib}/libexec \
# --bindir=''${!outputBin}/bin --sbindir=''${!outputBin}/sbin \
# --includedir=''${!outputInclude}/include \
# --mandir=''${!outputMan}/share/man --infodir=''${!outputInfo}/share/info \
# --localedir=''${!outputLib}/share/locale \
# -Dauto_features=''${mesonAutoFeatures:-enabled} \
# -Dwrap_mode=''${mesonWrapMode:-nodownload} \
# $mesonFlags"
# mesonFlags="''${crossMesonFlags+$crossMesonFlags }--buildtype=''${mesonBuildType:-plain} $mesonFlags"
# echo "meson flags: $mesonFlags ''${mesonFlagsArray[@]}"
# meson setup build $mesonFlags "''${mesonFlagsArray[@]}"
# cd build
# '';
# preBuild = ''
# build_bin() {
# # build_bin is what buildRustCrate would use to invoke rustc, but we want to drive the build
# # with meson instead. however, meson doesn't know how to plumb our rust dependencies into cargo,
# # so we still need to use build_bin for just one portion of the build.
# #
# # so, this mocks out the original build_bin:
# # - we patch upstream flare to call our `crate2nix_cmd.sh` when it wants to compile the rust.
# # - we don't actually invoke meson (ninja) at all here, but rather in the `installPhase`.
# # if we invoked it here, the whole build would just get re-done in installPhase anyway.
# #
# # rustc invocation copied from <pkgs/build-support/rust/build-rust-crate/lib.sh>
# crate_name_=flare
# main_file=../src/main.rs
# fix_link="-C linker=${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc"
# cat >> crate2nix_cmd.sh <<EOF
# set -x
# rmdir target/bin
# rmdir target
# ln -s ../target .
# rustc \
# $fix_link \
# --crate-name $crate_name_ \
# $main_file \
# --crate-type bin \
# $BIN_RUSTC_OPTS \
# --out-dir target/bin \
# -L dependency=target/deps \
# $LINK \
# $EXTRA_LINK_ARGS \
# $EXTRA_LINK_ARGS_BINS \
# $EXTRA_LIB \
# --cap-lints allow \
# $BUILD_OUT_DIR \
# $EXTRA_BUILD \
# $EXTRA_FEATURES \
# $EXTRA_RUSTC_FLAGS \
# --color ''${colors}
# EOF
# }
# '';
# installPhase = "ninjaInstallPhase";
};
# clapper = attrs: attrs // {
# # XXX: newsflash uses clapper-sys (clapper-rs) from here: <https://gitlab.gnome.org/JanGernert/clapper-rs>
# nativeBuildInputs = [ pkg-config ] ++ clapper.nativeBuildInputs;
# buildInputs = [ clapper gtk4 ] ++ clapper.buildInputs;
# # inherit (clapper) nativeBuildInputs buildInputs;
# };
clapper-gtk-sys = attrs: attrs // {
nativeBuildInputs = [ pkg-config ];
buildInputs = [ clapper ] ++ clapper.buildInputs;
};
clapper-sys = attrs: attrs // {
# XXX: newsflash uses clapper-sys (clapper-rs) from here: <https://gitlab.gnome.org/JanGernert/clapper-rs>
nativeBuildInputs = [ pkg-config ];
buildInputs = [ clapper ] ++ clapper.buildInputs;
# inherit (clapper) nativeBuildInputs buildInputs;
};
gstreamer-audio-sys = attrs: attrs // {
nativeBuildInputs = [ pkg-config ];
buildInputs = [ gst_all_1.gst-plugins-base ];
};
gstreamer-base-sys = attrs: attrs // {
nativeBuildInputs = [ pkg-config ];
buildInputs = [ gst_all_1.gst-plugins-base ];
};
gstreamer-pbutils-sys = attrs: attrs // {
nativeBuildInputs = [ pkg-config ];
buildInputs = [ gst_all_1.gst-plugins-base ];
};
gstreamer-play-sys = attrs: attrs // {
nativeBuildInputs = [ pkg-config ];
buildInputs = [
gst_all_1.gst-plugins-bad
gst_all_1.gst-plugins-base
];
};
gstreamer-sys = attrs: attrs // {
nativeBuildInputs = [ pkg-config ];
buildInputs = [ gst_all_1.gst-plugins-base ];
};
gstreamer-video-sys = attrs: attrs // {
nativeBuildInputs = [ pkg-config ];
buildInputs = [ gst_all_1.gst-plugins-base ];
};
libadwaita-sys = attrs: attrs // {
nativeBuildInputs = [ pkg-config ];
buildInputs = [ libadwaita ];
};
libxml = attrs: attrs // {
nativeBuildInputs = [ pkg-config ];
buildInputs = [ libxml2 ];
};
javascriptcore6-sys = attrs: attrs // {
nativeBuildInputs = [ pkg-config ];
buildInputs = [ webkitgtk_6_0 ];
};
rav1e = attrs: attrs // {
# TODO: `rav1e` is actually packaged in nixpkgs as a library:
# is there any way i can reuse that?
CARGO_ENCODED_RUSTFLAGS = "";
};
webkit6-sys = attrs: attrs // {
nativeBuildInputs = [ pkg-config ];
buildInputs = [ webkitgtk_6_0 ];
};
};
defaultCrateOverrides' = defaultCrateOverrides // (lib.mapAttrs (crate: fn:
# map each `extraCrateOverrides` to first pass their attrs through `defaultCrateOverrides`
attrs: fn ((defaultCrateOverrides."${crate}" or (a: a)) attrs)
) extraCrateOverrides);
crate2NixOverrides = crates: crates // {
# crate2nix sometimes "misses" dependencies, or gets them wrong in a way that crateOverrides can't patch.
# this function lets me patch over Cargo.nix without actually modifying it by hand.
ashpd = crates.ashpd // {
# specifically, it needs zvariant; providing that through zbus is a convenient way to also
# coerce the feature flags so as to reduce rebuilds
dependencies = crates.ashpd.dependencies ++ crates.zbus.dependencies;
};
clapper = crates.clapper // {
workspace_member = "libclapper-rs";
};
clapper-gtk = crates.clapper // {
workspace_member = "libclapper-gtk-rs";
};
clapper-gtk-sys = crates.clapper-gtk-sys // {
workspace_member = "libclapper-gtk-rs/sys";
};
};
cargoNix = import ./Cargo.nix {
inherit pkgs;
release = false; #< XXX(2023/12/06): `release=true` is incompatible with cross compilation
# rootFeatures = [ "default" ];
rootFeatures = [ ]; #< avoids --cfg feature="default", simplifying the rustc CLI so that i can pass it around easier
defaultCrateOverrides = defaultCrateOverrides';
};
in cargoNix.rootCrate
# builtCrates = cargoNix.internal.builtRustCratesWithFeatures {
# packageId = "news_flash_gtk";
# features = [ "default" ];
# buildRustCrateForPkgsFunc = pkgs: pkgs.buildRustCrate.override {
# defaultCrateOverrides = defaultCrateOverrides';
# };
# crateConfigs = crate2NixOverrides cargoNix.internal.crates;
# runTests = false;
# };
# in builtCrates.crates.news_flash_gtk.overrideAttrs (super: {
# passthru = (super.passthru or {}) // {
# inherit (builtCrates) crates;
# };
# })

View File

@@ -7,8 +7,8 @@ let
src = fetchFromGitHub {
owner = "nix-community";
repo = "nixpkgs-wayland";
rev = "6642ef653bf2fcd39b118ada1371ba4519bdcc7b";
hash = "sha256-NWY1LnxdZOGFt5/VHcL4GPGSP6iBniOZBHz3WCxwhZI=";
rev = "423d21c157c2a7b384ae9c766e25759576dceb87";
hash = "sha256-2BAfUaRzrhHwT/JodTq++DUf/nljgoFEc5A79SS8ydU=";
};
flake = import "${src}/flake.nix";
evaluated = flake.outputs {
@@ -25,7 +25,7 @@ let
in src.overrideAttrs (base: {
# attributes required by update scripts
pname = "nixpkgs-wayland";
version = "0-unstable-2024-07-28";
version = "0-unstable-2024-08-06";
src = src;
# passthru only nixpkgs-wayland's own packages -- not the whole nixpkgs-with-nixpkgs-wayland-as-overlay:

View File

@@ -27,12 +27,12 @@
}:
let
lock = {
master.rev = "170e9953db70bbfb7e1ecf557dbbcee749109953";
master.sha256 = "sha256-KdwYAhVIlEdIQQdvoEP4lGYqnhaa5rqXuRZ9mS4I9u0=";
staging.rev = "170e9953db70bbfb7e1ecf557dbbcee749109953";
staging.sha256 = "sha256-KdwYAhVIlEdIQQdvoEP4lGYqnhaa5rqXuRZ9mS4I9u0=";
staging-next.rev = "170e9953db70bbfb7e1ecf557dbbcee749109953";
staging-next.sha256 = "sha256-KdwYAhVIlEdIQQdvoEP4lGYqnhaa5rqXuRZ9mS4I9u0=";
master.rev = "0048951e7a3eba3dce8bf6ba893390d29375c5d0";
master.sha256 = "sha256-NSvqhZYv0LY2eh0a3yoAvJjGlWeOLSjmPpivSnY9pXg=";
staging.rev = "0048951e7a3eba3dce8bf6ba893390d29375c5d0";
staging.sha256 = "sha256-NSvqhZYv0LY2eh0a3yoAvJjGlWeOLSjmPpivSnY9pXg=";
staging-next.rev = "0048951e7a3eba3dce8bf6ba893390d29375c5d0";
staging-next.sha256 = "sha256-NSvqhZYv0LY2eh0a3yoAvJjGlWeOLSjmPpivSnY9pXg=";
};
lock' = lock."${variant}";
unpatchedSrc = fetchzip {
@@ -81,7 +81,7 @@ in
src.overrideAttrs (base: {
# attributes needed for update scripts
pname = "nixpkgs";
version = "24.05-unstable-2024-07-29";
version = "24.05-unstable-2024-08-09";
passthru = (base.passthru or {}) // nixpkgs // {
src = unpatchedSrc // {
inherit (lock') rev;

View File

@@ -32,6 +32,27 @@ in
# hash = "sha256-fGuS46f9qSMRHvWZvTmcirKufIqlXHwwhckeK1RNejE=";
# })
(fetchpatch' {
title = "nixos/pam: replace apparmor warnings with assertions";
prUrl = "https://github.com/NixOS/nixpkgs/pull/332119";
saneCommit = "17e5fa9dc3c6d9f1fbfa2b23f6e1ae5c7e17bebd";
hash = "sha256-9UrJB/ijXL07H/SESquCCqI1boVoYpDcYqxD+Mx2Mwc=";
})
(fetchpatch' {
title = "hare-ev: 2024-07-11 -> 2024-08-06";
prUrl = "https://github.com/NixOS/nixpkgs/pull/333378";
hash = "sha256-3RnqId/Rk0A5YyvsixLvKyLFOiFuvlThKdT00D6hjWI=";
})
(fetchpatch' {
# this causes a rebuild of systemd and everything above it:
# PR against staging is live: <https://github.com/NixOS/nixpkgs/pull/332399>
title = "libcap: ship the optional 'captree' component";
saneCommit = "30d6d5d6e86c490978b9615a9c685ffd92c81116";
hash = "sha256-n8EERqqegrE+4Ogl7AuXkcRW9sgQhe5xyugZJrVr19Y=";
})
(fetchpatch' {
# merged into staging 2024-07-25
title = "texinfo: set texinfo_cv_sys_iconv_converts_euc_cn=yes when crosscompiling";

View File

@@ -58,16 +58,6 @@ let
sane-bin = {
# anything added to this attrset gets symlink-joined into `sane-scripts`
# and is made available through `sane-scripts.passthru`
backup-ls = static-nix-shell.mkBash {
pname = "sane-backup-ls";
srcRoot = ./src;
pkgs = [ "duplicity" ];
};
backup-restore = static-nix-shell.mkBash {
pname = "sane-backup-restore";
srcRoot = ./src;
pkgs = [ "duplicity" ];
};
bt-add = static-nix-shell.mkPython3 {
pname = "sane-bt-add";
srcRoot = ./src;

View File

@@ -1,11 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p bash -p duplicity
# N.B. must be run as root
set -ex
# source the URL; hack to satisfy resholve
external_cmd="source /run/secrets/duplicity_passphrase.env"
$external_cmd
duplicity list-current-files --archive-dir /var/lib/duplicity $DUPLICITY_URL

View File

@@ -1,14 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p bash -p duplicity
# N.B. must be run as root
set -ex
dest_path="$1"
source_path="$2"
# source the URL; hack to satisfy resholve
external_cmd="source /run/secrets/duplicity_passphrase.env"
$external_cmd
duplicity restore --archive-dir /var/lib/duplicity --file-to-restore "$source_path" $DUPLICITY_URL "$dest_path"

View File

@@ -86,11 +86,13 @@ options:
--producer PRODUCER use when the artist is a pseudonym, and this is their umbrella name.
--title TITLE
--trackno TRACK_NUMBER
--derive apply existing tags (e.g. album) found in the file set to any files in the set missing such tags
--derive apply existing tags (e.g. album) found in the file set to any files in the set missing such tags.
additionally, extrapolate from the file path any missing tags.
--ignore-existing completely ignore the existing on-disk tags. compute tags only from those manually provided, and what can be derived from the file path (if --derive was passed)
--override-existing apply derived tags to each file, even those which already have tags.
only makes sense when paired with --derive.
--type audio|image|text skip files which aren't of some specific media type
fix-tags options:
--force: apply path-based tag to each file, even those which already have tags
"""
from dataclasses import dataclass
@@ -228,6 +230,27 @@ def loose_compare_lists(a: list[str], b: list[str]) -> bool:
b = sorted(clean_for_loose_compare(i) for i in b)
return a == b
def choose_best(a: list[str], b: list[str]) -> list[str]:
primary = a[:]
secondary = b[:]
if len(primary) < len(secondary):
primary, secondary = secondar, primary
return substitute_from_if_better(primary, secondary)
def substitute_from_if_better(primary: list[str], secondary: list[str]) -> list[str]:
"""
if any item in secondary looks like a higher-fidelity equivalent to one in `primary`,
replace the one in `primary` with the better one from `secondary`
"""
substituted = primary[:]
for s in secondary:
for i, p in enumerate(primary):
if p == clean_for_fs(s):
substituted[i] = s
return substituted
def clean_for_fs(a: str, single_field: bool=False) -> str:
preserve = 'abcdefghijklmnopqrstuvwxyz0123456789._-'
a = romanize(a)
@@ -286,34 +309,60 @@ class Tags:
def __repr__(self) -> str:
return f"artist:{self.producer}/{self.albumartist}/{self.artist}, album:{self.album}, title:{self.title}, trackno:{self.tracknumber}"
def merge(self, other: 'Tags', merge_field) -> 'Tags':
def clone(self) -> 'Tags':
return Tags(
album=merge_field(self.album, other.album),
albumartist=merge_field(self.albumartist, other.albumartist),
artist=merge_field(self.artist, other.artist),
producer=merge_field(self.producer, other.producer),
title=merge_field(self.title, other.title),
tracknumber=merge_field(self.tracknumber, other.tracknumber),
album=self.album,
albumartist=self.albumartist,
artist=self.artist,
producer=self.producer,
title=self.title,
tracknumber=self.tracknumber,
)
def merge(self, other: 'Tags', merge_field) -> 'Tags':
new_tags = self.clone()
new_tags.merge_in_place(other, merge_field)
return new_tags
def map(self, f) -> 'Tags':
new_tags = self.clone()
new_tags.update(f)
return new_tags
def merge_in_place(self, other: 'Tags', merge_field) -> None:
self.album = merge_field(self.album, other.album)
self.albumartist = merge_field(self.albumartist, other.albumartist)
self.artist = merge_field(self.artist, other.artist)
self.producer = merge_field(self.producer, other.producer)
self.title = merge_field(self.title, other.title)
self.tracknumber = merge_field(self.tracknumber, other.tracknumber)
def update(self, f) -> None:
self.merge_in_place(self, lambda v, _: f(v))
def or_(self, other: 'Tags') -> 'Tags':
"""
substitute any tags missing tags in `self` with those from `fallback`.
i.e. `self` takes precedence over `fallback`.
"""
# def merge_field(primary: list[str], secondary: list[str]) -> list[str]:
# # primary_lower = [i.lower() for i in primary]
# # return primary + [i for i in secondary if i.lower() not in primary_lower]
# return primary or secondary
return self.merge(other, lambda a, b: a or b)
def extrapolate_from(self, other: 'Tags') -> 'Tags':
"""
if anything in @other looks like a better version of the same data in @self,
then use the better version.
for example, if @self has `The.Best.Original.Mix` where @other has `The Best (Original Mix)`,
then use @other's version instead of @self's
"""
return self.merge(other, substitute_from_if_better)
def union(self, other: 'Tags') -> 'Tags':
def merge_field(a: list[str], b: list[str]) -> list[str]:
return a + [i for i in b if i not in a]
return self.merge(other, merge_field)
def unambiguous(self) -> 'Tags':
return self.merge(self, lambda f, _: f if len(f) == 1 else [])
return self.map(lambda f: f if len(f) == 1 else [])
# def intersection(self, other: 'Tags') -> 'Tags':
# def merge_field(primary: list[str], secondary: list[str]):
@@ -329,13 +378,10 @@ class Tags:
stripped = [ i.strip().replace('\u200b', '') for i in field ]
unique = []
for i in stripped:
if i not in unique:
if i and i not in unique:
unique.append(i)
return unique
self.title = trim(self.title)
self.artist = trim(self.artist)
self.albumartist = trim(self.albumartist)
self.album = trim(self.album)
self.update(lambda f: trim(f))
def cleanup_trackno(self) -> None:
self.tracknumber = self.tracknumber[:] # to avoid modifying shared structures
@@ -369,22 +415,13 @@ class Tags:
def promote_albumartist(self) -> None:
"""
1. replace shorthands like "V.A." with "Various Artists".
2. if there's only an album artist, and no track artist, turn the album artist into the track artist.
3. if the artist and album artist are nearly identical, try to merge them.
1. if the artist and album artist are nearly identical, try to merge them.
2. if the artist is empty, but we know the album artist, then make that also be the artist.
"""
if loose_compare_lists(self.artist, self.albumartist):
# artist & album artist are nearly identical:
# probably guessed one of them from filename, which was lacking certain symbols of the actual artist.
# recover whichever of these fields had the fewer characters removed (i.e. is longest)
if len("".join(self.artist)) > len("".join(self.albumartist)):
self.albumartist = self.artist
elif self.albumartist == [clean_for_fs(a) for a in self.albumartist] and self.artist != [clean_for_fs(a) for a in self.artist]:
# the album artist was taken from disk (bad), but the artist was provided in some way that preserves more info
self.albumartist = self.artist
elif self.albumartist and all(self.albumartist):
self.artist = self.albumartist
self.albumartist, self.artist = (
substitute_from_if_better(self.albumartist, self.artist),
substitute_from_if_better(self.artist, self.albumartist),
)
if self.artist == []:
self.artist = self.albumartist
@@ -782,20 +819,53 @@ class MediaFile:
self.meta.set_tag(self.tag_field_names.tracknumber, tags.tracknumber)
self.meta.flush()
class Tagger:
def __init__(self, dry_run: bool, force: bool, derive_from_path: bool, manual_tags: Tags):
self.dry_run = dry_run
self.force = force
self.manual_tags = manual_tags
self.derive_from_path = derive_from_path
class MediaFileWithNeighbors(MediaFile):
"""
a MediaFile, augmented with tags that are common to all of its neighbors
"""
neighbor_tags: Tags = Tags()
def with_base_tags(self, fallback_tags: Tags) -> 'Tagger':
return Tagger(
dry_run = self.dry_run,
force = self.force,
manual_tags = self.manual_tags.or_(fallback_tags),
derive_from_path = self.derive_from_path,
)
class TagsProvider:
def __init__(self, ignore_existing: bool, override_existing: bool, derive: bool, manual_tags: Tags):
self.ignore_existing = ignore_existing
self.override_existing = override_existing
self.derive = derive
self.manual_tags = manual_tags
def can_derive_from_neighbors(self) -> bool:
return self.derive and not self.ignore_existing
def on_disk(self, file_: MediaFile) -> Tags:
if self.ignore_existing:
return Tags()
return file_.tags_on_disk()
def derived(self, file_: MediaFile) -> Tags:
if not self.derive:
return Tags()
on_disk = self.on_disk(file_)
path_tags = Tags.from_path(file_.path_)
my_derived_tags = path_tags.extrapolate_from(on_disk)
if on_disk.is_artist_item(file_.ext):
# we can't generalize *any* tags to an artist item (e.g. Justice/artist.png)
return my_derived_tags
if not isinstance(file_, MediaFileWithNeighbors):
return my_derived_tags
neighbor_tags = file_.neighbor_tags
if self.override_existing:
# our derived tags overrule anything generalized from our neighbors
return my_derived_tags.or_(neighbor_tags)
else:
return neighbor_tags.or_(my_derived_tags)
class Tagger:
def __init__(self, dry_run: bool, tags_provider: TagsProvider):
self.dry_run = dry_run
self.tags_provider = tags_provider
def show(self, file_: MediaFile) -> None:
tags = self.tags_for(file_)
@@ -814,31 +884,19 @@ class Tagger:
def tags_for(self, file_: MediaFile) -> Tags:
"""
return the tags stored in @file_, plus any we can derive from its path or our manual tags.
return the tags stored in @file_, plus any we can derive from its path/neighbors, or our manual tags.
"""
old_tags = file_.tags_on_disk()
path_tags = Tags.from_path(file_.path_) if self.derive_from_path else Tags()
manual_tags = self.manual_tags
if self.force:
# manual_tags > path_tags > old_tags
new_tags = manual_tags.or_(path_tags).or_(old_tags)
manual = self.tags_provider.manual_tags
derived = self.tags_provider.derived(file_)
on_disk = self.tags_provider.on_disk(file_)
if self.tags_provider.override_existing:
# manual tags > derived tags > on_disk
new_tags = manual.or_(derived).or_(on_disk)
else:
# manual_tags > old_tags > path_tags
# old_tags overrule path_tags
new_tags = manual_tags.or_(old_tags).or_(path_tags)
# special case that explicitly supplying empty tags should delete the existing
if manual_tags.album == [""]:
new_tags.album = []
if manual_tags.albumartist == [""]:
new_tags.albumartist = []
if manual_tags.artist == [""]:
new_tags.artist = []
if manual_tags.producer == [""]:
new_tags.producer = []
if manual_tags.title == [""]:
new_tags.title = []
if manual_tags.tracknumber == [""]:
new_tags.tracknumber = []
# manual tags > on_disk > derived tags
new_tags = manual.or_(on_disk).or_(derived)
new_tags.trim_fields()
new_tags.cleanup_trackno()
new_tags.expand_shorthands()
@@ -856,9 +914,8 @@ class Tagger:
self.show_tagdif(file_.path_, old_tags, new_tags)
if self.confirm():
if self.guard_dry_run("writing tags"):
file_.write_tags(new_tags)
if self.guard_dry_run("writing tags"):
file_.write_tags(new_tags)
def fix_path(self, file_: MediaFile) -> None:
tags = self.tags_for(file_)
@@ -873,11 +930,10 @@ class Tagger:
self.show_pathdif(file_.path_, new_path)
if self.confirm():
if self.guard_dry_run(f"moving file"):
assert not os.path.exists(new_path), f"{file_.path_} -> {new_path} would clobber destination!"
# os.renames creates the necessary parents, and then prunes leaf directories
os.renames(file_.path_, new_path)
if self.guard_dry_run(f"moving file"):
assert not os.path.exists(new_path), f"{file_.path_} -> {new_path} would clobber destination!"
# os.renames creates the necessary parents, and then prunes leaf directories
os.renames(file_.path_, new_path)
def show_pathdif(self, old_path: str, new_path: str):
logger.info( "updating path:")
@@ -893,10 +949,6 @@ class Tagger:
logger.debug(f"skipping unchanged {path_}")
logger.debug(f" {tags}")
def confirm(self) -> bool:
# TODO: actually prompt
return True
def guard_dry_run(self, msg: str) -> bool:
if self.dry_run:
print(f"dry run: not {msg}")
@@ -905,43 +957,35 @@ class Tagger:
return True
class Gatherer:
def __init__(self, roots: list[str], media_type: MediaType|None = None, derive_tags: bool = False):
def __init__(self, roots: list[str], media_type: MediaType|None, tags_provider: TagsProvider):
self.roots = roots
self.media_type = media_type
self.derive_tags = derive_tags
self.tags_provider = tags_provider
def files(self) -> list[tuple[MediaFile, Tags]]:
def files(self) -> list[MediaFile]:
"""
returns a list where each item is a tuple of:
- a file to be processed
- additional tags which are applicable to that file
iterates over files which match the media_type.
note that the yielded file may actually be a more specialized MediaFileWithNeighbors instance,
in case we're deriving tags from neighboring files.
"""
for root in self.roots:
_tags_seen, files = self.files_below(root)
for file_, tags in files:
for file_ in files:
if self.media_type is not None and not file_.is_type(self.media_type):
continue
ftags = file_.tags_on_disk()
if ftags == Tags():
ftags = Tags.from_path(file_.path_)
if ftags.is_artist_item(file_.ext):
# we can't generalize *any* tags to an artist item (e.g. Justice/artist.png)
yield file_, Tags()
else:
yield file_, tags
yield file_
def files_below(self, root: str) -> tuple[Tags, list[tuple[MediaFile, Tags]]]:
def files_below(self, root: str) -> tuple[Tags, list[MediaFileWithNeighbors]]:
"""
returns: (tags_seen, files)
where each file is (file, derived_tag)
"""
if not os.path.isdir(root):
# single file
file_ = MediaFile.new(root)
return file_.tags_on_disk(), [ (file_, Tags()) ]
file_ = MediaFileWithNeighbors.new(root)
return self.tags_provider.on_disk(file_), [ file_ ]
if not self.derive_tags:
if not self.tags_provider.can_derive_from_neighbors():
# directory, but don't derive any tags
def _gen():
for filename in os.listdir(root):
@@ -964,8 +1008,9 @@ class Gatherer:
derived_tags.tracknumber = []
def _gen():
for files_below in filelists:
for file_, tags in files_below:
yield file_, tags.or_(derived_tags)
for file_ in files_below:
file_.neighbor_tags = file_.neighbor_tags.or_(derived_tags)
yield file_
return tags_seen, _gen()
@@ -980,10 +1025,12 @@ def main():
parser.add_argument('--album', help="manually specify the tag")
parser.add_argument('--album-artist', help="manually specify the tag")
parser.add_argument('--artist', help="manually specify the tag")
parser.add_argument('--ignore-existing', action='store_true', default=False, help="completely ignore existing tags")
parser.add_argument('--override-existing', action='store_true', default=False, help="give higher precedence to derived tags than to existing tags")
parser.add_argument('--producer', help="manually specify the tag")
parser.add_argument('--title', help="manually specify the tag")
parser.add_argument('--trackno', help="manually specify the tag")
parser.add_argument('--derive', action='store_true', help="apply tags already existing in one file (e.g. album tag) to adjacent files in the set")
parser.add_argument('--derive', action='store_true', default=False, help="apply tags already existing in one file (e.g. album tag) to adjacent files in the set")
parser.add_argument('--type', type=MediaType, help="only apply operation to a specific type of media")
subparsers = parser.add_subparsers(help="what to do")
@@ -998,7 +1045,6 @@ def main():
fix_tags_parser = subparsers.add_parser("fix-tags")
fix_tags_parser.set_defaults(subcommand="fix_tags")
fix_tags_parser.add_argument('--force', action='store_true', help="give higher credence to path-based and manual tags than any existing tags")
fix_tags_parser.add_argument("path", nargs="+", help="relative path to a file to tag")
fix_paths_parser = subparsers.add_parser("fix-paths")
@@ -1010,8 +1056,6 @@ def main():
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
gatherer = Gatherer(args.path, args.type, getattr(args, "derive", False))
manual_tags = Tags(
album=[args.album] if args.album is not None else [],
albumartist=[args.album_artist] if args.album_artist is not None else [],
@@ -1021,27 +1065,30 @@ def main():
tracknumber=[args.trackno] if args.trackno is not None else [],
)
tagger = Tagger(
dry_run=args.dry_run,
force=getattr(args, "force", False),
derive_from_path=getattr(args, "derive", False),
manual_tags=manual_tags,
tags_provider = TagsProvider(
ignore_existing=args.ignore_existing,
override_existing=args.override_existing,
derive=args.derive,
manual_tags=manual_tags
)
gatherer = Gatherer(args.path, args.type, tags_provider)
tagger = Tagger(dry_run=args.dry_run, tags_provider=tags_provider)
if args.subcommand == "show":
for f, tags in gatherer.files():
tagger.with_base_tags(tags).show(f)
for f in gatherer.files():
tagger.show(f)
elif args.subcommand == "show_missing":
for f, tags in gatherer.files():
tagger_ = tagger.with_base_tags(tags)
if not tagger_.is_sufficiently_tagged(f):
for f in gatherer.files():
if not tagger.is_sufficiently_tagged(f):
tagger.show(f)
elif args.subcommand == "fix_tags":
for f, tags in gatherer.files():
tagger.with_base_tags(tags).tag_file(f)
for f in gatherer.files():
tagger.tag_file(f)
elif args.subcommand == "fix_paths":
for f, tags in gatherer.files():
tagger.with_base_tags(tags).fix_path(f)
for f in gatherer.files():
tagger.fix_path(f)
else:
assert False, f"unrecognized command {args.subcommand}"

View File

@@ -10,7 +10,7 @@ usage:
sane-vpn [flags ...] operation [operation flags]
flags:
--debug
--verbose
--no-proxy-dns
operations:
@@ -234,6 +234,8 @@ def vpn_do(config: VpnConfig, cmd: list[str]) -> None:
wrapped_cmd = [
"sanebox",
# method: pastaonly, should be enough, but it leaves us as root in the user namespace.
# some apps don't like that -- bwrap is the fix to have uid mapping
"--sanebox-method", "bwrap",
"--sanebox-keep-namespace", "all",
"--sanebox-path", "/",
@@ -269,7 +271,7 @@ def vpn_toggle(config: VpnConfig, dir_: ToggleDir) -> None:
"ip", "rule", verb,
"not", "from", "all",
"fwmark", str(config.fwmark),
"lookup", str(config.id),
"lookup", str(config.id_),
"priority", str(config.priority_fwmark),
])
@@ -280,21 +282,21 @@ def vpn_toggle(config: VpnConfig, dir_: ToggleDir) -> None:
def dns_toggle(dns: list[str], dir_: ToggleDir) -> None:
if dir_ == ToggleDir.Up:
formatted_nameservers = "\n".join(
formatted_nameservers = ",\n".join(
'{ socket_addr = "{ns}:53", protocol = "udp", trust_nx_responses = false }'.replace("{ns}", ns)
for ns in dns
)
text = '''
text = f'''
[[zones]]
zone = "."
zone_type = "Forward"
stores = { type = "forward", name_servers = [
{formatted_name_servers}
]}
'''.replace("{formatted_nameservers}", formatted_nameservers)
stores = {{ type = "forward", name_servers = [
{formatted_nameservers}
]}}
'''
elif dir_ == ToggleDir.Down:
text = ""
with open("/var/lib/trust-dns/dhcp-config/sane-vpn.toml", "w") as f:
with open("/var/lib/trust-dns/dhcp-configs/sane-vpn.toml", "w") as f:
f.write(text)
subprocess.check_call([ "systemctl", "restart", "trust-dns-localhost" ])

View File

@@ -31,6 +31,7 @@ stdenv.mkDerivation {
--replace-fail '@iptables@' '${lib.getExe' iptables "iptables"}' \
--replace-fail '@landlockSandboxer@' '${lib.getExe landlock-sandboxer}' \
--replace-fail '@pasta@' '${lib.getExe' passt "pasta"}' \
--replace-fail '@readlink@' '${lib.getExe' coreutils "readlink"}' \
runHook postBuild
'';

View File

@@ -9,6 +9,7 @@ IP_FALLBACK='@ip@'
IPTABLES_FALLBACK='@iptables@'
LANDLOCK_SANDBOXER_FALLBACK='@landlockSandboxer@'
PASTA_FALLBACK='@pasta@'
READLINK_FALLBACK='@readlink@'
## EARLY DEBUG HOOKS
@@ -78,7 +79,9 @@ capabilities=()
# keepNamespace:
# - "cgroup"
# - "ipc"
# - "net"
# - "pid": if this process may wany to query /proc/$PID/... of parent/sibling processes.
# - "user": only applicable if running as root
# - "uts"
# - "all": as if all the above were specified
keepNamespace=()
@@ -89,11 +92,13 @@ netDev=
netGateway=default
# list of IP addresses to use for DNS servers inside the sandbox (not supported by all backends)
dns=()
# list of `VAR=VALUE` environment variables to add to the sandboxed program's environment
portalEnv=()
# VAR -> VALUE map of environment variables to add to the sandboxed program's environment
declare -A portalEnv
# arguments to forward onto a specific backend (if that backend is active)
bwrapFlags=()
bwrapArgs=()
capshArgs=()
pastaArgs=()
usage() {
echo 'sanebox: run a program inside a sandbox'
@@ -117,18 +122,20 @@ usage() {
echo ' use a specific sandboxer'
echo ' --sanebox-autodetect <existing|existingFile|existingFileOrParent|existingOrParent|parent>'
echo ' add files which appear later as CLI arguments into the sandbox'
echo ' --sanebox-cap <sys_admin|net_raw|net_admin|...>'
echo ' --sanebox-cap <all|sys_admin|net_raw|net_admin|...>'
echo ' allow the sandboxed program to use the provided linux capability (both inside and outside the sandbox)'
echo ' special cap "all" to preserve all capabilities possible'
echo ' --sanebox-portal'
echo ' set environment variables so that the sandboxed program will attempt to use xdg-desktop-portal for operations like opening files'
echo ' --sanebox-no-portal'
echo ' undo a previous `--sanebox-portal` arg'
echo ' --sanebox-bwrap-arg <arg>'
echo ' --sanebox-capsh-arg <arg>'
echo ' --sanebox-pasta-arg <arg>'
echo ' --sanebox-net-dev <iface>|all'
echo ' --sanebox-net-gateway <ip-address>'
echo ' --sanebox-dns <server>|host'
echo ' --sanebox-keep-namespace <cgroup|ipc|pid|uts|all>'
echo ' --sanebox-keep-namespace <all|cgroup|ipc|net|pid|uts>'
echo ' do not unshare the provided linux namespace'
echo ' --sanebox-path <path>'
echo ' allow access to the host <path> within the sandbox'
@@ -311,7 +318,8 @@ readlinkOnce() {
linkTarget=${linkCache[$path]}
elif [ -L "$path" ]; then
# path is a link, but not in the cache
linkTarget=$(readlink "$path")
locate _readlink "readlink" "$READLINK_FALLBACK"
linkTarget=$("$_readlink" "$path")
# insert it into the cache, in case we traverse it again
linkCache[$path]=$linkTarget
else
@@ -516,23 +524,45 @@ parseArgs() {
# N.B.: these named temporary variables ensure that "set -x" causes $1 to be printed
local cap=$1
shift
capabilities+=("$cap")
if [ "$cap" = all ]; then
# this tries to remain exhaustive, but new capabilities are occassionally added to the kernel:
# add anything here as it's found to be missing
capabilities+=(
audit_control audit_read audit_write block_suspend bpf checkpoint_restore
chown dac_override dac_read_search fowner fsetid ipc_lock
ipc_owner kill lease linux_immutable mac_admin mac_override
mknod net_admin net_bind_service net_broadcast net_raw perfmon
setfcap setgid setpcap setuid sys_admin sys_boot
sys_chroot sys_module sys_nice sys_pacct sys_ptrace sys_rawio
sys_resource sys_time sys_tty_config syslog wake_alarm
)
else
capabilities+=("$cap")
fi
;;
(--sanebox-portal)
# instruct glib/gtk apps to perform actions such as opening external files via dbus calls to org.freedesktop.portal.*.
# note that GIO_USE_PORTALS primarily acts as a *fallback*: apps only open files via the portal if they don't know how to themelves.
# this switch is typically accompanied by removing all MIME associations from the app's view, then.
# GTK_USE_PORTALS is the old name, beginning to be phased out as of 2023-10-02
portalEnv=("GIO_USE_PORTALS=1" "GTK_USE_PORTAL=1" "NIXOS_XDG_OPEN_USE_PORTAL=1")
portalEnv[GIO_USE_PORTALS]=1
portalEnv[GTK_USE_PORTAL]=1
portalEnv[NIXOS_XDG_OPEN_USE_PORTAL]=1
;;
(--sanebox-no-portal)
# override a previous --sanebox-portal call
portalEnv=()
unset portalEnv
declare -A portalEnv
;;
(--sanebox-bwrap-arg)
local bwrapFlag=$1
local bwrapArg=$1
shift
bwrapFlags+=("$bwrapFlag")
bwrapArgs+=("$bwrapArg")
;;
(--sanebox-capsh-arg)
local capshArg=$1
shift
capshArgs+=("$capshArg")
;;
(--sanebox-pasta-arg)
local pastaArg=$1
@@ -559,9 +589,8 @@ parseArgs() {
(--sanebox-keep-namespace)
local namespace=$1
shift
# TODO: should this include `net` namespace??
if [ "$namespace" = all ]; then
keepNamespace+=("cgroup" "ipc" "pid" "uts")
keepNamespace+=("cgroup" "ipc" "net" "pid" "user" "uts")
else
keepNamespace+=("$namespace")
fi
@@ -599,6 +628,7 @@ bwrapUnshareCgroup=(--unshare-cgroup)
bwrapUnshareIpc=(--unshare-ipc)
bwrapUnshareNet=(--unshare-net)
bwrapUnsharePid=(--unshare-pid)
bwrapUnshareUts=(--unshare-user)
bwrapUnshareUts=(--unshare-uts)
bwrapVirtualizeDev=(--dev /dev)
bwrapVirtualizeProc=(--proc /proc)
@@ -620,15 +650,15 @@ bwrapIngestPath() {
# - /mnt/servo -> never hangs
# may be possible to place ever mount in a subdir, and mount the super dir?
# or maybe configure remote mounts to somehow never hang.
# test -r "$1" && bwrapFlags+=("--dev-bind-try" "$1" "$1")
# test -r "$1" && bwrapArgs+=("--dev-bind-try" "$1" "$1")
# N.B.: test specifically whether this path is a link, not whether it's a non-symlink under a symlink'd dir.
# this way, the filetype of this path is *always* the same both inside and outside the sandbox.
readlinkOnce linkTarget "$1"
if [ -n "$linkTarget" ]; then
bwrapFlags+=("--symlink" "$linkTarget" "$1")
bwrapArgs+=("--symlink" "$linkTarget" "$1")
else
bwrapFlags+=("--dev-bind-try" "$1" "$1")
bwrapArgs+=("--dev-bind-try" "$1" "$1")
fi
# default to virtualizing a few directories in a way that's safe (doesn't impact outside environment)
@@ -675,47 +705,65 @@ bwrapIngestKeepNamespace() {
(ipc)
bwrapUnshareIpc=()
;;
(net)
bwrapUnshareNet=()
;;
(pid)
bwrapUnsharePid=()
;;
(user)
bwrapUnshareUser=()
;;
(uts)
bwrapUnshareUts=()
;;
esac
}
bwrapIngestCapability() {
bwrapFlags+=("--cap-add" "cap_$1")
bwrapArgs+=("--cap-add" "cap_$1")
# a program run inside a user namespace has no capabilities outside the namespace.
# so, disable the user namespace.
# N.B.: this only applies to root. non-root users still get a user namespace, because that's required in order to do any of the other namespacing.
# bwrapUnshareUser=()
}
bwrapGetCli() {
# --unshare-all implies the following:
# --unshare-pid: mean that the /proc mount does not expose /proc/$PID/ for every other process on the machine.
# --unshare-net creates a new net namespace with only the loopback interface.
# if `bwrapFlags` contains --share-net, thiss is canceled and the program sees an unsandboxed network.
# if `bwrapArgs` contains --share-net, this is canceled and the program sees an unsandboxed network.
# --unshare-ipc
# --unshare-cgroup
# --unshare-uts
# --unshare-user (implicit to every non-suid call to bwrap)
locate _bwrap "bwrap" "$BWRAP_FALLBACK"
locate _env "env" "$ENV_FALLBACK"
if [ -n "$bwrapUsePasta" ]; then
# pasta drops us into an environment where we're root, but some apps complain if run as root.
# TODO: this really belongs on the `pastaonlyGetCli` side.
# TODO: i think we need to add `/dev/net/tun` to the namespace for nested pasta calls to work?
bwrapFlags+=(
bwrapArgs+=(
# --unshare-user is necessary for --uid to work when called as pseudo root
--unshare-user
--uid "$UID"
--gid "${GROUPS[0]}"
)
fi
for envName in "${!portalEnv[@]}"; do
bwrapArgs+=(--setenv "$envName" "${portalEnv[$envName]}")
done
cliArgs=(
"$_bwrap" "${bwrapUnshareCgroup[@]}" "${bwrapUnshareIpc[@]}"
"${bwrapUnshareNet[@]}" "${bwrapUnsharePid[@]}"
"$_bwrap" \
"${bwrapUnshareCgroup[@]}"
"${bwrapUnshareIpc[@]}"
"${bwrapUnshareNet[@]}"
"${bwrapUnsharePid[@]}"
"${bwrapUnshareUser[@]}"
"${bwrapUnshareUts[@]}"
"${bwrapVirtualizeDev[@]}" "${bwrapVirtualizeProc[@]}" "${bwrapVirtualizeTmp[@]}"
"${bwrapFlags[@]}" --
"$_env" "${portalEnv[@]}" "${cliArgs[@]}"
"${bwrapArgs[@]}" --
"${cliArgs[@]}"
)
if [ -n "$bwrapUsePasta" ]; then
pastaonlyGetCli
@@ -735,7 +783,9 @@ landlockSetup() {
# typical failure mode:
# - /tmp: application can't perform its task
# - /dev/{null,random,urandom,zero}: application warns but works around it
# - /dev/fd/*: application fails to open its stdin/stdout/etc
paths+=(
/dev/fd
/dev/null
/dev/random
/dev/urandom
@@ -791,12 +841,12 @@ landlockGetCli() {
# so trampoline through `capsh` as well, to drop privs.
# N.B: capsh passes its arg to bash (via /nix/store/.../bash), which means you have to `-c "my command"` to
# invoke the actual user command.
locate _sandboxer "sandboxer" "$LANDLOCK_SANDBOXER_FALLBACK"
locate _capsh "capsh" "$CAPSH_FALLBACK"
locate _sandboxer "landlock-sandboxer" "$LANDLOCK_SANDBOXER_FALLBACK"
locate _env "env" "$ENV_FALLBACK"
capshonlyGetCli
cliArgs=("$_env" LL_FS_RO= LL_FS_RW="$landlockPaths" "${landlockNetFlags[@]}"
"$_sandboxer"
"$_capsh" "--caps=$capshCapsArg" --no-new-privs --shell="$_env" -- "${portalEnv[@]}" "${cliArgs[@]}"
"${cliArgs[@]}"
)
}
@@ -805,7 +855,8 @@ landlockGetCli() {
# this backend exists because apps which are natively bwrap may complain about having ambient privileges.
# then, run them in a capsh sandbox, which ignores any path sandboxing and just lowers privs to what's needed.
capshCapsArg=
# all=: means to clear all capabilities
capshCapsArg="all="
capshonlySetup() {
debug "capshonlySetup: noop"
@@ -835,12 +886,32 @@ capshonlyIngestCapability() {
# `capsh --caps=CAP_FOO=eip -- true` will fail if we don't have CAP_FOO,
# but for my use i'd still like to try running the command even if i can't grant it all capabilities.
# therefore, only grant it those capabilities i know will succeed.
if capsh "--has-p=cap_$1" 2>/dev/null; then
if [ -z "$capshCapsArg" ]; then
capshCapsArg=cap_$1=eip
else
capshCapsArg=cap_$1,$capshCapsArg
locate _capsh "capsh" "$CAPSH_FALLBACK"
local hasP=
local hasI=
if "$_capsh" "--has-a=cap_$1" 2>/dev/null; then
# XXX: this ambient special case could probably be removed:
# a capability can't be ambient without also being I and P, IIUC.
hasP=1
hasI=1
else
if "$_capsh" "--has-p=cap_$1" 2>/dev/null; then
hasP=1
fi
if "$_capsh" "--has-i=cap_$1" 2>/dev/null; then
hasI=1
fi
fi
if [ -n "$hasI" ] || [ -n "$hasP" ]; then
# hasP means "able to add to E or I set.
# so, if we have the cap in *either* P or I, then we can place it in I here.
# only if we have it in P can we add it to P and E.
local ext=i
if [ -n "$hasP" ]; then
ext="e${ext}p"
fi
capshCapsArg="$capshCapsArg cap_$1+$ext"
else
debug "capsh: don't have capability $1"
fi
@@ -849,8 +920,14 @@ capshonlyIngestCapability() {
capshonlyGetCli() {
locate _capsh "capsh" "$CAPSH_FALLBACK"
locate _env "env" "$ENV_FALLBACK"
local envArgs=()
for envName in "${!portalEnv[@]}"; do
envArgs+=("$envName=${portalEnv[$envName]}")
done
cliArgs=(
"$_capsh" "--caps=$capshCapsArg" --no-new-privs --shell="$_env" -- "${portalEnv[@]}" "${cliArgs[@]}"
"$_capsh" "--caps=$capshCapsArg" --no-new-privs --shell="$_env" "${capshArgs[@]}" -- "${envArgs[@]}" "${cliArgs[@]}"
)
}
@@ -858,7 +935,6 @@ capshonlyGetCli() {
## PASTA-ONLY BACKEND
# this backend exists mostly as a helper for the bwrap backend
pastaArgs=()
pastaNetSetup=
pastaOutboundPorts=()
pastaonlySetup() {
@@ -912,11 +988,18 @@ pastaonlyGetCli() {
"${cliArgs[@]}"
)
locate _pasta "pasta" "$PASTA_FALLBACK"
if [ "$UID" = 0 ]; then
# default pasta will change to `nobody` if invoked as root, but there are times i actually want to run as root.
pastaArgs+=(--runas 0)
fi
local pastaOutboundPortsStr=none
if [ "${#pastaOutboundPorts[@]}" -ne 0 ]; then
pastaOutboundPortsStr="${pastaOutboundPorts[*]}"
pastaOutboundPortsStr="${pastaOutboundPortsStr// /,}"
fi
cliArgs=(
"$_pasta" --ipv4-only -U "$pastaOutboundPortsStr" -T "$pastaOutboundPortsStr" -u none -t none --config-net
"${pastaArgs[@]}" --

View File

@@ -118,7 +118,7 @@
, xdg-utils
}:
let
version = "7.16.0";
version = "7.18.0";
ringrtcPrebuild = fetchurl {
# version is found in signal-desktop's package.json as "@signalapp/ringrtc"
@@ -150,7 +150,7 @@ let
repo = "Signal-Desktop";
leaveDotGit = true; # signal calculates the release date via `git`
rev = "v${version}";
hash = "sha256-HHpv+Kv7Y+653CBSpRePfWQmeRzznmdmUaU5AIxLQUw=";
hash = "sha256-5wv8xH3jwfb+I6a0/N7ZHQKNigBjFHHNX9rQDCMoHfY=";
};
# note that `package.json` locks the electron version, but we seem to not be strictly beholden to that.
@@ -167,7 +167,7 @@ buildNpmPackage rec {
pname = "signal-desktop-from-src";
inherit src version;
npmDepsHash = "sha256-CJTTLjP3eiJSa/ZWoeBP/9S1Krtb7ozsutRdH2HGfe8=";
npmDepsHash = "sha256-tHwlcbMsU65T6mnOkhsy4qfBEOvY9AneYtIpv954emg=";
patches = [
# ./debug.patch

View File

@@ -1,14 +1,14 @@
diff --git a/app/main.ts b/app/main.ts
index 8388480e6..baedcfb55 100644
index 8d440386c..83fbc3c3e 100644
--- a/app/main.ts
+++ b/app/main.ts
@@ -749,7 +749,7 @@ async function createWindow() {
const titleBarOverlay = await getTitleBarOverlay();
@@ -686,7 +686,7 @@ async function createWindow() {
: DEFAULT_HEIGHT;
const windowOptions: Electron.BrowserWindowConstructorOptions = {
- show: false,
+ show: true,
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
width,
height,
minWidth: MIN_WIDTH,

View File

@@ -6,8 +6,8 @@ let
src = fetchFromGitHub {
owner = "Mic92";
repo = "sops-nix";
rev = "eb34eb588132d653e4c4925d862f1e5a227cc2ab";
hash = "sha256-s6YhI8UHwQvO4cIFLwl1wZ1eS5Cuuw7ld2VzUchdFP0=";
rev = "8ae477955dfd9cbf5fa4eb82a8db8ddbb94e79d9";
hash = "sha256-3m/iyyjCdRBF8xyehf59QlckIcmShyTesymSb+N4Ap4=";
};
flake = import "${src}/flake.nix";
evaluated = flake.outputs {
@@ -21,7 +21,7 @@ in src.overrideAttrs (base: {
# attributes required by update scripts
pname = "sops-nix";
# nix-update-script insists on this weird `assets-` version format
version = "assets-unstable-2024-07-27";
version = "assets-unstable-2024-08-05";
src = src;
passthru = base.passthru

View File

@@ -9,13 +9,13 @@
}:
stdenv.mkDerivation (finalAttrs: {
pname = "syshud";
version = "0-unstable-2024-07-29";
version = "0-unstable-2024-08-03";
src = fetchFromGitHub {
owner = "System64fumo";
repo = "syshud";
rev = "f245db6bcd2278cfae6d296c152bfc526f1f7601";
hash = "sha256-XxyYLRPIWNsCJFTI7ZoIEvJ0gt2Ok9EgK2fhRf2VWZQ=";
rev = "ea0b6a52e110d18783a418013c1be82ff32709a7";
hash = "sha256-MLVjdPcnSHCxQ9KbmM1V/jnmh7NrzZZAg13D3a240Q0=";
};
postPatch = ''
substituteInPlace Makefile \

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