Compare commits
145 Commits
wip-secure
...
2024-08-11
Author | SHA1 | Date | |
---|---|---|---|
9b12bd24c0 | |||
d9a876e49a | |||
5a9dd89475 | |||
5e71e5a067 | |||
f0b0d15ad7 | |||
8aebc1fe87 | |||
764c2a3276 | |||
a2f34be9d6 | |||
bda172bc2d | |||
a91a2d8a7f | |||
875d919fa8 | |||
a323f321b5 | |||
f986936bbd | |||
3d773fe375 | |||
055ad222e3 | |||
3aafcb0aa8 | |||
c85f02ca68 | |||
eeafc34ccf | |||
039ffcdcd4 | |||
2a35cb5379 | |||
3db009bc98 | |||
1e840e72b3 | |||
ce1c3ec804 | |||
09dd69a855 | |||
cbe71868ef | |||
7b043d0c87 | |||
fd0188025d | |||
1c57ffa798 | |||
1d205a89bc | |||
5ff643aa2f | |||
bfdf63e641 | |||
c695f7a979 | |||
b3b38451b5 | |||
1ee81db537 | |||
b9947c05ca | |||
2de6491583 | |||
4525df58e0 | |||
9d1ffc7c43 | |||
a69af91b7b | |||
7b5d655c91 | |||
de6ffe6b75 | |||
f8aea34e96 | |||
49efb94a0a | |||
9b1e053ead | |||
235dc86155 | |||
6dad290cd5 | |||
cc6ed6c0ec | |||
cc3aba3cc2 | |||
41f08125bb | |||
27487fe870 | |||
d45ea622d1 | |||
247fd3f807 | |||
816e2a7065 | |||
be842d5c5e | |||
fa6ec981e0 | |||
52b4c1542a | |||
3ff59247da | |||
d9c0855c4e | |||
1a67a05238 | |||
1cdeedd9ec | |||
6830bb7097 | |||
316b0bee3a | |||
638655ff83 | |||
5e57e78411 | |||
3859619ae0 | |||
646c2dd85a | |||
0655b6906c | |||
3019f90f5d | |||
020e5f8c6e | |||
809c3af7fa | |||
93cb1bc546 | |||
53acab834c | |||
3a0610b029 | |||
9cee460d7e | |||
e657507a76 | |||
c706a19836 | |||
566e15286b | |||
d1b4e9c923 | |||
5eca45891b | |||
722fe8f368 | |||
e25dd98f6c | |||
54e9d4a0ae | |||
9f3a13eeb8 | |||
5605ffda4b | |||
9165925469 | |||
f65bf2b433 | |||
0f60a86ed4 | |||
b488b6748d | |||
ef6b7cf175 | |||
0906d76f83 | |||
90c495e74c | |||
74662df720 | |||
2b3278eb7f | |||
9b4e91fbd9 | |||
734627232a | |||
3adbbe5fa7 | |||
b4a244df7a | |||
97268e9b26 | |||
bebf6bdaeb | |||
04fc601c9c | |||
ee062d61d0 | |||
0dba9987c5 | |||
4761690b6d | |||
604782c3a6 | |||
365d33c357 | |||
a39ad8a508 | |||
c49e9a4c2b | |||
36491842cc | |||
81ea2210c9 | |||
f678508b33 | |||
6135be5f72 | |||
c8989ca1a8 | |||
1d665f8ecc | |||
7c284ad8da | |||
1c26674da7 | |||
dae8481176 | |||
42b27f0433 | |||
84be0cae5a | |||
fbfd0afca4 | |||
e586b7b449 | |||
222c37b056 | |||
53b17ec230 | |||
7697704aff | |||
c490b6e6ad | |||
89d678c729 | |||
c64163290c | |||
eaeb8380dc | |||
05a9e8e819 | |||
cf20230d96 | |||
9dbb2a6266 | |||
113b107d73 | |||
96dfe79a8c | |||
6e5bde17aa | |||
3eb66c098b | |||
515aab5370 | |||
f925dd9a20 | |||
cbe6bdf158 | |||
949a52dee1 | |||
2ee1fb17c4 | |||
48cc718700 | |||
6a7dd31755 | |||
2197951e12 | |||
883db3e9ba | |||
312b0a5554 | |||
07de46c616 |
32
TODO.md
32
TODO.md
@@ -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
|
||||
|
@@ -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" ];
|
||||
# };
|
||||
}
|
||||
|
@@ -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" ];
|
||||
# };
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -20,6 +20,7 @@
|
||||
./nginx.nix
|
||||
./nixos-prebuild.nix
|
||||
./ntfy
|
||||
./ollama.nix
|
||||
./pict-rs.nix
|
||||
./pleroma.nix
|
||||
./postgres.nix
|
||||
|
@@ -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/";
|
||||
# # }
|
||||
# ];
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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.
|
||||
|
23
hosts/by-name/servo/services/ollama.nix
Normal file
23
hosts/by-name/servo/services/ollama.nix
Normal 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;
|
||||
}
|
@@ -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"/*)
|
||||
|
@@ -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)
|
||||
|
@@ -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/.
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
};
|
||||
}
|
||||
|
@@ -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
|
||||
);
|
||||
};
|
||||
|
@@ -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"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
@@ -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";
|
||||
|
@@ -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";
|
||||
};
|
||||
}
|
||||
|
@@ -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
|
||||
|
13
hosts/common/programs/firefox-xdg-open.nix
Normal file
13
hosts/common/programs/firefox-xdg-open.nix
Normal 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" ];
|
||||
};
|
||||
}
|
@@ -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"
|
||||
];
|
||||
|
||||
|
@@ -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
|
||||
# ];
|
||||
};
|
||||
}
|
||||
|
42
hosts/common/programs/foliate.nix
Normal file
42
hosts/common/programs/foliate.nix
Normal 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
|
||||
};
|
||||
}
|
@@ -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" ];
|
||||
|
@@ -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"
|
||||
];
|
||||
|
||||
|
23
hosts/common/programs/gocryptfs.nix
Normal file
23
hosts/common/programs/gocryptfs.nix
Normal 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
|
||||
];
|
||||
};
|
||||
}
|
@@ -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!!
|
||||
|
@@ -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
|
||||
};
|
||||
}
|
||||
|
@@ -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
|
||||
};
|
||||
}
|
||||
|
@@ -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";
|
||||
};
|
||||
}
|
||||
|
@@ -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
|
||||
;
|
||||
|
@@ -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 = {
|
||||
|
@@ -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')
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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')
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
];
|
||||
};
|
||||
})
|
||||
]
|
||||
|
@@ -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
|
||||
|
@@ -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 {
|
||||
|
@@ -18,6 +18,6 @@
|
||||
persist.byStore.ephemeral = [
|
||||
".local/share/tor-browser"
|
||||
];
|
||||
mime.urlAssociations."^https?://.+\.onion" = "torbrowser.desktop";
|
||||
mime.urlAssociations."^https?://.+\.onion$" = "torbrowser.desktop";
|
||||
};
|
||||
}
|
||||
|
30
hosts/common/programs/xdg-desktop-portal-gnome/default.nix
Normal file
30
hosts/common/programs/xdg-desktop-portal-gnome/default.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
}
|
@@ -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);
|
@@ -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
|
||||
|
@@ -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" ];
|
||||
};
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
})
|
||||
];
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
./duplicity.nix
|
||||
./rsync-net
|
||||
];
|
||||
}
|
||||
|
@@ -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}
|
||||
'';
|
||||
};
|
||||
}
|
@@ -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" ];
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
@@ -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;
|
||||
};
|
||||
|
@@ -2,9 +2,9 @@
|
||||
|
||||
{
|
||||
imports = [
|
||||
./ephemeral.nix
|
||||
./ephemeral
|
||||
./initrd.nix
|
||||
./plaintext.nix
|
||||
./private.nix
|
||||
./private
|
||||
];
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
97
modules/persist/stores/ephemeral/default.nix
Normal file
97
modules/persist/stores/ephemeral/default.nix
Normal 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 ];
|
||||
}
|
17
modules/persist/stores/ephemeral/gocryptfs-ephemeral
Executable file
17
modules/persist/stores/ephemeral/gocryptfs-ephemeral
Executable 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 "$@"
|
@@ -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" ];
|
||||
};
|
||||
}
|
||||
|
149
modules/persist/stores/private/default.nix
Normal file
149
modules/persist/stores/private/default.nix
Normal 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" ];
|
||||
};
|
||||
}
|
||||
|
6
modules/persist/stores/private/gocryptfs-private
Executable file
6
modules/persist/stores/private/gocryptfs-private
Executable 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"
|
46
modules/persist/stores/private/provision-private-key
Executable file
46
modules/persist/stores/private/provision-private-key
Executable 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"
|
@@ -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"
|
||||
];
|
||||
};
|
||||
});
|
||||
|
@@ -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" (
|
||||
|
@@ -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:
|
||||
|
@@ -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; ''
|
||||
|
@@ -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 {
|
||||
|
29
pkgs/additional/curlftpfs-sane/default.nix
Normal file
29
pkgs/additional/curlftpfs-sane/default.nix
Normal 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
|
||||
'';
|
||||
})
|
@@ -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 { };
|
||||
|
@@ -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>
|
||||
|
@@ -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]);
|
||||
},
|
||||
});
|
||||
});
|
@@ -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;
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
@@ -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"
|
||||
]
|
||||
}
|
||||
|
@@ -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
@@ -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"
|
||||
}
|
@@ -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
|
||||
|
@@ -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];
|
||||
};
|
||||
}
|
||||
})
|
||||
|
50
pkgs/additional/koreader-from-src/update
Executable file
50
pkgs/additional/koreader-from-src/update
Executable 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}"
|
||||
|
50
pkgs/additional/koreader-from-src/versions.json
Normal file
50
pkgs/additional/koreader-from-src/versions.json
Normal 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="
|
||||
}
|
||||
}
|
@@ -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";
|
||||
};
|
||||
}
|
||||
|
||||
|
17
pkgs/additional/landlock-sandboxer/no-warn-old-kernel.diff
Normal file
17
pkgs/additional/landlock-sandboxer/no-warn-old-kernel.diff
Normal 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;
|
41
pkgs/additional/libfuse-sane/default.nix
Normal file
41
pkgs/additional/libfuse-sane/default.nix
Normal 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
|
15
pkgs/additional/libfuse-sane/pass_fuse_fd.patch
Normal file
15
pkgs/additional/libfuse-sane/pass_fuse_fd.patch
Normal 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;
|
17645
pkgs/additional/newsflash-nixified/Cargo.nix
Normal file
17645
pkgs/additional/newsflash-nixified/Cargo.nix
Normal file
File diff suppressed because it is too large
Load Diff
9
pkgs/additional/newsflash-nixified/crate-hashes.json
Normal file
9
pkgs/additional/newsflash-nixified/crate-hashes.json
Normal 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"
|
||||
}
|
282
pkgs/additional/newsflash-nixified/default.nix
Normal file
282
pkgs/additional/newsflash-nixified/default.nix
Normal 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;
|
||||
# };
|
||||
# })
|
@@ -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:
|
||||
|
@@ -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;
|
||||
|
@@ -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";
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
@@ -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"
|
@@ -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}"
|
||||
|
||||
|
@@ -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" ])
|
||||
|
||||
|
@@ -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
|
||||
'';
|
||||
|
@@ -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[@]}" --
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
Reference in New Issue
Block a user